forked from Hay1tsme/artemis
add support for Maimai DX CN 2023 (舞萌DX 2023)
This commit is contained in:
parent
d09f3e9907
commit
dfa6b80e1b
1
.gitignore
vendored
1
.gitignore
vendored
@ -161,3 +161,4 @@ deliver/*
|
|||||||
*.gz
|
*.gz
|
||||||
|
|
||||||
dbdump-*.json
|
dbdump-*.json
|
||||||
|
/config/
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import hashlib
|
||||||
from typing import Dict, List, Any, Optional, Tuple, Union, Final
|
from typing import Dict, List, Any, Optional, Tuple, Union, Final
|
||||||
import logging, coloredlogs
|
import logging, coloredlogs
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
@ -512,6 +513,47 @@ class AllnetServlet:
|
|||||||
self.logger.info(f"Ping from {Utils.get_ip_addr(request)}")
|
self.logger.info(f"Ping from {Utils.get_ip_addr(request)}")
|
||||||
return b"naomi ok"
|
return b"naomi ok"
|
||||||
|
|
||||||
|
# def handle_qr_alive(self, request: Request, _: Dict):
|
||||||
|
# return b"alive"
|
||||||
|
#
|
||||||
|
# def handle_qr_lookup(self, request: Request, _: Dict) -> bytes:
|
||||||
|
# req = json.loads(request.content.getvalue())
|
||||||
|
# access_code = req["qrCode"][-20:]
|
||||||
|
# timestamp = req["timestamp"]
|
||||||
|
#
|
||||||
|
# try:
|
||||||
|
# userId = self.chimedb.handle_lookup(access_code)
|
||||||
|
# data = json.dumps({
|
||||||
|
# "userID": userId,
|
||||||
|
# "errorID": 0,
|
||||||
|
# "timestamp": timestamp,
|
||||||
|
# "key": self.hash_data(userId, timestamp)
|
||||||
|
# })
|
||||||
|
# except Exception as e:
|
||||||
|
#
|
||||||
|
# self.logger.error(e.with_traceback(None))
|
||||||
|
#
|
||||||
|
# data = json.dumps({
|
||||||
|
# "userID": -1,
|
||||||
|
# "errorID": 1,
|
||||||
|
# "timestamp": timestamp,
|
||||||
|
# "key": self.hash_data(-1, timestamp)
|
||||||
|
# })
|
||||||
|
#
|
||||||
|
# self.logger.info(data)
|
||||||
|
# return data.encode()
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def hash_data(self, chip_id, timestamp):
|
||||||
|
# input_string = f"{chip_id}{timestamp}XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW"
|
||||||
|
# hash_object = hashlib.sha256(input_string.encode('utf-8'))
|
||||||
|
# hex_dig = hash_object.hexdigest()
|
||||||
|
#
|
||||||
|
# formatted_hex = format(int(hex_dig, 16), '064x').upper()
|
||||||
|
#
|
||||||
|
# return formatted_hex
|
||||||
|
|
||||||
|
|
||||||
def billing_req_to_dict(self, data: bytes):
|
def billing_req_to_dict(self, data: bytes):
|
||||||
"""
|
"""
|
||||||
Parses an billing request string into a python dictionary
|
Parses an billing request string into a python dictionary
|
||||||
|
118
core/chimedb.py
Normal file
118
core/chimedb.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
from twisted.web.http import Request
|
||||||
|
|
||||||
|
from core.config import CoreConfig
|
||||||
|
from core.data import Data
|
||||||
|
|
||||||
|
|
||||||
|
class ChimeServlet:
|
||||||
|
def __init__(self, core_cfg: CoreConfig, cfg_folder: str) -> None:
|
||||||
|
self.config = core_cfg
|
||||||
|
self.config_folder = cfg_folder
|
||||||
|
|
||||||
|
self.data = Data(core_cfg)
|
||||||
|
|
||||||
|
self.logger = logging.getLogger("chimedb")
|
||||||
|
if not hasattr(self.logger, "initted"):
|
||||||
|
log_fmt_str = "[%(asctime)s] Chimedb | %(levelname)s | %(message)s"
|
||||||
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
|
|
||||||
|
fileHandler = TimedRotatingFileHandler(
|
||||||
|
"{0}/{1}.log".format(self.config.server.log_dir, "aimedb"),
|
||||||
|
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(self.config.aimedb.loglevel)
|
||||||
|
coloredlogs.install(
|
||||||
|
level=core_cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
|
)
|
||||||
|
self.logger.initted = True
|
||||||
|
|
||||||
|
self.logger.info("Serving")
|
||||||
|
|
||||||
|
def handle_qr_alive(self, request: Request, _: Dict):
|
||||||
|
return b"alive"
|
||||||
|
|
||||||
|
def handle_qr_lookup(self, request: Request, _: Dict) -> bytes:
|
||||||
|
req = json.loads(request.content.getvalue())
|
||||||
|
access_code = req["qrCode"][-20:]
|
||||||
|
timestamp = req["timestamp"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
userId = self._lookup(access_code)
|
||||||
|
data = json.dumps({
|
||||||
|
"userID": userId,
|
||||||
|
"errorID": 0,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"key": self._hash_key(userId, timestamp)
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
self.logger.error(e.with_traceback(None))
|
||||||
|
|
||||||
|
data = json.dumps({
|
||||||
|
"userID": -1,
|
||||||
|
"errorID": 1,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"key": self._hash_key(-1, timestamp)
|
||||||
|
})
|
||||||
|
|
||||||
|
return data.encode()
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_key(self, chip_id, timestamp):
|
||||||
|
input_string = f"{chip_id}{timestamp}{self.config.chime.key}"
|
||||||
|
hash_object = hashlib.sha256(input_string.encode('utf-8'))
|
||||||
|
hex_dig = hash_object.hexdigest()
|
||||||
|
|
||||||
|
formatted_hex = format(int(hex_dig, 16), '064x').upper()
|
||||||
|
|
||||||
|
return formatted_hex
|
||||||
|
|
||||||
|
def _lookup(self, access_code):
|
||||||
|
user_id = self.data.card.get_user_id_from_card(access_code)
|
||||||
|
|
||||||
|
self.logger.info(f"access_code {access_code} -> user_id {user_id}")
|
||||||
|
|
||||||
|
if not user_id or user_id <= 0:
|
||||||
|
user_id = self._register(access_code)
|
||||||
|
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
def _register(self, access_code):
|
||||||
|
user_id = -1
|
||||||
|
|
||||||
|
if self.config.server.allow_user_registration:
|
||||||
|
user_id = self.data.user.create_user()
|
||||||
|
|
||||||
|
if user_id is None:
|
||||||
|
self.logger.error("Failed to register user!")
|
||||||
|
user_id = -1
|
||||||
|
else:
|
||||||
|
card_id = self.data.card.create_card(user_id, access_code)
|
||||||
|
|
||||||
|
if card_id is None:
|
||||||
|
self.logger.error("Failed to register card!")
|
||||||
|
user_id = -1
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"Register access code {access_code} -> user_id {user_id}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.logger.info(f"Registration blocked!: access code {access_code}")
|
||||||
|
|
||||||
|
return user_id
|
@ -351,6 +351,22 @@ class MuchaConfig:
|
|||||||
self.__config, "core", "mucha", "hostname", default="localhost"
|
self.__config, "core", "mucha", "hostname", default="localhost"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class ChimeConfig:
|
||||||
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
|
self.__config = parent_config
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enable(self) -> int:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "chime", "enable", default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key(self) -> str:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "chime", "key", default=""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CoreConfig(dict):
|
class CoreConfig(dict):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -362,6 +378,7 @@ class CoreConfig(dict):
|
|||||||
self.billing = BillingConfig(self)
|
self.billing = BillingConfig(self)
|
||||||
self.aimedb = AimedbConfig(self)
|
self.aimedb = AimedbConfig(self)
|
||||||
self.mucha = MuchaConfig(self)
|
self.mucha = MuchaConfig(self)
|
||||||
|
self.chime = ChimeConfig(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def str_to_loglevel(cls, level_str: str):
|
def str_to_loglevel(cls, level_str: str):
|
||||||
|
@ -63,3 +63,7 @@ mucha:
|
|||||||
enable: False
|
enable: False
|
||||||
hostname: "localhost"
|
hostname: "localhost"
|
||||||
loglevel: "info"
|
loglevel: "info"
|
||||||
|
|
||||||
|
chime:
|
||||||
|
enable: False
|
||||||
|
key: ""
|
||||||
|
28
index.py
28
index.py
@ -13,6 +13,9 @@ from twisted.web.http import Request
|
|||||||
from routes import Mapper
|
from routes import Mapper
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
from core.chimedb import ChimeServlet
|
||||||
|
|
||||||
|
|
||||||
class HttpDispatcher(resource.Resource):
|
class HttpDispatcher(resource.Resource):
|
||||||
def __init__(self, cfg: CoreConfig, config_dir: str):
|
def __init__(self, cfg: CoreConfig, config_dir: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -25,6 +28,7 @@ class HttpDispatcher(resource.Resource):
|
|||||||
self.title = TitleServlet(cfg, config_dir)
|
self.title = TitleServlet(cfg, config_dir)
|
||||||
self.allnet = AllnetServlet(cfg, config_dir)
|
self.allnet = AllnetServlet(cfg, config_dir)
|
||||||
self.mucha = MuchaServlet(cfg, config_dir)
|
self.mucha = MuchaServlet(cfg, config_dir)
|
||||||
|
self.chime = ChimeServlet(cfg, config_dir)
|
||||||
|
|
||||||
self.map_get.connect(
|
self.map_get.connect(
|
||||||
"allnet_downloadorder_ini",
|
"allnet_downloadorder_ini",
|
||||||
@ -144,6 +148,30 @@ class HttpDispatcher(resource.Resource):
|
|||||||
conditions=dict(method=["POST"]),
|
conditions=dict(method=["POST"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Chime
|
||||||
|
if cfg.chime.enable:
|
||||||
|
self.map_post.connect(
|
||||||
|
"chime_qr_alive",
|
||||||
|
"/qrcode/api/alive_check",
|
||||||
|
controller="chime",
|
||||||
|
action="handle_qr_alive",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"chime_chime_alive",
|
||||||
|
"/wc_aime/api/alive_check",
|
||||||
|
controller="chime",
|
||||||
|
action="handle_qr_alive",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"chime_qr_lookup",
|
||||||
|
"/wc_aime/api/get_data",
|
||||||
|
controller="chime",
|
||||||
|
action="handle_qr_lookup",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
|
||||||
for code, game in self.title.title_registry.items():
|
for code, game in self.title.title_registry.items():
|
||||||
get_matchers, post_matchers = game.get_endpoint_matchers()
|
get_matchers, post_matchers = game.get_endpoint_matchers()
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ database = Mai2Data
|
|||||||
reader = Mai2Reader
|
reader = Mai2Reader
|
||||||
game_codes = [
|
game_codes = [
|
||||||
Mai2Constants.GAME_CODE_DX,
|
Mai2Constants.GAME_CODE_DX,
|
||||||
|
Mai2Constants.GAME_CODE_DX_CN,
|
||||||
Mai2Constants.GAME_CODE_FINALE,
|
Mai2Constants.GAME_CODE_FINALE,
|
||||||
Mai2Constants.GAME_CODE_MILK,
|
Mai2Constants.GAME_CODE_MILK,
|
||||||
Mai2Constants.GAME_CODE_MURASAKI,
|
Mai2Constants.GAME_CODE_MURASAKI,
|
||||||
|
43
titles/mai2/cn2023.py
Normal file
43
titles/mai2/cn2023.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from core.config import CoreConfig
|
||||||
|
from titles.mai2.festival import Mai2Festival
|
||||||
|
from titles.mai2.const import Mai2Constants
|
||||||
|
from titles.mai2.config import Mai2Config
|
||||||
|
|
||||||
|
|
||||||
|
class Mai2CN2023(Mai2Festival):
|
||||||
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
|
super().__init__(cfg, game_cfg)
|
||||||
|
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
||||||
|
|
||||||
|
def handle_get_game_setting_api_request(self, data: Dict):
|
||||||
|
return {
|
||||||
|
"gameSetting": {
|
||||||
|
"isMaintenance": False,
|
||||||
|
"requestInterval": 1800,
|
||||||
|
"rebootStartTime": "2020-01-01 07:00:00.0",
|
||||||
|
"rebootEndTime": "2020-01-01 07:59:59.0",
|
||||||
|
"movieUploadLimit": 100,
|
||||||
|
"movieStatus": 1,
|
||||||
|
"movieServerUri": "",
|
||||||
|
"deliverServerUri": "",
|
||||||
|
"oldServerUri": "",
|
||||||
|
"usbDlServerUri": "",
|
||||||
|
"rebootInterval": 0,
|
||||||
|
},
|
||||||
|
"isAouAccession": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
|
||||||
|
extend = self.data.profile.get_profile_extend(data["userId"], self.version)
|
||||||
|
if extend is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
extend_dict = extend._asdict()
|
||||||
|
extend_dict.pop("id")
|
||||||
|
extend_dict.pop("user")
|
||||||
|
extend_dict.pop("version")
|
||||||
|
extend_dict["isPhotoAgree"] = False
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userExtend": extend_dict}
|
@ -28,6 +28,51 @@ class Mai2Constants:
|
|||||||
GAME_CODE_MILK = "SDDZ"
|
GAME_CODE_MILK = "SDDZ"
|
||||||
GAME_CODE_FINALE = "SDEY"
|
GAME_CODE_FINALE = "SDEY"
|
||||||
GAME_CODE_DX = "SDEZ"
|
GAME_CODE_DX = "SDEZ"
|
||||||
|
GAME_CODE_DX_CN = "SDGB"
|
||||||
|
|
||||||
|
ChnHandlerMap_2023 = {
|
||||||
|
"df7e9c35e8f1d31dc327d583408e90d0": "GetGameChargeApi",
|
||||||
|
"1ac0c2058d942a18399610a37dd20358": "GetGameEventApi",
|
||||||
|
"372ef22cc41839e3b8c4707cad5324ee": "GetGameNgMusicIdApi",
|
||||||
|
"86fe0258e34b7fa265791edfbb6366aa": "GetGameRankingApi",
|
||||||
|
"86adcd106b5e4c4949f86e534010bb99": "GetGameSettingApi",
|
||||||
|
"bc5548b3ff3548140901279b17349e9c": "GetGameTournamentInfoApi",
|
||||||
|
"f9f83fc287e241c3d2a941ab5c9845a9": "GetTransferFriendApi",
|
||||||
|
"4f24fc175e4502e405d3650ecac5ee20": "GetUserActivityApi",
|
||||||
|
"48cd2ccd926ebe544e38fbd1495d1210": "GetUserCardApi",
|
||||||
|
"b98d4b35dadcc3c121971802b892aa26": "GetUserCharacterApi",
|
||||||
|
"49df62c94b5bf9c6092880a91166671e": "GetUserChargeApi",
|
||||||
|
"26474eb8e8f83f522c3fa017c7d3c6f6": "GetUserCourseApi",
|
||||||
|
"40ed28c7e603f7559cd8d16c6399e6eb": "GetUserDataApi",
|
||||||
|
"40cd997e881eb29040e20c7b5b2b22af": "GetUserExtendApi",
|
||||||
|
"943671dac71ea85ebe850856887cde3e": "GetUserFavoriteApi",
|
||||||
|
"4bcf0a53b49884c582801620fc8fda89": "GetUserFriendSeasonRankingApi",
|
||||||
|
"14e68e6a7a4cce9b1b87e91365271230": "GetUserGhostApi",
|
||||||
|
"a0cf80f9feb2261a9975bfbde6b36261": "GetUserItemApi",
|
||||||
|
"f8cbf404789ebadeefde45db227960a9": "GetUserLoginBonusApi",
|
||||||
|
"4a800f9ad31e9463895a024ad8c8f92e": "GetUserMapApi",
|
||||||
|
"d3bd8bb98e306472a16873db22a9f52f": "GetUserMusicApi",
|
||||||
|
"b2a3e39bc0932be284ffc7a24b8942f6": "GetUserOptionApi",
|
||||||
|
"defaae160852c0438f5c61fedfa85628": "GetUserPortraitApi",
|
||||||
|
"7b75e0110f28db678a8ea1839956a3ae": "GetUserPreviewApi",
|
||||||
|
"edafd9a8158b9a2b768c504e5e9beef7": "GetUserRatingApi",
|
||||||
|
"b58c88ca93d22577b1f661eb638ab353": "GetUserRecommendRateMusicApi",
|
||||||
|
"d3b1c5303f39dc883121e3bc2752dc32": "GetUserRecommendSelectionMusicApi",
|
||||||
|
"bfaa99205d692df04acd9152019b1158": "GetUserRegionApi",
|
||||||
|
"262f6b3ee97d3ade84fbb1c77f1a3f8f": "GetUserScoreRankingApi",
|
||||||
|
"92b4b94dfb05fbab78f7cb9f3362b86e": "PingApi",
|
||||||
|
"e632f71d878ca3d663a869c3eaf4a6d3": "UploadUserChargelogApi",
|
||||||
|
"24f785ce415736c9fcaf192a2d781054": "UploadUserPhotoApi",
|
||||||
|
"fa332c56cea3f545a3dd4417bae27aea": "UploadUserPlaylogApi",
|
||||||
|
"3123dd873f5cf3647a3f8f7e28e63950": "UploadUserPortraitApi",
|
||||||
|
"732e94bf8e87711b2016d9765f0e3ec4": "UpsertClientBookkeepingApi",
|
||||||
|
"43c43ca5ff0fc70b9a89db11334714c0": "UpsertClientSettingApi",
|
||||||
|
"df76314dfb75cd41b787213f45ddc639": "UpsertClientTestmodeApi",
|
||||||
|
"60d168b9b3792a6824ee20b02a385b48": "UpsertClientUploadApi",
|
||||||
|
"58896357b8cf354aa942a694828d2ca4": "UpsertUserAllApi",
|
||||||
|
"0223710b103248981096f31b1024cedd": "UserLoginApi",
|
||||||
|
"cc30d041222440909b40ef617de972f1": "UserLogoutApi",
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG_NAME = "mai2.yaml"
|
CONFIG_NAME = "mai2.yaml"
|
||||||
|
|
||||||
@ -54,6 +99,17 @@ class Mai2Constants:
|
|||||||
VER_MAIMAI_DX_FESTIVAL = 19
|
VER_MAIMAI_DX_FESTIVAL = 19
|
||||||
VER_MAIMAI_DX_FESTIVAL_PLUS = 20
|
VER_MAIMAI_DX_FESTIVAL_PLUS = 20
|
||||||
|
|
||||||
|
ChnHandlerMapMatch = {
|
||||||
|
VER_MAIMAI_DX_FESTIVAL: ChnHandlerMap_2023
|
||||||
|
}
|
||||||
|
|
||||||
|
ChnAesMatch = {
|
||||||
|
VER_MAIMAI_DX_FESTIVAL: [
|
||||||
|
b"F2Rc8F0x2Ly6LiIFy9K>s_Y0Bum62H;R",
|
||||||
|
b"PR12H;E2Brw@5kJ<"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
VERSION_STRING = (
|
VERSION_STRING = (
|
||||||
"maimai",
|
"maimai",
|
||||||
"maimai PLUS",
|
"maimai PLUS",
|
||||||
|
42
titles/mai2/cryption.py
Normal file
42
titles/mai2/cryption.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import zlib
|
||||||
|
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
import base64
|
||||||
|
|
||||||
|
class CipherAES:
|
||||||
|
def __init__(self,AES_KEY,AES_IV, BLOCK_SIZE=128, KEY_SIZE=256):
|
||||||
|
self.BLOCK_SIZE = BLOCK_SIZE
|
||||||
|
self.KEY_SIZE = KEY_SIZE
|
||||||
|
self.AES_KEY = AES_KEY
|
||||||
|
self.AES_IV = AES_IV
|
||||||
|
|
||||||
|
def _pad(self,data):
|
||||||
|
block_size = self.BLOCK_SIZE // 8
|
||||||
|
padding_length = block_size - len(data) % block_size
|
||||||
|
return data + bytes([padding_length]) * padding_length
|
||||||
|
|
||||||
|
def _unpad(self, padded_data):
|
||||||
|
pad_char = padded_data[-1]
|
||||||
|
if not 1 <= pad_char <= self.BLOCK_SIZE // 8:
|
||||||
|
raise ValueError("Invalid padding")
|
||||||
|
return padded_data[:-pad_char]
|
||||||
|
|
||||||
|
def encrypt(self, plaintext):
|
||||||
|
if isinstance(plaintext, str):
|
||||||
|
plaintext = plaintext.encode('utf-8')
|
||||||
|
backend = default_backend()
|
||||||
|
cipher = Cipher(algorithms.AES(self.AES_KEY), modes.CBC(self.AES_IV), backend=backend)
|
||||||
|
encryptor = cipher.encryptor()
|
||||||
|
|
||||||
|
padded_plaintext = self._pad(plaintext)
|
||||||
|
ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
|
||||||
|
return ciphertext
|
||||||
|
|
||||||
|
def decrypt(self, ciphertext):
|
||||||
|
backend = default_backend()
|
||||||
|
cipher = Cipher(algorithms.AES(self.AES_KEY), modes.CBC(self.AES_IV), backend=backend)
|
||||||
|
decryptor = cipher.decryptor()
|
||||||
|
|
||||||
|
decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()
|
||||||
|
return self._unpad(decrypted_data)
|
@ -13,6 +13,8 @@ from typing import Tuple, List, Dict
|
|||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
from core.title import BaseServlet
|
from core.title import BaseServlet
|
||||||
|
from . import cryption
|
||||||
|
from .cn2023 import Mai2CN2023
|
||||||
from .config import Mai2Config
|
from .config import Mai2Config
|
||||||
from .const import Mai2Constants
|
from .const import Mai2Constants
|
||||||
from .base import Mai2Base
|
from .base import Mai2Base
|
||||||
@ -60,6 +62,30 @@ class Mai2Servlet(BaseServlet):
|
|||||||
Mai2FestivalPlus,
|
Mai2FestivalPlus,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
self.cn_versions = [
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Mai2CN2023,
|
||||||
|
None,
|
||||||
|
]
|
||||||
|
|
||||||
self.logger = logging.getLogger("mai2")
|
self.logger = logging.getLogger("mai2")
|
||||||
if not hasattr(self.logger, "initted"):
|
if not hasattr(self.logger, "initted"):
|
||||||
log_fmt_str = "[%(asctime)s] Mai2 | %(levelname)s | %(message)s"
|
log_fmt_str = "[%(asctime)s] Mai2 | %(levelname)s | %(message)s"
|
||||||
@ -232,12 +258,11 @@ class Mai2Servlet(BaseServlet):
|
|||||||
def handle_mai2(self, request: Request, game_code: str, matchers: Dict) -> bytes:
|
def handle_mai2(self, request: Request, game_code: str, matchers: Dict) -> bytes:
|
||||||
endpoint = matchers['endpoint']
|
endpoint = matchers['endpoint']
|
||||||
version = int(matchers['version'])
|
version = int(matchers['version'])
|
||||||
if endpoint.lower() == "ping":
|
|
||||||
return zlib.compress(b'{"returnCode": "1"}')
|
|
||||||
|
|
||||||
req_raw = request.content.getvalue()
|
req_raw = request.content.getvalue()
|
||||||
internal_ver = 0
|
internal_ver = 0
|
||||||
client_ip = Utils.get_ip_addr(request)
|
client_ip = Utils.get_ip_addr(request)
|
||||||
|
|
||||||
if version < 105: # 1.0
|
if version < 105: # 1.0
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
||||||
elif version >= 105 and version < 110: # PLUS
|
elif version >= 105 and version < 110: # PLUS
|
||||||
@ -250,11 +275,43 @@ class Mai2Servlet(BaseServlet):
|
|||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||||
elif version >= 125 and version < 130: # UNiVERSE PLUS
|
elif version >= 125 and version < 130: # UNiVERSE PLUS
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
||||||
elif version >= 130 and version < 135: # FESTiVAL
|
elif version >= 130 and version < 135: # FESTiVAL OR CN2023
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
||||||
elif version >= 135: # FESTiVAL PLUS
|
elif version >= 135: # FESTiVAL PLUS
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS
|
||||||
|
|
||||||
|
is_cn = False
|
||||||
|
if endpoint in Mai2Constants.ChnHandlerMapMatch[internal_ver].keys():
|
||||||
|
endpoint = Mai2Constants.ChnHandlerMapMatch[internal_ver][endpoint]
|
||||||
|
is_cn = True
|
||||||
|
if endpoint.endswith("MaimaiChn"):
|
||||||
|
is_cn = True
|
||||||
|
endpoint = endpoint[:-9]
|
||||||
|
|
||||||
|
cn_crypto = cryption.CipherAES(Mai2Constants.ChnAesMatch[internal_ver][0], Mai2Constants.ChnAesMatch[internal_ver][1])
|
||||||
|
|
||||||
|
try:
|
||||||
|
unzip = zlib.decompress(req_raw)
|
||||||
|
except zlib.error as e:
|
||||||
|
self.logger.error(
|
||||||
|
f"Failed to decompress v{version} {endpoint} request -> {e}"
|
||||||
|
)
|
||||||
|
return zlib.compress(cn_crypto.encrypt(b'{"stat": "0"}') if is_cn else b'{"stat": "0"}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
decrypted = cn_crypto.decrypt(unzip)
|
||||||
|
is_cn = True
|
||||||
|
except:
|
||||||
|
self.logger.info(f"Failed to decrypt v{version} {endpoint} request, maybe not encrypted!")
|
||||||
|
is_cn = False
|
||||||
|
decrypted = unzip
|
||||||
|
|
||||||
|
req_data = json.loads(decrypted)
|
||||||
|
|
||||||
|
if endpoint.lower() == "ping":
|
||||||
|
return zlib.compress(cn_crypto.encrypt(b'{"returnCode": "1"}') if is_cn else b'{"returnCode": "1"}')
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
request.getHeader("Mai-Encoding") is not None
|
request.getHeader("Mai-Encoding") is not None
|
||||||
or request.getHeader("X-Mai-Encoding") is not None
|
or request.getHeader("X-Mai-Encoding") is not None
|
||||||
@ -269,22 +326,11 @@ class Mai2Servlet(BaseServlet):
|
|||||||
f"Encryption v{enc_ver} - User-Agent: {request.getHeader('User-Agent')}"
|
f"Encryption v{enc_ver} - User-Agent: {request.getHeader('User-Agent')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
unzip = zlib.decompress(req_raw)
|
|
||||||
|
|
||||||
except zlib.error as e:
|
|
||||||
self.logger.error(
|
|
||||||
f"Failed to decompress v{version} {endpoint} request -> {e}"
|
|
||||||
)
|
|
||||||
return zlib.compress(b'{"stat": "0"}')
|
|
||||||
|
|
||||||
req_data = json.loads(unzip)
|
|
||||||
|
|
||||||
self.logger.info(f"v{version} {endpoint} request from {client_ip}")
|
self.logger.info(f"v{version} {endpoint} request from {client_ip}")
|
||||||
self.logger.debug(req_data)
|
self.logger.debug(req_data)
|
||||||
|
|
||||||
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
||||||
handler_cls = self.versions[internal_ver](self.core_cfg, self.game_cfg)
|
handler_cls = self.versions[internal_ver](self.core_cfg, self.game_cfg) if not is_cn else self.cn_versions[internal_ver](self.core_cfg, self.game_cfg)
|
||||||
|
|
||||||
if not hasattr(handler_cls, func_to_find):
|
if not hasattr(handler_cls, func_to_find):
|
||||||
self.logger.warning(f"Unhandled v{version} request {endpoint}")
|
self.logger.warning(f"Unhandled v{version} request {endpoint}")
|
||||||
@ -297,14 +343,14 @@ class Mai2Servlet(BaseServlet):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
|
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
|
||||||
return zlib.compress(b'{"stat": "0"}')
|
return zlib.compress(cn_crypto.encrypt(b'{"stat": "0"}') if is_cn else b'{"stat": "0"}')
|
||||||
|
|
||||||
if resp == None:
|
if resp == None:
|
||||||
resp = {"returnCode": 1}
|
resp = {"returnCode": 1}
|
||||||
|
|
||||||
self.logger.debug(f"Response {resp}")
|
self.logger.debug(f"Response {resp}")
|
||||||
|
|
||||||
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
return zlib.compress(cn_crypto.encrypt(json.dumps(resp, ensure_ascii=False).encode("utf-8")) if is_cn else json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
||||||
|
|
||||||
def handle_old_srv(self, request: Request, game_code: str, matchers: Dict) -> bytes:
|
def handle_old_srv(self, request: Request, game_code: str, matchers: Dict) -> bytes:
|
||||||
endpoint = matchers['endpoint']
|
endpoint = matchers['endpoint']
|
||||||
|
Loading…
Reference in New Issue
Block a user