diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 0c6b80e97e..fc61573416 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1,2 @@
+github: ppy
custom: https://osu.ppy.sh/home/support
diff --git a/osu.Android.props b/osu.Android.props
index 956093b2ac..db62667fc2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
index 0ba775e5c7..37f1a846ad 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
@@ -45,8 +45,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
new Spinner
{
- Duration = 2000,
- Position = OsuPlayfield.BASE_SIZE / 2
+ Duration = 6000,
+ Position = OsuPlayfield.BASE_SIZE / 2,
}
}
},
diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
new file mode 100644
index 0000000000..f3a4f10210
--- /dev/null
+++ b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
@@ -0,0 +1,104 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Moq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Checks;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Storyboards;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Resources;
+using FileInfo = osu.Game.IO.FileInfo;
+
+namespace osu.Game.Tests.Editing.Checks
+{
+ [TestFixture]
+ public class CheckAudioInVideoTest
+ {
+ private CheckAudioInVideo check;
+ private IBeatmap beatmap;
+
+ [SetUp]
+ public void Setup()
+ {
+ check = new CheckAudioInVideo();
+ beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Files = new List(new[]
+ {
+ new BeatmapSetFileInfo
+ {
+ Filename = "abc123.mp4",
+ FileInfo = new FileInfo { Hash = "abcdef" }
+ }
+ })
+ }
+ }
+ };
+ }
+
+ [Test]
+ public void TestRegularVideoFile()
+ {
+ using (var resourceStream = TestResources.OpenResource("Videos/test-video.mp4"))
+ Assert.IsEmpty(check.Run(getContext(resourceStream)));
+ }
+
+ [Test]
+ public void TestVideoFileWithAudio()
+ {
+ using (var resourceStream = TestResources.OpenResource("Videos/test-video-with-audio.mp4"))
+ {
+ var issues = check.Run(getContext(resourceStream)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateHasAudioTrack);
+ }
+ }
+
+ [Test]
+ public void TestVideoFileWithTrackButNoAudio()
+ {
+ using (var resourceStream = TestResources.OpenResource("Videos/test-video-with-track-but-no-audio.mp4"))
+ {
+ var issues = check.Run(getContext(resourceStream)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateHasAudioTrack);
+ }
+ }
+
+ [Test]
+ public void TestMissingFile()
+ {
+ beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
+
+ var issues = check.Run(getContext(null)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateMissingFile);
+ }
+
+ private BeatmapVerifierContext getContext(Stream resourceStream)
+ {
+ var storyboard = new Storyboard();
+ var layer = storyboard.GetLayer("Video");
+ layer.Add(new StoryboardVideo("abc123.mp4", 0));
+
+ var mockWorkingBeatmap = new Mock(beatmap, null, null);
+ mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream);
+ mockWorkingBeatmap.As().SetupGet(w => w.Storyboard).Returns(storyboard);
+
+ return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
new file mode 100644
index 0000000000..9b090591bc
--- /dev/null
+++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
@@ -0,0 +1,128 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using ManagedBass;
+using Moq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Checks;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Resources;
+using osuTK.Audio;
+using FileInfo = osu.Game.IO.FileInfo;
+
+namespace osu.Game.Tests.Editing.Checks
+{
+ [TestFixture]
+ public class CheckTooShortAudioFilesTest
+ {
+ private CheckTooShortAudioFiles check;
+ private IBeatmap beatmap;
+
+ [SetUp]
+ public void Setup()
+ {
+ check = new CheckTooShortAudioFiles();
+ beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Files = new List(new[]
+ {
+ new BeatmapSetFileInfo
+ {
+ Filename = "abc123.wav",
+ FileInfo = new FileInfo { Hash = "abcdef" }
+ }
+ })
+ }
+ }
+ };
+
+ // 0 = No output device. This still allows decoding.
+ if (!Bass.Init(0) && Bass.LastError != Errors.Already)
+ throw new AudioException("Could not initialize Bass.");
+ }
+
+ [Test]
+ public void TestDifferentExtension()
+ {
+ beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
+ beatmap.BeatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo
+ {
+ Filename = "abc123.jpg",
+ FileInfo = new FileInfo { Hash = "abcdef" }
+ });
+
+ // Should fail to load, but not produce an error due to the extension not being expected to load.
+ Assert.IsEmpty(check.Run(getContext(null, allowMissing: true)));
+ }
+
+ [Test]
+ public void TestRegularAudioFile()
+ {
+ using (var resourceStream = TestResources.OpenResource("Samples/test-sample.mp3"))
+ {
+ Assert.IsEmpty(check.Run(getContext(resourceStream)));
+ }
+ }
+
+ [Test]
+ public void TestBlankAudioFile()
+ {
+ using (var resourceStream = TestResources.OpenResource("Samples/blank.wav"))
+ {
+ // This is a 0 ms duration audio file, commonly used to silence sliderslides/ticks, and so should be fine.
+ Assert.IsEmpty(check.Run(getContext(resourceStream)));
+ }
+ }
+
+ [Test]
+ public void TestTooShortAudioFile()
+ {
+ using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3"))
+ {
+ var issues = check.Run(getContext(resourceStream)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Single().Template is CheckTooShortAudioFiles.IssueTemplateTooShort);
+ }
+ }
+
+ [Test]
+ public void TestMissingAudioFile()
+ {
+ using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3"))
+ {
+ Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true)));
+ }
+ }
+
+ [Test]
+ public void TestCorruptAudioFile()
+ {
+ using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav"))
+ {
+ var issues = check.Run(getContext(resourceStream)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Single().Template is CheckTooShortAudioFiles.IssueTemplateBadFormat);
+ }
+ }
+
+ private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false)
+ {
+ var mockWorkingBeatmap = new Mock(beatmap, null, null);
+ mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream);
+
+ return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs
new file mode 100644
index 0000000000..c9adc030c1
--- /dev/null
+++ b/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs
@@ -0,0 +1,86 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Moq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Checks;
+using osu.Game.Rulesets.Objects;
+using FileInfo = osu.Game.IO.FileInfo;
+
+namespace osu.Game.Tests.Editing.Checks
+{
+ [TestFixture]
+ public class CheckZeroByteFilesTest
+ {
+ private CheckZeroByteFiles check;
+ private IBeatmap beatmap;
+
+ [SetUp]
+ public void Setup()
+ {
+ check = new CheckZeroByteFiles();
+ beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Files = new List(new[]
+ {
+ new BeatmapSetFileInfo
+ {
+ Filename = "abc123.jpg",
+ FileInfo = new FileInfo { Hash = "abcdef" }
+ }
+ })
+ }
+ }
+ };
+ }
+
+ [Test]
+ public void TestNonZeroBytes()
+ {
+ Assert.IsEmpty(check.Run(getContext(byteLength: 44)));
+ }
+
+ [Test]
+ public void TestZeroBytes()
+ {
+ var issues = check.Run(getContext(byteLength: 0)).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Single().Template is CheckZeroByteFiles.IssueTemplateZeroBytes);
+ }
+
+ [Test]
+ public void TestMissing()
+ {
+ Assert.IsEmpty(check.Run(getContextMissing()));
+ }
+
+ private BeatmapVerifierContext getContext(long byteLength)
+ {
+ var mockStream = new Mock();
+ mockStream.Setup(s => s.Length).Returns(byteLength);
+
+ var mockWorkingBeatmap = new Mock();
+ mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(mockStream.Object);
+
+ return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
+ }
+
+ private BeatmapVerifierContext getContextMissing()
+ {
+ var mockWorkingBeatmap = new Mock();
+ mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns((Stream)null);
+
+ return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
+ }
+ }
+}
diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs
index e888f51e98..dbeb453d4d 100644
--- a/osu.Game.Tests/ImportTest.cs
+++ b/osu.Game.Tests/ImportTest.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests
protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false)
{
var osu = new TestOsuGameBase(withBeatmap);
- Task.Run(() => host.Run(osu))
+ Task.Factory.StartNew(() => host.Run(osu), TaskCreationOptions.LongRunning)
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
diff --git a/osu.Game.Tests/Resources/Samples/blank.wav b/osu.Game.Tests/Resources/Samples/blank.wav
new file mode 100644
index 0000000000..878bf23cea
Binary files /dev/null and b/osu.Game.Tests/Resources/Samples/blank.wav differ
diff --git a/osu.Game.Tests/Resources/Samples/corrupt.wav b/osu.Game.Tests/Resources/Samples/corrupt.wav
new file mode 100644
index 0000000000..87c7de4b7b
Binary files /dev/null and b/osu.Game.Tests/Resources/Samples/corrupt.wav differ
diff --git a/osu.Game.Tests/Resources/Samples/test-sample-cut.mp3 b/osu.Game.Tests/Resources/Samples/test-sample-cut.mp3
new file mode 100644
index 0000000000..003fe23dca
Binary files /dev/null and b/osu.Game.Tests/Resources/Samples/test-sample-cut.mp3 differ
diff --git a/osu.Game.Tests/Resources/Videos/test-video-with-audio.mp4 b/osu.Game.Tests/Resources/Videos/test-video-with-audio.mp4
new file mode 100644
index 0000000000..5d380ab50c
Binary files /dev/null and b/osu.Game.Tests/Resources/Videos/test-video-with-audio.mp4 differ
diff --git a/osu.Game.Tests/Resources/Videos/test-video-with-track-but-no-audio.mp4 b/osu.Game.Tests/Resources/Videos/test-video-with-track-but-no-audio.mp4
new file mode 100644
index 0000000000..7cdd1939e9
Binary files /dev/null and b/osu.Game.Tests/Resources/Videos/test-video-with-track-but-no-audio.mp4 differ
diff --git a/osu.Game.Tests/Resources/Videos/test-video.mp4 b/osu.Game.Tests/Resources/Videos/test-video.mp4
new file mode 100644
index 0000000000..795483c096
Binary files /dev/null and b/osu.Game.Tests/Resources/Videos/test-video.mp4 differ
diff --git a/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs b/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs
index b14684200f..319a768e65 100644
--- a/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
public static TournamentGameBase LoadTournament(GameHost host, TournamentGameBase tournament = null)
{
tournament ??= new TournamentGameBase();
- Task.Run(() => host.Run(tournament))
+ Task.Factory.StartNew(() => host.Run(tournament), TaskCreationOptions.LongRunning)
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
WaitForOrAssert(() => tournament.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return tournament;
diff --git a/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs b/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs
new file mode 100644
index 0000000000..f5709b5158
--- /dev/null
+++ b/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.IO;
+
+namespace osu.Game.IO.FileAbstraction
+{
+ public class StreamFileAbstraction : TagLib.File.IFileAbstraction
+ {
+ public StreamFileAbstraction(string filename, Stream fileStream)
+ {
+ ReadStream = fileStream;
+ Name = filename;
+ }
+
+ public string Name { get; }
+
+ public Stream ReadStream { get; }
+ public Stream WriteStream => ReadStream;
+
+ public void CloseStream(Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+
+ stream.Close();
+ }
+ }
+}
diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs
index 5f71b4be4a..39fc7f1da8 100644
--- a/osu.Game/Online/Rooms/Room.cs
+++ b/osu.Game/Online/Rooms/Room.cs
@@ -130,12 +130,6 @@ namespace osu.Game.Online.Rooms
set => MaxAttempts.Value = value;
}
- ///
- /// The position of this in the list. This is not read from or written to the API.
- ///
- [JsonIgnore]
- public readonly Bindable Position = new Bindable(-1); // Todo: This does not need to exist.
-
public Room()
{
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
@@ -192,8 +186,6 @@ namespace osu.Game.Online.Rooms
RecentParticipants.Clear();
RecentParticipants.AddRange(other.RecentParticipants);
}
-
- Position.Value = other.Position.Value;
}
public void RemoveExpiredPlaylistItems()
diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs
index 81f4808789..6ed91e983a 100644
--- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs
+++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs
@@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Edit
new CheckAudioQuality(),
new CheckMutedObjects(),
new CheckFewHitsounds(),
+ new CheckTooShortAudioFiles(),
+ new CheckAudioInVideo(),
+
+ // Files
+ new CheckZeroByteFiles(),
// Compose
new CheckUnsnappedObjects(),
diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs
new file mode 100644
index 0000000000..ac2542beb0
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs
@@ -0,0 +1,112 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using osu.Game.IO.FileAbstraction;
+using osu.Game.Rulesets.Edit.Checks.Components;
+using osu.Game.Storyboards;
+using TagLib;
+using File = TagLib.File;
+
+namespace osu.Game.Rulesets.Edit.Checks
+{
+ public class CheckAudioInVideo : ICheck
+ {
+ public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Audio track in video files");
+
+ public IEnumerable PossibleTemplates => new IssueTemplate[]
+ {
+ new IssueTemplateHasAudioTrack(this),
+ new IssueTemplateMissingFile(this),
+ new IssueTemplateFileError(this)
+ };
+
+ public IEnumerable Run(BeatmapVerifierContext context)
+ {
+ var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
+ var videoPaths = new List();
+
+ foreach (var layer in context.WorkingBeatmap.Storyboard.Layers)
+ {
+ foreach (var element in layer.Elements)
+ {
+ if (!(element is StoryboardVideo video))
+ continue;
+
+ // Ensures we don't check the same video file multiple times in case of multiple elements using it.
+ if (!videoPaths.Contains(video.Path))
+ videoPaths.Add(video.Path);
+ }
+ }
+
+ foreach (var filename in videoPaths)
+ {
+ string storagePath = beatmapSet.GetPathForFile(filename);
+
+ if (storagePath == null)
+ {
+ // There's an element in the storyboard that requires this resource, so it being missing is worth warning about.
+ yield return new IssueTemplateMissingFile(this).Create(filename);
+
+ continue;
+ }
+
+ Issue issue;
+
+ try
+ {
+ // We use TagLib here for platform invariance; BASS cannot detect audio presence on Linux.
+ using (Stream data = context.WorkingBeatmap.GetStream(storagePath))
+ using (File tagFile = File.Create(new StreamFileAbstraction(filename, data)))
+ {
+ if (tagFile.Properties.AudioChannels == 0)
+ continue;
+ }
+
+ issue = new IssueTemplateHasAudioTrack(this).Create(filename);
+ }
+ catch (CorruptFileException)
+ {
+ issue = new IssueTemplateFileError(this).Create(filename, "Corrupt file");
+ }
+ catch (UnsupportedFormatException)
+ {
+ issue = new IssueTemplateFileError(this).Create(filename, "Unsupported format");
+ }
+
+ yield return issue;
+ }
+ }
+
+ public class IssueTemplateHasAudioTrack : IssueTemplate
+ {
+ public IssueTemplateHasAudioTrack(ICheck check)
+ : base(check, IssueType.Problem, "\"{0}\" has an audio track.")
+ {
+ }
+
+ public Issue Create(string filename) => new Issue(this, filename);
+ }
+
+ public class IssueTemplateFileError : IssueTemplate
+ {
+ public IssueTemplateFileError(ICheck check)
+ : base(check, IssueType.Error, "Could not check whether \"{0}\" has an audio track ({1}).")
+ {
+ }
+
+ public Issue Create(string filename, string errorReason) => new Issue(this, filename, errorReason);
+ }
+
+ public class IssueTemplateMissingFile : IssueTemplate
+ {
+ public IssueTemplateMissingFile(ICheck check)
+ : base(check, IssueType.Warning, "Could not check whether \"{0}\" has an audio track, because it is missing.")
+ {
+ }
+
+ public Issue Create(string filename) => new Issue(this, filename);
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs
new file mode 100644
index 0000000000..57f7c60916
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs
@@ -0,0 +1,85 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using ManagedBass;
+using osu.Framework.Audio.Callbacks;
+using osu.Game.Rulesets.Edit.Checks.Components;
+
+namespace osu.Game.Rulesets.Edit.Checks
+{
+ public class CheckTooShortAudioFiles : ICheck
+ {
+ private const int ms_threshold = 25;
+ private const int min_bytes_threshold = 100;
+
+ private readonly string[] audioExtensions = { "mp3", "ogg", "wav" };
+
+ public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Too short audio files");
+
+ public IEnumerable PossibleTemplates => new IssueTemplate[]
+ {
+ new IssueTemplateTooShort(this),
+ new IssueTemplateBadFormat(this)
+ };
+
+ public IEnumerable Run(BeatmapVerifierContext context)
+ {
+ var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
+
+ foreach (var file in beatmapSet.Files)
+ {
+ using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.StoragePath))
+ {
+ if (data == null)
+ continue;
+
+ var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data));
+ int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle);
+
+ if (decodeStream == 0)
+ {
+ // If the file is not likely to be properly parsed by Bass, we don't produce Error issues about it.
+ // Image files and audio files devoid of audio data both fail, for example, but neither would be issues in this check.
+ if (hasAudioExtension(file.Filename) && probablyHasAudioData(data))
+ yield return new IssueTemplateBadFormat(this).Create(file.Filename);
+
+ continue;
+ }
+
+ long length = Bass.ChannelGetLength(decodeStream);
+ double ms = Bass.ChannelBytes2Seconds(decodeStream, length) * 1000;
+
+ // Extremely short audio files do not play on some soundcards, resulting in nothing being heard in-game for some users.
+ if (ms > 0 && ms < ms_threshold)
+ yield return new IssueTemplateTooShort(this).Create(file.Filename, ms);
+ }
+ }
+ }
+
+ private bool hasAudioExtension(string filename) => audioExtensions.Any(filename.ToLower().EndsWith);
+ private bool probablyHasAudioData(Stream data) => data.Length > min_bytes_threshold;
+
+ public class IssueTemplateTooShort : IssueTemplate
+ {
+ public IssueTemplateTooShort(ICheck check)
+ : base(check, IssueType.Problem, "\"{0}\" is too short ({1:0} ms), should be at least {2:0} ms.")
+ {
+ }
+
+ public Issue Create(string filename, double ms) => new Issue(this, filename, ms, ms_threshold);
+ }
+
+ public class IssueTemplateBadFormat : IssueTemplate
+ {
+ public IssueTemplateBadFormat(ICheck check)
+ : base(check, IssueType.Error, "Could not check whether \"{0}\" is too short (code \"{1}\").")
+ {
+ }
+
+ public Issue Create(string filename) => new Issue(this, filename, Bass.LastError);
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs
new file mode 100644
index 0000000000..3a994fabfa
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs
@@ -0,0 +1,43 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using osu.Game.Rulesets.Edit.Checks.Components;
+
+namespace osu.Game.Rulesets.Edit.Checks
+{
+ public class CheckZeroByteFiles : ICheck
+ {
+ public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Files, "Zero-byte files");
+
+ public IEnumerable PossibleTemplates => new IssueTemplate[]
+ {
+ new IssueTemplateZeroBytes(this)
+ };
+
+ public IEnumerable Run(BeatmapVerifierContext context)
+ {
+ var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
+
+ foreach (var file in beatmapSet.Files)
+ {
+ using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.StoragePath))
+ {
+ if (data?.Length == 0)
+ yield return new IssueTemplateZeroBytes(this).Create(file.Filename);
+ }
+ }
+ }
+
+ public class IssueTemplateZeroBytes : IssueTemplate
+ {
+ public IssueTemplateZeroBytes(ICheck check)
+ : base(check, IssueType.Problem, "\"{0}\" is a 0-byte file.")
+ {
+ }
+
+ public Issue Create(string filename) => new Issue(this, filename);
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
index 381849189d..97377278a6 100644
--- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
@@ -116,8 +116,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
if (ignoredRooms.Contains(room.RoomID.Value.Value))
return;
- room.Position.Value = -room.RoomID.Value.Value;
-
try
{
foreach (var pi in room.Playlist)
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index 907b7e308a..85efdcef1a 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -129,7 +129,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void updateSorting()
{
foreach (var room in roomFlow)
- roomFlow.SetLayoutPosition(room, room.Room.Position.Value);
+ roomFlow.SetLayoutPosition(room, -(room.Room.RoomID.Value ?? 0));
}
protected override bool OnClick(ClickEvent e)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 184c9d3f63..9ee6f4cf52 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,11 +36,12 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 38b920420b..110de79285 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+