From ae05df3b8c5d0f44bb1de43c3dbb8087d90adc35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jul 2023 15:56:51 +0200 Subject: [PATCH 1/7] Add `ModScoreV2` --- osu.Game/Rulesets/Mods/ModScoreV2.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/ModScoreV2.cs diff --git a/osu.Game/Rulesets/Mods/ModScoreV2.cs b/osu.Game/Rulesets/Mods/ModScoreV2.cs new file mode 100644 index 0000000000..6d56b2d86f --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModScoreV2.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// This mod is used strictly to mark osu!stable scores set with the "Score V2" mod active. + /// It should not be used in any real capacity going forward. + /// + public class ModScoreV2 : Mod + { + public override string Name => "Score V2"; + public override string Acronym => @"SV2"; + public override ModType Type => ModType.System; + public override LocalisableString Description => "Score set on earlier osu! versions with the V2 scoring algorithm active."; + public override double ScoreMultiplier => 1; + } +} From 10ba04512d5963eb1ae6d028d83d65dd644d8ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jul 2023 16:06:05 +0200 Subject: [PATCH 2/7] Add `ScoreV2` to `LegacyMods` --- osu.Game/Beatmaps/Legacy/LegacyMods.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/Legacy/LegacyMods.cs b/osu.Game/Beatmaps/Legacy/LegacyMods.cs index 0e517ea3df..747015d90a 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyMods.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyMods.cs @@ -38,6 +38,7 @@ namespace osu.Game.Beatmaps.Legacy Key1 = 1 << 26, Key3 = 1 << 27, Key2 = 1 << 28, + ScoreV2 = 1 << 29, Mirror = 1 << 30, } } From 2cd5fd5944e48e9625353e3295ec017e1399ab73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jul 2023 16:09:25 +0200 Subject: [PATCH 3/7] Add failing legacy mod conversion test cases --- osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs | 4 +++- osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs | 4 +++- osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs | 4 +++- osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs index b74120fa3c..dacfd649ef 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs @@ -5,6 +5,7 @@ using System; using NUnit.Framework; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Catch.Tests @@ -24,7 +25,8 @@ namespace osu.Game.Rulesets.Catch.Tests new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } }, - new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } } + new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } }, + new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } }, }; [TestCaseSource(nameof(catch_mod_mapping))] diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs index 3a9639e04d..cb2abc1595 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs @@ -5,6 +5,7 @@ using System; using NUnit.Framework; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests @@ -36,7 +37,8 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { LegacyMods.Key3, new[] { typeof(ManiaModKey3) } }, new object[] { LegacyMods.Key2, new[] { typeof(ManiaModKey2) } }, new object[] { LegacyMods.Mirror, new[] { typeof(ManiaModMirror) } }, - new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } } + new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } }, + new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } }, }; [TestCaseSource(nameof(mania_mod_mapping))] diff --git a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs index 05366e9444..2cf9842c83 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Tests.Beatmaps; @@ -28,7 +29,8 @@ namespace osu.Game.Rulesets.Osu.Tests new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } }, new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } }, new object[] { LegacyMods.Target, new[] { typeof(OsuModTargetPractice) } }, - new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } } + new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } }, + new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } }, }; [TestCaseSource(nameof(osu_mod_mapping))] diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs index 541987d63e..5f7a78ddf1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Tests.Beatmaps; @@ -25,7 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Tests new object[] { LegacyMods.Flashlight, new[] { typeof(TaikoModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(TaikoModAutoplay) } }, new object[] { LegacyMods.Random, new[] { typeof(TaikoModRandom) } }, - new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } } + new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } }, + new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } }, }; [TestCaseSource(nameof(taiko_mod_mapping))] From 7be5e0e97832bfc8fdb4c5989839a820861d8a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jul 2023 16:15:17 +0200 Subject: [PATCH 4/7] Implement back-and-forth conversion of `ModScoreV2` and `LegacyMods` --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 9 +++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 9 +++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 ++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 9 +++++++++ osu.Game/Rulesets/Ruleset.cs | 4 ++++ 5 files changed, 35 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 8f1a1b8ef5..e51e5cc5db 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -91,6 +91,9 @@ namespace osu.Game.Rulesets.Catch if (mods.HasFlagFast(LegacyMods.Relax)) yield return new CatchModRelax(); + + if (mods.HasFlagFast(LegacyMods.ScoreV2)) + yield return new ModScoreV2(); } public override IEnumerable GetModsFor(ModType type) @@ -140,6 +143,12 @@ namespace osu.Game.Rulesets.Catch new CatchModNoScope(), }; + case ModType.System: + return new Mod[] + { + new ModScoreV2(), + }; + default: return Array.Empty(); } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2e96c89516..bd6ab4086b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -157,6 +157,9 @@ namespace osu.Game.Rulesets.Mania if (mods.HasFlagFast(LegacyMods.Mirror)) yield return new ManiaModMirror(); + + if (mods.HasFlagFast(LegacyMods.ScoreV2)) + yield return new ModScoreV2(); } public override LegacyMods ConvertToLegacyMods(Mod[] mods) @@ -285,6 +288,12 @@ namespace osu.Game.Rulesets.Mania new ModAdaptiveSpeed() }; + case ModType.System: + return new Mod[] + { + new ModScoreV2(), + }; + default: return Array.Empty(); } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b44d999d4f..036d13c5aa 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -113,6 +113,9 @@ namespace osu.Game.Rulesets.Osu if (mods.HasFlagFast(LegacyMods.TouchDevice)) yield return new OsuModTouchDevice(); + + if (mods.HasFlagFast(LegacyMods.ScoreV2)) + yield return new ModScoreV2(); } public override LegacyMods ConvertToLegacyMods(Mod[] mods) @@ -212,6 +215,7 @@ namespace osu.Game.Rulesets.Osu return new Mod[] { new OsuModTouchDevice(), + new ModScoreV2(), }; default: diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index aa31b1924f..de3fa1750f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -116,6 +116,9 @@ namespace osu.Game.Rulesets.Taiko if (mods.HasFlagFast(LegacyMods.Random)) yield return new TaikoModRandom(); + + if (mods.HasFlagFast(LegacyMods.ScoreV2)) + yield return new ModScoreV2(); } public override LegacyMods ConvertToLegacyMods(Mod[] mods) @@ -176,6 +179,12 @@ namespace osu.Game.Rulesets.Taiko new ModAdaptiveSpeed() }; + case ModType.System: + return new Mod[] + { + new ModScoreV2(), + }; + default: return Array.Empty(); } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 490ec1475c..cd432e050b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -192,6 +192,10 @@ namespace osu.Game.Rulesets case ModAutoplay: value |= LegacyMods.Autoplay; break; + + case ModScoreV2: + value |= LegacyMods.ScoreV2; + break; } } From 9377622cd4010d7141eecaeebd87663fe50f358b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jul 2023 16:36:51 +0200 Subject: [PATCH 5/7] Add backwards migration to populate ScoreV2 mod for already-imported scores --- osu.Game/Database/RealmAccess.cs | 67 +++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index f9f11c49ff..1af0cf30ba 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -15,17 +15,20 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Input.Bindings; using osu.Game.IO.Legacy; using osu.Game.Models; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -79,8 +82,9 @@ namespace osu.Game.Database /// 29 2023-06-12 Run migration of old lazer scores to be best-effort in the new scoring number space. No actual realm changes. /// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations. /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. + /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. /// - private const int schema_version = 31; + private const int schema_version = 32; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -730,6 +734,8 @@ namespace osu.Game.Database Logger.Log($"Running realm migration to version {targetVersion}..."); Stopwatch stopwatch = new Stopwatch(); + var files = new RealmFileStore(this, storage); + stopwatch.Start(); switch (targetVersion) @@ -904,7 +910,6 @@ namespace osu.Game.Database case 28: { - var files = new RealmFileStore(this, storage); var scores = migration.NewRealm.All(); foreach (var score in scores) @@ -986,6 +991,64 @@ namespace osu.Game.Database break; } + + case 32: + { + foreach (var score in migration.NewRealm.All()) + { + if (!score.IsLegacyScore || !score.Ruleset.IsLegacyRuleset()) + continue; + + string? replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(@".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath(); + if (replayFilename == null) + continue; + + try + { + using (var stream = files.Store.GetStream(replayFilename)) + { + if (stream == null) + continue; + + // Trimmed down logic from LegacyScoreDecoder to extract the mods bitmask. + using (SerializationReader sr = new SerializationReader(stream)) + { + sr.ReadByte(); // Ruleset. + sr.ReadInt32(); // Version. + sr.ReadString(); // Beatmap hash. + sr.ReadString(); // Username. + sr.ReadString(); // MD5Hash. + sr.ReadUInt16(); // Count300. + sr.ReadUInt16(); // Count100. + sr.ReadUInt16(); // Count50. + sr.ReadUInt16(); // CountGeki. + sr.ReadUInt16(); // CountKatu. + sr.ReadUInt16(); // CountMiss. + + // we should have this in LegacyTotalScore already, but if we're reading through this anyways... + int totalScore = sr.ReadInt32(); + + sr.ReadUInt16(); // Max combo. + sr.ReadBoolean(); // Perfect. + + var legacyMods = (LegacyMods)sr.ReadInt32(); + + if (!legacyMods.HasFlagFast(LegacyMods.ScoreV2) || score.APIMods.Any(mod => mod.Acronym == @"SV2")) + continue; + + score.APIMods = score.APIMods.Append(new APIMod(new ModScoreV2())).ToArray(); + score.LegacyTotalScore = score.TotalScore = totalScore; + } + } + } + catch (Exception e) + { + Logger.Error(e, $"Failed to read replay {replayFilename} during score migration", LoggingTarget.Database); + } + } + + break; + } } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From e12255bbe5e41a6cba7acb28282f74c008769575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jul 2023 17:01:28 +0200 Subject: [PATCH 6/7] Do not run legacy conversion with ScoreV2 mod present --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 60530c31cb..bc3629c25b 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -205,6 +206,10 @@ namespace osu.Game.Database if (ruleset is not ILegacyRuleset legacyRuleset) return score.TotalScore; + var mods = score.Mods; + if (mods.Any(mod => mod is ModScoreV2)) + return score.TotalScore; + var playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods); if (playableBeatmap.HitObjects.Count == 0) @@ -212,7 +217,7 @@ namespace osu.Game.Database ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator(); - sv1Simulator.Simulate(beatmap, playableBeatmap, score.Mods); + sv1Simulator.Simulate(beatmap, playableBeatmap, mods); return ConvertFromLegacyTotalScore(score, new DifficultyAttributes { From 9e4ffc8c12cdce5bb34fee06fafdec76ac41337e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Jul 2023 21:10:01 +0200 Subject: [PATCH 7/7] Split helper method for populations from replay --- osu.Game/Database/RealmAccess.cs | 93 ++++++------------- .../StandardisedScoreMigrationTools.cs | 35 +++++++ 2 files changed, 63 insertions(+), 65 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1af0cf30ba..f32b161bb6 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -26,7 +26,6 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Input.Bindings; -using osu.Game.IO.Legacy; using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -914,31 +913,13 @@ namespace osu.Game.Database foreach (var score in scores) { - string? replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(@".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath(); - if (replayFilename == null) - continue; - - try + score.PopulateFromReplay(files, sr => { - using (var stream = files.Store.GetStream(replayFilename)) - { - if (stream == null) - continue; - - // Trimmed down logic from LegacyScoreDecoder to extract the version from replays. - using (SerializationReader sr = new SerializationReader(stream)) - { - sr.ReadByte(); // Ruleset. - int version = sr.ReadInt32(); - if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) - score.IsLegacyScore = true; - } - } - } - catch (Exception e) - { - Logger.Error(e, $"Failed to read replay {replayFilename} during score migration", LoggingTarget.Database); - } + sr.ReadByte(); // Ruleset. + int version = sr.ReadInt32(); + if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) + score.IsLegacyScore = true; + }); } break; @@ -999,52 +980,34 @@ namespace osu.Game.Database if (!score.IsLegacyScore || !score.Ruleset.IsLegacyRuleset()) continue; - string? replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(@".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath(); - if (replayFilename == null) - continue; - - try + score.PopulateFromReplay(files, sr => { - using (var stream = files.Store.GetStream(replayFilename)) - { - if (stream == null) - continue; + sr.ReadByte(); // Ruleset. + sr.ReadInt32(); // Version. + sr.ReadString(); // Beatmap hash. + sr.ReadString(); // Username. + sr.ReadString(); // MD5Hash. + sr.ReadUInt16(); // Count300. + sr.ReadUInt16(); // Count100. + sr.ReadUInt16(); // Count50. + sr.ReadUInt16(); // CountGeki. + sr.ReadUInt16(); // CountKatu. + sr.ReadUInt16(); // CountMiss. - // Trimmed down logic from LegacyScoreDecoder to extract the mods bitmask. - using (SerializationReader sr = new SerializationReader(stream)) - { - sr.ReadByte(); // Ruleset. - sr.ReadInt32(); // Version. - sr.ReadString(); // Beatmap hash. - sr.ReadString(); // Username. - sr.ReadString(); // MD5Hash. - sr.ReadUInt16(); // Count300. - sr.ReadUInt16(); // Count100. - sr.ReadUInt16(); // Count50. - sr.ReadUInt16(); // CountGeki. - sr.ReadUInt16(); // CountKatu. - sr.ReadUInt16(); // CountMiss. + // we should have this in LegacyTotalScore already, but if we're reading through this anyways... + int totalScore = sr.ReadInt32(); - // we should have this in LegacyTotalScore already, but if we're reading through this anyways... - int totalScore = sr.ReadInt32(); + sr.ReadUInt16(); // Max combo. + sr.ReadBoolean(); // Perfect. - sr.ReadUInt16(); // Max combo. - sr.ReadBoolean(); // Perfect. + var legacyMods = (LegacyMods)sr.ReadInt32(); - var legacyMods = (LegacyMods)sr.ReadInt32(); + if (!legacyMods.HasFlagFast(LegacyMods.ScoreV2) || score.APIMods.Any(mod => mod.Acronym == @"SV2")) + return; - if (!legacyMods.HasFlagFast(LegacyMods.ScoreV2) || score.APIMods.Any(mod => mod.Acronym == @"SV2")) - continue; - - score.APIMods = score.APIMods.Append(new APIMod(new ModScoreV2())).ToArray(); - score.LegacyTotalScore = score.TotalScore = totalScore; - } - } - } - catch (Exception e) - { - Logger.Error(e, $"Failed to read replay {replayFilename} during score migration", LoggingTarget.Database); - } + score.APIMods = score.APIMods.Append(new APIMod(new ModScoreV2())).ToArray(); + score.LegacyTotalScore = score.TotalScore = totalScore; + }); } break; diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index bc3629c25b..b8afdad294 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -5,7 +5,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Extensions; +using osu.Game.IO.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; @@ -287,6 +290,38 @@ namespace osu.Game.Database } } + /// + /// Used to populate the model using data parsed from its corresponding replay file. + /// + /// The score to run population from replay for. + /// A instance to use for fetching replay. + /// + /// Delegate describing the population to execute. + /// The delegate's argument is a instance which permits to read data from the replay stream. + /// + public static void PopulateFromReplay(this ScoreInfo score, RealmFileStore files, Action populationFunc) + { + string? replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(@".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath(); + if (replayFilename == null) + return; + + try + { + using (var stream = files.Store.GetStream(replayFilename)) + { + if (stream == null) + return; + + using (SerializationReader sr = new SerializationReader(stream)) + populationFunc.Invoke(sr); + } + } + catch (Exception e) + { + Logger.Error(e, $"Failed to read replay {replayFilename} during score migration", LoggingTarget.Database); + } + } + private class FakeHit : HitObject { private readonly Judgement judgement;