Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of REPO JP TranslationAndFont Updated v4.0.0
REPOJPTranslationAndFontUpdated.dll
Decompiled 7 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using REPOJapaneseTranslation.Localization; using REPOJapaneseTranslation.Patches; using TMPro; using UnityEngine; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; [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("LocalBuild")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Japanese localization and Checkpoint Revenge font mod for R.E.P.O. v0.4.4.")] [assembly: AssemblyFileVersion("4.0.0.0")] [assembly: AssemblyInformationalVersion("4.0.0")] [assembly: AssemblyProduct("REPO JP Translation and Font Updated")] [assembly: AssemblyTitle("REPOJPTranslationAndFontUpdated")] [assembly: AssemblyVersion("4.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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 REPOJapaneseTranslation { [BepInPlugin("local.REPOJPTranslationAndFontUpdated", "REPO JP Translation and Font Updated", "4.0.0")] [BepInProcess("REPO.exe")] public sealed class Plugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("local.REPOJPTranslationAndFontUpdated"); private float _nextTextRefreshTime; private int _lastObservedTextCount = -1; private GameObject? _runtimeRunnerObject; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static ConfigEntry<bool> EnableTranslation { get; private set; } internal static ConfigEntry<bool> EnableJapaneseFont { get; private set; } internal static ConfigEntry<float> FontSizeScale { get; private set; } internal static ConfigEntry<float> FontLineHeightScale { get; private set; } internal static ConfigEntry<bool> LogUntranslated { get; private set; } private void Awake() { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); EnableTranslation = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableTranslation", true, "ゲーム内テキストを日本語へ翻訳します。"); EnableJapaneseFont = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableJapaneseFont", true, "チェックポイント★リベンジをTextMeshProへ適用します。"); FontSizeScale = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FontSizeScale", 0.72f, new ConfigDescription("日本語フォントの表示倍率。1.0がゲーム本来のフォントサイズです。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1f), Array.Empty<object>())); FontLineHeightScale = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FontLineHeightScale", 1.35f, new ConfigDescription("複数行テキストの行高倍率。文字が重なる場合に大きくします。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 2f), Array.Empty<object>())); LogUntranslated = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogUntranslated", false, "未翻訳テキストをBepInExログへ出力します(翻訳追加作業用)。"); TranslationManager.Initialize(((BaseUnityPlugin)this).Info.Location); FontManager.Initialize(((BaseUnityPlugin)this).Info.Location); _harmony.PatchAll(typeof(TMPTextTranslationPatch).Assembly); InstallTextRefreshHooks(); InstallTMPComponentHooks(); InstallUGUITextHooks(); CreateRuntimeRunner(); Log.LogInfo((object)"REPO JP Translation and Font Updated v4.0.0 を読み込みました。"); Log.LogInfo((object)$"翻訳エントリ: {TranslationManager.TranslationCount}件"); } private void CreateRuntimeRunner() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown _runtimeRunnerObject = new GameObject("REPO JP Translation Runtime"); ((Object)_runtimeRunnerObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)_runtimeRunnerObject); _runtimeRunnerObject.AddComponent<TranslationRuntimeRunner>(); } private void InstallTextRefreshHooks() { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(TextRefreshHook), "Postfix", (Type[])null, (Type[])null); Type[] array = new Type[5] { typeof(CameraMainMenu), typeof(MenuPageMain), typeof(MenuManager), typeof(RunManager), typeof(GameDirector) }; HashSet<MethodBase> hashSet = new HashSet<MethodBase>(); Type[] array2 = array; foreach (Type type in array2) { MethodInfo methodInfo2 = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); if (methodInfo2 == null || !hashSet.Add(methodInfo2)) { Log.LogWarning((object)("TMP更新対象が見つかりません: " + type.Name + ".Update")); continue; } _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("TMP更新対象をパッチしました: " + type.Name + ".Update")); } Log.LogInfo((object)$"TMP更新パッチ数: {hashSet.Count}"); } private void InstallTMPComponentHooks() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(TMPComponentRefreshHook), "Postfix", (Type[])null, (Type[])null); Type[] array = new Type[2] { typeof(TextMeshProUGUI), typeof(TextMeshPro) }; int num = 0; Type[] array2 = array; foreach (Type type in array2) { MethodInfo methodInfo2 = AccessTools.Method(type, "OnEnable", (Type[])null, (Type[])null); if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("TMP表示対象をパッチしました: " + type.Name + ".OnEnable")); num++; } MethodInfo methodInfo3 = AccessTools.Method(type, "Rebuild", new Type[1] { typeof(CanvasUpdate) }, (Type[])null); if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("TMP表示対象をパッチしました: " + type.Name + ".Rebuild")); num++; } } Log.LogInfo((object)$"TMP表示パッチ数: {num}"); } private void InstallUGUITextHooks() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(UGUITextRefreshHook), "Postfix", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(UGUITextSetterHook), "Prefix", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(Text), "OnEnable", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.PropertySetter(typeof(Text), "text"); int num = 0; if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"UGUI表示対象をパッチしました: Text.OnEnable"); num++; } if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"UGUI表示対象をパッチしました: Text.text"); num++; } Log.LogInfo((object)$"UGUI表示パッチ数: {num}"); } private void OnDestroy() { _harmony.UnpatchSelf(); if ((Object)(object)_runtimeRunnerObject != (Object)null) { Object.Destroy((Object)(object)_runtimeRunnerObject); } } internal void RefreshVisibleTextThrottled() { if (!(Time.unscaledTime < _nextTextRefreshTime)) { _nextTextRefreshTime = Time.unscaledTime + 0.5f; RefreshVisibleText(); } } internal bool RefreshTextComponent(TMP_Text textComponent) { if (!FontManager.ShouldProcess(textComponent)) { return false; } if (!EnableTranslation.Value || string.IsNullOrEmpty(textComponent.text)) { return false; } string text = TranslationManager.Translate(textComponent.text); bool flag = text != textComponent.text; if (flag) { textComponent.text = text; } FontManager.AdjustTranslatedLayout(textComponent, text); return flag; } internal bool RefreshTextComponent(Text textComponent) { if (!EnableTranslation.Value || string.IsNullOrEmpty(textComponent.text)) { return false; } string text = TranslationManager.Translate(textComponent.text); bool flag = text != textComponent.text; if (flag) { textComponent.text = text; } FontManager.AdjustTranslatedLayout(textComponent, text); return flag; } private void RefreshVisibleText() { TMP_Text[] array = Object.FindObjectsOfType<TMP_Text>(true); Text[] array2 = Object.FindObjectsOfType<Text>(true); int num = 0; TMP_Text[] array3 = array; foreach (TMP_Text textComponent in array3) { if (RefreshTextComponent(textComponent)) { num++; } } Text[] array4 = array2; foreach (Text textComponent2 in array4) { if (RefreshTextComponent(textComponent2)) { num++; } } int num2 = array.Length + array2.Length; if (num > 0 || num2 != _lastObservedTextCount) { Log.LogInfo((object)($"UI走査: TMP={array.Length}個、UGUI={array2.Length}個、" + $"翻訳更新={num}個")); _lastObservedTextCount = num2; } } } internal sealed class TranslationRuntimeRunner : MonoBehaviour { private void Awake() { Plugin.Log.LogInfo((object)"常駐UI走査ランナーを開始しました。"); } private void Update() { Plugin.Instance.RefreshVisibleTextThrottled(); } } internal static class PluginInfo { public const string PLUGIN_GUID = "local.REPOJPTranslationAndFontUpdated"; public const string PLUGIN_NAME = "REPO JP Translation and Font Updated"; public const string PLUGIN_VERSION = "4.0.0"; } } namespace REPOJapaneseTranslation.Patches { internal static class MenuPageTranslator { internal static void TranslatePage(MenuPage? menuPage) { if (!((Object)(object)menuPage == (Object)null)) { menuPage.menuHeaderName = TranslationManager.Translate(menuPage.menuHeaderName, logUntranslated: false); TMP_Text[] componentsInChildren = ((Component)menuPage).GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text text in componentsInChildren) { TranslateTextComponent(text); } } } private static void TranslateTextComponent(TMP_Text text) { if (!((Object)(object)text == (Object)null) && !string.IsNullOrEmpty(text.text) && FontManager.ShouldProcess(text)) { string text2 = TranslationManager.Translate(text.text, logUntranslated: false); if (text2 != text.text) { text.text = text2; } FontManager.AdjustTranslatedLayout(text, text2); } } } [HarmonyPatch(typeof(MenuButton), "Awake")] internal static class MenuButtonTranslationPatch { [HarmonyPrefix] private static void Prefix(MenuButton __instance) { if (!((Object)(object)__instance == (Object)null) && !string.IsNullOrEmpty(__instance.buttonTextString)) { __instance.buttonTextString = TranslationManager.Translate(__instance.buttonTextString); } } } [HarmonyPatch(typeof(MenuManager), "PageOpen", new Type[] { typeof(MenuPageIndex), typeof(bool) })] internal static class MenuPageOpenTranslationPatch { [HarmonyPostfix] private static void Postfix(MenuPage __result) { MenuPageTranslator.TranslatePage(__result); } } [HarmonyPatch(typeof(MenuPage), "Start")] internal static class MenuPageStartTranslationPatch { [HarmonyPostfix] private static void Postfix(MenuPage __instance) { MenuPageTranslator.TranslatePage(__instance); } } [HarmonyPatch(typeof(MenuManager), "PagePopUp")] internal static class MenuPopUpTranslationPatch { [HarmonyPrefix] private static void Prefix(ref string headerText, ref string bodyText, ref string buttonText) { headerText = TranslationManager.Translate(headerText); bodyText = TranslationManager.Translate(bodyText); buttonText = TranslationManager.Translate(buttonText); } } [HarmonyPatch(typeof(MenuManager), "PagePopUpTwoOptions")] internal static class MenuTwoOptionPopUpTranslationPatch { [HarmonyPrefix] private static void Prefix(ref string popUpHeader, ref string popUpText, ref string option1Text, ref string option2Text) { popUpHeader = TranslationManager.Translate(popUpHeader); popUpText = TranslationManager.Translate(popUpText); option1Text = TranslationManager.Translate(option1Text); option2Text = TranslationManager.Translate(option2Text); } } [HarmonyPatch(typeof(MenuPageSaves), "SaveFileSelected")] internal static class MenuPageSavesSaveFileSelectedPatch { [HarmonyPostfix] private static void Postfix(MenuPageSaves __instance) { if ((Object)(object)__instance == (Object)null) { return; } string newValue = TranslationManager.Translate("Total Haul:", logUntranslated: false); string newValue2 = TranslationManager.Translate("Current Moon:", logUntranslated: false); TMP_Text[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.text) && FontManager.ShouldProcess(val)) { string text = val.text; string text2 = text.Replace("Total Haul:", newValue).Replace("Current Moon:", newValue2); if (text2 != text) { val.text = text2; } FontManager.AdjustTranslatedLayout(val, text2); } } } } internal static class TextRefreshHook { private static bool s_loggedFirstTick; internal static void Postfix() { if (!s_loggedFirstTick) { s_loggedFirstTick = true; Plugin.Log.LogInfo((object)"TMP更新フックが動作しました。"); } Plugin.Instance.RefreshVisibleTextThrottled(); } } internal static class UGUITextRefreshHook { private static bool s_loggedFirstComponent; internal static void Postfix(Text __instance) { if (!s_loggedFirstComponent) { s_loggedFirstComponent = true; Plugin.Log.LogInfo((object)"UGUI Textコンポーネントを検出しました。"); } Plugin.Instance.RefreshTextComponent(__instance); } } internal static class UGUITextSetterHook { internal static void Prefix(Text __instance, ref string __0) { __0 = TranslationManager.Translate(__0); FontManager.AdjustTranslatedLayout(__instance, __0); } } internal static class TMPComponentRefreshHook { private static bool s_loggedFirstComponent; internal static void Postfix(TMP_Text __instance) { if (!s_loggedFirstComponent) { s_loggedFirstComponent = true; Plugin.Log.LogInfo((object)("TMP表示コンポーネントを検出しました: " + ((object)__instance).GetType().Name)); } Plugin.Instance.RefreshTextComponent(__instance); } } [HarmonyPatch] internal static class TMPFontPatch { [HarmonyPatch(typeof(RunManager), "Awake")] [HarmonyPostfix] private static void ApplyFontAfterSceneLoad() { FontManager.ApplyToAllTextComponents(); } } [HarmonyPatch] internal static class TMPTextTranslationPatch { [HarmonyTargetMethods] private static IEnumerable<MethodBase> TargetMethods() { MethodInfo methodInfo = AccessTools.PropertySetter(typeof(TMP_Text), "text"); if (methodInfo != null) { yield return methodInfo; } foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(TMP_Text))) { if (!(declaredMethod.Name != "SetText")) { ParameterInfo[] parameters = declaredMethod.GetParameters(); if (parameters.Length != 0 && !(parameters[0].ParameterType != typeof(string))) { yield return declaredMethod; } } } } [HarmonyPrefix] private static void TranslateText(TMP_Text __instance, ref string __0) { if (FontManager.ShouldProcess(__instance)) { __0 = TranslationManager.Translate(__0); FontManager.AdjustTranslatedLayout(__instance, __0); } } } [HarmonyPatch(typeof(TruckScreenText), "UpdateTaxmanNickname")] internal static class TruckScreenTextUpdateTaxmanNicknamePatch { [HarmonyPrefix] private static void Prefix(ref string newName) { newName = TranslationManager.Translate(newName, logUntranslated: false); } } [HarmonyPatch(typeof(TuckScreenLocked), "LockChatToggle")] internal static class TuckScreenLockedLockChatTogglePatch { [HarmonyPrefix] private static void Prefix(ref string _lockedText) { _lockedText = TranslationManager.Translate(_lockedText, logUntranslated: false); } } } namespace REPOJapaneseTranslation.Localization { internal static class FontManager { private readonly struct LayoutProfile { internal float Scale { get; } internal float MinimumScale { get; } internal bool AllowWrapping { get; } internal bool AutoSizing { get; } internal float AdditionalLineSpacing { get; } internal bool ConstrainToBounds { get; } internal float AbsoluteSize { get; } internal LayoutProfile(float scale, float minimumScale, bool allowWrapping, bool autoSizing = true, float additionalLineSpacing = 0f, bool constrainToBounds = false, float absoluteSize = 0f) { Scale = scale; MinimumScale = minimumScale; AllowWrapping = allowWrapping; AutoSizing = autoSizing; AdditionalLineSpacing = additionalLineSpacing; ConstrainToBounds = constrainToBounds; AbsoluteSize = absoluteSize; } } private readonly struct TMPFontState { private WeakReference<TMP_Text> Owner { get; } internal TMP_FontAsset Font { get; } internal float FontSize { get; } internal float FontSizeMin { get; } internal float FontSizeMax { get; } internal bool AutoSizing { get; } internal bool WordWrapping { get; } internal TextOverflowModes Overflow { get; } internal float LineSpacing { get; } internal TMPFontState(TMP_Text component) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) Owner = new WeakReference<TMP_Text>(component); Font = component.font; FontSize = component.fontSize; FontSizeMin = component.fontSizeMin; FontSizeMax = component.fontSizeMax; AutoSizing = component.enableAutoSizing; WordWrapping = component.enableWordWrapping; Overflow = component.overflowMode; LineSpacing = component.lineSpacing; } internal bool Matches(TMP_Text component) { if (Owner.TryGetTarget(out TMP_Text target)) { return target == component; } return false; } internal void Restore(TMP_Text component) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) component.font = Font; component.fontSize = FontSize; component.fontSizeMin = FontSizeMin; component.fontSizeMax = FontSizeMax; component.enableAutoSizing = AutoSizing; component.enableWordWrapping = WordWrapping; component.overflowMode = Overflow; component.lineSpacing = LineSpacing; } } private readonly struct UGUIFontState { private WeakReference<Text> Owner { get; } internal Font Font { get; } internal int FontSize { get; } internal int MinSize { get; } internal int MaxSize { get; } internal bool BestFit { get; } internal HorizontalWrapMode Overflow { get; } internal UGUIFontState(Text component) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) Owner = new WeakReference<Text>(component); Font = component.font; FontSize = component.fontSize; MinSize = component.resizeTextMinSize; MaxSize = component.resizeTextMaxSize; BestFit = component.resizeTextForBestFit; Overflow = component.horizontalOverflow; } internal bool Matches(Text component) { if (Owner.TryGetTarget(out Text target)) { return target == component; } return false; } internal void Restore(Text component) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) component.font = Font; component.fontSize = FontSize; component.resizeTextMinSize = MinSize; component.resizeTextMaxSize = MaxSize; component.resizeTextForBestFit = BestFit; component.horizontalOverflow = Overflow; } } private const string FontResourceName = "REPOJapaneseTranslation.fonts.CP_Revenge.ttf"; private const string FontEnginePatchId = "local.REPOJPTranslationAndFontUpdated.FontEngine"; private static readonly Dictionary<Font, byte[]> s_fontBytesByDummyFont = new Dictionary<Font, byte[]>(); private static readonly Dictionary<int, TMPFontState> s_originalTMPStates = new Dictionary<int, TMPFontState>(); private static readonly Dictionary<int, UGUIFontState> s_originalUGUIStates = new Dictionary<int, UGUIFontState>(); private static readonly HashSet<string> s_compactTitles = new HashSet<string>(StringComparer.Ordinal) { "ゲームプレイ", "グラフィックス", "オーディオ", "コントロール", "パスワード", "ロビー", "メインメニュー", "警告", "自爆", "ランダム", "プリセット", "セーブをロード", "ゲームをホストする", "設定を初期化", "デフォルト設定にリセット" }; private static readonly HashSet<string> s_dialogButtons = new HashSet<string>(StringComparer.Ordinal) { "はい", "はい!", "いいえ", "いいえ…", "戻る", "続ける", "スキップ", "わかりました", "セーブをロード", "セーブを削除" }; private static readonly HashSet<string> s_uniformMenuItems = new HashSet<string>(StringComparer.Ordinal) { "プライベートゲーム", "パブリックゲーム", "フレンドに参加", "シングルプレイ", "シングルプレイモード", "チュートリアル", "続ける", "カスタマイズ", "設定", "自爆", "メインメニュー", "タイトルに戻る", "ゲームを終わる", "ゲームを終了する" }; private static readonly HashSet<string> s_settingsRows = new HashSet<string>(StringComparer.Ordinal) { "ヒント", "プレイヤー名", "光過敏症設定", "クモ恐怖症", "照準感度", "カメラスムージング", "カメラシェイク", "シャドウ距離", "モーションブラー", "レンズ歪み", "ブルーム", "色収差", "グレイン", "グリッチループ", "ピクセル化", "音楽音量", "効果音音量", "近接ボイス音量", "マイク音量", "プッシュトゥトーク", "掴む", "回転", "押す", "引く", "ウィンドウモード", "垂直同期", "ガンマ", "最大FPS", "ライト距離", "シャドウ品質", "シャドウ距離", "マスター音量", "テキスト読み上げ音量", "オン", "オフ", "デフォルト", "フルスクリーン", "ウィンドウ", "ボーダーレス", "低", "中", "高", "最高" }; private static TMP_FontAsset? s_checkpointFont; private static Font? s_checkpointUnityFont; private static bool s_initialized; private static bool s_fontEnginePatched; internal static void Initialize(string pluginLocation) { if (s_initialized) { return; } s_initialized = true; if (!Plugin.EnableJapaneseFont.Value) { return; } try { Assembly executingAssembly = Assembly.GetExecutingAssembly(); using Stream stream = executingAssembly.GetManifestResourceStream("REPOJapaneseTranslation.fonts.CP_Revenge.ttf"); if (stream == null) { Plugin.Log.LogError((object)"埋め込みフォントが見つかりません: REPOJapaneseTranslation.fonts.CP_Revenge.ttf"); return; } byte[] array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num == 0) { throw new EndOfStreamException("埋め込みフォントを最後まで読み取れませんでした。"); } } PatchFontEngine(); s_checkpointFont = CreateFontAsset(array); if ((Object)(object)s_checkpointFont == (Object)null) { Plugin.Log.LogError((object)"チェックポイント★リベンジのTMPフォント生成に失敗しました。"); return; } ApplyToAllTextComponents(); Plugin.Log.LogInfo((object)$"チェックポイント★リベンジを読み込みました (埋め込み未改変TTF ({array.Length:N0} bytes))。"); } catch (Exception arg) { Plugin.Log.LogError((object)$"フォント初期化に失敗しました: {arg}"); } } internal static void ApplyToText(TMP_Text? textComponent) { if (ShouldProcess(textComponent) && ContainsJapanese(textComponent.text)) { AdjustTranslatedLayout(textComponent, textComponent.text); } } internal static bool ShouldProcess(TMP_Text? textComponent) { if ((Object)(object)textComponent == (Object)null) { return false; } string text = GetHierarchyPath(textComponent.transform).ToLowerInvariant(); bool flag = text.Contains("monitor") || text.Contains("truckscreen") || (text.Contains("truck") && text.Contains("screen")); return !flag; } internal static void ApplyToText(Text? textComponent) { if ((Object)(object)textComponent != (Object)null && ContainsJapanese(textComponent.text)) { AdjustTranslatedLayout(textComponent, textComponent.text); } } internal static void ApplyToAllTextComponents() { if ((Object)(object)s_checkpointFont == (Object)null) { return; } TMP_Text[] array = Object.FindObjectsOfType<TMP_Text>(true); TMP_Text[] array2 = array; foreach (TMP_Text textComponent in array2) { if (ShouldProcess(textComponent)) { ApplyToText(textComponent); } } Plugin.Log.LogDebug((object)$"{array.Length}個のTMPテキストを確認しました。"); } internal static void AdjustTranslatedLayout(TMP_Text textComponent, string translatedText) { if (!Plugin.EnableJapaneseFont.Value || (Object)(object)s_checkpointFont == (Object)null || !ShouldProcess(textComponent)) { return; } int instanceID = ((Object)textComponent).GetInstanceID(); if (!s_originalTMPStates.TryGetValue(instanceID, out var value) || !value.Matches(textComponent)) { value = new TMPFontState(textComponent); s_originalTMPStates[instanceID] = value; } if (!ContainsJapanese(translatedText)) { value.Restore(textComponent); if (IsSettingsHierarchy(GetHierarchyPath(textComponent.transform)) && ContainsDigit(translatedText)) { ApplyOriginalFontScale(textComponent, value, 0.78f); } return; } LayoutProfile layoutProfile = ResolveProfile(textComponent, translatedText); textComponent.font = s_checkpointFont; textComponent.enableAutoSizing = layoutProfile.AutoSizing; float val = ((layoutProfile.AbsoluteSize > 0f) ? layoutProfile.AbsoluteSize : (value.FontSize * layoutProfile.Scale)); float val2 = ((layoutProfile.AbsoluteSize > 0f) ? layoutProfile.AbsoluteSize : (value.FontSizeMax * layoutProfile.Scale)); float val3 = ((layoutProfile.AbsoluteSize > 0f) ? (layoutProfile.AbsoluteSize * 0.78f) : (value.FontSizeMax * layoutProfile.MinimumScale)); textComponent.fontSize = Math.Max(1f, val); textComponent.fontSizeMax = Math.Max(1f, val2); textComponent.fontSizeMin = Math.Max(1f, val3); textComponent.enableWordWrapping = layoutProfile.AllowWrapping; textComponent.overflowMode = (TextOverflowModes)(layoutProfile.ConstrainToBounds ? 1 : 0); textComponent.lineSpacing = value.LineSpacing + layoutProfile.AdditionalLineSpacing; } internal static void AdjustTranslatedLayout(Text textComponent, string translatedText) { if (Plugin.EnableJapaneseFont.Value && !((Object)(object)s_checkpointUnityFont == (Object)null) && !((Object)(object)textComponent == (Object)null)) { int instanceID = ((Object)textComponent).GetInstanceID(); if (!s_originalUGUIStates.TryGetValue(instanceID, out var value) || !value.Matches(textComponent)) { value = new UGUIFontState(textComponent); s_originalUGUIStates[instanceID] = value; } if (!ContainsJapanese(translatedText)) { value.Restore(textComponent); return; } float num = ResolveUGUIScale(translatedText); textComponent.font = s_checkpointUnityFont; textComponent.fontSize = Math.Max(1, (int)Math.Round((float)value.FontSize * num)); textComponent.resizeTextForBestFit = true; textComponent.resizeTextMinSize = Math.Max(1, (int)Math.Round((float)value.MaxSize * 0.28f)); textComponent.resizeTextMaxSize = Math.Max(1, (int)Math.Round((float)value.MaxSize * num)); textComponent.horizontalOverflow = (HorizontalWrapMode)(!translatedText.Contains('\n')); } } private static LayoutProfile ResolveProfile(TMP_Text component, string text) { string text2 = RemoveSimpleRichText(text).Trim(); string text3 = GetHierarchyPath(component.transform).ToLowerInvariant(); switch (text2) { case "パスワード": return new LayoutProfile(0.5f, 0.25f, allowWrapping: false); case "ロビー": return new LayoutProfile(0.46f, 0.23f, allowWrapping: false); case "セーブをロード": return new LayoutProfile(0.52f, 0.26f, allowWrapping: false); case "光敏感性発作についての警告": case "光過敏症の警告": return new LayoutProfile(0.62f, 0.52f, allowWrapping: true, autoSizing: false, 12f); default: if (text2.StartsWith("このゲームには点滅する光が含まれています。", StringComparison.Ordinal)) { return new LayoutProfile(0.65f, 0.54f, allowWrapping: true, autoSizing: false, 14f); } if (text2.StartsWith("一部の方は、光のパターンや点滅する光", StringComparison.Ordinal)) { return new LayoutProfile(0.58f, 0.46f, allowWrapping: true, autoSizing: false, 24f); } if (text2 == "メニュー") { return new LayoutProfile(0.4f, 0.28f, allowWrapping: false); } if (s_uniformMenuItems.Contains(text2)) { return new LayoutProfile(1f, 0.78f, allowWrapping: false, autoSizing: false, 0f, constrainToBounds: false, 20f); } if (text2.StartsWith("総回収額:", StringComparison.Ordinal) || text2.StartsWith("現在の月:", StringComparison.Ordinal)) { return new LayoutProfile(0.68f, 0.48f, allowWrapping: false); } switch (text2) { case "(クリックして名前変更)": return new LayoutProfile(0.52f, 0.36f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "シングルプレイモード": return new LayoutProfile(0.44f, 0.3f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "ウィンドウモード": return new LayoutProfile(0.23f, 0.16f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "モーションブラー": return new LayoutProfile(0.24f, 0.16f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "カメラスムージング": case "カメラの滑らかさ": return new LayoutProfile(0.22f, 0.15f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "カメラアニメーション": case "カメラの動き": return new LayoutProfile(0.22f, 0.15f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "アンチエイリアシング": return new LayoutProfile(0.22f, 0.15f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "リッチプレゼンス": return new LayoutProfile(0.22f, 0.15f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "アイテム自動装備": return new LayoutProfile(0.22f, 0.15f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); case "オン": case "オフ": return new LayoutProfile(0.48f, 0.34f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); default: { if (IsControlHierarchy(text3)) { return new LayoutProfile(0.52f, 0.34f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); } if (text2 == "プリセット") { return new LayoutProfile(0.82f, 0.58f, allowWrapping: false); } if (text2 == "確認") { return new LayoutProfile(0.95f, 0.66f, allowWrapping: false); } if (text2.StartsWith("マイク", StringComparison.Ordinal)) { return new LayoutProfile(0.36f, 0.22f, allowWrapping: true); } if (s_dialogButtons.Contains(text2)) { return new LayoutProfile(0.58f, 0.3f, allowWrapping: false); } if (s_settingsRows.Contains(text2)) { return new LayoutProfile(0.3f, 0.18f, allowWrapping: false, autoSizing: true, 0f, constrainToBounds: true); } if (s_compactTitles.Contains(text2)) { return new LayoutProfile(0.52f, 0.25f, allowWrapping: false); } bool flag = text3.Contains("popup") || text3.Contains("dialog") || text3.Contains("confirm") || text3.Contains("warning"); bool flag2 = IsSettingsHierarchy(text3); bool flag3 = text3.Contains("password"); bool flag4 = text3.Contains("lobby"); bool flag5 = text3.Contains("save"); if (flag3) { return new LayoutProfile(0.5f, 0.24f, text.Contains('\n')); } if (flag4) { return new LayoutProfile(0.5f, 0.24f, text.Contains('\n')); } if (flag5) { return new LayoutProfile(0.62f, 0.34f, text.Length > 18 || text.Contains('\n')); } if (flag2) { return new LayoutProfile(0.3f, 0.18f, text.Length > 20 || text.Contains('\n'), autoSizing: true, 0f, constrainToBounds: true); } if (flag) { return new LayoutProfile(0.45f, 0.2f, allowWrapping: true, autoSizing: true, 0f, constrainToBounds: true); } if (text.Contains('\n') || text2.Length >= 18) { return new LayoutProfile(0.46f, 0.2f, allowWrapping: true, autoSizing: true, 0f, constrainToBounds: true); } return new LayoutProfile(Plugin.FontSizeScale.Value, 0.28f, allowWrapping: false); } } } } private static float ResolveUGUIScale(string text) { string text2 = RemoveSimpleRichText(text).Trim(); if (s_dialogButtons.Contains(text2)) { return 0.58f; } if (s_settingsRows.Contains(text2)) { return 0.47f; } if (s_compactTitles.Contains(text2)) { return 0.52f; } if (text2.Length < 18 && !text.Contains('\n')) { return Plugin.FontSizeScale.Value; } return 0.46f; } private static bool ContainsJapanese(string? text) { if (string.IsNullOrEmpty(text)) { return false; } foreach (char c in text) { if ((c >= '\u3040' && c <= 'ヿ') || (c >= '㐀' && c <= '鿿') || (c >= '豈' && c <= '\ufaff') || (c >= 'ヲ' && c <= '゚')) { return true; } } return false; } private static bool ContainsDigit(string? text) { if (string.IsNullOrEmpty(text)) { return false; } foreach (char c in text) { if (char.IsDigit(c)) { return true; } } return false; } private static bool IsSettingsHierarchy(string hierarchy) { string text = hierarchy.ToLowerInvariant(); if (!text.Contains("setting") && !text.Contains("graphic") && !text.Contains("audio") && !text.Contains("control")) { return text.Contains("gameplay"); } return true; } private static bool IsControlHierarchy(string hierarchy) { string text = hierarchy.ToLowerInvariant(); if (!text.Contains("control") && !text.Contains("keybind")) { return text.Contains("input"); } return true; } private static void ApplyOriginalFontScale(TMP_Text component, TMPFontState original, float scale) { component.enableAutoSizing = false; component.fontSize = Math.Max(1f, original.FontSize * scale); component.fontSizeMax = Math.Max(1f, original.FontSizeMax * scale); component.fontSizeMin = Math.Max(1f, original.FontSizeMax * scale * 0.65f); component.overflowMode = (TextOverflowModes)0; } private static string GetHierarchyPath(Transform? transform) { if ((Object)(object)transform == (Object)null) { return string.Empty; } string text = ((Object)transform).name; Transform parent = transform.parent; int num = 0; while ((Object)(object)parent != (Object)null && num++ < 12) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } private static string RemoveSimpleRichText(string text) { StringBuilder stringBuilder = new StringBuilder(text.Length); bool flag = false; foreach (char c in text) { if (c == '<') { flag = true; } else if (c == '>' && flag) { flag = false; } else if (!flag) { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private static TMP_FontAsset? CreateFontAsset(byte[] fontBytes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) FontEngine.InitializeFontEngine(); FontEngineError val = FontEngine.LoadFontFace(fontBytes); if ((int)val != 0) { Plugin.Log.LogError((object)$"TTFの検証に失敗しました: {val}"); return null; } Font val2 = (s_checkpointUnityFont = new Font("CP_Revenge")); s_fontBytesByDummyFont[val2] = fontBytes; TMP_FontAsset val3 = TMP_FontAsset.CreateFontAsset(val2); if ((Object)(object)val3 == (Object)null) { s_fontBytesByDummyFont.Remove(val2); s_checkpointUnityFont = null; Object.Destroy((Object)(object)val2); return null; } ((Object)val3).name = "Checkpoint Revenge Dynamic"; val3.atlasPopulationMode = (AtlasPopulationMode)1; FaceInfo faceInfo = val3.faceInfo; ((FaceInfo)(ref faceInfo)).lineHeight = ((FaceInfo)(ref faceInfo)).lineHeight * Plugin.FontLineHeightScale.Value; val3.faceInfo = faceInfo; ConfigureTextMeshProMaterial(val3); return val3; } private static void ConfigureTextMeshProMaterial(TMP_FontAsset fontAsset) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) Shader val = Shader.Find("TextMeshPro/Distance Field"); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"TextMeshPro/Distance Fieldシェーダーが見つかりません。"); return; } Material material = ((TMP_Asset)fontAsset).material; material.shader = val; if (material.HasProperty("_GlowColor")) { material.SetColor("_GlowColor", Color.clear); } if (material.HasProperty("_GlowPower")) { material.SetFloat("_GlowPower", 0f); } } private static void PatchFontEngine() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown if (!s_fontEnginePatched) { MethodInfo method = typeof(FontEngine).GetMethod("LoadFontFace", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(Font), typeof(int) }, null); MethodInfo method2 = typeof(FontManager).GetMethod("LoadFontFacePrefix", BindingFlags.Static | BindingFlags.NonPublic); if (method == null || method2 == null) { throw new MissingMethodException("FontEngine.LoadFontFace(Font, int) が見つかりません。"); } new Harmony("local.REPOJPTranslationAndFontUpdated.FontEngine").Patch((MethodBase)method, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); s_fontEnginePatched = true; } } private static bool LoadFontFacePrefix(Font font, int pointSize, ref FontEngineError __result) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected I4, but got Unknown if ((Object)(object)font == (Object)null || !s_fontBytesByDummyFont.TryGetValue(font, out byte[] value)) { return true; } __result = (FontEngineError)(int)FontEngine.LoadFontFace(value, pointSize); return false; } } internal static class TranslationManager { private sealed class TranslationTemplate { private readonly Regex _pattern; private readonly string _translatedTemplate; private readonly string[] _placeholderTokens; internal int SourceLength { get; } private TranslationTemplate(Regex pattern, string translatedTemplate, string[] placeholderTokens, int sourceLength) { _pattern = pattern; _translatedTemplate = translatedTemplate; _placeholderTokens = placeholderTokens; SourceLength = sourceLength; } internal static TranslationTemplate Create(string sourceTemplate, string translatedTemplate) { MatchCollection matchCollection = s_placeholderTokenRegex.Matches(sourceTemplate); StringBuilder stringBuilder = new StringBuilder(); string[] array = new string[matchCollection.Count]; stringBuilder.Append('^'); int num = 0; int num2; for (int i = 0; i < matchCollection.Count; i++) { Match match = matchCollection[i]; num2 = num; stringBuilder.Append(Regex.Escape(sourceTemplate.Substring(num2, match.Index - num2))); stringBuilder.Append($"(?<p{i}>.+?)"); array[i] = match.Value; num = match.Index + match.Length; } num2 = num; stringBuilder.Append(Regex.Escape(sourceTemplate.Substring(num2, sourceTemplate.Length - num2))); stringBuilder.Append('$'); Regex pattern = new Regex(stringBuilder.ToString(), RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant); return new TranslationTemplate(pattern, translatedTemplate, array, sourceTemplate.Length); } internal bool TryTranslate(string text, out string result) { Match match = _pattern.Match(text); if (!match.Success) { result = text; return false; } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); for (int i = 0; i < _placeholderTokens.Length; i++) { string key = _placeholderTokens[i]; if (!dictionary.ContainsKey(key)) { dictionary[key] = match.Groups[$"p{i}"].Value; } } result = _translatedTemplate; foreach (KeyValuePair<string, string> item in dictionary) { item.Deconstruct(out var key2, out var value); string oldValue = key2; string newValue = value; result = result.Replace(oldValue, newValue, StringComparison.Ordinal); } return true; } } private static readonly Regex s_placeholderTokenRegex = new Regex("\\{[^{}]+\\}|\\[[^\\[\\]]+\\]", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex s_actionSuffixRegex = new Regex("^(?<body>.+?)(?<suffix>\\s*(?:<[^>]+>)*\\[[^\\[\\]]+\\](?:</[^>]+>)*\\s*)$", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex s_richTextSuffixRegex = new Regex("^(?<body>.+?)(?<suffix>\\s*(?:<[^>]+>)+\\s*)$", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex s_labeledPrefixRegex = new Regex("^(?<leadingTags>(?:<[^>]+>)*)(?<label>[^<>]+?)(?<separator>\\s*>\\s*)(?<trailingTags>(?:</[^>]+>)*)(?<body>.+)$", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Dictionary<string, string> s_translations = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly Dictionary<string, string> s_translationsUpper = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly List<TranslationTemplate> s_templates = new List<TranslationTemplate>(); private static readonly HashSet<string> s_loggedUntranslated = new HashSet<string>(StringComparer.Ordinal); private static bool s_initialized; internal static int TranslationCount => s_translations.Count; internal static void Initialize(string pluginLocation) { if (s_initialized) { return; } s_initialized = true; Assembly executingAssembly = Assembly.GetExecutingAssembly(); using Stream stream = executingAssembly.GetManifestResourceStream("REPOJapaneseTranslation.translations.ja.json"); if (stream == null) { Plugin.Log.LogError((object)"埋め込みリソース 'REPOJapaneseTranslation.translations.ja.json' が見つかりません。"); return; } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); LoadJson(streamReader.ReadToEnd()); string path = Path.GetDirectoryName(pluginLocation) ?? string.Empty; string path2 = Path.Combine(path, "translations.custom.json"); if (File.Exists(path2)) { LoadJson(File.ReadAllText(path2, Encoding.UTF8)); Plugin.Log.LogInfo((object)"translations.custom.jsonを追加読み込みしました。"); } Plugin.Log.LogInfo((object)$"翻訳辞書を読み込みました: {s_translations.Count} 件"); } private static void LoadJson(string json) { //IL_00f3: Expected O, but got Unknown try { Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); if (dictionary == null) { return; } foreach (var (text3, text4) in dictionary) { if (!string.IsNullOrEmpty(text3) && text3[0] != '_' && !text3.StartsWith("//", StringComparison.Ordinal)) { string text5 = NormalizeTranslationValue(text4); s_translations[text3] = text5; string key = NormalizeKey(text3).ToUpperInvariant(); s_translationsUpper[key] = text5; if (s_placeholderTokenRegex.IsMatch(text3)) { s_templates.Add(TranslationTemplate.Create(NormalizeKey(text3), text5)); } } } s_templates.Sort((TranslationTemplate left, TranslationTemplate right) => right.SourceLength.CompareTo(left.SourceLength)); } catch (JsonException ex) { JsonException ex2 = ex; Plugin.Log.LogError((object)("翻訳JSONのパースに失敗しました: " + ((Exception)(object)ex2).Message)); } } internal static string Translate(string text, bool logUntranslated = true) { if (!Plugin.EnableTranslation.Value || string.IsNullOrEmpty(text)) { return text; } SplitOuterWhitespace(text, out string leadingWhitespace, out string coreText, out string trailingWhitespace); if (coreText.Length == 0) { return text; } if (TryTranslateCore(coreText, out string result)) { return leadingWhitespace + result + trailingWhitespace; } if (logUntranslated && Plugin.LogUntranslated.Value && s_loggedUntranslated.Add(text)) { Plugin.Log.LogInfo((object)("[未翻訳] \"" + text.Replace("\n", "\\n") + "\"")); } return text; } private static bool TryTranslateCore(string text, out string result) { if (TryTranslateInlineText(text, out result)) { return true; } if (TryTranslateMultilineBlock(text, out result)) { return true; } result = text; return false; } private static bool TryTranslateInlineText(string text, out string result) { if (TryTranslateLookup(text, out result)) { return true; } if (TryTranslateTemplate(text, out result)) { return true; } if (TryTranslateLabeledPrefix(text, out result)) { return true; } if (TryTranslateRichTextSuffix(text, out result)) { return true; } if (TryTranslateActionSuffix(text, out result)) { return true; } if (TryTranslatePhraseSequence(text, out result)) { return true; } result = text; return false; } private static bool TryTranslateLookup(string text, out string result) { if (s_translations.TryGetValue(text, out result)) { return true; } string text2 = NormalizeKey(text); if (text2 != text && s_translations.TryGetValue(text2, out result)) { return true; } string key = text2.ToUpperInvariant(); if (s_translationsUpper.TryGetValue(key, out result)) { return true; } result = text; return false; } private static bool TryTranslateTemplate(string text, out string result) { string text2 = NormalizeKey(text); foreach (TranslationTemplate s_template in s_templates) { if (s_template.TryTranslate(text2, out result)) { return true; } } result = text; return false; } private static bool TryTranslateLabeledPrefix(string text, out string result) { Match match = s_labeledPrefixRegex.Match(text); if (!match.Success) { result = text; return false; } string value = match.Groups["leadingTags"].Value; string value2 = match.Groups["label"].Value; string value3 = match.Groups["separator"].Value; string value4 = match.Groups["trailingTags"].Value; string value5 = match.Groups["body"].Value; if (!TryTranslateCore(value5, out string result2)) { result = text; return false; } string result3; string text2 = (TryTranslateLookup(value2, out result3) ? result3 : value2); result = value + text2 + value3 + value4 + result2; return true; } private static bool TryTranslateActionSuffix(string text, out string result) { Match match = s_actionSuffixRegex.Match(text); if (!match.Success) { result = text; return false; } string value = match.Groups["body"].Value; string value2 = match.Groups["suffix"].Value; if (!TryTranslateLookup(value, out string result2) && !TryTranslateTemplate(value, out result2) && !TryTranslatePhraseSequence(value, out result2)) { result = text; return false; } result = result2 + value2; return true; } private static bool TryTranslateRichTextSuffix(string text, out string result) { Match match = s_richTextSuffixRegex.Match(text); if (!match.Success) { result = text; return false; } string value = match.Groups["body"].Value; string value2 = match.Groups["suffix"].Value; if (!TryTranslateLookup(value, out string result2) && !TryTranslateTemplate(value, out result2) && !TryTranslatePhraseSequence(value, out result2)) { result = text; return false; } result = result2 + value2; return true; } private static bool TryTranslateMultilineBlock(string text, out string result) { if (!text.Contains('\n')) { result = text; return false; } string[] array = text.Split('\n'); bool flag = false; for (int i = 0; i < array.Length; i++) { string text2 = array[i]; if (string.IsNullOrWhiteSpace(text2)) { continue; } SplitOuterWhitespace(text2, out string leadingWhitespace, out string coreText, out string trailingWhitespace); if (coreText.Length != 0 && TryTranslateInlineText(coreText, out string result2)) { string text3 = leadingWhitespace + result2 + trailingWhitespace; if (!(text3 == text2)) { array[i] = text3; flag = true; } } } result = (flag ? string.Join("\n", array) : text); return flag; } private static bool TryTranslatePhraseSequence(string text, out string result) { result = text; if (!CanUsePhraseSegmentation(text)) { return false; } SplitNumericPrefix(text, out string prefix, out string body); if (body.Length == 0) { return false; } string[] array = body.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { return false; } List<string> list = new List<string>(); int matchedTokenCount; for (int i = 0; i < array.Length; i += matchedTokenCount) { if (!TryMatchLongestPhrase(array, i, out matchedTokenCount, out string translatedFragment)) { return false; } list.Add(translatedFragment); } if (list.Count < 2) { return false; } result = prefix + string.Join("\n", list); return true; } private static bool TryMatchLongestPhrase(IReadOnlyList<string> tokens, int startIndex, out int matchedTokenCount, out string translatedFragment) { int val = tokens.Count - startIndex; int num = Math.Min(5, val); for (int num2 = num; num2 >= 1; num2--) { string text = string.Join(" ", tokens, startIndex, num2); if (TryTranslateLookup(text, out translatedFragment)) { matchedTokenCount = num2; return true; } } matchedTokenCount = 0; translatedFragment = string.Empty; return false; } private static bool CanUsePhraseSegmentation(string text) { bool result = false; foreach (char c in text) { if (char.IsLetter(c)) { result = true; } else if (!char.IsDigit(c) && !char.IsWhiteSpace(c) && c != '\'' && c != '-' && c != '(' && c != ')' && c != '&') { return false; } } return result; } private static void SplitNumericPrefix(string text, out string prefix, out string body) { int i; for (i = 0; i < text.Length && char.IsDigit(text[i]); i++) { } if (i == 0 || i >= text.Length || !char.IsWhiteSpace(text[i])) { prefix = string.Empty; body = text; return; } for (; i < text.Length && char.IsWhiteSpace(text[i]); i++) { } prefix = text.Substring(0, i); int num = i; body = text.Substring(num, text.Length - num); } private static void SplitOuterWhitespace(string text, out string leadingWhitespace, out string coreText, out string trailingWhitespace) { int i; for (i = 0; i < text.Length && char.IsWhiteSpace(text[i]); i++) { } int num = text.Length - 1; while (num >= i && char.IsWhiteSpace(text[num])) { num--; } leadingWhitespace = text.Substring(0, i); string text2; if (i > num) { text2 = string.Empty; } else { int num2 = i; text2 = text.Substring(num2, num + 1 - num2); } coreText = text2; string text3; if (num + 1 >= text.Length) { text3 = string.Empty; } else { int num2 = num + 1; text3 = text.Substring(num2, text.Length - num2); } trailingWhitespace = text3; } private static string NormalizeKey(string text) { return text.Replace("\r\n", "\n").Trim(); } private static string NormalizeTranslationValue(string text) { return text.TrimStart(' ', '\t', '\u3000'); } } }