Elin Decompiled Documentation EA 23.357 Nightly
Loading...
Searching...
No Matches
ModManager.cs
Go to the documentation of this file.
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.IO;
5using System.Linq;
6using HeathenEngineering.SteamworksIntegration;
7using HeathenEngineering.SteamworksIntegration.API;
8using IniParser.Model;
9using Steamworks;
10using UnityEngine;
11using UnityEngine.UI;
12
13[Serializable]
14public class ModManager : ModManagerCore
15{
16 private sealed class WorkshopDownload
17 {
18 public readonly WorkshopItem item;
19
20 public readonly BaseModPackage package;
21
22 public readonly bool isUpdate;
23
24 public float lastChange;
25
26 public float progress;
27
28 public string Title => package.title.IsEmpty(item.Title).IsEmpty(item.FileId.ToString());
29
31 {
32 this.package = package;
33 item = (WorkshopItem)package.item;
35 lastChange = now;
36 }
37 }
38
39 public static List<object> ListPluginObject = new List<object>();
40
41 public static bool disableMod;
42
44
45 public List<FileInfo> replaceFiles = new List<FileInfo>();
46
47 public readonly List<ModGroup> emptyGroups = new List<ModGroup>();
48
49 private HashSet<ulong> _subscribedItems;
50
51 private readonly HashSet<ulong> _blockedItems = new HashSet<ulong>();
52
53 private int _missingContent;
54
55 public static List<string> ListChainLoad => BaseModManager.listChainLoad;
56
57 public static DirectoryInfo DirWorkshop => Instance.dirWorkshop;
58
60
62
63 public override void Init(string path, string defaultPackage = "_Elona")
64 {
65 base.Init(path, defaultPackage);
66 Debug.Log("IsOffline:" + BaseCore.IsOffline);
67 IniData elinIni = Core.GetElinIni();
68 if (elinIni != null)
69 {
71 {
72 string key = elinIni.GetKey("path_workshop");
73 if (!key.IsEmpty())
74 {
75 dirWorkshop = new DirectoryInfo(key);
76 }
77 }
78 else
79 {
80 string path2 = Path.Combine(App.Client.GetAppInstallDirectory(SteamSettings.behaviour.settings.applicationId), "../../workshop/content/2135150");
81 dirWorkshop = new DirectoryInfo(path2);
82 elinIni.Global["path_workshop"] = dirWorkshop.FullName;
83 Core.SaveElinIni(elinIni);
84 }
85 }
86 if ((!(dirWorkshop?.Exists)) ?? true)
87 {
88 dirWorkshop = null;
89 }
90 Debug.Log("Workshop:" + dirWorkshop);
91 Debug.Log("Packages:" + BaseModManager.rootMod);
92 Debug.Log("Core Mod:" + BaseModManager.rootDefaultPacakge);
93 }
94
95 public static string FormatLoadOrder(BaseModPackage p)
96 {
97 string arg = p.dirInfo?.FullName;
98 int num = (p.willActivate ? 1 : 0);
99 if (ModLoadOrderPreset.IsValidId(p.id))
100 {
101 return $"{arg},{num},{p.id.Trim()}";
102 }
103 return "";
104 }
105
106 public void SaveLoadOrder()
107 {
108 SplitGroups();
109 if (disableMod)
110 {
111 return;
112 }
113 List<string> list = new List<string>();
114 ModGroup modGroup = null;
115 foreach (ModGroup emptyGroup in emptyGroups)
116 {
117 if (emptyGroup.after == null)
118 {
119 list.Add((modGroup = emptyGroup).ToString());
120 }
121 }
122 foreach (BaseModPackage package in packages)
123 {
124 string str = package.dirInfo?.FullName;
125 string text = ((package.builtin || str.IsEmpty()) ? "" : FormatLoadOrder(package));
126 if (!text.IsEmpty())
127 {
128 if (package.group != modGroup)
129 {
130 modGroup = package.group;
131 list.Add(modGroup?.ToString() ?? "#group");
132 }
133 list.Add(text);
134 }
135 foreach (ModGroup emptyGroup2 in emptyGroups)
136 {
137 if (emptyGroup2.after == package)
138 {
139 list.Add((modGroup = emptyGroup2).ToString());
140 }
141 }
142 }
143 File.WriteAllLines(CorePath.PathLoadOrder, list);
144 }
145
146 public void SplitGroups()
147 {
148 HashSet<ModGroup> hashSet = new HashSet<ModGroup>();
149 ModGroup modGroup = null;
150 ModGroup modGroup2 = null;
151 foreach (BaseModPackage package in packages)
152 {
153 if (package.group != modGroup)
154 {
155 modGroup = package.group;
156 modGroup2 = ((modGroup == null || hashSet.Add(modGroup)) ? modGroup : modGroup.Clone());
157 }
158 package.group = modGroup2;
159 }
160 foreach (ModGroup emptyGroup in emptyGroups)
161 {
162 foreach (BaseModPackage package2 in packages)
163 {
164 if (package2.group != null && package2.group == emptyGroup.after?.group)
165 {
166 emptyGroup.after = package2;
167 }
168 }
169 }
170 }
171
172 public void LoadLoadOrder()
173 {
174 emptyGroups.Clear();
175 foreach (BaseModPackage package in packages)
176 {
177 package.orderIndex = -1;
178 package.group = null;
179 }
180 string pathLoadOrder = CorePath.PathLoadOrder;
181 if (!File.Exists(pathLoadOrder))
182 {
183 return;
184 }
185 Dictionary<string, BaseModPackage> dictionary = new Dictionary<string, BaseModPackage>(StringComparer.OrdinalIgnoreCase);
186 Dictionary<string, BaseModPackage> dictionary2 = new Dictionary<string, BaseModPackage>();
187 foreach (BaseModPackage package2 in packages)
188 {
189 if (!package2.builtin)
190 {
191 string text = NormalizeOrderPath(package2.dirInfo?.FullName);
192 if (!text.IsEmpty())
193 {
194 dictionary.TryAdd(text, package2);
195 }
196 string text2 = BaseModPackage.NormalizeId(package2.id);
197 if (!text2.IsEmpty())
198 {
199 dictionary2.TryAdd(text2, package2);
200 }
201 }
202 }
203 int num = 0;
204 int num2 = 0;
205 ModGroup modGroup = null;
206 BaseModPackage after = null;
207 HashSet<BaseModPackage> hashSet = new HashSet<BaseModPackage>();
208 foreach (string item in File.ReadAllLines(pathLoadOrder).Append("#group"))
209 {
210 string path;
211 bool activate;
212 string id;
213 if (ModGroup.TryParse(item, out var group))
214 {
215 if (modGroup != null && num2 == 0)
216 {
217 modGroup.after = after;
218 emptyGroups.Add(modGroup);
219 }
220 modGroup = group;
221 num2 = 0;
222 }
223 else if (ParseLoadOrderLine(item, out path, out activate, out id))
224 {
225 num2++;
226 if (!dictionary.TryGetValue(NormalizeOrderPath(path), out var value))
227 {
228 dictionary2.TryGetValue(BaseModPackage.NormalizeId(id) ?? "", out value);
229 }
230 if (value != null && hashSet.Add(value))
231 {
232 value.orderIndex = num;
233 value.willActivate = activate;
234 value.group = modGroup;
235 after = value;
236 num++;
237 }
238 }
239 }
240 static bool ParseLoadOrderLine(string line, out string reference, out bool reference2, out string reference3)
241 {
242 reference = null;
243 reference2 = true;
244 reference3 = null;
245 if (line.IsEmpty() || line.Trim().IsEmpty())
246 {
247 return false;
248 }
249 int num3 = line.LastIndexOf(',');
250 if (num3 <= 0)
251 {
252 return false;
253 }
254 string text3 = line[(num3 + 1)..].Trim();
255 if (text3 == "0" || text3 == "1")
256 {
257 reference = line[..num3];
258 reference2 = text3 == "1";
259 return !reference.IsEmpty();
260 }
261 int num4 = line.LastIndexOf(',', num3 - 1);
262 if (num4 <= 0)
263 {
264 return false;
265 }
266 string text4 = line[(num4 + 1)..num3].Trim();
267 if (text4 != "0" && text4 != "1")
268 {
269 return false;
270 }
271 reference = line[..num4];
272 reference2 = text4 == "1";
273 reference3 = text3;
274 return !reference.IsEmpty();
275 }
276 }
277
278 private static string NormalizeOrderPath(string path)
279 {
280 if (!path.IsEmpty())
281 {
282 return path.NormalizePath().TrimEnd('/');
283 }
284 return "";
285 }
286
287 public static string GetPresetPath(string name)
288 {
289 return CorePath.PathLoadOrderPreset + name.SanitizeFileName() + ".txt";
290 }
291
292 public List<FileInfo> ListPresets()
293 {
294 if (!Directory.Exists(CorePath.PathLoadOrderPreset))
295 {
296 return new List<FileInfo>();
297 }
298 return new DirectoryInfo(CorePath.PathLoadOrderPreset).GetFiles("*.txt", SearchOption.TopDirectoryOnly).OrderBy((FileInfo f) => f.Name, StringComparer.OrdinalIgnoreCase).ToList();
299 }
300
301 public bool SavePreset(string name, out string path)
302 {
303 path = GetPresetPath(name);
304 ModLoadOrderPreset.Preset preset = ModLoadOrderPreset.Save(packages, name, EClass.core.version.GetText());
305 if (disableMod || preset.entries.Count == 0)
306 {
307 return false;
308 }
309 Directory.CreateDirectory(CorePath.PathLoadOrderPreset);
310 File.WriteAllText(path, ModLoadOrderPreset.Serialize(preset));
311 return true;
312 }
313
314 public bool TryLoadPresetFile(FileInfo file, out ModLoadOrderPreset.Preset preset, out string error)
315 {
316 preset = null;
317 string text;
318 try
319 {
320 text = File.ReadAllText(file.FullName);
321 }
322 catch (Exception ex)
323 {
324 error = ex.Message;
325 return false;
326 }
327 if (!ModLoadOrderPreset.TryParse(text, out preset, out error))
328 {
329 return false;
330 }
331 if (preset.name.IsEmpty())
332 {
333 preset.name = Path.GetFileNameWithoutExtension(file.Name);
334 }
335 return true;
336 }
337
338 public ModLoadOrderPreset.ApplyResult ApplyPreset(ModLoadOrderPreset.Preset preset, bool disableOthers = true)
339 {
340 ModLoadOrderPreset.ApplyResult result = ModLoadOrderPreset.Apply(packages, preset, disableOthers);
342 return result;
343 }
344
345 public IEnumerator RefreshMods(Action onComplete, bool syncMods)
346 {
347 if ((bool)_loading)
348 {
349 UnityEngine.Object.Destroy(_loading.gameObject);
350 }
351 _loading = Util.Instantiate<LoadingScreen>("LoadingScreen");
352 bool flag = !BaseCore.IsOffline && syncMods && UserGeneratedContent.Client.GetNumSubscribedItems() != 0;
353 WaitForEndOfFrame awaiter = new WaitForEndOfFrame();
354 packages.Clear();
355 _subscribedItems = null;
356 _blockedItems.Clear();
357 _missingContent = 0;
358 disableMod |= Application.isEditor && EClass.debug.skipMod;
361 if (!disableMod && dirWorkshop != null)
362 {
363 _loading.Log("Loading workshop contents...");
364 if (flag)
365 {
366 yield return LoadWorkshopPackages();
367 }
369 }
371 SortPackages();
372 List<BaseModPackage> list = packages.Where((BaseModPackage p) => p.CanActivate && !p.parseError.IsEmpty()).ToList();
373 if (list.Count > 0)
374 {
375 Halt(list);
376 yield break;
377 }
378 _loading.Log($"Total number of mods:{packages.Count}");
379 _loading.Log("Activating Mods...");
380 yield return awaiter;
383 BaseModManager.isInitialized = true;
384 yield return awaiter;
385 onComplete?.Invoke();
386 if ((bool)_loading)
387 {
388 UnityEngine.Object.Destroy(_loading.gameObject);
389 }
390 yield return null;
391 }
392
393 private void Halt(List<BaseModPackage> broken)
394 {
395 List<string> list = broken.Select((BaseModPackage p) => p.title.IsEmpty(p.dirInfo?.Name) + ": package.xml does not parse - " + p.parseError).ToList();
396 list.Add("Fix the package.xml, or unsubscribe / delete the mod, then restart the game.");
397 string text = string.Join("\n", list);
398 Debug.LogError("#mod game will not start:\n" + text);
399 EGui.CreatePopup(() => new EGui.UpdateInfo(text), (EGui _) => false);
400 }
401
402 private void SortPackages()
403 {
405 List<BaseModPackage> list = ModOrder.SolveOrder(packages, useLoadorder: true);
406 if (list.Count != packages.Count)
407 {
408 Debug.LogError($"#mod order {list.Count} of {packages.Count} packages");
409 return;
410 }
411 packages.Clear();
412 packages.AddRange(list);
413 SplitGroups();
414 }
415
416 private void MapActivatedPackages()
417 {
418 foreach (BaseModPackage item in packages.Where((BaseModPackage p) => p.activated && !p.id.IsEmpty()))
419 {
420 mappedPackages[item.id] = item as ModPackage;
421 }
422 }
423
424 public ModPackage AddPackage(DirectoryInfo dir, bool isInPackages = false)
425 {
426 ModPackage modPackage = new ModPackage
427 {
428 dirInfo = new DirectoryInfo(dir.FullName.NormalizePath()),
429 installed = true,
430 isInPackages = isInPackages,
431 Mapping = new FileMapping(dir)
432 };
433 packages.Add(modPackage);
434 return modPackage;
435 }
436
437 public ModPackage AddWorkshopPackage(WorkshopItem item, bool isInPackages = false)
438 {
439 bool itemInstallInfo = UserGeneratedContent.Client.GetItemInstallInfo(item.FileId, out var _, out var folderPath, out var _);
440 if (folderPath.IsEmpty())
441 {
442 folderPath = Path.Combine(DirWorkshop.FullName, item.FileId.ToString());
443 }
444 DirectoryInfo directoryInfo = new DirectoryInfo(folderPath);
445 itemInstallInfo &= directoryInfo.Exists;
446 ModPackage modPackage = AddPackage(directoryInfo, isInPackages);
447 modPackage.installed = itemInstallInfo;
448 modPackage.banned = item.IsBanned;
449 modPackage.workshopId = item.FileId.ToString();
450 modPackage.item = item;
451 return modPackage;
452 }
453
454 public int CountUserMod()
455 {
456 return packages.Count((BaseModPackage p) => !p.builtin);
457 }
458
459 public void LoadLocalPackages()
460 {
461 _loading.Log("Loading local Package...");
462 DirectoryInfo[] directories = new DirectoryInfo(BaseModManager.rootMod).GetDirectories();
463 Array.Reverse(directories);
464 DirectoryInfo[] array = directories;
465 foreach (DirectoryInfo directoryInfo in array)
466 {
467 if (!disableMod || BaseModPackage.IsBuiltinDir(directoryInfo.Name))
468 {
469 if (directoryInfo.Name == "Mod_FixedPackageLoader")
470 {
471 IO.DeleteDirectory(directoryInfo.FullName);
472 }
473 else
474 {
475 AddPackage(directoryInfo, isInPackages: true);
476 }
477 }
478 }
479 }
480
481 public void LoadCustomPackage()
482 {
483 _loading.Log("Loading user Custom...");
484 DirectoryInfo[] directories = new DirectoryInfo(CorePath.custom).GetDirectories();
485 ModPackage package = new ModPackage();
486 DirectoryInfo[] array = directories;
487 foreach (DirectoryInfo dir in array)
488 {
489 ParseExtra(dir, package);
490 }
491 }
492
493 public void InitPackagesMeta()
494 {
495 Text status = _loading.Log("Initializing mods...");
496 HashSet<BaseModPackage> hashSet = new HashSet<BaseModPackage>();
497 int num = 0;
498 foreach (BaseModPackage package in packages)
499 {
500 num++;
501 try
502 {
503 if (!package.Init())
504 {
505 hashSet.Add(package);
506 Debug.LogWarning("Not a package: " + package.dirInfo.FullName);
507 continue;
508 }
509 if (package.parseError.IsEmpty() && package.id.IsEmpty())
510 {
511 hashSet.Add(package);
512 Debug.LogWarning("No <id> in package.xml, skipped: " + package.dirInfo.FullName);
513 continue;
514 }
515 if (!package.id.IsEmpty())
516 {
517 mappedPackages[package.id] = package as ModPackage;
518 }
519 Debug.Log(package.ToString());
520 SetStatus(status, $"Initializing mods {num}/{packages.Count}: {package.title}");
521 }
522 catch (Exception ex)
523 {
524 _loading.Log("Mod " + package.title + "/" + package.id + " has failed to initialize, " + ex.Message);
525 Debug.LogError(ex);
526 }
527 }
528 if (hashSet.Count > 0)
529 {
530 packages.RemoveAll(hashSet.Contains);
531 _loading.Log($"Ignored {hashSet.Count} folder(s) without a usable package.xml");
532 }
533 SetStatus(status, $"Initialized {packages.Count} mods");
534 }
535
536 public void ActivatePackages()
537 {
539 ListPluginObject.Clear();
541 ModUtil.OnModsActivated();
542 ModUtil.LoadTypeFallback();
543 }
544
546 {
547 Dictionary<string, BaseModPackage> loaded = new Dictionary<string, BaseModPackage>();
548 Dictionary<string, BaseModPackage> claimed = new Dictionary<string, BaseModPackage>();
549 int num = 0;
550 foreach (BaseModPackage package in packages)
551 {
552 package.blockedBy = null;
553 package.langDepError = null;
554 }
555 Dictionary<string, BaseModPackage> dictionary = new Dictionary<string, BaseModPackage>();
556 foreach (BaseModPackage package2 in packages)
557 {
558 string text = BaseModPackage.NormalizeId(package2.id);
559 if (text != null && !package2.hasPublishedPackage)
560 {
561 dictionary.TryAdd(text, package2);
562 }
563 }
564 foreach (ModPackage item in from p in packages.OfType<ModPackage>()
565 where !disableMod || p.builtin
566 select p)
567 {
568 if (!item.IsValidVersion())
569 {
570 _loading?.Log("Skipped (made for game " + item.version + "): " + item.title + "/" + item.id);
571 }
572 else
573 {
574 if (item.banned)
575 {
576 continue;
577 }
578 if (item.CanActivate)
579 {
580 BaseModPackage baseModPackage = FindIncompatible(item, loaded, claimed);
581 if (baseModPackage != null)
582 {
583 item.blockedBy = baseModPackage;
584 num++;
585 _loading?.Log("Blocked: " + item.title + "/" + item.id + " <-> " + baseModPackage.title + "/" + baseModPackage.id);
586 continue;
587 }
588 string text2 = UnmetDependency(item, loaded, dictionary);
589 if (text2 != null)
590 {
591 item.langDepError = text2;
592 _loading?.Log("Skipped (" + text2 + "): " + item.title + "/" + item.id);
593 continue;
594 }
595 }
596 try
597 {
598 item.Activate();
599 }
600 catch (Exception ex)
601 {
602 _loading?.Log("Failed to activate mod " + item.title + ": " + ex.Message);
603 }
604 finally
605 {
606 if (item.activated)
607 {
608 BaseModManager.listChainLoad.Add(item.dirInfo.FullName);
609 RegisterLoaded(item, loaded, claimed);
610 }
611 }
612 }
613 }
614 if (num > 0)
615 {
616 Debug.LogWarning($"#mod {num} package(s) skipped due to incompatibility");
617 }
618 }
619
620 private string UnmetDependency(BaseModPackage p, Dictionary<string, BaseModPackage> loaded, Dictionary<string, BaseModPackage> installed)
621 {
622 if (p.dependency == null)
623 {
624 return null;
625 }
626 string[][] dependency = p.dependency;
627 foreach (string[] array in dependency)
628 {
629 if (array == null || array.Length == 0)
630 {
631 continue;
632 }
633 BaseModPackage baseModPackage = null;
634 BaseModPackage baseModPackage2 = null;
635 bool flag = false;
636 string[] array2 = array;
637 for (int j = 0; j < array2.Length; j++)
638 {
639 string text = BaseModPackage.NormalizeId(array2[j]);
640 if (text == null)
641 {
642 continue;
643 }
644 if (loaded.ContainsKey(text))
645 {
646 flag = true;
647 break;
648 }
649 if (!installed.TryGetValue(text, out var value) || value == p)
650 {
651 continue;
652 }
653 if (packages.IndexOf(value) > packages.IndexOf(p))
654 {
655 if (baseModPackage == null)
656 {
657 baseModPackage = value;
658 }
659 }
660 else if (baseModPackage2 == null)
661 {
662 baseModPackage2 = value;
663 }
664 }
665 if (!flag)
666 {
667 if (baseModPackage != null)
668 {
669 return "mod_info_dep_order\t" + baseModPackage.title.IsEmpty(baseModPackage.dirInfo?.Name);
670 }
671 if (baseModPackage2 != null)
672 {
673 return "mod_info_dep_failed\t" + baseModPackage2.title.IsEmpty(baseModPackage2.dirInfo?.Name);
674 }
675 return "mod_info_dep_missing\t" + string.Join('\t', array);
676 }
677 }
678 return null;
679 }
680
681 private static BaseModPackage FindIncompatible(BaseModPackage p, Dictionary<string, BaseModPackage> loaded, Dictionary<string, BaseModPackage> claimed)
682 {
683 if (p.builtin)
684 {
685 return null;
686 }
687 string text = BaseModPackage.NormalizeId(p.id);
688 if (text == null)
689 {
690 return null;
691 }
692 string[][] incompatible = p.incompatible;
693 if (incompatible != null)
694 {
695 string[][] array = incompatible;
696 foreach (string[] array2 in array)
697 {
698 if (array2 == null)
699 {
700 continue;
701 }
702 string[] array3 = array2;
703 for (int j = 0; j < array3.Length; j++)
704 {
705 string text2 = BaseModPackage.NormalizeId(array3[j]);
706 if (text2 == null)
707 {
708 continue;
709 }
710 BaseModPackage value;
711 if (text2 == text)
712 {
713 Debug.Log("#mod incompatible: '" + p.id + "' self decl, ignored");
714 }
715 else if (loaded.TryGetValue(text2, out value) && value != null)
716 {
717 if (!value.builtin)
718 {
719 return value;
720 }
721 Debug.Log("#mod incompatible: '" + p.id + "' targets builtin '" + value.id + "', ignored");
722 }
723 }
724 }
725 }
726 if (claimed.TryGetValue(text, out var value2) && value2 != null && !value2.builtin)
727 {
728 return value2;
729 }
730 return null;
731 }
732
733 private static void RegisterLoaded(BaseModPackage p, Dictionary<string, BaseModPackage> loaded, Dictionary<string, BaseModPackage> claimed)
734 {
735 if (p.builtin)
736 {
737 return;
738 }
739 string text = BaseModPackage.NormalizeId(p.id);
740 if (text == null)
741 {
742 return;
743 }
744 loaded[text] = p;
745 string[][] incompatible = p.incompatible;
746 if (incompatible == null)
747 {
748 return;
749 }
750 string[][] array = incompatible;
751 foreach (string[] array2 in array)
752 {
753 if (array2 == null)
754 {
755 continue;
756 }
757 string[] array3 = array2;
758 for (int j = 0; j < array3.Length; j++)
759 {
760 string text2 = BaseModPackage.NormalizeId(array3[j]);
761 if (text2 != null && !(text2 == text))
762 {
763 claimed.TryAdd(text2, p);
764 }
765 }
766 }
767 }
768
769 public IEnumerator LoadWorkshopPackages()
770 {
771 PublishedFileId_t[] subscribedItems = UserGeneratedContent.Client.GetSubscribedItems();
772 if (!subscribedItems.IsEmpty())
773 {
774 _subscribedItems = subscribedItems.Select((PublishedFileId_t id) => id.m_PublishedFileId).ToHashSet();
775 yield return QuerySubscriptions(subscribedItems);
776 yield return DownloadSubscriptions();
777 if (_missingContent > 0)
778 {
779 yield return new WaitForSecondsRealtime(3f);
780 }
781 }
782 }
783
784 private IEnumerator QuerySubscriptions(PublishedFileId_t[] subscribed)
785 {
786 WaitForEndOfFrame awaiter = new WaitForEndOfFrame();
787 Text status = _loading.Log("Fetching subscriptions...");
788 Dictionary<ulong, WorkshopItem> fetched = new Dictionary<ulong, WorkshopItem>();
789 bool aborted = false;
790 for (int i = 0; i < subscribed.Length; i += 50)
791 {
792 PublishedFileId_t[] array = subscribed.Skip(i).Take(50).ToArray();
793 UgcQuery query = UgcQuery.Get(array);
794 bool completed = false;
795 bool discard = false;
796 SetStatus(status, $"Fetching subscriptions {i + array.Length}/{subscribed.Length}...(Hit ESC to cancel)");
797 if (query == null || !query.Execute(OnQueryCompleted))
798 {
799 Debug.LogWarning("Workshop: steam rejected the subscription query");
800 query?.Dispose();
801 aborted = true;
802 break;
803 }
804 float deadline = Time.realtimeSinceStartup + 30f;
805 while (!completed)
806 {
807 if (IsCancelRequested() || Time.realtimeSinceStartup > deadline)
808 {
809 discard = true;
810 aborted = true;
811 query.Dispose();
812 break;
813 }
814 yield return awaiter;
815 }
816 if (aborted)
817 {
818 Debug.LogWarning("Workshop: subscription query cancelled or timed out");
819 break;
820 }
821 void OnQueryCompleted(UgcQuery result)
822 {
823 completed = true;
824 if (discard)
825 {
826 return;
827 }
828 foreach (WorkshopItem results in result.ResultsList)
829 {
830 SteamUGCDetails_t sourceItemDetails = results.SourceItemDetails;
831 if (sourceItemDetails.m_eResult == EResult.k_EResultOK && sourceItemDetails.m_nPublishedFileId.m_PublishedFileId != 0L)
832 {
833 fetched[sourceItemDetails.m_nPublishedFileId.m_PublishedFileId] = results;
834 }
835 }
836 }
837 }
838 foreach (WorkshopItem value in fetched.Values)
839 {
840 if (value.IsBanned)
841 {
842 _blockedItems.Add(value.FileId.m_PublishedFileId);
844 }
845 else
846 {
847 AddWorkshopPackage(value);
848 }
849 }
850 SetStatus(status, $"Fetched {fetched.Count}/{subscribed.Length} subscriptions");
851 if (aborted)
852 {
853 yield break;
854 }
855 for (int j = 0; j < subscribed.Length; j++)
856 {
857 PublishedFileId_t publishedFileId_t = subscribed[j];
858 if (!fetched.ContainsKey(publishedFileId_t.m_PublishedFileId) && !Directory.Exists(Path.Combine(dirWorkshop.FullName, publishedFileId_t.ToString())))
859 {
861 }
862 }
863 }
864
865 private IEnumerator DownloadSubscriptions()
866 {
867 float now = Time.realtimeSinceStartup;
868 List<WorkshopDownload> queue = (from p in packages
869 where p.item is WorkshopItem workshopItem && (!p.installed || workshopItem.IsNeedsUpdate)
870 select new WorkshopDownload(p, now)).ToList();
871 if (queue.Count == 0)
872 {
873 yield break;
874 }
875 WaitForEndOfFrame awaiter = new WaitForEndOfFrame();
876 Text status = _loading.Log($"Downloading {queue.Count} mods...");
877 Dictionary<ulong, EResult> results = new Dictionary<ulong, EResult>();
878 int total = queue.Count;
879 int completed = 0;
880 float lastActivity = now;
881 float nextStatus = 0f;
882 UserGeneratedContent.Client.EventItemDownloaded.AddListener(OnItemDownloaded);
883 try
884 {
885 while (queue.Count > 0)
886 {
887 now = Time.realtimeSinceStartup;
888 for (int num = queue.Count - 1; num >= 0; num--)
889 {
890 WorkshopDownload workshopDownload = queue[num];
891 WorkshopItem item = workshopDownload.item;
892 ulong publishedFileId = item.FileId.m_PublishedFileId;
893 string path = null;
894 string reason = null;
895 bool flag = false;
896 EResult value;
897 if (!workshopDownload.package.downloadStarted)
898 {
899 workshopDownload.package.downloadStarted = true;
900 workshopDownload.lastChange = now;
901 if (item.DownloadItem(highPriority: true))
902 {
903 Debug.Log("Start downloading: " + workshopDownload.Title + " | " + $"Installed={item.IsInstalled}, " + $"Update={item.IsNeedsUpdate}, " + $"Downloading={item.IsDownloading}, " + $"Pending={item.IsDownloadPending}");
904 continue;
905 }
906 reason = "cannot start the download";
907 }
908 else if (results.TryGetValue(publishedFileId, out value) && value != EResult.k_EResultOK)
909 {
910 reason = $"download failed ({value})";
911 }
912 else if (IsItemReady(item, out path))
913 {
914 flag = true;
915 }
916 else
917 {
918 if (item.IsDownloadPending)
919 {
920 workshopDownload.lastChange = now;
921 continue;
922 }
923 if (item.IsDownloading)
924 {
925 float downloadCompletion = item.DownloadCompletion;
926 if (downloadCompletion > workshopDownload.progress)
927 {
928 workshopDownload.progress = downloadCompletion;
929 float lastChange;
930 lastActivity = (lastChange = now);
931 workshopDownload.lastChange = lastChange;
932 }
933 continue;
934 }
935 if (!(now - workshopDownload.lastChange > 20f))
936 {
937 continue;
938 }
939 reason = "cannot start the download";
940 }
941 queue.RemoveAt(num);
942 lastActivity = now;
943 if (flag)
944 {
945 completed++;
946 InstallWorkshopPackage(workshopDownload, path);
947 }
948 else
949 {
950 FailWorkshopPackage(workshopDownload, reason);
951 }
952 }
953 if (queue.Count == 0)
954 {
955 break;
956 }
957 if (IsCancelRequested())
958 {
959 AbortDownloads(queue, "cancelled");
960 break;
961 }
962 if (now - lastActivity > 90f)
963 {
964 AbortDownloads(queue, "not responding");
965 break;
966 }
967 if (now >= nextStatus)
968 {
969 nextStatus = now + 0.5f;
970 WorkshopDownload workshopDownload2 = queue.Find((WorkshopDownload d) => d.item.IsDownloading) ?? queue[0];
971 SetStatus(status, $"Downloading mods {completed}/{total}: " + $"{workshopDownload2.Title} {Mathf.FloorToInt(workshopDownload2.progress * 100f)}%...(Hit ESC to cancel)");
972 }
973 yield return awaiter;
974 }
975 }
976 finally
977 {
978 UserGeneratedContent.Client.EventItemDownloaded.RemoveListener(OnItemDownloaded);
979 }
980 SetStatus(status, $"Downloaded {completed}/{total} mods");
981 packages.RemoveAll((BaseModPackage p) => p.item is WorkshopItem && !p.installed);
982 void OnItemDownloaded(DownloadItemResult_t result)
983 {
984 results[result.m_nPublishedFileId.m_PublishedFileId] = result.m_eResult;
985 }
986 }
987
989 {
990 HashSet<string> hashSet = packages.Select((BaseModPackage p) => p.dirInfo.FullName).ToHashSet(StringComparer.OrdinalIgnoreCase);
991 DirectoryInfo[] directories = dirWorkshop.GetDirectories();
992 foreach (DirectoryInfo directoryInfo in directories)
993 {
994 ulong result;
995 bool flag = ulong.TryParse(directoryInfo.Name, out result);
996 if ((!flag || !_blockedItems.Contains(result)) && (_subscribedItems == null || (flag && _subscribedItems.Contains(result))) && !hashSet.Contains(new DirectoryInfo(directoryInfo.FullName.NormalizePath()).FullName))
997 {
998 ModPackage modPackage = AddPackage(directoryInfo);
999 if (flag)
1000 {
1001 modPackage.workshopId = directoryInfo.Name;
1002 }
1003 }
1004 }
1005 }
1006
1007 private void InstallWorkshopPackage(WorkshopDownload download, string path)
1008 {
1009 BaseModPackage package = download.package;
1010 package.installed = true;
1011 package.dirInfo = new DirectoryInfo(path.NormalizePath());
1012 if (package is ModPackage modPackage)
1013 {
1014 modPackage.Mapping = new FileMapping(package.dirInfo);
1015 }
1016 Debug.Log("Downloaded: " + download.Title + " -> " + package.dirInfo.FullName);
1017 }
1018
1019 private void FailWorkshopPackage(WorkshopDownload download, string reason)
1020 {
1021 if (download.isUpdate)
1022 {
1023 Debug.LogWarning("Workshop update failed: " + download.Title + ", " + reason);
1024 return;
1025 }
1027 Debug.LogWarning("Workshop download failed: " + download.Title + ", " + reason);
1028 }
1029
1030 private void AbortDownloads(List<WorkshopDownload> queue, string reason)
1031 {
1032 foreach (WorkshopDownload item in queue)
1033 {
1034 FailWorkshopPackage(item, reason);
1035 }
1036 queue.Clear();
1037 }
1038
1039 private static bool IsItemReady(WorkshopItem item, out string path)
1040 {
1041 path = null;
1042 if (!item.IsInstalled || item.IsNeedsUpdate || item.IsDownloading || item.IsDownloadPending)
1043 {
1044 return false;
1045 }
1046 if (UserGeneratedContent.Client.GetItemInstallInfo(item.FileId, out var _, out path, out var _) && !path.IsEmpty())
1047 {
1048 return Directory.Exists(path);
1049 }
1050 return false;
1051 }
1052
1053 private static bool IsCancelRequested()
1054 {
1055 return UnityEngine.Input.GetKey(KeyCode.Escape);
1056 }
1057
1058 private static void SetStatus(Text status, string message)
1059 {
1060 if ((bool)status)
1061 {
1062 status.text = message;
1063 }
1064 }
1065
1066 public override void ParseExtra(DirectoryInfo dir, BaseModPackage package)
1067 {
1068 ModPackage modPackage = (ModPackage)package;
1069 switch (dir.Name.ToLower())
1070 {
1071 case "talktext":
1072 modPackage.ParseTalkText(dir);
1073 break;
1074 case "map":
1075 if (!package.builtin)
1076 {
1077 modPackage.ParseMap(dir);
1078 }
1079 break;
1080 case "map piece":
1081 if (!package.builtin)
1082 {
1083 modPackage.ParseMapPiece(dir);
1084 }
1085 break;
1086 case "texture replace":
1087 replaceFiles.AddRange(modPackage.ParseTextureReplace(dir));
1088 break;
1089 case "texture":
1090 modPackage.ParseTexture(dir);
1091 break;
1092 case "portrait":
1093 modPackage.ParsePortrait(dir);
1094 break;
1095 case "langmod":
1096 modPackage.ParseLangMod(dir);
1097 break;
1098 case "sound":
1099 modPackage.ParseSound(dir);
1100 break;
1101 case "lang":
1102 modPackage.AddOrUpdateLang(dir);
1103 break;
1104 }
1105 }
1106}
$
Definition: ModManager.cs:106
static bool IsOffline
Definition: BaseCore.cs:9
Version version
Definition: BaseCore.cs:17
static bool isInitialized
static string rootDefaultPacakge
static BaseModManager Instance
static string rootMod
static List< string > listChainLoad
DirectoryInfo dirInfo
static string NormalizeId(string s)
static bool IsBuiltinDir(string dirName)
bool hasPublishedPackage
string[][] dependency
string[][] incompatible
bool skipMod
Definition: CoreDebug.cs:150
static string PathLoadOrderPreset
Definition: CorePath.cs:208
static string custom
Definition: CorePath.cs:159
static string PathLoadOrder
Definition: CorePath.cs:206
Definition: Core.cs:14
static void SaveElinIni(IniData ini)
Definition: Core.cs:825
static IniData GetElinIni()
Definition: Core.cs:791
Definition: EClass.cs:6
static Core core
Definition: EClass.cs:7
static CoreDebug debug
Definition: EClass.cs:49
Text Log(string s)
BaseModPackage after
Definition: ModGroup.cs:12
static bool TryParse(string line, out ModGroup group)
Definition: ModGroup.cs:36
readonly BaseModPackage package
Definition: ModManager.cs:20
WorkshopDownload(BaseModPackage package, float now)
Definition: ModManager.cs:30
readonly WorkshopItem item
Definition: ModManager.cs:18
static new ModManager Instance
Definition: ModManager.cs:61
override void ParseExtra(DirectoryInfo dir, BaseModPackage package)
Definition: ModManager.cs:1066
void InstallWorkshopPackage(WorkshopDownload download, string path)
Definition: ModManager.cs:1007
string UnmetDependency(BaseModPackage p, Dictionary< string, BaseModPackage > loaded, Dictionary< string, BaseModPackage > installed)
Definition: ModManager.cs:620
void SortPackages()
Definition: ModManager.cs:402
LoadingScreen _loading
Definition: ModManager.cs:43
bool TryLoadPresetFile(FileInfo file, out ModLoadOrderPreset.Preset preset, out string error)
Definition: ModManager.cs:314
void LoadCachedWorkshopPackages()
Definition: ModManager.cs:988
static string FormatLoadOrder(BaseModPackage p)
Definition: ModManager.cs:95
static bool IsInitialized
Definition: ModManager.cs:59
IEnumerator QuerySubscriptions(PublishedFileId_t[] subscribed)
Definition: ModManager.cs:784
readonly HashSet< ulong > _blockedItems
Definition: ModManager.cs:51
static string NormalizeOrderPath(string path)
Definition: ModManager.cs:278
static List< string > ListChainLoad
Definition: ModManager.cs:55
void ActivatePackages()
Definition: ModManager.cs:536
static BaseModPackage FindIncompatible(BaseModPackage p, Dictionary< string, BaseModPackage > loaded, Dictionary< string, BaseModPackage > claimed)
Definition: ModManager.cs:681
List< FileInfo > replaceFiles
Definition: ModManager.cs:45
void SplitGroups()
Definition: ModManager.cs:146
void LoadLoadOrder()
Definition: ModManager.cs:172
HashSet< ulong > _subscribedItems
Definition: ModManager.cs:49
ModPackage AddWorkshopPackage(WorkshopItem item, bool isInPackages=false)
Definition: ModManager.cs:437
ModLoadOrderPreset.ApplyResult ApplyPreset(ModLoadOrderPreset.Preset preset, bool disableOthers=true)
Definition: ModManager.cs:338
static bool IsItemReady(WorkshopItem item, out string path)
Definition: ModManager.cs:1039
static string GetPresetPath(string name)
Definition: ModManager.cs:287
override void Init(string path, string defaultPackage="_Elona")
Definition: ModManager.cs:63
void LoadCustomPackage()
Definition: ModManager.cs:481
IEnumerator RefreshMods(Action onComplete, bool syncMods)
Definition: ModManager.cs:345
void AbortDownloads(List< WorkshopDownload > queue, string reason)
Definition: ModManager.cs:1030
static bool disableMod
Definition: ModManager.cs:41
IEnumerator LoadWorkshopPackages()
Definition: ModManager.cs:769
static void RegisterLoaded(BaseModPackage p, Dictionary< string, BaseModPackage > loaded, Dictionary< string, BaseModPackage > claimed)
Definition: ModManager.cs:733
IEnumerator DownloadSubscriptions()
Definition: ModManager.cs:865
void FailWorkshopPackage(WorkshopDownload download, string reason)
Definition: ModManager.cs:1019
void ActivatePackageList()
Definition: ModManager.cs:545
void LoadLocalPackages()
Definition: ModManager.cs:459
static bool IsCancelRequested()
Definition: ModManager.cs:1053
ModPackage AddPackage(DirectoryInfo dir, bool isInPackages=false)
Definition: ModManager.cs:424
readonly List< ModGroup > emptyGroups
Definition: ModManager.cs:47
void Halt(List< BaseModPackage > broken)
Definition: ModManager.cs:393
static List< object > ListPluginObject
Definition: ModManager.cs:39
int CountUserMod()
Definition: ModManager.cs:454
void MapActivatedPackages()
Definition: ModManager.cs:416
int _missingContent
Definition: ModManager.cs:53
bool SavePreset(string name, out string path)
Definition: ModManager.cs:301
void SaveLoadOrder()
Definition: ModManager.cs:106
List< FileInfo > ListPresets()
Definition: ModManager.cs:292
static void SetStatus(Text status, string message)
Definition: ModManager.cs:1058
static DirectoryInfo DirWorkshop
Definition: ModManager.cs:57
void InitPackagesMeta()
Definition: ModManager.cs:493
IReadOnlyList< FileInfo > ParseTalkText(DirectoryInfo dir)
Definition: ModPackage.cs:32
void ParseLangMod(DirectoryInfo dir)
Definition: ModPackage.cs:167
IReadOnlyList< FileInfo > ParseTexture(DirectoryInfo dir)
Definition: ModPackage.cs:109
void AddOrUpdateLang(DirectoryInfo dir)
Definition: ModPackage.cs:376
IReadOnlyList< FileInfo > ParseMapPiece(DirectoryInfo dir, bool addToList=true)
Definition: ModPackage.cs:71
IReadOnlyList< FileInfo > ParseTextureReplace(DirectoryInfo dir)
Definition: ModPackage.cs:92
IReadOnlyList< FileInfo > ParsePortrait(DirectoryInfo dir)
Definition: ModPackage.cs:129
IReadOnlyList< FileInfo > ParseMap(DirectoryInfo dir, bool addToList=true)
Definition: ModPackage.cs:50
IReadOnlyList< FileInfo > ParseSound(DirectoryInfo dir)
Definition: ModPackage.cs:182
string GetText()
Definition: Version.cs:34