Perform extra checks when loading rulesets (#36641)

This commit is contained in:
Bartłomiej Dach
2026-02-10 05:34:43 +01:00
committed by GitHub
parent a1136c3cea
commit 655d725f0c
2 changed files with 77 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@@ -116,6 +117,69 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestFakedRulesetIdIsDetected()
{
RunTestWithRealm((realm, storage) =>
{
LoadTestRuleset.HasImplementations = true;
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
var ruleset = new LoadTestRuleset();
string rulesetShortName = ruleset.RulesetInfo.ShortName;
realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, 0)
{
Available = true,
}));
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.True);
// Availability is updated on construction of a RealmRulesetStore
using var _ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.False);
});
}
[Test]
public void TestMultipleRulesetWithSameOnlineIdsAreDetected()
{
RunTestWithRealm((realm, storage) =>
{
LoadTestRuleset.HasImplementations = true;
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
LoadTestRuleset.OnlineID = 2;
var first = new LoadTestRuleset();
var second = new CatchRuleset();
realm.Write(r => r.Add(new RulesetInfo(first.ShortName, first.RulesetInfo.Name, first.RulesetInfo.InstantiationInfo, first.RulesetInfo.OnlineID)
{
Available = true,
}));
realm.Write(r => r.Add(new RulesetInfo(second.ShortName, second.RulesetInfo.Name, second.RulesetInfo.InstantiationInfo, second.RulesetInfo.OnlineID)
{
Available = true,
}));
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.True);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
// Availability is updated on construction of a RealmRulesetStore
using var _ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.False);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.False);
realm.Write(r => r.Remove(r.Find<RulesetInfo>(first.ShortName)!));
using var __ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
});
}
private class LoadTestRuleset : Ruleset
{
public override string RulesetAPIVersionSupported => Version;
@@ -124,6 +188,13 @@ namespace osu.Game.Tests.Database
public static string Version { get; set; } = CURRENT_RULESET_API_VERSION;
public static int OnlineID { get; set; } = -1;
public LoadTestRuleset()
{
RulesetInfo.OnlineID = OnlineID;
}
public override IEnumerable<Mod> GetModsFor(ModType type)
{
if (!HasImplementations)

View File

@@ -93,6 +93,12 @@ namespace osu.Game.Rulesets
$"Ruleset API version is too old (was {instance.RulesetAPIVersionSupported}, expected {Ruleset.CURRENT_RULESET_API_VERSION})");
}
if (r.OnlineID != instanceInfo.OnlineID)
throw new InvalidOperationException($@"Online ID mismatch for ruleset {r.ShortName}: database has {r.OnlineID}, constructed instance has {instanceInfo.OnlineID}");
if (r.OnlineID > 0 && rulesets.Any(otherRuleset => otherRuleset.ShortName != r.ShortName && otherRuleset.OnlineID == r.OnlineID))
throw new InvalidOperationException($@"Ruleset {r.ShortName} shares online ID {r.OnlineID} with another ruleset");
// If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution.
// To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw.
resolvedType.Assembly.GetTypes();