begin move

This commit is contained in:
Kevin Trocolli committed 2024-01-09 03:07:04 -05:00
1 parent b056ff218d
commit 14fa0f5e8e
82 files changed
+1661 -1690

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
from core.config import CoreConfig from core.config import CoreConfig
from core.allnet import AllnetServlet from core.allnet import AllnetServlet, BillingServlet
from core.aimedb import AimedbFactory from core.aimedb import AimedbServlette
from core.title import TitleServlet from core.title import TitleServlet
from core.utils import Utils from core.utils import Utils
from core.mucha import MuchaServlet from core.mucha import MuchaServlet
+1 -1
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) 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) 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)}") raise ADBHeaderException(f"Length is incorrect! Expect {head.length}, got {len(data)}")
return head return head
+77 -85
View File
@@ -2,8 +2,8 @@ from twisted.internet.protocol import Factory, Protocol
import logging, coloredlogs import logging, coloredlogs
from Crypto.Cipher import AES from Crypto.Cipher import AES
import struct import struct
from typing import Dict, Tuple, Callable, Union from typing import Dict, Tuple, Callable, Union, Optional
from typing_extensions import Final import asyncio
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig from core.config import CoreConfig
@@ -11,15 +11,37 @@ from core.utils import create_sega_auth_key
from core.data import Data from core.data import Data
from .adb_handlers import * from .adb_handlers import *
class AimedbServlette():
class AimedbProtocol(Protocol):
request_list: Dict[int, Tuple[Callable[[bytes, int], Union[ADBBaseResponse, bytes]], int, str]] = {} request_list: Dict[int, Tuple[Callable[[bytes, int], Union[ADBBaseResponse, bytes]], int, str]] = {}
def __init__(self, core_cfg: CoreConfig) -> None: def __init__(self, core_cfg: CoreConfig) -> None:
self.logger = logging.getLogger("aimedb")
self.config = core_cfg self.config = core_cfg
self.data = Data(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!!!") self.logger.error("!!!KEY NOT SET!!!")
exit(1) exit(1)
@@ -40,27 +62,30 @@ class AimedbProtocol(Protocol):
self.register_handler(0x13, 0x14, self.handle_log_ex, 'aime_log_ex') self.register_handler(0x13, 0x14, self.handle_log_ex, 'aime_log_ex')
self.register_handler(0x64, 0x65, self.handle_hello, 'hello') 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: 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) self.request_list[cmd] = (handler, resp, name)
def append_padding(self, data: bytes): def start(self) -> None:
"""Appends 0s to the end of the data until it's at the correct size""" self.logger.info(f"Start on port {self.config.aimedb.port}")
length = struct.unpack_from("<H", data, 6) asyncio.create_task(asyncio.start_server(self.dataReceived, self.config.server.listen_address, self.config.aimedb.port))
padding_size = length[0] - len(data)
data += bytes(padding_size)
return data
def connectionMade(self) -> None: async def dataReceived(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
self.logger.debug(f"{self.transport.getPeer().host} Connected") 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 connectionLost(self, reason) -> None: async def process_data(self, data: bytes, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> Optional[bytes]:
self.logger.debug( addr = writer.get_extra_info('peername')[0]
f"{self.transport.getPeer().host} Disconnected - {reason.value}"
)
def dataReceived(self, data: bytes) -> None:
cipher = AES.new(self.config.aimedb.key.encode(), AES.MODE_ECB) cipher = AES.new(self.config.aimedb.key.encode(), AES.MODE_ECB)
try: try:
@@ -68,9 +93,9 @@ class AimedbProtocol(Protocol):
except Exception as e: except Exception as e:
self.logger.error(f"Failed to decrypt {data.hex()} because {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: try:
head = ADBHeader.from_data(decrypted) head = ADBHeader.from_data(decrypted)
@@ -79,7 +104,9 @@ class AimedbProtocol(Protocol):
self.logger.error(f"Error parsing ADB header: {e}") self.logger.error(f"Error parsing ADB header: {e}")
try: try:
encrypted = cipher.encrypt(ADBBaseResponse().make()) encrypted = cipher.encrypt(ADBBaseResponse().make())
self.transport.write(encrypted) writer.write(encrypted)
await writer.drain()
return
except Exception as e: except Exception as e:
self.logger.error(f"Failed to encrypt default response because {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: if head.keychip_id == "ABCD1234567" or head.store_id == 0xfff0:
self.logger.warning(f"Request from uninitialized AMLib: {vars(head)}") 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')) handler, resp_code, name = self.request_list.get(head.cmd, (self.handle_default, None, 'default'))
if resp_code is None: if resp_code is None:
self.logger.warning(f"No handler for cmd {hex(head.cmd)}") self.logger.warning(f"No handler for cmd {hex(head.cmd)}")
elif resp_code > 0: 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): if type(resp) == ADBBaseResponse or issubclass(type(resp), ADBBaseResponse):
resp_bytes = resp.make() resp_bytes = resp.make()
if len(resp_bytes) != resp.head.length:
resp_bytes = self.append_padding(resp_bytes)
elif type(resp) == bytes: elif type(resp) == bytes:
resp_bytes = resp resp_bytes = resp
elif resp is None: # Nothing to send, probably a goodbye elif resp is None: # Nothing to send, probably a goodbye
self.logger.warn(f"None return by handler for {name}")
return return
else: 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)}") raise TypeError(f"Unsupported type returned by ADB handler for {name}: {type(resp)}")
try: try:
encrypted = cipher.encrypt(resp_bytes) encrypted = cipher.encrypt(resp_bytes)
self.logger.debug(f"Response {resp_bytes.hex()}") self.logger.debug(f"Response {resp_bytes.hex()}")
self.transport.write(encrypted) writer.write(encrypted)
except Exception as e: except Exception as e:
self.logger.error(f"Failed to encrypt {resp_bytes.hex()} because {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) req = ADBHeader.from_data(data)
return ADBBaseResponse(resp_code, length, 1, req.game_id, req.store_id, req.keychip_id, req.protocol_ver) 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: async def handle_hello(self, data: bytes, resp_code: int) -> ADBBaseResponse:
return self.handle_default(data, resp_code) 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) h = ADBHeader.from_data(data)
if h.protocol_ver >= 0x3030: if h.protocol_ver >= 0x3030:
req = h req = h
@@ -143,26 +175,26 @@ class AimedbProtocol(Protocol):
# We don't currently support campaigns # We don't currently support campaigns
return resp 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) req = ADBLookupRequest(data)
user_id = self.data.card.get_user_id_from_card(req.access_code) 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_banned = self.data.card.get_card_banned(req.access_code)
is_locked = self.data.card.get_card_locked(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: if is_banned and is_locked:
ret.head.status = ADBStatus.BAN_SYS_USER ret.head.status = ADBStatus.BAN_SYS_USER
elif is_banned: elif is_banned:
ret.head.status = ADBStatus.BAN_SYS ret.head.status = ADBStatus.BAN_SYS
elif is_locked: elif is_locked:
ret.head.status = ADBStatus.LOCK_USER ret.head.status = ADBStatus.LOCK_USER
ret = ADBLookupResponse.from_req(req.head, user_id)
self.logger.info( self.logger.info(
f"access_code {req.access_code} -> user_id {ret.user_id}" f"access_code {req.access_code} -> user_id {ret.user_id}"
) )
return ret 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) req = ADBLookupRequest(data)
user_id = self.data.card.get_user_id_from_card(req.access_code) user_id = self.data.card.get_user_id_from_card(req.access_code)
@@ -191,7 +223,7 @@ class AimedbProtocol(Protocol):
return ret 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 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 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) 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. I've never seen this used.
""" """
@@ -239,7 +271,7 @@ class AimedbProtocol(Protocol):
return ADBFelicaLookupResponse.from_req(req.head, ac) 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) req = ADBFelicaLookup2Request(data)
access_code = self.data.card.to_access_code(req.idm) access_code = self.data.card.to_access_code(req.idm)
user_id = self.data.card.get_user_id_from_card(access_code=access_code) user_id = self.data.card.get_user_id_from_card(access_code=access_code)
@@ -263,7 +295,7 @@ class AimedbProtocol(Protocol):
return resp 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) req = ADBCampaignClearRequest(data)
resp = ADBCampaignClearResponse.from_req(req.head) resp = ADBCampaignClearResponse.from_req(req.head)
@@ -271,7 +303,7 @@ class AimedbProtocol(Protocol):
# We don't support campaign stuff # We don't support campaign stuff
return resp 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) req = ADBLookupRequest(data)
user_id = -1 user_id = -1
@@ -305,17 +337,17 @@ class AimedbProtocol(Protocol):
return resp return resp
# TODO: Save these in some capacity, as deemed relevant # 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) req = ADBStatusLogRequest(data)
self.logger.info(f"User {req.aime_id} logged {req.status.name} event") 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) 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) 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}") 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) 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) req = ADBLogExRequest(data)
strs = [] strs = []
self.logger.info(f"Recieved {req.num_logs} or {len(req.logs)} logs") 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}") 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) 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)
+166 -114
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 pytz
import base64 import base64
import zlib import zlib
import json 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 enum import Enum
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from Crypto.Hash import SHA from Crypto.Hash import SHA
from Crypto.Signature import PKCS1_v1_5 from Crypto.Signature import PKCS1_v1_5
from time import strptime from os import path, environ, mkdir, access, W_OK
from os import path
import urllib.parse
import math
from .config import CoreConfig from .config import CoreConfig
from .utils import Utils from .utils import Utils
@@ -91,7 +95,6 @@ class DLI_STATUS(Enum):
class AllnetServlet: class AllnetServlet:
def __init__(self, core_cfg: CoreConfig, cfg_folder: str): def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
super().__init__()
self.config = core_cfg self.config = core_cfg
self.config_folder = cfg_folder self.config_folder = cfg_folder
self.data = Data(core_cfg) self.data = Data(core_cfg)
@@ -126,19 +129,20 @@ class AllnetServlet:
self.logger.error("No games detected!") self.logger.error("No games detected!")
self.logger.info( 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) 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" is_dfi = pragma_header is not None and pragma_header == "DFI"
data = await request.body()
try: try:
if is_dfi: if is_dfi:
req_urlencode = self.from_dfi(request.content.getvalue()) req_urlencode = self.from_dfi(data)
else: else:
req_urlencode = request.content.getvalue().decode() req_urlencode = data
req_dict = self.allnet_req_to_dict(req_urlencode) req_dict = self.allnet_req_to_dict(req_urlencode)
if req_dict is None: if req_dict is None:
@@ -155,7 +159,7 @@ class AllnetServlet:
except AllnetRequestException as e: except AllnetRequestException as e:
if e.message != "": if e.message != "":
self.logger.error(e) self.logger.error(e)
return b"" return PlainTextResponse()
if req.format_ver == 3: if req.format_ver == 3:
resp = AllnetPowerOnResponse3(req.token) resp = AllnetPowerOnResponse3(req.token)
@@ -176,7 +180,7 @@ class AllnetServlet:
resp.stat = ALLNET_STAT.bad_machine.value resp.stat = ALLNET_STAT.bad_machine.value
resp_dict = {k: v for k, v in vars(resp).items() if v is not None} resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
return (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: if machine is not None:
arcade = self.data.arcade.get_arcade(machine["arcade"]) arcade = self.data.arcade.get_arcade(machine["arcade"])
@@ -190,7 +194,7 @@ class AllnetServlet:
resp.stat = ALLNET_STAT.bad_shop.value resp.stat = ALLNET_STAT.bad_shop.value
resp_dict = {k: v for k, v in vars(resp).items() if v is not None} resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
return (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: elif (not arcade["ip"] or arcade["ip"] is None) and self.config.server.strict_ip_checking:
msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)." msg = f"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.stat = ALLNET_STAT.bad_shop.value
resp_dict = {k: v for k, v in vars(resp).items() if v is not None} resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8") return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
country = ( country = (
@@ -245,20 +249,20 @@ class AllnetServlet:
resp.stat = ALLNET_STAT.bad_game.value resp.stat = ALLNET_STAT.bad_game.value
resp_dict = {k: v for k, v in vars(resp).items() if v is not None} resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8") return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
else: else:
self.logger.info( 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}" 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.uri = f"http://{self.config.server.hostname}:{self.config.server.port}/{req.game_id}/{req.ver.replace('.', '')}/"
resp.host = f"{self.config.title.hostname}:{self.config.title.port}" 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_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)) resp_str = urllib.parse.unquote(urllib.parse.urlencode(resp_dict))
self.logger.debug(f"Allnet response: {resp_str}") 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(".", "") int_ver = req.ver.replace(".", "")
@@ -277,18 +281,19 @@ class AllnetServlet:
request.responseHeaders.addRawHeader('Pragma', 'DFI') request.responseHeaders.addRawHeader('Pragma', 'DFI')
return self.to_dfi(resp_str)""" 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) 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" is_dfi = pragma_header is not None and pragma_header == "DFI"
data = await request.body()
try: try:
if is_dfi: if is_dfi:
req_urlencode = self.from_dfi(request.content.getvalue()) req_urlencode = self.from_dfi(data)
else: else:
req_urlencode = request.content.getvalue().decode() req_urlencode = data.decode()
req_dict = self.allnet_req_to_dict(req_urlencode) req_dict = self.allnet_req_to_dict(req_urlencode)
if req_dict is None: if req_dict is None:
@@ -305,7 +310,7 @@ class AllnetServlet:
except AllnetRequestException as e: except AllnetRequestException as e:
if e.message != "": if e.message != "":
self.logger.error(e) self.logger.error(e)
return b"" return PlainTextResponse()
self.logger.info( self.logger.info(
f"DownloadOrder from {request_ip} -> {req.game_id} v{req.ver} serial {req.serial}" 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 not self.config.allnet.allow_online_updates
or not self.config.allnet.update_cfg_folder 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 else: # TODO: Keychip check
if path.exists( if path.exists(
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-app.ini" f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-app.ini"
): ):
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( if path.exists(
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-opt.ini" 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.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}") 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') request.responseHeaders.addRawHeader('Pragma', 'DFI')
return self.to_dfi(res_str)""" return self.to_dfi(res_str)"""
return res_str return PlainTextResponse(res_str)
def handle_dlorder_ini(self, request: Request, match: Dict) -> bytes: async def handle_dlorder_ini(self, request: Request) -> bytes:
if "file" not in match: req_file = request.path_params.get("file", "").replace("%0A", "")
return b""
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}"): if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"):
self.logger.info(f"Request for DL INI file {req_file} from {Utils.get_ip_addr(request)} successful") self.logger.info(f"Request for DL INI file {req_file} from {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}") self.data.base.log_event("allnet", "DLORDER_INI_SENT", logging.INFO, f"{Utils.get_ip_addr(request)} successfully recieved {req_file}")
return open( return PlainTextResponse(open(
f"{self.config.allnet.update_cfg_folder}/{req_file}", "rb" f"{self.config.allnet.update_cfg_folder}/{req_file}", "r"
).read() ).read())
self.logger.info(f"DL INI File {req_file} not found") 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: async def handle_dlorder_report(self, request: Request) -> bytes:
req_raw = request.content.getvalue() req_raw = await request.body()
client_ip = Utils.get_ip_addr(request) client_ip = Utils.get_ip_addr(request)
try: try:
req_dict: Dict = json.loads(req_raw) req_dict: Dict = json.loads(req_raw)
except Exception as e: except Exception as e:
self.logger.warning(f"Failed to parse DL Report: {e}") self.logger.warning(f"Failed to parse DL Report: {e}")
return "NG" return PlainTextResponse("NG")
dl_data_type = DLIMG_TYPE.app dl_data_type = DLIMG_TYPE.app
dl_data = req_dict.get("appimage", {}) dl_data = req_dict.get("appimage", {})
@@ -374,13 +379,13 @@ class AllnetServlet:
if dl_data is None or not dl_data: 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") 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) rep = DLReport(dl_data, dl_data_type)
if not rep.validate(): if not rep.validate():
self.logger.warning(f"Failed to parse DL Report: Invalid format - {rep.err}") 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}:"\ msg = f"{rep.serial} @ {client_ip} reported {rep.rep_type.name} download state {rep.rf_state.name} for {rep.gd} v{rep.dav}:"\
f" {rep.tdsc}/{rep.tsc} segments downloaded for working files {rep.wfl} with {rep.dfl if rep.dfl else 'none'} complete." f" {rep.tdsc}/{rep.tsc} segments downloaded for working files {rep.wfl} with {rep.dfl if rep.dfl else 'none'} complete."
@@ -388,10 +393,10 @@ class AllnetServlet:
self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data) self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data)
self.logger.info(msg) self.logger.info(msg)
return "OK" return PlainTextResponse("OK")
def handle_loaderstaterecorder(self, request: Request, match: Dict) -> bytes: async def handle_loaderstaterecorder(self, request: Request) -> bytes:
req_data = request.content.getvalue() req_data = await request.body()
sections = req_data.decode("utf-8").split("\r\n") sections = req_data.decode("utf-8").split("\r\n")
req_dict = dict(urllib.parse.parse_qsl(sections[0])) req_dict = dict(urllib.parse.parse_qsl(sections[0]))
@@ -403,18 +408,94 @@ class AllnetServlet:
ip = Utils.get_ip_addr(request) 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: 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})") 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: async def handle_alive(self, request: Request) -> bytes:
return "OK".encode() return PlainTextResponse("OK")
def handle_billing_request(self, request: Request, _: Dict): async def handle_naomitest(self, request: Request) -> bytes:
req_raw = request.content.getvalue() self.logger.info(f"Ping from {Utils.get_ip_addr(request)}")
return PlainTextResponse("naomi ok")
if request.getHeader('Content-Type') == "application/octet-stream": 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.headers.get('Content-Type', '') == "application/octet-stream":
req_unzip = zlib.decompressobj(-zlib.MAX_WBITS).decompress(req_raw) req_unzip = zlib.decompressobj(-zlib.MAX_WBITS).decompress(req_raw)
else: else:
req_unzip = req_raw req_unzip = req_raw
@@ -423,8 +504,8 @@ class AllnetServlet:
request_ip = Utils.get_ip_addr(request) request_ip = Utils.get_ip_addr(request)
if req_dict is None: if req_dict is None:
self.logger.error(f"Failed to parse request {request.content.getvalue()}") self.logger.error(f"Failed to parse request {req_raw}")
return b"" return PlainTextResponse()
self.logger.debug(f"request {req_dict}") self.logger.debug(f"request {req_dict}")
@@ -436,7 +517,7 @@ class AllnetServlet:
req = BillingInfo(req_dict[0]) req = BillingInfo(req_dict[0])
except KeyError as e: except KeyError as e:
self.logger.error(f"Billing request failed to parse: {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)): for x in range(1, len(req_dict)):
if not req_dict[x]: if not req_dict[x]:
@@ -467,7 +548,7 @@ class AllnetServlet:
) )
self.logger.warning(msg) 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 = ( msg = (
f"Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount " f"Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount "
@@ -496,7 +577,6 @@ class AllnetServlet:
# TODO: playhistory # TODO: playhistory
#resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig)
resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig, req.requestno, req.protocolver) resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig, req.requestno, req.protocolver)
resp_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\r\n" 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)}") self.logger.debug(f"response {vars(resp)}")
if req.traceleft > 0: if req.traceleft > 0:
self.logger.info(f"Requesting 20 more of {req.traceleft} unsent tracelogs") self.logger.info(f"Requesting 20 more of {req.traceleft} unsent tracelogs")
return f"result=6&waittime=0&linelimit=20\r\n".encode() return PlainTextResponse("result=6&waittime=0&linelimit=20\r\n")
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(resp_str)
class AllnetPowerOnRequest: class AllnetPowerOnRequest:
def __init__(self, req: Dict) -> None: def __init__(self, req: Dict) -> None:
@@ -613,7 +646,6 @@ class AllnetPowerOnResponse3(AllnetPowerOnResponse):
self.minute = None self.minute = None
self.second = None self.second = None
class AllnetPowerOnResponse2(AllnetPowerOnResponse): class AllnetPowerOnResponse2(AllnetPowerOnResponse):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
@@ -623,7 +655,6 @@ class AllnetPowerOnResponse2(AllnetPowerOnResponse):
self.timezone = "+09:00" self.timezone = "+09:00"
self.res_class = "PowerOnResponseV2" self.res_class = "PowerOnResponseV2"
class AllnetDownloadOrderRequest: class AllnetDownloadOrderRequest:
def __init__(self, req: Dict) -> None: def __init__(self, req: Dict) -> None:
self.game_id = req.get("game_id", "") self.game_id = req.get("game_id", "")
@@ -631,7 +662,6 @@ class AllnetDownloadOrderRequest:
self.serial = req.get("serial", "") self.serial = req.get("serial", "")
self.encode = req.get("encode", "") self.encode = req.get("encode", "")
class AllnetDownloadOrderResponse: class AllnetDownloadOrderResponse:
def __init__(self, stat: int = 1, serial: str = "", uri: str = "") -> None: def __init__(self, stat: int = 1, serial: str = "", uri: str = "") -> None:
self.stat = stat self.stat = stat
@@ -781,7 +811,6 @@ class BillingResponse:
# playhistory -> YYYYMM/C:... # playhistory -> YYYYMM/C:...
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period # YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
class AllnetRequestException(Exception): class AllnetRequestException(Exception):
def __init__(self, message="") -> None: def __init__(self, message="") -> None:
self.message = message self.message = message
@@ -849,3 +878,26 @@ class DLReport:
return False return False
return True 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
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)
+62 -84
View File
@@ -1,17 +1,49 @@
import logging, os import logging, os
from typing import Any from typing import Any
class ServerConfig: class ServerConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def listen_address(self) -> str: def listen_address(self) -> str:
"""
Address Artemis will bind to and listen on
"""
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
self.__config, "core", "server", "listen_address", default="127.0.0.1" 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 @property
def allow_user_registration(self) -> bool: def allow_user_registration(self) -> bool:
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
@@ -43,9 +75,13 @@ class ServerConfig:
) )
@property @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( return CoreConfig.get_config_field(
self.__config, "core", "server", "threading", default=False self.__config, "core", "title", "proxy_port", default=0
) )
@property @property
@@ -66,7 +102,6 @@ class ServerConfig:
self.__config, "core", "server", "strict_ip_checking", default=False self.__config, "core", "server", "strict_ip_checking", default=False
) )
class TitleConfig: class TitleConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config 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 @property
def reboot_start_time(self) -> str: def reboot_start_time(self) -> str:
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
self.__config, "core", "title", "reboot_start_time", default="" self.__config, "core", "title", "reboot_start_time", default="04:00"
) )
@property @property
def reboot_end_time(self) -> str: def reboot_end_time(self) -> str:
return CoreConfig.get_config_field( 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: class DatabaseConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config 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 @property
def enable_memcached(self) -> bool: def enable_memcached(self) -> bool:
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
@@ -198,21 +192,14 @@ class DatabaseConfig:
self.__config, "core", "database", "memcached_host", default="localhost" self.__config, "core", "database", "memcached_host", default="localhost"
) )
class FrontendConfig: class FrontendConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def enable(self) -> int: def standalone(self) -> int:
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
self.__config, "core", "frontend", "enable", default=False self.__config, "core", "frontend", "standalone", default=True
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "frontend", "port", default=8090
) )
@property @property
@@ -223,6 +210,11 @@ class FrontendConfig:
) )
) )
@property
def secret(self) -> str:
CoreConfig.get_config_field(
self.__config, "core", "frontend", "secret", default=""
)
class AllnetConfig: class AllnetConfig:
def __init__(self, parent_config: "CoreConfig") -> None: 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 @property
def allow_online_updates(self) -> int: def allow_online_updates(self) -> int:
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
@@ -260,11 +240,24 @@ class AllnetConfig:
self.__config, "core", "allnet", "update_cfg_folder", default="" self.__config, "core", "allnet", "update_cfg_folder", default=""
) )
class BillingConfig: class BillingConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config 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 @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
@@ -289,7 +282,6 @@ class BillingConfig:
self.__config, "core", "billing", "signing_key", default="cert/billing.key" self.__config, "core", "billing", "signing_key", default="cert/billing.key"
) )
class AimedbConfig: class AimedbConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config self.__config = parent_config
@@ -326,17 +318,10 @@ class AimedbConfig:
self.__config, "core", "aimedb", "id_lifetime_seconds", default=86400 self.__config, "core", "aimedb", "id_lifetime_seconds", default=86400
) )
class MuchaConfig: class MuchaConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property
def enable(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "mucha", "enable", default=False
)
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel( 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): class CoreConfig(dict):
def __init__(self) -> None: def __init__(self) -> None:
self.server = ServerConfig(self) self.server = ServerConfig(self)
+1
View File
@@ -0,0 +1 @@
Generic single-database configuration.
+89
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
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()
+24
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"}
+11 -2
View File
@@ -2,7 +2,9 @@ import logging, coloredlogs
from typing import Any, Dict, List from typing import Any, Dict, List
from twisted.web import resource from twisted.web import resource
from twisted.web.util import redirectTo 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 logging.handlers import TimedRotatingFileHandler
from twisted.web.server import Session from twisted.web.server import Session
from zope.interface import Interface, Attribute, implementer from zope.interface import Interface, Attribute, implementer
@@ -98,9 +100,16 @@ class FrontendServlet(resource.Resource):
self.putChild(b"game", fe_game) self.putChild(b"game", fe_game)
self.logger.info( 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): def render_GET(self, request):
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.uri.decode()}") self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.uri.decode()}")
template = self.environment.get_template("core/frontend/index.jinja") template = self.environment.get_template("core/frontend/index.jinja")
+7 -6
View File
@@ -2,7 +2,7 @@ from typing import Dict, Any, Optional, List
import logging, coloredlogs import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from twisted.web import resource from twisted.web import resource
from twisted.web.http import Request from starlette.requests import Request
from datetime import datetime from datetime import datetime
from Crypto.Cipher import Blowfish from Crypto.Cipher import Blowfish
import pytz import pytz
@@ -12,7 +12,7 @@ from .utils import Utils
from .title import TitleServlet from .title import TitleServlet
class MuchaServlet: class MuchaServlet:
mucha_registry: List[str] = [] mucha_registry: Dict[str, str] = {}
def __init__(self, cfg: CoreConfig, cfg_dir: str) -> None: def __init__(self, cfg: CoreConfig, cfg_dir: str) -> None:
self.config = cfg self.config = cfg
self.config_dir = cfg_dir self.config_dir = cfg_dir
@@ -39,11 +39,12 @@ class MuchaServlet:
for _, mod in TitleServlet.title_registry.items(): for _, mod in TitleServlet.title_registry.items():
if hasattr(mod, "get_mucha_info"): 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 self.config, self.config_dir
) )
if enabled: 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") 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()}") self.logger.debug(f"Decrypt SN to {sn_decrypt.hex()}")
resp = MuchaAuthResponse( 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)}") self.logger.debug(f"Mucha response {vars(resp)}")
@@ -100,7 +101,7 @@ class MuchaServlet:
self.logger.warning(f"Unknown gameCd {req.gameCd}") self.logger.warning(f"Unknown gameCd {req.gameCd}")
return b"RESULTS=000" 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)}") self.logger.debug(f"Mucha response {vars(resp)}")
+20 -20
View File
@@ -1,7 +1,9 @@
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
import logging, coloredlogs import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler 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.config import CoreConfig
from core.data import Data from core.data import Data
@@ -28,18 +30,16 @@ class BaseServlet:
""" """
return False 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 """Called during boot to get all matcher endpoints this title servlet handles
Returns: 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, List[Route]: A list of Routes, WebSocketRoutes, or similar classes
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.
""" """
return ( return [
[("render_GET", "/{game}/{version}/{endpoint}", {'game': R'S...'})], Route("/{game}/{version}/{endpoint}", self.render_POST, methods=["POST"]),
[("render_POST", "/{game}/{version}/{endpoint}", {'game': R'S...'})] Route("/{game}/{version}/{endpoint}", self.render_GET, methods=["GET"]),
) ]
def setup(self) -> None: def setup(self) -> None:
"""Called once during boot, should contain any additional setup the handler must do, such as starting any sub-services """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 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: 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 """Called once during boot to check if this game is a mucha game
Args: Args:
@@ -72,15 +72,15 @@ class BaseServlet:
Returns: Returns:
Tuple[bool, str]: Tuple where offset 0 is true if the game is enabled, false otherwise, and offset 1 is the game CD 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: async def render_POST(self, request: Request) -> bytes:
self.logger.warn(f"{game_code} Does not dispatch POST") self.logger.warn(f"Game Does not dispatch POST")
return None return Response()
def render_GET(self, request: Request, game_code: str, matchers: Dict) -> bytes: async def render_GET(self, request: Request) -> bytes:
self.logger.warn(f"{game_code} Does not dispatch GET") self.logger.warn(f"Game Does not dispatch GET")
return None return Response()
class TitleServlet: class TitleServlet:
title_registry: Dict[str, BaseServlet] = {} 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.error(f"{folder} missing game_code or index in __init__.py, or is_game_enabled in index")
self.logger.info( 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: def render_GET(self, request: Request, endpoints: dict) -> bytes:
+3 -23
View File
@@ -1,6 +1,6 @@
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from types import ModuleType from types import ModuleType
from twisted.web.http import Request from starlette.requests import Request
import logging import logging
import importlib import importlib
from os import walk from os import walk
@@ -34,36 +34,16 @@ class Utils:
@classmethod @classmethod
def get_ip_addr(cls, req: Request) -> str: def get_ip_addr(cls, req: Request) -> str:
return ( return req.headers.get("x-forwarded-for", req.client.host)
req.getAllHeaders()[b"x-forwarded-for"].decode()
if b"x-forwarded-for" in req.getAllHeaders()
else req.getClientAddress().host
)
@classmethod @classmethod
def get_title_port(cls, cfg: CoreConfig): def get_title_port(cls, cfg: CoreConfig):
if cls.real_title_port is not None: return cls.real_title_port if cls.real_title_port is not None: return cls.real_title_port
if cfg.title.port == 0: cls.real_title_port = cfg.server.proxy_port if cfg.server.is_using_proxy else cfg.server.port
cls.real_title_port = cfg.allnet.port
else:
cls.real_title_port = cfg.title.port
return cls.real_title_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]: 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) logger = logging.getLogger(err_logger)
try: try:
+14 -51
View File
@@ -1,9 +1,11 @@
import yaml #!/usr/bin/env python3
import argparse import argparse
import logging import logging
from core.config import CoreConfig from os import mkdir, path, access, W_OK
from core.data import Data import yaml
from os import path, mkdir, access, W_OK import asyncio
from core import Data, CoreConfig
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Database utilities") parser = argparse.ArgumentParser(description="Database utilities")
@@ -16,19 +18,9 @@ if __name__ == "__main__":
type=str, type=str,
help="Version of the database to upgrade/rollback to", 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("--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("--access_code", "-a", type=str, help="Access code for new/transfer user", default="00000000000000000000")
parser.add_argument("--new_ac", "-n", type=str, help="Access code to transfer to") parser.add_argument("action", type=str, help="create, upgrade, create-owner")
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"
)
args = parser.parse_args() args = parser.parse_args()
cfg = CoreConfig() cfg = CoreConfig()
@@ -51,41 +43,12 @@ if __name__ == "__main__":
if args.action == "create": if args.action == "create":
data.create_database() data.create_database()
elif args.action == "recreate": elif args.action == "upgrade":
data.recreate_database() data.schema_upgrade(args.version)
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 == "create-owner": 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": else:
data.migrate_card(args.old_ac, args.new_ac, args.force) logging.getLogger("database").info(f"Unknown action {args.action}")
elif args.action == "cleanup":
data.delete_hanging_users()
elif args.action == "version":
data.show_versions()
data.logger.info("Done")
+10 -15
View File
@@ -1,26 +1,24 @@
server: 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_user_registration: True
allow_unregistered_serials: True allow_unregistered_serials: True
name: "ARTEMiS" name: "ARTEMiS"
is_develop: True is_develop: True
is_using_proxy: False is_using_proxy: False
threading: False proxy_port: 0
log_dir: "logs" log_dir: "logs"
check_arcade_ip: False check_arcade_ip: False
strict_ip_checking: False strict_ip_checking: False
title: title:
loglevel: "info" 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_start_time: "04:00"
reboot_end_time: "05:00" reboot_end_time: "05:00"
ssl_key: "cert/title.key"
database: database:
host: "localhost" host: "localhost"
username: "aime" username: "aime"
@@ -30,23 +28,22 @@ database:
protocol: "mysql" protocol: "mysql"
sha2_password: False sha2_password: False
loglevel: "warn" loglevel: "warn"
user_table_autoincrement_start: 10000
enable_memcached: True enable_memcached: True
memcached_host: "localhost" memcached_host: "localhost"
frontend: frontend:
enable: False standalone: True
port: 8090
loglevel: "info" loglevel: "info"
secret: ""
allnet: allnet:
loglevel: "info" loglevel: "info"
port: 80
ip_check: False
allow_online_updates: False allow_online_updates: False
update_cfg_folder: "" update_cfg_folder: ""
billing: billing:
standalone: True
loglevel: "info"
port: 8443 port: 8443
ssl_key: "cert/server.key" ssl_key: "cert/server.key"
ssl_cert: "cert/server.pem" ssl_cert: "cert/server.pem"
@@ -60,6 +57,4 @@ aimedb:
id_lifetime_seconds: 86400 id_lifetime_seconds: 86400
mucha: mucha:
enable: False
hostname: "localhost"
loglevel: "info" loglevel: "info"
+63 -306
View File
@@ -1,335 +1,92 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
from typing import Dict
import yaml import yaml
from os import path, mkdir, access, W_OK from os import path, mkdir, access, W_OK, environ
from core import * import uvicorn
import logging
import asyncio
from twisted.web import server, resource from core import CoreConfig, AimedbServlette
from twisted.internet import reactor, endpoints
from twisted.web.http import Request
from routes import Mapper
from threading import Thread
class HttpDispatcher(resource.Resource): async def launch_main(cfg: CoreConfig, ssl: bool) -> None:
def __init__(self, cfg: CoreConfig, config_dir: str): if ssl:
super().__init__() server_cfg = uvicorn.Config(
self.config = cfg "core.app:app",
self.isLeaf = True host=cfg.server.listen_address,
self.map_get = Mapper() port=cfg.server.port if args.port == 0 else args.port,
self.map_post = Mapper() reload=cfg.server.is_develop,
self.logger = logging.getLogger("core") log_level="info" if cfg.server.is_develop else "critical",
ssl_version=3,
self.title = TitleServlet(cfg, config_dir) ssl_certfile=cfg.server.ssl_cert,
self.allnet = AllnetServlet(cfg, config_dir) ssl_keyfile=cfg.server.ssl_key
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"]),
) )
else:
self.map_post.connect( server_cfg = uvicorn.Config(
"allnet_downloadorder_report", "core.app:app",
"/report-api/Report", host=cfg.server.listen_address,
controller="allnet", port=cfg.server.port if args.port == 0 else args.port,
action="handle_dlorder_report", reload=cfg.server.is_develop,
conditions=dict(method=["POST"]), log_level="info" if cfg.server.is_develop else "critical"
) )
server = uvicorn.Server(server_cfg)
await server.serve()
self.map_get.connect( async def launch_billing(cfg: CoreConfig) -> None:
"allnet_ping", server_cfg = uvicorn.Config(
"/naomitest.html", "core.allnet:app",
controller="allnet", host=cfg.server.listen_address,
action="handle_naomitest", port=cfg.billing.port,
conditions=dict(method=["GET"]), reload=cfg.server.is_develop,
) log_level="info" if cfg.server.is_develop else "critical",
self.map_post.connect( ssl_version=3,
"allnet_poweron", ssl_certfile=cfg.billing.ssl_cert,
"/sys/servlet/PowerOn", ssl_keyfile=cfg.billing.ssl_key
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"]),
) )
server = uvicorn.Server(server_cfg)
if cfg.billing.standalone:
await server.serve()
else:
while True:
pass
# 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( async def launcher(cfg: CoreConfig, ssl: bool) -> None:
"mucha_boardauth", AimedbServlette(cfg).start()
"/mucha_front/boardauth.do", done, pending = await asyncio.wait(
controller="mucha", [
action="handle_boardauth", asyncio.create_task(launch_main(cfg, ssl)),
conditions=dict(method=["POST"]), asyncio.create_task(launch_billing(cfg)),
)
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() return_when=asyncio.FIRST_COMPLETED,
)
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""
logging.getLogger("core").info("Shutdown")
for pending_task in pending:
pending_task.cancel("Another service died, server is shutting down")
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="ARTEMiS main entry point") parser = argparse.ArgumentParser(description="Artemis main entry point")
parser.add_argument( parser.add_argument(
"--config", "-c", type=str, default="config", help="Configuration folder" "--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() args = parser.parse_args()
if not path.exists(f"{args.config}/core.yaml"): if not path.exists(f"{args.config}/core.yaml"):
print( 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() cfg: CoreConfig = CoreConfig()
if path.exists(f"{args.config}/core.yaml"): if path.exists(f"{args.config}/core.yaml"):
cfg.update(yaml.safe_load(open(f"{args.config}/core.yaml"))) cfg.update(yaml.safe_load(open(f"{args.config}/core.yaml")))
if not path.exists(cfg.server.log_dir): environ["ARTEMIS_CFG_DIR"] = args.config
mkdir(cfg.server.log_dir)
if not access(cfg.server.log_dir, W_OK): asyncio.run(launcher(cfg, args.ssl))
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()
+1 -1
View File
@@ -1,4 +1,4 @@
# vim: set fileencoding=utf-8 #!/usr/bin/env python3
import argparse import argparse
import re import re
import os import os
+4 -4
View File
@@ -1,6 +1,5 @@
mypy mypy
wheel wheel
twisted
pytz pytz
pyyaml pyyaml
sqlalchemy==1.4.46 sqlalchemy==1.4.46
@@ -12,10 +11,11 @@ inflection
coloredlogs coloredlogs
pylibmc; platform_system != "Windows" pylibmc; platform_system != "Windows"
wacky wacky
Routes
bcrypt bcrypt
jinja2 jinja2
protobuf protobuf
autobahn
pillow pillow
pyjwt pyjwt==2.8.0
websockets
starlette
asyncio
+2 -2
View File
@@ -11,7 +11,7 @@ class ChuniAir(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AIR self.version = ChuniConstants.VER_CHUNITHM_AIR
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.10.00" ret["gameSetting"]["dataVersion"] = "1.10.00"
return ret return ret
+2 -2
View File
@@ -11,7 +11,7 @@ class ChuniAirPlus(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AIR_PLUS self.version = ChuniConstants.VER_CHUNITHM_AIR_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.15.00" ret["gameSetting"]["dataVersion"] = "1.15.00"
return ret return ret
+2 -2
View File
@@ -13,7 +13,7 @@ class ChuniAmazon(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AMAZON self.version = ChuniConstants.VER_CHUNITHM_AMAZON
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.30.00" ret["gameSetting"]["dataVersion"] = "1.30.00"
return ret return ret
+2 -2
View File
@@ -13,7 +13,7 @@ class ChuniAmazonPlus(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS self.version = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.35.00" ret["gameSetting"]["dataVersion"] = "1.35.00"
return ret return ret
+43 -43
View File
@@ -22,7 +22,7 @@ class ChuniBase:
self.game = ChuniConstants.GAME_CODE self.game = ChuniConstants.GAME_CODE
self.version = ChuniConstants.VER_CHUNITHM 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 Handles the login bonus logic, required for the game because
getUserLoginBonus gets called after getUserItem and therefore the getUserLoginBonus gets called after getUserItem and therefore the
@@ -119,11 +119,11 @@ class ChuniBase:
return {"returnCode": 1} 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"]}) # self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]})
return {"returnCode": 1} 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) game_charge_list = self.data.static.get_enabled_charges(self.version)
if game_charge_list is None or len(game_charge_list) == 0: if game_charge_list is None or len(game_charge_list) == 0:
@@ -145,7 +145,7 @@ class ChuniBase:
) )
return {"length": len(charges), "gameChargeList": charges} 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) game_events = self.data.static.get_enabled_events(self.version)
if game_events is None or len(game_events) == 0: if game_events is None or len(game_events) == 0:
@@ -177,10 +177,10 @@ class ChuniBase:
"gameEventList": event_list, "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": []} 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 { return {
"type": data["type"], "type": data["type"],
"length": 1, "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) rankings = self.data.score.get_rankings(self.version)
return {"type": data["type"], "gameRankingList": rankings} 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": []} 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 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 == "": if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
reboot_start = datetime.strftime( reboot_start = datetime.strftime(
@@ -240,7 +240,7 @@ class ChuniBase:
"isDumpUpload": "false", "isDumpUpload": "false",
"isAou": "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( user_activity_list = self.data.profile.get_profile_activity(
data["userId"], data["kind"] data["userId"], data["kind"]
) )
@@ -261,7 +261,7 @@ class ChuniBase:
"userActivityList": activity_list, "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"]) characters = self.data.item.get_characters(data["userId"])
if characters is None: if characters is None:
return { return {
@@ -296,7 +296,7 @@ class ChuniBase:
"userCharacterList": character_list, "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"]) user_charge_list = self.data.profile.get_profile_charge(data["userId"])
charge_list = [] charge_list = []
@@ -312,14 +312,14 @@ class ChuniBase:
"userChargeList": charge_list, "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 { return {
"userId": data["userId"], "userId": data["userId"],
"length": 0, "length": 0,
"userRecentPlayerList": [], # playUserId, playUserName, playDate, friendPoint "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"]) user_course_list = self.data.score.get_courses(data["userId"])
if user_course_list is None: if user_course_list is None:
return { return {
@@ -354,7 +354,7 @@ class ChuniBase:
"userCourseList": course_list, "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) p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None: if p is None:
return {} return {}
@@ -366,7 +366,7 @@ class ChuniBase:
return {"userId": data["userId"], "userData": profile} 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) p = self.data.profile.get_profile_data_ex(data["userId"], self.version)
if p is None: if p is None:
return {} return {}
@@ -378,7 +378,7 @@ class ChuniBase:
return {"userId": data["userId"], "userDataEx": profile} 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"]) user_duel_list = self.data.item.get_duels(data["userId"])
if user_duel_list is None: if user_duel_list is None:
return {} return {}
@@ -396,7 +396,7 @@ class ChuniBase:
"userDuelList": duel_list, "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"]) p = self.data.profile.get_rival(data["rivalId"])
if p is None: if p is None:
return {} return {}
@@ -409,7 +409,7 @@ class ChuniBase:
"userRivalData": userRivalData "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"] rival_id = data["rivalId"]
next_index = int(data["nextIndex"]) next_index = int(data["nextIndex"])
max_count = int(data["maxCount"]) max_count = int(data["maxCount"])
@@ -462,7 +462,7 @@ class ChuniBase:
return result 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 = [] user_fav_item_list = []
# still needs to be implemented on WebUI # still needs to be implemented on WebUI
@@ -482,14 +482,14 @@ class ChuniBase:
"userFavoriteItemList": user_fav_item_list, "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 This is handled via the webui, which we don't have right now
""" """
return {"userId": data["userId"], "length": 0, "userFavoriteMusicList": []} 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) kind = int(int(data["nextIndex"]) / 10000000000)
next_idx = int(int(data["nextIndex"]) % 10000000000) next_idx = int(int(data["nextIndex"]) % 10000000000)
user_item_list = self.data.item.get_items(data["userId"], kind) user_item_list = self.data.item.get_items(data["userId"], kind)
@@ -526,7 +526,7 @@ class ChuniBase:
"userItemList": items, "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_id = data["userId"]
user_login_bonus = self.data.item.get_all_login_bonus(user_id, self.version) user_login_bonus = self.data.item.get_all_login_bonus(user_id, self.version)
# ignore the loginBonus request if its disabled in config # ignore the loginBonus request if its disabled in config
@@ -552,7 +552,7 @@ class ChuniBase:
"userLoginBonusList": user_login_list, "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"]) user_map_list = self.data.item.get_maps(data["userId"])
if user_map_list is None: if user_map_list is None:
return {} return {}
@@ -570,7 +570,7 @@ class ChuniBase:
"userMapList": map_list, "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"]) music_detail = self.data.score.get_scores(data["userId"])
if music_detail is None: if music_detail is None:
return { return {
@@ -629,7 +629,7 @@ class ChuniBase:
"userMusicList": song_list, # 240 "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"]) p = self.data.profile.get_profile_option(data["userId"])
option = p._asdict() option = p._asdict()
@@ -638,7 +638,7 @@ class ChuniBase:
return {"userId": data["userId"], "userGameOption": option} 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"]) p = self.data.profile.get_profile_option_ex(data["userId"])
option = p._asdict() option = p._asdict()
@@ -650,7 +650,7 @@ class ChuniBase:
def read_wtf8(self, src): def read_wtf8(self, src):
return bytes([ord(c) for c in src]).decode("utf-8") 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) profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None: if profile is None:
return None return None
@@ -692,7 +692,7 @@ class ChuniBase:
"userNameEx": profile["userName"], "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"]) recent_rating_list = self.data.profile.get_profile_recent_rating(data["userId"])
if recent_rating_list is None: if recent_rating_list is None:
return { return {
@@ -707,7 +707,7 @@ class ChuniBase:
"userRecentRatingList": recent_rating_list["recentRating"], "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 # TODO: Region
return { return {
"userId": data["userId"], "userId": data["userId"],
@@ -715,7 +715,7 @@ class ChuniBase:
"userRegionList": [], "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 # Default values
team_id = 65535 team_id = 65535
team_name = self.game_cfg.team.team_name 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 { return {
"userId": data["userId"], "userId": data["userId"],
"length": 0, "length": 0,
@@ -758,7 +758,7 @@ class ChuniBase:
"teamCourseSettingList": [], "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 { return {
"userId": data["userId"], "userId": data["userId"],
"length": 1, "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 { return {
"userId": data["userId"], "userId": data["userId"],
"length": 0, "length": 0,
@@ -790,7 +790,7 @@ class ChuniBase:
"teamCourseRuleList": [] "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 { return {
"userId": data["userId"], "userId": data["userId"],
"length": 1, "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"] upsert = data["upsertUserAll"]
user_id = data["userId"] user_id = data["userId"]
@@ -927,28 +927,28 @@ class ChuniBase:
return {"returnCode": "1"} 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 # add tickets after they got bought, this makes sure the tickets are
# still valid after an unsuccessful logout # still valid after an unsuccessful logout
self.data.profile.put_profile_charge(data["userId"], data["userCharge"]) self.data.profile.put_profile_charge(data["userId"], data["userCharge"])
return {"returnCode": "1"} 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"} 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"} 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"} 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"} 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"} 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 { return {
"userId": data["userId"], "userId": data["userId"],
"userNetBattleData": {"recentNBSelectMusicList": []}, "userNetBattleData": {"recentNBSelectMusicList": []},
+2 -2
View File
@@ -13,7 +13,7 @@ class ChuniCrystal(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.40.00" ret["gameSetting"]["dataVersion"] = "1.40.00"
return ret return ret
+2 -2
View File
@@ -13,7 +13,7 @@ class ChuniCrystalPlus(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.45.00" ret["gameSetting"]["dataVersion"] = "1.45.00"
return ret return ret
+28 -30
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 import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
import zlib import zlib
@@ -33,7 +35,6 @@ from .newplus import ChuniNewPlus
from .sun import ChuniSun from .sun import ChuniSun
from .sunplus import ChuniSunPlus from .sunplus import ChuniSunPlus
class ChuniServlet(BaseServlet): class ChuniServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
super().__init__(core_cfg, cfg_dir) 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()}" 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 @classmethod
def is_game_enabled( def is_game_enabled(
cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str 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]: 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: 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: def get_routes(self) -> List[Route]:
endpoint = matchers['endpoint'] return [
version = int(matchers['version']) Route("/{game:str}/{version:int}/ChuniServlet/{endpoint:str}", self.render_POST, methods=['POST']),
game_code = matchers['game'] 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": 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 encrtped = False
internal_ver = 0 internal_ver = 0
@@ -201,7 +199,7 @@ class ChuniServlet(BaseServlet):
internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS
elif game_code == "SDGS": # Int elif game_code == "SDGS": # Int
if version < 110: # SUPERSTAR 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 elif version >= 110 and version < 115: # NEW
internal_ver = ChuniConstants.VER_CHUNITHM_NEW internal_ver = ChuniConstants.VER_CHUNITHM_NEW
elif version >= 115 and version < 120: # NEW PLUS!! 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 # doing encrypted. The likelyhood of false positives is low but
# technically not 0 # technically not 0
if internal_ver < ChuniConstants.VER_CHUNITHM_NEW: if internal_ver < ChuniConstants.VER_CHUNITHM_NEW:
endpoint = request.getHeader("User-Agent").split("#")[0] endpoint = request.headers.get("User-Agent").split("#")[0]
else: else:
if internal_ver not in self.hash_table: if internal_ver not in self.hash_table:
self.logger.error( self.logger.error(
f"v{version} does not support encryption or no keys entered" 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]: elif endpoint.lower() not in self.hash_table[internal_ver]:
self.logger.error( self.logger.error(
f"No hash found for v{version} endpoint {endpoint}" 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()] endpoint = self.hash_table[internal_ver][endpoint.lower()]
@@ -246,7 +244,7 @@ class ChuniServlet(BaseServlet):
self.logger.error( self.logger.error(
f"Failed to decrypt v{version} request to {endpoint} -> {e}" 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 encrtped = True
@@ -258,7 +256,7 @@ class ChuniServlet(BaseServlet):
self.logger.error( self.logger.error(
f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}" 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: try:
unzip = zlib.decompress(req_raw) unzip = zlib.decompress(req_raw)
@@ -267,7 +265,7 @@ class ChuniServlet(BaseServlet):
self.logger.error( self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}" f"Failed to decompress v{version} {endpoint} request -> {e}"
) )
return b"" return Response(zlib.compress(b'{"stat": "0"}'))
req_data = json.loads(unzip) req_data = json.loads(unzip)
@@ -285,11 +283,11 @@ class ChuniServlet(BaseServlet):
else: else:
try: try:
handler = getattr(handler_cls, func_to_find) handler = getattr(handler_cls, func_to_find)
resp = handler(req_data) resp = await handler(req_data)
except Exception as e: except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {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: if resp == None:
resp = {"returnCode": 1} resp = {"returnCode": 1}
@@ -299,7 +297,7 @@ class ChuniServlet(BaseServlet):
zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
if not encrtped: if not encrtped:
return zipped return Response(zipped)
padded = pad(zipped, 16) padded = pad(zipped, 16)
@@ -309,4 +307,4 @@ class ChuniServlet(BaseServlet):
bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]), bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]),
) )
return crypt.encrypt(padded) return Response(crypt.encrypt(padded))
+34 -34
View File
@@ -33,7 +33,7 @@ class ChuniNew(ChuniBase):
if self.version == ChuniConstants.VER_CHUNITHM_SUN_PLUS: if self.version == ChuniConstants.VER_CHUNITHM_SUN_PLUS:
return "215" 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 # use UTC time and convert it to JST time by adding +9
# matching therefore starts one hour before and lasts for 8 hours # matching therefore starts one hour before and lasts for 8 hours
match_start = datetime.strftime( match_start = datetime.strftime(
@@ -82,26 +82,26 @@ class ChuniNew(ChuniBase):
"matchErrorLimit": self.game_cfg.matching.match_error_limit, "matchErrorLimit": self.game_cfg.matching.match_error_limit,
"romVersion": self.game_cfg.version.version(self.version)["rom"], "romVersion": self.game_cfg.version.version(self.version)["rom"],
"dataVersion": self.game_cfg.version.version(self.version)["data"], "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/", "matchingUri": f"http://{self.core_cfg.server.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/", "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 # 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/", "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.title.hostname}:{self.core_cfg.title.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, "isDumpUpload": False,
"isAou": 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"} 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"} 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"} 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"]) user_map_areas = self.data.item.get_map_areas(data["userId"])
map_areas = [] map_areas = []
@@ -113,10 +113,10 @@ class ChuniNew(ChuniBase):
return {"userId": data["userId"], "userMapAreaList": map_areas} 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": []} 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) profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None: if profile is None:
return None return None
@@ -164,7 +164,7 @@ class ChuniNew(ChuniBase):
} }
return data1 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) p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None: if p is None:
return {} return {}
@@ -177,13 +177,13 @@ class ChuniNew(ChuniBase):
"isLogin": False, "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} 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} 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) returns all current active banners (gachas)
""" """
@@ -213,7 +213,7 @@ class ChuniNew(ChuniBase):
"registIdList": [], "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 returns all valid cards for a given gachaId
""" """
@@ -237,7 +237,7 @@ class ChuniNew(ChuniBase):
"ssrBookCalcList": [], "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) p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None: if p is None:
return {} 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"]) user_gachas = self.data.item.get_user_gachas(data["userId"])
if user_gachas is None: if user_gachas is None:
return {"userId": data["userId"], "length": 0, "userGachaList": []} return {"userId": data["userId"], "length": 0, "userGachaList": []}
@@ -281,7 +281,7 @@ class ChuniNew(ChuniBase):
"userGachaList": user_gacha_list, "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( user_print_list = self.data.item.get_user_print_states(
data["userId"], has_completed=True data["userId"], has_completed=True
) )
@@ -316,7 +316,7 @@ class ChuniNew(ChuniBase):
"userPrintedCardList": print_list, "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_id = data["userId"]
user_print_states = self.data.item.get_user_print_states( user_print_states = self.data.item.get_user_print_states(
@@ -338,13 +338,13 @@ class ChuniNew(ChuniBase):
"userCardPrintStateList": card_print_state_list, "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) 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) 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: Handle a gacha roll API request, with:
gachaId: the gachaId where the cards should be pulled from 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} 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"] upsert = data["cmUpsertUserGacha"]
user_id = data["userId"] user_id = data["userId"]
place_id = data["placeId"] place_id = data["placeId"]
@@ -441,7 +441,7 @@ class ChuniNew(ChuniBase):
"userCardPrintStateList": card_print_state_list, "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 { return {
"returnCode": 1, "returnCode": 1,
"orderId": 0, "orderId": 0,
@@ -449,7 +449,7 @@ class ChuniNew(ChuniBase):
"apiName": "CMUpsertUserPrintlogApi", "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_print_detail = data["userPrintDetail"]
user_id = data["userId"] user_id = data["userId"]
@@ -474,7 +474,7 @@ class ChuniNew(ChuniBase):
"apiName": "CMUpsertUserPrintApi", "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"] upsert = data["userCardPrintState"]
user_id = data["userId"] user_id = data["userId"]
place_id = data["placeId"] place_id = data["placeId"]
@@ -491,7 +491,7 @@ class ChuniNew(ChuniBase):
return {"returnCode": "1", "apiName": "CMUpsertUserPrintSubtractApi"} 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"] order_ids = data["orderIdList"]
user_id = data["userId"] user_id = data["userId"]
@@ -501,11 +501,11 @@ class ChuniNew(ChuniBase):
return {"returnCode": "1", "apiName": "CMUpsertUserPrintCancelApi"} 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 # matchmaking ping request
return {"returnCode": "1"} 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 room_id = 1
# check if there is a free matching room # check if there is a free matching room
matching_room = self.data.item.get_oldest_free_matching(self.version) matching_room = self.data.item.get_oldest_free_matching(self.version)
@@ -554,7 +554,7 @@ class ChuniNew(ChuniBase):
return {"roomId": 1, "matchingWaitState": matching_wait} 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"]) matching_room = self.data.item.get_matching(self.version, data["roomId"])
members = matching_room["matchingMemberInfoList"] members = matching_room["matchingMemberInfoList"]
@@ -579,10 +579,10 @@ class ChuniNew(ChuniBase):
# no idea, maybe to differentiate between CPUs and real players? # no idea, maybe to differentiate between CPUs and real players?
"matchingMemberRoleList": role_list, "matchingMemberRoleList": role_list,
# TCP/UDP connection? # 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 # get all matching rooms, because Chuni only returns the userId
# not the actual roomId # not the actual roomId
matching_rooms = self.data.item.get_all_matchings(self.version) matching_rooms = self.data.item.get_all_matchings(self.version)
@@ -612,7 +612,7 @@ class ChuniNew(ChuniBase):
return {"returnCode": "1"} 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 polling_interval = 1
# get the current active room # get the current active room
matching_room = self.data.item.get_matching(self.version, data["roomId"]) matching_room = self.data.item.get_matching(self.version, data["roomId"])
+2 -2
View File
@@ -11,8 +11,8 @@ class ChuniNewPlus(ChuniNew):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_NEW_PLUS self.version = ChuniConstants.VER_CHUNITHM_NEW_PLUS
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:
user_data = super().handle_cm_get_user_preview_api_request(data) user_data = await super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker 1.35 A028 # hardcode lastDataVersion for CardMaker 1.35 A028
user_data["lastDataVersion"] = "2.05.00" user_data["lastDataVersion"] = "2.05.00"
+2 -2
View File
@@ -13,7 +13,7 @@ class ChuniParadise(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_PARADISE self.version = ChuniConstants.VER_CHUNITHM_PARADISE
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.50.00" ret["gameSetting"]["dataVersion"] = "1.50.00"
return ret return ret
+2 -2
View File
@@ -11,7 +11,7 @@ class ChuniPlus(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_PLUS self.version = ChuniConstants.VER_CHUNITHM_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.05.00" ret["gameSetting"]["dataVersion"] = "1.05.00"
return ret return ret
+2 -2
View File
@@ -11,7 +11,7 @@ class ChuniStar(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_STAR self.version = ChuniConstants.VER_CHUNITHM_STAR
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.20.00" ret["gameSetting"]["dataVersion"] = "1.20.00"
return ret return ret
+2 -2
View File
@@ -11,7 +11,7 @@ class ChuniStarPlus(ChuniBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_STAR_PLUS self.version = ChuniConstants.VER_CHUNITHM_STAR_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.25.00" ret["gameSetting"]["dataVersion"] = "1.25.00"
return ret return ret
+2 -2
View File
@@ -11,8 +11,8 @@ class ChuniSun(ChuniNewPlus):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_SUN self.version = ChuniConstants.VER_CHUNITHM_SUN
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:
user_data = super().handle_cm_get_user_preview_api_request(data) user_data = await super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker 1.35 A032 # hardcode lastDataVersion for CardMaker 1.35 A032
user_data["lastDataVersion"] = "2.10.00" user_data["lastDataVersion"] = "2.10.00"
+2 -2
View File
@@ -11,8 +11,8 @@ class ChuniSunPlus(ChuniSun):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_SUN_PLUS self.version = ChuniConstants.VER_CHUNITHM_SUN_PLUS
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:
user_data = super().handle_cm_get_user_preview_api_request(data) 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 # 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" user_data["lastDataVersion"] = "2.15.00"
+7 -7
View File
@@ -29,11 +29,11 @@ class CardMakerBase:
def _parse_int_ver(version: str) -> str: def _parse_int_ver(version: str) -> str:
return version.replace(".", "")[:3] 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: 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: 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 # grab the dict with all games version numbers from user config
games_ver = self.game_cfg.version.version(self.version) 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 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 == "": if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
reboot_start = datetime.strftime( reboot_start = datetime.strftime(
@@ -110,11 +110,11 @@ class CardMakerBase:
"isAou": False, "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": []} 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"} 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"} return {"returnCode": 1, "apiName": "UpsertClientBookkeepingApi"}
+2 -2
View File
@@ -12,7 +12,7 @@ class CardMaker135(CardMakerBase):
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = CardMakerConstants.VER_CARD_MAKER_135 self.version = CardMakerConstants.VER_CARD_MAKER_135
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = await super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.35.00" ret["gameSetting"]["dataVersion"] = "1.35.00"
return ret return ret
+16 -17
View File
@@ -5,10 +5,11 @@ import string
import logging import logging
import coloredlogs import coloredlogs
import zlib import zlib
from starlette.routing import Route
from starlette.responses import Response
from starlette.requests import Request
from os import path from os import path
from typing import Tuple, List, Dict from typing import List
from twisted.web.http import Request
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig from core.config import CoreConfig
@@ -19,7 +20,6 @@ from .const import CardMakerConstants
from .base import CardMakerBase from .base import CardMakerBase
from .cm135 import CardMaker135 from .cm135 import CardMaker135
class CardMakerServlet(BaseServlet): class CardMakerServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
super().__init__(core_cfg, cfg_dir) super().__init__(core_cfg, cfg_dir)
@@ -72,16 +72,15 @@ class CardMakerServlet(BaseServlet):
return True return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]: def get_routes(self) -> List[Route]:
return ( return [
[], Route("/SDED/{version:int}/{endpoint:str}", self.render_POST)
[("render_POST", "/SDED/{version}/{endpoint}", {})] ]
)
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes: async def render_POST(self, request: Request) -> bytes:
version = int(matchers['version']) version: int = request.path_params.get('version')
endpoint = matchers['endpoint'] endpoint: str = request.path_params.get('endpoint')
req_raw = request.content.getvalue() req_raw = await request.body()
internal_ver = 0 internal_ver = 0
client_ip = Utils.get_ip_addr(request) client_ip = Utils.get_ip_addr(request)
@@ -103,7 +102,7 @@ class CardMakerServlet(BaseServlet):
self.logger.error( self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}" 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) req_data = json.loads(unzip)
@@ -114,7 +113,7 @@ class CardMakerServlet(BaseServlet):
if not hasattr(self.versions[internal_ver], func_to_find): if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}") self.logger.warning(f"Unhandled v{version} request {endpoint}")
return zlib.compress(b'{"returnCode": 1}') return Response(zlib.compress(b'{"returnCode": 1}'))
try: try:
handler = getattr(self.versions[internal_ver], func_to_find) handler = getattr(self.versions[internal_ver], func_to_find)
@@ -123,11 +122,11 @@ class CardMakerServlet(BaseServlet):
except Exception as e: except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}") self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
raise raise
return zlib.compress(b'{"stat": "0"}') return Response(zlib.compress(b'{"stat": "0"}'))
if resp is None: if resp is None:
resp = {"returnCode": 1} resp = {"returnCode": 1}
self.logger.debug(f"Response {resp}") 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")))
+15 -15
View File
@@ -28,13 +28,13 @@ class CxbBase:
return [] return []
def handle_action_rpreq_request(self, data: Dict) -> Dict: async def handle_action_rpreq_request(self, data: Dict) -> Dict:
return {} return {}
def handle_action_hitreq_request(self, data: Dict) -> Dict: async def handle_action_hitreq_request(self, data: Dict) -> Dict:
return {"data": []} 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( profile = self.data.profile.get_profile_index(
0, data["usercheck"]["authid"], self.version 0, data["usercheck"]["authid"], self.version
) )
@@ -45,11 +45,11 @@ class CxbBase:
self.logger.info(f"No profile for aime id {data['usercheck']['authid']}") self.logger.info(f"No profile for aime id {data['usercheck']['authid']}")
return {"exist": "false", "logout": "true"} 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']}") self.logger.info(f"New profile for {data['entry']['authid']}")
return {"token": data["entry"]["authid"], "uid": 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( profile = self.data.profile.get_profile_index(
0, data["login"]["authid"], self.version 0, data["login"]["authid"], self.version
) )
@@ -198,7 +198,7 @@ class CxbBase:
).decode("utf-8") ).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_start = data["loadrange"]["range"][0]
range_end = data["loadrange"]["range"][1] range_end = data["loadrange"]["range"][1]
uid = data["loadrange"]["uid"] uid = data["loadrange"]["uid"]
@@ -282,7 +282,7 @@ class CxbBase:
return {"index": index, "data": data1, "version": versionindex} 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"] save_data = data["saveindex"]
try: try:
@@ -443,7 +443,7 @@ class CxbBase:
i += 1 i += 1
return {} return {}
def handle_action_sprankreq_request(self, data: Dict) -> Dict: async def handle_action_sprankreq_request(self, data: Dict) -> Dict:
uid = data["sprankreq"]["uid"] uid = data["sprankreq"]["uid"]
self.logger.info(f"Get best rankings for {uid}") self.logger.info(f"Get best rankings for {uid}")
p = self.data.score.get_best_rankings(uid) p = self.data.score.get_best_rankings(uid)
@@ -475,16 +475,16 @@ class CxbBase:
"rankx": [1, 1, 1], "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"}]} 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": []} return {"msgs": []}
def handle_auth_logout_request(self, data: Dict) -> Dict: async def handle_auth_logout_request(self, data: Dict) -> Dict:
return {"auth": True} 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"] uid = data["rankreg"]["uid"]
self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}") self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}")
@@ -527,7 +527,7 @@ class CxbBase:
) )
return {} return {}
def handle_action_addenergy_request(self, data: Dict) -> Dict: async def handle_action_addenergy_request(self, data: Dict) -> Dict:
uid = data["addenergy"]["uid"] uid = data["addenergy"]["uid"]
self.logger.info(f"Add energy to user {uid}") self.logger.info(f"Add energy to user {uid}")
profile = self.data.profile.get_profile_index(0, uid, self.version) profile = self.data.profile.get_profile_index(0, uid, self.version)
@@ -570,10 +570,10 @@ class CxbBase:
) )
return array[0] 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) self.logger.info(data)
return {"eventreq": ""} 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) self.logger.info(data)
return {"stampreq": ""} return {"stampreq": ""}
+35 -39
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 traceback
import sys import sys
import yaml import yaml
@@ -62,6 +64,14 @@ class CxbServlet(BaseServlet):
CxbRevSunriseS2(core_cfg, self.game_cfg), 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 @classmethod
def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool: def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool:
game_cfg = CxbConfig() 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 "" t_port = f":{title_port_int}" if title_port_int and not self.core_cfg.server.is_using_proxy else ""
return ( 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]]]: async def preprocess(self, req: Request) -> Dict:
return ( req_bytes = await req.body()
[],
[
("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?
try: try:
req_json: Dict = json.loads(req_bytes) req_json: Dict = json.loads(req_bytes)
@@ -126,8 +122,8 @@ class CxbServlet(BaseServlet):
return req_json return req_json
def handle_data(self, request: Request, game_code: str, matchers: Dict) -> bytes: async def handle_data(self, request: Request) -> bytes:
req_json = self.preprocess(request) req_json = await self.preprocess(request)
func_to_find = "handle_data_" func_to_find = "handle_data_"
version_string = "Base" version_string = "Base"
internal_ver = 0 internal_ver = 0
@@ -135,7 +131,7 @@ class CxbServlet(BaseServlet):
if req_json == {}: if req_json == {}:
self.logger.warning(f"Empty json request to /data") self.logger.warning(f"Empty json request to /data")
return b"" return Response()
subcmd = list(req_json.keys())[0] subcmd = list(req_json.keys())[0]
if subcmd == "dldate": if subcmd == "dldate":
@@ -145,14 +141,14 @@ class CxbServlet(BaseServlet):
or "filetype" not in req_json["dldate"] or "filetype" not in req_json["dldate"]
): ):
self.logger.warning(f"Malformed dldate request: {req_json}") self.logger.warning(f"Malformed dldate request: {req_json}")
return b"" return Response()
filetype = req_json["dldate"]["filetype"] filetype = req_json["dldate"]["filetype"]
filetype_split = filetype.split("/") filetype_split = filetype.split("/")
if len(filetype_split) < 2 or not filetype_split[0].isnumeric(): if len(filetype_split) < 2 or not filetype_split[0].isnumeric():
self.logger.warning(f"Malformed dldate request: {req_json}") self.logger.warning(f"Malformed dldate request: {req_json}")
return b"" return Response()
version = int(filetype_split[0]) version = int(filetype_split[0])
filename = filetype_split[len(filetype_split) - 1] filename = filetype_split[len(filetype_split) - 1]
@@ -184,7 +180,7 @@ class CxbServlet(BaseServlet):
if not hasattr(self.versions[internal_ver], func_to_find): 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}") 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.info(f"{version_string} request for filetype {filetype}")
self.logger.debug(req_json) self.logger.debug(req_json)
@@ -192,7 +188,7 @@ class CxbServlet(BaseServlet):
handler = getattr(self.versions[internal_ver], func_to_find) handler = getattr(self.versions[internal_ver], func_to_find)
try: try:
resp = handler(req_json) resp = await handler(req_json)
except Exception as e: except Exception as e:
self.logger.error(f"Error handling request for file {filetype} - {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) traceback.print_exception(tp, val, tb, limit=1)
with open("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), "a") as f: 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) traceback.print_exception(tp, val, tb, limit=1, file=f)
return "" return Response()
self.logger.debug(f"{version_string} Response {resp}") 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: async def handle_action(self, request: Request) -> bytes:
req_json = self.preprocess(request) req_json = await self.preprocess(request)
subcmd = list(req_json.keys())[0] subcmd = list(req_json.keys())[0]
func_to_find = f"handle_action_{subcmd}_request" func_to_find = f"handle_action_{subcmd}_request"
if not hasattr(self.versions[0], func_to_find): if not hasattr(self.versions[0], func_to_find):
self.logger.warn(f"No handler for action {subcmd} request") self.logger.warn(f"No handler for action {subcmd} request")
return "" return Response()
self.logger.info(f"Action {subcmd} Request") self.logger.info(f"Action {subcmd} Request")
self.logger.debug(req_json) self.logger.debug(req_json)
@@ -221,7 +217,7 @@ class CxbServlet(BaseServlet):
handler = getattr(self.versions[0], func_to_find) handler = getattr(self.versions[0], func_to_find)
try: try:
resp = handler(req_json) resp = await handler(req_json)
except Exception as e: except Exception as e:
self.logger.error(f"Error handling action {subcmd} request - {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) traceback.print_exception(tp, val, tb, limit=1)
with open("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), "a") as f: 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) traceback.print_exception(tp, val, tb, limit=1, file=f)
return "" return Response()
self.logger.debug(f"Response {resp}") 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: async def handle_auth(self, request: Request) -> bytes:
req_json = self.preprocess(request) req_json = await self.preprocess(request)
subcmd = list(req_json.keys())[0] subcmd = list(req_json.keys())[0]
func_to_find = f"handle_auth_{subcmd}_request" func_to_find = f"handle_auth_{subcmd}_request"
if not hasattr(self.versions[0], func_to_find): if not hasattr(self.versions[0], func_to_find):
self.logger.warn(f"No handler for auth {subcmd} request") self.logger.warn(f"No handler for auth {subcmd} request")
return "" return Response()
self.logger.info(f"Action {subcmd} Request") self.logger.info(f"Action {subcmd} Request")
self.logger.debug(req_json) self.logger.debug(req_json)
@@ -250,7 +246,7 @@ class CxbServlet(BaseServlet):
handler = getattr(self.versions[0], func_to_find) handler = getattr(self.versions[0], func_to_find)
try: try:
resp = handler(req_json) resp = await handler(req_json)
except Exception as e: except Exception as e:
self.logger.error(f"Error handling auth {subcmd} request - {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) traceback.print_exception(tp, val, tb, limit=1)
with open("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), "a") as f: 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) traceback.print_exception(tp, val, tb, limit=1, file=f)
return "" return Response()
self.logger.debug(f"Response {resp}") self.logger.debug(f"Response {resp}")
return json.dumps(resp, ensure_ascii=False).encode("utf-8") return JSONResponse(resp)
+40 -40
View File
@@ -17,10 +17,10 @@ class CxbRev(CxbBase):
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV 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": ""} 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": if data["putlog"]["type"] == "ResultLog":
score_data = json.loads(data["putlog"]["data"]) score_data = json.loads(data["putlog"]["data"])
userid = score_data["usid"] userid = score_data["usid"]
@@ -45,7 +45,7 @@ class CxbRev(CxbBase):
return {"data": True} return {"data": True}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open(r"titles/cxb/data/rss/MusicArchiveList.csv") as music: with open(r"titles/cxb/data/rss/MusicArchiveList.csv") as music:
lines = music.readlines() lines = music.readlines()
@@ -56,7 +56,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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" ret_str = "\r\n#ItemListIcon\r\n"
with open( with open(
r"titles/cxb/data/rss/Item/ItemArchiveList_Icon.csv", encoding="utf-8" r"titles/cxb/data/rss/Item/ItemArchiveList_Icon.csv", encoding="utf-8"
@@ -67,7 +67,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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" ret_str = "\r\n#ItemListSkinNotes\r\n"
with open( with open(
r"titles/cxb/data/rss/Item/ItemArchiveList_SkinNotes.csv", encoding="utf-8" r"titles/cxb/data/rss/Item/ItemArchiveList_SkinNotes.csv", encoding="utf-8"
@@ -78,7 +78,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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" ret_str = "\r\n#ItemListSkinEffect\r\n"
with open( with open(
r"titles/cxb/data/rss/Item/ItemArchiveList_SkinEffect.csv", encoding="utf-8" r"titles/cxb/data/rss/Item/ItemArchiveList_SkinEffect.csv", encoding="utf-8"
@@ -89,7 +89,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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" ret_str = "\r\n#ItemListSkinBg\r\n"
with open( with open(
r"titles/cxb/data/rss/Item/ItemArchiveList_SkinBg.csv", encoding="utf-8" r"titles/cxb/data/rss/Item/ItemArchiveList_SkinBg.csv", encoding="utf-8"
@@ -100,7 +100,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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" ret_str = "\r\n#ItemListTitle\r\n"
with open( with open(
r"titles/cxb/data/rss/Item/ItemList_Title.csv", encoding="shift-jis" r"titles/cxb/data/rss/Item/ItemList_Title.csv", encoding="shift-jis"
@@ -111,7 +111,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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" ret_str = "\r\n#ShopListMusic\r\n"
with open( with open(
r"titles/cxb/data/rss/Shop/ShopList_Music.csv", encoding="shift-jis" r"titles/cxb/data/rss/Shop/ShopList_Music.csv", encoding="shift-jis"
@@ -122,7 +122,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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" ret_str = "\r\n#ShopListIcon\r\n"
with open( with open(
r"titles/cxb/data/rss/Shop/ShopList_Icon.csv", encoding="shift-jis" r"titles/cxb/data/rss/Shop/ShopList_Icon.csv", encoding="shift-jis"
@@ -133,7 +133,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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" ret_str = "\r\n#ShopListTitle\r\n"
with open( with open(
r"titles/cxb/data/rss/Shop/ShopList_Title.csv", encoding="shift-jis" 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" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} 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": ""} 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": ""} return {"data": ""}
@cached(lifetime=86400) @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" ret_str = "\r\n#ShopListSale\r\n"
with open( with open(
r"titles/cxb/data/rss/Shop/ShopList_Sale.csv", encoding="shift-jis" 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" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} return {"data": ""}
@cached(lifetime=86400) @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:]) extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
with open( with open(
@@ -178,14 +178,14 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} 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": ""} return {"data": ""}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open(r"titles/cxb/data/rss/NewsList.csv", encoding="UTF-8") as news: with open(r"titles/cxb/data/rss/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines() lines = news.readlines()
@@ -193,11 +193,11 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} return {"data": ""}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict: async def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/data/rss/License_Offline.csv", encoding="UTF-8") as lic: with open(r"titles/cxb/data/rss/License_Offline.csv", encoding="UTF-8") as lic:
lines = lic.readlines() lines = lic.readlines()
@@ -206,7 +206,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open( with open(
r"titles/cxb/data/rss/Course/CourseList.csv", encoding="UTF-8" r"titles/cxb/data/rss/Course/CourseList.csv", encoding="UTF-8"
@@ -217,7 +217,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 # Removed the CSVs since the format isnt quite right
extra_num = int(data["dldate"]["filetype"][-4:]) extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
@@ -230,7 +230,7 @@ class CxbRev(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open( with open(
r"titles/cxb/data/rss/MissionList.csv", encoding="shift-jis" r"titles/cxb/data/rss/MissionList.csv", encoding="shift-jis"
@@ -240,14 +240,14 @@ class CxbRev(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} 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": ""} return {"data": ""}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open( with open(
r"titles/cxb/data/rss/Event/EventArchiveList.csv", encoding="shift-jis" 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" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} 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": ""} 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 self, data: Dict
) -> Dict: ) -> Dict:
return {"data": ""} return {"data": ""}
def handle_data_event_achievement_single_accumulation_request( async def handle_data_event_achievement_single_accumulation_request(
self, data: Dict self, data: Dict
) -> Dict: ) -> Dict:
return {"data": ""} 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": ""} 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": ""} 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": ""} 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": ""} 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": ""} return {"data": ""}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open( with open(
r"titles/cxb/data/rss/Event/EventStampList.csv", encoding="shift-jis" 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" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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"} 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} return {"data": True}
+31 -31
View File
@@ -17,11 +17,11 @@ class CxbRevSunriseS1(CxbBase):
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1 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": ""} return {"data": ""}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open(r"titles/cxb/data/rss1/MusicArchiveList.csv") as music: with open(r"titles/cxb/data/rss1/MusicArchiveList.csv") as music:
lines = music.readlines() lines = music.readlines()
@@ -32,7 +32,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 # ItemListIcon load
ret_str = "#ItemListIcon\r\n" ret_str = "#ItemListIcon\r\n"
with open( with open(
@@ -54,7 +54,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 # ShopListIcon load
ret_str = "#ShopListIcon\r\n" ret_str = "#ShopListIcon\r\n"
with open( with open(
@@ -119,26 +119,26 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} return {"data": ""}
def handle_data_exxxxx_request(self, data: Dict) -> Dict: async def handle_data_exxxxx_request(self, data: Dict) -> Dict:
return {"data": ""} 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": ""} 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": ""} return {"data": ""}
def handle_data_oexxxx_request(self, data: Dict) -> Dict: async def handle_data_oexxxx_request(self, data: Dict) -> Dict:
return {"data": ""} 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": ""} return {"data": ""}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open(r"titles/cxb/data/rss1/NewsList.csv", encoding="UTF-8") as news: with open(r"titles/cxb/data/rss1/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines() lines = news.readlines()
@@ -146,14 +146,14 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} 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": ""} return {"data": ""}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open(r"titles/cxb/data/rss1/MusicArchiveList.csv") as music: with open(r"titles/cxb/data/rss1/MusicArchiveList.csv") as music:
lines = music.readlines() lines = music.readlines()
@@ -167,7 +167,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict: async def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/data/rss1/License.csv", encoding="UTF-8") as licenses: with open(r"titles/cxb/data/rss1/License.csv", encoding="UTF-8") as licenses:
lines = licenses.readlines() lines = licenses.readlines()
@@ -176,7 +176,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open( with open(
r"titles/cxb/data/rss1/Course/CourseList.csv", encoding="UTF-8" r"titles/cxb/data/rss1/Course/CourseList.csv", encoding="UTF-8"
@@ -187,7 +187,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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:]) extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
with open( with open(
@@ -198,16 +198,16 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} 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": ""} 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": ""} 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 = "" ret_str = ""
# Lord forgive me for the sins I am about to commit # Lord forgive me for the sins I am about to commit
for i in range(0, 10): for i in range(0, 10):
@@ -226,7 +226,7 @@ class CxbRevSunriseS1(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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:]) partner_num = int(data["dldate"]["filetype"][-4:])
ret_str = f"{partner_num},,{partner_num},1,10000,\r\n" ret_str = f"{partner_num},,{partner_num},1,10000,\r\n"
with open(r"titles/cxb/data/rss1/Partner0000.csv") as partner: 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" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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} 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"} 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 # story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu
ret_str = "\r\n" ret_str = "\r\n"
ret_str += ( ret_str += (
@@ -253,23 +253,23 @@ class CxbRevSunriseS1(CxbBase):
ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n" ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n"
return {"data": ret_str} 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:]) story_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
for i in range(1, 11): for i in range(1, 11):
ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n" ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return {"data": ret_str} 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"} 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"} 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": ""} 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] event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id: if "EventStampMapListCs1002" in event_id:
return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"} 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: else:
return {"data": ""} 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] event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id: if "EventStampMapListCs1002" in event_id:
return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"} return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
+31 -31
View File
@@ -17,11 +17,11 @@ class CxbRevSunriseS2(CxbBase):
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2_OMNI 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": ""} return {"data": ""}
@cached(lifetime=86400) @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] version = data["dldate"]["filetype"].split("/")[0]
ret_str = "" ret_str = ""
@@ -41,7 +41,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 # ItemListIcon load
ret_str = "#ItemListIcon\r\n" ret_str = "#ItemListIcon\r\n"
with open( with open(
@@ -63,7 +63,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 # ShopListIcon load
ret_str = "#ShopListIcon\r\n" ret_str = "#ShopListIcon\r\n"
with open( with open(
@@ -128,7 +128,7 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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="" ret_str=""
with open(r"titles/cxb/data/rss2/ExtraStageList.csv") as extra: with open(r"titles/cxb/data/rss2/ExtraStageList.csv") as extra:
lines = extra.readlines() lines = extra.readlines()
@@ -136,19 +136,19 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) 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": ""} 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": ""} 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": ""} return {"data": ""}
def handle_data_oexxxx_request(self, data: Dict) -> Dict: async def handle_data_oexxxx_request(self, data: Dict) -> Dict:
return {"data": ""} 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="" ret_str=""
with open(r"titles/cxb/data/rss2/FreeCoupon.csv") as coupon: with open(r"titles/cxb/data/rss2/FreeCoupon.csv") as coupon:
lines = coupon.readlines() lines = coupon.readlines()
@@ -157,7 +157,7 @@ class CxbRevSunriseS2(CxbBase):
return({"data":ret_str}) return({"data":ret_str})
@cached(lifetime=86400) @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 = "" ret_str = ""
with open(r"titles/cxb/data/rss2/NewsList.csv", encoding="UTF-8") as news: with open(r"titles/cxb/data/rss2/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines() lines = news.readlines()
@@ -165,14 +165,14 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} 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": ""} return {"data": ""}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open(r"titles/cxb/data/rss2/MusicArchiveList.csv") as music: with open(r"titles/cxb/data/rss2/MusicArchiveList.csv") as music:
lines = music.readlines() lines = music.readlines()
@@ -186,7 +186,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict: async def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/data/rss2/License.csv", encoding="UTF-8") as licenses: with open(r"titles/cxb/data/rss2/License.csv", encoding="UTF-8") as licenses:
lines = licenses.readlines() lines = licenses.readlines()
@@ -195,7 +195,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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 = "" ret_str = ""
with open( with open(
r"titles/cxb/data/rss2/Course/CourseList.csv", encoding="UTF-8" r"titles/cxb/data/rss2/Course/CourseList.csv", encoding="UTF-8"
@@ -206,7 +206,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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:]) extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
with open( with open(
@@ -217,16 +217,16 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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": ""} 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": ""} 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": ""} 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 = "" ret_str = ""
# Lord forgive me for the sins I am about to commit # Lord forgive me for the sins I am about to commit
for i in range(0, 10): for i in range(0, 10):
@@ -245,7 +245,7 @@ class CxbRevSunriseS2(CxbBase):
return {"data": ret_str} return {"data": ret_str}
@cached(lifetime=86400) @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:]) partner_num = int(data["dldate"]["filetype"][-4:])
ret_str = f"{partner_num},,{partner_num},1,10000,\r\n" ret_str = f"{partner_num},,{partner_num},1,10000,\r\n"
with open(r"titles/cxb/data/rss2/Partner0000.csv") as partner: 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" ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str} 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} 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"} 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 # story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu
ret_str = "\r\n" ret_str = "\r\n"
ret_str += ( ret_str += (
@@ -272,7 +272,7 @@ class CxbRevSunriseS2(CxbBase):
ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n" ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n"
return {"data": ret_str} 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:]) story_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
# Each stories appears to have 10 pieces based on the wiki but as on how they are set.... no clue # 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" ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return {"data": ret_str} 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"} 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"} 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 { return {
"data": "Cs4001,0,10000,1601510400,1604188799,1,nv2006,1,\r\nCs4005,0,10000,1609459200,1615766399,1,nv2006,1,\r\n" "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] event_id = data["dldate"]["filetype"].split("/")[2]
if "Cs4001" in event_id: if "Cs4001" in event_id:
return { return {
@@ -308,7 +308,7 @@ class CxbRevSunriseS2(CxbBase):
else: else:
return {"data": ""} 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] event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id: if "EventStampMapListCs1002" in event_id:
return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"} return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
+39 -40
View File
@@ -1,8 +1,7 @@
import datetime import datetime
from typing import Any, List, Dict from typing import Dict
import logging import logging
import json import urllib.parse
import urllib
from threading import Thread from threading import Thread
from core.config import CoreConfig from core.config import CoreConfig
@@ -24,13 +23,13 @@ class DivaBase:
dt = datetime.datetime.now() dt = datetime.datetime.now()
self.time_lut = urllib.parse.quote(dt.strftime("%Y-%m-%d %H:%M:%S:16.0")) 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 "" return ""
def handle_game_init_request(self, data: Dict) -> Dict: async def handle_game_init_request(self, data: Dict) -> Dict:
return f"" return f""
def handle_attend_request(self, data: Dict) -> Dict: async def handle_attend_request(self, data: Dict) -> Dict:
encoded = "&" encoded = "&"
params = { 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", "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 return encoded
def handle_ping_request(self, data: Dict) -> Dict: async def handle_ping_request(self, data: Dict) -> Dict:
encoded = "&" encoded = "&"
params = { params = {
"ping_b_msg": f"Welcome to {self.core_cfg.server.name} network!", "ping_b_msg": f"Welcome to {self.core_cfg.server.name} network!",
@@ -89,7 +88,7 @@ class DivaBase:
return encoded return encoded
def handle_pv_list_request(self, data: Dict) -> Dict: async def handle_pv_list_request(self, data: Dict) -> Dict:
pvlist = "" pvlist = ""
with open(r"titles/diva/data/PvList0.dat", encoding="utf-8") as shop: with open(r"titles/diva/data/PvList0.dat", encoding="utf-8") as shop:
lines = shop.readlines() lines = shop.readlines()
@@ -126,7 +125,7 @@ class DivaBase:
return response return response
def handle_shop_catalog_request(self, data: Dict) -> Dict: async def handle_shop_catalog_request(self, data: Dict) -> Dict:
catalog = "" catalog = ""
shopList = self.data.static.get_enabled_shops(self.version) shopList = self.data.static.get_enabled_shops(self.version)
@@ -164,7 +163,7 @@ class DivaBase:
return response 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) profile = self.data.profile.get_profile(data["pd_id"], self.version)
module = self.data.static.get_enabled_shop(self.version, int(data["mdl_id"])) module = self.data.static.get_enabled_shop(self.version, int(data["mdl_id"]))
@@ -191,7 +190,7 @@ class DivaBase:
return response return response
def handle_cstmz_itm_ctlg_request(self, data: Dict) -> Dict: async def handle_cstmz_itm_ctlg_request(self, data: Dict) -> Dict:
catalog = "" catalog = ""
itemList = self.data.static.get_enabled_items(self.version) itemList = self.data.static.get_enabled_items(self.version)
@@ -229,7 +228,7 @@ class DivaBase:
return response 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) profile = self.data.profile.get_profile(data["pd_id"], self.version)
item = self.data.static.get_enabled_item( item = self.data.static.get_enabled_item(
self.version, int(data["cstmz_itm_id"]) self.version, int(data["cstmz_itm_id"])
@@ -264,7 +263,7 @@ class DivaBase:
return response return response
def handle_festa_info_request(self, data: Dict) -> Dict: async def handle_festa_info_request(self, data: Dict) -> Dict:
encoded = "&" encoded = "&"
params = { params = {
"fi_id": "1,2", "fi_id": "1,2",
@@ -287,7 +286,7 @@ class DivaBase:
return encoded return encoded
def handle_contest_info_request(self, data: Dict) -> Dict: async def handle_contest_info_request(self, data: Dict) -> Dict:
response = "" response = ""
response += f"&ci_lut={self.time_lut}" response += f"&ci_lut={self.time_lut}"
@@ -295,7 +294,7 @@ class DivaBase:
return response return response
def handle_qst_inf_request(self, data: Dict) -> Dict: async def handle_qst_inf_request(self, data: Dict) -> Dict:
quest = "" quest = ""
questList = self.data.static.get_enabled_quests(self.version) questList = self.data.static.get_enabled_quests(self.version)
@@ -345,43 +344,43 @@ class DivaBase:
return response return response
def handle_nv_ranking_request(self, data: Dict) -> Dict: async def handle_nv_ranking_request(self, data: Dict) -> Dict:
return f"" return f""
def handle_ps_ranking_request(self, data: Dict) -> Dict: async def handle_ps_ranking_request(self, data: Dict) -> Dict:
return f"" return f""
def handle_ng_word_request(self, data: Dict) -> Dict: async def handle_ng_word_request(self, data: Dict) -> Dict:
return f"" 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"" 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"" 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"" 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"" return f""
def handle_banner_info_request(self, data: Dict) -> Dict: async def handle_banner_info_request(self, data: Dict) -> Dict:
return f"" return f""
def handle_banner_data_request(self, data: Dict) -> Dict: async def handle_banner_data_request(self, data: Dict) -> Dict:
return f"" 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"" 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"" 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"" 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 = self.data.profile.get_profile(data["aime_id"], self.version)
profile_shop = self.data.item.get_shop(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 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.data.profile.create_profile(
self.version, data["aime_id"], data["player_name"] self.version, data["aime_id"], data["player_name"]
) )
return f"&cd_adm_result=1&pd_id={data['aime_id']}" 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 = self.data.profile.get_profile(data["pd_id"], self.version)
profile_shop = self.data.item.get_shop(data["pd_id"], self.version) profile_shop = self.data.item.get_shop(data["pd_id"], self.version)
if profile is None: if profile is None:
@@ -583,10 +582,10 @@ class DivaBase:
return response return response
def handle_pd_unlock_request(self, data: Dict) -> Dict: async def handle_pd_unlock_request(self, data: Dict) -> Dict:
return f"" 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) profile = self.data.profile.get_profile(data["pd_id"], self.version)
if profile is None: if profile is None:
return return
@@ -705,7 +704,7 @@ class DivaBase:
pd_by_pv_id.append(urllib.parse.quote(f"{song}***")) pd_by_pv_id.append(urllib.parse.quote(f"{song}***"))
pd_by_pv_id.append(",") 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(",") song_id = data["pd_pv_id_lst"].split(",")
pv = "" pv = ""
@@ -732,10 +731,10 @@ class DivaBase:
return response return response
def handle_stage_start_request(self, data: Dict) -> Dict: async def handle_stage_start_request(self, data: Dict) -> Dict:
return f"" 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) profile = self.data.profile.get_profile(data["pd_id"], self.version)
pd_song_list = data["stg_ply_pv_id"].split(",") pd_song_list = data["stg_ply_pv_id"].split(",")
@@ -914,7 +913,7 @@ class DivaBase:
return response 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) profile = self.data.profile.get_profile(data["pd_id"], self.version)
self.data.profile.update_profile( self.data.profile.update_profile(
@@ -922,7 +921,7 @@ class DivaBase:
) )
return f"" 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( self.data.item.put_shop(
data["pd_id"], data["pd_id"],
self.version, self.version,
@@ -952,7 +951,7 @@ class DivaBase:
response = "&shp_rslt=1" response = "&shp_rslt=1"
return response 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) profile = self.data.profile.get_profile(data["aime_id"], self.version)
if profile is None: if profile is None:
return "&cd_adm_result=0" return "&cd_adm_result=0"
@@ -972,7 +971,7 @@ class DivaBase:
return response 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) profile = self.data.profile.get_profile(data["pd_id"], self.version)
# make sure user has enough Vocaloid Points # make sure user has enough Vocaloid Points
@@ -992,7 +991,7 @@ class DivaBase:
return response 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) profile = self.data.profile.get_profile(data["pd_id"], self.version)
# TODO: return correct error number instead of 0 # TODO: return correct error number instead of 0
+18 -28
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 yaml
import logging, coloredlogs import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler 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 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]]]: def get_routes(self) -> List[Route]:
return ( return [
[], Route("/DivaServlet/", self.render_POST, methods=['POST'])
[("render_POST", "/DivaServlet/", {})] ]
)
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]: 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: 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 @classmethod
def is_game_enabled( def is_game_enabled(
@@ -78,9 +79,9 @@ class DivaServlet(BaseServlet):
return True return True
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes: async def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
req_raw = request.content.getvalue() req_raw = await request.body()
url_header = request.getAllHeaders() url_header = request.headers
# Ping Dispatch # Ping Dispatch
if "THIS_STRING_SEPARATES" in str(url_header): if "THIS_STRING_SEPARATES" in str(url_header):
@@ -103,9 +104,7 @@ class DivaServlet(BaseServlet):
self.logger.debug( self.logger.debug(
f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}" f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}"
) )
return f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}".encode( return PlainTextResponse(f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}")
"utf-8"
)
# Main Dispatch # Main Dispatch
json_string = json.dumps( json_string = json.dumps(
@@ -122,7 +121,7 @@ class DivaServlet(BaseServlet):
) # Decompressing the gzip ) # Decompressing the gzip
except zlib.error as e: except zlib.error as e:
self.logger.error(f"Failed to defalte! {e} -> {gz_string}") 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_kvp = urllib.parse.unquote(url_data)
req_data = {} req_data = {}
@@ -141,27 +140,18 @@ class DivaServlet(BaseServlet):
# Load the requests # Load the requests
try: try:
handler = getattr(self.base, func_to_find) handler = getattr(self.base, func_to_find)
resp = handler(req_data) resp = await handler(req_data)
except AttributeError as e: except AttributeError as e:
self.logger.warning(f"Unhandled {req_data['cmd']} request {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( return PlainTextResponse(f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok")
"utf-8"
)
except Exception as e: except Exception as e:
self.logger.error(f"Error handling method {func_to_find} {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( return PlainTextResponse(f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok")
"utf-8"
)
request.responseHeaders.addRawHeader(b"content-type", b"text/plain")
self.logger.debug( self.logger.debug(
f"Response cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}" f"Response cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}"
) )
return ( return PlainTextResponse(f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}")
f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}".encode(
"utf-8"
)
)
+1 -1
View File
@@ -3,7 +3,7 @@ import yaml
import jinja2 import jinja2
from os import path from os import path
from twisted.web.util import redirectTo from twisted.web.util import redirectTo
from twisted.web.http import Request from starlette.requests import Request
from twisted.web.server import Session from twisted.web.server import Session
from core.frontend import FE_Base, IUserSession from core.frontend import FE_Base, IUserSession
+24 -23
View File
@@ -1,18 +1,18 @@
import json import json
import traceback import traceback
import inflection from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import JSONResponse
import yaml import yaml
import logging import logging
import coloredlogs import coloredlogs
from os import path from os import path
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
from logging.handlers import TimedRotatingFileHandler 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.config import CoreConfig
from core.title import BaseServlet
from core.utils import Utils from core.utils import Utils
from titles.idac.base import IDACBase from titles.idac.base import IDACBase
from titles.idac.season2 import IDACSeason2 from titles.idac.season2 import IDACSeason2
@@ -22,7 +22,7 @@ from titles.idac.echo import IDACEchoUDP
from titles.idac.matching import IDACMatching from titles.idac.matching import IDACMatching
class IDACServlet: class IDACServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg self.core_cfg = core_cfg
self.game_cfg = IDACConfig() self.game_cfg = IDACConfig()
@@ -73,11 +73,10 @@ class IDACServlet:
return True return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]: def get_routes(self) -> List[Route]:
return ( return [
[], Route("/{version:int}/initiald/{category:str}/{endpoint:str}", self.render_POST, methods=["POST"])
[("render_POST", "/SDGT/{version}/initiald/{category}/{endpoint}", {})] ]
)
def get_allnet_info( def get_allnet_info(
self, game_code: str, game_ver: int, keychip: str self, game_code: str, game_ver: int, keychip: str
@@ -88,15 +87,15 @@ class IDACServlet:
return ( return (
f"", f"",
# requires http or else it defaults to https # 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: async def render_POST(self, request: Request) -> bytes:
req_raw = request.content.getvalue() req_raw = await request.body()
internal_ver = 0 internal_ver = 0
version = int(matchers['version']) version: int = request.path_params.get('version')
category = matchers['category'] category: str = request.path_params.get('category')
endpoint = matchers['endpoint'] endpoint: str = request.path_params.get('endpoint')
client_ip = Utils.get_ip_addr(request) client_ip = Utils.get_ip_addr(request)
if version >= 100 and version < 140: # IDAC Season 1 if version >= 100 and version < 140: # IDAC Season 1
@@ -104,7 +103,7 @@ class IDACServlet:
elif version >= 140 and version < 171: # IDAC Season 2 elif version >= 140 and version < 171: # IDAC Season 2
internal_ver = IDACConstants.VER_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) req_data = json.loads(req_raw)
@@ -119,7 +118,7 @@ class IDACServlet:
if not hasattr(self.versions[internal_ver], func_to_find): if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}") self.logger.warning(f"Unhandled v{version} request {endpoint}")
return '{"status_code": "0"}'.encode("utf-8") return JSONResponse('{"status_code": "0"}')
resp = None resp = None
try: try:
@@ -129,17 +128,16 @@ class IDACServlet:
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
self.logger.error(f"Error handling v{version} method {endpoint} - {e}") 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: if resp is None:
resp = {"status_code": "0"} resp = {"status_code": "0"}
self.logger.debug(f"Response {resp}") 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: def decode_header(self, app: str) -> Dict:
app: str = data[b"application"].decode()
ret = {} ret = {}
for x in app.split(", "): for x in app.split(", "):
@@ -149,6 +147,8 @@ class IDACServlet:
return ret return ret
def setup(self): def setup(self):
return
"""
if self.game_cfg.server.enable: if self.game_cfg.server.enable:
endpoints.serverFromString( endpoints.serverFromString(
reactor, 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}") 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}")
"""
+6 -6
View File
@@ -109,9 +109,9 @@ class IDACSeason2(IDACBase):
ver_str = version.replace(".", "")[:3] ver_str = version.replace(".", "")[:3]
if self.core_cfg.server.is_develop: 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: 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 { return {
"status_code": "0", "status_code": "0",
@@ -136,10 +136,10 @@ class IDACSeason2(IDACBase):
"server_maintenance_end_hour": 0, "server_maintenance_end_hour": 0,
"server_maintenance_end_minutes": 0, "server_maintenance_end_minutes": 0,
"domain_api_game": domain_api_game, "domain_api_game": domain_api_game,
"domain_matching": f"http://{self.core_cfg.title.hostname}:{self.game_config.server.matching}", "domain_matching": f"http://{self.core_cfg.server.hostname}:{self.game_config.server.matching}",
"domain_echo1": f"{self.core_cfg.title.hostname}:{self.game_config.server.echo1}", "domain_echo1": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo1}",
"domain_echo2": f"{self.core_cfg.title.hostname}:{self.game_config.server.echo2}", "domain_echo2": f"{self.core_cfg.server.hostname}:{self.game_config.server.echo2}",
"domain_ping": f"{self.core_cfg.title.hostname}", "domain_ping": f"{self.core_cfg.server.hostname}",
"battle_gift_event_master": [], "battle_gift_event_master": [],
"round_event": [ "round_event": [
{ {
+10 -10
View File
@@ -24,10 +24,10 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
t_port = Utils.get_title_port(self.core_config) t_port = Utils.get_title_port(self.core_config)
news_str = f"http://{self.core_config.title.hostname}:{t_port}/idz/news/news80**.txt" news_str = f"http://{self.core_config.server.hostname}:{t_port}/idz/news/news80**.txt"
err_str = f"http://{self.core_config.title.hostname}:{t_port}/idz/error" 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_news = len(news_str)
len_error = len(err_str) len_error = len(err_str)
@@ -36,7 +36,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s", f"{len_hostname}s",
ret, ret,
0x4 + offset, 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, 0x84 + offset, self.game_cfg.ports.userdb)
struct.pack_into("<I", ret, 0x86 + offset, self.game_cfg.ports.userdb + 1) struct.pack_into("<I", ret, 0x86 + offset, self.game_cfg.ports.userdb + 1)
@@ -45,7 +45,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s", f"{len_hostname}s",
ret, ret,
0x88 + offset, 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, 0x108 + offset, self.game_cfg.ports.match - 1)
struct.pack_into("<I", ret, 0x10A + offset, self.game_cfg.ports.match - 3) struct.pack_into("<I", ret, 0x10A + offset, self.game_cfg.ports.match - 3)
@@ -59,7 +59,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s", f"{len_hostname}s",
ret, ret,
0x114 + offset, 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) struct.pack_into("<I", ret, 0x194 + offset, self.game_cfg.ports.echo + 2)
@@ -67,7 +67,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s", f"{len_hostname}s",
ret, ret,
0x0199 + offset, 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) struct.pack_into("<I", ret, 0x0219 + offset, self.game_cfg.ports.echo + 3)
@@ -75,19 +75,19 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
f"{len_hostname}s", f"{len_hostname}s",
ret, ret,
0x021C + offset, 0x021C + offset,
self.core_config.title.hostname.encode(), self.core_config.server.hostname.encode(),
) )
struct.pack_into( struct.pack_into(
f"{len_hostname}s", f"{len_hostname}s",
ret, ret,
0x029C + offset, 0x029C + offset,
self.core_config.title.hostname.encode(), self.core_config.server.hostname.encode(),
) )
struct.pack_into( struct.pack_into(
f"{len_hostname}s", f"{len_hostname}s",
ret, ret,
0x031C + offset, 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) struct.pack_into("<I", ret, 0x39C + offset, self.game_cfg.ports.echo)
+16 -19
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 yaml
import logging import logging
import coloredlogs import coloredlogs
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from os import path from os import path
from typing import Tuple, List, Dict from typing import Tuple, List, Dict
from twisted.internet import reactor, endpoints
from twisted.web import server, resource
import importlib import importlib
from core.config import CoreConfig from core.config import CoreConfig
@@ -83,16 +83,15 @@ class IDZServlet(BaseServlet):
return True return True
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]: def get_routes(self) -> List[Route]:
return[ return [
[("render_GET", "/idz/news/{endpoint:.*?}", {}), Route("/idz/news/{endpoint:str}", self.render_GET),
("render_GET", "/idz/error", {})], Route("/idz/error", self.render_GET)
[]
] ]
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]: def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
hostname = ( hostname = (
self.core_cfg.title.hostname self.core_cfg.server.hostname
if not self.game_cfg.server.hostname if not self.game_cfg.server.hostname
else self.game_cfg.server.hostname else self.game_cfg.server.hostname
) )
@@ -136,6 +135,7 @@ class IDZServlet(BaseServlet):
except AttributeError as e: except AttributeError as e:
continue continue
"""
endpoints.serverFromString( endpoints.serverFromString(
reactor, reactor,
f"tcp:{self.game_cfg.ports.userdb}:interface={self.core_cfg.server.listen_address}", f"tcp:{self.game_cfg.ports.userdb}:interface={self.core_cfg.server.listen_address}",
@@ -155,18 +155,15 @@ class IDZServlet(BaseServlet):
reactor.listenUDP( reactor.listenUDP(
self.game_cfg.ports.userdb + 1, IDZEcho(self.core_cfg, self.game_cfg) 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}") 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: async def render_GET(self, request: Request) -> bytes:
url_path = matchers['endpoint'] url_path = request.path_params.get('endpoint', '')
if not url_path:
return Response()
self.logger.info(f"IDZ GET request: {url_path}") 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 = ( news = (
self.game_cfg.server.news self.game_cfg.server.news
@@ -176,4 +173,4 @@ class IDZServlet(BaseServlet):
news += "\r\n" news += "\r\n"
news = "1979/01/01 00:00:00 2099/12/31 23:59:59 " + news 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"})
Loaded 50 of 82 files, more files were not shown because too many files have changed in this diff. Show more