using 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 EnableTranslation { get; private set; } internal static ConfigEntry EnableJapaneseFont { get; private set; } internal static ConfigEntry FontSizeScale { get; private set; } internal static ConfigEntry FontLineHeightScale { get; private set; } internal static ConfigEntry 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("General", "EnableTranslation", true, "ゲーム内テキストを日本語へ翻訳します。"); EnableJapaneseFont = ((BaseUnityPlugin)this).Config.Bind("General", "EnableJapaneseFont", true, "チェックポイント★リベンジをTextMeshProへ適用します。"); FontSizeScale = ((BaseUnityPlugin)this).Config.Bind("General", "FontSizeScale", 0.72f, new ConfigDescription("日本語フォントの表示倍率。1.0がゲーム本来のフォントサイズです。", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 1f), Array.Empty())); FontLineHeightScale = ((BaseUnityPlugin)this).Config.Bind("General", "FontLineHeightScale", 1.35f, new ConfigDescription("複数行テキストの行高倍率。文字が重なる場合に大きくします。", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 2f), Array.Empty())); LogUntranslated = ((BaseUnityPlugin)this).Config.Bind("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(); } 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 hashSet = new HashSet(); 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(true); Text[] array2 = Object.FindObjectsOfType(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(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(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 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 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(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 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(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 s_fontBytesByDummyFont = new Dictionary(); private static readonly Dictionary s_originalTMPStates = new Dictionary(); private static readonly Dictionary s_originalUGUIStates = new Dictionary(); private static readonly HashSet s_compactTitles = new HashSet(StringComparer.Ordinal) { "ゲームプレイ", "グラフィックス", "オーディオ", "コントロール", "パスワード", "ロビー", "メインメニュー", "警告", "自爆", "ランダム", "プリセット", "セーブをロード", "ゲームをホストする", "設定を初期化", "デフォルト設定にリセット" }; private static readonly HashSet s_dialogButtons = new HashSet(StringComparer.Ordinal) { "はい", "はい!", "いいえ", "いいえ…", "戻る", "続ける", "スキップ", "わかりました", "セーブをロード", "セーブを削除" }; private static readonly HashSet s_uniformMenuItems = new HashSet(StringComparer.Ordinal) { "プライベートゲーム", "パブリックゲーム", "フレンドに参加", "シングルプレイ", "シングルプレイモード", "チュートリアル", "続ける", "カスタマイズ", "設定", "自爆", "メインメニュー", "タイトルに戻る", "ゲームを終わる", "ゲームを終了する" }; private static readonly HashSet s_settingsRows = new HashSet(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(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($"(?.+?)"); 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 dictionary = new Dictionary(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 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("^(?.+?)(?\\s*(?:<[^>]+>)*\\[[^\\[\\]]+\\](?:]+>)*\\s*)$", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex s_richTextSuffixRegex = new Regex("^(?.+?)(?\\s*(?:<[^>]+>)+\\s*)$", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex s_labeledPrefixRegex = new Regex("^(?(?:<[^>]+>)*)(?