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