diff --git a/titles/chuni/index.py b/titles/chuni/index.py index f792e6b..ce4e02e 100644 --- a/titles/chuni/index.py +++ b/titles/chuni/index.py @@ -1,20 +1,22 @@ -from starlette.requests import Request -from starlette.routing import Route -from starlette.responses import Response +import asyncio +import re import logging import coloredlogs -from logging.handlers import TimedRotatingFileHandler import zlib import yaml import json import inflection import string +from os import path +from typing import Tuple, Dict, List +from logging.handlers import TimedRotatingFileHandler +from starlette.requests import Request +from starlette.routing import Route +from starlette.responses import Response from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Protocol.KDF import PBKDF2 from Crypto.Hash import SHA1 -from os import path -from typing import Tuple, Dict, List from core import CoreConfig, Utils from core.title import BaseServlet @@ -39,6 +41,7 @@ from .luminous import ChuniLuminous from .luminousplus import ChuniLuminousPlus from .verse import ChuniVerse + class ChuniServlet(BaseServlet): def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: super().__init__(core_cfg, cfg_dir) @@ -98,15 +101,15 @@ class ChuniServlet(BaseServlet): known_iter_counts = { ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS: 67, - f"{ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS}_int": 25, # SUPERSTAR + f"{ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS}_int": 25, # SUPERSTAR ChuniConstants.VER_CHUNITHM_PARADISE: 44, - f"{ChuniConstants.VER_CHUNITHM_PARADISE}_int": 51, # SUPERSTAR PLUS + f"{ChuniConstants.VER_CHUNITHM_PARADISE}_int": 51, # SUPERSTAR PLUS ChuniConstants.VER_CHUNITHM_NEW: 54, f"{ChuniConstants.VER_CHUNITHM_NEW}_int": 49, f"{ChuniConstants.VER_CHUNITHM_NEW}_chn": 37, ChuniConstants.VER_CHUNITHM_NEW_PLUS: 25, f"{ChuniConstants.VER_CHUNITHM_NEW_PLUS}_int": 31, - f"{ChuniConstants.VER_CHUNITHM_NEW_PLUS}_chn": 35, # NEW + f"{ChuniConstants.VER_CHUNITHM_NEW_PLUS}_chn": 35, # NEW ChuniConstants.VER_CHUNITHM_SUN: 70, f"{ChuniConstants.VER_CHUNITHM_SUN}_int": 35, ChuniConstants.VER_CHUNITHM_SUN_PLUS: 36, @@ -126,7 +129,7 @@ class ChuniServlet(BaseServlet): version_idx = version else: version_idx = int(version.split("_")[0]) - + salt = bytes.fromhex(keys[2]) if len(keys) >= 4: @@ -156,12 +159,9 @@ class ChuniServlet(BaseServlet): and version_idx >= ChuniConstants.VER_CHUNITHM_NEW ): method_fixed += "C3Exp" - elif ( - isinstance(version, str) - and version.endswith("_chn") - ): + elif isinstance(version, str) and version.endswith("_chn"): method_fixed += "Chn" - + hash = PBKDF2( method_fixed, salt, @@ -170,7 +170,8 @@ class ChuniServlet(BaseServlet): hmac_hash_module=SHA1, ) - hashed_name = hash.hex()[:32] # truncate unused bytes like the game does + # truncate unused bytes like the game does + hashed_name = hash.hex()[:32] self.hash_table[version][hashed_name] = method_fixed self.logger.debug( @@ -192,7 +193,9 @@ class ChuniServlet(BaseServlet): return True - 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]: title_port_int = Utils.get_title_port(self.core_cfg) title_port_ssl_int = Utils.get_title_port_ssl(self.core_cfg) @@ -206,7 +209,7 @@ class ChuniServlet(BaseServlet): if proto == "https": t_port = f":{title_port_ssl_int}" if title_port_ssl_int != 443 else "" - else: + else: t_port = f":{title_port_int}" if title_port_int != 80 else "" return ( @@ -216,8 +219,16 @@ class ChuniServlet(BaseServlet): def get_routes(self) -> List[Route]: return [ - Route("/{game:str}/{version:int}/ChuniServlet/{endpoint:str}", self.render_POST, methods=['POST']), - Route("/{game:str}/{version:int}/ChuniServlet/MatchingServer/{endpoint:str}", self.render_POST, methods=['POST']), + Route( + "/{game:str}/{version:int}/ChuniServlet/{endpoint:str}", + self.render_POST, + methods=["POST"], + ), + Route( + "/{game:str}/{version:int}/ChuniServlet/MatchingServer/{endpoint:str}", + self.render_POST, + methods=["POST"], + ), ] async def render_POST(self, request: Request) -> bytes: @@ -234,67 +245,71 @@ class ChuniServlet(BaseServlet): internal_ver = 0 client_ip = Utils.get_ip_addr(request) - if game_code == "SDHD" or game_code == "SDBT": # JP - if version < 105: # 1.0 - internal_ver = ChuniConstants.VER_CHUNITHM - elif version >= 105 and version < 110: # PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_PLUS - elif version >= 110 and version < 115: # AIR - internal_ver = ChuniConstants.VER_CHUNITHM_AIR - elif version >= 115 and version < 120: # AIR PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_AIR_PLUS - elif version >= 120 and version < 125: # STAR - internal_ver = ChuniConstants.VER_CHUNITHM_STAR - elif version >= 125 and version < 130: # STAR PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_STAR_PLUS - elif version >= 130 and version < 135: # AMAZON - internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON - elif version >= 135 and version < 140: # AMAZON PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS - elif version >= 140 and version < 145: # CRYSTAL - internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL - elif version >= 145 and version < 150: # CRYSTAL PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS - elif version >= 150 and version < 200: # PARADISE - internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE - elif version >= 200 and version < 205: # NEW!! - internal_ver = ChuniConstants.VER_CHUNITHM_NEW - elif version >= 205 and version < 210: # NEW PLUS!! - internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS - elif version >= 210 and version < 215: # SUN - internal_ver = ChuniConstants.VER_CHUNITHM_SUN - elif version >= 215 and version < 220: # SUN PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS - elif version >= 220 and version < 225: # LUMINOUS - internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS - elif version >= 225 and version < 230: # LUMINOUS PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS_PLUS - elif version >= 230: # VERSE - internal_ver = ChuniConstants.VER_CHUNITHM_VERSE - elif game_code == "SDGS": # Int - if version < 105: # SUPERSTAR - internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS - elif version >= 105 and version < 110: # SUPERSTAR PLUS *Cursed but needed due to different encryption key - internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE - elif version >= 110 and version < 115: # NEW - internal_ver = ChuniConstants.VER_CHUNITHM_NEW - elif version >= 115 and version < 120: # NEW PLUS!! - internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS - elif version >= 120 and version < 125: # SUN - internal_ver = ChuniConstants.VER_CHUNITHM_SUN - elif version >= 125 and version < 130: # SUN PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS - elif version >= 130 and version < 135: # LUMINOUS - internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS - elif version >= 135: # LUMINOUS PLUS - internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS_PLUS - elif game_code == "SDHJ": # Chn - if version < 110: # NEW - internal_ver = ChuniConstants.VER_CHUNITHM_NEW - elif version >= 110 and version < 120: # NEW *Cursed but needed due to different encryption key - internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS - elif version >= 120: # LUMINOUS - internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS + if game_code == "SDHD" or game_code == "SDBT": # JP + if version < 105: # 1.0 + internal_ver = ChuniConstants.VER_CHUNITHM + elif version >= 105 and version < 110: # PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_PLUS + elif version >= 110 and version < 115: # AIR + internal_ver = ChuniConstants.VER_CHUNITHM_AIR + elif version >= 115 and version < 120: # AIR PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_AIR_PLUS + elif version >= 120 and version < 125: # STAR + internal_ver = ChuniConstants.VER_CHUNITHM_STAR + elif version >= 125 and version < 130: # STAR PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_STAR_PLUS + elif version >= 130 and version < 135: # AMAZON + internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON + elif version >= 135 and version < 140: # AMAZON PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS + elif version >= 140 and version < 145: # CRYSTAL + internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL + elif version >= 145 and version < 150: # CRYSTAL PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS + elif version >= 150 and version < 200: # PARADISE + internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE + elif version >= 200 and version < 205: # NEW!! + internal_ver = ChuniConstants.VER_CHUNITHM_NEW + elif version >= 205 and version < 210: # NEW PLUS!! + internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS + elif version >= 210 and version < 215: # SUN + internal_ver = ChuniConstants.VER_CHUNITHM_SUN + elif version >= 215 and version < 220: # SUN PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS + elif version >= 220 and version < 225: # LUMINOUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS + elif version >= 225 and version < 230: # LUMINOUS PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS_PLUS + elif version >= 230: # VERSE + internal_ver = ChuniConstants.VER_CHUNITHM_VERSE + elif game_code == "SDGS": # Int + if version < 105: # SUPERSTAR + internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS + elif ( + version >= 105 and version < 110 + ): # SUPERSTAR PLUS *Cursed but needed due to different encryption key + internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE + elif version >= 110 and version < 115: # NEW + internal_ver = ChuniConstants.VER_CHUNITHM_NEW + elif version >= 115 and version < 120: # NEW PLUS!! + internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS + elif version >= 120 and version < 125: # SUN + internal_ver = ChuniConstants.VER_CHUNITHM_SUN + elif version >= 125 and version < 130: # SUN PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS + elif version >= 130 and version < 135: # LUMINOUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS + elif version >= 135: # LUMINOUS PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS_PLUS + elif game_code == "SDHJ": # Chn + if version < 110: # NEW + internal_ver = ChuniConstants.VER_CHUNITHM_NEW + elif ( + version >= 110 and version < 120 + ): # NEW *Cursed but needed due to different encryption key + internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS + elif version >= 120: # LUMINOUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: # If we get a 32 character long hex string, it's a hash and we're @@ -381,7 +396,7 @@ class ChuniServlet(BaseServlet): else: endpoint = endpoint - func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" + func_to_find = "handle_" + self.strict_underscore(endpoint) + "_request" handler_cls = self.versions[internal_ver](self.core_cfg, self.game_cfg) if not hasattr(handler_cls, func_to_find): @@ -419,3 +434,9 @@ class ChuniServlet(BaseServlet): ) return Response(crypt.encrypt(padded)) + + def strict_underscore(self, name: str) -> str: + # Insert underscores between *all* capital letters + name = re.sub(r"([A-Z])([A-Z])", r"\1_\2", name) + return inflection.underscore(name) + diff --git a/titles/chuni/verse.py b/titles/chuni/verse.py index 31138b6..6932bda 100644 --- a/titles/chuni/verse.py +++ b/titles/chuni/verse.py @@ -60,8 +60,8 @@ class ChuniVerse(ChuniLuminousPlus): "length": len(game_course_level_list), "gameCourseLevelList": game_course_level_list, } - - async def handle_get_game_uc_condition_api_request(self, data: Dict) -> Dict: + + async def handle_get_game_u_c_condition_api_request(self, data: Dict) -> Dict: unlock_challenges = await self.data.static.get_unlock_challenges(self.version) game_unlock_challenge_condition_list = [] @@ -84,8 +84,8 @@ class ChuniVerse(ChuniLuminousPlus): unlock_condition = conditions.get( unlock_challenge_id, { - "type": 3, # always unlocked - "conditionId": 0, + "type": MapAreaConditionType.ALWAYS_UNLOCKED.value, # always unlocked + "conditionId": -1, }, ) @@ -114,7 +114,7 @@ class ChuniVerse(ChuniLuminousPlus): "gameUnlockChallengeConditionList": game_unlock_challenge_condition_list, } - async def handle_get_user_uc_api_request(self, data: Dict) -> Dict: + async def handle_get_user_u_c_api_request(self, data: Dict) -> Dict: user_id = data["userId"] user_unlock_challenges = await self.data.item.get_unlock_challenges( @@ -167,7 +167,9 @@ class ChuniVerse(ChuniLuminousPlus): # try adding recommendations in order of: title → artist → genre for field in ("title", "artist", "genre"): - await self._add_recommendations(field, user_rec_music_set, music_info_list, rec_limit) + await self._add_recommendations( + field, user_rec_music_set, music_info_list, rec_limit + ) if len(user_rec_music_set) >= rec_limit: break @@ -220,9 +222,7 @@ class ChuniVerse(ChuniLuminousPlus): excluding music IDs already in the user's recent ratings and recommendations. """ # Collect all existing songId to exclude from recommendations - existing_music_ids = { - info["songId"] for info in music_info_list - } + existing_music_ids = {info["songId"] for info in music_info_list} for music_info in music_info_list: if len(user_rec_music_set) >= limit: