Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ItemRotationControl v1.0.2
Zichen-ItemRotationControl-1.0.2.dll
Decompiled 4 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using Zichen_ItemRotationControl.I18N; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Zichen-ItemRotationControl-1.0.2")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+edf53df903579000416037543c35c2d7b4f23cc6")] [assembly: AssemblyProduct("Zichen-ItemRotationControl-1.0.2")] [assembly: AssemblyTitle("Zichen-ItemRotationControl-1.0.2")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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; } } } [BepInPlugin("zichen.itemrotationcontrol", "A.Item Rotation Control", "1.0.2")] public sealed class ZichenItemRotationControlPlugin : BaseUnityPlugin { public const string PluginGuid = "zichen.itemrotationcontrol"; public const string PluginVersion = "1.0.2"; public const string PluginName = "A.Item Rotation Control"; private const string ContactConfigSection = "模组信息"; private const string RotationConfigSection = "A.物品旋转控制"; private ConfigEntry<string> moduleNameInfo; private ConfigEntry<string> moduleVersionInfo; private ConfigEntry<string> contactInfo; private static ConfigEntry<bool> rotationControlEnabled; private static ConfigEntry<bool> invertItemRotationHorizontal; private static ConfigEntry<bool> invertItemRotationVertical; private static ConfigEntry<float> itemRotationSensitivity; private static int itemRotationInputDepth; private Harmony harmony; private I18NRegistry _i18n; private I18NSwitcher _i18nSwitcher; private bool _autoLocaleChecked; public static ZichenItemRotationControlPlugin Instance { get; private set; } public static bool UseChinese() { return Instance?._i18n?.UseChinese == true; } private void Awake() { DetachFromManager(); MakePluginHostPersistent(); ResetConfigIfVersionChanged(); Instance = this; bool defaultChinese = LanguageDetector.DetectDefault(); _i18n = new I18NRegistry(((BaseUnityPlugin)this).Config); _i18n.BindLanguage(defaultChinese); moduleNameInfo = _i18n.BindReadOnly("Mod Info", "模组信息", "Mod Name", "模组名称", "物品旋转控制", "物品旋转控制"); moduleVersionInfo = _i18n.BindReadOnly("Mod Info", "模组信息", "Mod Version", "模组版本号", "1.0.2", "1.0.2"); contactInfo = _i18n.BindReadOnly("Mod Info", "模组信息", "Contact QQ", "REPO交流QQ群", "824639225", "824639225"); rotationControlEnabled = _i18n.Bind("A.物品旋转控制", "A.物品旋转控制", "Enabled", "启用", "When enabled, mouse input while holding right-click to rotate items is modified.", "开启后才会修改按住右键旋转物品时的鼠标输入。", defaultValue: true); invertItemRotationHorizontal = _i18n.Bind("A.物品旋转控制", "A.物品旋转控制", "Invert Horizontal", "反转左右旋转", "When enabled, the horizontal mouse direction is reversed while rotating items with right-click held.", "开启后按住右键旋转物品时,鼠标左右方向会反过来。", defaultValue: true); invertItemRotationVertical = _i18n.Bind("A.物品旋转控制", "A.物品旋转控制", "Invert Vertical", "反转上下旋转", "When enabled, the vertical mouse direction is reversed while rotating items with right-click held.", "开启后按住右键旋转物品时,鼠标上下方向会反过来。", defaultValue: true); itemRotationSensitivity = _i18n.Bind("A.物品旋转控制", "A.物品旋转控制", "Rotation Sensitivity", "旋转灵敏度/倍率", "Mouse sensitivity multiplier while rotating items with right-click held. 1.0 is the vanilla speed.", "按住右键旋转物品时的鼠标灵敏度倍率。1.0 为原版速度。", 1f, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f)); ApplyItemRotationPatch(); if (harmony != null) { _i18nSwitcher = new I18NSwitcher(_i18n, harmony, "A.Item Rotation Control", ((BaseUnityPlugin)this).Logger); _i18nSwitcher.InstallRepoConfigHook(); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"zichen-item-rotation-control loaded."); if (!_autoLocaleChecked) { ((MonoBehaviour)this).StartCoroutine(AutoFollowGameLocaleOnce()); } } private IEnumerator AutoFollowGameLocaleOnce() { float waited = 0f; while (waited < 20f) { waited += 0.2f; yield return (object)new WaitForSecondsRealtime(0.2f); if (LanguageDetector.TryReadGameLocaleCode(out var code)) { if (LanguageDetector.IsChineseCode(code) && _i18n?.LanguageEntry != null && _i18n.LanguageEntry.Value != I18NRegistry.DisplayLanguage.中文) { _i18n.LanguageEntry.Value = I18NRegistry.DisplayLanguage.中文; ((BaseUnityPlugin)this).Logger.LogInfo((object)"已根据游戏内语言设置自动切换为中文。"); } _autoLocaleChecked = true; yield break; } } _autoLocaleChecked = true; } private void DetachFromManager() { ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); harmony = null; } } private void ApplyItemRotationPatch() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown //IL_00be: Expected O, but got Unknown //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown try { harmony = new Harmony("zichen.itemrotationcontrol.patch"); MethodInfo methodInfo = AccessTools.Method(typeof(PhysGrabber), "ObjectTurning", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(SemiFunc), "InputMouseX", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(SemiFunc), "InputMouseY", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Failed to find item rotation input methods."); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ZichenItemRotationControlPlugin), "BeginItemRotationInputPatch", (Type[])null), new HarmonyMethod(typeof(ZichenItemRotationControlPlugin), "EndItemRotationInputPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(ZichenItemRotationControlPlugin), "InputMouseXPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(ZichenItemRotationControlPlugin), "InputMouseYPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Item rotation controls patched (v0.5.0 input system)."); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Failed to patch item rotation controls: {arg}"); } } private static void BeginItemRotationInputPatch() { itemRotationInputDepth++; } private static void EndItemRotationInputPatch() { itemRotationInputDepth = Math.Max(0, itemRotationInputDepth - 1); } private static void InputMouseXPostfix(ref float __result) { if (itemRotationInputDepth > 0 && rotationControlEnabled != null && rotationControlEnabled.Value) { __result = AdjustItemRotationAxis(__result, horizontal: true); } } private static void InputMouseYPostfix(ref float __result) { if (itemRotationInputDepth > 0 && rotationControlEnabled != null && rotationControlEnabled.Value) { __result = AdjustItemRotationAxis(__result, horizontal: false); } } internal static float AdjustItemRotationAxis(float value, bool horizontal) { float num = ((itemRotationSensitivity == null) ? 1f : Mathf.Clamp(itemRotationSensitivity.Value, 0.1f, 3f)); float num2 = value * num; if (horizontal && invertItemRotationHorizontal != null && invertItemRotationHorizontal.Value) { num2 = 0f - num2; } if (!horizontal && invertItemRotationVertical != null && invertItemRotationVertical.Value) { num2 = 0f - num2; } return num2; } private void MakePluginHostPersistent() { try { ((Component)this).transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to mark plugin host persistent: " + ex.GetType().Name + ": " + ex.Message)); } } private void ResetConfigIfVersionChanged() { try { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; string text = ReadConfigPluginVersion(configFilePath); if (!string.IsNullOrWhiteSpace(text) && !(text == "1.0.2")) { ResetConfigFileToDefaults(configFilePath); ((BaseUnityPlugin)this).Logger.LogWarning((object)"Config version changed. Old config was reset to defaults."); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to reset config by version: " + ex.Message)); } } private static string ReadConfigPluginVersion(string configPath) { if (!File.Exists(configPath)) { return null; } Match match = Regex.Match(File.ReadAllText(configPath), "(?m)^模组版本号\\s*=\\s*(.+?)\\s*$"); if (!match.Success) { return null; } return match.Groups[1].Value.Trim(); } private void ResetConfigFileToDefaults(string configPath) { ((BaseUnityPlugin)this).Config.Clear(); if (File.Exists(configPath)) { File.Delete(configPath); } ((BaseUnityPlugin)this).Config.Reload(); } } namespace Zichen_ItemRotationControl.I18N { internal static class LanguageDetector { public static readonly string[] DefaultChineseKeywords = new string[6] { "chinese", "简体", "繁體", "繁体", "汉化", "中文" }; public static readonly string[] DefaultChineseEnvironmentPluginGuids = new string[1] { "gravydevsupreme.xunity.autotranslator" }; private const string ChineseLocaleCodePrefix = "zh"; private static bool _reflectionResolved; private static Type _localizationSettingsType; private static PropertyInfo _selectedLocaleProp; private static PropertyInfo _localeIdentifierProp; private static PropertyInfo _identifierCodeProp; public static bool DetectDefault() { if (DetectFromGameLocale()) { return true; } return DetectDefault(DefaultChineseKeywords, DefaultChineseEnvironmentPluginGuids); } public static bool IsGameLocaleChinese() { return DetectFromGameLocale(); } public static bool TryReadGameLocaleCode(out string code) { code = null; try { code = TryReadGameActiveLocaleCode(); if (!string.IsNullOrEmpty(code)) { return true; } code = TryReadSelectedLocaleCode(); return !string.IsNullOrEmpty(code); } catch { code = null; return false; } } public static bool IsChineseCode(string code) { if (!string.IsNullOrEmpty(code)) { return code.StartsWith("zh", StringComparison.OrdinalIgnoreCase); } return false; } private static bool DetectFromGameLocale() { if (TryReadGameLocaleCode(out var code)) { return IsChineseCode(code); } return false; } private static string TryReadGameActiveLocaleCode() { try { Type type = Type.GetType("LocalizationManager, Assembly-CSharp"); if (type == null) { return null; } object obj = type.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null) { return null; } object obj2 = type.GetField("ActiveLocale")?.GetValue(obj); if (obj2 == null) { return null; } return ReadLocaleCode(obj2.GetType().GetField("LocaleReference")?.GetValue(obj2)); } catch { return null; } } private static string TryReadSelectedLocaleCode() { try { ResolveReflectionCache(); if (_localizationSettingsType == null || _selectedLocaleProp == null) { return null; } return ReadLocaleCode(_selectedLocaleProp.GetValue(null, null)); } catch { return null; } } private static string ReadLocaleCode(object locale) { if (locale == null) { return null; } try { object obj = _localeIdentifierProp?.GetValue(locale, null); if (obj == null) { return null; } return _identifierCodeProp?.GetValue(obj, null) as string; } catch { return null; } } private static void ResolveReflectionCache() { if (_reflectionResolved) { return; } _reflectionResolved = true; try { _localizationSettingsType = Type.GetType("UnityEngine.Localization.Settings.LocalizationSettings, Unity.Localization"); if (_localizationSettingsType != null) { _selectedLocaleProp = _localizationSettingsType.GetProperty("SelectedLocale"); if (_selectedLocaleProp != null) { _localeIdentifierProp = _selectedLocaleProp.PropertyType?.GetProperty("Identifier"); _identifierCodeProp = (_localeIdentifierProp?.PropertyType)?.GetProperty("Code"); } } } catch { _localizationSettingsType = null; _selectedLocaleProp = null; } } public static bool DetectDefault(IEnumerable<string> chineseKeywords, IEnumerable<string> chineseEnvironmentPluginGuids) { string[] array = ToFilteredArray(chineseKeywords) ?? DefaultChineseKeywords; string[] array2 = ToFilteredArray(chineseEnvironmentPluginGuids) ?? DefaultChineseEnvironmentPluginGuids; try { foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { string text = pluginInfo.Key ?? string.Empty; PluginInfo value = pluginInfo.Value; object obj; if (value == null) { obj = null; } else { BepInPlugin metadata = value.Metadata; obj = ((metadata != null) ? metadata.Name : null); } if (obj == null) { obj = string.Empty; } string text2 = (string)obj; foreach (string value2 in array) { if (text.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } for (int j = 0; j < array2.Length; j++) { if (string.Equals(text, array2[j], StringComparison.OrdinalIgnoreCase)) { return true; } } } } catch { } return false; } private static string[] ToFilteredArray(IEnumerable<string> source) { if (source == null) { return null; } List<string> list = new List<string>(); foreach (string item in source) { if (!string.IsNullOrEmpty(item)) { list.Add(item); } } if (list.Count != 0) { return list.ToArray(); } return null; } } internal sealed class I18NRegistry { public sealed class CfgI18N { public string SectionEN; public string SectionCN; public string KeyEN; public string KeyCN; public string DescEN; public string DescCN; public ConfigurationManagerAttributes Attrs; public string ValueEN; public string ValueCN; public ConfigEntry<string> ReadOnlyEntry; } public enum DisplayLanguage { 中文, English } private readonly ConfigFile _config; private readonly ManualLogSource _logger; private readonly List<CfgI18N> _all = new List<CfgI18N>(20); private readonly Dictionary<string, CfgI18N> _bySection = new Dictionary<string, CfgI18N>(StringComparer.Ordinal); private readonly Dictionary<string, Dictionary<string, CfgI18N>> _bySectionAndKey = new Dictionary<string, Dictionary<string, CfgI18N>>(StringComparer.Ordinal); public ConfigEntry<DisplayLanguage> LanguageEntry { get; private set; } public bool UseChinese { get { if (LanguageEntry != null) { return LanguageEntry.Value == DisplayLanguage.中文; } return false; } } public int RegisteredCount => _all.Count; public IReadOnlyList<CfgI18N> All => _all; public event Action LanguageChanged; public I18NRegistry(ConfigFile config, ManualLogSource logger = null) { _config = config ?? throw new ArgumentNullException("config"); _logger = logger; } public ConfigEntry<DisplayLanguage> BindLanguage(bool defaultChinese, string sectionEN = "Mod Info", string sectionCN = "模组信息") { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown DisplayLanguage displayLanguage = ((!defaultChinese) ? DisplayLanguage.English : DisplayLanguage.中文); CfgI18N cfgI18N = NewI18N(sectionEN, sectionCN, "Language", "语言", "Switch display language.", "切换显示语言。", readOnly: false); LanguageEntry = _config.Bind<DisplayLanguage>(sectionEN, "Language", displayLanguage, new ConfigDescription(cfgI18N.DescEN, (AcceptableValueBase)null, new object[1] { cfgI18N.Attrs })); LanguageEntry.SettingChanged += delegate { RefreshAttrs(); InvokeLanguageChanged(); }; return LanguageEntry; } public ConfigEntry<T> Bind<T>(string sectEN, string sectCN, string keyEN, string keyCN, string descEN, string descCN, T defaultValue, AcceptableValueBase acceptableValues = null, int order = 0, Action<ConfigEntryBase> customDrawer = null, bool? showRangeAsPercent = null, bool? isAdvanced = null, bool? browsable = null) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown CfgI18N cfgI18N = NewI18N(sectEN, sectCN, keyEN, keyCN, descEN, descCN, readOnly: false); if (order != 0) { cfgI18N.Attrs.Order = order; } if (customDrawer != null) { cfgI18N.Attrs.CustomDrawer = customDrawer; } if (showRangeAsPercent.HasValue) { cfgI18N.Attrs.ShowRangeAsPercent = showRangeAsPercent.Value; } if (isAdvanced.HasValue) { cfgI18N.Attrs.IsAdvanced = isAdvanced.Value; } if (browsable.HasValue) { cfgI18N.Attrs.Browsable = browsable.Value; } return _config.Bind<T>(sectEN, keyEN, defaultValue, new ConfigDescription(descEN, acceptableValues, new object[1] { cfgI18N.Attrs })); } public ConfigEntry<string> BindReadOnly(string sectEN, string sectCN, string keyEN, string keyCN, string valueEN, string valueCN, int order = 0, Action<ConfigEntryBase> customDrawer = null) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown CfgI18N cfgI18N = NewI18N(sectEN, sectCN, keyEN, keyCN, "", "", readOnly: true); cfgI18N.ValueEN = valueEN; cfgI18N.ValueCN = valueCN; if (order != 0) { cfgI18N.Attrs.Order = order; } if (customDrawer != null) { cfgI18N.Attrs.CustomDrawer = customDrawer; } cfgI18N.ReadOnlyEntry = _config.Bind<string>(sectEN, keyEN, valueEN, new ConfigDescription("", (AcceptableValueBase)null, new object[1] { cfgI18N.Attrs })); string text = (UseChinese ? valueCN : valueEN); if (cfgI18N.ReadOnlyEntry.Value != text) { cfgI18N.ReadOnlyEntry.Value = text; } cfgI18N.ReadOnlyEntry.SettingChanged += MakeReadOnlyValueGuard(cfgI18N); return cfgI18N.ReadOnlyEntry; } private EventHandler MakeReadOnlyValueGuard(CfgI18N i18n) { return delegate { if (i18n.ReadOnlyEntry != null) { string text = (UseChinese ? i18n.ValueCN : i18n.ValueEN); if (i18n.ReadOnlyEntry.Value != text) { i18n.ReadOnlyEntry.Value = text; InvokeLanguageChanged(); } } }; } private void InvokeLanguageChanged() { Action action = this.LanguageChanged; if (action == null) { return; } Delegate[] invocationList = action.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action2 = (Action)invocationList[i]; try { action2(); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("LanguageChanged 订阅方异常:" + ex.Message)); } } } } private CfgI18N NewI18N(string sectEN, string sectCN, string keyEN, string keyCN, string descEN, string descCN, bool readOnly) { CfgI18N cfgI18N = new CfgI18N { SectionEN = sectEN, SectionCN = sectCN, KeyEN = keyEN, KeyCN = keyCN, DescEN = descEN, DescCN = descCN, Attrs = (readOnly ? new ConfigurationManagerAttributes { ReadOnly = true, HideDefaultButton = true } : new ConfigurationManagerAttributes()) }; ApplyI18N(cfgI18N); _all.Add(cfgI18N); _bySection[sectEN] = cfgI18N; if (!_bySectionAndKey.TryGetValue(sectEN, out var value)) { value = new Dictionary<string, CfgI18N>(StringComparer.Ordinal); _bySectionAndKey[sectEN] = value; } value[keyEN] = cfgI18N; return cfgI18N; } private void ApplyI18N(CfgI18N i18n) { bool useChinese = UseChinese; i18n.Attrs.Category = (useChinese ? i18n.SectionCN : i18n.SectionEN); i18n.Attrs.DispName = (useChinese ? i18n.KeyCN : i18n.KeyEN); i18n.Attrs.Description = (useChinese ? i18n.DescCN : i18n.DescEN); if (i18n.ReadOnlyEntry != null) { string text = (useChinese ? i18n.ValueCN : i18n.ValueEN); if (i18n.ReadOnlyEntry.Value != text) { i18n.ReadOnlyEntry.Value = text; } } } public void RefreshAttrs() { for (int i = 0; i < _all.Count; i++) { ApplyI18N(_all[i]); } } public void RefreshNow() { RefreshAttrs(); InvokeLanguageChanged(); } public bool TryGetBySection(string sectEN, out CfgI18N i18n) { return _bySection.TryGetValue(sectEN, out i18n); } public bool TryGetByKey(string keyEN, out CfgI18N i18n) { i18n = null; foreach (KeyValuePair<string, Dictionary<string, CfgI18N>> item in _bySectionAndKey) { if (item.Value.TryGetValue(keyEN, out i18n)) { return true; } } return false; } } internal sealed class ConfigurationManagerAttributes { public bool? ShowRangeAsPercent; public Action<ConfigEntryBase> CustomDrawer; public bool? Browsable; public string Category; public object DefaultValue; public bool? HideDefaultButton; public bool? HideSettingName; public string Description; public string DispName; public int? Order; public bool? ReadOnly; public bool? IsAdvanced; } internal sealed class I18NSwitcher { private sealed class LiveLabel { public WeakReference<TextMeshProUGUI> Tmp; public I18NRegistry.CfgI18N I18N; public bool IsSection; } private sealed class LiveValue { public WeakReference<Object> InputField; public I18NRegistry.CfgI18N I18N; } private sealed class LiveToggleButtons { public WeakReference<TextMeshProUGUI> LeftTmp; public WeakReference<TextMeshProUGUI> RightTmp; public bool IsLanguage; } private readonly I18NRegistry _registry; private readonly Harmony _harmony; private readonly ManualLogSource _logger; private readonly string _pluginName; private readonly string _pluginNameFixedForm; private readonly List<LiveLabel> _liveLabels = new List<LiveLabel>(64); private readonly List<LiveValue> _liveValues = new List<LiveValue>(8); private readonly List<LiveToggleButtons> _liveToggleButtons = new List<LiveToggleButtons>(16); private static readonly Dictionary<Type, FieldInfo> _labelTmpFieldCache = new Dictionary<Type, FieldInfo>(8); private static readonly Dictionary<Type, FieldInfo> _inputStringSystemFieldCache = new Dictionary<Type, FieldInfo>(2); private static readonly Dictionary<Type, MethodInfo> _inputStringSystemSetValueCache = new Dictionary<Type, MethodInfo>(2); private static readonly Dictionary<Type, FieldInfo> _toggleLeftTmpCache = new Dictionary<Type, FieldInfo>(4); private static readonly Dictionary<Type, FieldInfo> _toggleRightTmpCache = new Dictionary<Type, FieldInfo>(4); private static readonly object _reflectionCacheLock = new object(); private static readonly object _globalInstancesLock = new object(); private static readonly List<WeakReference<I18NSwitcher>> _globalInstances = new List<WeakReference<I18NSwitcher>>(2); public int LiveUiCount => _liveLabels.Count + _liveValues.Count + _liveToggleButtons.Count; public I18NSwitcher(I18NRegistry registry, Harmony harmony, string pluginName, ManualLogSource logger = null) { _registry = registry ?? throw new ArgumentNullException("registry"); _harmony = harmony ?? throw new ArgumentNullException("harmony"); _pluginName = pluginName ?? throw new ArgumentNullException("pluginName"); _pluginNameFixedForm = FixNamingForOwnership(pluginName); _logger = logger; _registry.LanguageChanged += RefreshLiveUi; } private static string FixNamingForOwnership(string input) { if (string.IsNullOrEmpty(input)) { return input; } input = Regex.Replace(input, "([a-z])([A-Z])", "$1 $2"); input = Regex.Replace(input, "([A-Z])([A-Z][a-z])", "$1 $2"); input = Regex.Replace(input, "\\s+", " "); input = Regex.Replace(input, "([A-Z]\\.)\\s([A-Z]\\.)", "$1$2"); return input.Trim(); } public void InstallRepoConfigHook() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("MenuLib.MenuAPI"); if (type == null) { ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)"MenuLib 未安装,跳过 REPOConfig 双语劫持。"); } return; } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(I18NSwitcher), "PostfixDispatch", (Type[])null, (Type[])null)) { priority = 0 }; HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal) { "CreateREPOLabel", "CreateREPOToggle", "CreateREPOSlider", "CreateREPOInputField", "CreateREPOButton" }; int num = 0; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type)) { if (!hashSet.Contains(declaredMethod.Name)) { continue; } ParameterInfo[] parameters = declaredMethod.GetParameters(); if (parameters.Length != 0 && !(parameters[0].ParameterType != typeof(string))) { try { _harmony.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch { } } } if (num > 0) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogInfo((object)$"REPOConfig 双语显示已挂载({num} 个 UI 创建方法)。"); } } else { ManualLogSource logger3 = _logger; if (logger3 != null) { logger3.LogInfo((object)"REPOConfig 没找到可劫持的 UI 方法。"); } } RegisterGlobalInstance(this); } catch (Exception ex) { ManualLogSource logger4 = _logger; if (logger4 != null) { logger4.LogInfo((object)("REPOConfig 劫持失败:" + ex.Message)); } } } private static void RegisterGlobalInstance(I18NSwitcher inst) { lock (_globalInstancesLock) { _globalInstances.Add(new WeakReference<I18NSwitcher>(inst)); } } public static void PostfixDispatch(object[] __args, object __result) { try { if (__args == null || __args.Length == 0 || __result == null || !(__args[0] is string text) || string.IsNullOrEmpty(text)) { return; } List<I18NSwitcher> list = null; lock (_globalInstancesLock) { for (int num = _globalInstances.Count - 1; num >= 0; num--) { if (!_globalInstances[num].TryGetTarget(out var target)) { _globalInstances.RemoveAt(num); } else { (list ?? (list = new List<I18NSwitcher>(_globalInstances.Count))).Add(target); } } } if (list != null) { for (int i = 0; i < list.Count; i++) { list[i].TryRegisterLiveLabel(text, __result); } } } catch { } } private static string GetOwningPluginName(object uiElement) { try { object obj = ((uiElement is Component) ? uiElement : null); GameObject val = (GameObject)(((obj != null) ? ((Component)obj).gameObject : null) ?? ((uiElement is GameObject) ? uiElement : null)); if ((Object)(object)val == (Object)null) { return null; } Transform val2 = val.transform; while ((Object)(object)val2 != (Object)null) { string name = ((Object)((Component)val2).gameObject).name; if (name != null && name.StartsWith("Menu Page ", StringComparison.Ordinal)) { return name.Substring("Menu Page ".Length); } val2 = val2.parent; } } catch { } return null; } private void TryRegisterLiveLabel(string label, object uiElement) { string owningPluginName = GetOwningPluginName(uiElement); if ((owningPluginName != null && !string.Equals(owningPluginName, _pluginNameFixedForm, StringComparison.Ordinal)) || (!_registry.TryGetBySection(label, out var i18n) && !_registry.TryGetByKey(label, out i18n))) { return; } bool flag = string.Equals(label, i18n.SectionEN, StringComparison.Ordinal); TextMeshProUGUI labelTmp = GetLabelTmp(uiElement); if ((Object)(object)labelTmp == (Object)null) { return; } bool useChinese = _registry.UseChinese; string text = ((!flag) ? (useChinese ? i18n.KeyCN : i18n.KeyEN) : (useChinese ? i18n.SectionCN : i18n.SectionEN)); if (!string.IsNullOrEmpty(text)) { try { ((TMP_Text)labelTmp).text = text; } catch { } } _liveLabels.Add(new LiveLabel { Tmp = new WeakReference<TextMeshProUGUI>(labelTmp), I18N = i18n, IsSection = flag }); if (!flag && i18n.ReadOnlyEntry != null) { Object val = (Object)((uiElement is Object) ? uiElement : null); if (val != null && HasInputStringSystemField(((object)val).GetType())) { _liveValues.Add(new LiveValue { InputField = new WeakReference<Object>(val), I18N = i18n }); string text2 = (useChinese ? i18n.ValueCN : i18n.ValueEN); if (i18n.ReadOnlyEntry.Value != text2) { i18n.ReadOnlyEntry.Value = text2; } TrySetInputFieldValue(val, text2); } } if (flag) { return; } Type type = uiElement.GetType(); FieldInfo value; FieldInfo value2; lock (_reflectionCacheLock) { if (!_toggleLeftTmpCache.TryGetValue(type, out value)) { value = AccessTools.Field(type, "leftButtonTMP"); _toggleLeftTmpCache[type] = value; } if (!_toggleRightTmpCache.TryGetValue(type, out value2)) { value2 = AccessTools.Field(type, "rightButtonTMP"); _toggleRightTmpCache[type] = value2; } } if (value != null && value2 != null) { object? value3 = value.GetValue(uiElement); TextMeshProUGUI val2 = (TextMeshProUGUI)((value3 is TextMeshProUGUI) ? value3 : null); object? value4 = value2.GetValue(uiElement); TextMeshProUGUI val3 = (TextMeshProUGUI)((value4 is TextMeshProUGUI) ? value4 : null); if ((Object)(object)val2 != (Object)null && (Object)(object)val3 != (Object)null) { _liveToggleButtons.Add(new LiveToggleButtons { LeftTmp = new WeakReference<TextMeshProUGUI>(val2), RightTmp = new WeakReference<TextMeshProUGUI>(val3), IsLanguage = false }); ApplyToggleButtonText(val2, val3, useChinese, isLanguage: false); } } } private static TextMeshProUGUI GetLabelTmp(object uiElement) { try { Type type = uiElement.GetType(); FieldInfo value; lock (_reflectionCacheLock) { if (!_labelTmpFieldCache.TryGetValue(type, out value)) { value = AccessTools.Field(type, "labelTMP"); _labelTmpFieldCache[type] = value; } } object? obj = value?.GetValue(uiElement); return (TextMeshProUGUI)((obj is TextMeshProUGUI) ? obj : null); } catch { return null; } } private static bool HasInputStringSystemField(Type t) { lock (_reflectionCacheLock) { if (!_inputStringSystemFieldCache.TryGetValue(t, out var value)) { value = AccessTools.Field(t, "inputStringSystem"); _inputStringSystemFieldCache[t] = value; } return value != null; } } private static bool TrySetInputFieldValue(Object ifObj, string newVal) { try { Type type = ((object)ifObj).GetType(); FieldInfo value; lock (_reflectionCacheLock) { if (!_inputStringSystemFieldCache.TryGetValue(type, out value)) { value = AccessTools.Field(type, "inputStringSystem"); _inputStringSystemFieldCache[type] = value; } } if (value == null) { return false; } object value2 = value.GetValue(ifObj); if (value2 == null) { return false; } MethodInfo value3; lock (_reflectionCacheLock) { Type type2 = value2.GetType(); if (!_inputStringSystemSetValueCache.TryGetValue(type2, out value3)) { value3 = AccessTools.Method(type2, "SetValue", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); _inputStringSystemSetValueCache[type2] = value3; } } if (value3 == null) { return false; } value3.Invoke(value2, new object[2] { newVal ?? string.Empty, false }); return true; } catch { return false; } } private static void ApplyToggleButtonText(TextMeshProUGUI leftTmp, TextMeshProUGUI rightTmp, bool cn, bool isLanguage) { try { if (isLanguage) { ((TMP_Text)leftTmp).text = (cn ? "中文" : "Chinese"); ((TMP_Text)rightTmp).text = (cn ? "英文" : "English"); } else { ((TMP_Text)leftTmp).text = (cn ? "开" : "ON"); ((TMP_Text)rightTmp).text = (cn ? "关" : "OFF"); } } catch { } } private void RefreshLiveUi() { bool useChinese = _registry.UseChinese; for (int num = _liveLabels.Count - 1; num >= 0; num--) { LiveLabel liveLabel = _liveLabels[num]; if (!liveLabel.Tmp.TryGetTarget(out var target) || (Object)(object)target == (Object)null || (Object)(object)((Component)target).gameObject == (Object)null) { _liveLabels.RemoveAt(num); } else { string text = ((!liveLabel.IsSection) ? (useChinese ? liveLabel.I18N.KeyCN : liveLabel.I18N.KeyEN) : (useChinese ? liveLabel.I18N.SectionCN : liveLabel.I18N.SectionEN)); try { ((TMP_Text)target).text = text; } catch { _liveLabels.RemoveAt(num); } } } for (int num2 = _liveValues.Count - 1; num2 >= 0; num2--) { LiveValue liveValue = _liveValues[num2]; if (!liveValue.InputField.TryGetTarget(out var target2) || target2 == (Object)null) { _liveValues.RemoveAt(num2); } else { string newVal = (useChinese ? liveValue.I18N.ValueCN : liveValue.I18N.ValueEN); if (!TrySetInputFieldValue(target2, newVal)) { _liveValues.RemoveAt(num2); } } } for (int num3 = _liveToggleButtons.Count - 1; num3 >= 0; num3--) { LiveToggleButtons liveToggleButtons = _liveToggleButtons[num3]; if (!liveToggleButtons.LeftTmp.TryGetTarget(out var target3) || (Object)(object)target3 == (Object)null || !liveToggleButtons.RightTmp.TryGetTarget(out var target4) || (Object)(object)target4 == (Object)null) { _liveToggleButtons.RemoveAt(num3); } else { ApplyToggleButtonText(target3, target4, useChinese, liveToggleButtons.IsLanguage); } } } } }