Clarify BepInDependency version errors and docs (#1330)

The chainloader collected both not-installed dependencies and
installed-but-incompatible-version dependencies into one list and
reported them all as "missing dependencies", which is confusing when
the plugin is present but on a version outside the requested range.

Separate the two cases and log distinct, actionable messages: one for
dependencies that are not installed, and one for dependencies present
with an incompatible version (showing the found version and the
required range).

Also expand the BepInDependency(string, string) xmldoc: the version is
a SemVer range, link the range syntax, and warn about the BepInEx 5 ->
6 behaviour change (a bare version was a minimum, now it is exact).

Fixes #779
This commit is contained in:
HetCreep authored and GitHub committed 2026-06-08 14:48:55 +02:00
1 parent f8ba0a41af
commit 77d438cf69
2 files changed
+41 -11

No files matched your search

+29 -9
View File
@@ -344,6 +344,7 @@ public abstract class BaseChainloader<TPlugin>
{
var dependsOnInvalidPlugin = false;
var missingDependencies = new List<BepInDependency>();
var incompatibleDependencies = new List<KeyValuePair<BepInDependency, string>>();
foreach (var dependency in plugin.Dependencies)
{
static bool IsHardDependency(BepInDependency dep) =>
@@ -359,15 +360,22 @@ public abstract class BaseChainloader<TPlugin>
pluginVersion = pluginInfo?.Metadata.Version;
}
if (!dependencyExists || dependency.VersionRange != null &&
!dependency.VersionRange.IsSatisfied(pluginVersion))
// The dependency is not installed at all.
if (!dependencyExists)
{
// If the dependency is hard, collect it into a list to show
if (IsHardDependency(dependency))
missingDependencies.Add(dependency);
continue;
}
// The dependency is installed, but its version does not satisfy the requested range.
if (dependency.VersionRange != null && !dependency.VersionRange.IsSatisfied(pluginVersion))
{
if (IsHardDependency(dependency))
incompatibleDependencies.Add(new KeyValuePair<BepInDependency, string>(dependency, pluginVersion?.ToString()));
continue;
}
// If the dependency is a hard and is invalid (e.g. has missing dependencies), report that to the user
if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency))
{
@@ -387,13 +395,25 @@ public abstract class BaseChainloader<TPlugin>
continue;
}
if (missingDependencies.Count != 0)
if (missingDependencies.Count != 0 || incompatibleDependencies.Count != 0)
{
var message = $@"Could not load [{plugin}] because it has missing dependencies: {
string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
}";
DependencyErrors.Add(message);
Logger.Log(LogLevel.Error, message);
if (missingDependencies.Count != 0)
{
var message = $@"Could not load [{plugin}] because it has missing dependencies: {
string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
}. Install the listed plugin(s) and restart the game.";
DependencyErrors.Add(message);
Logger.Log(LogLevel.Error, message);
}
if (incompatibleDependencies.Count != 0)
{
var message = $@"Could not load [{plugin}] because the following dependencies are installed with an incompatible version: {
string.Join(", ", incompatibleDependencies.Select(s => $"{s.Key.DependencyGUID} (found {s.Value}, requires {s.Key.VersionRange})").ToArray())
}. Update the listed plugin(s) to a version that satisfies the requirement.";
DependencyErrors.Add(message);
Logger.Log(LogLevel.Error, message);
}
invalidPlugins.Add(plugin.Metadata.GUID);
continue;
+12 -2
View File
@@ -120,8 +120,18 @@ public class BepInDependency : Attribute, ICacheable
/// not load and an error will be logged instead.
/// </summary>
/// <param name="guid">The GUID of the referenced plugin.</param>
/// <param name="version">The version range of the referenced plugin.</param>
/// <remarks>When version is supplied the dependency is always treated as HardDependency</remarks>
/// <param name="version">
/// The version requirement of the referenced plugin, parsed as a SemVer range
/// (see <see href="https://github.com/adamreeve/semver.net#ranges" />). A plain version such as
/// <c>1.2.0</c> requires that exact version; use a range such as <c>&gt;=1.2.0</c>, <c>1.2.*</c>,
/// <c>~1.2.0</c> or <c>^1.2.0</c> to accept more than one version.
/// </param>
/// <remarks>
/// When a version is supplied the dependency is always treated as a hard dependency.
/// Plugins migrating from BepInEx 5 should note a behaviour change: a bare version was previously
/// treated as a minimum (<c>&gt;=</c>), whereas in BepInEx 6 it is an exact match. Use
/// <c>&gt;=1.2.0</c> to keep the old behaviour.
/// </remarks>
public BepInDependency(string guid, string version) : this(guid)
{
VersionRange = Range.Parse(version);