using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BoneLib;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Bonelab;
using Il2CppSystem;
using Il2CppTMPro;
using LabLocalizer;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "LabLocalizer", "1.0.0", "CAitStudio", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("LabLocalizer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+2b29869b1a1e29dbb38160d669445a7b8a70af2a")]
[assembly: AssemblyProduct("LabLocalizer")]
[assembly: AssemblyTitle("LabLocalizer")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 LabLocalizer
{
public class Core : MelonMod
{
private class BundleFontEntry
{
public string BundlePath;
public string FontName;
public TMP_FontAsset FontAsset;
public string SettingValue => "bundle:" + Path.GetFileName(BundlePath) + "|" + FontName;
public string DisplayName => FontName + " [" + Path.GetFileName(BundlePath) + "]";
}
private sealed class LocalizationReleaseInfo
{
public Version Version { get; set; } = new Version(0, 0, 0);
public string Title { get; set; } = "";
}
private sealed class LocalizationReleaseFetchResult
{
public LocalizationReleaseInfo ReleaseInfo { get; set; }
public bool NetworkFailed { get; set; }
}
public const string ModVersion = "1.0.0";
public static string ModFolder;
public static string TranslationFolder;
public static string FontFolder;
public static string FontBundleFolder;
public static Dictionary<string, string> Translations = new Dictionary<string, string>();
public static Dictionary<string, string> TranslationCache = new Dictionary<string, string>();
public static Dictionary<string, string> ReverseTranslations = new Dictionary<string, string>();
public static Dictionary<string, HashSet<string>> ReverseTranslationCandidates = new Dictionary<string, HashSet<string>>();
public static HashSet<string> AmbiguousTranslatedTexts = new HashSet<string>();
public static HashSet<string> ExportedTexts = new HashSet<string>();
private static Dictionary<string, string> PendingExportLines = new Dictionary<string, string>();
public static HashSet<string> KnownTranslatedTexts = new HashSet<string>();
public static HashSet<string> IgnoredTexts = new HashSet<string>();
public static HashSet<int> TranslatedObjectIDs = new HashSet<int>();
public static HashSet<int> RegisteredTexts = new HashSet<int>();
public static Dictionary<int, bool> ClipboardProxyTextCache = new Dictionary<int, bool>();
public static Dictionary<int, bool> NotificationTextCache = new Dictionary<int, bool>();
public static Dictionary<int, string> OriginalTexts = new Dictionary<int, string>();
public static Dictionary<int, string> LastSetTextInputs = new Dictionary<int, string>();
public static Dictionary<int, string> LastSetTextOutputs = new Dictionary<int, string>();
public static Dictionary<int, HashSet<string>> RichTextPlainExportKeys = new Dictionary<int, HashSet<string>>();
public static Dictionary<int, Dictionary<string, string>> PlainTextExportLocations = new Dictionary<int, Dictionary<string, string>>();
public static List<TMP_Text> ActiveTexts = new List<TMP_Text>();
public static TMP_FontAsset CustomFont;
private static bool CustomFontFromBundle = false;
public static string LastUsedFontPath = "";
public static bool ReadOnlyMode = false;
public static bool ForceReadOnlyMode = false;
private static FileSystemWatcher fileWatcher;
private static DateTime lastReloadTime = DateTime.MinValue;
private static readonly TimeSpan reloadCooldown = TimeSpan.FromSeconds(1.0);
private static float exportFlushTimer = 0f;
private static bool scanScheduled = false;
private static bool reloadScheduled = false;
private static readonly Regex TemplateTokenRegex = new Regex("\\{\\{(?<ARG>[A-Z])\\}\\}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex InvisibleCharsRegex = new Regex("[\\u200B\\u200C\\u200D\\u200E\\u200F\\uFEFF]", RegexOptions.Compiled);
private static readonly Regex JoinedServerRegex = new Regex("^(?<PLAYER>.+?)\\s+joined\\s+the\\s+server\\.?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex LeftServerRegex = new Regex("^(?<PLAYER>.+?)\\s+left\\s+the\\s+server\\.?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex CodeStringRegex = new Regex("^(?<PREFIX>.*?\\bcode\\s*:\\s*)(?<STRING>[A-Za-z0-9][A-Za-z0-9_\\-]{2,})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex RichTextTagRegex = new Regex("</?[A-Za-z][^>\\r\\n]*>", RegexOptions.Compiled);
private Page _page;
private static Page _fontPage;
private static string _settingsFile;
private static MelonPreferences_Category _preferencesCategory;
private static MelonPreferences_Entry<string> _lastUsedFontPathEntry;
private static MelonPreferences_Entry<bool> _readOnlyModeEntry;
private const string BundleFontPrefix = "bundle:";
private static readonly List<AssetBundle> LoadedFontBundles = new List<AssetBundle>();
private static readonly List<BundleFontEntry> BundleFonts = new List<BundleFontEntry>();
private static readonly HashSet<string> LoadedFontBundlePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private static bool FontBundlesLoaded;
private static readonly HashSet<int> QuestFixedFontMaterials = new HashSet<int>();
private static Shader RuntimeTmpShader;
private const string LocalizationManifestDownloadUrl = "https://github.com/MonoIAR/PJ_LabLocalizer/releases/latest/download/manifest.json";
private static bool localizationUpdateCheckScheduled;
public static bool ShouldExportText(string text, TMP_Text textComponent = null)
{
try
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
text = SanitizeText(text, trim: true);
bool flag = false;
bool flag2 = false;
string text2 = text;
foreach (char c in text2)
{
if (char.IsDigit(c))
{
flag = true;
}
if (char.IsLetter(c))
{
flag2 = true;
}
}
if (flag && !flag2)
{
return false;
}
if (IsIgnoredText(text))
{
return false;
}
string key = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true));
if (ReverseTranslations.ContainsKey(key))
{
return false;
}
if (IsTranslationOfOtherText(text))
{
return false;
}
return true;
}
catch
{
return false;
}
}
public static bool IsIgnoredText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
text = SanitizeText(text, trim: true);
if (IgnoredTexts.Contains(text))
{
return true;
}
string item = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true));
if (IgnoredTexts.Contains(item))
{
return true;
}
string text2 = StripRichTextTags(text);
if (text2 != text)
{
text2 = SanitizeText(text2, trim: true);
if (IgnoredTexts.Contains(text2))
{
return true;
}
string item2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text2), trim: true));
if (IgnoredTexts.Contains(item2))
{
return true;
}
}
return false;
}
public static bool IsDynamicText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
if (TemplateTokenRegex.IsMatch(text))
{
return true;
}
string input = NormalizeTextTemplate(text);
return TemplateTokenRegex.IsMatch(input);
}
public static bool IsGlobalUI(TMP_Text textComponent)
{
try
{
string text = GetHierarchyPath(textComponent.transform).ToLower();
if (text.Contains("dontdestroyonload"))
{
return true;
}
if (text.Contains("menu"))
{
return true;
}
if (text.Contains("canvas"))
{
return true;
}
if (text.Contains("ui"))
{
return true;
}
if (text.Contains("button"))
{
return true;
}
return false;
}
catch
{
return false;
}
}
public static bool IsGlobalCommon(TMP_Text textComponent, string text)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
try
{
Scene scene = ((Component)textComponent).gameObject.scene;
string name = ((Scene)(ref scene)).name;
if (name == "DontDestroyOnLoad")
{
return true;
}
if (string.IsNullOrEmpty(name))
{
return true;
}
string[] obj = new string[16]
{
"Loading", "Save", "Server", "Notification", "Fusion", "LAVA-GANG", "Subtitle", "Title", "Disconnected", "Started",
"AutoUpdater", "spawnables", "Loaded", "Please wait", "Successfully", "New Notification"
};
string text2 = text.ToLower();
string[] array = obj;
foreach (string text3 in array)
{
if (text2.Contains(text3.ToLower()))
{
return true;
}
}
return false;
}
catch
{
return false;
}
}
public static string GetHierarchyPath(Transform transform)
{
string text = ((Object)transform).name;
while ((Object)(object)transform.parent != (Object)null)
{
transform = transform.parent;
text = ((Object)transform).name + "/" + text;
}
return text;
}
public static bool IsClipboardProxyText(TMP_Text textComponent)
{
try
{
if ((Object)(object)textComponent == (Object)null)
{
return false;
}
int instanceID = ((Object)textComponent).GetInstanceID();
if (ClipboardProxyTextCache.TryGetValue(instanceID, out var value))
{
return value;
}
bool flag = HasClipboardProxyInHierarchy(textComponent.transform);
ClipboardProxyTextCache[instanceID] = flag;
return flag;
}
catch
{
return false;
}
}
public static bool IsNotificationText(TMP_Text textComponent)
{
try
{
if ((Object)(object)textComponent == (Object)null)
{
return false;
}
int instanceID = ((Object)textComponent).GetInstanceID();
if (NotificationTextCache.TryGetValue(instanceID, out var value))
{
return value;
}
bool flag = HasHeadTitlesInHierarchy(textComponent.transform);
NotificationTextCache[instanceID] = flag;
return flag;
}
catch
{
return false;
}
}
private static bool HasHeadTitlesInHierarchy(Transform transform)
{
if ((Object)(object)transform == (Object)null)
{
return false;
}
try
{
TutorialRig instance = TutorialRig.Instance;
if ((Object)(object)instance != (Object)null && (Object)(object)instance.headTitles != (Object)null && (Object)(object)((Component)instance.headTitles).transform != (Object)null && transform.IsChildOf(((Component)instance.headTitles).transform))
{
return true;
}
}
catch
{
}
Transform val = transform;
while ((Object)(object)val != (Object)null)
{
if (LooksLikeHeadTitlesName(((Object)val).name))
{
return true;
}
val = val.parent;
}
return false;
}
private static bool LooksLikeHeadTitlesName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return false;
}
return name.Replace(" ", "").Replace("_", "").Replace("-", "")
.ToLowerInvariant()
.Contains("headtitles");
}
private static bool HasClipboardProxyInHierarchy(Transform transform)
{
Transform val = transform;
while ((Object)(object)val != (Object)null)
{
if (LooksLikeClipboardProxyName(((Object)val).name))
{
return true;
}
GameObject gameObject = ((Component)val).gameObject;
if ((Object)(object)gameObject != (Object)null && LooksLikeClipboardProxyName(((Object)gameObject).name))
{
return true;
}
MonoBehaviour[] array = Il2CppArrayBase<MonoBehaviour>.op_Implicit(gameObject.GetComponents<MonoBehaviour>());
foreach (MonoBehaviour val2 in array)
{
if (!((Object)(object)val2 == (Object)null))
{
string text = "";
try
{
Type il2CppType = ((Object)val2).GetIl2CppType();
text = ((il2CppType != null) ? il2CppType.FullName : null);
}
catch
{
text = ((object)val2).GetType().FullName ?? ((object)val2).GetType().Name;
}
if (LooksLikeClipboardProxyName(text))
{
return true;
}
}
}
val = val.parent;
}
return false;
}
private static bool LooksLikeClipboardProxyName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return false;
}
string text = name.Replace(" ", "").Replace("_", "").Replace("-", "")
.ToLowerInvariant();
if (!text.Contains("clipboardproxy") && !text.Contains("clipboard"))
{
return text.Contains("infoboard");
}
return true;
}
public static string GetExportFile(TMP_Text textComponent, string text)
{
string path = Path.Combine(TranslationFolder, "Global");
if (IsNotificationText(textComponent))
{
return Path.Combine(path, "Notification.txt");
}
if (IsClipboardProxyText(textComponent))
{
return Path.Combine(path, "ClipboardProxy.txt");
}
if (!string.IsNullOrEmpty(text) && text.Length >= 100)
{
return Path.Combine(path, "LongText.txt");
}
if (IsDynamicText(text))
{
return Path.Combine(path, "Dynamic.txt");
}
if (IsGlobalUI(textComponent))
{
return Path.Combine(path, "UI.txt");
}
if (IsGlobalCommon(textComponent, text))
{
return Path.Combine(path, "Common.txt");
}
return Path.Combine(path, "Scenes.txt");
}
private static void EnsureModFileStructure()
{
try
{
if (!string.IsNullOrWhiteSpace(ModFolder) && !string.IsNullOrWhiteSpace(TranslationFolder) && !string.IsNullOrWhiteSpace(FontFolder) && !string.IsNullOrWhiteSpace(FontBundleFolder))
{
Directory.CreateDirectory(ModFolder);
Directory.CreateDirectory(TranslationFolder);
Directory.CreateDirectory(FontFolder);
Directory.CreateDirectory(FontBundleFolder);
Directory.CreateDirectory(Path.Combine(TranslationFolder, "Global"));
}
}
catch (Exception ex)
{
MelonLogger.Error("Failed to ensure file structure: " + ex.Message);
}
}
private static void EnsureVersionFile()
{
try
{
if (!string.IsNullOrWhiteSpace(ModFolder))
{
File.WriteAllText(Path.Combine(ModFolder, "Version.txt"), "1.0.0");
}
}
catch (Exception ex)
{
MelonLogger.Warning("Failed to ensure version file: " + ex.Message);
}
}
private static void MigrateLegacyModFolder()
{
try
{
string text = Path.Combine(Environment.CurrentDirectory, "LabLocalizer");
if (Directory.Exists(text) && !string.Equals(Path.GetFullPath(text).TrimEnd(Path.DirectorySeparatorChar), Path.GetFullPath(ModFolder).TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase) && (!Directory.Exists(ModFolder) || Directory.GetFileSystemEntries(ModFolder).Length == 0))
{
CopyDirectory(text, ModFolder);
MelonLogger.Msg("Migrated LabLocalizer data to UserData: " + ModFolder);
}
}
catch (Exception ex)
{
MelonLogger.Warning("Failed to migrate legacy LabLocalizer folder: " + ex.Message);
}
}
private static void CopyDirectory(string sourceDirectory, string targetDirectory)
{
Directory.CreateDirectory(targetDirectory);
string[] directories = Directory.GetDirectories(sourceDirectory, "*", SearchOption.AllDirectories);
foreach (string path in directories)
{
string relativePath = Path.GetRelativePath(sourceDirectory, path);
Directory.CreateDirectory(Path.Combine(targetDirectory, relativePath));
}
directories = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories);
foreach (string text in directories)
{
string relativePath2 = Path.GetRelativePath(sourceDirectory, text);
string text2 = Path.Combine(targetDirectory, relativePath2);
Directory.CreateDirectory(Path.GetDirectoryName(text2));
File.Copy(text, text2, overwrite: false);
}
}
private static void SetupPreferences()
{
try
{
string text = "";
bool result = true;
if (File.Exists(_settingsFile))
{
string[] array = File.ReadAllLines(_settingsFile);
foreach (string text2 in array)
{
if (string.IsNullOrWhiteSpace(text2))
{
continue;
}
string[] array2 = text2.Split(new string[1] { "=" }, 2, StringSplitOptions.None);
if (array2.Length >= 2)
{
string text3 = array2[0].Trim();
string value = array2[1].Trim();
switch (text3)
{
case "LastUsedFontPath":
text = NormalizeFontSettingValue(value);
break;
case "LockState":
case "ReadOnly":
bool.TryParse(value, out result);
break;
}
}
}
}
_preferencesCategory = MelonPreferences.CreateCategory("LabLocalizer", "Lab Localizer");
_lastUsedFontPathEntry = _preferencesCategory.CreateEntry<string>("LastUsedFontPath", text, "Last Used Font File", (string)null, false, false, (ValueValidator)null, (string)null);
_readOnlyModeEntry = _preferencesCategory.CreateEntry<bool>("ReadOnly", result, "Read-Only", (string)null, false, false, (ValueValidator)null, (string)null);
LastUsedFontPath = NormalizeFontSettingValue(_lastUsedFontPathEntry.Value);
ForceReadOnlyMode = IsAndroidPlatform();
ReadOnlyMode = ForceReadOnlyMode || _readOnlyModeEntry.Value;
if (_lastUsedFontPathEntry.Value != LastUsedFontPath)
{
_lastUsedFontPathEntry.Value = LastUsedFontPath;
MelonPreferences.Save();
}
if (ForceReadOnlyMode)
{
MelonLogger.Msg("Android platform detected. Read-Only mode is forced and the Read-Only menu toggle is hidden.");
}
MelonLogger.Msg($"Preferences loaded. LastUsedFont: {LastUsedFontPath}, ReadOnly: {ReadOnlyMode}");
}
catch (Exception ex)
{
MelonLogger.Error("Failed to setup preferences: " + ex.Message);
}
}
private static void DisableFileWatcher()
{
if (fileWatcher != null)
{
fileWatcher.EnableRaisingEvents = false;
fileWatcher.Dispose();
fileWatcher = null;
}
}
public static void SaveSettings()
{
try
{
LastUsedFontPath = NormalizeFontSettingValue(LastUsedFontPath);
if (_lastUsedFontPathEntry != null)
{
_lastUsedFontPathEntry.Value = LastUsedFontPath;
}
if (_readOnlyModeEntry != null)
{
_readOnlyModeEntry.Value = ReadOnlyMode;
}
MelonPreferences.Save();
}
catch (Exception ex)
{
MelonLogger.Error("Failed to save preferences: " + ex.Message);
}
}
private static bool IsAndroidPlatform()
{
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Invalid comparison between Unknown and I4
try
{
return HelperMethods.IsAndroid();
}
catch
{
return (int)MelonUtils.CurrentPlatform == 3;
}
}
public override void OnInitializeMelon()
{
//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
//IL_00f9: Expected O, but got Unknown
ModFolder = Path.Combine(MelonEnvironment.UserDataDirectory, "LabLocalizer");
TranslationFolder = Path.Combine(ModFolder, "Translations");
FontFolder = Path.Combine(ModFolder, "Font");
FontBundleFolder = Path.Combine(ModFolder, "FontBundles");
_settingsFile = Path.Combine(ModFolder, "settings.txt");
MigrateLegacyModFolder();
EnsureModFileStructure();
EnsureVersionFile();
SetupPreferences();
CreateDefaultFiles();
LoadTranslations();
LoadFont();
SetupFileWatcher();
SetupBoneMenu();
Hooking.OnLevelLoaded += OnLevelLoaded;
try
{
Harmony harmonyInstance = ((MelonBase)this).HarmonyInstance;
MethodInfo methodInfo = AccessTools.Method(typeof(TMP_Text), "set_text", (Type[])null, (Type[])null);
if (methodInfo != null)
{
harmonyInstance.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TMP_set_text_Patch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
MelonLogger.Msg("Patched TMP_Text.set_text");
}
else
{
MelonLogger.Warning("Could not find TMP_Text.set_text method");
}
}
catch (Exception ex)
{
MelonLogger.Error("Failed to patch: " + ex.Message);
}
((MelonBase)this).LoggerInstance.Msg("LabLocalizer Initialized. Version: 1.0.0");
if (ForceReadOnlyMode)
{
SchedulePostLevelScan();
}
}
public override void OnUpdate()
{
exportFlushTimer += Time.deltaTime;
if (exportFlushTimer >= 2f)
{
exportFlushTimer = 0f;
FlushPendingExports();
}
}
public override void OnDeinitializeMelon()
{
FlushPendingExports();
Hooking.OnLevelLoaded -= OnLevelLoaded;
DisableFileWatcher();
}
public static void ApplyFont(TMP_Text textComponent)
{
try
{
if (!((Object)(object)CustomFont == (Object)null) && !((Object)(object)textComponent == (Object)null) && (Object)(object)textComponent.font != (Object)(object)CustomFont)
{
if (CustomFontFromBundle)
{
PrepareBundleFontForRuntime(CustomFont, textComponent);
}
textComponent.font = CustomFont;
}
}
catch
{
}
}
public static void LoadFont()
{
try
{
EnsureModFileStructure();
LoadFontBundles();
BundleFontEntry bundleFontEntry = FindBundleFont(NormalizeFontSettingValue(LastUsedFontPath));
if (bundleFontEntry != null && (Object)(object)bundleFontEntry.FontAsset != (Object)null)
{
CustomFont = bundleFontEntry.FontAsset;
CustomFontFromBundle = true;
PrepareBundleFontForRuntime(CustomFont);
ApplyFontToAllTexts();
MelonLogger.Msg("Loaded last used bundled TMP font: " + bundleFontEntry.DisplayName);
return;
}
List<BundleFontEntry> availableBundleFonts = GetAvailableBundleFonts();
for (int i = 0; i < availableBundleFonts.Count; i++)
{
BundleFontEntry bundleFontEntry2 = availableBundleFonts[i];
if (bundleFontEntry2 != null && !((Object)(object)bundleFontEntry2.FontAsset == (Object)null))
{
CustomFont = bundleFontEntry2.FontAsset;
CustomFontFromBundle = true;
PrepareBundleFontForRuntime(CustomFont);
LastUsedFontPath = bundleFontEntry2.SettingValue;
SaveSettings();
ApplyFontToAllTexts();
MelonLogger.Msg("Loaded bundled TMP font: " + bundleFontEntry2.DisplayName);
return;
}
}
string lastUsedFontFullPath = GetLastUsedFontFullPath();
if (!string.IsNullOrEmpty(lastUsedFontFullPath) && File.Exists(lastUsedFontFullPath))
{
if (TryLoadPcFont(lastUsedFontFullPath, out var fontAsset))
{
CustomFont = fontAsset;
CustomFontFromBundle = false;
ApplyFontToAllTexts();
MelonLogger.Msg("Loaded last used font: " + Path.GetFileName(lastUsedFontFullPath));
return;
}
}
else if (!string.IsNullOrEmpty(LastUsedFontPath))
{
MelonLogger.Warning("Last used font file is missing, falling back to available fonts.");
LastUsedFontPath = "";
SaveSettings();
}
foreach (string availableFontFile in GetAvailableFontFiles())
{
if (TryLoadPcFont(availableFontFile, out var fontAsset2))
{
CustomFont = fontAsset2;
CustomFontFromBundle = false;
LastUsedFontPath = NormalizeFontSettingValue(availableFontFile);
SaveSettings();
ApplyFontToAllTexts();
MelonLogger.Msg("Loaded Font: " + Path.GetFileName(availableFontFile));
return;
}
}
MelonLogger.Warning("No custom font found.");
}
catch (Exception ex)
{
MelonLogger.Error(ex.ToString());
}
}
private static bool TryLoadPcFont(string fontPath, out TMP_FontAsset fontAsset)
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Expected O, but got Unknown
fontAsset = null;
try
{
if (string.IsNullOrWhiteSpace(fontPath) || !File.Exists(fontPath))
{
return false;
}
Font val = new Font(fontPath);
if ((Object)(object)val == (Object)null)
{
return false;
}
fontAsset = TMP_FontAsset.CreateFontAsset(val);
return (Object)(object)fontAsset != (Object)null;
}
catch (Exception ex)
{
MelonLogger.Warning("Failed to load font '" + fontPath + "': " + ex.Message);
return false;
}
}
public static List<string> GetAvailableFontFiles()
{
try
{
EnsureModFileStructure();
List<string> list = new List<string>();
if (!Directory.Exists(FontFolder))
{
return list;
}
string[] files = Directory.GetFiles(FontFolder);
foreach (string text in files)
{
string text2 = Path.GetExtension(text).ToLowerInvariant();
if (text2 == ".ttf" || text2 == ".otf")
{
list.Add(text);
}
}
list.Sort(StringComparer.OrdinalIgnoreCase);
return list;
}
catch (Exception ex)
{
MelonLogger.Error("Failed to get available fonts: " + ex.Message);
return new List<string>();
}
}
private static List<BundleFontEntry> GetAvailableBundleFonts()
{
LoadFontBundles();
return new List<BundleFontEntry>(BundleFonts);
}
private static void ReloadFontBundles()
{
foreach (AssetBundle loadedFontBundle in LoadedFontBundles)
{
try
{
if ((Object)(object)loadedFontBundle != (Object)null)
{
loadedFontBundle.Unload(false);
}
}
catch (Exception ex)
{
MelonLogger.Warning("Failed to unload font AssetBundle: " + ex.Message);
}
}
BundleFonts.Clear();
LoadedFontBundlePaths.Clear();
LoadedFontBundles.Clear();
FontBundlesLoaded = false;
LoadFontBundles();
}
private static void LoadFontBundles()
{
if (!FontBundlesLoaded)
{
FontBundlesLoaded = true;
ScanNewFontBundles();
}
}
private static void ScanNewFontBundles()
{
try
{
EnsureModFileStructure();
if (Directory.Exists(FontBundleFolder))
{
string[] files = Directory.GetFiles(FontBundleFolder);
for (int i = 0; i < files.Length; i++)
{
TryLoadFontBundle(files[i]);
}
MelonLogger.Msg($"Available bundled TMP fonts: {BundleFonts.Count}.");
}
}
catch (Exception ex)
{
MelonLogger.Error("Failed to scan font bundles: " + ex.Message);
}
}
private static void TryLoadFontBundle(string bundlePath)
{
try
{
if (string.IsNullOrWhiteSpace(bundlePath) || !File.Exists(bundlePath))
{
return;
}
switch (Path.GetExtension(bundlePath).ToLowerInvariant())
{
case ".meta":
return;
case ".txt":
return;
}
string fullPath = Path.GetFullPath(bundlePath);
if (LoadedFontBundlePaths.Contains(fullPath))
{
return;
}
AssetBundle val = AssetBundle.LoadFromFile(bundlePath);
if ((Object)(object)val == (Object)null)
{
MelonLogger.Warning("Failed to load font AssetBundle: " + bundlePath);
return;
}
LoadedFontBundles.Add(val);
LoadedFontBundlePaths.Add(fullPath);
Object[] array = Il2CppArrayBase<Object>.op_Implicit((Il2CppArrayBase<Object>)(object)val.LoadAllAssets());
int num = 0;
Object[] array2 = array;
foreach (Object val2 in array2)
{
if (!(val2 == (Object)null))
{
TMP_FontAsset val3 = null;
try
{
val3 = ((Il2CppObjectBase)val2).TryCast<TMP_FontAsset>();
}
catch
{
}
if (!((Object)(object)val3 == (Object)null) && !string.IsNullOrWhiteSpace(((Object)val3).name))
{
PrepareBundleFontForRuntime(val3);
num++;
BundleFonts.Add(new BundleFontEntry
{
BundlePath = bundlePath,
FontName = ((Object)val3).name,
FontAsset = val3
});
}
}
}
MelonLogger.Msg($"Loaded font AssetBundle '{Path.GetFileName(bundlePath)}': {num} TMP font(s), {array.Length} asset(s).");
}
catch (Exception ex)
{
MelonLogger.Warning("Failed to read font AssetBundle '" + bundlePath + "': " + ex.Message);
}
}
private static string NormalizeFontSettingValue(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "";
}
string text = value.Trim();
if (IsBundleFontSetting(text))
{
return text;
}
string fileName = Path.GetFileName(text);
if (!string.IsNullOrWhiteSpace(fileName))
{
return fileName;
}
return "";
}
private static bool IsBundleFontSetting(string value)
{
if (!string.IsNullOrWhiteSpace(value))
{
return value.StartsWith("bundle:", StringComparison.OrdinalIgnoreCase);
}
return false;
}
private static string GetLastUsedFontFullPath()
{
string text = NormalizeFontSettingValue(LastUsedFontPath);
if (string.IsNullOrWhiteSpace(text) || IsBundleFontSetting(text))
{
return "";
}
return Path.Combine(FontFolder, text);
}
private static BundleFontEntry FindBundleFont(string settingValue)
{
if (!IsBundleFontSetting(settingValue))
{
return null;
}
string[] array = settingValue.Substring("bundle:".Length).Split(new char[1] { '|' }, 2);
if (array.Length != 2)
{
return null;
}
LoadFontBundles();
foreach (BundleFontEntry bundleFont in BundleFonts)
{
if (bundleFont != null && string.Equals(Path.GetFileName(bundleFont.BundlePath), array[0], StringComparison.OrdinalIgnoreCase) && string.Equals(bundleFont.FontName, array[1], StringComparison.OrdinalIgnoreCase))
{
return bundleFont;
}
}
return null;
}
public static void SelectPcFont(string fontPath)
{
try
{
EnsureModFileStructure();
if (string.IsNullOrWhiteSpace(fontPath))
{
MelonLogger.Warning("SelectPcFont called with empty font path.");
RefreshFontMenu();
return;
}
if (!File.Exists(fontPath))
{
MelonLogger.Warning("Font file not found: " + fontPath);
RefreshFontMenu();
return;
}
if (!TryLoadPcFont(fontPath, out var fontAsset))
{
RefreshFontMenu();
return;
}
CustomFont = fontAsset;
CustomFontFromBundle = false;
LastUsedFontPath = NormalizeFontSettingValue(fontPath);
SaveSettings();
ApplyFontToAllTexts();
RefreshFontMenu();
MelonLogger.Msg("Selected font file: " + Path.GetFileName(fontPath));
}
catch (Exception ex)
{
MelonLogger.Error("Failed to select font file: " + ex.Message);
}
}
public static void SelectBundleFont(string settingValue)
{
try
{
BundleFontEntry bundleFontEntry = FindBundleFont(settingValue);
if (bundleFontEntry == null || (Object)(object)bundleFontEntry.FontAsset == (Object)null)
{
MelonLogger.Warning("Bundled TMP font not found: " + settingValue);
RefreshFontMenu();
return;
}
CustomFont = bundleFontEntry.FontAsset;
CustomFontFromBundle = true;
PrepareBundleFontForRuntime(CustomFont);
LastUsedFontPath = bundleFontEntry.SettingValue;
SaveSettings();
ApplyFontToAllTexts();
RefreshFontMenu();
MelonLogger.Msg("Selected bundled TMP font: " + bundleFontEntry.DisplayName);
}
catch (Exception ex)
{
MelonLogger.Error("Failed to select bundled TMP font: " + ex.Message);
}
}
private static void ApplyFontToAllTexts()
{
try
{
TMP_Text[] array = Il2CppArrayBase<TMP_Text>.op_Implicit(Object.FindObjectsOfType<TMP_Text>(true));
TMP_Text[] array2 = array;
foreach (TMP_Text val in array2)
{
if ((Object)(object)val != (Object)null)
{
ApplyFont(val);
}
}
MelonLogger.Msg($"Applied font to {array.Length} text components");
}
catch (Exception ex)
{
MelonLogger.Error("Failed to apply font to all texts: " + ex.Message);
}
}
private static void PrepareBundleFontForRuntime(TMP_FontAsset fontAsset)
{
if (!((Object)(object)fontAsset == (Object)null))
{
FixQuestFontMaterial(fontAsset);
}
}
private static void PrepareBundleFontForRuntime(TMP_FontAsset fontAsset, TMP_Text sourceText)
{
if (!((Object)(object)fontAsset == (Object)null))
{
FixQuestFontMaterial(fontAsset);
}
}
private static void FixQuestFontMaterial(TMP_FontAsset fontAsset)
{
try
{
Material material = ((TMP_Asset)fontAsset).material;
if ((Object)(object)material == (Object)null)
{
return;
}
int instanceID = ((Object)material).GetInstanceID();
if (QuestFixedFontMaterials.Contains(instanceID))
{
return;
}
Shader runtimeTmpShader = GetRuntimeTmpShader();
if ((Object)(object)runtimeTmpShader == (Object)null)
{
return;
}
Texture mainTexture = material.mainTexture;
int renderQueue = material.renderQueue;
material.shader = runtimeTmpShader;
if ((Object)(object)mainTexture != (Object)null)
{
material.mainTexture = mainTexture;
if (material.HasProperty("_MainTex"))
{
material.SetTexture("_MainTex", mainTexture);
}
}
material.renderQueue = renderQueue;
QuestFixedFontMaterials.Add(instanceID);
MelonLogger.Msg("Font material shader fixed: " + ((Object)fontAsset).name + " -> " + ((Object)runtimeTmpShader).name);
}
catch (Exception ex)
{
MelonLogger.Warning("Failed to fix bundled font material: " + ex.Message);
}
}
private static Shader GetRuntimeTmpShader()
{
if ((Object)(object)RuntimeTmpShader != (Object)null)
{
return RuntimeTmpShader;
}
if (IsAndroidPlatform())
{
RuntimeTmpShader = Shader.Find("TextMeshPro/Mobile/Distance Field");
if ((Object)(object)RuntimeTmpShader == (Object)null)
{
RuntimeTmpShader = Shader.Find("TextMeshPro/Distance Field");
}
}
else
{
RuntimeTmpShader = Shader.Find("TextMeshPro/Distance Field");
if ((Object)(object)RuntimeTmpShader == (Object)null)
{
RuntimeTmpShader = Shader.Find("TextMeshPro/Mobile/Distance Field");
}
}
if ((Object)(object)RuntimeTmpShader != (Object)null)
{
MelonLogger.Msg("TMP shader fallback: " + ((Object)RuntimeTmpShader).name);
}
return RuntimeTmpShader;
}
private static void SetupFileWatcher()
{
try
{
EnsureModFileStructure();
if (ReadOnlyMode)
{
DisableFileWatcher();
MelonLogger.Msg("FileSystemWatcher disabled because Read-Only mode is enabled.");
}
else if (!Directory.Exists(TranslationFolder))
{
MelonLogger.Warning("Translation folder does not exist: " + TranslationFolder);
}
else if (fileWatcher == null)
{
fileWatcher = new FileSystemWatcher();
fileWatcher.Path = TranslationFolder;
fileWatcher.Filter = "*.txt";
fileWatcher.IncludeSubdirectories = true;
fileWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime;
fileWatcher.Changed += OnTranslationFileChanged;
fileWatcher.Created += OnTranslationFileChanged;
fileWatcher.Deleted += OnTranslationFileChanged;
fileWatcher.Renamed += OnTranslationFileRenamed;
fileWatcher.EnableRaisingEvents = true;
MelonLogger.Msg("FileSystemWatcher started. Watching: " + TranslationFolder);
}
}
catch (Exception ex)
{
MelonLogger.Error("Failed to setup FileSystemWatcher: " + ex.Message);
}
}
private static void OnLevelLoaded(LevelInfo levelInfo)
{
ClipboardProxyTextCache.Clear();
NotificationTextCache.Clear();
RichTextPlainExportKeys.Clear();
PlainTextExportLocations.Clear();
SchedulePostLevelScan();
ScheduleLocalizationUpdateCheck();
}
private static void SchedulePostLevelScan()
{
if (!scanScheduled)
{
scanScheduled = true;
MelonCoroutines.Start(DelayedPostLevelScan());
}
}
private static IEnumerator DelayedPostLevelScan()
{
yield return (object)new WaitForSeconds(0.5f);
ForceScanAllTexts();
yield return (object)new WaitForSeconds(1.5f);
ForceScanAllTexts();
scanScheduled = false;
}
private static void OnTranslationFileChanged(object sender, FileSystemEventArgs e)
{
try
{
if (e == null || string.IsNullOrWhiteSpace(e.FullPath) || string.Equals(Path.GetExtension(e.FullPath), ".txt", StringComparison.OrdinalIgnoreCase))
{
ScheduleTranslationReload((e != null) ? e.Name : "unknown");
}
}
catch (Exception ex)
{
MelonLogger.Error("Error in OnTranslationFileChanged: " + ex.Message);
}
}
private static void OnTranslationFileRenamed(object sender, RenamedEventArgs e)
{
OnTranslationFileChanged(sender, e);
}
private static void ScheduleTranslationReload(string name)
{
if (!reloadScheduled && !(DateTime.Now - lastReloadTime < reloadCooldown))
{
lastReloadTime = DateTime.Now;
reloadScheduled = true;
MelonLogger.Msg("Translation file changed: " + name + " - Reloading...");
MelonCoroutines.Start(DelayedReload());
}
}
private static IEnumerator DelayedReload()
{
yield return (object)new WaitForSeconds(0.35f);
LoadTranslations();
TranslationCache.Clear();
LastSetTextInputs.Clear();
LastSetTextOutputs.Clear();
ForceScanAllTexts();
reloadScheduled = false;
MelonLogger.Msg("Translations reloaded and applied.");
}
private static void SetReadOnlyMode(bool value)
{
if (ForceReadOnlyMode)
{
ReadOnlyMode = true;
SaveSettings();
DisableFileWatcher();
PendingExportLines.Clear();
MelonLogger.Msg("Read-Only mode is forced on Android.");
return;
}
ReadOnlyMode = value;
SaveSettings();
if (ReadOnlyMode)
{
DisableFileWatcher();
PendingExportLines.Clear();
MelonLogger.Msg("Read-Only mode enabled. File watching and text exporting are disabled.");
}
else
{
ClearTextRuntimeCaches();
SetupFileWatcher();
ForceScanAllTexts();
MelonLogger.Msg("Read-Only mode disabled. File watching and text exporting are enabled.");
}
}
private static void ClearTextRuntimeCaches()
{
RegisteredTexts.Clear();
ActiveTexts.Clear();
OriginalTexts.Clear();
TranslatedObjectIDs.Clear();
LastSetTextInputs.Clear();
LastSetTextOutputs.Clear();
ClipboardProxyTextCache.Clear();
NotificationTextCache.Clear();
RichTextPlainExportKeys.Clear();
PlainTextExportLocations.Clear();
}
private static void RegisterNewTextComponent(TMP_Text textComponent)
{
try
{
if ((Object)(object)textComponent == (Object)null)
{
return;
}
int instanceID = ((Object)textComponent).GetInstanceID();
if (RegisteredTexts.Contains(instanceID))
{
return;
}
RegisteredTexts.Add(instanceID);
ActiveTexts.Add(textComponent);
string text = textComponent.text;
if (string.IsNullOrWhiteSpace(text))
{
return;
}
text = SanitizeText(text, trim: true);
string item = EscapeTranslationFileValue(SanitizeText(NormalizeTextTemplate(text), trim: true));
if (ExportedTexts.Contains(item))
{
string text2 = text;
if (TryResolveOriginalFromTranslatedText(text, out var original))
{
text2 = original;
}
OriginalTexts[instanceID] = text2;
string text3 = Translate(text2);
if (text3 != text2)
{
textComponent.text = text3;
TranslatedObjectIDs.Add(instanceID);
}
ApplyFont(textComponent);
return;
}
string text4 = text;
if (TryResolveOriginalFromTranslatedText(text, out var original2))
{
text4 = original2;
}
OriginalTexts[instanceID] = text4;
if (ShouldExportText(text4, textComponent))
{
ExportText(textComponent, text4);
}
string text5 = Translate(text4);
if (text5 != text4)
{
textComponent.text = text5;
TranslatedObjectIDs.Add(instanceID);
}
ApplyFont(textComponent);
}
catch
{
}
}
private static void ForceScanAllTexts()
{
try
{
TMP_Text[] array = Il2CppArrayBase<TMP_Text>.op_Implicit(Object.FindObjectsOfType<TMP_Text>(true));
MelonLogger.Msg($"Force scanning {array.Length} text components...");
int num = 0;
TMP_Text[] array2 = array;
foreach (TMP_Text val in array2)
{
if ((Object)(object)val == (Object)null)
{
continue;
}
int instanceID = ((Object)val).GetInstanceID();
string text = val.text;
ApplyFont(val);
if (!RegisteredTexts.Contains(instanceID))
{
RegisterNewTextComponent(val);
}
else
{
if (string.IsNullOrWhiteSpace(text) || IsStandaloneDynamicValue(text))
{
continue;
}
string text2 = null;
text2 = (TryResolveOriginalFromTranslatedText(text, out var original) ? original : ((!OriginalTexts.TryGetValue(instanceID, out var value)) ? text : GetCurrentDynamicOriginal(value, text)));
string text3 = Translate(text2);
if (text2 != text3 && text != text3)
{
val.text = text3;
TranslatedObjectIDs.Add(instanceID);
num++;
if (!OriginalTexts.ContainsKey(instanceID) || HasSameTemplateShape(OriginalTexts[instanceID], text2))
{
OriginalTexts[instanceID] = text2;
}
}
else if (text2 == text3 && text != text2)
{
val.text = text2;
TranslatedObjectIDs.Remove(instanceID);
LastSetTextInputs.Remove(instanceID);
LastSetTextOutputs.Remove(instanceID);
num++;
}
}
}
MelonLogger.Msg($"Force scan completed. Updated {num} text components.");
}
catch (Exception ex)
{
MelonLogger.Error("ForceScanAllTexts error: " + ex.Message);
}
}
private static void ScheduleLocalizationUpdateCheck()
{
if (!localizationUpdateCheckScheduled)
{
localizationUpdateCheckScheduled = true;
MelonCoroutines.Start(DelayedLocalizationUpdateCheck(manualCheck: false));
}
}
private static void StartManualLocalizationUpdateCheck()
{
if (!localizationUpdateCheckScheduled)
{
localizationUpdateCheckScheduled = true;
MelonCoroutines.Start(DelayedLocalizationUpdateCheck(manualCheck: true));
}
}
private static IEnumerator DelayedLocalizationUpdateCheck(bool manualCheck)
{
if (!manualCheck)
{
yield return (object)new WaitForSeconds(1f);
}
Task<LocalizationReleaseFetchResult> checkTask = FetchLatestLocalizationReleaseAsync();
while (!checkTask.IsCompleted)
{
yield return null;
}
localizationUpdateCheckScheduled = false;
LocalizationReleaseFetchResult result;
try
{
result = checkTask.Result;
}
catch
{
SendNetworkFailureNotification();
yield break;
}
if (result == null)
{
yield break;
}
if (result.NetworkFailed)
{
SendNetworkFailureNotification();
yield break;
}
LocalizationReleaseInfo releaseInfo = result.ReleaseInfo;
if (releaseInfo == null)
{
yield break;
}
Version installedLocalizationVersion = GetInstalledLocalizationVersion();
if (installedLocalizationVersion != null && installedLocalizationVersion.CompareTo(releaseInfo.Version) >= 0)
{
if (manualCheck)
{
SendLatestVersionNotification();
}
yield break;
}
string text = (string.IsNullOrWhiteSpace(releaseInfo.Title) ? releaseInfo.Version.ToString() : releaseInfo.Title);
Notifier.Send(new Notification
{
Title = new NotificationText("中文本地化模组"),
Message = new NotificationText("中文本地化模组有更新可用:" + text + "。"),
ShowTitleOnPopup = true,
PopupLength = 4f,
Type = (NotificationType)0
});
}
private static void SendLatestVersionNotification()
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Expected O, but got Unknown
Notifier.Send(new Notification
{
Title = new NotificationText("中文本地化模组"),
Message = new NotificationText("已是最新版本"),
ShowTitleOnPopup = true,
PopupLength = 4f,
Type = (NotificationType)0
});
}
private static async Task<LocalizationReleaseFetchResult> FetchLatestLocalizationReleaseAsync()
{
_ = 1;
try
{
using HttpClient httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(20.0)
};
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LabLocalizer/1.0");
using HttpResponseMessage response = await httpClient.GetAsync("https://github.com/MonoIAR/PJ_LabLocalizer/releases/latest/download/manifest.json");
if (!response.IsSuccessStatusCode)
{
return new LocalizationReleaseFetchResult
{
NetworkFailed = false
};
}
string manifestText = await response.Content.ReadAsStringAsync();
return new LocalizationReleaseFetchResult
{
ReleaseInfo = ParseLocalizationRelease(manifestText),
NetworkFailed = false
};
}
catch
{
return new LocalizationReleaseFetchResult
{
NetworkFailed = true
};
}
}
private static void SendNetworkFailureNotification()
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Expected O, but got Unknown
Notifier.Send(new Notification
{
Title = new NotificationText("中文本地化模组"),
Message = new NotificationText("无法连接到Github,请检查你的网络连接"),
ShowTitleOnPopup = true,
PopupLength = 4f,
Type = (NotificationType)1
});
}
private static LocalizationReleaseInfo ParseLocalizationRelease(string manifestText)
{
try
{
using JsonDocument jsonDocument = JsonDocument.Parse(manifestText);
if (!jsonDocument.RootElement.TryGetProperty("version", out var value))
{
return null;
}
string text = value.GetString();
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
if (!Version.TryParse(text.Trim(), out Version result))
{
return null;
}
string title = "";
if (jsonDocument.RootElement.TryGetProperty("title", out var value2))
{
title = value2.GetString() ?? "";
}
return new LocalizationReleaseInfo
{
Version = result,
Title = title
};
}
catch
{
return null;
}
}
private static Version GetInstalledLocalizationVersion()
{
try
{
if (string.IsNullOrWhiteSpace(ModFolder))
{
return null;
}
string path = Path.Combine(ModFolder, "Version.txt");
if (!File.Exists(path))
{
return null;
}
string text = File.ReadAllText(path).Trim();
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
Version result;
return Version.TryParse(text, out result) ? result : null;
}
catch
{
return null;
}
}
private static string GetPendingExportKey(string file, string escaped)
{
return file + "\n" + escaped;
}
private static void QueueExportLine(string file, string escaped)
{
if (!string.IsNullOrWhiteSpace(file) && !string.IsNullOrWhiteSpace(escaped))
{
string pendingExportKey = GetPendingExportKey(file, escaped);
if (!PendingExportLines.ContainsKey(pendingExportKey))
{
PendingExportLines.Add(pendingExportKey, escaped + " => " + escaped);
}
}
}
private static void RemovePendingExportLine(string file, string escaped)
{
if (!string.IsNullOrWhiteSpace(file) && !string.IsNullOrWhiteSpace(escaped))
{
PendingExportLines.Remove(GetPendingExportKey(file, escaped));
}
}
private static void RemoveAutoGeneratedSelfTranslationLine(string file, string escaped)
{
try
{
if (string.IsNullOrWhiteSpace(file) || string.IsNullOrWhiteSpace(escaped) || !File.Exists(file))
{
return;
}
string text = escaped + " => " + escaped;
string[] array = File.ReadAllLines(file);
List<string> list = new List<string>(array.Length);
bool flag = false;
string[] array2 = array;
foreach (string text2 in array2)
{
if (!flag && text2 == text)
{
flag = true;
}
else
{
list.Add(text2);
}
}
if (flag)
{
File.WriteAllLines(file, list.ToArray(), Encoding.UTF8);
ExportedTexts.Remove(escaped);
}
}
catch (Exception ex)
{
MelonLogger.Warning("Failed to remove superseded plain text export: " + ex.Message);
}
}
private static void RememberPlainTextExportLocation(int objectId, string plainExportKey, string file, string escaped)
{
if (objectId != 0 && !string.IsNullOrWhiteSpace(plainExportKey))
{
if (!PlainTextExportLocations.TryGetValue(objectId, out var value))
{
value = new Dictionary<string, string>();
PlainTextExportLocations[objectId] = value;
}
value[plainExportKey] = GetPendingExportKey(file, escaped);
}
}
private static void RemoveSupersededPlainTextExport(int objectId, string plainExportKey)
{
if (objectId != 0 && !string.IsNullOrWhiteSpace(plainExportKey) && PlainTextExportLocations.TryGetValue(objectId, out var value) && value.TryGetValue(plainExportKey, out var value2))
{
int num = value2.IndexOf('\n');
if (num > 0)
{
string file = value2.Substring(0, num);
string escaped = value2.Substring(num + 1);
RemovePendingExportLine(file, escaped);
RemoveAutoGeneratedSelfTranslationLine(file, escaped);
value.Remove(plainExportKey);
}
}
}
public static void FlushPendingExports()
{
try
{
if (ReadOnlyMode)
{
PendingExportLines.Clear();
}
else
{
if (PendingExportLines.Count == 0)
{
return;
}
EnsureModFileStructure();
Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
foreach (KeyValuePair<string, string> pendingExportLine in PendingExportLines)
{
int num = pendingExportLine.Key.IndexOf('\n');
if (num > 0)
{
string key = pendingExportLine.Key.Substring(0, num);
if (!dictionary.TryGetValue(key, out var value))
{
value = (dictionary[key] = new List<string>());
}
value.Add(pendingExportLine.Value);
}
}
bool enableRaisingEvents = fileWatcher != null && fileWatcher.EnableRaisingEvents;
try
{
if (fileWatcher != null)
{
fileWatcher.EnableRaisingEvents = false;
}
foreach (KeyValuePair<string, List<string>> item2 in dictionary)
{
string key2 = item2.Key;
Directory.CreateDirectory(Path.GetDirectoryName(key2));
HashSet<string> hashSet = new HashSet<string>();
if (File.Exists(key2))
{
string[] array = File.ReadAllLines(key2);
foreach (string text in array)
{
int num2 = FindTranslationSeparator(text);
if (num2 > 0)
{
hashSet.Add(text.Substring(0, num2).Trim());
}
}
}
else
{
File.WriteAllText(key2, "", Encoding.UTF8);
}
StringBuilder stringBuilder = new StringBuilder();
foreach (string item3 in item2.Value)
{
int num3 = FindTranslationSeparator(item3);
if (num3 > 0)
{
string item = item3.Substring(0, num3).Trim();
if (!hashSet.Contains(item))
{
hashSet.Add(item);
stringBuilder.AppendLine(item3);
}
}
}
if (stringBuilder.Length > 0)
{
File.AppendAllText(key2, stringBuilder.ToString(), Encoding.UTF8);
}
}
PendingExportLines.Clear();
return;
}
finally
{
if (fileWatcher != null)
{
fileWatcher.EnableRaisingEvents = enableRaisingEvents;
}
}
}
}
catch (Exception ex)
{
MelonLogger.Error("Failed to flush exports: " + ex.Message);
}
}
public static void ExportText(TMP_Text textComponent, string text)
{
try
{
if (ReadOnlyMode || string.IsNullOrWhiteSpace(text))
{
return;
}
text = SanitizeText(text, trim: true);
if (IsTranslationOfOtherText(text) || !ShouldExportText(text, textComponent))
{
return;
}
int num = (((Object)(object)textComponent != (Object)null) ? ((Object)textComponent).GetInstanceID() : 0);
string plainExportKey = GetPlainExportKey(text);
bool flag = StripRichTextTags(text) != text;
if (!flag && num != 0 && RichTextPlainExportKeys.TryGetValue(num, out var value) && value.Contains(plainExportKey))
{
return;
}
string text2 = SanitizeText(NormalizeTextTemplate(text), trim: true);
string text3 = EscapeTranslationFileValue(text2);
if (ExportedTexts.Contains(text3))
{
return;
}
string exportFile = GetExportFile(textComponent, text2);
ExportedTexts.Add(text3);
if (flag && num != 0 && !string.IsNullOrWhiteSpace(plainExportKey))
{
RemoveSupersededPlainTextExport(num, plainExportKey);
if (!RichTextPlainExportKeys.TryGetValue(num, out value))
{
value = new HashSet<string>();
RichTextPlainExportKeys[num] = value;
}
value.Add(plainExportKey);
}
else if (!flag)
{
RememberPlainTextExportLocation(num, plainExportKey, exportFile, text3);
}
QueueExportLine(exportFile, text3);
}
catch
{
}
}
public static string Translate(string original)
{
try
{
if (string.IsNullOrWhiteSpace(original))
{
return original;
}
original = SanitizeText(original, trim: true);
if (IsStandaloneDynamicValue(original))
{
TranslationCache[original] = original;
return original;
}
if (TranslationCache.TryGetValue(original, out var value))
{
return value;
}
if (Translations.TryGetValue(original, out var value2))
{
RememberTranslatedText(value2);
TranslationCache[original] = value2;
return value2;
}
if (TryTranslateTemplate(original, out var translated))
{
RememberTranslatedText(translated);
TranslationCache[original] = translated;
return translated;
}
TranslationCache[original] = original;
return original;
}
catch
{
return original;
}
}
public static bool IsTranslationOfOtherText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
text = SanitizeText(text, trim: true);
if (IsKnownTranslatedLookupText(text))
{
return true;
}
string original;
return TryResolveOriginalFromTranslatedText(text, out original);
}
private static bool IsKnownTranslatedLookupText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
text = SanitizeText(text, trim: true);
if (KnownTranslatedTexts.Contains(text) || AmbiguousTranslatedTexts.Contains(text))
{
return true;
}
string item = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true));
if (KnownTranslatedTexts.Contains(item) || AmbiguousTranslatedTexts.Contains(item))
{
return true;
}
string text2 = StripRichTextTags(text);
if (text2 != text)
{
text2 = SanitizeText(text2, trim: true);
if (KnownTranslatedTexts.Contains(text2) || AmbiguousTranslatedTexts.Contains(text2))
{
return true;
}
string item2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text2), trim: true));
if (KnownTranslatedTexts.Contains(item2) || AmbiguousTranslatedTexts.Contains(item2))
{
return true;
}
}
return false;
}
private static void RememberTranslatedText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return;
}
text = SanitizeText(text, trim: true);
if (string.IsNullOrWhiteSpace(text))
{
return;
}
KnownTranslatedTexts.Add(text);
string text2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true));
if (!string.IsNullOrWhiteSpace(text2))
{
KnownTranslatedTexts.Add(text2);
}
string text3 = StripRichTextTags(text);
if (text3 != text)
{
text3 = SanitizeText(text3, trim: true);
if (!string.IsNullOrWhiteSpace(text3))
{
KnownTranslatedTexts.Add(text3);
}
string text4 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text3), trim: true));
if (!string.IsNullOrWhiteSpace(text4))
{
KnownTranslatedTexts.Add(text4);
}
}
}
private static void RegisterReverseTranslation(string translated, string original)
{
if (string.IsNullOrWhiteSpace(translated) || string.IsNullOrWhiteSpace(original))
{
return;
}
translated = SanitizeText(translated, trim: true);
original = SanitizeText(original, trim: true);
if (!string.IsNullOrWhiteSpace(translated) && !string.IsNullOrWhiteSpace(original))
{
RememberTranslatedText(translated);
if (!ReverseTranslationCandidates.TryGetValue(translated, out var value))
{
value = new HashSet<string>();
ReverseTranslationCandidates[translated] = value;
}
value.Add(original);
if (value.Count == 1)
{
ReverseTranslations[translated] = original;
AmbiguousTranslatedTexts.Remove(translated);
}
else
{
ReverseTranslations.Remove(translated);
AmbiguousTranslatedTexts.Add(translated);
}
}
}
public static string SanitizeText(string text, bool trim = false)
{
if (text == null)
{
return null;
}
text = text.Replace("\r\n", "\n").Replace("\r", "\n");
text = InvisibleCharsRegex.Replace(text, "");
if (trim)
{
text = text.Trim();
}
return text;
}
public static string StripRichTextTags(string text)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
return RichTextTagRegex.Replace(text, "");
}
private static string GetPlainExportKey(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return "";
}
return EscapeTranslationFileValue(SanitizeText(NormalizeTextTemplate(StripRichTextTags(SanitizeText(text, trim: true))), trim: true));
}
private static string EscapeTranslationFileValue(string text)
{
if (text == null)
{
return "";
}
text = SanitizeText(text);
StringBuilder stringBuilder = new StringBuilder(text.Length + 16);
string text2 = text;
foreach (char c in text2)
{
switch (c)
{
case '\\':
stringBuilder.Append("\\\\");
break;
case '\n':
stringBuilder.Append("\\n");
break;
case '\r':
stringBuilder.Append("\\r");
break;
default:
stringBuilder.Append(c);
break;
}
}
return stringBuilder.ToString().Replace(" => ", "\\=>");
}
private static string UnescapeTranslationFileValue(string text)
{
if (text == null)
{
return null;
}
StringBuilder stringBuilder = new StringBuilder(text.Length);
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c != '\\' || i + 1 >= text.Length)
{
stringBuilder.Append(c);
continue;
}
char c2 = text[i + 1];
switch (c2)
{
case 'n':
stringBuilder.Append('\n');
i++;
continue;
case 'r':
stringBuilder.Append('\r');
i++;
continue;
case '\\':
stringBuilder.Append('\\');
i++;
continue;
case '=':
if (i + 2 < text.Length && text[i + 2] == '>')
{
stringBuilder.Append(" => ");
i += 2;
continue;
}
break;
}
stringBuilder.Append(c2);
i++;
}
return SanitizeText(stringBuilder.ToString());
}
private static int FindTranslationSeparator(string line)
{
if (string.IsNullOrEmpty(line))
{
return -1;
}
for (int i = 0; i <= line.Length - 4; i++)
{
if (line[i] == ' ' && line[i + 1] == '=' && line[i + 2] == '>' && line[i + 3] == ' ')
{
return i;
}
}
return -1;
}
public static string NormalizeTextTemplate(string text)
{
List<string> values;
return NormalizeTextTemplate(text, out values);
}
public static string NormalizeTextTemplate(string text, out List<string> values)
{
List<string> capturedValues = new List<string>();
values = capturedValues;
int placeholderIndex = 0;
if (string.IsNullOrWhiteSpace(text))
{
return text;
}
text = SanitizeText(text);
StringBuilder result = new StringBuilder(text.Length);
StringBuilder plainTextBuffer = new StringBuilder();
bool flag = false;
string text2 = text;
foreach (char c in text2)
{
if (c == '<')
{
FlushPlainText();
flag = true;
result.Append(c);
}
else if (flag)
{
result.Append(c);
if (c == '>')
{
flag = false;
}
}
else
{
plainTextBuffer.Append(c);
}
}
FlushPlainText();
return result.ToString();
void FlushPlainText()
{
if (plainTextBuffer.Length != 0)
{
string value = NormalizePlainTextSegment(plainTextBuffer.ToString(), capturedValues, ref placeholderIndex);
result.Append(value);
plainTextBuffer.Clear();
}
}
}
private static string NormalizePlainTextSegment(string text, List<string> capturedValues, ref int placeholderIndex)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
if (TryApplyNamedTemplateRule(text, capturedValues, ref placeholderIndex, out var template))
{
return template;
}
return TemplatizeNumbers(text, capturedValues, ref placeholderIndex);
}
private static bool TryApplyNamedTemplateRule(string text, List<string> capturedValues, ref int placeholderIndex, out string template)
{
template = text;
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
Match match = JoinedServerRegex.Match(text);
if (match.Success)
{
capturedValues.Add(match.Groups["PLAYER"].Value);
string text2 = CreateTemplateToken(placeholderIndex++);
template = JoinedServerRegex.Replace(text, text2 + " joined the server");
return true;
}
Match match2 = LeftServerRegex.Match(text);
if (match2.Success)
{
capturedValues.Add(match2.Groups["PLAYER"].Value);
string text3 = CreateTemplateToken(placeholderIndex++);
template = LeftServerRegex.Replace(text, text3 + " left the server");
return true;
}
Match match3 = CodeStringRegex.Match(text);
if (match3.Success)
{
capturedValues.Add(match3.Groups["STRING"].Value);
template = match3.Groups["PREFIX"].Value + CreateTemplateToken(placeholderIndex++);
return true;
}
return false;
}
private static string TemplatizeNumbers(string text, List<string> capturedValues, ref int placeholderIndex)
{
StringBuilder stringBuilder = new StringBuilder(text.Length + 16);
int num = 0;
while (num < text.Length)
{
char c = text[num];
if (!IsTemplateNumber(c))
{
stringBuilder.Append(c);
num++;
continue;
}
int num2 = num;
int num3 = num;
for (int i = num; i < text.Length && IsTemplateNumberOrControl(text[i]); i++)
{
if (IsTemplateNumber(text[i]))
{
num3 = i;
}
}
string item = text.Substring(num2, num3 - num2 + 1);
capturedValues.Add(item);
stringBuilder.Append(CreateTemplateToken(placeholderIndex++));
num = num3 + 1;
}
return stringBuilder.ToString();
}
private static bool IsTemplateNumberOrControl(char c)
{
if ('*' > c || c > ':')
{
if ('0' <= c)
{
return c <= '9';
}
return false;
}
return true;
}
private static bool IsTemplateNumber(char c)
{
if ('0' > c || c > '9')
{
if ('0' <= c)
{
return c <= '9';
}
return false;
}
return true;
}
private static string CreateTemplateToken(int index)
{
return "{{" + (char)(65 + index) + "}}";
}
private static bool TryTranslateTemplate(string original, out string translated)
{
translated = original;
List<string> values;
string text = NormalizeTextTemplate(original, out values);
if (text == original)
{
return false;
}
if (IsStandaloneTemplateValue(text))
{
return false;
}
if (!Translations.TryGetValue(text, out var value))
{
return false;
}
translated = FillTemplatePlaceholders(value, values);
return true;
}
public static bool TryResolveOriginalFromTranslatedText(string text, out string original)
{
original = text;
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
text = SanitizeText(text, trim: true);
if (IsStandaloneDynamicValue(text))
{
return false;
}
if (AmbiguousTranslatedTexts.Contains(text))
{
return false;
}
if (ReverseTranslations.TryGetValue(text, out var value))
{
original = value;
return true;
}
string text2 = StripRichTextTags(text);
if (text2 != text)
{
text2 = SanitizeText(text2, trim: true);
if (AmbiguousTranslatedTexts.Contains(text2))
{
return false;
}
if (ReverseTranslations.TryGetValue(text2, out value))
{
original = value;
return true;
}
}
List<string> values;
string text3 = NormalizeTextTemplate(text, out values);
if (text3 == text)
{
if (text2 == text)
{
return false;
}
text3 = NormalizeTextTemplate(text2, out values);
if (text3 == text2)
{
return false;
}
}
string text4 = NormalizeTemplateTokens(text3);
if (AmbiguousTranslatedTexts.Contains(text4))
{
return false;
}
if (!ReverseTranslations.TryGetValue(text4, out var value2))
{
return false;
}
original = FillTemplatePlaceholders(value2, values);
return true;
}
private static string NormalizeTemplateTokens(string text)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
return TemplateTokenRegex.Replace(text, (Match match) => match.Value.ToUpperInvariant());
}
private static bool IsStandaloneTemplateValue(string template)
{
if (string.IsNullOrWhiteSpace(template))
{
return false;
}
string text = NormalizeTemplateTokens(SanitizeText(template, trim: true));
if (TemplateTokenRegex.IsMatch(text))
{
return !ContainsUntemplatedCharacters(text);
}
return false;
}
private static bool ContainsUntemplatedCharacters(string text)
{
if (string.IsNullOrEmpty(text))
{
return false;
}
bool flag = false;
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (flag)
{
if (c == '}' && i + 1 < text.Length && text[i + 1] == '}')
{
i++;
flag = false;
}
}
else if (c == '{' && i + 1 < text.Length && text[i + 1] == '{')
{
i++;
flag = true;
}
else if (!char.IsWhiteSpace(c))
{
return true;
}
}
return false;
}
public static bool IsStandaloneDynamicValue(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
string text2 = SanitizeText(text, trim: true);
string text3 = NormalizeTextTemplate(text2);
if (text3 != text2)
{
return IsStandaloneTemplateValue(text3);
}
return false;
}
private static bool HasSameTemplateShape(string first, string second)
{
if (string.IsNullOrWhiteSpace(first) || string.IsNullOrWhiteSpace(second))
{
return false;
}
string text = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(first), trim: true));
string text2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(second), trim: true));
if (!string.IsNullOrWhiteSpace(text) && text == text2)
{
return TemplateTokenRegex.IsMatch(text);
}
return false;
}
private static string GetCurrentDynamicOriginal(string savedOriginal, string currentText)
{
if (string.IsNullOrWhiteSpace(savedOriginal))
{
return currentText;
}
if (string.IsNullOrWhiteSpace(currentText))
{
return savedOriginal;
}
savedOriginal = SanitizeText(savedOriginal, trim: true);
currentText = SanitizeText(currentText, trim: true);
if (HasSameTemplateShape(savedOriginal, currentText))
{
return currentText;
}
return savedOriginal;
}
private static string FillTemplatePlaceholders(string template, List<string> values)
{
if (string.IsNullOrEmpty(template) || values == null || values.Count == 0)
{
return template;
}
return TemplateTokenRegex.Replace(template, delegate(Match match)
{
int templateTokenIndex = GetTemplateTokenIndex(match.Value);
if (templateTokenIndex < 0)
{
return match.Value;
}
return (templateTokenIndex >= values.Count) ? match.Value : values[templateTokenIndex];
});
}
private static int GetTemplateTokenIndex(string token)
{
Match match = TemplateTokenRegex.Match(token);
if (!match.Success)
{
return -1;
}
string value = match.Groups["ARG"].Value;
if (string.IsNullOrEmpty(value))
{
return -1;
}
return char.ToUpperInvariant(value[0]) - 65;
}
public static void CreateDefaultFiles()
{
EnsureModFileStructure();
string text = Path.Combine(TranslationFolder, "Global");
Directory.CreateDirectory(text);
string[] array = new string[8]
{
Path.Combine(text, "UI.txt"),
Path.Combine(text, "Common.txt"),
Path.Combine(text, "Notification.txt"),
Path.Combine(text, "ClipboardProxy.txt"),
Path.Combine(text, "LongText.txt"),
Path.Combine(text, "Dynamic.txt"),
Path.Combine(text, "Scenes.txt"),
Path.Combine(text, "Ignore.txt")
};
foreach (string text2 in array)
{
if (!File.Exists(text2))
{
if (IsIgnoreFile(text2))
{
File.WriteAllText(text2, "# One ignored text per line. Ignored texts will not be exported." + Environment.NewLine + "# Lines starting with # are comments. Use \\n for line breaks." + Environment.NewLine, Encoding.UTF8);
}
else
{
File.WriteAllText(text2, "", Encoding.UTF8);
}
}
}
}
public static void LoadTranslations()
{
try
{
EnsureModFileStructure();
Translations.Clear();
ReverseTranslations.Clear();
ReverseTranslationCandidates.Clear();
AmbiguousTranslatedTexts.Clear();
KnownTranslatedTexts.Clear();
IgnoredTexts.Clear();
LastSetTextInputs.Clear();
LastSetTextOutputs.Clear();
LoadIgnoreRules();
HashSet<string> hashSet = new HashSet<string>();
string[] files = Directory.GetFiles(TranslationFolder, "*.txt", SearchOption.AllDirectories);
Array.Sort(files, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
int num = 0;
int num2 = 0;
Dictionary<string, string> dictionary = new Dictionary<string, string>();
string[] array = files;
foreach (string text in array)
{
if (IsIgnoreFile(text))
{
continue;
}
string[] array2 = File.ReadAllLines(text);
foreach (string text2 in array2)
{
if (string.IsNullOrWhiteSpace(text2))
{
continue;
}
string text3 = SanitizeText(text2);
if (string.IsNullOrWhiteSpace(text3) || text3.TrimStart().StartsWith("#"))
{
continue;
}
int num3 = FindTranslationSeparator(text3);
if (num3 < 0)
{
continue;
}
string text4 = UnescapeTranslationFileValue(text3.Substring(0, num3));
string text5 = UnescapeTranslationFileValue(text3.Substring(num3 + 4));
text4 = SanitizeText(text4, trim: true);
text5 = SanitizeText(text5, trim: true);
string item = EscapeTranslationFileValue(text4);
if (!hashSet.Contains(item))
{
hashSet.Add(item);
}
if (!(text4 == text5))
{
if (!Translations.ContainsKey(text4))
{
Translations.Add(text4, text5);
dictionary[text4] = text;
num++;
}
else if (Translations[text4] != text5)
{
num2++;
string value;
string text6 = (dictionary.TryGetValue(text4, out value) ? value : "unknown file");
MelonLogger.Warning("Translation conflict detected for original text:" + Environment.NewLine + text4 + Environment.NewLine + "Using: " + Translations[text4] + Environment.NewLine + "From: " + text6 + Environment.NewLine + "Ignored: " + text5 + Environment.NewLine + "From: " + text);
}
RegisterReverseTranslation(text5, text4);
string text7 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text5), trim: true));
string text8 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text4), trim: true));
RememberTranslatedText(text5);
RememberTranslatedText(text7);
string value2;
if (!string.IsNullOrWhiteSpace(text8) && !Translations.ContainsKey(text8))
{
Translations.Add(text8, text5);
dictionary[text8] = text;
}
else if (!string.IsNullOrWhiteSpace(text8) && Translations.TryGetValue(text8, out value2) && value2 != text5)
{
num2++;
string value3;
string text9 = (dictionary.TryGetValue(text8, out value3) ? value3 : "unknown file");
MelonLogger.Warning("Translation conflict detected for normalized text:" + Environment.NewLine + text8 + Environment.NewLine + "Using: " + value2 + Environment.NewLine + "From: " + text9 + Environment.NewLine + "Ignored: " + text5 + Environment.NewLine + "From: " + text);
}
if (text7 != text5 && !string.IsNullOrWhiteSpace(text7))
{
RegisterReverseTranslation(text7, text8);
}
}
}
}
ExportedTexts = hashSet;
MelonLogger.Msg($"Loaded {num} translations from {files.Length} files. Already exported: {ExportedTexts.Count} unique texts. Conflicts: {num2}.");
}
catch (Exception ex)
{
MelonLogger.Error(ex.ToString());
}
}
private static bool IsIgnoreFile(string file)
{
return string.Equals(Path.GetFileName(file), "Ignore.txt", StringComparison.OrdinalIgnoreCase);
}
private static void LoadIgnoreRules()
{
try
{
string path = Path.Combine(TranslationFolder, "Global", "Ignore.txt");
if (!File.Exists(path))
{
return;
}
string[] array = File.ReadAllLines(path);
foreach (string text in array)
{
if (!string.IsNullOrWhiteSpace(text))
{
string text2 = SanitizeText(text, trim: true);
if (!string.IsNullOrWhiteSpace(text2) && !text2.StartsWith("#"))
{
RegisterIgnoredText(UnescapeTranslationFileValue(text2));
}
}
}
MelonLogger.Msg($"Loaded {IgnoredTexts.Count} ignore text keys.");
}
catch (Exception ex)
{
MelonLogger.Warning("Failed to load ignore rules: " + ex.Message);
}
}
private static void RegisterIgnoredText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return;
}
text = SanitizeText(text, trim: true);
if (string.IsNullOrWhiteSpace(text))
{
return;
}
IgnoredTexts.Add(text);
string text2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true));
if (!string.IsNullOrWhiteSpace(text2))
{
IgnoredTexts.Add(text2);
}
string text3 = StripRichTextTags(text);
if (text3 != text)
{
text3 = SanitizeText(text3, trim: true);
if (!string.IsNullOrWhiteSpace(text3))
{
IgnoredTexts.Add(text3);
}
string text4 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text3), trim: true));
if (!string.IsNullOrWhiteSpace(text4))
{
IgnoredTexts.Add(text4);
}
}
}
private void SetupBoneMenu()
{
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_00b7: 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)
try
{
_page = Page.Root.CreatePage("Lab Localizer", Color.white, 0, true);
_page.CreateFunction("Load Translations", Color.white, (Action)delegate
{
MelonCoroutines.Start(ExecuteScanOnMainThread());
});
_page.CreateFunction("检查更新", Color.white, (Action)StartManualLocalizationUpdateCheck);
if (!ForceReadOnlyMode)
{
_page.CreateBool("Read-Only", Color.white, ReadOnlyMode, (Action<bool>)SetReadOnlyMode);
}
_fontPage = _page.CreatePage("Font Settings", Color.white, 0, true);
RefreshFontMenu();
MelonLogger.Msg("BoneMenu setup complete");
}
catch (Exception ex)
{
MelonLogger.Error("Failed to setup BoneMenu: " + ex.Message);
}
}
private static void RefreshFontMenu()
{
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
try
{
if (_fontPage != null)
{
_fontPage.RemoveAll();
_fontPage.CreateFunction("Refresh Font List", Color.white, (Action)RefreshFontMenu);
ReloadFontBundles();
List<BundleFontEntry> availableBundleFonts = GetAvailableBundleFonts();
List<string> availableFontFiles = GetAvailableFontFiles();
BuildPcFontMenu(availableBundleFonts, availableFontFiles);
}
}
catch (Exception ex)
{
MelonLogger.Error("Failed to rebuild font menu: " + ex.Message);
}
}
private static void BuildPcFontMenu(List<BundleFontEntry> bundleFonts, List<string> fonts)
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_0147: Unknown result type (might be due to invalid IL or missing references)
if (bundleFonts.Count == 0 && fonts.Count == 0)
{
_fontPage.CreateFunction("No fonts found", Color.gray, (Action)delegate
{
});
return;
}
string b = NormalizeFontSettingValue(LastUsedFontPath);
foreach (BundleFontEntry bundleFont in bundleFonts)
{
if (bundleFont != null && !((Object)(object)bundleFont.FontAsset == (Object)null))
{
string capturedSetting = bundleFont.SettingValue;
string text = "AB: " + bundleFont.DisplayName;
if (string.Equals(capturedSetting, b, StringComparison.OrdinalIgnoreCase))
{
text += " (Current)";
}
_fontPage.CreateFunction(text, Color.white, (Action)delegate
{
SelectBundleFont(capturedSetting);
});
}
}
foreach (string font in fonts)
{
string capturedPath = font;
string fileName = Path.GetFileName(font);
string text2 = "File: " + fileName;
if (string.Equals(fileName, b, StringComparison.OrdinalIgnoreCase))
{
text2 += " (Current)";
}
_fontPage.CreateFunction(text2, Color.white, (Action)delegate
{
SelectPcFont(capturedPath);
});
}
}
private IEnumerator ExecuteScanOnMainThread()
{
yield return null;
LoadTranslations();
TranslationCache.Clear();
ForceScanAllTexts();
MelonLogger.Msg("Translations manually loaded and applied.");
}
}
[HarmonyPatch(typeof(TMP_Text), "set_text")]
public class TMP_set_text_Patch
{
public static void Prefix(TMP_Text __instance, ref string value)
{
try
{
if ((Object)(object)__instance == (Object)null)
{
return;
}
int instanceID = ((Object)__instance).GetInstanceID();
string text = value;
if (Core.LastSetTextInputs.TryGetValue(instanceID, out var value2) && Core.LastSetTextOutputs.TryGetValue(instanceID, out var value3) && text == value2)
{
value = value3;
Core.ApplyFont(__instance);
Core.TranslatedObjectIDs.Add(instanceID);
return;
}
if (Core.LastSetTextOutputs.TryGetValue(instanceID, out value3) && text == value3)
{
Core.ApplyFont(__instance);
Core.TranslatedObjectIDs.Add(instanceID);
return;
}
if (Core.IsStandaloneDynamicValue(value))
{
Core.ApplyFont(__instance);
Core.LastSetTextInputs[instanceID] = text;
Core.LastSetTextOutputs[instanceID] = value;
return;
}
string original;
bool flag = !string.IsNullOrWhiteSpace(value) && Core.TryResolveOriginalFromTranslatedText(value.Trim(), out original);
if (!string.IsNullOrWhiteSpace(value) && !flag)
{
Core.OriginalTexts[instanceID] = value;
}
if (!string.IsNullOrWhiteSpace(value) && !flag && !Core.ReadOnlyMode)
{
Core.ExportText(__instance, value);
}
string value4 = (value = Core.Translate(value));
Core.LastSetTextInputs[instanceID] = text;
Core.LastSetTextOutputs[instanceID] = value4;
Core.ApplyFont(__instance);
Core.TranslatedObjectIDs.Add(instanceID);
}
catch
{
}
}
}
}