using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TextCore; 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")] [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__42 : 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__42(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.25f; break; } if (5__2 < 8f) { if (SyncFixedSceneLabelsInRelayScene(stage + ".relay-scene") > 0) { _fixedSceneLabelWatcher = null; _fixedSceneLabelWatcherOwner = null; return false; } <>2__current = (object)new WaitForSeconds(0.25f); <>1__state = 1; return true; } 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__41 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string stage; public HUDManager hudManager; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__41(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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.1f; break; } if ((Object)(object)hudManager != (Object)null && 5__2 < 12f) { TMP_Text text = FindSystemOnlineTitleByFullPath(); if (IsExactSystemOnlineTitle(text)) { ApplySystemOnlineNativeTranslation(text, stage + ".watcher"); _systemOnlineWatcher = null; _systemOnlineWatcherOwner = null; return false; } <>2__current = (object)new WaitForSeconds(0.1f); <>1__state = 1; return true; } 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 = 4096; 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 readonly Dictionary NativeTextRoleCache = new Dictionary(); 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) { 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); return; } 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) && _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) { 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) { 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) && _fixedSceneLabelWatcher == null) { _fixedSceneLabelWatcherOwner = hudManager; _fixedSceneLabelWatcher = ((MonoBehaviour)hudManager).StartCoroutine(WaitForFixedSceneLabels(stage)); } } public static void Shutdown() { if (_systemOnlineWatcher != null && (Object)(object)_systemOnlineWatcherOwner != (Object)null) { ((MonoBehaviour)_systemOnlineWatcherOwner).StopCoroutine(_systemOnlineWatcher); } if (_fixedSceneLabelWatcher != null && (Object)(object)_fixedSceneLabelWatcherOwner != (Object)null) { ((MonoBehaviour)_fixedSceneLabelWatcherOwner).StopCoroutine(_fixedSceneLabelWatcher); } _systemOnlineWatcher = null; _fixedSceneLabelWatcher = null; _systemOnlineWatcherOwner = null; _fixedSceneLabelWatcherOwner = null; NativeTextRoleCache.Clear(); } public static void TryReplaceSystemOnlineText(TMP_Text? text, string stage) { if ((Object)(object)text == (Object)null) { 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"); 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) { } 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"); Plugin.Log.LogInfo((object)("NativeRelay[" + stage + "] target=SystemOnline action=applied name=" + ((Object)text).name + " path=" + BuildPath(text.transform) + " text=" + text.text)); } [IteratorStateMachine(typeof(d__41))] private static IEnumerator WaitForSystemOnlineTitle(HUDManager hudManager, string stage) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__41(0) { hudManager = hudManager, stage = stage }; } [IteratorStateMachine(typeof(d__42))] private static IEnumerator WaitForFixedSceneLabels(string stage) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__42(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? 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)) { 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++; 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.TryTranslate(text.text, out string translated) && !string.IsNullOrWhiteSpace(translated)) { if (!string.Equals(text.text, translated, StringComparison.Ordinal)) { text.text = translated; } FontFallbackService.ApplyFallback(text, translated); 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) { if (NativeTextRoleCache.Count >= 4096) { NativeTextRoleCache.Clear(); } NativeTextRoleCache[((Object)text).GetInstanceID()] = 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 CustomLocalizationExtensionService { private enum MatchKind { None, Exact, Contains, Regex } private sealed class RegexEntry { public string Pattern { get; } public Regex Regex { get; } public string Replacement { get; } public bool Disabled { get; set; } public bool WarnedTimeout { get; set; } public RegexEntry(string pattern, Regex regex, string replacement) { Pattern = pattern; Regex = regex; Replacement = replacement; } } private sealed class StyleRule { public MatchKind Kind { get; } public string Pattern { get; } public Regex? Regex { get; set; } public Color? Color { get; set; } public float? FontSize { get; set; } public bool? RichText { get; set; } public bool Disabled { get; set; } public bool WarnedTimeout { get; set; } public bool HasRegex => Kind == MatchKind.Regex; public StyleRule(MatchKind kind, string pattern) { Kind = kind; Pattern = pattern; } public bool IsMatch(string value) { if (Disabled) { return false; } try { return Kind switch { MatchKind.Exact => string.Equals(value, Pattern, StringComparison.Ordinal), MatchKind.Contains => value.IndexOf(Pattern, StringComparison.Ordinal) >= 0, MatchKind.Regex => Regex != null && Regex.IsMatch(value), _ => false, }; } catch (RegexMatchTimeoutException) { Disabled = true; if (!WarnedTimeout) { WarnedTimeout = true; Plugin.Log.LogWarning((object)("CustomLocalization regex timeout; disabled style rule '" + Pattern + "'.")); } return false; } } } private sealed class CachedStyleLookup { public string Value { get; } public bool AllowRegexStyle { get; } public bool Matched { get; } public StyleRule? Style { get; } public CachedStyleLookup(string value, bool allowRegexStyle, bool matched, StyleRule? style) { Value = value; AllowRegexStyle = allowRegexStyle; Matched = matched; Style = style; } } [CompilerGenerated] private sealed class d__45 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private string pluginDir; public string <>3__pluginDir; string IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__45(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = Path.Combine(pluginDir, "V81TestChn", "custom-localization"); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = Path.Combine(pluginDir, "custom-localization"); <>1__state = 2; return true; case 2: <>1__state = -1; <>2__current = Path.Combine(Paths.ConfigPath, "V81TestChn", "custom-localization"); <>1__state = 3; return true; case 3: <>1__state = -1; <>2__current = Path.Combine(Paths.ConfigPath, "V81TestChn", "custom-translations"); <>1__state = 4; return true; case 4: <>1__state = -1; <>2__current = Path.Combine(Paths.ConfigPath, "translations", "custom"); <>1__state = 5; return true; case 5: <>1__state = -1; 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(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__45 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__45(0); } d__.pluginDir = <>3__pluginDir; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private const int StyleCacheLimit = 2048; private const int DefaultMaxExactRules = 4096; private const int DefaultMaxIgnoreCaseRules = 4096; private const int DefaultMaxRegexRules = 64; private const int DefaultMaxStyleRules = 64; private const int DefaultMaxLoadedFiles = 32; private const int DefaultMaxConfigFileBytes = 262144; private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(25.0); private static readonly Dictionary ExactEntries = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary ExactIgnoreCaseEntries = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly List RegexEntries = new List(); private static readonly List StyleRules = new List(); private static readonly Dictionary StyleCache = new Dictionary(); private static ConfigEntry? _enabled; private static ConfigEntry? _preferCustomTranslations; private static ConfigEntry? _enableRegex; private static ConfigEntry? _maxExactRules; private static ConfigEntry? _maxIgnoreCaseRules; private static ConfigEntry? _maxRegexRules; private static ConfigEntry? _maxStyleRules; private static ConfigEntry? _maxLoadedFiles; private static ConfigEntry? _maxConfigFileBytes; private static bool _warnedRegexDisabled; private static bool _warnedExactLimit; private static bool _warnedIgnoreCaseLimit; private static bool _hasStyleRules; private static bool _hasGlobalStyleRules; public static bool PreferCustomTranslations { get { ConfigEntry? enabled = _enabled; if (enabled != null && enabled.Value) { return _preferCustomTranslations?.Value ?? false; } return false; } } public static bool EnableRegex { get { ConfigEntry? enabled = _enabled; if (enabled != null && enabled.Value) { return _enableRegex?.Value ?? false; } return false; } } public static bool HasStyleRules { get { ConfigEntry? enabled = _enabled; if (enabled != null && enabled.Value) { return _hasStyleRules; } return false; } } public static bool HasGlobalStyleRules { get { ConfigEntry? enabled = _enabled; if (enabled != null && enabled.Value) { return _hasGlobalStyleRules; } return false; } } public static void Initialize(string pluginDir, ConfigFile config) { _enabled = config.Bind("CustomLocalization", "Enabled", true, "Load optional custom localization cfg files from plugin/config custom-localization directories."); _preferCustomTranslations = config.Bind("CustomLocalization", "PreferCustomTranslations", false, "When true, custom exact translations are checked before built-in translations. Regex translations are still gated by EnableRegex."); _enableRegex = config.Bind("CustomLocalization", "EnableRegex", false, "Enable custom regex translations and regex style rules. Disabled by default to keep global text paths cheap."); _maxExactRules = config.Bind("CustomLocalization", "MaxExactRules", 4096, "Maximum custom exact translation rules to load."); _maxIgnoreCaseRules = config.Bind("CustomLocalization", "MaxIgnoreCaseRules", 4096, "Maximum custom ignorecase translation rules to load."); _maxRegexRules = config.Bind("CustomLocalization", "MaxRegexRules", 64, "Maximum custom regex translation rules to load."); _maxStyleRules = config.Bind("CustomLocalization", "MaxStyleRules", 64, "Maximum custom style rules to load."); _maxLoadedFiles = config.Bind("CustomLocalization", "MaxLoadedFiles", 32, "Maximum custom localization cfg files to load."); _maxConfigFileBytes = config.Bind("CustomLocalization", "MaxConfigFileBytes", 262144, "Maximum size in bytes for each custom localization cfg file."); Load(pluginDir); } public static void Shutdown() { Clear(); _enabled = null; _preferCustomTranslations = null; _enableRegex = null; _maxExactRules = null; _maxIgnoreCaseRules = null; _maxRegexRules = null; _maxStyleRules = null; _maxLoadedFiles = null; _maxConfigFileBytes = null; } public static void Clear() { ExactEntries.Clear(); ExactIgnoreCaseEntries.Clear(); RegexEntries.Clear(); StyleRules.Clear(); ClearStyleCache(); _warnedRegexDisabled = false; _warnedExactLimit = false; _warnedIgnoreCaseLimit = false; _hasStyleRules = false; _hasGlobalStyleRules = false; } public static bool TryTranslateFastExact(string? source, out string translated) { translated = source ?? string.Empty; ConfigEntry? enabled = _enabled; if (enabled == null || !enabled.Value || string.IsNullOrEmpty(source)) { return false; } if (ExactEntries.TryGetValue(source, out translated) || ExactIgnoreCaseEntries.TryGetValue(source, out translated)) { return !string.Equals(source, translated, StringComparison.Ordinal); } return false; } public static bool TryTranslate(string? source, out string translated, bool allowRegex) { translated = source ?? string.Empty; ConfigEntry? enabled = _enabled; if (enabled == null || !enabled.Value || string.IsNullOrEmpty(source)) { return false; } if (TryTranslateFastExact(source, out translated)) { return true; } if (!allowRegex || !EnableRegex || RegexEntries.Count == 0) { return false; } foreach (RegexEntry regexEntry in RegexEntries) { if (regexEntry.Disabled) { continue; } try { if (!regexEntry.Regex.IsMatch(source)) { continue; } translated = regexEntry.Regex.Replace(source, regexEntry.Replacement); return !string.Equals(source, translated, StringComparison.Ordinal); } catch (RegexMatchTimeoutException) { regexEntry.Disabled = true; if (!regexEntry.WarnedTimeout) { regexEntry.WarnedTimeout = true; Plugin.Log.LogWarning((object)("CustomLocalization regex timeout; disabled rule '" + regexEntry.Pattern + "'.")); } } } return false; } public static void ApplyStyle(TMP_Text? text, string? value, bool allowRegexStyle = false) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)text == (Object)null) && TryFindStyle((Component)(object)text, value, allowRegexStyle, out StyleRule style)) { if (style.RichText.HasValue) { text.richText = style.RichText.Value; } if (style.Color.HasValue) { ((Graphic)text).color = style.Color.Value; } if (style.FontSize.HasValue) { text.fontSize = style.FontSize.Value; } } } public static void ApplyStyle(Text? text, string? value, bool allowRegexStyle = false) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)text == (Object)null) && TryFindStyle((Component)(object)text, value, allowRegexStyle, out StyleRule style)) { if (style.RichText.HasValue) { text.supportRichText = style.RichText.Value; } if (style.Color.HasValue) { ((Graphic)text).color = style.Color.Value; } if (style.FontSize.HasValue) { text.fontSize = Mathf.RoundToInt(style.FontSize.Value); } } } public static void ApplyStyle(TextMesh? text, string? value, bool allowRegexStyle = false) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)text == (Object)null) && TryFindStyle((Component)(object)text, value, allowRegexStyle, out StyleRule style)) { if (style.RichText.HasValue) { text.richText = style.RichText.Value; } if (style.Color.HasValue) { text.color = style.Color.Value; } if (style.FontSize.HasValue) { text.fontSize = Mathf.RoundToInt(style.FontSize.Value); } } } private static void Load(string pluginDir) { Clear(); ConfigEntry? enabled = _enabled; if (enabled == null || !enabled.Value) { return; } int num = 0; int num2 = 0; int maxLoadedFiles = GetMaxLoadedFiles(); foreach (string item in ResolveCustomLocalizationDirectories(pluginDir)) { if (!Directory.Exists(item)) { continue; } foreach (string item2 in Directory.EnumerateFiles(item, "*.cfg", SearchOption.AllDirectories)) { if (num2 >= maxLoadedFiles) { Plugin.Log.LogWarning((object)$"CustomLocalization MaxLoadedFiles={maxLoadedFiles} reached; remaining cfg files skipped."); LogLoadSummary(num); return; } num2++; if (LoadCfgFile(item2)) { num++; } } } LogLoadSummary(num); } private static void LogLoadSummary(int filesLoaded) { Plugin.Log.LogInfo((object)$"CustomLocalization loaded files={filesLoaded}; exact={ExactEntries.Count}; ignorecase={ExactIgnoreCaseEntries.Count}; regex={RegexEntries.Count}; style={StyleRules.Count}."); } [IteratorStateMachine(typeof(d__45))] private static IEnumerable ResolveCustomLocalizationDirectories(string pluginDir) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__45(-2) { <>3__pluginDir = pluginDir }; } private static bool LoadCfgFile(string file) { bool flag = false; try { FileInfo fileInfo = new FileInfo(file); int maxConfigFileBytes = GetMaxConfigFileBytes(); if (fileInfo.Length > maxConfigFileBytes) { Plugin.Log.LogWarning((object)$"CustomLocalization skipped '{file}' because it is {fileInfo.Length} bytes; MaxConfigFileBytes={maxConfigFileBytes}."); return false; } foreach (string item in File.ReadLines(file)) { string text = item.Trim(); if (text.Length != 0 && !text.StartsWith("#", StringComparison.Ordinal) && !text.StartsWith("//", StringComparison.Ordinal)) { flag |= TryAddLine(text, file); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("CustomLocalization failed reading '" + file + "': " + ex.GetType().Name + ": " + ex.Message)); } return flag; } private static bool TryAddLine(string line, string file) { string text; int length; if (line.StartsWith("style:", StringComparison.OrdinalIgnoreCase)) { text = line; length = "style:".Length; return TryAddStyleRule(text.Substring(length, text.Length - length), file); } int num = FindUnescapedSeparator(line, '='); if (num <= 0) { return false; } string text2 = line.Substring(0, num).Trim(); text = line; length = num + 1; string text3 = UnescapeCustomValue(text.Substring(length, text.Length - length)); if (text2.StartsWith("regex:", StringComparison.OrdinalIgnoreCase) || text2.StartsWith("r:", StringComparison.OrdinalIgnoreCase)) { string text4; if (!text2.StartsWith("regex:", StringComparison.OrdinalIgnoreCase)) { text = text2; length = "r:".Length; text4 = text.Substring(length, text.Length - length); } else { text = text2; length = "regex:".Length; text4 = text.Substring(length, text.Length - length); } return TryAddRegex(UnescapeCustomRegexPattern(text4.Trim()).Trim(), text3, file); } if (text2.StartsWith("ignorecase:", StringComparison.OrdinalIgnoreCase) || text2.StartsWith("i:", StringComparison.OrdinalIgnoreCase)) { string value; if (!text2.StartsWith("ignorecase:", StringComparison.OrdinalIgnoreCase)) { text = text2; length = "i:".Length; value = text.Substring(length, text.Length - length); } else { text = text2; length = "ignorecase:".Length; value = text.Substring(length, text.Length - length); } return TryAddExact(UnescapeCustomValue(value).Trim(), text3, ignoreCase: true, file); } string text5 = text2; if (text5.StartsWith("exact:", StringComparison.OrdinalIgnoreCase)) { text = text5; length = "exact:".Length; text5 = text.Substring(length, text.Length - length).Trim(); } text5 = UnescapeCustomValue(text5).Trim(); return TryAddExact(text5, text3, ignoreCase: false, file); } private static bool TryAddExact(string key, string value, bool ignoreCase, string file) { if (string.IsNullOrWhiteSpace(key)) { return false; } if (ignoreCase) { if (!ExactIgnoreCaseEntries.ContainsKey(key) && ExactIgnoreCaseEntries.Count >= GetMaxIgnoreCaseRules()) { WarnIgnoreCaseLimitOnce(file); return false; } ExactIgnoreCaseEntries[key] = value; return true; } if (!ExactEntries.ContainsKey(key) && ExactEntries.Count >= GetMaxExactRules()) { WarnExactLimitOnce(file); return false; } ExactEntries[key] = value; return true; } private static bool TryAddRegex(string pattern, string replacement, string file) { if (string.IsNullOrWhiteSpace(pattern)) { return false; } if (!EnableRegex) { WarnRegexDisabledOnce(); return false; } int maxRegexRules = GetMaxRegexRules(); if (RegexEntries.Count >= maxRegexRules) { Plugin.Log.LogWarning((object)$"CustomLocalization skipped regex in '{file}' because MaxRegexRules={maxRegexRules} was reached."); return false; } try { RegexEntries.Add(new RegexEntry(pattern, new Regex(pattern, RegexOptions.Compiled | RegexOptions.CultureInvariant, RegexTimeout), replacement)); return true; } catch (Exception ex) when (((ex is ArgumentException || ex is RegexMatchTimeoutException) ? 1 : 0) != 0) { Plugin.Log.LogWarning((object)("CustomLocalization skipped invalid regex in '" + file + "': " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static bool TryAddStyleRule(string payload, string file) { //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) List list = SplitUnescaped(payload, '|'); if (list.Count == 0) { return false; } int maxStyleRules = GetMaxStyleRules(); if (StyleRules.Count >= maxStyleRules) { Plugin.Log.LogWarning((object)$"CustomLocalization skipped style rule in '{file}' because MaxStyleRules={maxStyleRules} was reached."); return false; } string text = list[0].Trim(); int num = FindUnescapedSeparator(text, ':'); if (num <= 0 || num >= text.Length - 1) { return false; } MatchKind matchKind = ParseMatchKind(UnescapeCustomValue(text.Substring(0, num))); string text2 = text; int num2 = num + 1; string value = text2.Substring(num2, text2.Length - num2).Trim(); string text3 = ((matchKind == MatchKind.Regex) ? UnescapeCustomRegexPattern(value) : UnescapeCustomValue(value)); text3 = text3.Trim(); if (matchKind == MatchKind.None || string.IsNullOrWhiteSpace(text3)) { return false; } if (matchKind == MatchKind.Regex && !EnableRegex) { WarnRegexDisabledOnce(); return false; } StyleRule styleRule = new StyleRule(matchKind, text3); Color val = default(Color); for (int i = 1; i < list.Count; i++) { int num3 = FindUnescapedSeparator(list[i], '='); if (num3 <= 0) { continue; } string text4 = UnescapeCustomValue(list[i].Substring(0, num3)).Trim(); text2 = list[i]; num2 = num3 + 1; string text5 = UnescapeCustomValue(text2.Substring(num2, text2.Length - num2)).Trim(); float result; bool result2; if (text4.Equals("color", StringComparison.OrdinalIgnoreCase) && ColorUtility.TryParseHtmlString(text5, ref val)) { if (val.a <= 0f) { Plugin.Log.LogWarning((object)("CustomLocalization style in '" + file + "' has alpha=0 and will be invisible.")); } styleRule.Color = val; } else if (text4.Equals("fontSize", StringComparison.OrdinalIgnoreCase) && float.TryParse(text5, NumberStyles.Float, CultureInfo.InvariantCulture, out result) && result > 0f) { styleRule.FontSize = Mathf.Clamp(result, 4f, 128f); } else if (text4.Equals("richText", StringComparison.OrdinalIgnoreCase) && bool.TryParse(text5, out result2)) { styleRule.RichText = result2; } } if (!styleRule.Color.HasValue && !styleRule.FontSize.HasValue && !styleRule.RichText.HasValue) { return false; } if (matchKind == MatchKind.Regex) { try { styleRule.Regex = new Regex(text3, RegexOptions.Compiled | RegexOptions.CultureInvariant, RegexTimeout); } catch (Exception ex) when (((ex is ArgumentException || ex is RegexMatchTimeoutException) ? 1 : 0) != 0) { Plugin.Log.LogWarning((object)("CustomLocalization skipped invalid style regex in '" + file + "': " + ex.GetType().Name + ": " + ex.Message)); return false; } } StyleRules.Add(styleRule); _hasStyleRules = true; if (!styleRule.HasRegex) { _hasGlobalStyleRules = true; } ClearStyleCache(); return true; } private static bool TryFindStyle(Component component, string? value, bool allowRegexStyle, out StyleRule style) { style = null; ConfigEntry? enabled = _enabled; if (enabled == null || !enabled.Value || string.IsNullOrEmpty(value)) { return false; } int instanceID = ((Object)component).GetInstanceID(); if (TryGetCachedStyle(instanceID, value, allowRegexStyle, out var matched, out style)) { return matched; } bool flag = TryFindStyle(value, allowRegexStyle, out style); CacheStyleResult(instanceID, value, allowRegexStyle, flag, style); return flag; } private static bool TryFindStyle(string value, bool allowRegexStyle, out StyleRule style) { style = null; foreach (StyleRule styleRule in StyleRules) { if ((!styleRule.HasRegex || allowRegexStyle) && styleRule.IsMatch(value)) { style = styleRule; return true; } } return false; } private static bool TryGetCachedStyle(int componentId, string value, bool allowRegexStyle, out bool matched, out StyleRule style) { style = null; matched = false; if (!StyleCache.TryGetValue(componentId, out CachedStyleLookup value2) || !string.Equals(value2.Value, value, StringComparison.Ordinal) || value2.AllowRegexStyle != allowRegexStyle) { return false; } style = value2.Style; matched = value2.Matched; return true; } private static void CacheStyleResult(int componentId, string value, bool allowRegexStyle, bool matched, StyleRule? style) { if (StyleCache.Count >= 2048) { StyleCache.Clear(); } StyleCache[componentId] = new CachedStyleLookup(value, allowRegexStyle, matched, style); } private static void ClearStyleCache() { StyleCache.Clear(); } public static void ClearStyleCacheOnly() { ClearStyleCache(); } public static void ClearRuntimeCaches() { ClearStyleCache(); } private static int FindUnescapedSeparator(string value, char separator) { bool flag = false; bool flag2 = false; for (int i = 0; i < value.Length; i++) { char c = value[i]; if (flag) { flag = false; continue; } switch (c) { case '\\': flag = true; continue; case '<': flag2 = true; continue; case '>': flag2 = false; continue; } if (!flag2 && c == separator) { return i; } } return -1; } private static List SplitUnescaped(string value, char separator) { List list = new List(); int num2; for (int i = 0; i <= value.Length; i += num2 + 1) { string text = value; int num = i; num2 = FindUnescapedSeparator(text.Substring(num, text.Length - num), separator); if (num2 < 0) { text = value; num = i; list.Add(text.Substring(num, text.Length - num)); break; } list.Add(value.Substring(i, num2)); } return list; } private static string UnescapeCustomValue(string value) { if (value.IndexOf('\\') < 0) { return value; } StringBuilder stringBuilder = new StringBuilder(value.Length); bool flag = false; foreach (char c in value) { if (!flag) { if (c == '\\') { flag = true; } else { stringBuilder.Append(c); } continue; } StringBuilder stringBuilder2 = stringBuilder; stringBuilder2.Append(c switch { 'n' => '\n', 'r' => '\r', 't' => '\t', _ => c, }); flag = false; } if (flag) { stringBuilder.Append('\\'); } return stringBuilder.ToString(); } private static string UnescapeCustomRegexPattern(string value) { if (value.IndexOf('\\') < 0) { return value; } StringBuilder stringBuilder = new StringBuilder(value.Length); bool flag = false; foreach (char c in value) { if (!flag) { if (c == '\\') { flag = true; } else { stringBuilder.Append(c); } continue; } if ((c == '=' || c == '\\' || c == '|') ? true : false) { stringBuilder.Append(c); } else { stringBuilder.Append('\\'); stringBuilder.Append(c); } flag = false; } if (flag) { stringBuilder.Append('\\'); } return stringBuilder.ToString(); } private static void WarnRegexDisabledOnce() { if (!_warnedRegexDisabled) { _warnedRegexDisabled = true; Plugin.Log.LogWarning((object)"CustomLocalization.EnableRegex=false; skipped custom regex rule(s)."); } } private static void WarnExactLimitOnce(string file) { if (!_warnedExactLimit) { _warnedExactLimit = true; Plugin.Log.LogWarning((object)$"CustomLocalization skipped exact rule in '{file}' because MaxExactRules={GetMaxExactRules()} was reached."); } } private static void WarnIgnoreCaseLimitOnce(string file) { if (!_warnedIgnoreCaseLimit) { _warnedIgnoreCaseLimit = true; Plugin.Log.LogWarning((object)$"CustomLocalization skipped ignorecase rule in '{file}' because MaxIgnoreCaseRules={GetMaxIgnoreCaseRules()} was reached."); } } private static int GetMaxExactRules() { return Math.Max(0, _maxExactRules?.Value ?? 4096); } private static int GetMaxIgnoreCaseRules() { return Math.Max(0, _maxIgnoreCaseRules?.Value ?? 4096); } private static int GetMaxRegexRules() { return Math.Max(0, _maxRegexRules?.Value ?? 64); } private static int GetMaxStyleRules() { return Math.Max(0, _maxStyleRules?.Value ?? 64); } private static int GetMaxLoadedFiles() { return Math.Max(0, _maxLoadedFiles?.Value ?? 32); } private static int GetMaxConfigFileBytes() { return Math.Max(1024, _maxConfigFileBytes?.Value ?? 262144); } private static MatchKind ParseMatchKind(string value) { if (value.Equals("exact", StringComparison.OrdinalIgnoreCase)) { return MatchKind.Exact; } if (value.Equals("contains", StringComparison.OrdinalIgnoreCase)) { return MatchKind.Contains; } if (!value.Equals("regex", StringComparison.OrdinalIgnoreCase) && !value.Equals("r", StringComparison.OrdinalIgnoreCase)) { return MatchKind.None; } return MatchKind.Regex; } } internal static class TranslationService { internal static class ChatDynamicTranslator { public static bool CanHandleCheap(string? source) { if (string.IsNullOrWhiteSpace(source)) { return false; } string text = StripRichTextTagsCheap(source).Trim(); if (!text.EndsWith(" joined the ship.", StringComparison.OrdinalIgnoreCase) && !text.EndsWith(" started the ship.", StringComparison.OrdinalIgnoreCase) && !text.EndsWith(" disconnected.", StringComparison.OrdinalIgnoreCase) && !text.EndsWith(" was left behind.", StringComparison.OrdinalIgnoreCase) && !text.EndsWith(" was kicked.", StringComparison.OrdinalIgnoreCase)) { return text.EndsWith(" died.", StringComparison.OrdinalIgnoreCase); } return true; } public static bool Translate(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } string text = source.Trim(); if (TryUnwrapColorTag(text, out string openTag, out string body, out string closeTag) && Translate(body, out string translated2)) { translated = openTag + translated2 + closeTag; return true; } (string, string)[] array = new(string, string)[6] { (" joined the ship.", " 加入了飞船。"), (" started the ship.", " 启动了飞船。"), (" disconnected.", " 断开了连接。"), (" was left behind.", " 被抛下了。"), (" was kicked.", " 被踢出了。"), (" died.", " 死亡了。") }; for (int i = 0; i < array.Length; i++) { var (text2, text3) = array[i]; if (text.EndsWith(text2, StringComparison.OrdinalIgnoreCase) && text.Length > text2.Length) { string text4 = text; int length = text2.Length; translated = text4.Substring(0, text4.Length - length) + text3; return true; } } return false; } private static bool TryUnwrapColorTag(string source, out string openTag, out string body, out string closeTag) { openTag = string.Empty; body = source; closeTag = string.Empty; if (!source.StartsWith("", StringComparison.OrdinalIgnoreCase)) { return false; } int num = source.IndexOf('>'); if (num <= 0) { return false; } openTag = source.Substring(0, num + 1); closeTag = source.Substring(source.Length - "".Length, "".Length); body = source.Substring(num + 1, source.Length - num - 1 - "".Length); return true; } } 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 source2 = source.Trim(); Match match = SafeRegexMatch(source2, "^Drop\\s+(?.+?)\\s*[:\\uff1a]\\s*(?\\[[^\\]]+\\])$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match.Success) { string text = BuildTerminalLocalizedItemName(match.Groups["item"].Value.Trim()); translated = "丢弃 " + text + ":" + match.Groups["key"].Value.Trim(); return true; } Match match2 = SafeRegexMatch(source2, "^(?.+?)\\s*[:\\uff1a]\\s*(?\\[[^\\]]+\\])(?.*)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match2.Success) { return false; } bool impliesHold; string key = NormalizeAction(match2.Groups["action"].Value, out impliesHold); if (!ControlTipActionEntries.TryGetValue(key, out string value)) { return false; } string text2 = match2.Groups["key"].Value.Trim(); string text3 = NormalizeSuffix(match2.Groups["suffix"].Value, impliesHold); translated = value + ":" + text2 + text3; 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; } } internal static class EndGameDynamicTranslator { public static bool CanHandleCheap(string? source) { if (!LooksLikeEndgameStatTextCheap(source) && !LooksLikeVoteTextCheap(source) && !LooksLikeDaysLeftTextCheap(source) && (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 (!TranslateStatLine(source, out translated) && !TranslatePlayersFired(source, out translated) && !HudDynamicTranslator.TranslateVotes(source, out translated) && !HudDynamicTranslator.TranslateDaysLeft(source, out translated)) { return TranslatePlayerStatus(source, out translated); } return true; } 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; } return false; } public static bool TranslatePlayersFired(string source, out string translated) { translated = source; string a = source.Trim(); if (string.Equals(a, "YOU ARE FIRED.", StringComparison.OrdinalIgnoreCase)) { translated = "你被解雇了!"; return true; } if (string.Equals(a, "You did not meet the profit quota before the deadline.", StringComparison.OrdinalIgnoreCase)) { translated = "你未能在截止日期前达到目标金额"; return true; } return false; } public static bool TranslatePlayerStatus(string source, out string translated) { translated = source; string source2 = source.Trim(); 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 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 { public static bool CanHandleCheap(string? source) { if (!LooksLikeRandomSeedTextCheap(source) && !LooksLikeVoteTextCheap(source)) { return LooksLikeDaysLeftTextCheap(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 => TranslateScanValue(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 (!TranslateRandomSeedFast(source, out translated) && !TranslateVotesFast(source, out translated)) { return TranslateDaysLeftFast(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 TranslateRewardLine(string source, out string translated) { translated = source; string text = source.Trim(); if (text.Length == 0) { return false; } Match match = SafeRegexMatch(text, "^TOTAL\\s*[:\\uff1a]\\s*(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match.Success) { translated = "总计:" + match.Groups["amount"].Value.Trim(); return true; } Match match2 = SafeRegexMatch(StripRichTextTags(text), "^Value\\s*[:\\uff1a]\\s*(?.+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match2.Success) { translated = "价值:" + match2.Groups["value"].Value.Trim(); return true; } Match match3 = SafeRegexMatch(text, "^(?.+?)\\s+collected!$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match3.Success) { string text2 = BuildTerminalLocalizedItemName(match3.Groups["item"].Value.Trim()); translated = text2 + "已收集!"; return true; } Match match4 = SafeRegexMatch(text, "^(?\\$?\\s*[+-]?\\d+(?:\\.\\d+)?)\\s+Collected$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match4.Success) { translated = SafeRegexReplace(match4.Groups["amount"].Value, "\\s+", string.Empty, RegexOptions.CultureInvariant) + " 已收集"; return true; } Match match5 = SafeRegexMatch(text, "^(?.+?)\\s*(?\\(x\\d+\\))?\\s*[:\\uff1a]\\s*(?\\$?\\s*[+-]?\\d+(?:\\.\\d+)?)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match5.Success) { return false; } string text3 = match5.Groups["item"].Value.Trim(); if (text3.Equals("DUE", StringComparison.OrdinalIgnoreCase) || text3.Equals("VALUE", StringComparison.OrdinalIgnoreCase) || text3.Equals("Random seed", StringComparison.OrdinalIgnoreCase) || text3.Equals("DEADLINE", StringComparison.OrdinalIgnoreCase) || text3.Equals("PROFIT QUOTA", StringComparison.OrdinalIgnoreCase)) { return false; } string text4 = BuildTerminalLocalizedItemName(text3); string text5 = match5.Groups["count"].Value.Trim(); string text6 = SafeRegexReplace(match5.Groups["value"].Value, "\\s+", string.Empty, RegexOptions.CultureInvariant); translated = ((text5.Length == 0) ? (text4 + ":" + text6) : (text4 + " " + text5 + ":" + text6)); return true; } private static bool TranslateRandomSeedFast(string source, out string translated) { translated = source; string text = StripRichTextTagsCheap(source).Trim(); if (!text.StartsWith("Random seed:", StringComparison.OrdinalIgnoreCase)) { return false; } string text2 = text; int length = "Random seed:".Length; string text3 = text2.Substring(length, text2.Length - length).Trim(); if (!LooksLikeSignedInteger(text3)) { return false; } translated = "随机种子:" + text3; return true; } private static bool TranslateVotesFast(string source, out string translated) { translated = source; string text = StripRichTextTagsCheap(source).Trim(); int num; if (text.Length >= 2) { if (text[0] == '(') { if (text[text.Length - 1] == ')') { num = 1; goto IL_0061; } } if (text[0] == '(') { num = ((text[text.Length - 1] == ')') ? 1 : 0); } else { num = 0; } } else { num = 0; } goto IL_0061; IL_0061: bool flag = (byte)num != 0; string text2; string text3; if (!flag) { text2 = text; } else { text3 = text; text2 = text3.Substring(1, text3.Length - 1 - 1).Trim(); } string text4 = text2; int num2 = (text4.EndsWith(" Votes", StringComparison.OrdinalIgnoreCase) ? " Votes".Length : (text4.EndsWith(" Vote", StringComparison.OrdinalIgnoreCase) ? " Vote".Length : 0)); if (num2 == 0) { return false; } text3 = text4; int num3 = num2; string text5 = RemoveAsciiWhitespace(text3.Substring(0, text3.Length - num3)); int num4 = text5.IndexOf('/'); if (num4 > 0 && num4 < text5.Length - 1 && AllDigits(text5.Substring(0, num4))) { text3 = text5; num3 = num4 + 1; if (AllDigits(text3.Substring(num3, text3.Length - num3))) { translated = (flag ? ("(" + text5 + " 票)") : (text5 + " 票")); return true; } } return false; } private static bool TranslateDaysLeftFast(string source, out string translated) { translated = source; string text = StripRichTextTagsCheap(source).Trim(); int num = (text.EndsWith(" Days Left", StringComparison.OrdinalIgnoreCase) ? " Days Left".Length : (text.EndsWith(" Day Left", StringComparison.OrdinalIgnoreCase) ? " Day Left".Length : 0)); if (num == 0) { return false; } string text2 = text; int num2 = num; string text3 = text2.Substring(0, text2.Length - num2).Trim(); if (!AllDigits(text3)) { return false; } translated = "剩余 " + text3 + " 天"; return true; } } internal static class PlanetInfoDynamicTranslator { public static bool CanHandleCheap(string? source) { return LooksLikePlanetInfoTextCheap(source); } public static bool Translate(string? source, out string translated) { translated = source ?? string.Empty; if (!string.IsNullOrWhiteSpace(source)) { return TranslateLine(source, out translated); } return false; } public static bool TranslateFast(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source) || !CanHandleCheap(source)) { return false; } if (source.IndexOf('\n') < 0) { return TranslateLineFast(source, out 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); int num4 = num; string text = source.Substring(num4, num3 - num4); 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 (TranslateLineFast(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; } if (!flag) { return false; } translated = stringBuilder.ToString(); return true; } public static bool TranslateLine(string source, out string translated) { translated = source; string text = StripRichTextTags(source).Trim(); if (text.Length == 0) { return false; } (string, string, bool)[] array = new(string, string, bool)[13] { ("CELESTIAL BODY:", "天体:", false), ("CELESTIAL_BODY:", "天体:", false), ("天体:", "天体:", false), ("天体:", "天体:", false), ("POPULATION:", "人口:", true), ("人口:", "人口:", true), ("人口:", "人口:", true), ("CONDITIONS:", "环境:", true), ("环境:", "环境:", true), ("环境:", "环境:", true), ("FAUNA:", "生态:", true), ("生态:", "生态:", true), ("生态:", "生态:", true) }; for (int i = 0; i < array.Length; i++) { var (text2, text3, flag) = array[i]; if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase)) { string text4 = text; int length = text2.Length; string text5 = text4.Substring(length, text4.Length - length).Trim(); translated = ((text5.Length == 0) ? text3 : (text3 + " " + (flag ? TranslateKnownValue(text5) : text5))); return true; } } return false; } public static string TranslateKnownValue(string value) { if (TryTranslateExact(value, out string translated) && !string.Equals(translated, value, StringComparison.Ordinal)) { return SanitizeTranslatedText(translated); } if (TryTranslateRegex(value, out string translated2) && !string.Equals(translated2, value, StringComparison.Ordinal)) { return SanitizeTranslatedText(translated2); } if (TryTranslateKnownPlanetName(value, out string translated3)) { return translated3; } string text = TranslateKnownValueCore(NormalizeLoose(value)); if (text.Length != 0) { return text; } return value; } private static bool TranslateLineFast(string source, out string translated) { translated = source; int num = source.Length - source.TrimStart().Length; string leading = ((num > 0) ? source.Substring(0, num) : string.Empty); string trimmed = StripRichTextTagsCheap(source).Trim(); if (trimmed.Length == 0) { return false; } string rewritten = source; bool result = TryRewrite("CELESTIAL BODY:", "天体:", translateValue: false) || TryRewrite("CELESTIAL_BODY:", "天体:", translateValue: false) || TryRewrite("天体:", "天体:", translateValue: false) || TryRewrite("天体:", "天体:", translateValue: false) || TryRewrite("POPULATION:", "人口:", translateValue: true) || TryRewrite("人口:", "人口:", translateValue: true) || TryRewrite("人口:", "人口:", translateValue: true) || TryRewrite("CONDITIONS:", "环境:", translateValue: true) || TryRewrite("环境:", "环境:", translateValue: true) || TryRewrite("环境:", "环境:", translateValue: true) || TryRewrite("FAUNA:", "生态:", translateValue: true) || TryRewrite("生态:", "生态:", translateValue: true) || TryRewrite("生态:", "生态:", translateValue: true); translated = rewritten; return result; bool TryRewrite(string label, string localizedLabel, bool translateValue) { if (!trimmed.StartsWith(label, StringComparison.OrdinalIgnoreCase)) { return false; } string text = trimmed; int length = label.Length; string text2 = text.Substring(length, text.Length - length).Trim(); rewritten = ((text2.Length == 0) ? (leading + localizedLabel) : (leading + localizedLabel + " " + (translateValue ? TranslateKnownValueFast(text2) : text2))); return true; } } private static string TranslateKnownValueFast(string value) { if (TryTranslateExact(value, out string translated) && !string.Equals(translated, value, StringComparison.Ordinal)) { return SanitizeTranslatedText(translated); } string text = TranslateKnownValueCore(NormalizeLoose(value)); if (text.Length != 0) { return text; } return value; } private static string TranslateKnownValueCore(string normalized) { return normalized switch { "None" => "无", "Unknown" => "未知", "Abandoned" => "废弃", "Arid. Thick haze, worsened by industrial artifacts." => "干旱。浓雾因工业废弃物而加重。", "Arid. Low habitability, worsened by industrial artifacts." => "干旱。宜居性低,并因工业废弃物而加重。", "Waning forests. Abandoned facilities littered across the landscape." => "衰败森林,废弃设施遍布地表。", "Rumored active machinery left behind." => "传闻有活跃机械遗留。", "Dominated by a few species." => "由少数物种主导。", "Jagged and weathered terrain." => "崎岖且风化的地形。", "Ecosystem supports territorial behaviour." => "生态系统支持领地行为。", "Ecosystem supports territorial behavior." => "生态系统支持领地行为。", _ => string.Empty, }; } } internal static class TerminalDynamicTranslator { public static bool CanHandleCheap(string? source) { if (string.IsNullOrWhiteSpace(source)) { return false; } string text = source.Trim(); bool flag = text.StartsWith("Ordered ", StringComparison.OrdinalIgnoreCase) && (text.IndexOf("new balance", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("新余额", StringComparison.Ordinal) >= 0 || text.IndexOf("新餘額", StringComparison.Ordinal) >= 0); if (!(text.StartsWith("You have requested to order ", StringComparison.OrdinalIgnoreCase) || flag) && !text.StartsWith("The Company is buying", StringComparison.OrdinalIgnoreCase) && !text.Equals("Cancelled order.", StringComparison.OrdinalIgnoreCase)) { return text.Equals("You have cancelled the order.", StringComparison.OrdinalIgnoreCase); } return true; } public static bool Translate(DynamicTextDomain domain, string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } if (!TranslateOrderRequest(source, out translated) && !TranslateOrderedItemConfirmation(source, out translated) && !TranslateTerminalStatus(source, out translated) && !TranslateCompanyBuyingStatus(source, out translated)) { return TryTranslateMapScreenDescription(source, out translated); } return true; } public static bool TranslateTerminalStatus(string source, out string translated) { translated = source; string text = source.Trim(); if (text.Equals("Cancelled order.", StringComparison.OrdinalIgnoreCase)) { translated = "已取消订单。"; return true; } if (text.Equals("You have cancelled the order.", StringComparison.OrdinalIgnoreCase)) { translated = "你已取消订单。"; return true; } return false; } public static bool TranslateCompanyBuyingStatus(string source, out string translated) { translated = source; string source2 = source.Replace("\r\n", "\n").Trim(); Match match = SafeRegexMatch(source2, "^The Company is buying at (?.+?)\\.\\s*Do you want to route the autopilot to the Company building\\?\\s*Please CONFIRM or DENY\\.?\\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); if (match.Success) { translated = "公司当前收购比例为 " + match.Groups["percent"].Value.Trim() + "。是否将自动驾驶航线设为公司大楼?\n请输入 CONFIRM 或 DENY。"; return true; } Match match2 = SafeRegexMatch(source2, "^The Company is buying(?: your goods)? at (?.+?)\\.$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match2.Success) { return false; } translated = "公司当前收购比例为 " + match2.Groups["percent"].Value.Trim() + "。"; return true; } public static bool TranslateOrderRequest(string source, out string translated) { translated = source; string text = source.Replace("\r\n", "\n").Trim(); Match match = SafeRegexMatch(text, "^You have requested to order (?.+?)\\.\\s*(?You have a free warranty!\\s*)?(?:(?:Total cost(?: of items?)?|商品总价)\\s*:?\\s*(?.+?)\\.)\\s*Please CONFIRM or DENY\\.?\\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); if (match.Success) { string text2 = BuildTerminalLocalizedItemName(match.Groups["item"].Value); string text3 = (match.Groups["warranty"].Success ? "\n你享有免费保修!" : string.Empty); string text4 = NormalizeTransactionCost(match.Groups["cost"].Value); translated = "\n\n你已请求订购 " + text2 + "。" + text3 + "\n商品总价:" + text4 + "。\n\n请输入 CONFIRM 或 DENY。\n\n"; return true; } Match match2 = SafeRegexMatch(text, "^(?:\\u60a8|\\u4f60)\\s*(?:\\u5df2)?\\u8bf7\\u6c42\\u8ba2\\u8d2d\\s*(?.+?)[\\u3002.]\\s*(?\\u4f60\\u4eab\\u6709\\u514d\\u8d39\\u4fdd\\u4fee\\uff01\\s*)?(?:\\u5546\\u54c1\\u603b\\u4ef7|\\u7269\\u54c1\\u603b\\u4ef7|\\u5355\\u4ef6\\u603b\\u4ef7)\\s*[:\\uff1a.\\s]*(?\\$?\\s*[+-]?\\d+(?:\\.\\d+)?)(?:[\\s::.。]*)\\s*\\u8bf7\\u8f93\\u5165\\s+CONFIRM\\s+\\u6216\\s+DENY[\\u3002.]?\\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); if (match2.Success) { string text5 = BuildTerminalLocalizedItemName(match2.Groups["item"].Value); string text6 = (match2.Groups["warranty"].Success ? "\n你享有免费保修!" : string.Empty); string text7 = NormalizeTransactionCost(match2.Groups["cost"].Value); translated = "\n\n你已请求订购 " + text5 + "。" + text6 + "\n商品总价:" + text7 + "。\n\n请输入 CONFIRM 或 DENY。\n\n"; return true; } Match match3 = SafeRegexMatch(text, "^You have a free warranty!\\s+(?:(?:Total cost(?: of items?)?|商品总价)\\s*:?\\s*(?.+?)\\.)\\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); if (match3.Success) { translated = "你享有免费保修!\n商品总价:" + NormalizeTransactionCost(match3.Groups["cost"].Value) + "。"; return true; } Match match4 = SafeRegexMatch(text, "^You have requested to order (?.+?)\\.\\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); if (match4.Success) { translated = "你已请求订购 " + BuildTerminalLocalizedItemName(match4.Groups["item"].Value) + "。"; return true; } Match match5 = SafeRegexMatch(text, "^(?:您|你)已请求订购\\s+(?.+?)[。.]?\\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); if (match5.Success) { translated = "你已请求订购 " + BuildTerminalLocalizedItemName(match5.Groups["item"].Value) + "。"; return true; } if (string.Equals(text, "You have a free warranty!", StringComparison.OrdinalIgnoreCase)) { translated = "你享有免费保修!"; return true; } Match match6 = SafeRegexMatch(text, "^(?:Total cost(?: of items?)?|商品总价)\\s*:?\\s*(?.+?)\\.?\\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); if (match6.Success) { translated = "商品总价:" + NormalizeTransactionCost(match6.Groups["cost"].Value) + "。"; return true; } if (SafeRegexMatch(text, "^Please\\s+CONFIRM\\s+or\\s+DENY\\.?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success) { translated = "请输入 CONFIRM 或 DENY。"; return true; } return false; } public static string NormalizeTransactionCost(string cost) { string text = SafeRegexReplace(cost ?? string.Empty, "\\s+", " ", RegexOptions.CultureInvariant).Trim(); Match match = SafeRegexMatch(text, "(?\\$?\\s*[+-]?\\d+(?:\\.\\d+)?)", RegexOptions.CultureInvariant); if (!match.Success) { return text.Trim(' ', '.', ':', ':', '。'); } return SafeRegexReplace(match.Groups["cost"].Value, "\\s+", string.Empty, RegexOptions.CultureInvariant); } public static bool TranslateOrderedItemConfirmation(string source, out string translated) { translated = source; if (!TrySafeRegexMatch(OrderedTerminalItemConfirmationRegex, source, out Match match) || !match.Success) { return false; } string text = BuildTerminalLocalizedItemName(match.Groups["item"].Value.Trim()); string text2 = match.Groups["credits"].Value.Trim(); string text3 = match.Groups["rest"].Value.Trim(); if (text3.Length == 0) { translated = "已订购 " + text + "。\n你的新余额为 " + text2 + "。"; return true; } string text4 = TranslateOrderDetail(text3); translated = "已订购 " + text + "。\n你的新余额为 " + text2 + "。\n\n" + text4; return true; } public static string StandardizeCruiserWarrantyText(string source) { if (string.IsNullOrEmpty(source) || (!source.Contains("warranty", StringComparison.OrdinalIgnoreCase) && !source.Contains("Cruiser", StringComparison.OrdinalIgnoreCase))) { return source; } return SafeRegexReplace(TerminalCruiserWarrantyRegex, source, "公司对此产品质量非常有信心,因此提供终身保修。\n如果你的巡航车丢失或损坏,可以获得一次免费替换。\n在载具运送途中无法购买物品。"); } public static string TranslateOrderDetail(string source) { if (string.IsNullOrWhiteSpace(source)) { return string.Empty; } string text = SafeRegexReplace(source, "\\s+", " ").Trim(); string text2 = StandardizeCruiserWarrantyText(text); if (!string.Equals(text2, text, StringComparison.Ordinal)) { return SanitizeTranslatedText(text2); } return SanitizeTranslatedText(StandardizeCruiserWarrantyText(TranslateOrderDetailPhrases(TranslateTerminalOutputBody(text)))); } public static string TranslateOrderDetailPhrases(string source) { string source2 = source; source2 = ReplaceIgnoreCase(source2, "Press [B] to rearrange objects in your ship and [V] to confirm.", "按 [B] 在飞船内整理物品,按 [V] 确认。"); source2 = ReplaceIgnoreCase(source2, "Press [B] to rearrange fish in your ship and [V] to confirm.", "按 [B] 在飞船内整理金鱼,按 [V] 确认。"); source2 = ReplaceIgnoreCase(source2, "Press the button to activate the teleporter. It will teleport whoever is currently being monitored on the ship's radar. You will not be able to keep any of your held items through the teleport. It takes about 10 seconds to recharge.", "按下按钮即可启动传送器。它会传送当前在飞船雷达监视中的目标。传送过程中将无法保留手持物品。冷却时间约 10 秒。"); source2 = ReplaceIgnoreCase(source2, "Press the button and step onto the inverse teleporter while it activates.", "启动时按下按钮并踏上逆向传送器。"); source2 = ReplaceIgnoreCase(source2, "Use the light switch to enable cozy lights.", "使用灯光开关启用温馨灯串。"); source2 = ReplaceIgnoreCase(source2, "Use the light switch to enable the disco.", "使用灯光开关启用迪斯科球。"); source2 = ReplaceIgnoreCase(source2, "Your electric chair can be activated by any powerful source of voltage!", "电椅可由任何强大电压源激活!"); source2 = ReplaceIgnoreCase(source2, "Hold the cord to activate the loud horn.", "拉住绳索即可启动扬声喇叭。"); string translated; return TranslateSignalTransmitterInstructions(source2, out translated) ? translated : source2; } public static bool TranslateSignalTransmitterInstructions(string source, out string translated) { translated = SafeRegexReplace(SignalTransmitterInstructionsRegex, source, "信号发射器可通过 \"transmit\" 命令激活,后接不超过 10 个字符的消息。"); return !string.Equals(translated, source, StringComparison.Ordinal); } } internal static class WeightUnitTranslator { public static bool CanHandleCheap(string? source) { if (string.IsNullOrWhiteSpace(source)) { return false; } string text = StripRichTextTagsCheap(source).Trim(); if (text.Equals("lb", StringComparison.OrdinalIgnoreCase)) { return true; } if (!text.EndsWith("lb", StringComparison.OrdinalIgnoreCase)) { return false; } string text2 = text; return LooksLikeSimpleNumber(text2.Substring(0, text2.Length - 2).Trim()); } public static bool Translate(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrEmpty(source) || !CanHandleCheap(source)) { return false; } translated = Normalize(source); return !string.Equals(translated, source, StringComparison.Ordinal); } public static string Normalize(string? source) { if (string.IsNullOrEmpty(source)) { return source ?? string.Empty; } StringBuilder stringBuilder = null; for (int i = 0; i < source.Length - 1; i++) { if ((source[i] != 'l' && source[i] != 'L') || (source[i + 1] != 'b' && source[i + 1] != 'B') || HasAsciiLetterBefore(source, i) || HasAsciiLetterAfter(source, i + 1)) { stringBuilder?.Append(source[i]); continue; } if (stringBuilder == null) { stringBuilder = new StringBuilder(source.Length); } if (stringBuilder.Length == 0 && i > 0) { stringBuilder.Append(source, 0, i); } stringBuilder.Append('磅'); i++; } if (stringBuilder == null) { return source; } char c; if (source.Length > 0) { c = source[source.Length - 1]; if (source.Length >= 2) { if (source[source.Length - 2] != 'l') { if (source[source.Length - 2] != 'L') { goto IL_010e; } } if (c == 'b' || c == 'B') { goto IL_0116; } } goto IL_010e; } goto IL_0116; IL_010e: stringBuilder.Append(c); goto IL_0116; IL_0116: return stringBuilder.ToString(); } private static bool LooksLikeSimpleNumber(string value) { if (string.IsNullOrWhiteSpace(value)) { return false; } bool result = false; bool flag = false; for (int i = 0; i < value.Length; i++) { char c = value[i]; if ((c == '+' || c == '-') && i == 0) { continue; } if (c == '.' && !flag) { flag = true; continue; } if (!char.IsDigit(c)) { return false; } result = true; } return result; } } private enum KnownSlowCfgRegexKind { PurchasedItemsOnRoute, PurchasedVehicleOnRoute, ColonAll, ColonNone } private sealed class RegexEntry { public Regex? Regex { get; } public KnownSlowCfgRegexKind? Kind { get; } public string Pattern { get; } public string Replacement { get; } public bool Disabled { get; private set; } public RegexEntry(string pattern, Regex regex, string replacement) { Regex = regex; Kind = null; Pattern = pattern; Replacement = replacement; } public RegexEntry(string pattern, KnownSlowCfgRegexKind kind, string replacement) { Regex = null; Kind = kind; Pattern = pattern; Replacement = replacement; } public void DisableAfterTimeout() { Disabled = true; } } [CompilerGenerated] private sealed class d__182 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; string IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__182(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = Path.Combine(Paths.ConfigPath, "translations", "zh-CN"); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = Path.Combine(Paths.ConfigPath, "translations", "zh-Hans"); <>1__state = 2; return true; case 2: <>1__state = -1; 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(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__182(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__180 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private string pluginDir; public string <>3__pluginDir; string IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__180(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = Path.Combine(pluginDir, "V81TestChn", "translations", "zh-CN.json"); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = Path.Combine(pluginDir, "translations", "zh-CN.json"); <>1__state = 2; return true; case 2: <>1__state = -1; 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(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__180 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__180(0); } d__.pluginDir = <>3__pluginDir; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__181 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private string pluginDir; public string <>3__pluginDir; string IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__181(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = Path.Combine(pluginDir, "V81TestChn", "translations-cfg", "zh-CN"); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = Path.Combine(pluginDir, "V81TestChn", "translations-cfg", "zh-Hans"); <>1__state = 2; return true; case 2: <>1__state = -1; <>2__current = Path.Combine(pluginDir, "translations-cfg", "zh-CN"); <>1__state = 3; return true; case 3: <>1__state = -1; <>2__current = Path.Combine(pluginDir, "translations-cfg", "zh-Hans"); <>1__state = 4; return true; case 4: <>1__state = -1; 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(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__181 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__181(0); } d__.pluginDir = <>3__pluginDir; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__189 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private Match <>2__current; private int <>l__initialThreadId; private string source; public string <>3__source; private string pattern; public string <>3__pattern; private RegexOptions options; public RegexOptions <>3__options; private IEnumerator <>7__wrap1; Match IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__189(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; MatchCollection matchCollection; try { matchCollection = Regex.Matches(source, pattern, options, RegexTimeout); } catch (RegexMatchTimeoutException ex) { LogRegexTimeout(pattern, ex); return false; } <>7__wrap1 = matchCollection.GetEnumerator(); <>1__state = -3; break; } case 1: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { Match match = (Match)<>7__wrap1.Current; <>2__current = match; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap1 = null; 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; if (<>7__wrap1 is IDisposable disposable) { disposable.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__189 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__189(0); } d__.source = <>3__source; d__.pattern = <>3__pattern; d__.options = <>3__options; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private const int TerminalRollingWindowChars = 250; private static readonly Dictionary ExactMap = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary ExactMapIgnoreCase = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly List> CompositeEntries = new List>(); private static readonly List RegexEntries = new List(); private static readonly HashSet RegexPatternSet = new HashSet(StringComparer.Ordinal); private static readonly HashSet WarnedRegexTimeoutPatterns = new HashSet(StringComparer.Ordinal); private static readonly Dictionary TranslationResultCache = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary CompositeTranslationResultCache = new Dictionary(StringComparer.Ordinal); private const int MaxTranslationResultCache = 8000; private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(25.0); private const string TemperatureUnitCelsius = "Celsius"; private const string TemperatureUnitFahrenheit = "Fahrenheit"; private const int KnownDynamicHitLogBudget = 80; private static ConfigEntry? _temperatureUnit; private static ConfigEntry? _logKnownDynamicHits; private static int _knownDynamicHitLogCount; [ThreadStatic] private static bool _suppressKnownDynamicHitLog; private static readonly Regex ControlTipItemNameRegex = new Regex("^(?\\s*)(?Drop|\\u4e22\\u5f03)\\s+(?.+?)(?\\s*[:\\uff1a]\\s*\\[[^\\]]+\\]\\s*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout); private static readonly Regex HighFeverFahrenheitRegex = new Regex("^(?\\s*)HIGH\\s+FEVER\\s+DETECTED!\\s+REACHING\\s+(?-?\\d+)°F(?\\s*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant, RegexTimeout); private static readonly Regex OrderedTerminalItemConfirmationRegex = new Regex("^Ordered(?: the)?\\s+(?!\\d+\\s)(?.+?)[.!]\\s*(?:Your new balance is|(?:\\u4f60|\\u60a8)\\u7684(?:\\u65b0\\u4f59\\u989d\\u4e3a|\\u65b0\\u9918\\u984d(?:\\u70ba|\\u4e3a)))\\s*(?[$\\u25a0]?\\s*[+-]?\\d+(?:\\.\\d+)?)\\s*[\\.\\u3002]?(?[\\s\\S]*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant, RegexTimeout); private static readonly Regex TerminalCruiserWarrantyRegex = new Regex("(?:We\\s+are\\s+so\\s+confident\\s+in\\s+the\\s+quality\\s+of\\s+this\\s+product,\\s*it\\s+comes\\s+with\\s+a\\s+life-time\\s+warranty!\\s+If\\s+your\\s+(?:Company\\s+)?Cruiser\\s+is\\s+lost\\s+or\\s+destroyed,\\s+you\\s+can\\s+get\\s+one\\s+free\\s+replacement\\.\\s+Items\\s+cannot\\s+be\\s+purchased\\s+while\\s+the\\s+vehicle\\s+is\\s+en\\s+route\\.|The\\s+Company\\s+is\\s+very\\s+confident\\s+in\\s+the\\s+quality\\s+of\\s+this\\s+product,\\s*and\\s+therefore\\s+is\\s+supplying\\s+a\\s+lifetime\\s+warranty!\\s+If\\s+your\\s+(?:Company\\s+)?Cruiser\\s+is\\s+lost\\s+or\\s+destroyed,\\s+you\\s+can\\s+get\\s+a\\s+free\\s+replacement\\s+once\\.\\s+Cannot\\s+purchase\\s+while\\s+vehicle\\s+is\\s+on\\s+route\\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant, RegexTimeout); private static readonly Regex SignalTransmitterInstructionsRegex = new Regex("The signal transmitter can be activated with the\\s+[\"\\u201c\\u201d]transmit[\"\\u201c\\u201d]\\s+command followed by any message under\\s+10\\s+letters\\.", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant, RegexTimeout); private const string TerminalCruiserWarrantyLocalizedText = "公司对此产品质量非常有信心,因此提供终身保修。\n如果你的巡航车丢失或损坏,可以获得一次免费替换。\n在载具运送途中无法购买物品。"; private const string BootSplashCanonicalText = " BG IG, A System-Act Ally\n Copyright (C) 2084-2108, Halden Electronics Inc.\n\nCPU Type : BORSON 300 CPU at 2500 MHz\nMemory test : 4521586K OK\n\nBoot Distributioner Application v0.04\nCopyright (C) 2107 Distributioner\n Detecting Sting X ROM\n Detecting Web LNV Extender\n Detecting Heartbeats OK\n\n\nUTGF Device Listening...\n\nBody ID Neural Device Class\n________________________________________\n\n2 52 Jo152 H515\n2 52 Sa5155 H515\n2 52 Bo75 H515\n2 52 Eri510 H515\n1 36 Ell567 H515\n1 36 Jos912 H515\n0\n"; private const string BootSplashLocalizedText = " BG IG, 系统法案盟友\n 版权所有 (C) 2084-2108, Halden Electronics Inc.\n\nCPU 类型 : BORSON 300 CPU at 2500 MHz\n内存测试 : 4521586K OK\n\n启动 Distributioner 应用 v0.04\n版权所有 (C) 2107 Distributioner\n 检测 Sting X ROM\n 检测 Web LNV 扩展器\n 检测心跳 OK\n\n\nUTGF 设备监听中...\n\n身体 ID 神经 设备类别\n________________________________________\n\n2 52 Jo152 H515\n2 52 Sa5155 H515\n2 52 Bo75 H515\n2 52 Eri510 H515\n1 36 Ell567 H515\n1 36 Jos912 H515\n0\n"; private static readonly string[] BootSplashEnglishMarkers = new string[4] { "BG IG, A System-Act Ally", "Boot Distributioner Application v0.04", "UTGF Device Listening", "Body ID Neural Device Class" }; private static readonly string[] BootSplashPollutedMarkers = new string[12] { "系统-法案盟友", "系统法案盟友", "CPU类型", "CPU 类型", "记忆体测试", "内存测试", "启动分配器应用程序", "启动 Distributioner", "检测芯跳", "检测心跳", "UTGF设备监听", "UTGF 设备监听" }; private static readonly Dictionary TerminalHeadingEntries = new Dictionary(StringComparer.Ordinal) { [">MOONS"] = ">星球 〈Moons〉", ["> MOONS"] = "> 星球 〈Moons〉", [">STORE"] = ">商店 〈Store〉", ["> STORE"] = "> 商店 〈Store〉", [">BESTIARY"] = ">图鉴 〈Bestiary〉", ["BESTIARY"] = ">图鉴 〈Bestiary〉", [">STORAGE"] = ">储存 〈Storage〉", ["> STORAGE"] = "> 储存 〈Storage〉", [">OTHER"] = ">其他 〈Other〉", ["> OTHER"] = "> 其他 〈Other〉" }; private static readonly Dictionary TerminalBodyEntries = new Dictionary(StringComparer.Ordinal) { ["Welcome to the FORTUNE-9 OS"] = "欢迎使用 FORTUNE-9 OS", ["Courtesy of the Company"] = "由公司提供", ["Type \"Help\" for a list of commands."] = "输入 \"Help\" 查看命令列表。", ["Welcome to the Company store"] = "欢迎来到公司商店。", ["Welcome to the Company store."] = "欢迎来到公司商店。", ["Good luck."] = "祝你好运。", ["Use words BUY and INFO on any item."] = "可对任意商品使用 BUY 和 INFO 命令。", ["Order tools in bulk by typing a number."] = "输入数量即可批量购买工具。", ["The selection of ship decor rotates per-quota. Be"] = "飞船装饰的商品会按配额周期轮换。", ["sure to check back next week:"] = "记得下周再来查看:", ["To see the company store's selection of useful items."] = "输入以查看公司商店的实用物品列表。", ["To see the company store's selection of useful items"] = "输入以查看公司商店的实用物品列表。", ["To see the list of moons the autopilot can route to."] = "输入以查看自动驾驶系统可到达的卫星列表。", ["To see the list of moons the autopilot can route to"] = "输入以查看自动驾驶系统可到达的卫星列表。", ["To access objects placed into storage."] = "输入以访问已放入储存区的物品。", ["To access objects placed into storage"] = "输入以访问已放入储存区的物品。", ["To see the list of other commands."] = "输入以查看其他命令列表。", ["To see the list of other commands"] = "输入以查看其他命令列表。", ["To see the list of wildlife on record."] = "输入以查看已记录的生物列表。", ["To see the list of wildlife on record"] = "输入以查看已记录的生物列表。", ["To read a log, use keyword \"VIEW\" before its name."] = "要阅读日志,请在名称前输入关键字 \"VIEW\"。", ["Welcome to the exomoons catalogue."] = "欢迎查阅外卫星目录。", ["Welcome to the exomoons catalogue"] = "欢迎查阅外卫星目录。", ["To route the autopilot to a moon, use the word ROUTE."] = "要将自动驾驶导航至某个卫星,请使用 ROUTE 指令。", ["To route the autopilot to a moon, use the word ROUTE"] = "要将自动驾驶导航至某个卫星,请使用 ROUTE 指令。", ["To route the autopilot to a moon, use the word"] = "要将自动驾驶导航至某个卫星,请使用指令", ["ROUTE."] = "ROUTE。", ["INFO."] = "INFO。", ["To learn about any moon, use the word INFO."] = "要了解任意卫星,请使用 INFO 指令。", ["To learn about any moon, use the word INFO"] = "要了解任意卫星,请使用 INFO 指令。", ["To learn about any moon, use INFO."] = "要了解任意卫星,请使用 INFO 指令。", ["To learn about any moon, use INFO"] = "要了解任意卫星,请使用 INFO 指令。", ["Do you want to route the autopilot to the Company building?"] = "是否将自动驾驶导航至公司大楼?", ["Do you want to route the autopilot to the Company"] = "是否将自动驾驶导航至公司", ["building?"] = "大楼?", ["Happy [currentDay]."] = "今天是 [currentDay]。", ["Sent transmission."] = "已发送传输。", ["Switched radar to player."] = "已将雷达切换到玩家。", ["Switching radar cam view."] = "正在切换雷达摄像头视角。", ["Toggling radar cam"] = "正在切换雷达摄像头。", ["Cancelled ejection sequence."] = "已取消弹射序列。", ["Cancelled order."] = "已取消订单。", ["Our contractors enjoy fast, free shipping while on the job! Any purchased items will arrive hourly at your approximate location."] = "我们的承包商在工作期间享有快速免费配送服务,已购物品会按小时送达你大致所在位置。", ["There was no action supplied with the word."] = "该单词未提供对应动作。", ["There was no object supplied with the action, or your word was typed incorrectly or does not exist."] = "未为该动作提供对象,或你输入的单词有误或不存在。", ["This action was not compatible with this object."] = "该动作与当前对象不兼容。", ["An error occured! Try again."] = "发生错误,请重试。", ["The autopilot ship is already orbiting this moon!"] = "自动驾驶飞船已经在这颗卫星的轨道上!", ["To purchase decorations, the ship cannot be landed."] = "购买装饰时,飞船不能处于着陆状态。", ["You have cancelled the order."] = "你已取消订单。", ["You selected the Challenge Moon save file. You can't route to another moon during the challenge."] = "你选择了挑战卫星存档。在挑战期间无法切换航线到其他卫星。", ["Press [B] to rearrange objects in your ship and [V] to confirm."] = "按 [B] 在飞船内整理物品,按 [V] 确认。", ["Press [B] to rearrange fish in your ship and [V] to confirm."] = "按 [B] 在飞船内整理金鱼,按 [V] 确认。", ["Press the button to activate the teleporter. It will teleport whoever is currently being monitored on the ship's radar. You will not be able to keep any of your held items through the teleport. It takes about 10 seconds to recharge."] = "按下按钮即可启动传送器。它会传送当前在飞船雷达监视中的目标。传送过程中将无法保留手持物品。冷却时间约 10 秒。", ["Use the light switch to enable cozy lights."] = "使用灯光开关启用温馨灯串。", ["Use the light switch to enable the disco."] = "使用灯光开关启用迪斯科球。", ["Your electric chair can be activated by any powerful source of voltage!"] = "电椅可由任何强大电压源激活!", ["Hold the cord to activate the loud horn."] = "拉住绳索即可启动扬声喇叭。", ["The signal transmitter can be activated with the \"transmit\" command followed by any message under 10 letters."] = "信号发送器可通过 \"transmit\" 命令激活,后接不超过 10 个字符的消息。", ["Press the button and step onto the inverse teleporter while it activates."] = "启动时按下按钮并踏上逆向传送器。", ["To scan for the number of items left on the current planet"] = "扫描当前星球上剩余物品的数量。" }; private static readonly Dictionary ControlTipActionEntries = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Push"] = "推", ["Pull up"] = "拉起", ["Pull switch"] = "拉动开关", ["Sit down"] = "坐下", ["Switch headlight"] = "切换车灯", ["Switch headlights"] = "切换车灯", ["Toggle cabin window"] = "开关驾驶室车窗", ["Toggle wiper"] = "切换雨刷", ["Toggle wipers"] = "切换雨刷", ["Honk"] = "鸣笛", ["Try ignition"] = "尝试点火", ["Eject"] = "弹射", ["Tune radio"] = "调谐收音机", ["Toggle radio"] = "开关收音机", ["Gas pedal"] = "油门", ["Brake pedal"] = "刹车", ["Boost"] = "加速", ["Open hood"] = "打开引擎盖", ["Shut hood"] = "关闭引擎盖", ["Close hood"] = "关闭引擎盖", ["Use door"] = "使用门", ["Exit"] = "离开" }; private static readonly Dictionary TerminalBilingualOverrides = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Shovel"] = "铲子", ["Clock"] = "时钟", ["Bell"] = "钟", ["Brass bell"] = "黄铜钟", ["Brush"] = "刷子", ["Chemical jug"] = "化学罐", ["Flashlight"] = "手电筒", ["Pro flashlight"] = "专业手电筒", ["Jetpack"] = "喷气背包", ["Lock-picker"] = "开锁器", ["Lockpicker"] = "开锁器", ["Mapper tool"] = "测绘工具", ["Company Cruiser"] = "公司巡航车", ["Cruiser"] = "货车", ["Boombox"] = "音响", ["Cupboard"] = "橱柜", ["Extension ladder"] = "伸缩梯", ["Radar-booster"] = "雷达增幅器", ["Spray paint"] = "喷漆", ["Weed killer"] = "除草剂", ["Belt bag"] = "腰包", ["Stun grenade"] = "眩晕手雷", ["Survival kit"] = "生存套装", ["TZP-Inhalant"] = "TZP 吸入剂", ["Walkie-talkie"] = "对讲机", ["Zap gun"] = "电击枪", ["Loud horn"] = "扬声喇叭", ["Signal Translator"] = "信号翻译器", ["Teleporter"] = "传送器", ["Inverse Teleporter"] = "逆向传送器", ["Record player"] = "唱片机", ["Romantic table"] = "浪漫桌", ["Toilet"] = "马桶", ["Cozy lights"] = "温馨灯串", ["Disco ball"] = "迪斯科球", ["Sofa chair"] = "沙发椅", ["Table"] = "桌子", ["Jack-o-Lantern"] = "南瓜灯", ["Goldfish"] = "金鱼", ["Classic painting"] = "经典油画", ["Television"] = "电视", ["Electric chair"] = "电椅", ["Shower"] = "淋浴器", ["Plushie pajama man"] = "毛绒睡衣公仔", ["Dog house"] = "狗屋", ["Fridge"] = "冰箱", ["Microwave"] = "微波炉", ["Coffee mug"] = "咖啡杯", ["Fancy lamp"] = "精致灯具", ["Plasma ball"] = "等离子球", ["Remote"] = "遥控器", ["Bunkbeds"] = "上下铺", ["File cabinet"] = "文件柜", ["Small rug"] = "小地毯", ["Welcome mat"] = "门口地垫", ["Green suit"] = "绿色套装", ["Hazard suit"] = "防危套装", ["Pajama suit"] = "睡衣套装", ["Purple suit"] = "紫色套装", ["Purple suits"] = "紫色套装", ["Bunny suit"] = "兔子套装", ["Bee suit"] = "蜜蜂套装", ["VIEW MONITOR"] = "查看监视器", ["SWITCH"] = "切换视角", ["PING"] = "提示", ["TRANSMIT"] = "发送", ["SCAN"] = "扫描", ["Player name"] = "玩家名称", ["Radar booster name"] = "雷达增幅器名称", ["message"] = "消息" }; private static readonly KeyValuePair[] ForcedPhraseEntries = new KeyValuePair[65] { new KeyValuePair("ENTERING THE ATMOSPHERE...", "正在进入大气层…"), new KeyValuePair("Entering the atmosphere...", "正在进入大气层…"), new KeyValuePair("Autosaving...", "自动保存中..."), new KeyValuePair("RECEIVING SIGNAL", "正在接收信号"), new KeyValuePair("Light Switch", "电灯开关"), new KeyValuePair("Light switch", "电灯开关"), new KeyValuePair("Ship Magnet Activated", "飞船磁吸已启用"), new KeyValuePair("Ship Magnet Deactivated", "飞船磁吸已停用"), new KeyValuePair("Toggle Sprint", "切换冲刺"), new KeyValuePair("Toggle 冲刺", "切换冲刺"), new KeyValuePair("Head Bobbing", "镜头晃动"), new KeyValuePair("头部晓动", "镜头晃动"), new KeyValuePair("Terrain / Grass Detail", "地形 / 草丛细节"), new KeyValuePair("Motion Blur", "动态模糊"), new KeyValuePair("Pixel Resolution", "像素分辨率"), new KeyValuePair("Indirect lighting", "间接光照"), new KeyValuePair("Ultra performance", "极致性能"), new KeyValuePair("Performance", "性能"), new KeyValuePair("Retro", "复古"), new KeyValuePair("Moderate", "中等"), new KeyValuePair("High (Default)", "高(默认)"), new KeyValuePair("Subtle", "轻微"), new KeyValuePair("Ultra", "极高"), new KeyValuePair("High", "高"), new KeyValuePair("Medium", "中"), new KeyValuePair("Low", "低"), new KeyValuePair("GRAPHICS", "图形"), new KeyValuePair("Graphics", "图形"), new KeyValuePair("SYSTEMS ONLINE", "系统在线"), new KeyValuePair("HAZARD LEVEL:", "危险等级:"), new KeyValuePair("HAZARD LEVEL", "危险等级"), new KeyValuePair("HAZARD_LEVEL:", "危险等级:"), new KeyValuePair("HAZARD_LEVEL", "危险等级"), new KeyValuePair("Hazard Level:", "危险等级:"), new KeyValuePair("Hazard Level", "危险等级"), new KeyValuePair("Hazard level:", "危险等级:"), new KeyValuePair("Hazard level", "危险等级"), new KeyValuePair("DOOR HYDRAULICS:", "舱门液压:"), new KeyValuePair("DOOR HYDRAULICS", "舱门液压"), new KeyValuePair("With detected mods", "包含已检测到的模组"), new KeyValuePair("Press \"/\" to talk.", "按 \"/\" 说话。"), new KeyValuePair("Typing...", "输入中..."), new KeyValuePair("Join", "加入"), new KeyValuePair("Delete", "删除"), new KeyValuePair("Go back", "返回"), new KeyValuePair("Walk : [W/A/S/D]", "移动:[W/A/S/D]"), new KeyValuePair("Sprint: [Shift]", "冲刺:[Shift]"), new KeyValuePair("Scan : [RMB]", "扫描:[右键]"), new KeyValuePair("[Hands full]", "[双手已满]"), new KeyValuePair("HANDS FULL", "双手已满"), new KeyValuePair("TOTAL:", "总价值:"), new KeyValuePair("TOTAL", "总价值"), new KeyValuePair("Paycheck!", "薪水!"), new KeyValuePair("Paycheck", "薪水"), new KeyValuePair("QUOTA REACHED!", "配额已达成!"), new KeyValuePair("QUOTA REACHED", "配额已达成"), new KeyValuePair("NEW PROFIT QUOTA:", "新利润配额:"), new KeyValuePair("NEW PROFIT QUOTA", "新利润配额"), new KeyValuePair("Overtime bonus:", "加班奖金:"), new KeyValuePair("Overtime bonus", "加班奖金"), new KeyValuePair("Equip to belt : [E]", "装备到腰带:[E]"), new KeyValuePair("Equipped to utility belt!", "已装备到工具腰带!"), new KeyValuePair("Press TAB to select the utility belt. This can only hold one-handed tools.", "按 TAB 选择工具腰带。工具腰带只能存放单手工具。"), new KeyValuePair("(Dead)", "(死亡)"), new KeyValuePair("Deceased", "死亡") }; public static int EntryCount => ExactMap.Count + RegexEntries.Count; public static void Initialize(ConfigFile config) { _temperatureUnit = config.Bind("InfectionStatus", "TemperatureUnit", "Celsius", "Temperature unit for infection high-fever status prompts. Use Celsius or Fahrenheit. Invalid values fall back to Celsius."); _logKnownDynamicHits = config.Bind("Diagnostics", "LogKnownDynamicTranslationHits", false, "Log a small budget of known dynamic translation hits such as votes, random seed, control tips, and chat system messages."); } public static void ClearCaches() { TranslationResultCache.Clear(); CompositeTranslationResultCache.Clear(); _knownDynamicHitLogCount = 0; } public static void Load(string pluginDir) { ExactMap.Clear(); ExactMapIgnoreCase.Clear(); CompositeEntries.Clear(); RegexEntries.Clear(); RegexPatternSet.Clear(); WarnedRegexTimeoutPatterns.Clear(); TranslationResultCache.Clear(); CompositeTranslationResultCache.Clear(); List list = new List(); LoadPluginCfgDirectories(pluginDir, list); LoadCfgDirectories(list); CompositeEntries.AddRange(from entry in ExactMap where entry.Key.Length >= 4 && entry.Key != entry.Value orderby entry.Key.Length descending select entry); Plugin.Log.LogInfo((object)$"TranslationService loaded {ExactMap.Count} exact + {RegexEntries.Count} regex entries from {list.Count} source(s)."); if (list.Count > 0) { Plugin.Log.LogInfo((object)("TranslationService sources: " + string.Join("; ", list))); } } public static bool TryTranslate(string? source, out string translated) { translated = string.Empty; if (string.IsNullOrEmpty(source)) { return false; } if (MayBeHighFeverFahrenheitStatus(source) && TryTranslateHighFeverFahrenheitStatus(source, out translated)) { translated = SanitizeTranslatedText(translated); return true; } if (CustomLocalizationExtensionService.PreferCustomTranslations && CustomLocalizationExtensionService.TryTranslate(source, out translated, allowRegex: true)) { translated = SanitizeTranslatedText(translated); CacheTranslationResult(source, translated); return true; } if (TryTranslateKnownDynamicText(source, out translated)) { translated = SanitizeTranslatedText(translated); CacheTranslationResult(source, translated); return true; } if (TryTranslateExact(source, out translated)) { translated = SanitizeTranslatedText(translated); if (!string.Equals(translated, source, StringComparison.Ordinal)) { CacheTranslationResult(source, translated); return true; } CacheTranslationResult(source, null); return false; } if (TryGetCachedTranslation(source, out string translated2, out bool hasTranslation)) { translated = translated2; return hasTranslation; } if (TryPreserveBootSplashText(source, out translated)) { if (!string.Equals(translated, source, StringComparison.Ordinal)) { CacheTranslationResult(source, translated); return true; } CacheTranslationResult(source, null); return false; } if (TryTranslateMapScreenDescription(source, out translated)) { if (!string.Equals(translated, source, StringComparison.Ordinal)) { CacheTranslationResult(source, translated); return true; } CacheTranslationResult(source, null); return false; } if (TryTranslateControlTipItemName(source, out translated)) { translated = SanitizeTranslatedText(translated); if (!string.Equals(translated, source, StringComparison.Ordinal)) { CacheTranslationResult(source, translated); return true; } CacheTranslationResult(source, null); return false; } if (TryTranslateRegex(source, out translated)) { translated = SanitizeTranslatedText(translated); if (!string.Equals(translated, source, StringComparison.Ordinal)) { CacheTranslationResult(source, translated); return true; } CacheTranslationResult(source, null); return false; } if (!CustomLocalizationExtensionService.PreferCustomTranslations && CustomLocalizationExtensionService.TryTranslate(source, out translated, allowRegex: true)) { translated = SanitizeTranslatedText(translated); CacheTranslationResult(source, translated); return true; } translated = TranslateComposite(source); if (!string.Equals(translated, source, StringComparison.Ordinal)) { CacheTranslationResult(source, translated); return true; } CacheTranslationResult(source, null); return false; } public static bool TryTranslateFastExact(string? source, out string translated) { translated = string.Empty; if (string.IsNullOrEmpty(source)) { return false; } if (CustomLocalizationExtensionService.PreferCustomTranslations && CustomLocalizationExtensionService.TryTranslateFastExact(source, out translated)) { translated = SanitizeTranslatedText(translated); return true; } if (TryTranslateExact(source, out translated) || TryTranslateForcedPhraseExact(source, out translated)) { translated = SanitizeTranslatedText(translated); return !string.Equals(translated, source, StringComparison.Ordinal); } if (!CustomLocalizationExtensionService.PreferCustomTranslations && CustomLocalizationExtensionService.TryTranslateFastExact(source, out translated)) { translated = SanitizeTranslatedText(translated); return true; } if (TryGetCachedTranslation(source, out translated, out var hasTranslation)) { return hasTranslation; } return false; } public static bool TryTranslateKnownDynamicText(string? source, out string translated) { translated = source ?? string.Empty; if (string.IsNullOrWhiteSpace(source)) { return false; } if (TryTranslateHostModWarning(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "HostModWarning"); } if (TryTranslateTerminalOrderRequest(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "TerminalOrder"); } if (TryTranslateOrderedTerminalItemConfirmation(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "TerminalOrderedItem"); } if (TryTranslateControlTipText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "ControlTip"); } if (TryTranslateStandaloneControlText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "ControlTip"); } if (TryTranslateScanValueText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "ScanValue"); } if (TryTranslateHudRewardLine(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "HudRewards"); } if (TryTranslateRandomSeedText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "RandomSeed"); } if (TryTranslateVotesText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "Votes"); } if (TryTranslateDaysLeftText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "DaysLeft"); } if (TryTranslateSaveFileStatsTextFast(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "SaveFileStats"); } if (TryTranslatePlayersFiredText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "PlayersFired"); } if (source.IndexOf('\n') >= 0 && TryTranslateKnownDynamicLines(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "KnownDynamicLines"); } if (TryTranslateFixedSceneLabel(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "FixedSceneLabel"); } if (TryTranslatePlanetInfoLine(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "PlanetInfo"); } if (TryTranslateMapScreenDescription(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "MapScreen"); } if (TryTranslateTimePeriodText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "TimePeriod"); } if (TryTranslatePlayerStatusText(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "PlayerStatus"); } if (ChatDynamicTranslator.Translate(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "ChatSystem"); } if (TryTranslateEndgameStatLine(source, out translated)) { return FinishKnownDynamicTranslation(source, ref translated, "EndgameStats"); } if (WeightUnitTranslator.CanHandleCheap(source)) { translated = SanitizeTranslatedText(WeightUnitTranslator.Normalize(source)); if (!string.Equals(translated, source, StringComparison.Ordinal)) { LogKnownDynamicHit("WeightUnit", source, translated); return true; } } translated = source; return false; } private static bool FinishKnownDynamicTranslation(string source, ref string translated, string kind) { translated = SanitizeTranslatedText(translated); if (string.Equals(translated, source, StringComparison.Ordinal)) { return false; } LogKnownDynamicHit(kind, source, translated); return true; } private static bool TryTranslateKnownDynamicLines(string source, out string translated) { translated = source; string[] array = source.Split('\n'); bool flag = false; for (int i = 0; i < array.Length; i++) { string text = array[i]; 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 (TryTranslateKnownDynamicText(source2, out string translated2)) { array[i] = (flag2 ? (translated2 + "\r") : translated2); flag = true; } } if (!flag) { return false; } translated = string.Join("\n", array); return true; } public static bool LooksLikeKnownDynamicText(string? source) { bool suppressKnownDynamicHitLog = _suppressKnownDynamicHitLog; _suppressKnownDynamicHitLog = true; try { string translated; return TryTranslateKnownDynamicText(source, out translated) && !string.Equals(translated, source, StringComparison.Ordinal); } finally { _suppressKnownDynamicHitLog = suppressKnownDynamicHitLog; } } public static bool LooksLikeControlTipText(string? source) { string translated; if (!string.IsNullOrWhiteSpace(source)) { return TryTranslateControlTipText(source, out translated); } return false; } public static bool LooksLikeRandomSeedText(string? source) { string translated; if (!string.IsNullOrWhiteSpace(source)) { return TryTranslateRandomSeedText(source, out translated); } return false; } public static bool LooksLikeVoteText(string? source) { string translated; if (!string.IsNullOrWhiteSpace(source)) { return TryTranslateVotesText(source, out translated); } return false; } public static bool LooksLikeDaysLeftText(string? source) { string translated; if (!string.IsNullOrWhiteSpace(source)) { return TryTranslateDaysLeftText(source, out translated); } return false; } public static bool LooksLikeEndgameStatText(string? source) { string translated; if (!string.IsNullOrWhiteSpace(source)) { return TryTranslateEndgameStatLine(source, out translated); } return false; } public static bool LooksLikePlayerStatusText(string? source) { string translated; if (!string.IsNullOrWhiteSpace(source)) { return TryTranslatePlayerStatusText(source, out translated); } return false; } public static bool LooksLikeWeightUnitText(string? source) { return WeightUnitTranslator.CanHandleCheap(source); } public static string NormalizeWeightUnitText(string? source) { return WeightUnitTranslator.Normalize(source); } public static string TranslateStunGrenadeControlTip(string? source, bool pinPulled) { if (string.IsNullOrWhiteSpace(source)) { return source ?? string.Empty; } Match match = SafeRegexMatch(source.Trim(), "^(?:Use\\s+grenade|Pull\\s+pin|Throw\\s+grenade|使用雷|使用闪光弹|使用闪光震撼弹|拔出拉环|拉保险针|投掷雷|投掷闪光弹|投掷闪光震撼弹)\\s*[::]\\s*(?.+?)\\s*$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (!match.Success) { return source; } string text = match.Groups["key"].Value.Trim(); if (!pinPulled) { return "拔出拉环:" + text; } return "投掷闪光弹:" + text; } private static bool TryTranslateHostModWarning(string source, out string translated) { translated = source; string a = NormalizeLoose(StripRichTextTags(source)); if (string.Equals(a, "The host is detected to be using a modified version of Lethal Company; you are likely to experience unintended behavior. (Modding requires caution and is not supported.)", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "The host is detected to be using a modified version of Lethal Company; you are likely to experience unintended behavior. (Modifying Lethal Company is dangerous and not recommended.)", StringComparison.OrdinalIgnoreCase)) { translated = "检测到主机正在使用修改版 Lethal Company;\n你可能会遇到异常情况。\n(使用模组需谨慎,且不受官方支持。)"; return true; } return false; } private static bool TryTranslateTerminalOrderRequest(string source, out string translated) { return TerminalDynamicTranslator.TranslateOrderRequest(source, out translated); } private static string NormalizeTerminalTransactionCost(string cost) { return TerminalDynamicTranslator.NormalizeTransactionCost(cost); } private static bool TryTranslateFixedSceneLabel(string source, out string translated) { translated = source; string text = StripRichTextTags(source).Trim(); if (text.Length == 0) { return false; } translated = text switch { "PROFIT" => "利润", "PROFIT:" => "利润:", "QUOTA" => "配额", "QUOTA:" => "配额:", "PROFIT QUOTA" => "利润配额", "PROFIT QUOTA:" => "利润配额:", "DEADLINE" => "截止日期", "DEADLINE:" => "截止日期:", "Day" => "天", "Days" => "天", "Park" => "停车挡", "Reverse" => "倒车挡", "Drive" => "前进挡", _ => source, }; if (!string.Equals(translated, source, StringComparison.Ordinal)) { return true; } Match match = SafeRegexMatch(text, "^(?\\d+)\\s+Days?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (match.Success) { translated = match.Groups["count"].Value + " 天"; return true; } return false; } private static bool TryTranslateControlTipText(string source, out string translated) { return ControlTipTranslator.Translate(source, out translated); } private static string NormalizeControlTipAction(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 NormalizeControlTipSuffix(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 bool TryTranslateScanValueText(string source, out string translated) { return HudDynamicTranslator.TranslateScanValue(source, out translated); } private static bool TryTranslateHudRewardLine(string source, out string translated) { return HudDynamicTranslator.TranslateRewardLine(source, out translated); } private static bool TryTranslateStandaloneControlText(string source, out string translated) { return ControlTipTranslator.TranslateStandalone(source, out translated); } private static bool TryTranslateRandomSeedText(string source, out string translated) { return HudDynamicTranslator.TranslateRandomSeed(source, out translated); } private static bool TryTranslateVotesText(string source, out string translated) { return HudDynamicTranslator.TranslateVotes(source, out translated); } private static bool TryTranslateDaysLeftText(string source, out string translated) { return HudDynamicTranslator.TranslateDaysLeft(source, out translated); } private static bool TryTranslatePlayersFiredText(string source, out string translated) { return EndGameDynamicTranslator.TranslatePlayersFired(source, out translated); } private static bool TryTranslatePlanetInfoLine(string source, out string translated) { return PlanetInfoDynamicTranslator.TranslateLine(source, out translated); } private static bool TryTranslateTimePeriodText(string source, out string translated) { translated = source; string a = StripRichTextTags(source).Trim(); if (string.Equals(a, "AM", StringComparison.OrdinalIgnoreCase)) { translated = "上午"; return true; } if (string.Equals(a, "PM", StringComparison.OrdinalIgnoreCase)) { translated = "下午"; return true; } Match match = SafeRegexMatch(source.Trim(), "^(?