chuni: fix encrypted hash, update unlock challenge req

This commit is contained in:
2025-08-19 19:25:33 +02:00
parent 91f06ccfd2
commit 3c7ac3ac58
2 changed files with 112 additions and 91 deletions

View File

@ -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)
@ -156,10 +159,7 @@ 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(
@ -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)
@ -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:
@ -274,7 +285,9 @@ class ChuniServlet(BaseServlet):
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
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
@ -291,7 +304,9 @@ class ChuniServlet(BaseServlet):
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
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
@ -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)

View File

@ -61,7 +61,7 @@ class ChuniVerse(ChuniLuminousPlus):
"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: