1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 20:22:55 +08:00

Fix another potential crash in bubbles mod

Storing `DrawableHitObject` for later use is not safe – especially
when accessing `HitObject` – as it's a pooled class. In the case here,
it's important to note that `PrepareForUse` can be called a frame or
more later in execution, which made this unsafe.

Closes https://github.com/ppy/osu/issues/24444.
This commit is contained in:
Dean Herbert 2023-11-02 02:42:36 +09:00
parent 57d8b5ddc9
commit 48bdeaeff1
No known key found for this signature in database

View File

@ -2,11 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -22,6 +20,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -90,20 +89,17 @@ namespace osu.Game.Rulesets.Osu.Mods
break; break;
default: default:
addBubble();
break;
}
void addBubble()
{
BubbleDrawable bubble = bubblePool.Get(); BubbleDrawable bubble = bubblePool.Get();
bubble.DrawableOsuHitObject = drawableOsuHitObject; bubble.WasHit = drawable.IsHit;
bubble.Position = getPosition(drawableOsuHitObject);
bubble.AccentColour = drawable.AccentColour.Value;
bubble.InitialSize = new Vector2(bubbleSize); bubble.InitialSize = new Vector2(bubbleSize);
bubble.FadeTime = bubbleFade; bubble.FadeTime = bubbleFade;
bubble.MaxSize = maxSize; bubble.MaxSize = maxSize;
bubbleContainer.Add(bubble); bubbleContainer.Add(bubble);
break;
} }
}; };
@ -118,18 +114,38 @@ namespace osu.Game.Rulesets.Osu.Mods
}; };
} }
private Vector2 getPosition(DrawableOsuHitObject drawableObject)
{
switch (drawableObject)
{
// SliderHeads are derived from HitCircles,
// so we must handle them before to avoid them using the wrong positioning logic
case DrawableSliderHead:
return drawableObject.HitObject.Position;
// Using hitobject position will cause issues with HitCircle placement due to stack leniency.
case DrawableHitCircle:
return drawableObject.Position;
default:
return drawableObject.HitObject.Position;
}
}
#region Pooled Bubble drawable #region Pooled Bubble drawable
private partial class BubbleDrawable : PoolableDrawable private partial class BubbleDrawable : PoolableDrawable
{ {
public DrawableOsuHitObject? DrawableOsuHitObject { get; set; }
public Vector2 InitialSize { get; set; } public Vector2 InitialSize { get; set; }
public float MaxSize { get; set; } public float MaxSize { get; set; }
public double FadeTime { get; set; } public double FadeTime { get; set; }
public bool WasHit { get; set; }
public Color4 AccentColour { get; set; }
private readonly Box colourBox; private readonly Box colourBox;
private readonly CircularContainer content; private readonly CircularContainer content;
@ -157,15 +173,12 @@ namespace osu.Game.Rulesets.Osu.Mods
protected override void PrepareForUse() protected override void PrepareForUse()
{ {
Debug.Assert(DrawableOsuHitObject.IsNotNull()); Colour = WasHit ? Colour4.White : Colour4.Black;
Colour = DrawableOsuHitObject.IsHit ? Colour4.White : Colour4.Black;
Scale = new Vector2(1); Scale = new Vector2(1);
Position = getPosition(DrawableOsuHitObject);
Size = InitialSize; Size = InitialSize;
//We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect.
ColourInfo colourDarker = DrawableOsuHitObject.AccentColour.Value.Darken(0.1f); ColourInfo colourDarker = AccentColour.Darken(0.1f);
// The absolute length of the bubble's animation, can be used in fractions for animations of partial length // The absolute length of the bubble's animation, can be used in fractions for animations of partial length
double duration = 1700 + Math.Pow(FadeTime, 1.07f); double duration = 1700 + Math.Pow(FadeTime, 1.07f);
@ -178,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Mods
.ScaleTo(MaxSize * 1.5f, duration * 0.2f, Easing.OutQuint) .ScaleTo(MaxSize * 1.5f, duration * 0.2f, Easing.OutQuint)
.FadeOut(duration * 0.2f, Easing.OutCirc).Expire(); .FadeOut(duration * 0.2f, Easing.OutCirc).Expire();
if (!DrawableOsuHitObject.IsHit) return; if (!WasHit) return;
content.BorderThickness = InitialSize.X / 3.5f; content.BorderThickness = InitialSize.X / 3.5f;
content.BorderColour = Colour4.White; content.BorderColour = Colour4.White;
@ -192,24 +205,6 @@ namespace osu.Game.Rulesets.Osu.Mods
// Avoids transparency overlap issues during the bubble "pop" // Avoids transparency overlap issues during the bubble "pop"
.TransformTo(nameof(BorderThickness), 0f); .TransformTo(nameof(BorderThickness), 0f);
} }
private Vector2 getPosition(DrawableOsuHitObject drawableObject)
{
switch (drawableObject)
{
// SliderHeads are derived from HitCircles,
// so we must handle them before to avoid them using the wrong positioning logic
case DrawableSliderHead:
return drawableObject.HitObject.Position;
// Using hitobject position will cause issues with HitCircle placement due to stack leniency.
case DrawableHitCircle:
return drawableObject.Position;
default:
return drawableObject.HitObject.Position;
}
}
} }
#endregion #endregion