diff --git a/core/allnet.py b/core/allnet.py index 4c0c542..2738c8e 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -12,8 +12,8 @@ from Crypto.Signature import PKCS1_v1_5 from time import strptime from core.config import CoreConfig -from core.data import Data from core.utils import Utils +from core.data import Data from core.const import * @@ -65,11 +65,11 @@ class AllnetServlet: self.uri_registry[code] = (uri, host) self.logger.info( - f"Allnet serving {len(self.uri_registry)} games on port {core_cfg.allnet.port}" + f"Serving {len(self.uri_registry)} game codes port {core_cfg.allnet.port}" ) def handle_poweron(self, request: Request, _: Dict): - request_ip = request.getClientAddress().host + request_ip = Utils.get_ip_addr(request) try: req_dict = self.allnet_req_to_dict(request.content.getvalue()) if req_dict is None: @@ -78,13 +78,7 @@ class AllnetServlet: req = AllnetPowerOnRequest(req_dict[0]) # Validate the request. Currently we only validate the fields we plan on using - if ( - not req.game_id - or not req.ver - or not req.token - or not req.serial - or not req.ip - ): + if not req.game_id or not req.ver or not req.serial or not req.ip: raise AllnetRequestException( f"Bad auth request params from {request_ip} - {vars(req)}" ) @@ -94,7 +88,7 @@ class AllnetServlet: self.logger.error(e) return b"" - if req.format_ver == 3: + if req.format_ver == "3": resp = AllnetPowerOnResponse3(req.token) else: resp = AllnetPowerOnResponse2() @@ -168,7 +162,7 @@ class AllnetServlet: return self.dict_to_http_form_string([vars(resp)]).encode("utf-8") def handle_dlorder(self, request: Request, _: Dict): - request_ip = request.getClientAddress().host + request_ip = Utils.get_ip_addr(request) try: req_dict = self.allnet_req_to_dict(request.content.getvalue()) if req_dict is None: @@ -261,7 +255,7 @@ class AllnetServlet: return resp_str.encode("utf-8") def handle_naomitest(self, request: Request, _: Dict) -> bytes: - self.logger.info(f"Ping from {request.getClientAddress().host}") + self.logger.info(f"Ping from {Utils.get_ip_addr(request)}") return b"naomi ok" def kvp_to_dict(self, kvp: List[str]) -> List[Dict[str, Any]]: @@ -346,20 +340,16 @@ class AllnetPowerOnRequest: def __init__(self, req: Dict) -> None: if req is None: raise AllnetRequestException("Request processing failed") - self.game_id: str = req["game_id"] if "game_id" in req else "" - self.ver: str = req["ver"] if "ver" in req else "" - self.serial: str = req["serial"] if "serial" in req else "" - self.ip: str = req["ip"] if "ip" in req else "" - self.firm_ver: str = req["firm_ver"] if "firm_ver" in req else "" - self.boot_ver: str = req["boot_ver"] if "boot_ver" in req else "" - self.encode: str = req["encode"] if "encode" in req else "" - - try: - self.hops = int(req["hops"]) if "hops" in req else 0 - self.format_ver = int(req["format_ver"]) if "format_ver" in req else 2 - self.token = int(req["token"]) if "token" in req else 0 - except ValueError as e: - raise AllnetRequestException(f"Failed to parse int: {e}") + self.game_id: str = req.get("game_id", "") + self.ver: str = req.get("ver", "") + self.serial: str = req.get("serial", "") + self.ip: str = req.get("ip", "") + self.firm_ver: str = req.get("firm_ver", "") + self.boot_ver: str = req.get("boot_ver", "") + self.encode: str = req.get("encode", "") + self.hops = int(req.get("hops", "0")) + self.format_ver = req.get("format_ver", "2") + self.token = int(req.get("token", "0")) class AllnetPowerOnResponse3: @@ -413,10 +403,10 @@ class AllnetPowerOnResponse2: class AllnetDownloadOrderRequest: def __init__(self, req: Dict) -> None: - self.game_id = req["game_id"] if "game_id" in req else "" - self.ver = req["ver"] if "ver" in req else "" - self.serial = req["serial"] if "serial" in req else "" - self.encode = req["encode"] if "encode" in req else "" + self.game_id = req.get("game_id", "") + self.ver = req.get("ver", "") + self.serial = req.get("serial", "") + self.encode = req.get("encode", "") class AllnetDownloadOrderResponse: diff --git a/core/config.py b/core/config.py index 383f053..9e152c0 100644 --- a/core/config.py +++ b/core/config.py @@ -267,24 +267,6 @@ class MuchaConfig: 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: diff --git a/core/data/database.py b/core/data/database.py index 1af5c08..0ce039e 100644 --- a/core/data/database.py +++ b/core/data/database.py @@ -71,7 +71,8 @@ class Data: games = Utils.get_all_titles() for game_dir, game_mod in games.items(): try: - title_db = game_mod.database(self.config) + if hasattr(game_mod, "database") and hasattr(game_mod, "current_schema_version"): + game_mod.database(self.config) metadata.create_all(self.__engine.connect()) self.base.set_schema_ver( @@ -109,7 +110,8 @@ class Data: mod = importlib.import_module(f"titles.{dir}") try: - title_db = mod.database(self.config) + if hasattr(mod, "database"): + mod.database(self.config) metadata.drop_all(self.__engine.connect()) except Exception as e: diff --git a/core/frontend.py b/core/frontend.py index 2fdefae..e230681 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -10,9 +10,8 @@ from twisted.python.components import registerAdapter import jinja2 import bcrypt -from core.config import CoreConfig +from core import CoreConfig, Utils from core.data import Data -from core.utils import Utils class IUserSession(Interface): @@ -143,7 +142,7 @@ class FE_Gate(FE_Base): def render_POST(self, request: Request): uri = request.uri.decode() - ip = request.getClientAddress().host + ip = Utils.get_ip_addr(request) if uri == "/gate/gate.login": access_code: str = request.args[b"access_code"][0].decode() diff --git a/core/mucha.py b/core/mucha.py index 1b73b84..9dfef03 100644 --- a/core/mucha.py +++ b/core/mucha.py @@ -6,7 +6,7 @@ from twisted.web.http import Request from datetime import datetime import pytz -from core.config import CoreConfig +from core import CoreConfig from core.utils import Utils @@ -47,11 +47,13 @@ class MuchaServlet: self.mucha_registry.append(game_cd) self.logger.info( - f"Serving {len(self.mucha_registry)} games on port {self.config.mucha.port}" + f"Serving {len(self.mucha_registry)} games" ) def handle_boardauth(self, request: Request, _: Dict) -> bytes: req_dict = self.mucha_preprocess(request.content.getvalue()) + client_ip = Utils.get_ip_addr(request) + if req_dict is None: self.logger.error( f"Error processing mucha request {request.content.getvalue()}" @@ -61,7 +63,7 @@ class MuchaServlet: req = MuchaAuthRequest(req_dict) self.logger.debug(f"Mucha request {vars(req)}") self.logger.info( - f"Boardauth request from {request.getClientAddress().host} for {req.gameVer}" + f"Boardauth request from {client_ip} for {req.gameVer}" ) if req.gameCd not in self.mucha_registry: @@ -71,7 +73,7 @@ class MuchaServlet: # TODO: Decrypt S/N resp = MuchaAuthResponse( - f"{self.config.mucha.hostname}{':' + self.config.mucha.port if self.config.server.is_develop else ''}" + f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}" ) self.logger.debug(f"Mucha response {vars(resp)}") @@ -80,6 +82,8 @@ class MuchaServlet: def handle_updatecheck(self, request: Request, _: Dict) -> bytes: req_dict = self.mucha_preprocess(request.content.getvalue()) + client_ip = Utils.get_ip_addr(request) + if req_dict is None: self.logger.error( f"Error processing mucha request {request.content.getvalue()}" @@ -89,7 +93,7 @@ class MuchaServlet: req = MuchaUpdateRequest(req_dict) self.logger.debug(f"Mucha request {vars(req)}") self.logger.info( - f"Updatecheck request from {request.getClientAddress().host} for {req.gameVer}" + f"Updatecheck request from {client_ip} for {req.gameVer}" ) if req.gameCd not in self.mucha_registry: @@ -132,24 +136,19 @@ class MuchaServlet: class MuchaAuthRequest: def __init__(self, request: Dict) -> None: - self.gameVer = ( - "" if "gameVer" not in request else request["gameVer"] - ) # gameCd + boardType + countryCd + version - self.sendDate = ( - "" if "sendDate" not in request else request["sendDate"] - ) # %Y%m%d - 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.mac = "" if "mac" not in request else request["mac"] - 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"] + # gameCd + boardType + countryCd + version + self.gameVer = request.get("gameVer", "") + self.sendDate = request.get("sendDate", "") # %Y%m%d + self.serialNum = request.get("serialNum", "") + self.gameCd = request.get("gameCd", "") + self.boardType = request.get("boardType", "") + self.boardId = request.get("boardId", "") + self.mac = request.get("mac", "") + self.placeId = request.get("placeId", "") + self.storeRouterIp = request.get("storeRouterIp", "") + self.countryCd = request.get("countryCd", "") + self.useToken = request.get("useToken", "") + self.allToken = request.get("allToken", "") class MuchaAuthResponse: @@ -199,14 +198,12 @@ class MuchaAuthResponse: 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"] - ) + self.gameVer = request.get("gameVer", "") + self.gameCd = request.get("gameCd", "") + self.serialNum = request.get("serialNum", "") + self.countryCd = request.get("countryCd", "") + self.placeId = request.get("placeId", "") + self.storeRouterIp = request.get("storeRouterIp", "") class MuchaUpdateResponse: diff --git a/core/title.py b/core/title.py index c9580d2..7a0a99b 100644 --- a/core/title.py +++ b/core/title.py @@ -68,7 +68,7 @@ class TitleServlet: self.logger.error(f"{folder} missing game_code or index in __init__.py") self.logger.info( - f"Serving {len(self.title_registry)} game codes on port {core_cfg.title.port}" + f"Serving {len(self.title_registry)} game codes {'on port ' + str(core_cfg.title.port) if core_cfg.title.port > 0 else ''}" ) def render_GET(self, request: Request, endpoints: dict) -> bytes: diff --git a/core/utils.py b/core/utils.py index 24417bb..d18289e 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,5 +1,6 @@ from typing import Dict, Any from types import ModuleType +from twisted.web.http import Request import logging import importlib from os import walk @@ -21,3 +22,7 @@ class Utils: logging.getLogger("core").error(f"get_all_titles: {dir} - {e}") raise return ret + + @classmethod + def get_ip_addr(cls, req: Request) -> str: + return req.getAllHeaders()[b"x-forwarded-for"].decode() if b"x-forwarded-for" in req.getAllHeaders() else req.getClientAddress().host diff --git a/example_config/core.yaml b/example_config/core.yaml index 94e89ba..561293c 100644 --- a/example_config/core.yaml +++ b/example_config/core.yaml @@ -48,6 +48,3 @@ mucha: enable: False hostname: "localhost" loglevel: "info" - port: 8444 - ssl_key: "cert/server.key" - ssl_cert: "cert/server.pem" diff --git a/example_config/nginx_example.conf b/example_config/nginx_example.conf index fe6f7a7..6ffcd9c 100644 --- a/example_config/nginx_example.conf +++ b/example_config/nginx_example.conf @@ -4,6 +4,8 @@ server { server_name naominet.jp; location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass_request_headers on; proxy_pass http://localhost:8000/; } } @@ -14,6 +16,8 @@ server { server_name your.hostname.here; location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass_request_headers on; proxy_pass http://localhost:8080/; } } @@ -75,6 +79,8 @@ server { ssl_prefer_server_ciphers off; location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass_request_headers on; proxy_pass http://localhost:8080/; } } @@ -95,6 +101,8 @@ server { ssl_prefer_server_ciphers off; location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass_request_headers on; proxy_pass http://localhost:8080/SDBT/104/; } } @@ -131,6 +139,8 @@ server { add_header Strict-Transport-Security "max-age=63072000" always; location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass_request_headers on; proxy_pass http://localhost:8090/; } } \ No newline at end of file diff --git a/example_config/pokken.yaml b/example_config/pokken.yaml index e465ceb..7400060 100644 --- a/example_config/pokken.yaml +++ b/example_config/pokken.yaml @@ -3,9 +3,6 @@ server: enable: True loglevel: "info" port: 9000 - port_matching: 9001 - port_stun: 9002 - port_turn: 9003 - port_admission: 9004 - ssl_cert: cert/pokken.crt - ssl_key: cert/pokken.key \ No newline at end of file + port_stun: 9001 + port_turn: 9002 + port_admission: 9003 \ No newline at end of file diff --git a/index.py b/index.py index d30d51f..0852d06 100644 --- a/index.py +++ b/index.py @@ -73,7 +73,7 @@ class HttpDispatcher(resource.Resource): "mucha_updatacheck", "/mucha/updatacheck.do", controller="mucha", - action="handle_updatacheck", + action="handle_updatecheck", conditions=dict(method=["POST"]), ) @@ -96,9 +96,11 @@ class HttpDispatcher(resource.Resource): 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 {request.getClientAddress().host} to port {request.getHost().port}" + f"Unknown GET endpoint {request.uri.decode()} from {client_ip} to port {request.getHost().port}" ) request.setResponseCode(404) return b"Endpoint not found." @@ -107,9 +109,11 @@ class HttpDispatcher(resource.Resource): 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 {request.getClientAddress().host} to port {request.getHost().port}" + f"Unknown POST endpoint {request.uri.decode()} from {client_ip} to port {request.getHost().port}" ) request.setResponseCode(404) return b"Endpoint not found." @@ -163,6 +167,9 @@ if __name__ == "__main__": if path.exists(f"{args.config}/core.yaml"): cfg.update(yaml.safe_load(open(f"{args.config}/core.yaml"))) + if not path.exists(cfg.server.log_dir): + mkdir(cfg.server.log_dir) + logger = logging.getLogger("core") log_fmt_str = "[%(asctime)s] Core | %(levelname)s | %(message)s" log_fmt = logging.Formatter(log_fmt_str) @@ -182,9 +189,6 @@ if __name__ == "__main__": logger.setLevel(log_lv) coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str) - if not path.exists(cfg.server.log_dir): - mkdir(cfg.server.log_dir) - if not access(cfg.server.log_dir, W_OK): logger.error( f"Log directory {cfg.server.log_dir} NOT writable, please check permissions" diff --git a/read.py b/read.py index 538198a..341c502 100644 --- a/read.py +++ b/read.py @@ -9,8 +9,7 @@ import logging, coloredlogs from logging.handlers import TimedRotatingFileHandler from typing import List, Optional -from core import CoreConfig -from core.utils import Utils +from core import CoreConfig, Utils class BaseReader: diff --git a/titles/chuni/index.py b/titles/chuni/index.py index a8b581e..de01346 100644 --- a/titles/chuni/index.py +++ b/titles/chuni/index.py @@ -11,7 +11,7 @@ from Crypto.Util.Padding import pad from os import path from typing import Tuple -from core import CoreConfig +from core import CoreConfig, Utils from titles.chuni.config import ChuniConfig from titles.chuni.const import ChuniConstants from titles.chuni.base import ChuniBase @@ -111,6 +111,7 @@ class ChuniServlet: encrtped = False internal_ver = 0 endpoint = url_split[len(url_split) - 1] + client_ip = Utils.get_ip_addr(request) if version < 105: # 1.0 internal_ver = ChuniConstants.VER_CHUNITHM @@ -179,7 +180,7 @@ class ChuniServlet: req_data = json.loads(unzip) self.logger.info( - f"v{version} {endpoint} request from {request.getClientAddress().host}" + f"v{version} {endpoint} request from {client_ip}" ) self.logger.debug(req_data) diff --git a/titles/chuni/new.py b/titles/chuni/new.py index 03ad305..0d74ba6 100644 --- a/titles/chuni/new.py +++ b/titles/chuni/new.py @@ -60,6 +60,9 @@ class ChuniNew(ChuniBase): "isAou": "false", } + def handle_remove_token_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } + def handle_delete_token_api_request(self, data: Dict) -> Dict: return {"returnCode": "1"} diff --git a/titles/cm/cm136.py b/titles/cm/cm136.py index 5feeca4..fb5b6b5 100644 --- a/titles/cm/cm136.py +++ b/titles/cm/cm136.py @@ -26,6 +26,8 @@ class CardMaker136(CardMakerBase): ret["gameConnectList"][0]["titleUri"] = f"{uri}/SDHD/205/" ret["gameConnectList"][1]["titleUri"] = f"{uri}/SDEZ/125/" ret["gameConnectList"][2]["titleUri"] = f"{uri}/SDDT/135/" + + return ret def handle_get_game_setting_api_request(self, data: Dict) -> Dict: ret = super().handle_get_game_setting_api_request(data) diff --git a/titles/cxb/index.py b/titles/cxb/index.py index d6ab74b..36c762e 100644 --- a/titles/cxb/index.py +++ b/titles/cxb/index.py @@ -98,11 +98,11 @@ class CxbServlet(resource.Resource): ).listen(server.Site(CxbServlet(self.core_cfg, self.cfg_dir))) self.logger.info( - f"Crossbeats title server ready on port {self.game_cfg.server.port} & {self.game_cfg.server.port_secure}" + f"Ready on ports {self.game_cfg.server.port} & {self.game_cfg.server.port_secure}" ) else: self.logger.info( - f"Crossbeats title server ready on port {self.game_cfg.server.port}" + f"Ready on port {self.game_cfg.server.port}" ) def render_POST(self, request: Request): diff --git a/titles/diva/read.py b/titles/diva/read.py index 9c409ef..2eeacdc 100644 --- a/titles/diva/read.py +++ b/titles/diva/read.py @@ -298,6 +298,6 @@ class DivaReader(BaseReader): tree[key] = ( value if len(vector) == 1 - else self.add_branch(tree[key] if key in tree else {}, vector[1:], value) + else self.add_branch(tree.get(key, {}), vector[1:], value) ) return tree diff --git a/titles/pokken/base.py b/titles/pokken/base.py index f1f9eb3..6c2bf26 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -1,6 +1,6 @@ from datetime import datetime, timedelta -import json -from typing import Any +import json, logging +from typing import Any, Dict from core.config import CoreConfig from titles.pokken.config import PokkenConfig @@ -12,6 +12,7 @@ class PokkenBase: self.core_cfg = core_cfg self.game_cfg = game_cfg self.version = 0 + self.logger = logging.getLogger("pokken") def handle_noop(self, request: Any) -> bytes: res = jackal_pb2.Response() @@ -20,25 +21,26 @@ class PokkenBase: return res.SerializeToString() - def handle_ping(self, request: jackal_pb2.PingRequestData) -> bytes: + def handle_ping(self, request: jackal_pb2.Request) -> bytes: res = jackal_pb2.Response() res.result = 1 res.type = jackal_pb2.MessageType.PING return res.SerializeToString() - def handle_register_pcb(self, request: jackal_pb2.RegisterPcbRequestData) -> bytes: + def handle_register_pcb(self, request: jackal_pb2.Request) -> bytes: res = jackal_pb2.Response() res.result = 1 res.type = jackal_pb2.MessageType.REGISTER_PCB + self.logger.info(f"Register PCB {request.register_pcb.pcb_id}") regist_pcb = jackal_pb2.RegisterPcbResponseData() - regist_pcb.server_time = int(datetime.now().timestamp() / 1000) + regist_pcb.server_time = int(datetime.now().timestamp()) biwa_setting = { "MatchingServer": { "host": f"https://{self.game_cfg.server.hostname}", - "port": self.game_cfg.server.port_matching, - "url": "/matching", + "port": self.game_cfg.server.port, + "url": "/SDAK/100/matching", }, "StunServer": { "addr": self.game_cfg.server.hostname, @@ -60,7 +62,7 @@ class PokkenBase: return res.SerializeToString() - def handle_save_ads(self, request: jackal_pb2.SaveAdsRequestData) -> bytes: + def handle_save_ads(self, request: jackal_pb2.Request) -> bytes: res = jackal_pb2.Response() res.result = 1 res.type = jackal_pb2.MessageType.SAVE_ADS @@ -68,7 +70,7 @@ class PokkenBase: return res.SerializeToString() def handle_save_client_log( - self, request: jackal_pb2.SaveClientLogRequestData + self, request: jackal_pb2.Request ) -> bytes: res = jackal_pb2.Response() res.result = 1 @@ -77,7 +79,7 @@ class PokkenBase: return res.SerializeToString() def handle_check_diagnosis( - self, request: jackal_pb2.CheckDiagnosisRequestData + self, request: jackal_pb2.Request ) -> bytes: res = jackal_pb2.Response() res.result = 1 @@ -86,7 +88,7 @@ class PokkenBase: return res.SerializeToString() def handle_load_client_settings( - self, request: jackal_pb2.CheckDiagnosisRequestData + self, request: jackal_pb2.Request ) -> bytes: res = jackal_pb2.Response() res.result = 1 @@ -108,3 +110,36 @@ class PokkenBase: res.load_client_settings.CopyFrom(settings) return res.SerializeToString() + + def handle_load_ranking(self, request: jackal_pb2.Request) -> bytes: + res = jackal_pb2.Response() + res.result = 1 + res.type = jackal_pb2.MessageType.LOAD_RANKING + ranking = jackal_pb2.LoadRankingResponseData() + + ranking.ranking_id = 1 + ranking.ranking_start = 0 + ranking.ranking_end = 1 + ranking.event_end = True + ranking.modify_date = int(datetime.now().timestamp() / 1000) + res.load_ranking.CopyFrom(ranking) + + def handle_matching_noop(self, data: Dict = {}, client_ip: str = "127.0.0.1") -> Dict: + return {} + + def handle_matching_start_matching(self, data: Dict = {}, client_ip: str = "127.0.0.1") -> Dict: + return {} + + def handle_matching_is_matching(self, data: Dict = {}, client_ip: str = "127.0.0.1") -> Dict: + """ + "sessionId":"12345678", + "A":{ + "pcb_id": data["data"]["must"]["pcb_id"], + "gip": client_ip + }, + "list":[] + """ + return {} + + def handle_matching_stop_matching(self, data: Dict = {}, client_ip: str = "127.0.0.1") -> Dict: + return {} \ No newline at end of file diff --git a/titles/pokken/config.py b/titles/pokken/config.py index 3907838..b53fc86 100644 --- a/titles/pokken/config.py +++ b/titles/pokken/config.py @@ -31,43 +31,24 @@ class PokkenServerConfig: self.__config, "pokken", "server", "port", default=9000 ) - @property - def port_matching(self) -> int: - return CoreConfig.get_config_field( - self.__config, "pokken", "server", "port_matching", default=9001 - ) - @property def port_stun(self) -> int: return CoreConfig.get_config_field( - self.__config, "pokken", "server", "port_stun", default=9002 + self.__config, "pokken", "server", "port_stun", default=9001 ) @property def port_turn(self) -> int: return CoreConfig.get_config_field( - self.__config, "pokken", "server", "port_turn", default=9003 + self.__config, "pokken", "server", "port_turn", default=9002 ) @property def port_admission(self) -> int: return CoreConfig.get_config_field( - self.__config, "pokken", "server", "port_admission", default=9004 + self.__config, "pokken", "server", "port_admission", default=9003 ) - @property - def ssl_cert(self) -> str: - return CoreConfig.get_config_field( - self.__config, "pokken", "server", "ssl_cert", default="cert/pokken.crt" - ) - - @property - def ssl_key(self) -> str: - return CoreConfig.get_config_field( - self.__config, "pokken", "server", "ssl_key", default="cert/pokken.key" - ) - - class PokkenConfig(dict): def __init__(self) -> None: self.server = PokkenServerConfig(self) diff --git a/titles/pokken/index.py b/titles/pokken/index.py index bbf1204..42a5a0e 100644 --- a/titles/pokken/index.py +++ b/titles/pokken/index.py @@ -1,18 +1,20 @@ from typing import Tuple from twisted.web.http import Request -from twisted.web import resource, server -from twisted.internet import reactor, endpoints +from twisted.web import resource +import json, ast +from datetime import datetime import yaml import logging, coloredlogs from logging.handlers import TimedRotatingFileHandler -from titles.pokken.proto import jackal_pb2 +import inflection from os import path from google.protobuf.message import DecodeError -from core.config import CoreConfig +from core import CoreConfig, Utils from titles.pokken.config import PokkenConfig from titles.pokken.base import PokkenBase from titles.pokken.const import PokkenConstants +from titles.pokken.proto import jackal_pb2 class PokkenServlet(resource.Resource): @@ -65,17 +67,10 @@ class PokkenServlet(resource.Resource): if not game_cfg.server.enable: return (False, "", "") - if core_cfg.server.is_develop: - return ( - True, - f"https://{game_cfg.server.hostname}:{game_cfg.server.port}/{game_code}/$v/", - f"{game_cfg.server.hostname}:{game_cfg.server.port}/", - ) - return ( True, - f"https://{game_cfg.server.hostname}/{game_code}/$v/", - f"{game_cfg.server.hostname}/", + f"https://{game_cfg.server.hostname}:{game_cfg.server.port}/{game_code}/$v/", + f"{game_cfg.server.hostname}/SDAK/$v/", ) @classmethod @@ -90,46 +85,19 @@ class PokkenServlet(resource.Resource): ) if not game_cfg.server.enable: - return (False, "", "") + return (False, "") - return (True, "PKFN") + return (True, "PKF2") - def setup(self): - """ - There's currently no point in having this server on because Twisted - won't play ball with both the fact that it's TLSv1.1, and because the - types of certs that pokken will accept are too flimsy for Twisted - so it will throw a fit. Currently leaving this here in case a bypass - is discovered in the future, but it's unlikly. For now, just use NGINX. - """ - if self.game_cfg.server.enable and self.core_cfg.server.is_develop: - key_exists = path.exists(self.game_cfg.server.ssl_key) - cert_exists = path.exists(self.game_cfg.server.ssl_cert) - - if key_exists and cert_exists: - endpoints.serverFromString( - reactor, - f"ssl:{self.game_cfg.server.port}" - f":interface={self.core_cfg.server.listen_address}:privateKey={self.game_cfg.server.ssl_key}:" - f"certKey={self.game_cfg.server.ssl_cert}", - ).listen(server.Site(self)) - - self.logger.info( - f"Pokken title server ready on port {self.game_cfg.server.port}" - ) - - else: - self.logger.error( - f"Could not find cert at {self.game_cfg.server.ssl_key} or key at {self.game_cfg.server.ssl_cert}, Pokken not running." - ) + def setup(self) -> None: + # TODO: Setup stun, turn (UDP) and admission (WSS) servers + pass def render_POST( self, request: Request, version: int = 0, endpoints: str = "" ) -> bytes: - if endpoints == "": - endpoints = request.uri.decode() - if endpoints.startswith("/matching"): - self.logger.info("Matching request") + if endpoints == "matching": + return self.handle_matching(request) content = request.content.getvalue() if content == b"": @@ -143,12 +111,50 @@ class PokkenServlet(resource.Resource): self.logger.warn(f"{e} {content}") return b"" - endpoint = jackal_pb2.MessageType(pokken_request.type).name.lower() - - self.logger.info(f"{endpoint} request") + endpoint = jackal_pb2.MessageType.DESCRIPTOR.values_by_number[ + pokken_request.type + ].name.lower() handler = getattr(self.base, f"handle_{endpoint}", None) if handler is None: self.logger.warn(f"No handler found for message type {endpoint}") return self.base.handle_noop(pokken_request) - return handler(pokken_request) + + self.logger.info(f"{endpoint} request from {Utils.get_ip_addr(request)}") + self.logger.debug(pokken_request) + + ret = handler(pokken_request) + self.logger.debug(f"Response: {ret}") + return ret + + def handle_matching(self, request: Request) -> bytes: + content = request.content.getvalue() + client_ip = Utils.get_ip_addr(request) + + if content is None or content == b"": + self.logger.info("Empty matching request") + return json.dumps(self.base.handle_matching_noop()).encode() + + json_content = ast.literal_eval(content.decode().replace('null', 'None').replace('true', 'True').replace('false', 'False')) + self.logger.info(f"Matching {json_content['call']} request") + self.logger.debug(json_content) + + handler = getattr(self.base, f"handle_matching_{inflection.underscore(json_content['call'])}", None) + if handler is None: + self.logger.warn(f"No handler found for message type {json_content['call']}") + return json.dumps(self.base.handle_matching_noop()).encode() + + ret = handler(json_content, client_ip) + + if ret is None: + ret = {} + if "result" not in ret: + ret["result"] = "true" + if "data" not in ret: + ret["data"] = {} + if "timestamp" not in ret: + ret["timestamp"] = int(datetime.now().timestamp() * 1000) + + self.logger.debug(f"Response {ret}") + + return json.dumps(ret).encode() diff --git a/titles/wacca/frontend.py b/titles/wacca/frontend.py index e4f2be0..69ab1ee 100644 --- a/titles/wacca/frontend.py +++ b/titles/wacca/frontend.py @@ -1,6 +1,7 @@ import yaml import jinja2 from twisted.web.http import Request +from os import path from core.frontend import FE_Base from core.config import CoreConfig @@ -16,7 +17,10 @@ class WaccaFrontend(FE_Base): super().__init__(cfg, environment) self.data = WaccaData(cfg) self.game_cfg = WaccaConfig() - self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/wacca.yaml"))) + if path.exists(f"{cfg_dir}/{WaccaConstants.CONFIG_NAME}"): + self.game_cfg.update( + yaml.safe_load(open(f"{cfg_dir}/{WaccaConstants.CONFIG_NAME}")) + ) self.nav_name = "Wacca" def render_GET(self, request: Request) -> bytes: diff --git a/titles/wacca/index.py b/titles/wacca/index.py index 2a4a0cf..0b51849 100644 --- a/titles/wacca/index.py +++ b/titles/wacca/index.py @@ -8,7 +8,7 @@ from twisted.web.http import Request from typing import Dict, Tuple from os import path -from core.config import CoreConfig +from core import CoreConfig, Utils from titles.wacca.config import WaccaConfig from titles.wacca.config import WaccaConfig from titles.wacca.const import WaccaConstants @@ -89,25 +89,30 @@ class WaccaServlet: request.responseHeaders.addRawHeader(b"X-Wacca-Hash", hash.hex().encode()) return json.dumps(resp).encode() + client_ip = Utils.get_ip_addr(request) try: req_json = json.loads(request.content.getvalue()) version_full = Version(req_json["appVersion"]) except: self.logger.error( - f"Failed to parse request toi {request.uri} -> {request.content.getvalue()}" + f"Failed to parse request to {url_path} -> {request.content.getvalue()}" ) resp = BaseResponse() resp.status = 1 resp.message = "不正なリクエスト エラーです" return end(resp.make()) - url_split = url_path.split("/") - start_req_idx = url_split.index("api") + 1 + if "/api/" in url_path: + func_to_find = ( + "handle_" + url_path.partition("api/")[2].replace("/", "_") + "_request" + ) - func_to_find = "handle_" - for x in range(len(url_split) - start_req_idx): - func_to_find += f"{url_split[x + start_req_idx]}_" - func_to_find += "request" + else: + self.logger.error(f"Malformed url {url_path}") + resp = BaseResponse() + resp.status = 1 + resp.message = "Bad URL" + return end(resp.make()) ver_search = int(version_full) @@ -136,7 +141,7 @@ class WaccaServlet: return end(resp.make()) self.logger.info( - f"v{req_json['appVersion']} {url_path} request from {request.getClientAddress().host} with chipId {req_json['chipId']}" + f"v{req_json['appVersion']} {url_path} request from {client_ip} with chipId {req_json['chipId']}" ) self.logger.debug(req_json)