begin move

This commit is contained in:
Hay1tsme 2024-01-09 03:07:04 -05:00
parent b056ff218d
commit 14fa0f5e8e
82 changed files with 1683 additions and 1712 deletions

View File

@ -1,6 +1,6 @@
from core.config import CoreConfig
from core.allnet import AllnetServlet
from core.aimedb import AimedbFactory
from core.allnet import AllnetServlet, BillingServlet
from core.aimedb import AimedbServlette
from core.title import TitleServlet
from core.utils import Utils
from core.mucha import MuchaServlet

View File

@ -102,7 +102,7 @@ class ADBHeader:
magic, protocol_ver, cmd, length, status, game_id, store_id, keychip_id = struct.unpack_from("<5H6sI12s", data)
head = cls(magic, protocol_ver, cmd, length, status, game_id, store_id, keychip_id)
if head.length != len(data):
if head.length > len(data):
raise ADBHeaderException(f"Length is incorrect! Expect {head.length}, got {len(data)}")
return head

View File

@ -2,8 +2,8 @@ from twisted.internet.protocol import Factory, Protocol
import logging, coloredlogs
from Crypto.Cipher import AES
import struct
from typing import Dict, Tuple, Callable, Union
from typing_extensions import Final
from typing import Dict, Tuple, Callable, Union, Optional
import asyncio
from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig
@ -11,15 +11,37 @@ from core.utils import create_sega_auth_key
from core.data import Data
from .adb_handlers import *
class AimedbProtocol(Protocol):
class AimedbServlette():
request_list: Dict[int, Tuple[Callable[[bytes, int], Union[ADBBaseResponse, bytes]], int, str]] = {}
def __init__(self, core_cfg: CoreConfig) -> None:
self.logger = logging.getLogger("aimedb")
self.config = core_cfg
def __init__(self, core_cfg: CoreConfig) -> None:
self.config = core_cfg
self.data = Data(core_cfg)
if core_cfg.aimedb.key == "":
self.logger = logging.getLogger("aimedb")
if not hasattr(self.logger, "initted"):
log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "aimedb"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.config.aimedb.loglevel)
coloredlogs.install(
level=core_cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.initted = True
if not core_cfg.aimedb.key:
self.logger.error("!!!KEY NOT SET!!!")
exit(1)
@ -40,27 +62,30 @@ class AimedbProtocol(Protocol):
self.register_handler(0x13, 0x14, self.handle_log_ex, 'aime_log_ex')
self.register_handler(0x64, 0x65, self.handle_hello, 'hello')
self.register_handler(0x66, 0, self.handle_goodbye, 'goodbye')
def register_handler(self, cmd: int, resp:int, handler: Callable[[bytes, int], Union[ADBBaseResponse, bytes]], name: str) -> None:
self.request_list[cmd] = (handler, resp, name)
def start(self) -> None:
self.logger.info(f"Start on port {self.config.aimedb.port}")
asyncio.create_task(asyncio.start_server(self.dataReceived, self.config.server.listen_address, self.config.aimedb.port))
async def dataReceived(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
self.logger.debug(f"Connection made from {writer.get_extra_info('peername')[0]}")
while True:
try:
data: bytes = await reader.read()
if len(data) == 0:
self.logger.debug("Connection closed")
return
await self.process_data(data, reader, writer)
await writer.drain()
except ConnectionResetError as e:
self.logger.warn("Connection reset, disconnecting")
return
def append_padding(self, data: bytes):
"""Appends 0s to the end of the data until it's at the correct size"""
length = struct.unpack_from("<H", data, 6)
padding_size = length[0] - len(data)
data += bytes(padding_size)
return data
def connectionMade(self) -> None:
self.logger.debug(f"{self.transport.getPeer().host} Connected")
def connectionLost(self, reason) -> None:
self.logger.debug(
f"{self.transport.getPeer().host} Disconnected - {reason.value}"
)
def dataReceived(self, data: bytes) -> None:
async def process_data(self, data: bytes, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> Optional[bytes]:
addr = writer.get_extra_info('peername')[0]
cipher = AES.new(self.config.aimedb.key.encode(), AES.MODE_ECB)
try:
@ -68,9 +93,9 @@ class AimedbProtocol(Protocol):
except Exception as e:
self.logger.error(f"Failed to decrypt {data.hex()} because {e}")
return None
return
self.logger.debug(f"{self.transport.getPeer().host} wrote {decrypted.hex()}")
self.logger.debug(f"{addr} wrote {decrypted.hex()}")
try:
head = ADBHeader.from_data(decrypted)
@ -79,7 +104,9 @@ class AimedbProtocol(Protocol):
self.logger.error(f"Error parsing ADB header: {e}")
try:
encrypted = cipher.encrypt(ADBBaseResponse().make())
self.transport.write(encrypted)
writer.write(encrypted)
await writer.drain()
return
except Exception as e:
self.logger.error(f"Failed to encrypt default response because {e}")
@ -89,46 +116,51 @@ class AimedbProtocol(Protocol):
if head.keychip_id == "ABCD1234567" or head.store_id == 0xfff0:
self.logger.warning(f"Request from uninitialized AMLib: {vars(head)}")
if head.cmd == 0x66:
self.logger.info("Goodbye")
writer.close()
return
handler, resp_code, name = self.request_list.get(head.cmd, (self.handle_default, None, 'default'))
if resp_code is None:
self.logger.warning(f"No handler for cmd {hex(head.cmd)}")
elif resp_code > 0:
self.logger.info(f"{name} from {head.keychip_id} ({head.game_id}) @ {self.transport.getPeer().host}")
self.logger.info(f"{name} from {head.keychip_id} ({head.game_id}) @ {addr}")
resp = handler(decrypted, resp_code)
resp = await handler(decrypted, resp_code)
if type(resp) == ADBBaseResponse or issubclass(type(resp), ADBBaseResponse):
resp_bytes = resp.make()
if len(resp_bytes) != resp.head.length:
resp_bytes = self.append_padding(resp_bytes)
elif type(resp) == bytes:
resp_bytes = resp
elif resp is None: # Nothing to send, probably a goodbye
self.logger.warn(f"None return by handler for {name}")
return
else:
self.logger.error(f"Unsupported type returned by ADB handler for {name}: {type(resp)}")
raise TypeError(f"Unsupported type returned by ADB handler for {name}: {type(resp)}")
try:
try:
encrypted = cipher.encrypt(resp_bytes)
self.logger.debug(f"Response {resp_bytes.hex()}")
self.transport.write(encrypted)
writer.write(encrypted)
except Exception as e:
self.logger.error(f"Failed to encrypt {resp_bytes.hex()} because {e}")
def handle_default(self, data: bytes, resp_code: int, length: int = 0x20) -> ADBBaseResponse:
async def handle_default(self, data: bytes, resp_code: int, length: int = 0x20) -> ADBBaseResponse:
req = ADBHeader.from_data(data)
return ADBBaseResponse(resp_code, length, 1, req.game_id, req.store_id, req.keychip_id, req.protocol_ver)
def handle_hello(self, data: bytes, resp_code: int) -> ADBBaseResponse:
return self.handle_default(data, resp_code)
async def handle_hello(self, data: bytes, resp_code: int) -> ADBBaseResponse:
return await self.handle_default(data, resp_code)
def handle_campaign(self, data: bytes, resp_code: int) -> ADBBaseResponse:
async def handle_campaign(self, data: bytes, resp_code: int) -> ADBBaseResponse:
h = ADBHeader.from_data(data)
if h.protocol_ver >= 0x3030:
req = h
@ -143,26 +175,26 @@ class AimedbProtocol(Protocol):
# We don't currently support campaigns
return resp
def handle_lookup(self, data: bytes, resp_code: int) -> ADBBaseResponse:
async def handle_lookup(self, data: bytes, resp_code: int) -> ADBBaseResponse:
req = ADBLookupRequest(data)
user_id = self.data.card.get_user_id_from_card(req.access_code)
is_banned = self.data.card.get_card_banned(req.access_code)
is_locked = self.data.card.get_card_locked(req.access_code)
ret = ADBLookupResponse.from_req(req.head, user_id)
if is_banned and is_locked:
ret.head.status = ADBStatus.BAN_SYS_USER
elif is_banned:
ret.head.status = ADBStatus.BAN_SYS
elif is_locked:
ret.head.status = ADBStatus.LOCK_USER
ret = ADBLookupResponse.from_req(req.head, user_id)
self.logger.info(
f"access_code {req.access_code} -> user_id {ret.user_id}"
)
return ret
def handle_lookup_ex(self, data: bytes, resp_code: int) -> ADBBaseResponse:
async def handle_lookup_ex(self, data: bytes, resp_code: int) -> ADBBaseResponse:
req = ADBLookupRequest(data)
user_id = self.data.card.get_user_id_from_card(req.access_code)
@ -191,7 +223,7 @@ class AimedbProtocol(Protocol):
return ret
def handle_felica_lookup(self, data: bytes, resp_code: int) -> bytes:
async def handle_felica_lookup(self, data: bytes, resp_code: int) -> bytes:
"""
On official, I think a card has to be registered for this to actually work, but
I'm making the executive decision to not implement that and just kick back our
@ -207,7 +239,7 @@ class AimedbProtocol(Protocol):
)
return ADBFelicaLookupResponse.from_req(req.head, ac)
def handle_felica_register(self, data: bytes, resp_code: int) -> bytes:
async def handle_felica_register(self, data: bytes, resp_code: int) -> bytes:
"""
I've never seen this used.
"""
@ -239,7 +271,7 @@ class AimedbProtocol(Protocol):
return ADBFelicaLookupResponse.from_req(req.head, ac)
def handle_felica_lookup_ex(self, data: bytes, resp_code: int) -> bytes:
async def handle_felica_lookup_ex(self, data: bytes, resp_code: int) -> bytes:
req = ADBFelicaLookup2Request(data)
access_code = self.data.card.to_access_code(req.idm)
user_id = self.data.card.get_user_id_from_card(access_code=access_code)
@ -263,7 +295,7 @@ class AimedbProtocol(Protocol):
return resp
def handle_campaign_clear(self, data: bytes, resp_code: int) -> ADBBaseResponse:
async def handle_campaign_clear(self, data: bytes, resp_code: int) -> ADBBaseResponse:
req = ADBCampaignClearRequest(data)
resp = ADBCampaignClearResponse.from_req(req.head)
@ -271,7 +303,7 @@ class AimedbProtocol(Protocol):
# We don't support campaign stuff
return resp
def handle_register(self, data: bytes, resp_code: int) -> bytes:
async def handle_register(self, data: bytes, resp_code: int) -> bytes:
req = ADBLookupRequest(data)
user_id = -1
@ -305,17 +337,17 @@ class AimedbProtocol(Protocol):
return resp
# TODO: Save these in some capacity, as deemed relevant
def handle_status_log(self, data: bytes, resp_code: int) -> bytes:
async def handle_status_log(self, data: bytes, resp_code: int) -> bytes:
req = ADBStatusLogRequest(data)
self.logger.info(f"User {req.aime_id} logged {req.status.name} event")
return ADBBaseResponse(resp_code, 0x20, 1, req.head.game_id, req.head.store_id, req.head.keychip_id, req.head.protocol_ver)
def handle_log(self, data: bytes, resp_code: int) -> bytes:
async def handle_log(self, data: bytes, resp_code: int) -> bytes:
req = ADBLogRequest(data)
self.logger.info(f"User {req.aime_id} logged {req.status.name} event, credit_ct: {req.credit_ct} bet_ct: {req.bet_ct} won_ct: {req.won_ct}")
return ADBBaseResponse(resp_code, 0x20, 1, req.head.game_id, req.head.store_id, req.head.keychip_id, req.head.protocol_ver)
def handle_log_ex(self, data: bytes, resp_code: int) -> bytes:
async def handle_log_ex(self, data: bytes, resp_code: int) -> bytes:
req = ADBLogExRequest(data)
strs = []
self.logger.info(f"Recieved {req.num_logs} or {len(req.logs)} logs")
@ -324,43 +356,3 @@ class AimedbProtocol(Protocol):
self.logger.debug(f"User {req.logs[x].aime_id} logged {req.logs[x].status.name} event, credit_ct: {req.logs[x].credit_ct} bet_ct: {req.logs[x].bet_ct} won_ct: {req.logs[x].won_ct}")
return ADBLogExResponse.from_req(req.head)
def handle_goodbye(self, data: bytes, resp_code: int) -> None:
self.logger.info(f"goodbye from {self.transport.getPeer().host}")
self.transport.loseConnection()
return
class AimedbFactory(Factory):
protocol = AimedbProtocol
def __init__(self, cfg: CoreConfig) -> None:
self.config = cfg
log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
self.logger = logging.getLogger("aimedb")
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "aimedb"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.config.aimedb.loglevel)
coloredlogs.install(
level=cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str
)
if self.config.aimedb.key == "":
self.logger.error("Please set 'key' field in your config file.")
exit(1)
self.logger.info(f"Ready on port {self.config.aimedb.port}")
def buildProtocol(self, addr):
return AimedbProtocol(self.config)

View File

@ -1,20 +1,24 @@
from typing import Dict, List, Any, Optional, Tuple, Union, Final
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
from twisted.web.http import Request
from datetime import datetime
import pytz
import base64
import zlib
import json
import yaml
import logging
import coloredlogs
import urllib.parse
import math
from typing import Dict, List, Any, Optional, Union, Final
from logging.handlers import TimedRotatingFileHandler
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.applications import Starlette
from starlette.routing import Route
from datetime import datetime
from enum import Enum
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA
from Crypto.Signature import PKCS1_v1_5
from time import strptime
from os import path
import urllib.parse
import math
from os import path, environ, mkdir, access, W_OK
from .config import CoreConfig
from .utils import Utils
@ -91,7 +95,6 @@ class DLI_STATUS(Enum):
class AllnetServlet:
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
super().__init__()
self.config = core_cfg
self.config_folder = cfg_folder
self.data = Data(core_cfg)
@ -126,19 +129,20 @@ class AllnetServlet:
self.logger.error("No games detected!")
self.logger.info(
f"Serving {len(TitleServlet.title_registry)} game codes port {core_cfg.allnet.port}"
f"Serving {len(TitleServlet.title_registry)} game codes"
)
def handle_poweron(self, request: Request, _: Dict):
async def handle_poweron(self, request: Request):
request_ip = Utils.get_ip_addr(request)
pragma_header = request.getHeader('Pragma')
pragma_header = request.headers.get('Pragma', "")
is_dfi = pragma_header is not None and pragma_header == "DFI"
data = await request.body()
try:
if is_dfi:
req_urlencode = self.from_dfi(request.content.getvalue())
req_urlencode = self.from_dfi(data)
else:
req_urlencode = request.content.getvalue().decode()
req_urlencode = data
req_dict = self.allnet_req_to_dict(req_urlencode)
if req_dict is None:
@ -155,7 +159,7 @@ class AllnetServlet:
except AllnetRequestException as e:
if e.message != "":
self.logger.error(e)
return b""
return PlainTextResponse()
if req.format_ver == 3:
resp = AllnetPowerOnResponse3(req.token)
@ -176,7 +180,7 @@ class AllnetServlet:
resp.stat = ALLNET_STAT.bad_machine.value
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
if machine is not None:
arcade = self.data.arcade.get_arcade(machine["arcade"])
@ -190,7 +194,7 @@ class AllnetServlet:
resp.stat = ALLNET_STAT.bad_shop.value
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
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)."
@ -201,7 +205,7 @@ class AllnetServlet:
resp.stat = ALLNET_STAT.bad_shop.value
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
country = (
@ -245,20 +249,20 @@ class AllnetServlet:
resp.stat = ALLNET_STAT.bad_game.value
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
else:
self.logger.info(
f"Allowed unknown game {req.game_id} v{req.ver} to authenticate from {request_ip} due to 'is_develop' being enabled. S/N: {req.serial}"
)
resp.uri = f"http://{self.config.title.hostname}:{self.config.title.port}/{req.game_id}/{req.ver.replace('.', '')}/"
resp.host = f"{self.config.title.hostname}:{self.config.title.port}"
resp.uri = f"http://{self.config.server.hostname}:{self.config.server.port}/{req.game_id}/{req.ver.replace('.', '')}/"
resp.host = f"{self.config.server.hostname}:{self.config.server.port}"
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
resp_str = urllib.parse.unquote(urllib.parse.urlencode(resp_dict))
self.logger.debug(f"Allnet response: {resp_str}")
return (resp_str + "\n").encode("utf-8")
return PlainTextResponse(resp_str + "\n")
int_ver = req.ver.replace(".", "")
@ -277,18 +281,19 @@ class AllnetServlet:
request.responseHeaders.addRawHeader('Pragma', 'DFI')
return self.to_dfi(resp_str)"""
return resp_str.encode("utf-8")
return PlainTextResponse(resp_str)
def handle_dlorder(self, request: Request, _: Dict):
async def handle_dlorder(self, request: Request):
request_ip = Utils.get_ip_addr(request)
pragma_header = request.getHeader('Pragma')
pragma_header = request.headers.get('Pragma', "")
is_dfi = pragma_header is not None and pragma_header == "DFI"
data = await request.body()
try:
if is_dfi:
req_urlencode = self.from_dfi(request.content.getvalue())
req_urlencode = self.from_dfi(data)
else:
req_urlencode = request.content.getvalue().decode()
req_urlencode = data.decode()
req_dict = self.allnet_req_to_dict(req_urlencode)
if req_dict is None:
@ -305,7 +310,7 @@ class AllnetServlet:
except AllnetRequestException as e:
if e.message != "":
self.logger.error(e)
return b""
return PlainTextResponse()
self.logger.info(
f"DownloadOrder from {request_ip} -> {req.game_id} v{req.ver} serial {req.serial}"
@ -316,18 +321,18 @@ class AllnetServlet:
not self.config.allnet.allow_online_updates
or not self.config.allnet.update_cfg_folder
):
return urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n"
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n")
else: # TODO: Keychip check
if path.exists(
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-app.ini"
):
resp.uri = f"http://{self.config.title.hostname}:{self.config.title.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-app.ini"
resp.uri = f"http://{self.config.server.hostname}:{self.config.server.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-app.ini"
if path.exists(
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-opt.ini"
):
resp.uri += f"|http://{self.config.title.hostname}:{self.config.title.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}")
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}")
@ -337,33 +342,33 @@ class AllnetServlet:
request.responseHeaders.addRawHeader('Pragma', 'DFI')
return self.to_dfi(res_str)"""
return res_str
return PlainTextResponse(res_str)
def handle_dlorder_ini(self, request: Request, match: Dict) -> bytes:
if "file" not in match:
return b""
async def handle_dlorder_ini(self, request: Request) -> bytes:
req_file = request.path_params.get("file", "").replace("%0A", "")
req_file = match["file"].replace("%0A", "")
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")
self.data.base.log_event("allnet", "DLORDER_INI_SENT", logging.INFO, f"{Utils.get_ip_addr(request)} successfully recieved {req_file}")
return open(
f"{self.config.allnet.update_cfg_folder}/{req_file}", "rb"
).read()
return PlainTextResponse(open(
f"{self.config.allnet.update_cfg_folder}/{req_file}", "r"
).read())
self.logger.info(f"DL INI File {req_file} not found")
return b""
return PlainTextResponse()
def handle_dlorder_report(self, request: Request, match: Dict) -> bytes:
req_raw = request.content.getvalue()
async def handle_dlorder_report(self, request: Request) -> bytes:
req_raw = await request.body()
client_ip = Utils.get_ip_addr(request)
try:
req_dict: Dict = json.loads(req_raw)
except Exception as e:
self.logger.warning(f"Failed to parse DL Report: {e}")
return "NG"
return PlainTextResponse("NG")
dl_data_type = DLIMG_TYPE.app
dl_data = req_dict.get("appimage", {})
@ -374,13 +379,13 @@ class AllnetServlet:
if dl_data is None or not dl_data:
self.logger.warning(f"Failed to parse DL Report: Invalid format - contains neither appimage nor optimage")
return "NG"
return PlainTextResponse("NG")
rep = DLReport(dl_data, dl_data_type)
if not rep.validate():
self.logger.warning(f"Failed to parse DL Report: Invalid format - {rep.err}")
return "NG"
return PlainTextResponse("NG")
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."
@ -388,10 +393,10 @@ class AllnetServlet:
self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data)
self.logger.info(msg)
return "OK"
return PlainTextResponse("OK")
def handle_loaderstaterecorder(self, request: Request, match: Dict) -> bytes:
req_data = request.content.getvalue()
async def handle_loaderstaterecorder(self, request: Request) -> bytes:
req_data = await request.body()
sections = req_data.decode("utf-8").split("\r\n")
req_dict = dict(urllib.parse.parse_qsl(sections[0]))
@ -403,18 +408,94 @@ class AllnetServlet:
ip = Utils.get_ip_addr(request)
if serial is None or num_files_dld is None or num_files_to_dl is None or dl_state is None:
return "NG".encode()
return PlainTextResponse("NG")
self.logger.info(f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})")
return "OK".encode()
return PlainTextResponse("OK")
def handle_alive(self, request: Request, match: Dict) -> bytes:
return "OK".encode()
async def handle_alive(self, request: Request) -> bytes:
return PlainTextResponse("OK")
def handle_billing_request(self, request: Request, _: Dict):
req_raw = request.content.getvalue()
async def handle_naomitest(self, request: Request) -> bytes:
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]]]:
"""
Parses an allnet request string into a python dictionary
"""
try:
sections = data.split("\r\n")
ret = []
for x in sections:
ret.append(dict(urllib.parse.parse_qsl(x)))
return ret
except Exception as e:
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
return None
def from_dfi(self, data: bytes) -> str:
zipped = base64.b64decode(data)
unzipped = zlib.decompress(zipped)
return unzipped.decode("utf-8")
def to_dfi(self, data: str) -> bytes:
unzipped = data.encode('utf-8')
zipped = zlib.compress(unzipped)
return base64.b64encode(zipped)
class BillingServlet:
def __init__(self, core_cfg: CoreConfig, cfg_folder: str) -> None:
self.config = core_cfg
self.config_folder = cfg_folder
self.data = Data(core_cfg)
self.logger = logging.getLogger("billing")
if not hasattr(self.logger, "initialized"):
log_fmt_str = "[%(asctime)s] Billing | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "billing"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(core_cfg.allnet.loglevel)
coloredlogs.install(
level=core_cfg.billing.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.initialized = True
def billing_req_to_dict(self, data: bytes):
"""
Parses an billing request string into a python dictionary
"""
try:
sections = data.decode("ascii").split("\r\n")
ret = []
for x in sections:
ret.append(dict(urllib.parse.parse_qsl(x)))
return ret
except Exception as e:
self.logger.error(f"billing_req_to_dict: {e} while parsing {data}")
return None
async def handle_billing_request(self, request: Request):
req_raw = await request.body()
if request.getHeader('Content-Type') == "application/octet-stream":
if request.headers.get('Content-Type', '') == "application/octet-stream":
req_unzip = zlib.decompressobj(-zlib.MAX_WBITS).decompress(req_raw)
else:
req_unzip = req_raw
@ -423,8 +504,8 @@ class AllnetServlet:
request_ip = Utils.get_ip_addr(request)
if req_dict is None:
self.logger.error(f"Failed to parse request {request.content.getvalue()}")
return b""
self.logger.error(f"Failed to parse request {req_raw}")
return PlainTextResponse()
self.logger.debug(f"request {req_dict}")
@ -436,7 +517,7 @@ class AllnetServlet:
req = BillingInfo(req_dict[0])
except KeyError as e:
self.logger.error(f"Billing request failed to parse: {e}")
return f"result=5&linelimit=&message=field is missing or formatting is incorrect\r\n".encode()
return PlainTextResponse("result=5&linelimit=&message=field is missing or formatting is incorrect\r\n")
for x in range(1, len(req_dict)):
if not req_dict[x]:
@ -467,7 +548,7 @@ class AllnetServlet:
)
self.logger.warning(msg)
return f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n".encode()
return PlainTextResponse(f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n")
msg = (
f"Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount "
@ -496,7 +577,6 @@ class AllnetServlet:
# TODO: playhistory
#resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig)
resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig, req.requestno, req.protocolver)
resp_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\r\n"
@ -504,56 +584,9 @@ class AllnetServlet:
self.logger.debug(f"response {vars(resp)}")
if req.traceleft > 0:
self.logger.info(f"Requesting 20 more of {req.traceleft} unsent tracelogs")
return f"result=6&waittime=0&linelimit=20\r\n".encode()
return resp_str.encode("utf-8")
def handle_naomitest(self, request: Request, _: Dict) -> bytes:
self.logger.info(f"Ping from {Utils.get_ip_addr(request)}")
return b"naomi ok"
def billing_req_to_dict(self, data: bytes):
"""
Parses an billing request string into a python dictionary
"""
try:
sections = data.decode("ascii").split("\r\n")
ret = []
for x in sections:
ret.append(dict(urllib.parse.parse_qsl(x)))
return ret
except Exception as e:
self.logger.error(f"billing_req_to_dict: {e} while parsing {data}")
return None
def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]:
"""
Parses an allnet request string into a python dictionary
"""
try:
sections = data.split("\r\n")
ret = []
for x in sections:
ret.append(dict(urllib.parse.parse_qsl(x)))
return ret
except Exception as e:
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
return None
def from_dfi(self, data: bytes) -> str:
zipped = base64.b64decode(data)
unzipped = zlib.decompress(zipped)
return unzipped.decode("utf-8")
def to_dfi(self, data: str) -> bytes:
unzipped = data.encode('utf-8')
zipped = zlib.compress(unzipped)
return base64.b64encode(zipped)
return PlainTextResponse("result=6&waittime=0&linelimit=20\r\n")
return PlainTextResponse(resp_str)
class AllnetPowerOnRequest:
def __init__(self, req: Dict) -> None:
@ -613,7 +646,6 @@ class AllnetPowerOnResponse3(AllnetPowerOnResponse):
self.minute = None
self.second = None
class AllnetPowerOnResponse2(AllnetPowerOnResponse):
def __init__(self) -> None:
super().__init__()
@ -623,7 +655,6 @@ class AllnetPowerOnResponse2(AllnetPowerOnResponse):
self.timezone = "+09:00"
self.res_class = "PowerOnResponseV2"
class AllnetDownloadOrderRequest:
def __init__(self, req: Dict) -> None:
self.game_id = req.get("game_id", "")
@ -631,7 +662,6 @@ class AllnetDownloadOrderRequest:
self.serial = req.get("serial", "")
self.encode = req.get("encode", "")
class AllnetDownloadOrderResponse:
def __init__(self, stat: int = 1, serial: str = "", uri: str = "") -> None:
self.stat = stat
@ -781,7 +811,6 @@ class BillingResponse:
# playhistory -> YYYYMM/C:...
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
class AllnetRequestException(Exception):
def __init__(self, message="") -> None:
self.message = message
@ -849,3 +878,26 @@ class DLReport:
return False
return True
cfg_dir = environ.get("DIANA_CFG_DIR", "config")
cfg: CoreConfig = CoreConfig()
if path.exists(f"{cfg_dir}/core.yaml"):
cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml")))
if not path.exists(cfg.server.log_dir):
mkdir(cfg.server.log_dir)
if not access(cfg.server.log_dir, W_OK):
print(
f"Log directory {cfg.server.log_dir} NOT writable, please check permissions"
)
exit(1)
billing = BillingServlet(cfg, cfg_dir)
app = Starlette(
cfg.server.is_develop,
[
Route("/request", billing.handle_billing_request, methods=["POST"]),
Route("/request/", billing.handle_billing_request, methods=["POST"]),
]
)

89
core/app.py Normal file
View File

@ -0,0 +1,89 @@
import yaml
import logging
import coloredlogs
from logging.handlers import TimedRotatingFileHandler
from starlette.routing import Route
from starlette.requests import Request
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from os import environ, path, mkdir, W_OK, access
from typing import List
from core import CoreConfig, TitleServlet, MuchaServlet, AllnetServlet, BillingServlet, AimedbServlette
from core.frontend import FrontendServlet
async def dummy_rt(request: Request):
return PlainTextResponse("Service OK")
cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config")
cfg: CoreConfig = CoreConfig()
if path.exists(f"{cfg_dir}/core.yaml"):
cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml")))
if not path.exists(cfg.server.log_dir):
mkdir(cfg.server.log_dir)
if not access(cfg.server.log_dir, W_OK):
print(
f"Log directory {cfg.server.log_dir} NOT writable, please check permissions"
)
exit(1)
logger = logging.getLogger("core")
log_fmt_str = "[%(asctime)s] Core | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(cfg.server.log_dir, "core"), when="d", backupCount=10
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)
log_lv = logging.DEBUG if cfg.server.is_develop else logging.INFO
logger.setLevel(log_lv)
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
logger.info(f"Artemis starting in {'develop' if cfg.server.is_develop else 'production'} mode")
title = TitleServlet(cfg, cfg_dir) # This has to be loaded first to load plugins
mucha = MuchaServlet(cfg, cfg_dir)
allnet = AllnetServlet(cfg, cfg_dir)
route_lst: List[Route] = [
# Allnet
Route("/sys/servlet/PowerOn", allnet.handle_poweron, methods=["GET", "POST"]),
Route("/sys/servlet/DownloadOrder", allnet.handle_dlorder, methods=["GET", "POST"]),
Route("/sys/servlet/LoaderStateRecorder", allnet.handle_loaderstaterecorder, methods=["GET", "POST"]),
Route("/sys/servlet/Alive", allnet.handle_alive, methods=["GET", "POST"]),
Route("/report-api/Report", allnet.handle_dlorder_report, methods=["POST"]),
Route("/dl/ini/{file:str}", allnet.handle_dlorder_ini),
Route("/naomitest.html", allnet.handle_naomitest),
# Mucha
Route("/mucha_front/boardauth.do", mucha.handle_boardauth, methods=["POST"]),
Route("/mucha_front/updatacheck.do", mucha.handle_updatecheck, methods=["POST"]),
Route("/mucha_front/downloadstate.do", mucha.handle_dlstate, methods=["POST"]),
]
if not cfg.billing.standalone:
billing = BillingServlet(cfg, cfg_dir)
route_lst += [
Route("/request", billing.handle_billing_request, methods=["POST"]),
Route("/request/", billing.handle_billing_request, methods=["POST"]),
]
if not cfg.frontend.standalone:
frontend = FrontendServlet(cfg, cfg_dir)
route_lst += frontend.get_routes()
else:
route_lst.append(Route("/", dummy_rt))
route_lst.append(Route("/robots.txt", FrontendServlet.robots))
for code, game in title.title_registry.items():
route_lst += game.get_routes()
app = Starlette(cfg.server.is_develop, route_lst)

View File

@ -1,16 +1,48 @@
import logging, os
from typing import Any
class ServerConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def listen_address(self) -> str:
"""
Address Artemis will bind to and listen on
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "listen_address", default="127.0.0.1"
)
@property
def hostname(self) -> str:
"""
Hostname sent to games
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "hostname", default="localhost"
)
@property
def port(self) -> int:
"""
Port the game will listen on
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "port", default=8080
)
@property
def ssl_key(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "server", "ssl_key", default="cert/title.key"
)
@property
def ssl_cert(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "ssl_cert", default="cert/title.pem"
)
@property
def allow_user_registration(self) -> bool:
@ -43,9 +75,13 @@ class ServerConfig:
)
@property
def threading(self) -> bool:
def proxy_port(self) -> int:
"""
What port the proxy is listening on. This will be sent instead of 'port' if
is_using_proxy is True and this value is non-zero
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "threading", default=False
self.__config, "core", "title", "proxy_port", default=0
)
@property
@ -66,7 +102,6 @@ class ServerConfig:
self.__config, "core", "server", "strict_ip_checking", default=False
)
class TitleConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@ -79,49 +114,18 @@ class TitleConfig:
)
)
@property
def hostname(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "hostname", default="localhost"
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "title", "port", default=8080
)
@property
def port_ssl(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "title", "port_ssl", default=0
)
@property
def ssl_key(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "ssl_key", default="cert/title.key"
)
@property
def ssl_cert(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "ssl_cert", default="cert/title.pem"
)
@property
def reboot_start_time(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "reboot_start_time", default=""
self.__config, "core", "title", "reboot_start_time", default="04:00"
)
@property
def reboot_end_time(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "reboot_end_time", default=""
self.__config, "core", "title", "reboot_end_time", default="05:00"
)
class DatabaseConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@ -176,16 +180,6 @@ class DatabaseConfig:
)
)
@property
def user_table_autoincrement_start(self) -> int:
return CoreConfig.get_config_field(
self.__config,
"core",
"database",
"user_table_autoincrement_start",
default=10000,
)
@property
def enable_memcached(self) -> bool:
return CoreConfig.get_config_field(
@ -198,21 +192,14 @@ class DatabaseConfig:
self.__config, "core", "database", "memcached_host", default="localhost"
)
class FrontendConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def enable(self) -> int:
def standalone(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "frontend", "enable", default=False
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "frontend", "port", default=8090
self.__config, "core", "frontend", "standalone", default=True
)
@property
@ -222,7 +209,12 @@ class FrontendConfig:
self.__config, "core", "frontend", "loglevel", default="info"
)
)
@property
def secret(self) -> str:
CoreConfig.get_config_field(
self.__config, "core", "frontend", "secret", default=""
)
class AllnetConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -236,18 +228,6 @@ class AllnetConfig:
)
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "allnet", "port", default=80
)
@property
def ip_check(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "allnet", "ip_check", default=False
)
@property
def allow_online_updates(self) -> int:
return CoreConfig.get_config_field(
@ -260,10 +240,23 @@ class AllnetConfig:
self.__config, "core", "allnet", "update_cfg_folder", default=""
)
class BillingConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def standalone(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "billing", "standalone", default=True
)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "billing", "loglevel", default="info"
)
)
@property
def port(self) -> int:
@ -289,7 +282,6 @@ class BillingConfig:
self.__config, "core", "billing", "signing_key", default="cert/billing.key"
)
class AimedbConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@ -326,17 +318,10 @@ class AimedbConfig:
self.__config, "core", "aimedb", "id_lifetime_seconds", default=86400
)
class MuchaConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def enable(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "mucha", "enable", default=False
)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(
@ -345,13 +330,6 @@ class MuchaConfig:
)
)
@property
def hostname(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "mucha", "hostname", default="localhost"
)
class CoreConfig(dict):
def __init__(self) -> None:
self.server = ServerConfig(self)

1
core/data/alembic/README Normal file
View File

@ -0,0 +1 @@
Generic single-database configuration.

View File

@ -0,0 +1,89 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = .
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; this defaults
# to ./versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat ./versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = driver://user:pass@localhost/dbname
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks=black
# black.type=console_scripts
# black.entrypoint=black
# black.options=-l 79
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

77
core/data/alembic/env.py Normal file
View File

@ -0,0 +1,77 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -2,7 +2,9 @@ import logging, coloredlogs
from typing import Any, Dict, List
from twisted.web import resource
from twisted.web.util import redirectTo
from twisted.web.http import Request
from starlette.requests import Request
from starlette.routing import Route
from starlette.responses import Response, PlainTextResponse
from logging.handlers import TimedRotatingFileHandler
from twisted.web.server import Session
from zope.interface import Interface, Attribute, implementer
@ -98,8 +100,15 @@ class FrontendServlet(resource.Resource):
self.putChild(b"game", fe_game)
self.logger.info(
f"Ready on port {self.config.frontend.port} serving {len(fe_game.children)} games"
f"Ready on port {self.config.server.port} serving {len(fe_game.children)} games"
)
def get_routes(self) -> List[Route]:
return []
@classmethod
async def robots(cls, request: Request) -> PlainTextResponse:
return PlainTextResponse("User-agent: *\nDisallow: /\n\nUser-agent: AdsBot-Google\nDisallow: /")
def render_GET(self, request):
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.uri.decode()}")

View File

@ -2,7 +2,7 @@ from typing import Dict, Any, Optional, List
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
from twisted.web import resource
from twisted.web.http import Request
from starlette.requests import Request
from datetime import datetime
from Crypto.Cipher import Blowfish
import pytz
@ -12,7 +12,7 @@ from .utils import Utils
from .title import TitleServlet
class MuchaServlet:
mucha_registry: List[str] = []
mucha_registry: Dict[str, str] = {}
def __init__(self, cfg: CoreConfig, cfg_dir: str) -> None:
self.config = cfg
self.config_dir = cfg_dir
@ -39,11 +39,12 @@ class MuchaServlet:
for _, mod in TitleServlet.title_registry.items():
if hasattr(mod, "get_mucha_info"):
enabled, game_cd = mod.get_mucha_info(
enabled, game_cds, netid_prefixes = mod.get_mucha_info(
self.config, self.config_dir
)
if enabled:
self.mucha_registry.append(game_cd)
for x in range(len(game_cds)):
self.mucha_registry[game_cds[x]] = netid_prefixes[x]
self.logger.info(f"Serving {len(self.mucha_registry)} games")
@ -75,7 +76,7 @@ class MuchaServlet:
self.logger.debug(f"Decrypt SN to {sn_decrypt.hex()}")
resp = MuchaAuthResponse(
f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}"
f"{self.config.server.hostname}{':' + str(self.config.server.port) if self.config.server.is_develop else ''}"
)
self.logger.debug(f"Mucha response {vars(resp)}")
@ -100,7 +101,7 @@ class MuchaServlet:
self.logger.warning(f"Unknown gameCd {req.gameCd}")
return b"RESULTS=000"
resp = MuchaUpdateResponse(req.gameVer, f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}")
resp = MuchaUpdateResponse(req.gameVer, f"{self.config.server.hostname}{':' + str(self.config.server.port) if self.config.server.is_develop else ''}")
self.logger.debug(f"Mucha response {vars(resp)}")

View File

@ -1,7 +1,9 @@
from typing import Dict, List, Tuple
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
from twisted.web.http import Request
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route
from core.config import CoreConfig
from core.data import Data
@ -28,18 +30,16 @@ class BaseServlet:
"""
return False
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
def get_routes(self) -> List[Route]:
"""Called during boot to get all matcher endpoints this title servlet handles
Returns:
Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]: A 2-length tuple where offset 0 is GET and offset 1 is POST,
containing a list of 3-length tuples where offset 0 is the name of the function in the handler that should be called, offset 1
is the matching string, and offset 2 is a dict containing rules for the matcher.
List[Route]: A list of Routes, WebSocketRoutes, or similar classes
"""
return (
[("render_GET", "/{game}/{version}/{endpoint}", {'game': R'S...'})],
[("render_POST", "/{game}/{version}/{endpoint}", {'game': R'S...'})]
)
return [
Route("/{game}/{version}/{endpoint}", self.render_POST, methods=["POST"]),
Route("/{game}/{version}/{endpoint}", self.render_GET, methods=["GET"]),
]
def setup(self) -> None:
"""Called once during boot, should contain any additional setup the handler must do, such as starting any sub-services
@ -58,11 +58,11 @@ class BaseServlet:
Tuple[str, str]: A tuple where offset 0 is the allnet uri field, and offset 1 is the allnet host field
"""
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port(self.core_cfg) != 80:
return (f"http://{self.core_cfg.title.hostname}:{Utils.get_title_port(self.core_cfg)}/{game_code}/{game_ver}/", "")
return (f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/{game_code}/{game_ver}/", "")
return (f"http://{self.core_cfg.title.hostname}/{game_code}/{game_ver}/", "")
return (f"http://{self.core_cfg.server.hostname}/{game_code}/{game_ver}/", "")
def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str]:
def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, List[str], List[str]]:
"""Called once during boot to check if this game is a mucha game
Args:
@ -72,15 +72,15 @@ class BaseServlet:
Returns:
Tuple[bool, str]: Tuple where offset 0 is true if the game is enabled, false otherwise, and offset 1 is the game CD
"""
return (False, "")
return (False, [], [])
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
self.logger.warn(f"{game_code} Does not dispatch POST")
return None
async def render_POST(self, request: Request) -> bytes:
self.logger.warn(f"Game Does not dispatch POST")
return Response()
def render_GET(self, request: Request, game_code: str, matchers: Dict) -> bytes:
self.logger.warn(f"{game_code} Does not dispatch GET")
return None
async def render_GET(self, request: Request) -> bytes:
self.logger.warn(f"Game Does not dispatch GET")
return Response()
class TitleServlet:
title_registry: Dict[str, BaseServlet] = {}
@ -136,7 +136,7 @@ class TitleServlet:
self.logger.error(f"{folder} missing game_code or index in __init__.py, or is_game_enabled in index")
self.logger.info(
f"Serving {len(self.title_registry)} game codes {'on port ' + str(core_cfg.title.port) if core_cfg.title.port > 0 else ''}"
f"Serving {len(self.title_registry)} game codes {'on port ' + str(core_cfg.server.port) if core_cfg.server.port > 0 else ''}"
)
def render_GET(self, request: Request, endpoints: dict) -> bytes:

View File

@ -1,6 +1,6 @@
from typing import Dict, Any, Optional
from types import ModuleType
from twisted.web.http import Request
from starlette.requests import Request
import logging
import importlib
from os import walk
@ -34,36 +34,16 @@ class Utils:
@classmethod
def get_ip_addr(cls, req: Request) -> str:
return (
req.getAllHeaders()[b"x-forwarded-for"].decode()
if b"x-forwarded-for" in req.getAllHeaders()
else req.getClientAddress().host
)
return req.headers.get("x-forwarded-for", req.client.host)
@classmethod
def get_title_port(cls, cfg: CoreConfig):
if cls.real_title_port is not None: return cls.real_title_port
if cfg.title.port == 0:
cls.real_title_port = cfg.allnet.port
else:
cls.real_title_port = cfg.title.port
cls.real_title_port = cfg.server.proxy_port if cfg.server.is_using_proxy else cfg.server.port
return cls.real_title_port
@classmethod
def get_title_port_ssl(cls, cfg: CoreConfig):
if cls.real_title_port_ssl is not None: return cls.real_title_port_ssl
if cfg.title.port_ssl == 0:
cls.real_title_port_ssl = 443
else:
cls.real_title_port_ssl = cfg.title.port_ssl
return cls.real_title_port_ssl
def create_sega_auth_key(aime_id: int, game: str, place_id: int, keychip_id: str, b64_secret: str, exp_seconds: int = 86400, err_logger: str = 'aimedb') -> Optional[str]:
logger = logging.getLogger(err_logger)
try:

View File

@ -1,9 +1,11 @@
import yaml
#!/usr/bin/env python3
import argparse
import logging
from core.config import CoreConfig
from core.data import Data
from os import path, mkdir, access, W_OK
from os import mkdir, path, access, W_OK
import yaml
import asyncio
from core import Data, CoreConfig
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Database utilities")
@ -16,19 +18,9 @@ if __name__ == "__main__":
type=str,
help="Version of the database to upgrade/rollback to",
)
parser.add_argument(
"--game",
"-g",
type=str,
help="Game code of the game who's schema will be updated/rolled back. Ex. SDFE",
)
parser.add_argument("--email", "-e", type=str, help="Email for the new user")
parser.add_argument("--old_ac", "-o", type=str, help="Access code to transfer from")
parser.add_argument("--new_ac", "-n", type=str, help="Access code to transfer to")
parser.add_argument("--force", "-f", type=bool, help="Force the action to happen")
parser.add_argument(
"action", type=str, help="DB Action, create, recreate, upgrade, or rollback"
)
parser.add_argument("--access_code", "-a", type=str, help="Access code for new/transfer user", default="00000000000000000000")
parser.add_argument("action", type=str, help="create, upgrade, create-owner")
args = parser.parse_args()
cfg = CoreConfig()
@ -50,42 +42,13 @@ if __name__ == "__main__":
if args.action == "create":
data.create_database()
elif args.action == "recreate":
data.recreate_database()
elif args.action == "upgrade" or args.action == "rollback":
if args.version is None:
data.logger.warning("No version set, upgrading to latest")
if args.game is None:
data.logger.warning("No game set, upgrading core schema")
data.migrate_database(
"CORE",
int(args.version) if args.version is not None else None,
args.action,
)
else:
data.migrate_database(
args.game,
int(args.version) if args.version is not None else None,
args.action,
)
elif args.action == "autoupgrade":
data.autoupgrade()
elif args.action == "upgrade":
data.schema_upgrade(args.version)
elif args.action == "create-owner":
data.create_owner(args.email)
loop = asyncio.get_event_loop()
loop.run_until_complete(data.create_owner(args.email, args.access_code))
elif args.action == "migrate-card":
data.migrate_card(args.old_ac, args.new_ac, args.force)
elif args.action == "cleanup":
data.delete_hanging_users()
elif args.action == "version":
data.show_versions()
data.logger.info("Done")
else:
logging.getLogger("database").info(f"Unknown action {args.action}")

View File

@ -1,26 +1,24 @@
server:
listen_address: "127.0.0.1"
listen_address: "127.0.0.1"
hostname: "localhost"
port: 8080
ssl_key: "cert/title.key"
ssl_cert: "cert/title.crt"
allow_user_registration: True
allow_unregistered_serials: True
name: "ARTEMiS"
is_develop: True
is_using_proxy: False
threading: False
proxy_port: 0
log_dir: "logs"
check_arcade_ip: False
strict_ip_checking: False
title:
loglevel: "info"
hostname: "localhost"
port: 8080
port_ssl: 0
ssl_cert: "cert/title.crt"
ssl_key: "cert/title.key"
reboot_start_time: "04:00"
reboot_end_time: "05:00"
ssl_key: "cert/title.key"
database:
host: "localhost"
username: "aime"
@ -30,23 +28,22 @@ database:
protocol: "mysql"
sha2_password: False
loglevel: "warn"
user_table_autoincrement_start: 10000
enable_memcached: True
memcached_host: "localhost"
frontend:
enable: False
port: 8090
standalone: True
loglevel: "info"
secret: ""
allnet:
loglevel: "info"
port: 80
ip_check: False
allow_online_updates: False
update_cfg_folder: ""
billing:
standalone: True
loglevel: "info"
port: 8443
ssl_key: "cert/server.key"
ssl_cert: "cert/server.pem"
@ -60,6 +57,4 @@ aimedb:
id_lifetime_seconds: 86400
mucha:
enable: False
hostname: "localhost"
loglevel: "info"

375
index.py
View File

@ -1,335 +1,92 @@
#!/usr/bin/env python3
import argparse
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
from typing import Dict
import yaml
from os import path, mkdir, access, W_OK
from core import *
from os import path, mkdir, access, W_OK, environ
import uvicorn
import logging
import asyncio
from twisted.web import server, resource
from twisted.internet import reactor, endpoints
from twisted.web.http import Request
from routes import Mapper
from threading import Thread
from core import CoreConfig, AimedbServlette
class HttpDispatcher(resource.Resource):
def __init__(self, cfg: CoreConfig, config_dir: str):
super().__init__()
self.config = cfg
self.isLeaf = True
self.map_get = Mapper()
self.map_post = Mapper()
self.logger = logging.getLogger("core")
self.title = TitleServlet(cfg, config_dir)
self.allnet = AllnetServlet(cfg, config_dir)
self.mucha = MuchaServlet(cfg, config_dir)
self.map_get.connect(
"allnet_downloadorder_ini",
"/dl/ini/{file}",
controller="allnet",
action="handle_dlorder_ini",
conditions=dict(method=["GET"]),
async def launch_main(cfg: CoreConfig, ssl: bool) -> None:
if ssl:
server_cfg = uvicorn.Config(
"core.app:app",
host=cfg.server.listen_address,
port=cfg.server.port if args.port == 0 else args.port,
reload=cfg.server.is_develop,
log_level="info" if cfg.server.is_develop else "critical",
ssl_version=3,
ssl_certfile=cfg.server.ssl_cert,
ssl_keyfile=cfg.server.ssl_key
)
else:
server_cfg = uvicorn.Config(
"core.app:app",
host=cfg.server.listen_address,
port=cfg.server.port if args.port == 0 else args.port,
reload=cfg.server.is_develop,
log_level="info" if cfg.server.is_develop else "critical"
)
server = uvicorn.Server(server_cfg)
await server.serve()
self.map_post.connect(
"allnet_downloadorder_report",
"/report-api/Report",
controller="allnet",
action="handle_dlorder_report",
conditions=dict(method=["POST"]),
async def launch_billing(cfg: CoreConfig) -> None:
server_cfg = uvicorn.Config(
"core.allnet:app",
host=cfg.server.listen_address,
port=cfg.billing.port,
reload=cfg.server.is_develop,
log_level="info" if cfg.server.is_develop else "critical",
ssl_version=3,
ssl_certfile=cfg.billing.ssl_cert,
ssl_keyfile=cfg.billing.ssl_key
)
server = uvicorn.Server(server_cfg)
if cfg.billing.standalone:
await server.serve()
else:
while True:
pass
self.map_get.connect(
"allnet_ping",
"/naomitest.html",
controller="allnet",
action="handle_naomitest",
conditions=dict(method=["GET"]),
)
self.map_post.connect(
"allnet_poweron",
"/sys/servlet/PowerOn",
controller="allnet",
action="handle_poweron",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"allnet_downloadorder",
"/sys/servlet/DownloadOrder",
controller="allnet",
action="handle_dlorder",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"allnet_loaderstaterecorder",
"/sys/servlet/LoaderStateRecorder",
controller="allnet",
action="handle_loaderstaterecorder",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"allnet_alive",
"/sys/servlet/Alive",
controller="allnet",
action="handle_alive",
conditions=dict(method=["POST"]),
)
self.map_get.connect(
"allnet_alive",
"/sys/servlet/Alive",
controller="allnet",
action="handle_alive",
conditions=dict(method=["GET"]),
)
self.map_post.connect(
"allnet_billing",
"/request",
controller="allnet",
action="handle_billing_request",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"allnet_billing",
"/request/",
controller="allnet",
action="handle_billing_request",
conditions=dict(method=["POST"]),
)
# Maintain compatability
self.map_post.connect(
"mucha_boardauth",
"/mucha/boardauth.do",
controller="mucha",
action="handle_boardauth",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"mucha_updatacheck",
"/mucha/updatacheck.do",
controller="mucha",
action="handle_updatecheck",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"mucha_dlstate",
"/mucha/downloadstate.do",
controller="mucha",
action="handle_dlstate",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"mucha_boardauth",
"/mucha_front/boardauth.do",
controller="mucha",
action="handle_boardauth",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"mucha_updatacheck",
"/mucha_front/updatacheck.do",
controller="mucha",
action="handle_updatecheck",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"mucha_dlstate",
"/mucha_front/downloadstate.do",
controller="mucha",
action="handle_dlstate",
conditions=dict(method=["POST"]),
)
for code, game in self.title.title_registry.items():
get_matchers, post_matchers = game.get_endpoint_matchers()
for m in get_matchers:
self.map_get.connect(
"title_get",
m[1],
controller="title",
action="render_GET",
title=code,
subaction=m[0],
conditions=dict(method=["GET"]),
requirements=m[2],
)
for m in post_matchers:
self.map_post.connect(
"title_post",
m[1],
controller="title",
action="render_POST",
title=code,
subaction=m[0],
conditions=dict(method=["POST"]),
requirements=m[2],
)
def render_GET(self, request: Request) -> bytes:
test = self.map_get.match(request.uri.decode())
client_ip = Utils.get_ip_addr(request)
if test is None:
self.logger.debug(
f"Unknown GET endpoint {request.uri.decode()} from {client_ip} to port {request.getHost().port}"
)
request.setResponseCode(404)
return b"Endpoint not found."
return self.dispatch(test, request)
def render_POST(self, request: Request) -> bytes:
test = self.map_post.match(request.uri.decode())
client_ip = Utils.get_ip_addr(request)
if test is None:
self.logger.debug(
f"Unknown POST endpoint {request.uri.decode()} from {client_ip} to port {request.getHost().port}"
)
request.setResponseCode(404)
return b"Endpoint not found."
return self.dispatch(test, request)
def dispatch(self, matcher: Dict, request: Request) -> bytes:
controller = getattr(self, matcher["controller"], None)
if controller is None:
self.logger.error(
f"Controller {matcher['controller']} not found via endpoint {request.uri.decode()}"
)
request.setResponseCode(404)
return b"Endpoint not found."
handler = getattr(controller, matcher["action"], None)
if handler is None:
self.logger.error(
f"Action {matcher['action']} not found in controller {matcher['controller']} via endpoint {request.uri.decode()}"
)
request.setResponseCode(404)
return b"Endpoint not found."
url_vars = matcher
url_vars.pop("controller")
url_vars.pop("action")
ret = handler(request, url_vars)
if type(ret) == str:
return ret.encode()
elif type(ret) == bytes or type(ret) == tuple: # allow for bytes or tuple (data, response code) responses
return ret
elif ret is None:
self.logger.warning(f"None returned by controller for {request.uri.decode()} endpoint")
return b""
else:
self.logger.warning(f"Unknown data type returned by controller for {request.uri.decode()} endpoint")
return b""
async def launcher(cfg: CoreConfig, ssl: bool) -> None:
AimedbServlette(cfg).start()
done, pending = await asyncio.wait(
[
asyncio.create_task(launch_main(cfg, ssl)),
asyncio.create_task(launch_billing(cfg)),
],
return_when=asyncio.FIRST_COMPLETED,
)
logging.getLogger("core").info("Shutdown")
for pending_task in pending:
pending_task.cancel("Another service died, server is shutting down")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="ARTEMiS main entry point")
parser = argparse.ArgumentParser(description="Artemis main entry point")
parser.add_argument(
"--config", "-c", type=str, default="config", help="Configuration folder"
)
parser.add_argument(
"--port", "-p", type=int, default=0, help="Port override"
)
parser.add_argument(
"--ssl", "-s", type=bool, help="Launch with SSL"
)
args = parser.parse_args()
if not path.exists(f"{args.config}/core.yaml"):
print(
f"The config folder you specified ({args.config}) does not exist or does not contain core.yaml.\nDid you copy the example folder?"
f"The config folder you specified ({args.config}) does not exist or does not contain core.yaml. Defaults will be used.\nDid you copy the example folder?"
)
exit(1)
cfg: CoreConfig = CoreConfig()
if path.exists(f"{args.config}/core.yaml"):
cfg.update(yaml.safe_load(open(f"{args.config}/core.yaml")))
if not path.exists(cfg.server.log_dir):
mkdir(cfg.server.log_dir)
environ["ARTEMIS_CFG_DIR"] = args.config
if not access(cfg.server.log_dir, W_OK):
print(
f"Log directory {cfg.server.log_dir} NOT writable, please check permissions"
)
exit(1)
logger = logging.getLogger("core")
log_fmt_str = "[%(asctime)s] Core | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(cfg.server.log_dir, "core"), when="d", backupCount=10
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)
log_lv = logging.DEBUG if cfg.server.is_develop else logging.INFO
logger.setLevel(log_lv)
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
if not cfg.aimedb.key:
logger.error("!!AIMEDB KEY BLANK, SET KEY IN CORE.YAML!!")
exit(1)
logger.info(
f"ARTEMiS starting in {'develop' if cfg.server.is_develop else 'production'} mode"
)
allnet_server_str = f"tcp:{cfg.allnet.port}:interface={cfg.server.listen_address}"
title_server_str = f"tcp:{cfg.title.port}:interface={cfg.server.listen_address}"
title_https_server_str = f"ssl:{cfg.title.port_ssl}:interface={cfg.server.listen_address}:privateKey={cfg.title.ssl_key}:certKey={cfg.title.ssl_cert}"
adb_server_str = f"tcp:{cfg.aimedb.port}:interface={cfg.server.listen_address}"
frontend_server_str = (
f"tcp:{cfg.frontend.port}:interface={cfg.server.listen_address}"
)
billing_server_str = f"tcp:{cfg.billing.port}:interface={cfg.server.listen_address}"
if cfg.server.is_develop:
billing_server_str = (
f"ssl:{cfg.billing.port}:interface={cfg.server.listen_address}"
f":privateKey={cfg.billing.ssl_key}:certKey={cfg.billing.ssl_cert}"
)
dispatcher = HttpDispatcher(cfg, args.config)
endpoints.serverFromString(reactor, allnet_server_str).listen(
server.Site(dispatcher)
)
endpoints.serverFromString(reactor, adb_server_str).listen(AimedbFactory(cfg))
if cfg.frontend.enable:
endpoints.serverFromString(reactor, frontend_server_str).listen(
server.Site(FrontendServlet(cfg, args.config))
)
if cfg.billing.port > 0:
endpoints.serverFromString(reactor, billing_server_str).listen(
server.Site(dispatcher)
)
if cfg.title.port > 0:
endpoints.serverFromString(reactor, title_server_str).listen(
server.Site(dispatcher)
)
if cfg.title.port_ssl > 0:
endpoints.serverFromString(reactor, title_https_server_str).listen(
server.Site(dispatcher)
)
if cfg.server.threading:
Thread(target=reactor.run, args=(False,)).start()
else:
reactor.run()
asyncio.run(launcher(cfg, args.ssl))

View File

@ -1,4 +1,4 @@
# vim: set fileencoding=utf-8
#!/usr/bin/env python3
import argparse
import re
import os

View File

@ -1,6 +1,5 @@
mypy
wheel
twisted
pytz
pyyaml
sqlalchemy==1.4.46
@ -12,10 +11,11 @@ inflection
coloredlogs
pylibmc; platform_system != "Windows"
wacky
Routes
bcrypt
jinja2
protobuf
autobahn
pillow
pyjwt
pyjwt==2.8.0
websockets
starlette
asyncio

View File

@ -11,7 +11,7 @@ class ChuniAir(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AIR
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.10.00"
return ret

View File

@ -11,7 +11,7 @@ class ChuniAirPlus(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AIR_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.15.00"
return ret

View File

@ -13,7 +13,7 @@ class ChuniAmazon(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AMAZON
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.30.00"
return ret

View File

@ -13,7 +13,7 @@ class ChuniAmazonPlus(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.35.00"
return ret

View File

@ -22,7 +22,7 @@ class ChuniBase:
self.game = ChuniConstants.GAME_CODE
self.version = ChuniConstants.VER_CHUNITHM
def handle_game_login_api_request(self, data: Dict) -> Dict:
async def handle_game_login_api_request(self, data: Dict) -> Dict:
"""
Handles the login bonus logic, required for the game because
getUserLoginBonus gets called after getUserItem and therefore the
@ -119,11 +119,11 @@ class ChuniBase:
return {"returnCode": 1}
def handle_game_logout_api_request(self, data: Dict) -> Dict:
async def handle_game_logout_api_request(self, data: Dict) -> Dict:
# self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]})
return {"returnCode": 1}
def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
async def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
game_charge_list = self.data.static.get_enabled_charges(self.version)
if game_charge_list is None or len(game_charge_list) == 0:
@ -145,7 +145,7 @@ class ChuniBase:
)
return {"length": len(charges), "gameChargeList": charges}
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
async def handle_get_game_event_api_request(self, data: Dict) -> Dict:
game_events = self.data.static.get_enabled_events(self.version)
if game_events is None or len(game_events) == 0:
@ -177,10 +177,10 @@ class ChuniBase:
"gameEventList": event_list,
}
def handle_get_game_idlist_api_request(self, data: Dict) -> Dict:
async def handle_get_game_idlist_api_request(self, data: Dict) -> Dict:
return {"type": data["type"], "length": 0, "gameIdlistList": []}
def handle_get_game_message_api_request(self, data: Dict) -> Dict:
async def handle_get_game_message_api_request(self, data: Dict) -> Dict:
return {
"type": data["type"],
"length": 1,
@ -193,14 +193,14 @@ class ChuniBase:
}]
}
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
async def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
rankings = self.data.score.get_rankings(self.version)
return {"type": data["type"], "gameRankingList": rankings}
def handle_get_game_sale_api_request(self, data: Dict) -> Dict:
async def handle_get_game_sale_api_request(self, data: Dict) -> Dict:
return {"type": data["type"], "length": 0, "gameSaleList": []}
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
# if reboot start/end time is not defined use the default behavior of being a few hours ago
if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
reboot_start = datetime.strftime(
@ -240,7 +240,7 @@ class ChuniBase:
"isDumpUpload": "false",
"isAou": "false",
}
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
async def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
user_activity_list = self.data.profile.get_profile_activity(
data["userId"], data["kind"]
)
@ -261,7 +261,7 @@ class ChuniBase:
"userActivityList": activity_list,
}
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
async def handle_get_user_character_api_request(self, data: Dict) -> Dict:
characters = self.data.item.get_characters(data["userId"])
if characters is None:
return {
@ -296,7 +296,7 @@ class ChuniBase:
"userCharacterList": character_list,
}
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
async def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
user_charge_list = self.data.profile.get_profile_charge(data["userId"])
charge_list = []
@ -312,14 +312,14 @@ class ChuniBase:
"userChargeList": charge_list,
}
def handle_get_user_recent_player_api_request(self, data: Dict) -> Dict:
async def handle_get_user_recent_player_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"length": 0,
"userRecentPlayerList": [], # playUserId, playUserName, playDate, friendPoint
}
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
async def handle_get_user_course_api_request(self, data: Dict) -> Dict:
user_course_list = self.data.score.get_courses(data["userId"])
if user_course_list is None:
return {
@ -354,7 +354,7 @@ class ChuniBase:
"userCourseList": course_list,
}
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
async def handle_get_user_data_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None:
return {}
@ -366,7 +366,7 @@ class ChuniBase:
return {"userId": data["userId"], "userData": profile}
def handle_get_user_data_ex_api_request(self, data: Dict) -> Dict:
async def handle_get_user_data_ex_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_data_ex(data["userId"], self.version)
if p is None:
return {}
@ -378,7 +378,7 @@ class ChuniBase:
return {"userId": data["userId"], "userDataEx": profile}
def handle_get_user_duel_api_request(self, data: Dict) -> Dict:
async def handle_get_user_duel_api_request(self, data: Dict) -> Dict:
user_duel_list = self.data.item.get_duels(data["userId"])
if user_duel_list is None:
return {}
@ -396,7 +396,7 @@ class ChuniBase:
"userDuelList": duel_list,
}
def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_rival(data["rivalId"])
if p is None:
return {}
@ -409,7 +409,7 @@ class ChuniBase:
"userRivalData": userRivalData
}
def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
rival_id = data["rivalId"]
next_index = int(data["nextIndex"])
max_count = int(data["maxCount"])
@ -462,7 +462,7 @@ class ChuniBase:
return result
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
async def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
user_fav_item_list = []
# still needs to be implemented on WebUI
@ -482,14 +482,14 @@ class ChuniBase:
"userFavoriteItemList": user_fav_item_list,
}
def handle_get_user_favorite_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_favorite_music_api_request(self, data: Dict) -> Dict:
"""
This is handled via the webui, which we don't have right now
"""
return {"userId": data["userId"], "length": 0, "userFavoriteMusicList": []}
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = int(int(data["nextIndex"]) / 10000000000)
next_idx = int(int(data["nextIndex"]) % 10000000000)
user_item_list = self.data.item.get_items(data["userId"], kind)
@ -526,7 +526,7 @@ class ChuniBase:
"userItemList": items,
}
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
async def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
user_login_bonus = self.data.item.get_all_login_bonus(user_id, self.version)
# ignore the loginBonus request if its disabled in config
@ -552,7 +552,7 @@ class ChuniBase:
"userLoginBonusList": user_login_list,
}
def handle_get_user_map_api_request(self, data: Dict) -> Dict:
async def handle_get_user_map_api_request(self, data: Dict) -> Dict:
user_map_list = self.data.item.get_maps(data["userId"])
if user_map_list is None:
return {}
@ -570,7 +570,7 @@ class ChuniBase:
"userMapList": map_list,
}
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
music_detail = self.data.score.get_scores(data["userId"])
if music_detail is None:
return {
@ -629,7 +629,7 @@ class ChuniBase:
"userMusicList": song_list, # 240
}
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
async def handle_get_user_option_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_option(data["userId"])
option = p._asdict()
@ -638,7 +638,7 @@ class ChuniBase:
return {"userId": data["userId"], "userGameOption": option}
def handle_get_user_option_ex_api_request(self, data: Dict) -> Dict:
async def handle_get_user_option_ex_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_option_ex(data["userId"])
option = p._asdict()
@ -650,7 +650,7 @@ class ChuniBase:
def read_wtf8(self, src):
return bytes([ord(c) for c in src]).decode("utf-8")
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
async def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None:
return None
@ -692,7 +692,7 @@ class ChuniBase:
"userNameEx": profile["userName"],
}
def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
async def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
recent_rating_list = self.data.profile.get_profile_recent_rating(data["userId"])
if recent_rating_list is None:
return {
@ -707,7 +707,7 @@ class ChuniBase:
"userRecentRatingList": recent_rating_list["recentRating"],
}
def handle_get_user_region_api_request(self, data: Dict) -> Dict:
async def handle_get_user_region_api_request(self, data: Dict) -> Dict:
# TODO: Region
return {
"userId": data["userId"],
@ -715,7 +715,7 @@ class ChuniBase:
"userRegionList": [],
}
def handle_get_user_team_api_request(self, data: Dict) -> Dict:
async def handle_get_user_team_api_request(self, data: Dict) -> Dict:
# Default values
team_id = 65535
team_name = self.game_cfg.team.team_name
@ -750,7 +750,7 @@ class ChuniBase:
},
}
def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict:
async def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"length": 0,
@ -758,7 +758,7 @@ class ChuniBase:
"teamCourseSettingList": [],
}
def handle_get_team_course_setting_api_request_proto(self, data: Dict) -> Dict:
async def handle_get_team_course_setting_api_request_proto(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"length": 1,
@ -782,7 +782,7 @@ class ChuniBase:
],
}
def handle_get_team_course_rule_api_request(self, data: Dict) -> Dict:
async def handle_get_team_course_rule_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"length": 0,
@ -790,7 +790,7 @@ class ChuniBase:
"teamCourseRuleList": []
}
def handle_get_team_course_rule_api_request_proto(self, data: Dict) -> Dict:
async def handle_get_team_course_rule_api_request_proto(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"length": 1,
@ -807,7 +807,7 @@ class ChuniBase:
],
}
def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
async def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
upsert = data["upsertUserAll"]
user_id = data["userId"]
@ -927,28 +927,28 @@ class ChuniBase:
return {"returnCode": "1"}
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
async def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
# add tickets after they got bought, this makes sure the tickets are
# still valid after an unsuccessful logout
self.data.profile.put_profile_charge(data["userId"], data["userCharge"])
return {"returnCode": "1"}
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return {"returnCode": "1"}
def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict:
return {"returnCode": "1"}
def handle_upsert_client_error_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_error_api_request(self, data: Dict) -> Dict:
return {"returnCode": "1"}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
return {"returnCode": "1"}
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
return {"returnCode": "1"}
def handle_get_user_net_battle_data_api_request(self, data: Dict) -> Dict:
async def handle_get_user_net_battle_data_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"userNetBattleData": {"recentNBSelectMusicList": []},

View File

@ -13,7 +13,7 @@ class ChuniCrystal(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.40.00"
return ret

View File

@ -13,7 +13,7 @@ class ChuniCrystalPlus(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.45.00"
return ret

View File

@ -1,4 +1,6 @@
from twisted.web.http import Request
from starlette.requests import Request
from starlette.routing import Route
from starlette.responses import Response
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
import zlib
@ -33,7 +35,6 @@ from .newplus import ChuniNewPlus
from .sun import ChuniSun
from .sunplus import ChuniSunPlus
class ChuniServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
super().__init__(core_cfg, cfg_dir)
@ -124,15 +125,6 @@ class ChuniServlet(BaseServlet):
f"Hashed v{version} method {method_fixed} with {bytes.fromhex(keys[2])} to get {hash.hex()}"
)
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[
("render_POST", "/{game}/{version}/ChuniServlet/{endpoint}", {}),
("render_POST", "/{game}/{version}/ChuniServlet/MatchingServer/{endpoint}", {})
]
)
@classmethod
def is_game_enabled(
cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str
@ -150,19 +142,25 @@ class ChuniServlet(BaseServlet):
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port(self.core_cfg) != 80:
return (f"http://{self.core_cfg.title.hostname}:{Utils.get_title_port(self.core_cfg)}/{game_code}/{game_ver}/", self.core_cfg.title.hostname)
return (f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/{game_code}/{game_ver}/", self.core_cfg.server.hostname)
return (f"http://{self.core_cfg.title.hostname}/{game_code}/{game_ver}/", self.core_cfg.title.hostname)
return (f"http://{self.core_cfg.server.hostname}/{game_code}/{game_ver}/", self.core_cfg.server.hostname)
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
endpoint = matchers['endpoint']
version = int(matchers['version'])
game_code = matchers['game']
def get_routes(self) -> List[Route]:
return [
Route("/{game:str}/{version:int}/ChuniServlet/{endpoint:str}", self.render_POST, methods=['POST']),
Route("/{game:str}/{version:int}/ChuniServlet/MatchingServer/{endpoint:str}", self.render_POST, methods=['POST']),
]
async def render_POST(self, request: Request) -> bytes:
endpoint: str = request.path_params.get('endpoint')
version: int = request.path_params.get('version')
game_code: str = request.path_params.get('game')
if endpoint.lower() == "ping":
return zlib.compress(b'{"returnCode": "1"}')
return Response(zlib.compress(b'{"returnCode": "1"}'))
req_raw = request.content.getvalue()
req_raw = await request.body()
encrtped = False
internal_ver = 0
@ -201,7 +199,7 @@ class ChuniServlet(BaseServlet):
internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS
elif game_code == "SDGS": # Int
if version < 110: # SUPERSTAR
internal_ver = ChuniConstants.PARADISE
internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE # FIXME: Not sure what was intended to go here? was just "PARADISE"
elif version >= 110 and version < 115: # NEW
internal_ver = ChuniConstants.VER_CHUNITHM_NEW
elif version >= 115 and version < 120: # NEW PLUS!!
@ -216,20 +214,20 @@ class ChuniServlet(BaseServlet):
# doing encrypted. The likelyhood of false positives is low but
# technically not 0
if internal_ver < ChuniConstants.VER_CHUNITHM_NEW:
endpoint = request.getHeader("User-Agent").split("#")[0]
endpoint = request.headers.get("User-Agent").split("#")[0]
else:
if internal_ver not in self.hash_table:
self.logger.error(
f"v{version} does not support encryption or no keys entered"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
elif endpoint.lower() not in self.hash_table[internal_ver]:
self.logger.error(
f"No hash found for v{version} endpoint {endpoint}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
endpoint = self.hash_table[internal_ver][endpoint.lower()]
@ -246,7 +244,7 @@ class ChuniServlet(BaseServlet):
self.logger.error(
f"Failed to decrypt v{version} request to {endpoint} -> {e}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
encrtped = True
@ -258,7 +256,7 @@ class ChuniServlet(BaseServlet):
self.logger.error(
f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
try:
unzip = zlib.decompress(req_raw)
@ -267,7 +265,7 @@ class ChuniServlet(BaseServlet):
self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}"
)
return b""
return Response(zlib.compress(b'{"stat": "0"}'))
req_data = json.loads(unzip)
@ -285,11 +283,11 @@ class ChuniServlet(BaseServlet):
else:
try:
handler = getattr(handler_cls, func_to_find)
resp = handler(req_data)
resp = await handler(req_data)
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
if resp == None:
resp = {"returnCode": 1}
@ -299,7 +297,7 @@ class ChuniServlet(BaseServlet):
zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
if not encrtped:
return zipped
return Response(zipped)
padded = pad(zipped, 16)
@ -309,4 +307,4 @@ class ChuniServlet(BaseServlet):
bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]),
)
return crypt.encrypt(padded)
return Response(crypt.encrypt(padded))

View File

@ -33,7 +33,7 @@ class ChuniNew(ChuniBase):
if self.version == ChuniConstants.VER_CHUNITHM_SUN_PLUS:
return "215"
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
# use UTC time and convert it to JST time by adding +9
# matching therefore starts one hour before and lasts for 8 hours
match_start = datetime.strftime(
@ -82,26 +82,26 @@ class ChuniNew(ChuniBase):
"matchErrorLimit": self.game_cfg.matching.match_error_limit,
"romVersion": self.game_cfg.version.version(self.version)["rom"],
"dataVersion": self.game_cfg.version.version(self.version)["data"],
"matchingUri": f"http://{self.core_cfg.title.hostname}:{t_port}/SDHD/{self._interal_ver_to_intver()}/ChuniServlet/",
"matchingUriX": f"http://{self.core_cfg.title.hostname}:{t_port}/SDHD/{self._interal_ver_to_intver()}/ChuniServlet/",
"matchingUri": f"http://{self.core_cfg.server.hostname}:{t_port}/SDHD/{self._interal_ver_to_intver()}/ChuniServlet/",
"matchingUriX": f"http://{self.core_cfg.server.hostname}:{t_port}/SDHD/{self._interal_ver_to_intver()}/ChuniServlet/",
# might be really important for online battle to connect the cabs via UDP port 50201
"udpHolePunchUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/{self._interal_ver_to_intver()}/ChuniServlet/",
"reflectorUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/{self._interal_ver_to_intver()}/ChuniServlet/",
"udpHolePunchUri": f"http://{self.core_cfg.server.hostname}:{self.core_cfg.server.port}/SDHD/{self._interal_ver_to_intver()}/ChuniServlet/",
"reflectorUri": f"http://{self.core_cfg.server.hostname}:{self.core_cfg.server.port}/SDHD/{self._interal_ver_to_intver()}/ChuniServlet/",
},
"isDumpUpload": False,
"isAou": False,
}
def handle_remove_token_api_request(self, data: Dict) -> Dict:
async def handle_remove_token_api_request(self, data: Dict) -> Dict:
return {"returnCode": "1"}
def handle_delete_token_api_request(self, data: Dict) -> Dict:
async def handle_delete_token_api_request(self, data: Dict) -> Dict:
return {"returnCode": "1"}
def handle_create_token_api_request(self, data: Dict) -> Dict:
async def handle_create_token_api_request(self, data: Dict) -> Dict:
return {"returnCode": "1"}
def handle_get_user_map_area_api_request(self, data: Dict) -> Dict:
async def handle_get_user_map_area_api_request(self, data: Dict) -> Dict:
user_map_areas = self.data.item.get_map_areas(data["userId"])
map_areas = []
@ -113,10 +113,10 @@ class ChuniNew(ChuniBase):
return {"userId": data["userId"], "userMapAreaList": map_areas}
def handle_get_user_symbol_chat_setting_api_request(self, data: Dict) -> Dict:
async def handle_get_user_symbol_chat_setting_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "symbolCharInfoList": []}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
async def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None:
return None
@ -164,7 +164,7 @@ class ChuniNew(ChuniBase):
}
return data1
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None:
return {}
@ -177,13 +177,13 @@ class ChuniNew(ChuniBase):
"isLogin": False,
}
def handle_printer_login_api_request(self, data: Dict) -> Dict:
async def handle_printer_login_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
def handle_printer_logout_api_request(self, data: Dict) -> Dict:
async def handle_printer_logout_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
def handle_get_game_gacha_api_request(self, data: Dict) -> Dict:
async def handle_get_game_gacha_api_request(self, data: Dict) -> Dict:
"""
returns all current active banners (gachas)
"""
@ -213,7 +213,7 @@ class ChuniNew(ChuniBase):
"registIdList": [],
}
def handle_get_game_gacha_card_by_id_api_request(self, data: Dict) -> Dict:
async def handle_get_game_gacha_card_by_id_api_request(self, data: Dict) -> Dict:
"""
returns all valid cards for a given gachaId
"""
@ -237,7 +237,7 @@ class ChuniNew(ChuniBase):
"ssrBookCalcList": [],
}
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None:
return {}
@ -262,7 +262,7 @@ class ChuniNew(ChuniBase):
],
}
def handle_get_user_gacha_api_request(self, data: Dict) -> Dict:
async def handle_get_user_gacha_api_request(self, data: Dict) -> Dict:
user_gachas = self.data.item.get_user_gachas(data["userId"])
if user_gachas is None:
return {"userId": data["userId"], "length": 0, "userGachaList": []}
@ -281,7 +281,7 @@ class ChuniNew(ChuniBase):
"userGachaList": user_gacha_list,
}
def handle_get_user_printed_card_api_request(self, data: Dict) -> Dict:
async def handle_get_user_printed_card_api_request(self, data: Dict) -> Dict:
user_print_list = self.data.item.get_user_print_states(
data["userId"], has_completed=True
)
@ -316,7 +316,7 @@ class ChuniNew(ChuniBase):
"userPrintedCardList": print_list,
}
def handle_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
async def handle_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
user_print_states = self.data.item.get_user_print_states(
@ -338,13 +338,13 @@ class ChuniNew(ChuniBase):
"userCardPrintStateList": card_print_state_list,
}
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
return super().handle_get_user_character_api_request(data)
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
return super().handle_get_user_item_api_request(data)
def handle_roll_gacha_api_request(self, data: Dict) -> Dict:
async def handle_roll_gacha_api_request(self, data: Dict) -> Dict:
"""
Handle a gacha roll API request, with:
gachaId: the gachaId where the cards should be pulled from
@ -386,7 +386,7 @@ class ChuniNew(ChuniBase):
return {"length": len(rolled_cards), "gameGachaCardList": rolled_cards}
def handle_cm_upsert_user_gacha_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_gacha_api_request(self, data: Dict) -> Dict:
upsert = data["cmUpsertUserGacha"]
user_id = data["userId"]
place_id = data["placeId"]
@ -441,7 +441,7 @@ class ChuniNew(ChuniBase):
"userCardPrintStateList": card_print_state_list,
}
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
return {
"returnCode": 1,
"orderId": 0,
@ -449,7 +449,7 @@ class ChuniNew(ChuniBase):
"apiName": "CMUpsertUserPrintlogApi",
}
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
user_print_detail = data["userPrintDetail"]
user_id = data["userId"]
@ -474,7 +474,7 @@ class ChuniNew(ChuniBase):
"apiName": "CMUpsertUserPrintApi",
}
def handle_cm_upsert_user_print_subtract_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_print_subtract_api_request(self, data: Dict) -> Dict:
upsert = data["userCardPrintState"]
user_id = data["userId"]
place_id = data["placeId"]
@ -491,7 +491,7 @@ class ChuniNew(ChuniBase):
return {"returnCode": "1", "apiName": "CMUpsertUserPrintSubtractApi"}
def handle_cm_upsert_user_print_cancel_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_print_cancel_api_request(self, data: Dict) -> Dict:
order_ids = data["orderIdList"]
user_id = data["userId"]
@ -501,11 +501,11 @@ class ChuniNew(ChuniBase):
return {"returnCode": "1", "apiName": "CMUpsertUserPrintCancelApi"}
def handle_ping_request(self, data: Dict) -> Dict:
async def handle_ping_request(self, data: Dict) -> Dict:
# matchmaking ping request
return {"returnCode": "1"}
def handle_begin_matching_api_request(self, data: Dict) -> Dict:
async def handle_begin_matching_api_request(self, data: Dict) -> Dict:
room_id = 1
# check if there is a free matching room
matching_room = self.data.item.get_oldest_free_matching(self.version)
@ -554,7 +554,7 @@ class ChuniNew(ChuniBase):
return {"roomId": 1, "matchingWaitState": matching_wait}
def handle_end_matching_api_request(self, data: Dict) -> Dict:
async def handle_end_matching_api_request(self, data: Dict) -> Dict:
matching_room = self.data.item.get_matching(self.version, data["roomId"])
members = matching_room["matchingMemberInfoList"]
@ -579,10 +579,10 @@ class ChuniNew(ChuniBase):
# no idea, maybe to differentiate between CPUs and real players?
"matchingMemberRoleList": role_list,
# TCP/UDP connection?
"reflectorUri": f"{self.core_cfg.title.hostname}",
"reflectorUri": f"{self.core_cfg.server.hostname}",
}
def handle_remove_matching_member_api_request(self, data: Dict) -> Dict:
async def handle_remove_matching_member_api_request(self, data: Dict) -> Dict:
# get all matching rooms, because Chuni only returns the userId
# not the actual roomId
matching_rooms = self.data.item.get_all_matchings(self.version)
@ -612,7 +612,7 @@ class ChuniNew(ChuniBase):
return {"returnCode": "1"}
def handle_get_matching_state_api_request(self, data: Dict) -> Dict:
async def handle_get_matching_state_api_request(self, data: Dict) -> Dict:
polling_interval = 1
# get the current active room
matching_room = self.data.item.get_matching(self.version, data["roomId"])

View File

@ -11,8 +11,8 @@ class ChuniNewPlus(ChuniNew):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_NEW_PLUS
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = await super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker 1.35 A028
user_data["lastDataVersion"] = "2.05.00"

View File

@ -13,7 +13,7 @@ class ChuniParadise(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_PARADISE
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.50.00"
return ret

View File

@ -11,7 +11,7 @@ class ChuniPlus(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.05.00"
return ret

View File

@ -11,7 +11,7 @@ class ChuniStar(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_STAR
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.20.00"
return ret

View File

@ -11,7 +11,7 @@ class ChuniStarPlus(ChuniBase):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_STAR_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.25.00"
return ret

View File

@ -11,8 +11,8 @@ class ChuniSun(ChuniNewPlus):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_SUN
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = await super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker 1.35 A032
user_data["lastDataVersion"] = "2.10.00"

View File

@ -11,8 +11,8 @@ class ChuniSunPlus(ChuniSun):
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_SUN_PLUS
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = await super().handle_cm_get_user_preview_api_request(data)
# I don't know if lastDataVersion is going to matter, I don't think CardMaker 1.35 works this far up
user_data["lastDataVersion"] = "2.15.00"

View File

@ -29,11 +29,11 @@ class CardMakerBase:
def _parse_int_ver(version: str) -> str:
return version.replace(".", "")[:3]
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
async def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port(self.core_cfg) != 80:
uri = f"http://{self.core_cfg.title.hostname}:{Utils.get_title_port(self.core_cfg)}"
uri = f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}"
else:
uri = f"http://{self.core_cfg.title.hostname}"
uri = f"http://{self.core_cfg.server.hostname}"
# grab the dict with all games version numbers from user config
games_ver = self.game_cfg.version.version(self.version)
@ -62,7 +62,7 @@ class CardMakerBase:
],
}
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
# if reboot start/end time is not defined use the default behavior of being a few hours ago
if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
reboot_start = datetime.strftime(
@ -110,11 +110,11 @@ class CardMakerBase:
"isAou": False,
}
def handle_get_client_bookkeeping_api_request(self, data: Dict) -> Dict:
async def handle_get_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return {"placeId": data["placeId"], "length": 0, "clientBookkeepingList": []}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientBookkeepingApi"}

View File

@ -12,7 +12,7 @@ class CardMaker135(CardMakerBase):
super().__init__(core_cfg, game_cfg)
self.version = CardMakerConstants.VER_CARD_MAKER_135
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.35.00"
return ret

View File

@ -5,10 +5,11 @@ import string
import logging
import coloredlogs
import zlib
from starlette.routing import Route
from starlette.responses import Response
from starlette.requests import Request
from os import path
from typing import Tuple, List, Dict
from twisted.web.http import Request
from typing import List
from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig
@ -19,7 +20,6 @@ from .const import CardMakerConstants
from .base import CardMakerBase
from .cm135 import CardMaker135
class CardMakerServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
super().__init__(core_cfg, cfg_dir)
@ -72,16 +72,15 @@ class CardMakerServlet(BaseServlet):
return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[("render_POST", "/SDED/{version}/{endpoint}", {})]
)
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
version = int(matchers['version'])
endpoint = matchers['endpoint']
req_raw = request.content.getvalue()
def get_routes(self) -> List[Route]:
return [
Route("/SDED/{version:int}/{endpoint:str}", self.render_POST)
]
async def render_POST(self, request: Request) -> bytes:
version: int = request.path_params.get('version')
endpoint: str = request.path_params.get('endpoint')
req_raw = await request.body()
internal_ver = 0
client_ip = Utils.get_ip_addr(request)
@ -103,7 +102,7 @@ class CardMakerServlet(BaseServlet):
self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
req_data = json.loads(unzip)
@ -114,7 +113,7 @@ class CardMakerServlet(BaseServlet):
if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}")
return zlib.compress(b'{"returnCode": 1}')
return Response(zlib.compress(b'{"returnCode": 1}'))
try:
handler = getattr(self.versions[internal_ver], func_to_find)
@ -123,11 +122,11 @@ class CardMakerServlet(BaseServlet):
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
raise
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
if resp is None:
resp = {"returnCode": 1}
self.logger.debug(f"Response {resp}")
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
return Response(zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")))

View File

@ -28,13 +28,13 @@ class CxbBase:
return []
def handle_action_rpreq_request(self, data: Dict) -> Dict:
async def handle_action_rpreq_request(self, data: Dict) -> Dict:
return {}
def handle_action_hitreq_request(self, data: Dict) -> Dict:
async def handle_action_hitreq_request(self, data: Dict) -> Dict:
return {"data": []}
def handle_auth_usercheck_request(self, data: Dict) -> Dict:
async def handle_auth_usercheck_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_index(
0, data["usercheck"]["authid"], self.version
)
@ -45,11 +45,11 @@ class CxbBase:
self.logger.info(f"No profile for aime id {data['usercheck']['authid']}")
return {"exist": "false", "logout": "true"}
def handle_auth_entry_request(self, data: Dict) -> Dict:
async def handle_auth_entry_request(self, data: Dict) -> Dict:
self.logger.info(f"New profile for {data['entry']['authid']}")
return {"token": data["entry"]["authid"], "uid": data["entry"]["authid"]}
def handle_auth_login_request(self, data: Dict) -> Dict:
async def handle_auth_login_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_index(
0, data["login"]["authid"], self.version
)
@ -198,7 +198,7 @@ class CxbBase:
).decode("utf-8")
)
def handle_action_loadrange_request(self, data: Dict) -> Dict:
async def handle_action_loadrange_request(self, data: Dict) -> Dict:
range_start = data["loadrange"]["range"][0]
range_end = data["loadrange"]["range"][1]
uid = data["loadrange"]["uid"]
@ -282,7 +282,7 @@ class CxbBase:
return {"index": index, "data": data1, "version": versionindex}
def handle_action_saveindex_request(self, data: Dict) -> Dict:
async def handle_action_saveindex_request(self, data: Dict) -> Dict:
save_data = data["saveindex"]
try:
@ -443,7 +443,7 @@ class CxbBase:
i += 1
return {}
def handle_action_sprankreq_request(self, data: Dict) -> Dict:
async def handle_action_sprankreq_request(self, data: Dict) -> Dict:
uid = data["sprankreq"]["uid"]
self.logger.info(f"Get best rankings for {uid}")
p = self.data.score.get_best_rankings(uid)
@ -475,16 +475,16 @@ class CxbBase:
"rankx": [1, 1, 1],
}
def handle_action_getadv_request(self, data: Dict) -> Dict:
async def handle_action_getadv_request(self, data: Dict) -> Dict:
return {"data": [{"r": "1", "i": "100300", "c": "20"}]}
def handle_action_getmsg_request(self, data: Dict) -> Dict:
async def handle_action_getmsg_request(self, data: Dict) -> Dict:
return {"msgs": []}
def handle_auth_logout_request(self, data: Dict) -> Dict:
async def handle_auth_logout_request(self, data: Dict) -> Dict:
return {"auth": True}
def handle_action_rankreg_request(self, data: Dict) -> Dict:
async def handle_action_rankreg_request(self, data: Dict) -> Dict:
uid = data["rankreg"]["uid"]
self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}")
@ -527,7 +527,7 @@ class CxbBase:
)
return {}
def handle_action_addenergy_request(self, data: Dict) -> Dict:
async def handle_action_addenergy_request(self, data: Dict) -> Dict:
uid = data["addenergy"]["uid"]
self.logger.info(f"Add energy to user {uid}")
profile = self.data.profile.get_profile_index(0, uid, self.version)
@ -570,10 +570,10 @@ class CxbBase:
)
return array[0]
def handle_action_eventreq_request(self, data: Dict) -> Dict:
async def handle_action_eventreq_request(self, data: Dict) -> Dict:
self.logger.info(data)
return {"eventreq": ""}
def handle_action_stampreq_request(self, data: Dict) -> Dict:
async def handle_action_stampreq_request(self, data: Dict) -> Dict:
self.logger.info(data)
return {"stampreq": ""}

View File

@ -1,4 +1,6 @@
from twisted.web.http import Request
from starlette.requests import Request
from starlette.routing import Route
from starlette.responses import Response, PlainTextResponse, JSONResponse
import traceback
import sys
import yaml
@ -62,6 +64,14 @@ class CxbServlet(BaseServlet):
CxbRevSunriseS2(core_cfg, self.game_cfg),
]
def get_routes(self) -> List[Route]:
return [
Route("/data", self.handle_data, methods=['POST']),
Route("/action", self.handle_action, methods=['POST']),
Route("/v2/action", self.handle_action, methods=['POST']),
Route("/auth", self.handle_auth, methods=['POST']),
]
@classmethod
def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool:
game_cfg = CxbConfig()
@ -88,26 +98,12 @@ class CxbServlet(BaseServlet):
t_port = f":{title_port_int}" if title_port_int and not self.core_cfg.server.is_using_proxy else ""
return (
f"{proto}://{self.core_cfg.title.hostname}{t_port}",
f"{proto}://{self.core_cfg.server.hostname}{t_port}",
"",
)
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[
("handle_data", "/data", {}),
("handle_action", "/action", {}),
("handle_action", "/v2/action", {}),
("handle_auth", "/auth", {}),
]
)
def preprocess(self, req: Request) -> Dict:
try:
req_bytes = req.content.getvalue()
except:
req_bytes = req.content.read() # Can we just use this one?
async def preprocess(self, req: Request) -> Dict:
req_bytes = await req.body()
try:
req_json: Dict = json.loads(req_bytes)
@ -126,8 +122,8 @@ class CxbServlet(BaseServlet):
return req_json
def handle_data(self, request: Request, game_code: str, matchers: Dict) -> bytes:
req_json = self.preprocess(request)
async def handle_data(self, request: Request) -> bytes:
req_json = await self.preprocess(request)
func_to_find = "handle_data_"
version_string = "Base"
internal_ver = 0
@ -135,7 +131,7 @@ class CxbServlet(BaseServlet):
if req_json == {}:
self.logger.warning(f"Empty json request to /data")
return b""
return Response()
subcmd = list(req_json.keys())[0]
if subcmd == "dldate":
@ -145,14 +141,14 @@ class CxbServlet(BaseServlet):
or "filetype" not in req_json["dldate"]
):
self.logger.warning(f"Malformed dldate request: {req_json}")
return b""
return Response()
filetype = req_json["dldate"]["filetype"]
filetype_split = filetype.split("/")
if len(filetype_split) < 2 or not filetype_split[0].isnumeric():
self.logger.warning(f"Malformed dldate request: {req_json}")
return b""
return Response()
version = int(filetype_split[0])
filename = filetype_split[len(filetype_split) - 1]
@ -184,7 +180,7 @@ class CxbServlet(BaseServlet):
if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warn(f"{version_string} has no handler for filetype {filetype} / {func_to_find}")
return({"data":""})
return JSONResponse({"data":""})
self.logger.info(f"{version_string} request for filetype {filetype}")
self.logger.debug(req_json)
@ -192,7 +188,7 @@ class CxbServlet(BaseServlet):
handler = getattr(self.versions[internal_ver], func_to_find)
try:
resp = handler(req_json)
resp = await handler(req_json)
except Exception as e:
self.logger.error(f"Error handling request for file {filetype} - {e}")
@ -201,19 +197,19 @@ class CxbServlet(BaseServlet):
traceback.print_exception(tp, val, tb, limit=1)
with open("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), "a") as f:
traceback.print_exception(tp, val, tb, limit=1, file=f)
return ""
return Response()
self.logger.debug(f"{version_string} Response {resp}")
return json.dumps(resp, ensure_ascii=False).encode("utf-8")
return JSONResponse(resp, ensure_ascii=False)
def handle_action(self, request: Request, game_code: str, matchers: Dict) -> bytes:
req_json = self.preprocess(request)
async def handle_action(self, request: Request) -> bytes:
req_json = await self.preprocess(request)
subcmd = list(req_json.keys())[0]
func_to_find = f"handle_action_{subcmd}_request"
if not hasattr(self.versions[0], func_to_find):
self.logger.warn(f"No handler for action {subcmd} request")
return ""
return Response()
self.logger.info(f"Action {subcmd} Request")
self.logger.debug(req_json)
@ -221,7 +217,7 @@ class CxbServlet(BaseServlet):
handler = getattr(self.versions[0], func_to_find)
try:
resp = handler(req_json)
resp = await handler(req_json)
except Exception as e:
self.logger.error(f"Error handling action {subcmd} request - {e}")
@ -230,19 +226,19 @@ class CxbServlet(BaseServlet):
traceback.print_exception(tp, val, tb, limit=1)
with open("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), "a") as f:
traceback.print_exception(tp, val, tb, limit=1, file=f)
return ""
return Response()
self.logger.debug(f"Response {resp}")
return json.dumps(resp, ensure_ascii=False).encode("utf-8")
return JSONResponse(resp)
def handle_auth(self, request: Request, game_code: str, matchers: Dict) -> bytes:
req_json = self.preprocess(request)
async def handle_auth(self, request: Request) -> bytes:
req_json = await self.preprocess(request)
subcmd = list(req_json.keys())[0]
func_to_find = f"handle_auth_{subcmd}_request"
if not hasattr(self.versions[0], func_to_find):
self.logger.warn(f"No handler for auth {subcmd} request")
return ""
return Response()
self.logger.info(f"Action {subcmd} Request")
self.logger.debug(req_json)
@ -250,7 +246,7 @@ class CxbServlet(BaseServlet):
handler = getattr(self.versions[0], func_to_find)
try:
resp = handler(req_json)
resp = await handler(req_json)
except Exception as e:
self.logger.error(f"Error handling auth {subcmd} request - {e}")
@ -259,7 +255,7 @@ class CxbServlet(BaseServlet):
traceback.print_exception(tp, val, tb, limit=1)
with open("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), "a") as f:
traceback.print_exception(tp, val, tb, limit=1, file=f)
return ""
return Response()
self.logger.debug(f"Response {resp}")
return json.dumps(resp, ensure_ascii=False).encode("utf-8")
return JSONResponse(resp)

View File

@ -17,10 +17,10 @@ class CxbRev(CxbBase):
super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV
def handle_data_path_list_request(self, data: Dict) -> Dict:
async def handle_data_path_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_putlog_request(self, data: Dict) -> Dict:
async def handle_data_putlog_request(self, data: Dict) -> Dict:
if data["putlog"]["type"] == "ResultLog":
score_data = json.loads(data["putlog"]["data"])
userid = score_data["usid"]
@ -45,7 +45,7 @@ class CxbRev(CxbBase):
return {"data": True}
@cached(lifetime=86400)
def handle_data_music_list_request(self, data: Dict) -> Dict:
async def handle_data_music_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss/MusicArchiveList.csv") as music:
lines = music.readlines()
@ -56,7 +56,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_icon_request(self, data: Dict) -> Dict:
async def handle_data_item_list_icon_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListIcon\r\n"
with open(
r"titles/cxb/data/rss/Item/ItemArchiveList_Icon.csv", encoding="utf-8"
@ -67,7 +67,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_skin_notes_request(self, data: Dict) -> Dict:
async def handle_data_item_list_skin_notes_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListSkinNotes\r\n"
with open(
r"titles/cxb/data/rss/Item/ItemArchiveList_SkinNotes.csv", encoding="utf-8"
@ -78,7 +78,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_skin_effect_request(self, data: Dict) -> Dict:
async def handle_data_item_list_skin_effect_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListSkinEffect\r\n"
with open(
r"titles/cxb/data/rss/Item/ItemArchiveList_SkinEffect.csv", encoding="utf-8"
@ -89,7 +89,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_skin_bg_request(self, data: Dict) -> Dict:
async def handle_data_item_list_skin_bg_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListSkinBg\r\n"
with open(
r"titles/cxb/data/rss/Item/ItemArchiveList_SkinBg.csv", encoding="utf-8"
@ -100,7 +100,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_title_request(self, data: Dict) -> Dict:
async def handle_data_item_list_title_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListTitle\r\n"
with open(
r"titles/cxb/data/rss/Item/ItemList_Title.csv", encoding="shift-jis"
@ -111,7 +111,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_shop_list_music_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_music_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ShopListMusic\r\n"
with open(
r"titles/cxb/data/rss/Shop/ShopList_Music.csv", encoding="shift-jis"
@ -122,7 +122,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_shop_list_icon_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_icon_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ShopListIcon\r\n"
with open(
r"titles/cxb/data/rss/Shop/ShopList_Icon.csv", encoding="shift-jis"
@ -133,7 +133,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_shop_list_title_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_title_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ShopListTitle\r\n"
with open(
r"titles/cxb/data/rss/Shop/ShopList_Title.csv", encoding="shift-jis"
@ -143,17 +143,17 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_shop_list_skin_hud_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_skin_hud_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_shop_list_skin_arrow_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_skin_arrow_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_shop_list_skin_hit_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_skin_hit_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_shop_list_sale_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_sale_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ShopListSale\r\n"
with open(
r"titles/cxb/data/rss/Shop/ShopList_Sale.csv", encoding="shift-jis"
@ -163,11 +163,11 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
async def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_exxxxx_request(self, data: Dict) -> Dict:
async def handle_data_exxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = ""
with open(
@ -178,14 +178,14 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
async def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
async def handle_data_free_coupon_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_news_list_request(self, data: Dict) -> Dict:
async def handle_data_news_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines()
@ -193,11 +193,11 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict:
async def handle_data_tips_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict:
async def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss/License_Offline.csv", encoding="UTF-8") as lic:
lines = lic.readlines()
@ -206,7 +206,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_course_list_request(self, data: Dict) -> Dict:
async def handle_data_course_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(
r"titles/cxb/data/rss/Course/CourseList.csv", encoding="UTF-8"
@ -217,7 +217,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_csxxxx_request(self, data: Dict) -> Dict:
async def handle_data_csxxxx_request(self, data: Dict) -> Dict:
# Removed the CSVs since the format isnt quite right
extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = ""
@ -230,7 +230,7 @@ class CxbRev(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_mission_list_request(self, data: Dict) -> Dict:
async def handle_data_mission_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(
r"titles/cxb/data/rss/MissionList.csv", encoding="shift-jis"
@ -240,14 +240,14 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
async def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
async def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_event_list_request(self, data: Dict) -> Dict:
async def handle_data_event_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(
r"titles/cxb/data/rss/Event/EventArchiveList.csv", encoding="shift-jis"
@ -257,39 +257,39 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_event_music_list_request(self, data: Dict) -> Dict:
async def handle_data_event_music_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_mission_list_request(self, data: Dict) -> Dict:
async def handle_data_event_mission_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_achievement_single_high_score_list_request(
async def handle_data_event_achievement_single_high_score_list_request(
self, data: Dict
) -> Dict:
return {"data": ""}
def handle_data_event_achievement_single_accumulation_request(
async def handle_data_event_achievement_single_accumulation_request(
self, data: Dict
) -> Dict:
return {"data": ""}
def handle_data_event_ranking_high_score_list_request(self, data: Dict) -> Dict:
async def handle_data_event_ranking_high_score_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_ranking_accumulation_list_request(self, data: Dict) -> Dict:
async def handle_data_event_ranking_accumulation_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_ranking_stamp_list_request(self, data: Dict) -> Dict:
async def handle_data_event_ranking_stamp_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_ranking_store_list_request(self, data: Dict) -> Dict:
async def handle_data_event_ranking_store_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_ranking_area_list_request(self, data: Dict) -> Dict:
async def handle_data_event_ranking_area_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
async def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(
r"titles/cxb/data/rss/Event/EventStampList.csv", encoding="shift-jis"
@ -299,8 +299,8 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
async def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
def handle_data_server_state_request(self, data: Dict) -> Dict:
async def handle_data_server_state_request(self, data: Dict) -> Dict:
return {"data": True}

View File

@ -17,11 +17,11 @@ class CxbRevSunriseS1(CxbBase):
super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1
def handle_data_path_list_request(self, data: Dict) -> Dict:
async def handle_data_path_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_music_list_request(self, data: Dict) -> Dict:
async def handle_data_music_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss1/MusicArchiveList.csv") as music:
lines = music.readlines()
@ -32,7 +32,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_detail_request(self, data: Dict) -> Dict:
async def handle_data_item_list_detail_request(self, data: Dict) -> Dict:
# ItemListIcon load
ret_str = "#ItemListIcon\r\n"
with open(
@ -54,7 +54,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_shop_list_detail_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_detail_request(self, data: Dict) -> Dict:
# ShopListIcon load
ret_str = "#ShopListIcon\r\n"
with open(
@ -119,26 +119,26 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
async def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_exxxxx_request(self, data: Dict) -> Dict:
async def handle_data_exxxxx_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict:
async def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
async def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_oexxxx_request(self, data: Dict) -> Dict:
async def handle_data_oexxxx_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
async def handle_data_free_coupon_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_news_list_request(self, data: Dict) -> Dict:
async def handle_data_news_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss1/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines()
@ -146,14 +146,14 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict:
async def handle_data_tips_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_release_info_list_request(self, data: Dict) -> Dict:
async def handle_data_release_info_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_random_music_list_request(self, data: Dict) -> Dict:
async def handle_data_random_music_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss1/MusicArchiveList.csv") as music:
lines = music.readlines()
@ -167,7 +167,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict:
async def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss1/License.csv", encoding="UTF-8") as licenses:
lines = licenses.readlines()
@ -176,7 +176,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_course_list_request(self, data: Dict) -> Dict:
async def handle_data_course_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(
r"titles/cxb/data/rss1/Course/CourseList.csv", encoding="UTF-8"
@ -187,7 +187,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_csxxxx_request(self, data: Dict) -> Dict:
async def handle_data_csxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = ""
with open(
@ -198,16 +198,16 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_mission_list_request(self, data: Dict) -> Dict:
async def handle_data_mission_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
async def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
async def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_partner_list_request(self, data: Dict) -> Dict:
async def handle_data_partner_list_request(self, data: Dict) -> Dict:
ret_str = ""
# Lord forgive me for the sins I am about to commit
for i in range(0, 10):
@ -226,7 +226,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_partnerxxxx_request(self, data: Dict) -> Dict:
async def handle_data_partnerxxxx_request(self, data: Dict) -> Dict:
partner_num = int(data["dldate"]["filetype"][-4:])
ret_str = f"{partner_num},,{partner_num},1,10000,\r\n"
with open(r"titles/cxb/data/rss1/Partner0000.csv") as partner:
@ -235,13 +235,13 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_server_state_request(self, data: Dict) -> Dict:
async def handle_data_server_state_request(self, data: Dict) -> Dict:
return {"data": True}
def handle_data_settings_request(self, data: Dict) -> Dict:
async def handle_data_settings_request(self, data: Dict) -> Dict:
return {"data": "2,\r\n"}
def handle_data_story_list_request(self, data: Dict) -> Dict:
async def handle_data_story_list_request(self, data: Dict) -> Dict:
# story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu
ret_str = "\r\n"
ret_str += (
@ -253,23 +253,23 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n"
return {"data": ret_str}
def handle_data_stxxxx_request(self, data: Dict) -> Dict:
async def handle_data_stxxxx_request(self, data: Dict) -> Dict:
story_num = int(data["dldate"]["filetype"][-4:])
ret_str = ""
for i in range(1, 11):
ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return {"data": ret_str}
def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
async def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
return {"data": "Cs1032,1,1,1,1,1,1,1,1,1,1,\r\n"}
def handle_data_premium_list_request(self, data: Dict) -> Dict:
async def handle_data_premium_list_request(self, data: Dict) -> Dict:
return {"data": "1,,,,10,,,,,99,,,,,,,,,100,,\r\n"}
def handle_data_event_list_request(self, data: Dict) -> Dict:
async def handle_data_event_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_detail_list_request(self, data: Dict) -> Dict:
async def handle_data_event_detail_list_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id:
return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
@ -278,7 +278,7 @@ class CxbRevSunriseS1(CxbBase):
else:
return {"data": ""}
def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
async def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id:
return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}

View File

@ -17,11 +17,11 @@ class CxbRevSunriseS2(CxbBase):
super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2_OMNI
def handle_data_path_list_request(self, data: Dict) -> Dict:
async def handle_data_path_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_music_list_request(self, data: Dict) -> Dict:
async def handle_data_music_list_request(self, data: Dict) -> Dict:
version = data["dldate"]["filetype"].split("/")[0]
ret_str = ""
@ -41,7 +41,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_detail_request(self, data: Dict) -> Dict:
async def handle_data_item_list_detail_request(self, data: Dict) -> Dict:
# ItemListIcon load
ret_str = "#ItemListIcon\r\n"
with open(
@ -63,7 +63,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_shop_list_detail_request(self, data: Dict) -> Dict:
async def handle_data_shop_list_detail_request(self, data: Dict) -> Dict:
# ShopListIcon load
ret_str = "#ShopListIcon\r\n"
with open(
@ -128,7 +128,7 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
async def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
ret_str=""
with open(r"titles/cxb/data/rss2/ExtraStageList.csv") as extra:
lines = extra.readlines()
@ -136,19 +136,19 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
def handle_data_exxxxx_request(self, data: Dict) -> Dict:
async def handle_data_exxxxx_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict:
async def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
async def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_oexxxx_request(self, data: Dict) -> Dict:
async def handle_data_oexxxx_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
async def handle_data_free_coupon_request(self, data: Dict) -> Dict:
ret_str=""
with open(r"titles/cxb/data/rss2/FreeCoupon.csv") as coupon:
lines = coupon.readlines()
@ -157,7 +157,7 @@ class CxbRevSunriseS2(CxbBase):
return({"data":ret_str})
@cached(lifetime=86400)
def handle_data_news_list_request(self, data: Dict) -> Dict:
async def handle_data_news_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss2/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines()
@ -165,14 +165,14 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict:
async def handle_data_tips_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_release_info_list_request(self, data: Dict) -> Dict:
async def handle_data_release_info_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_random_music_list_request(self, data: Dict) -> Dict:
async def handle_data_random_music_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss2/MusicArchiveList.csv") as music:
lines = music.readlines()
@ -186,7 +186,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict:
async def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/data/rss2/License.csv", encoding="UTF-8") as licenses:
lines = licenses.readlines()
@ -195,7 +195,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_course_list_request(self, data: Dict) -> Dict:
async def handle_data_course_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(
r"titles/cxb/data/rss2/Course/CourseList.csv", encoding="UTF-8"
@ -206,7 +206,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_csxxxx_request(self, data: Dict) -> Dict:
async def handle_data_csxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = ""
with open(
@ -217,16 +217,16 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_mission_list_request(self, data: Dict) -> Dict:
async def handle_data_mission_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
async def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
async def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_partner_list_request(self, data: Dict) -> Dict:
async def handle_data_partner_list_request(self, data: Dict) -> Dict:
ret_str = ""
# Lord forgive me for the sins I am about to commit
for i in range(0, 10):
@ -245,7 +245,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_partnerxxxx_request(self, data: Dict) -> Dict:
async def handle_data_partnerxxxx_request(self, data: Dict) -> Dict:
partner_num = int(data["dldate"]["filetype"][-4:])
ret_str = f"{partner_num},,{partner_num},1,10000,\r\n"
with open(r"titles/cxb/data/rss2/Partner0000.csv") as partner:
@ -254,13 +254,13 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_server_state_request(self, data: Dict) -> Dict:
async def handle_data_server_state_request(self, data: Dict) -> Dict:
return {"data": True}
def handle_data_settings_request(self, data: Dict) -> Dict:
async def handle_data_settings_request(self, data: Dict) -> Dict:
return {"data": "2,\r\n"}
def handle_data_story_list_request(self, data: Dict) -> Dict:
async def handle_data_story_list_request(self, data: Dict) -> Dict:
# story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu
ret_str = "\r\n"
ret_str += (
@ -272,7 +272,7 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n"
return {"data": ret_str}
def handle_data_stxxxx_request(self, data: Dict) -> Dict:
async def handle_data_stxxxx_request(self, data: Dict) -> Dict:
story_num = int(data["dldate"]["filetype"][-4:])
ret_str = ""
# Each stories appears to have 10 pieces based on the wiki but as on how they are set.... no clue
@ -280,18 +280,18 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return {"data": ret_str}
def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
async def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
return {"data": "Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}
def handle_data_premium_list_request(self, data: Dict) -> Dict:
async def handle_data_premium_list_request(self, data: Dict) -> Dict:
return {"data": "1,,,,10,,,,,99,,,,,,,,,100,,\r\n"}
def handle_data_event_list_request(self, data: Dict) -> Dict:
async def handle_data_event_list_request(self, data: Dict) -> Dict:
return {
"data": "Cs4001,0,10000,1601510400,1604188799,1,nv2006,1,\r\nCs4005,0,10000,1609459200,1615766399,1,nv2006,1,\r\n"
}
def handle_data_event_detail_list_request(self, data: Dict) -> Dict:
async def handle_data_event_detail_list_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2]
if "Cs4001" in event_id:
return {
@ -308,7 +308,7 @@ class CxbRevSunriseS2(CxbBase):
else:
return {"data": ""}
def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
async def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id:
return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}

View File

@ -1,8 +1,7 @@
import datetime
from typing import Any, List, Dict
from typing import Dict
import logging
import json
import urllib
import urllib.parse
from threading import Thread
from core.config import CoreConfig
@ -24,13 +23,13 @@ class DivaBase:
dt = datetime.datetime.now()
self.time_lut = urllib.parse.quote(dt.strftime("%Y-%m-%d %H:%M:%S:16.0"))
def handle_test_request(self, data: Dict) -> Dict:
async def handle_test_request(self, data: Dict) -> Dict:
return ""
def handle_game_init_request(self, data: Dict) -> Dict:
async def handle_game_init_request(self, data: Dict) -> Dict:
return f""
def handle_attend_request(self, data: Dict) -> Dict:
async def handle_attend_request(self, data: Dict) -> Dict:
encoded = "&"
params = {
"atnd_prm1": "0,1,1,0,0,0,1,0,100,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
@ -44,7 +43,7 @@ class DivaBase:
return encoded
def handle_ping_request(self, data: Dict) -> Dict:
async def handle_ping_request(self, data: Dict) -> Dict:
encoded = "&"
params = {
"ping_b_msg": f"Welcome to {self.core_cfg.server.name} network!",
@ -89,7 +88,7 @@ class DivaBase:
return encoded
def handle_pv_list_request(self, data: Dict) -> Dict:
async def handle_pv_list_request(self, data: Dict) -> Dict:
pvlist = ""
with open(r"titles/diva/data/PvList0.dat", encoding="utf-8") as shop:
lines = shop.readlines()
@ -126,7 +125,7 @@ class DivaBase:
return response
def handle_shop_catalog_request(self, data: Dict) -> Dict:
async def handle_shop_catalog_request(self, data: Dict) -> Dict:
catalog = ""
shopList = self.data.static.get_enabled_shops(self.version)
@ -164,7 +163,7 @@ class DivaBase:
return response
def handle_buy_module_request(self, data: Dict) -> Dict:
async def handle_buy_module_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
module = self.data.static.get_enabled_shop(self.version, int(data["mdl_id"]))
@ -191,7 +190,7 @@ class DivaBase:
return response
def handle_cstmz_itm_ctlg_request(self, data: Dict) -> Dict:
async def handle_cstmz_itm_ctlg_request(self, data: Dict) -> Dict:
catalog = ""
itemList = self.data.static.get_enabled_items(self.version)
@ -229,7 +228,7 @@ class DivaBase:
return response
def handle_buy_cstmz_itm_request(self, data: Dict) -> Dict:
async def handle_buy_cstmz_itm_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
item = self.data.static.get_enabled_item(
self.version, int(data["cstmz_itm_id"])
@ -264,7 +263,7 @@ class DivaBase:
return response
def handle_festa_info_request(self, data: Dict) -> Dict:
async def handle_festa_info_request(self, data: Dict) -> Dict:
encoded = "&"
params = {
"fi_id": "1,2",
@ -287,7 +286,7 @@ class DivaBase:
return encoded
def handle_contest_info_request(self, data: Dict) -> Dict:
async def handle_contest_info_request(self, data: Dict) -> Dict:
response = ""
response += f"&ci_lut={self.time_lut}"
@ -295,7 +294,7 @@ class DivaBase:
return response
def handle_qst_inf_request(self, data: Dict) -> Dict:
async def handle_qst_inf_request(self, data: Dict) -> Dict:
quest = ""
questList = self.data.static.get_enabled_quests(self.version)
@ -345,43 +344,43 @@ class DivaBase:
return response
def handle_nv_ranking_request(self, data: Dict) -> Dict:
async def handle_nv_ranking_request(self, data: Dict) -> Dict:
return f""
def handle_ps_ranking_request(self, data: Dict) -> Dict:
async def handle_ps_ranking_request(self, data: Dict) -> Dict:
return f""
def handle_ng_word_request(self, data: Dict) -> Dict:
async def handle_ng_word_request(self, data: Dict) -> Dict:
return f""
def handle_rmt_wp_list_request(self, data: Dict) -> Dict:
async def handle_rmt_wp_list_request(self, data: Dict) -> Dict:
return f""
def handle_pv_def_chr_list_request(self, data: Dict) -> Dict:
async def handle_pv_def_chr_list_request(self, data: Dict) -> Dict:
return f""
def handle_pv_ng_mdl_list_request(self, data: Dict) -> Dict:
async def handle_pv_ng_mdl_list_request(self, data: Dict) -> Dict:
return f""
def handle_cstmz_itm_ng_mdl_lst_request(self, data: Dict) -> Dict:
async def handle_cstmz_itm_ng_mdl_lst_request(self, data: Dict) -> Dict:
return f""
def handle_banner_info_request(self, data: Dict) -> Dict:
async def handle_banner_info_request(self, data: Dict) -> Dict:
return f""
def handle_banner_data_request(self, data: Dict) -> Dict:
async def handle_banner_data_request(self, data: Dict) -> Dict:
return f""
def handle_cm_ply_info_request(self, data: Dict) -> Dict:
async def handle_cm_ply_info_request(self, data: Dict) -> Dict:
return f""
def handle_pstd_h_ctrl_request(self, data: Dict) -> Dict:
async def handle_pstd_h_ctrl_request(self, data: Dict) -> Dict:
return f""
def handle_pstd_item_ng_lst_request(self, data: Dict) -> Dict:
async def handle_pstd_item_ng_lst_request(self, data: Dict) -> Dict:
return f""
def handle_pre_start_request(self, data: Dict) -> str:
async def handle_pre_start_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["aime_id"], self.version)
profile_shop = self.data.item.get_shop(data["aime_id"], self.version)
@ -422,13 +421,13 @@ class DivaBase:
return response
def handle_registration_request(self, data: Dict) -> Dict:
async def handle_registration_request(self, data: Dict) -> Dict:
self.data.profile.create_profile(
self.version, data["aime_id"], data["player_name"]
)
return f"&cd_adm_result=1&pd_id={data['aime_id']}"
def handle_start_request(self, data: Dict) -> Dict:
async def handle_start_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
profile_shop = self.data.item.get_shop(data["pd_id"], self.version)
if profile is None:
@ -583,10 +582,10 @@ class DivaBase:
return response
def handle_pd_unlock_request(self, data: Dict) -> Dict:
async def handle_pd_unlock_request(self, data: Dict) -> Dict:
return f""
def handle_spend_credit_request(self, data: Dict) -> Dict:
async def handle_spend_credit_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
if profile is None:
return
@ -705,7 +704,7 @@ class DivaBase:
pd_by_pv_id.append(urllib.parse.quote(f"{song}***"))
pd_by_pv_id.append(",")
def handle_get_pv_pd_request(self, data: Dict) -> Dict:
async def handle_get_pv_pd_request(self, data: Dict) -> Dict:
song_id = data["pd_pv_id_lst"].split(",")
pv = ""
@ -732,10 +731,10 @@ class DivaBase:
return response
def handle_stage_start_request(self, data: Dict) -> Dict:
async def handle_stage_start_request(self, data: Dict) -> Dict:
return f""
def handle_stage_result_request(self, data: Dict) -> Dict:
async def handle_stage_result_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
pd_song_list = data["stg_ply_pv_id"].split(",")
@ -914,7 +913,7 @@ class DivaBase:
return response
def handle_end_request(self, data: Dict) -> Dict:
async def handle_end_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
self.data.profile.update_profile(
@ -922,7 +921,7 @@ class DivaBase:
)
return f""
def handle_shop_exit_request(self, data: Dict) -> Dict:
async def handle_shop_exit_request(self, data: Dict) -> Dict:
self.data.item.put_shop(
data["pd_id"],
self.version,
@ -952,7 +951,7 @@ class DivaBase:
response = "&shp_rslt=1"
return response
def handle_card_procedure_request(self, data: Dict) -> str:
async def handle_card_procedure_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["aime_id"], self.version)
if profile is None:
return "&cd_adm_result=0"
@ -972,7 +971,7 @@ class DivaBase:
return response
def handle_change_name_request(self, data: Dict) -> str:
async def handle_change_name_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
# make sure user has enough Vocaloid Points
@ -992,7 +991,7 @@ class DivaBase:
return response
def handle_change_passwd_request(self, data: Dict) -> str:
async def handle_change_passwd_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
# TODO: return correct error number instead of 0

View File

@ -1,4 +1,6 @@
from twisted.web.http import Request
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.routing import Route
import yaml
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
@ -51,17 +53,16 @@ class DivaServlet(BaseServlet):
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
)
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[("render_POST", "/DivaServlet/", {})]
)
def get_routes(self) -> List[Route]:
return [
Route("/DivaServlet/", self.render_POST, methods=['POST'])
]
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port(self.core_cfg) != 80:
return (f"http://{self.core_cfg.title.hostname}:{Utils.get_title_port(self.core_cfg)}/DivaServlet/", self.core_cfg.title.hostname)
return (f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/DivaServlet/", self.core_cfg.server.hostname)
return (f"http://{self.core_cfg.title.hostname}/DivaServlet/", self.core_cfg.title.hostname)
return (f"http://{self.core_cfg.server.hostname}/DivaServlet/", self.core_cfg.server.hostname)
@classmethod
def is_game_enabled(
@ -78,9 +79,9 @@ class DivaServlet(BaseServlet):
return True
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
req_raw = request.content.getvalue()
url_header = request.getAllHeaders()
async def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
req_raw = await request.body()
url_header = request.headers
# Ping Dispatch
if "THIS_STRING_SEPARATES" in str(url_header):
@ -103,9 +104,7 @@ class DivaServlet(BaseServlet):
self.logger.debug(
f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}"
)
return f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}".encode(
"utf-8"
)
return PlainTextResponse(f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}")
# Main Dispatch
json_string = json.dumps(
@ -122,7 +121,7 @@ class DivaServlet(BaseServlet):
) # Decompressing the gzip
except zlib.error as e:
self.logger.error(f"Failed to defalte! {e} -> {gz_string}")
return "stat=0"
return PlainTextResponse("stat=0")
req_kvp = urllib.parse.unquote(url_data)
req_data = {}
@ -141,27 +140,18 @@ class DivaServlet(BaseServlet):
# Load the requests
try:
handler = getattr(self.base, func_to_find)
resp = handler(req_data)
resp = await handler(req_data)
except AttributeError as e:
self.logger.warning(f"Unhandled {req_data['cmd']} request {e}")
return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok".encode(
"utf-8"
)
return PlainTextResponse(f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok")
except Exception as e:
self.logger.error(f"Error handling method {func_to_find} {e}")
return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok".encode(
"utf-8"
)
return PlainTextResponse(f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok")
request.responseHeaders.addRawHeader(b"content-type", b"text/plain")
self.logger.debug(
f"Response cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}"
)
return (
f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}".encode(
"utf-8"
)
)
return PlainTextResponse(f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}")

View File

@ -3,7 +3,7 @@ import yaml
import jinja2
from os import path
from twisted.web.util import redirectTo
from twisted.web.http import Request
from starlette.requests import Request
from twisted.web.server import Session
from core.frontend import FE_Base, IUserSession

View File

@ -1,18 +1,18 @@
import json
import traceback
import inflection
from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import JSONResponse
import yaml
import logging
import coloredlogs
from os import path
from typing import Dict, List, Tuple
from logging.handlers import TimedRotatingFileHandler
from twisted.web import server
from twisted.web.http import Request
from twisted.internet import reactor, endpoints
from core.config import CoreConfig
from core.title import BaseServlet
from core.utils import Utils
from titles.idac.base import IDACBase
from titles.idac.season2 import IDACSeason2
@ -22,7 +22,7 @@ from titles.idac.echo import IDACEchoUDP
from titles.idac.matching import IDACMatching
class IDACServlet:
class IDACServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
self.game_cfg = IDACConfig()
@ -72,12 +72,11 @@ class IDACServlet:
return False
return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[("render_POST", "/SDGT/{version}/initiald/{category}/{endpoint}", {})]
)
def get_routes(self) -> List[Route]:
return [
Route("/{version:int}/initiald/{category:str}/{endpoint:str}", self.render_POST, methods=["POST"])
]
def get_allnet_info(
self, game_code: str, game_ver: int, keychip: str
@ -88,15 +87,15 @@ class IDACServlet:
return (
f"",
# requires http or else it defaults to https
f"http://{self.core_cfg.title.hostname}{t_port}/{game_code}/{game_ver}/",
f"http://{self.core_cfg.server.hostname}{t_port}/{game_code}/{game_ver}/",
)
def render_POST(self, request: Request, game_code: int, matchers: Dict) -> bytes:
req_raw = request.content.getvalue()
async def render_POST(self, request: Request) -> bytes:
req_raw = await request.body()
internal_ver = 0
version = int(matchers['version'])
category = matchers['category']
endpoint = matchers['endpoint']
version: int = request.path_params.get('version')
category: str = request.path_params.get('category')
endpoint: str = request.path_params.get('endpoint')
client_ip = Utils.get_ip_addr(request)
if version >= 100 and version < 140: # IDAC Season 1
@ -104,7 +103,7 @@ class IDACServlet:
elif version >= 140 and version < 171: # IDAC Season 2
internal_ver = IDACConstants.VER_IDAC_SEASON_2
header_application = self.decode_header(request.getAllHeaders())
header_application = self.decode_header(request.headers.get("application", ""))
req_data = json.loads(req_raw)
@ -119,7 +118,7 @@ class IDACServlet:
if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}")
return '{"status_code": "0"}'.encode("utf-8")
return JSONResponse('{"status_code": "0"}')
resp = None
try:
@ -129,17 +128,16 @@ class IDACServlet:
except Exception as e:
traceback.print_exc()
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return '{"status_code": "0"}'.encode("utf-8")
return JSONResponse('{"status_code": "0"}')
if resp is None:
resp = {"status_code": "0"}
self.logger.debug(f"Response {resp}")
return json.dumps(resp, ensure_ascii=False).encode("utf-8")
return JSONResponse(json.dumps(resp, ensure_ascii=False))
def decode_header(self, data: Dict) -> Dict:
app: str = data[b"application"].decode()
def decode_header(self, app: str) -> Dict:
ret = {}
for x in app.split(", "):
@ -149,6 +147,8 @@ class IDACServlet:
return ret
def setup(self):
return
"""
if self.game_cfg.server.enable:
endpoints.serverFromString(
reactor,
@ -165,3 +165,4 @@ class IDACServlet:
)
self.logger.info(f"Matching listening on {self.game_cfg.server.matching} with echos on {self.game_cfg.server.echo1} and {self.game_cfg.server.echo2}")
"""

View File

@ -109,9 +109,9 @@ class IDACSeason2(IDACBase):
ver_str = version.replace(".", "")[:3]
if self.core_cfg.server.is_develop:
domain_api_game = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDGT/{ver_str}/"
domain_api_game = f"http://{self.core_cfg.server.hostname}:{self.core_cfg.server.port}/SDGT/{ver_str}/"
else:
domain_api_game = f"http://{self.core_cfg.title.hostname}/SDGT/{ver_str}/"
domain_api_game = f"http://{self.core_cfg.server.hostname}/SDGT/{ver_str}/"
return {
"status_code": "0",
@ -136,10 +136,10 @@ class IDACSeason2(IDACBase):
"server_maintenance_end_hour": 0,
"server_maintenance_end_minutes": 0,
"domain_api_game": domain_api_game,
"domain_matching": f"http://{self.core_cfg.title.hostname}:{self.game_config.server.matching}",
"domain_echo1": f"{self.core_cfg.title.hostname}:{self.game_config.server.echo1}",
"domain_echo2": f"{self.core_cfg.title.hostname}:{self.game_config.server.echo2}",
"domain_ping": f"{self.core_cfg.title.hostname}",
"domain_matching": f"http://{self.core_cfg.server.hostname}:{self.game_config.server.matching}",
"domain_echo1": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo1}",
"domain_echo2": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo2}",
"domain_ping": f"{self.core_cfg.server.hostname}",
"battle_gift_event_master": [],
"round_event": [
{

View File

@ -24,10 +24,10 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
t_port = Utils.get_title_port(self.core_config)
news_str = f"http://{self.core_config.title.hostname}:{t_port}/idz/news/news80**.txt"
err_str = f"http://{self.core_config.title.hostname}:{t_port}/idz/error"
news_str = f"http://{self.core_config.server.hostname}:{t_port}/idz/news/news80**.txt"
err_str = f"http://{self.core_config.server.hostname}:{t_port}/idz/error"
len_hostname = len(self.core_config.title.hostname)
len_hostname = len(self.core_config.server.hostname)
len_news = len(news_str)
len_error = len(err_str)
@ -36,7 +36,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s",
ret,
0x4 + offset,
self.core_config.title.hostname.encode(),
self.core_config.server.hostname.encode(),
)
struct.pack_into("<I", ret, 0x84 + offset, self.game_cfg.ports.userdb)
struct.pack_into("<I", ret, 0x86 + offset, self.game_cfg.ports.userdb + 1)
@ -45,7 +45,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s",
ret,
0x88 + offset,
self.core_config.title.hostname.encode(),
self.core_config.server.hostname.encode(),
)
struct.pack_into("<I", ret, 0x108 + offset, self.game_cfg.ports.match - 1)
struct.pack_into("<I", ret, 0x10A + offset, self.game_cfg.ports.match - 3)
@ -59,7 +59,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s",
ret,
0x114 + offset,
self.core_config.title.hostname.encode(),
self.core_config.server.hostname.encode(),
)
struct.pack_into("<I", ret, 0x194 + offset, self.game_cfg.ports.echo + 2)
@ -67,7 +67,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s",
ret,
0x0199 + offset,
self.core_config.title.hostname.encode(),
self.core_config.server.hostname.encode(),
)
struct.pack_into("<I", ret, 0x0219 + offset, self.game_cfg.ports.echo + 3)
@ -75,19 +75,19 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s",
ret,
0x021C + offset,
self.core_config.title.hostname.encode(),
self.core_config.server.hostname.encode(),
)
struct.pack_into(
f"{len_hostname}s",
ret,
0x029C + offset,
self.core_config.title.hostname.encode(),
self.core_config.server.hostname.encode(),
)
struct.pack_into(
f"{len_hostname}s",
ret,
0x031C + offset,
self.core_config.title.hostname.encode(),
self.core_config.server.hostname.encode(),
)
struct.pack_into("<I", ret, 0x39C + offset, self.game_cfg.ports.echo)

View File

@ -1,12 +1,12 @@
from twisted.web.http import Request
from starlette.requests import Request
from starlette.responses import Response, PlainTextResponse
from starlette.routing import Route
import yaml
import logging
import coloredlogs
from logging.handlers import TimedRotatingFileHandler
from os import path
from typing import Tuple, List, Dict
from twisted.internet import reactor, endpoints
from twisted.web import server, resource
import importlib
from core.config import CoreConfig
@ -83,16 +83,15 @@ class IDZServlet(BaseServlet):
return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return[
[("render_GET", "/idz/news/{endpoint:.*?}", {}),
("render_GET", "/idz/error", {})],
[]
def get_routes(self) -> List[Route]:
return [
Route("/idz/news/{endpoint:str}", self.render_GET),
Route("/idz/error", self.render_GET)
]
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
hostname = (
self.core_cfg.title.hostname
self.core_cfg.server.hostname
if not self.game_cfg.server.hostname
else self.game_cfg.server.hostname
)
@ -135,7 +134,8 @@ class IDZServlet(BaseServlet):
except AttributeError as e:
continue
"""
endpoints.serverFromString(
reactor,
f"tcp:{self.game_cfg.ports.userdb}:interface={self.core_cfg.server.listen_address}",
@ -155,18 +155,15 @@ class IDZServlet(BaseServlet):
reactor.listenUDP(
self.game_cfg.ports.userdb + 1, IDZEcho(self.core_cfg, self.game_cfg)
)
"""
self.logger.info(f"UserDB Listening on port {self.game_cfg.ports.userdb}")
def render_GET(self, request: Request, game_code: str, matchers: Dict) -> bytes:
url_path = matchers['endpoint']
async def render_GET(self, request: Request) -> bytes:
url_path = request.path_params.get('endpoint', '')
if not url_path:
return Response()
self.logger.info(f"IDZ GET request: {url_path}")
request.responseHeaders.setRawHeaders(
"Content-Type", [b"text/plain; charset=utf-8"]
)
request.responseHeaders.setRawHeaders(
"Last-Modified", [b"Sun, 23 Apr 2023 05:33:20 GMT"]
)
news = (
self.game_cfg.server.news
@ -176,4 +173,4 @@ class IDZServlet(BaseServlet):
news += "\r\n"
news = "1979/01/01 00:00:00 2099/12/31 23:59:59 " + news
return news.encode()
return PlainTextResponse(news, media_type="text/plain; charset=utf-8", headers={"Last-Modified": "Sun, 23 Apr 2023 05:33:20 GMT"})

View File

@ -5,7 +5,7 @@ import struct
from typing import Dict, Optional, List, Type
from twisted.web import server, resource
from twisted.internet import reactor, endpoints
from twisted.web.http import Request
from starlette.requests import Request
from routes import Mapper
import random
from os import walk

View File

@ -26,12 +26,12 @@ class Mai2Base:
self.date_time_format = "%Y-%m-%d %H:%M:%S"
if not self.core_config.server.is_using_proxy and Utils.get_title_port(self.core_config) != 80:
self.old_server = f"http://{self.core_config.title.hostname}:{Utils.get_title_port(cfg)}/197/MaimaiServlet/"
self.old_server = f"http://{self.core_config.server.hostname}:{Utils.get_title_port(cfg)}/197/MaimaiServlet/"
else:
self.old_server = f"http://{self.core_config.title.hostname}/197/MaimaiServlet/"
self.old_server = f"http://{self.core_config.server.hostname}/197/MaimaiServlet/"
def handle_get_game_setting_api_request(self, data: Dict):
async def handle_get_game_setting_api_request(self, data: Dict):
# if reboot start/end time is not defined use the default behavior of being a few hours ago
if self.core_config.title.reboot_start_time == "" or self.core_config.title.reboot_end_time == "":
reboot_start = datetime.strftime(
@ -74,14 +74,14 @@ class Mai2Base:
},
}
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
async def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
return {"length": 0, "gameRankingList": []}
def handle_get_game_tournament_info_api_request(self, data: Dict) -> Dict:
async def handle_get_game_tournament_info_api_request(self, data: Dict) -> Dict:
# TODO: Tournament support
return {"length": 0, "gameTournamentInfoList": []}
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
async def handle_get_game_event_api_request(self, data: Dict) -> Dict:
events = self.data.static.get_enabled_events(self.version)
events_lst = []
if events is None or not events:
@ -108,10 +108,10 @@ class Mai2Base:
"gameEventList": events_lst,
}
def handle_get_game_ng_music_id_api_request(self, data: Dict) -> Dict:
async def handle_get_game_ng_music_id_api_request(self, data: Dict) -> Dict:
return {"length": 0, "musicIdList": []}
def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
async def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
game_charge_list = self.data.static.get_enabled_tickets(self.version, 1)
if game_charge_list is None:
return {"length": 0, "gameChargeList": []}
@ -130,19 +130,19 @@ class Mai2Base:
return {"length": len(charge_list), "gameChargeList": charge_list}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
def handle_upsert_client_upload_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_upload_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientUploadApi"}
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientBookkeepingApi"}
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientTestmodeApi"}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
async def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_detail(data["userId"], self.version, False)
w = self.data.profile.get_web_option(data["userId"], self.version)
if p is None or w is None:
@ -169,7 +169,7 @@ class Mai2Base:
"totalLv": profile["totalLv"],
}
def handle_user_login_api_request(self, data: Dict) -> Dict:
async def handle_user_login_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
consec = self.data.profile.get_consec_login(data["userId"], self.version)
@ -210,7 +210,7 @@ class Mai2Base:
"consecutiveLoginCount": consec_ct, # Number of consecutive days we've logged in.
}
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
async def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
playlog = data["userPlaylog"]
@ -218,7 +218,7 @@ class Mai2Base:
return {"returnCode": 1, "apiName": "UploadUserPlaylogApi"}
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
async def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
charge = data["userCharge"]
@ -234,7 +234,7 @@ class Mai2Base:
return {"returnCode": 1, "apiName": "UpsertUserChargelogApi"}
def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
async def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
upsert = data["upsertUserAll"]
@ -375,10 +375,10 @@ class Mai2Base:
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
def handle_user_logout_api_request(self, data: Dict) -> Dict:
async def handle_user_logout_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
async def handle_get_user_data_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_detail(data["userId"], self.version, False)
if profile is None:
return
@ -390,7 +390,7 @@ class Mai2Base:
return {"userId": data["userId"], "userData": profile_dict}
def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
async def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
extend = self.data.profile.get_profile_extend(data["userId"], self.version)
if extend is None:
return
@ -402,7 +402,7 @@ class Mai2Base:
return {"userId": data["userId"], "userExtend": extend_dict}
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
async def handle_get_user_option_api_request(self, data: Dict) -> Dict:
options = self.data.profile.get_profile_option(data["userId"], self.version, False)
if options is None:
return
@ -414,7 +414,7 @@ class Mai2Base:
return {"userId": data["userId"], "userOption": options_dict}
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
async def handle_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None:
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
@ -448,7 +448,7 @@ class Mai2Base:
"userCardList": card_list[start_idx:end_idx],
}
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
async def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
user_charges = self.data.item.get_charges(data["userId"])
if user_charges is None:
return {"userId": data["userId"], "length": 0, "userChargeList": []}
@ -467,16 +467,16 @@ class Mai2Base:
"userChargeList": user_charge_list,
}
def handle_get_user_present_api_request(self, data: Dict) -> Dict:
async def handle_get_user_present_api_request(self, data: Dict) -> Dict:
return { "userId": data.get("userId", 0), "length": 0, "userPresentList": []}
def handle_get_transfer_friend_api_request(self, data: Dict) -> Dict:
async def handle_get_transfer_friend_api_request(self, data: Dict) -> Dict:
return {}
def handle_get_user_present_event_api_request(self, data: Dict) -> Dict:
async def handle_get_user_present_event_api_request(self, data: Dict) -> Dict:
return { "userId": data.get("userId", 0), "length": 0, "userPresentEventList": []}
def handle_get_user_boss_api_request(self, data: Dict) -> Dict:
async def handle_get_user_boss_api_request(self, data: Dict) -> Dict:
b = self.data.profile.get_boss_list(data["userId"])
if b is None:
return { "userId": data.get("userId", 0), "userBossData": {}}
@ -486,7 +486,7 @@ class Mai2Base:
return { "userId": data.get("userId", 0), "userBossData": boss_lst}
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = int(data["nextIndex"] / 10000000000)
next_idx = int(data["nextIndex"] % 10000000000)
user_item_list = self.data.item.get_items(data["userId"], kind)
@ -514,7 +514,7 @@ class Mai2Base:
"userItemList": items,
}
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
async def handle_get_user_character_api_request(self, data: Dict) -> Dict:
characters = self.data.item.get_characters(data["userId"])
chara_list = []
@ -528,7 +528,7 @@ class Mai2Base:
return {"userId": data["userId"], "userCharacterList": chara_list}
def handle_get_user_favorite_api_request(self, data: Dict) -> Dict:
async def handle_get_user_favorite_api_request(self, data: Dict) -> Dict:
favorites = self.data.item.get_favorites(data["userId"], data["itemKind"])
if favorites is None:
return
@ -545,7 +545,7 @@ class Mai2Base:
return {"userId": data["userId"], "userFavoriteData": userFavs}
def handle_get_user_ghost_api_request(self, data: Dict) -> Dict:
async def handle_get_user_ghost_api_request(self, data: Dict) -> Dict:
ghost = self.data.profile.get_profile_ghost(data["userId"], self.version)
if ghost is None:
return
@ -557,7 +557,7 @@ class Mai2Base:
return {"userId": data["userId"], "userGhost": ghost_dict}
def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
async def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
rating = self.data.profile.get_recent_rating(data["userId"])
if rating is None:
return
@ -567,7 +567,7 @@ class Mai2Base:
return {"userId": data["userId"], "length": len(lst), "userRecentRatingList": lst}
def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
rating = self.data.profile.get_profile_rating(data["userId"], self.version)
if rating is None:
return
@ -579,7 +579,7 @@ class Mai2Base:
return {"userId": data["userId"], "userRating": rating_dict}
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
async def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
"""
kind 1 is playlist, kind 2 is music list
"""
@ -607,7 +607,7 @@ class Mai2Base:
return {"userActivity": {"playList": plst, "musicList": mlst}}
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
async def handle_get_user_course_api_request(self, data: Dict) -> Dict:
user_courses = self.data.score.get_courses(data["userId"])
if user_courses is None:
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": []}
@ -621,11 +621,11 @@ class Mai2Base:
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": course_list}
def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
async def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
# No support for custom pfps
return {"length": 0, "userPortraitList": []}
def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict:
async def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict:
friend_season_ranking = self.data.item.get_friend_season_ranking(data["userId"])
if friend_season_ranking is None:
return {
@ -661,7 +661,7 @@ class Mai2Base:
"userFriendSeasonRankingList": friend_season_ranking_list,
}
def handle_get_user_map_api_request(self, data: Dict) -> Dict:
async def handle_get_user_map_api_request(self, data: Dict) -> Dict:
maps = self.data.item.get_maps(data["userId"])
if maps is None:
return {
@ -694,7 +694,7 @@ class Mai2Base:
"userMapList": map_list,
}
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
async def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
login_bonuses = self.data.item.get_login_bonuses(data["userId"])
if login_bonuses is None:
return {
@ -727,10 +727,10 @@ class Mai2Base:
"userLoginBonusList": login_bonus_list,
}
def handle_get_user_region_api_request(self, data: Dict) -> Dict:
async def handle_get_user_region_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "length": 0, "userRegionList": []}
def handle_get_user_web_option_api_request(self, data: Dict) -> Dict:
async def handle_get_user_web_option_api_request(self, data: Dict) -> Dict:
w = self.data.profile.get_web_option(data["userId"], self.version)
if w is None:
return {"userId": data["userId"], "userWebOption": {}}
@ -742,10 +742,10 @@ class Mai2Base:
return {"userId": data["userId"], "userWebOption": web_opt}
def handle_get_user_survival_api_request(self, data: Dict) -> Dict:
async def handle_get_user_survival_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "length": 0, "userSurvivalList": []}
def handle_get_user_grade_api_request(self, data: Dict) -> Dict:
async def handle_get_user_grade_api_request(self, data: Dict) -> Dict:
g = self.data.profile.get_grade_status(data["userId"])
if g is None:
return {"userId": data["userId"], "userGradeStatus": {}, "length": 0, "userGradeList": []}
@ -755,7 +755,7 @@ class Mai2Base:
return {"userId": data["userId"], "userGradeStatus": grade_stat, "length": 0, "userGradeList": []}
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
user_id = data.get("userId", 0)
next_index = data.get("nextIndex", 0)
max_ct = data.get("maxCount", 50)
@ -794,10 +794,10 @@ class Mai2Base:
"userMusicList": [{"userMusicDetailList": music_detail_list}],
}
def handle_upload_user_portrait_api_request(self, data: Dict) -> Dict:
async def handle_upload_user_portrait_api_request(self, data: Dict) -> Dict:
self.logger.debug(data)
def handle_upload_user_photo_api_request(self, data: Dict) -> Dict:
async def handle_upload_user_photo_api_request(self, data: Dict) -> Dict:
if not self.game_config.uploads.photos or not self.game_config.uploads.photos_dir:
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}

View File

@ -15,7 +15,7 @@ class Mai2DX(Mai2Base):
super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX
def handle_get_game_setting_api_request(self, data: Dict):
async def handle_get_game_setting_api_request(self, data: Dict):
return {
"gameSetting": {
"isMaintenance": False,
@ -33,7 +33,7 @@ class Mai2DX(Mai2Base):
"isAouAccession": False,
}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
async def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_detail(data["userId"], self.version)
o = self.data.profile.get_profile_option(data["userId"], self.version)
if p is None or o is None:
@ -69,7 +69,7 @@ class Mai2DX(Mai2Base):
else 0, # New with uni+
}
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
async def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
playlog = data["userPlaylog"]
@ -77,7 +77,7 @@ class Mai2DX(Mai2Base):
return {"returnCode": 1, "apiName": "UploadUserPlaylogApi"}
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
async def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
charge = data["userCharge"]
@ -93,7 +93,7 @@ class Mai2DX(Mai2Base):
return {"returnCode": 1, "apiName": "UpsertUserChargelogApi"}
def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
async def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
upsert = data["upsertUserAll"]
@ -215,7 +215,7 @@ class Mai2DX(Mai2Base):
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
async def handle_get_user_data_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
if profile is None:
return
@ -227,7 +227,7 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "userData": profile_dict}
def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
async def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
extend = self.data.profile.get_profile_extend(data["userId"], self.version)
if extend is None:
return
@ -239,7 +239,7 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "userExtend": extend_dict}
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
async def handle_get_user_option_api_request(self, data: Dict) -> Dict:
options = self.data.profile.get_profile_option(data["userId"], self.version)
if options is None:
return
@ -251,7 +251,7 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "userOption": options_dict}
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
async def handle_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None:
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
@ -285,7 +285,7 @@ class Mai2DX(Mai2Base):
"userCardList": card_list[start_idx:end_idx],
}
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
async def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
user_charges = self.data.item.get_charges(data["userId"])
if user_charges is None:
return {"userId": data["userId"], "length": 0, "userChargeList": []}
@ -310,7 +310,7 @@ class Mai2DX(Mai2Base):
"userChargeList": user_charge_list,
}
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = int(data["nextIndex"] / 10000000000)
next_idx = int(data["nextIndex"] % 10000000000)
user_item_list = self.data.item.get_items(data["userId"], kind)
@ -338,7 +338,7 @@ class Mai2DX(Mai2Base):
"userItemList": items,
}
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
async def handle_get_user_character_api_request(self, data: Dict) -> Dict:
characters = self.data.item.get_characters(data["userId"])
chara_list = []
@ -350,7 +350,7 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "userCharacterList": chara_list}
def handle_get_user_favorite_api_request(self, data: Dict) -> Dict:
async def handle_get_user_favorite_api_request(self, data: Dict) -> Dict:
favorites = self.data.item.get_favorites(data["userId"], data["itemKind"])
if favorites is None:
return
@ -367,7 +367,7 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "userFavoriteData": userFavs}
def handle_get_user_ghost_api_request(self, data: Dict) -> Dict:
async def handle_get_user_ghost_api_request(self, data: Dict) -> Dict:
ghost = self.data.profile.get_profile_ghost(data["userId"], self.version)
if ghost is None:
return
@ -379,7 +379,7 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "userGhost": ghost_dict}
def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
rating = self.data.profile.get_profile_rating(data["userId"], self.version)
if rating is None:
return
@ -391,7 +391,7 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "userRating": rating_dict}
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
async def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
"""
kind 1 is playlist, kind 2 is music list
"""
@ -419,7 +419,7 @@ class Mai2DX(Mai2Base):
return {"userActivity": {"playList": plst, "musicList": mlst}}
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
async def handle_get_user_course_api_request(self, data: Dict) -> Dict:
user_courses = self.data.score.get_courses(data["userId"])
if user_courses is None:
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": []}
@ -433,11 +433,11 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": course_list}
def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
async def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
# No support for custom pfps
return {"length": 0, "userPortraitList": []}
def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict:
async def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict:
friend_season_ranking = self.data.item.get_friend_season_ranking(data["userId"])
if friend_season_ranking is None:
return {
@ -473,7 +473,7 @@ class Mai2DX(Mai2Base):
"userFriendSeasonRankingList": friend_season_ranking_list,
}
def handle_get_user_map_api_request(self, data: Dict) -> Dict:
async def handle_get_user_map_api_request(self, data: Dict) -> Dict:
maps = self.data.item.get_maps(data["userId"])
if maps is None:
return {
@ -506,7 +506,7 @@ class Mai2DX(Mai2Base):
"userMapList": map_list,
}
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
async def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
login_bonuses = self.data.item.get_login_bonuses(data["userId"])
if login_bonuses is None:
return {
@ -539,7 +539,7 @@ class Mai2DX(Mai2Base):
"userLoginBonusList": login_bonus_list,
}
def handle_get_user_region_api_request(self, data: Dict) -> Dict:
async def handle_get_user_region_api_request(self, data: Dict) -> Dict:
"""
class UserRegionList:
regionId: int
@ -548,7 +548,7 @@ class Mai2DX(Mai2Base):
"""
return {"userId": data["userId"], "length": 0, "userRegionList": []}
def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
rival_id = data["rivalId"]
@ -559,7 +559,7 @@ class Mai2DX(Mai2Base):
"""
return {"userId": user_id, "userRivalData": {}}
def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
rival_id = data["rivalId"]
next_idx = data["nextIndex"]
@ -577,7 +577,7 @@ class Mai2DX(Mai2Base):
"""
return {"userId": user_id, "nextIndex": 0, "userRivalMusicList": []}
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
user_id = data.get("userId", 0)
next_index = data.get("nextIndex", 0)
max_ct = data.get("maxCount", 50)
@ -616,8 +616,8 @@ class Mai2DX(Mai2Base):
"userMusicList": [{"userMusicDetailList": music_detail_list}],
}
def handle_user_login_api_request(self, data: Dict) -> Dict:
ret = super().handle_user_login_api_request(data)
async def handle_user_login_api_request(self, data: Dict) -> Dict:
ret = await super().handle_user_login_api_request(data)
if ret is None or not ret:
return ret
ret['loginId'] = ret.get('loginCount', 0)

View File

@ -11,26 +11,26 @@ class Mai2Festival(Mai2UniversePlus):
super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = await super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker
user_data["lastDataVersion"] = "1.30.00"
return user_data
def handle_user_login_api_request(self, data: Dict) -> Dict:
user_login = super().handle_user_login_api_request(data)
async def handle_user_login_api_request(self, data: Dict) -> Dict:
user_login = await super().handle_user_login_api_request(data)
# useless?
user_login["Bearer"] = "ARTEMiSTOKEN"
return user_login
def handle_get_user_recommend_rate_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_recommend_rate_music_api_request(self, data: Dict) -> Dict:
"""
userRecommendRateMusicIdList: list[int]
"""
return {"userId": data["userId"], "userRecommendRateMusicIdList": []}
def handle_get_user_recommend_select_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_recommend_select_music_api_request(self, data: Dict) -> Dict:
"""
userRecommendSelectionMusicIdList: list[int]
"""

View File

@ -11,14 +11,14 @@ class Mai2FestivalPlus(Mai2Festival):
super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = await super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker
user_data["lastDataVersion"] = "1.35.00"
return user_data
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
async def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
user_id = data.get("userId", 0)
kind = data.get("kind", 2)
next_index = data.get("nextIndex", 0)

View File

@ -1,9 +1,9 @@
from twisted.web.http import Request
from twisted.web.server import NOT_DONE_YET
from starlette.requests import Request
from starlette.responses import Response, JSONResponse
from starlette.routing import Route
import json
import inflection
import yaml
import string
import logging, coloredlogs
import zlib
from logging.handlers import TimedRotatingFileHandler
@ -101,33 +101,29 @@ class Mai2Servlet(BaseServlet):
return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[
("handle_movie", "/{version}/MaimaiServlet/api/movie/{endpoint}", {}),
("handle_old_srv", "/{version}/MaimaiServlet/old/{endpoint}", {}),
("handle_old_srv_userdata", "/{version}/MaimaiServlet/old/{endpoint}/{placeid}/{keychip}/{userid}", {}),
("handle_old_srv_userdata", "/{version}/MaimaiServlet/old/{endpoint}/{userid}", {}),
("handle_usbdl", "/{version}/MaimaiServlet/usbdl/{endpoint}", {}),
("handle_deliver", "/{version}/MaimaiServlet/deliver/{endpoint}", {}),
],
[
("handle_movie", "/{version}/MaimaiServlet/api/movie/{endpoint}", {}),
("handle_mai", "/{version}/MaimaiServlet/{endpoint}", {}),
("handle_mai2", "/{version}/Maimai2Servlet/{endpoint}", {}),
]
)
def get_routes(self) -> List[Route]:
return [
Route("/{version:int}/MaimaiServlet/api/movie/{endpoint:str}", self.handle_movie, methods=['GET', 'POST']),
Route("/{version:int}/MaimaiServlet/old/{endpoint:str}", self.handle_old_srv),
Route("/{version:int}/MaimaiServlet/old/{endpoint:str}/{placeid:str}/{keychip:str}/{userid:int}", self.handle_old_srv_userdata),
Route("/{version:int}/MaimaiServlet/old/{endpoint:str}/{userid:int}", self.handle_old_srv_userdata),
Route("/{version:int}/MaimaiServlet/old/{endpoint:str}/{userid:int}", self.handle_old_srv_userdata),
Route("/{version:int}/MaimaiServlet/usbdl/{endpoint:str}", self.handle_usbdl),
Route("/{version:int}/MaimaiServlet/deliver/{endpoint:str}", self.handle_deliver),
Route("/{version:int}/MaimaiServlet/{endpoint:str}", self.handle_mai, methods=['POST']),
Route("/{version:int}/Maimai2Servlet/{endpoint:str}", self.handle_mai2, methods=['POST']),
]
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port(self.core_cfg) != 80:
return (
f"http://{self.core_cfg.title.hostname}:{Utils.get_title_port(self.core_cfg)}/{game_ver}/",
f"{self.core_cfg.title.hostname}",
f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/{game_ver}/",
f"{self.core_cfg.server.hostname}",
)
return (
f"http://{self.core_cfg.title.hostname}/{game_ver}/",
f"{self.core_cfg.title.hostname}",
f"http://{self.core_cfg.server.hostname}/{game_ver}/",
f"{self.core_cfg.server.hostname}",
)
def setup(self):
@ -155,13 +151,22 @@ class Mai2Servlet(BaseServlet):
f"Failed to make movie upload directory at {self.game_cfg.uploads.movies_dir}"
)
def handle_mai(self, request: Request, game_code: str, matchers: Dict) -> bytes:
endpoint = matchers['endpoint']
version = int(matchers['version'])
async def handle_movie(self, request: Request):
return JSONResponse()
async def handle_usbdl(self, request: Request):
return Response("OK")
async def handle_deliver(self, request: Request):
return Response(status_code=404)
async def handle_mai(self, request: Request) -> bytes:
endpoint: str = request.path_params.get('endpoint')
version: int = request.path_params.get('version')
if endpoint.lower() == "ping":
return zlib.compress(b'{"returnCode": "1"}')
return Response(zlib.compress(b'{"returnCode": "1"}'))
req_raw = request.content.getvalue()
req_raw = await request.body()
internal_ver = 0
client_ip = Utils.get_ip_addr(request)
@ -199,7 +204,7 @@ class Mai2Servlet(BaseServlet):
self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
req_data = json.loads(unzip)
@ -216,26 +221,26 @@ class Mai2Servlet(BaseServlet):
else:
try:
handler = getattr(handler_cls, func_to_find)
resp = handler(req_data)
resp = await handler(req_data)
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"returnCode": "0"}')
return Response(zlib.compress(b'{"returnCode": "0"}'))
if resp == None:
resp = {"returnCode": 1}
self.logger.debug(f"Response {resp}")
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
return Response(zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")))
def handle_mai2(self, request: Request, game_code: str, matchers: Dict) -> bytes:
endpoint = matchers['endpoint']
version = int(matchers['version'])
async def handle_mai2(self, request: Request) -> bytes:
endpoint: str = request.path_params.get('endpoint')
version: int = request.path_params.get('version')
if endpoint.lower() == "ping":
return zlib.compress(b'{"returnCode": "1"}')
return Response(zlib.compress(b'{"returnCode": "1"}'))
req_raw = request.content.getvalue()
req_raw = await request.body()
internal_ver = 0
client_ip = Utils.get_ip_addr(request)
if version < 105: # 1.0
@ -256,17 +261,17 @@ class Mai2Servlet(BaseServlet):
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS
if (
request.getHeader("Mai-Encoding") is not None
or request.getHeader("X-Mai-Encoding") is not None
request.headers.get("Mai-Encoding") is not None
or request.headers.get("X-Mai-Encoding") is not None
):
# The has is some flavor of MD5 of the endpoint with a constant bolted onto the end of it.
# See cake.dll's Obfuscator function for details. Hopefully most DLL edits will remove
# these two(?) headers to not cause issues, but given the general quality of SEGA data...
enc_ver = request.getHeader("Mai-Encoding")
enc_ver = request.headers.get("Mai-Encoding")
if enc_ver is None:
enc_ver = request.getHeader("X-Mai-Encoding")
enc_ver = request.headers.get("X-Mai-Encoding")
self.logger.debug(
f"Encryption v{enc_ver} - User-Agent: {request.getHeader('User-Agent')}"
f"Encryption v{enc_ver} - User-Agent: {request.headers.get('User-Agent')}"
)
try:
@ -276,7 +281,7 @@ class Mai2Servlet(BaseServlet):
self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
req_data = json.loads(unzip)
@ -293,80 +298,27 @@ class Mai2Servlet(BaseServlet):
else:
try:
handler = getattr(handler_cls, func_to_find)
resp = handler(req_data)
resp = await handler(req_data)
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
if resp == None:
resp = {"returnCode": 1}
self.logger.debug(f"Response {resp}")
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
return Response(zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")))
def handle_old_srv(self, request: Request, game_code: str, matchers: Dict) -> bytes:
endpoint = matchers['endpoint']
version = matchers['version']
async def handle_old_srv(self, request: Request) -> bytes:
endpoint = request.path_params.get('endpoint')
version = request.path_params.get('version')
self.logger.info(f"v{version} old server {endpoint}")
return zlib.compress(b"ok")
return Response(zlib.compress(b"ok"))
def handle_old_srv_userdata(self, request: Request, game_code: str, matchers: Dict) -> bytes:
endpoint = matchers['endpoint']
version = matchers['version']
async def handle_old_srv_userdata(self, request: Request) -> bytes:
endpoint = request.path_params.get('endpoint')
version = request.path_params.get('version')
self.logger.info(f"v{version} old server {endpoint}")
return zlib.compress(b"{}")
def render_GET(self, request: Request, version: int, url_path: str) -> bytes:
self.logger.debug(f"v{version} GET {url_path}")
url_split = url_path.split("/")
if (url_split[0] == "api" and url_split[1] == "movie") or url_split[
0
] == "movie":
if url_split[2] == "moviestart":
return json.dumps({"moviestart": {"status": "OK"}}).encode()
else:
request.setResponseCode(404)
return b""
elif url_split[0] == "usbdl":
if url_split[1] == "CONNECTIONTEST":
self.logger.info(f"v{version} usbdl server test")
return b""
elif self.game_cfg.deliver.udbdl_enable and path.exists(
f"{self.game_cfg.deliver.content_folder}/usb/{url_split[-1]}"
):
with open(
f"{self.game_cfg.deliver.content_folder}/usb/{url_split[-1]}", "rb"
) as f:
return f.read()
else:
request.setResponseCode(404)
return b""
elif url_split[0] == "deliver":
file = url_split[len(url_split) - 1]
self.logger.info(f"v{version} {file} deliver inquire")
self.logger.debug(
f"{self.game_cfg.deliver.content_folder}/net_deliver/{file}"
)
if self.game_cfg.deliver.enable and path.exists(
f"{self.game_cfg.deliver.content_folder}/net_deliver/{file}"
):
with open(
f"{self.game_cfg.deliver.content_folder}/net_deliver/{file}", "rb"
) as f:
return f.read()
else:
request.setResponseCode(404)
return b""
else:
return zlib.compress(b"{}")
return Response(zlib.compress(b"{}"))

View File

@ -15,7 +15,7 @@ class Mai2Universe(Mai2SplashPlus):
super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_detail(data["userId"], self.version)
if p is None:
return {}
@ -30,7 +30,7 @@ class Mai2Universe(Mai2SplashPlus):
"isExistSellingCard": True,
}
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
# user already exists, because the preview checks that already
p = self.data.profile.get_profile_detail(data["userId"], self.version)
@ -52,13 +52,13 @@ class Mai2Universe(Mai2SplashPlus):
return {"userId": data["userId"], "userData": user_data}
def handle_cm_login_api_request(self, data: Dict) -> Dict:
async def handle_cm_login_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
def handle_cm_logout_api_request(self, data: Dict) -> Dict:
async def handle_cm_logout_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
selling_cards = self.data.static.get_enabled_cards(self.version)
if selling_cards is None:
return {"length": 0, "sellingCardList": []}
@ -88,7 +88,7 @@ class Mai2Universe(Mai2SplashPlus):
return {"length": len(selling_card_list), "sellingCardList": selling_card_list}
def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None:
return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []}
@ -124,10 +124,10 @@ class Mai2Universe(Mai2SplashPlus):
"userCardList": card_list[start_idx:end_idx],
}
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
super().handle_get_user_item_api_request(data)
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
characters = self.data.item.get_characters(data["userId"])
chara_list = []
@ -153,10 +153,10 @@ class Mai2Universe(Mai2SplashPlus):
"userCharacterList": chara_list,
}
def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
return {"length": 0, "userPrintDetailList": []}
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
upsert = data["userPrintDetail"]
@ -209,12 +209,12 @@ class Mai2Universe(Mai2SplashPlus):
"endDate": datetime.strftime(end_date, Mai2Constants.DATE_TIME_FORMAT),
}
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
return {
"returnCode": 1,
"orderId": 0,
"serialId": data["userPrintlog"]["serialId"],
}
def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}

View File

@ -11,8 +11,8 @@ class Mai2UniversePlus(Mai2Universe):
super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = await super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker 1.35
user_data["lastDataVersion"] = "1.25.00"

View File

@ -103,7 +103,7 @@ class OngekiBase:
self.game = OngekiConstants.GAME_CODE
self.version = OngekiConstants.VER_ONGEKI
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
# if reboot start/end time is not defined use the default behavior of being a few hours ago
if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
reboot_start = datetime.strftime(
@ -148,7 +148,7 @@ class OngekiBase:
"isAou": "true",
}
def handle_get_game_idlist_api_request(self, data: Dict) -> Dict:
async def handle_get_game_idlist_api_request(self, data: Dict) -> Dict:
"""
Gets lists of song IDs, either disabled songs or recomended songs depending on type?
"""
@ -156,7 +156,7 @@ class OngekiBase:
# id - int
return {"type": data["type"], "length": 0, "gameIdlistList": []}
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
async def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
game_ranking_list = self.data.static.get_ranking_list(self.version)
ranking_list = []
@ -171,7 +171,7 @@ class OngekiBase:
"gameRankingList": ranking_list,
}
def handle_get_game_point_api_request(self, data: Dict) -> Dict:
async def handle_get_game_point_api_request(self, data: Dict) -> Dict:
get_game_point = self.data.static.get_static_game_point()
game_point = []
@ -194,16 +194,16 @@ class OngekiBase:
"gamePointList": game_point,
}
def handle_game_login_api_request(self, data: Dict) -> Dict:
async def handle_game_login_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "gameLogin"}
def handle_game_logout_api_request(self, data: Dict) -> Dict:
async def handle_game_logout_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "gameLogout"}
def handle_extend_lock_time_api_request(self, data: Dict) -> Dict:
async def handle_extend_lock_time_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "ExtendLockTimeApi"}
def handle_get_game_reward_api_request(self, data: Dict) -> Dict:
async def handle_get_game_reward_api_request(self, data: Dict) -> Dict:
get_game_rewards = self.data.static.get_reward_list(self.version)
reward_list = []
@ -221,7 +221,7 @@ class OngekiBase:
"gameRewardList": reward_list,
}
def handle_get_game_present_api_request(self, data: Dict) -> Dict:
async def handle_get_game_present_api_request(self, data: Dict) -> Dict:
get_present = self.data.static.get_present_list(self.version)
present_list = []
@ -238,13 +238,13 @@ class OngekiBase:
"gamePresentList": present_list,
}
def handle_get_game_message_api_request(self, data: Dict) -> Dict:
async def handle_get_game_message_api_request(self, data: Dict) -> Dict:
return {"length": 0, "gameMessageList": []}
def handle_get_game_sale_api_request(self, data: Dict) -> Dict:
async def handle_get_game_sale_api_request(self, data: Dict) -> Dict:
return {"length": 0, "gameSaleList": []}
def handle_get_game_tech_music_api_request(self, data: Dict) -> Dict:
async def handle_get_game_tech_music_api_request(self, data: Dict) -> Dict:
music_list = self.data.static.get_tech_music(self.version)
prep_music_list = []
@ -262,7 +262,7 @@ class OngekiBase:
"gameTechMusicList": prep_music_list,
}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
if self.core_cfg.server.is_develop:
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
@ -273,7 +273,7 @@ class OngekiBase:
self.data.static.put_client_setting_data(cab['id'], client_setting_data)
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
if self.core_cfg.server.is_develop:
return {"returnCode": 1, "apiName": "UpsertClientTestmodeApi"}
@ -282,16 +282,16 @@ class OngekiBase:
self.data.static.put_client_testmode_data(region_id, client_testmode_data)
return {"returnCode": 1, "apiName": "UpsertClientTestmodeApi"}
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "upsertClientBookkeeping"}
def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "upsertClientDevelop"}
def handle_upsert_client_error_api_request(self, data: Dict) -> Dict:
async def handle_upsert_client_error_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "upsertClientError"}
def handle_upsert_user_gplog_api_request(self, data: Dict) -> Dict:
async def handle_upsert_user_gplog_api_request(self, data: Dict) -> Dict:
user = data["userId"]
if user >= 200000000000000: # Account for guest play
user = None
@ -309,10 +309,10 @@ class OngekiBase:
return {"returnCode": 1, "apiName": "UpsertUserGplogApi"}
def handle_extend_lock_time_api_request(self, data: Dict) -> Dict:
async def handle_extend_lock_time_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "ExtendLockTimeApi"}
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
async def handle_get_game_event_api_request(self, data: Dict) -> Dict:
evts = self.data.static.get_enabled_events(self.version)
if evts is None:
@ -342,7 +342,7 @@ class OngekiBase:
"gameEventList": evt_list,
}
def handle_get_game_id_list_api_request(self, data: Dict) -> Dict:
async def handle_get_game_id_list_api_request(self, data: Dict) -> Dict:
game_idlist: List[str, Any] = [] # 1 to 230 & 8000 to 8050
if data["type"] == 1:
@ -362,10 +362,10 @@ class OngekiBase:
"gameIdlistList": game_idlist,
}
def handle_get_user_region_api_request(self, data: Dict) -> Dict:
async def handle_get_user_region_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "length": 0, "userRegionList": []}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
async def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None:
@ -417,7 +417,7 @@ class OngekiBase:
"isWarningConfirmed": True,
}
def handle_get_user_tech_count_api_request(self, data: Dict) -> Dict:
async def handle_get_user_tech_count_api_request(self, data: Dict) -> Dict:
"""
Gets the number of AB and ABPs a player has per-difficulty (7, 7+, 8, etc)
The game sends this in upsert so we don't have to calculate it all out thankfully
@ -436,7 +436,7 @@ class OngekiBase:
"userTechCountList": userTechCountList,
}
def handle_get_user_tech_event_api_request(self, data: Dict) -> Dict:
async def handle_get_user_tech_event_api_request(self, data: Dict) -> Dict:
user_tech_event_list = self.data.item.get_tech_event(self.version, data["userId"])
if user_tech_event_list is None:
return {}
@ -455,7 +455,7 @@ class OngekiBase:
"userTechEventList": tech_evt,
}
def handle_get_user_tech_event_ranking_api_request(self, data: Dict) -> Dict:
async def handle_get_user_tech_event_ranking_api_request(self, data: Dict) -> Dict:
user_tech_event_ranks = self.data.item.get_tech_event_ranking(self.version, data["userId"])
if user_tech_event_ranks is None:
return {
@ -481,7 +481,7 @@ class OngekiBase:
"userTechEventRankingList": evt_ranking,
}
def handle_get_user_kop_api_request(self, data: Dict) -> Dict:
async def handle_get_user_kop_api_request(self, data: Dict) -> Dict:
kop_list = self.data.profile.get_kop(data["userId"])
if kop_list is None:
return {}
@ -496,7 +496,7 @@ class OngekiBase:
"userKopList": kop_list,
}
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
song_list = self.util_generate_music_list(data["userId"])
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
@ -516,7 +516,7 @@ class OngekiBase:
"userMusicList": song_list[start_idx:end_idx],
}
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = data["nextIndex"] / 10000000000
p = self.data.item.get_items(data["userId"], kind)
@ -552,7 +552,7 @@ class OngekiBase:
"userItemList": items,
}
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
async def handle_get_user_option_api_request(self, data: Dict) -> Dict:
o = self.data.profile.get_profile_options(data["userId"])
if o is None:
return {}
@ -566,7 +566,7 @@ class OngekiBase:
return {"userId": data["userId"], "userOption": user_opts}
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
async def handle_get_user_data_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None:
return {}
@ -594,7 +594,7 @@ class OngekiBase:
return {"userId": data["userId"], "userData": user_data}
def handle_get_user_event_ranking_api_request(self, data: Dict) -> Dict:
async def handle_get_user_event_ranking_api_request(self, data: Dict) -> Dict:
user_event_ranking_list = self.data.item.get_ranking_event_ranks(self.version, data["userId"])
if user_event_ranking_list is None:
return {}
@ -617,7 +617,7 @@ class OngekiBase:
"userEventRankingList": prep_event_ranking,
}
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
async def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
user_login_bonus_list = self.data.item.get_login_bonuses(data["userId"])
if user_login_bonus_list is None:
return {}
@ -635,7 +635,7 @@ class OngekiBase:
"userLoginBonusList": login_bonuses,
}
def handle_get_user_bp_base_request(self, data: Dict) -> Dict:
async def handle_get_user_bp_base_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile(
self.game, self.version, user_id=data["userId"]
)
@ -648,7 +648,7 @@ class OngekiBase:
"userBpBaseList": profile["userBpBaseList"],
}
def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
async def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
recent_rating = self.data.profile.get_profile_recent_rating(data["userId"])
if recent_rating is None:
return {
@ -665,7 +665,7 @@ class OngekiBase:
"userRecentRatingList": userRecentRatingList,
}
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
async def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
activity = self.data.profile.get_profile_activity(data["userId"], data["kind"])
if activity is None:
return {}
@ -692,7 +692,7 @@ class OngekiBase:
"userActivityList": user_activity,
}
def handle_get_user_story_api_request(self, data: Dict) -> Dict:
async def handle_get_user_story_api_request(self, data: Dict) -> Dict:
user_stories = self.data.item.get_stories(data["userId"])
if user_stories is None:
return {}
@ -710,7 +710,7 @@ class OngekiBase:
"userStoryList": story_list,
}
def handle_get_user_chapter_api_request(self, data: Dict) -> Dict:
async def handle_get_user_chapter_api_request(self, data: Dict) -> Dict:
user_chapters = self.data.item.get_chapters(data["userId"])
if user_chapters is None:
return {}
@ -728,14 +728,14 @@ class OngekiBase:
"userChapterList": chapter_list,
}
def handle_get_user_training_room_by_key_api_request(self, data: Dict) -> Dict:
async def handle_get_user_training_room_by_key_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"length": 0,
"userTrainingRoomList": [],
}
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
async def handle_get_user_character_api_request(self, data: Dict) -> Dict:
user_characters = self.data.item.get_characters(data["userId"])
if user_characters is None:
return {}
@ -753,7 +753,7 @@ class OngekiBase:
"userCharacterList": character_list,
}
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
async def handle_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None:
return {}
@ -771,7 +771,7 @@ class OngekiBase:
"userCardList": card_list,
}
def handle_get_user_deck_by_key_api_request(self, data: Dict) -> Dict:
async def handle_get_user_deck_by_key_api_request(self, data: Dict) -> Dict:
# Auth key doesn't matter, it just wants all the decks
decks = self.data.item.get_decks(data["userId"])
if decks is None:
@ -790,7 +790,7 @@ class OngekiBase:
"userDeckList": deck_list,
}
def handle_get_user_trade_item_api_request(self, data: Dict) -> Dict:
async def handle_get_user_trade_item_api_request(self, data: Dict) -> Dict:
user_trade_items = self.data.item.get_trade_items(data["userId"])
if user_trade_items is None:
return {}
@ -808,7 +808,7 @@ class OngekiBase:
"userTradeItemList": trade_item_list,
}
def handle_get_user_scenario_api_request(self, data: Dict) -> Dict:
async def handle_get_user_scenario_api_request(self, data: Dict) -> Dict:
user_scenerio = self.data.item.get_scenerios(data["userId"])
if user_scenerio is None:
return {}
@ -826,7 +826,7 @@ class OngekiBase:
"userScenarioList": scenerio_list,
}
def handle_get_user_ratinglog_api_request(self, data: Dict) -> Dict:
async def handle_get_user_ratinglog_api_request(self, data: Dict) -> Dict:
rating_log = self.data.profile.get_profile_rating_log(data["userId"])
if rating_log is None:
return {}
@ -844,7 +844,7 @@ class OngekiBase:
"userRatinglogList": userRatinglogList,
}
def handle_get_user_mission_point_api_request(self, data: Dict) -> Dict:
async def handle_get_user_mission_point_api_request(self, data: Dict) -> Dict:
user_mission_point_list = self.data.item.get_mission_points(self.version, data["userId"])
if user_mission_point_list is None:
return {}
@ -864,7 +864,7 @@ class OngekiBase:
"userMissionPointList": mission_point_list,
}
def handle_get_user_event_point_api_request(self, data: Dict) -> Dict:
async def handle_get_user_event_point_api_request(self, data: Dict) -> Dict:
user_event_point_list = self.data.item.get_event_points(data["userId"])
if user_event_point_list is None:
return {}
@ -886,7 +886,7 @@ class OngekiBase:
"userEventPointList": event_point_list,
}
def handle_get_user_music_item_api_request(self, data: Dict) -> Dict:
async def handle_get_user_music_item_api_request(self, data: Dict) -> Dict:
user_music_item_list = self.data.item.get_music_items(data["userId"])
if user_music_item_list is None:
return {}
@ -904,7 +904,7 @@ class OngekiBase:
"userMusicItemList": music_item_list,
}
def handle_get_user_event_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_event_music_api_request(self, data: Dict) -> Dict:
user_evt_music_list = self.data.item.get_event_music(data["userId"])
if user_evt_music_list is None:
return {}
@ -922,7 +922,7 @@ class OngekiBase:
"userEventMusicList": evt_music_list,
}
def handle_get_user_boss_api_request(self, data: Dict) -> Dict:
async def handle_get_user_boss_api_request(self, data: Dict) -> Dict:
p = self.data.item.get_bosses(data["userId"])
if p is None:
return {}
@ -940,7 +940,7 @@ class OngekiBase:
"userBossList": boss_list,
}
def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
async def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
upsert = data["upsertUserAll"]
user_id = data["userId"]
@ -1070,7 +1070,7 @@ class OngekiBase:
return {"returnCode": 1, "apiName": "upsertUserAll"}
def handle_get_user_rival_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rival_api_request(self, data: Dict) -> Dict:
"""
Added in Bright
"""
@ -1094,7 +1094,7 @@ class OngekiBase:
"userRivalList": rival_list,
}
def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
"""
Added in Bright
"""
@ -1112,7 +1112,7 @@ class OngekiBase:
"userRivalDataList": rivals,
}
def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
"""
Added in Bright
"""

View File

@ -15,13 +15,13 @@ class OngekiBright(OngekiBase):
super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_BRIGHT
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.30.00"
ret["gameSetting"]["onlineDataVersion"] = "1.30.00"
return ret
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
# check for a bright profile
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None:
@ -55,13 +55,13 @@ class OngekiBright(OngekiBase):
return {"userId": data["userId"], "userData": user_data}
def handle_printer_login_api_request(self, data: Dict):
async def handle_printer_login_api_request(self, data: Dict):
return {"returnCode": 1}
def handle_printer_logout_api_request(self, data: Dict):
async def handle_printer_logout_api_request(self, data: Dict):
return {"returnCode": 1}
def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None:
return {}
@ -90,7 +90,7 @@ class OngekiBright(OngekiBase):
"userCardList": card_list[start_idx:end_idx],
}
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
user_characters = self.data.item.get_characters(data["userId"])
if user_characters is None:
return {
@ -124,7 +124,7 @@ class OngekiBright(OngekiBase):
"userCharacterList": character_list[start_idx:end_idx],
}
def handle_get_user_gacha_api_request(self, data: Dict) -> Dict:
async def handle_get_user_gacha_api_request(self, data: Dict) -> Dict:
user_gachas = self.data.item.get_user_gachas(data["userId"])
if user_gachas is None:
return {"userId": data["userId"], "length": 0, "userGachaList": []}
@ -143,10 +143,10 @@ class OngekiBright(OngekiBase):
"userGachaList": user_gacha_list,
}
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
return self.handle_get_user_item_api_request(data)
def handle_cm_get_user_gacha_supply_api_request(self, data: Dict) -> Dict:
async def handle_cm_get_user_gacha_supply_api_request(self, data: Dict) -> Dict:
# not used for now? not sure what it even does
user_gacha_supplies = self.data.item.get_user_gacha_supplies(data["userId"])
if user_gacha_supplies is None:
@ -160,7 +160,7 @@ class OngekiBright(OngekiBase):
"supplyCardList": supply_list,
}
def handle_get_game_gacha_api_request(self, data: Dict) -> Dict:
async def handle_get_game_gacha_api_request(self, data: Dict) -> Dict:
"""
returns all current active banners (gachas)
"Select Gacha" requires maxSelectPoint set and isCeiling set to 1
@ -207,7 +207,7 @@ class OngekiBright(OngekiBase):
"registIdList": [],
}
def handle_roll_gacha_api_request(self, data: Dict) -> Dict:
async def handle_roll_gacha_api_request(self, data: Dict) -> Dict:
"""
Handle a gacha roll API request
"""
@ -323,7 +323,7 @@ class OngekiBright(OngekiBase):
"gameGachaCardList": game_gacha_card_list,
}
def handle_cm_upsert_user_gacha_api_request(self, data: Dict):
async def handle_cm_upsert_user_gacha_api_request(self, data: Dict):
upsert = data["cmUpsertUserGacha"]
user_id = data["userId"]
@ -405,7 +405,7 @@ class OngekiBright(OngekiBase):
return {"returnCode": 1, "apiName": "CMUpsertUserGachaApi"}
def handle_cm_upsert_user_select_gacha_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_select_gacha_api_request(self, data: Dict) -> Dict:
upsert = data["cmUpsertUserSelectGacha"]
user_id = data["userId"]
@ -442,7 +442,7 @@ class OngekiBright(OngekiBase):
return {"returnCode": 1, "apiName": "cmUpsertUserSelectGacha"}
def handle_get_game_gacha_card_by_id_api_request(self, data: Dict) -> Dict:
async def handle_get_game_gacha_card_by_id_api_request(self, data: Dict) -> Dict:
game_gacha_cards = self.data.static.get_gacha_cards(data["gachaId"])
if game_gacha_cards == []:
# fallback to be at least able to select that gacha
@ -522,7 +522,7 @@ class OngekiBright(OngekiBase):
"ssrBookCalcList": [],
}
def handle_get_game_theater_api_request(self, data: Dict) -> Dict:
async def handle_get_game_theater_api_request(self, data: Dict) -> Dict:
"""
shows a banner after every print, not sure what its used for
"""
@ -548,7 +548,7 @@ class OngekiBright(OngekiBase):
return {"length": 0, "gameTheaterList": [], "registIdList": []}
def handle_cm_upsert_user_print_playlog_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_print_playlog_api_request(self, data: Dict) -> Dict:
return {
"returnCode": 1,
"orderId": 0,
@ -556,7 +556,7 @@ class OngekiBright(OngekiBase):
"apiName": "CMUpsertUserPrintPlaylogApi",
}
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
return {
"returnCode": 1,
"orderId": 0,
@ -564,7 +564,7 @@ class OngekiBright(OngekiBase):
"apiName": "CMUpsertUserPrintlogApi",
}
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
user_print_detail = data["userPrintDetail"]
# generate random serial id
@ -589,7 +589,7 @@ class OngekiBright(OngekiBase):
"apiName": "CMUpsertUserPrintApi",
}
def handle_cm_upsert_user_all_api_request(self, data: Dict) -> Dict:
async def handle_cm_upsert_user_all_api_request(self, data: Dict) -> Dict:
upsert = data["cmUpsertUserAll"]
user_id = data["userId"]

View File

@ -15,8 +15,8 @@ class OngekiBrightMemory(OngekiBright):
super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.35.00"
ret["gameSetting"]["onlineDataVersion"] = "1.35.00"
ret["gameSetting"]["maxCountCharacter"] = 50
@ -27,7 +27,7 @@ class OngekiBrightMemory(OngekiBright):
ret["gameSetting"]["maxCountRivalMusic"] = 300
return ret
def handle_get_user_memory_chapter_api_request(self, data: Dict) -> Dict:
async def handle_get_user_memory_chapter_api_request(self, data: Dict) -> Dict:
memories = self.data.item.get_memorychapters(data["userId"])
if not memories:
return {
@ -134,5 +134,5 @@ class OngekiBrightMemory(OngekiBright):
"userMemoryChapterList": memory_chp,
}
def handle_get_game_music_release_state_api_request(self, data: Dict) -> Dict:
async def handle_get_game_music_release_state_api_request(self, data: Dict) -> Dict:
return {"techScore": 0, "cardNum": 0}

View File

@ -1,6 +1,6 @@
import yaml
import jinja2
from twisted.web.http import Request
from starlette.requests import Request
from os import path
from twisted.web.util import redirectTo
from twisted.web.server import Session

View File

@ -1,4 +1,6 @@
from twisted.web.http import Request
from starlette.requests import Request
from starlette.routing import Route
from starlette.responses import Response
import json
import inflection
import yaml
@ -120,35 +122,28 @@ class OngekiServlet(BaseServlet):
return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[("render_POST", "/SDDT/{version}/{endpoint}", {})]
)
def get_routes(self) -> List[Route]:
return [
Route("/SDDT/{version:int}/{endpoint:str}", self.render_POST, methods=['POST'])
]
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
title_port_int = Utils.get_title_port(self.core_cfg)
title_port_ssl_int = Utils.get_title_port_ssl(self.core_cfg)
proto = "https" if self.game_cfg.server.use_https and game_ver >= 120 else "http"
if proto == "https":
t_port = f":{title_port_ssl_int}" if title_port_ssl_int and not self.core_cfg.server.is_using_proxy else ""
else:
t_port = f":{title_port_int}" if title_port_int and not self.core_cfg.server.is_using_proxy else ""
t_port = f":{title_port_int}" if title_port_int and not self.core_cfg.server.is_using_proxy else ""
return (
f"{proto}://{self.core_cfg.title.hostname}{t_port}/{game_code}/{game_ver}/",
f"{self.core_cfg.title.hostname}{t_port}/",
f"{proto}://{self.core_cfg.server.hostname}{t_port}/{game_code}/{game_ver}/",
f"{self.core_cfg.server.hostname}{t_port}/",
)
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
endpoint = matchers['endpoint']
version = int(matchers['version'])
async def render_POST(self, request: Request) -> bytes:
endpoint: str = request.path_params.get('endpoint', '')
version: int = request.path_params.get('version', 0)
if endpoint.lower() == "ping":
return zlib.compress(b'{"returnCode": 1}')
return Response(zlib.compress(b'{"returnCode": 1}'))
req_raw = request.content.getvalue()
req_raw = await request.body()
encrtped = False
internal_ver = 0
client_ip = Utils.get_ip_addr(request)
@ -178,13 +173,13 @@ class OngekiServlet(BaseServlet):
self.logger.error(
f"v{version} does not support encryption or no keys entered"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
elif endpoint.lower() not in self.hash_table[internal_ver]:
self.logger.error(
f"No hash found for v{version} endpoint {endpoint}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
endpoint = self.hash_table[internal_ver][endpoint.lower()]
@ -201,7 +196,7 @@ class OngekiServlet(BaseServlet):
self.logger.error(
f"Failed to decrypt v{version} request to {endpoint} -> {e}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
encrtped = True
@ -213,7 +208,7 @@ class OngekiServlet(BaseServlet):
self.logger.error(
f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
try:
unzip = zlib.decompress(req_raw)
@ -222,7 +217,7 @@ class OngekiServlet(BaseServlet):
self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}"
)
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
req_data = json.loads(unzip)
@ -235,15 +230,15 @@ class OngekiServlet(BaseServlet):
if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}")
return zlib.compress(b'{"returnCode": 1}')
return Response(zlib.compress(b'{"returnCode": 1}'))
try:
handler = getattr(self.versions[internal_ver], func_to_find)
resp = handler(req_data)
resp = await handler(req_data)
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}')
return Response(zlib.compress(b'{"stat": "0"}'))
if resp == None:
resp = {"returnCode": 1}
@ -253,7 +248,7 @@ class OngekiServlet(BaseServlet):
zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
if not encrtped or version < 120:
return zipped
return Response(zipped)
padded = pad(zipped, 16)
@ -263,4 +258,4 @@ class OngekiServlet(BaseServlet):
bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]),
)
return crypt.encrypt(padded)
return Response(crypt.encrypt(padded))

View File

@ -11,8 +11,8 @@ class OngekiPlus(OngekiBase):
super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.05.00"
ret["gameSetting"]["onlineDataVersion"] = "1.05.00"
return ret

View File

@ -11,8 +11,8 @@ class OngekiRed(OngekiBase):
super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_RED
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.20.00"
ret["gameSetting"]["onlineDataVersion"] = "1.20.00"
return ret

View File

@ -11,8 +11,8 @@ class OngekiRedPlus(OngekiBase):
super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_RED_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.25.00"
ret["gameSetting"]["onlineDataVersion"] = "1.25.00"
ret["gameSetting"]["maxCountCharacter"] = 50

View File

@ -11,8 +11,8 @@ class OngekiSummer(OngekiBase):
super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_SUMMER
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.10.00"
ret["gameSetting"]["onlineDataVersion"] = "1.10.00"
return ret

View File

@ -11,8 +11,8 @@ class OngekiSummerPlus(OngekiBase):
super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_SUMMER_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.15.00"
ret["gameSetting"]["onlineDataVersion"] = "1.15.00"
return ret

View File

@ -20,21 +20,21 @@ class PokkenBase:
self.data = PokkenData(core_cfg)
self.SUPPORT_SET_NONE = 4294967295
def handle_noop(self, request: Any) -> bytes:
async def handle_noop(self, request: Any) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = request.type
return res.SerializeToString()
def handle_ping(self, request: jackal_pb2.Request) -> bytes:
async def handle_ping(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.PING
return res.SerializeToString()
def handle_register_pcb(self, request: jackal_pb2.Request) -> bytes:
async def handle_register_pcb(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.REGISTER_PCB
@ -61,35 +61,35 @@ class PokkenBase:
"logfilename": "JackalMatchingLibrary.log",
"biwalogfilename": "./biwa.log",
}
regist_pcb.bnp_baseuri = f"{self.core_cfg.title.hostname}/bna"
regist_pcb.bnp_baseuri = f"{self.core_cfg.server.hostname}/bna"
regist_pcb.biwa_setting = json.dumps(biwa_setting)
res.register_pcb.CopyFrom(regist_pcb)
return res.SerializeToString()
def handle_save_ads(self, request: jackal_pb2.Request) -> bytes:
async def handle_save_ads(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.SAVE_ADS
return res.SerializeToString()
def handle_save_client_log(self, request: jackal_pb2.Request) -> bytes:
async def handle_save_client_log(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.SAVE_CLIENT_LOG
return res.SerializeToString()
def handle_check_diagnosis(self, request: jackal_pb2.Request) -> bytes:
async def handle_check_diagnosis(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.CHECK_DIAGNOSIS
return res.SerializeToString()
def handle_load_client_settings(self, request: jackal_pb2.Request) -> bytes:
async def handle_load_client_settings(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.LOAD_CLIENT_SETTINGS
@ -112,7 +112,7 @@ class PokkenBase:
return res.SerializeToString()
def handle_load_ranking(self, request: jackal_pb2.Request) -> bytes:
async def handle_load_ranking(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.LOAD_RANKING
@ -126,7 +126,7 @@ class PokkenBase:
res.load_ranking.CopyFrom(ranking)
return res.SerializeToString()
def handle_load_user(self, request: jackal_pb2.Request) -> bytes:
async def handle_load_user(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.LOAD_USER
@ -287,13 +287,13 @@ class PokkenBase:
res.load_user.CopyFrom(load_usr)
return res.SerializeToString()
def handle_set_bnpassid_lock(self, data: jackal_pb2.Request) -> bytes:
async def handle_set_bnpassid_lock(self, data: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.SET_BNPASSID_LOCK
return res.SerializeToString()
def handle_save_user(self, request: jackal_pb2.Request) -> bytes:
async def handle_save_user(self, request: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.SAVE_USER
@ -394,38 +394,31 @@ class PokkenBase:
return res.SerializeToString()
def handle_save_ingame_log(self, data: jackal_pb2.Request) -> bytes:
async def handle_save_ingame_log(self, data: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.SAVE_INGAME_LOG
return res.SerializeToString()
def handle_save_charge(self, data: jackal_pb2.Request) -> bytes:
async def handle_save_charge(self, data: jackal_pb2.Request) -> bytes:
res = jackal_pb2.Response()
res.result = 1
res.type = jackal_pb2.MessageType.SAVE_CHARGE
return res.SerializeToString()
def handle_matching_noop(
async def handle_matching_noop(
self, data: Dict = {}, client_ip: str = "127.0.0.1"
) -> Dict:
return {}
def handle_matching_start_matching(
async def handle_matching_start_matching(
self, data: Dict = {}, client_ip: str = "127.0.0.1"
) -> Dict:
return {}
def handle_matching_is_matching(
async def handle_matching_is_matching(
self, data: Dict = {}, client_ip: str = "127.0.0.1"
) -> Dict:
"""
"sessionId":"12345678",
"A":{
"pcb_id": data["data"]["must"]["pcb_id"],
"gip": client_ip
},
"""
return {
"data": {
"sessionId":"12345678",
@ -437,15 +430,15 @@ class PokkenBase:
}
}
def handle_matching_stop_matching(
async def handle_matching_stop_matching(
self, data: Dict = {}, client_ip: str = "127.0.0.1"
) -> Dict:
return {}
def handle_admission_noop(self, data: Dict, req_ip: str = "127.0.0.1") -> Dict:
async def handle_admission_noop(self, data: Dict, req_ip: str = "127.0.0.1") -> Dict:
return {}
def handle_admission_joinsession(self, data: Dict, req_ip: str = "127.0.0.1") -> Dict:
async def handle_admission_joinsession(self, data: Dict, req_ip: str = "127.0.0.1") -> Dict:
self.logger.info(f"Admission: JoinSession from {req_ip}")
return {
'data': {

View File

@ -3,6 +3,7 @@ from enum import Enum
class PokkenConstants:
GAME_CODE = "SDAK"
GAME_CDS = ["PKF1"]
CONFIG_NAME = "pokken.yaml"
@ -10,6 +11,12 @@ class PokkenConstants:
VERSION_NAMES = "Pokken Tournament"
SERIAL_IDENT = [2747]
NETID_PREFIX = ["ABGN"]
SERIAL_REGIONS = [1]
SERIAL_ROLES = [3]
SERIAL_CAB_IDENTS = [19]
class BATTLE_TYPE(Enum):
TUTORIAL = 1
AI = 2

View File

@ -1,6 +1,6 @@
import yaml
import jinja2
from twisted.web.http import Request
from starlette.requests import Request
from os import path
from twisted.web.server import Session

View File

@ -1,8 +1,10 @@
from typing import Tuple, List, Dict
from twisted.web.http import Request
from twisted.web import resource
from twisted.internet import reactor
import json, ast
from starlette.requests import Request
from starlette.requests import Request
from starlette.responses import Response, JSONResponse
from starlette.routing import Route, WebSocketRoute
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
import ast
from datetime import datetime
import yaml
import logging, coloredlogs
@ -17,8 +19,6 @@ from .config import PokkenConfig
from .base import PokkenBase
from .const import PokkenConstants
from .proto import jackal_pb2
from .services import PokkenAdmissionFactory
class PokkenServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
@ -69,47 +69,73 @@ class PokkenServlet(BaseServlet):
return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[
("render_POST", "/pokken/", {}),
("handle_matching", "/pokken/matching", {}),
]
)
def get_routes(self) -> List[Route]:
return [
Route("/pokken/", self.render_POST, methods=['POST']),
Route("/pokken/matching", self.handle_matching, methods=['POST']),
WebSocketRoute("/pokken/admission", self.handle_admission)
]
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
if self.game_cfg.ports.game != 443:
return (
f"https://{self.game_cfg.server.hostname}:{self.game_cfg.ports.game}/pokken/",
f"{self.game_cfg.server.hostname}/pokken/",
)
return (
f"https://{self.game_cfg.server.hostname}/pokken/",
f"{self.game_cfg.server.hostname}/pokken/",
f"https://{self.game_cfg.server.hostname}:{self.game_cfg.ports.game}/pokken/",
f"{self.game_cfg.server.hostname}:{self.game_cfg.ports.game}/pokken/",
)
def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str]:
game_cfg = PokkenConfig()
if not self.game_cfg.server.enable:
return (False, [], [])
if path.exists(f"{cfg_dir}/{PokkenConstants.CONFIG_NAME}"):
game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{PokkenConstants.CONFIG_NAME}"))
)
return (True, PokkenConstants.GAME_CDS, PokkenConstants.NETID_PREFIX)
async def handle_admission(self, ws: WebSocket) -> None:
client_ip = Utils.get_ip_addr(ws)
await ws.accept()
while True:
try:
msg: Dict = await ws.receive_json()
except WebSocketDisconnect as e:
self.logger.debug(f"Client {client_ip} disconnected - {e}")
break
except Exception as e:
self.logger.error(f"Could not load JSON from message from {client_ip} - {e}")
if ws.client_state != WebSocketState.DISCONNECTED:
await ws.close()
break
self.logger.debug(f"Admission: Message from {client_ip}:{ws.client.port} - {msg}")
api = msg.get("api", "noop")
handler = getattr(self.base, f"handle_admission_{api.lower()}")
resp = await handler(msg, client_ip)
if resp is None:
resp = {}
if not game_cfg.server.enable:
return (False, "")
if "type" not in resp:
resp['type'] = "res"
if "data" not in resp:
resp['data'] = {}
if "api" not in resp:
resp['api'] = api
if "result" not in resp:
resp['result'] = 'true'
self.logger.debug(f"Websocket response: {resp}")
try:
await ws.send_json(resp)
except WebSocketDisconnect as e:
self.logger.debug(f"Client {client_ip} disconnected - {e}")
break
except Exception as e:
self.logger.error(f"Could not send JSON message to {client_ip} - {e}")
break
if ws.client_state != WebSocketState.DISCONNECTED:
await ws.close()
return (True, "PKF1")
def setup(self) -> None:
if self.game_cfg.server.enable_matching:
reactor.listenTCP(
self.game_cfg.ports.admission, PokkenAdmissionFactory(self.core_cfg, self.game_cfg)
)
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
content = request.content.getvalue()
async def render_POST(self, request: Request) -> bytes:
content = await request.body()
if content == b"":
self.logger.info("Empty request")
return b""
@ -134,19 +160,19 @@ class PokkenServlet(BaseServlet):
self.logger.info(f"{endpoint} request from {Utils.get_ip_addr(request)}")
ret = handler(pokken_request)
return ret
ret = await handler(pokken_request)
return Response(ret)
def handle_matching(self, request: Request, game_code: str, matchers: Dict) -> bytes:
async def handle_matching(self, request: Request) -> bytes:
if not self.game_cfg.server.enable_matching:
return b""
return Response()
content = request.content.getvalue()
content = await request.body()
client_ip = Utils.get_ip_addr(request)
if content is None or content == b"":
self.logger.info("Empty matching request")
return json.dumps(self.base.handle_matching_noop()).encode()
return JSONResponse(self.base.handle_matching_noop())
json_content = ast.literal_eval(
content.decode()
@ -166,7 +192,7 @@ class PokkenServlet(BaseServlet):
self.logger.warning(
f"No handler found for message type {json_content['call']}"
)
return json.dumps(self.base.handle_matching_noop()).encode()
return JSONResponse(self.base.handle_matching_noop())
ret = handler(json_content, client_ip)
@ -181,4 +207,4 @@ class PokkenServlet(BaseServlet):
self.logger.debug(f"Response {ret}")
return json.dumps(ret).encode()
return JSONResponse(ret)

View File

@ -32,42 +32,42 @@ class SaoBase:
self.logger.warning(f"Failed to find csv file {file}.csv")
return ret
def handle_noop(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_noop(self, header: SaoRequestHeader, request: bytes) -> bytes:
self.logger.info(f"Using Generic handler")
resp_thing = SaoNoopResponse(header.cmd + 1)
return resp_thing.make()
def handle_c122(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c122(self, header: SaoRequestHeader, request: bytes) -> bytes:
#common/get_maintenance_info
resp = SaoGetMaintResponse(header.cmd +1)
return resp.make()
def handle_c12e(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c12e(self, header: SaoRequestHeader, request: bytes) -> bytes:
#common/ac_cabinet_boot_notification
resp = SaoCommonAcCabinetBootNotificationResponse(header.cmd +1)
return resp.make()
def handle_c100(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c100(self, header: SaoRequestHeader, request: bytes) -> bytes:
#common/get_app_versions
resp = SaoCommonGetAppVersionsRequest(header.cmd +1)
return resp.make()
def handle_c102(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c102(self, header: SaoRequestHeader, request: bytes) -> bytes:
#common/master_data_version_check
resp = SaoMasterDataVersionCheckResponse(header.cmd +1)
return resp.make()
def handle_c10a(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c10a(self, header: SaoRequestHeader, request: bytes) -> bytes:
#common/paying_play_start
resp = SaoCommonPayingPlayStartRequest(header.cmd +1)
return resp.make()
def handle_ca02(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_ca02(self, header: SaoRequestHeader, request: bytes) -> bytes:
#quest_multi_play_room/get_quest_scene_multi_play_photon_server
resp = SaoGetQuestSceneMultiPlayPhotonServerResponse(header.cmd +1)
return resp.make()
def handle_c11e(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c11e(self, header: SaoRequestHeader, request: bytes) -> bytes:
#common/get_auth_card_data
req = SaoGetAuthCardDataRequest(header, request)
@ -120,12 +120,12 @@ class SaoBase:
resp = SaoGetAuthCardDataResponse(header.cmd +1, profile_data)
return resp.make()
def handle_c40c(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c40c(self, header: SaoRequestHeader, request: bytes) -> bytes:
#home/check_ac_login_bonus
resp = SaoHomeCheckAcLoginBonusResponse(header.cmd +1)
return resp.make()
def handle_c104(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c104(self, header: SaoRequestHeader, request: bytes) -> bytes:
#common/login
req = SaoCommonLoginRequest(header, request)
@ -135,17 +135,17 @@ class SaoBase:
resp = SaoCommonLoginResponse(header.cmd +1, profile_data)
return resp.make()
def handle_c404(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c404(self, header: SaoRequestHeader, request: bytes) -> bytes:
#home/check_comeback_event
resp = SaoCheckComebackEventRequest(header.cmd +1)
return resp.make()
def handle_c000(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c000(self, header: SaoRequestHeader, request: bytes) -> bytes:
#ticket/ticket
resp = SaoTicketResponse(header.cmd +1)
return resp.make()
def handle_c500(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c500(self, header: SaoRequestHeader, request: bytes) -> bytes:
#user_info/get_user_basic_data
req = SaoGetUserBasicDataRequest(header, request)
@ -154,7 +154,7 @@ class SaoBase:
resp = SaoGetUserBasicDataResponse(header.cmd +1, profile_data)
return resp.make()
def handle_c600(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c600(self, header: SaoRequestHeader, request: bytes) -> bytes:
#have_object/get_hero_log_user_data_list
req = SaoGetHeroLogUserDataListRequest(header, request)
@ -163,7 +163,7 @@ class SaoBase:
resp = SaoGetHeroLogUserDataListResponse(header.cmd +1, hero_data)
return resp.make()
def handle_c602(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c602(self, header: SaoRequestHeader, request: bytes) -> bytes:
#have_object/get_equipment_user_data_list
req = SaoGetEquipmentUserDataListRequest(header, request)
@ -172,7 +172,7 @@ class SaoBase:
resp = SaoGetEquipmentUserDataListResponse(header.cmd +1, equipment_data)
return resp.make()
def handle_c604(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c604(self, header: SaoRequestHeader, request: bytes) -> bytes:
#have_object/get_item_user_data_list
req = SaoGetItemUserDataListRequest(header, request)
@ -181,21 +181,21 @@ class SaoBase:
resp = SaoGetItemUserDataListResponse(header.cmd +1, item_data)
return resp.make()
def handle_c606(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c606(self, header: SaoRequestHeader, request: bytes) -> bytes:
#have_object/get_support_log_user_data_list
supportIdsData = self.game_data.static.get_support_log_ids(0, True)
resp = SaoGetSupportLogUserDataListResponse(header.cmd +1, supportIdsData)
return resp.make()
def handle_c800(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c800(self, header: SaoRequestHeader, request: bytes) -> bytes:
#custom/get_title_user_data_list
titleIdsData = self.game_data.static.get_title_ids(0, True)
resp = SaoGetTitleUserDataListResponse(header.cmd +1, titleIdsData)
return resp.make()
def handle_c608(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c608(self, header: SaoRequestHeader, request: bytes) -> bytes:
#have_object/get_episode_append_data_list
req = SaoGetEpisodeAppendDataListRequest(header, request)
@ -204,7 +204,7 @@ class SaoBase:
resp = SaoGetEpisodeAppendDataListResponse(header.cmd +1, profile_data)
return resp.make()
def handle_c804(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c804(self, header: SaoRequestHeader, request: bytes) -> bytes:
#custom/get_party_data_list
req = SaoGetPartyDataListRequest(header, request)
@ -216,17 +216,17 @@ class SaoBase:
resp = SaoGetPartyDataListResponse(header.cmd +1, hero1_data, hero2_data, hero3_data)
return resp.make()
def handle_c902(self, header: SaoRequestHeader, request: bytes) -> bytes: # for whatever reason, having all entries empty or filled changes nothing
async def handle_c902(self, header: SaoRequestHeader, request: bytes) -> bytes: # for whatever reason, having all entries empty or filled changes nothing
#quest/get_quest_scene_prev_scan_profile_card
resp = SaoGetQuestScenePrevScanProfileCardResponse(header.cmd +1)
return resp.make()
def handle_c124(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c124(self, header: SaoRequestHeader, request: bytes) -> bytes:
#common/get_resource_path_info
resp = SaoGetResourcePathInfoResponse(header.cmd +1)
return resp.make()
def handle_c900(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c900(self, header: SaoRequestHeader, request: bytes) -> bytes:
#quest/get_quest_scene_user_data_list // QuestScene.csv
req = SaoGetQuestSceneUserDataListRequest(header, request)
@ -235,22 +235,22 @@ class SaoBase:
resp = SaoGetQuestSceneUserDataListResponse(header.cmd +1, quest_data)
return resp.make()
def handle_c400(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c400(self, header: SaoRequestHeader, request: bytes) -> bytes:
#home/check_yui_medal_get_condition
resp = SaoCheckYuiMedalGetConditionResponse(header.cmd +1)
return resp.make()
def handle_c402(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c402(self, header: SaoRequestHeader, request: bytes) -> bytes:
#home/get_yui_medal_bonus_user_data
resp = SaoGetYuiMedalBonusUserDataResponse(header.cmd +1)
return resp.make()
def handle_c40a(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c40a(self, header: SaoRequestHeader, request: bytes) -> bytes:
#home/check_profile_card_used_reward
resp = SaoCheckProfileCardUsedRewardResponse(header.cmd +1)
return resp.make()
def handle_c814(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c814(self, header: SaoRequestHeader, request: bytes) -> bytes:
#custom/synthesize_enhancement_hero_log
req = SaoSynthesizeEnhancementHeroLogRequest(header, request)
@ -324,7 +324,7 @@ class SaoBase:
resp = SaoSynthesizeEnhancementHeroLogResponse(header.cmd +1, synthesize_hero_log_data)
return resp.make()
def handle_c816(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c816(self, header: SaoRequestHeader, request: bytes) -> bytes:
#custom/synthesize_enhancement_equipment
req_data = SaoSynthesizeEnhancementEquipmentRequest(header, request)
synthesize_equipment_data = self.game_data.item.get_user_equipment(req_data.user_id, req_data.origin_user_equipment_id)
@ -386,7 +386,7 @@ class SaoBase:
resp = SaoSynthesizeEnhancementEquipmentResponse(header.cmd +1, synthesize_equipment_data)
return resp.make()
def handle_c806(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c806(self, header: SaoRequestHeader, request: bytes) -> bytes:
#custom/change_party
req_data = SaoChangePartyRequest(header, request)
party_hero_list = []
@ -421,7 +421,7 @@ class SaoBase:
resp = SaoNoopResponse(header.cmd +1)
return resp.make()
def handle_c904(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c904(self, header: SaoRequestHeader, request: bytes) -> bytes:
#quest/episode_play_start
req_data = SaoEpisodePlayStartRequest(header, request)
@ -439,7 +439,7 @@ class SaoBase:
resp = SaoEpisodePlayStartResponse(header.cmd +1, profile_data)
return resp.make()
def handle_c908(self, header: SaoRequestHeader, request: bytes) -> bytes: # Level calculation missing for the profile and heroes
async def handle_c908(self, header: SaoRequestHeader, request: bytes) -> bytes: # Level calculation missing for the profile and heroes
#quest/episode_play_end
req_data = SaoEpisodePlayEndRequest(header, request)
@ -599,7 +599,7 @@ class SaoBase:
resp = SaoEpisodePlayEndResponse(header.cmd +1)
return resp.make()
def handle_c914(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c914(self, header: SaoRequestHeader, request: bytes) -> bytes:
#quest/trial_tower_play_start
req_data = SaoTrialTowerPlayStartRequest(header, request)
@ -618,7 +618,7 @@ class SaoBase:
resp = SaoEpisodePlayStartResponse(header.cmd +1, profile_data)
return resp.make()
def handle_c918(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c918(self, header: SaoRequestHeader, request: bytes) -> bytes:
#quest/trial_tower_play_end
req_data = SaoTrialTowerPlayEndRequest(header, request)
@ -799,7 +799,7 @@ class SaoBase:
resp = SaoTrialTowerPlayEndResponse(header.cmd +1)
return resp.make()
def handle_c90a(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c90a(self, header: SaoRequestHeader, request: bytes) -> bytes:
#quest/episode_play_end_unanalyzed_log_fixed
req = SaoEpisodePlayEndUnanalyzedLogFixedRequest(header, request)
@ -809,7 +809,7 @@ class SaoBase:
resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(header.cmd +1, end_session_data[4])
return resp.make()
def handle_c91a(self, header: SaoRequestHeader, request: bytes) -> bytes: # handler is identical to the episode
async def handle_c91a(self, header: SaoRequestHeader, request: bytes) -> bytes: # handler is identical to the episode
#quest/trial_tower_play_end_unanalyzed_log_fixed
req = TrialTowerPlayEndUnanalyzedLogFixed(header, request)
@ -818,58 +818,58 @@ class SaoBase:
resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(header.cmd +1, end_session_data[4])
return resp.make()
def handle_cd00(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_cd00(self, header: SaoRequestHeader, request: bytes) -> bytes:
#defrag_match/get_defrag_match_basic_data
resp = SaoGetDefragMatchBasicDataResponse(header.cmd +1)
return resp.make()
def handle_cd02(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_cd02(self, header: SaoRequestHeader, request: bytes) -> bytes:
#defrag_match/get_defrag_match_ranking_user_data
resp = SaoGetDefragMatchRankingUserDataResponse(header.cmd +1)
return resp.make()
def handle_cd04(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_cd04(self, header: SaoRequestHeader, request: bytes) -> bytes:
#defrag_match/get_defrag_match_league_point_ranking_list
resp = SaoGetDefragMatchLeaguePointRankingListResponse(header.cmd +1)
return resp.make()
def handle_cd06(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_cd06(self, header: SaoRequestHeader, request: bytes) -> bytes:
#defrag_match/get_defrag_match_league_score_ranking_list
resp = SaoGetDefragMatchLeagueScoreRankingListResponse(header.cmd +1)
return resp.make()
def handle_d404(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_d404(self, header: SaoRequestHeader, request: bytes) -> bytes:
#other/bnid_serial_code_check
resp = SaoBnidSerialCodeCheckResponse(header.cmd +1)
return resp.make()
def handle_c306(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c306(self, header: SaoRequestHeader, request: bytes) -> bytes:
#card/scan_qr_quest_profile_card
resp = SaoScanQrQuestProfileCardResponse(header.cmd +1)
return resp.make()
def handle_c700(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_c700(self, header: SaoRequestHeader, request: bytes) -> bytes:
# shop/get_shop_resource_sales_data_list
# TODO: Get user shop data
req = GetShopResourceSalesDataListRequest(header, request)
resp = GetShopResourceSalesDataListResponse(header.cmd + 1)
return resp.make()
def handle_d100(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_d100(self, header: SaoRequestHeader, request: bytes) -> bytes:
# shop/get_yui_medal_shop_user_data_list
# TODO: Get user shop data
req = GetYuiMedalShopUserDataListRequest(header, request)
resp = GetYuiMedalShopUserDataListResponse(header.cmd + 1)
return resp.make()
def handle_cf0e(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_cf0e(self, header: SaoRequestHeader, request: bytes) -> bytes:
# gasha/get_gasha_medal_shop_user_data_list
# TODO: Get user shop data
req = GetGashaMedalShopUserDataListRequest(header, request)
resp = GetGashaMedalShopUserDataListResponse(header.cmd + 1)
return resp.make()
def handle_d5da(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_d5da(self, header: SaoRequestHeader, request: bytes) -> bytes:
# master_data/get_m_yui_medal_shops
req = GetMYuiMedalShopDataRequest(header, request)
resp = GetMYuiMedalShopDataResponse(header.cmd + 1)
@ -897,7 +897,7 @@ class SaoBase:
self.logger.debug(f"Load {len(resp.data_list)} Yui Medal Shops")
return resp.make()
def handle_d5dc(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_d5dc(self, header: SaoRequestHeader, request: bytes) -> bytes:
# master_data/get_m_yui_medal_shop_items
req = GetMYuiMedalShopItemsRequest(header, request)
resp = GetMYuiMedalShopItemsResponse(header.cmd + 1)
@ -927,7 +927,7 @@ class SaoBase:
self.logger.debug(f"Load {len(resp.data_list)} Yui Medal Shop Items")
return resp.make()
def handle_d5fc(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_d5fc(self, header: SaoRequestHeader, request: bytes) -> bytes:
# master_data/get_m_gasha_medal_shops
req = GetMGashaMedalShopsRequest(header, request)
resp = GetMGashaMedalShopsResponse(header.cmd + 1)
@ -942,11 +942,11 @@ class SaoBase:
self.logger.debug(f"Load {len(resp.data_list)} Gasha Medal Shops")
return resp.make()
def handle_d5fe(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_d5fe(self, header: SaoRequestHeader, request: bytes) -> bytes:
# master_data/get_m_gasha_medal_shop_items
return SaoNoopResponse(header.cmd + 1).make()
def handle_d604(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_d604(self, header: SaoRequestHeader, request: bytes) -> bytes:
# master_data_2/get_m_res_earn_campaign_shops
req = GetMResEarnCampaignShopsRequest(header, request)
resp = GetMResEarnCampaignShopsResponse(header.cmd + 1)
@ -968,6 +968,6 @@ class SaoBase:
#self.logger.debug(f"Load {len(resp.data_list)} Res Earn Campaign Shops")
return SaoNoopResponse(header.cmd + 1).make()
def handle_d606(self, header: SaoRequestHeader, request: bytes) -> bytes:
async def handle_d606(self, header: SaoRequestHeader, request: bytes) -> bytes:
# master_data_2/get_m_res_earn_campaign_shop_items
return SaoNoopResponse(header.cmd + 1).make()

View File

@ -3,12 +3,22 @@ from enum import Enum
class SaoConstants:
GAME_CODE = "SDEW"
GAME_CDS = ["SAO1"]
CONFIG_NAME = "sao.yaml"
VER_SAO = 0
VERSION_NAMES = ("Sword Art Online Arcade")
SERIAL_IDENT_SATALITE = 4
SERIAL_IDENT_TERMINAL = 5
SERIAL_IDENT = [2825]
NETID_PREFIX = ["ABLN"]
SERIAL_REGIONS = [1]
SERIAL_ROLES = [3]
SERIAL_CAB_IDENTS = [SERIAL_IDENT_SATALITE, SERIAL_IDENT_TERMINAL]
@classmethod
def game_ver_to_string(cls, ver: int):

View File

@ -1,5 +1,7 @@
from typing import Tuple, Dict, List
from twisted.web.http import Request
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route
import yaml
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
@ -55,11 +57,10 @@ class SaoServlet(BaseServlet):
if self.game_cfg.hash.verify_hash:
self.static_hash = md5(self.game_cfg.hash.hash_base.encode()).digest() # Greate hashing guys, really validates the data
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[("render_POST", "/{datecode}/proto/if/{category}/{endpoint}", {})]
)
def get_routes(self) -> List[Route]:
return [
Route("/{datecode:int}/proto/if/{category:str}/{endpoint:str}", self.render_POST, methods=['POST'])
]
@classmethod
def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool:
@ -86,30 +87,29 @@ class SaoServlet(BaseServlet):
proto = "https"
port = f":{port_ssl}" if not self.core_cfg.server.is_using_proxy and port_ssl != 443 else ""
return (f"{proto}://{self.core_cfg.title.hostname}{port}/", "")
return (f"{proto}://{self.core_cfg.server.hostname}{port}/", "")
def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str]:
if not self.game_cfg.server.enable:
return (False, "")
return (False, [], [])
return (True, "SAO1")
return (True, SaoConstants.GAME_CDS, SaoConstants.NETID_PREFIX)
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
endpoint = matchers.get('endpoint', '')
request.responseHeaders.addRawHeader(b"content-type", b"text/html; charset=utf-8")
async def render_POST(self, request: Request) -> bytes:
endpoint = request.path_params.get('endpoint', '')
iv = b""
req_raw = request.content.read()
req_raw = await request.body()
if len(req_raw) < 40:
self.logger.warn(f"Malformed request to {endpoint} - {req_raw.hex()}")
return b""
return Response()
req_header = SaoRequestHeader(req_raw)
cmd_str = f"{req_header.cmd:04x}"
if self.game_cfg.hash.verify_hash and self.static_hash != req_header.hash:
self.logger.error(f"Hash mismatch! Expecting {self.static_hash} but recieved {req_header.hash}")
return b""
return Response()
if self.game_cfg.crypt.enable:
iv = req_raw[40:48]
@ -124,7 +124,7 @@ class SaoServlet(BaseServlet):
handler = getattr(self.base, f"handle_{cmd_str}", self.base.handle_noop)
self.logger.info(f"{endpoint} - {cmd_str} request")
self.logger.debug(f"Request: {req_raw.hex()}")
resp = handler(req_header, req_data)
resp = await handler(req_header, req_data)
if resp is None:
resp = SaoNoopResponse(req_header.cmd + 1).make()
@ -137,7 +137,7 @@ class SaoServlet(BaseServlet):
else:
self.logger.error(f"Unknown response type {type(resp)}")
return b""
return Response()
self.logger.debug(f"Response: {resp.hex()}")
@ -153,5 +153,6 @@ class SaoServlet(BaseServlet):
tmp = struct.pack("!I", crypt_data_len) # does it want the length of the encrypted response??
resp = resp[:20] + tmp + iv + data_crypt
self.logger.debug(f"Encrypted Response: {resp.hex()}")
return resp
return Response(resp, media_type="text/html; charset=utf-8")

View File

@ -83,18 +83,18 @@ class WaccaBase:
else:
self.region_id = WaccaConstants.Region[prefecture_name]
def handle_housing_get_request(self, data: Dict) -> Dict:
async def handle_housing_get_request(self, data: Dict) -> Dict:
req = BaseRequest(data)
housing_id = 1337
self.logger.info(f"{req.chipId} -> {housing_id}")
resp = HousingGetResponse(housing_id)
return resp.make()
def handle_advertise_GetRanking_request(self, data: Dict) -> Dict:
async def handle_advertise_GetRanking_request(self, data: Dict) -> Dict:
req = AdvertiseGetRankingRequest(data)
return AdvertiseGetRankingResponse().make()
def handle_housing_start_request(self, data: Dict) -> Dict:
async def handle_housing_start_request(self, data: Dict) -> Dict:
req = HousingStartRequestV1(data)
allnet_region_id = None
@ -126,16 +126,16 @@ class WaccaBase:
resp = HousingStartResponseV1(region_id)
return resp.make()
def handle_advertise_GetNews_request(self, data: Dict) -> Dict:
async def handle_advertise_GetNews_request(self, data: Dict) -> Dict:
resp = GetNewsResponseV1()
return resp.make()
def handle_user_status_logout_request(self, data: Dict) -> Dict:
async def handle_user_status_logout_request(self, data: Dict) -> Dict:
req = UserStatusLogoutRequest(data)
self.logger.info(f"Log out user {req.userId} from {req.chipId}")
return BaseResponse().make()
def handle_user_status_get_request(self, data: Dict) -> Dict:
async def handle_user_status_get_request(self, data: Dict) -> Dict:
req = UserStatusGetRequest(data)
resp = UserStatusGetV1Response()
@ -181,7 +181,7 @@ class WaccaBase:
return resp.make()
def handle_user_status_login_request(self, data: Dict) -> Dict:
async def handle_user_status_login_request(self, data: Dict) -> Dict:
req = UserStatusLoginRequest(data)
resp = UserStatusLoginResponseV1()
is_consec_day = True
@ -227,7 +227,7 @@ class WaccaBase:
return resp.make()
def handle_user_status_create_request(self, data: Dict) -> Dict:
async def handle_user_status_create_request(self, data: Dict) -> Dict:
req = UserStatusCreateRequest(data)
profileId = self.data.profile.create_profile(
@ -268,7 +268,7 @@ class WaccaBase:
return UserStatusCreateResponseV2(profileId, req.username).make()
def handle_user_status_getDetail_request(self, data: Dict) -> Dict:
async def handle_user_status_getDetail_request(self, data: Dict) -> Dict:
req = UserStatusGetDetailRequest(data)
resp = UserStatusGetDetailResponseV1()
@ -433,7 +433,7 @@ class WaccaBase:
return resp.make()
def handle_user_trial_get_request(self, data: Dict) -> Dict:
async def handle_user_trial_get_request(self, data: Dict) -> Dict:
req = UserTrialGetRequest(data)
resp = UserTrialGetResponse()
@ -475,7 +475,7 @@ class WaccaBase:
return resp.make()
def handle_user_trial_update_request(self, data: Dict) -> Dict:
async def handle_user_trial_update_request(self, data: Dict) -> Dict:
req = UserTrialUpdateRequest(data)
total_score = 0
@ -571,7 +571,7 @@ class WaccaBase:
)
return BaseResponse().make()
def handle_user_sugoroku_update_request(self, data: Dict) -> Dict:
async def handle_user_sugoroku_update_request(self, data: Dict) -> Dict:
ver_split = data["appVersion"].split(".")
resp = BaseResponse()
@ -603,10 +603,10 @@ class WaccaBase:
)
return resp.make()
def handle_user_info_getMyroom_request(self, data: Dict) -> Dict:
async def handle_user_info_getMyroom_request(self, data: Dict) -> Dict:
return UserInfogetMyroomResponseV1().make()
def handle_user_music_unlock_request(self, data: Dict) -> Dict:
async def handle_user_music_unlock_request(self, data: Dict) -> Dict:
req = UserMusicUnlockRequest(data)
profile = self.data.profile.get_profile(req.profileId)
@ -672,12 +672,12 @@ class WaccaBase:
return UserMusicUnlockResponse(current_wp, new_tickets).make()
def handle_user_info_getRanking_request(self, data: Dict) -> Dict:
async def handle_user_info_getRanking_request(self, data: Dict) -> Dict:
# total score, high score by song, cumulative socre, stage up score, other score, WP ranking
# This likely requies calculating standings at regular intervals and caching the results
return UserInfogetRankingResponse().make()
def handle_user_music_update_request(self, data: Dict) -> Dict:
async def handle_user_music_update_request(self, data: Dict) -> Dict:
ver_split = data["appVersion"].split(".")
if int(ver_split[0]) >= 3:
resp = UserMusicUpdateResponseV3()
@ -831,18 +831,18 @@ class WaccaBase:
return resp.make()
# TODO: Coop and vs data
def handle_user_music_updateCoop_request(self, data: Dict) -> Dict:
async def handle_user_music_updateCoop_request(self, data: Dict) -> Dict:
coop_info = data["params"][4]
return self.handle_user_music_update_request(data)
def handle_user_music_updateVersus_request(self, data: Dict) -> Dict:
async def handle_user_music_updateVersus_request(self, data: Dict) -> Dict:
vs_info = data["params"][4]
return self.handle_user_music_update_request(data)
def handle_user_music_updateTrial_request(self, data: Dict) -> Dict:
async def handle_user_music_updateTrial_request(self, data: Dict) -> Dict:
return self.handle_user_music_update_request(data)
def handle_user_mission_update_request(self, data: Dict) -> Dict:
async def handle_user_mission_update_request(self, data: Dict) -> Dict:
req = UserMissionUpdateRequest(data)
page_status = req.params[1][1]
@ -860,7 +860,7 @@ class WaccaBase:
return BaseResponse().make()
def handle_user_goods_purchase_request(self, data: Dict) -> Dict:
async def handle_user_goods_purchase_request(self, data: Dict) -> Dict:
req = UserGoodsPurchaseRequest(data)
resp = UserGoodsPurchaseResponse()
@ -905,13 +905,13 @@ class WaccaBase:
return resp.make()
def handle_competition_status_login_request(self, data: Dict) -> Dict:
async def handle_competition_status_login_request(self, data: Dict) -> Dict:
return BaseResponse().make()
def handle_competition_status_update_request(self, data: Dict) -> Dict:
async def handle_competition_status_update_request(self, data: Dict) -> Dict:
return BaseResponse().make()
def handle_user_rating_update_request(self, data: Dict) -> Dict:
async def handle_user_rating_update_request(self, data: Dict) -> Dict:
req = UserRatingUpdateRequest(data)
user_id = self.data.profile.profile_to_aime_user(req.profileId)
@ -931,7 +931,7 @@ class WaccaBase:
return BaseResponse().make()
def handle_user_status_update_request(self, data: Dict) -> Dict:
async def handle_user_status_update_request(self, data: Dict) -> Dict:
req = UserStatusUpdateRequestV1(data)
user_id = self.data.profile.profile_to_aime_user(req.profileId)
@ -970,7 +970,7 @@ class WaccaBase:
)
return BaseResponse().make()
def handle_user_info_update_request(self, data: Dict) -> Dict:
async def handle_user_info_update_request(self, data: Dict) -> Dict:
req = UserInfoUpdateRequest(data)
user_id = self.data.profile.profile_to_aime_user(req.profileId)
@ -989,7 +989,7 @@ class WaccaBase:
return BaseResponse().make()
def handle_user_vip_get_request(self, data: Dict) -> Dict:
async def handle_user_vip_get_request(self, data: Dict) -> Dict:
req = UserVipGetRequest(data)
resp = UserVipGetResponse()
@ -1021,7 +1021,7 @@ class WaccaBase:
return resp.make()
def handle_user_vip_start_request(self, data: Dict) -> Dict:
async def handle_user_vip_start_request(self, data: Dict) -> Dict:
req = UserVipStartRequest(data)
profile = self.data.profile.get_profile(req.profileId)

View File

@ -1,6 +1,6 @@
import yaml
import jinja2
from twisted.web.http import Request
from starlette.requests import Request
from os import path
from twisted.web.server import Session

View File

@ -1,16 +1,19 @@
from starlette.routing import Route
import yaml
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
import logging
import json
from hashlib import md5
from twisted.web.http import Request
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from typing import Dict, Tuple, List
from os import path
import traceback
import sys
from core import CoreConfig, Utils
from core.title import BaseServlet
from .config import WaccaConfig
from .config import WaccaConfig
from .const import WaccaConstants
@ -23,7 +26,7 @@ from .handlers.base import BaseResponse, BaseRequest
from .handlers.helpers import Version
class WaccaServlet:
class WaccaServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
self.game_cfg = WaccaConfig()
@ -62,15 +65,12 @@ class WaccaServlet:
coloredlogs.install(
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
)
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return (
[],
[
("render_POST", "/WaccaServlet/api/{api}/{endpoint}", {}),
("render_POST", "/WaccaServlet/api/{api}/{branch}/{endpoint}", {})
]
)
def get_routes(self) -> List[Route]:
return [
Route("/WaccaServlet/api/{api:str}/{endpoint:str}", self.render_POST, methods=['POST']),
Route("/WaccaServlet/api/{api:str}/{branch:str}/{endpoint:str}", self.render_POST, methods=['POST']),
]
@classmethod
def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool:
@ -88,22 +88,22 @@ class WaccaServlet:
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port(self.core_cfg) != 80:
return (
f"http://{self.core_cfg.title.hostname}:{Utils.get_title_port(self.core_cfg)}/WaccaServlet",
self.core_cfg.title.hostname,
f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/WaccaServlet",
self.core_cfg.server.hostname,
)
return (f"http://{self.core_cfg.title.hostname}/WaccaServlet", self.core_cfg.title.hostname)
return (f"http://{self.core_cfg.server.hostname}/WaccaServlet", self.core_cfg.server.hostname)
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
async def render_POST(self, request: Request) -> bytes:
def end(resp: Dict) -> bytes:
hash = md5(json.dumps(resp, ensure_ascii=False).encode()).digest()
request.responseHeaders.addRawHeader(b"X-Wacca-Hash", hash.hex().encode())
return json.dumps(resp).encode()
return JSONResponse(resp, headers=["X-Wacca-Hash", hash.hex()])
api = matchers['api']
branch = matchers.get('branch', '')
endpoint = matchers['endpoint']
api = request.path_params.get('api', '')
branch = request.path_params.get('branch', '')
endpoint = request.path_params.get('endpoint', '')
client_ip = Utils.get_ip_addr(request)
bod = await request.body()
if branch:
url_path = f"{api}/{branch}/{endpoint}"
@ -114,13 +114,13 @@ class WaccaServlet:
func_to_find = f"handle_{api}_{endpoint}_request"
try:
req_json = json.loads(request.content.getvalue())
req_json = json.loads(bod)
version_full = Version(req_json["appVersion"])
req = BaseRequest(req_json)
except KeyError as e:
self.logger.error(
f"Failed to parse request to {request.content.getvalue()} -> Missing required value {e}"
f"Failed to parse request to {bod} -> Missing required value {e}"
)
resp = BaseResponse()
resp.status = 1
@ -129,7 +129,7 @@ class WaccaServlet:
except Exception as e:
self.logger.error(
f"Failed to parse request to {url_path} -> {request.content.getvalue()} -> {e}"
f"Failed to parse request to {url_path} -> {bod} -> {e}"
)
resp = BaseResponse()
resp.status = 1
@ -176,7 +176,7 @@ class WaccaServlet:
try:
handler = getattr(self.versions[internal_ver], func_to_find)
resp = handler(req_json)
resp = await handler(req_json)
self.logger.debug(f"{req.appVersion} response {resp}")
return end(resp)

View File

@ -37,13 +37,13 @@ class WaccaLily(WaccaS):
(210003, 0),
]
def handle_advertise_GetNews_request(self, data: Dict) -> Dict:
async def handle_advertise_GetNews_request(self, data: Dict) -> Dict:
resp = GetNewsResponseV3()
return resp.make()
def handle_user_status_create_request(self, data: Dict) -> Dict:
async def handle_user_status_create_request(self, data: Dict) -> Dict:
req = UserStatusCreateRequest(data)
ret = super().handle_user_status_create_request(data)
ret = await super().handle_user_status_create_request(data)
new_user = self.data.profile.get_profile(aime_id=req.aimeId)
@ -64,7 +64,7 @@ class WaccaLily(WaccaS):
return ret
def handle_user_status_get_request(self, data: Dict) -> Dict:
async def handle_user_status_get_request(self, data: Dict) -> Dict:
req = UserStatusGetRequest(data)
resp = UserStatusGetV2Response()
@ -145,7 +145,7 @@ class WaccaLily(WaccaS):
return resp.make()
def handle_user_status_login_request(self, data: Dict) -> Dict:
async def handle_user_status_login_request(self, data: Dict) -> Dict:
req = UserStatusLoginRequest(data)
resp = UserStatusLoginResponseV2()
is_consec_day = True
@ -189,7 +189,7 @@ class WaccaLily(WaccaS):
return resp.make()
def handle_user_status_getDetail_request(self, data: Dict) -> Dict:
async def handle_user_status_getDetail_request(self, data: Dict) -> Dict:
req = UserStatusGetDetailRequest(data)
if req.appVersion.minor >= 53:
resp = UserStatusGetDetailResponseV3()
@ -440,10 +440,10 @@ class WaccaLily(WaccaS):
return resp.make()
def handle_user_info_getMyroom_request(self, data: Dict) -> Dict:
async def handle_user_info_getMyroom_request(self, data: Dict) -> Dict:
return UserInfogetMyroomResponseV2().make()
def handle_user_status_update_request(self, data: Dict) -> Dict:
async def handle_user_status_update_request(self, data: Dict) -> Dict:
super().handle_user_status_update_request(data)
req = UserStatusUpdateRequestV2(data)
self.data.profile.update_profile_lastplayed(

View File

@ -39,7 +39,7 @@ class WaccaLilyR(WaccaLily):
(210003, 0),
]
def handle_housing_start_request(self, data: Dict) -> Dict:
async def handle_housing_start_request(self, data: Dict) -> Dict:
req = HousingStartRequestV2(data)
allnet_region_id = None
@ -71,9 +71,9 @@ class WaccaLilyR(WaccaLily):
resp = HousingStartResponseV1(region_id)
return resp.make()
def handle_user_status_create_request(self, data: Dict) -> Dict:
async def handle_user_status_create_request(self, data: Dict) -> Dict:
req = UserStatusCreateRequest(data)
resp = super().handle_user_status_create_request(data)
resp = await super().handle_user_status_create_request(data)
self.data.item.put_item(
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210054
@ -102,5 +102,5 @@ class WaccaLilyR(WaccaLily):
return resp
def handle_user_status_logout_request(self, data: Dict) -> Dict:
async def handle_user_status_logout_request(self, data: Dict) -> Dict:
return BaseResponse().make()

View File

@ -47,12 +47,12 @@ class WaccaReverse(WaccaLilyR):
(310006, 0),
]
def handle_user_status_login_request(self, data: Dict) -> Dict:
resp = super().handle_user_status_login_request(data)
async def handle_user_status_login_request(self, data: Dict) -> Dict:
resp = await super().handle_user_status_login_request(data)
resp["params"].append([])
return resp
def handle_user_status_getDetail_request(self, data: Dict) -> Dict:
async def handle_user_status_getDetail_request(self, data: Dict) -> Dict:
req = UserStatusGetDetailRequest(data)
resp = UserStatusGetDetailResponseV4()
@ -305,9 +305,9 @@ class WaccaReverse(WaccaLilyR):
return resp.make()
def handle_user_status_create_request(self, data: Dict) -> Dict:
async def handle_user_status_create_request(self, data: Dict) -> Dict:
req = UserStatusCreateRequest(data)
resp = super().handle_user_status_create_request(data)
resp = await super().handle_user_status_create_request(data)
self.data.item.put_item(
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310001

View File

@ -31,6 +31,6 @@ class WaccaS(WaccaBase):
super().__init__(cfg, game_cfg)
self.version = WaccaConstants.VER_WACCA_S
def handle_advertise_GetNews_request(self, data: Dict) -> Dict:
async def handle_advertise_GetNews_request(self, data: Dict) -> Dict:
resp = GetNewsResponseV2()
return resp.make()