mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 18:23:04 +08:00
Merge pull request #27698 from bdach/banish-rulesets-that-do-stupid-crap-to-the-BLAGOLE
Attempt to disable custom rulesets that can be linked to an unhandled crash
This commit is contained in:
commit
95b50576bd
@ -678,16 +678,21 @@ namespace osu.Game
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows a maximum of one unhandled exception, per second of execution.
|
/// Allows a maximum of one unhandled exception, per second of execution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool onExceptionThrown(Exception _)
|
/// <returns>Whether to ignore the exception and continue running.</returns>
|
||||||
|
private bool onExceptionThrown(Exception ex)
|
||||||
{
|
{
|
||||||
bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0;
|
if (Interlocked.Decrement(ref allowableExceptions) < 0)
|
||||||
|
{
|
||||||
Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {allowableExceptions} more allowable exceptions" : "denied")} .");
|
Logger.Log("Too many unhandled exceptions, crashing out.");
|
||||||
|
RulesetStore.TryDisableCustomRulesetsCausing(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Log($"Unhandled exception has been allowed with {allowableExceptions} more allowable exceptions.");
|
||||||
// restore the stock of allowable exceptions after a short delay.
|
// restore the stock of allowable exceptions after a short delay.
|
||||||
Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions));
|
Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions));
|
||||||
|
|
||||||
return continueExecution;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
@ -13,17 +16,20 @@ namespace osu.Game.Rulesets
|
|||||||
{
|
{
|
||||||
public class RealmRulesetStore : RulesetStore
|
public class RealmRulesetStore : RulesetStore
|
||||||
{
|
{
|
||||||
|
private readonly RealmAccess realmAccess;
|
||||||
public override IEnumerable<RulesetInfo> AvailableRulesets => availableRulesets;
|
public override IEnumerable<RulesetInfo> AvailableRulesets => availableRulesets;
|
||||||
|
|
||||||
private readonly List<RulesetInfo> availableRulesets = new List<RulesetInfo>();
|
private readonly List<RulesetInfo> availableRulesets = new List<RulesetInfo>();
|
||||||
|
|
||||||
public RealmRulesetStore(RealmAccess realm, Storage? storage = null)
|
public RealmRulesetStore(RealmAccess realmAccess, Storage? storage = null)
|
||||||
: base(storage)
|
: base(storage)
|
||||||
{
|
{
|
||||||
prepareDetachedRulesets(realm);
|
this.realmAccess = realmAccess;
|
||||||
|
prepareDetachedRulesets();
|
||||||
|
informUserAboutBrokenRulesets();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareDetachedRulesets(RealmAccess realmAccess)
|
private void prepareDetachedRulesets()
|
||||||
{
|
{
|
||||||
realmAccess.Write(realm =>
|
realmAccess.Write(realm =>
|
||||||
{
|
{
|
||||||
@ -143,5 +149,48 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
instance.CreateBeatmapProcessor(converter.Convert());
|
instance.CreateBeatmapProcessor(converter.Convert());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void informUserAboutBrokenRulesets()
|
||||||
|
{
|
||||||
|
if (RulesetStorage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (string brokenRulesetDll in RulesetStorage.GetFiles(@".", @"*.dll.broken"))
|
||||||
|
{
|
||||||
|
Logger.Log($"Ruleset '{Path.GetFileNameWithoutExtension(brokenRulesetDll)}' has been disabled due to causing a crash.\n\n"
|
||||||
|
+ "Please update the ruleset or report the issue to the developers of the ruleset if no updates are available.", level: LogLevel.Important);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TryDisableCustomRulesetsCausing(Exception exception)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stackTrace = new StackTrace(exception);
|
||||||
|
|
||||||
|
foreach (var frame in stackTrace.GetFrames())
|
||||||
|
{
|
||||||
|
var declaringAssembly = frame.GetMethod()?.DeclaringType?.Assembly;
|
||||||
|
if (declaringAssembly == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (UserRulesetAssemblies.Contains(declaringAssembly))
|
||||||
|
{
|
||||||
|
string sourceLocation = declaringAssembly.Location;
|
||||||
|
string destinationLocation = Path.ChangeExtension(sourceLocation, @".dll.broken");
|
||||||
|
|
||||||
|
if (File.Exists(sourceLocation))
|
||||||
|
{
|
||||||
|
Logger.Log($"Unhandled exception traced back to custom ruleset {Path.GetFileNameWithoutExtension(sourceLocation)}. Marking as broken.");
|
||||||
|
File.Move(sourceLocation, destinationLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Log($"Attempt to trace back crash to custom ruleset failed: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets
|
|||||||
private const string ruleset_library_prefix = @"osu.Game.Rulesets";
|
private const string ruleset_library_prefix = @"osu.Game.Rulesets";
|
||||||
|
|
||||||
protected readonly Dictionary<Assembly, Type> LoadedAssemblies = new Dictionary<Assembly, Type>();
|
protected readonly Dictionary<Assembly, Type> LoadedAssemblies = new Dictionary<Assembly, Type>();
|
||||||
|
protected readonly HashSet<Assembly> UserRulesetAssemblies = new HashSet<Assembly>();
|
||||||
|
protected readonly Storage? RulesetStorage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All available rulesets.
|
/// All available rulesets.
|
||||||
@ -41,9 +43,9 @@ namespace osu.Game.Rulesets
|
|||||||
// to load as unable to locate the game core assembly.
|
// to load as unable to locate the game core assembly.
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly;
|
AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly;
|
||||||
|
|
||||||
var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets");
|
RulesetStorage = storage?.GetStorageForDirectory(@"rulesets");
|
||||||
if (rulesetStorage != null)
|
if (RulesetStorage != null)
|
||||||
loadUserRulesets(rulesetStorage);
|
loadUserRulesets(RulesetStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -105,7 +107,11 @@ namespace osu.Game.Rulesets
|
|||||||
var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll");
|
var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll");
|
||||||
|
|
||||||
foreach (string? ruleset in rulesets.Where(f => !f.Contains(@"Tests")))
|
foreach (string? ruleset in rulesets.Where(f => !f.Contains(@"Tests")))
|
||||||
loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset));
|
{
|
||||||
|
var assembly = loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset));
|
||||||
|
if (assembly != null)
|
||||||
|
UserRulesetAssemblies.Add(assembly);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFromDisk()
|
private void loadFromDisk()
|
||||||
@ -126,21 +132,25 @@ namespace osu.Game.Rulesets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadRulesetFromFile(string file)
|
private Assembly? loadRulesetFromFile(string file)
|
||||||
{
|
{
|
||||||
string filename = Path.GetFileNameWithoutExtension(file);
|
string filename = Path.GetFileNameWithoutExtension(file);
|
||||||
|
|
||||||
if (LoadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename))
|
if (LoadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename))
|
||||||
return;
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
addRuleset(Assembly.LoadFrom(file));
|
var assembly = Assembly.LoadFrom(file);
|
||||||
|
addRuleset(assembly);
|
||||||
|
return assembly;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LogFailedLoad(filename, e);
|
LogFailedLoad(filename, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addRuleset(Assembly assembly)
|
private void addRuleset(Assembly assembly)
|
||||||
|
Loading…
Reference in New Issue
Block a user