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 @@ - +