Survivalcraft API 1.8.2.3 v1.8.2.3
Survivalcraft 2.4
载入中...
搜索中...
未找到
ModsManager.cs
浏览该文件的文档.
1// Game.ModsManager
2
3using System.Collections.Frozen;
4using System.Reflection;
5using System.Security.Cryptography;
6using System.Text;
7using System.Text.Json;
8using System.Xml.Linq;
9using Engine;
11using Game;
13using NuGet.Versioning;
14using XmlUtilities;
16#if DEBUG
17using System.IO.Compression;
18#endif
19public static class ModsManager {
20 public static string ModSuffix = ".scmod";
21 public static string APIVersionString = "1.8.2.3";
22 public static string ShortAPIVersionString = "1.8";
23 public static NuGetVersion APINuGetVersion = new(1, 8, 2, 3);
24 public static string GameVersion = "2.4.0.0";
25 public static string ShortGameVersion = "2.4";
26 public static string ReportLink = "https://gitee.com/SC-SPM/SurvivalcraftApi/issues";
27 public static string APILatestReleaseLink_API = "https://gitee.com/api/v5/repos/SC-SPM/SurvivalcraftApi/releases/latest";
28 public static string APILatestReleaseLink_Client = "https://gitee.com/SC-SPM/SurvivalcraftApi/releases/latest";
29 public static string APIReleasesLink_API = "https://gitee.com/api/v5/repos/SC-SPM/SurvivalcraftApi/releases";
30 public static string APIReleasesLink_Client = "https://gitee.com/SC-SPM/SurvivalcraftApi/releases/";
31 public static string fName = "ModsManager";
32
33 [Obsolete("使用ApiVersionString")]
34 public enum ApiVersionEnum //不准确,弃用
35 {
39 }
40
41 [Obsolete("使用ApiVersionString")] public const ApiVersionEnum ApiVersion = ApiVersionEnum.Version180;
42
43#if !ANDROID
44 public static string ExternalPath => "app:";
45 public static string DocPath = "app:/doc";
46 public static string WorldsDirectoryName = $"{DocPath}/Worlds";
47#endif
48#if ANDROID
49 public static string ExternalPath => EngineActivity.BasePath;
50 public static string DocPath = EngineActivity.BasePath;
51 public static string WorldsDirectoryName = $"{ExternalPath}/Worlds";
52#endif
53 public static string ProcessModListPath = $"{ExternalPath}/ProcessModLists";
54
55 public static string ScreenCapturePath { get; } = $"{ExternalPath}/ScreenCapture";
56
57 public static string UserDataPath { get; } = $"{DocPath}/UserId.dat";
58 public static string CharacterSkinsDirectoryName { get; } = $"{DocPath}/CharacterSkins";
59 public static string FurniturePacksDirectoryName { get; } = $"{DocPath}/FurniturePacks";
60
61 public static string BlockTexturesDirectoryName { get; } = $"{DocPath}/TexturePacks";
62 public static string CommunityContentCachePath { get; } = $"{DocPath}/CommunityContentCache.xml";
63 public static string OriginalCommunityContentCachePath { get; } = $"{DocPath}/OriginalCommunityContentCache.xml";
64 public static string ModsSettingsPath { get; } = $"{DocPath}/ModSettings.xml";
65 public static string SettingPath { get; } = $"{DocPath}/Settings.xml";
66 public static string ConfigsPath { get; } = $"{DocPath}/Configs.xml";
67 public static string LogPath { get; } = $"{ExternalPath}/Bugs";
68 public static string ModsPath = $"{ExternalPath}/Mods";
69 public static bool IsAndroid => OperatingSystem.IsAndroid();
70 //public static bool IsAndroid => VersionsManager.Platform == Platform.Android;
71
73 internal static bool ConfigLoaded;
74
75 public class ModSettings {
76 public string languageType = string.Empty;
77 }
78
79 public class ModHook(string name) {
80 public string HookName = name;
81 public Dictionary<ModLoader, bool> Loaders = [];
82 public Dictionary<ModLoader, string> DisableReason = [];
83
84 public void Add(ModLoader modLoader) {
85 if (!Loaders.TryGetValue(modLoader, out _)) {
86 Loaders.Add(modLoader, true);
87 }
88 }
89
90 public void Remove(ModLoader modLoader) {
91 Loaders.Remove(modLoader, out _);
92 }
93
94 public void Disable(ModLoader from, ModLoader toDisable, string reason) {
95 if (Loaders.TryGetValue(toDisable, out _)) {
96 if (!DisableReason.TryGetValue(from, out _)) {
97 DisableReason.Add(from, reason);
98 }
99 }
100 }
101 }
102
103 static bool AllowContinue = true;
104 public static Dictionary<string, string> Configs = [];
108 public static List<ModEntity> ModListAll = [];
112 public static List<ModEntity> ModList = [];
116 public static Dictionary<string, ModEntity> PackageNameToModEntity = [];
117 public static List<ModLoader> ModLoaders = [];
118
119 //仅手动禁用的
120 public static Dictionary<string, HashSet<string>> DisabledMods = [];
121
122 public static Dictionary<string, ModHook> ModHooks = [];
123 public static Dictionary<string, Assembly> Dlls = [];
124
125 public static bool GetModEntity(string packagename, out ModEntity modEntity) {
126 modEntity = ModList.Find(px => px.modInfo.PackageName == packagename);
127 return modEntity != null;
128 }
129
130 public static bool GetAllowContinue() => AllowContinue;
131
132 internal static void Reboot() {
135 foreach (ModEntity mod in ModList) {
136 mod.Dispose();
137 }
138 ScreensManager.SwitchScreen("Loading");
139 }
140
146 public static void HookAction(string HookName, Func<ModLoader, bool> action) //按先加载→后加载模组(先主题模组后辅助模组的顺序)执行
147 {
148 if (ModHooks.TryGetValue(HookName, out ModHook modHook)) {
149 foreach (ModLoader modLoader in modHook.Loaders.Keys) {
150 if (TryInvoke(modHook, modLoader, action)) {
151 break;
152 }
153 }
154 }
155 }
156
157 public static void HookActionReverse(string HookName, Func<ModLoader, bool> action) //按后加载→先加载模组(先辅助模组后主题模组的顺序)执行
158 {
159 if (ModHooks.TryGetValue(HookName, out ModHook modHook)) {
160 foreach (ModLoader modLoader in modHook.Loaders.Keys.Reverse()) {
161 if (TryInvoke(modHook, modLoader, action)) {
162 break;
163 }
164 }
165 }
166 }
167
168 public static Dictionary<KeyValuePair<ModHook, ModLoader>, bool> m_hookBugLogged = [];
169
170 public static bool TryInvoke(ModHook modHook, ModLoader modLoader, Func<ModLoader, bool> action) {
171 try {
172 if (action.Invoke(modLoader)) {
173 return true;
174 }
175 return false;
176 }
177 catch (Exception ex) {
178 KeyValuePair<ModHook, ModLoader> keyValuePair = new(modHook, modLoader);
179 if (!m_hookBugLogged.GetValueOrDefault(keyValuePair, false)) {
180 Log.Error(ex);
181 }
182 m_hookBugLogged[keyValuePair] = true;
183 return false;
184 }
185 }
186
192 public static void RegisterHook(string HookName, ModLoader modLoader) {
193 if (!ModHooks.TryGetValue(HookName, out ModHook modHook)) {
194 modHook = new ModHook(HookName);
195 ModHooks.Add(HookName, modHook);
196 }
197 modHook.Add(modLoader);
198 }
199
200 public static void DisableHook(ModLoader from, string HookName, string packageName, string reason) {
201 ModEntity modEntity = ModList.Find(p => p.modInfo.PackageName == packageName);
202 if (modEntity != null
203 && ModHooks.TryGetValue(HookName, out ModHook modHook)) {
204 modHook.Disable(from, modEntity.Loader, reason);
205 }
206 }
207
208 public static T GetInPakOrStorageFile<T>(string filePath, string suffix = "txt") where T : class =>
209 //string storagePath = Storage.CombinePaths(ExternelPath, filepath + prefix);
210 ContentManager.Get<T>(filePath, suffix);
211
212 public static ModInfo DeserializeJson(string json) {
213 ModInfo modInfo = new();
214 JsonElement jsonElement = JsonDocument.Parse(json, JsonDocumentReader.DefaultJsonOptions).RootElement;
215 if (jsonElement.TryGetProperty("Name", out JsonElement name)) {
216 modInfo.Name = name.GetString();
217 }
218 if (jsonElement.TryGetProperty("Version", out JsonElement version)
219 && version.ValueKind == JsonValueKind.String) {
220 modInfo.Version = version.GetString()?.Trim();
221 if (modInfo.Version != null) {
222 NuGetVersion.TryParse(modInfo.Version, out modInfo.NuGetVersion);
223 }
224 }
225 if (jsonElement.TryGetProperty("ApiVersion", out JsonElement apiVersion)
226 && apiVersion.ValueKind == JsonValueKind.String) {
227 string apiVersionString = apiVersion.GetString()?.Trim();
228 modInfo.ApiVersion = apiVersionString;
229 if (apiVersionString == "1.80") {
230 apiVersionString = "1.8";
231 }
232 else if (apiVersionString == "1.81") {
233 apiVersionString = "1.8.1";
234 }
235 TryParseVersionRange(apiVersionString, out modInfo.ApiVersionRange);
236 }
237 if (jsonElement.TryGetProperty("Description", out JsonElement description)
238 && description.ValueKind == JsonValueKind.String) {
239 modInfo.Description = description.GetString();
240 }
241 if (jsonElement.TryGetProperty("ScVersion", out JsonElement scVersion)
242 && scVersion.ValueKind == JsonValueKind.String) {
243 modInfo.ScVersion = scVersion.GetString();
244 }
245 if (jsonElement.TryGetProperty("Link", out JsonElement link)
246 && link.ValueKind == JsonValueKind.String) {
247 modInfo.Link = link.GetString();
248 }
249 if (jsonElement.TryGetProperty("Author", out JsonElement author)
250 && author.ValueKind == JsonValueKind.String) {
251 modInfo.Author = author.GetString();
252 }
253 if (jsonElement.TryGetProperty("PackageName", out JsonElement packageName)
254 && packageName.ValueKind == JsonValueKind.String) {
255 modInfo.PackageName = packageName.GetString();
256 }
257 /*if (jsonElement.TryGetProperty("Email", out JsonElement Email) && Email.ValueKind == JsonValueKind.String)
258 {
259 modInfo.Email = packageName.GetString();
260 }*/
261 if (jsonElement.TryGetProperty("Dependencies", out JsonElement dependencies)) {
262 if (dependencies.ValueKind == JsonValueKind.Array) {
263 modInfo.Dependencies = dependencies.EnumerateArray()
264 .Where(dependency => dependency.ValueKind == JsonValueKind.String)
265 .Select(dependency => dependency.GetString())
266 .ToList();
267 foreach (string dependency in modInfo.Dependencies) {
268 int index = dependency.IndexOf(':');
269 if (index != -1) {
270 string dependencyPackageName = dependency.Substring(0, index);
271 string dependencyVersion = dependency.Substring(index + 1).Trim();
272 if (TryParseVersionRange(dependencyVersion, out VersionRange dependencyVersionRange)) {
273 modInfo.DependencyRanges.Add(dependencyPackageName, dependencyVersionRange);
274 }
275 }
276 else {
277 modInfo.DependencyRanges.Add(dependency, VersionRange.All);
278 }
279 }
280 }
281 else if (dependencies.ValueKind == JsonValueKind.Object) {
282 foreach (JsonProperty dependency in dependencies.EnumerateObject()) {
283 if (dependency.Value.ValueKind == JsonValueKind.String) {
284 string dependencyPackageName = dependency.Name;
285 string dependencyVersion = dependency.Value.GetString()?.Trim();
286 if (TryParseVersionRange(dependencyVersion, out VersionRange dependencyVersionRange)) {
287 modInfo.DependencyRanges.Add(dependencyPackageName, dependencyVersionRange);
288 }
289 }
290 }
291 }
292 }
293 if (jsonElement.TryGetProperty("LoadOrder", out JsonElement loadOrder)
294 && loadOrder.ValueKind == JsonValueKind.Number) {
295 modInfo.LoadOrder = loadOrder.GetInt32();
296 //Log.Information("获取模组的Order:" + modInfo.LoadOrder);
297 }
298 if (jsonElement.TryGetProperty("NonPersistentMod", out JsonElement nonPersistentMod)
299 && nonPersistentMod.ValueKind == JsonValueKind.True) {
300 modInfo.NonPersistentMod = true;
301 }
302 return modInfo;
303 }
304
305 public static void SaveModSettings(XElement xElement) {
306 foreach (ModEntity modEntity in ModList) {
307 modEntity.SaveSettings(xElement);
308 }
309 }
310
311 public static void SaveConfigs() {
312 XElement element = new("Configs");
313 foreach (KeyValuePair<string, string> c in Configs) {
314 element.SetAttributeValue(c.Key, c.Value);
315 }
316 using (Stream stream = Storage.OpenFile(ConfigsPath, OpenFileMode.Create)) {
317 XmlUtils.SaveXmlToStream(element, stream, Encoding.UTF8, true);
318 }
319 }
320
321 public static void LoadConfigs() {
322 //加载Config
323 try {
325 using (Stream stream = Storage.OpenFile(ConfigsPath, OpenFileMode.Read)) {
326 XElement xElement = XmlUtils.LoadXmlFromStream(stream, null, true);
327 LoadConfigsFromXml(xElement);
328 }
329 }
330 }
331 catch (Exception e) {
332 Log.Error($"Load configs failed. Reason: {e}");
333 ConfigLoaded = false;
334 }
335 }
336
337 public static void LoadConfigsFromXml(XElement xElement) {
338 try {
339 if (xElement.Name != "Configs") {
340 return;
341 }
342 foreach (XAttribute c in xElement.Attributes()) {
343 if (!Configs.ContainsKey(c.Name.LocalName)) {
344 SetConfig(c.Name.LocalName, c.Value);
345 }
346 }
347 ConfigLoaded = true;
348 }
349 catch (Exception e) {
350 Log.Error($"Load configs failed. Reason: {e}");
351 ConfigLoaded = false;
352 }
353 }
354
355 public static void LoadModSettings(XElement xElement) {
356 foreach (ModEntity modEntity in ModList) {
357 modEntity.LoadSettings(xElement);
358 }
359 }
360
361 public static void SetConfig(string key, string value) {
362 if (!Configs.TryAdd(key, value)) {
363 Configs[key] = value;
364 }
365 }
366
367 public static string ImportMod(string name, Stream stream) {
370 }
373 }
374 string realName = name;
375 if (!realName.EndsWith(ModSuffix)) {
376 realName = realName + ModSuffix;
377 }
378 string nameWithoutSuffix = Storage.GetFileNameWithoutExtension(realName);
379 string path = Storage.CombinePaths(ModsPath, realName);
380 int num = 1;
381 while (Storage.FileExists(path)) {
382 realName = $"{nameWithoutSuffix}({num}){ModSuffix}";
383 path = Storage.CombinePaths(ModsPath, realName);
384 num++;
385 }
386 using (Stream fileStream = Storage.OpenFile(path, OpenFileMode.CreateOrOpen)) {
387 stream.CopyTo(fileStream);
388 }
389 return realName;
390 }
391
392 public static void ModListAllDo(Action<ModEntity> entity) {
393 for (int i = 0; i < ModList.Count; i++) {
394 entity?.Invoke(ModList[i]);
395 }
396 }
397
398 public static void Initialize() {
401 }
402 ModHooks.Clear();
403 ModListAll.Clear();
404 ModList.Clear();
406 ModLoaders.Clear();
409#if !BROWSER
411 return;
412 }
413 ModEntity FastDebug = new FastDebugModEntity();
414 ModListAll.Add(FastDebug);
416 ModListAll.Sort((x, y) =>
417 (x.IsDisabled ? int.MaxValue : x.modInfo?.LoadOrder ?? int.MaxValue).CompareTo(
418 y.IsDisabled ? int.MaxValue : y.modInfo?.LoadOrder ?? int.MaxValue
419 )
420 );
421 //float api = float.Parse(APIVersion);
422 //读取SCMOD文件到ModListAll列表
423 foreach (ModEntity modEntity1 in ModListAll) {
424 if (modEntity1.IsDisabled) {
425 continue;
426 }
427 string packageName = modEntity1.modInfo?.PackageName;
428 if (packageName == null) {
429 continue;
430 }
431 //ModInfo disabledmod = ToDisable.Find(l => l.PackageName == modInfo.PackageName);
432 //if (disabledmod != null && disabledmod.PackageName != SurvivalCraftModEntity.modInfo.PackageName && disabledmod.PackageName != FastDebug.modInfo.PackageName)
433 //{
434 // ToDisable.Add(modInfo);
435 // ToRemove.Add(modEntity1);
436 // continue;
437 //}
438 //float.TryParse(modInfo.ApiVersionString, out float curr);
439 //if (curr < api)
440 //{//api版本检测
441 // ToDisable.Add(modInfo);
442 // ToRemove.Add(modEntity1);
443 // AddException(new Exception($"[{modEntity1.modInfo.PackageName}]Target version {modInfo.Version} is less than api version {APIVersion}."), true);
444 //}
445 List<ModEntity> modEntities = ModListAll.FindAll(px => !px.IsDisabled && px.modInfo?.PackageName == packageName);
446 if (modEntities.Count > 1) {
447 modEntity1.IsDisabled = true;
448 modEntity1.DisableReason = ModDisableReason.Duplicated;
449 AddException(new Exception($"Multiple mods with PackageName [{packageName}], please keep only one."));
450 }
451 }
452 AppDomain.CurrentDomain.AssemblyResolve += (_, args) => {
453 try {
454#nullable enable
455 Assembly? assembly = Dlls.GetValueOrDefault(args.Name)
456 ?? TypeCache.LoadedAssemblies.FirstOrDefault(asm => asm.GetName().FullName == args.Name);
457 return assembly;
458#nullable disable
459 }
460 catch (Exception e) {
461 Log.Error($"Load assembly [{args.Name}] failed:{e}");
462 Log.Debug(e);
463 throw;
464 }
465 };
466#endif
467 }
468
469 public static void AddException(Exception e, bool AllowContinue_ = false) {
470 LoadingScreen.Error(e.ToString());
471 Log.Error(e);
472 AllowContinue = !SettingsManager.DisplayLog || AllowContinue_;
473 }
474
479 public static void GetScmods(string path) {
480 foreach (string item in Storage.ListFileNames(path)) {
481 string ms = Storage.GetExtension(item).ToLowerInvariant();
482 string ks = Storage.CombinePaths(path, item);
483 using Stream stream = Storage.OpenFile(ks, OpenFileMode.Read);
484 try {
485 if (ms == ModSuffix) {
486 Stream keepOpenStream = GetDecipherStream(stream);
487 ModEntity modEntity = new(ks, ZipArchive.Open(keepOpenStream, true));
488 if (modEntity.modInfo == null) {
489 LoadingScreen.Warning($"The modinfo.json is missing or broken from [{Storage.GetFileName(modEntity.ModFilePath)}], and this mod will be disabled.");
490 }
491 else if (modEntity.IsDisabled && modEntity.DisableReason == ModDisableReason.InvalidPackageName) {
492 LoadingScreen.Warning($"The package name [{modEntity.modInfo.PackageName}] of [{Storage.GetFileName(modEntity.ModFilePath)}] is not allowed, and this mod will not be loaded.");
493 continue;
494 }
495 ModListAll.Add(modEntity);
496 }
497 }
498 catch (Exception e) {
499 AddException(e);
500 stream.Close();
501 }
502 }
503 foreach (string dir in Storage.ListDirectoryNames(path)) {
504 GetScmods(Storage.CombinePaths(path, dir));
505 }
506 }
507
508 public static string StreamToString(Stream stream) {
509 stream.Seek(0, SeekOrigin.Begin);
510 return new StreamReader(stream).ReadToEnd();
511 }
512
516 public static byte[] StreamToBytes(Stream stream) {
517 byte[] bytes = new byte[stream.Length];
518 stream.Seek(0, SeekOrigin.Begin);
519 stream.ReadExactly(bytes);
520 // 设置当前流的位置为流的开始
521 return bytes;
522 }
523
524 [Obsolete("Use GetSha256 instead.")]
525 public static string GetMd5(string input) {
526#if BROWSER
527 throw new NotSupportedException("MD5 is not supported on browser. Use GetSha256 instead.");
528#else
529 byte[] data = MD5.HashData(Encoding.Default.GetBytes(input));
530 StringBuilder sBuilder = new();
531 for (int i = 0; i < data.Length; i++) {
532 sBuilder.Append(data[i].ToString("x2"));
533 }
534 return sBuilder.ToString();
535#endif
536 }
537
538 public static string GetSha256(string input) {
539 byte[] data = SHA256.HashData(Encoding.Default.GetBytes(input));
540 StringBuilder sBuilder = new();
541 for (int i = 0; i < data.Length; i++) {
542 sBuilder.Append(data[i].ToString("x2"));
543 }
544 return sBuilder.ToString();
545 }
546
547 public static bool FindElement(XElement xElement, Func<XElement, bool> func, out XElement elementout) {
548 elementout = xElement.Descendants().FirstOrDefault(func);
549 return elementout != null;
550 }
551
552 public static bool FindElementByGuid(XElement xElement, string guid, out XElement elementout) {
553 elementout = xElement.Descendants().FirstOrDefault(e => e.Attribute("Guid")?.Value == guid);
554 return elementout != null;
555 }
556
557 public static bool HasAttribute(XElement element, Func<string, bool> func, out XAttribute xAttributeout) {
558 xAttributeout = element.Attributes()
559 .FirstOrDefault(a => func(a.Name.LocalName));
560 return xAttributeout != null;
561 }
562
563 public static void CombineClo(XElement clothesRoot, Stream toCombineStream) {
564 XElement toCombineRoot = XmlUtils.LoadXmlFromStream(toCombineStream, Encoding.UTF8, true);
565 foreach (XElement element in toCombineRoot.Elements()) {
566 string indexValue = element.Attribute("Index")?.Value;
567 if (indexValue == null) {
568 clothesRoot.Add(toCombineRoot);
569 continue;
570 }
571 List<XAttribute> newAttributes = [];
572 foreach (XAttribute attribute in element.Attributes()) {
573 if (attribute.Name.LocalName.StartsWith("new-")) {
574 newAttributes.Add(attribute);
575 }
576 }
577 if (newAttributes.Count > 0
578 && FindElement(clothesRoot, e => e.Attribute("Index")?.Value == indexValue, out XElement element1)) {
579 foreach (XAttribute newAttribute in newAttributes) {
580 element1.SetAttributeValue(newAttribute.Name.LocalName.Substring(4), newAttribute.Value);
581 }
582 }
583 else if (HasAttribute(element, name => name.StartsWith("r-"), out XAttribute _)
584 && FindElement(clothesRoot, e => e.Attribute("Index")?.Value == indexValue, out XElement element2)) {
585 element2.Remove();
586 element.Remove();
587 }
588 else {
589 clothesRoot.Add(toCombineRoot);
590 }
591 }
592 }
593
594 public static void CombineCr(XElement xElement, Stream cloorcr) {
595 XElement MergeXml = XmlUtils.LoadXmlFromStream(cloorcr, Encoding.UTF8, true);
596 CombineCrLogic(xElement, MergeXml);
597 }
598
599 public static void CombineCrLogic(XElement xElement, XElement needCombine) {
600 foreach (XElement element in needCombine.Elements()) {
601 if (element.Attribute("Result") != null) {
602 if (HasAttribute(element, name => name.StartsWith("new-"), out XAttribute attribute)) {
603 if (FindElement(
604 xElement,
605 ele => { //原始标签
606 foreach (XAttribute xAttribute in element.Attributes()) //待修改的标签
607 {
608 if (xAttribute.Name == attribute.Name) {
609 continue;
610 }
611 if (ele.Attribute(xAttribute.Name) == null) {
612 return false;
613 }
614 }
615 return true;
616 },
617 out XElement element1
618 )) {
619 element1.SetAttributeValue(attribute.Name.LocalName.Substring(4), attribute.Value);
620 element1.SetValue(element.Value);
621 }
622 }
623 else if (HasAttribute(element, name => name.StartsWith("r-"), out XAttribute attribute1)) {
624 if (FindElement(
625 xElement,
626 ele => { //原始标签
627 foreach (XAttribute xAttribute in element.Attributes()) //待修改的标签
628 {
629 if (xAttribute.Name == attribute1.Name) {
630 continue;
631 }
632 if (ele.Attribute(xAttribute.Name) == null) {
633 return false;
634 }
635 }
636 return true;
637 },
638 out XElement element1
639 )) {
640 element1.Remove();
641 element.Remove();
642 }
643 }
644 else {
645 xElement.Add(element);
646 }
647 }
648 CombineCrLogic(xElement, element);
649 }
650 }
651
652 public static void Modify(XElement source, XElement change) {
653 if (FindElement(
654 source,
655 item => item.Name.LocalName == change.Name.LocalName
656 && item.Attribute("Guid") != null
657 && change.Attribute("Guid") != null
658 && item.Attribute("Guid")?.Value == change.Attribute("Guid")?.Value,
659 out XElement xElement1
660 )) {
661 foreach (XElement xElement in change.Elements()) {
662 Modify(xElement1, xElement);
663 }
664 }
665 else {
666 source.Add(change);
667 }
668 }
669
670 public class ClassSubstitute: IEquatable<ClassSubstitute> {
671 public string PackageName;
672 public string ClassName;
673
674 public ClassSubstitute(string packageName, string className) {
675 PackageName = packageName;
676 ClassName = className;
677 }
678
679 public bool Equals(ClassSubstitute other) {
680 if (other is null) {
681 return false;
682 }
683 if (ReferenceEquals(this, other)) {
684 return true;
685 }
686 return PackageName == other.PackageName && ClassName == other.ClassName;
687 }
688
689 public override bool Equals(object obj) {
690 return Equals(obj as ClassSubstitute);
691 }
692
693 public override int GetHashCode() => HashCode.Combine(PackageName, ClassName);
694
695 public static bool operator ==(ClassSubstitute left, ClassSubstitute right) => Equals(left, right);
696
697 public static bool operator !=(ClassSubstitute left, ClassSubstitute right) => !Equals(left, right);
698 }
699
700 public static FrozenDictionary<string, string> ImportantDatabaseClasses;
701 public static Dictionary<string, List<ClassSubstitute>> ClassSubstitutes = [];
702 public static Dictionary<string, List<ClassSubstitute>> OldClassSubstitutes = [];
703 public static Dictionary<string, ClassSubstitute> SelectedClassSubstitutes = [];
704
705 //对于关键(绑定了API1.7新的ModLoader接口的)组件,对修改行为进行检查报错
706 //修饰就是用的internal,不提供其他模组的调用权限
707 internal static void InitImportantDatabaseClasses() {
708 if (ModList.Count <= 3) {
709 return;
710 }
711 ImportantDatabaseClasses = new KeyValuePair<string, string>[] {
712 new("7347a83f-2d46-4fdf-bce2-52677de0b568", "Game.ComponentBody"),
713 new("4e14ce27-fdef-46ca-8ea0-26af43c215e5", "Game.ComponentHealth"),
714 new("7ecfafc4-4603-424c-87dd-1df59e7ef413", "Game.ComponentPlayer"),
715 new("9dc356e5-7dc8-45f6-8779-827ddee9966c", "Game.ComponentMiner"),
716 new("6f538db3-f1fe-4e91-8ef5-627c0b1a74ba", "Game.ComponentRunAwayBehavior"),
717 new("8b3d07dc-6498-4691-9686-cf4edabb8f3f", "Game.ComponentGui"),
718 new("e2636c38-f179-4aa1-b087-ed6920d66e8e", "Game.SubsystemTerrain"),
719 new("96e79f99-a082-4190-9ab6-835dc49ebbdd", "Game.SubsystemExplosions"),
720 new("dafb8e14-11b9-44b7-a208-424b770aeaa9", "Game.SubsystemProjectiles"),
721 new("32d392de-69c1-4d04-9e0b-5c7463201892", "Game.SubsystemPickables"),
722 new("54a4f6d5-98dd-4dc3-bf6d-04dfd972c6b7", "Game.SubsystemTime"),
723 new("b2e68ecd-49fc-4c05-b784-424da13f8550", "Game.ComponentDispenser"),
724 new("f6b020bb-8994-6ae6-289b-a842e3eb9ca5", "Game.ComponentFactors"),
725 new("a346c456-5087-48c4-835a-5829b3f35c68", "Game.ComponentLevel"),
726 new("1df4e627-c959-4e6a-bfa2-b7ee3ef08c99", "Game.ComponentClothing"),
727 }.ToFrozenDictionary();
728 }
729
730 public static void CombineDataBase(XElement databaseRoot, Stream toCombineStream) {
731 CombineDataBase(databaseRoot, toCombineStream, string.Empty);
732 }
733
734 public static void CombineDataBase(XElement databaseRoot, Stream toCombineStream, string modPackageName) {
735 XElement toCombineRoot = XmlUtils.LoadXmlFromStream(toCombineStream, Encoding.UTF8, true);
736 XElement databaseObjects = databaseRoot.Element("DatabaseObjects");
737 foreach (XElement element in toCombineRoot.Elements()) {
738 // 为实体添加模组来源信息
739 if (!string.IsNullOrEmpty(modPackageName)
740 && element.Name.LocalName == "EntityTemplate") {
741 string guid = element.Attribute("Guid")?.Value;
742 bool isNewEntity = true;
743 if (!string.IsNullOrEmpty(guid)) { // 检查是否为新增实体(在原数据库中不存在)
744 isNewEntity = !FindElementByGuid(databaseObjects, guid, out _);
745 }
746 if (isNewEntity) { // 只为新增的实体添加ModSource
747 XElement parameterElement = new("Parameter");
748 parameterElement.SetAttributeValue("Name", "ModSource");
749 parameterElement.SetAttributeValue("Value", modPackageName);
750 parameterElement.SetAttributeValue("Type", "string");
751 element.Add(parameterElement);
752 }
753 }
754 //处理修改
755 if (HasAttribute(element, str => str.StartsWith("new-"), out XAttribute newAttribute)) {
756 XAttribute guidAttribute = element.Attribute("Guid");
757 if (guidAttribute == null) {
758 continue;
759 }
760 string guid = guidAttribute.Value;
761 if (FindElementByGuid(databaseObjects, guid, out XElement oldElement)) {
762 string newAttributeName = newAttribute.Name.LocalName.Substring(4);
763 if (newAttributeName == "Value"
764 && oldElement.Attribute("Name")?.Value == "Class") {
765 if (ClassSubstitutes.TryGetValue(guid, out List<ClassSubstitute> classSubstitutes)) {
766 classSubstitutes.Add(new ClassSubstitute (modPackageName, newAttribute.Value));
767 }
768 else {
770 guid,
771 [new ClassSubstitute("survivalcraft", oldElement.Attribute("Value")!.Value), new ClassSubstitute(modPackageName, newAttribute.Value)]
772 );
773 }
774 }
775 else {
776 oldElement.SetAttributeValue(newAttributeName, newAttribute.Value);
777 }
778 }
779 }
780 else {
781 Modify(databaseObjects, element);
782 }
783 }
784 }
785
786 public static void DealWithClassSubstitutes() {
787 if (ClassSubstitutes.Count > 0) {
788 Queue<(string, XElement)> needToSolves = [];
789 foreach ((string guid, List<ClassSubstitute> substitutes) in ClassSubstitutes) {
790 // 如果有 2 个或更多候选项
791 if (substitutes.Count >= 2 && FindElementByGuid(DatabaseManager.DatabaseNode, guid, out XElement element)) {
792 // 如果手动选择过
793 if (SelectedClassSubstitutes.TryGetValue(guid, out ClassSubstitute selected)) {
794 // 如果选择项还能从候选项找到
795 if (substitutes.Any(x => x == selected)) {
796 if (OldClassSubstitutes.TryGetValue(guid, out List<ClassSubstitute> oldSubstitutes)) {
797 // 如果候选项与旧候选项一致,则使用选择项
798 if (substitutes.Count == oldSubstitutes.Count && oldSubstitutes.SequenceEqual(substitutes)) {
799 element.SetAttributeValue("Value", selected.ClassName);
800 }
801 // 否则需要手动重选
802 else {
803 SelectedClassSubstitutes.Remove(guid);
804 needToSolves.Enqueue((guid, element));
805 }
806 }
807 else {
808 element.SetAttributeValue("Value", selected.ClassName);
809 }
810 }
811 else {
812 SelectedClassSubstitutes.Remove(guid);
813 needToSolves.Enqueue((guid, element));
814 }
815 }
816 // 未手动选择过
817 // 当只有两个候选项,且不重要时,直接使用第二个(第一个是原版的)
818 else if (substitutes.Count == 2 && !(ImportantDatabaseClasses?.ContainsKey(guid) ?? false)) {
819 element.SetAttributeValue("Value", substitutes.Last().ClassName);
820 }
821 else {
822 needToSolves.Enqueue((guid, element));
823 }
824 }
825 else {
826 SelectedClassSubstitutes.Remove(guid);
827 }
828 }
829 if (needToSolves.Count > 0) {
830 AllowContinue = false;
831 void Handle() {
832 if (needToSolves.TryDequeue(out (string, XElement) tuple)) {
833 DialogsManager.ShowDialog(ScreensManager.RootWidget, new SelectClassSubstituteDialog(tuple.Item1, tuple.Item2, Handle));
834 }
835 else {
836 AllowContinue = true;
837 }
838 };
839 Handle();
840 }
841 }
842 else {
844 }
845 }
846
847 public static bool TryParseVersionRange(string value, out VersionRange versionRange) {
848 if (string.IsNullOrEmpty(value)) {
849 versionRange = null;
850 return false;
851 }
852 value = value.Trim();
853 if (value.Length == 0) {
854 versionRange = null;
855 return false;
856 }
857 char firstChar = value[0];
858 switch (firstChar) {
859 case '=': {
860 if (NuGetVersion.TryParse(value.Substring(1), out NuGetVersion nuGetVersion)) {
861 versionRange = new VersionRange(nuGetVersion, true, nuGetVersion, true);
862 return true;
863 }
864 break;
865 }
866 case '>': {
867 if (value.Length > 1) {
868 if (value[1] == '=') {
869 if (NuGetVersion.TryParse(value.Substring(2), out NuGetVersion nuGetVersion)) {
870 versionRange = new VersionRange(nuGetVersion, true);
871 return true;
872 }
873 }
874 else {
875 if (NuGetVersion.TryParse(value.Substring(1), out NuGetVersion nuGetVersion)) {
876 versionRange = new VersionRange(nuGetVersion, false);
877 return true;
878 }
879 }
880 }
881 break;
882 }
883 case '<': {
884 if (value.Length > 1) {
885 if (value[1] == '=') {
886 if (NuGetVersion.TryParse(value.Substring(2), out NuGetVersion nuGetVersion)) {
887 versionRange = new VersionRange(null, false, nuGetVersion, true);
888 return true;
889 }
890 }
891 else {
892 if (NuGetVersion.TryParse(value.Substring(1), out NuGetVersion nuGetVersion)) {
893 versionRange = new VersionRange(null, false, nuGetVersion);
894 return true;
895 }
896 }
897 }
898 break;
899 }
900 case '^': {
901 if (NuGetVersion.TryParse(value.Substring(1), out NuGetVersion nuGetVersion)) {
902 versionRange = new VersionRange(nuGetVersion, true, new NuGetVersion(nuGetVersion.Major + 1, 0, 0, 0));
903 return true;
904 }
905 break;
906 }
907 case '~': {
908 if (NuGetVersion.TryParse(value.Substring(1), out NuGetVersion nuGetVersion)) {
909 versionRange = new VersionRange(nuGetVersion, true, new NuGetVersion(nuGetVersion.Major, nuGetVersion.Minor + 1, 0, 0));
910 return true;
911 }
912 break;
913 }
914 default:
915 if (VersionRange.TryParse(value, out versionRange)) {
916 return true;
917 }
918 break;
919 }
920 versionRange = null;
921 return false;
922 }
923
924 public static string HeadingCode = "有头有脸天才少年,耍猴表演敢为人先";
925 public static string HeadingCode2 = "修改他人mod请获得原作者授权,否则小心出名!";
926
927 public static Stream GetDecipherStream(Stream stream) {
928 MemoryStream keepOpenStream = new();
929 byte[] buff = new byte[stream.Length];
930 stream.ReadExactly(buff);
931 byte[] hc = Encoding.UTF8.GetBytes(HeadingCode);
932 bool decipher = true;
933 for (int i = 0; i < hc.Length; i++) {
934 if (hc[i] != buff[i]) {
935 decipher = false;
936 break;
937 }
938 }
939 byte[] hc2 = Encoding.UTF8.GetBytes(HeadingCode2);
940 bool decipher2 = true;
941 for (int i = 0; i < hc2.Length; i++) {
942 if (hc2[i] != buff[i]) {
943 decipher2 = false;
944 break;
945 }
946 }
947 if (decipher) {
948 byte[] buff2 = new byte[buff.Length - hc.Length];
949 for (int i = 0; i < buff2.Length; i++) {
950 buff2[i] = buff[buff.Length - 1 - i];
951 }
952 keepOpenStream.Write(buff2, 0, buff2.Length);
953 keepOpenStream.Flush();
954 }
955 else if (decipher2) {
956 byte[] buff2 = new byte[buff.Length - hc2.Length];
957 int k = 0;
958 int t = 0;
959 int l = (buff2.Length + 1) / 2;
960 for (int i = 0; i < buff2.Length; i++) {
961 if (i % 2 == 0) {
962 buff2[i] = buff[hc2.Length + k];
963 k++;
964 }
965 else {
966 buff2[i] = buff[hc2.Length + l + t];
967 t++;
968 }
969 }
970 keepOpenStream.Write(buff2, 0, buff2.Length);
971 keepOpenStream.Flush();
972 }
973 else {
974 stream.Position = 0L;
975 stream.CopyTo(keepOpenStream);
976 }
977 stream.Dispose();
978 keepOpenStream.Position = 0L;
979 return keepOpenStream;
980 }
981
982 public static bool StrengtheningMod(string path) {
983 Stream stream = Storage.OpenFile(path, OpenFileMode.Read);
984 byte[] buff = new byte[stream.Length];
985 stream.ReadExactly(buff);
986 byte[] hc = Encoding.UTF8.GetBytes(HeadingCode);
987 bool decipher = true;
988 for (int i = 0; i < hc.Length; i++) {
989 if (hc[i] != buff[i]) {
990 decipher = false;
991 break;
992 }
993 }
994 byte[] hc2 = Encoding.UTF8.GetBytes(HeadingCode2);
995 bool decipher2 = true;
996 for (int i = 0; i < hc2.Length; i++) {
997 if (hc2[i] != buff[i]) {
998 decipher2 = false;
999 break;
1000 }
1001 }
1002 if (decipher || decipher2) {
1003 return false;
1004 }
1005 byte[] buff2 = new byte[buff.Length + hc2.Length];
1006 int k = 0;
1007 int l = hc2.Length;
1008 for (int i = 0; i < hc2.Length; i++) {
1009 buff2[i] = hc2[i];
1010 }
1011 for (int i = 0; i < buff.Length; i++) {
1012 if (i % 2 == 0) {
1013 buff2[k + l] = buff[i];
1014 k++;
1015 }
1016 }
1017 k = 0;
1018 l = hc2.Length + (buff.Length + 1) / 2;
1019 for (int i = 0; i < buff.Length; i++) {
1020 if (i % 2 != 0) {
1021 buff2[k + l] = buff[i];
1022 k++;
1023 }
1024 }
1025 string newPath = $"{path.Substring(0, path.LastIndexOf('.'))}({LanguageControl.Get(fName, 63)}).scmod";
1026 FileStream fileStream = new(Storage.GetSystemPath(newPath), FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
1027 fileStream.Write(buff2, 0, buff2.Length);
1028 fileStream.Flush();
1029 stream.Dispose();
1030 fileStream.Dispose();
1031 return true;
1032 }
1033}
static void Error(object message)
定义 Log.cs:80
static void Debug(object message)
定义 Log.cs:32
static ReadOnlyList< Assembly > LoadedAssemblies
static string GetSystemPath(string path)
static void CreateDirectory(string path)
static bool DirectoryExists(string path)
static IEnumerable< string > ListDirectoryNames(string path)
static IEnumerable< string > ListFileNames(string path)
static Stream OpenFile(string path, OpenFileMode openFileMode)
static string GetExtension(string path)
static bool FileExists(string path)
static string GetFileNameWithoutExtension(string path)
static string CombinePaths(params string[] paths)
static void ShowDialog(ContainerWidget parentWidget, Dialog dialog)
static readonly JsonDocumentOptions DefaultJsonOptions
static void Error(string mesg)
static void Warning(string mesg)
virtual void Dispose()
ModDisableReason DisableReason
virtual void LoadSettings(XElement xElement)
加载设置
virtual void SaveSettings(XElement xElement)
保存设置
string PackageName
static void SwitchScreen(string name, params object[] parameters)
static ContainerWidget RootWidget
static bool LoadSettings()
文件存在则读取并返回真否则返回假
static ZipArchive Open(Stream stream, bool keepStreamOpen=false)
bool Equals(ClassSubstitute other)
override bool Equals(object obj)
ClassSubstitute(string packageName, string className)
Dictionary< ModLoader, bool > Loaders
void Disable(ModLoader from, ModLoader toDisable, string reason)
Dictionary< ModLoader, string > DisableReason
void Remove(ModLoader modLoader)
void Add(ModLoader modLoader)
static string GameVersion
static void HookAction(string HookName, Func< ModLoader, bool > action)
执行Hook
static string DocPath
static List< ModLoader > ModLoaders
static NuGetVersion APINuGetVersion
static ModInfo DeserializeJson(string json)
static bool TryInvoke(ModHook modHook, ModLoader modLoader, Func< ModLoader, bool > action)
static string UserDataPath
static string APIReleasesLink_Client
static byte[] StreamToBytes(Stream stream)
将 Stream 转成 byte[]
static string ModsSettingsPath
static void AddException(Exception e, bool AllowContinue_=false)
static string LogPath
static string HeadingCode2
static bool GetModEntity(string packagename, out ModEntity modEntity)
static string HeadingCode
static bool FindElement(XElement xElement, Func< XElement, bool > func, out XElement elementout)
static void Reboot()
static void CombineCrLogic(XElement xElement, XElement needCombine)
static bool FindElementByGuid(XElement xElement, string guid, out XElement elementout)
static string ModSuffix
static bool HasAttribute(XElement element, Func< string, bool > func, out XAttribute xAttributeout)
static Dictionary< string, ModEntity > PackageNameToModEntity
含所有已启用的模组
static void DisableHook(ModLoader from, string HookName, string packageName, string reason)
static string ScreenCapturePath
const ApiVersionEnum ApiVersion
static void CombineClo(XElement clothesRoot, Stream toCombineStream)
static string ModsPath
static void LoadModSettings(XElement xElement)
static Dictionary< string, ModHook > ModHooks
static Dictionary< string, List< ClassSubstitute > > ClassSubstitutes
static void HookActionReverse(string HookName, Func< ModLoader, bool > action)
static void SetConfig(string key, string value)
static string CharacterSkinsDirectoryName
static string CommunityContentCachePath
static void CombineCr(XElement xElement, Stream cloorcr)
static string BlockTexturesDirectoryName
static ModEntity SurvivalCraftModEntity
static string WorldsDirectoryName
static string SettingPath
static string OriginalCommunityContentCachePath
static string APILatestReleaseLink_API
static Dictionary< string, ClassSubstitute > SelectedClassSubstitutes
static void GetScmods(string path)
获取所有文件
static string GetSha256(string input)
static Dictionary< string, List< ClassSubstitute > > OldClassSubstitutes
static bool AllowContinue
static string GetMd5(string input)
static string FurniturePacksDirectoryName
static string ShortGameVersion
static string ProcessModListPath
static void LoadConfigs()
static bool StrengtheningMod(string path)
static List< ModEntity > ModList
所有已启用的模组
static string APIVersionString
static Dictionary< string, HashSet< string > > DisabledMods
static string ShortAPIVersionString
static string APILatestReleaseLink_Client
static void Modify(XElement source, XElement change)
static string ExternalPath
static bool GetAllowContinue()
static void CombineDataBase(XElement databaseRoot, Stream toCombineStream)
static void LoadConfigsFromXml(XElement xElement)
static T GetInPakOrStorageFile< T >(string filePath, string suffix="txt")
static string ReportLink
static Dictionary< string, string > Configs
static Dictionary< string, Assembly > Dlls
static void ModListAllDo(Action< ModEntity > entity)
static void RegisterHook(string HookName, ModLoader modLoader)
注册Hook
static FrozenDictionary< string, string > ImportantDatabaseClasses
static void DealWithClassSubstitutes()
static string ConfigsPath
static void CombineDataBase(XElement databaseRoot, Stream toCombineStream, string modPackageName)
static void Initialize()
static List< ModEntity > ModListAll
所有模组,含禁用的
static Dictionary< KeyValuePair< ModHook, ModLoader >, bool > m_hookBugLogged
static bool TryParseVersionRange(string value, out VersionRange versionRange)
static string StreamToString(Stream stream)
static void SaveConfigs()
static bool ConfigLoaded
static bool IsAndroid
static void SaveModSettings(XElement xElement)
static Stream GetDecipherStream(Stream stream)
static string APIReleasesLink_API
static string fName
static string ImportMod(string name, Stream stream)
static void InitImportantDatabaseClasses()
static XElement LoadXmlFromStream(Stream stream, Encoding encoding, bool throwOnError)
static void SaveXmlToStream(XElement node, Stream stream, Encoding encoding, bool throwOnError)