1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 06:42:54 +08:00

Merge pull request #22858 from peppy/fix-old-beatmap-crash-on-load

Fix crash on attempting to load some old beatmaps which specify background images incorrectly
This commit is contained in:
Bartłomiej Dach 2023-04-05 22:28:05 +02:00 committed by GitHub
commit 8a52658c1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 26 deletions

View File

@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NUnit.Framework;
@ -161,6 +160,21 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
[Test]
public void TestDecodeImageSpecifiedAsVideo()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("image-specified-as-video.osb"))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var metadata = beatmap.Metadata;
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
}
}
[Test]
public void TestDecodeBeatmapTimingPoints()
{
@ -320,6 +334,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var comboColors = decoder.Decode(stream).ComboColours;
Debug.Assert(comboColors != null);
Color4[] expectedColors =
{
new Color4(142, 199, 255, 255),
@ -330,7 +346,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
new Color4(255, 177, 140, 255),
new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored.
};
Assert.AreEqual(expectedColors.Length, comboColors?.Count);
Assert.AreEqual(expectedColors.Length, comboColors.Count);
for (int i = 0; i < expectedColors.Length; i++)
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
@ -415,14 +431,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(positionData);
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(new Vector2(192, 168), positionData!.Position);
Assert.AreEqual(956, hitObjects[0].StartTime);
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
positionData = hitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(new Vector2(304, 56), positionData!.Position);
Assert.AreEqual(1285, hitObjects[1].StartTime);
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
}
@ -578,8 +594,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestFallbackDecoderForCorruptedHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
Decoder<Beatmap> decoder = null!;
Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
@ -596,8 +612,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestFallbackDecoderForMissingHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
Decoder<Beatmap> decoder = null!;
Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("missing-header.osu"))
using (var stream = new LineBufferedReader(resStream))
@ -614,8 +630,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeFileWithEmptyLinesAtStart()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
Decoder<Beatmap> decoder = null!;
Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("empty-lines-at-start.osu"))
using (var stream = new LineBufferedReader(resStream))
@ -632,8 +648,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeFileWithEmptyLinesAndNoHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
Decoder<Beatmap> decoder = null!;
Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("empty-line-instead-of-header.osu"))
using (var stream = new LineBufferedReader(resStream))
@ -650,8 +666,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeFileWithContentImmediatelyAfterHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
Decoder<Beatmap> decoder = null!;
Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("no-empty-line-after-header.osu"))
using (var stream = new LineBufferedReader(resStream))
@ -678,7 +694,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestAllowFallbackDecoderOverwrite()
{
Decoder<Beatmap> decoder = null;
Decoder<Beatmap> decoder = null!;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osuTK;
@ -30,35 +28,35 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(storyboard.HasDrawable);
Assert.AreEqual(6, storyboard.Layers.Count());
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
Assert.IsNotNull(background);
Assert.AreEqual(16, background.Elements.Count);
Assert.IsTrue(background.VisibleWhenFailing);
Assert.IsTrue(background.VisibleWhenPassing);
Assert.AreEqual("Background", background.Name);
StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2);
StoryboardLayer fail = storyboard.Layers.Single(l => l.Depth == 2);
Assert.IsNotNull(fail);
Assert.AreEqual(0, fail.Elements.Count);
Assert.IsTrue(fail.VisibleWhenFailing);
Assert.IsFalse(fail.VisibleWhenPassing);
Assert.AreEqual("Fail", fail.Name);
StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1);
StoryboardLayer pass = storyboard.Layers.Single(l => l.Depth == 1);
Assert.IsNotNull(pass);
Assert.AreEqual(0, pass.Elements.Count);
Assert.IsFalse(pass.VisibleWhenFailing);
Assert.IsTrue(pass.VisibleWhenPassing);
Assert.AreEqual("Pass", pass.Name);
StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0);
StoryboardLayer foreground = storyboard.Layers.Single(l => l.Depth == 0);
Assert.IsNotNull(foreground);
Assert.AreEqual(151, foreground.Elements.Count);
Assert.IsTrue(foreground.VisibleWhenFailing);
Assert.IsTrue(foreground.VisibleWhenPassing);
Assert.AreEqual("Foreground", foreground.Name);
StoryboardLayer overlay = storyboard.Layers.FirstOrDefault(l => l.Depth == int.MinValue);
StoryboardLayer overlay = storyboard.Layers.Single(l => l.Depth == int.MinValue);
Assert.IsNotNull(overlay);
Assert.IsEmpty(overlay.Elements);
Assert.IsTrue(overlay.VisibleWhenFailing);
@ -76,7 +74,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var sprite = background.Elements.ElementAt(0) as StoryboardSprite;
Assert.NotNull(sprite);
Assert.IsTrue(sprite.HasCommands);
Assert.IsTrue(sprite!.HasCommands);
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
Assert.IsTrue(sprite.IsDrawable);
Assert.AreEqual(Anchor.Centre, sprite.Origin);
@ -171,6 +169,21 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
[Test]
public void TestDecodeImageSpecifiedAsVideo()
{
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("image-specified-as-video.osb"))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);
StoryboardLayer foreground = storyboard.Layers.Single(l => l.Name == "Video");
Assert.That(foreground.Elements.Count, Is.Zero);
}
}
[Test]
public void TestDecodeOutOfRangeLoopAnimationType()
{

View File

@ -0,0 +1,4 @@
osu file format v14
[Events]
Video,0,"BG.jpg",0,0

View File

@ -363,6 +363,19 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]);
break;
case LegacyEventType.Video:
string filename = CleanFilename(split[2]);
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
// video extensions and handle similar to a background if it doesn't match.
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename)))
{
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
}
break;
case LegacyEventType.Background:
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
break;

View File

@ -109,6 +109,14 @@ namespace osu.Game.Beatmaps.Formats
int offset = Parsing.ParseInt(split[1]);
string path = CleanFilename(split[2]);
// See handling in LegacyBeatmapDecoder for the special case where a video type is used but
// the file extension is not a valid video.
//
// This avoids potential weird crashes when ffmpeg attempts to parse an image file as a video
// (see https://github.com/ppy/osu/issues/22829#issuecomment-1465552451).
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path)))
break;
storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset));
break;
}
@ -276,7 +284,8 @@ namespace osu.Game.Beatmaps.Formats
switch (type)
{
case "A":
timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive, startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit);
timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive,
startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit);
break;
case "H":

View File

@ -71,7 +71,7 @@ namespace osu.Game
[Cached(typeof(OsuGameBase))]
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider
{
public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" };
public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv", ".mpg", ".wmv", ".m4v" };
public const string OSU_PROTOCOL = "osu://";