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 ValheimCN v1.1.8
plugins/ValheimCN.dll
Decompiled 14 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json.Linq; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [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("ValheimCN")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ValheimCN")] [assembly: AssemblyTitle("ValheimCN")] [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; } } } namespace ValheimCN { internal static class ConfigCommentRewriter { private const string CreatedByPrefix = "Settings file was created by plugin "; private const string PluginGuidPrefix = "Plugin GUID: "; private const string SettingTypePrefix = "Setting type: "; private const string DefaultValuePrefix = "Default value: "; private const string AcceptableValuesPrefix = "Acceptable values: "; internal static string Rewrite(string text, Func<string, string> lookupDescription) { if (string.IsNullOrEmpty(text)) { return text; } string[] array = text.Split(new char[1] { '\n' }); StringBuilder stringBuilder = new StringBuilder(text.Length + 512); bool flag = false; for (int num = 0; num < array.Length; num++) { if (num > 0) { stringBuilder.Append('\n'); } string text2 = array[num]; int num2; string text3; if (text2.Length > 0) { num2 = ((text2[text2.Length - 1] == '\r') ? 1 : 0); if (num2 != 0) { text3 = text2.Substring(0, text2.Length - 1); goto IL_007f; } } else { num2 = 0; } text3 = text2; goto IL_007f; IL_007f: string text4 = text3; string text5 = RewriteCommentLine(text4, lookupDescription); if (!string.Equals(text5, text4, StringComparison.Ordinal)) { flag = true; } stringBuilder.Append(text5); if (num2 != 0) { stringBuilder.Append('\r'); } } if (!flag) { return text; } return stringBuilder.ToString(); } internal static string RewriteCommentLine(string line, Func<string, string> lookupDescription) { if (string.IsNullOrEmpty(line) || line[0] != '#') { return line; } if (line.Length > 2 && line[1] == '#' && line[2] == ' ') { string text = line.Substring(3); string text2 = lookupDescription?.Invoke(text); if (!string.IsNullOrEmpty(text2)) { return "## " + text2; } if (text.StartsWith("Settings file was created by plugin ", StringComparison.Ordinal)) { return "## 本配置文件由插件 " + text.Substring("Settings file was created by plugin ".Length) + " 自动生成"; } if (text.StartsWith("Plugin GUID: ", StringComparison.Ordinal)) { return "## 插件 GUID:" + text.Substring("Plugin GUID: ".Length); } return line; } if (line.Length > 2 && line[1] == ' ') { string text3 = line.Substring(2); if (text3.StartsWith("Setting type: ", StringComparison.Ordinal)) { return "# 设置类型:" + text3.Substring("Setting type: ".Length); } if (text3.StartsWith("Default value: ", StringComparison.Ordinal)) { return "# 默认值:" + text3.Substring("Default value: ".Length); } if (text3.StartsWith("Acceptable values: ", StringComparison.Ordinal)) { return "# 可选值:" + text3.Substring("Acceptable values: ".Length); } } return line; } } internal static class ConfigLocalizer { internal static readonly Regex Cjk = new Regex("[一-鿿]", RegexOptions.Compiled); private static readonly Dictionary<ModDictionary, int> Pending = new Dictionary<ModDictionary, int>(); private static readonly List<ModDictionary> Buffer = new List<ModDictionary>(8); private static readonly HashSet<string> WarnedPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private const int PendingTimeoutMs = 60000; internal static bool Enabled => LocEngine.ConfigUiEnabled; internal static void RequestAll() { IList<ModDictionary> dictionaries = LocEngine.Dictionaries; for (int i = 0; i < dictionaries.Count; i++) { RequestFor(dictionaries[i]); } } internal static void RequestFor(ModDictionary d) { if (d != null && Enabled) { if (Pending.Count > 128) { Pending.Clear(); } Pending[d] = Environment.TickCount; } } internal static void Drain() { if (Pending.Count == 0) { return; } if (!Enabled) { Pending.Clear(); return; } Buffer.Clear(); foreach (KeyValuePair<ModDictionary, int> item in Pending) { Buffer.Add(item.Key); } for (int i = 0; i < Buffer.Count; i++) { ModDictionary modDictionary = Buffer[i]; int value; if (LocalizeOne(modDictionary)) { Pending.Remove(modDictionary); } else if (Pending.TryGetValue(modDictionary, out value) && Environment.TickCount - value > 60000) { Pending.Remove(modDictionary); } } } private static bool LocalizeOne(ModDictionary d) { if (d == null || string.IsNullOrEmpty(d.TargetGuid)) { return true; } if (!d.HasConfigText) { return true; } if (!LocEngine.IsPluginLoaded(d.TargetGuid)) { return true; } if (!Chainloader.PluginInfos.TryGetValue(d.TargetGuid, out var value) || value == null || (Object)(object)value.Instance == (Object)null) { return false; } ConfigFile config = value.Instance.Config; if (config == null) { return true; } int num = ApplyDescriptions(config, d); if (num > 0) { config.Save(); } bool flag = RewriteFile(config.ConfigFilePath, d); if (num > 0) { ValheimCNPlugin.Log($"已把 {d.TargetName} 的 {num} 条配置说明改写为中文。"); } else if (flag) { ValheimCNPlugin.Log(d.TargetName + " 的 cfg 注释已汉化。"); } return true; } private static int ApplyDescriptions(ConfigFile cfg, ModDictionary d) { int num = 0; foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in cfg) { ConfigEntryBase value = item.Value; if (value == null) { continue; } ConfigDescription description = value.Description; if (description != null && !string.IsNullOrEmpty(description.Description) && !Cjk.IsMatch(description.Description)) { string text = d.TranslateConfigDescription(description.Description); if (!string.IsNullOrEmpty(text) && TrySetDescription(value, text)) { num++; } } } return num; } private static bool TrySetDescription(ConfigEntryBase entry, string cn) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown try { PropertyInfo property = ((object)entry).GetType().GetProperty("Description", BindingFlags.Instance | BindingFlags.Public); if (property != null && property.PropertyType == typeof(string)) { MethodInfo setMethod = property.GetSetMethod(nonPublic: false); if (setMethod != null) { setMethod.Invoke(entry, new object[1] { cn }); return true; } } ConfigDescription description = entry.Description; ConfigDescription val = new ConfigDescription(cn, description.AcceptableValues, description.Tags); PropertyInfo property2 = typeof(ConfigEntryBase).GetProperty("Description", BindingFlags.Instance | BindingFlags.Public); if (property2 != null) { MethodInfo setMethod2 = property2.GetSetMethod(nonPublic: true); if (setMethod2 != null) { setMethod2.Invoke(entry, new object[1] { val }); return true; } } FieldInfo field = typeof(ConfigEntryBase).GetField("<Description>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { field.SetValue(entry, val); return true; } } catch (Exception) { } return false; } internal static void OnConfigSaved(ConfigFile file) { if (file == null || !Enabled) { return; } string configFilePath = file.ConfigFilePath; if (!string.IsNullOrEmpty(configFilePath)) { ModDictionary modDictionary = LocEngine.Get(Path.GetFileNameWithoutExtension(configFilePath)); if (modDictionary != null && modDictionary.Active && modDictionary.HasConfigText) { RewriteFile(configFilePath, modDictionary); } } } internal static bool RewriteFile(string path, ModDictionary dict) { if (dict == null || string.IsNullOrEmpty(path) || !File.Exists(path)) { return false; } try { byte[] array = File.ReadAllBytes(path); bool flag = array.Length >= 3 && array[0] == 239 && array[1] == 187 && array[2] == 191; int num = (flag ? 3 : 0); string text; try { text = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(array, num, array.Length - num); } catch (DecoderFallbackException) { return false; } string text2 = ConfigCommentRewriter.Rewrite(text, dict.TranslateConfigDescription); if (string.Equals(text2, text, StringComparison.Ordinal)) { return false; } File.WriteAllText(path, text2, new UTF8Encoding(flag)); return true; } catch (Exception ex2) { if (WarnedPaths.Add(path)) { ValheimCNPlugin.Warn("汉化 cfg 注释失败(" + Path.GetFileName(path) + "):" + ex2.Message); } return false; } } } internal static class GameResourceBridge { private static readonly Dictionary<string, string> _map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static bool _built; private static string _langAtBuild; public static bool Enabled { get { if (ValheimCNPlugin.CfgBridgeGameResources != null) { return ValheimCNPlugin.CfgBridgeGameResources.Value; } return false; } } public static void EnsureBuilt() { string text = SafeLang(); if (!_built || !(_langAtBuild == text) || (_map.Count <= 0 && HasDataSource())) { Build(); _built = true; _langAtBuild = text; } } private static bool HasDataSource() { try { if ((Object)(object)ObjectDB.instance != (Object)null && GetField(ObjectDB.instance, "m_items") is IEnumerable enumerable) { { IEnumerator enumerator = enumerable.GetEnumerator(); try { if (enumerator.MoveNext()) { _ = enumerator.Current; return true; } } finally { IDisposable disposable = enumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } } } } catch { } try { if ((Object)(object)ZoneSystem.instance != (Object)null && GetField(ZoneSystem.instance, "m_locations") is IEnumerable enumerable2) { { IEnumerator enumerator = enumerable2.GetEnumerator(); try { if (enumerator.MoveNext()) { _ = enumerator.Current; return true; } } finally { IDisposable disposable2 = enumerator as IDisposable; if (disposable2 != null) { disposable2.Dispose(); } } } } } catch { } return false; } private static string SafeLang() { try { return (Localization.instance != null) ? Localization.instance.GetSelectedLanguage() : "?"; } catch { return "?"; } } private static void Build() { _map.Clear(); try { ScanPrefabs(); } catch (Exception ex) { ValheimCNPlugin.Warn("[GameResourceBridge] prefabs scan failed: " + ex.Message); } try { ScanLocations(); } catch (Exception ex2) { ValheimCNPlugin.Warn("[GameResourceBridge] locations scan failed: " + ex2.Message); } } private static void ScanPrefabs() { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || !(GetField(instance, "m_items") is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { GameObject val = (GameObject)((item is GameObject) ? item : null); if (!((Object)(object)val == (Object)null)) { string text = PrefabChinese(val); if (!string.IsNullOrEmpty(text)) { Add(Norm(((Object)val).name), text); } } } } private static string PrefabChinese(GameObject go) { ItemDrop component = go.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null) { return null; } object field = GetField(component, "m_itemData"); if (field == null) { return null; } object field2 = GetField(field, "m_shared"); if (field2 == null) { return null; } string text = GetField(field2, "m_name") as string; if (string.IsNullOrEmpty(text) || !text.StartsWith("$")) { return null; } return LocalizeSafe(text); } private static void ScanLocations() { ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || !(GetField(instance, "m_locations") is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { if (item == null) { continue; } string text = GetField(item, "m_name") as string; string text2 = null; string text3 = GetField(item, "m_prefabName") as string; if (!string.IsNullOrEmpty(text3)) { text2 = Lookup(Norm(text3)); } if (text2 == null) { object field = GetField(item, "m_prefab"); string text4 = ((field != null) ? (GetProp(field, "Name") as string) : null); if (!string.IsNullOrEmpty(text4)) { text2 = Lookup(Norm(text4)); } } if (!string.IsNullOrEmpty(text2) && !string.IsNullOrEmpty(text)) { Add(Norm(text), text2); } } } private static string Lookup(string k) { if (!_map.TryGetValue(k, out var value)) { return null; } return value; } private static void Add(string key, string cn) { if (!string.IsNullOrEmpty(key) && !_map.ContainsKey(key)) { _map[key] = cn; } } public static bool TryBridge(string text, out string cn) { cn = null; if (!Enabled) { return false; } EnsureBuilt(); if (string.IsNullOrEmpty(text)) { return false; } return _map.TryGetValue(Norm(text), out cn); } private static string Norm(string s) { if (string.IsNullOrEmpty(s)) { return ""; } return Regex.Replace(s, "[\\s_\\-]", "").ToLowerInvariant(); } private static string LocalizeSafe(string tok) { try { if (Localization.instance == null) { return null; } string text = Localization.instance.Localize(tok); return (text == tok) ? null : text; } catch { return null; } } private static object GetField(object obj, string name) { if (obj == null) { return null; } FieldInfo field = obj.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return null; } try { return field.GetValue(obj); } catch { return null; } } private static object GetProp(object obj, string name) { if (obj == null) { return null; } PropertyInfo property = obj.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null) { return null; } try { return property.GetValue(obj); } catch { return null; } } } internal static class LocEngine { internal static string DictDir; private static ManualLogSource _logger; private static readonly List<ModDictionary> Dicts = new List<ModDictionary>(); private static string _cachedLang; private static float _nextLangCheck = -1f; private static bool _hasInactive; private static float _nextActivationCheck; internal static readonly Dictionary<string, string> PluginNameOverrides = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, string> GuiCache = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly Dictionary<string, string> CfgCache = new Dictionary<string, string>(StringComparer.Ordinal); private const int CacheCap = 4096; private static readonly HashSet<Component> Pending = new HashSet<Component>(); private const int PendingCap = 2048; private static Component[] _pendingBuffer; internal static int DictCount => Dicts.Count; internal static int EntryCount { get { int num = 0; for (int i = 0; i < Dicts.Count; i++) { num += Dicts[i].Exact.Count + Dicts[i].Regexes.Count; } return num; } } internal static IList<ModDictionary> Dictionaries => Dicts; internal static bool ConfigUiEnabled { get { if (ValheimCNPlugin.CfgConfigDescriptions == null || !ValheimCNPlugin.CfgConfigDescriptions.Value) { return false; } if (!ShouldTranslate()) { return false; } RefreshActiveStateIfStale(); return true; } } internal static void Initialize(string configPath, ManualLogSource logger) { _logger = logger; DictDir = Path.Combine(configPath, "ValheimCN"); Directory.CreateDirectory(DictDir); SeedBundledDictionaries(); LoadAll(); RefreshActiveState(); } private static void SeedBundledDictionaries() { try { string path = Path.Combine(Path.GetDirectoryName(typeof(LocEngine).Assembly.Location) ?? "", "Localization"); if (!Directory.Exists(path)) { return; } string[] files = Directory.GetFiles(path, "*.json", SearchOption.AllDirectories); foreach (string text in files) { string text2 = Path.Combine(DictDir, Path.GetFileName(text)); if (!File.Exists(text2)) { File.Copy(text, text2, overwrite: false); ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)("已释放词典 " + Path.GetFileName(text) + " 到 " + DictDir)); } } } } catch (Exception ex) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogWarning((object)("释放内置词典失败:" + ex.Message)); } } } private static void LoadAll() { Dicts.Clear(); string[] files; try { files = Directory.GetFiles(DictDir, "*.json", SearchOption.AllDirectories); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("读取词典目录失败:" + ex.Message)); } return; } string[] array = files; foreach (string path in array) { try { ModDictionary modDictionary = Parse(File.ReadAllText(path, Encoding.UTF8), path); if (modDictionary == null) { continue; } Dicts.Add(modDictionary); if (!string.IsNullOrEmpty(modDictionary.MismatchNote)) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogInfo((object)(Path.GetFileName(path) + ":" + modDictionary.MismatchNote)); } } } catch (Exception ex2) { ManualLogSource logger3 = _logger; if (logger3 != null) { logger3.LogError((object)("词典 " + Path.GetFileName(path) + " 解析失败:" + ex2.Message)); } } } } private static ModDictionary Parse(string json, string path) { JObject val = JObject.Parse(json); ModDictionary modDictionary = new ModDictionary { FilePath = path }; JToken obj = val["meta"]; JObject val2 = (JObject)(object)((obj is JObject) ? obj : null); if (val2 != null) { modDictionary.TargetGuid = ((string)val2["targetGuid"]) ?? ""; modDictionary.TargetName = ((string)val2["targetName"]) ?? ""; modDictionary.TargetVersion = ((string)val2["targetVersion"]) ?? ""; modDictionary.DisplayName = ((string)val2["displayName"]) ?? ""; modDictionary.ExpectedMd5 = ((string)val2["md5"]) ?? ""; modDictionary.ExpectedSize = ((long?)val2["size"]).GetValueOrDefault(); } if (string.IsNullOrEmpty(modDictionary.TargetName)) { modDictionary.TargetName = Path.GetFileNameWithoutExtension(path); } JToken obj2 = val["objects"]; JArray val3 = (JArray)(object)((obj2 is JArray) ? obj2 : null); if (val3 != null) { foreach (JToken item in val3) { modDictionary.Objects.Add((string)item); } } JToken obj3 = val["objectPrefixes"]; JArray val4 = (JArray)(object)((obj3 is JArray) ? obj3 : null); if (val4 != null) { foreach (JToken item2 in val4) { modDictionary.ObjectPrefixes.Add((string)item2); } } JToken obj4 = val["panelRoots"]; JArray val5 = (JArray)(object)((obj4 is JArray) ? obj4 : null); if (val5 != null) { foreach (JToken item3 in val5) { modDictionary.PanelRoots.Add((string)item3); } } JToken obj5 = val["panelRootPrefixes"]; JArray val6 = (JArray)(object)((obj5 is JArray) ? obj5 : null); if (val6 != null) { foreach (JToken item4 in val6) { modDictionary.PanelRootPrefixes.Add((string)item4); } } JToken obj6 = val["exact"]; JObject val7 = (JObject)(object)((obj6 is JObject) ? obj6 : null); if (val7 != null) { foreach (KeyValuePair<string, JToken> item5 in val7) { modDictionary.Exact[item5.Key] = (string)item5.Value; } } JToken obj7 = val["regex"]; JArray val8 = (JArray)(object)((obj7 is JArray) ? obj7 : null); if (val8 != null) { ParseRegexArray(val8, modDictionary, modDictionary.Regexes); } JToken obj8 = val["globalRegex"]; JArray val9 = (JArray)(object)((obj8 is JArray) ? obj8 : null); if (val9 != null) { ParseRegexArray(val9, modDictionary, modDictionary.GlobalRegexes); } ReadMap(val["globalExact"], modDictionary.GlobalExact); ReadMap(val["configDescriptions"], modDictionary.ConfigDescriptions); ReadMap(val["configNames"], modDictionary.ConfigNames); ReadMap(val["configSections"], modDictionary.ConfigSections); ReadMap(val["configValues"], modDictionary.ConfigValues); JToken obj9 = val["loadingTips"]; JArray val10 = (JArray)(object)((obj9 is JArray) ? obj9 : null); if (val10 != null) { foreach (JToken item6 in val10) { modDictionary.LoadingTips.Add((string)item6); } } VerifyFingerprint(modDictionary); return modDictionary; } private static void ReadMap(JToken token, Dictionary<string, string> target) { JObject val = (JObject)(object)((token is JObject) ? token : null); if (val == null) { return; } foreach (KeyValuePair<string, JToken> item in val) { target[item.Key] = (string)item.Value; } } private static void ParseRegexArray(JArray arr, ModDictionary d, List<RegexRule> target) { foreach (JToken item in arr) { string text = (string)item[(object)"pattern"]; string text2 = (string)item[(object)"replacement"]; bool valueOrDefault = (bool?)item[(object)"chain"] == true; if (string.IsNullOrEmpty(text)) { string text3 = (string)item[(object)"template"]; if (string.IsNullOrEmpty(text3)) { continue; } try { RegexRule regexRule = new RegexRule { Template = text3, Replacement = (text2 ?? ""), Owner = d, Chain = valueOrDefault }; regexRule.CompileTemplate(); target.Add(regexRule); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("插值规则无效,已跳过:" + text3 + "(" + ex.Message + ")")); } } continue; } try { target.Add(new RegexRule { Re = new Regex(text, RegexOptions.Compiled), Replacement = (text2 ?? ""), Chain = valueOrDefault }); } catch (Exception ex2) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogWarning((object)("正则无效,已跳过:" + text + "(" + ex2.Message + ")")); } } } } private static void VerifyFingerprint(ModDictionary d) { if (string.IsNullOrEmpty(d.ExpectedMd5) || string.IsNullOrEmpty(d.TargetName) || (!string.IsNullOrEmpty(d.TargetGuid) && !IsPluginLoaded(d.TargetGuid))) { return; } try { string text = LocateTargetDll(d); if (text != null && !string.Equals(Md5Of(text), d.ExpectedMd5, StringComparison.OrdinalIgnoreCase)) { d.MismatchNote = "原版 MOD 有新版,词典稍旧:已汉化的文字照常生效;如发现未翻译的文案,请到「请求汉化」页提交反馈。"; } } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)("指纹校验跳过:" + ex.Message)); } } } private static string LocateTargetDll(ModDictionary d) { try { if (!string.IsNullOrEmpty(d.TargetGuid) && Chainloader.PluginInfos.TryGetValue(d.TargetGuid, out var value) && !string.IsNullOrEmpty((value != null) ? value.Location : null) && File.Exists(value.Location)) { return value.Location; } } catch (Exception) { } try { string[] files = Directory.GetFiles(Path.Combine(Paths.GameRootPath, "BepInEx", "plugins"), d.TargetName + ".dll", SearchOption.AllDirectories); return (files.Length != 0) ? files[0] : null; } catch (Exception) { return null; } } private static string CurrentFileVersion(string path) { try { string text = FileVersionInfo.GetVersionInfo(path)?.FileVersion; return string.IsNullOrEmpty(text) ? "未知版本" : text; } catch (Exception) { return "未知版本"; } } internal static string Md5Of(string path) { using MD5 mD = MD5.Create(); using FileStream inputStream = File.OpenRead(path); byte[] array = mD.ComputeHash(inputStream); StringBuilder stringBuilder = new StringBuilder(32); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2")); } return stringBuilder.ToString(); } internal static void RefreshActiveState() { bool hasInactive = false; PluginNameOverrides.Clear(); foreach (ModDictionary dict in Dicts) { if (string.IsNullOrEmpty(dict.TargetGuid)) { dict.Active = true; continue; } bool flag = dict.ActiveChecked && dict.Active; bool flag2 = (dict.Active = IsPluginLoaded(dict.TargetGuid)); dict.ActiveChecked = true; if (flag2 && !flag) { ConfigLocalizer.RequestFor(dict); VerifyFingerprint(dict); ClearCaches(); } if (flag2 && !string.IsNullOrEmpty(dict.DisplayName)) { PluginNameOverrides[dict.TargetGuid] = dict.DisplayName; } if (!flag2) { hasInactive = true; dict.MismatchNote = null; if (!dict.NotifiedInactive) { dict.NotifiedInactive = true; ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)(Path.GetFileName(dict.FilePath) + ":未检测到插件 " + dict.TargetGuid + ",词典停用。")); } if (flag) { ClearCaches(); } } } else if (dict.NotifiedInactive) { dict.NotifiedInactive = false; ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogInfo((object)(Path.GetFileName(dict.FilePath) + ":检测到插件 " + dict.TargetGuid + ",词典已启用。")); } } } _hasInactive = hasInactive; _nextActivationCheck = Time.realtimeSinceStartup + 5f; } internal static void Tick() { if (_hasInactive && !(Time.realtimeSinceStartup < _nextActivationCheck)) { RefreshActiveState(); } } private static void RefreshActiveStateIfStale() { if (_hasInactive && !(Time.realtimeSinceStartup < _nextActivationCheck)) { RefreshActiveState(); } } internal static bool IsPluginLoaded(string guid) { try { return Chainloader.PluginInfos.ContainsKey(guid); } catch (Exception) { return false; } } internal static bool IsEnglishGame() { RefreshLanguageIfNeeded(); return string.Equals(_cachedLang, "English", StringComparison.OrdinalIgnoreCase); } internal static string CurrentLanguage() { RefreshLanguageIfNeeded(); return _cachedLang; } private static void RefreshLanguageIfNeeded() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_cachedLang != null && realtimeSinceStartup < _nextLangCheck) { return; } _nextLangCheck = realtimeSinceStartup + 2f; try { Localization instance = Localization.instance; if (instance == null) { _cachedLang = ""; } else { _cachedLang = instance.GetSelectedLanguage() ?? ""; } } catch (Exception) { _cachedLang = ""; } } internal static bool ShouldTranslate() { if ((Object)(object)ValheimCNPlugin.Instance == (Object)null) { return false; } if (ValheimCNPlugin.CfgEnabled == null || !ValheimCNPlugin.CfgEnabled.Value) { return false; } return !IsEnglishGame(); } internal static string Translate(string text, Component host) { if (string.IsNullOrEmpty(text)) { return text; } if (!ShouldTranslate()) { return text; } RefreshActiveStateIfStale(); bool flag = (Object)(object)host != (Object)null && (Object)(object)host.gameObject != (Object)null && host.gameObject.activeInHierarchy; for (int i = 0; i < Dicts.Count; i++) { ModDictionary modDictionary = Dicts[i]; if (!modDictionary.Active) { continue; } string text2 = null; if (modDictionary.OwnsObject(host)) { string text3 = modDictionary.Translate(text); if (text3 != null) { if (text3 == text) { if (!flag) { QueuePending(host); } } else if (!flag) { QueuePending(host); } else { text2 = text3; } } } else if (modDictionary.HasHierarchyRoot && modDictionary.WouldTranslate(text)) { QueuePending(host); } if (text2 == null && (modDictionary.GlobalRegexes.Count > 0 || modDictionary.GlobalExact.Count > 0)) { text2 = modDictionary.TranslateGlobal(text); } if (text2 == null) { continue; } if (ValheimCNPlugin.CfgVerbose != null && ValheimCNPlugin.CfgVerbose.Value) { string text4 = (((Object)(object)host != (Object)null && (Object)(object)host.gameObject != (Object)null) ? ((Object)host.gameObject).name : "(global)"); ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)("[ValheimCN] " + text4 + ": \"" + text + "\" -> \"" + text2 + "\"")); } } return text2; } if (GameResourceBridge.TryBridge(text, out var cn)) { return cn; } return text; } internal static string TranslateGlobalOnly(string text) { if (string.IsNullOrEmpty(text)) { return text; } if (!ShouldTranslate()) { return text; } if (GuiCache.TryGetValue(text, out var value)) { return value; } RefreshActiveStateIfStale(); string text2 = text; for (int i = 0; i < Dicts.Count; i++) { ModDictionary modDictionary = Dicts[i]; if (!modDictionary.Active) { continue; } string text3 = modDictionary.TranslateGlobal(text); if (text3 == null) { continue; } if (ValheimCNPlugin.CfgVerbose != null && ValheimCNPlugin.CfgVerbose.Value) { ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)("[ValheimCN] (imgui): \"" + text + "\" -> \"" + text3 + "\"")); } } text2 = text3; break; } CachePut(GuiCache, text, text2); return text2; } private static void CachePut(Dictionary<string, string> cache, string key, string value) { if (cache.Count >= 4096) { cache.Clear(); } cache[key] = value; } private static void ClearCaches() { GuiCache.Clear(); CfgCache.Clear(); } private static void QueuePending(Component host) { if (!((Object)(object)host == (Object)null) && Pending.Count < 2048) { Pending.Add(host); } } internal static void DrainPending() { if (Pending.Count == 0 || !ShouldTranslate()) { return; } int count = Pending.Count; if (_pendingBuffer == null || _pendingBuffer.Length < count) { _pendingBuffer = (Component[])(object)new Component[count]; } Pending.CopyTo(_pendingBuffer); for (int i = 0; i < count; i++) { Component val = _pendingBuffer[i]; _pendingBuffer[i] = null; if ((Object)(object)val == (Object)null) { Pending.Remove(val); continue; } GameObject gameObject = val.gameObject; if ((Object)(object)gameObject == (Object)null) { Pending.Remove(val); } else if (gameObject.activeInHierarchy) { if (!OwnedByActiveDict(val)) { Pending.Remove(val); continue; } ApplyCurrentText(val); Pending.Remove(val); } } } private static bool OwnedByActiveDict(Component host) { for (int i = 0; i < Dicts.Count; i++) { ModDictionary modDictionary = Dicts[i]; if (modDictionary.Active && modDictionary.OwnsObject(host)) { return true; } } return false; } private static void ApplyCurrentText(Component host) { try { Text val = (Text)(object)((host is Text) ? host : null); if ((Object)(object)val != (Object)null) { string text = val.text; val.text = text; return; } TMP_Text val2 = (TMP_Text)(object)((host is TMP_Text) ? host : null); if ((Object)(object)val2 != (Object)null) { string text2 = val2.text; val2.text = text2; } } catch (Exception) { } } internal static ModDictionary Get(string targetGuid) { for (int i = 0; i < Dicts.Count; i++) { if (string.Equals(Dicts[i].TargetGuid, targetGuid, StringComparison.OrdinalIgnoreCase)) { return Dicts[i]; } } return null; } internal static string ConfigName(string guid, string key) { return CfgCached("N", guid, key, (ModDictionary d) => d.TranslateConfigName(key)); } internal static string ConfigDescription(string guid, string desc) { return CfgCached("D", guid, desc, (ModDictionary d) => d.TranslateConfigDescription(desc)); } internal static string ConfigSection(string guid, string section) { return CfgCached("S", guid, section, (ModDictionary d) => d.TranslateConfigSection(section)); } internal static string ConfigValue(string guid, string value) { return CfgCached("V", guid, value, (ModDictionary d) => d.TranslateConfigValue(value)); } internal static string BilingualizeDescription(string guid, string cnDesc) { if (string.IsNullOrEmpty(cnDesc)) { return cnDesc; } if (!ConfigUiEnabled) { return cnDesc; } string key = "B\n" + guid + "\n" + cnDesc; if (CfgCache.TryGetValue(key, out var value)) { return value; } ModDictionary modDictionary = ActiveDict(guid); string text = ((modDictionary == null) ? cnDesc : modDictionary.BilingualizeValueNames(cnDesc)); CachePut(CfgCache, key, text); return text; } private static string CfgCached(string channel, string guid, string input, Func<ModDictionary, string> lookup) { if (string.IsNullOrEmpty(input)) { return null; } if (!ConfigUiEnabled) { return null; } string key = channel + "\n" + guid + "\n" + input; if (CfgCache.TryGetValue(key, out var value)) { if (value.Length != 0) { return value; } return null; } ModDictionary modDictionary = ActiveDict(guid); string text = ((modDictionary == null) ? null : lookup(modDictionary)); CachePut(CfgCache, key, text ?? ""); return text; } private static ModDictionary ActiveDict(string guid) { if (string.IsNullOrEmpty(guid)) { return null; } ModDictionary modDictionary = Get(guid); if (modDictionary == null || !modDictionary.Active) { return null; } return modDictionary; } internal static void Reload() { LoadAll(); RefreshActiveState(); ClearCaches(); } } internal sealed class RegexRule { public Regex Re; public string Replacement; public string Template; public bool Chain; public ModDictionary Owner; private Regex _templateRegex; private int[] _slotIndexes; private string _literalProbe; private static readonly Regex Placeholder = new Regex("{([0-9])}", RegexOptions.Compiled); private static readonly Regex NonAscii = new Regex("[^\\x00-\\x7F]", RegexOptions.Compiled); private string BuildLiteralProbe() { string text = (string.IsNullOrEmpty(Replacement) ? Template : Replacement); if (string.IsNullOrEmpty(text)) { return null; } string text2 = Placeholder.Replace(text, ""); if (string.IsNullOrEmpty(text2)) { return null; } Match match = NonAscii.Match(text2); if (!match.Success) { return null; } int index = match.Index; int i; for (i = index; i < text2.Length && (text2[i] < '0' || text2[i] > '9'); i++) { } string text3 = text2.Substring(index, i - index).Trim(); while (text3.Length > 0 && " :,.".IndexOf(text3[0]) >= 0) { text3 = text3.Substring(1); } while (text3.Length > 0 && " :,.".IndexOf(text3[text3.Length - 1]) >= 0) { text3 = text3.Substring(0, text3.Length - 1); } if (text3.Length < 2) { return null; } return text3; } public bool ApplyTemplate(string text) { if (!string.IsNullOrEmpty(_literalProbe) && text.IndexOf(_literalProbe, StringComparison.Ordinal) >= 0) { return false; } if (_templateRegex == null) { return false; } Match match = _templateRegex.Match(text); if (!match.Success) { return false; } string replacement = Placeholder.Replace(Replacement ?? "", delegate(Match p) { int num = p.Groups[1].Value[0] - 48; int num2 = -1; for (int i = 0; i < _slotIndexes.Length; i++) { if (_slotIndexes[i] == num) { num2 = i; break; } } return (num2 >= 0) ? ("$" + (num2 + 1)) : ""; }); string value = match.Result(replacement); Owner.TemplateResults[text] = value; return true; } public void CompileTemplate() { if (string.IsNullOrEmpty(Template)) { return; } MatchCollection matchCollection = Placeholder.Matches(Template); List<int> list = new List<int>(); foreach (Match item in matchCollection) { list.Add(item.Groups[1].Value[0] - 48); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('^'); int num = 0; int num2 = 0; foreach (Match item2 in matchCollection) { stringBuilder.Append(EscapeLiteral(Template.Substring(num, item2.Index - num))); _ = item2.Groups[1].Value[0]; bool flag = item2.Index + item2.Length < Template.Length; if (IsNumericSlot(Template, item2)) { stringBuilder.Append("(?<![\\d.,])([\\d.,]+)"); } else if (flag) { stringBuilder.Append("([^\\n]*?)"); } else { stringBuilder.Append("([^\\n]*)"); } num = item2.Index + item2.Length; num2++; } stringBuilder.Append(EscapeLiteral(Template.Substring(num))); stringBuilder.Append('$'); _templateRegex = new Regex(stringBuilder.ToString(), RegexOptions.Singleline); _slotIndexes = list.ToArray(); _literalProbe = BuildLiteralProbe(); } private static bool IsNumericSlot(string template, Match slot) { int num = slot.Index + slot.Length; if (num < template.Length) { char c = template[num]; if (c >= '0' && c <= '9') { return true; } } return false; } private static string EscapeLiteral(string s) { if (s.Length != 0) { return Regex.Escape(s); } return ""; } } internal sealed class ModDictionary { public string FilePath; public string TargetGuid; public string TargetName; public string TargetVersion; public string DisplayName; public string ExpectedMd5; public long ExpectedSize; public readonly HashSet<string> Objects = new HashSet<string>(StringComparer.Ordinal); public readonly List<string> ObjectPrefixes = new List<string>(); public readonly List<string> PanelRoots = new List<string>(); public readonly List<string> PanelRootPrefixes = new List<string>(); public readonly List<RegexRule> GlobalRegexes = new List<RegexRule>(); public readonly Dictionary<string, string> GlobalExact = new Dictionary<string, string>(StringComparer.Ordinal); public readonly Dictionary<string, string> Exact = new Dictionary<string, string>(StringComparer.Ordinal); public readonly List<RegexRule> Regexes = new List<RegexRule>(); public readonly Dictionary<string, string> ConfigDescriptions = new Dictionary<string, string>(StringComparer.Ordinal); public readonly Dictionary<string, string> ConfigNames = new Dictionary<string, string>(StringComparer.Ordinal); public readonly Dictionary<string, string> ConfigSections = new Dictionary<string, string>(StringComparer.Ordinal); public readonly Dictionary<string, string> ConfigValues = new Dictionary<string, string>(StringComparer.Ordinal); public readonly List<string> LoadingTips = new List<string>(); public readonly Dictionary<string, string> TemplateResults = new Dictionary<string, string>(StringComparer.Ordinal); public bool Active = true; public bool ActiveChecked; public bool NotifiedInactive; public string MismatchNote; private static readonly Regex ColorTag = new Regex("<color=(#[0-9A-Fa-f]{6,8})>(.*?)</color>", RegexOptions.Compiled); private static readonly Regex LetterProbe = new Regex("[A-Za-z]", RegexOptions.Compiled); public bool HasHierarchyRoot { get { if (PanelRoots.Count <= 0) { return PanelRootPrefixes.Count > 0; } return true; } } public bool HasConfigText { get { if (ConfigDescriptions.Count <= 0 && ConfigNames.Count <= 0 && ConfigSections.Count <= 0) { return ConfigValues.Count > 0; } return true; } } public bool OwnsObject(Component host) { if ((Object)(object)host == (Object)null || (Object)(object)host.gameObject == (Object)null) { return false; } if (OwnsByName(((Object)host.gameObject).name)) { return true; } if (PanelRoots.Count == 0 && PanelRootPrefixes.Count == 0) { return false; } Transform parent = host.transform.parent; while ((Object)(object)parent != (Object)null) { if (OwnsRoot(((Object)((Component)parent).gameObject).name)) { return true; } parent = parent.parent; } return false; } private bool OwnsByName(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (Objects.Contains(name)) { return true; } for (int i = 0; i < ObjectPrefixes.Count; i++) { if (name.StartsWith(ObjectPrefixes[i], StringComparison.Ordinal)) { return true; } } return false; } private bool OwnsRoot(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (PanelRoots.Contains(name)) { return true; } for (int i = 0; i < PanelRootPrefixes.Count; i++) { if (name.StartsWith(PanelRootPrefixes[i], StringComparison.Ordinal)) { return true; } } return false; } public string Translate(string text) { if (string.IsNullOrEmpty(text)) { return null; } if (!LetterProbe.IsMatch(text)) { return null; } if (text.IndexOf('\n') >= 0) { if (Exact.TryGetValue(text, out var value)) { return value; } string[] array = text.Split(new char[1] { '\n' }); bool flag = false; for (int i = 0; i < array.Length; i++) { string text2 = TranslateLine(array[i]); if ((object)text2 != array[i]) { array[i] = text2; flag = true; } } if (flag) { return string.Join("\n", array); } return TranslateFlat(text); } return TranslateRichLine(text); } private string TranslateLine(string line) { if (string.IsNullOrEmpty(line)) { return line; } return TranslateRichLine(line) ?? line; } private string TranslateRichLine(string line) { Match match = ColorTag.Match(line); if (match.Success) { Group obj = match.Groups[2]; string text = TranslateFlat(obj.Value); if (text != null) { return line.Substring(0, obj.Index) + text + line.Substring(obj.Index + obj.Length); } } return TranslateFlat(line); } private string TranslateFlat(string text) { if (Exact.TryGetValue(text, out var value)) { return value; } TemplateResults.Clear(); return RunRegexes(text, Regexes); } public string TranslateGlobal(string text) { if (string.IsNullOrEmpty(text)) { return null; } if (!LetterProbe.IsMatch(text)) { return null; } if (text.IndexOf('\n') >= 0) { if (TryGlobalExact(text, out var result)) { return result; } string[] array = text.Split(new char[1] { '\n' }); bool flag = false; for (int i = 0; i < array.Length; i++) { string text2 = TranslateGlobalLine(array[i]); if (text2 != null) { array[i] = text2; flag = true; } } if (!flag) { return null; } return string.Join("\n", array); } return TranslateGlobalLine(text); } private string TranslateGlobalLine(string line) { if (string.IsNullOrEmpty(line)) { return null; } string text = TranslateGlobalFlat(line); if (text != null) { return text; } Match match = ColorTag.Match(line); if (match.Success) { Group obj = match.Groups[2]; string text2 = TranslateGlobalFlat(obj.Value); if (text2 != null) { return line.Substring(0, obj.Index) + text2 + line.Substring(obj.Index + obj.Length); } } return null; } private string TranslateGlobalFlat(string text) { if (string.IsNullOrEmpty(text) || !LetterProbe.IsMatch(text)) { return null; } if (TryGlobalExact(text, out var result)) { return result; } if (GlobalRegexes.Count == 0) { return null; } TemplateResults.Clear(); string text2 = RunRegexes(text, GlobalRegexes); if (text2 != null) { return text2; } if (SplitDecoration(text, out var lead, out var tail, out var core)) { TemplateResults.Clear(); text2 = RunRegexes(core, GlobalRegexes); if (text2 != null) { return lead + text2 + tail; } } return null; } private bool TryGlobalExact(string text, out string result) { result = null; if (GlobalExact.TryGetValue(text, out result)) { return true; } if (!SplitDecoration(text, out var lead, out var tail, out var core)) { return false; } if (!GlobalExact.TryGetValue(core, out result)) { return false; } result = lead + result + tail; return true; } private static bool SplitDecoration(string text, out string lead, out string tail, out string core) { lead = null; tail = null; core = null; int i = 0; int num; for (num = text.Length; i < num && char.IsWhiteSpace(text[i]); i++) { } while (num > i && char.IsWhiteSpace(text[num - 1])) { num--; } if (i + 1 < num && !char.IsLetterOrDigit(text[i]) && !char.IsWhiteSpace(text[i])) { int j; for (j = i + 1; j < num && char.IsWhiteSpace(text[j]); j++) { } if (j < num && j > i + 1) { i = j; } } if (i == 0 && num == text.Length) { return false; } core = text.Substring(i, num - i); if (core.Length == 0) { return false; } lead = text.Substring(0, i); tail = text.Substring(num); return true; } public bool WouldTranslate(string text) { return Translate(text) != null; } private string RunRegexes(string text, List<RegexRule> rules) { if (rules == null || rules.Count == 0) { return null; } string text2 = text; bool flag = false; for (int i = 0; i < rules.Count; i++) { RegexRule regexRule = rules[i]; string value2; if (string.IsNullOrEmpty(regexRule.Template)) { if (regexRule.Re == null) { continue; } if (regexRule.Chain) { if (regexRule.Re.IsMatch(text2)) { text2 = regexRule.Re.Replace(text2, regexRule.Replacement); flag = true; } } else if (regexRule.Re.IsMatch(text2)) { return regexRule.Re.Replace(text2, regexRule.Replacement); } } else if (regexRule.Chain) { if (regexRule.ApplyTemplate(text2) && TemplateResults.TryGetValue(text2, out var value)) { text2 = value; flag = true; } } else if (regexRule.ApplyTemplate(text2) && TemplateResults.TryGetValue(text2, out value2)) { return value2; } } if (!flag) { return null; } return text2; } public string TranslateConfigDescription(string desc) { if (string.IsNullOrEmpty(desc)) { return null; } if (!ConfigDescriptions.TryGetValue(desc, out var value)) { return null; } return value; } public string TranslateConfigName(string key) { if (string.IsNullOrEmpty(key)) { return null; } if (!ConfigNames.TryGetValue(key, out var value)) { return null; } return value; } public string TranslateConfigSection(string section) { if (string.IsNullOrEmpty(section)) { return null; } if (!ConfigSections.TryGetValue(section, out var value)) { return null; } return value; } public string TranslateConfigValue(string value) { if (string.IsNullOrEmpty(value)) { return null; } if (ConfigValues.TryGetValue(value, out var value2)) { return value2; } string text = value.Replace(" ", ""); if (!string.Equals(text, value, StringComparison.Ordinal) && ConfigValues.TryGetValue(text, out value2)) { return value2; } return null; } public string BilingualizeValueNames(string cnDesc) { if (string.IsNullOrEmpty(cnDesc) || ConfigValues.Count == 0) { return cnDesc; } string[] array = cnDesc.Split(new char[1] { '\n' }); bool flag = false; for (int i = 0; i < array.Length; i++) { string text = BilingualizeLine(array[i]); if (text != array[i]) { array[i] = text; flag = true; } } if (!flag) { return cnDesc; } return string.Join("\n", array); } private string BilingualizeLine(string line) { int num = FirstColon(line); int num2 = line.IndexOf('='); int num3 = ((num > 0) ? num : ((num2 > 0) ? num2 : (-1))); if (num3 > 0) { string text = line.Substring(0, num3).Trim(); if (text.Length > 0 && IsAsciiName(text)) { string text2 = TranslateConfigValue(text); if (text2 != null) { return text2 + "(" + text + ")" + line.Substring(num3); } } } if (num > 0) { int i; for (i = num + 1; i < line.Length && line[i] == ' '; i++) { } int j; for (j = i; j < line.Length && IsNameChar(line[j]); j++) { } string text3 = line.Substring(i, j - i).Trim(); if (text3.Length > 0 && IsAsciiName(text3)) { string text4 = TranslateConfigValue(text3); if (text4 != null) { return line.Substring(0, i) + text4 + "(" + text3 + ")" + line.Substring(j); } } } return line; } private static bool IsNameChar(char c) { if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_') { return c == ' '; } return true; } private static int FirstColon(string s) { int num = s.IndexOf(':'); int num2 = s.IndexOf(':'); if (num < 0) { return num2; } if (num2 < 0) { return num; } if (num >= num2) { return num2; } return num; } private static bool IsAsciiName(string s) { bool result = false; foreach (char c in s) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { result = true; } else if ((c < '0' || c > '9') && c != '_' && c != ' ') { return false; } } return result; } public string GetLoadingTip(Random rng) { if (LoadingTips.Count == 0) { return null; } int index = rng?.Next(LoadingTips.Count) ?? new Random().Next(LoadingTips.Count); return LoadingTips[index]; } } internal static class ConfigManagerPatches { private static bool _applied; private static PropertyInfo _pluginInfoGetter; private static float _nextInstallTry; private static int _installTries; internal static bool SuppressImgui; private static string _currentGuid; internal static string CurrentGuid => _currentGuid; internal static bool Applied => _applied; internal static void TryLateInstall() { if (!_applied && _installTries < 30) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _nextInstallTry)) { _nextInstallTry = realtimeSinceStartup + 2f; _installTries++; Install(ValheimCNPlugin.HarmonyInstance); } } } internal static void Install(Harmony harmony) { if (_applied || harmony == null) { return; } try { Type type = AccessTools.TypeByName("ConfigurationManager.SettingEntryBase"); if (!(type == null)) { Type type2 = AccessTools.TypeByName("ConfigurationManager.SettingFieldDrawer"); Type type3 = AccessTools.TypeByName("ConfigurationManager.ConfigurationManager"); _pluginInfoGetter = type.GetProperty("PluginInfo", BindingFlags.Instance | BindingFlags.Public); bool num = Patch(harmony, AccessTools.Method(type, "get_DispName", (Type[])null, (Type[])null), null, "DispNamePostfix"); Patch(harmony, AccessTools.Method(type, "get_Category", (Type[])null, (Type[])null), null, "CategoryPostfix"); Patch(harmony, AccessTools.Method(type, "get_Description", (Type[])null, (Type[])null), null, "DescriptionPostfix"); if (type2 != null) { Patch(harmony, AccessTools.Method(type2, "DrawComboboxField", (Type[])null, (Type[])null), "ContextPrefix", null); Patch(harmony, AccessTools.Method(type2, "DrawComboboxField", (Type[])null, (Type[])null), "ComboboxSuppressPrefix", null, "ComboboxSuppressFinalizer"); Patch(harmony, AccessTools.Method(type2, "ObjectToGuiContent", (Type[])null, (Type[])null), null, "ValuePostfix"); } if (type3 != null) { Patch(harmony, AccessTools.Method(type3, "DrawSingleSetting", (Type[])null, (Type[])null), "ContextPrefix", null); } if (num) { _applied = true; ValheimCNPlugin.Log("已挂载配置界面汉化补丁(条目名 / 分组标题 / 悬停提示;下拉框值不翻)。"); } } } catch (Exception ex) { ValheimCNPlugin.Warn("配置界面补丁挂载失败:" + ex.Message); } } private static bool Patch(Harmony harmony, MethodBase target, string prefix, string postfix, string finalizer = null) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (target == null) { return false; } try { MethodInfo methodInfo = ((prefix == null) ? null : AccessTools.Method(typeof(ConfigManagerPatches), prefix, (Type[])null, (Type[])null)); MethodInfo methodInfo2 = ((postfix == null) ? null : AccessTools.Method(typeof(ConfigManagerPatches), postfix, (Type[])null, (Type[])null)); MethodInfo methodInfo3 = ((finalizer == null) ? null : AccessTools.Method(typeof(ConfigManagerPatches), finalizer, (Type[])null, (Type[])null)); if (methodInfo == null && methodInfo2 == null && methodInfo3 == null) { return false; } harmony.Patch(target, (methodInfo == null) ? ((HarmonyMethod)null) : new HarmonyMethod(methodInfo), (methodInfo2 == null) ? ((HarmonyMethod)null) : new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (methodInfo3 == null) ? ((HarmonyMethod)null) : new HarmonyMethod(methodInfo3), (HarmonyMethod)null); return true; } catch (Exception ex) { ValheimCNPlugin.Warn("配置界面补丁跳过 " + target.Name + ":" + ex.Message); return false; } } private static void DispNamePostfix(object __instance, ref string __result) { if (!string.IsNullOrEmpty(__result)) { bool flag = __result[0] == '!'; string key = (flag ? __result.Substring(1) : __result); string text = LocEngine.ConfigName(GuidOf(__instance), key); if (!string.IsNullOrEmpty(text)) { __result = (flag ? ("!" + text) : text); } } } private static void CategoryPostfix(object __instance, ref string __result) { if (!string.IsNullOrEmpty(__result)) { string text = LocEngine.ConfigSection(GuidOf(__instance), __result); if (!string.IsNullOrEmpty(text)) { __result = text; } } } private static void DescriptionPostfix(object __instance, ref string __result) { if (!string.IsNullOrEmpty(__result)) { string guid = GuidOf(__instance); string text = LocEngine.ConfigDescription(guid, __result); if (!string.IsNullOrEmpty(text)) { __result = text; } __result = LocEngine.BilingualizeDescription(guid, __result); } } private static void ContextPrefix(object __0) { _currentGuid = ((__0 == null) ? null : GuidOf(__0)); } private static void ComboboxSuppressPrefix() { SuppressImgui = true; } private static void ComboboxSuppressFinalizer() { SuppressImgui = false; _currentGuid = null; } private static void ValuePostfix(object __0, ref GUIContent __result) { if (__result == null || string.IsNullOrEmpty(_currentGuid)) { return; } string text = __result.text; if (!string.IsNullOrEmpty(text)) { string text2 = LocEngine.ConfigValue(_currentGuid, text); if (text2 == null && __0 is Enum) { text2 = LocEngine.ConfigValue(_currentGuid, __0.ToString()); } if (!string.IsNullOrEmpty(text2)) { __result.text = text2; } } } private static string GuidOf(object entry) { if (entry == null || _pluginInfoGetter == null) { return null; } try { object? value = _pluginInfoGetter.GetValue(entry, null); BepInPlugin val = (BepInPlugin)((value is BepInPlugin) ? value : null); return (val == null) ? null : val.GUID; } catch (Exception) { return null; } } } internal static class ConfigSavePatch { internal static void Apply(Harmony harmony) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown if (harmony == null) { return; } try { MethodInfo methodInfo = AccessTools.Method(typeof(ConfigFile), "Save", (Type[])null, (Type[])null); if (methodInfo == null) { ValheimCNPlugin.Warn("未找到 ConfigFile.Save,cfg 注释保存后置补丁未挂载。"); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(ConfigSavePatch), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ValheimCNPlugin.Log("已拦截 ConfigFile.Save(保存后同步汉化 cfg 注释)。"); } catch (Exception ex) { ValheimCNPlugin.Warn("cfg 补丁挂载失败:" + ex.Message); } } private static void Postfix(ConfigFile __instance) { try { ConfigLocalizer.OnConfigSaved(__instance); } catch (Exception) { } } } internal static class GuiLabelPatch { internal static int Apply(Harmony harmony) { int num = 0; num += Patch(harmony, typeof(GUI), "Label", new Type[3] { typeof(Rect), typeof(string), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUI), "Label", new Type[2] { typeof(Rect), typeof(string) }); num += Patch(harmony, typeof(GUI), "Button", new Type[3] { typeof(Rect), typeof(string), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUI), "Button", new Type[2] { typeof(Rect), typeof(string) }); num += Patch(harmony, typeof(GUI), "Toggle", new Type[4] { typeof(Rect), typeof(bool), typeof(string), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUI), "Toggle", new Type[3] { typeof(Rect), typeof(bool), typeof(string) }); num += Patch(harmony, typeof(GUI), "Box", new Type[3] { typeof(Rect), typeof(string), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUI), "Box", new Type[2] { typeof(Rect), typeof(string) }); num += Patch(harmony, typeof(GUI), "Window", new Type[4] { typeof(int), typeof(Rect), typeof(WindowFunction), typeof(string) }); num += Patch(harmony, typeof(GUI), "Window", new Type[5] { typeof(int), typeof(Rect), typeof(WindowFunction), typeof(string), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUI), "Window", new Type[4] { typeof(int), typeof(Rect), typeof(Func<int>), typeof(string) }); num += Patch(harmony, typeof(GUI), "Label", new Type[2] { typeof(Rect), typeof(GUIContent) }); num += Patch(harmony, typeof(GUI), "Label", new Type[3] { typeof(Rect), typeof(GUIContent), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUI), "Button", new Type[2] { typeof(Rect), typeof(GUIContent) }); num += Patch(harmony, typeof(GUI), "Button", new Type[3] { typeof(Rect), typeof(GUIContent), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUI), "Toggle", new Type[3] { typeof(Rect), typeof(bool), typeof(GUIContent) }); num += Patch(harmony, typeof(GUI), "Toggle", new Type[4] { typeof(Rect), typeof(bool), typeof(GUIContent), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUI), "Box", new Type[2] { typeof(Rect), typeof(GUIContent) }); num += Patch(harmony, typeof(GUI), "Box", new Type[3] { typeof(Rect), typeof(GUIContent), typeof(GUIStyle) }); num += Patch(harmony, typeof(GUILayout), "Label", new Type[2] { typeof(string), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Label", new Type[3] { typeof(string), typeof(GUIStyle), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Box", new Type[2] { typeof(string), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Box", new Type[3] { typeof(string), typeof(GUIStyle), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Button", new Type[2] { typeof(string), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Button", new Type[3] { typeof(string), typeof(GUIStyle), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Toggle", new Type[3] { typeof(bool), typeof(string), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Toggle", new Type[4] { typeof(bool), typeof(string), typeof(GUIStyle), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Window", new Type[5] { typeof(int), typeof(Rect), typeof(WindowFunction), typeof(string), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Window", new Type[6] { typeof(int), typeof(Rect), typeof(WindowFunction), typeof(string), typeof(GUIStyle), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Label", new Type[2] { typeof(GUIContent), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Label", new Type[3] { typeof(GUIContent), typeof(GUIStyle), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Button", new Type[2] { typeof(GUIContent), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Button", new Type[3] { typeof(GUIContent), typeof(GUIStyle), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Box", new Type[2] { typeof(GUIContent), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Box", new Type[3] { typeof(GUIContent), typeof(GUIStyle), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Toggle", new Type[3] { typeof(bool), typeof(GUIContent), typeof(GUILayoutOption[]) }); num += Patch(harmony, typeof(GUILayout), "Toggle", new Type[4] { typeof(bool), typeof(GUIContent), typeof(GUIStyle), typeof(GUILayoutOption[]) }); if (num > 0) { ValheimCNPlugin.Log($"已拦截 {num} 个 IMGUI 文本入口(GUI + GUILayout,string + GUIContent)。"); } return num; } private static int Patch(Harmony harmony, Type owner, string name, Type[] signature) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(owner, name, signature, (Type[])null); if (methodInfo == null) { return 0; } bool flag = false; ParameterInfo[] parameters = methodInfo.GetParameters(); for (int i = 0; i < parameters.Length; i++) { if (parameters[i].ParameterType == typeof(GUIContent)) { flag = true; break; } } HarmonyMethod val = (flag ? new HarmonyMethod(typeof(GuiLabelPatch), "PrefixContent", (Type[])null) : new HarmonyMethod(typeof(GuiLabelPatch), "Prefix", (Type[])null)); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return 1; } catch (Exception ex) { ValheimCNPlugin.Warn("拦截 " + owner.Name + "." + name + " 失败:" + ex.Message); return 0; } } private static void Prefix(ref string text) { if (string.IsNullOrEmpty(text)) { return; } if (ConfigManagerPatches.SuppressImgui) { text = LocEngine.ConfigValue(ConfigManagerPatches.CurrentGuid, text) ?? text; return; } try { text = LocEngine.TranslateGlobalOnly(text); } catch (Exception) { } } private static void PrefixContent(ref GUIContent content) { if (content == null) { return; } if (ConfigManagerPatches.SuppressImgui) { if (!string.IsNullOrEmpty(content.text)) { content.text = LocEngine.ConfigValue(ConfigManagerPatches.CurrentGuid, content.text) ?? content.text; } return; } try { if (!string.IsNullOrEmpty(content.text)) { content.text = LocEngine.TranslateGlobalOnly(content.text); } if (!string.IsNullOrEmpty(content.tooltip)) { content.tooltip = LocEngine.TranslateGlobalOnly(content.tooltip); } } catch (Exception) { } } } [HarmonyPatch(typeof(FejdStartup), "Awake")] internal static class MainMenuButtonPatch { private const string ButtonName = "ValheimCN_MenuButton"; private const float AboveGap = 34f; private static void Postfix(FejdStartup __instance, GameObject ___m_menuList) { //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown try { if (ValheimCNPlugin.CfgShowPanelButton == null || !ValheimCNPlugin.CfgShowPanelButton.Value || (Object)(object)___m_menuList == (Object)null) { return; } Button[] componentsInChildren = ___m_menuList.GetComponentsInChildren<Button>(); if (componentsInChildren.Length == 0) { return; } Transform transform = ___m_menuList.transform; if ((Object)(object)transform.Find("ValheimCN_MenuButton") != (Object)null) { return; } GameObject val = Object.Instantiate<GameObject>(((Component)componentsInChildren[0]).gameObject, transform); ((Object)val).name = "ValheimCN_MenuButton"; val.transform.SetAsLastSibling(); PlaceAboveFirst(val, componentsInChildren); SetButtonText(val, "英灵神殿MOD汉化"); Button btn = val.GetComponent<Button>(); if (!((Object)(object)btn == (Object)null)) { ClearPersistentCalls(btn); ((UnityEventBase)btn.onClick).RemoveAllListeners(); ((UnityEvent)btn.onClick).AddListener((UnityAction)delegate { Canvas componentInParent = ((Component)btn).GetComponentInParent<Canvas>(); CNPanel.OpenOn(((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).gameObject : ((Component)btn).gameObject); }); ValheimCNPlugin.Log("已在主菜单「开始游戏」上方插入「英灵汉化」按钮。"); } } catch (Exception ex) { ValheimCNPlugin.Warn("插入主菜单按钮失败:" + ex.Message); } } private static void PlaceAboveFirst(GameObject clone, Button[] buttons) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) RectTransform val = null; for (int i = 0; i < buttons.Length; i++) { RectTransform val2 = (RectTransform)((Component)buttons[i]).transform; if ((Object)(object)val == (Object)null || val2.anchoredPosition.y > val.anchoredPosition.y) { val = val2; } } if (!((Object)(object)val == (Object)null)) { ((RectTransform)clone.transform).anchoredPosition = val.anchoredPosition + new Vector2(0f, val.sizeDelta.y + 34f); } } private static void SetButtonText(GameObject buttonObject, string text) { Text componentInChildren = buttonObject.GetComponentInChildren<Text>(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = text; return; } TMP_Text componentInChildren2 = buttonObject.GetComponentInChildren<TMP_Text>(); if ((Object)(object)componentInChildren2 != (Object)null) { componentInChildren2.text = text; } } private static void ClearPersistentCalls(Button button) { try { object obj = typeof(UnityEventBase).GetField("m_PersistentCalls", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(button.onClick); obj?.GetType().GetMethod("Clear")?.Invoke(obj, null); } catch (Exception) { } } } [HarmonyPatch(typeof(FejdStartup), "UpdateKeyboard")] internal static class MainMenuInputBlockKeyboard { private static bool Prefix() { return !CNPanel.IsOpen; } } [HarmonyPatch(typeof(FejdStartup), "UpdateGamepad")] internal static class MainMenuInputBlockGamepad { private static bool Prefix() { return !CNPanel.IsOpen; } } internal static class MarsarahUI { [HarmonyPatch(typeof(Hud), "Awake")] internal static class HudAwakePatch { private static void Postfix() { _currentTip = null; } } [HarmonyPatch(typeof(Hud), "UpdateBlackScreen")] internal static class HudLoadingTipPatch { private static void Postfix(Hud __instance) { try { if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.m_loadingTip == (Object)null || ValheimCNPlugin.CfgLoadingTips == null || !ValheimCNPlugin.CfgLoadingTips.Value || !LocEngine.ShouldTranslate() || !LocEngine.IsPluginLoaded("Marsarah.MarsarahUI")) { return; } ModDictionary modDictionary = LocEngine.Get("Marsarah.MarsarahUI"); if ((modDictionary != null && !modDictionary.Active) || modDictionary == null || modDictionary.LoadingTips.Count == 0) { return; } if (string.IsNullOrEmpty(_currentTip)) { lock (Rng) { _currentTip = modDictionary.GetLoadingTip(Rng); } } if (!string.IsNullOrEmpty(_currentTip)) { __instance.m_loadingTip.text = _currentTip; } } catch (Exception) { } } } internal const string Guid = "Marsarah.MarsarahUI"; private static string _currentTip; private static readonly Random Rng = new Random(); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class PluginNamePatch { internal static void Postfix(BepInPlugin __instance, ref string __result) { if ((ValheimCNPlugin.CfgEnabled == null || ValheimCNPlugin.CfgEnabled.Value) && LocEngine.PluginNameOverrides.TryGetValue(__instance.GUID, out var value) && value != null) { __result = value; } } } internal static class TextSetTextPatch { internal static int Apply(Harmony harmony) { return 0 + Patch(harmony, typeof(Text), "UnityEngine.UI.Text") + Patch(harmony, typeof(TMP_Text), "TMPro.TMP_Text"); } private static int Patch(Harmony harmony, Type owner, string display) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.PropertySetter(owner, "text"); if (methodInfo == null) { ValheimCNPlugin.Warn("未能定位 " + display + ".text 的赋值方法,该类型文本不会被汉化。"); return 0; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TextSetTextPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ValheimCNPlugin.Log("已拦截 " + display + ".text 赋值。"); return 1; } catch (Exception ex) { ValheimCNPlugin.Warn("拦截 " + display + ".text 失败:" + ex.Message); return 0; } } private static void Prefix(object __instance, ref string value) { try { if (__instance != null && !string.IsNullOrEmpty(value)) { Component host = (Component)((__instance is Component) ? __instance : null); value = LocEngine.Translate(value, host); } } catch (Exception) { } } } [BepInPlugin("Catnip.ValheimCN", "ValheimCN - 英灵汉化", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class ValheimCNPlugin : BaseUnityPlugin { public const string GUID = "Catnip.ValheimCN"; public const string NAME = "ValheimCN - 英灵汉化"; public const string VERSION = "1.0.0"; internal static ValheimCNPlugin Instance; internal static Harmony HarmonyInstance; internal static ConfigEntry<bool> CfgEnabled; internal static ConfigEntry<bool> CfgLoadingTips; internal static ConfigEntry<bool> CfgConfigDescriptions; internal static ConfigEntry<bool> CfgVerbose; internal static ConfigEntry<bool> CfgShowPanelButton; internal static ConfigEntry<KeyCode> CfgPanelHotkey; internal static ConfigEntry<bool> CfgBridgeGameResources; private bool _cfgSwitchWasOn = true; private void Awake() { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Expected O, but got Unknown Instance = this; try { CfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("00 - 总开关", "启用汉化", true, "关闭后本插件完全不介入,所有 MOD 保持英文原文。"); CfgLoadingTips = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - 汉化范围", "汉化增强加载提示", true, "MarsarahUI 的增强加载提示原本只在游戏语言为英文时生效。开启后,中文环境下也显示这些提示的中文版。"); CfgConfigDescriptions = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - 汉化范围", "汉化配置文件说明", true, "把各 MOD 的配置界面汉化:条目名、分组标题、候选项(如 Digital Clock),以及 cfg 文件里的说明文字。只改显示文字,不改键名,你已有的设置值不会丢。"); CfgBridgeGameResources = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - 汉化范围", "桥接游戏原生资源名", true, "自动把 MOD 直接复用的游戏资源 / 地点(如 AutoPOIMarker 的地图标记)的英文标识符,桥接到游戏官方中文名(prefab / 地点的本地化)。能减少手写词典量且用官方译名。仅整串精确匹配、非 English 才生效;若发现误翻可在 cfg 里关掉。"); CfgVerbose = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - 调试", "输出详细日志", false, "在 BepInEx 日志里打印每一条被替换的文本,用于排查词典缺失。会让日志文件变大。"); CfgShowPanelButton = ((BaseUnityPlugin)this).Config.Bind<bool>("03 - 汉化界面", "主菜单显示汉化按钮", true, "在游戏主菜单底部显示「英灵汉化」按钮,可以查看已汉化的 MOD、提交汉化请求、查看打赏方式。"); CfgPanelHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("03 - 汉化界面", "游戏内打开界面的热键", (KeyCode)289, "在游戏中按下该键随时打开/关闭汉化界面。设为 None 可禁用热键。"); LocEngine.Initialize(Paths.ConfigPath, ((BaseUnityPlugin)this).Logger); HarmonyInstance = new Harmony("Catnip.ValheimCN"); HarmonyInstance.PatchAll(); TextSetTextPatch.Apply(HarmonyInstance); GuiLabelPatch.Apply(HarmonyInstance); ConfigSavePatch.Apply(HarmonyInstance); CNHotkey.EnsureCreated(); ConfigManagerPatches.Install(HarmonyInstance); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} 已加载,词典 {2} 份,条目 {3} 条。", "ValheimCN - 英灵汉化", "1.0.0", LocEngine.DictCount, LocEngine.EntryCount)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("ValheimCN 初始化失败:" + ex)); } } private void Update() { try { LocEngine.Tick(); ConfigManagerPatches.TryLateInstall(); bool flag = CfgConfigDescriptions != null && CfgConfigDescriptions.Value; if (flag && !_cfgSwitchWasOn) { ConfigLocalizer.RequestAll(); } _cfgSwitchWasOn = flag; ConfigLocalizer.Drain(); LocEngine.DrainPending(); } catch (Exception) { } } internal static void Log(string msg) { ValheimCNPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)msg); } } internal static void Warn(string msg) { ValheimCNPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)msg); } } } internal sealed class CNHotkey : MonoBehaviour { private static CNHotkey _instance; internal static void EnsureCreated() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("ValheimCN_Hotkey"); Object.DontDestroyOnLoad((Object)val); _instance = val.AddComponent<CNHotkey>(); } } private void Update() { try { if (!HotkeyDown()) { return; } if (CNPanel.IsOpen) { CNPanel.Close(); return; } Transform val = FindCanvas(); if ((Object)(object)val != (Object)null) { CNPanel.Open(val); } } catch (Exception) { } } private static bool HotkeyDown() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { KeyCode val = (KeyCode)((ValheimCNPlugin.CfgPanelHotkey == null) ? 289 : ((int)ValheimCNPlugin.CfgPanelHotkey.Value)); return (int)val != 0 && Input.GetKeyDown(val); } catch (Exception) { return false; } } private static Transform FindCanvas() { try { Hud instance = Hud.instance; if ((Object)(object)instance != (Object)null) { Canvas componentInParent = ((Component)instance).GetComponentInParent<Canvas>(); if ((Object)(object)componentInParent != (Object)null) { return ((Component)componentInParent).transform; } if ((Object)(object)((Component)instance).gameObject != (Object)null) { return ((Component)instance).gameObject.transform; } } } catch (Exception) { } try { Canvas[] array = Object.FindObjectsOfType<Canvas>(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null && ((Behaviour)array[i]).isActiveAndEnabled) { return ((Component)array[i]).transform; } } } catch (Exception) { } return null; } private void OnDestroy() { if (_instance == this) { _instance = null; } } } internal sealed class CNPanel : MonoBehaviour { internal enum Tab { Localized, Inactive, Request, Donate } private const float PanelWFrac = 0.427f; private const float PanelHFrac = 0.537f; private const float PanelY = 20f; private const float TabLeft = -80f; private const float TabTop = -23f; private const float TabH = 37f; private const float TabGap = 7f; private const float TabPadX = 36f; private const float TabMinW = 104f; private const int TabFontSize = 16; private const float ContentLeft = 82f; private const float ContentRight = 30f; private const float ContentTop = 44f; private const float BottomMargin = 20f; private const float ButtonH = 36f; internal static bool Opened; private RectTransform _content; private Button[] _tabs; private Text[] _tabTexts; private Tab _current; private static CNPanel Instance; internal static bool IsOpen => Opened; internal static void Toggle(Transform canvas) { if (Opened) { Close(); } else { Open(canvas); } } internal static void Open(Transform parent) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) if (Opened || (Object)(object)parent == (Object)null) { return; } GameObject val = new GameObject("ValheimCN_Panel", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(parent, false); ((Transform)val2).localScale = Vector3.one; ((Transform)val2).localRotation = Quaternion.identity; ((Transform)val2).localPosition = Vector3.zero; UICore.Stretch(val2, 0f, 0f, 0f, 0f); ((Transform)val2).SetAsLastSibling(); float canvasW = 1920f; float canvasH = 1080f; RectTransform val3 = (RectTransform)(object)((parent is RectTransform) ? parent : null); if ((Object)(object)val3 != (Object)null) { Rect rect = val3.rect; if (((Rect)(ref rect)).width > 1f) { rect = val3.rect; if (((Rect)(ref rect)).height > 1f) { rect = val3.rect; canvasW = ((Rect)(ref rect)).width; rect = val3.rect; canvasH = ((Rect)(ref rect)).height; } } } val.AddComponent<CNPanel>().Build(canvasW, canvasH); } internal static void Close() { if ((Object)(object)Instance != (Object)null) { Object.Destroy((Object)(object)((Component)Instance).gameObject); } } private void Build(float canvasW, float canvasH) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Expected O, but got Unknown Opened = true; Instance = this; float num = canvasW * 0.427f; float num2 = canvasH * 0.537f; Image obj = UICore.Box(((Component)this).transform, "Mask", UICore.MaskColor); UICore.Stretch(UICore.Rect(((Component)obj).gameObject), 0f, 0f, 0f, 0f); ((Graphic)obj).raycastTarget = true; Image val = UICore.FramedPanel(((Component)this).transform, "Panel", UICore.PanelColor, new Color(0.22f, 0.58f, 0.6f, 1f)); RectTransform obj2 = UICore.Rect(((Component)val).gameObject); obj2.anchorMin = new Vector2(0.5f, 0.5f); obj2.anchorMax = new Vector2(0.5f, 0.5f); obj2.pivot = new Vector2(0.5f, 0.5f); obj2.anchoredPosition = new Vector2(0f, 20f); obj2.sizeDelta = new Vector2(num, num2); ((Transform)obj2).localScale = Vector3.one; ((Graphic)val).raycastTarget = true; UICore.Place(UICore.Rect(((Component)UICore.Label(((Component)val).transform, "Title", "英灵神殿汉化MOD合集", 13, UICore.AccentColor, (TextAnchor)3)).gameObject), 24f, 0f - num2 + 26f, 300f, 20f); BuildTabs(((Component)val).transform); float h = num2 - 44f - 72f; GameObject val2 = new GameObject("Content", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(((Component)val).transform, false); _content = UICore.Rect(val2); UICore.Place(_content, 82f, -44f, num - 82f - 30f, h); UICore.MakeButton(((Component)val).transform, "关闭", num - 24f - 90f, 0f - num2 + 20f + 36f, 90f, 36f, Close); ShowTab(Tab.Localized); } private void BuildTabs(Transform parent) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_012f: Unknown result type (might be due to invalid IL or missing references) string[] array = new string[4] { "已汉化 MOD", "未生效", "请求汉化", "支持打赏" }; _tabs = (Button[])(object)new Button[array.Length]; _tabTexts = (Text[])(object)new Text[array.Length]; float num = 104f; string[] array2 = array; foreach (string text in array2) { num = Mathf.Max(num, MeasureTextWidth(text, 16) + 36f); } float num2 = -23f; for (int j = 0; j < array.Length; j++) { int index = j; Image val = UICore.Box(parent, "Tab" + j, UICore.TabOffColor); UICore.Place(UICore.Rect(((Component)val).gameObject), -80f, num2, num, 37f); Button val2 = ((Component)val).gameObject.AddComponent<Button>(); ((Selectable)val2).targetGraphic = (Graphic)(object)val; ((UnityEvent)val2.onClick).AddListener((UnityAction)delegate { ShowTab((Tab)index); }); _tabs[j] = val2; _tabTexts[j] = UICore.Label(((Component)val).transform, "Text", array[j], 16, UICore.GrayText, (TextAnchor)3); UICore.Place(UICore.Rect(((Component)_tabTexts[j]).gameObject), 14f, 1f, num - 18f, 35f); num2 -= 44f; } } private static float MeasureTextWidth(string text, int fontSize) { if (string.IsNullOrEmpty(text)) { return 0f; } float num = 0f; foreach (char c in text) { num += ((c >= '⺀') ? 1f : 0.55f); } return num * (float)fontSize; } private void ShowTab(Tab tab) { //IL_0027: 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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) _current = tab; for (int i = 0; i < _tabs.Length; i++) { bool flag = tab == (Tab)i; ((Graphic)((Component)_tabs[i]).GetComponent<Image>()).color = (flag ? UICore.TabOnColor : UICore.TabOffColor); ((Graphic)_tabTexts[i]).color = (flag ? UICore.AccentColor : UICore.GrayText); } UICore.ClearChildren((Transform)(object)_content); RectTransform content = UICore.MakeScroll((Transform)(object)_content, "Scroller"); switch (tab) { case Tab.Localized: PanelTabs.BuildLocalized(content); break; case Tab.Inactive: PanelTabs.BuildInactive(content); break; case Tab.Request: PanelTabs.BuildRequest(content); break; case Tab.Donate: PanelTabs.BuildDonate(content); break; } } private void Update() { if (Input.GetKeyDown((KeyCode)27)) { Close(); } } private void OnDestroy() { Opened = false; if (Instance == this) { Instance = null; } } internal static void OpenOn(GameObject context) { if (!((Object)(object)context == (Object)null)) { Canvas val = context.GetComponentInParent<Canvas>(); if ((Object)(object)val != (Object)null && (Object)(object)val.rootCanvas != (Object)null) { val = val.rootCanvas; } Open(((Object)(object)val != (Object)null) ? ((Component)val).transform : context.transform); } } internal static void ToggleOn(GameObject context) { if (Opened) { Close(); } else { OpenOn(context); } } } internal static class PanelTabs { private const float RowH = 36f; private const float Gap = 3f; private const float RowTop = 33f; private static readonly HashSet<string> LibraryGuids = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "denikson-BepInExPack_Valheim", "BepInEx.Harmony", "BepInEx.Preloader", "com.jotunn.jotunn", "Jotunn", "YamlDotNet", "Catnip.ValheimCN" }; private const string RequestFormUrl = "https://docs.qq.com/form/page/DUHhtbW9RdXF3Z0dJ"; private static HashSet<string> _requested; private static readonly string RequestedFile = Path.Combine(LocEngine.DictDir, "requested.txt"); private const string DonateAlipayFile = "alipay.png"; private const string DonateWechatFile = "wechat.png"; private static MethodInfo _unityLoadImage; private static bool _unityLoadImageSearched; private static float W(RectTransform content) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Rect rect = content.rect; return ((Rect)(ref rect)).width; } private static void SetContentHeight(RectTransform content, float bottomY, float pad) { //IL_0015: 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) float num = 0f - bottomY + pad; if (num < 0f) { num = 0f; } content.sizeDelta = new Vector2(content.sizeDelta.x, num); } internal static void BuildLocalized(RectTransform content) { //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) IList<ModDictionary> dictionaries = LocEngine.Dictionaries; if (dictionaries == null || dictionaries.Count == 0) { EmptyHint(content, "还没有安装任何汉化词典。", "把词典 JSON 放进 BepInEx/config/ValheimCN/ 后重启游戏。"); SetContentHeight(content, -170f, 8f); return; } float w = W(content); List<ModDictionary> list = new List<ModDictionary>(); int num = 0; for (int i = 0; i < dictionaries.Count; i++) { if (dictionaries[i].Active) { list.Add(dictionaries[i]); } else { num++; } } int num2 = 0; int num3 = 0; for (int j = 0; j < list.Count; j++) { num2 += list[j].Exact.Count + list[j].Regexes.Count; num3 += list[j].ConfigSections.Count + list[j].ConfigNames.Count + list[j].ConfigDescriptions.Count + list[j].ConfigValues.Count; } UICore.Place(UICore.Rect(((Component)UICore.Label((Transform)(object)content, "Summary", $"已生效 {list.Count} 个 MOD,共 {num2} 条词条、{num3} 条配置。", 14, UICore.GrayText, (TextAnchor)3)).gameObject), 0f, 0f, w, 22f); float num4 = -33f; for (int k = 0; k < list.Count; k++) { BuildDictRow(content, list[k], num4, k % 2 == 1); num4 -= 39f; } num4 += 3f; List<string> list2 = new List<string>(); for (int l = 0; l < list.Count; l++) { if (!string.IsNullOrEmpty(list[l].MismatchNote)) { list2.Add(string.IsNullOrEmpty(list[l].TargetName) ? Path.GetFileNameWithoutExtension(list[l].FilePath) : list[l].TargetName); } } string text = null; if (num > 0) { text = $"另有 {num} 份词典未生效(未订阅对应的原版 MOD),见「未生效」页。"; } if (list2.Count > 0) { string text2 = ((list2.Count <= 3) ? string.Join("、", list2) : (list2[0] + $" 等 {list2.Count} 个 MOD")) + " 出了新版本。已汉化的文字照常生效;如发现未翻译的文案,请到「请求汉化」页提交反馈。"; text = ((text == null) ? "" : (text + "\n")) + "「MOD 有新版」:" + text2; } float num5 = 0f; if (text != null) { num5 = ((list2.Count > 0) ? 58f : 30f); UICore.Place(UICore.Rect(((Component)UICore.Paragraph((Transform)(object)content, "Note", text, 12, (list2.Count > 0) ? UICore.WarnColor : UICore.DimText, (TextAnchor)0)).gameObject), 0f, num4 - 6f, w, num5); } SetContentHeight(content, num4 - 6f - num5, 8f); } private static void BuildDictRow(RectTransform content, ModDictionary d, float y, bool alt) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) Image obj = UICore.MakeRowStretch((Transform)(object)content, "Row", y, 36f, alt ? UICore.RowAltColor : UICore.RowColor); bool flag = d.Active && !string.IsNullOrEmpty(d.MismatchNote); UICore.Place(UICore.Rect(((Component)UICore.Box(((Component)obj).transform, "Bar", (!d.Active) ? UICore.DimText : (flag ? UICore.WarnColor : UICore.OkColor))).gameObject), 0f, 0f, 4f, 36f); UICore.PlaceFill(UICore.Rect(((Component)UICore.Label(text: string.IsNullOrEmpty(d.TargetName) ? Path.GetFileNameWithoutExtension(d.FilePath) : d.TargetName, parent: ((Component)obj).transform, name: "Name", size: 15, color: UICore.TextColor, anchor: (TextAnchor)3)).gameObject), 12f, 548f, 0f, 36f); UICore.Place(UICore.Rect(((Component)UICore.Label(((Component)obj).transform, "Ver", string.IsNullOrEmpty(d.TargetVersion) ? "" : ("v" + d.TargetVersion), 12, UICore.DimText, (TextAnchor)3)).gameObject), 196f, 0f, 70f, 36f); int count = d.Exact.Count; int count2 = d.Regexes.Count; int count3 = d.ConfigDescriptions.Count; int count4 = d.LoadingTips.Count; List<string> list = new List<string>(); if (count > 0) { list.Add($"词条 {count}"); } if (count2 > 0) { list.Add($"模板 {count2}"); } if (count3 > 0) { list.Add($"配置说明 {count3}"); } if (count4 > 0) { list.Add($"加载提示 {count4}"); } UICore.Place(UICore.Rect(((Component)UICore.Label(((Component)obj).transform, "Count", string.Join(" · ", list), 12, UICore.GrayText, (TextAnchor)3)).gameObject), 272f, 0f, 290f, 36f); UICore.PlaceRight(UICore.Rect(((Component)UICore.Label(text: (!d.Active) ? "未生效" : (flag ? "已生效·有新版" : "已生效"), color: (!d.Active) ? UICore.DimText : (flag ? UICore.WarnColor : UICore.OkColor), parent: ((Component)obj).transform, name: "Status", size: 13, anchor: (TextAnchor)5)).gameObject), 12f, 0f, 104f, 36f); } internal static void BuildInactive(RectTransform content) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) IList<ModDictionary> dictionaries = LocEngine.Dictionaries; List<ModDictionary> list = new List<ModDictionary>(); if (dictionaries != null) { for (int i = 0; i < dictionaries.Count; i++) { if (!dictionaries[i].Active) { list.Add(dictionaries[i]); } } } if (list.Count == 0) { EmptyHint(content, "所有词典均已生效。", "未启用源 MOD(未订阅或未安装)的词典才会显示在这里。"); SetContentHeight(content, -170f, 8f); return; } float w = W(content); UICore.Place(UICore.Rect(((Component)UICore.Label((Transform)(object)content, "Summary", $"未生效 {list.Count} 份词典。", 14, UICore.GrayText, (TextAnchor)3)).gameObject), 0f, 0f, w, 22f); UICore.Place(UICore.Rect(((Component)UICore.Paragraph((Transform)(object)content, "Explain", "这些词典对应的原版 MOD 未订阅(或未安装、加载失败),当前不起作用。\n词典会自动跳过、不会出错;订阅/安装原版 MOD 后重启游戏,会自动