-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathVaultLoad.cs
More file actions
882 lines (774 loc) · 40 KB
/
VaultLoad.cs
File metadata and controls
882 lines (774 loc) · 40 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
using InnoVault.GameSystem;
using Microsoft.Xna.Framework.Graphics;
using ReLogic.Content;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Terraria.Audio;
using Terraria.Graphics.Effects;
using Terraria.Graphics.Shaders;
using Terraria.ModLoader;
namespace InnoVault
{
/// <summary>
/// 管理资源
/// </summary>
public static class VaultLoad
{
/// <summary>
/// 在绝大部分内容加载完成后被设置为<see langword="true"/>
/// </summary>
public static bool LoadenContent { get; private set; } = false;
/// <summary>
/// 存储处理过的类型,在加载完成后会立刻清理释放
/// </summary>
private readonly static HashSet<Type> ProcessedTypes = [];
/// <summary>
/// 一个非常靠后的加载钩子,此时本地化、配方修改、菜单排序等内容已经设置完成
/// </summary>
public static event Action EndLoadenEvent;
/// <summary>
/// 将对应的资源枚举类型对应到实际的编译码类型
/// </summary>
public readonly static Dictionary<AssetMode, Type> AssetModeToTypeMap = new() {
{ AssetMode.None, null },
{ AssetMode.Sound, typeof(SoundStyle) },
{ AssetMode.Texture, typeof(Asset<Texture2D>) },
{ AssetMode.Effects, typeof(Asset<Effect>) },
{ AssetMode.ArmorShader, typeof(ArmorShaderData) },
{ AssetMode.MiscShader, typeof(MiscShaderData) },
{ AssetMode.TextureValue, typeof(Texture2D) },
{ AssetMode.EffectValue, typeof(Effect) },
{ AssetMode.SoundArray, typeof(IList<SoundStyle>) },
{ AssetMode.TextureArray, typeof(IList<Asset<Texture2D>>) },
{ AssetMode.EffectArray, typeof(IList<Asset<Effect>>) },
{ AssetMode.ArmorShaderArray, typeof(IList<ArmorShaderData>) },
{ AssetMode.MiscShaderArray, typeof(IList<MiscShaderData>) },
{ AssetMode.TextureValueArray, typeof(IList<Texture2D>) },
{ AssetMode.EffectValueArray, typeof(IList<Effect>) },
};
/// <summary>
/// 将实际的编译时类型映射到资源的枚举类型
/// </summary>
public readonly static Dictionary<Type, AssetMode> TypeToAssetModeMap = new() {
{ typeof(SoundStyle), AssetMode.Sound },
{ typeof(Asset<Texture2D>), AssetMode.Texture },
{ typeof(Asset<Effect>), AssetMode.Effects },
{ typeof(ArmorShaderData), AssetMode.ArmorShader },
{ typeof(MiscShaderData), AssetMode.MiscShader },
{ typeof(Texture2D), AssetMode.TextureValue },
{ typeof(Effect), AssetMode.EffectValue },
{ typeof(IList<SoundStyle>), AssetMode.SoundArray },
{ typeof(IList<Asset<Texture2D>>), AssetMode.TextureArray },
{ typeof(IList<Asset<Effect>>), AssetMode.EffectArray },
{ typeof(IList<ArmorShaderData>), AssetMode.ArmorShaderArray },
{ typeof(IList<MiscShaderData>), AssetMode.MiscShaderArray },
{ typeof(IList<Texture2D>), AssetMode.TextureValueArray },
{ typeof(IList<Effect>), AssetMode.EffectValueArray },
};
internal static void LoadData() {
try {//BossBarLoader的GotoSavedStyle是非常靠后的加载调用
VaultHook.Add(typeof(BossBarLoader).GetMethod("GotoSavedStyle"
, BindingFlags.NonPublic | BindingFlags.Static), EndLoaden);
} catch {
VaultMod.Instance.Logger.Error(
"Failed to hook BossBarLoader.GotoSavedStyle for VaultLoadenEvent. " +
"This may cause some resources to not load correctly.");
}
}
internal static void UnLoadData() {
EndLoadenEvent = null;
LoadenContent = false;
}
private static void EndLoaden(Action orig) {
orig.Invoke();
EndLoadenEvent?.Invoke();
LoadenContent = true;
}
internal static void LoadAsset() {
//初始化自定义加载器管理器
VaultLoadenHandleManager.Initialize();
ProcessedTypes.Clear();
foreach (var t in VaultUtils.GetAnyModCodeType()) {
ProcessClassAssets(t, load: true);
ProcessTypeAssets(t, load: true);
}
ProcessedTypes.Clear();
}
internal static void UnLoadAsset() {
ProcessedTypes.Clear();
foreach (var t in VaultUtils.GetAnyModCodeType()) {
ProcessClassAssets(t, load: false);
ProcessTypeAssets(t, load: false);
}
ProcessedTypes.Clear();
//卸载自定义加载器
VaultLoadenHandleManager.Unload();
}
internal static void ProcessTypeAssets(Type type, bool load) {
//反射所有的静态字段,无论是否公开
BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
//寻找并加载字段
foreach (var field in type.GetFields(flags)) {
ProcessMemberAsset(field, type, load);
}
//寻找并加载属性
foreach (var property in type.GetProperties(flags)) {
ProcessMemberAsset(property, type, load);
}
}
internal static void UnloadMember(MemberInfo member) {
if (member is FieldInfo field) {
field.SetValue(null, null);
}
else if (member is PropertyInfo prop && prop.CanWrite && prop.GetSetMethod(true) != null) {
prop.SetValue(null, null);
}
}
internal static bool FindattributeByMod(Type type, VaultLoadenAttribute attribute) {
if (attribute.Mod != null) {
return true; //如果已经手动指定了模组对象就不需要进行查找了
}
attribute.Mod = VaultUtils.FindModByType(type, ModLoader.Mods);
return attribute.Mod != null;
}
internal static void CheckAttributePath(Type type, string targetName, VaultLoadenAttribute attribute) {
if (attribute.Path.EndsWith('/')) {//如果以该符号结尾,补全成员的句柄名
attribute.Path += targetName;
}
if (!string.IsNullOrEmpty(type.Namespace)) {//替换该特殊词柄为命名空间路径
string namespacePath = type.Namespace.Replace('.', '/');
attribute.Path = attribute.Path.Replace("{@namespace}", namespacePath);
}
if (attribute.Path.Contains("{@classPath}")) {//替换为母类路径,类级别标签加载在字段标签之前,所以这里不用担心类标签数据还没有加载完成
VaultLoadenAttribute momClassAttribute = VaultUtils.GetAttributeSafely<VaultLoadenAttribute>(type, (phase, ex) => {
VaultMod.Instance.Logger.Warn($"Failed to resolve '{{@classPath}}' for member '{targetName}'" +
$": Could not read [VaultLoaden] attribute from containing class '{type.FullName}', by {phase}. " +
$"Path will fall back to default (namespace + class name). Reason: {ex.Message}");
}
);
string classPath;
if (momClassAttribute != null) {
classPath = momClassAttribute.Path;
}
else {
classPath = type.Namespace.Replace('.', '/') + type.Name;
}
attribute.Path = attribute.Path.Replace("{@classPath}", classPath);
}
string[] pathParts = attribute.Path.Split('/');//切割路径,检测是否以模组名字开头,如果包含模组名部分则切除
if (pathParts.Length == 0) {
throw new Exception($"Attribute path on member \"{targetName}\" is empty or invalid: \"{attribute.Path}\"");
}
if (attribute.Path.StartsWith('@')) {//用@指定其他模组,重新设置源模组对象
pathParts[0] = pathParts[0][1..]; //去掉@
if (ModLoader.TryGetMod(pathParts[0], out Mod newMod)) {
attribute.Mod = newMod;
}
else {
string modName = attribute.Mod != null ? attribute.Mod.Name : pathParts[0];
//改为记录调试日志而非抛出异常,支持弱联动
VaultMod.Instance.Logger.Debug($"Member {targetName} couldn't find Mod \"{pathParts[0]}\". " +
$"Original Mod Name: \"{modName}\". " +
$"Resource will use default value instead.");
//将资源对象设置为null,后续会使用默认值
attribute.Mod = null;
return;
}
}
if (pathParts[0] == attribute.Mod.Name) {//最后检测一下是否对齐源模组名称,如果对齐则进行剔除
attribute.Path = string.Join("/", pathParts.Skip(1));
}
}
internal static void ProcessMemberAsset(MemberInfo member, Type type, bool load) {
VaultLoadenAttribute attribute = VaultUtils.GetAttributeSafely<VaultLoadenAttribute>(member, (phase, ex) => {
VaultMod.Instance.Logger.Warn($"Skipped {member.MemberType.ToString().ToLower()} {member.Name} " +
$"Due To {phase} Load Error: {ex.Message}"
);
});
if (attribute == null) {
return;
}
if (!FindattributeByMod(type, attribute)) {
return;//存在无法找到源模组的情况,在这种情况下需要返回
}
if (load) {
CheckAttributePath(type, member.Name, attribute);
LoadMember(member, attribute);
}
else {
UnloadMember(member);
attribute.Mod = null;
}
}
internal static bool GetAttributeAssetArrayByIsAssignableFromMode(Type elementType, out AssetMode assetMode) {
assetMode = AssetMode.None;
foreach (Type targetElement in TypeToAssetModeMap.Keys) {
if (targetElement.IsAssignableFrom(elementType)) {
assetMode = TypeToAssetModeMap[targetElement];
break;
}
}
return assetMode != AssetMode.None;
}
internal static AssetMode GetAttributeAssetMode(Type type) {
if (TypeToAssetModeMap.TryGetValue(type, out AssetMode assetMode)) {
return assetMode;
}
if (type.IsArray && type.GetElementType() != null) {
var elementType = type.GetElementType();
if (GetAttributeAssetArrayByIsAssignableFromMode(elementType, out assetMode)) {
return assetMode;
}
}
else if (type.IsGenericType && typeof(IList<>).IsAssignableFrom(type.GetGenericTypeDefinition())) {
var elementType = type.GetGenericArguments()[0];
if (GetAttributeAssetArrayByIsAssignableFromMode(elementType, out assetMode)) {
return assetMode;
}
}
else {
var ilistInterface = type.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>));
if (ilistInterface != null) {
var elementType = ilistInterface.GetGenericArguments()[0];
if (GetAttributeAssetArrayByIsAssignableFromMode(elementType, out assetMode)) {
return assetMode;
}
}
}
//检查是否有自定义加载器可以处理该类型
if (VaultLoadenHandleManager.FindLoader(type) != null) {
return AssetMode.Custom;
}
//检查数组元素类型是否有自定义加载器
Type arrayElementType = GetArrayElementType(type);
if (arrayElementType != null && VaultLoadenHandleManager.FindArrayElementLoader(arrayElementType) != null) {
return AssetMode.CustomArray;
}
return AssetMode.None;
}
/// <summary>
/// 获取数组或列表的元素类型
/// </summary>
/// <param name="type">数组或列表类型</param>
/// <returns>元素类型,如果不是数组或列表则返回null</returns>
internal static Type GetArrayElementType(Type type) {
if (type.IsArray) {
return type.GetElementType();
}
if (type.IsGenericType && typeof(IList<>).IsAssignableFrom(type.GetGenericTypeDefinition())) {
return type.GetGenericArguments()[0];
}
var ilistInterface = type.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>));
if (ilistInterface != null) {
return ilistInterface.GetGenericArguments()[0];
}
return null;
}
private static object LoadValue(MemberInfo member, VaultLoadenAttribute attribute) {
return attribute.AssetMode switch {//根据资源类型来加载值
AssetMode.Sound => new SoundStyle(attribute.Mod.Name + "/" + attribute.Path),
AssetMode.Texture => LoadTexture(attribute),
AssetMode.Effects => LoadEffect(attribute),
AssetMode.ArmorShader => new ArmorShaderData(LoadEffect(attribute), attribute.EffectPassname),
AssetMode.MiscShader => LoadMiscShader(attribute),
AssetMode.TextureValue => LoadTextureValue(attribute),
AssetMode.EffectValue => LoadEffectValue(attribute),
AssetMode.SoundArray => LoadArrayAsset<SoundStyle>(member, attribute),
AssetMode.TextureArray => LoadArrayAsset<Asset<Texture2D>>(member, attribute),
AssetMode.EffectArray => LoadArrayAsset<Asset<Effect>>(member, attribute),
AssetMode.ArmorShaderArray => LoadArrayAsset<ArmorShaderData>(member, attribute),
AssetMode.MiscShaderArray => LoadArrayAsset<MiscShaderData>(member, attribute),
AssetMode.TextureValueArray => LoadArrayAsset<Texture2D>(member, attribute),
AssetMode.EffectValueArray => LoadArrayAsset<Effect>(member, attribute),
AssetMode.Custom => LoadCustomAsset(member, attribute),
AssetMode.CustomArray => LoadCustomArrayAsset(member, attribute),
_ => null,
};
}
/// <summary>
/// 使用自定义加载器加载资源
/// </summary>
private static object LoadCustomAsset(MemberInfo member, VaultLoadenAttribute attribute) {
Type memberType = member is FieldInfo field ? field.FieldType : (member as PropertyInfo)?.PropertyType;
if (memberType == null) {
return null;
}
var loader = VaultLoadenHandleManager.FindLoader(memberType);
if (loader == null) {
VaultMod.Instance.Logger.Warn($"No custom loader found for type {memberType} on member {member.Name}");
return null;
}
try {
return loader.HandleLoad(member, attribute);
} catch (Exception ex) {
VaultMod.Instance.Logger.Error($"Custom loader {loader.GetType().Name} failed to load {member.Name}: {ex.Message}");
return loader.GetDefaultValue(memberType);
}
}
/// <summary>
/// 使用自定义加载器加载数组资源
/// </summary>
private static object LoadCustomArrayAsset(MemberInfo member, VaultLoadenAttribute attribute) {
Type memberType = member is FieldInfo field ? field.FieldType : (member as PropertyInfo)?.PropertyType;
if (memberType == null) {
return null;
}
Type elementType = GetArrayElementType(memberType);
if (elementType == null) {
return null;
}
var loader = VaultLoadenHandleManager.FindArrayElementLoader(elementType);
if (loader == null) {
VaultMod.Instance.Logger.Warn($"No custom array loader found for element type {elementType} on member {member.Name}");
return null;
}
//获取当前集合的值以推断长度
object currentValue = null;
if (member is FieldInfo f) {
currentValue = f.GetValue(null);
}
else if (member is PropertyInfo p && p.CanRead) {
currentValue = p.GetValue(null);
}
int count = attribute.ArrayCount;
string origPath = attribute.Path;
if (count == 0 && currentValue != null) {
if (currentValue is Array arr) {
count = arr.Length;
}
else if (currentValue is System.Collections.ICollection col) {
count = col.Count;
}
}
//尝试自动探测资源数量
if (count == 0 && attribute.Mod != null) {
count = ProbeAssetCount(attribute.Mod, origPath, attribute.StartIndex);
}
if (count == 0) {
//返回空列表
var emptyList = (System.Collections.IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
return emptyList;
}
//创建列表并加载每个元素
var listType = typeof(List<>).MakeGenericType(elementType);
var resultList = (System.Collections.IList)Activator.CreateInstance(listType, count);
for (int i = attribute.StartIndex; i < attribute.StartIndex + count; i++) {
attribute.Path = origPath + i;
try {
var item = loader.HandleLoad(member, attribute);
resultList.Add(item);
} catch (Exception ex) {
VaultMod.Instance.Logger.Error($"Custom loader {loader.GetType().Name} failed to load array element {i} for {member.Name}: {ex.Message}");
resultList.Add(loader.GetDefaultValue(elementType));
}
}
//恢复原始路径
attribute.Path = origPath;
return resultList;
}
private static T LoadValue<T>(MemberInfo member, VaultLoadenAttribute attribute) => (T)LoadValue(member, attribute);
internal static IList<T> LoadArrayAsset<T>(MemberInfo member, VaultLoadenAttribute attribute) {
//取出当前集合的值
object currentValue = null;
bool isValue = false;
if (member is FieldInfo field) {
isValue = true;
currentValue = field.GetValue(null);
}
if (member is PropertyInfo prop && prop.CanWrite && prop.GetSetMethod(true) != null) {
isValue = true;
currentValue = prop.GetValue(null);
}
if (!isValue) {
return null;
}
int count = attribute.ArrayCount;
string origPath = attribute.Path;
if (count == 0) {
//首先尝试从已有集合推断长度
if (currentValue != null) {
count = currentValue switch {
T[] arrVal => arrVal.Length,
IList<T> listVal => listVal.Count,
_ => 0
};
}
//如果仍然为0则尝试自动探测资源文件数量
if (count == 0 && attribute.Mod != null) {
count = ProbeAssetCount(attribute.Mod, origPath, attribute.StartIndex);
if (count > 0) {
VaultMod.Instance.Logger.Debug($"Auto-probed {count} assets for {member.Name} at path: {attribute.Mod.Name}/{origPath}");
}
}
//如果仍然无法确定数量,返回空列表而非null
if (count == 0) {
VaultMod.Instance.Logger.Debug($"No assets found for {member.Name} at path: {attribute.Mod?.Name}/{origPath}, returning empty list.");
return new List<T>();
}
attribute.ArrayCount = count;
}
//按数量逐个加载
var newList = new List<T>(count);
AssetMode origAssetMode = attribute.AssetMode;
if (TypeToAssetModeMap.TryGetValue(typeof(T), out var assetMode)) {
attribute.AssetMode = assetMode;//进行集合类别的元素降级,防止无限迭代
}
for (int i = attribute.StartIndex; i < attribute.StartIndex + count; i++) {
attribute.Path = origPath + i;
newList.Add(LoadValue<T>(member, attribute));//这里如果处理不当会触发死循环,前面的orig参数用于避免这种情况
}
//恢复
attribute.Path = origPath;
attribute.AssetMode = origAssetMode;
return newList;
}
internal static void LoadMember(MemberInfo member, VaultLoadenAttribute attribute) {
Type valueType = member is FieldInfo field ? field.FieldType : (member as PropertyInfo)?.PropertyType;
if (valueType == null) {
return;
}
if (member is PropertyInfo prop && (!prop.CanWrite || prop.GetSetMethod(true) == null)) {//对于属性需要检测其是否可写
VaultMod.Instance.Logger.Error($"Property {member.Name} is marked with VaultLoadenAttribute but has no setter.");
return;
}
if (attribute.Mod == null) {//如果模组对象为null(例如外部模组未启用),使用默认值
VaultMod.Instance.Logger.Warn($"{member.MemberType} {member.Name} from Mod is Null, using default value instead.");
//尝试为成员设置默认值
object defaultValue = GetDefaultValue(valueType);
if (member is FieldInfo fieldInfo) {
fieldInfo.SetValue(null, defaultValue);
}
else if (member is PropertyInfo propInfo) {
propInfo.SetValue(null, defaultValue);
}
return;
}
if (attribute.AssetMode == AssetMode.None) {//自动指定资源类型
attribute.AssetMode = GetAttributeAssetMode(valueType);
}
if (attribute.AssetMode == AssetMode.None) {//第二次检测,如果还是None就跳过
VaultMod.Instance.Logger.Warn($"Cannot determine asset mode for {member.Name} of type {valueType}. Skipped.");
return;
}
object value = LoadValue(member, attribute);
if (valueType.IsArray && value is System.Collections.IList list) {//IList<T>类型不能直接赋值给T[],所以这里添加一个特判,对数组进行额外的转换处理
var array = Array.CreateInstance(valueType.GetElementType(), list.Count);
list.CopyTo(array, 0);
value = array;
}
if (member is FieldInfo fieldInfo2) {
fieldInfo2.SetValue(null, value);
}
else if (member is PropertyInfo propInfo2) {
propInfo2.SetValue(null, value);
}
}
/// <summary>
/// 获取指定类型的默认值
/// </summary>
/// <param name="type">目标类型</param>
/// <returns>该类型的默认值</returns>
private static object GetDefaultValue(Type type) {
if (type == typeof(Texture2D)) {
return VaultAsset.placeholder3.Value;
}
else if (type == typeof(Asset<Texture2D>)) {
return VaultAsset.placeholder3;
}
else if (type == typeof(IList<Texture2D>)) {
return new List<Texture2D> { VaultAsset.placeholder3.Value };
}
else if (type == typeof(IList<Asset<Texture2D>>)) {
return new List<Asset<Texture2D>> { VaultAsset.placeholder3 };
}
//尝试从自定义加载器获取默认值
var customLoader = VaultLoadenHandleManager.FindLoader(type);
if (customLoader != null) {
return customLoader.GetDefaultValue(type);
}
//检查数组元素的自定义加载器
Type elementType = GetArrayElementType(type);
if (elementType != null) {
var arrayLoader = VaultLoadenHandleManager.FindArrayElementLoader(elementType);
if (arrayLoader != null) {
var listType = typeof(List<>).MakeGenericType(elementType);
return Activator.CreateInstance(listType);
}
}
if (type.IsValueType) {
return Activator.CreateInstance(type);
}
return null;
}
internal static Asset<Effect> LoadEffect(VaultLoadenAttribute attribute, AssetRequestMode requestMode = AssetRequestMode.AsyncLoad) {
Asset<Effect> asset = attribute.Mod.Assets.Request<Effect>(attribute.Path, requestMode);
string effectName = attribute.Path.Split('/')[^1];
string effectKey = attribute.Mod.Name + ":" + effectName;
if (string.IsNullOrEmpty(attribute.EffectPassname)) {
attribute.EffectPassname = effectName + "Pass";
}
if (Filters.Scene[effectKey] == null) {
Filters.Scene[effectKey] = new Filter(new ScreenShaderData(asset, attribute.EffectPassname), EffectPriority.VeryHigh);
}
return asset;
}
internal static Effect LoadEffectValue(VaultLoadenAttribute attribute) => LoadEffect(attribute, AssetRequestMode.ImmediateLoad).Value;
internal static MiscShaderData LoadMiscShader(VaultLoadenAttribute attribute) {
MiscShaderData miscShader = new MiscShaderData(LoadEffect(attribute), attribute.EffectPassname);
string effectName = attribute.Path.Split('/')[^1];
string effectKey = attribute.Mod.Name + ":" + effectName;
if (!GameShaders.Misc.TryGetValue(effectKey, out var value) || value == null) {
GameShaders.Misc[effectKey] = miscShader;
}
return miscShader;
}
internal static Asset<Texture2D> LoadTexture(VaultLoadenAttribute attribute, AssetRequestMode assetRequestMode = AssetRequestMode.AsyncLoad) {
if (attribute.Mod == null) {
return VaultAsset.placeholder3;
}
if (!attribute.Mod.HasAsset(attribute.Path)) {
VaultMod.Instance.Logger.Warn($"Texture asset not found: {attribute.Mod.Name}/{attribute.Path}. Using placeholder instead.");
return VaultAsset.placeholder3;
}
return attribute.Mod.Assets.Request<Texture2D>(attribute.Path, assetRequestMode);
}
internal static Texture2D LoadTextureValue(VaultLoadenAttribute attribute) => LoadTexture(attribute, AssetRequestMode.ImmediateLoad).Value;
/// <summary>
/// 自动探测指定路径下存在多少个连续编号的资源文件
/// 从startIndex开始迭代检测,直到找不到下一个序号的资源为止
/// </summary>
/// <param name="mod">目标模组实例</param>
/// <param name="basePath">资源基础路径(不含序号后缀)</param>
/// <param name="startIndex">起始序号,默认为0</param>
/// <param name="maxProbe">最大探测数量上限,防止无限循环,默认为1000</param>
/// <returns>探测到的资源文件数量</returns>
internal static int ProbeAssetCount(Mod mod, string basePath, int startIndex = 0, int maxProbe = 1000) {
if (mod == null || string.IsNullOrEmpty(basePath)) {
return 0;
}
int count = 0;
for (int i = startIndex; i < startIndex + maxProbe; i++) {
string probePath = basePath + i;
if (mod.HasAsset(probePath)) {
count++;
}
else {
//遇到第一个不存在的资源就停止探测
break;
}
}
return count;
}
/// <summary>
/// 检测指定路径的资源是否存在
/// </summary>
/// <param name="mod">目标模组实例</param>
/// <param name="path">资源路径</param>
/// <returns>资源是否存在</returns>
internal static bool HasAssetSafe(Mod mod, string path) {
if (mod == null || string.IsNullOrEmpty(path)) {
return false;
}
return mod.HasAsset(path);
}
/// <summary>
/// 检查类级路径,确保路径正确并替换命名空间等占位符
/// </summary>
/// <param name="type">类类型</param>
/// <param name="attribute">类级别的 VaultLoadenAttribute</param>
private static void CheckClassAttributePath(Type type, VaultLoadenAttribute attribute) {
//复用 VaultLoad 的路径检查逻辑,但不追加成员名
if (!string.IsNullOrEmpty(type.Namespace)) {
string namespacePath = type.Namespace.Replace('.', '/');
attribute.Path = attribute.Path.Replace("{@namespace}", namespacePath);
}
string[] pathParts = attribute.Path.Split('/');
if (pathParts.Length == 0) {
throw new Exception($"Attribute path on class {type.FullName} is empty or invalid: \"{attribute.Path}\"");
}
if (attribute.Path.StartsWith('@')) {
pathParts[0] = pathParts[0][1..]; //去掉@
if (ModLoader.TryGetMod(pathParts[0], out Mod newMod)) {
attribute.Mod = newMod;
}
else {
string modName = attribute.Mod != null ? attribute.Mod.Name : pathParts[0];
//改为记录调试日志而非抛出异常,支持弱联动
VaultMod.Instance.Logger.Debug($"Class {type.FullName} couldn't find Mod \"{pathParts[0]}\". Original Mod Name: \"{modName}\". " +
$"Class resources will use default values instead.");
//将资源对象设置为null,后续会使用默认值
attribute.Mod = null;
return;
}
}
if (pathParts[0] == attribute.Mod.Name) {
attribute.Path = string.Join("/", pathParts.Skip(1));
}
}
/// <summary>
/// 处理类中的单个静态成员的资源加载或卸载
/// </summary>
/// <param name="member">要处理的成员</param>
/// <param name="type">成员所在的类</param>
/// <param name="classAttribute">类级别的<see cref="VaultLoadenAttribute"/></param>
/// <param name="load">true 表示加载,false 表示卸载</param>
private static void ProcessClassMemberAsset(MemberInfo member, Type type, VaultLoadenAttribute classAttribute, bool load) {
VaultLoadenAttribute memberAttribute = VaultUtils.GetAttributeSafely<VaultLoadenAttribute>(member, (phase, ex) => {
VaultMod.Instance.Logger.Warn($"Skipped {member.MemberType.ToString().ToLower()} {member.Name} " +
$"in class {type.FullName} due to {phase} load error: {ex.Message}");
}
);
if (memberAttribute != null) {
//成员有自己的 VaultLoadenAttribute,直接跳过
return;
}
//使用类级别的 VaultLoadenAttribute
Type valueType = member is FieldInfo field ? field.FieldType : (member as PropertyInfo)?.PropertyType;
if (valueType == null) {
return;
}
//仅处理支持的资源类型
AssetMode assetMode = GetAttributeAssetMode(valueType);
if (assetMode == AssetMode.None) {
//VaultMod.Instance.Logger.Warn($"Cannot determine asset mode for {member.Name} of type {valueType} in class {type.FullName}. Skipped.");
return;
}
//使用成员名称构造资源路径
string memberName = member.Name;
//使用成员名称构造资源路径
string memberPath;
if (classAttribute.PathConcatenation) {
//启用路径扩展,成员名按下划线拆分,作为子目录
var segments = member.Name.Split('_', StringSplitOptions.RemoveEmptyEntries);
memberPath = classAttribute.Path.TrimEnd('/');
foreach (var seg in segments) {
memberPath += "/" + seg;
}
}
else {
//默认规则直接拼接成员名
memberPath = classAttribute.Path.EndsWith('/') ? classAttribute.Path + memberName : $"{classAttribute.Path}/{memberName}";
}
//创建临时的 VaultLoadenAttribute 用于加载
var tempAttribute = new VaultLoadenAttribute(memberPath, assetMode, classAttribute.EffectPassname) {
Mod = classAttribute.Mod
};
if (load) {
//加载资源
CheckAttributePath(type, memberName, tempAttribute);
//VaultMod.Instance.Logger.Debug($"CheckAttributePath set TempAttribute resource path for {member.MemberType} : {tempAttribute.Path}");
LoadMember(member, tempAttribute);
}
else {
//卸载资源
UnloadMember(member);
}
}
private static void ProcessClassMemberPassInto(MemberInfo member, Type type, VaultLoadenAttribute classAttribute, bool load) {
if (member is FieldInfo field && field.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false)) {
//自动实现的属性会生成一个隐形的字段,而GetFields会将这个字段也找出来,导致错误的加载现象
//在针对成员自身的标签加载中可以因为标签识别而自动避开这个错误,但类级别标签的加载中却不行
//因为隐藏字段都属于编译器自行生成,有CompilerGeneratedAttribute标签
//所以在这里添加一个检测跳过这些隐藏字段
return;
}
Type memberType = member is FieldInfo f ? f.FieldType : (member as PropertyInfo)?.PropertyType;
if (memberType == null) {
return;
}
//检查 AssetMode(如果类级别指定了 AssetMode)
if (classAttribute.AssetMode != AssetMode.None) {
VaultLoadenAttribute memberAttribute = VaultUtils.GetAttributeSafely<VaultLoadenAttribute>(member, (phase, ex) => {
VaultMod.Instance.Logger.Warn($"Skipped {member.MemberType.ToString().ToLower()} {member.Name} " +
$"in class {type.FullName} due to {phase} load error: {ex.Message}");
}
);
if (GetAttributeAssetMode(memberType) != classAttribute.AssetMode) {
//VaultMod.Instance.Logger.Debug($"Skipped {member.MemberType} {member.Name} in class {type.FullName} due to mismatched AssetMode");
return;
}
}
//处理成员
ProcessClassMemberAsset(member, type, classAttribute, load);
}
/// <summary>
/// 处理单个类型的类级别资源加载或卸载
/// </summary>
/// <param name="type">要处理的类型</param>
/// <param name="load">true 表示加载,false 表示卸载</param>
private static void ProcessClassAssets(Type type, bool load) {
//检查类上是否有 VaultLoadenAttribute
VaultLoadenAttribute classAttribute = VaultUtils.GetAttributeSafely<VaultLoadenAttribute>(type, (phase, ex) => {
VaultMod.Instance.Logger.Warn($"Skipped class {type.FullName} due to {phase} load error: {ex.Message}");
}
);
if (classAttribute == null) {
return; //类上没有 VaultLoadenAttribute,跳过
}
//查找模组
if (!FindattributeByMod(type, classAttribute)) {
VaultMod.Instance.Logger.Warn($"Cannot find mod for class {type.FullName}. Skipped.");
return;
}
ProcessClassAssetsWithAttribute(type, classAttribute, load);
}
/// <summary>
/// 处理类及其成员
/// </summary>
private static void ProcessClassAssetsWithAttribute(Type type, VaultLoadenAttribute attribute, bool load) {
//避免在扫描一些使用了动态代码生成或者IL源码注入的模组时出现无限递归调用
//这种情况有可能出现吗?首先得标记了VaultLoaden,才能进入这里的处理,然后还得出现动态生成的自循环互相嵌套类
//如果真的发生了那种事,这行代码就会起作用,不管如何,我Fuck可能会这样干的混蛋
if (!ProcessedTypes.Add(type)) {
return;//已处理过,跳过
}
//类路径校验(只有 load 阶段做)
if (load) {
CheckClassAttributePath(type, attribute);
}
BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
//处理字段
foreach (var field in type.GetFields(flags)) {
ProcessClassMemberPassInto(field, type, attribute, load);
}
//处理属性
foreach (var property in type.GetProperties(flags)) {
ProcessClassMemberPassInto(property, type, attribute, load);
}
flags = BindingFlags.NonPublic | BindingFlags.Public;
//递归处理嵌套类
foreach (var nestedType in type.GetNestedTypes(flags)) {
//检查嵌套类上是否有 VaultLoadenAttribute
VaultLoadenAttribute subClassAttribute = VaultUtils.GetAttributeSafely<VaultLoadenAttribute>(nestedType, (phase, ex) => {
VaultMod.Instance.Logger.Warn($"Skipped nested class {nestedType.FullName} due to {phase} load error: {ex.Message}");
});
if (subClassAttribute != null) {
continue; //嵌套类如果有自己的 VaultLoadenAttribute,就交给独立流程,不要在递归嵌套类流程里处理
}
//继承外层类的 attribute,并拼接路径
var nestedAttr = new VaultLoadenAttribute(
CombinePath(attribute.Path, nestedType.Name),
attribute.AssetMode,
attribute.EffectPassname,
attribute.StartIndex,
attribute.ArrayCount,
attribute.PathConcatenation,
attribute.Mod
);
ProcessClassAssetsWithAttribute(nestedType, nestedAttr, load);
}
}
private static string CombinePath(string basePath, string nestedName) {
if (string.IsNullOrEmpty(basePath)) {
return nestedName + "/";
}
//确保 basePath 末尾只有一个 "/"
if (!basePath.EndsWith('/')) {
basePath += "/";
}
return basePath + nestedName + "/";
}
}
}