From e0edaf996fa36c5ca15414739d34431de878da55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Aug 2022 16:03:37 +0900 Subject: [PATCH 1/4] Test ruleset compatibility during initial startup to avoid runtime errors As we continue to break the ruleset API, it makes more sense to proactively check known changes and bail early during ruleset loading to avoid a user experiencing a crash at a random point during execution. This is a RFC and needs to be tested against known broken rulesets. There might be some other calls we want to add in addition to the ones I've listed. --- osu.Game/Rulesets/RealmRulesetStore.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs index 62a7a13c07..c014b2d095 100644 --- a/osu.Game/Rulesets/RealmRulesetStore.cs +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Beatmaps; using osu.Game.Database; namespace osu.Game.Rulesets @@ -83,17 +84,36 @@ namespace osu.Game.Rulesets r.InstantiationInfo = instanceInfo.InstantiationInfo; r.Available = true; + testRulesetCompatibility(r); + detachedRulesets.Add(r.Clone()); } catch (Exception ex) { r.Available = false; - Logger.Log($"Could not load ruleset {r}: {ex.Message}"); + Logger.Log($"Could not load ruleset {r.Name}. Please check for an update from the developer.", level: LogLevel.Error); + Logger.Log($"Ruleset load failed with {ex.Message}"); } } availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); }); } + + private void testRulesetCompatibility(RulesetInfo rulesetInfo) + { + // do various operations to ensure that we are in a good state. + // if we can avoid loading the ruleset at this point (rather than erroring later in runtime) then that is preferred. + var instance = rulesetInfo.CreateInstance(); + + instance.CreateAllMods(); + instance.CreateIcon(); + instance.CreateResourceStore(); + + var beatmap = new Beatmap(); + var converter = instance.CreateBeatmapConverter(beatmap); + + instance.CreateBeatmapProcessor(converter.Convert()); + } } } From b0a740071e93ff1a899d969f1fcf8f4a83bf95c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Aug 2022 16:14:38 +0900 Subject: [PATCH 2/4] Centralise logging of failed ruleset loads --- osu.Game/Rulesets/RealmRulesetStore.cs | 4 +--- osu.Game/Rulesets/RulesetStore.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs index c014b2d095..7b1f3a3f6c 100644 --- a/osu.Game/Rulesets/RealmRulesetStore.cs +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Database; @@ -91,8 +90,7 @@ namespace osu.Game.Rulesets catch (Exception ex) { r.Available = false; - Logger.Log($"Could not load ruleset {r.Name}. Please check for an update from the developer.", level: LogLevel.Error); - Logger.Log($"Ruleset load failed with {ex.Message}"); + LogFailedLoad(r.Name, ex); } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 6b3e43cc1c..fdbcd0ed1e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets } catch (Exception e) { - Logger.Error(e, $"Failed to load ruleset {filename}"); + LogFailedLoad(filename, e); } } @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets } catch (Exception e) { - Logger.Error(e, $"Failed to add ruleset {assembly}"); + LogFailedLoad(assembly.FullName, e); } } @@ -173,6 +173,12 @@ namespace osu.Game.Rulesets AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly; } + protected void LogFailedLoad(string name, Exception exception) + { + Logger.Log($"Could not load ruleset {name}. Please check for an update from the developer.", level: LogLevel.Error); + Logger.Log($"Ruleset load failed: {exception}"); + } + #region Implementation of IRulesetStore IRulesetInfo? IRulesetStore.GetRuleset(int id) => GetRuleset(id); From bb46f72f9ee3e48b6d723e634119eb737cb086fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Aug 2022 16:17:50 +0900 Subject: [PATCH 3/4] Fix `Pippidon` crash on empty beatmap conversion --- .../Beatmaps/PippidonBeatmapConverter.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs index 8f0b31ef1b..0a4fa84ce1 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs @@ -21,8 +21,11 @@ namespace osu.Game.Rulesets.Pippidon.Beatmaps public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) : base(beatmap, ruleset) { - minPosition = beatmap.HitObjects.Min(getUsablePosition); - maxPosition = beatmap.HitObjects.Max(getUsablePosition); + if (beatmap.HitObjects.Any()) + { + minPosition = beatmap.HitObjects.Min(getUsablePosition); + maxPosition = beatmap.HitObjects.Max(getUsablePosition); + } } public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition && h is IHasYPosition); From 9d31f61ca91cd8b3a1815692070083079febe93a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Aug 2022 14:35:44 +0900 Subject: [PATCH 4/4] Don't throw when a ruleset type is completely missing --- osu.Game/Rulesets/RealmRulesetStore.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs index 7b1f3a3f6c..dba7f47f2f 100644 --- a/osu.Game/Rulesets/RealmRulesetStore.cs +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -68,8 +68,14 @@ namespace osu.Game.Rulesets { try { - var resolvedType = Type.GetType(r.InstantiationInfo) - ?? throw new RulesetLoadException(@"Type could not be resolved"); + var resolvedType = Type.GetType(r.InstantiationInfo); + + if (resolvedType == null) + { + // ruleset DLL was probably deleted. + r.Available = false; + continue; + } var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo ?? throw new RulesetLoadException(@"Instantiation failure");