From d89d6ed13fac448d79a593c8d441b08cc3b42fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 24 May 2026 13:55:53 +0200 Subject: [PATCH] Fix legacy beatmap export dropping background specification (#37892) - Closes https://github.com/ppy/osu/issues/37884 - Closes https://github.com/ppy/osu/pull/37890 Due to lack of population of `Storyboard.Beatmap` and `Storyboard.BeatmapInfo` post-decoding, `LegacyBeatmapExporter` would completely drop background specifications on exported beatmap packages. This affects both direct legacy export to file (`.osz`) as well as beatmap submission. I will not pretend that the API here is optimal but I do not see very easy opportunities to curtail misuse. Storyboards can be treated as either parts of a beatmap or standalone entities, and if a requirement is added to forcibly provide a beatmap and its info when encoding out a storyboard, I also foresee a requirement to bypass this later when design mode is implemented, which would be a return to square one. There is likely room for cleanup around `Storyboard` to maybe make this nicer (remove passing of both `Beatmap` and `BeatmapInfo` and just pass `Beatmap` instead, maybe shuffle some properties from `Beatmap` to `Storyboard` to remove the requirement of having to bolt the beatmap on to begin with). I leave voicing opinions on that, and how soon that should be done, to reviewers. My primary intent at this time is to hotfix a major issue in a released build. The external editing feature is not involved in this bug and any attempts to claim so are misdirections. --- .../Beatmaps/IO/LegacyBeatmapExporterTest.cs | 23 +++++++++++++++++++ osu.Game/Database/LegacyBeatmapExporter.cs | 2 ++ osu.Game/Storyboards/Storyboard.cs | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs index 16d2947ce2..af41dc4edd 100644 --- a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs @@ -122,6 +122,29 @@ namespace osu.Game.Tests.Beatmaps.IO () => Is.EqualTo(384).Within(0.00001)); } + [Test] + public void TestBackgroundSpecificationPreserved() + { + IWorkingBeatmap beatmap = null!; + MemoryStream outStream = null!; + + // Ensure importer encoding is correct + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"241526 Soleily - Renatus.osz")); + AddAssert("beatmap background is correct", () => beatmap.BeatmapInfo.Metadata.BackgroundFile, () => Is.EqualTo("machinetop_background.jpg")); + + // Ensure exporter legacy conversion is correct + AddStep("export", () => + { + outStream = new MemoryStream(); + + new LegacyBeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null); + }); + + AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream)); + AddAssert("beatmap background is still correct", () => beatmap.BeatmapInfo.Metadata.BackgroundFile, () => Is.EqualTo("machinetop_background.jpg")); + } + [Test] public void TestExportStability() { diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index beb50ec4c8..aa07057aa8 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -74,6 +74,8 @@ namespace osu.Game.Database using var storyboardStreamReader = new LineBufferedReader(storyboardStream); var beatmapStoryboard = new LegacyStoryboardDecoder().Decode(storyboardStreamReader); + beatmapStoryboard.Beatmap = beatmapContent; + beatmapStoryboard.BeatmapInfo = beatmapInfo; MutateBeatmap(model, playableBeatmap); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index d3c10aa162..5bee6c8a06 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -18,7 +18,7 @@ namespace osu.Game.Storyboards private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; - public BeatmapInfo BeatmapInfo = new BeatmapInfo(); + public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo(); public IBeatmap Beatmap { get; set; } = new Beatmap(); ///