|
@ -53,17 +53,6 @@ namespace IPA.Loader |
|
|
Logger.loader.Info($"Loading metadata took {sw.Elapsed}"); |
|
|
Logger.loader.Info($"Loading metadata took {sw.Elapsed}"); |
|
|
sw.Reset(); |
|
|
sw.Reset(); |
|
|
|
|
|
|
|
|
// old loader system
|
|
|
|
|
|
#if false
|
|
|
|
|
|
Resolve(); |
|
|
|
|
|
InitFeatures(); |
|
|
|
|
|
ComputeLoadOrder(); |
|
|
|
|
|
FilterDisabled(); |
|
|
|
|
|
FilterWithoutFiles(); |
|
|
|
|
|
|
|
|
|
|
|
ResolveDependencies(); |
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
sw.Start(); |
|
|
sw.Start(); |
|
|
|
|
|
|
|
|
// Features contribute to load order considerations
|
|
|
// Features contribute to load order considerations
|
|
@ -499,271 +488,6 @@ namespace IPA.Loader |
|
|
// the thing -> the reason
|
|
|
// the thing -> the reason
|
|
|
internal static Dictionary<PluginMetadata, IgnoreReason> ignoredPlugins = new(); |
|
|
internal static Dictionary<PluginMetadata, IgnoreReason> ignoredPlugins = new(); |
|
|
|
|
|
|
|
|
#if false
|
|
|
|
|
|
internal static void Resolve() |
|
|
|
|
|
{ // resolves duplicates and conflicts, etc
|
|
|
|
|
|
PluginsMetadata.Sort((a, b) => b.Version.CompareTo(a.Version)); |
|
|
|
|
|
|
|
|
|
|
|
var ids = new HashSet<string>(); |
|
|
|
|
|
var ignore = new Dictionary<PluginMetadata, IgnoreReason>(); |
|
|
|
|
|
var resolved = new List<PluginMetadata>(PluginsMetadata.Count); |
|
|
|
|
|
foreach (var meta in PluginsMetadata) |
|
|
|
|
|
{ |
|
|
|
|
|
if (meta.Id != null) |
|
|
|
|
|
{ |
|
|
|
|
|
if (ids.Contains(meta.Id)) |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.loader.Warn($"Found duplicates of {meta.Id}, using newest"); |
|
|
|
|
|
var ireason = new IgnoreReason(Reason.Duplicate) |
|
|
|
|
|
{ |
|
|
|
|
|
ReasonText = $"Duplicate entry of same ID ({meta.Id})", |
|
|
|
|
|
RelatedTo = resolved.First(p => p.Id == meta.Id) |
|
|
|
|
|
}; |
|
|
|
|
|
ignore.Add(meta, ireason); |
|
|
|
|
|
ignoredPlugins.Add(meta, ireason); |
|
|
|
|
|
continue; // because of sorted order, hightest order will always be the first one
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool processedLater = false; |
|
|
|
|
|
foreach (var meta2 in PluginsMetadata) |
|
|
|
|
|
{ |
|
|
|
|
|
if (ignore.ContainsKey(meta2)) continue; |
|
|
|
|
|
if (meta == meta2) |
|
|
|
|
|
{ |
|
|
|
|
|
processedLater = true; |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!meta2.Manifest.Conflicts.ContainsKey(meta.Id)) continue; |
|
|
|
|
|
|
|
|
|
|
|
var range = meta2.Manifest.Conflicts[meta.Id]; |
|
|
|
|
|
if (!range.IsSatisfied(meta.Version)) continue; |
|
|
|
|
|
|
|
|
|
|
|
Logger.loader.Warn($"{meta.Id}@{meta.Version} conflicts with {meta2.Id}"); |
|
|
|
|
|
|
|
|
|
|
|
if (processedLater) |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.loader.Warn($"Ignoring {meta2.Name}"); |
|
|
|
|
|
ignore.Add(meta2, new IgnoreReason(Reason.Conflict) |
|
|
|
|
|
{ |
|
|
|
|
|
ReasonText = $"{meta.Id}@{meta.Version} conflicts with {meta2.Id}", |
|
|
|
|
|
RelatedTo = meta |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.loader.Warn($"Ignoring {meta.Name}"); |
|
|
|
|
|
ignore.Add(meta, new IgnoreReason(Reason.Conflict) |
|
|
|
|
|
{ |
|
|
|
|
|
ReasonText = $"{meta2.Id}@{meta2.Version} conflicts with {meta.Id}", |
|
|
|
|
|
RelatedTo = meta2 |
|
|
|
|
|
}); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (ignore.TryGetValue(meta, out var reason)) |
|
|
|
|
|
{ |
|
|
|
|
|
ignoredPlugins.Add(meta, reason); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
if (meta.Id != null) |
|
|
|
|
|
ids.Add(meta.Id); |
|
|
|
|
|
|
|
|
|
|
|
resolved.Add(meta); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
PluginsMetadata = resolved; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void FilterDisabled() |
|
|
|
|
|
{ |
|
|
|
|
|
var enabled = new List<PluginMetadata>(PluginsMetadata.Count); |
|
|
|
|
|
|
|
|
|
|
|
var disabled = DisabledConfig.Instance.DisabledModIds; |
|
|
|
|
|
foreach (var meta in PluginsMetadata) |
|
|
|
|
|
{ |
|
|
|
|
|
if (disabled.Contains(meta.Id ?? meta.Name)) |
|
|
|
|
|
DisabledPlugins.Add(meta); |
|
|
|
|
|
else |
|
|
|
|
|
enabled.Add(meta); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
PluginsMetadata = enabled; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void FilterWithoutFiles() |
|
|
|
|
|
{ |
|
|
|
|
|
var enabled = new List<PluginMetadata>(PluginsMetadata.Count); |
|
|
|
|
|
|
|
|
|
|
|
foreach (var meta in PluginsMetadata) |
|
|
|
|
|
{ |
|
|
|
|
|
var passed = true; |
|
|
|
|
|
foreach (var file in meta.AssociatedFiles) |
|
|
|
|
|
{ |
|
|
|
|
|
if (!file.Exists) |
|
|
|
|
|
{ |
|
|
|
|
|
passed = false; |
|
|
|
|
|
ignoredPlugins.Add(meta, new IgnoreReason(Reason.MissingFiles) |
|
|
|
|
|
{ |
|
|
|
|
|
ReasonText = $"File {Utils.GetRelativePath(file.FullName, UnityGame.InstallPath)} (declared by {meta.Name}) does not exist" |
|
|
|
|
|
}); |
|
|
|
|
|
Logger.loader.Warn($"File {Utils.GetRelativePath(file.FullName, UnityGame.InstallPath)}" + |
|
|
|
|
|
$" (declared by {meta.Name}) does not exist! Mod installation is incomplete, not loading it."); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (passed) |
|
|
|
|
|
enabled.Add(meta); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
PluginsMetadata = enabled; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
internal static void ComputeLoadOrder() |
|
|
|
|
|
{ |
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
Logger.loader.Debug(string.Join(", ", PluginsMetadata.Select(p => p.ToString()).StrJP())); |
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
static bool InsertInto(HashSet<PluginMetadata> root, PluginMetadata meta, bool isRoot = false) |
|
|
|
|
|
{ // this is slow, and hella recursive
|
|
|
|
|
|
bool inserted = false; |
|
|
|
|
|
foreach (var sr in root) |
|
|
|
|
|
{ |
|
|
|
|
|
inserted = inserted || InsertInto(sr.Dependencies, meta); |
|
|
|
|
|
|
|
|
|
|
|
if (meta.Id != null) |
|
|
|
|
|
{ |
|
|
|
|
|
if (sr.Manifest.Dependencies.ContainsKey(meta.Id)) |
|
|
|
|
|
inserted = inserted || sr.Dependencies.Add(meta); |
|
|
|
|
|
else if (sr.Manifest.LoadAfter.Contains(meta.Id)) |
|
|
|
|
|
inserted = inserted || sr.LoadsAfter.Add(meta); |
|
|
|
|
|
} |
|
|
|
|
|
if (sr.Id != null) |
|
|
|
|
|
if (meta.Manifest.LoadBefore.Contains(sr.Id)) |
|
|
|
|
|
inserted = inserted || sr.LoadsAfter.Add(meta); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (isRoot) |
|
|
|
|
|
{ |
|
|
|
|
|
foreach (var sr in root) |
|
|
|
|
|
{ |
|
|
|
|
|
InsertInto(meta.Dependencies, sr); |
|
|
|
|
|
|
|
|
|
|
|
if (sr.Id != null) |
|
|
|
|
|
{ |
|
|
|
|
|
if (meta.Manifest.Dependencies.ContainsKey(sr.Id)) |
|
|
|
|
|
meta.Dependencies.Add(sr); |
|
|
|
|
|
else if (meta.Manifest.LoadAfter.Contains(sr.Id)) |
|
|
|
|
|
meta.LoadsAfter.Add(sr); |
|
|
|
|
|
} |
|
|
|
|
|
if (meta.Id != null) |
|
|
|
|
|
if (sr.Manifest.LoadBefore.Contains(meta.Id)) |
|
|
|
|
|
meta.LoadsAfter.Add(sr); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
root.Add(meta); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return inserted; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var pluginTree = new HashSet<PluginMetadata>(); |
|
|
|
|
|
foreach (var meta in PluginsMetadata) |
|
|
|
|
|
InsertInto(pluginTree, meta, true); |
|
|
|
|
|
|
|
|
|
|
|
static void DeTree(List<PluginMetadata> into, HashSet<PluginMetadata> tree) |
|
|
|
|
|
{ |
|
|
|
|
|
foreach (var st in tree) |
|
|
|
|
|
if (!into.Contains(st)) |
|
|
|
|
|
{ |
|
|
|
|
|
DeTree(into, st.Dependencies); |
|
|
|
|
|
DeTree(into, st.LoadsAfter); |
|
|
|
|
|
into.Add(st); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
PluginsMetadata = new List<PluginMetadata>(); |
|
|
|
|
|
DeTree(PluginsMetadata, pluginTree); |
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
Logger.loader.Debug(string.Join(", ", PluginsMetadata.Select(p => p.ToString()).StrJP())); |
|
|
|
|
|
#endif
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
internal static void ResolveDependencies() |
|
|
|
|
|
{ |
|
|
|
|
|
var metadata = new List<PluginMetadata>(); |
|
|
|
|
|
var pluginsToLoad = new Dictionary<string, Version>(); |
|
|
|
|
|
var disabledLookup = DisabledPlugins.NonNull(m => m.Id).ToDictionary(m => m.Id, m => m.Version); |
|
|
|
|
|
foreach (var meta in PluginsMetadata) |
|
|
|
|
|
{ |
|
|
|
|
|
bool ignoreBcNoLoader = true; |
|
|
|
|
|
var missingDeps = new List<(string id, Range version, bool disabled)>(); |
|
|
|
|
|
foreach (var dep in meta.Manifest.Dependencies) |
|
|
|
|
|
{ |
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
Logger.loader.Debug($"Looking for dependency {dep.Key} with version range {dep.Value.Intersect(new SemVer.Range("*.*.*"))}"); |
|
|
|
|
|
#endif
|
|
|
|
|
|
if (dep.Key == SelfMeta.Id) |
|
|
|
|
|
ignoreBcNoLoader = false; |
|
|
|
|
|
|
|
|
|
|
|
if (pluginsToLoad.ContainsKey(dep.Key) && dep.Value.IsSatisfied(pluginsToLoad[dep.Key])) |
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
if (disabledLookup.ContainsKey(dep.Key) && dep.Value.IsSatisfied(disabledLookup[dep.Key])) |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.loader.Warn($"Dependency {dep.Key} was found, but disabled. Disabling {meta.Name} too."); |
|
|
|
|
|
missingDeps.Add((dep.Key, dep.Value, true)); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.loader.Warn($"{meta.Name} is missing dependency {dep.Key}@{dep.Value}"); |
|
|
|
|
|
missingDeps.Add((dep.Key, dep.Value, false)); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (meta.PluginType != null && !meta.IsSelf && !meta.IsBare && ignoreBcNoLoader) |
|
|
|
|
|
{ |
|
|
|
|
|
ignoredPlugins.Add(meta, new IgnoreReason(Reason.Dependency) |
|
|
|
|
|
{ |
|
|
|
|
|
ReasonText = "BSIPA Plugin does not reference BSIPA!" |
|
|
|
|
|
}); |
|
|
|
|
|
for (int i = 0; i < 20; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.loader.Warn($"HEY {meta.Id} YOU DEPEND ON BSIPA SO DEPEND ON BSIPA"); |
|
|
|
|
|
} |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (missingDeps.Count == 0) |
|
|
|
|
|
{ |
|
|
|
|
|
metadata.Add(meta); |
|
|
|
|
|
if (meta.Id != null) |
|
|
|
|
|
pluginsToLoad.Add(meta.Id, meta.Version); |
|
|
|
|
|
} |
|
|
|
|
|
else if (missingDeps.Any(t => !t.disabled)) |
|
|
|
|
|
{ // missing deps
|
|
|
|
|
|
ignoredPlugins.Add(meta, new IgnoreReason(Reason.Dependency) |
|
|
|
|
|
{ |
|
|
|
|
|
ReasonText = $"Missing dependencies {string.Join(", ", missingDeps.Where(t => !t.disabled).Select(t => $"{t.id}@{t.version}").StrJP())}" |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
DisabledPlugins.Add(meta); |
|
|
|
|
|
DisabledConfig.Instance.DisabledModIds.Add(meta.Id ?? meta.Name); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DisabledConfig.Instance.Changed(); |
|
|
|
|
|
PluginsMetadata = metadata; |
|
|
|
|
|
} |
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
internal static void DoOrderResolution() |
|
|
internal static void DoOrderResolution() |
|
|
{ |
|
|
{ |
|
|
#if DEBUG
|
|
|
#if DEBUG
|
|
@ -963,7 +687,7 @@ namespace IPA.Loader |
|
|
if (!TryResolveId(id, out var depMeta, out var depDisabled, out var depIgnored) |
|
|
if (!TryResolveId(id, out var depMeta, out var depDisabled, out var depIgnored) |
|
|
|| !range.Matches(depMeta.HVersion)) |
|
|
|| !range.Matches(depMeta.HVersion)) |
|
|
{ |
|
|
{ |
|
|
Logger.loader.Warn($"Dependency '{id}@{range}' for '{plugin.Id}' does not exist; ignoring '{plugin.Id}'"); |
|
|
|
|
|
|
|
|
Logger.loader.Warn($"'{plugin.Id}' is missing depencency '{id}@{range}'; ignoring"); |
|
|
ignoredPlugins.Add(plugin, new(Reason.Dependency) |
|
|
ignoredPlugins.Add(plugin, new(Reason.Dependency) |
|
|
{ |
|
|
{ |
|
|
ReasonText = $"Dependency '{id}@{range}' not found", |
|
|
ReasonText = $"Dependency '{id}@{range}' not found", |
|
|