From 09c4f8cda43e2d63f4d19e42393db6b2e604520e Mon Sep 17 00:00:00 2001 From: Midorica Date: Sat, 8 Jul 2023 18:44:02 -0400 Subject: [PATCH 1/3] Async request to CXB profile loading --- titles/cxb/base.py | 299 +++++++++++++++++++++++++-------------------- 1 file changed, 164 insertions(+), 135 deletions(-) diff --git a/titles/cxb/base.py b/titles/cxb/base.py index 749e8ac..a649f31 100644 --- a/titles/cxb/base.py +++ b/titles/cxb/base.py @@ -11,6 +11,7 @@ from titles.cxb.config import CxbConfig from titles.cxb.const import CxbConstants from titles.cxb.database import CxbData +from threading import Thread class CxbBase: def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: @@ -54,6 +55,151 @@ class CxbBase: self.logger.warn(f"User {data['login']['authid']} does not have a profile") return {} + def task_generateCoupon(index, data1): + # Coupons + for i in range(500, 510): + index.append(str(i)) + couponid = int(i) - 500 + dataValue = [ + { + "couponId": str(couponid), + "couponNum": "1", + "couponLog": [], + } + ] + data1.append( + b64encode( + bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8") + ).decode("utf-8") + ) + + def task_generateShopListTitle(index, data1): + # ShopList_Title + for i in range(200000, 201451): + index.append(str(i)) + shopid = int(i) - 200000 + dataValue = [ + { + "shopId": shopid, + "shopState": "2", + "isDisable": "t", + "isDeleted": "f", + "isSpecialFlag": "f", + } + ] + data1.append( + b64encode( + bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8") + ).decode("utf-8") + ) + + def task_generateShopListIcon(index, data1): + # ShopList_Icon + for i in range(202000, 202264): + index.append(str(i)) + shopid = int(i) - 200000 + dataValue = [ + { + "shopId": shopid, + "shopState": "2", + "isDisable": "t", + "isDeleted": "f", + "isSpecialFlag": "f", + } + ] + data1.append( + b64encode( + bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8") + ).decode("utf-8") + ) + + def task_generateStories(index, data1): + # Stories + for i in range(900000, 900003): + index.append(str(i)) + storyid = int(i) - 900000 + dataValue = [ + { + "storyId": storyid, + "unlockState1": ["t"] * 10, + "unlockState2": ["t"] * 10, + "unlockState3": ["t"] * 10, + "unlockState4": ["t"] * 10, + "unlockState5": ["t"] * 10, + "unlockState6": ["t"] * 10, + "unlockState7": ["t"] * 10, + "unlockState8": ["t"] * 10, + "unlockState9": ["t"] * 10, + "unlockState10": ["t"] * 10, + "unlockState11": ["t"] * 10, + "unlockState12": ["t"] * 10, + "unlockState13": ["t"] * 10, + "unlockState14": ["t"] * 10, + "unlockState15": ["t"] * 10, + "unlockState16": ["t"] * 10, + } + ] + data1.append( + b64encode( + bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8") + ).decode("utf-8") + ) + + def task_generateScoreData(song, index, data1): + song_data = song["data"] + songCode = [] + + songCode.append( + { + "mcode": song_data["mcode"], + "musicState": song_data["musicState"], + "playCount": song_data["playCount"], + "totalScore": song_data["totalScore"], + "highScore": song_data["highScore"], + "everHighScore": song_data["everHighScore"] + if "everHighScore" in song_data + else ["0", "0", "0", "0", "0"], + "clearRate": song_data["clearRate"], + "rankPoint": song_data["rankPoint"], + "normalCR": song_data["normalCR"] + if "normalCR" in song_data + else ["0", "0", "0", "0", "0"], + "survivalCR": song_data["survivalCR"] + if "survivalCR" in song_data + else ["0", "0", "0", "0", "0"], + "ultimateCR": song_data["ultimateCR"] + if "ultimateCR" in song_data + else ["0", "0", "0", "0", "0"], + "nohopeCR": song_data["nohopeCR"] + if "nohopeCR" in song_data + else ["0", "0", "0", "0", "0"], + "combo": song_data["combo"], + "coupleUserId": song_data["coupleUserId"], + "difficulty": song_data["difficulty"], + "isFullCombo": song_data["isFullCombo"], + "clearGaugeType": song_data["clearGaugeType"], + "fieldType": song_data["fieldType"], + "gameType": song_data["gameType"], + "grade": song_data["grade"], + "unlockState": song_data["unlockState"], + "extraState": song_data["extraState"], + } + ) + index.append(song_data["index"]) + data1.append( + b64encode( + bytes(json.dumps(songCode[0], separators=(",", ":")), "utf-8") + ).decode("utf-8") + ) + + def task_generateIndexData(versionindex): + try: + v_profile = self.data.profile.get_profile_index(0, uid, self.version) + v_profile_data = v_profile["data"] + versionindex.append(int(v_profile_data["appVersion"])) + except: + versionindex.append("10400") + def handle_action_loadrange_request(self, data: Dict) -> Dict: range_start = data["loadrange"]["range"][0] range_end = data["loadrange"]["range"][1] @@ -107,146 +253,29 @@ class CxbBase: 900000 = Stories """ - # Coupons - for i in range(500, 510): - index.append(str(i)) - couponid = int(i) - 500 - dataValue = [ - { - "couponId": str(couponid), - "couponNum": "1", - "couponLog": [], - } - ] - data1.append( - b64encode( - bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8") - ).decode("utf-8") - ) + # Async threads to generate the response + thread_Coupon = Thread(target=CxbBase.task_generateCoupon(index, data1)) + thread_ShopListTitle = Thread(target=CxbBase.task_generateShopListTitle(index, data1)) + thread_ShopListIcon = Thread(target=CxbBase.task_generateShopListIcon(index, data1)) + thread_Stories = Thread(target=CxbBase.task_generateStories(index, data1)) - # ShopList_Title - for i in range(200000, 201451): - index.append(str(i)) - shopid = int(i) - 200000 - dataValue = [ - { - "shopId": shopid, - "shopState": "2", - "isDisable": "t", - "isDeleted": "f", - "isSpecialFlag": "f", - } - ] - data1.append( - b64encode( - bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8") - ).decode("utf-8") - ) + thread_Coupon.start() + thread_ShopListTitle.start() + thread_ShopListIcon.start() + thread_Stories.start() - # ShopList_Icon - for i in range(202000, 202264): - index.append(str(i)) - shopid = int(i) - 200000 - dataValue = [ - { - "shopId": shopid, - "shopState": "2", - "isDisable": "t", - "isDeleted": "f", - "isSpecialFlag": "f", - } - ] - data1.append( - b64encode( - bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8") - ).decode("utf-8") - ) - - # Stories - for i in range(900000, 900003): - index.append(str(i)) - storyid = int(i) - 900000 - dataValue = [ - { - "storyId": storyid, - "unlockState1": ["t"] * 10, - "unlockState2": ["t"] * 10, - "unlockState3": ["t"] * 10, - "unlockState4": ["t"] * 10, - "unlockState5": ["t"] * 10, - "unlockState6": ["t"] * 10, - "unlockState7": ["t"] * 10, - "unlockState8": ["t"] * 10, - "unlockState9": ["t"] * 10, - "unlockState10": ["t"] * 10, - "unlockState11": ["t"] * 10, - "unlockState12": ["t"] * 10, - "unlockState13": ["t"] * 10, - "unlockState14": ["t"] * 10, - "unlockState15": ["t"] * 10, - "unlockState16": ["t"] * 10, - } - ] - data1.append( - b64encode( - bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8") - ).decode("utf-8") - ) + thread_Coupon.join() + thread_ShopListTitle.join() + thread_ShopListIcon.join() + thread_Stories.join() for song in songs: - song_data = song["data"] - songCode = [] - - songCode.append( - { - "mcode": song_data["mcode"], - "musicState": song_data["musicState"], - "playCount": song_data["playCount"], - "totalScore": song_data["totalScore"], - "highScore": song_data["highScore"], - "everHighScore": song_data["everHighScore"] - if "everHighScore" in song_data - else ["0", "0", "0", "0", "0"], - "clearRate": song_data["clearRate"], - "rankPoint": song_data["rankPoint"], - "normalCR": song_data["normalCR"] - if "normalCR" in song_data - else ["0", "0", "0", "0", "0"], - "survivalCR": song_data["survivalCR"] - if "survivalCR" in song_data - else ["0", "0", "0", "0", "0"], - "ultimateCR": song_data["ultimateCR"] - if "ultimateCR" in song_data - else ["0", "0", "0", "0", "0"], - "nohopeCR": song_data["nohopeCR"] - if "nohopeCR" in song_data - else ["0", "0", "0", "0", "0"], - "combo": song_data["combo"], - "coupleUserId": song_data["coupleUserId"], - "difficulty": song_data["difficulty"], - "isFullCombo": song_data["isFullCombo"], - "clearGaugeType": song_data["clearGaugeType"], - "fieldType": song_data["fieldType"], - "gameType": song_data["gameType"], - "grade": song_data["grade"], - "unlockState": song_data["unlockState"], - "extraState": song_data["extraState"], - } - ) - index.append(song_data["index"]) - data1.append( - b64encode( - bytes(json.dumps(songCode[0], separators=(",", ":")), "utf-8") - ).decode("utf-8") - ) + thread_ScoreData = Thread(target=CxbBase.task_generateScoreData(song, index, data1)) + thread_ScoreData.start() for v in index: - try: - v_profile = self.data.profile.get_profile_index(0, uid, self.version) - v_profile_data = v_profile["data"] - versionindex.append(int(v_profile_data["appVersion"])) - except: - versionindex.append("10400") + thread_IndexData = Thread(target=CxbBase.task_generateIndexData(versionindex)) + thread_IndexData.start() return {"index": index, "data": data1, "version": versionindex} @@ -544,4 +573,4 @@ class CxbBase: def handle_action_stampreq_request(self, data: Dict) -> Dict: self.logger.info(data) - return {"stampreq": ""} + return {"stampreq": ""} \ No newline at end of file From 85b73e634d64c5d2408a8b6b77399b1df25f31bf Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 12 Jul 2023 00:41:53 -0400 Subject: [PATCH 2/3] mucha: add DownloadState --- core/mucha.py | 81 ++++++++++++++++++++++++++++++++++++++++++--------- index.py | 7 +++++ 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/core/mucha.py b/core/mucha.py index eecae3a..6bb34d7 100644 --- a/core/mucha.py +++ b/core/mucha.py @@ -4,6 +4,7 @@ from logging.handlers import TimedRotatingFileHandler from twisted.web import resource from twisted.web.http import Request from datetime import datetime +from Crypto.Cipher import Blowfish import pytz from core import CoreConfig @@ -56,18 +57,22 @@ class MuchaServlet: self.logger.error( f"Error processing mucha request {request.content.getvalue()}" ) - return b"" + return b"RESULTS=000" req = MuchaAuthRequest(req_dict) + self.logger.info(f"Boardauth request from {client_ip} for {req.gameVer}") self.logger.debug(f"Mucha request {vars(req)}") - self.logger.info(f"Boardauth request from {client_ip} for {req.gameVer}") if req.gameCd not in self.mucha_registry: self.logger.warn(f"Unknown gameCd {req.gameCd}") - return b"" + return b"RESULTS=000" # TODO: Decrypt S/N + #cipher = Blowfish.new(req.sendDate.encode(), Blowfish.MODE_ECB) + #sn_decrypt = cipher.decrypt(bytes.fromhex(req.serialNum)) + #self.logger.debug(f"Decrypt SN to {sn_decrypt.hex()}") + resp = MuchaAuthResponse( f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}" ) @@ -84,22 +89,37 @@ class MuchaServlet: self.logger.error( f"Error processing mucha request {request.content.getvalue()}" ) - return b"" + return b"RESULTS=000" req = MuchaUpdateRequest(req_dict) + self.logger.info(f"Updatecheck request from {client_ip} for {req.gameVer}") self.logger.debug(f"Mucha request {vars(req)}") - self.logger.info(f"Updatecheck request from {client_ip} for {req.gameVer}") if req.gameCd not in self.mucha_registry: self.logger.warn(f"Unknown gameCd {req.gameCd}") - return b"" + return b"RESULTS=000" - resp = MuchaUpdateResponseStub(req.gameVer) + resp = MuchaUpdateResponse(req.gameVer, 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)}") return self.mucha_postprocess(vars(resp)) + def handle_dlstate(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()}" + ) + return b"" + + req = MuchaDownloadStateRequest(req_dict) + self.logger.info(f"DownloadState request from {client_ip} for {req.gameCd} -> {req.updateVer}") + self.logger.debug(f"request {vars(req)}") + return b"RESULTS=001" + def mucha_preprocess(self, data: bytes) -> Optional[Dict]: try: ret: Dict[str, Any] = {} @@ -202,22 +222,57 @@ class MuchaUpdateRequest: class MuchaUpdateResponse: def __init__(self, game_ver: str, mucha_url: str) -> None: - self.RESULTS = "001" + self.RESULTS = "001" + self.EXE_VER = game_ver + 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.UPDATE_URL_1 = f"http://{mucha_url}/updUrl1/" + self.UPDATE_SIZE_1 = "20" + + self.CHECK_CRC_1 = "0000000000000000" + self.CHECK_URL_1 = f"http://{mucha_url}/checkUrl/" + self.CHECK_SIZE_1 = "20" + self.INFO_SIZE_1 = "0" self.COM_SIZE_1 = "0" self.COM_TIME_1 = "0" self.LAN_INFO_SIZE_1 = "0" + self.USER_ID = "" self.PASSWORD = "" +""" +RESULTS +EXE_VER +UPDATE_VER_%d +UPDATE_URL_%d +UPDATE_SIZE_%d + +CHECK_CRC_%d +CHECK_URL_%d +CHECK_SIZE_%d + +INFO_SIZE_1 +COM_SIZE_1 +COM_TIME_1 +LAN_INFO_SIZE_1 + +USER_ID +PASSWORD +""" class MuchaUpdateResponseStub: def __init__(self, game_ver: str) -> None: self.RESULTS = "001" self.UPDATE_VER_1 = game_ver + +class MuchaDownloadStateRequest: + def __init__(self, request: Dict) -> None: + self.gameCd = request.get("gameCd", "") + self.updateVer = request.get("updateVer", "") + self.serialNum = request.get("serialNum", "") + self.fileSize = request.get("fileSize", "") + self.compFileSize = request.get("compFileSize", "") + self.boardId = request.get("boardId", "") + self.placeId = request.get("placeId", "") + self.storeRouterIp = request.get("storeRouterIp", "") diff --git a/index.py b/index.py index 15d6866..2ff7c04 100644 --- a/index.py +++ b/index.py @@ -113,6 +113,13 @@ class HttpDispatcher(resource.Resource): 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_get.connect( "title_get", From 2f135968858f68a8e53a418806ff7c316ae6750f Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 15 Jul 2023 00:15:14 -0400 Subject: [PATCH 3/3] fix db ignoring port in config, createing database no longer runs over version --- core/data/database.py | 6 +++--- core/data/schema/base.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/data/database.py b/core/data/database.py index 1688812..9fb2606 100644 --- a/core/data/database.py +++ b/core/data/database.py @@ -27,9 +27,9 @@ class Data: if self.config.database.sha2_password: passwd = sha256(self.config.database.password.encode()).digest() - self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4" + self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4" else: - self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4" + self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4" if Data.engine is None: Data.engine = create_engine(self.__url, pool_recycle=3600) @@ -94,7 +94,7 @@ class Data: game_mod.database(self.config) metadata.create_all(self.__engine.connect()) - self.base.set_schema_ver( + self.base.touch_schema_ver( game_mod.current_schema_version, game_mod.game_codes[0] ) diff --git a/core/data/schema/base.py b/core/data/schema/base.py index 7957301..319101f 100644 --- a/core/data/schema/base.py +++ b/core/data/schema/base.py @@ -102,6 +102,18 @@ class BaseData: return None return row["version"] + + def touch_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]: + sql = insert(schema_ver).values(game=game, version=ver) + conflict = sql.on_duplicate_key_update(version=schema_ver.c.version) + + result = self.execute(conflict) + if result is None: + self.logger.error( + f"Failed to update schema version for game {game} (v{ver})" + ) + return None + return result.lastrowid def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]: sql = insert(schema_ver).values(game=game, version=ver)