From e1f49c8888a892c42f35d2e74597fbbd533b2a3d Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:37:52 +0300 Subject: [PATCH 1/6] fix: Support http range for `/files/beatmap/audio` --- packages/server/scripts/beatmapFile.ts | 46 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/packages/server/scripts/beatmapFile.ts b/packages/server/scripts/beatmapFile.ts index 16215752..42196c4c 100644 --- a/packages/server/scripts/beatmapFile.ts +++ b/packages/server/scripts/beatmapFile.ts @@ -1,17 +1,15 @@ -import { ServerResponse } from 'http'; +import fs from 'fs'; +import type { ServerResponse } from 'http'; import path from 'path'; -import { directoryWalker } from '../utils/directories'; -import { ExtendedIncomingMessage } from '../utils/http'; -import { sendJson } from '../utils/index'; +import type { ExtendedIncomingMessage } from '../utils/http'; +import { getContentType, sendJson } from '../utils/index'; export function beatmapFileShortcut( req: ExtendedIncomingMessage, res: ServerResponse, beatmapFileType: 'audio' | 'background' | 'file' ) { - const url = req.pathname || '/'; - const osuInstance: any = req.instanceManager.getInstance( req.instanceManager.focusedClient ); @@ -40,10 +38,36 @@ export function beatmapFileShortcut( }); } - directoryWalker({ - res, - baseUrl: url, - pathname: fileName || '', - folderPath: folder + const filePath = path.join(folder, fileName); + const fileStat = fs.statSync(filePath); + + if (req.headers.range) { + const range = req.headers.range.replace('bytes=', '').split('-'); + const start = parseInt(range[0]); + const end = range[1] ? parseInt(range[1]) : fileStat.size - 1; + + if (start >= fileStat.size || end >= fileStat.size) { + res.writeHead(416, { + 'Content-Range': `bytes */${fileStat.size}` + }); + return res.end(); + } + + res.writeHead(206, { + 'Accept-Ranges': 'bytes', + 'Content-Type': getContentType(fileName), + 'Content-Range': `bytes ${start}-${end}/${fileStat.size}`, + 'Content-Length': end - start + 1 + }); + + fs.createReadStream(filePath, { start, end }).pipe(res); + return; + } + + res.writeHead(200, { + 'Content-Type': getContentType(fileName), + 'Content-Length': fileStat.size }); + + fs.createReadStream(filePath).pipe(res); } From 175cbad0a19ed0e4178172758cb8400ba98c4e9d Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:41:19 +0300 Subject: [PATCH 2/6] chore: remove error for `favicon.ico` not found --- packages/server/router/index.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/server/router/index.ts b/packages/server/router/index.ts index 623a22c5..5229ac2b 100644 --- a/packages/server/router/index.ts +++ b/packages/server/router/index.ts @@ -436,6 +436,24 @@ export default function buildBaseApi(server: Server) { ); }); + server.app.route('/favicon.ico', 'GET', (req, res) => { + fs.readFile(path.join(pkgAssetsPath, 'favicon.ico'), (err, content) => { + if (err) { + wLogger.debug(`/${'favicon.ico'}`, err); + res.writeHead(404, { 'Content-Type': 'text/html' }); + + res.end('page not found'); + return; + } + + res.writeHead(200, { + 'Content-Type': 'image/vnd.microsoft.icon; charset=utf-8' + }); + + res.end(content); + }); + }); + server.app.route(/.*/, 'GET', (req, res) => { const url = req.pathname || '/'; const staticPath = getStaticPath(); From 24d9e70079f90bf3be8545e0b5c16bf4cdef4c59 Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:43:32 +0300 Subject: [PATCH 3/6] fix: Use lazer grade system for lazer client --- packages/common/utils/downloader.ts | 4 +- packages/server/utils/directories.ts | 4 +- packages/tosu/src/api/utils/buildResult.ts | 1 + packages/tosu/src/api/utils/buildResultV2.ts | 12 +- packages/tosu/src/states/gameplay.ts | 3 + packages/tosu/src/states/resultScreen.ts | 2 + packages/tosu/src/utils/calculators.ts | 206 +++++++++++-------- 7 files changed, 134 insertions(+), 98 deletions(-) diff --git a/packages/common/utils/downloader.ts b/packages/common/utils/downloader.ts index 072ea60d..08da77e1 100644 --- a/packages/common/utils/downloader.ts +++ b/packages/common/utils/downloader.ts @@ -18,9 +18,7 @@ export const updateProgressBar = ( const progressBar = '█'.repeat(filledWidth) + '░'.repeat(emptyWidth); process.stdout.write( - `${coloredText} ${title}: [${progressBar}] ${(progress * 100).toFixed( - 2 - )}%${message}\r` + `${coloredText} ${title}: [${progressBar}] ${(progress * 100).toFixed(2)}%${message}\r` ); if (progress === 1) { diff --git a/packages/server/utils/directories.ts b/packages/server/utils/directories.ts index 42198815..ae0c14c5 100644 --- a/packages/server/utils/directories.ts +++ b/packages/server/utils/directories.ts @@ -120,9 +120,7 @@ export function readDirectory( const html = folders.map((r) => { const slashAtTheEnd = getContentType(r) === '' ? '/' : ''; - return `
  • ${r}
  • `; + return `
  • ${r}
  • `; }); return callback( diff --git a/packages/tosu/src/api/utils/buildResult.ts b/packages/tosu/src/api/utils/buildResult.ts index ef4b093f..60b7263f 100644 --- a/packages/tosu/src/api/utils/buildResult.ts +++ b/packages/tosu/src/api/utils/buildResult.ts @@ -230,6 +230,7 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { name: resultScreen.playerName, score: resultScreen.score, accuracy: calculateAccuracy({ + _round: true, hits: resultScreenHits, mode: gameplay.mode }), diff --git a/packages/tosu/src/api/utils/buildResultV2.ts b/packages/tosu/src/api/utils/buildResultV2.ts index ba6c3b54..d195403c 100644 --- a/packages/tosu/src/api/utils/buildResultV2.ts +++ b/packages/tosu/src/api/utils/buildResultV2.ts @@ -32,7 +32,8 @@ import { CalculateMods } from '@/utils/osuMods.types'; const convertMemoryPlayerToResult = ( memoryPlayer: MemoryLeaderboardPlayer, - gameMode: any + gameMode: any, + client: ClientType ): Leaderboard => { const hits = { 300: memoryPlayer.h300, @@ -53,7 +54,7 @@ const convertMemoryPlayerToResult = ( name: memoryPlayer.name, score: memoryPlayer.score, - accuracy: calculateAccuracy({ hits, mode: gameMode }), + accuracy: calculateAccuracy({ _round: true, hits, mode: gameMode }), hits, @@ -69,6 +70,7 @@ const convertMemoryPlayerToResult = ( rate: memoryPlayer.mods.rate }, rank: calculateGrade({ + _lazer: client === ClientType.lazer, mods: memoryPlayer.mods.number, mode: gameMode, hits @@ -263,7 +265,11 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { }, play: buildPlay(gameplay, beatmapPP, currentMods), leaderboard: gameplay.leaderboardScores.map((slot) => - convertMemoryPlayerToResult(slot, Rulesets[gameplay.mode]) + convertMemoryPlayerToResult( + slot, + Rulesets[gameplay.mode], + osuInstance.client + ) ), performance: { accuracy: beatmapPP.ppAcc, diff --git a/packages/tosu/src/states/gameplay.ts b/packages/tosu/src/states/gameplay.ts index 394c5e44..ed35df64 100644 --- a/packages/tosu/src/states/gameplay.ts +++ b/packages/tosu/src/states/gameplay.ts @@ -104,6 +104,7 @@ export class Gameplay extends AbstractState { this.accuracy = 100.0; this.unstableRate = 0; this.gradeCurrent = calculateGrade({ + _lazer: this.game.client === ClientType.lazer, mods: this.mods.number, mode: this.mode, hits: { @@ -410,6 +411,7 @@ export class Gameplay extends AbstractState { objectCount - this.hit300 - this.hit100 - this.hit50 - this.hitMiss; this.gradeCurrent = calculateGrade({ + _lazer: this.game.client === ClientType.lazer, mods: this.mods.number, mode: this.mode, hits: { @@ -423,6 +425,7 @@ export class Gameplay extends AbstractState { }); this.gradeExpected = calculateGrade({ + _lazer: this.game.client === ClientType.lazer, mods: this.mods.number, mode: this.mode, hits: { diff --git a/packages/tosu/src/states/resultScreen.ts b/packages/tosu/src/states/resultScreen.ts index c7dac79a..ddb1a0fe 100644 --- a/packages/tosu/src/states/resultScreen.ts +++ b/packages/tosu/src/states/resultScreen.ts @@ -109,12 +109,14 @@ export class ResultScreen extends AbstractState { }; this.grade = calculateGrade({ + _lazer: this.game.client === ClientType.lazer, mods: this.mods.number, mode: this.mode, hits }); this.accuracy = calculateAccuracy({ + _round: true, mode: this.mode, hits }); diff --git a/packages/tosu/src/utils/calculators.ts b/packages/tosu/src/utils/calculators.ts index 4d45d809..68b4d993 100644 --- a/packages/tosu/src/utils/calculators.ts +++ b/packages/tosu/src/utils/calculators.ts @@ -5,8 +5,10 @@ import { OsuMods } from '@/utils/osuMods.types'; */ export const calculateAccuracy = ({ hits, - mode + mode, + _round }: { + _round: boolean; hits: { 300: any; 100: any; @@ -53,17 +55,20 @@ export const calculateAccuracy = ({ break; } - return parseFloat(acc.toFixed(2)); + if (_round === true) return parseFloat(acc.toFixed(2)); + return acc; }; /** * Used to calculate grade out of hits */ export const calculateGrade = ({ + _lazer, hits, mods, mode }: { + _lazer: boolean; hits: { 300: number; 100: number; @@ -78,10 +83,7 @@ export const calculateGrade = ({ let silver = false; if (typeof mods === 'string') { - silver = - mods.toLowerCase().indexOf('hd') > -1 - ? true - : mods.toLowerCase().indexOf('fl') > -1; + silver = /hd|fl/i.test(mods.toLowerCase()); } if (typeof mods === 'number') { @@ -90,104 +92,130 @@ export const calculateGrade = ({ (mods & OsuMods.Flashlight) === OsuMods.Flashlight; } - let total = 0; - let acc = 0.0; + const acc = calculateAccuracy({ hits, mode, _round: false }) / 100; + let rank = ''; - let r300 = 0; - let r50 = 0; + if (_lazer === true) { + switch (mode) { + case 0: + case 1: { + if (acc === 1) rank = silver ? 'XH' : 'X'; + else if (acc >= 0.95 && hits[0] === 0) + rank = silver ? 'SH' : 'S'; + else if (acc >= 0.9) rank = 'A'; + else if (acc >= 0.8) rank = 'B'; + else if (acc >= 0.7) rank = 'C'; + else rank = 'D'; - let rank = ''; + break; + } - switch (mode) { - case 0: - total = hits[300] + hits[100] + hits[50] + hits[0]; - acc = - total > 0 - ? (hits[50] * 50 + hits[100] * 100 + hits[300] * 300) / - (total * 300) - : 1; - - r300 = hits[300] / total; - r50 = hits[50] / total; - - if (r300 === 1) rank = silver ? 'XH' : 'X'; - else if (r300 > 0.9 && r50 < 0.01 && hits[0] === 0) { - rank = silver ? 'SH' : 'S'; - } else if ((r300 > 0.8 && hits[0] === 0) || r300 > 0.9) rank = 'A'; - else if ((r300 > 0.7 && hits[0] === 0) || r300 > 0.8) rank = 'B'; - else if (r300 > 0.6) rank = 'C'; - else rank = 'D'; + case 2: { + if (acc === 1) rank = silver ? 'XH' : 'X'; + else if (acc >= 0.98) rank = silver ? 'SH' : 'S'; + else if (acc >= 0.94) rank = 'A'; + else if (acc >= 0.9) rank = 'B'; + else if (acc >= 0.85) rank = 'C'; + else rank = 'D'; - break; + break; + } - case 1: - total = hits[300] + hits[100] + hits[50] + hits[0]; - acc = - total > 0 - ? (hits[100] * 150 + hits[300] * 300) / (total * 300) - : 1; + case 3: { + if (acc === 1) rank = silver ? 'XH' : 'X'; + else if (acc >= 0.95) rank = silver ? 'SH' : 'S'; + else if (acc >= 0.9) rank = 'A'; + else if (acc >= 0.8) rank = 'B'; + else if (acc >= 0.7) rank = 'C'; + else rank = 'D'; - r300 = hits[300] / total; - r50 = hits[50] / total; + break; + } + } + } else { + switch (mode) { + case 0: { + const total = hits[300] + hits[100] + hits[50] + hits[0]; + if (total === 0) { + rank = silver ? 'XH' : 'X'; + break; + } - if (r300 === 1) rank = silver ? 'XH' : 'X'; - else if (r300 > 0.9 && r50 < 0.01 && hits[0] === 0) { - rank = silver ? 'SH' : 'S'; - } else if ((r300 > 0.8 && hits[0] === 0) || r300 > 0.9) rank = 'A'; - else if ((r300 > 0.7 && hits[0] === 0) || r300 > 0.8) rank = 'B'; - else if (r300 > 0.6) rank = 'C'; - else rank = 'D'; + let r300 = 0; + let r50 = 0; - break; + r300 = hits[300] / total; + r50 = hits[50] / total; - case 2: - total = hits[300] + hits[100] + hits[50] + hits[0] + hits.katu; - acc = total > 0 ? (hits[50] + hits[100] + hits[300]) / total : 1; + if (r300 === 1) { + rank = silver ? 'XH' : 'X'; + } else if (r300 > 0.9 && r50 < 0.01 && hits[0] === 0) { + rank = silver ? 'SH' : 'S'; + } else if ((r300 > 0.8 && hits[0] === 0) || r300 > 0.9) { + rank = 'A'; + } else if ((r300 > 0.7 && hits[0] === 0) || r300 > 0.8) { + rank = 'B'; + } else if (r300 > 0.6) { + rank = 'C'; + } else { + rank = 'D'; + } - r300 = hits[300] / total; - r50 = hits[50] / total; + break; + } - if (acc === 1) rank = silver ? 'XH' : 'X'; - else if (acc > 0.98) rank = silver ? 'SH' : 'S'; - else if (acc > 0.94) rank = 'A'; - else if (acc > 0.9) rank = 'B'; - else if (acc > 0.85) rank = 'C'; - else rank = 'D'; + case 1: { + const total = hits[300] + hits[100] + hits[50] + hits[0]; + if (total === 0) { + rank = silver ? 'XH' : 'X'; + break; + } - break; + let r300 = 0; + let r50 = 0; - case 3: - total = - hits[300] + - hits[100] + - hits[50] + - hits[0] + - hits.geki + - hits.katu; - acc = - total > 0 - ? (hits[50] * 50 + - hits[100] * 100 + - hits.katu * 200 + - (hits[300] + hits.geki) * 300) / - (total * 300) - : 1; - - r300 = hits[300] / total; - r50 = hits[50] / total; - - if (acc === 1) rank = silver ? 'XH' : 'X'; - else if (acc > 0.95) rank = silver ? 'SH' : 'S'; - else if (acc > 0.9) rank = 'A'; - else if (acc > 0.8) rank = 'B'; - else if (acc > 0.7) rank = 'C'; - else rank = 'D'; + r300 = hits[300] / total; + r50 = hits[50] / total; - break; - } + if (r300 === 1) { + rank = silver ? 'XH' : 'X'; + } else if (r300 > 0.9 && r50 < 0.01 && hits[0] === 0) { + rank = silver ? 'SH' : 'S'; + } else if ((r300 > 0.8 && hits[0] === 0) || r300 > 0.9) { + rank = 'A'; + } else if ((r300 > 0.7 && hits[0] === 0) || r300 > 0.8) { + rank = 'B'; + } else if (r300 > 0.6) { + rank = 'C'; + } else { + rank = 'D'; + } + + break; + } + + case 2: { + if (acc === 1) rank = silver ? 'XH' : 'X'; + else if (acc > 0.98) rank = silver ? 'SH' : 'S'; + else if (acc > 0.94) rank = 'A'; + else if (acc > 0.9) rank = 'B'; + else if (acc > 0.85) rank = 'C'; + else rank = 'D'; + + break; + } + + case 3: { + if (acc === 1) rank = silver ? 'XH' : 'X'; + else if (acc > 0.95) rank = silver ? 'SH' : 'S'; + else if (acc > 0.9) rank = 'A'; + else if (acc > 0.8) rank = 'B'; + else if (acc > 0.7) rank = 'C'; + else rank = 'D'; - if (total === 0) { - rank = silver ? 'XH' : 'X'; + break; + } + } } return rank; From 703162a3a0e0b848e28ace2ae9cf08613659426c Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:42:18 +0300 Subject: [PATCH 4/6] feat: New values for `/websocket/v2`: `isBreak` & `isKiai` --- packages/tosu/src/api/types/v2.ts | 2 + packages/tosu/src/api/utils/buildResultSC.ts | 6 ++- packages/tosu/src/api/utils/buildResultV2.ts | 2 + packages/tosu/src/instances/lazerInstance.ts | 2 +- packages/tosu/src/instances/osuInstance.ts | 2 +- packages/tosu/src/states/beatmap.ts | 56 ++++++++++++++++---- 6 files changed, 57 insertions(+), 13 deletions(-) diff --git a/packages/tosu/src/api/types/v2.ts b/packages/tosu/src/api/types/v2.ts index 8b97b6a2..e9416e82 100644 --- a/packages/tosu/src/api/types/v2.ts +++ b/packages/tosu/src/api/types/v2.ts @@ -117,6 +117,8 @@ export interface Profile { } export interface Beatmap { + isKiai: boolean; + isBreak: boolean; isConvert: boolean; time: BeatmapTime; status: NumberName; diff --git a/packages/tosu/src/api/utils/buildResultSC.ts b/packages/tosu/src/api/utils/buildResultSC.ts index 0bb9107f..5d3d8ff8 100644 --- a/packages/tosu/src/api/utils/buildResultSC.ts +++ b/packages/tosu/src/api/utils/buildResultSC.ts @@ -123,7 +123,11 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { acc[v] = value <= 0 ? 0 : value; return acc; }, {}), - mapBreaks: beatmapPP.breaks, + mapBreaks: beatmapPP.breaks.map((r) => ({ + startTime: r.start, + endTime: r.end, + hasEffect: r.hasEffect + })), mapKiaiPoints: [], // TODO: add mapPosition: formatMilliseconds(global.playTime), // convert to osu format mapTimingPoints: beatmapPP.timingPoints.map((r) => ({ diff --git a/packages/tosu/src/api/utils/buildResultV2.ts b/packages/tosu/src/api/utils/buildResultV2.ts index d195403c..b20f6266 100644 --- a/packages/tosu/src/api/utils/buildResultV2.ts +++ b/packages/tosu/src/api/utils/buildResultV2.ts @@ -230,6 +230,8 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { backgroundColour: user.backgroundColour?.toString(16) }, beatmap: { + isKiai: beatmapPP.isKiai, + isBreak: beatmapPP.isBreak, isConvert: beatmapPP.mode === 0 ? beatmapPP.mode !== currentMode : false, time: { diff --git a/packages/tosu/src/instances/lazerInstance.ts b/packages/tosu/src/instances/lazerInstance.ts index da5f645a..23c6e91f 100644 --- a/packages/tosu/src/instances/lazerInstance.ts +++ b/packages/tosu/src/instances/lazerInstance.ts @@ -108,7 +108,7 @@ export class LazerInstance extends AbstractInstance { this.previousMP3Length = menu.mp3Length; } - beatmapPP.updateRealTimeBPM(global.playTime, currentMods.rate); + beatmapPP.updateEventsStatus(global.playTime, currentMods.rate); switch (global.status) { case GameState.menu: diff --git a/packages/tosu/src/instances/osuInstance.ts b/packages/tosu/src/instances/osuInstance.ts index 56b68d55..771baf13 100644 --- a/packages/tosu/src/instances/osuInstance.ts +++ b/packages/tosu/src/instances/osuInstance.ts @@ -123,7 +123,7 @@ export class OsuInstance extends AbstractInstance { this.previousMP3Length = menu.mp3Length; } - beatmapPP.updateRealTimeBPM(global.playTime, currentMods.rate); + beatmapPP.updateEventsStatus(global.playTime, currentMods.rate); switch (global.status) { case GameState.menu: diff --git a/packages/tosu/src/states/beatmap.ts b/packages/tosu/src/states/beatmap.ts index 70f1774d..4e134c68 100644 --- a/packages/tosu/src/states/beatmap.ts +++ b/packages/tosu/src/states/beatmap.ts @@ -1,11 +1,7 @@ import rosu from '@kotrikd/rosu-pp'; import { ClientType, config, wLogger } from '@tosu/common'; import fs from 'fs'; -import { - BeatmapBreakEvent, - Beatmap as ParsedBeatmap, - TimingPoint -} from 'osu-classes'; +import { Beatmap as ParsedBeatmap, TimingPoint } from 'osu-classes'; import { BeatmapDecoder } from 'osu-parsers'; import path from 'path'; @@ -77,7 +73,21 @@ interface BeatmapPPTimings { full: number; } +interface BreakPoint { + hasEffect: boolean; + start: number; + end: number; +} + +interface KiaiPoint { + start: number; + end: number; +} + export class BeatmapPP extends AbstractState { + isKiai: boolean; + isBreak: boolean; + beatmap?: rosu.Beatmap; lazerBeatmap?: ParsedBeatmap; performanceAttributes?: rosu.PerformanceAttributes; @@ -123,7 +133,8 @@ export class BeatmapPP extends AbstractState { }; timingPoints: TimingPoint[] = []; - breaks: BeatmapBreakEvent[] = []; + breaks: BreakPoint[] = []; + kiais: KiaiPoint[] = []; constructor(game: AbstractInstance) { super(game); @@ -132,6 +143,9 @@ export class BeatmapPP extends AbstractState { } init() { + this.isKiai = false; + this.isBreak = false; + this.strains = []; this.strainsAll = { series: [], @@ -208,6 +222,7 @@ export class BeatmapPP extends AbstractState { }; this.timingPoints = []; this.breaks = []; + this.kiais = []; } updatePPAttributes( @@ -466,7 +481,11 @@ export class BeatmapPP extends AbstractState { this.minBPM = Math.round(bpmMin * this.clockRate); this.maxBPM = Math.round(bpmMax * this.clockRate); - this.breaks = this.lazerBeatmap.events.breaks; + this.breaks = this.lazerBeatmap.events.breaks.map((r) => ({ + hasEffect: r.hasEffect, + start: r.startTime, + end: r.endTime + })); const firstObj = Math.round( this.lazerBeatmap.hitObjects.at(0)?.startTime ?? 0 @@ -481,6 +500,20 @@ export class BeatmapPP extends AbstractState { this.timingPoints = this.lazerBeatmap.controlPoints.timingPoints; + const kiais: KiaiPoint[] = []; + const points = this.lazerBeatmap.controlPoints.effectPoints; + for (let i = 0; i < points.length; i++) { + const point = points[i]; + if (point.kiai === false) { + kiais[kiais.length - 1].end = point.startTime; + continue; + } + + kiais.push({ start: point.startTime, end: -1 }); + } + + this.kiais = kiais; + this.resetReportCount('beatmapPP updateMapMetadataTimings'); } catch (exc) { this.reportError( @@ -799,17 +832,20 @@ export class BeatmapPP extends AbstractState { } } - updateRealTimeBPM(timeMS: number, multiply: number) { + updateEventsStatus(ms: number, multiply: number) { if (!this.lazerBeatmap) return; const bpm = this.lazerBeatmap.controlPoints.timingPoints - // @ts-ignore + // @ts-expect-error .toReversed() - .find((r) => r.startTime <= timeMS && r.bpm !== 0)?.bpm || + .find((r) => r.startTime <= ms && r.bpm !== 0)?.bpm || this.lazerBeatmap.controlPoints.timingPoints[0]?.bpm || 0.0; this.realtimeBPM = Math.round(bpm * multiply); + + this.isKiai = this.kiais.some((r) => ms >= r.start && ms <= r.end); + this.isBreak = this.breaks.some((r) => ms >= r.start && ms <= r.end); } } From 25077eff00fb115d02c02190fb8c65c5fd67391e Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:21:57 +0300 Subject: [PATCH 5/6] feat: Allow wildcard ip matching in `ALLOWED_IPS` --- packages/server/index.ts | 1 - packages/server/utils/counters.ts | 2 +- packages/server/utils/index.ts | 29 +++++++++++++++++++++++------ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/server/index.ts b/packages/server/index.ts index 708cb495..ca96c20d 100644 --- a/packages/server/index.ts +++ b/packages/server/index.ts @@ -121,7 +121,6 @@ export class Server { } wLogger.warn('[request]', 'Unallowed request', req.url, { - address: req.socket.remoteAddress, origin: req.headers.origin, referer: req.headers.referer }); diff --git a/packages/server/utils/counters.ts b/packages/server/utils/counters.ts index 5ae1d2c4..79244eab 100644 --- a/packages/server/utils/counters.ts +++ b/packages/server/utils/counters.ts @@ -712,7 +712,7 @@ export function buildSettings(res: http.ServerResponse) { .replace('{NAME}', 'ALLOWED_IPS') .replace( '{DESCRIPTION}', - `Specify IP's which allowed to change and access tosu API's` + `IP's or domain names, which allowed to change and access tosu API's.

    - Supports wildcard syntax: 192.*.60.*
    - Compares against values printed out in «Unallowed request» message in console` ) .replace( '{INPUT}', diff --git a/packages/server/utils/index.ts b/packages/server/utils/index.ts index 121a8a0c..32cbab83 100644 --- a/packages/server/utils/index.ts +++ b/packages/server/utils/index.ts @@ -116,12 +116,29 @@ function isAllowedIP(url: string | undefined) { const allowedIPs = config.allowedIPs.split(','); try { - const parseURL = new URL(url); - return allowedIPs.some( - (r) => - r.toLowerCase().trim() === - parseURL.hostname.toLowerCase().trim() - ); + const hostname = new URL(url).hostname.toLowerCase().trim(); + return allowedIPs.some((pattern) => { + // compare IP's length and match wildcard like comparision + if (pattern.includes('*') && pattern.includes('.')) { + const patternLength = pattern.match(/\./g)?.length || 0; + const hostnameLength = hostname.match(/\./g)?.length || 0; + + if (patternLength !== 3 || hostnameLength !== 3) return false; + + const patternParts = pattern.split('.'); + const hostnameParts = hostname.split('.'); + + const matches = hostnameParts.filter((r, index) => { + if (patternParts[index] === '*') return true; + + return patternParts[index] === r; + }); + + return matches.length === 4; + } + + return pattern.toLowerCase().trim() === hostname; + }); } catch (error) { return allowedIPs.some( (r) => r.toLowerCase().trim() === url.toLowerCase().trim() From 3f6518322f10edf4a06bc19b6ae0c61f872adc38 Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:12:43 +0300 Subject: [PATCH 6/6] chore: fix pr issues --- packages/tosu/src/api/utils/buildResult.ts | 2 +- packages/tosu/src/api/utils/buildResultV2.ts | 4 ++-- packages/tosu/src/states/gameplay.ts | 6 +++--- packages/tosu/src/states/resultScreen.ts | 4 ++-- packages/tosu/src/utils/calculators.ts | 14 +++++++------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/tosu/src/api/utils/buildResult.ts b/packages/tosu/src/api/utils/buildResult.ts index 60b7263f..50d99082 100644 --- a/packages/tosu/src/api/utils/buildResult.ts +++ b/packages/tosu/src/api/utils/buildResult.ts @@ -230,7 +230,7 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => { name: resultScreen.playerName, score: resultScreen.score, accuracy: calculateAccuracy({ - _round: true, + isRound: true, hits: resultScreenHits, mode: gameplay.mode }), diff --git a/packages/tosu/src/api/utils/buildResultV2.ts b/packages/tosu/src/api/utils/buildResultV2.ts index b20f6266..7b4f577f 100644 --- a/packages/tosu/src/api/utils/buildResultV2.ts +++ b/packages/tosu/src/api/utils/buildResultV2.ts @@ -54,7 +54,7 @@ const convertMemoryPlayerToResult = ( name: memoryPlayer.name, score: memoryPlayer.score, - accuracy: calculateAccuracy({ _round: true, hits, mode: gameMode }), + accuracy: calculateAccuracy({ isRound: true, hits, mode: gameMode }), hits, @@ -70,7 +70,7 @@ const convertMemoryPlayerToResult = ( rate: memoryPlayer.mods.rate }, rank: calculateGrade({ - _lazer: client === ClientType.lazer, + isLazer: client === ClientType.lazer, mods: memoryPlayer.mods.number, mode: gameMode, hits diff --git a/packages/tosu/src/states/gameplay.ts b/packages/tosu/src/states/gameplay.ts index ed35df64..38541dcd 100644 --- a/packages/tosu/src/states/gameplay.ts +++ b/packages/tosu/src/states/gameplay.ts @@ -104,7 +104,7 @@ export class Gameplay extends AbstractState { this.accuracy = 100.0; this.unstableRate = 0; this.gradeCurrent = calculateGrade({ - _lazer: this.game.client === ClientType.lazer, + isLazer: this.game.client === ClientType.lazer, mods: this.mods.number, mode: this.mode, hits: { @@ -411,7 +411,7 @@ export class Gameplay extends AbstractState { objectCount - this.hit300 - this.hit100 - this.hit50 - this.hitMiss; this.gradeCurrent = calculateGrade({ - _lazer: this.game.client === ClientType.lazer, + isLazer: this.game.client === ClientType.lazer, mods: this.mods.number, mode: this.mode, hits: { @@ -425,7 +425,7 @@ export class Gameplay extends AbstractState { }); this.gradeExpected = calculateGrade({ - _lazer: this.game.client === ClientType.lazer, + isLazer: this.game.client === ClientType.lazer, mods: this.mods.number, mode: this.mode, hits: { diff --git a/packages/tosu/src/states/resultScreen.ts b/packages/tosu/src/states/resultScreen.ts index ddb1a0fe..6c070756 100644 --- a/packages/tosu/src/states/resultScreen.ts +++ b/packages/tosu/src/states/resultScreen.ts @@ -109,14 +109,14 @@ export class ResultScreen extends AbstractState { }; this.grade = calculateGrade({ - _lazer: this.game.client === ClientType.lazer, + isLazer: this.game.client === ClientType.lazer, mods: this.mods.number, mode: this.mode, hits }); this.accuracy = calculateAccuracy({ - _round: true, + isRound: true, mode: this.mode, hits }); diff --git a/packages/tosu/src/utils/calculators.ts b/packages/tosu/src/utils/calculators.ts index 68b4d993..c7a4a862 100644 --- a/packages/tosu/src/utils/calculators.ts +++ b/packages/tosu/src/utils/calculators.ts @@ -6,9 +6,9 @@ import { OsuMods } from '@/utils/osuMods.types'; export const calculateAccuracy = ({ hits, mode, - _round + isRound }: { - _round: boolean; + isRound: boolean; hits: { 300: any; 100: any; @@ -55,7 +55,7 @@ export const calculateAccuracy = ({ break; } - if (_round === true) return parseFloat(acc.toFixed(2)); + if (isRound === true) return parseFloat(acc.toFixed(2)); return acc; }; @@ -63,12 +63,12 @@ export const calculateAccuracy = ({ * Used to calculate grade out of hits */ export const calculateGrade = ({ - _lazer, + isLazer, hits, mods, mode }: { - _lazer: boolean; + isLazer: boolean; hits: { 300: number; 100: number; @@ -92,10 +92,10 @@ export const calculateGrade = ({ (mods & OsuMods.Flashlight) === OsuMods.Flashlight; } - const acc = calculateAccuracy({ hits, mode, _round: false }) / 100; + const acc = calculateAccuracy({ hits, mode, isRound: false }) / 100; let rank = ''; - if (_lazer === true) { + if (isLazer === true) { switch (mode) { case 0: case 1: {