`dotnet format` expects me to put a space between the spread `..` and
the expression after which is UTTERLY STUPID AND UGLY AND WRONG AND
CANNOT BE `.editorconfig`'d BECAUSE [INSERT EXCESSIVELY LONG BLEEP].
God I hate when good features are kneecapped by this sort of stupidity.
Resolves https://github.com/ppy/osu/discussions/36107.
The replay linked in the aforementioned discussion happens to contain
the magic sequence for ZIP headers in the binary stream (50 4b 05 06)
which led to it getting classified as a ZIP with no entries and
completely bogus everything in the header at
https://github.com/ppy/osu/blob/c8b18acd4dd1d98170a069240b6666cea3d5da07/osu.Game/Database/ImportTask.cs#L51-L56
and then finally dying of
2025-12-23 07:26:45 [error]: [?????] Model creation of replay-osu_2040232_4828629841.osr failed.
2025-12-23 07:26:45 [error]: System.InvalidOperationException: Sequence contains no matching element
2025-12-23 07:26:45 [error]: at System.Linq.ThrowHelper.ThrowNoMatchException()
2025-12-23 07:26:45 [error]: at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
2025-12-23 07:26:45 [error]: at osu.Game.Scoring.ScoreImporter.CreateModel(ArchiveReader archive, ImportParameters parameters)
2025-12-23 07:26:45 [error]: at osu.Game.Database.RealmArchiveModelImporter`1.importFromArchive(ArchiveReader archive, ImportParameters parameters, CancellationToken cancellationToken)
at
https://github.com/ppy/osu/blob/554961036e23615b35eb6b66b341b10924664c4d/osu.Game/Scoring/ScoreImporter.cs#L46
The test scene doesn't exercise the custom sample playback, but I hope I
can be forgiven for this as setting up a custom editor beatmap just for
this to work is rather cumbersome.
Even without the ID resetting logic before `Populate()` getting broken
and annotated with a TODO, this was kinda stupid. Why was purging logic
allowed to run *using a not-yet-completely-validated online ID of the
set*?
This is the other part of hopefully fixing scenarios like
https://osu.ppy.sh/community/forums/topics/2162457?n=8 (hopefully with
no babies poured out in the mean time, but only time will tell).
The end of `Populate()` is too early to do any validation of online IDs,
as population happens in `PostImport()` via `ProcessBeatmap`.
Additionally, this modifies the check to also include the set's ID. This
is one part of curtailing issues like
https://osu.ppy.sh/community/forums/topics/2162457?n=8 - the featured
artist template beatmap in question has a single difficulty with a bogus
positive beatmap set ID and a beatmap ID of 0, and my opinion is that
this sort of situation should for all intents and purposes cause the
beatmap set's ID to be reset.
A few facts of life:
- Guest difficulties are at this point a staple of mapping.
- People are very much used to flinging `.osu`s around (because there's
no better alternative).
- Currently there are two ways to get an `.osu` out of lazer. You can:
- Export the beatmap as "compatibility" to an `.osz`, then
transmogrify the `.osz` to a `.zip`, then extract the `.zip`, then
pluck out the `.osu`. This is the "correct" way to make sure stable
works, but is also stupidly arcane.
- Use "edit externally" to mount the beatmap files to disk, then
copy-paste out the `.osu`. This is the *wrong* way to make sure
stable works, because the mounting process exposes the raw "for
editing" format with features stable doesn't support, but it the
actual easy one.
- Reports about guest difficulties exported from lazer "working wrong on
stable" are prevalent. Probably mostly because of the preceding point.
What this PR does is introduce a *third* method to export an `.osu`,
which is designed to be both the easiest one yet *and* correct. I am
hoping this will curb the complaints until support for direct submission
of guest difficulties is added - which I still hope to see, but it will
be a significant effort *client-side* (the server side has been ready
for years now).
And yes, you will notice that much of the code added in
`LegacyBeatmapExporter` related to manipulation of the path is
copy-pasted from `LegacyExporter`. I don't care enough to invent
protected / abstract / whatever else OOP faff for something that may not
survive review and is mostly a weird semi-temporary wart.