diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs
index 808dba2900..9e638d4f2f 100644
--- a/osu.Game/Utils/ModUtils.cs
+++ b/osu.Game/Utils/ModUtils.cs
@@ -83,6 +83,48 @@ namespace osu.Game.Utils
.All(m => allowedSet.Contains(m.GetType()));
}
+ ///
+ /// Check the provided combination of mods are valid for a local gameplay session.
+ ///
+ /// The mods to check.
+ /// Invalid mods, if any where found. Can be null if all mods were valid.
+ /// Whether the input mods were all valid. If false, will contain all invalid entries.
+ public static bool CheckValidForGameplay(IEnumerable mods, out Mod[]? invalidMods)
+ {
+ mods = mods.ToArray();
+
+ List? foundInvalid = null;
+
+ void addInvalid(Mod mod)
+ {
+ foundInvalid ??= new List();
+ foundInvalid.Add(mod);
+ }
+
+ foreach (var mod in mods)
+ {
+ bool valid = mod.Type != ModType.System
+ && mod.HasImplementation
+ && !(mod is MultiMod);
+
+ if (!valid)
+ {
+ // if this mod was found as invalid, we can exclude it before potentially excluding more incompatible types.
+ addInvalid(mod);
+ continue;
+ }
+
+ foreach (var type in mod.IncompatibleMods)
+ {
+ foreach (var invalid in mods.Where(m => type.IsInstanceOfType(m)))
+ addInvalid(invalid);
+ }
+ }
+
+ invalidMods = foundInvalid?.ToArray();
+ return foundInvalid == null;
+ }
+
///
/// Flattens a set of s, returning a new set with all s removed.
///