public static Score FromDb(DbScore dbScore, RankedStatus status, DbScore[] scores = null) { var score = new Score { ScoreId = dbScore.Id, User = Base.UserCache[dbScore.UserId], Date = dbScore.Date, UserId = dbScore.UserId, FileChecksum = dbScore.FileChecksum, ReplayChecksum = dbScore.ReplayChecksum, TotalScore = dbScore.TotalScore, PerformancePoints = dbScore.PerformancePoints, MaxCombo = dbScore.MaxCombo, Count50 = dbScore.Count50, Count100 = dbScore.Count100, Count300 = dbScore.Count300, CountMiss = dbScore.CountMiss, CountKatu = dbScore.CountKatu, CountGeki = dbScore.CountGeki, Perfect = dbScore.Perfect, Mods = dbScore.Mods }; score.Relaxing = (score.Mods & Mods.Relax) != 0; score.CalculateLeaderboardRank(scores, status); return(score); }
/// <summary> /// Check if User has Obtained an achievement on this Score Submission /// </summary> /// <param name="factory">Context Factory</param> /// <param name="user">Who tries to Obtain</param> /// <param name="score">Submitted Score</param> /// <param name="map">Beatmap</param> /// <param name="set">Beatmap Set</param> /// <param name="oldLb">Old LeaderBoard</param> /// <param name="newLb">New LeaderBoard</param> /// <returns>Obtained Achievements</returns> public static string ProcessAchievements(SoraDbContext ctx, DbUser user, DbScore score, Beatmap map, BeatmapSet set, DbLeaderboard oldLb, DbLeaderboard newLb ) { var l = new List <Achievement>(); /* * if ((int) newLB.PerformancePointsOsu == 4914) * { * var ach = DBAchievement.GetAchievement(factory, "oog"); * if (!user.AlreadyOptainedAchievement(ach)) * _l.Add(ach); * } */ // Insert custom achievements here. I'll implement a Plugin System later! but this will work for now. // END OF CUSTOM ACHIEVEMENTS var retVal = l.Aggregate("", (current, ach) => current + ach.ToOsuString() + "/"); retVal.TrimEnd('/'); return(retVal); }
public async Task <IActionResult> GetReplay( [FromQuery(Name = "c")] int replayId, [FromQuery(Name = "m")] PlayMode mode, [FromQuery(Name = "u")] string userName, [FromQuery(Name = "h")] string pass, [FromServices] SoraDbContext ctx ) { var user = await DbUser.GetDbUser(ctx, userName); if (user == null) { return(Ok("err: pass")); } if (!user.IsPassword(pass)) { return(Ok("err: pass")); } var s = await DbScore.GetScore(ctx, replayId); if (s == null) { return(NotFound()); } return(File(System.IO.File.OpenRead("data/replays/" + s.ReplayMd5), "binary/octet-stream", s.ReplayMd5)); }
public static async Task <(bool Pass, DbScore score)> ParseScore(SoraDbContext ctx, string encScore, string iv, string osuVersion) { var decryptedScore = Crypto.DecryptString( Convert.FromBase64String(encScore), Encoding.ASCII.GetBytes(string.Format(PRIVATE_KEY, osuVersion)), Convert.FromBase64String(iv) ); var x = decryptedScore.Split(':'); var score = new DbScore { FileMd5 = x[0], ScoreOwner = await DbUser.GetDbUser(ctx, x[1]), Count300 = int.Parse(x[3]), Count100 = int.Parse(x[4]), Count50 = int.Parse(x[5]), CountGeki = int.Parse(x[6]), CountKatu = int.Parse(x[7]), CountMiss = int.Parse(x[8]), TotalScore = int.Parse(x[9]), MaxCombo = short.Parse(x[10]), Mods = (Mod)uint.Parse(x[13]), PlayMode = (PlayMode)byte.Parse(x[15]), Date = DateTime.Now, }; score.UserId = score.ScoreOwner?.Id ?? 0; return(bool.Parse(x[14]), score); }
public static string CalculateRank(DbScore score) { var tHits = score.Count50 + score.Count100 + score.Count300 + score.CountMiss; var ratio300 = (float)score.Count300 / tHits; var ratio50 = (float)score.Count50 / tHits; if (ratio300 == 1) { return((score.Mods & Mods.Hidden) > 0 || (score.Mods & Mods.Flashlight) > 0 ? "SSHD" : "SS"); } if (ratio300 > 0.9 && ratio50 <= 0.01 && score.CountMiss == 0) { return((score.Mods & Mods.Hidden) > 0 || (score.Mods & Mods.Flashlight) > 0 ? "SHD" : "S"); } if (ratio300 > 0.8 && score.CountMiss == 0 || ratio300 > 0.9) { return("A"); } if (ratio300 > 0.7 && score.CountMiss == 0 || ratio300 > 0.8) { return("B"); } return(ratio300 > 0.6 ? "C" : "D"); }
public DbUser RegisterUser(string username, string email, string pass) { PassHashingService phs = new PassHashingService(); string salt = phs.Salt(); string passwordToDb = phs.HashedPass(pass, salt); using (var ctx = new MyDbContext()) { if (CheckIfUserUnique(username, email, ctx)) { DbUser newUser = new DbUser { Username = username, Email = email, Password = passwordToDb, Salt = salt }; DbScore newScore = new DbScore { User = newUser, Wins = 0, Losses = 0 }; ctx.Users.Add(newUser); ctx.Scores.Add(newScore); ctx.SaveChanges(); return(newUser); } return(null); } }
public Scoreboard(Beatmap bm, BeatmapSet bmParent, IAsyncEnumerable <DbScore> scores, DbScore ownScore = null) { _bm = bm; _parent = bmParent; _scores = scores; _ownScore = ownScore; }
public async Task<IActionResult> GetReplay( [FromQuery(Name = "c")] int replayId, [FromQuery(Name = "m")] PlayMode mode, [FromQuery(Name = "u")] string userName, [FromQuery(Name = "h")] string pass ) { var user = await DbUser.GetDbUser(_ctx, userName); if (user == null) return Ok("err: pass"); if (!user.IsPassword(pass)) return Ok("err: pass"); var s = await DbScore.GetScore(_ctx, replayId); if (s == null) return NotFound(); return File(System.IO.File.OpenRead("data/replays/" + s.ReplayMd5), "binary/octet-stream", s.ReplayMd5); }
public static async Task <double> CalculatePerformancePoints(DbScore score) { var beatmapMd5 = await GetBeatmapByMd5(score.FileChecksum); if (beatmapMd5 == string.Empty) { return(0.0); } var workingBeatmap = new ProcessorWorkingBeatmap($"./data/beatmaps/{beatmapMd5}.osu"); var psp = new ProcessorScoreDecoder(workingBeatmap); var parsedScore = psp.Parse(score); var categoryAttribs = new Dictionary <string, double>(); var pp = parsedScore.ScoreInfo.Ruleset .CreateInstance() .CreatePerformanceCalculator(workingBeatmap, parsedScore.ScoreInfo) .Calculate(categoryAttribs); return(pp); }
public async Task<IActionResult> GetScoreResult( [FromQuery(Name = "v")] ScoreboardType type, [FromQuery(Name = "c")] string fileMd5, [FromQuery(Name = "f")] string f, [FromQuery(Name = "m")] PlayMode playMode, [FromQuery(Name = "i")] int i, [FromQuery(Name = "mods")] Mod mods, [FromQuery(Name = "us")] string us, [FromQuery(Name = "ha")] string pa, [FromServices] IServiceProvider serviceProvider) { try { var dbUser = await DbUser.GetDbUser(_ctx, us); var user = dbUser?.ToUser(); if (dbUser?.IsPassword(pa) != true) return Ok("error: pass"); var cacheHash = Hex.ToHex( Crypto.GetMd5( $"{fileMd5}{playMode}{mods}{type}{user.Id}{user.UserName}" ) ); if (_cache.TryGet($"sora:Scoreboards:{cacheHash}", out string cachedData)) return Ok(cachedData); var scores = await DbScore.GetScores(_ctx, fileMd5, dbUser, playMode, type == ScoreboardType.Friends, type == ScoreboardType.Country, type == ScoreboardType.Mods, mods); var beatmap = DbBeatmap.GetBeatmap(_ctx, fileMd5); BeatmapSet apiSet; if (beatmap == null) { apiSet = await _pisstaube.FetchBeatmapSetAsync(fileMd5); if (apiSet == null) goto JustContinue; var beatmaps = DbBeatmap.FromBeatmapSet(apiSet).ToList(); var beatmapChecksums = beatmaps.Select(s => s.FileMd5); var dbBeatmaps = _ctx.Beatmaps.Where(rset => beatmapChecksums.Any(lFileMd5 => rset.FileMd5 == lFileMd5)) .ToList(); var concurrentLock = new object(); var pool = serviceProvider.GetRequiredService<DbContextPool<SoraDbContext>>(); Task.WaitAll(beatmaps.Select(rawBeatmap => Task.Run(async () => { var context = pool.Rent(); var dbBeatmap = dbBeatmaps.FirstOrDefault(s => s.FileMd5 == rawBeatmap.FileMd5); if (dbBeatmap != null && (dbBeatmap.Flags & DbBeatmapFlags.RankedFreeze) != 0) { rawBeatmap.RankedStatus = dbBeatmap.RankedStatus; rawBeatmap.Flags = dbBeatmap.Flags; } context.Beatmaps.AddOrUpdate(rawBeatmap); await context.SaveChangesAsync(); pool.Return(context); })).ToArray()); beatmap = beatmaps.FirstOrDefault(s => s.FileMd5 == fileMd5); } JustContinue: var ownScore = await DbScore.GetLatestScore(_ctx, new DbScore { FileMd5 = fileMd5, UserId = user.Id, PlayMode = playMode, TotalScore = 0 }); var sScores = scores.Select(s => new Score { Count100 = s.Count100, Count50 = s.Count50, Count300 = s.Count300, Date = s.Date, Mods = s.Mods, CountGeki = s.CountGeki, CountKatu = s.CountKatu, CountMiss = s.CountMiss, FileMd5 = s.FileMd5, MaxCombo = s.MaxCombo, PlayMode = s.PlayMode, TotalScore = s.TotalScore, UserId = s.UserId, UserName = s.ScoreOwner.UserName, }).ToList(); // Fetch the correct position for sScore for (var j = 0; j < scores.Count; j++) { sScores[j].Position = await scores[j].Position(_ctx); } Score ownsScore = null; if (ownScore != null) ownsScore = new Score { Count100 = ownScore.Count100, Count50 = ownScore.Count50, Count300 = ownScore.Count300, Date = ownScore.Date, Mods = ownScore.Mods, CountGeki = ownScore.CountGeki, CountKatu = ownScore.CountKatu, CountMiss = ownScore.CountMiss, FileMd5 = ownScore.FileMd5, MaxCombo = ownScore.MaxCombo, PlayMode = ownScore.PlayMode, TotalScore = ownScore.TotalScore, UserId = dbUser.Id, UserName = dbUser.UserName, Position = await ownScore.Position(_ctx) }; BeatmapSet set = null; if (beatmap != null) set = new BeatmapSet { SetID = beatmap.Id, Artist = beatmap.Artist, Title = beatmap.Title, RankedStatus = beatmap.RankedStatus, ChildrenBeatmaps = new List<Beatmap> { new Beatmap { FileMD5 = beatmap.FileMd5, DiffName = beatmap.DiffName, ParentSetID = beatmap.SetId, BeatmapID = beatmap.Id, Mode = beatmap.PlayMode } } }; var sboard = new Scoreboard(set?.ChildrenBeatmaps.FirstOrDefault(bm => bm.FileMD5 == fileMd5), set, sScores, ownsScore); _cache.Set($"sora:Scoreboards:{cacheHash}", cachedData = sboard.ToOsuString(), TimeSpan.FromSeconds(30)); return Ok(cachedData); } catch (Exception ex) { Logger.Err(ex); return Ok("Failed"); } }
public async Task<IActionResult> PostSubmitModular() { if (!Directory.Exists("data/replays")) Directory.CreateDirectory("data/replays"); string encScore = Request.Form["score"]; string iv = Request.Form["iv"]; string osuver = Request.Form["osuver"]; string passwd = Request.Form["pass"]; var (pass, score) = ScoreSubmissionParser.ParseScore(encScore, iv, osuver); var dbUser = await DbUser.GetDbUser(_ctx, score.UserName); if (dbUser == null) return Ok("error: pass"); if (!dbUser.IsPassword(passwd)) return Ok("error: pass"); if (!_ps.TryGet(dbUser.Id, out var pr)) return Ok("error: pass"); // User not logged in in Bancho! if (!pass || !RankedMods.IsRanked(score.Mods)) { var lb = await DbLeaderboard.GetLeaderboardAsync(_ctx, dbUser); lb.IncreasePlaycount(score.PlayMode); lb.IncreaseScore((ulong) score.TotalScore, false, score.PlayMode); await lb.SaveChanges(_ctx); // Send to other People await _ev.RunEvent( EventType.BanchoUserStatsRequest, new BanchoUserStatsRequestArgs {UserIds = new List<int> {score.Id}, Pr = pr} ); // Send to self await _ev.RunEvent( EventType.BanchoSendUserStatus, new BanchoSendUserStatusArgs {Status = pr.Status, Pr = pr} ); return Ok("Thanks for your hard work! onii-chyan~"); // even though, we're Sora, we can still be cute! } var replayFileData = Request.Form.Files.GetFile("score"); var dbScore = new DbScore { Accuracy = score.ComputeAccuracy(), Count100 = score.Count100, Count50 = score.Count50, Count300 = score.Count300, Date = score.Date, Mods = score.Mods, CountGeki = score.CountGeki, CountKatu = score.CountKatu, CountMiss = score.CountMiss, FileMd5 = score.FileMd5, MaxCombo = score.MaxCombo, PlayMode = score.PlayMode, ScoreOwner = dbUser, TotalScore = score.TotalScore, UserId = dbUser.Id }; await _pisstaube.DownloadBeatmapAsync(dbScore.FileMd5); await using (var m = new MemoryStream()) { replayFileData.CopyTo(m); m.Position = 0; dbScore.ReplayMd5 = Hex.ToHex(Crypto.GetMd5(m)) ?? string.Empty; if (!string.IsNullOrEmpty(dbScore.ReplayMd5)) { await using var replayFile = System.IO.File.Create($"data/replays/{dbScore.ReplayMd5}"); m.Position = 0; m.WriteTo(replayFile); m.Close(); replayFile.Close(); } } dbScore.PerformancePoints = dbScore.ComputePerformancePoints(); var oldScore = await DbScore.GetLatestScore(_ctx, dbScore); var oldLb = await DbLeaderboard.GetLeaderboardAsync(_ctx, dbScore.ScoreOwner); var oldStdPos = oldLb.GetPosition(_ctx, dbScore.PlayMode); var oldAcc = oldLb.GetAccuracy(_ctx, dbScore.PlayMode); double newAcc; if (oldScore != null && oldScore.TotalScore <= dbScore.TotalScore) { _ctx.Remove(oldScore); System.IO.File.Delete($"data/replays/{oldScore.ReplayMd5}"); await DbScore.InsertScore(_ctx, dbScore); } else if (oldScore == null) { await DbScore.InsertScore(_ctx, dbScore); } else { System.IO.File.Delete($"data/replays/{oldScore.ReplayMd5}"); } var newlb = await DbLeaderboard.GetLeaderboardAsync(_ctx, dbScore.ScoreOwner); newlb.IncreasePlaycount(dbScore.PlayMode); newlb.IncreaseScore((ulong) dbScore.TotalScore, true, dbScore.PlayMode); newlb.IncreaseScore((ulong) dbScore.TotalScore, false, dbScore.PlayMode); newlb.UpdatePp(_ctx, dbScore.PlayMode); await newlb.SaveChanges(_ctx); var newStdPos = newlb.GetPosition(_ctx, dbScore.PlayMode); newAcc = newlb.GetAccuracy(_ctx, dbScore.PlayMode); var newScore = await DbScore.GetLatestScore(_ctx, dbScore); var set = await _pisstaube.FetchBeatmapSetAsync(dbScore.FileMd5); var bm = set?.ChildrenBeatmaps.First(x => x.FileMD5 == dbScore.FileMd5) ?? new Beatmap(); ulong oldRankedScore; ulong newRankedScore; double oldPp; double newPp; switch (dbScore.PlayMode) { case PlayMode.Osu: oldRankedScore = oldLb.RankedScoreOsu; newRankedScore = newlb.RankedScoreOsu; oldPp = oldLb.PerformancePointsOsu; newPp = newlb.PerformancePointsOsu; break; case PlayMode.Taiko: oldRankedScore = oldLb.RankedScoreTaiko; newRankedScore = newlb.RankedScoreTaiko; oldPp = oldLb.PerformancePointsTaiko; newPp = newlb.PerformancePointsTaiko; break; case PlayMode.Ctb: oldRankedScore = oldLb.RankedScoreCtb; newRankedScore = newlb.RankedScoreCtb; oldPp = oldLb.PerformancePointsCtb; newPp = newlb.PerformancePointsCtb; break; case PlayMode.Mania: oldRankedScore = oldLb.RankedScoreMania; newRankedScore = newlb.RankedScoreMania; oldPp = oldLb.PerformancePointsMania; newPp = newlb.PerformancePointsMania; break; default: return Ok(""); } var newScorePosition = newScore != null ? await newScore.Position(_ctx) : 0; var oldScorePosition = oldScore != null ? await oldScore.Position(_ctx) : 0; if (newScorePosition == 1) _sora.SendMessage( $"[http://{_config.Server.ScreenShotHostname}/{dbScore.ScoreOwner.Id} {dbScore.ScoreOwner.UserName}] " + $"has reached #1 on [https://osu.ppy.sh/b/{bm.BeatmapID} {set?.Title} [{bm.DiffName}]] " + $"using {ModUtil.ToString(newScore.Mods)} " + $"Good job! +{newScore.PerformancePoints:F}PP", "#announce", false ); Logger.Info( $"{LCol.RED}{dbScore.ScoreOwner.UserName}", $"{LCol.PURPLE}( {dbScore.ScoreOwner.Id} ){LCol.WHITE}", $"has just submitted a Score! he earned {LCol.BLUE}{newScore?.PerformancePoints:F}PP", $"{LCol.WHITE}with an Accuracy of {LCol.RED}{newScore?.Accuracy * 100:F}", $"{LCol.WHITE}on {LCol.YELLOW}{set?.Title} [{bm.DiffName}]", $"{LCol.WHITE}using {LCol.BLUE}{ModUtil.ToString(newScore?.Mods ?? Mod.None)}" ); var bmChart = new Chart( "beatmap", "Beatmap Ranking", $"https://osu.ppy.sh/b/{bm.BeatmapID}", oldScorePosition, newScorePosition, oldScore?.MaxCombo ?? 0, newScore?.MaxCombo ?? 0, oldScore?.Accuracy * 100 ?? 0, newScore?.Accuracy * 100 ?? 0, (ulong) (oldScore?.TotalScore ?? 0), (ulong) (newScore?.TotalScore ?? 0), oldScore?.PerformancePoints ?? 0, newScore?.PerformancePoints ?? 0, newScore?.Id ?? 0 ); var overallChart = new Chart( "overall", "Global Ranking", $"https://osu.ppy.sh/u/{dbUser.Id}", (int) oldStdPos, (int) newStdPos, 0, 0, oldAcc * 100, newAcc * 100, oldRankedScore, newRankedScore, oldPp, newPp, newScore?.Id ?? 0, AchievementProcessor.ProcessAchievements( _ctx, dbScore.ScoreOwner, score, bm, set, oldLb, newlb ) ); pr["LB"] = newlb; pr.Stats.Accuracy = (float) newlb.GetAccuracy(_ctx, score.PlayMode); pr.Stats.Position = newlb.GetPosition(_ctx, score.PlayMode); switch (score.PlayMode) { case PlayMode.Osu: pr.Stats.PerformancePoints = (ushort) newlb.PerformancePointsOsu; pr.Stats.TotalScore = (ushort) newlb.TotalScoreOsu; pr.Stats.RankedScore = (ushort) newlb.RankedScoreOsu; pr.Stats.PlayCount = (ushort) newlb.PlayCountOsu; break; case PlayMode.Taiko: pr.Stats.PerformancePoints = (ushort) newlb.PerformancePointsTaiko; pr.Stats.TotalScore = (ushort) newlb.TotalScoreTaiko; pr.Stats.RankedScore = (ushort) newlb.RankedScoreTaiko; pr.Stats.PlayCount = (ushort) newlb.PlayCountTaiko; break; case PlayMode.Ctb: pr.Stats.PerformancePoints = (ushort) newlb.PerformancePointsCtb; pr.Stats.TotalScore = (ushort) newlb.TotalScoreCtb; pr.Stats.RankedScore = (ushort) newlb.RankedScoreCtb; pr.Stats.PlayCount = (ushort) newlb.PlayCountCtb; break; case PlayMode.Mania: pr.Stats.PerformancePoints = (ushort) newlb.PerformancePointsMania; pr.Stats.TotalScore = (ushort) newlb.TotalScoreMania; pr.Stats.RankedScore = (ushort) newlb.RankedScoreMania; pr.Stats.PlayCount = (ushort) newlb.PlayCountMania; break; } // Send to other People await _ev.RunEvent( EventType.BanchoUserStatsRequest, new BanchoUserStatsRequestArgs {UserIds = new List<int> {score.Id}, Pr = pr} ); // Send to self await _ev.RunEvent( EventType.BanchoSendUserStatus, new BanchoSendUserStatusArgs {Status = pr.Status, Pr = pr} ); return Ok( $"beatmapId:{bm.BeatmapID}|beatmapSetId:{bm.ParentSetID}|beatmapPlaycount:0|beatmapPasscount:0|approvedDate:\n\n" + bmChart.ToOsuString() + "\n" + overallChart.ToOsuString() ); }
public async Task <IActionResult> GetScoreResult( [FromQuery(Name = "v")] ScoreboardType type, [FromQuery(Name = "c")] string fileMd5, [FromQuery(Name = "f")] string f, [FromQuery(Name = "m")] PlayMode playMode, [FromQuery(Name = "i")] int i, [FromQuery(Name = "mods")] Mod mods, [FromQuery(Name = "us")] string us, [FromQuery(Name = "ha")] string pa, [FromServices] IServiceProvider serviceProvider, [FromServices] SoraDbContext ctx, [FromServices] DbContextPool <SoraDbContext> ctxPool, [FromServices] Pisstaube pisstaube, [FromServices] Cache cache) { try { var dbUser = await DbUser.GetDbUser(ctx, us); if (dbUser?.IsPassword(pa) != true) { return(Ok("error: pass")); } var cacheHash = Hex.ToHex( Crypto.GetMd5( $"{fileMd5}{playMode}{mods}{type}{dbUser.Id}{dbUser.UserName}" ) ); if (cache.TryGet($"sora:Scoreboards:{cacheHash}", out string cachedData)) { return(Ok(cachedData)); } var scores = DbScore.GetScores(ctx, fileMd5, dbUser, playMode, type == ScoreboardType.Friends, type == ScoreboardType.Country, type == ScoreboardType.Mods, mods); BeatmapSet set = null; DbScore ownScore = null; var beatmap = DbBeatmap.GetBeatmap(ctx, fileMd5); if (beatmap == null) { var apiSet = await pisstaube.FetchBeatmapSetAsync(fileMd5); if (apiSet == null) { goto JustContinue; } var beatmaps = DbBeatmap.FromBeatmapSet(apiSet).ToList(); var beatmapChecksums = beatmaps.Select(s => s.FileMd5); var dbBeatmaps = ctx.Beatmaps.Where(rset => beatmapChecksums.Any(lFileMd5 => rset.FileMd5 == lFileMd5)) .ToList(); var pool = serviceProvider.GetRequiredService <DbContextPool <SoraDbContext> >(); Task.WaitAll(beatmaps.Select(rawBeatmap => Task.Run(async() => { var context = pool.Rent(); try { var dbBeatmap = dbBeatmaps.FirstOrDefault(s => s.FileMd5 == rawBeatmap.FileMd5); if (dbBeatmap != null && (dbBeatmap.Flags & DbBeatmapFlags.RankedFreeze) != 0) { rawBeatmap.RankedStatus = dbBeatmap.RankedStatus; rawBeatmap.Flags = dbBeatmap.Flags; } context.Beatmaps.AddOrUpdate(rawBeatmap); await context.SaveChangesAsync(); } finally { pool.Return(context); } })).ToArray()); beatmap = beatmaps.FirstOrDefault(s => s.FileMd5 == fileMd5); } await foreach (var score in DbScore.GetScores(ctx, fileMd5, dbUser, playMode, false, false, false, mods, true)) { ownScore = score; break; } if (beatmap != null) { set = new BeatmapSet { SetID = beatmap.Id, Artist = beatmap.Artist, Title = beatmap.Title, RankedStatus = beatmap.RankedStatus, ChildrenBeatmaps = new List <Beatmap> { new Beatmap { FileMD5 = beatmap.FileMd5, DiffName = beatmap.DiffName, ParentSetID = beatmap.SetId, BeatmapID = beatmap.Id, Mode = beatmap.PlayMode, }, }, } } ; JustContinue: var sboard = new Scoreboard(set?.ChildrenBeatmaps.FirstOrDefault(bm => bm.FileMD5 == fileMd5), set, scores, ownScore); cache.Set($"sora:Scoreboards:{cacheHash}", cachedData = await sboard.ToOsuString(ctxPool), TimeSpan.FromSeconds(30)); return(Ok(cachedData)); } catch (Exception ex) { Logger.Err(ex); return(Ok("Failed")); } }