using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; using UnityEngine.UI; using YuanAPI.LocalizationPatches; using YuanAPI.PropRegistryPatches; using YuanAPI.ResourceRegistryPatches; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("YuanAPI")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+1d7912e4bbac7d745607ccc7198ac80e5edd0fd0")] [assembly: AssemblyProduct("YuanAPI")] [assembly: AssemblyTitle("YuanAPI")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 YuanAPI { [MeansImplicitUse] [AttributeUsage(AttributeTargets.Class)] internal class Submodule : Attribute { public bool UseAutoPatch = true; } [AttributeUsage(AttributeTargets.Method)] internal class AutoInit : Attribute { } internal static class SubmoduleManager { private static bool _hasInitialized = false; private static Harmony _harmony = new Harmony("cc.lymone.HoL.YuanAPI.Submodule"); private static Dictionary _initDelegates = new Dictionary(); internal static HashSet HasInitialized = new HashSet(); internal static void Initialize() { if (_hasInitialized) { return; } YuanLogger.LogDebug("Initializing Submodule"); Assembly executingAssembly = Assembly.GetExecutingAssembly(); List list = (from t in executingAssembly.GetTypes() where t.GetCustomAttribute()?.UseAutoPatch ?? false select t).ToList(); foreach (Type item in list) { MethodInfo method = item.GetMethod("Initialize", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null); if (method != null) { PatchType(item, method); } else { YuanLogger.LogWarning("SubmoduleManager: type is submodule but not have Initialize method"); } } _hasInitialized = true; YuanLogger.LogInfo($"SubmoduleManager: Patched {list.Count} submodule classes"); } private static void PatchType(Type type, MethodInfo initMethod) { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown Type type2 = type; List list = (from m in type2.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public) where !m.IsSpecialName where m.DeclaringType == type2 where m.GetCustomAttribute() != null where m.Name != "Initialize" select m).ToList(); MethodInfo method = typeof(InitializePatch).GetMethod("InitializePrefix"); _harmony.Patch((MethodBase)initMethod, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _initDelegates[type2] = CreateInitDelegate(initMethod); foreach (MethodInfo item in list) { MethodInfo method2 = typeof(InitializePatch).GetMethod("MethodPrefix"); _harmony.Patch((MethodBase)item, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); YuanLogger.LogDebug("Patched: " + type2.Name + "." + item.Name); } } private static Action CreateInitDelegate(MethodInfo initMethod) { return (Action)Delegate.CreateDelegate(typeof(Action), initMethod); } public static bool TryGetInitDelegate(Type type, out Action initDelegate) { return _initDelegates.TryGetValue(type, out initDelegate); } } [HarmonyPatch] public static class InitializePatch { public static bool MethodPrefix(MethodBase __originalMethod) { if (SubmoduleManager.TryGetInitDelegate(__originalMethod.DeclaringType, out Action initDelegate)) { initDelegate(); } return true; } public static bool InitializePrefix(MethodBase __originalMethod) { if (__originalMethod?.DeclaringType == null) { return true; } string fullName = __originalMethod.DeclaringType.FullName; return SubmoduleManager.HasInitialized.Add(fullName); } } [BepInPlugin("cc.lymone.HoL.YuanAPI", "YuanAPI", "0.2.1")] public class YuanAPIPlugin : BaseUnityPlugin { public const string MODNAME = "YuanAPI"; public const string MODGUID = "cc.lymone.HoL.YuanAPI"; public const string VERSION = "0.2.1"; internal static Harmony Harmony = new Harmony("cc.lymone.HoL.YuanAPI"); public static readonly Version BuildFor = new Version(0, 7, 851); public static readonly Version Version = Version.Parse("0.2.1"); internal static event Action OnStart; private void Awake() { YuanLogger.SetLogger(new LoggerWrapper(((BaseUnityPlugin)this).Logger)); SubmoduleManager.Initialize(); } private void Start() { YuanAPIPlugin.OnStart?.Invoke(); } } [Submodule] public class Localization { public class LocalizationInstance { [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private bool k__BackingField; public string Locale { get; set; } public string Namespace { get; set; } public bool IsSyncGlobalLang { [CompilerGenerated] get { return k__BackingField; } set { if (k__BackingField != value) { if (value) { OnLanguageChanged += SyncLang; } else { OnLanguageChanged -= SyncLang; } k__BackingField = value; } } } internal LocalizationInstance(string locale, string @namespace, bool syncGlobalLocale) { Locale = locale; Namespace = @namespace; IsSyncGlobalLang = syncGlobalLocale; } private void SyncLang(string locale) { Locale = locale; } public string t(string key) { return GetText(Locale, Namespace, key); } public string t(string @namespace, string key) { return GetText(Locale, @namespace, key); } public string t(string locale, string @namespace, string key) { return GetText(locale, @namespace, key); } public string t(string key, params object[] args) { return t(Locale, Namespace, key, args); } public string t(string @namespace, string key, params object[] args) { return t(Locale, @namespace, key, args); } public string t(string locale, string @namespace, string key, params object[] args) { return string.Format(GetText(locale, @namespace, key), args); } } private static Dictionary<(string loc, string ns, string key), string> _store = new Dictionary<(string, string, string), string>(); private static List _locales = new List(); private static Dictionary _localeShowNames = new Dictionary(); private static Dictionary> _fallbackChains = new Dictionary>(); private static Dictionary> _searchOrders = new Dictionary>(); [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private static string k__BackingField = "zh-CN"; public const string DefaultLocale = "zh-CN"; public const string DefaultNamespace = "Common"; public const string VanillaNamespace = "Vanilla"; public static string CurrentLocale { [CompilerGenerated] get { return k__BackingField; } internal set { if (!(k__BackingField == value)) { k__BackingField = value; Localization.OnLanguageChanged?.Invoke(value); } } } public static event Action OnLanguageChanged; public static void Initialize() { YuanLogger.LogDebug("Localization Initialize Called"); YuanAPIPlugin.Harmony.PatchAll(typeof(SetPanelPatch)); YuanAPIPlugin.Harmony.PatchAll(typeof(YuanAPI.LocalizationPatches.SaveDataPatch)); RegisterLocale("zh-CN", "简体中文", new List()); RegisterLocale("en-US", "English(US)", new List(1) { "zh-CN" }); LoadFromAllText(); LoadFromRandName(); YuanAPIPlugin.OnStart += InjectAllText; YuanAPIPlugin.OnStart += JnjectRandName; } private static void LoadFromAllText() { List list = (from field in typeof(AllText).GetFields(BindingFlags.Static | BindingFlags.Public) where field.FieldType == typeof(List>) select field).ToList(); if (list.Count == 0) { YuanLogger.LogError("Localization:未能成功找到 AllText 原版字段"); return; } foreach (FieldInfo item in list) { string fieldName = item.Name; List> enumerable = (List>)item.GetValue(null); enumerable.ForEach(delegate(List item, int index) { if (item.Count == 0) { _locales.ForEach(delegate(string locale) { _store[(locale, "Vanilla", $"{fieldName}.{index}")] = ""; }); } else { _locales.ForEach(delegate(string locale, int langIndex) { _store[(locale, "Vanilla", $"{fieldName}.{index}")] = item[langIndex]; }); } }); } List>> text_AllShenFen = AllText.Text_AllShenFen; text_AllShenFen.ForEach(delegate(List> group, int gIndex) { group.ForEach(delegate(List item, int iIndex) { _locales.ForEach(delegate(string locale, int langIndex) { _store[(locale, "Vanilla", $"Text_AllShenFen.{gIndex}.{iIndex}")] = item[langIndex]; }); }); }); List zhuangTouShai = AllText.Text_ZhuangTouShai; if (zhuangTouShai != null) { _locales.ForEach(delegate(string locale, int langIndex) { _store[(locale, "Vanilla", "Text_ZhuangTouShai")] = zhuangTouShai[langIndex]; }); } YuanLogger.LogDebug($"Localization:成功读入 AllText 的 {list.Count + 2} 个字段"); } private static void InjectAllText() { List list = (from field in typeof(AllText).GetFields(BindingFlags.Static | BindingFlags.Public) where field.FieldType == typeof(List>) select field).ToList(); if (list.Count == 0) { YuanLogger.LogError("Localization:AllText 原版字段未能成功读入,拒绝注入"); return; } foreach (FieldInfo item in list) { string fieldName = item.Name; List> list2 = (List>)item.GetValue(null); List> list3 = new List>(); int j; for (j = 0; j < list2.Count; j++) { list3.Add(_locales.Select((string locale) => GetText(locale, "Vanilla", $"{fieldName}.{j}")).ToList()); } item.SetValue(null, list3); } List>> text_AllShenFen = AllText.Text_AllShenFen; int count = text_AllShenFen.Count; for (int i = 0; i < count; i++) { List> list4 = new List>(); List> list5 = text_AllShenFen[i]; int count2 = text_AllShenFen.Count; int k; for (k = 0; k < count2; k++) { list4.Add(_locales.Select((string locale) => GetText(locale, "Vanilla", $"Text_AllShenFen.{i}.{k}")).ToList()); } AllText.Text_AllShenFen[i] = list4; } AllText.Text_ZhuangTouShai = _locales.Select((string locale) => GetText(locale, "Vanilla", "Text_ZhuangTouShai")).ToList(); YuanLogger.LogDebug($"Localization:成功注入 AllText 的 {_locales.Count} 种语言"); } private static void LoadFromRandName() { List list = (from field in typeof(RandName).GetFields(BindingFlags.Static | BindingFlags.Public) where field.FieldType == typeof(List) select field).ToList(); if (list.Count == 0) { YuanLogger.LogError("Localization:未能成功找到 RandName 原版字段"); return; } foreach (FieldInfo item in list) { string fieldName = item.Name; List enumerable = (List)item.GetValue(null); enumerable.ForEach(delegate(string item, int index) { string[] parts = (item ?? string.Empty).Split(new char[1] { '|' }); _locales.ForEach(delegate(string locale, int langIndex) { _store[(locale, "Vanilla", $"RandName.{fieldName}.{index}")] = parts[langIndex]; }); }); } YuanLogger.LogDebug($"Localization:成功读入 RandName 的 {list.Count} 个字段"); } private static void JnjectRandName() { List list = (from field in typeof(RandName).GetFields(BindingFlags.Static | BindingFlags.Public) where field.FieldType == typeof(List) select field).ToList(); if (list.Count == 0) { YuanLogger.LogError("Localization:RandName 原版字段未能成功读入,拒绝注入"); return; } foreach (FieldInfo item in list) { string fieldName = item.Name; List list2 = (List)item.GetValue(null); List list3 = new List(); int i; for (i = 0; i < list2.Count; i++) { List values = _locales.Select((string locale) => GetText(locale, "Vanilla", $"RandName.{fieldName}.{i}")).ToList(); list3.Add(string.Join("|", values)); } item.SetValue(null, list3); } YuanLogger.LogDebug($"Localization:成功注入 RandName 的 {_locales.Count} 种语言"); } [AutoInit] public static void RegisterLocale(string locale, string showName, List fallbackChain = null) { if (string.IsNullOrWhiteSpace(locale)) { throw new ArgumentException("locale 必须是非空字符串", "locale"); } if (_locales.Contains(locale)) { YuanLogger.LogWarning("Localization: " + locale + " 语言重复注册,忽略本次注册"); return; } _locales.Add(locale); _localeShowNames[locale] = showName; if (fallbackChain == null) { fallbackChain = new List(1) { "zh-CN" }; } SetFallbackChain(locale, fallbackChain); _searchOrders.Clear(); } [AutoInit] public static void SetFallbackChain(string locale, List fallbackChain) { if (!_locales.Contains(locale)) { throw new ArgumentException("locale 未注册", "locale"); } _fallbackChains[locale] = fallbackChain.Where((string s) => !string.IsNullOrWhiteSpace(s)).ToList(); EnsureNoCycles(); } [AutoInit] public static void LoadFromPath(string path) { string text = Path.Combine(path, "locales"); if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(text)) { throw new DirectoryNotFoundException("路径不存在:" + text); } foreach (string locale in _locales) { string path2 = Path.Combine(text, locale); if (!Directory.Exists(path2)) { continue; } foreach (string item in Directory.EnumerateFiles(path2, "*.json")) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item); LoadOneFile(locale, fileNameWithoutExtension, item); } } } [AutoInit] public static void EditText(string loc, string ns, string key, string value) { _store[(loc, ns, key)] = value ?? string.Empty; } public static void EditText(string ns, string key, List values) { int count = values?.Count ?? 0; _locales.ForEach(delegate(string locale, int index) { if (index < count) { EditText(locale, ns, key, values?[index]); } }); } public static string GetLocale(int index) { return _locales[index]; } public static string GetText(string locale, string @namespace, string key) { if (string.IsNullOrWhiteSpace(key)) { return string.Empty; } List list = BuildSearchOrders(locale); foreach (string item in list) { if (_store.TryGetValue((item, @namespace, key), out var value)) { return value; } } YuanLogger.LogWarning("Localization: 无法解析 " + locale + "/" + @namespace + ":" + key); return @namespace + ":" + key; } public static List GetTextAllLocales(string @namespace, string key) { return _locales.Select((string locale) => GetText(locale, @namespace, key)).ToList(); } public static List GetAllLocales() { return _locales; } public static List GetAllShowNames() { return _locales.Select((string locale) => _localeShowNames[locale]).ToList(); } public static int LocaleCount() { return _locales.Count; } [AutoInit] public static LocalizationInstance CreateInstance(string locale = "zh-CN", string @namespace = "Common", bool syncGlobalLocale = true) { return new LocalizationInstance(locale, @namespace, syncGlobalLocale); } private static void LoadOneFile(string locale, string @namespace, string filePath) { //IL_00cb: Expected O, but got Unknown //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 try { using FileStream stream = File.OpenRead(filePath); using StreamReader streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true); JsonTextReader val = new JsonTextReader((TextReader)streamReader); try { JObject val2 = JObject.Load((JsonReader)(object)val); if ((int)((JToken)val2).Type != 1) { throw new InvalidDataException("根必须是对象:" + filePath); } Dictionary dictionary = new Dictionary(); FlattenStringLeaves((JToken)(object)val2, dictionary); foreach (KeyValuePair item in dictionary) { EditText(locale, @namespace, item.Key, item.Value); } } finally { ((IDisposable)val)?.Dispose(); } } catch (JsonReaderException val3) { JsonReaderException val4 = val3; throw new InvalidDataException($"JSON 语法不合法({val4.LineNumber}:{val4.LinePosition}) : {filePath}", (Exception?)(object)val4); } catch (Exception ex) { throw new InvalidDataException("文件读取错误(" + ex.GetType().Name + ") : " + filePath, ex); } } private static void FlattenStringLeaves(JToken token, Dictionary output, string prefix = null) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_0064: 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_0069: Invalid comparison between Unknown and I4 //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 //IL_00a5: Unknown result type (might be due to invalid IL or missing references) JObject val = (JObject)(object)((token is JObject) ? token : null); if (val != null) { foreach (JProperty item in val.Properties()) { string text = (string.IsNullOrEmpty(prefix) ? item.Name : (prefix + "." + item.Name)); JToken value = item.Value; JTokenType type = value.Type; JTokenType val2 = type; if ((int)val2 != 1) { if ((int)val2 != 8) { throw new InvalidDataException($"JSON 值必须为字符串(键:{text},实际类型:{value.Type})。"); } output[text] = Extensions.Value((IEnumerable)value) ?? string.Empty; } else { FlattenStringLeaves(value, output, text); } } return; } throw new InvalidDataException("根必须是对象类型。"); } private static List BuildSearchOrders(string locale) { if (_searchOrders.TryGetValue(locale, out var value)) { return value; } List result = new List(); HashSet visited = new HashSet(); Dfs(locale); result = result.Where((string loc) => _locales.Contains(loc)).ToList(); _searchOrders[locale] = result; return result; void Dfs(string loc) { if (visited.Add(loc)) { result.Add(loc); if (_fallbackChains.TryGetValue(loc, out var value2)) { foreach (string item in value2.Where((string n) => !string.IsNullOrWhiteSpace(n))) { Dfs(item); } } } } } private static void EnsureNoCycles() { HashSet visiting = new HashSet(); HashSet visited = new HashSet(); foreach (string item in _fallbackChains.Keys.Where(Dfs)) { YuanLogger.LogError("Localization: 检测到回退链路的环:起点 " + item); } bool Dfs(string loc) { if (visiting.Contains(loc)) { return true; } if (visited.Contains(loc)) { return false; } visiting.Add(loc); if (_fallbackChains.TryGetValue(loc, out var value) && value.Where((string n) => !string.IsNullOrWhiteSpace(n)).Any(Dfs)) { return true; } visiting.Remove(loc); visited.Add(loc); return false; } } } public enum PropCategory { Offering, Fertilizer, Food, Snack, Textile, Mineral, Rouge, JewelryF, JewelryM, Book, Ink, Art, Antique, Weapon, TeaSet, Incense, Vase, Wine, Music, Pelt, Spell, Poison, Produce, Medicine } public class PropData { public string PropNamespace { get; set; } = "Common"; public string PropID { get; set; } public string Uid => PropNamespace + ":" + PropID; public int? Price { get; set; } = null; public int? Category { get; set; } = null; public Dictionary PropEffect { get; set; } = new Dictionary(); public string TextNamespace { get; set; } = "Common"; public string TextKey { get; set; } public string PrefabPath { get; set; } public bool IsValid() { return !string.IsNullOrEmpty(PropNamespace) && !string.IsNullOrEmpty(PropID) && !string.IsNullOrEmpty(TextNamespace) && !string.IsNullOrEmpty(TextKey) && !string.IsNullOrEmpty(PrefabPath) && Price.HasValue && Category.HasValue; } internal static PropData FromVanillaPropData(List listData, int index) { PropData propData = new PropData(); propData.PropNamespace = "Vanilla"; propData.PropID = index.ToString(); propData.Price = int.Parse(listData[0]); propData.Category = int.Parse(listData[1]); propData.PropEffect = listData[2].Split(new char[1] { '|' }).Select((string str, int effect) => (effect, int.Parse(str))).ToDictionary<(int, int), int, int>(((int effect, int) x) => x.effect, ((int effect, int) x) => x.Item2); propData.TextNamespace = "Vanilla"; propData.TextKey = $"Prop.{index}"; propData.PrefabPath = $"AllProp/{index}"; return propData; } internal List ToVanillaPropData() { if (!IsValid()) { throw new InvalidDataException("无法使用非法数据构造数据序列"); } List list = new List(); list.Add(Price.ToString()); list.Add(Category.ToString()); list.Add(string.Join("|", from PropEffectType effect in Enum.GetValues(typeof(PropEffectType)) select PropEffect.TryGetValue((int)effect, out var value) ? value.ToString() : "0")); return list; } public static PropData operator +(PropData sample, PropData that) { if (sample == null) { return that; } if (that == null) { return sample; } PropData propData = new PropData { PropNamespace = ((that.PropNamespace != "Common") ? that.PropNamespace : sample.PropNamespace), PropID = ((!string.IsNullOrEmpty(that.PropID)) ? that.PropID : sample.PropID), Price = (that.Price ?? sample.Price), Category = (that.Category ?? sample.Category), TextNamespace = ((that.TextNamespace != "Common") ? that.TextNamespace : sample.TextNamespace), TextKey = ((!string.IsNullOrEmpty(that.TextKey)) ? that.TextKey : sample.TextKey), PrefabPath = ((!string.IsNullOrEmpty(that.PrefabPath)) ? that.PrefabPath : sample.PrefabPath), PropEffect = new Dictionary() }; foreach (KeyValuePair item in sample.PropEffect) { propData.PropEffect[item.Key] = item.Value; } foreach (KeyValuePair item2 in that.PropEffect) { propData.PropEffect[item2.Key] = item2.Value; } return propData; } } public enum PropEffectType { Writing, Might, Business, Arts, Health, Mood, Charisma, Luck, Life, Cunning, SorcerySkill, MedicineSkill, DaoismSkill, AugurSkill, CharismaSkill, CraftSkill } [Submodule] public class PropRegistry { public class PropRegistryInstance : IDisposable { private List _propList = new List(); private bool _disposed; public PropData Sample { get; set; } internal PropRegistryInstance(PropData sample, List propList) { Sample = sample; propList?.ForEach(Add); } public void Add(PropData prop) { _propList.Add(Sample + prop); } public void Dispose() { if (!_disposed) { YuanLogger.LogDebug("PropRegistryInstance RegisterProps"); RegisterProps(_propList); _disposed = true; } } } private static List _allProps = new List(); private static List _patchedVanillaProps = new List(); private static Dictionary<(string ns, string id), int> _uidMap = new Dictionary<(string, string), int>(); public const string DefaultNamespace = "Common"; public const string VanillaNamespace = "Vanilla"; internal static int VanillaPropCount { get; private set; } public static void Initialize() { YuanLogger.LogDebug("PropRegistry Initialize Called"); ResourceRegistry.Initialize(); Localization.Initialize(); YuanAPIPlugin.Harmony.PatchAll(typeof(YuanAPI.PropRegistryPatches.ResourcesPatch)); YuanAPIPlugin.Harmony.PatchAll(typeof(YuanAPI.PropRegistryPatches.SaveDataPatch)); YuanAPIPlugin.OnStart += InjectMainload; } public static PropData GetProp(string @namespace, string id) { return _allProps[_uidMap[(@namespace, id)]]; } public static PropData GetProp(string uid) { string[] array = uid.Split(new char[1] { ':' }); return _allProps[_uidMap[(array[0], array[1])]]; } public static PropData GetProp(int index) { return _allProps[index]; } public static string GetUid(int index) { return _allProps[index].PropNamespace + ":" + _allProps[index].PropID; } public static bool TryGetUid(int index, out string uid) { bool result = index >= 0 && index < _allProps.Count; uid = _allProps[index].PropNamespace + ":" + _allProps[index].PropID; return result; } public static int GetIndex(string @namespace, string id) { return _uidMap[(@namespace, id)]; } public static bool TryGetIndex(string @namespace, string id, out int index) { return _uidMap.TryGetValue((@namespace, id), out index); } public static bool TryGetIndex(string uid, out int index) { string[] array = uid.Split(new char[1] { ':' }); if (array.Length == 2) { return _uidMap.TryGetValue((array[0], array[1]), out index); } index = -1; return false; } private static void InjectMainload() { VanillaPropCount = Mainload.AllPropdata.Count; foreach (PropData allProp in _allProps) { List textAllLocales = Localization.GetTextAllLocales(allProp.TextNamespace, allProp.TextKey); AllText.Text_AllProp.Add(textAllLocales); Localization.EditText("Vanilla", $"Text_AllProp.{VanillaPropCount++}", textAllLocales); } VanillaPropCount = Mainload.AllPropdata.Count; List list = Mainload.AllPropdata.Select(PropData.FromVanillaPropData).ToList(); list.AddRange(_allProps); _allProps = list; List<(string, string)> list2 = _uidMap.Keys.ToList(); CollectionExtensions.Do<(string, string)>((IEnumerable<(string, string)>)list2, (Action<(string, string)>)delegate((string ns, string id) str) { _uidMap[str] += VanillaPropCount; }); for (int i = 0; i < VanillaPropCount; i++) { _uidMap.Add(("Vanilla", i.ToString()), i); } foreach (PropData patchedVanillaProp in _patchedVanillaProps) { if (int.TryParse(patchedVanillaProp.PropID, out var result) && result >= 0 && result < VanillaPropCount) { _allProps[result] = patchedVanillaProp; AllText.Text_AllProp[result] = Localization.GetTextAllLocales(patchedVanillaProp.TextNamespace, patchedVanillaProp.TextKey); } } Mainload.AllPropdata = _allProps.Select((PropData prop) => prop.ToVanillaPropData()).ToList(); YuanLogger.LogDebug($"PropRegistry: 添加了{Mainload.AllPropdata.Count - VanillaPropCount}个物品"); } [AutoInit] public static void RegisterProps(List props) { int count = _allProps.Count; foreach (PropData prop in props) { int value; if (!prop.IsValid()) { YuanLogger.LogError("PropRegistry: 名为 " + prop.PropNamespace + ":" + prop.PropID + " 的数据为空或非法,将跳过注册"); } else if (prop.PropNamespace == "Vanilla") { _patchedVanillaProps.Add(prop); } else if (_uidMap.TryGetValue((prop.PropNamespace, prop.PropID), out value)) { _allProps[value] = prop; } else { _allProps.Add(prop); _uidMap.Add((prop.PropNamespace, prop.PropID), count++); } } } [AutoInit] public static PropRegistryInstance CreateInstance(PropData sample = null, List propList = null) { return new PropRegistryInstance(sample, propList); } } public class ResourceData { public string ModId; public string KeyWord; public string ModPath; public AssetBundle Bundle; public ResourceData(string modId, string keyWord, string modPath) { ModId = modId; ModPath = modPath; KeyWord = keyWord; } public ResourceData(string modId, string keyWord) { ModId = modId; ModPath = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location); KeyWord = keyWord; } public bool HasAssetBundle() { return (Object)(object)Bundle != (Object)null; } public void LoadAssetBundle(string bundleName) { Bundle = AssetBundle.LoadFromFile(ModPath + "/" + bundleName); if ((Object)(object)Bundle == (Object)null) { throw new ResourceException("Failed to load asset bundle at " + ModPath + "/" + bundleName); } } } public class ResourceException : Exception { public ResourceException(string message) : base(message) { } } [Submodule] public class ResourceRegistry { internal static List ModResources = new List(); internal static string[] SpriteFileExtensions = new string[3] { ".jpg", ".png", ".tif" }; internal static string[] AudioClipFileExtensions = new string[4] { ".mp3", ".ogg", ".waw", ".aif" }; public static void Initialize() { YuanAPIPlugin.Harmony.PatchAll(typeof(YuanAPI.ResourceRegistryPatches.ResourcesPatch)); } [AutoInit] public static void AddResource(ResourceData resource) { ModResources.Add(resource); } } public static class EnumerableExtension { public static void ForEach(this IEnumerable enumerable, Action action) { int num = 0; foreach (T item in enumerable) { action(item, num); num++; } } } public interface IYuanLogger { void LogFatal(object data); void LogError(object data); void LogWarning(object data); void LogMessage(object data); void LogInfo(object data); void LogDebug(object data); } public class LoggerWrapper : IYuanLogger { public ManualLogSource logSource; public LoggerWrapper(ManualLogSource logSource) { this.logSource = logSource; } public void LogFatal(object data) { logSource.LogFatal(data); } public void LogError(object data) { logSource.LogError(data); } public void LogWarning(object data) { logSource.LogWarning(data); } public void LogMessage(object data) { logSource.LogMessage(data); } public void LogInfo(object data) { logSource.LogInfo(data); } public void LogDebug(object data) { logSource.LogDebug(data); } } public static class YuanLogger { private static IYuanLogger _logger; public static void SetLogger(IYuanLogger logger) { _logger = logger; } public static void LogFatal(object data) { _logger.LogFatal(data); } public static void LogError(object data) { _logger.LogError(data); } public static void LogWarning(object data) { _logger.LogWarning(data); } public static void LogMessage(object data) { _logger.LogMessage(data); } public static void LogInfo(object data) { _logger.LogInfo(data); } public static void LogDebug(object data) { _logger.LogDebug(data); } } internal static class GameVersion { public static Version GetGameVersion() { if (Mainload.Vision_now.Length <= 2) { return null; } Version.TryParse(Mainload.Vision_now.Substring(2), out Version version); return version; } } public class Version : IComparable, IEquatable { private const string SemVerPattern = "^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"; private static readonly Regex SemVerRegex = new Regex("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", RegexOptions.Compiled); public int Major { get; } public int Minor { get; } public int Patch { get; } public string? PreRelease { get; } public string? BuildMetadata { get; } public Version(int major, int minor, int patch, string? preRelease = null, string? buildMetadata = null) { if (major < 0) { throw new ArgumentException("Major version cannot be negative", "major"); } if (minor < 0) { throw new ArgumentException("Minor version cannot be negative", "minor"); } if (patch < 0) { throw new ArgumentException("Patch version cannot be negative", "patch"); } Major = major; Minor = minor; Patch = patch; PreRelease = preRelease; BuildMetadata = buildMetadata; } public Version(string versionString) { if (string.IsNullOrWhiteSpace(versionString)) { throw new ArgumentException("Version string cannot be null or empty", "versionString"); } Match match = SemVerRegex.Match(versionString); if (match.Success) { Major = int.Parse(match.Groups["major"].Value); Minor = int.Parse(match.Groups["minor"].Value); Patch = int.Parse(match.Groups["patch"].Value); PreRelease = (match.Groups["prerelease"].Success ? match.Groups["prerelease"].Value : null); BuildMetadata = (match.Groups["buildmetadata"].Success ? match.Groups["buildmetadata"].Value : null); } throw new ArgumentException("Version string is not SemVer Version", "versionString"); } public static Version Parse(string versionString) { if (string.IsNullOrWhiteSpace(versionString)) { throw new ArgumentException("Version string cannot be null or empty", "versionString"); } Match match = SemVerRegex.Match(versionString); if (match.Success) { int major = int.Parse(match.Groups["major"].Value); int minor = int.Parse(match.Groups["minor"].Value); int patch = int.Parse(match.Groups["patch"].Value); string preRelease = (match.Groups["prerelease"].Success ? match.Groups["prerelease"].Value : null); string buildMetadata = (match.Groups["buildmetadata"].Success ? match.Groups["buildmetadata"].Value : null); return new Version(major, minor, patch, preRelease, buildMetadata); } throw new ArgumentException("Version string is not SemVer Version", "versionString"); } public static bool TryParse(string versionString, out Version? version) { version = null; if (string.IsNullOrWhiteSpace(versionString)) { return false; } try { version = Parse(versionString); return true; } catch { return false; } } public static bool operator ==(Version? left, Version? right) { if ((object)left == right) { return true; } if ((object)left == null || (object)right == null) { return false; } return left.CompareTo(right) == 0; } public static bool operator !=(Version? left, Version? right) { return !(left == right); } public static bool operator <(Version? left, Version? right) { if ((object)left == null) { return (object)right != null; } return left.CompareTo(right) < 0; } public static bool operator <=(Version? left, Version? right) { if ((object)left == null) { return true; } return left.CompareTo(right) <= 0; } public static bool operator >(Version? left, Version? right) { return !(left <= right); } public static bool operator >=(Version? left, Version? right) { return !(left < right); } public int CompareTo(Version? other) { if ((object)other == null) { return 1; } int num = Major.CompareTo(other.Major); if (num != 0) { return num; } int num2 = Minor.CompareTo(other.Minor); if (num2 != 0) { return num2; } int num3 = Patch.CompareTo(other.Patch); if (num3 != 0) { return num3; } return ComparePreRelease(PreRelease, other.PreRelease); } private static int ComparePreRelease(string? preRelease1, string? preRelease2) { if (string.IsNullOrEmpty(preRelease1) && string.IsNullOrEmpty(preRelease2)) { return 0; } if (string.IsNullOrEmpty(preRelease1)) { return 1; } if (string.IsNullOrEmpty(preRelease2)) { return -1; } string[] array = preRelease1.Split(new char[1] { '.' }); string[] array2 = preRelease2.Split(new char[1] { '.' }); int num = Math.Min(array.Length, array2.Length); for (int i = 0; i < num; i++) { string text = array[i]; string text2 = array2[i]; int result; bool flag = int.TryParse(text, out result); int result2; bool flag2 = int.TryParse(text2, out result2); if (flag && flag2) { int num2 = result.CompareTo(result2); if (num2 != 0) { return num2; } continue; } if (flag) { return -1; } if (flag2) { return 1; } int num3 = string.Compare(text, text2, StringComparison.Ordinal); if (num3 != 0) { return num3; } } return array.Length.CompareTo(array2.Length); } public bool Equals(Version? other) { return CompareTo(other) == 0; } public override bool Equals(object? obj) { return obj is Version other && Equals(other); } public override int GetHashCode() { int major = Major; major = (major * 397) ^ Minor; major = (major * 397) ^ Patch; return (major * 397) ^ (PreRelease?.GetHashCode() ?? 0); } public override string ToString() { string text = $"{Major}.{Minor}.{Patch}"; if (!string.IsNullOrEmpty(PreRelease)) { text = text + "-" + PreRelease; } if (!string.IsNullOrEmpty(BuildMetadata)) { text = text + "+" + BuildMetadata; } return text; } } } namespace YuanAPI.Tools { public class CoinTool { public bool TryChangeCoins(int count) { if (count < 0 && FormulaData.GetCoinsNum() < -count) { return false; } FormulaData.ChangeCoins(count); return true; } } public enum TipLv { Info, Warning, ShortInfo } public static class MsgTool { public static void TipMsg(string msg, TipLv lv = TipLv.Info) { if (!string.IsNullOrEmpty(msg)) { List> tip_Show = Mainload.Tip_Show; List list = new List(2); int num = (int)lv; list.Add(num.ToString()); list.Add(msg); tip_Show.Add(list); } } } public static class PropTool { public static bool AddProp(int propId, int propCount, bool storage = true, bool silence = false) { if (propId < 0 || propId >= Mainload.AllPropdata.Count) { return false; } int.TryParse(Mainload.FamilyData[5], out var result); if (storage) { if (result - propCount < 0) { if (!silence) { MsgTool.TipMsg(AllText.Text_TipShow[21][Mainload.SetData[4]], TipLv.Warning); } return false; } Mainload.FamilyData[5] = (result - propCount).ToString(); } string text = propId.ToString(); int count = Mainload.Prop_have.Count; for (int i = 0; i < count; i++) { if (!(Mainload.Prop_have[i][0] != text)) { Mainload.Prop_have[i][1] = (int.Parse(Mainload.Prop_have[i][1]) + propCount).ToString(); return true; } } Mainload.Prop_have.Add(new List(2) { text, propCount.ToString() }); return true; } public static bool AddProp(string propId, int propCount, bool storage = true, bool silence = false) { return AddProp(int.Parse(propId), propCount, storage, silence); } } } namespace YuanAPI.ResourceRegistryPatches { [HarmonyPatch] public static class ResourcesPatch { [HarmonyPrefix] [HarmonyPriority(0)] [HarmonyPatch(typeof(Resources), "Load", new Type[] { typeof(string), typeof(Type) })] public static bool Prefix(ref string path, Type systemTypeInstance, ref Object __result, bool __runOriginal) { if (!__runOriginal) { return false; } foreach (ResourceData modResource in ResourceRegistry.ModResources) { if (!path.Contains(modResource.KeyWord) || !modResource.HasAssetBundle()) { continue; } if (modResource.Bundle.Contains(path + ".prefab")) { Object val = modResource.Bundle.LoadAsset(path + ".prefab"); YuanLogger.LogDebug("Loading registered asset " + path + ": " + ((val != (Object)null) ? "Success" : "Failure")); __result = val; return false; } string[] spriteFileExtensions = ResourceRegistry.SpriteFileExtensions; foreach (string text in spriteFileExtensions) { if (modResource.Bundle.Contains(path + text)) { Object val2 = modResource.Bundle.LoadAsset(path + text, systemTypeInstance); YuanLogger.LogDebug("Loading registered asset " + path + ": " + ((val2 != (Object)null) ? "Success" : "Failure")); __result = val2; return false; } } string[] audioClipFileExtensions = ResourceRegistry.AudioClipFileExtensions; foreach (string text2 in audioClipFileExtensions) { if (modResource.Bundle.Contains(path + text2)) { Object val3 = modResource.Bundle.LoadAsset(path + text2, systemTypeInstance); YuanLogger.LogDebug("Loading registered asset " + path + ": " + ((val3 != (Object)null) ? "Success" : "Failure")); __result = val3; return false; } } } return true; } } } namespace YuanAPI.PropRegistryPatches { [HarmonyPatch] public static class ResourcesPatch { [HarmonyPrefix] [HarmonyPriority(400)] [HarmonyPatch(typeof(Resources), "Load", new Type[] { typeof(string), typeof(Type) })] public static bool Prefix(ref string path, Type systemTypeInstance, ref Object __result, bool __runOriginal) { if (!__runOriginal) { return false; } if (!path.StartsWith("AllProp/")) { return true; } int index = int.Parse(path.Substring("AllProp/".Length)); path = PropRegistry.GetProp(index).PrefabPath; return true; } } [HarmonyPatch] public class SaveDataPatch { [HarmonyPostfix] [HarmonyPatch(typeof(SaveData), "SaveGameData")] public static void SaveGameDataPostfix() { YuanLogger.LogDebug("SaveGameData to UID"); List> propHave = Mainload.Prop_have.Select((List prop) => new List(prop)).ToList(); int propCount = PropRegistry.VanillaPropCount; propHave.ForEach(delegate(List prop) { if (!int.TryParse(prop[0], out var result)) { YuanLogger.LogError("SaveData: 无法解析数据ID" + prop[0] + ",这可能不是YuanAPI导致的,本次保存将跳过该数据"); propHave.Remove(prop); } else if (result >= propCount) { prop[0] = PropRegistry.GetUid(result); } }); ES3.Save>>("Prop_have", (object)propHave, Mainload.CunDangIndex_now + "/GameData.es3"); } [HarmonyPostfix] [HarmonyPatch(typeof(SaveData), "ReadGameData")] public static void ReadGameDataPostfix() { YuanLogger.LogDebug("ReadGameData in UID"); List> propHave = Mainload.Prop_have; int propCount = PropRegistry.VanillaPropCount; propHave.ForEach(delegate(List prop) { if (!int.TryParse(prop[0], out var result) || result < 0 || result >= propCount) { if (!PropRegistry.TryGetIndex(prop[0], out var index)) { YuanLogger.LogError("SaveData: 无法解析数据ID" + prop[0] + ",请检查是否有模组未加载,本次加载将跳过该数据"); propHave.Remove(prop); } else { prop[0] = index.ToString(); } } }); } } } namespace YuanAPI.LocalizationPatches { [HarmonyPatch] public class SaveDataPatch { [HarmonyPostfix] [HarmonyPatch(typeof(SaveData), "ReadSetData")] public static void LanguageLimitPatch() { List list = ES3.Load>("SetData", "FW/SetData.es3", Mainload.SetData); Mainload.SetData[4] = ((list[4] < Localization.LocaleCount()) ? list[4] : 0); Localization.CurrentLocale = Localization.GetLocale(Mainload.SetData[4]); } } [HarmonyPatch] public class SetPanelPatch { [HarmonyPostfix] [HarmonyPatch(typeof(SetPanel), "SetLanguage")] public static void SetLanguagePostFix() { Localization.CurrentLocale = Localization.GetLocale(Mainload.SetData[4]); } [HarmonyPostfix] [HarmonyPatch(typeof(SetPanel), "InitShow")] public static void LoadLanguageOption(SetPanel __instance) { Dropdown component = ((Component)((Component)__instance).transform.Find("Language").Find("AllClass")).GetComponent(); component.options = ((IEnumerable)Localization.GetAllShowNames()).Select((Func)((string name) => new OptionData(name))).ToList(); component.value = Mainload.SetData[4]; } } }