From 9ee155792e45a75bb62f1931f5e68cebfba56f73 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 17 Feb 2023 01:37:59 -0500 Subject: [PATCH] add mucha handler, fix CXB log duplication --- core/__init__.py | 1 + core/allnet.py | 2 +- core/config.py | 28 +++++++ core/mucha.py | 163 +++++++++++++++++++++++++++++++++++++++ core/title.py | 15 +++- example_config/core.yaml | 8 ++ index.py | 4 + titles/cxb/index.py | 28 +++---- 8 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 core/mucha.py diff --git a/core/__init__.py b/core/__init__.py index ddce50c..c72ba0a 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -3,3 +3,4 @@ from core.allnet import AllnetServlet from core.aimedb import AimedbFactory from core.title import TitleServlet from core.utils import Utils +from core.mucha import MuchaServlet diff --git a/core/allnet.py b/core/allnet.py index b944ba4..c5f95fa 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -11,7 +11,7 @@ from core.config import CoreConfig from core.data import Data from core.utils import Utils -class AllnetServlet(): +class AllnetServlet: def __init__(self, core_cfg: CoreConfig, cfg_folder: str): super().__init__() self.config = core_cfg diff --git a/core/config.py b/core/config.py index d11dcdc..c97ee96 100644 --- a/core/config.py +++ b/core/config.py @@ -157,6 +157,34 @@ class AimedbConfig: def key(self) -> str: return CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'key', default="") +class MuchaConfig: + def __init__(self, parent_config: "CoreConfig") -> None: + self.__config = parent_config + + @property + def enable(self) -> int: + return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'enable', default=False) + + @property + def loglevel(self) -> int: + return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'loglevel', default="info")) + + @property + def hostname(self) -> str: + return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'hostname', default="localhost") + + @property + def port(self) -> int: + return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'port', default=8444) + + @property + def ssl_cert(self) -> str: + return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'ssl_cert', default="cert/server.pem") + + @property + def signing_key(self) -> str: + return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'signing_key', default="cert/billing.key") + class CoreConfig(dict): def __init__(self) -> None: self.server = ServerConfig(self) diff --git a/core/mucha.py b/core/mucha.py new file mode 100644 index 0000000..a861101 --- /dev/null +++ b/core/mucha.py @@ -0,0 +1,163 @@ +from typing import Dict, Any, Optional +import logging, coloredlogs +from logging.handlers import TimedRotatingFileHandler +from twisted.web import resource +from twisted.web.http import Request +from datetime import datetime +import pytz + +from core.config import CoreConfig + +class MuchaServlet: + def __init__(self, cfg: CoreConfig) -> None: + self.config = cfg + + self.logger = logging.getLogger('mucha') + log_fmt_str = "[%(asctime)s] Mucha | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "mucha"), 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(logging.INFO) + coloredlogs.install(level=logging.INFO, logger=self.logger, fmt=log_fmt_str) + + def handle_boardauth(self, request: Request) -> bytes: + req_dict = self.mucha_preprocess(request.content.getvalue()) + if req_dict is None: + self.logger.error(f"Error processing mucha request {request.content.getvalue()}") + return b"" + + req = MuchaAuthRequest(req_dict) + self.logger.info(f"Mucha request {vars(req)}") + resp = MuchaAuthResponse(mucha_url=f"{self.config.mucha.hostname}:{self.config.mucha.port}") + self.logger.info(f"Mucha response {vars(resp)}") + + return self.mucha_postprocess(vars(resp)) + + def handle_updatecheck(self, request: Request) -> bytes: + req_dict = self.mucha_preprocess(request.content.getvalue()) + if req_dict is None: + self.logger.error(f"Error processing mucha request {request.content.getvalue()}") + return b"" + + req = MuchaUpdateRequest(req_dict) + self.logger.info(f"Mucha request {vars(req)}") + resp = MuchaUpdateResponse(mucha_url=f"{self.config.mucha.hostname}:{self.config.mucha.port}") + self.logger.info(f"Mucha response {vars(resp)}") + + return self.mucha_postprocess(vars(resp)) + + def mucha_preprocess(self, data: bytes) -> Optional[Dict]: + try: + ret: Dict[str, Any] = {} + + for x in data.decode().split('&'): + kvp = x.split('=') + if len(kvp) == 2: + ret[kvp[0]] = kvp[1] + + return ret + + except: + self.logger.error(f"Error processing mucha request {data}") + return None + + def mucha_postprocess(self, data: dict) -> Optional[bytes]: + try: + urlencode = "" + for k,v in data.items(): + urlencode += f"{k}={v}&" + + return urlencode.encode() + + except: + self.logger.error("Error processing mucha response") + return None + +class MuchaAuthRequest(): + def __init__(self, request: Dict) -> None: + self.gameVer = "" if "gameVer" not in request else request["gameVer"] + self.sendDate = "" if "sendDate" not in request else request["sendDate"] + self.serialNum = "" if "serialNum" not in request else request["serialNum"] + self.gameCd = "" if "gameCd" not in request else request["gameCd"] + self.boardType = "" if "boardType" not in request else request["boardType"] + self.boardId = "" if "boardId" not in request else request["boardId"] + self.placeId = "" if "placeId" not in request else request["placeId"] + self.storeRouterIp = "" if "storeRouterIp" not in request else request["storeRouterIp"] + self.countryCd = "" if "countryCd" not in request else request["countryCd"] + self.useToken = "" if "useToken" not in request else request["useToken"] + self.allToken = "" if "allToken" not in request else request["allToken"] + +class MuchaAuthResponse(): + def __init__(self, mucha_url: str = "localhost") -> None: + self.RESULTS = "001" + self.AUTH_INTERVAL = "86400" + self.SERVER_TIME = datetime.strftime(datetime.now(), "%Y%m%d%H%M") + self.UTC_SERVER_TIME = datetime.strftime(datetime.now(pytz.UTC), "%Y%m%d%H%M") + + self.CHARGE_URL = f"https://{mucha_url}/charge/" + self.FILE_URL = f"https://{mucha_url}/file/" + self.URL_1 = f"https://{mucha_url}/url1/" + self.URL_2 = f"https://{mucha_url}/url2/" + self.URL_3 = f"https://{mucha_url}/url3/" + + self.PLACE_ID = "JPN123" + self.COUNTRY_CD = "JPN" + self.SHOP_NAME = "TestShop!" + self.SHOP_NICKNAME = "TestShop" + self.AREA_0 = "008" + self.AREA_1 = "009" + self.AREA_2 = "010" + self.AREA_3 = "011" + self.AREA_FULL_0 = "" + self.AREA_FULL_1 = "" + self.AREA_FULL_2 = "" + self.AREA_FULL_3 = "" + + self.SHOP_NAME_EN = "TestShop!" + self.SHOP_NICKNAME_EN = "TestShop" + self.AREA_0_EN = "008" + self.AREA_1_EN = "009" + self.AREA_2_EN = "010" + self.AREA_3_EN = "011" + self.AREA_FULL_0_EN = "" + self.AREA_FULL_1_EN = "" + self.AREA_FULL_2_EN = "" + self.AREA_FULL_3_EN = "" + + self.PREFECTURE_ID = "1" + self.EXPIRATION_DATE = "null" + self.USE_TOKEN = "0" + self.CONSUME_TOKEN = "0" + self.DONGLE_FLG = "1" + self.FORCE_BOOT = "0" + +class MuchaUpdateRequest(): + def __init__(self, request: Dict) -> None: + self.gameVer = "" if "gameVer" not in request else request["gameVer"] + self.gameCd = "" if "gameCd" not in request else request["gameCd"] + self.serialNum = "" if "serialNum" not in request else request["serialNum"] + self.countryCd = "" if "countryCd" not in request else request["countryCd"] + self.placeId = "" if "placeId" not in request else request["placeId"] + self.storeRouterIp = "" if "storeRouterIp" not in request else request["storeRouterIp"] + +class MuchaUpdateResponse(): + def __init__(self, game_ver: str = "PKFN0JPN01.01", mucha_url: str = "localhost") -> None: + self.RESULTS = "001" + self.UPDATE_VER_1 = game_ver + self.UPDATE_URL_1 = f"https://{mucha_url}/updUrl1/" + self.UPDATE_SIZE_1 = "0" + self.UPDATE_CRC_1 = "0000000000000000" + self.CHECK_URL_1 = f"https://{mucha_url}/checkUrl/" + self.EXE_VER_1 = game_ver + self.INFO_SIZE_1 = "0" + self.COM_SIZE_1 = "0" + self.COM_TIME_1 = "0" + self.LAN_INFO_SIZE_1 = "0" diff --git a/core/title.py b/core/title.py index 8387065..4d475cb 100644 --- a/core/title.py +++ b/core/title.py @@ -51,16 +51,25 @@ class TitleServlet(): self.logger.info(f"Serving {len(globals()['game_registry'])} game codes") def render_GET(self, request: Request, endpoints: dict) -> bytes: - print(endpoints) + code = endpoints["game"] + if code not in self.title_registry: + self.logger.warn(f"Unknown game code {code}") + + index = self.title_registry[code] + if not hasattr(index, "render_GET"): + self.logger.warn(f"{code} does not dispatch GET") + return b"" + + return index.render_GET(request, endpoints["version"], endpoints["endpoint"]) def render_POST(self, request: Request, endpoints: dict) -> bytes: - print(endpoints) code = endpoints["game"] if code not in self.title_registry: self.logger.warn(f"Unknown game code {code}") index = self.title_registry[code] if not hasattr(index, "render_POST"): - self.logger.warn(f"{code} does not dispatch on POST") + self.logger.warn(f"{code} does not dispatch POST") + return b"" return index.render_POST(request, endpoints["version"], endpoints["endpoint"]) diff --git a/example_config/core.yaml b/example_config/core.yaml index cb08e69..86d31a5 100644 --- a/example_config/core.yaml +++ b/example_config/core.yaml @@ -43,3 +43,11 @@ aimedb: loglevel: "info" port: 22345 key: "" + +mucha: + enable: False + hostname: "localhost" + loglevel: "info" + port: 8444 + ssl_key: "cert/server.key" + ssl_cert: "cert/server.pem" diff --git a/index.py b/index.py index 8b8ee86..7fae586 100644 --- a/index.py +++ b/index.py @@ -19,11 +19,15 @@ class HttpDispatcher(resource.Resource): self.allnet = AllnetServlet(cfg, config_dir) self.title = TitleServlet(cfg, config_dir) + self.mucha = MuchaServlet(cfg) self.map_post.connect('allnet_poweron', '/sys/servlet/PowerOn', controller="allnet", action='handle_poweron', conditions=dict(method=['POST'])) self.map_post.connect('allnet_downloadorder', '/sys/servlet/DownloadOrder', controller="allnet", action='handle_dlorder', conditions=dict(method=['POST'])) self.map_post.connect('allnet_billing', '/request', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST'])) + 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_updatacheck', conditions=dict(method=['POST'])) + self.map_get.connect("title_get", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_GET", requirements=dict(game=R"S...")) self.map_post.connect("title_post", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_POST", requirements=dict(game=R"S...")) diff --git a/titles/cxb/index.py b/titles/cxb/index.py index 62dc70d..f01cf3b 100644 --- a/titles/cxb/index.py +++ b/titles/cxb/index.py @@ -25,21 +25,23 @@ class CxbServlet(resource.Resource): self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/cxb.yaml"))) self.logger = logging.getLogger("cxb") - log_fmt_str = "[%(asctime)s] CXB | %(levelname)s | %(message)s" - log_fmt = logging.Formatter(log_fmt_str) - fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), encoding='utf8', - when="d", backupCount=10) + if not hasattr(self.logger, "inited"): + log_fmt_str = "[%(asctime)s] CXB | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), encoding='utf8', + when="d", backupCount=10) - fileHandler.setFormatter(log_fmt) - - consoleHandler = logging.StreamHandler() - consoleHandler.setFormatter(log_fmt) + fileHandler.setFormatter(log_fmt) + + consoleHandler = logging.StreamHandler() + consoleHandler.setFormatter(log_fmt) - self.logger.addHandler(fileHandler) - self.logger.addHandler(consoleHandler) - - self.logger.setLevel(self.game_cfg.server.loglevel) - coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) + self.logger.addHandler(fileHandler) + self.logger.addHandler(consoleHandler) + + self.logger.setLevel(self.game_cfg.server.loglevel) + coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) + self.logger.inited = True self.versions = [ CxbRev(core_cfg, self.game_cfg),