diff --git a/changelog.md b/changelog.md index 42c7cfe..0e8b39a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,17 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240526 ++ Fixed missing awaits causing coroutine error + +## 20240524 +### DIVA ++ Fixed new profile start request causing coroutine error + +## 20240523 +### DIVA ++ Fixed binary handler & render_POST errors + ## 20240408 ### System + Modified the game specific documentation diff --git a/core/allnet.py b/core/allnet.py index b03d168..861a603 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -171,7 +171,7 @@ class AllnetServlet: if machine is None and not self.config.server.allow_unregistered_serials: msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}." await self.data.base.log_event( - "allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg + "allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg, {"serial": req.serial}, None, None, None, request_ip, req.game_id, req.ver ) self.logger.warning(msg) @@ -183,9 +183,9 @@ class AllnetServlet: arcade = await self.data.arcade.get_arcade(machine["arcade"]) if self.config.server.check_arcade_ip: if arcade["ip"] and arcade["ip"] is not None and arcade["ip"] != req.ip: - msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip} (expected {arcade['ip']})." + msg = f"{req.serial} attempted allnet auth from bad IP {req.ip} (expected {arcade['ip']})." await self.data.base.log_event( - "allnet", "ALLNET_AUTH_BAD_IP", logging.ERROR, msg + "allnet", "ALLNET_AUTH_BAD_IP", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver ) self.logger.warning(msg) @@ -194,9 +194,9 @@ class AllnetServlet: return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") elif (not arcade["ip"] or arcade["ip"] is None) and self.config.server.strict_ip_checking: - msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)." + msg = f"{req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)." await self.data.base.log_event( - "allnet", "ALLNET_AUTH_NO_SHOP_IP", logging.ERROR, msg + "allnet", "ALLNET_AUTH_NO_SHOP_IP", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver ) self.logger.warning(msg) @@ -204,7 +204,17 @@ class AllnetServlet: resp_dict = {k: v for k, v in vars(resp).items() if v is not None} return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") + if machine['game'] and machine['game'] != req.game_id: + msg = f"{req.serial} attempted allnet auth with bad game ID {req.game_id} (expected {machine['game']})." + await self.data.base.log_event( + "allnet", "ALLNET_AUTH_BAD_GAME", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver + ) + self.logger.warning(msg) + resp.stat = ALLNET_STAT.bad_game.value + resp_dict = {k: v for k, v in vars(resp).items() if v is not None} + return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") + country = ( arcade["country"] if machine["country"] is None else machine["country"] ) @@ -236,11 +246,14 @@ class AllnetServlet: arcade["timezone"] if arcade["timezone"] is not None else "+0900" if req.format_ver == 3 else "+09:00" ) + else: + arcade = None + if req.game_id not in TitleServlet.title_registry: if not self.config.server.is_develop: msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}." await self.data.base.log_event( - "allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg + "allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg, {}, None, arcade['id'] if arcade else None, machine['id'] if machine else None, request_ip, req.game_id, req.ver ) self.logger.warning(msg) @@ -271,8 +284,17 @@ class AllnetServlet: resp_dict = {k: v for k, v in vars(resp).items() if v is not None} return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") - msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}" - await self.data.base.log_event("allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg) + if machine and arcade: + msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}" + await self.data.base.log_event( + "allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver + ) + else: + msg = f"Allow unregistered serial {req.serial} to authenticate from {request_ip}: {req.game_id} v{req.ver}" + await self.data.base.log_event( + "allnet", "ALLNET_AUTH_SUCCESS_UNREG", logging.INFO, msg, {"serial": req.serial}, None, None, None, request_ip, req.game_id, req.ver + ) + self.logger.info(msg) resp_dict = {k: v for k, v in vars(resp).items() if v is not None} @@ -329,7 +351,11 @@ class AllnetServlet: ): return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n") - else: # TODO: Keychip check + else: + machine = await self.data.arcade.get_machine(req.serial) + if not machine or not machine['ota_enable'] or not machine['is_cab'] or machine['is_blacklisted']: + return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n") + if path.exists( f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-app.ini" ): @@ -340,8 +366,13 @@ class AllnetServlet: ): resp.uri += f"|http://{self.config.server.hostname}:{self.config.server.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-opt.ini" - self.logger.debug(f"Sending download uri {resp.uri}") - await self.data.base.log_event("allnet", "DLORDER_REQ_SUCCESS", logging.INFO, f"{Utils.get_ip_addr(request)} requested DL Order for {req.serial} {req.game_id} v{req.ver}") + if resp.uri: + self.logger.info(f"Sending download uri {resp.uri}") + await self.data.base.log_event( + "allnet", "DLORDER_REQ_SUCCESS", logging.INFO, f"Send download URI to {req.serial} for {req.game_id} v{req.ver} from {Utils.get_ip_addr(request)}", {"uri": resp.uri}, None, + machine['arcade'], machine['id'], request_ip, req.game_id, req.ver + ) + # Maybe add a log event for checkin but no url sent? res_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n" @@ -357,13 +388,16 @@ class AllnetServlet: async def handle_dlorder_ini(self, request: Request) -> bytes: req_file = request.path_params.get("file", "").replace("%0A", "").replace("\n", "") + request_ip = Utils.get_ip_addr(request) if not req_file: return PlainTextResponse(status_code=404) if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"): - self.logger.info(f"Request for DL INI file {req_file} from {Utils.get_ip_addr(request)} successful") - await self.data.base.log_event("allnet", "DLORDER_INI_SENT", logging.INFO, f"{Utils.get_ip_addr(request)} successfully recieved {req_file}") + self.logger.info(f"Request for DL INI file {req_file} from {request_ip} successful") + await self.data.base.log_event( + "allnet", "DLORDER_INI_SENT", logging.INFO, f"{request_ip} successfully recieved {req_file}", {"file": req_file}, ip=request_ip + ) return PlainTextResponse(open( f"{self.config.allnet.update_cfg_folder}/{req_file}", "r", encoding="utf-8" @@ -401,7 +435,13 @@ class AllnetServlet: msg = f"{rep.serial} @ {client_ip} reported {rep.rep_type.name} download state {rep.rf_state.name} for {rep.gd} v{rep.dav}:"\ f" {rep.tdsc}/{rep.tsc} segments downloaded for working files {rep.wfl} with {rep.dfl if rep.dfl else 'none'} complete." - await self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data) + machine = await self.data.arcade.get_machine(rep.serial) + if machine: + await self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data, None, machine['arcade'], machine['id'], client_ip, rep.gd, rep.dav) + + else: + msg = "Unknown serial " + msg + await self.data.base.log_event("allnet", "DL_REPORT_UNREG", logging.INFO, msg, dl_data, None, None, None, client_ip, rep.gd, rep.dav) self.logger.info(msg) return PlainTextResponse("OK") @@ -421,14 +461,24 @@ class AllnetServlet: if serial is None or num_files_dld is None or num_files_to_dl is None or dl_state is None: return PlainTextResponse("NG") - self.logger.info(f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})") + msg = f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})" + machine = await self.data.arcade.get_machine(serial) + if machine: + await self.data.base.log_event("allnet", "LSR_REPORT", logging.INFO, msg, req_dict, None, machine['arcade'], machine['id'], ip) + + else: + msg = "Unregistered " + msg + await self.data.base.log_event("allnet", "LSR_REPORT_UNREG", logging.INFO, msg, req_dict, None, None, None, ip) + + self.logger.info(msg) return PlainTextResponse("OK") async def handle_alive(self, request: Request) -> bytes: return PlainTextResponse("OK") async def handle_naomitest(self, request: Request) -> bytes: - self.logger.info(f"Ping from {Utils.get_ip_addr(request)}") + # This could be spam-able, removing + #self.logger.info(f"Ping from {Utils.get_ip_addr(request)}") return PlainTextResponse("naomi ok") def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]: @@ -558,18 +608,35 @@ class BillingServlet: if machine is None and not self.config.server.allow_unregistered_serials: msg = f"Unrecognised serial {req.keychipid} attempted billing checkin from {request_ip} for {req.gameid} v{req.gamever}." await self.data.base.log_event( - "allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg + "allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg, ip=request_ip, game=req.gameid, version=req.gamever ) self.logger.warning(msg) return PlainTextResponse(f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n") + + log_details = { + "playcount": req.playcnt, + "billing_type": req.billingtype.name, + "nearfull": req.nearfull, + "playlimit": req.playlimit, + } + + if machine is not None: + await self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, "", log_details, None, machine['arcade'], machine['id'], request_ip, req.gameid, req.gamever) + + self.logger.info( + f"Unregistered Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount " + f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}" + ) + else: + log_details['serial'] = req.keychipid + await self.data.base.log_event("billing", "BILLING_CHECKIN_OK_UNREG", logging.INFO, "", log_details, None, None, None, request_ip, req.gameid, req.gamever) + + self.logger.info( + f"Unregistered Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount " + f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}" + ) - msg = ( - f"Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount " - f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}" - ) - self.logger.info(msg) - await self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg) if req.traceleft > 0: self.logger.warn(f"{req.traceleft} unsent tracelogs") kc_playlimit = req.playlimit diff --git a/core/data/alembic/versions/2bf9f38d9444_add_event_log_info.py b/core/data/alembic/versions/2bf9f38d9444_add_event_log_info.py new file mode 100644 index 0000000..5c2492d --- /dev/null +++ b/core/data/alembic/versions/2bf9f38d9444_add_event_log_info.py @@ -0,0 +1,48 @@ +"""add_event_log_info + +Revision ID: 2bf9f38d9444 +Revises: 81e44dd6047a +Create Date: 2024-05-21 23:00:17.468407 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '2bf9f38d9444' +down_revision = '81e44dd6047a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('event_log', sa.Column('user', sa.INTEGER(), nullable=True)) + op.add_column('event_log', sa.Column('arcade', sa.INTEGER(), nullable=True)) + op.add_column('event_log', sa.Column('machine', sa.INTEGER(), nullable=True)) + op.add_column('event_log', sa.Column('ip', sa.TEXT(length=39), nullable=True)) + op.alter_column('event_log', 'when_logged', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.create_foreign_key(None, 'event_log', 'machine', ['machine'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'event_log', 'arcade', ['arcade'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'event_log', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'event_log', type_='foreignkey') + op.drop_constraint(None, 'event_log', type_='foreignkey') + op.drop_constraint(None, 'event_log', type_='foreignkey') + op.alter_column('event_log', 'when_logged', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('current_timestamp()'), + existing_nullable=False) + op.drop_column('event_log', 'ip') + op.drop_column('event_log', 'machine') + op.drop_column('event_log', 'arcade') + op.drop_column('event_log', 'user') + # ### end Alembic commands ### diff --git a/core/data/alembic/versions/2d024cf145a1_add_event_log_game_version.py b/core/data/alembic/versions/2d024cf145a1_add_event_log_game_version.py new file mode 100644 index 0000000..b947763 --- /dev/null +++ b/core/data/alembic/versions/2d024cf145a1_add_event_log_game_version.py @@ -0,0 +1,46 @@ +"""add_event_log_game_version + +Revision ID: 2d024cf145a1 +Revises: 2bf9f38d9444 +Create Date: 2024-05-21 23:41:31.445331 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '2d024cf145a1' +down_revision = '2bf9f38d9444' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('event_log', sa.Column('game', sa.TEXT(length=4), nullable=True)) + op.add_column('event_log', sa.Column('version', sa.TEXT(length=24), nullable=True)) + op.alter_column('event_log', 'ip', + existing_type=mysql.TINYTEXT(), + type_=sa.TEXT(length=39), + existing_nullable=True) + op.alter_column('event_log', 'when_logged', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('event_log', 'when_logged', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('current_timestamp()'), + existing_nullable=False) + op.alter_column('event_log', 'ip', + existing_type=sa.TEXT(length=39), + type_=mysql.TINYTEXT(), + existing_nullable=True) + op.drop_column('event_log', 'version') + op.drop_column('event_log', 'game') + # ### end Alembic commands ### diff --git a/core/data/alembic/versions/7dc13e364e53_cxb_add_playlog_grade.py b/core/data/alembic/versions/7dc13e364e53_cxb_add_playlog_grade.py new file mode 100644 index 0000000..abde273 --- /dev/null +++ b/core/data/alembic/versions/7dc13e364e53_cxb_add_playlog_grade.py @@ -0,0 +1,28 @@ +"""cxb_add_playlog_grade + +Revision ID: 7dc13e364e53 +Revises: 2d024cf145a1 +Create Date: 2024-05-28 22:31:22.264926 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '7dc13e364e53' +down_revision = '2d024cf145a1' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('cxb_playlog', sa.Column('grade', sa.Integer(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('cxb_playlog', 'grade') + # ### end Alembic commands ### diff --git a/core/data/schema/base.py b/core/data/schema/base.py index 55eceaf..e315964 100644 --- a/core/data/schema/base.py +++ b/core/data/schema/base.py @@ -8,7 +8,8 @@ from sqlalchemy.engine.base import Connection from sqlalchemy.sql import text, func, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy import MetaData, Table, Column -from sqlalchemy.types import Integer, String, TIMESTAMP, JSON +from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, INTEGER, TEXT +from sqlalchemy.schema import ForeignKey from sqlalchemy.dialects.mysql import insert from core.config import CoreConfig @@ -22,6 +23,12 @@ event_log = Table( Column("system", String(255), nullable=False), Column("type", String(255), nullable=False), Column("severity", Integer, nullable=False), + Column("user", INTEGER, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("arcade", INTEGER, ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade")), + Column("machine", INTEGER, ForeignKey("machine.id", ondelete="cascade", onupdate="cascade")), + Column("ip", TEXT(39)), + Column("game", TEXT(4)), + Column("version", TEXT(24)), Column("message", String(1000), nullable=False), Column("details", JSON, nullable=False), Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()), @@ -75,12 +82,19 @@ class BaseData: return randrange(10000, 9999999) async def log_event( - self, system: str, type: str, severity: int, message: str, details: Dict = {} + self, system: str, type: str, severity: int, message: str, details: Dict = {}, user: int = None, + arcade: int = None, machine: int = None, ip: str = None, game: str = None, version: str = None ) -> Optional[int]: sql = event_log.insert().values( system=system, type=type, severity=severity, + user=user, + arcade=arcade, + machine=machine, + ip=ip, + game=game, + version=version, message=message, details=json.dumps(details), ) @@ -94,8 +108,8 @@ class BaseData: return result.lastrowid - async def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]: - sql = event_log.select().limit(entries).all() + async def get_event_log(self, entries: int = 100) -> Optional[List[Row]]: + sql = event_log.select().limit(entries) result = await self.execute(sql) if result is None: diff --git a/core/frontend.py b/core/frontend.py index 382a082..bb3e9aa 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -133,6 +133,7 @@ class FrontendServlet(): ]), Mount("/sys", routes=[ Route("/", self.system.render_GET, methods=['GET']), + Route("/logs", self.system.render_logs, methods=['GET']), Route("/lookup.user", self.system.lookup_user, methods=['GET']), Route("/lookup.shop", self.system.lookup_shop, methods=['GET']), Route("/add.user", self.system.add_user, methods=['POST']), @@ -783,6 +784,35 @@ class FE_System(FE_Base): cabadd={"id": cab_id, "serial": serial}, ), media_type="text/html; charset=utf-8") + async def render_logs(self, request: Request): + template = self.environment.get_template("core/templates/sys/logs.jinja") + events = [] + + usr_sesh = self.validate_session(request) + if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.SYSADMIN): + return RedirectResponse("/sys/?e=11", 303) + + logs = await self.data.base.get_event_log() + if not logs: + logs = [] + + for log in logs: + evt = log._asdict() + if not evt['user']: evt["user"] = "NONE" + if not evt['arcade']: evt["arcade"] = "NONE" + if not evt['machine']: evt["machine"] = "NONE" + if not evt['ip']: evt["ip"] = "NONE" + if not evt['game']: evt["game"] = "NONE" + if not evt['version']: evt["version"] = "NONE" + evt['when_logged'] = evt['when_logged'].strftime("%x %X") + events.append(evt) + + return Response(template.render( + title=f"{self.core_config.server.name} | Event Logs", + sesh=vars(usr_sesh), + events=events + ), media_type="text/html; charset=utf-8") + class FE_Arcade(FE_Base): async def render_GET(self, request: Request): template = self.environment.get_template("core/templates/arcade/index.jinja") diff --git a/core/templates/sys/index.jinja b/core/templates/sys/index.jinja index b589d90..637b8c9 100644 --- a/core/templates/sys/index.jinja +++ b/core/templates/sys/index.jinja @@ -51,6 +51,9 @@ +
+ +
{% endif %}
diff --git a/core/templates/sys/logs.jinja b/core/templates/sys/logs.jinja new file mode 100644 index 0000000..3dfe531 --- /dev/null +++ b/core/templates/sys/logs.jinja @@ -0,0 +1,182 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} +

Event Logs

+ + + + + + + + + + + + + + + + + {% if events is not defined or events|length == 0 %} + + + + {% endif %} +
Viewing last 100 logs
SeveritySystemNameUserArcadeMachineGameVersionMessageParams
No Events
+
+ +  + + +
+ +{% endblock content %} \ No newline at end of file diff --git a/core/templates/widgets/err_banner.jinja b/core/templates/widgets/err_banner.jinja index eec204a..25208df 100644 --- a/core/templates/widgets/err_banner.jinja +++ b/core/templates/widgets/err_banner.jinja @@ -21,6 +21,8 @@ New Nickname too long You must be logged in to preform this action {% elif error == 10 %} Invalid serial number +{% elif error == 11 %} +Access Denied {% else %} An unknown error occoured {% endif %} diff --git a/titles/cxb/rev.py b/titles/cxb/rev.py index f811721..7e4f591 100644 --- a/titles/cxb/rev.py +++ b/titles/cxb/rev.py @@ -40,6 +40,7 @@ class CxbRev(CxbBase): score_data["slow2"], score_data["fail"], score_data["combo"], + score_data["grade"], ) return {"data": True} return {"data": True} diff --git a/titles/cxb/schema/score.py b/titles/cxb/schema/score.py index 7be33d8..5acc1ae 100644 --- a/titles/cxb/schema/score.py +++ b/titles/cxb/schema/score.py @@ -39,6 +39,7 @@ playlog = Table( Column("slow2", Integer), Column("fail", Integer), Column("combo", Integer), + Column("grade", Integer), Column("date_scored", TIMESTAMP, server_default=func.now()), mysql_charset="utf8mb4", ) @@ -104,6 +105,7 @@ class CxbScoreData(BaseData): this_slow2: int, fail: int, combo: int, + grade: int, ) -> Optional[int]: """ Add an entry to the user's play log @@ -123,6 +125,7 @@ class CxbScoreData(BaseData): slow2=this_slow2, fail=fail, combo=combo, + grade=grade, ) result = await self.execute(sql) diff --git a/titles/diva/base.py b/titles/diva/base.py index 7fe3fb5..3b96848 100644 --- a/titles/diva/base.py +++ b/titles/diva/base.py @@ -431,7 +431,7 @@ class DivaBase: profile = await self.data.profile.get_profile(data["pd_id"], self.version) profile_shop = await self.data.item.get_shop(data["pd_id"], self.version) if profile is None: - return + return {} mdl_have = "F" * 250 # generate the mdl_have string if "unlock_all_modules" is disabled diff --git a/titles/diva/index.py b/titles/diva/index.py index f414ab2..01e5eeb 100644 --- a/titles/diva/index.py +++ b/titles/diva/index.py @@ -79,7 +79,7 @@ class DivaServlet(BaseServlet): return True - async def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes: + async def render_POST(self, request: Request) -> bytes: req_raw = await request.body() url_header = request.headers @@ -98,8 +98,17 @@ class DivaServlet(BaseServlet): self.logger.info(f"Binary {bin_req_data['cmd']} Request") self.logger.debug(bin_req_data) - handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request") - resp = handler(bin_req_data) + try: + handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request") + resp = handler(bin_req_data) + + except AttributeError as e: + self.logger.warning(f"Unhandled {bin_req_data['cmd']} request {e}") + return PlainTextResponse(f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok") + + except Exception as e: + self.logger.error(f"Error handling method {e}") + return PlainTextResponse(f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok") self.logger.debug( f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}" diff --git a/titles/diva/schema/customize.py b/titles/diva/schema/customize.py index 838a7d2..21ccf7d 100644 --- a/titles/diva/schema/customize.py +++ b/titles/diva/schema/customize.py @@ -54,7 +54,7 @@ class DivaCustomizeItemData(BaseData): Given a game version and an aime id, return the cstmz_itm_have hex string required for diva directly """ - items_list = self.get_customize_items(aime_id, version) + items_list = await self.get_customize_items(aime_id, version) if items_list is None: items_list = [] item_have = 0 diff --git a/titles/diva/schema/module.py b/titles/diva/schema/module.py index a6286ee..de091b1 100644 --- a/titles/diva/schema/module.py +++ b/titles/diva/schema/module.py @@ -50,7 +50,7 @@ class DivaModuleData(BaseData): Given a game version and an aime id, return the mdl_have hex string required for diva directly """ - module_list = self.get_modules(aime_id, version) + module_list = await self.get_modules(aime_id, version) if module_list is None: module_list = [] module_have = 0