diff --git a/packages/server/assets/homepage.html b/packages/server/assets/homepage.html index 50b9a062..47418456 100644 --- a/packages/server/assets/homepage.html +++ b/packages/server/assets/homepage.html @@ -56,7 +56,7 @@ diff --git a/packages/server/router/index.ts b/packages/server/router/index.ts index 3d909613..62a98916 100644 --- a/packages/server/router/index.ts +++ b/packages/server/router/index.ts @@ -383,22 +383,29 @@ export default function buildBaseApi(server: Server) { return sendJson(res, { error: 'not_ready' }); } - const { allTimesData, menuData } = osuInstance.getServices([ - 'allTimesData', - 'menuData' - ]); - - const beatmapFilePath = - query.path || - path.join( + const { allTimesData, menuData, beatmapPpData } = + osuInstance.getServices([ + 'allTimesData', + 'menuData', + 'beatmapPpData' + ]); + + let beatmap: rosu.Beatmap; + const exists = fs.existsSync(query.path); + if (exists) { + const beatmapFilePath = path.join( allTimesData.GameFolder, 'Songs', menuData.Folder, menuData.Path ); - const beatmapContent = fs.readFileSync(beatmapFilePath, 'utf8'); - const beatmap = new rosu.Beatmap(beatmapContent); + const beatmapContent = fs.readFileSync(beatmapFilePath, 'utf8'); + beatmap = new rosu.Beatmap(beatmapContent); + } else { + beatmap = beatmapPpData.getCurrentBeatmap(); + } + if (query.mode !== undefined) beatmap.convert(query.mode); const params: rosu.PerformanceArgs = {}; @@ -417,7 +424,11 @@ export default function buildBaseApi(server: Server) { if (query.acc) params.accuracy = +query.acc; const calculate = new rosu.Performance(params).calculate(beatmap); - return sendJson(res, calculate); + sendJson(res, calculate); + + // free beatmap only when map path specified + if (query.path) beatmap.free(); + calculate.free(); } catch (exc) { wLogger.error('calculate/pp', (exc as any).message); wLogger.debug('calculate/pp', exc); diff --git a/packages/tosu/src/entities/AbstractEntity/index.ts b/packages/tosu/src/entities/AbstractEntity/index.ts index 47c6808a..df6cadcf 100644 --- a/packages/tosu/src/entities/AbstractEntity/index.ts +++ b/packages/tosu/src/entities/AbstractEntity/index.ts @@ -28,4 +28,13 @@ export abstract class AbstractEntity { resetReportCount(id: string | number) { this.errorsCount[id] = 0; } + + preventThrow(callback) { + try { + const result = callback(); + return result; + } catch (error) { + return error as Error; + } + } } diff --git a/packages/tosu/src/entities/AllTimesData/index.ts b/packages/tosu/src/entities/AllTimesData/index.ts index 45f6313d..06b0189e 100644 --- a/packages/tosu/src/entities/AllTimesData/index.ts +++ b/packages/tosu/src/entities/AllTimesData/index.ts @@ -61,21 +61,12 @@ export class AllTimesData extends AbstractEntity { 'canRunSlowlyAddr' ]); - const skinOsuAddr = process.readInt(skinDataAddr + 0x7); - if (skinOsuAddr === 0) { - return; - } - const skinOsuBase = process.readInt(skinOsuAddr); - // [Status - 0x4] this.Status = process.readPointer(statusPtr); // [MenuMods + 0x9] this.MenuMods = process.readPointer(menuModsPtr); // ChatChecker - 0x20 this.ChatStatus = process.readByte(chatCheckerAddr - 0x20); - this.SkinFolder = process.readSharpString( - process.readInt(skinOsuBase + 0x44) - ); this.IsWatchingReplay = process.readByte( process.readInt(canRunSlowlyAddr + 0x46) ); @@ -96,6 +87,16 @@ export class AllTimesData extends AbstractEntity { ) ); + const skinOsuAddr = process.readInt(skinDataAddr + 0x7); + if (skinOsuAddr !== 0) { + const skinOsuBase = process.readInt(skinOsuAddr); + + this.SkinFolder = process.readSharpString( + process.readInt(skinOsuBase + 0x44) + ); + return; + } + if ( !this.osuInstance.isTourneyManager && !this.osuInstance.isTourneySpectator diff --git a/packages/tosu/src/entities/BeatmapPpData/index.ts b/packages/tosu/src/entities/BeatmapPpData/index.ts index c16ef051..c4f86425 100644 --- a/packages/tosu/src/entities/BeatmapPpData/index.ts +++ b/packages/tosu/src/entities/BeatmapPpData/index.ts @@ -60,6 +60,7 @@ export class BeatmapPPData extends AbstractEntity { PerformanceAttributes?: rosu.PerformanceAttributes; Mode: number; + clockRate: number = 1; beatmapContent?: string; strains: number[]; strainsAll: BeatmapStrains; @@ -68,8 +69,17 @@ export class BeatmapPPData extends AbstractEntity { maxBPM: number; ppAcc: BeatmapPPAcc; calculatedMapAttributes: BeatmapPPAttributes; - currAttributes: BeatmapPPCurrentAttributes; - timings: BeatmapPPTimings; + currAttributes: BeatmapPPCurrentAttributes = { + stars: 0.0, + pp: 0.0, + maxThisPlayPP: 0.0, + fcPP: 0.0 + }; + + timings: BeatmapPPTimings = { + firstObj: 0, + full: 0 + }; constructor(osuInstance: OsuInstance) { super(osuInstance); @@ -129,20 +139,6 @@ export class BeatmapPPData extends AbstractEntity { }; } - updatePPData( - strains: number[], - strainsAll: BeatmapStrains, - ppAcc: BeatmapPPAcc, - mapAttributes: BeatmapPPAttributes - ) { - this.strains = strains; - this.strainsAll = strainsAll; - if (config.calculatePP) { - this.ppAcc = ppAcc; - } - this.calculatedMapAttributes = mapAttributes; - } - updateCurrentAttributes(stars: number, pp: number) { if (this.currAttributes.pp.toFixed(2) !== pp.toFixed(2)) { wLogger.debug( @@ -153,32 +149,9 @@ export class BeatmapPPData extends AbstractEntity { } const maxThisPlayPP = Math.max(pp, this.currAttributes.maxThisPlayPP); - this.currAttributes = { - ...this.currAttributes, - stars, - pp, - maxThisPlayPP - }; - } - - updateFcPP(fcPP: number) { - this.currAttributes = { - ...this.currAttributes, - fcPP - }; - } - - updateBPM(commonBPM: number, minBPM: number, maxBPM: number) { - this.commonBPM = Math.round(commonBPM); - this.minBPM = Math.round(minBPM); - this.maxBPM = Math.round(maxBPM); - } - - updateTimings(firstObj: number, full: number) { - this.timings = { - firstObj, - full - }; + this.currAttributes.stars = stars; + this.currAttributes.pp = pp; + this.currAttributes.maxThisPlayPP = maxThisPlayPP; } resetCurrentAttributes() { @@ -237,22 +210,26 @@ export class BeatmapPPData extends AbstractEntity { mode: menuData.MenuGameMode }).build(); - const strains = difficulty.strains(this.beatmap); const fcPerformance = new rosu.Performance({ mods: currentMods }).calculate(this.beatmap); this.PerformanceAttributes = fcPerformance; + this.clockRate = attributes.clockRate; + + if (config.calculatePP) { + const ppAcc = {}; + for (const acc of [100, 99, 98, 97, 96, 95]) { + const calculate = new rosu.Performance({ + mods: currentMods, + accuracy: acc + }).calculate(fcPerformance); + ppAcc[acc] = fixDecimals(calculate.pp); + + calculate.free(); + } - const ppAcc = {}; - for (const acc of [100, 99, 98, 97, 96, 95]) { - const calculate = new rosu.Performance({ - mods: currentMods, - accuracy: acc - }).calculate(fcPerformance); - ppAcc[acc] = fixDecimals(calculate.pp); - - calculate.free(); + this.ppAcc = ppAcc as any; } const calculationTime = performance.now(); @@ -262,13 +239,6 @@ export class BeatmapPPData extends AbstractEntity { ).toFixed(2)}ms] Spend on attributes & strains calculation` ); - const resultStrains: BeatmapStrains = { - series: [], - xaxis: [] - }; - - let oldStrains: number[] = []; - try { const decoder = new BeatmapDecoder(); @@ -297,18 +267,17 @@ export class BeatmapPPData extends AbstractEntity { this.lazerBeatmap.events.backgroundPath || ''; } - this.updateBPM( - bpm * attributes.clockRate, - bpmMin * attributes.clockRate, - bpmMax * attributes.clockRate - ); + this.commonBPM = Math.round(bpm * this.clockRate); + this.minBPM = Math.round(bpmMin * this.clockRate); + this.maxBPM = Math.round(bpmMax * this.clockRate); const firstObj = Math.round( this.lazerBeatmap.hitObjects.at(0)?.startTime ?? 0 ); const full = Math.round(this.lazerBeatmap.totalLength); - this.updateTimings(firstObj, full); + this.timings.firstObj = firstObj; + this.timings.full = full; this.resetReportCount('BPPD(updateMapMetadataTimings)'); } catch (exc) { @@ -328,6 +297,66 @@ export class BeatmapPPData extends AbstractEntity { ).toFixed(2)}ms] Spend on parsing beatmap` ); + const endTime = performance.now(); + wLogger.debug( + `BPPD(updateMapMetadata) [${(endTime - startTime).toFixed( + 2 + )}ms] Total spent time` + ); + + this.calculatedMapAttributes = { + ar: attributes.ar, + cs: attributes.cs, + od: attributes.od, + hp: attributes.hp, + circles: this.lazerBeatmap.hittable, + sliders: this.lazerBeatmap.slidable, + spinners: this.lazerBeatmap.spinnable, + holds: this.lazerBeatmap.holdable, + maxCombo: fcPerformance.difficulty.maxCombo, + fullStars: fcPerformance.difficulty.stars, + stars: fcPerformance.difficulty.stars, + aim: fcPerformance.difficulty.aim, + speed: fcPerformance.difficulty.speed, + flashlight: fcPerformance.difficulty.flashlight, + sliderFactor: fcPerformance.difficulty.sliderFactor, + stamina: fcPerformance.difficulty.stamina, + rhythm: fcPerformance.difficulty.rhythm, + color: fcPerformance.difficulty.color, + peak: fcPerformance.difficulty.peak, + hitWindow: fcPerformance.difficulty.hitWindow + }; + + difficulty.free(); + attributes.free(); + + this.resetReportCount('BPPD(updateMapMetadata)'); + } catch (exc) { + this.reportError( + 'BPPD(updateMapMetadata)', + 10, + `BPPD(updateMapMetadata) ${(exc as any).message}` + ); + wLogger.debug(exc); + } + } + + updateGraph(currentMods: number) { + if (this.beatmap === undefined) return; + try { + const startTime = performance.now(); + const { menuData } = this.osuInstance.getServices(['menuData']); + + const resultStrains: BeatmapStrains = { + series: [], + xaxis: [] + }; + + const difficulty = new rosu.Difficulty({ mods: currentMods }); + const strains = difficulty.strains(this.beatmap); + + let oldStrains: number[] = []; + let strainsAmount = 0; switch (strains.mode) { case 0: @@ -348,11 +377,10 @@ export class BeatmapPPData extends AbstractEntity { } const sectionOffsetTime = strains.sectionLength; - const firstObjectTime = - this.timings.firstObj / attributes.clockRate; + const firstObjectTime = this.timings.firstObj / this.clockRate; const lastObjectTime = firstObjectTime + strainsAmount * sectionOffsetTime; - const mp3LengthTime = menuData.MP3Length / attributes.clockRate; + const mp3LengthTime = menuData.MP3Length / this.clockRate; const LEFT_OFFSET = Math.floor(firstObjectTime / sectionOffsetTime); const RIGHT_OFFSET = @@ -424,11 +452,9 @@ export class BeatmapPPData extends AbstractEntity { oldStrains = oldStrains.concat(Array(RIGHT_OFFSET).fill(0)); } - const graphProcessTime = performance.now(); + const endTIme = performance.now(); wLogger.debug( - `BPPD(updateMapMetadata) [${( - graphProcessTime - beatmapParseTime - ).toFixed(2)}ms] Spend on prcoessing graph strains` + `BPPD(updateGraph) [${(endTIme - startTime).toFixed(2)}ms] Spend on processing graph strains` ); for (let i = 0; i < LEFT_OFFSET; i++) { @@ -451,48 +477,18 @@ export class BeatmapPPData extends AbstractEntity { ); } - const endTime = performance.now(); - wLogger.debug( - `BPPD(updateMapMetadata) [${(endTime - startTime).toFixed( - 2 - )}ms] Total spent time` - ); - - this.updatePPData(oldStrains, resultStrains, ppAcc as never, { - ar: attributes.ar, - cs: attributes.cs, - od: attributes.od, - hp: attributes.hp, - circles: this.lazerBeatmap.hittable, - sliders: this.lazerBeatmap.slidable, - spinners: this.lazerBeatmap.spinnable, - holds: this.lazerBeatmap.holdable, - maxCombo: fcPerformance.difficulty.maxCombo, - fullStars: fcPerformance.difficulty.stars, - stars: fcPerformance.difficulty.stars, - aim: fcPerformance.difficulty.aim, - speed: fcPerformance.difficulty.speed, - flashlight: fcPerformance.difficulty.flashlight, - sliderFactor: fcPerformance.difficulty.sliderFactor, - stamina: fcPerformance.difficulty.stamina, - rhythm: fcPerformance.difficulty.rhythm, - color: fcPerformance.difficulty.color, - peak: fcPerformance.difficulty.peak, - hitWindow: fcPerformance.difficulty.hitWindow - }); - + this.strains = oldStrains; + this.strainsAll = resultStrains; this.Mode = strains.mode; - difficulty.free(); - attributes.free(); strains.free(); - this.resetReportCount('BPPD(updateMapMetadata)'); + this.resetReportCount('BPPD(updateGraph)'); } catch (exc) { this.reportError( - 'BPPD(updateMapMetadata)', + 'BPPD(updateGraph)', 10, - `BPPD(updateMapMetadata) ${(exc as any).message}` + `BPPD(updateGraph) ${(exc as any).message}` ); wLogger.debug(exc); } diff --git a/packages/tosu/src/entities/GamePlayData/index.ts b/packages/tosu/src/entities/GamePlayData/index.ts index 28dd153d..bf3da6e4 100644 --- a/packages/tosu/src/entities/GamePlayData/index.ts +++ b/packages/tosu/src/entities/GamePlayData/index.ts @@ -24,8 +24,8 @@ export class GamePlayData extends AbstractEntity { isDefaultState: boolean = true; isKeyOverlayDefaultState: boolean = true; - PerformanceAttributes: rosu.PerformanceAttributes; - GradualPerformance: rosu.GradualPerformance | null; + PerformanceAttributes: rosu.PerformanceAttributes | undefined; + GradualPerformance: rosu.GradualPerformance | undefined; Retries: number; PlayerName: string; @@ -115,7 +115,6 @@ export class GamePlayData extends AbstractEntity { this.isReplayUiHidden = false; this.previousPassedObjects = 0; - this.GradualPerformance = null; // below is gata that shouldn't be reseted on retry if (isRetry === true) { @@ -431,8 +430,6 @@ export class GamePlayData extends AbstractEntity { updateHitErrors() { try { - if (this.scoreBase === 0 || !this.scoreBase) return []; - const { process, patterns } = this.osuInstance.getServices([ 'process', 'patterns', @@ -440,24 +437,49 @@ export class GamePlayData extends AbstractEntity { 'menuData' ]); + const { rulesetsAddr } = patterns.getPatterns(['rulesetsAddr']); + + const rulesetAddr = process.readInt( + process.readInt(rulesetsAddr - 0xb) + 0x4 + ); + if (rulesetAddr === 0) { + wLogger.debug('GD(updateHitErrors) RulesetAddr is 0'); + return; + } + + const gameplayBase = process.readInt(rulesetAddr + 0x68); + if (gameplayBase === 0) { + wLogger.debug('GD(updateHitErrors) gameplayBase is zero'); + return; + } + + const scoreBase = process.readInt(gameplayBase + 0x38); + if (scoreBase === 0) { + wLogger.debug('GD(updateHitErrors) scoreBase is zero'); + return; + } + const leaderStart = patterns.getLeaderStart(); - const base = process.readInt(this.scoreBase + 0x38); + const base = process.readInt(scoreBase + 0x38); const items = process.readInt(base + 0x4); const size = process.readInt(base + 0xc); - for (let i = this.HitErrors.length - 1; i < size; i++) { + const errors: Array = []; + for (let i = 0; i < size; i++) { const current = items + leaderStart + 0x4 * i; const error = process.readInt(current); - this.HitErrors.push(error); + errors.push(error); } + this.HitErrors = errors; + this.resetReportCount('GD(updateHitErrors)'); } catch (exc) { this.reportError( 'GD(updateHitErrors)', - 10, + 50, `GD(updateHitErrors) ${(exc as any).message}` ); wLogger.debug(exc); @@ -553,113 +575,125 @@ export class GamePlayData extends AbstractEntity { } private updateStarsAndPerformance() { - const t1 = performance.now(); - if (!config.calculatePP) { - wLogger.debug( - 'GD(updateStarsAndPerformance) pp calculation disabled' - ); - return; - } + try { + const t1 = performance.now(); + if (!config.calculatePP) { + wLogger.debug( + 'GD(updateStarsAndPerformance) pp calculation disabled' + ); + return; + } - const { allTimesData, beatmapPpData, menuData } = - this.osuInstance.getServices([ - 'allTimesData', - 'beatmapPpData', - 'menuData' - ]); + const { allTimesData, beatmapPpData, menuData } = + this.osuInstance.getServices([ + 'allTimesData', + 'beatmapPpData', + 'menuData' + ]); - if (!allTimesData.GameFolder) { - wLogger.debug( - 'GD(updateStarsAndPerformance) game folder not found' - ); - return; - } + if (!allTimesData.GameFolder) { + wLogger.debug( + 'GD(updateStarsAndPerformance) game folder not found' + ); + return; + } - const currentBeatmap = beatmapPpData.getCurrentBeatmap(); - if (!currentBeatmap) { - wLogger.debug( - "GD(updateStarsAndPerformance) can't get current map" - ); - return; - } + const currentBeatmap = beatmapPpData.getCurrentBeatmap(); + if (!currentBeatmap) { + wLogger.debug( + "GD(updateStarsAndPerformance) can't get current map" + ); + return; + } - const currentState = `${menuData.MD5}:${menuData.MenuGameMode}:${this.Mods}:${menuData.MP3Length}`; - const isUpdate = this.previousState !== currentState; - - // update precalculated attributes - if ( - isUpdate || - !this.GradualPerformance || - !this.PerformanceAttributes - ) { - if (this.GradualPerformance) this.GradualPerformance.free(); - if (this.PerformanceAttributes) this.PerformanceAttributes.free(); - - const difficulty = new rosu.Difficulty({ mods: this.Mods }); - this.GradualPerformance = new rosu.GradualPerformance( - difficulty, - currentBeatmap - ); + const currentState = `${menuData.MD5}:${menuData.MenuGameMode}:${this.Mods}:${menuData.MP3Length}`; + const isUpdate = this.previousState !== currentState; - this.PerformanceAttributes = new rosu.Performance({ - mods: this.Mods - }).calculate(currentBeatmap); + // update precalculated attributes + if ( + isUpdate || + !this.GradualPerformance || + !this.PerformanceAttributes + ) { + if (this.GradualPerformance) this.GradualPerformance.free(); + if (this.PerformanceAttributes) + this.PerformanceAttributes.free(); + + const difficulty = new rosu.Difficulty({ mods: this.Mods }); + this.GradualPerformance = new rosu.GradualPerformance( + difficulty, + currentBeatmap + ); - this.previousState = currentState; - } + this.PerformanceAttributes = new rosu.Performance({ + mods: this.Mods + }).calculate(currentBeatmap); - if (!this.GradualPerformance && !this.PerformanceAttributes) return; - - const passedObjects = calculatePassedObjects( - this.Mode, - this.Hit300, - this.Hit100, - this.Hit50, - this.HitMiss, - this.HitKatu, - this.HitGeki - ); - - const offset = passedObjects - this.previousPassedObjects; - if (offset <= 0) return; - - const scoreParams: rosu.ScoreState = { - maxCombo: this.MaxCombo, - misses: this.HitMiss, - n50: this.Hit50, - n100: this.Hit100, - n300: this.Hit300, - nKatu: this.HitKatu, - nGeki: this.HitGeki - }; + this.previousState = currentState; + } - const curPerformance = this.GradualPerformance.nth( - scoreParams, - offset - 1 - )!; + if (!this.GradualPerformance && !this.PerformanceAttributes) return; - const fcPerformance = new rosu.Performance({ - mods: this.Mods, - misses: 0, - accuracy: this.Accuracy - }).calculate(this.PerformanceAttributes); - const t2 = performance.now(); - - if (curPerformance) { - beatmapPpData.updateCurrentAttributes( - curPerformance.difficulty.stars, - curPerformance.pp + const passedObjects = calculatePassedObjects( + this.Mode, + this.Hit300, + this.Hit100, + this.Hit50, + this.HitMiss, + this.HitKatu, + this.HitGeki ); - } - if (fcPerformance) { - beatmapPpData.updateFcPP(fcPerformance.pp); - } + const offset = passedObjects - this.previousPassedObjects; + if (offset <= 0) return; + + const scoreParams: rosu.ScoreState = { + maxCombo: this.MaxCombo, + misses: this.HitMiss, + n50: this.Hit50, + n100: this.Hit100, + n300: this.Hit300, + nKatu: this.HitKatu, + nGeki: this.HitGeki + }; + + const curPerformance = this.GradualPerformance.nth( + scoreParams, + offset - 1 + )!; + + const fcPerformance = new rosu.Performance({ + mods: this.Mods, + misses: 0, + accuracy: this.Accuracy + }).calculate(this.PerformanceAttributes); + const t2 = performance.now(); + + if (curPerformance) { + beatmapPpData.updateCurrentAttributes( + curPerformance.difficulty.stars, + curPerformance.pp + ); + } + + if (fcPerformance) { + beatmapPpData.currAttributes.fcPP = fcPerformance.pp; + } - this.previousPassedObjects = passedObjects; + this.previousPassedObjects = passedObjects; - wLogger.debug( - `GD(updateStarsAndPerformance) [${(t2 - t1).toFixed(2)}ms] elapsed time` - ); + wLogger.debug( + `GD(updateStarsAndPerformance) [${(t2 - t1).toFixed(2)}ms] elapsed time` + ); + + this.resetReportCount('GD(updateStarsAndPerformance)'); + } catch (exc) { + this.reportError( + 'GD(updateStarsAndPerformance)', + 10, + `GD(updateStarsAndPerformance) ${(exc as any).message}` + ); + wLogger.debug(exc); + } } } diff --git a/packages/tosu/src/entities/MenuData/index.ts b/packages/tosu/src/entities/MenuData/index.ts index 16318351..2eb00ed0 100644 --- a/packages/tosu/src/entities/MenuData/index.ts +++ b/packages/tosu/src/entities/MenuData/index.ts @@ -48,7 +48,7 @@ export class MenuData extends AbstractEntity { const beatmapAddr = process.readPointer(baseAddr - 0xc); if (beatmapAddr === 0) { wLogger.debug('MD(updateState) beatmapAddr is 0'); - return; + return 'not-ready'; } // [[Beatmap] + 0x6C] @@ -65,14 +65,22 @@ export class MenuData extends AbstractEntity { return; } - if (this.pendingMD5 !== newMD5) { + if ( + this.pendingMD5 !== newMD5 && + (this.osuInstance.isTourneySpectator || + this.osuInstance.isTourneyManager) + ) { this.mapChangeTime = performance.now(); this.pendingMD5 = newMD5; return; } - if (performance.now() - this.mapChangeTime < NEW_MAP_COMMIT_DELAY) { + if ( + performance.now() - this.mapChangeTime < NEW_MAP_COMMIT_DELAY && + (this.osuInstance.isTourneySpectator || + this.osuInstance.isTourneyManager) + ) { return; } diff --git a/packages/tosu/src/entities/ResultsScreenData/index.ts b/packages/tosu/src/entities/ResultsScreenData/index.ts index 6e280484..3ae5a143 100644 --- a/packages/tosu/src/entities/ResultsScreenData/index.ts +++ b/packages/tosu/src/entities/ResultsScreenData/index.ts @@ -53,17 +53,17 @@ export class ResultsScreenData extends AbstractEntity { this.Accuracy = 0; this.pp = 0; this.fcPP = 0; + + this.previousBeatmap = ''; } updateState() { try { - const { process, patterns, allTimesData, beatmapPpData, menuData } = + const { process, patterns, allTimesData } = this.osuInstance.getServices([ 'process', 'patterns', - 'allTimesData', - 'beatmapPpData', - 'menuData' + 'allTimesData' ]); if (process === null) { throw new Error('Process not found'); @@ -82,13 +82,13 @@ export class ResultsScreenData extends AbstractEntity { ); if (rulesetAddr === 0) { wLogger.debug('RSD(updateState) rulesetAddr is zero'); - return; + return 'not-ready'; } const resultScreenBase = process.readInt(rulesetAddr + 0x38); if (resultScreenBase === 0) { wLogger.debug('RSD(updateState) resultScreenBase is zero'); - return; + return 'not-ready'; } // OnlineId int64 `mem:"[Ruleset + 0x38] + 0x4"` @@ -147,6 +147,24 @@ export class ResultsScreenData extends AbstractEntity { process.readInt(resultScreenBase + 0xa0) ).toISOString(); + this.resetReportCount('RSD(updateState)'); + } catch (exc) { + this.reportError( + 'RSD(updateState)', + 10, + `RSD(updateState) ${(exc as any).message}` + ); + wLogger.debug(exc); + } + } + + updatePerformance() { + try { + const { beatmapPpData, menuData } = this.osuInstance.getServices([ + 'beatmapPpData', + 'menuData' + ]); + const key = `${menuData.MD5}${this.Mods}${this.Mode}${this.PlayerName}`; if (this.previousBeatmap === key) { return; @@ -154,7 +172,7 @@ export class ResultsScreenData extends AbstractEntity { const currentBeatmap = beatmapPpData.getCurrentBeatmap(); if (!currentBeatmap) { - wLogger.debug("RSD(updateState) can't get current map"); + wLogger.debug("RSD(updatePerformance) can't get current map"); return; } @@ -185,12 +203,12 @@ export class ResultsScreenData extends AbstractEntity { fcPerformance.free(); this.previousBeatmap = key; - this.resetReportCount('RSD(updateState)'); + this.resetReportCount('RSD(updatePerformance)'); } catch (exc) { this.reportError( - 'RSD(updateState)', + 'RSD(updatePerformance)', 10, - `RSD(updateState) ${(exc as any).message}` + `RSD(updatePerformance) ${(exc as any).message}` ); wLogger.debug(exc); } diff --git a/packages/tosu/src/objects/instanceManager/osuInstance.ts b/packages/tosu/src/objects/instanceManager/osuInstance.ts index 4b8effbb..3dd0dfbb 100644 --- a/packages/tosu/src/objects/instanceManager/osuInstance.ts +++ b/packages/tosu/src/objects/instanceManager/osuInstance.ts @@ -100,6 +100,7 @@ export class OsuInstance { ipcId: number = 0; previousState: string = ''; + previousMP3Length: number = 0; previousTime: number = 0; emitter: EventEmitter; @@ -130,7 +131,6 @@ export class OsuInstance { this.entities.set('userProfile', new UserProfile(this)); this.watchProcessHealth = this.watchProcessHealth.bind(this); - this.updateMapMetadata = this.updateMapMetadata.bind(this); this.updatePreciseData = this.updatePreciseData.bind(this); } @@ -212,7 +212,6 @@ export class OsuInstance { this.update(); this.initPreciseData(); - this.initMapMetadata(); this.watchProcessHealth(); } @@ -250,8 +249,11 @@ export class OsuInstance { while (!this.isDestroyed) { try { allTimesData.updateState(); - settings.updateState(); - menuData.updateState(); + const menuUpdate = menuData.updateState(); + if (menuUpdate === 'not-ready') { + await sleep(config.pollRate); + continue; + } // osu! calculates audioTrack length a little bit after updating menuData, sooo.. lets this thing run regardless of menuData updating if (menuData.Folder !== '' && menuData.Folder !== null) { @@ -277,12 +279,52 @@ export class OsuInstance { } } + // update important data before doing rest + if (allTimesData.Status === 7) { + const resultUpdate = resultsScreenData.updateState(); + if (resultUpdate === 'not-ready') { + await sleep(config.pollRate); + continue; + } + } + + settings.updateState(); + + const currentMods = + allTimesData.Status === 2 + ? gamePlayData.Mods + : allTimesData.Status === 7 + ? resultsScreenData.Mods + : allTimesData.MenuMods; + + const currentState = `${menuData.MD5}:${menuData.MenuGameMode}:${currentMods}`; + const updateGraph = + this.previousState !== currentState && + this.previousMP3Length !== menuData.MP3Length; + if ( + menuData.Path?.endsWith('.osu') && + allTimesData.GameFolder && + this.previousState !== currentState + ) { + this.previousState = currentState; + beatmapPpData.updateMapMetadata(currentMods); + beatmapPpData.updateGraph(currentMods); + } + + if ( + menuData.Path?.endsWith('.osu') && + allTimesData.GameFolder && + updateGraph + ) { + beatmapPpData.updateGraph(currentMods); + this.previousMP3Length = menuData.MP3Length; + } + switch (allTimesData.Status) { case 0: bassDensityData.updateState(); break; - // skip editor, to prevent constant data reset case 1: if (this.previousTime === allTimesData.PlayTime) break; @@ -317,7 +359,7 @@ export class OsuInstance { if ( allTimesData.PlayTime < - Math.min(50, beatmapPpData.timings.firstObj) + Math.min(0, beatmapPpData.timings.firstObj) ) { gamePlayData.init(true, 'not-default'); break; @@ -327,7 +369,7 @@ export class OsuInstance { break; case 7: - resultsScreenData.updateState(); + resultsScreenData.updatePerformance(); break; case 22: @@ -393,57 +435,6 @@ export class OsuInstance { }, config.preciseDataPollRate); } - initMapMetadata() { - wLogger.debug('OI(updateMapMetadata) Starting'); - - const entities = this.getServices([ - 'menuData', - 'allTimesData', - 'gamePlayData', - 'beatmapPpData', - 'resultsScreenData' - ]); - - this.updateMapMetadata(entities); - } - - updateMapMetadata(entries: { - menuData: MenuData; - allTimesData: AllTimesData; - gamePlayData: GamePlayData; - beatmapPpData: BeatmapPPData; - resultsScreenData: ResultsScreenData; - }) { - const { - menuData, - allTimesData, - gamePlayData, - beatmapPpData, - resultsScreenData - } = entries; - const currentMods = - allTimesData.Status === 2 - ? gamePlayData.Mods - : allTimesData.Status === 7 - ? resultsScreenData.Mods - : allTimesData.MenuMods; - - const currentState = `${menuData.MD5}:${menuData.MenuGameMode}:${currentMods}:${menuData.MP3Length}`; - - if ( - menuData.Path?.endsWith('.osu') && - allTimesData.GameFolder && - this.previousState !== currentState - ) { - this.previousState = currentState; - beatmapPpData.updateMapMetadata(currentMods); - } - - setTimeout(() => { - this.updateMapMetadata(entries); - }, config.pollRate); - } - watchProcessHealth() { if (this.isDestroyed === true) return; diff --git a/packages/tosu/src/utils/converters.ts b/packages/tosu/src/utils/converters.ts index c3b3773b..1fd54409 100644 --- a/packages/tosu/src/utils/converters.ts +++ b/packages/tosu/src/utils/converters.ts @@ -33,9 +33,5 @@ export const netDateBinaryToDate = ( const epochTicks = 621355968000000000n; const milliseconds = (dateData - epochTicks) / ticksPerMillisecond; - return addTimezoneOffset(new Date(Number(milliseconds))); + return new Date(Number(milliseconds)); }; - -function addTimezoneOffset(date: Date) { - return new Date(date.getTime() - date.getTimezoneOffset() * 60000); -}