Merge branch 'develop' into idac

This commit is contained in:
Dniel97 2024-05-29 22:34:40 +02:00
commit 0f12288005
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
16 changed files with 477 additions and 33 deletions

View File

@ -1,6 +1,17 @@
# Changelog # Changelog
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. 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 ## 20240408
### System ### System
+ Modified the game specific documentation + Modified the game specific documentation

View File

@ -171,7 +171,7 @@ class AllnetServlet:
if machine is None and not self.config.server.allow_unregistered_serials: if machine is None and not self.config.server.allow_unregistered_serials:
msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}." msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}."
await self.data.base.log_event( 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) self.logger.warning(msg)
@ -183,9 +183,9 @@ class AllnetServlet:
arcade = await self.data.arcade.get_arcade(machine["arcade"]) arcade = await self.data.arcade.get_arcade(machine["arcade"])
if self.config.server.check_arcade_ip: if self.config.server.check_arcade_ip:
if arcade["ip"] and arcade["ip"] is not None and arcade["ip"] != req.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( 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) self.logger.warning(msg)
@ -194,9 +194,9 @@ class AllnetServlet:
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") 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: 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( 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) self.logger.warning(msg)
@ -204,6 +204,16 @@ class AllnetServlet:
resp_dict = {k: v for k, v in vars(resp).items() if v is not None} 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") 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 = ( country = (
arcade["country"] if machine["country"] is None else machine["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" 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 req.game_id not in TitleServlet.title_registry:
if not self.config.server.is_develop: if not self.config.server.is_develop:
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}." msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
await self.data.base.log_event( 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) 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} 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") return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
if machine and arcade:
msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}" 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) 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) self.logger.info(msg)
resp_dict = {k: v for k, v in vars(resp).items() if v is not None} 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") 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( if path.exists(
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-app.ini" 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" 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}") if 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}") 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" 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: async def handle_dlorder_ini(self, request: Request) -> bytes:
req_file = request.path_params.get("file", "").replace("%0A", "").replace("\n", "") req_file = request.path_params.get("file", "").replace("%0A", "").replace("\n", "")
request_ip = Utils.get_ip_addr(request)
if not req_file: if not req_file:
return PlainTextResponse(status_code=404) return PlainTextResponse(status_code=404)
if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"): 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") 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"{Utils.get_ip_addr(request)} successfully recieved {req_file}") 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( return PlainTextResponse(open(
f"{self.config.allnet.update_cfg_folder}/{req_file}", "r", encoding="utf-8" 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}:"\ 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." 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) self.logger.info(msg)
return PlainTextResponse("OK") 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: 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") 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") return PlainTextResponse("OK")
async def handle_alive(self, request: Request) -> bytes: async def handle_alive(self, request: Request) -> bytes:
return PlainTextResponse("OK") return PlainTextResponse("OK")
async def handle_naomitest(self, request: Request) -> bytes: 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") return PlainTextResponse("naomi ok")
def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]: 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: 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}." msg = f"Unrecognised serial {req.keychipid} attempted billing checkin from {request_ip} for {req.gameid} v{req.gamever}."
await self.data.base.log_event( 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) self.logger.warning(msg)
return PlainTextResponse(f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n") return PlainTextResponse(f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n")
msg = ( log_details = {
f"Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount " "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}" f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}"
) )
self.logger.info(msg) else:
await self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg) 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}"
)
if req.traceleft > 0: if req.traceleft > 0:
self.logger.warn(f"{req.traceleft} unsent tracelogs") self.logger.warn(f"{req.traceleft} unsent tracelogs")
kc_playlimit = req.playlimit kc_playlimit = req.playlimit

View File

@ -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 ###

View File

@ -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 ###

View File

@ -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 ###

View File

@ -8,7 +8,8 @@ from sqlalchemy.engine.base import Connection
from sqlalchemy.sql import text, func, select from sqlalchemy.sql import text, func, select
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import MetaData, Table, Column 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 sqlalchemy.dialects.mysql import insert
from core.config import CoreConfig from core.config import CoreConfig
@ -22,6 +23,12 @@ event_log = Table(
Column("system", String(255), nullable=False), Column("system", String(255), nullable=False),
Column("type", String(255), nullable=False), Column("type", String(255), nullable=False),
Column("severity", Integer, 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("message", String(1000), nullable=False),
Column("details", JSON, nullable=False), Column("details", JSON, nullable=False),
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()), Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
@ -75,12 +82,19 @@ class BaseData:
return randrange(10000, 9999999) return randrange(10000, 9999999)
async def log_event( 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]: ) -> Optional[int]:
sql = event_log.insert().values( sql = event_log.insert().values(
system=system, system=system,
type=type, type=type,
severity=severity, severity=severity,
user=user,
arcade=arcade,
machine=machine,
ip=ip,
game=game,
version=version,
message=message, message=message,
details=json.dumps(details), details=json.dumps(details),
) )
@ -94,8 +108,8 @@ class BaseData:
return result.lastrowid return result.lastrowid
async def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]: async def get_event_log(self, entries: int = 100) -> Optional[List[Row]]:
sql = event_log.select().limit(entries).all() sql = event_log.select().limit(entries)
result = await self.execute(sql) result = await self.execute(sql)
if result is None: if result is None:

View File

@ -133,6 +133,7 @@ class FrontendServlet():
]), ]),
Mount("/sys", routes=[ Mount("/sys", routes=[
Route("/", self.system.render_GET, methods=['GET']), 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.user", self.system.lookup_user, methods=['GET']),
Route("/lookup.shop", self.system.lookup_shop, methods=['GET']), Route("/lookup.shop", self.system.lookup_shop, methods=['GET']),
Route("/add.user", self.system.add_user, methods=['POST']), Route("/add.user", self.system.add_user, methods=['POST']),
@ -783,6 +784,35 @@ class FE_System(FE_Base):
cabadd={"id": cab_id, "serial": serial}, cabadd={"id": cab_id, "serial": serial},
), media_type="text/html; charset=utf-8") ), 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): class FE_Arcade(FE_Base):
async def render_GET(self, request: Request): async def render_GET(self, request: Request):
template = self.environment.get_template("core/templates/arcade/index.jinja") template = self.environment.get_template("core/templates/arcade/index.jinja")

View File

@ -51,6 +51,9 @@
<button type="submit" class="btn btn-primary">Search</button> <button type="submit" class="btn btn-primary">Search</button>
</form> </form>
</div> </div>
<div class="col-sm-6" style="max-width: 25%;">
<a href="/sys/logs"><button class="btn btn-primary">Event Logs</button></a>
</div>
{% endif %} {% endif %}
</div> </div>
<div class="row" id="rowResult" style="margin: 10px;"> <div class="row" id="rowResult" style="margin: 10px;">

View File

@ -0,0 +1,182 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>Event Logs</h1>
<table class="table table-dark table-striped-columns" id="tbl_events">
<caption>Viewing last 100 logs</caption>
<thead>
<tr>
<th>Severity</th>
<th>System</th>
<th>Name</th>
<th>User</th>
<th>Arcade</th>
<th>Machine</th>
<th>Game</th>
<th>Version</th>
<th>Message</th>
<th>Params</th>
</tr>
</thead>
{% if events is not defined or events|length == 0 %}
<tr>
<td colspan="10" style="text-align:center"><i>No Events</i></td>
</tr>
{% endif %}
</table>
<div id="div_tbl_ctrl">
<select id="sel_per_page" onchange="update_tbl()">
<option value="10" selected>10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
&nbsp;
<button class="btn btn-primary" id="btn_prev" disabled onclick="chg_page(-1)"><<</button>
<button class="btn btn-primary" id="btn_next" onclick="chg_page(1)">>></button>
</div>
<script type="text/javascript">
{% if events is defined %}
const TBL_DATA = {{events}};
{% else %}
const TBL_DATA = [];
{% endif %}
var per_page = 0;
var page = 0;
function update_tbl() {
if (TBL_DATA.length == 0) { return; }
var tbl = document.getElementById("tbl_events");
for (var i = 0; i < per_page; i++) {
try{
tbl.deleteRow(1);
} catch {
break;
}
}
per_page = document.getElementById("sel_per_page").value;
if (per_page >= TBL_DATA.length) {
page = 0;
document.getElementById("btn_next").disabled = true;
document.getElementById("btn_prev").disabled = true;
}
for (var i = 0; i < per_page; i++) {
let off = (page * per_page) + i;
if (off >= TBL_DATA.length ) {
break;
}
var data = TBL_DATA[off];
var row = tbl.insertRow(i + 1);
var cell_severity = row.insertCell(0);
switch (data.severity) {
case 10:
cell_severity.innerHTML = "DEBUG";
row.classList.add("table-success");
break;
case 20:
cell_severity.innerHTML = "INFO";
row.classList.add("table-info");
break;
case 30:
cell_severity.innerHTML = "WARN";
row.classList.add("table-warning");
break;
case 40:
cell_severity.innerHTML = "ERROR";
row.classList.add("table-danger");
break;
case 50:
cell_severity.innerHTML = "CRITICAL";
row.classList.add("table-danger");
break;
default:
cell_severity.innerHTML = "---";
row.classList.add("table-primary");
break;
}
var cell_mod = row.insertCell(1);
cell_mod.innerHTML = data.system;
var cell_name = row.insertCell(2);
cell_name.innerHTML = data.type;
var cell_usr = row.insertCell(3);
if (data.user == 'NONE') {
cell_usr.innerHTML = "---";
} else {
cell_usr.innerHTML = "<a href=\"/user/" + data.user + "\">" + data.user + "</a>";
}
var cell_arcade = row.insertCell(4);
if (data.arcade == 'NONE') {
cell_arcade.innerHTML = "---";
} else {
cell_arcade.innerHTML = "<a href=\"/shop/" + data.arcade + "\">" + data.arcade + "</a>";
}
var cell_machine = row.insertCell(5);
if (data.arcade == 'NONE') {
cell_machine.innerHTML = "---";
} else {
cell_machine.innerHTML = "<a href=\"/cab/" + data.machine + "\">" + data.machine + "</a>";
}
var cell_game = row.insertCell(6);
if (data.game == 'NONE') {
cell_game.innerHTML = "---";
} else {
cell_game.innerHTML = data.game;
}
var cell_version = row.insertCell(7);
if (data.version == 'NONE') {
cell_version.innerHTML = "---";
} else {
cell_version.innerHTML = data.version;
}
var cell_msg = row.insertCell(8);
if (data.message == '') {
cell_msg.innerHTML = "---";
} else {
cell_msg.innerHTML = data.message;
}
var cell_deets = row.insertCell(9);
if (data.details == '{}') {
cell_deets.innerHTML = "---";
} else {
cell_deets.innerHTML = data.details;
}
}
}
function chg_page(num) {
var max_page = TBL_DATA.length / per_page;
page = page + num;
if (page > max_page) {
page = max_page;
document.getElementById("btn_next").disabled = true;
document.getElementById("btn_prev").disabled = false;
return;
} else if (page < 0) {
page = 0;
document.getElementById("btn_next").disabled = false;
document.getElementById("btn_prev").disabled = true;
return;
}
update_tbl();
}
update_tbl();
</script>
{% endblock content %}

View File

@ -21,6 +21,8 @@ New Nickname too long
You must be logged in to preform this action You must be logged in to preform this action
{% elif error == 10 %} {% elif error == 10 %}
Invalid serial number Invalid serial number
{% elif error == 11 %}
Access Denied
{% else %} {% else %}
An unknown error occoured An unknown error occoured
{% endif %} {% endif %}

View File

@ -40,6 +40,7 @@ class CxbRev(CxbBase):
score_data["slow2"], score_data["slow2"],
score_data["fail"], score_data["fail"],
score_data["combo"], score_data["combo"],
score_data["grade"],
) )
return {"data": True} return {"data": True}
return {"data": True} return {"data": True}

View File

@ -39,6 +39,7 @@ playlog = Table(
Column("slow2", Integer), Column("slow2", Integer),
Column("fail", Integer), Column("fail", Integer),
Column("combo", Integer), Column("combo", Integer),
Column("grade", Integer),
Column("date_scored", TIMESTAMP, server_default=func.now()), Column("date_scored", TIMESTAMP, server_default=func.now()),
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
@ -104,6 +105,7 @@ class CxbScoreData(BaseData):
this_slow2: int, this_slow2: int,
fail: int, fail: int,
combo: int, combo: int,
grade: int,
) -> Optional[int]: ) -> Optional[int]:
""" """
Add an entry to the user's play log Add an entry to the user's play log
@ -123,6 +125,7 @@ class CxbScoreData(BaseData):
slow2=this_slow2, slow2=this_slow2,
fail=fail, fail=fail,
combo=combo, combo=combo,
grade=grade,
) )
result = await self.execute(sql) result = await self.execute(sql)

View File

@ -431,7 +431,7 @@ class DivaBase:
profile = await self.data.profile.get_profile(data["pd_id"], self.version) 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) profile_shop = await self.data.item.get_shop(data["pd_id"], self.version)
if profile is None: if profile is None:
return return {}
mdl_have = "F" * 250 mdl_have = "F" * 250
# generate the mdl_have string if "unlock_all_modules" is disabled # generate the mdl_have string if "unlock_all_modules" is disabled

View File

@ -79,7 +79,7 @@ class DivaServlet(BaseServlet):
return True 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() req_raw = await request.body()
url_header = request.headers url_header = request.headers
@ -98,9 +98,18 @@ class DivaServlet(BaseServlet):
self.logger.info(f"Binary {bin_req_data['cmd']} Request") self.logger.info(f"Binary {bin_req_data['cmd']} Request")
self.logger.debug(bin_req_data) self.logger.debug(bin_req_data)
try:
handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request") handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request")
resp = handler(bin_req_data) 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( self.logger.debug(
f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}" f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}"
) )

View File

@ -54,7 +54,7 @@ class DivaCustomizeItemData(BaseData):
Given a game version and an aime id, return the cstmz_itm_have hex string Given a game version and an aime id, return the cstmz_itm_have hex string
required for diva directly 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: if items_list is None:
items_list = [] items_list = []
item_have = 0 item_have = 0

View File

@ -50,7 +50,7 @@ class DivaModuleData(BaseData):
Given a game version and an aime id, return the mdl_have hex string Given a game version and an aime id, return the mdl_have hex string
required for diva directly 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: if module_list is None:
module_list = [] module_list = []
module_have = 0 module_have = 0