using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; using UnityEngine.Video; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("V81TestChn")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+1facf15d35766b81c56e6948d20291ab6fc9c82c")] [assembly: AssemblyProduct("V81TestChn")] [assembly: AssemblyTitle("V81TestChn")] [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 V81TestChn { internal static class AlertTextureReplacementService { private enum NativeTextRole { None, SystemOnline, EnteringAtmosphere } private readonly struct CachedNativeTextRole { public int ParentId { get; } public NativeTextRole Role { get; } public CachedNativeTextRole(int parentId, NativeTextRole role) { ParentId = parentId; Role = role; } } [CompilerGenerated] private sealed class d__49 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string stage; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__49(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = 0f; break; case 1: <>1__state = -1; 5__2 += 0.5f; break; } if (5__2 < 8f) { if (SyncFixedSceneLabelsInRelayScene(stage + ".relay-scene") > 0) { _fixedSceneLabelWatcher = null; _fixedSceneLabelWatcherOwner = null; return false; } <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; } if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogWarning((object)("NativeRelay[" + stage + ".relay-scene] target=FixedSceneLabel action=timeout")); } _fixedSceneLabelWatcher = null; _fixedSceneLabelWatcherOwner = null; 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(); } } [CompilerGenerated] private sealed class d__48 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public HUDManager hudManager; public string stage; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__48(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = 0f; break; case 1: <>1__state = -1; 5__2 += 0.25f; break; } if ((Object)(object)hudManager != (Object)null && 5__2 < 12f) { TMP_Text text = FindSystemOnlineTitleByHudPath(hudManager); if (IsExactSystemOnlineTitle(text)) { ApplySystemOnlineNativeTranslation(text, stage + ".watcher"); _systemOnlineWatcher = null; _systemOnlineWatcherOwner = null; return false; } <>2__current = (object)new WaitForSeconds(0.25f); <>1__state = 1; return true; } if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogWarning((object)("NativeRelay[" + stage + ".watcher] target=SystemOnline action=timeout")); } _systemOnlineWatcher = null; _systemOnlineWatcherOwner = null; 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 const int NativeTextRoleCacheLimit = 16384; private const string SystemOnlineTitleObjectName = "TipLeft1"; private const string SystemOnlineTitleFullPath = "Systems/UI/Canvas/IngamePlayerHUD/BottomMiddle/SystemsOnline/TipLeft1"; private const string SystemOnlineTitlePathSuffix = "IngamePlayerHUD/BottomMiddle/SystemsOnline/TipLeft1"; private const string SystemOnlineRelativePath = "BottomMiddle/SystemsOnline/TipLeft1"; private const string EnteringAtmosphereTitleObjectName = "LoadText"; private const string RelaySceneName = "SampleSceneRelay"; private const string SystemOnlineLocalizedText = "系统在线"; private const string EnteringAtmosphereLocalizedText = "正在进入大气层..."; private const string HazardLevelLocalizedText = "风险级别:"; private const string LifeSupportOfflineLocalizedText = "[生命维持:离线]"; private static Coroutine? _systemOnlineWatcher; private static Coroutine? _fixedSceneLabelWatcher; private static HUDManager? _systemOnlineWatcherOwner; private static HUDManager? _fixedSceneLabelWatcherOwner; private static bool _fixedSceneLabelSceneLoadedSubscribed; private static string _fixedSceneLabelSceneLoadedStage = "SceneLoaded"; private static readonly Dictionary NativeTextRoleCache = new Dictionary(16384); private static readonly Dictionary FixedSceneLabels = new Dictionary(StringComparer.Ordinal) { ["TO MEET PROFIT QUOTA"] = "以达到利润配额", ["PERFORMANCE REPORT"] = "绩效报告", ["EMPLOYEE RANK"] = "员工等级", ["Fines"] = "罚款", ["(Dead)"] = "(死亡)", ["Deceased"] = "死亡", ["[LIFE SUPPORT: OFFLINE]"] = "[生命维持:离线]", ["You will keep your employee rank. Your ship and credits will be reset."] = "你的员工等级将被保留,但飞船和点数将被重置。" }; public static void Initialize(string pluginDir) { if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogInfo((object)"Native relay title translation enabled; SYSTEMS ONLINE uses original TMP object only."); } } public static void ForceApplySystemOnlineOverlay(HUDManager? hudManager, string stage) { TMP_Text val = FindSystemOnlineTitle(hudManager, allowGlobalFallback: false); if ((Object)(object)val != (Object)null) { ApplySystemOnlineNativeTranslation(val, stage); } else if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogWarning((object)("NativeRelay[" + stage + "] target=SystemOnline action=not-found")); AuditSystemOnlineBranch(stage, hudManager); } } public static void BeginSystemOnlineExactPathWatcher(HUDManager? hudManager, string stage) { if (!((Object)(object)hudManager == (Object)null)) { ResetSystemOnlineWatcherIfStale(hudManager); if (_systemOnlineWatcher == null) { _systemOnlineWatcherOwner = hudManager; _systemOnlineWatcher = ((MonoBehaviour)hudManager).StartCoroutine(WaitForSystemOnlineTitle(hudManager, stage)); } } } public static void TryApplySystemOnlineDirect(HUDManager? hudManager, string stage) { TMP_Text val = FindSystemOnlineTitle(hudManager, allowGlobalFallback: true); if ((Object)(object)val == (Object)null) { if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogWarning((object)("NativeRelay[" + stage + "] target=SystemOnline action=not-found")); } } else { ApplySystemOnlineNativeTranslation(val, stage); } } public static void QueueSystemOnlineSync(HUDManager? hudManager, string stage) { } public static void TryApplyEnteringAtmosphereOverlayFromLoadingScreen(HUDManager? hudManager, string stage) { TMP_Text val = FindEnteringAtmosphereTitle(hudManager); if ((Object)(object)val == (Object)null) { if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogWarning((object)("NativeRelay[" + stage + "] target=EnteringAtmosphere action=not-found")); } } else { ApplyEnteringAtmosphereNativeTranslation(val, stage); } } public static void HideEnteringAtmosphereOverlayForHud(HUDManager? hudManager, string stage) { TMP_Text val = FindEnteringAtmosphereTitle(hudManager); if ((Object)(object)val != (Object)null) { ApplyEnteringAtmosphereNativeTranslation(val, stage); } } public static void SyncEnteringAtmosphereOverlayState(TMP_Text? text, string stage) { if (!((Object)(object)text == (Object)null) && GetNativeTextRole(text) == NativeTextRole.EnteringAtmosphere) { ApplyEnteringAtmosphereNativeTranslation(text, stage); } } public static void SyncHazardLevelRelay(HUDManager? hudManager, string stage) { TMP_Text val = FindHazardLevelTitle(hudManager); if ((Object)(object)val != (Object)null) { ApplyHazardLevelNativeTranslation(val, stage); } } public static void SyncLandingInfo(HUDManager? hudManager, string stage) { if (!((Object)(object)hudManager == (Object)null)) { ApplyDynamicFieldTranslation((TMP_Text?)(object)hudManager.planetInfoHeaderText, stage, "PlanetInfoHeader"); ApplyDynamicFieldTranslation((TMP_Text?)(object)hudManager.planetInfoSummaryText, stage, "PlanetInfoSummary"); ApplyDynamicFieldTranslation((TMP_Text?)(object)hudManager.planetRiskLevelText, stage, "PlanetRiskLevel"); SyncHazardLevelRelay(hudManager, stage); } } public static void SyncFixedSceneLabels(HUDManager? hudManager, string stage) { if (!((Object)(object)hudManager == (Object)null)) { TMP_Text[] componentsInChildren = ((Component)hudManager).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { TryReplaceFixedSceneLabel(componentsInChildren[i], stage); } } } public static void BeginFixedSceneLabelWatcher(HUDManager? hudManager, string stage) { if (!((Object)(object)hudManager == (Object)null) && SyncFixedSceneLabelsInRelayScene(stage + ".initial") <= 0) { _fixedSceneLabelWatcherOwner = hudManager; _fixedSceneLabelSceneLoadedStage = stage; if (!_fixedSceneLabelSceneLoadedSubscribed) { SceneManager.sceneLoaded += OnRelaySceneLoadedForFixedLabels; _fixedSceneLabelSceneLoadedSubscribed = true; } } } public static void Shutdown() { try { if (_systemOnlineWatcher != null && (Object)(object)_systemOnlineWatcherOwner != (Object)null) { ((MonoBehaviour)_systemOnlineWatcherOwner).StopCoroutine(_systemOnlineWatcher); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("NativeRelay shutdown failed stopping SystemOnline watcher: " + ex.GetType().Name + ": " + ex.Message)); } try { if (_fixedSceneLabelWatcher != null && (Object)(object)_fixedSceneLabelWatcherOwner != (Object)null) { ((MonoBehaviour)_fixedSceneLabelWatcherOwner).StopCoroutine(_fixedSceneLabelWatcher); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("NativeRelay shutdown failed stopping fixed scene label watcher: " + ex2.GetType().Name + ": " + ex2.Message)); } _systemOnlineWatcher = null; _fixedSceneLabelWatcher = null; _systemOnlineWatcherOwner = null; _fixedSceneLabelWatcherOwner = null; if (_fixedSceneLabelSceneLoadedSubscribed) { SceneManager.sceneLoaded -= OnRelaySceneLoadedForFixedLabels; _fixedSceneLabelSceneLoadedSubscribed = false; } _fixedSceneLabelSceneLoadedStage = "SceneLoaded"; NativeTextRoleCache.Clear(); } private static void OnRelaySceneLoadedForFixedLabels(Scene scene, LoadSceneMode mode) { if (string.Equals(((Scene)(ref scene)).name, "SampleSceneRelay", StringComparison.Ordinal)) { SyncFixedSceneLabelsInRelayScene(_fixedSceneLabelSceneLoadedStage + ".scene-loaded"); SceneManager.sceneLoaded -= OnRelaySceneLoadedForFixedLabels; _fixedSceneLabelSceneLoadedSubscribed = false; _fixedSceneLabelWatcherOwner = null; _fixedSceneLabelSceneLoadedStage = "SceneLoaded"; } } private static void ResetSystemOnlineWatcherIfStale(HUDManager currentHud) { if (_systemOnlineWatcher == null) { _systemOnlineWatcherOwner = null; } else if (!((Object)(object)_systemOnlineWatcherOwner == (Object)(object)currentHud)) { StopStaleWatcher(_systemOnlineWatcherOwner, _systemOnlineWatcher, "SystemOnline"); _systemOnlineWatcher = null; _systemOnlineWatcherOwner = null; } } private static void ResetFixedSceneLabelWatcherIfStale(HUDManager currentHud) { if (_fixedSceneLabelWatcher == null) { _fixedSceneLabelWatcherOwner = null; } else if (!((Object)(object)_fixedSceneLabelWatcherOwner == (Object)(object)currentHud)) { StopStaleWatcher(_fixedSceneLabelWatcherOwner, _fixedSceneLabelWatcher, "FixedSceneLabel"); _fixedSceneLabelWatcher = null; _fixedSceneLabelWatcherOwner = null; } } private static void StopStaleWatcher(HUDManager? owner, Coroutine watcher, string label) { if ((Object)(object)owner == (Object)null) { return; } try { ((MonoBehaviour)owner).StopCoroutine(watcher); } catch (Exception ex) { Plugin.Log.LogWarning((object)("NativeRelay stale " + label + " watcher stop failed: " + ex.GetType().Name + ": " + ex.Message)); } } public static void TryReplaceSystemOnlineText(TMP_Text? text, string stage) { if ((Object)(object)text == (Object)null || !MightBeNativeRelayCandidate(text)) { return; } switch (GetNativeTextRole(text)) { case NativeTextRole.SystemOnline: ApplySystemOnlineNativeTranslation(text, stage); return; case NativeTextRole.EnteringAtmosphere: ApplyEnteringAtmosphereNativeTranslation(text, stage); return; } if (IsHazardLevelTitle(text.text)) { ApplyHazardLevelNativeTranslation(text, stage); } else { TryReplaceFixedSceneLabel(text, stage); } } public static bool TryReplaceFixedSceneLabel(TMP_Text? text, string stage) { if ((Object)(object)text == (Object)null || string.IsNullOrWhiteSpace(text.text)) { return false; } if (!TryResolveFixedSceneLabel(text.text, out string localized)) { return false; } ApplyLocalizedText(text, localized, stage, "FixedSceneLabel"); if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogInfo((object)("NativeRelay[" + stage + "] target=FixedSceneLabel action=applied name=" + ((Object)text).name + " path=" + BuildPath(text.transform) + " text=" + text.text)); } return true; } public static void TryReplaceSystemOnlineText(Text? text, string stage) { } private static bool MightBeNativeRelayCandidate(TMP_Text text) { if (string.Equals(((Object)text).name, "TipLeft1", StringComparison.OrdinalIgnoreCase) || string.Equals(((Object)text).name, "LoadText", StringComparison.OrdinalIgnoreCase)) { return true; } string text2 = text.text; if (string.IsNullOrWhiteSpace(text2) || text2.Length > 160) { return false; } int i; for (i = 0; i < text2.Length && char.IsWhiteSpace(text2[i]); i++) { } if (i >= text2.Length) { return false; } switch (char.ToUpperInvariant(text2[i])) { case '(': case 'D': case 'E': case 'F': case 'H': case 'P': case 'T': case 'Y': case '[': return true; default: return false; } } public static void TryReplaceSystemOnlineText(TextMesh? text, string stage) { } public static void TryReplace(Image? image, string stage) { } public static void TryReplace(RawImage? image, string stage) { } public static void TryReplace(SpriteRenderer? renderer, string stage) { } public static void TryReplace(Material? material, string stage) { } private static void ApplySystemOnlineNativeTranslation(TMP_Text text, string stage) { if (!((Behaviour)text).enabled) { ((Behaviour)text).enabled = true; } ApplyLocalizedText(text, ResolveLocalizedText(text.text, "系统在线"), stage, "SystemOnline"); if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogInfo((object)("NativeRelay[" + stage + "] target=SystemOnline action=applied name=" + ((Object)text).name + " path=" + BuildPath(text.transform) + " text=" + text.text)); } } [IteratorStateMachine(typeof(d__48))] private static IEnumerator WaitForSystemOnlineTitle(HUDManager hudManager, string stage) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__48(0) { hudManager = hudManager, stage = stage }; } [IteratorStateMachine(typeof(d__49))] private static IEnumerator WaitForFixedSceneLabels(string stage) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__49(0) { stage = stage }; } private static void ApplyEnteringAtmosphereNativeTranslation(TMP_Text text, string stage) { ApplyLocalizedText(text, ResolveLocalizedText(text.text, "正在进入大气层..."), stage, "EnteringAtmosphere"); } private static void ApplyHazardLevelNativeTranslation(TMP_Text text, string stage) { ApplyLocalizedText(text, "风险级别:", stage, "HazardLevel"); } private static void ApplyLocalizedText(TMP_Text text, string localized, string stage, string target) { if (!string.IsNullOrWhiteSpace(localized)) { if (!string.Equals(text.text, localized, StringComparison.Ordinal)) { text.text = localized; } FontFallbackService.ApplyFallback(text, localized); ApplyLifeSupportOfflineTypography(text, localized); } } private static void ApplyLifeSupportOfflineTypography(TMP_Text text, string localized) { if (string.Equals(localized, "[生命维持:离线]", StringComparison.Ordinal)) { text.richText = true; text.enableWordWrapping = false; text.overflowMode = (TextOverflowModes)0; text.fontStyle = (FontStyles)0; text.characterSpacing = Math.Max(text.characterSpacing, 6f); text.wordSpacing = Math.Max(text.wordSpacing, 8f); } } private static TMP_Text? FindSystemOnlineTitle(HUDManager? hudManager, bool allowGlobalFallback) { if ((Object)(object)hudManager != (Object)null) { if ((Object)(object)((Component)hudManager).transform != (Object)null) { Transform obj = ((Component)hudManager).transform.Find("BottomMiddle/SystemsOnline/TipLeft1"); TMP_Text val = ((obj != null) ? ((Component)obj).GetComponent() : null); if (IsExactSystemOnlineTitle(val)) { return val; } } TMP_Text[] componentsInChildren = ((Component)hudManager).GetComponentsInChildren(true); foreach (TMP_Text val2 in componentsInChildren) { if (IsExactSystemOnlineTitle(val2)) { return val2; } } } if (allowGlobalFallback) { TMP_Text val3 = FindSystemOnlineTitleByFullPath(); if (IsExactSystemOnlineTitle(val3)) { return val3; } TMP_Text[] componentsInChildren = Resources.FindObjectsOfTypeAll(); foreach (TMP_Text val4 in componentsInChildren) { if (IsExactSystemOnlineTitle(val4)) { return val4; } } } return null; } private static TMP_Text? FindSystemOnlineTitleByFullPath() { GameObject val = GameObject.Find("Systems/UI/Canvas/IngamePlayerHUD/BottomMiddle/SystemsOnline/TipLeft1"); if (!((Object)(object)val != (Object)null)) { return null; } return val.GetComponent(); } private static TMP_Text? FindSystemOnlineTitleByHudPath(HUDManager? hudManager) { if ((Object)(object)((hudManager != null) ? ((Component)hudManager).transform : null) == (Object)null) { return null; } Transform val = ((Component)hudManager).transform.Find("BottomMiddle/SystemsOnline/TipLeft1"); if (!((Object)(object)val == (Object)null)) { return ((Component)val).GetComponent(); } return null; } private static TMP_Text? FindEnteringAtmosphereTitle(HUDManager? hudManager) { if ((Object)(object)hudManager?.LoadingScreen == (Object)null) { return null; } TMP_Text[] componentsInChildren = ((Component)hudManager.LoadingScreen).GetComponentsInChildren(true); foreach (TMP_Text val in componentsInChildren) { if (IsEnteringAtmosphereTitleObject(val)) { return val; } } return null; } private static TMP_Text? FindHazardLevelTitle(HUDManager? hudManager) { if ((Object)(object)hudManager == (Object)null) { return null; } TMP_Text[] componentsInChildren = ((Component)hudManager).GetComponentsInChildren(true); foreach (TMP_Text val in componentsInChildren) { if (IsHazardLevelTitle(val.text)) { return val; } } return null; } private static bool IsSystemOnlineObject(TMP_Text? text) { return GetNativeTextRole(text) == NativeTextRole.SystemOnline; } private static bool IsSystemOnlinePath(Transform? transform) { if ((Object)(object)transform == (Object)null) { return false; } return BuildPath(transform).EndsWith("IngamePlayerHUD/BottomMiddle/SystemsOnline/TipLeft1", StringComparison.OrdinalIgnoreCase); } private static bool IsEnteringAtmosphereTitleObject(TMP_Text? text) { return GetNativeTextRole(text) == NativeTextRole.EnteringAtmosphere; } private static bool IsHazardLevelTitle(string? text) { if (string.IsNullOrWhiteSpace(text)) { return false; } string a = text.Trim(); if (!string.Equals(a, "HAZARD LEVEL:", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "风险级别:", StringComparison.Ordinal) && !string.Equals(a, "风险级别:", StringComparison.Ordinal) && !string.Equals(a, "危险等级:", StringComparison.Ordinal)) { return string.Equals(a, "危险等级:", StringComparison.Ordinal); } return true; } private static string ResolveLocalizedText(string? current, string fallback) { if (!string.IsNullOrWhiteSpace(current) && TranslationService.TryTranslate(current, out string translated)) { return translated; } return fallback; } private static bool TryResolveFixedSceneLabel(string? current, out string localized) { localized = string.Empty; if (string.IsNullOrWhiteSpace(current)) { return false; } if (FixedSceneLabels.TryGetValue(current.Trim(), out localized) && !string.IsNullOrWhiteSpace(localized)) { return true; } return false; } private static int SyncFixedSceneLabelsInRelayScene(string stage) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("SampleSceneRelay"); if (!((Scene)(ref sceneByName)).IsValid() || !((Scene)(ref sceneByName)).isLoaded) { return 0; } int num = 0; GameObject[] rootGameObjects = ((Scene)(ref sceneByName)).GetRootGameObjects(); for (int i = 0; i < rootGameObjects.Length; i++) { TMP_Text[] componentsInChildren = rootGameObjects[i].GetComponentsInChildren(true); for (int j = 0; j < componentsInChildren.Length; j++) { if (TryReplaceFixedSceneLabel(componentsInChildren[j], stage)) { num++; } } } return num; } private static bool HasNamedAncestor(Transform? transform, string expectedName) { Transform val = transform; while ((Object)(object)val != (Object)null) { if (string.Equals(((Object)val).name, expectedName, StringComparison.OrdinalIgnoreCase)) { return true; } val = val.parent; } return false; } private static void AuditSystemOnlineBranch(string stage, HUDManager? hudManager) { int num = 0; if ((Object)(object)hudManager != (Object)null) { TMP_Text[] componentsInChildren = ((Component)hudManager).GetComponentsInChildren(true); foreach (TMP_Text val in componentsInChildren) { if (HasNamedAncestor(val.transform, "SystemsOnline")) { num++; if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogInfo((object)$"NativeRelay[{stage}] target=SystemOnline action=audit exact={IsExactSystemOnlineTitle(val)} name={((Object)val).name} path={BuildPath(val.transform)} text={val.text}"); } } } } _ = 0; } private static void ApplyDynamicFieldTranslation(TMP_Text? text, string stage, string target) { if (!((Object)(object)text == (Object)null) && !string.IsNullOrWhiteSpace(text.text) && (TranslationService.TryTranslateKnownDynamicTextTargeted(DynamicTextDomain.PlanetInfo, text.text, out string translated) || TranslationService.TryTranslate(text.text, out translated)) && !string.IsNullOrWhiteSpace(translated)) { if (!string.Equals(text.text, translated, StringComparison.Ordinal)) { text.text = translated; } FontFallbackService.ApplyFallback(text, translated); if (Plugin.RuntimeLocalizationLogsEnabled) { Plugin.Log.LogInfo((object)("NativeRelay[" + stage + "] target=" + target + " action=applied name=" + ((Object)text).name + " path=" + BuildPath(text.transform) + " text=" + text.text)); } } } private static string BuildPath(Transform? transform) { if ((Object)(object)transform == (Object)null) { return ""; } string text = ((Object)transform).name; Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } private static bool IsExactSystemOnlineTitle(TMP_Text? text) { return GetNativeTextRole(text) == NativeTextRole.SystemOnline; } private static NativeTextRole GetNativeTextRole(TMP_Text? text) { if ((Object)(object)text == (Object)null) { return NativeTextRole.None; } if (TryGetCachedNativeTextRole(text, out var role)) { return role; } role = ResolveNativeTextRole(text); CacheNativeTextRole(text, role); return role; } private static bool TryGetCachedNativeTextRole(TMP_Text text, out NativeTextRole role) { int parentInstanceId = GetParentInstanceId(text.transform); if (NativeTextRoleCache.TryGetValue(((Object)text).GetInstanceID(), out var value) && value.ParentId == parentInstanceId) { role = value.Role; return true; } role = NativeTextRole.None; return false; } private static NativeTextRole ResolveNativeTextRole(TMP_Text text) { if (string.Equals(((Object)text).name, "LoadText", StringComparison.OrdinalIgnoreCase) && HasNamedAncestor(text.transform, "LoadingText")) { return NativeTextRole.EnteringAtmosphere; } if (string.Equals(((Object)text).name, "TipLeft1", StringComparison.OrdinalIgnoreCase) && IsSystemOnlinePath(text.transform)) { return NativeTextRole.SystemOnline; } return NativeTextRole.None; } private static void CacheNativeTextRole(TMP_Text text, NativeTextRole role) { int instanceID = ((Object)text).GetInstanceID(); if (NativeTextRoleCache.Count < RuntimePerformanceSettings.ComponentTextCacheLimit || NativeTextRoleCache.ContainsKey(instanceID)) { NativeTextRoleCache[instanceID] = new CachedNativeTextRole(GetParentInstanceId(text.transform), role); } } private static int GetParentInstanceId(Transform? transform) { if (!((Object)(object)((transform != null) ? transform.parent : null) == (Object)null)) { return ((Object)transform.parent).GetInstanceID(); } return 0; } } internal static class RadiationWarningAuditService { [CompilerGenerated] private sealed class d__9 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int sampleCount; public float sampleIntervalSeconds; public HUDManager hudManager; public string stage; private int 5__2; private float 5__3; private int 5__4; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__9(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0124: Unknown result type (might be due to invalid IL or missing references) int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_013e; } <>1__state = -1; 5__2 = Mathf.Max(1, sampleCount); 5__3 = Mathf.Max(0f, sampleIntervalSeconds); 5__4 = 0; goto IL_014e; IL_013e: 5__4++; goto IL_014e; IL_014e: if (5__4 < 5__2 && !((Object)(object)hudManager == (Object)null)) { Transform val = FindWarningRoot(hudManager); if (!((Object)(object)val == (Object)null)) { AuditImages(val, stage, 5__4); AuditRawImages(val, stage, 5__4); AuditSpriteRenderers(val, stage, 5__4); AuditGraphicMaterials(val, stage, 5__4); AuditRendererMaterials(val, stage, 5__4); if (5__4 + 1 < 5__2) { object obj = this; obj = (object)((!(5__3 > 0f)) ? ((WaitForSeconds)null) : new WaitForSeconds(5__3)); ((d__9)obj).<>2__current = obj; <>1__state = 1; return true; } goto IL_013e; } Plugin.Log.LogInfo((object)string.Format("RadiationAudit[{0}] sample={1} action=sampling-stopped-root-lost suffix={2}", stage, 5__4, "IngamePlayerHUD/SpecialHUDGraphics/RadiationIncrease")); } _activeAuditCoroutine = null; _activeHudManager = null; 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(); } } internal const string WarningRootPathSuffix = "IngamePlayerHUD/SpecialHUDGraphics/RadiationIncrease"; private static ConfigEntry? _enabled; private static ConfigEntry? _sampleCount; private static ConfigEntry? _sampleIntervalSeconds; private static HUDManager? _activeHudManager; private static Coroutine? _activeAuditCoroutine; public static void Initialize(ConfigFile config) { _enabled = config.Bind("90 诊断 - 辐射警告审计", "Enabled", false, "启用辐射警告原始 UI 子树的限量采样诊断。默认关闭,避免运行时枚举 UI 子树。"); _sampleCount = config.Bind("90 诊断 - 辐射警告审计", "SampleCount", 5, "辐射警告触发后采样的次数上限。"); _sampleIntervalSeconds = config.Bind("90 诊断 - 辐射警告审计", "SampleIntervalSeconds", 0.2f, "两次辐射警告诊断采样之间的间隔,单位为秒。"); } public static void Shutdown() { try { if (_activeAuditCoroutine != null && (Object)(object)_activeHudManager != (Object)null) { ((MonoBehaviour)_activeHudManager).StopCoroutine(_activeAuditCoroutine); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("RadiationWarningAuditService shutdown failed while stopping coroutine: " + ex.GetType().Name + ": " + ex.Message)); } finally { _activeAuditCoroutine = null; _activeHudManager = null; _enabled = null; _sampleCount = null; _sampleIntervalSeconds = null; } } public static void OnRadiationWarningTriggered(HUDManager hudManager, string stage) { if ((Object)(object)hudManager == (Object)null) { return; } ConfigEntry? enabled = _enabled; if (enabled == null || !enabled.Value) { return; } if ((Object)(object)FindWarningRoot(hudManager) == (Object)null) { Plugin.Log.LogWarning((object)("RadiationAudit[" + stage + "] action=root-not-found suffix=IngamePlayerHUD/SpecialHUDGraphics/RadiationIncrease")); return; } if (_activeAuditCoroutine != null && (Object)(object)_activeHudManager != (Object)null) { ((MonoBehaviour)_activeHudManager).StopCoroutine(_activeAuditCoroutine); _activeAuditCoroutine = null; } _activeHudManager = hudManager; _activeAuditCoroutine = ((MonoBehaviour)hudManager).StartCoroutine(SampleWarningSubtree(hudManager, stage, _sampleCount?.Value ?? 0, _sampleIntervalSeconds?.Value ?? 0f)); } [IteratorStateMachine(typeof(d__9))] private static IEnumerator SampleWarningSubtree(HUDManager hudManager, string stage, int sampleCount, float sampleIntervalSeconds) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__9(0) { hudManager = hudManager, stage = stage, sampleCount = sampleCount, sampleIntervalSeconds = sampleIntervalSeconds }; } private static void AuditGraphicMaterials(Transform root, string stage, int sampleIndex) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) HashSet hashSet = new HashSet(); Graphic[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Graphic val in componentsInChildren) { Material material; try { material = val.material; } catch (Exception ex) { Plugin.Log.LogWarning((object)$"RadiationAudit[{stage}] sample={sampleIndex} component=GraphicMaterial path={BuildPath(((Component)val).transform)} active={((Component)val).gameObject.activeInHierarchy} enabled={((Behaviour)val).enabled} alpha={val.color.a:0.###} material= texture= error={ex.GetType().Name}:{ex.Message}"); continue; } if ((Object)(object)material != (Object)null) { hashSet.Add(((Object)material).GetInstanceID()); } } } private static void AuditRendererMaterials(Transform root, string stage, int sampleIndex) { Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Material[] sharedMaterials = componentsInChildren[i].sharedMaterials; if (sharedMaterials != null && sharedMaterials.Length != 0) { for (int j = 0; j < sharedMaterials.Length; j++) { _ = sharedMaterials[j]; } } } } private static void AuditImages(Transform root, string stage, int sampleIndex) { Image[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Image val in componentsInChildren) { Sprite obj = val.overrideSprite ?? val.sprite; if (obj != null) { _ = obj.texture; } } } private static void AuditRawImages(Transform root, string stage, int sampleIndex) { RawImage[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { _ = componentsInChildren[i]; } } private static void AuditSpriteRenderers(Transform root, string stage, int sampleIndex) { SpriteRenderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { _ = componentsInChildren[i].sprite; } } private static Transform? FindWarningRoot(HUDManager hudManager) { Animator radiationGraphicAnimator = hudManager.radiationGraphicAnimator; Transform val = ((radiationGraphicAnimator != null) ? ((Component)radiationGraphicAnimator).transform : null); if (HasExpectedRootPath(val)) { return val; } Transform[] componentsInChildren = ((Component)hudManager).GetComponentsInChildren(true); foreach (Transform val2 in componentsInChildren) { if (HasExpectedRootPath(val2)) { return val2; } } return null; } private static bool HasExpectedRootPath(Transform? transform) { if ((Object)(object)transform == (Object)null) { return false; } return BuildPath(transform).EndsWith("IngamePlayerHUD/SpecialHUDGraphics/RadiationIncrease", StringComparison.OrdinalIgnoreCase); } private static string DescribeName(Object? obj) { if (!string.IsNullOrWhiteSpace((obj != null) ? obj.name : null)) { return obj.name; } return ""; } private static string BuildPath(Transform? transform) { if ((Object)(object)transform == (Object)null) { return ""; } string text = ((Object)transform).name; Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } internal static class RadiationWarningPlaybackService { private sealed class PlaybackState { public Transform Root { get; } public Image PanelImage { get; } public Sprite? OriginalRawOverrideSprite { get; } public Sprite? LastAppliedLocalizedSprite { get; set; } public string? LastAppliedOriginalSpriteName { get; set; } public bool CleanupCompleted { get; set; } public PlaybackState(Transform root, Image panelImage, Sprite? originalRawOverrideSprite) { Root = root; PanelImage = panelImage; OriginalRawOverrideSprite = originalRawOverrideSprite; } } [CompilerGenerated] private sealed class d__24 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string stage; public PlaybackState playbackState; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__24(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; float followDurationSeconds = GetFollowDurationSeconds(); 5__2 = Time.realtimeSinceStartup + followDurationSeconds; Plugin.Log.LogInfo((object)$"RadiationPlayback[{stage}] FollowAnimatorSpriteCurve started path={BuildPath(playbackState.Root)} panel={BuildPath(((Component)playbackState.PanelImage).transform)} duration={followDurationSeconds:0.###}"); <>1__state = -3; break; } case 1: <>1__state = -3; break; } if ((Object)(object)playbackState.PanelImage != (Object)null && Time.realtimeSinceStartup <= 5__2) { ApplyCurrentAnimatorSpriteSubstitution(playbackState, stage); <>2__current = null; <>1__state = 1; return true; } if ((Object)(object)playbackState.PanelImage != (Object)null) { ApplyCurrentAnimatorSpriteSubstitution(playbackState, stage); } Plugin.Log.LogInfo((object)("RadiationPlayback[" + stage + "] FollowAnimatorSpriteCurve finished path=" + BuildPath(playbackState.Root))); <>m__Finally1(); return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; CleanupPlaybackState(playbackState, stage, "finally"); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string TextureSubfolder = "textures"; private const string WarningRootPathSuffix = "IngamePlayerHUD/SpecialHUDGraphics/RadiationIncrease"; private const string PanelObjectName = "Panel"; private const float OriginalClipDurationSeconds = 1.7166667f; private const float DefaultFollowDurationSeconds = 1.85f; private const float MaxFollowDurationSeconds = 5f; private const long MaxTextureBytes = 8388608L; private const long MaxTexturePixels = 16777216L; private static readonly Dictionary OriginalSpriteFrameFiles = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["RedUIPanelGlitchB"] = FrameFileNameCandidates("RedUIPanelGlitchB", "C864CEDFD2-470F6C25C6"), ["RedUIPanelGlitchBWarning"] = FrameFileNameCandidates("RedUIPanelGlitchBWarning", "E5C5C6E52B-F9F9EA9261"), ["RedUIPanelGlitchBWarningRadiation"] = FrameFileNameCandidates("RedUIPanelGlitchBWarningRadiation", "FFCC97E055-2C688E6DC8"), ["RedUIPanelGlitchBWarningRadiationB"] = FrameFileNameCandidates("RedUIPanelGlitchBWarningRadiationB", "7D4ED37952-95F686DE01"), ["RedUIPanelGlitchBWarningRadiationC"] = FrameFileNameCandidates("RedUIPanelGlitchBWarningRadiationC", "7FBAA1AA30-9E2C036778"), ["RedUIPanelGlitchBWarningRadiationD"] = FrameFileNameCandidates("RedUIPanelGlitchBWarningRadiationD", "4877939F7B-47E20481B0") }; private static readonly Dictionary TextureCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ResolvedFrameTextureCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary SpriteCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly FieldInfo? RawOverrideSpriteField = typeof(Image).GetField("m_OverrideSprite", BindingFlags.Instance | BindingFlags.NonPublic); private static string? _textureDirectory; private static ConfigEntry? _enabled; private static ConfigEntry? _followDurationSeconds; private static HUDManager? _activeHudManager; private static Coroutine? _activePlaybackCoroutine; private static PlaybackState? _activePlaybackState; public static void Initialize(string pluginDir, ConfigFile config) { _textureDirectory = Path.Combine(pluginDir, "textures"); _enabled = config.Bind("04 资源 - 辐射警告", "Enabled", true, "启用跟随原版动画的辐射警告贴图替换。"); _followDurationSeconds = config.Bind("04 资源 - 辐射警告", "FollowDurationSeconds", 1.85f, "跟随原版 RadiationIncreaseWarning 动画并替换本地化贴图的持续时间,单位为秒。"); foreach (string key in OriginalSpriteFrameFiles.Keys) { ResolveFrameTexture(key, "Initialize"); } Plugin.Log.LogInfo((object)$"RadiationPlayback initialized mode=animator-follow clipSeconds={1.7166667f:0.###}"); } public static void OnRadiationWarningTriggered(HUDManager hudManager, string stage) { if ((Object)(object)hudManager == (Object)null) { return; } ConfigEntry? enabled = _enabled; if (enabled == null || !enabled.Value) { return; } if (_activePlaybackCoroutine != null) { Plugin.Log.LogInfo((object)("RadiationPlayback[" + stage + "] action=duplicate-trigger-restart mode=animator-follow")); CleanupActivePlayback(stage, "duplicate-restart"); } Transform val = FindWarningRoot(hudManager); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)("RadiationPlayback[" + stage + "] action=root-not-found suffix=IngamePlayerHUD/SpecialHUDGraphics/RadiationIncrease")); return; } Image val2 = FindWarningPanel(val); if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning((object)("RadiationPlayback[" + stage + "] action=panel-not-found path=" + BuildPath(val) + " child=Panel")); } else if (HasAllLocalizedTextures(stage)) { PlaybackState playbackState = new PlaybackState(val, val2, ReadRawOverrideSprite(val2)); _activeHudManager = hudManager; _activePlaybackState = playbackState; _activePlaybackCoroutine = ((MonoBehaviour)hudManager).StartCoroutine(FollowAnimatorSpriteCurve(playbackState, stage)); } } public static void Shutdown() { CleanupActivePlayback("Shutdown", "shutdown"); ClearCachedAssets(); } public static void ResetForHudLifecycle(HUDManager hudManager, string stage) { if (!((Object)(object)hudManager == (Object)null) && (_activePlaybackCoroutine != null || _activePlaybackState != null)) { CleanupActivePlayback(stage, "hud-reset"); } } [IteratorStateMachine(typeof(d__24))] private static IEnumerator FollowAnimatorSpriteCurve(PlaybackState playbackState, string stage) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__24(0) { playbackState = playbackState, stage = stage }; } private static float GetFollowDurationSeconds() { float num = _followDurationSeconds?.Value ?? 1.85f; if (float.IsNaN(num) || float.IsInfinity(num)) { num = 1.85f; } return Mathf.Clamp(num, 1.7166667f, 5f); } private static void ApplyCurrentAnimatorSpriteSubstitution(PlaybackState playbackState, string stage) { Image panelImage = playbackState.PanelImage; Sprite sprite = panelImage.sprite; string text = NormalizeSpriteName((sprite != null) ? ((Object)sprite).name : null); if (string.IsNullOrWhiteSpace(text)) { return; } if (!OriginalSpriteFrameFiles.ContainsKey(text)) { ClearLocalizedOverrideIfOwned(playbackState, "unmapped-original"); return; } Texture2D val = ResolveFrameTexture(text, stage); if (!((Object)(object)val == (Object)null)) { Sprite val2 = ResolveLocalizedSprite(text, sprite, val); if (!((Object)(object)val2 == (Object)null) && ReadRawOverrideSprite(panelImage) != val2) { panelImage.overrideSprite = val2; ((Graphic)panelImage).SetVerticesDirty(); ((Graphic)panelImage).SetMaterialDirty(); string.Equals(playbackState.LastAppliedOriginalSpriteName, text, StringComparison.OrdinalIgnoreCase); playbackState.LastAppliedOriginalSpriteName = text; playbackState.LastAppliedLocalizedSprite = val2; } } } private static void CleanupActivePlayback(string stage, string reason) { Coroutine activePlaybackCoroutine = _activePlaybackCoroutine; HUDManager activeHudManager = _activeHudManager; PlaybackState activePlaybackState = _activePlaybackState; _activePlaybackCoroutine = null; _activeHudManager = null; _activePlaybackState = null; if (activePlaybackCoroutine != null && (Object)(object)activeHudManager != (Object)null) { ((MonoBehaviour)activeHudManager).StopCoroutine(activePlaybackCoroutine); } if (activePlaybackState != null) { CleanupPlaybackState(activePlaybackState, stage, reason); } } private static void CleanupPlaybackState(PlaybackState playbackState, string stage, string reason) { if (!playbackState.CleanupCompleted) { playbackState.CleanupCompleted = true; ClearLocalizedOverrideIfOwned(playbackState, reason); if (_activePlaybackState == playbackState) { _activePlaybackState = null; _activePlaybackCoroutine = null; _activeHudManager = null; } string text = (((Object)(object)playbackState.PanelImage != (Object)null) ? BuildPath(((Component)playbackState.PanelImage).transform) : ""); Plugin.Log.LogInfo((object)("RadiationPlayback[" + stage + "] action=restore reason=" + reason + " path=" + BuildPath(playbackState.Root) + " panel=" + text)); } } private static void ClearLocalizedOverrideIfOwned(PlaybackState playbackState, string reason) { Image panelImage = playbackState.PanelImage; if (!((Object)(object)panelImage == (Object)null)) { Sprite val = ReadRawOverrideSprite(panelImage); if ((Object)(object)val != (Object)null && val != playbackState.LastAppliedLocalizedSprite && !IsCachedLocalizedSprite(val)) { Plugin.Log.LogInfo((object)("RadiationPlayback action=skip-restore reason=" + reason + " currentOverride=" + ((Object)val).name)); return; } panelImage.overrideSprite = playbackState.OriginalRawOverrideSprite; ((Graphic)panelImage).SetVerticesDirty(); ((Graphic)panelImage).SetMaterialDirty(); } } private static bool HasAllLocalizedTextures(string stage) { foreach (string key in OriginalSpriteFrameFiles.Keys) { if ((Object)(object)ResolveFrameTexture(key, stage) == (Object)null) { return false; } } return true; } private static string[] FrameFileNameCandidates(string baseName, string contentHash) { return new string[2] { baseName + " [" + contentHash + "].png", baseName + "[" + contentHash + "].png" }; } private static string? ResolveFrameFileName(string originalSpriteName, string stage) { if (!OriginalSpriteFrameFiles.TryGetValue(originalSpriteName, out string[] value)) { return null; } string[] array = value; foreach (string text in array) { if ((Object)(object)LoadTexture(text) != (Object)null) { return text; } } Plugin.Log.LogWarning((object)("RadiationPlayback[" + stage + "] action=incomplete-frame-set original=" + originalSpriteName + " candidates=" + string.Join("|", value) + " nativeFallback=true")); return null; } private static Texture2D? ResolveFrameTexture(string originalSpriteName, string stage) { if (ResolvedFrameTextureCache.TryGetValue(originalSpriteName, out Texture2D value)) { return value; } string text = ResolveFrameFileName(originalSpriteName, stage); if (text == null) { ResolvedFrameTextureCache[originalSpriteName] = null; return null; } Texture2D val = LoadTexture(text); ResolvedFrameTextureCache[originalSpriteName] = val; return val; } private static Image? FindWarningPanel(Transform root) { Transform obj = root.Find("Panel"); Image val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)val != (Object)null) { return val; } Image[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Image val2 in componentsInChildren) { if (string.Equals(((Object)val2).name, "Panel", StringComparison.OrdinalIgnoreCase)) { return val2; } } return null; } private static Sprite? ResolveLocalizedSprite(string originalSpriteName, Sprite? originalSprite, Texture2D texture) { string key = BuildSpriteCacheKey(originalSpriteName, originalSprite, texture); if (SpriteCache.TryGetValue(key, out Sprite value)) { return value; } Sprite val = CreateLocalizedSprite(texture, originalSprite); SpriteCache[key] = val; return val; } private static string BuildSpriteCacheKey(string originalSpriteName, Sprite? originalSprite, Texture2D texture) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)originalSprite == (Object)null) { return originalSpriteName + ":" + ((Object)texture).name + ":fallback"; } Rect rect = originalSprite.rect; return $"{originalSpriteName}:{((Object)texture).name}:{((Rect)(ref rect)).x}:{((Rect)(ref rect)).y}:{((Rect)(ref rect)).width}:{((Rect)(ref rect)).height}:{originalSprite.pivot.x}:{originalSprite.pivot.y}:{originalSprite.pixelsPerUnit}"; } private static Sprite? CreateLocalizedSprite(Texture2D texture, Sprite? template) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)template != (Object)null) { Rect rect = template.rect; if (((Rect)(ref rect)).width > 0f && ((Rect)(ref rect)).height > 0f && ((Rect)(ref rect)).xMax <= (float)((Texture)texture).width && ((Rect)(ref rect)).yMax <= (float)((Texture)texture).height) { Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(template.pivot.x / ((Rect)(ref rect)).width, template.pivot.y / ((Rect)(ref rect)).height); Sprite obj = Sprite.Create(texture, rect, val, template.pixelsPerUnit, 0u, (SpriteMeshType)0, template.border); ((Object)obj).name = ((Object)texture).name; return obj; } } Sprite obj2 = Sprite.Create(texture, new Rect(0f, 0f, (float)((Texture)texture).width, (float)((Texture)texture).height), new Vector2(0.5f, 0.5f), 100f); ((Object)obj2).name = ((Object)texture).name; return obj2; } catch (Exception ex) { Plugin.Log.LogWarning((object)("RadiationPlayback[Sprite] action=create-failed texture=" + ((Object)texture).name + " error=" + ex.GetType().Name + ": " + ex.Message)); return null; } } private static bool IsCachedLocalizedSprite(Sprite sprite) { foreach (Sprite value in SpriteCache.Values) { if (value == sprite) { return true; } } return false; } private static Sprite? ReadRawOverrideSprite(Image image) { if (RawOverrideSpriteField == null) { return null; } try { object? value = RawOverrideSpriteField.GetValue(image); return (Sprite?)((value is Sprite) ? value : null); } catch (Exception ex) { Plugin.Log.LogWarning((object)("RadiationPlayback[Image] action=read-raw-override-failed error=" + ex.GetType().Name + ": " + ex.Message)); return null; } } private static Transform? FindWarningRoot(HUDManager hudManager) { Animator radiationGraphicAnimator = hudManager.radiationGraphicAnimator; Transform val = ((radiationGraphicAnimator != null) ? ((Component)radiationGraphicAnimator).transform : null); if (HasExpectedRootPath(val)) { return val; } Transform[] componentsInChildren = ((Component)hudManager).GetComponentsInChildren(true); foreach (Transform val2 in componentsInChildren) { if (HasExpectedRootPath(val2)) { return val2; } } return null; } private static bool HasExpectedRootPath(Transform? transform) { if ((Object)(object)transform == (Object)null) { return false; } return BuildPath(transform).EndsWith("IngamePlayerHUD/SpecialHUDGraphics/RadiationIncrease", StringComparison.OrdinalIgnoreCase); } private static Texture2D? LoadTexture(string fileName) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown if (TextureCache.TryGetValue(fileName, out Texture2D value)) { return value; } if (string.IsNullOrWhiteSpace(_textureDirectory)) { return null; } string text = Path.Combine(_textureDirectory, fileName); if (!File.Exists(text)) { TextureCache[fileName] = null; return null; } Texture2D val = null; try { FileInfo fileInfo = new FileInfo(text); if (fileInfo.Length > 8388608) { Plugin.Log.LogWarning((object)$"RadiationPlayback[Texture] action=file-too-large file={fileName} bytes={fileInfo.Length} max={8388608L}"); TextureCache[fileName] = null; return null; } byte[] array = File.ReadAllBytes(text); val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array, false)) { Object.Destroy((Object)(object)val); val = null; TextureCache[fileName] = null; return null; } if (IsDecodedTextureTooLarge(val)) { Plugin.Log.LogWarning((object)("RadiationPlayback[Texture] action=decoded-texture-too-large file=" + fileName + " " + $"width={((Texture)val).width} height={((Texture)val).height} pixels={(long)((Texture)val).width * (long)((Texture)val).height} maxPixels={16777216L}")); Object.Destroy((Object)(object)val); val = null; TextureCache[fileName] = null; return null; } ((Object)val).name = Path.GetFileNameWithoutExtension(fileName); ((Texture)val).wrapMode = (TextureWrapMode)1; TextureCache[fileName] = val; Texture2D result = val; val = null; return result; } catch (Exception ex) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } Plugin.Log.LogWarning((object)("RadiationPlayback[Texture] action=load-failed file=" + fileName + " error=" + ex.GetType().Name + ": " + ex.Message)); TextureCache[fileName] = null; return null; } } private static void ClearCachedAssets() { HashSet hashSet = new HashSet(); foreach (Sprite value in SpriteCache.Values) { if (!((Object)(object)value == (Object)null) && hashSet.Add(((Object)value).GetInstanceID())) { Object.Destroy((Object)(object)value); } } HashSet destroyedTextureIds = new HashSet(); foreach (Texture2D value2 in TextureCache.Values) { DestroyTextureOnce(value2, destroyedTextureIds); } foreach (Texture2D value3 in ResolvedFrameTextureCache.Values) { DestroyTextureOnce(value3, destroyedTextureIds); } SpriteCache.Clear(); TextureCache.Clear(); ResolvedFrameTextureCache.Clear(); } private static bool IsDecodedTextureTooLarge(Texture2D texture) { if (((Texture)texture).width <= 0 || ((Texture)texture).height <= 0) { return true; } return (long)((Texture)texture).width * (long)((Texture)texture).height > 16777216; } private static void DestroyTextureOnce(Texture2D? texture, HashSet destroyedTextureIds) { if (!((Object)(object)texture == (Object)null) && destroyedTextureIds.Add(((Object)texture).GetInstanceID())) { Object.Destroy((Object)(object)texture); } } private static string NormalizeSpriteName(string? spriteName) { if (string.IsNullOrWhiteSpace(spriteName)) { return string.Empty; } string text = spriteName.Trim(); if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - "(Clone)".Length).TrimEnd(); } return text; } private static string BuildPath(Transform? transform) { if ((Object)(object)transform == (Object)null) { return ""; } string text = ((Object)transform).name; Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } internal static class ExternalEnglishCompatibilityService { private const int MaxSourceLength = 512; private const int RuntimeCacheLimit = 4096; private const string DiscountAlertNoDiscountLocalizedText = "暂无折扣\n明天再来查看"; private static readonly Dictionary ExactEntries = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Emote Menu"] = "动作菜单", ["Random Emote"] = "随机动作", ["Zoom"] = "缩放", ["Freeze"] = "冻结", ["Swap Page"] = "切换页面", ["Favorite Emote"] = "收藏动作", ["Set Quick Emote"] = "设置快捷动作", ["Tell autopilot ship to leave early"] = "命令自动驾驶飞船提前离开", ["Spectate Previous Player"] = "切换上一名玩家", ["Open Admin UI"] = "打开管理界面", ["Copy Lobby ID"] = "复制房间 ID", ["Enter tag or id..."] = "输入标签或 ID...", ["Enter tag or ip..."] = "输入标签或 IP...", ["Search Mods"] = "搜索模组", ["Search Configs"] = "搜索配置", ["Close"] = "关闭", ["Apply"] = "应用", ["Delete"] = "删除", ["Go back"] = "返回", ["OK"] = "确认", ["Server Password"] = "房间密码", ["Validate Steam Sessions"] = "验证 Steam 会话", ["Max Players"] = "最大人数", ["Server Access"] = "房间权限", ["Invite-only"] = "仅限邀请", ["Friends-only"] = "仅限好友", ["Server Tag"] = "房间标签", ["New File"] = "新存档", ["Players"] = "玩家", ["Today's discounts"] = "今日折扣", ["Target on ship"] = "飞船上的目标", ["Signal lost"] = "信号丢失", ["Antenna stored"] = "天线已存放", ["PERFORMANCE REPORT"] = "绩效报告", ["NO SURVIVORS"] = "无人生还", ["NOTES"] = "备注", ["DECEASED"] = "死亡", ["Collected"] = "已收集", ["Grade"] = "评级", ["Rating"] = "评级", ["Lost 100% scrap"] = "损失 100% 废料", ["No items were found"] = "未找到可售物品", ["Error selling items"] = "售卖物品时出错", ["You can't afford to sell that amount"] = "可售物品不足,无法卖出该金额", ["Successfully emptied temporary blacklist"] = "已清空临时黑名单", ["Successfully emptied temporary priority set"] = "已清空临时优先列表", ["Item Blacklist"] = "物品黑名单", ["Priority Items"] = "优先售卖物品", ["Flag Prefix"] = "标志前缀", ["Items to never sell by internal name (comma-separated)"] = "永不出售的物品内部名称(逗号分隔)", ["Items which are prioritized when selling"] = "出售时优先选择的物品", ["QUICKSELL"] = "快速出售", ["SELL RESULTS"] = "出售结果", ["Command"] = "命令", ["Invalid command"] = "命令无效", ["HELP PAGE"] = "帮助页面", ["ALL HELP PAGE"] = "全部出售帮助页面", ["AMOUNT HELP PAGE"] = "金额帮助页面", ["BLACKLIST HELP PAGE"] = "黑名单帮助页面", ["ITEM HELP PAGE"] = "物品帮助页面", ["PRIORITY HELP PAGE"] = "优先列表帮助页面", ["QUOTA HELP PAGE"] = "配额帮助页面", ["FLAG HELP PAGE"] = "标志帮助页面", ["OVERTIME HELP PAGE"] = "加班奖励帮助页面", ["PAGES HELP PAGE"] = "页面帮助", ["-A HELP PAGE"] = "-A 标志帮助", ["-E HELP PAGE"] = "-E 标志帮助", ["-N HELP PAGE"] = "-N 标志帮助", ["-O HELP PAGE"] = "-O 标志帮助", ["-P HELP PAGE"] = "-P 标志帮助", ["Combining flags"] = "组合标志", ["\"help\" to open this page or a specific help page"] = "\"help\":打开此页或指定帮助页", ["\"quota\" to sell exactly for quota"] = "\"quota\":按配额精确出售", ["\"all\" to sell all unfiltered scrap available"] = "\"all\":出售所有未过滤的可用废料", ["\"item\" to sell all items like the one you are holding or the one you specified"] = "\"item\":出售所持有或指定的同类物品", ["\"-a\" to ignore blacklist (used with quota, all, )"] = "\"-a\":忽略黑名单(用于 quota、all、)", ["\"-o\" to sell accounting for overtime (used with )"] = "\"-o\":计入加班奖励后出售(用于 )", ["The symbol which is used as prefix in flags (aka \"-\" in \"-e\")"] = "命令标志使用的前缀符号(例如 -e 中的 -)", ["Command variations"] = "命令用法", ["The sell command was initiated"] = "售卖命令已开始", ["The sell command completed it's job, terminating"] = "售卖命令已完成,正在结束", ["The overtime command was initiated"] = "加班奖励命令已开始", ["Use \"/sell help flags\" to see info on important flags"] = "输入 \"/sell help flags\" 查看重要标志说明", ["Use \"/sell help \" to see info on specific flag"] = "输入 \"/sell help \" 查看指定标志说明", ["Use \"/sell help \" to see info on specific command"] = "输入 \"/sell help \" 查看指定命令说明", ["Use \"/sell help pages\" to see info on all pages"] = "输入 \"/sell help pages\" 查看所有帮助页面", ["Use \"/sell help overtime\" to see info on the \"/ot\" (overtime command)"] = "输入 \"/sell help overtime\" 查看 \"/ot\"(加班奖励命令)说明", ["The value must be positive"] = "数值必须为正", ["Cannot find terminal!"] = "未找到终端!", ["Cannot find terminal?!"] = "未找到终端!", ["Quota is already fulfilled"] = "利润指标已完成", ["No item is held and no item was specified"] = "未持有物品,也未指定物品", ["No items on the desk"] = "柜台上没有物品", ["Door already open"] = "门已经打开", ["Wrong item name"] = "物品名称错误", ["No page with this name exists"] = "不存在这个帮助页面", ["Failed to evalute expression"] = "表达式计算失败", ["No items to sort"] = "没有可整理的物品", ["No items on the ship"] = "飞船上没有物品", ["Sorting all items..."] = "正在整理所有物品...", ["Finished sorting items"] = "物品整理完成", ["Invalid arguments"] = "参数无效", ["Invalid item name"] = "物品名称无效", ["Error running command"] = "运行命令时出错", ["Error getting position"] = "获取位置时出错", ["Automatic sorting failed"] = "自动整理失败", ["Error while autosorting items"] = "自动整理物品时出错", ["Automatic sorting failed due to an internal error, check the log for details"] = "自动整理因内部错误失败,请查看日志", ["Couldn't find ship"] = "未找到飞船", ["Raycast unsuccessful"] = "射线检测未成功", ["The ship must be in orbit"] = "飞船必须处于轨道中", ["You can't pick anything up while sorting items."] = "整理物品时无法拾取任何物品。", ["Toggles automatic item sorting when leaving a planet"] = "切换离开星球时是否自动整理物品", ["* The most paranoid employee."] = "* 最多疑的员工", ["The most paranoid employee."] = "最多疑的员工", ["* Sustained the most injuries."] = "* 受伤最多", ["Sustained the most injuries."] = "受伤最多", ["* Dislikes smoke."] = "* 讨厌烟雾", ["Dislikes smoke."] = "讨厌烟雾", ["* The least likely to die next time."] = "* 下次最不可能死亡", ["The least likely to die next time."] = "下次最不可能死亡", ["* I think this one's a serial killer."] = "* 我觉得这人像个连环杀手", ["I think this one's a serial killer."] = "我觉得这人像个连环杀手", ["* Go! Freaky on a Friday night."] = "* 周五夜晚尽情疯狂", ["Go! Freaky on a Friday night."] = "周五夜晚尽情疯狂" }; private static readonly Dictionary KeyTokenEntries = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Scroll Mouse"] = "滚动鼠标", ["keybind #"] = "按键绑定 #" }; private static readonly string[] SellingNamedMarkers = new string[3] { " item(s) named \"", " items named \"", " item named \"" }; private static readonly string[] SellingCountMarkers = new string[3] { " item(s) with a total value of ", " items with a total value of ", " item with a total value of " }; private static readonly Dictionary CanHandleCache = new Dictionary(4096, StringComparer.Ordinal); private static readonly Dictionary TranslationCache = new Dictionary(4096, StringComparer.Ordinal); public static void ClearRuntimeCaches() { CanHandleCache.Clear(); TranslationCache.Clear(); } public static bool CanHandleCheap(string? source) { if (string.IsNullOrWhiteSpace(source) || source.Length > 512) { return false; } if (CanHandleCache.TryGetValue(source, out var value)) { return value; } bool result = CanHandleCheapCore(source); CacheCanHandleResult(source, result); return result; } public static bool MightTranslateStatusLikeTextCheap(string? source) { if (string.IsNullOrWhiteSpace(source) || source.Length > 512) { return false; } if (ContainsLineBreak(source)) { int num = 0; while (num <= source.Length) { int num2 = source.IndexOf('\n', num); int num3 = ((num2 < 0) ? source.Length : num2); ReadOnlySpan line = source.AsSpan(num, num3 - num); if (line.Length > 0) { if (line[line.Length - 1] == '\r') { line = line.Slice(0, line.Length - 1); } } if (LineMightNeedExternalCompatibilityCheck(line) && MightTranslateStatusLikeSingleLineCheap(line.ToString())) { return true; } if (num2 < 0) { break; } num = num2 + 1; } return false; } return MightTranslateStatusLikeSingleLineCheap(source); } public static bool MightTranslateStatusLikeLabelCheap(string? label) { if (string.IsNullOrWhiteSpace(label) || label.Length > 64) { return false; } string text = StripMenuSelectionPrefix(StripLeadingSimpleRichTextTags(StripOuterSimpleRichTextEnvelope(label)).Trim()); if (text.EndsWith(":", StringComparison.Ordinal)) { string text2 = text; text = text2.Substring(0, text2.Length - 1).TrimEnd(); } return ExactEntries.ContainsKey(text); } private static bool CanHandleCheapCore(string source) { if (MightContainDeleteFilePrompt(source) && LooksLikeDeleteFilePrompt(source)) { return true; } if (MightContainDiscountAlertNoDiscountText(source) && LooksLikeDiscountAlertNoDiscountText(source)) { return true; } if (ContainsLineBreak(source)) { return CanHandleAnyLineCheap(source); } if (ContainsCjk(source)) { return false; } return CanHandleSingleLineCheap(source); } private static bool CanHandleAnyLineCheap(string source) { int num = 0; while (num <= source.Length) { int num2 = source.IndexOf('\n', num); int num3 = ((num2 < 0) ? source.Length : num2); ReadOnlySpan line = source.AsSpan(num, num3 - num); if (line.Length > 0) { if (line[line.Length - 1] == '\r') { line = line.Slice(0, line.Length - 1); } } if (LineMightNeedExternalCompatibilityCheck(line) && CanHandleSingleLineCheap(line.ToString())) { return true; } if (num2 < 0) { break; } num = num2 + 1; } return false; } private static bool LineMightNeedExternalCompatibilityCheck(ReadOnlySpan line) { line = line.Trim(); if (line.Length == 0 || line.Length > 512) { return false; } bool result = false; ReadOnlySpan readOnlySpan = line; for (int i = 0; i < readOnlySpan.Length; i++) { char ch = readOnlySpan[i]; if (IsCjk(ch)) { return false; } if (IsAsciiLetter(ch)) { result = true; } } return result; } private static bool MightContainDeleteFilePrompt(string source) { if (source.IndexOf("delete", StringComparison.OrdinalIgnoreCase) >= 0) { return source.IndexOf("file", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static bool MightContainDiscountAlertNoDiscountText(string source) { if (source.IndexOf("None", StringComparison.OrdinalIgnoreCase) >= 0) { return source.IndexOf("tomorrow", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static bool MightContainDiscountLine(string source) { if (source.IndexOf('$') >= 0) { if (source.IndexOf(" off!", StringComparison.OrdinalIgnoreCase) < 0) { return source.IndexOf(" up!", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } return false; } private static bool MightContainCompositeExternalUiShape(string text) { foreach (char c in text) { bool flag = char.IsWhiteSpace(c); if (!flag) { bool flag2; switch (c) { case '!': case '#': case '$': case '%': case '(': case ')': case '*': case '-': case ':': case '[': case ']': flag2 = true; break; default: flag2 = false; break; } flag = flag2; } if (flag) { return true; } } return false; } private static bool CanHandleSingleLineCheap(string source) { string text = StripLeadingSimpleRichTextTags(StripOuterSimpleRichTextEnvelope(source)).Trim(); if (text.Length == 0) { return false; } string text2 = StripMenuSelectionPrefix(text); if (ExactEntries.ContainsKey(text2) || LooksLikeBracketedKnownExternalUiToken(text2) || LooksLikeDecoratedKnownExternalUiToken(text2)) { return true; } if (!MightContainCompositeExternalUiShape(text2)) { return false; } if (!LooksLikeKnownExternalUiLabel(text2) && !LooksLikeSaveFileLabel(text2) && !LooksLikeAdvancedFeaturesPlayerLabel(text2) && !LooksLikeDeleteFilePrompt(text2) && text.IndexOf("Emote", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("Admin UI", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("Lobby ID", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("Server Access", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("Steam Sessions", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("FRIENDS ONLY", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("INVITE ONLY", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("discount", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf(" off!", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf(" up!", StringComparison.OrdinalIgnoreCase) < 0) { return LooksLikeChatCommandCompatibilityText(text2); } return true; } private static bool MightTranslateStatusLikeSingleLineCheap(string source) { string text = StripLeadingSimpleRichTextTags(StripOuterSimpleRichTextEnvelope(source)).Trim(); if (text.Length == 0 || ContainsCjk(text)) { return false; } string text2 = StripMenuSelectionPrefix(text).Trim(); if (!LooksLikeKnownExternalUiLabel(text2) && !LooksLikeBracketedKnownExternalUiToken(text2) && !LooksLikeDecoratedKnownExternalUiToken(text2)) { return LooksLikeChatCommandCompatibilityText(text2); } return true; } public static bool TryTranslateFast(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source) || source.Length > 512) { return false; } if (TryGetCachedTranslation(source, out translated, out var hasTranslation)) { return hasTranslation; } if (!CanHandleCheap(source)) { CacheTranslationResult(source, null); return false; } if (TryTranslateDiscountAlertNoDiscountText(source, out translated) || TryTranslateDeleteFilePrompt(source, out translated)) { CacheTranslationResult(source, translated); return true; } if (ContainsLineBreak(source)) { bool flag = TryTranslateLines(source, out translated); CacheTranslationResult(source, flag ? translated : null); return flag; } bool flag2 = TryTranslateSingleLinePreservingWhitespace(source, out translated); CacheTranslationResult(source, flag2 ? translated : null); return flag2; } public static bool TryTranslateDisplayTipText(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } if (source.Length <= 512) { return TryTranslateFast(source, out translated); } if (ContainsLineBreak(source)) { return TryTranslateDisplayTipLines(source, out translated); } return false; } public static bool MightContainDisplayTipCompatibilityText(string? source) { if (string.IsNullOrWhiteSpace(source)) { return false; } if (source.Length <= 512) { if (!CanHandleCheap(source)) { return MightContainDiscountLine(source); } return true; } return MightContainDiscountLine(source); } private static void CacheCanHandleResult(string source, bool result) { if ((result || !LooksLikeVolatileNegativeCacheSource(source)) && CanHandleCache.Count < RuntimePerformanceSettings.ExternalCompatibilityCacheLimit) { CanHandleCache[source] = result; } } private static bool TryGetCachedTranslation(string source, out string translated, out bool hasTranslation) { if (!TranslationCache.TryGetValue(source, out string value)) { translated = source; hasTranslation = false; return false; } hasTranslation = value != null; translated = value ?? source; return true; } private static void CacheTranslationResult(string source, string? translated) { if ((translated != null || !LooksLikeVolatileNegativeCacheSource(source)) && TranslationCache.Count < RuntimePerformanceSettings.ExternalCompatibilityCacheLimit) { TranslationCache[source] = translated; } } private static bool LooksLikeVolatileNegativeCacheSource(string source) { if (source.Length > 128) { return true; } foreach (char c in source) { bool flag = char.IsDigit(c); if (!flag) { bool flag2; switch (c) { case '\n': case '\r': case '#': case '$': case '%': case '(': case ')': case '/': case ':': case '<': case '>': case '[': case '\\': case ']': flag2 = true; break; default: flag2 = false; break; } flag = flag2; } if (flag) { return true; } } return false; } private static bool TryTranslateLines(string source, out string translated) { bool flag = false; StringBuilder stringBuilder = new StringBuilder(source.Length + 16); int num = 0; while (num <= source.Length) { int num2 = source.IndexOf('\n', num); int num3 = ((num2 < 0) ? source.Length : num2); string text = source.Substring(num, num3 - num); bool flag2 = text.EndsWith("\r", StringComparison.Ordinal); string source2; if (!flag2) { source2 = text; } else { string text2 = text; source2 = text2.Substring(0, text2.Length - 1); } if (TryTranslateSingleLinePreservingWhitespace(source2, out string translated2)) { stringBuilder.Append(translated2); if (flag2) { stringBuilder.Append('\r'); } flag = true; } else { stringBuilder.Append(text); } if (num2 < 0) { break; } stringBuilder.Append('\n'); num = num2 + 1; } translated = (flag ? stringBuilder.ToString() : source); return flag; } private static bool TryTranslateDisplayTipLines(string source, out string translated) { bool flag = false; StringBuilder stringBuilder = new StringBuilder(source.Length + 16); int num = 0; while (num <= source.Length) { int num2 = source.IndexOf('\n', num); int num3 = ((num2 < 0) ? source.Length : num2); string text = source.Substring(num, num3 - num); bool flag2 = text.EndsWith("\r", StringComparison.Ordinal); string text2; if (!flag2) { text2 = text; } else { string text3 = text; text2 = text3.Substring(0, text3.Length - 1); } string text4 = text2; if (text4.Length <= 512 && LineMightNeedExternalCompatibilityCheck(text4.AsSpan()) && TryTranslateSingleLinePreservingWhitespace(text4, out string translated2)) { stringBuilder.Append(translated2); if (flag2) { stringBuilder.Append('\r'); } flag = true; } else { stringBuilder.Append(text); } if (num2 < 0) { break; } stringBuilder.Append('\n'); num = num2 + 1; } translated = (flag ? stringBuilder.ToString() : source); return flag; } private static bool TryTranslateSingleLinePreservingWhitespace(string source, out string translated) { translated = source; int num = source.Length - source.TrimStart().Length; int num2 = source.Length - source.TrimEnd().Length; int num3 = source.Length - num - num2; if (num3 <= 0) { return false; } string text = ((num > 0) ? source.Substring(0, num) : string.Empty); string text2; if (num2 <= 0) { text2 = string.Empty; } else { int num4 = num2; int length = source.Length; int num5 = length - num4; text2 = source.Substring(num5, length - num5); } string text3 = text2; if (!TryTranslateSingleLineCore(source.Substring(num, num3), out string translated2)) { return false; } translated = text + translated2 + text3; return true; } private static bool TryTranslateSingleLineCore(string source, out string translated) { translated = source; string value = source.Trim(); string text = string.Empty; string text2 = string.Empty; string prefix; string inner; string suffix; while (TryExtractOuterSimpleRichTextEnvelope(value, out prefix, out inner, out suffix)) { text += prefix; text2 = suffix + text2; value = inner.Trim(); } text += ExtractLeadingSimpleRichTextPrefix(ref value); string text3 = string.Empty; if (value.StartsWith(">", StringComparison.Ordinal)) { text3 = "> "; value = StripMenuSelectionPrefix(value); } if (LooksLikeNonUiName(value)) { return false; } if (TryTranslateBracketedCommand(value, out translated) || TryTranslateDecoratedExactUiText(value, out translated) || TryTranslateControlTip(value, out translated) || TryTranslateLabelValue(value, out translated) || TryTranslateDiscountLine(value, out translated) || TryTranslateSaveFileLabel(value, out translated) || TryTranslateAdvancedFeaturesPlayerLabel(value, out translated) || TryTranslateDeleteFilePrompt(value, out translated) || TryTranslateChatCommandCompatibilityText(value, out translated) || TryTranslateExactUiText(value, out translated)) { translated = text3 + text + translated + text2; return true; } return false; } private static bool TryTranslateSaveFileLabel(string text, out string translated) { translated = text; string text2 = StripMenuSelectionPrefix(text).Trim(); if (!LooksLikeSaveFileLabel(text2)) { return false; } string text3 = text2; int length = "File ".Length; translated = text3.Substring(length, text3.Length - length).TrimStart() + " 号存档"; return true; } private static bool TryTranslateDeleteFilePrompt(string text, out string translated) { translated = text; if (TryExtractDeleteFileAlias(text, out string alias)) { translated = "要删除 " + alias + " 吗"; return true; } if (!TryExtractDeleteFileNumber(text, out string fileNumber)) { return false; } translated = "要删除 " + fileNumber + " 号存档吗"; return true; } private static bool TryTranslateChatCommandCompatibilityText(string text, out string translated) { translated = text; string text2 = text.Trim(); if (!TryTranslateCommandItemLabel(text2, "Item to sell:", "要售卖的物品", out translated) && !TryTranslateCommandItemLabel(text2, "Items with priority:", "优先售卖物品", out translated) && !TryTranslateNoItemsCalled(text2, out translated) && !TryTranslateThereIsScrapOnShip(text2, out translated) && !TryTranslateItemsCouldNotBeSorted(text2, out translated) && !TryTranslateItemsOfTypePosition(text2, out translated) && !TryTranslateMovingAllItemsOfType(text2, out translated) && !TryTranslateSellingItemsSummary(text2, out translated) && !TryTranslateSoldRequestedSummary(text2, out translated) && !TryTranslateCommandValueLabel(text2, "Overtime:", "加班奖励", out translated) && !TryTranslateCommandValueLabel(text2, "Money after takeoff:", "起飞后资金", out translated)) { return TryTranslateMoneyInTerminal(text2, out translated); } return true; } private static bool LooksLikeChatCommandCompatibilityText(string text) { string text2 = text.Trim(); if (!text2.StartsWith("Item to sell:", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("Items with priority:", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("No items called \"", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("There is ", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("Items of type ", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("Moving all items of type ", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("Selling ", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("Overtime:", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("Money after takeoff:", StringComparison.OrdinalIgnoreCase) && !text2.StartsWith("Money in terminal", StringComparison.OrdinalIgnoreCase) && text2.IndexOf(" sold / ", StringComparison.OrdinalIgnoreCase) <= 0) { return text2.EndsWith(" items couldn't be sorted", StringComparison.OrdinalIgnoreCase); } return true; } private static bool TryTranslateCommandItemLabel(string text, string prefix, string localizedLabel, out string translated) { translated = text; if (!text.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { return false; } int length = prefix.Length; string text2 = text.Substring(length, text.Length - length).Trim(); if (text2.Length == 0) { return false; } translated = localizedLabel + ":" + LocalizeCommandItemName(text2); return true; } private static bool TryTranslateNoItemsCalled(string text, out string translated) { translated = text; if (!text.StartsWith("No items called \"", StringComparison.OrdinalIgnoreCase)) { return false; } int num = text.IndexOf('"', "No items called \"".Length); if (num <= "No items called \"".Length) { return false; } string text2 = text.Substring("No items called \"".Length, num - "No items called \"".Length).Trim(); int num2 = num + 1; string text3 = text.Substring(num2, text.Length - num2).Trim(); if (text2.Length == 0 || (text3.Length > 0 && !string.Equals(text3, "were detected", StringComparison.OrdinalIgnoreCase))) { return false; } translated = "未找到名为 \"" + LocalizeCommandItemName(text2) + "\" 的物品"; return true; } private static bool TryTranslateThereIsScrapOnShip(string text, out string translated) { translated = text; if (!text.StartsWith("There is ", StringComparison.OrdinalIgnoreCase) || !text.EndsWith(" scrap on ship", StringComparison.OrdinalIgnoreCase)) { return false; } string text2 = text.Substring("There is ".Length, text.Length - "There is ".Length - " scrap on ship".Length).Trim(); if (!LooksLikeSimpleNumber(text2)) { return false; } translated = "飞船上有 " + text2 + " 件废料"; return true; } private static bool TryTranslateItemsCouldNotBeSorted(string text, out string translated) { translated = text; if (!text.EndsWith(" items couldn't be sorted", StringComparison.OrdinalIgnoreCase)) { return false; } int length = " items couldn't be sorted".Length; string text2 = text.Substring(0, text.Length - length).Trim(); if (!LooksLikeSimpleNumber(text2)) { return false; } translated = text2 + " 件物品无法整理"; return true; } private static bool TryTranslateItemsOfTypePosition(string text, out string translated) { translated = text; if (!text.StartsWith("Items of type ", StringComparison.OrdinalIgnoreCase)) { return false; } int num = text.IndexOf(" will be put on position ", StringComparison.OrdinalIgnoreCase); if (num <= "Items of type ".Length) { return false; } string text2 = text.Substring("Items of type ".Length, num - "Items of type ".Length).Trim(); string text3 = text; int num2 = num + " will be put on position ".Length; string text4 = text3.Substring(num2, text3.Length - num2).Trim(); bool flag = false; if (text4.EndsWith(" for this game", StringComparison.OrdinalIgnoreCase)) { flag = true; text3 = text4; num2 = " for this game".Length; text4 = text3.Substring(0, text3.Length - num2).TrimEnd(); } if (text2.Length == 0 || text4.Length == 0) { return false; } string text5 = LocalizeCommandItemName(text2); translated = (flag ? ("本局内,类型为 " + text5 + " 的物品将放到 " + text4 + " 位置") : ("类型为 " + text5 + " 的物品将放到 " + text4 + " 位置")); return true; } private static bool TryTranslateMovingAllItemsOfType(string text, out string translated) { translated = text; if (!text.StartsWith("Moving all items of type ", StringComparison.OrdinalIgnoreCase)) { return false; } int num = text.IndexOf(" to position ", StringComparison.OrdinalIgnoreCase); if (num <= "Moving all items of type ".Length) { return false; } string text2 = text.Substring("Moving all items of type ".Length, num - "Moving all items of type ".Length).Trim(); int num2 = num + " to position ".Length; string text3 = text.Substring(num2, text.Length - num2).Trim(); if (text2.Length == 0 || text3.Length == 0) { return false; } translated = "正在将所有 " + LocalizeCommandItemName(text2) + " 类物品移动到 " + text3 + " 位置"; return true; } private static bool TryTranslateSellingItemsSummary(string text, out string translated) { translated = text; if (!text.StartsWith("Selling ", StringComparison.OrdinalIgnoreCase)) { return false; } string markerText; int num = FindFirstMarker(text, "Selling ".Length, SellingNamedMarkers, out markerText); string text4; int num4; if (num > "Selling ".Length) { string text2 = text.Substring("Selling ".Length, num - "Selling ".Length).Trim(); int num2 = num + markerText.Length; int num3 = text.IndexOf("\" with a total value of ", num2, StringComparison.OrdinalIgnoreCase); if (!LooksLikeSimpleNumber(text2) || num3 <= num2) { return false; } string text3 = text.Substring(num2, num3 - num2).Trim(); text4 = text; num4 = num3 + "\" with a total value of ".Length; string text5 = text4.Substring(num4, text4.Length - num4).Trim(); if (text3.Length == 0 || text5.Length == 0) { return false; } translated = "正在出售 " + text2 + " 件名为 \"" + LocalizeCommandItemName(text3) + "\" 的物品,总价值 " + LocalizeCommandResultValueTail(text5); return true; } string markerText2; int num5 = FindFirstMarker(text, "Selling ".Length, SellingCountMarkers, out markerText2); if (num5 <= "Selling ".Length) { return false; } string text6 = text.Substring("Selling ".Length, num5 - "Selling ".Length).Trim(); text4 = text; num4 = num5 + markerText2.Length; string text7 = text4.Substring(num4, text4.Length - num4).Trim(); if (!LooksLikeSimpleNumber(text6) || text7.Length == 0) { return false; } translated = "正在出售 " + text6 + " 件物品,总价值 " + LocalizeCommandResultValueTail(text7); return true; } private static int FindFirstMarker(string text, int startIndex, string[] markers, out string markerText) { int num = -1; markerText = string.Empty; foreach (string text2 in markers) { int num2 = text.IndexOf(text2, startIndex, StringComparison.OrdinalIgnoreCase); if (num2 >= 0 && (num < 0 || num2 < num)) { num = num2; markerText = text2; } } return num; } private static string LocalizeCommandResultValueTail(string value) { string text = value.Replace(" overtime", " 加班奖励", StringComparison.OrdinalIgnoreCase).Replace(", sold every unblacklisted item", ",已出售所有未列入黑名单的物品", StringComparison.OrdinalIgnoreCase); if (!TryTranslateSoldRequestedResultTail(text, out string translated)) { return text; } return translated; } private static bool TryTranslateSoldRequestedSummary(string text, out string translated) { return TryTranslateSoldRequestedSummaryCore(text, out translated); } private static bool TryTranslateSoldRequestedResultTail(string text, out string translated) { translated = text; if (TryTranslateSoldRequestedSummaryCore(text, out translated)) { return true; } int num = text.LastIndexOf(':'); if (num <= 0 || num >= text.Length - 1) { return false; } string text2 = text.Substring(0, num).TrimEnd(); int num2 = num + 1; string text3 = text.Substring(num2, text.Length - num2).TrimStart(); if (text2.Length == 0 || !TryTranslateSoldRequestedSummaryCore(text3, out string translated2)) { return false; } translated = text2 + ":" + translated2; return true; } private static bool TryTranslateSoldRequestedSummaryCore(string text, out string translated) { translated = text; text = text.Trim(); int num = text.IndexOf(" sold / ", StringComparison.OrdinalIgnoreCase); if (num <= 0 || !text.EndsWith(" requested", StringComparison.OrdinalIgnoreCase)) { return false; } string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + " sold / ".Length, text.Length - num - " sold / ".Length - " requested".Length).Trim(); if (!LooksLikeSimpleNumber(text2) || !LooksLikeSimpleNumber(text3)) { return false; } translated = "已出售 " + text2 + " / 目标 " + text3; return true; } private static bool TryTranslateCommandValueLabel(string text, string prefix, string localizedLabel, out string translated) { translated = text; if (!text.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { return false; } int length = prefix.Length; string text2 = text.Substring(length, text.Length - length).Trim(); if (text2.Length == 0) { return false; } translated = localizedLabel + ":" + text2; return true; } private static bool TryTranslateMoneyInTerminal(string text, out string translated) { translated = text; if (!text.StartsWith("Money in terminal", StringComparison.OrdinalIgnoreCase)) { return false; } int num = FindTopLevelColon(text); if (num <= 0 || num >= text.Length - 1) { return false; } string text2 = text.Substring(0, num); int num2 = num + 1; string text3 = text.Substring(num2, text.Length - num2).Trim(); if (text3.Length == 0) { return false; } translated = ((text2.IndexOf("desk", StringComparison.OrdinalIgnoreCase) >= 0) ? ("终端资金(含柜台):" + text3) : ("终端资金:" + text3)); return true; } private static string LocalizeCommandItemName(string item) { string text = item.Trim(); if (text.Length == 0) { return text; } string text2 = TranslationService.BuildTerminalLocalizedItemName(text); if (!string.IsNullOrWhiteSpace(text2)) { return text2; } return text; } private static bool LooksLikeSimpleNumber(string value) { if (value.Length == 0) { return false; } for (int i = 0; i < value.Length; i++) { if (!char.IsDigit(value[i])) { return false; } } return true; } private static bool TryTranslateDiscountAlertNoDiscountText(string text, out string translated) { translated = text; int num = text.Length - text.TrimStart().Length; int num2 = text.Length - text.TrimEnd().Length; int num3 = text.Length - num - num2; if (num3 <= 0) { return false; } string text2 = ((num > 0) ? text.Substring(0, num) : string.Empty); string text3; if (num2 <= 0) { text3 = string.Empty; } else { int num4 = num2; int length = text.Length; int num5 = length - num4; text3 = text.Substring(num5, length - num5); } string text4 = text3; string text5 = text.Substring(num, num3); string text6 = string.Empty; string text7 = string.Empty; string prefix; string inner; string suffix; while (TryExtractOuterSimpleRichTextEnvelope(text5.Trim(), out prefix, out inner, out suffix)) { text6 += prefix; text7 = suffix + text7; text5 = inner; } if (!LooksLikeDiscountAlertNoDiscountCore(text5)) { return false; } translated = text2 + text6 + "暂无折扣\n明天再来查看" + text7 + text4; return true; } private static bool LooksLikeDiscountAlertNoDiscountText(string text) { return LooksLikeDiscountAlertNoDiscountCore(StripLeadingSimpleRichTextTags(StripOuterSimpleRichTextEnvelope(text))); } private static bool LooksLikeDiscountAlertNoDiscountCore(string text) { return NormalizeAsciiWhitespace(text.Replace("\\n", "\n", StringComparison.Ordinal).Replace("\r\n", "\n", StringComparison.Ordinal).Replace('\r', '\n')).Equals("None :( Check back tomorrow!", StringComparison.OrdinalIgnoreCase); } internal static bool TryTranslateBetterSavesDeleteFilePrompt(string? source, int fileToDelete, out string translated) { translated = source ?? string.Empty; if (fileToDelete <= 0 || string.IsNullOrWhiteSpace(source)) { return false; } if (TryExtractDeleteFileAlias(source, out string alias)) { translated = "要删除 " + alias + " 吗"; return true; } if (!TryExtractDeleteFileNumber(source, out string _) && !LooksLikeLocalizedDeleteFileNumberPrompt(source)) { return false; } translated = "要删除 " + fileToDelete.ToString(CultureInfo.InvariantCulture) + " 号存档吗"; return true; } private static bool TryTranslateExactUiText(string text, out string translated) { translated = text; if (ExactEntries.TryGetValue(text, out translated)) { return true; } if (text.EndsWith(":", StringComparison.Ordinal)) { string key = text.Substring(0, text.Length - 1).TrimEnd(); if (ExactEntries.TryGetValue(key, out string value)) { translated = value + ":"; return true; } } if (string.Equals(text, "FRIENDS ONLY means only friends or invited people can join.", StringComparison.OrdinalIgnoreCase)) { translated = "仅限好友:好友或受邀玩家可以加入"; return true; } if (string.Equals(text, "INVITE ONLY means you must send invites through Steam for players to join.", StringComparison.OrdinalIgnoreCase)) { translated = "仅限邀请:必须通过 Steam 邀请玩家加入"; return true; } return false; } private static bool TryTranslateBracketedCommand(string text, out string translated) { translated = text; if (text.Length >= 3 && text[0] == '[') { if (text[text.Length - 1] == ']') { string key = text.Substring(1, text.Length - 2).Trim(); if (!ExactEntries.TryGetValue(key, out string value)) { return false; } translated = "[ " + value + " ]"; return true; } } return false; } private static bool TryTranslateDecoratedExactUiText(string text, out string translated) { translated = text; if (!TryGetDecoratedKnownExternalUiTokenSpan(text, out var start, out var length)) { return false; } string key = text.Substring(start, length).Trim(); if (!ExactEntries.TryGetValue(key, out string value)) { return false; } string text2 = text.Substring(0, start); string text3 = value; int num = start + length; translated = text2 + text3 + text.Substring(num, text.Length - num); return true; } private static bool TryTranslateControlTip(string text, out string translated) { translated = text; int num = FindTopLevelColon(text); if (num <= 0 || num >= text.Length - 1) { return false; } string key = text.Substring(0, num).Trim(); if (!ExactEntries.TryGetValue(key, out string value)) { return false; } string payload = text.Substring(num + 1).Trim(); if (!LooksLikeControlPayload(payload)) { return false; } translated = value + ":" + NormalizeControlPayload(payload); return true; } private static bool TryTranslateLabelValue(string text, out string translated) { translated = text; int num = FindTopLevelColon(text); if (num <= 0 || num >= text.Length - 1) { return false; } string key = text.Substring(0, num).Trim(); if (!ExactEntries.TryGetValue(key, out string value)) { return false; } string text2 = text.Substring(num + 1).Trim(); if (text2.Length == 0 || LooksLikeNonUiName(text2)) { return false; } translated = value + ":" + text2; return true; } private static bool TryTranslateDiscountLine(string text, out string translated) { translated = text; string text2 = text.Trim(); string text3 = string.Empty; string text4; if (text2.StartsWith("*", StringComparison.Ordinal)) { text3 = "* "; text4 = text2; text2 = text4.Substring(1, text4.Length - 1).TrimStart(); } int num = text2.LastIndexOf('('); if (num <= 0 || !text2.EndsWith(")", StringComparison.Ordinal)) { return false; } string value = text2.Substring(0, num).TrimEnd(); string text5 = text2.Substring(num + 1, text2.Length - num - 2).Trim(); bool flag = text5.EndsWith("off!", StringComparison.OrdinalIgnoreCase); bool flag2 = text5.EndsWith("up!", StringComparison.OrdinalIgnoreCase); if ((!flag && !flag2) || !TryTranslateItemAndPrice(value, out string translated2)) { return false; } int num2 = (flag ? "off!".Length : "up!".Length); text4 = text5; int num3 = num2; string text6 = text4.Substring(0, text4.Length - num3).Trim(); if (text6.Length == 0 || !ContainsDigit(text6)) { return false; } translated = text3 + translated2 + " (" + text6 + (flag ? " 折扣)" : " 涨价)"); return true; } private static bool TryTranslateItemAndPrice(string value, out string translated) { translated = value; int num = value.LastIndexOf('$'); if (num <= 0) { return false; } string text = value.Substring(0, num).TrimEnd(); int num2 = num; string text2 = value.Substring(num2, value.Length - num2).TrimStart(); if (!LooksLikePrice(text2)) { return false; } string text3 = TranslationService.BuildTerminalLocalizedItemName(text); if (text3.Length == 0 || string.Equals(text3, text, StringComparison.Ordinal)) { return false; } translated = text3 + " " + text2; return true; } private static string NormalizeControlPayload(string payload) { string text = payload.Trim(); if (text.StartsWith("Hold ", StringComparison.OrdinalIgnoreCase)) { string text2 = text; int length = "Hold ".Length; return "按住 " + NormalizeKeyTokens(text2.Substring(length, text2.Length - length).Trim()); } text = NormalizeKeyTokens(text); if (text.IndexOf("(Hold)", StringComparison.OrdinalIgnoreCase) >= 0) { text = text.Replace("(Hold)", string.Empty, StringComparison.OrdinalIgnoreCase).TrimEnd() + "(长按)"; } return text; } private static string NormalizeKeyTokens(string value) { string text = value; foreach (KeyValuePair keyTokenEntry in KeyTokenEntries) { text = text.Replace("[" + keyTokenEntry.Key + "]", "[" + keyTokenEntry.Value + "]", StringComparison.OrdinalIgnoreCase); } return text; } private static bool LooksLikeControlPayload(string payload) { if (payload.Length < 3) { return false; } if (payload.StartsWith("Hold [", StringComparison.OrdinalIgnoreCase)) { return payload.EndsWith("]", StringComparison.Ordinal); } if (payload.IndexOf('[') >= 0) { return payload.IndexOf(']') > payload.IndexOf('['); } return false; } private static bool LooksLikeKnownExternalUiLabel(string text) { if (text.EndsWith(":", StringComparison.Ordinal)) { string key = text.Substring(0, text.Length - 1).TrimEnd(); return ExactEntries.ContainsKey(key); } int num = FindTopLevelColon(text); if (num <= 0 || num > 64) { return false; } string key2 = text.Substring(0, num).Trim(); return ExactEntries.ContainsKey(key2); } private static string StripMenuSelectionPrefix(string text) { string text2 = text.TrimStart(); if (!text2.StartsWith(">", StringComparison.Ordinal)) { return text; } string text3 = text2; return text3.Substring(1, text3.Length - 1).TrimStart(); } private static bool LooksLikeBracketedKnownExternalUiToken(string text) { if (text.Length >= 3 && text[0] == '[') { if (text[text.Length - 1] == ']') { string key = text.Substring(1, text.Length - 2).Trim(); return ExactEntries.ContainsKey(key); } } return false; } private static bool LooksLikeDecoratedKnownExternalUiToken(string text) { if (!TryGetDecoratedKnownExternalUiTokenSpan(text, out var start, out var length)) { return false; } string key = text.Substring(start, length).Trim(); return ExactEntries.ContainsKey(key); } private static bool TryGetDecoratedKnownExternalUiTokenSpan(string text, out int start, out int length) { start = 0; length = 0; if (text.Length < 5) { return false; } int i = 0; int num; for (num = text.Length - 1; i <= num && IsDecoratedTitleFrameChar(text[i]); i++) { } while (num >= i && IsDecoratedTitleFrameChar(text[num])) { num--; } if (i == 0 || num == text.Length - 1 || num < i) { return false; } start = i; length = num - i + 1; return true; } private static bool IsDecoratedTitleFrameChar(char ch) { bool flag = char.IsWhiteSpace(ch); if (!flag) { bool flag2 = ((ch == '-' || ch == '=' || ch == '_') ? true : false); flag = flag2; } return flag; } private static bool LooksLikeSaveFileLabel(string text) { if (!text.StartsWith("File ", StringComparison.OrdinalIgnoreCase)) { return false; } int length = "File ".Length; string text2 = text.Substring(length, text.Length - length).TrimStart(); if (text2.Length == 0) { return false; } for (int i = 0; i < text2.Length; i++) { if (!char.IsDigit(text2[i])) { return false; } } return true; } private static bool TryTranslateAdvancedFeaturesPlayerLabel(string text, out string translated) { translated = text; if (!LooksLikeAdvancedFeaturesPlayerLabel(text)) { return false; } string text2 = text.Trim(); int length = "Player #".Length; translated = "玩家 #" + text2.Substring(length, text2.Length - length); return true; } private static bool LooksLikeAdvancedFeaturesPlayerLabel(string text) { string text2 = text.Trim(); if (!text2.StartsWith("Player #", StringComparison.OrdinalIgnoreCase)) { return false; } string text3 = text2; int length = "Player #".Length; string text4 = text3.Substring(length, text3.Length - length); if (text4.Length == 0) { return false; } for (int i = 0; i < text4.Length; i++) { if (!char.IsDigit(text4[i])) { return false; } } return true; } private static bool LooksLikeDeleteFilePrompt(string text) { if (!TryExtractDeleteFileNumber(text, out string fileNumber)) { return TryExtractDeleteFileAlias(text, out fileNumber); } return true; } private static bool TryExtractDeleteFileNumber(string text, out string fileNumber) { fileNumber = string.Empty; string text2 = NormalizeAsciiWhitespace(StripLeadingSimpleRichTextTags(StripOuterSimpleRichTextEnvelope(text))); if (!text2.StartsWith("Do you want to delete File ", StringComparison.OrdinalIgnoreCase) || !text2.EndsWith("?", StringComparison.Ordinal)) { return false; } string text3 = text2.Substring("Do you want to delete File ".Length, text2.Length - "Do you want to delete File ".Length - 1).Trim(); if (text3.Length == 0) { return false; } for (int i = 0; i < text3.Length; i++) { if (!char.IsDigit(text3[i])) { return false; } } fileNumber = text3; return true; } private static bool TryExtractDeleteFileAlias(string text, out string alias) { alias = string.Empty; string text2 = NormalizeAsciiWhitespace(StripLeadingSimpleRichTextTags(StripOuterSimpleRichTextEnvelope(text))); if (!text2.StartsWith("Do you want to delete file (", StringComparison.OrdinalIgnoreCase) || !text2.EndsWith(")?", StringComparison.Ordinal)) { return false; } alias = text2.Substring("Do you want to delete file (".Length, text2.Length - "Do you want to delete file (".Length - ")?".Length).Trim(); return alias.Length > 0; } private static bool LooksLikeLocalizedDeleteFileNumberPrompt(string text) { string text2 = NormalizeAsciiWhitespace(StripLeadingSimpleRichTextTags(StripOuterSimpleRichTextEnvelope(text))); if (!text2.StartsWith("要删除 ", StringComparison.Ordinal) || !text2.EndsWith(" 号存档吗", StringComparison.Ordinal)) { return false; } string text3 = text2.Substring("要删除 ".Length, text2.Length - "要删除 ".Length - " 号存档吗".Length); if (text3.Length == 0) { return false; } for (int i = 0; i < text3.Length; i++) { if (!char.IsDigit(text3[i])) { return false; } } return true; } private static bool LooksLikeNonUiName(string text) { if (string.IsNullOrWhiteSpace(text)) { return true; } string text2 = text.Trim(); if (text2.IndexOf(' ') >= 0) { return false; } if (text2.IndexOf('-') >= 0 && ContainsDigit(text2)) { return true; } return LooksLikeCamelCaseIdentifier(text2); } private static bool LooksLikeCamelCaseIdentifier(string text) { if (text.Length < 6 || !IsAsciiLetter(text[0])) { return false; } bool flag = false; bool result = false; foreach (char c in text) { if (!IsAsciiLetter(c) && !char.IsDigit(c)) { return false; } if (char.IsLower(c)) { flag = true; } else if (flag && char.IsUpper(c)) { result = true; } } return result; } private static int FindTopLevelColon(string value) { bool flag = false; for (int i = 0; i < value.Length; i++) { char c = value[i]; if (c == '[') { flag = true; } else if (c == ']' && flag) { flag = false; } else if (!flag && c == ':') { return i; } } return -1; } private static string StripOuterSimpleRichTextEnvelope(string value) { string text = value.Trim(); for (int i = 0; i < 3; i++) { if (text.Length < 7) { break; } if (text[0] != '<') { break; } int num = text.IndexOf('>'); if (num <= 1 || num > 24) { break; } int num2 = 0; for (int j = 1; j < num; j++) { char c = text[j]; if (char.IsWhiteSpace(c) || c == '=') { break; } if (!IsAsciiLetter(c) && !char.IsDigit(c)) { num2 = 0; break; } num2++; } if (num2 == 0) { break; } string text2 = text.Substring(1, num2); string text3 = ""; if (!text.EndsWith(text3, StringComparison.OrdinalIgnoreCase)) { break; } text = text.Substring(num + 1, text.Length - num - 1 - text3.Length).Trim(); } return text; } private static bool TryExtractOuterSimpleRichTextEnvelope(string value, out string prefix, out string inner, out string suffix) { prefix = string.Empty; inner = string.Empty; suffix = string.Empty; string text = value.Trim(); if (!TryReadSimpleOpeningRichTextTag(text, out var tagEnd) || !TryReadSimpleOpeningTagName(text, tagEnd, out string tagName)) { return false; } string text2 = ""; if (!text.EndsWith(text2, StringComparison.OrdinalIgnoreCase)) { return false; } prefix = text.Substring(0, tagEnd + 1); int length = text2.Length; int length2 = text.Length; int num = length2 - length; suffix = text.Substring(num, length2 - num); inner = text.Substring(tagEnd + 1, text.Length - tagEnd - 1 - text2.Length); return true; } private static string StripLeadingSimpleRichTextTags(string value) { string text = value.Trim(); int tagEnd; while (TryReadSimpleOpeningRichTextTag(text, out tagEnd)) { string text2 = text; int num = tagEnd + 1; text = text2.Substring(num, text2.Length - num).TrimStart(); } return text; } private static string ExtractLeadingSimpleRichTextPrefix(ref string value) { string text = value.Trim(); StringBuilder stringBuilder = null; int tagEnd; while (TryReadSimpleOpeningRichTextTag(text, out tagEnd)) { if (stringBuilder == null) { stringBuilder = new StringBuilder(); } stringBuilder.Append(text, 0, tagEnd + 1); string text2 = text; int num = tagEnd + 1; text = text2.Substring(num, text2.Length - num).TrimStart(); } value = text; return stringBuilder?.ToString() ?? string.Empty; } private static bool TryReadSimpleOpeningRichTextTag(string value, out int tagEnd) { tagEnd = -1; if (value.Length < 4 || value[0] != '<' || value[1] == '/') { return false; } int num = value.IndexOf('>'); if (num <= 1 || num > 40) { return false; } int num2 = 0; for (int i = 1; i < num; i++) { char c = value[i]; if (char.IsWhiteSpace(c) || c == '=' || c == '#') { break; } if (!IsAsciiLetter(c) && !char.IsDigit(c)) { return false; } num2++; } if (num2 == 0) { return false; } tagEnd = num; return true; } private static bool TryReadSimpleOpeningTagName(string value, int tagEnd, out string tagName) { tagName = string.Empty; if (tagEnd <= 1 || tagEnd >= value.Length) { return false; } int num = 0; for (int i = 1; i < tagEnd; i++) { char c = value[i]; if (char.IsWhiteSpace(c) || c == '=' || c == '#') { break; } if (!IsAsciiLetter(c) && !char.IsDigit(c)) { return false; } num++; } if (num == 0) { return false; } tagName = value.Substring(1, num); return true; } private static bool LooksLikePrice(string value) { if (value.Length < 2 || value[0] != '$') { return false; } for (int i = 1; i < value.Length; i++) { if (!char.IsDigit(value[i]) && value[i] != ',' && value[i] != '.') { return false; } } return true; } private static bool ContainsDigit(string value) { for (int i = 0; i < value.Length; i++) { if (char.IsDigit(value[i])) { return true; } } return false; } private static string NormalizeAsciiWhitespace(string value) { StringBuilder stringBuilder = new StringBuilder(value.Length); bool flag = false; foreach (char c in value) { if (char.IsWhiteSpace(c)) { flag = stringBuilder.Length > 0; continue; } if (flag) { stringBuilder.Append(' '); flag = false; } stringBuilder.Append(c); } return stringBuilder.ToString(); } private static bool ContainsLineBreak(string value) { if (value.IndexOf('\n') < 0) { return value.IndexOf('\r') >= 0; } return true; } private static bool ContainsCjk(string value) { for (int i = 0; i < value.Length; i++) { if (IsCjk(value[i])) { return true; } } return false; } private static bool IsCjk(char ch) { if (ch >= '㐀') { return ch <= '\u9fff'; } return false; } private static bool IsAsciiLetter(char ch) { if (ch < 'A' || ch > 'Z') { if (ch >= 'a') { return ch <= 'z'; } return false; } return true; } } internal static class ExternalEnglishCompatibilityUiService { private readonly struct CachedProtectedInput { public int ParentId { get; } public bool Value { get; } public CachedProtectedInput(int parentId, bool value) { ParentId = parentId; Value = value; } } private readonly struct CachedComponentTranslation { public string Source { get; } public string? Translated { get; } public CachedComponentTranslation(string source, string? translated) { Source = source; Translated = translated; } } private const int ComponentCacheLimit = 16384; private static readonly List TmpTextBuffer = new List(128); private static readonly List UiTextBuffer = new List(128); private static readonly List TextMeshBuffer = new List(32); private static readonly Dictionary TmpProtectedInputCache = new Dictionary(16384); private static readonly Dictionary UiProtectedInputCache = new Dictionary(16384); private static readonly Dictionary TmpTranslationCache = new Dictionary(16384); private static readonly Dictionary UiTranslationCache = new Dictionary(16384); private static readonly Dictionary TextMeshTranslationCache = new Dictionary(16384); private static readonly HashSet RepairedTmpInputPlaceholderCache = new HashSet(16384); public static void ClearRuntimeCaches() { TmpTextBuffer.Clear(); UiTextBuffer.Clear(); TextMeshBuffer.Clear(); TmpProtectedInputCache.Clear(); UiProtectedInputCache.Clear(); TmpTranslationCache.Clear(); UiTranslationCache.Clear(); TextMeshTranslationCache.Clear(); RepairedTmpInputPlaceholderCache.Clear(); } public static int TranslateRoot(GameObject? root, bool includeInactive, string reason) { if ((Object)(object)root == (Object)null) { return 0; } int num = 0; try { root.GetComponentsInChildren(includeInactive, TmpTextBuffer); foreach (TMP_Text item in TmpTextBuffer) { if (TranslateTmpText(item, reason)) { num++; } } root.GetComponentsInChildren(includeInactive, UiTextBuffer); foreach (Text item2 in UiTextBuffer) { if (TranslateUiText(item2)) { num++; } } root.GetComponentsInChildren(includeInactive, TextMeshBuffer); foreach (TextMesh item3 in TextMeshBuffer) { if (TranslateTextMesh(item3)) { num++; } } return num; } finally { TmpTextBuffer.Clear(); UiTextBuffer.Clear(); TextMeshBuffer.Clear(); } } public static bool TranslateTmpText(TMP_Text? text, string reason) { if ((Object)(object)text == (Object)null) { return false; } string text2 = text.text; if (!MightNeedExternalTextTranslation(text2) || IsProtectedInputTextComponent(text)) { return false; } if (TryGetCachedTranslation((Component)(object)text, TmpTranslationCache, text2, out string translated)) { return ApplyTranslatedTmpText(text, text2, translated, reason); } if (!ExternalEnglishCompatibilityService.TryTranslateFast(text2, out translated) || string.Equals(text2, translated, StringComparison.Ordinal)) { CacheTranslation((Component)(object)text, TmpTranslationCache, text2, null); return false; } CacheTranslation((Component)(object)text, TmpTranslationCache, text2, translated); return ApplyTranslatedTmpText(text, text2, translated, reason); } public static bool TranslateTmpTextKnownNonInput(TMP_Text? text, string reason) { if ((Object)(object)text == (Object)null) { return false; } string text2 = text.text; if (!MightNeedExternalTextTranslation(text2)) { return false; } if (TryGetCachedTranslation((Component)(object)text, TmpTranslationCache, text2, out string translated)) { return ApplyTranslatedTmpText(text, text2, translated, reason); } if (!ExternalEnglishCompatibilityService.TryTranslateFast(text2, out translated) || string.Equals(text2, translated, StringComparison.Ordinal)) { CacheTranslation((Component)(object)text, TmpTranslationCache, text2, null); return false; } CacheTranslation((Component)(object)text, TmpTranslationCache, text2, translated); return ApplyTranslatedTmpText(text, text2, translated, reason); } public static bool TranslateTmpInputPlaceholder(TMP_InputField? input, string reason) { Graphic obj = ((input != null) ? input.placeholder : null); TMP_Text val = (TMP_Text)(object)((obj is TMP_Text) ? obj : null); if (val == null) { return false; } return TranslateTmpInputPlaceholder(val, reason); } private static bool TranslateTmpInputPlaceholder(TMP_Text placeholder, string reason) { string text = placeholder.text; if (!MightNeedExternalTextTranslation(text)) { return false; } if (TryGetCachedTranslation((Component)(object)placeholder, TmpTranslationCache, text, out string translated)) { return ApplyTranslatedTmpText(placeholder, text, translated, reason); } if ((!TranslationService.TryTranslateFastExact(text, out translated) && !ExternalEnglishCompatibilityService.TryTranslateFast(text, out translated)) || string.Equals(text, translated, StringComparison.Ordinal)) { CacheTranslation((Component)(object)placeholder, TmpTranslationCache, text, null); return false; } CacheTranslation((Component)(object)placeholder, TmpTranslationCache, text, translated); return ApplyTranslatedTmpText(placeholder, text, translated, reason); } private static bool TranslateUiText(Text? text) { if ((Object)(object)text == (Object)null) { return false; } string text2 = text.text; if (!MightNeedExternalTextTranslation(text2) || IsProtectedInputTextComponent(text)) { return false; } if (TryGetCachedTranslation((Component)(object)text, UiTranslationCache, text2, out string translated)) { return ApplyTranslatedUiText(text, text2, translated); } if (!ExternalEnglishCompatibilityService.TryTranslateFast(text2, out translated) || string.Equals(text2, translated, StringComparison.Ordinal)) { CacheTranslation((Component)(object)text, UiTranslationCache, text2, null); return false; } CacheTranslation((Component)(object)text, UiTranslationCache, text2, translated); return ApplyTranslatedUiText(text, text2, translated); } private static bool TranslateTextMesh(TextMesh? text) { if ((Object)(object)text == (Object)null) { return false; } string text2 = text.text; if (!MightNeedExternalTextTranslation(text2)) { return false; } if (TryGetCachedTranslation((Component)(object)text, TextMeshTranslationCache, text2, out string translated)) { return ApplyTranslatedTextMesh(text, text2, translated); } if (!ExternalEnglishCompatibilityService.TryTranslateFast(text2, out translated) || string.Equals(text2, translated, StringComparison.Ordinal)) { CacheTranslation((Component)(object)text, TextMeshTranslationCache, text2, null); return false; } CacheTranslation((Component)(object)text, TextMeshTranslationCache, text2, translated); return ApplyTranslatedTextMesh(text, text2, translated); } private static bool IsProtectedInputTextComponent(TMP_Text text) { int parentInstanceId = GetParentInstanceId((Component)(object)text); if (TmpProtectedInputCache.TryGetValue(((Object)text).GetInstanceID(), out var value) && value.ParentId == parentInstanceId) { return value.Value; } TMP_InputField componentInParent = ((Component)text).GetComponentInParent(true); bool flag = (Object)(object)componentInParent != (Object)null && componentInParent.textComponent == text; CacheProtectedInput((Component)(object)text, TmpProtectedInputCache, parentInstanceId, flag); return flag; } private static bool IsProtectedInputTextComponent(Text text) { int parentInstanceId = GetParentInstanceId((Component)(object)text); if (UiProtectedInputCache.TryGetValue(((Object)text).GetInstanceID(), out var value) && value.ParentId == parentInstanceId) { return value.Value; } InputField componentInParent = ((Component)text).GetComponentInParent(true); bool flag = (Object)(object)componentInParent != (Object)null && componentInParent.textComponent == text; CacheProtectedInput((Component)(object)text, UiProtectedInputCache, parentInstanceId, flag); return flag; } private static bool ApplyTranslatedTmpText(TMP_Text text, string source, string translated, string reason) { if (string.Equals(source, translated, StringComparison.Ordinal)) { return false; } TextPatches.MarkTmpHookTranslation(text, source, translated); text.text = translated; FontFallbackService.ApplyFallback(text, translated); RepairTranslatedTmpInputPlaceholder(text, translated); Plugin.ReportTranslationHit(); return true; } private static void RepairTranslatedTmpInputPlaceholder(TMP_Text text, string translated) { if (!ContainsCjk(translated)) { return; } int instanceID = ((Object)text).GetInstanceID(); if (RepairedTmpInputPlaceholderCache.Contains(instanceID)) { return; } TMP_InputField componentInParent = ((Component)text).GetComponentInParent(true); if (!((Object)(object)componentInParent == (Object)null) && componentInParent.textComponent != text && componentInParent.placeholder == text && RepairedTmpInputPlaceholderCache.Count < RuntimePerformanceSettings.ComponentTextCacheLimit) { float fontSize = text.fontSize; if (!(fontSize <= 0f)) { text.enableAutoSizing = true; text.fontSizeMax = fontSize; text.fontSizeMin = Math.Min((text.fontSizeMin > 0f) ? text.fontSizeMin : (fontSize * 0.65f), fontSize * 0.65f); text.enableWordWrapping = false; text.overflowMode = (TextOverflowModes)0; RepairedTmpInputPlaceholderCache.Add(instanceID); } } } private static bool ApplyTranslatedUiText(Text text, string source, string translated) { if (string.Equals(source, translated, StringComparison.Ordinal)) { return false; } text.text = translated; Plugin.ReportTranslationHit(); return true; } private static bool ApplyTranslatedTextMesh(TextMesh text, string source, string translated) { if (string.Equals(source, translated, StringComparison.Ordinal)) { return false; } text.text = translated; Plugin.ReportTranslationHit(); return true; } private static bool TryGetCachedTranslation(Component text, Dictionary cache, string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrEmpty(source) || !cache.TryGetValue(((Object)text).GetInstanceID(), out var value)) { return false; } if (string.Equals(value.Source, source, StringComparison.Ordinal)) { if (value.Translated == null) { return true; } translated = value.Translated; return true; } if (value.Translated != null && string.Equals(value.Translated, source, StringComparison.Ordinal)) { translated = source; return true; } return false; } private static void CacheTranslation(Component text, Dictionary cache, string? source, string? translated) { if (!string.IsNullOrEmpty(source)) { int instanceID = ((Object)text).GetInstanceID(); if (cache.Count < RuntimePerformanceSettings.ComponentTextCacheLimit || cache.ContainsKey(instanceID)) { cache[instanceID] = new CachedComponentTranslation(source, translated); } } } private static void CacheProtectedInput(Component text, Dictionary cache, int parentId, bool value) { int instanceID = ((Object)text).GetInstanceID(); if (cache.Count < RuntimePerformanceSettings.ComponentTextCacheLimit || cache.ContainsKey(instanceID)) { cache[instanceID] = new CachedProtectedInput(parentId, value); } } private static int GetParentInstanceId(Component text) { Transform val = (((Object)(object)text.transform == (Object)null) ? null : text.transform.parent); if (!((Object)(object)val == (Object)null)) { return ((Object)val).GetInstanceID(); } return 0; } private static bool MightNeedExternalTextTranslation(string? source) { if (string.IsNullOrWhiteSpace(source) || source.Length > 512) { return false; } bool flag = false; bool flag2 = false; int num = 0; while (num < source.Length) { char c = source[num]; if (c >= '豈') { if (c <= '\ufaff') { goto IL_0050; } } else if (c >= '㐀' && c <= '\u9fff') { goto IL_0050; } bool flag3 = false; goto IL_0058; IL_0058: if (flag3) { flag2 = true; } else { switch (c) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': flag3 = true; break; default: flag3 = false; break; } if (flag3) { flag = true; } } num++; continue; IL_0050: flag3 = true; goto IL_0058; } if (flag) { if (flag2) { return ExternalEnglishCompatibilityService.CanHandleCheap(source); } return true; } return false; } private static bool ContainsCjk(string value) { int num = 0; while (num < value.Length) { char c = value[num]; if (c >= '豈') { if (c <= '\ufaff') { goto IL_0030; } } else if (c >= '㐀' && c <= '\u9fff') { goto IL_0030; } bool flag = false; goto IL_0036; IL_0036: if (flag) { return true; } num++; continue; IL_0030: flag = true; goto IL_0036; } return false; } } internal static class ItemIdentityCompatibilityService { public static void Initialize() { } public static void Shutdown() { Clear(); } public static int TranslateResourceItemName(Item? item) { PreserveItemName(item); return 0; } public static bool TryTranslateItemName(Item? item) { PreserveItemName(item); return false; } public static void Clear() { } private static void PreserveItemName(Item? item) { if (!((Object)(object)item == (Object)null)) { OriginalResourceStateService.CaptureItem(item); string originalItemName = OriginalResourceStateService.GetOriginalItemName(item); if (item.itemName != originalItemName) { item.itemName = originalItemName; } } } } internal static class ConfigSections { public const string AutomaticTranslation = "02 Performance - Automatic Translation"; public const string CustomLocalization = "01 基础 - 自定义本地化"; public const string InfectionStatus = "01 基础 - 感染状态"; public const string Performance = "02 性能 - 运行时预算"; public const string FontCompatibility = "03 字体 - 字体兼容"; public const string RadiationWarning = "04 资源 - 辐射警告"; public const string DiagnosticsGeneral = "90 诊断 - 通用"; public const string DiagnosticsTextCollector = "90 诊断 - 未翻译文本收集"; public const string DiagnosticsFontFallback = "90 诊断 - 字体审计"; public const string RadiationWarningAudit = "90 诊断 - 辐射警告审计"; } [BepInPlugin("cn.codex.v81testchn", "V81 TEST CHN", "3.1.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class d__23 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__23(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if (!IsRuntimeShuttingDown && AutomaticTranslationService.NeedsMainThreadPump) { AutomaticTranslationService.PumpMainThread(); <>2__current = null; <>1__state = 1; return true; } 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 const string PluginGuid = "cn.codex.v81testchn"; public const string PluginName = "V81 TEST CHN"; public const string PluginVersion = "3.1.3"; private const string ConfigFileName = "LC Chinese Project.cfg"; private const string LegacyConfigFileName = "cn.codex.v81testchn.cfg"; internal static ManualLogSource Log; private readonly Harmony _harmony = new Harmony("cn.codex.v81testchn"); private static int _translationHits; private static bool _isShuttingDown; private static bool _cleanupInProgress; private static bool _runtimeShutDown; private static ConfigEntry? _logRuntimeLocalizationEvents; private static bool _logRuntimeLocalizationEventsFast; private ConfigFile? _runtimeConfig; private bool _cleanupCompleted; private Coroutine? _automaticTranslationPumpCoroutine; internal static bool IsRuntimeShuttingDown { get { if (!_cleanupInProgress) { return _runtimeShutDown; } return true; } } internal static bool RuntimeLocalizationLogsEnabled => _logRuntimeLocalizationEventsFast; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; _runtimeShutDown = false; _cleanupInProgress = false; ConfigFile runtimeConfig = CreateRuntimeConfig(); _logRuntimeLocalizationEvents = runtimeConfig.Bind("90 诊断 - 通用", "LogRuntimeLocalizationEvents", false, "Enable verbose runtime localization event logs. Default off to avoid IO spikes during gameplay."); _logRuntimeLocalizationEventsFast = _logRuntimeLocalizationEvents.Value; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); Application.quitting += OnUnityQuitting; string pluginDir = Path.GetDirectoryName(typeof(Plugin).Assembly.Location) ?? Paths.PluginPath; RuntimePerformanceSettings.Initialize(runtimeConfig); TranslationGuard.Initialize(runtimeConfig); TextPatches.Initialize(runtimeConfig); TryInitialize("CustomLocalizationExtensionService", delegate { CustomLocalizationExtensionService.Initialize(pluginDir, runtimeConfig); }); TryInitialize("ItemIdentityCompatibilityService", delegate { ItemIdentityCompatibilityService.Initialize(); }); try { TranslationService.Initialize(runtimeConfig); TranslationService.Load(pluginDir); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("TranslationService.Load failed: " + ex.GetType().Name + ": " + ex.Message)); } TryInitialize("AutomaticTranslationService", delegate { AutomaticTranslationService.Initialize(pluginDir, runtimeConfig); }); if (AutomaticTranslationService.NeedsMainThreadPump) { _automaticTranslationPumpCoroutine = ((MonoBehaviour)this).StartCoroutine(PumpAutomaticTranslation()); } TryInitialize("RuntimeTextCollector", delegate { RuntimeTextCollector.Initialize(pluginDir, runtimeConfig); }); int num = CountOwnHarmonyPatches(); int num2 = 0; if (num > 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)string.Format("Manual patch install skipped; {0} Harmony patches are already owned by {1}.", num, "cn.codex.v81testchn")); } else { num2 = TextPatches.Install(_harmony); } if (num2 == 0 && num == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Manual patch count is 0; global text hooks are not installed."); } TryInitialize("FontFallbackService", delegate { FontFallbackService.TryLoadFontAsset(pluginDir); }); TryInitialize("FontFallbackAuditService", delegate { FontFallbackAuditService.Initialize(runtimeConfig); }); TryInitialize("AlertTextureReplacementService", delegate { AlertTextureReplacementService.Initialize(pluginDir); }); TryInitialize("RadiationWarningAuditService", delegate { RadiationWarningAuditService.Initialize(runtimeConfig); }); TryInitialize("RadiationWarningPlaybackService", delegate { RadiationWarningPlaybackService.Initialize(pluginDir, runtimeConfig); }); TryInitialize("EndGameLocalizationService", delegate { EndGameLocalizationService.Initialize(pluginDir); }); TryInitialize("TargetedUiTranslator", delegate { TargetedUiTranslator.Initialize(); }); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} loaded. Entries: {1}; manualPatchCount={2}; harmonyPatchedMethods={3}", "V81 TEST CHN", TranslationService.EntryCount, num2, CountOwnHarmonyPatches())); } private ConfigFile CreateRuntimeConfig() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown string text = Path.Combine(Paths.ConfigPath, "LC Chinese Project.cfg"); TryCopyLegacyConfig(Path.Combine(Paths.ConfigPath, "cn.codex.v81testchn.cfg"), text); _runtimeConfig = new ConfigFile(text, true); return _runtimeConfig; } private static void TryCopyLegacyConfig(string legacyConfigPath, string configPath) { try { if (!File.Exists(configPath) && File.Exists(legacyConfigPath)) { Directory.CreateDirectory(Path.GetDirectoryName(configPath) ?? Paths.ConfigPath); File.Copy(legacyConfigPath, configPath, overwrite: false); Log.LogInfo((object)"Migrated config file to 'LC Chinese Project.cfg'."); } } catch (Exception ex) { Log.LogWarning((object)("Failed to migrate config file to 'LC Chinese Project.cfg': " + ex.GetType().Name + ": " + ex.Message)); } } [IteratorStateMachine(typeof(d__23))] private IEnumerator PumpAutomaticTranslation() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__23(0); } private void OnApplicationQuit() { _isShuttingDown = true; Log.LogInfo((object)"Plugin OnApplicationQuit"); CleanupPlugin(); } private void OnDestroy() { Log.LogInfo((object)$"Plugin OnDestroy; shuttingDown={_isShuttingDown}"); if (!_isShuttingDown && Application.isPlaying) { Log.LogWarning((object)"Preserving Harmony patches after early OnDestroy because the application is still running."); } else { CleanupPlugin(); } } private void OnUnityQuitting() { _isShuttingDown = true; Log.LogInfo((object)"Plugin Application.quitting"); CleanupPlugin(); } private void CleanupPlugin() { if (!_cleanupCompleted) { _cleanupCompleted = true; Application.quitting -= OnUnityQuitting; _cleanupInProgress = true; _runtimeShutDown = true; if (_automaticTranslationPumpCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_automaticTranslationPumpCoroutine); _automaticTranslationPumpCoroutine = null; } TryCleanup("Harmony.UnpatchSelf", (Action)_harmony.UnpatchSelf); TryCleanup("OriginalResourceStateService.RestoreAll", delegate { OriginalResourceStateService.RestoreAll(); }); TryCleanup("ItemIdentityCompatibilityService.Shutdown", delegate { ItemIdentityCompatibilityService.Shutdown(); }); TryCleanup("TextPatches.Clear", delegate { TextPatches.Clear(); }); TryCleanup("TranslationGuard.Clear", delegate { TranslationGuard.Clear(); }); TryCleanup("TargetedUiTranslator.Shutdown", delegate { TargetedUiTranslator.Shutdown(); }); TryCleanup("TranslationService.ClearCaches", delegate { TranslationService.ClearCaches(); }); TryCleanup("CustomLocalizationExtensionService.Shutdown", delegate { CustomLocalizationExtensionService.Shutdown(); }); TryCleanup("FontFallbackAuditService.Shutdown", delegate { FontFallbackAuditService.Shutdown(); }); TryCleanup("FontFallbackService.Shutdown", delegate { FontFallbackService.Shutdown(); }); TryCleanup("AlertTextureReplacementService.Shutdown", delegate { AlertTextureReplacementService.Shutdown(); }); TryCleanup("RadiationWarningAuditService.Shutdown", delegate { RadiationWarningAuditService.Shutdown(); }); TryCleanup("RadiationWarningPlaybackService.Shutdown", delegate { RadiationWarningPlaybackService.Shutdown(); }); TryCleanup("EndGameLocalizationService.Shutdown", delegate { EndGameLocalizationService.Shutdown(); }); TryCleanup("RuntimeTextCollector.Shutdown", delegate { RuntimeTextCollector.Shutdown(); }); TryCleanup("AutomaticTranslationService.Shutdown", delegate { AutomaticTranslationService.Shutdown(); }); _cleanupInProgress = false; } } private static void TryInitialize(string name, Action initialize) { try { initialize(); } catch (Exception ex) { Log.LogError((object)(name + " initialization failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static void TryCleanup(string name, Action cleanup) { try { cleanup(); } catch (Exception ex) { Log.LogWarning((object)(name + " failed during plugin cleanup: " + ex.GetType().Name + ": " + ex.Message)); } } internal static void ReportTranslationHit() { _translationHits++; } internal static void LogTargetedTranslation(string reason, int translated, int seen) { _ = reason == "HUDManager.UpdateScanNodes"; } internal static void LogPatchEntry(string reason) { } private static int CountOwnHarmonyPatches() { return Harmony.GetAllPatchedMethods().Count(delegate(MethodBase method) { Patches patchInfo = Harmony.GetPatchInfo(method); return patchInfo != null && patchInfo.Owners.Contains("cn.codex.v81testchn"); }); } } internal static class RuntimePerformanceSettings { private const int DefaultTmpHookCacheLimit = 16384; private const int DefaultComponentTextCacheLimit = 16384; private const int DefaultHudScannerCacheLimit = 16384; private const int DefaultExternalCompatibilityCacheLimit = 4096; private const int DefaultFontFallbackCacheLimit = 16384; private const int DefaultMenuTranslationWorkBudgetPerFrame = 12; private const bool DefaultEnableTargetedUiStyleRepairFastGate = true; private static ConfigEntry? _tmpHookCacheLimit; private static ConfigEntry? _componentTextCacheLimit; private static ConfigEntry? _hudScannerCacheLimit; private static ConfigEntry? _externalCompatibilityCacheLimit; private static ConfigEntry? _fontFallbackCacheLimit; private static ConfigEntry? _menuTranslationWorkBudgetPerFrame; private static ConfigEntry? _enableTargetedUiStyleRepairFastGate; public static int TmpHookCacheLimit { get; private set; } = 16384; public static int ComponentTextCacheLimit { get; private set; } = 16384; public static int HudScannerCacheLimit { get; private set; } = 16384; public static int ExternalCompatibilityCacheLimit { get; private set; } = 4096; public static int FontFallbackCacheLimit { get; private set; } = 16384; public static int MenuTranslationWorkBudgetPerFrame { get; private set; } = 12; public static bool EnableTargetedUiStyleRepairFastGate { get; private set; } = true; public static void Initialize(ConfigFile config) { _tmpHookCacheLimit = BindInt(config, "LargeModpackTmpHookCacheLimit", 16384, 1024, 32768, "Maximum entries for global TMP hook translation/no-op/color caches. Larger modpacks benefit from the default 16384."); _componentTextCacheLimit = BindInt(config, "LargeModpackComponentTextCacheLimit", 16384, 1024, 32768, "Maximum entries for targeted UI component text caches. Larger modpacks benefit from the default 16384."); _hudScannerCacheLimit = BindInt(config, "LargeModpackHudScannerCacheLimit", 16384, 1024, 32768, "Maximum entries for HUD scanner text and node caches. Larger modpacks benefit from the default 16384."); _externalCompatibilityCacheLimit = BindInt(config, "LargeModpackExternalCompatibilityCacheLimit", 4096, 512, 16384, "Maximum entries for generic external English compatibility runtime caches. Default 4096 keeps memory bounded."); _fontFallbackCacheLimit = BindInt(config, "LargeModpackFontFallbackCacheLimit", 16384, 1024, 32768, "Maximum entries for font fallback/style repair caches. Larger modpacks benefit from the default 16384."); _menuTranslationWorkBudgetPerFrame = BindInt(config, "MenuTranslationWorkBudgetPerFrame", 12, 4, 64, "Maximum targeted menu text components translated per frame. Lower values reduce spikes; higher values finish menu localization sooner."); _enableTargetedUiStyleRepairFastGate = config.Bind("02 性能 - 运行时预算", "EnableTargetedUiStyleRepairFastGate", true, "Skip repeated targeted UI style repairs for the same component/text/style state. Disable if a UI needs repeated style repair."); _enableTargetedUiStyleRepairFastGate.SettingChanged += OnSettingsChanged; RefreshFastValues(); } private static ConfigEntry BindInt(ConfigFile config, string key, int defaultValue, int min, int max, string description) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown ConfigEntry obj = config.Bind("02 性能 - 运行时预算", key, defaultValue, new ConfigDescription($"{description} Range: {min}-{max}. Changes apply immediately.", (AcceptableValueBase)(object)new AcceptableValueRange(min, max), Array.Empty())); obj.SettingChanged += OnSettingsChanged; return obj; } private static void OnSettingsChanged(object sender, EventArgs args) { RefreshFastValues(); } private static void RefreshFastValues() { TmpHookCacheLimit = Clamp(_tmpHookCacheLimit?.Value ?? 16384, 1024, 32768); ComponentTextCacheLimit = Clamp(_componentTextCacheLimit?.Value ?? 16384, 1024, 32768); HudScannerCacheLimit = Clamp(_hudScannerCacheLimit?.Value ?? 16384, 1024, 32768); ExternalCompatibilityCacheLimit = Clamp(_externalCompatibilityCacheLimit?.Value ?? 4096, 512, 16384); FontFallbackCacheLimit = Clamp(_fontFallbackCacheLimit?.Value ?? 16384, 1024, 32768); MenuTranslationWorkBudgetPerFrame = Clamp(_menuTranslationWorkBudgetPerFrame?.Value ?? 12, 4, 64); EnableTargetedUiStyleRepairFastGate = _enableTargetedUiStyleRepairFastGate?.Value ?? true; } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value <= max) { return value; } return max; } } internal static class FocusedTextAuditService { private static int _logBudget = 180; public static void AuditLoadedScene(string stage) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) if (_logBudget <= 0) { return; } TMP_Text[] array = Object.FindObjectsOfType(true); foreach (TMP_Text val in array) { if (!((Object)(object)val == (Object)null) && ShouldAudit(val.text)) { string sharedFace = "N/A"; Material fontSharedMaterial = val.fontSharedMaterial; Color color; if ((Object)(object)fontSharedMaterial != (Object)null && fontSharedMaterial.HasProperty(ShaderUtilities.ID_FaceColor)) { color = fontSharedMaterial.GetColor(ShaderUtilities.ID_FaceColor); sharedFace = ((object)(Color)(ref color)).ToString(); } string fontFace = "N/A"; Material fontMaterial = val.fontMaterial; if ((Object)(object)fontMaterial != (Object)null && fontMaterial.HasProperty(ShaderUtilities.ID_FaceColor)) { color = fontMaterial.GetColor(ShaderUtilities.ID_FaceColor); fontFace = ((object)(Color)(ref color)).ToString(); } Log(stage, "TMP", ((Object)val).name, ((Graphic)val).color, sharedFace, fontFace, val.text); if (_logBudget <= 0) { return; } } } Text[] array2 = Object.FindObjectsOfType(true); foreach (Text val2 in array2) { if (!((Object)(object)val2 == (Object)null) && ShouldAudit(val2.text)) { Log(stage, "UGUI.Text", ((Object)val2).name, ((Graphic)val2).color, "N/A", "N/A", val2.text); if (_logBudget <= 0) { return; } } } TextMesh[] array3 = Object.FindObjectsOfType(true); foreach (TextMesh val3 in array3) { if (!((Object)(object)val3 == (Object)null) && ShouldAudit(val3.text)) { Log(stage, "TextMesh", ((Object)val3).name, val3.color, "N/A", "N/A", val3.text); if (_logBudget <= 0) { break; } } } } private static void Log(string stage, string type, string name, Color color, string sharedFace, string fontFace, string text) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (_logBudget > 0) { _logBudget--; Plugin.Log.LogWarning((object)$"FocusedSceneAudit[{stage}] type={type}, name={name}, color={color}, sharedFace={sharedFace}, fontFace={fontFace}, text='{Trim(text)}'"); } } private static bool ShouldAudit(string? text) { if (string.IsNullOrWhiteSpace(text)) { return false; } if (!ContainsOrdinalIgnoreCase(text, "SYSTEMS ONLINE") && !ContainsOrdinalIgnoreCase(text, "joined the ship") && !ContainsOrdinalIgnoreCase(text, "started the ship") && !ContainsOrdinalIgnoreCase(text, "ENTERING THE ATMOSPHERE") && !ContainsOrdinalIgnoreCase(text, "系统在线") && !ContainsOrdinalIgnoreCase(text, "系统上线") && !ContainsOrdinalIgnoreCase(text, "正在进入大气层")) { return ContainsOrdinalIgnoreCase(text, "进入大气层"); } return true; } private static bool ContainsOrdinalIgnoreCase(string source, string needle) { return source.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0; } private static string Trim(string input) { input = input.Replace('\n', ' '); if (input.Length > 120) { return input.Substring(0, 120); } return input; } } internal static class RuntimeTextCollector { private sealed class RuntimeTextRecord { public string FirstSeenUtc { get; set; } = string.Empty; public string Scene { get; set; } = string.Empty; public string Component { get; set; } = string.Empty; public string ObjectPath { get; set; } = string.Empty; public string FontName { get; set; } = string.Empty; public string Text { get; set; } = string.Empty; } private const int MaxTextLength = 2000; private static readonly object SyncRoot = new object(); private static readonly Dictionary Records = new Dictionary(StringComparer.Ordinal); private static readonly List PendingRecords = new List(); private static ConfigEntry? _enabled; private static ConfigEntry? _collectorFlushIntervalSeconds; private static ConfigEntry? _collectorFlushEveryRecords; private static ConfigEntry? _maxCollectedRecords; private static ConfigEntry? _collectorUseFullTranslationCheck; private static string? _outputPath; private static bool _enabledFast; private static bool _isInitialized; private static bool _maxRecordsWarningLogged; private static DateTime _nextFlushUtc; private static Timer? _flushTimer; public static int Count { get { lock (SyncRoot) { return Records.Count; } } } public static bool IsEnabled => _enabledFast; public static void Initialize(string pluginDir, ConfigFile config) { _enabled = config.Bind("90 诊断 - 未翻译文本收集", "CollectUntranslatedText", false, "收集运行时疑似未翻译文本,并写入 logs/untranslated-texts.csv。默认关闭,避免影响帧时间。"); _collectorFlushIntervalSeconds = config.Bind("90 诊断 - 未翻译文本收集", "CollectorFlushIntervalSeconds", 30f, "启用未翻译文本收集后,待写入记录的定时追加间隔,单位为秒。"); _collectorFlushEveryRecords = config.Bind("90 诊断 - 未翻译文本收集", "CollectorFlushEveryRecords", 20, "启用未翻译文本收集后,每累计多少条新记录就追加写入一次。"); _maxCollectedRecords = config.Bind("90 诊断 - 未翻译文本收集", "MaxCollectedRecords", 5000, "单次插件运行最多保留的未翻译文本记录数量。达到上限后停止继续收集。"); _collectorUseFullTranslationCheck = config.Bind("90 诊断 - 未翻译文本收集", "CollectorUseFullTranslationCheck", false, "收集前是否使用完整翻译流程复查文本。默认关闭,避免诊断功能进入较重路径。"); _enabledFast = _enabled.Value; StopFlushTimer(); lock (SyncRoot) { Records.Clear(); PendingRecords.Clear(); _maxRecordsWarningLogged = false; } if (!IsEnabled) { _outputPath = null; _isInitialized = false; return; } try { string text = Path.Combine(pluginDir, "logs"); Directory.CreateDirectory(text); _outputPath = Path.Combine(text, "untranslated-texts.csv"); _isInitialized = true; _nextFlushUtc = DateTime.UtcNow.AddSeconds(GetFlushIntervalSeconds()); WriteHeaderIfMissing(); StartFlushTimer(); Plugin.Log.LogInfo((object)("RuntimeTextCollector writing untranslated text candidates to '" + _outputPath + "'.")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to initialize untranslated text collector: " + ex.Message)); } } public static void Shutdown() { StopFlushTimer(); FlushPending(); lock (SyncRoot) { Records.Clear(); PendingRecords.Clear(); } _outputPath = null; _enabledFast = false; _isInitialized = false; _maxRecordsWarningLogged = false; _collectorUseFullTranslationCheck = null; } public static void Record(TMP_Text component, string? source) { if (!((Object)(object)component == (Object)null) && IsCollectionActive()) { Record("TMP_Text", ((Component)component).gameObject, ((Object)(object)component.font != (Object)null) ? ((Object)component.font).name : string.Empty, source); } } public static void Record(Text component, string? source) { if (!((Object)(object)component == (Object)null) && IsCollectionActive()) { Record("UI_Text", ((Component)component).gameObject, ((Object)(object)component.font != (Object)null) ? ((Object)component.font).name : string.Empty, source); } } public static void Record(TextMesh component, string? source) { if (!((Object)(object)component == (Object)null) && IsCollectionActive()) { Record("TextMesh", ((Component)component).gameObject, ((Object)(object)component.font != (Object)null) ? ((Object)component.font).name : string.Empty, source); } } private static void Record(string componentType, GameObject gameObject, string fontName, string? source) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!IsCollectionActive() || !ShouldCollect(source)) { return; } string text = Normalize(source); Scene scene = gameObject.scene; string text2; if (!((Scene)(ref scene)).IsValid()) { text2 = string.Empty; } else { scene = gameObject.scene; text2 = ((Scene)(ref scene)).name; } string text3 = text2; string objectPath = GetObjectPath(gameObject); string key = text3 + "\n" + componentType + "\n" + objectPath + "\n" + text; bool flag = false; lock (SyncRoot) { if (Records.Count >= GetMaxCollectedRecords()) { WarnMaxCollectedRecordsOnce(); return; } if (Records.ContainsKey(key)) { flag = ShouldFlushPendingRecordsLocked(); } else { RuntimeTextRecord runtimeTextRecord = new RuntimeTextRecord { FirstSeenUtc = DateTime.UtcNow.ToString("o"), Scene = text3, Component = componentType, ObjectPath = objectPath, FontName = fontName, Text = text }; Records[key] = runtimeTextRecord; PendingRecords.Add(runtimeTextRecord); flag = ShouldFlushPendingRecordsLocked(); } } if (flag) { FlushPending(); } } private static bool IsCollectionActive() { if (IsEnabled && _isInitialized) { return !string.IsNullOrEmpty(_outputPath); } return false; } private static bool ShouldCollect(string? source) { if (string.IsNullOrWhiteSpace(source)) { return false; } string text = Normalize(source); if (text.Length <= 1 || text.Length > 2000) { return false; } bool flag = false; string translated = text; foreach (char c in translated) { if ((c < '一' || c > '\u9fff') && ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) { flag = true; } } if (!flag) { return false; } if (TranslationService.TryTranslateFastExact(text, out translated) || TranslationService.TryTranslateKnownDynamicTextFast(text, out translated) || CustomLocalizationExtensionService.TryTranslateFastExact(text, out translated)) { return false; } ConfigEntry? collectorUseFullTranslationCheck = _collectorUseFullTranslationCheck; if (collectorUseFullTranslationCheck != null && collectorUseFullTranslationCheck.Value) { return !TranslationService.TryTranslate(text, out translated); } return true; } private static string Normalize(string source) { return source.Replace("\r\n", "\n").Trim(); } private static string GetObjectPath(GameObject gameObject) { Stack stack = new Stack(); Transform val = gameObject.transform; while ((Object)(object)val != (Object)null) { stack.Push(((Object)val).name); val = val.parent; } return string.Join("/", stack.ToArray()); } private static void MaybeFlushPendingRecords() { bool flag = false; lock (SyncRoot) { flag = ShouldFlushPendingRecordsLocked(); } if (flag) { FlushPending(); } } private static bool ShouldFlushPendingRecordsLocked() { if (PendingRecords.Count > 0) { if (PendingRecords.Count < GetFlushEveryRecords()) { return DateTime.UtcNow >= _nextFlushUtc; } return true; } return false; } private static void FlushPending() { string outputPath; List list; lock (SyncRoot) { if (string.IsNullOrEmpty(_outputPath) || PendingRecords.Count == 0) { return; } outputPath = _outputPath; list = new List(PendingRecords); PendingRecords.Clear(); _nextFlushUtc = DateTime.UtcNow.AddSeconds(GetFlushIntervalSeconds()); } try { WriteHeaderIfMissing(outputPath); StringBuilder stringBuilder = new StringBuilder(); foreach (RuntimeTextRecord item in list) { stringBuilder.Append(Csv(item.FirstSeenUtc)).Append(',').Append(Csv(item.Scene)) .Append(',') .Append(Csv(item.Component)) .Append(',') .Append(Csv(item.ObjectPath)) .Append(',') .Append(Csv(item.FontName)) .Append(',') .Append(Csv(item.Text)) .AppendLine(); } File.AppendAllText(outputPath, stringBuilder.ToString(), Encoding.UTF8); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to flush untranslated text collector: " + ex.Message)); lock (SyncRoot) { if (_isInitialized && !string.IsNullOrEmpty(_outputPath)) { PendingRecords.InsertRange(0, list); } } } } private static void WriteHeaderIfMissing() { if (!string.IsNullOrEmpty(_outputPath)) { WriteHeaderIfMissing(_outputPath); } } private static void WriteHeaderIfMissing(string outputPath) { FileInfo fileInfo = new FileInfo(outputPath); if (!fileInfo.Exists || fileInfo.Length <= 0) { File.AppendAllText(outputPath, "firstSeenUtc,scene,component,objectPath,fontName,text" + Environment.NewLine, Encoding.UTF8); } } private static void StartFlushTimer() { StopFlushTimer(); if (IsEnabled && _isInitialized) { TimeSpan timeSpan = TimeSpan.FromSeconds(GetFlushIntervalSeconds()); _flushTimer = new Timer(FlushTimerCallback, null, timeSpan, timeSpan); } } private static void StopFlushTimer() { Timer? flushTimer = _flushTimer; _flushTimer = null; flushTimer?.Dispose(); } private static void FlushTimerCallback(object? state) { try { FlushPending(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("RuntimeTextCollector timer flush failed: " + ex.Message)); } } private static void WarnMaxCollectedRecordsOnce() { if (!_maxRecordsWarningLogged) { _maxRecordsWarningLogged = true; Plugin.Log.LogWarning((object)$"Max collected untranslated text records reached ({GetMaxCollectedRecords()}); RuntimeTextCollector will ignore additional records this session."); } } private static float GetFlushIntervalSeconds() { return Math.Max(1f, _collectorFlushIntervalSeconds?.Value ?? 30f); } private static int GetFlushEveryRecords() { return Math.Max(1, _collectorFlushEveryRecords?.Value ?? 20); } private static int GetMaxCollectedRecords() { return Math.Max(0, _maxCollectedRecords?.Value ?? 5000); } private static string Csv(string value) { return "\"" + value.Replace("\"", "\"\"").Replace("\r", "\\r").Replace("\n", "\\n") + "\""; } } internal static class TranslationService { internal static class ChatDynamicTranslator { private static readonly (string Suffix, string Replacement)[] SystemSuffixTranslations = new(string, string)[6] { (" joined the ship.", " 加入了飞船。"), (" started the ship.", " 启动了飞船。"), (" disconnected.", " 断开了连接。"), (" was left behind.", " 被抛下了。"), (" was kicked.", " 被踢出了。"), (" died.", " 死亡了。") }; public static bool CanHandleCheap(string? source) { if (string.IsNullOrWhiteSpace(source)) { return false; } (string, string)[] systemSuffixTranslations = SystemSuffixTranslations; for (int i = 0; i < systemSuffixTranslations.Length; i++) { string item = systemSuffixTranslations[i].Item1; if (RenderedTextEndsWith(source, item)) { return true; } } return false; } public static bool Translate(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } (string, string)[] systemSuffixTranslations = SystemSuffixTranslations; for (int i = 0; i < systemSuffixTranslations.Length; i++) { var (suffix, text) = systemSuffixTranslations[i]; if (TryFindRenderedSuffixSpan(source, suffix, out var suffixStart, out var suffixEnd) && HasRenderedTextBefore(source, suffixStart)) { string text2 = source.Substring(0, suffixStart); int num = suffixEnd; translated = text2 + text + source.Substring(num, source.Length - num); return true; } } return false; } private static bool TryFindRenderedSuffixSpan(string source, string suffix, out int suffixStart, out int suffixEnd) { suffixStart = 0; suffixEnd = 0; int sourceIndex = source.Length - 1; SkipTrailingRenderedWhitespace(source, ref sourceIndex); suffixEnd = sourceIndex + 1; for (int num = suffix.Length - 1; num >= 0; num--) { if (!TryReadPreviousRenderedChar(source, ref sourceIndex, out var ch) || !AsciiEqualsIgnoreCase(ch, suffix[num])) { return false; } } suffixStart = sourceIndex + 1; return true; } private static bool HasRenderedTextBefore(string source, int endExclusive) { int sourceIndex = endExclusive - 1; char ch; while (TryReadPreviousRenderedChar(source, ref sourceIndex, out ch)) { if (!char.IsWhiteSpace(ch)) { return true; } } return false; } private static bool RenderedTextEndsWith(string source, string suffix) { int sourceIndex = source.Length - 1; SkipTrailingRenderedWhitespace(source, ref sourceIndex); for (int num = suffix.Length - 1; num >= 0; num--) { if (!TryReadPreviousRenderedChar(source, ref sourceIndex, out var ch) || !AsciiEqualsIgnoreCase(ch, suffix[num])) { return false; } } return true; } private static void SkipTrailingRenderedWhitespace(string source, ref int sourceIndex) { char ch; while (TryReadPreviousRenderedChar(source, ref sourceIndex, out ch)) { if (!char.IsWhiteSpace(ch)) { sourceIndex++; break; } } } private static bool TryReadPreviousRenderedChar(string source, ref int sourceIndex, out char ch) { while (sourceIndex >= 0) { ch = source[sourceIndex--]; if (ch != '>') { return true; } int num = sourceIndex; while (num >= 0 && source[num] != '<') { num--; } if (num < 0) { return true; } sourceIndex = num - 1; } ch = '\0'; return false; } private static bool AsciiEqualsIgnoreCase(char left, char right) { if (left == right) { return true; } if (left >= 'A' && left <= 'Z') { left = (char)(left + 32); } if (right >= 'A' && right <= 'Z') { right = (char)(right + 32); } return left == right; } } internal static class ControlTipTranslator { public static bool CanHandleCheap(string? source) { if (!LooksLikeControlTipTextCheap(source)) { return LooksLikeSuitChangePromptCheap(source); } return true; } public static bool Translate(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } if (TranslateSuitChangePrompt(source, out translated)) { return true; } string text = source.Trim(); Match match = SafeRegexMatch(text, "^\\[\\s*Cooldown\\s*[:\\uff1a]\\s*(?\\d+)\\s*sec\\.?\\s*\\]$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match.Success) { translated = "[冷却:" + match.Groups["seconds"].Value + " 秒]"; return true; } if (TryTranslateBracketedControlStatus(text, out translated)) { return true; } if (TryTranslateKeyFirstControlTip(text, out translated)) { return true; } if (TryTranslateControlTipSegments(text, out translated)) { return true; } Match match2 = SafeRegexMatch(text, "^Drop\\s+(?.+?)\\s*[:\\uff1a]\\s*(?\\[[^\\]]+\\])$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match2.Success) { string text2 = BuildTerminalLocalizedItemName(match2.Groups["item"].Value.Trim()); translated = "丢弃 " + text2 + ":" + match2.Groups["key"].Value.Trim(); return true; } Match match3 = SafeRegexMatch(text, "^Throw\\s+(?.+?)\\s*[:\\uff1a]\\s*(?\\[[^\\]]+\\])$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match3.Success) { string text3 = BuildTerminalLocalizedItemName(match3.Groups["item"].Value.Trim()); translated = "扔出 " + text3 + ":" + match3.Groups["key"].Value.Trim(); return true; } Match match4 = SafeRegexMatch(text, "^(?.+?)\\s*[:\\uff1a]\\s*(?\\[[^\\]]+\\])(?.*)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match4.Success) { return TranslateLooseControlAction(text, out translated); } bool impliesHold; string key = NormalizeAction(match4.Groups["action"].Value, out impliesHold); if (!ControlTipActionEntries.TryGetValue(key, out string value)) { return false; } string text4 = match4.Groups["key"].Value.Trim(); string text5 = NormalizeSuffix(match4.Groups["suffix"].Value, impliesHold); translated = value + ":" + text4 + text5; return true; } private static bool TryTranslateControlTipSegments(string trimmed, out string translated) { translated = trimmed; if (trimmed.IndexOf('|') < 0) { return false; } string[] array = trimmed.Split('|'); bool flag = false; for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0 && text.IndexOf('|') < 0 && Translate(text, out string translated2) && !string.Equals(text, translated2, StringComparison.Ordinal)) { array[i] = translated2; flag = true; } } if (!flag) { return false; } translated = string.Join(" | ", array); return true; } private static bool TryTranslateBracketedControlStatus(string trimmed, out string translated) { translated = trimmed; if (trimmed.Length >= 3 && trimmed[0] == '[') { if (trimmed[trimmed.Length - 1] == ']') { string key = trimmed.Substring(1, trimmed.Length - 1 - 1).Trim(); if (!ControlTipActionEntries.TryGetValue(key, out string value)) { return false; } translated = "[" + value + "]"; return true; } } return false; } private static bool TryTranslateKeyFirstControlTip(string trimmed, out string translated) { translated = trimmed; Match match = SafeRegexMatch(trimmed, "^(?\\[[^\\]]+\\])\\s+(?[^:\\uff1a]+?)\\s*$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success) { return false; } bool impliesHold; string key = NormalizeAction(match.Groups["action"].Value, out impliesHold); if (!ControlTipActionEntries.TryGetValue(key, out string value)) { return false; } translated = match.Groups["key"].Value.Trim() + " " + value + (impliesHold ? "(长按)" : string.Empty); return true; } private static bool TranslateLooseControlAction(string trimmed, out string translated) { translated = trimmed; Match match = SafeRegexMatch(trimmed, "^(?[^:\\uff1a]+)\\s*[:\\uff1a]\\s*(?.+?)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success) { return false; } bool impliesHold; string key = NormalizeAction(match.Groups["action"].Value, out impliesHold); if (!ControlTipActionEntries.TryGetValue(key, out string value)) { return false; } string text = NormalizeLooseControlValue(match.Groups["value"].Value, impliesHold); if (text.Length == 0) { return false; } translated = value + ":" + text; return true; } private static bool TranslateSuitChangePrompt(string source, out string translated) { translated = source; Match match = SafeRegexMatch(StripRichTextTags(source).Trim(), "^(?:Change|\\u66f4\\u6362\\u670d\\u88c5)\\s*[:\\uff1a]\\s*(?.+?)\\s*$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success) { return false; } string text = match.Groups["suit"].Value.Trim(); if (text.Length == 0) { return false; } translated = "更换服装:" + BuildTerminalLocalizedItemName(text); return true; } public static bool TranslateStandalone(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } bool impliesHold; string key = NormalizeAction(StripRichTextTags(source), out impliesHold); if (!ControlTipActionEntries.TryGetValue(key, out translated)) { return false; } if (impliesHold) { translated += "(长按)"; } return true; } public static bool TranslateStandaloneFast(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } string text = StripRichTextTagsCheap(source).Trim(); if (text.Length == 0) { return false; } bool flag = false; if (text.EndsWith(" hold", StringComparison.OrdinalIgnoreCase)) { flag = true; string text2 = text; int length = " hold".Length; text = text2.Substring(0, text2.Length - length).TrimEnd(); } if (!ControlTipActionEntries.TryGetValue(text, out translated)) { return false; } if (flag) { translated += "(长按)"; } return true; } private static bool LooksLikeSuitChangePromptCheap(string? source) { if (string.IsNullOrWhiteSpace(source)) { return false; } string text = StripRichTextTagsCheap(source).TrimStart(); if (!text.StartsWith("Change:", StringComparison.OrdinalIgnoreCase) && !text.StartsWith("Change:", StringComparison.OrdinalIgnoreCase) && !text.StartsWith("更换服装:", StringComparison.Ordinal)) { return text.StartsWith("更换服装:", StringComparison.Ordinal); } return true; } private static string NormalizeAction(string action, out bool impliesHold) { impliesHold = false; string text = SafeRegexReplace(action, "\\s+", " ", RegexOptions.CultureInvariant).Trim(); if (text.EndsWith(" hold", StringComparison.OrdinalIgnoreCase)) { impliesHold = true; string text2 = text; int length = " hold".Length; text = text2.Substring(0, text2.Length - length).TrimEnd(); } return text; } private static string NormalizeSuffix(string suffix, bool actionImpliesHold) { string text = SafeRegexReplace(suffix ?? string.Empty, "\\s+", " ", RegexOptions.CultureInvariant).Trim(); if (actionImpliesHold || text.Contains("Hold", StringComparison.OrdinalIgnoreCase) || text.Contains("长按", StringComparison.Ordinal)) { return "(长按)"; } if (text.Length != 0) { return " " + text; } return string.Empty; } private static string NormalizeLooseControlValue(string value, bool actionImpliesHold) { string text = SafeRegexReplace(value ?? string.Empty, "\\s+", " ", RegexOptions.CultureInvariant).Trim(); if (text.Length == 0) { return string.Empty; } if (text.StartsWith("Hold ", StringComparison.OrdinalIgnoreCase)) { string text2 = text; int length = "Hold ".Length; return "按住 " + text2.Substring(length, text2.Length - length).Trim(); } if (text.StartsWith("Toggle ", StringComparison.OrdinalIgnoreCase)) { string text2 = text; int length = "Toggle ".Length; return "切换 " + text2.Substring(length, text2.Length - length).Trim(); } if (!actionImpliesHold) { return text; } return "按住 " + text; } } internal static class EndGameDynamicTranslator { public static bool CanHandleCheap(string? source) { if (!LooksLikeEndgameStatTextCheap(source) && !LooksLikeVoteTextCheap(source) && !LooksLikeDaysLeftTextCheap(source) && !LooksLikeShipLeaveEarlyWarningTextCheap(source) && (source == null || source.IndexOf("Notes", StringComparison.OrdinalIgnoreCase) < 0) && (source == null || source.IndexOf("Spectating", StringComparison.OrdinalIgnoreCase) < 0) && (source == null || source.IndexOf("Dead", StringComparison.OrdinalIgnoreCase) < 0)) { if (source == null) { return false; } return source.IndexOf("YOU ARE FIRED", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } public static bool Translate(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } if (!TranslatePlayerNotes(source, out translated) && !TranslateStatLine(source, out translated) && !TranslatePlayersFired(source, out translated) && !HudDynamicTranslator.TranslateShipLeaveEarlyWarning(source, out translated) && !HudDynamicTranslator.TranslateVotes(source, out translated) && !HudDynamicTranslator.TranslateDaysLeft(source, out translated)) { return TranslatePlayerStatus(source, out translated); } return true; } public static bool TranslatePlayerNotes(string source, out string translated) { translated = source; if (source.IndexOf("Notes", StringComparison.OrdinalIgnoreCase) < 0 && source.IndexOf("备注", StringComparison.Ordinal) < 0 && source.IndexOf("笔记", StringComparison.Ordinal) < 0) { return false; } string[] array = source.Split('\n'); bool flag = false; StringBuilder stringBuilder = new StringBuilder(source.Length + 16); for (int i = 0; i < array.Length; i++) { if (i > 0) { stringBuilder.Append('\n'); } string text = array[i]; bool num = text.EndsWith("\r", StringComparison.Ordinal); string text2; if (!num) { text2 = text; } else { string text3 = text; text2 = text3.Substring(0, text3.Length - 1); } string text4 = TranslatePlayerNoteLine(text2); if (!string.Equals(text2, text4, StringComparison.Ordinal)) { flag = true; } stringBuilder.Append(text4); if (num) { stringBuilder.Append('\r'); } } if (!flag) { return false; } translated = stringBuilder.ToString(); return true; } private static string TranslatePlayerNoteLine(string line) { string text = line.TrimStart(); string text2 = line.Substring(0, line.Length - text.Length); string a = text.Trim(); if (string.Equals(a, "Notes:", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Notes:", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "备注:", StringComparison.Ordinal) || string.Equals(a, "备注:", StringComparison.Ordinal) || string.Equals(a, "笔记:", StringComparison.Ordinal) || string.Equals(a, "笔记:", StringComparison.Ordinal)) { return text2 + "备注:"; } string text3 = string.Empty; string text4 = text; if (text4.StartsWith("*", StringComparison.Ordinal)) { string text5 = text4; string text6 = text5.Substring(1, text5.Length - 1).TrimStart(); text3 = "* "; text4 = text6; } if (!TranslatePlayerNoteToken(text4, out string translated)) { return line; } return text2 + text3 + translated; } private static bool TranslatePlayerNoteToken(string source, out string translated) { translated = source; translated = source.Trim().TrimEnd('.').Trim() .ToLowerInvariant() switch { "most lazy employee" => "最懒惰的员工", "the laziest employee" => "最懒惰的员工", "most profitable" => "最会赚钱的员工", "most paranoid employee" => "最偏执的员工", "the most paranoid employee" => "最偏执的员工", "sustained the most injuries" => "受伤严重的员工", "sustained the most injurie" => "受伤严重的员工", _ => string.Empty, }; return translated.Length > 0; } public static bool TranslateStatLine(string source, out string translated) { translated = source; string source2 = source.Trim(); Match match = SafeRegexMatch(source2, "^(?\\d+)\\s+(?:casualties|\\u4eba\\u4f24\\u4ea1)\\s*[:\\uff1a]\\s*(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match.Success) { translated = match.Groups["count"].Value + " 人伤亡:" + match.Groups["value"].Value.Trim(); return true; } Match match2 = SafeRegexMatch(source2, "^(?[<\\(])(?\\d+)\\s+bodies recovered(?[>\\)])$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match2.Success) { translated = match2.Groups["open"].Value + match2.Groups["count"].Value + " 具尸体已回收" + match2.Groups["close"].Value; return true; } Match match3 = SafeRegexMatch(source2, "^(?\\d+)\\s+bodies recovered$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match3.Success) { translated = match3.Groups["count"].Value + " 具尸体已回收"; return true; } Match match4 = SafeRegexMatch(source2, "^DUE:\\s*(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match4.Success) { translated = "应付:" + match4.Groups["amount"].Value.Trim(); return true; } Match match5 = SafeRegexMatch(source2, "^Days on the job\\s*:\\s*(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match5.Success) { translated = "工作天数:" + match5.Groups["value"].Value.Trim(); return true; } Match match6 = SafeRegexMatch(source2, "^Scrap value collected\\s*:\\s*(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match6.Success) { translated = "收集废料价值:" + match6.Groups["value"].Value.Trim(); return true; } Match match7 = SafeRegexMatch(source2, "^Deaths\\s*:\\s*(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match7.Success) { translated = "死亡次数:" + match7.Groups["value"].Value.Trim(); return true; } Match match8 = SafeRegexMatch(source2, "^Steps taken\\s*:\\s*(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match8.Success) { translated = "行走步数:" + match8.Groups["value"].Value.Trim(); return true; } Match match9 = SafeRegexMatch(source2, "^Your\\s+current\\s+overtime\\s+bonus\\s+is\\s+(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match9.Success) { translated = "当前加班奖励为 " + match9.Groups["value"].Value.Trim(); return true; } Match match10 = SafeRegexMatch(source2, "^Your\\s+new\\s+total\\s+credits\\s+will\\s+be\\s+(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match10.Success) { translated = "新的总信用点将为 " + match10.Groups["value"].Value.Trim(); return true; } return false; } public static bool TranslatePlayersFired(string source, out string translated) { translated = source; string text = source.Trim(); if (string.Equals(text, "YOU ARE FIRED.", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "YOU ARE FIRED", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "你被解雇了!", StringComparison.Ordinal) || string.Equals(text, "你已被公司解雇。", StringComparison.Ordinal)) { translated = "你已被公司解雇"; return true; } if (string.Equals(text, "You did not meet the profit quota before the deadline.", StringComparison.OrdinalIgnoreCase) || text.IndexOf("利润指标", StringComparison.Ordinal) >= 0 || text.IndexOf("目标金额", StringComparison.Ordinal) >= 0) { translated = "未能按期完成利润指标"; return true; } return false; } public static bool TranslatePlayerStatus(string source, out string translated) { translated = source; string source2 = source.Trim(); if (TranslateSpectatingStatus(source2, out translated)) { return true; } if (TranslateStatusToken(source2, out translated)) { return true; } Match match = SafeRegexMatch(source2, "^(?[\\s\\S]+?)(?\\r?\\n|\\s+)\\(?(?Dead|Deceased|Missing)\\)?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success || !TranslateStatusToken(match.Groups["status"].Value, out string translated2)) { return false; } translated = match.Groups["name"].Value.TrimEnd() + match.Groups["sep"].Value + translated2; return true; } private static bool TranslateSpectatingStatus(string source, out string translated) { translated = source; Match match = SafeRegexMatch(source, "^(?[<\\(\\uff08])?\\s*Spectating\\s*[:\\uff1a]\\s*(?.+?)\\s*(?[>\\)\\uff09])?(?:\\s*(?\\[[^\\]]+\\]))?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match.Success) { string text = match.Groups["name"].Value.Trim(); if (text.Length == 0) { return false; } string value = match.Groups["open"].Value; string value2 = match.Groups["close"].Value; string text2 = "正在旁观:" + text; translated = ((value == "<" || value2 == ">") ? ("<" + text2 + ">") : ((value.Length > 0 || value2.Length > 0) ? ("(" + text2 + ")") : text2)); string value3 = match.Groups["suffix"].Value; if (value3.Length > 0) { translated = translated + " " + value3; } return true; } Match match2 = SafeRegexMatch(source, "^(?[<\\((])?\\s*Spectating\\s*[::]\\s*(?.+?)\\s*(?[>\\))])?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match2.Success) { return false; } string text3 = match2.Groups["name"].Value.Trim(); if (text3.Length == 0) { return false; } string value4 = match2.Groups["open"].Value; string value5 = match2.Groups["close"].Value; string text4 = "正在旁观:" + text3; translated = ((value4 == "<" || value5 == ">") ? ("<" + text4 + ">") : ((value4.Length > 0 || value5.Length > 0) ? ("(" + text4 + ")") : text4)); return true; } private static bool TranslateStatusToken(string source, out string translated) { translated = source.Trim() switch { "(Dead)" => "(死亡)", "Dead" => "(死亡)", "Deceased" => "(死亡)", "(Deceased)" => "(死亡)", "Missing" => "(失踪)", "(Missing)" => "(失踪)", _ => string.Empty, }; return translated.Length > 0; } } internal static class HudDynamicTranslator { private static readonly Dictionary DeathCauseEntries = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Unknown"] = "未知", ["Bludgeoning"] = "钝击", ["Gravity"] = "坠落冲击", ["Blast"] = "爆炸", ["Strangulation"] = "勒颈", ["Suffocation"] = "窒息", ["Mauling"] = "撕咬", ["Gunshot"] = "枪击", ["Gunshots"] = "枪击", ["Crushing"] = "压碎", ["Drowning"] = "溺水", ["Abandoned"] = "被遗弃", ["Electrocution"] = "触电", ["Kicking"] = "踢击", ["Burning"] = "烧伤", ["Stabbing"] = "刺伤", ["Fan"] = "风扇切割", ["Inertia"] = "惯性冲击", ["Snipping"] = "剪切", ["Scratching"] = "抓伤" }; public static bool CanHandleCheap(string? source) { if (!LooksLikeLoadingInfoTextCheap(source) && !LooksLikeRandomSeedTextCheap(source) && !LooksLikeVoteTextCheap(source) && !LooksLikeDaysLeftTextCheap(source) && !LooksLikeShipLeaveEarlyWarningTextCheap(source) && !LooksLikeCombatNotificationTextCheap(source) && !LooksLikeHudStatusLineCheap(source)) { return LooksLikeHudNotificationTextCheap(source); } return true; } public static bool Translate(DynamicTextDomain domain, string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } return domain switch { DynamicTextDomain.HudScanner => TranslateScannerSubText(source, out translated) || TranslateScanValue(source, out translated) || TranslateScannerLabel(source, out translated) || TranslateFast(source, out translated), DynamicTextDomain.HudRewards => TranslateRewardLine(source, out translated) || TranslateFast(source, out translated), _ => TranslateFast(source, out translated), }; } public static bool TranslateFast(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } if (!TranslateLoadingInfoFast(source, out translated) && !TranslateRandomSeedFast(source, out translated) && !TranslateVotesFast(source, out translated) && !TranslateDaysLeftFast(source, out translated) && !TranslateCombatNotificationFast(source, out translated) && !TranslateHudStatusLineFast(source, out translated) && !TranslateShipLeaveEarlyWarning(source, out translated)) { return TranslateHudNotificationFast(source, out translated); } return true; } public static bool TranslateRandomSeed(string source, out string translated) { translated = source; Match match = SafeRegexMatch(StripRichTextTags(source).Trim(), "^Random\\s+seed\\s*:\\s*(?[+-]?\\d+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success) { return false; } translated = "随机种子:" + match.Groups["seed"].Value; return true; } public static bool TranslateVotes(string source, out string translated) { translated = source; Match match = SafeRegexMatch(StripRichTextTags(source).Trim(), "^(?[\\(\\uff08]?)(?\\d+\\s*/\\s*\\d+)\\s+Votes?(?[\\)\\uff09]?)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success) { return false; } string text = SafeRegexReplace(match.Groups["votes"].Value, "\\s+", string.Empty, RegexOptions.CultureInvariant); bool flag = match.Groups["open"].Value.Length > 0 || match.Groups["close"].Value.Length > 0; translated = (flag ? ("(" + text + " 票)") : (text + " 票")); return true; } public static bool TranslateDaysLeft(string source, out string translated) { translated = source; Match match = SafeRegexMatch(StripRichTextTags(source).Trim(), "^(?\\d+)\\s+Days?\\s+Left$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success) { return false; } translated = "剩余 " + match.Groups["days"].Value + " 天"; return true; } public static bool TranslateScanValue(string source, out string translated) { translated = source; Match match = SafeRegexMatch(StripRichTextTags(source).Trim(), "^VALUE\\s*:\\s*(?.+?)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success) { return false; } translated = "价值:" + match.Groups["value"].Value.Trim(); return true; } public static bool TranslateShipLeaveEarlyWarning(string source, out string translated) { translated = source; Match match = SafeRegexMatch(StripRichTextTagsCheap(source).Trim(), "^WARNING!\\s*Please\\s+return\\s+by\\s+(?