diff --git a/handlers/getFullReplayHandler.py b/handlers/getFullReplayHandler.py index 0c286f6..3457f97 100644 --- a/handlers/getFullReplayHandler.py +++ b/handlers/getFullReplayHandler.py @@ -15,8 +15,10 @@ class handler(requestsManager.asyncRequestHandler): @tornado.gen.engine @sentry.captureTornado def asyncGet(self, replayID): - - fullReplay = replayHelper.buildFullReplay(scoreID=replayID, relax=0 if replayID > 500000000 else 1) + try: + fullReplay = replayHelper.buildFullReplay(scoreID=replayID) + except (exceptions.fileNotFoundException, exceptions.scoreNotFoundError): + fullReplay = replayHelper.rxbuildFullReplay(scoreID=replayID) self.write(fullReplay) self.add_header("Content-type", "application/octet-stream") diff --git a/handlers/submitModularHandler.pyx b/handlers/submitModularHandler.pyx index 49adc19..fb337ba 100644 --- a/handlers/submitModularHandler.pyx +++ b/handlers/submitModularHandler.pyx @@ -137,7 +137,7 @@ class handler(requestsManager.asyncRequestHandler): log.debug("Beatmap is not submitted/outdated/unknown. Score submission aborted.") return - # Increment user playtime + # increment user playtime length = 0 if s.passed: try: @@ -283,24 +283,40 @@ class handler(requestsManager.asyncRequestHandler): # Send to cono ALL passed replays, even non high-scores if glob.conf.config["cono"]["enable"]: + if isRelaxing: + threading.Thread(target=lambda: glob.redis.publish( + "cono:analyze", json.dumps({ + "score_id": s.scoreID, + "beatmap_id": beatmapInfo.beatmapID, + "user_id": s.playerUserID, + "game_mode": s.gameMode, + "pp": s.pp, + "replay_data": base64.b64encode( + replayHelper.rxbuildFullReplay( + s.scoreID, + rawReplay=self.request.files["score"][0]["body"] + ) + ).decode(), + }) + )).start() + else: # We run this in a separate thread to avoid slowing down scores submission, # as cono needs a full replay - threading.Thread(target=lambda: glob.redis.publish( - "cono:analyze", json.dumps({ - "score_id": s.scoreID, - "beatmap_id": beatmapInfo.beatmapID, - "user_id": s.playerUserID, - "game_mode": s.gameMode, - "pp": s.pp, - "replay_data": base64.b64encode( - replayHelper.buildFullReplay( - s.scoreID, - rawReplay=self.request.files["score"][0]["body"], - relax=1 if isRelaxing else 0 - ) - ).decode(), - }) - )).start() + threading.Thread(target=lambda: glob.redis.publish( + "cono:analyze", json.dumps({ + "score_id": s.scoreID, + "beatmap_id": beatmapInfo.beatmapID, + "user_id": s.playerUserID, + "game_mode": s.gameMode, + "pp": s.pp, + "replay_data": base64.b64encode( + replayHelper.buildFullReplay( + s.scoreID, + rawReplay=self.request.files["score"][0]["body"] + ) + ).decode(), + }) + )).start() else: # Restrict if no replay was provided if not restricted: diff --git a/helpers/replayHelper.py b/helpers/replayHelper.py index 8c2200d..d033964 100644 --- a/helpers/replayHelper.py +++ b/helpers/replayHelper.py @@ -5,16 +5,80 @@ from constants import exceptions, dataTypes from helpers import binaryHelper from objects import glob -def buildFullReplay(scoreID=None, scoreData=None, rawReplay=None, relax=3): +def rxbuildFullReplay(scoreID=None, scoreData=None, rawReplay=None): if all(v is None for v in (scoreID, scoreData)) or all(v is not None for v in (scoreID, scoreData)): raise AttributeError("Either scoreID or scoreData must be provided, not neither or both") - if relax == 3: - raise AttributeError("Not specified whether the replay is relax or regular. WILL NOT BUILD REPLAY!") + if scoreData is None: + scoreData = glob.db.fetch( + "SELECT scores_relax.*, users.username FROM scores_relax LEFT JOIN users ON scores_relax.userid = users.id " + "WHERE scores_relax.id = %s", + [scoreID] + ) + else: + scoreID = scoreData["id"] + if scoreData is None or scoreID is None: + raise exceptions.scoreNotFoundError() + + if rawReplay is None: + # Make sure raw replay exists + fileName = ".data/replays/replay_{}.osr".format(scoreID) + if not os.path.isfile(fileName): + raise FileNotFoundError() + + # Read raw replay + with open(fileName, "rb") as f: + rawReplay = f.read() + + # Calculate missing replay data + rank = generalUtils.getRank(int(scoreData["play_mode"]), int(scoreData["mods"]), int(scoreData["accuracy"]), + int(scoreData["300_count"]), int(scoreData["100_count"]), int(scoreData["50_count"]), + int(scoreData["misses_count"])) + magicHash = generalUtils.stringMd5( + "{}p{}o{}o{}t{}a{}r{}e{}y{}o{}u{}{}{}".format(int(scoreData["100_count"]) + int(scoreData["300_count"]), + scoreData["50_count"], scoreData["gekis_count"], + scoreData["katus_count"], scoreData["misses_count"], + scoreData["beatmap_md5"], scoreData["max_combo"], + "True" if int(scoreData["full_combo"]) == 1 else "False", + scoreData["username"], scoreData["score"], rank, + scoreData["mods"], "True")) + # Add headers (convert to full replay) + fullReplay = binaryHelper.binaryWrite([ + [scoreData["play_mode"], dataTypes.byte], + [20150414, dataTypes.uInt32], + [scoreData["beatmap_md5"], dataTypes.string], + [scoreData["username"], dataTypes.string], + [magicHash, dataTypes.string], + [scoreData["300_count"], dataTypes.uInt16], + [scoreData["100_count"], dataTypes.uInt16], + [scoreData["50_count"], dataTypes.uInt16], + [scoreData["gekis_count"], dataTypes.uInt16], + [scoreData["katus_count"], dataTypes.uInt16], + [scoreData["misses_count"], dataTypes.uInt16], + [scoreData["score"], dataTypes.uInt32], + [scoreData["max_combo"], dataTypes.uInt16], + [scoreData["full_combo"], dataTypes.byte], + [scoreData["mods"], dataTypes.uInt32], + [0, dataTypes.byte], + [0, dataTypes.uInt64], + [rawReplay, dataTypes.rawReplay], + [0, dataTypes.uInt32], + [0, dataTypes.uInt32], + ]) + + # Return full replay + return fullReplay + +def buildFullReplay(scoreID=None, scoreData=None, rawReplay=None): + if all(v is None for v in (scoreID, scoreData)) or all(v is not None for v in (scoreID, scoreData)): + raise AttributeError("Either scoreID or scoreData must be provided, not neither or both") if scoreData is None: scoreData = glob.db.fetch( - "SELECT scores{relax}.*, users.username FROM scores LEFT JOIN users ON scores{relax}.userid = users.id WHERE scores{relax}.id = {scoreID}".format(scoreID=scoreID, relax="_relax" if relax == 1 else "")) + "SELECT scores.*, users.username FROM scores LEFT JOIN users ON scores.userid = users.id " + "WHERE scores.id = %s", + [scoreID] + ) else: scoreID = scoreData["id"] if scoreData is None or scoreID is None: