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+76d5542aaee300e504f6c2b67b5dd217d0c877b7")]
[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<Type, Action> _initDelegates = new Dictionary<Type, Action>();
internal static HashSet<string> HasInitialized = new HashSet<string>();
internal static void Initialize()
{
if (_hasInitialized)
{
return;
}
YuanLogger.LogDebug("Initializing Submodule");
Assembly executingAssembly = Assembly.GetExecutingAssembly();
List<Type> list = (from t in executingAssembly.GetTypes()
where t.GetCustomAttribute<Submodule>()?.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<MethodInfo> list = (from m in type2.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)
where !m.IsSpecialName
where m.DeclaringType == type2
where m.GetCustomAttribute<AutoInit>() != 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.1.1")]
public class YuanAPIPlugin : BaseUnityPlugin
{
public const string MODNAME = "YuanAPI";
public const string MODGUID = "cc.lymone.HoL.YuanAPI";
public const string VERSION = "0.1.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.1.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 <IsSyncGlobalLang>k__BackingField;
public string Locale { get; set; }
public string Namespace { get; set; }
public bool IsSyncGlobalLang
{
[CompilerGenerated]
get
{
return <IsSyncGlobalLang>k__BackingField;
}
set
{
if (<IsSyncGlobalLang>k__BackingField != value)
{
if (value)
{
OnLanguageChanged += SyncLang;
}
else
{
OnLanguageChanged -= SyncLang;
}
<IsSyncGlobalLang>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<string> _locales = new List<string>();
private static Dictionary<string, string> _localeShowNames = new Dictionary<string, string>();
private static Dictionary<string, List<string>> _fallbackChains = new Dictionary<string, List<string>>();
private static Dictionary<string, List<string>> _searchOrders = new Dictionary<string, List<string>>();
public const string DefaultLocale = "zh-CN";
public const string DefaultNamespace = "Common";
public const string VanillaNamespace = "Vanilla";
private static List<FieldInfo> _vanillaFields = new List<FieldInfo>();
private static Dictionary<string, int> _itemCounts = new Dictionary<string, int>();
public static event Action<string> 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<string>());
RegisterLocale("en-US", "English(US)", new List<string>(1) { "zh-CN" });
LoadFromAllText();
YuanAPIPlugin.OnStart += InjectAllText;
}
private static void LoadFromAllText()
{
_vanillaFields = (from field in typeof(AllText).GetFields(BindingFlags.Static | BindingFlags.Public)
where field.FieldType == typeof(List<List<string>>)
select field).ToList();
if (_vanillaFields.Count == 0)
{
YuanLogger.LogError("Localization:未能成功找到原版字段");
return;
}
int count = ((List<List<string>>)_vanillaFields[0].GetValue(null))[0].Count;
if (count != _locales.Count)
{
YuanLogger.LogError($"Localization:原版的本地化语言数 {count} 与YuanAPI定义不一致,将尝试处理已定义语言,建议升级YuanAPI");
}
foreach (FieldInfo vanillaField in _vanillaFields)
{
string fieldName = vanillaField.Name;
List<List<string>> list = (List<List<string>>)vanillaField.GetValue(null);
_itemCounts[fieldName] = list.Count;
list.ForEach(delegate(List<string> 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<List<List<string>>> text_AllShenFen = AllText.Text_AllShenFen;
text_AllShenFen.ForEach(delegate(List<List<string>> group, int gIndex)
{
group.ForEach(delegate(List<string> item, int iIndex)
{
_locales.ForEach(delegate(string locale, int langIndex)
{
_store[(locale, "Vanilla", $"Text_AllShenFen.{gIndex}.{iIndex}")] = item[langIndex];
});
});
});
YuanLogger.LogDebug($"Localization:成功读入{_vanillaFields.Count + 1}个字段");
}
private static void InjectAllText()
{
if (_vanillaFields == null || _vanillaFields.Count == 0)
{
YuanLogger.LogError("Localization:原版字段未能成功读入,拒绝注入");
return;
}
foreach (FieldInfo vanillaField in _vanillaFields)
{
string fieldName = vanillaField.Name;
int num = _itemCounts[fieldName];
List<List<string>> list = new List<List<string>>();
int j;
for (j = 0; j < num; j++)
{
list.Add(_locales.Select((string locale) => GetText(locale, "Vanilla", $"{fieldName}.{j}")).ToList());
}
vanillaField.SetValue(null, list);
}
int count = AllText.Text_AllShenFen.Count;
for (int i = 0; i < count; i++)
{
int count2 = AllText.Text_AllShenFen[i].Count;
List<List<string>> list2 = new List<List<string>>();
int k;
for (k = 0; k < count2; k++)
{
list2.Add(_locales.Select((string locale) => GetText(locale, "Vanilla", $"Text_AllShenFen.{i}.{k}")).ToList());
}
AllText.Text_AllShenFen[i] = list2;
}
YuanLogger.LogDebug($"Localization:成功注入{_locales.Count}种语言");
}
[AutoInit]
public static void RegisterLocale(string locale, string showName, List<string> 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<string>(1) { "zh-CN" };
}
SetFallbackChain(locale, fallbackChain);
_searchOrders.Clear();
}
[AutoInit]
public static void SetFallbackChain(string locale, List<string> 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<string> 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<string> 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<string> GetTextAllLocales(string @namespace, string key)
{
return _locales.Select((string locale) => GetText(locale, @namespace, key)).ToList();
}
public static List<string> GetAllLocales()
{
return _locales;
}
public static List<string> 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);
}
internal static void CallLanguageChanged(string locale)
{
Localization.OnLanguageChanged?.Invoke(locale);
}
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<string, string> dictionary = new Dictionary<string, string>();
FlattenStringLeaves((JToken)(object)val2, dictionary);
foreach (KeyValuePair<string, string> 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<string, string> 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<string>((IEnumerable<JToken>)value) ?? string.Empty;
}
else
{
FlattenStringLeaves(value, output, text);
}
}
return;
}
throw new InvalidDataException("根必须是对象类型。");
}
private static List<string> BuildSearchOrders(string locale)
{
if (_searchOrders.TryGetValue(locale, out var value))
{
return value;
}
List<string> result = new List<string>();
HashSet<string> visited = new HashSet<string>();
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<string> visiting = new HashSet<string>();
HashSet<string> visited = new HashSet<string>();
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<int, int> PropEffect { get; set; } = new Dictionary<int, int>();
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<string> 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<string> ToVanillaPropData()
{
if (!IsValid())
{
throw new InvalidDataException("无法使用非法数据构造数据序列");
}
List<string> list = new List<string>();
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<int, int>()
};
foreach (KeyValuePair<int, int> item in sample.PropEffect)
{
propData.PropEffect[item.Key] = item.Value;
}
foreach (KeyValuePair<int, int> 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<PropData> _propList = new List<PropData>();
private bool _disposed;
public PropData Sample { get; set; }
internal PropRegistryInstance(PropData sample, List<PropData> 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<PropData> _allProps = new List<PropData>();
private static List<PropData> _patchedVanillaProps = new List<PropData>();
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<string> textAllLocales = Localization.GetTextAllLocales(allProp.TextNamespace, allProp.TextKey);
AllText.Text_AllProp.Add(textAllLocales);
Localization.EditText("Vanilla", $"Text_AllProp.{VanillaPropCount++}", textAllLocales);
}
VanillaPropCount = Mainload.AllPropdata.Count;
List<PropData> 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<PropData> 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<PropData> propList = null)
{
return new PropRegistryInstance(sample, propList);
}
}
public class PropPrefabData
{
}
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<ResourceData> ModResources = new List<ResourceData>();
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 enum TipLv
{
Info,
Warning,
ShortInfo
}
public static class MsgTool
{
public static void TipMsg(string msg, TipLv lv = TipLv.Info)
{
if (!string.IsNullOrEmpty(msg))
{
List<List<string>> tip_Show = Mainload.Tip_Show;
List<string> list = new List<string>(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<string>(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);
}
}
public static class EnumerableExtension
{
public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> 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<Version>, IEquatable<Version>
{
private const string SemVerPattern = "^(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(?:-(?<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$";
private static readonly Regex SemVerRegex = new Regex("^(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(?:-(?<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?<buildmetadata>[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.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<List<string>> propHave = Mainload.Prop_have.Select((List<string> prop) => new List<string>(prop)).ToList();
int propCount = PropRegistry.VanillaPropCount;
propHave.ForEach(delegate(List<string> 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<List<List<string>>>("Prop_have", (object)propHave, Mainload.CunDangIndex_now + "/GameData.es3");
}
[HarmonyPostfix]
[HarmonyPatch(typeof(SaveData), "ReadGameData")]
public static void ReadGameDataPostfix()
{
YuanLogger.LogDebug("ReadGameData in UID");
List<List<string>> propHave = Mainload.Prop_have;
int propCount = PropRegistry.VanillaPropCount;
propHave.ForEach(delegate(List<string> 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<int> list = ES3.Load<List<int>>("SetData", "FW/SetData.es3", Mainload.SetData);
Mainload.SetData[4] = ((list[4] < Localization.LocaleCount()) ? list[4] : 0);
}
}
[HarmonyPatch]
public class SetPanelPatch
{
[HarmonyPostfix]
[HarmonyPatch(typeof(SetPanel), "SetLanguage")]
public static void SetLanguagePostFix()
{
Localization.CallLanguageChanged(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<Dropdown>();
component.options = ((IEnumerable<string>)Localization.GetAllShowNames()).Select((Func<string, OptionData>)((string name) => new OptionData(name))).ToList();
component.value = Mainload.SetData[4];
}
}
}