mirror of
https://gitea.tendokyu.moe/Hay1tsme/artemis.git
synced 2026-02-13 03:07:29 +08:00
mai2: fix Exp and Chn encryption
This commit is contained in:
@@ -38,7 +38,7 @@ class Mai2Servlet(BaseServlet):
|
||||
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
|
||||
super().__init__(core_cfg, cfg_dir)
|
||||
self.game_cfg = Mai2Config()
|
||||
self.hash_table: Dict[int, Dict[str, str]] = {}
|
||||
self.hash_table: Dict[str, Dict[str, str]] = {}
|
||||
if path.exists(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"):
|
||||
self.game_cfg.update(
|
||||
yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"))
|
||||
@@ -97,16 +97,21 @@ class Mai2Servlet(BaseServlet):
|
||||
self.logger.initted = True
|
||||
|
||||
for version, keys in self.game_cfg.crypto.keys.items():
|
||||
if version < Mai2Constants.VER_MAIMAI_DX:
|
||||
if int(str(version).split('_')[0]) < Mai2Constants.VER_MAIMAI_DX:
|
||||
continue
|
||||
|
||||
if len(keys) < 3:
|
||||
continue
|
||||
|
||||
if isinstance(version, int):
|
||||
version_idx = version
|
||||
else:
|
||||
version_idx = int(version.split("_")[0])
|
||||
|
||||
self.hash_table[version] = {}
|
||||
method_list = [
|
||||
method
|
||||
for method in dir(self.versions[version])
|
||||
for method in dir(self.versions[version_idx])
|
||||
if not method.startswith("__")
|
||||
]
|
||||
|
||||
@@ -115,6 +120,21 @@ class Mai2Servlet(BaseServlet):
|
||||
# remove the first 6 chars and the final 7 chars to get the canonical
|
||||
# endpoint name.
|
||||
method_fixed = inflection.camelize(method)[6:-7]
|
||||
|
||||
# This only applies for maimai DX International and later for some reason.
|
||||
if (
|
||||
isinstance(version, str)
|
||||
and version.endswith("_int")
|
||||
and version_idx >= Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||
):
|
||||
method_fixed += "MaimaiExp"
|
||||
elif (
|
||||
isinstance(version, str)
|
||||
and version.endswith("_chn")
|
||||
and version_idx >= Mai2Constants.VER_MAIMAI_DX_UNIVERSE # 1.00, 1.11 and 1.20 all use DX, but they add MaimaiChn in 1.20, we set 1.20 to use UNIVERSE code
|
||||
):
|
||||
method_fixed += "MaimaiChn"
|
||||
|
||||
hash = MD5.new((method_fixed + keys[2]).encode())
|
||||
|
||||
# truncate unused bytes like the game does
|
||||
@@ -310,7 +330,7 @@ class Mai2Servlet(BaseServlet):
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES
|
||||
elif version >= 145 and version <150: # BUDDiES PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
elif version >=150:
|
||||
elif version >= 150: # PRiSM
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
|
||||
elif game_code == "SDGA": # Int
|
||||
@@ -334,47 +354,58 @@ class Mai2Servlet(BaseServlet):
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES
|
||||
elif version >= 145 and version <150: # BUDDiES PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
elif version >=150:
|
||||
elif version >= 150: # PRiSM
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
|
||||
elif game_code == "SDGB": # Chn
|
||||
if version < 110: # Muji
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
||||
elif version >= 110 and version < 120: # Muji
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH # still DX, but need Splash to set encryption key
|
||||
elif version >= 120 and version < 130: # Muji (LMAO)
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE # still DX, but need UNIVERSE to set encryption key
|
||||
elif version >= 130 and version < 140: # FESTiVAL
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
||||
elif version >= 140 and version < 150: # BUDDiES
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES
|
||||
elif version >=150:
|
||||
elif version >= 150: # PRiSM
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
|
||||
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
||||
|
||||
if game_code == "SDGA":
|
||||
crypto_cfg_key = f"{internal_ver}_int"
|
||||
hash_table_key = f"{internal_ver}_int"
|
||||
elif game_code == "SDGB":
|
||||
crypto_cfg_key = f"{internal_ver}_chn"
|
||||
hash_table_key = f"{internal_ver}_chn"
|
||||
else:
|
||||
crypto_cfg_key = internal_ver
|
||||
hash_table_key = internal_ver
|
||||
|
||||
# If we get a 32 character long hex string, it's a hash and we're
|
||||
# dealing with an encrypted request. False positives shouldn't happen
|
||||
# as long as requests are suffixed with `Api`.
|
||||
if internal_ver not in self.hash_table:
|
||||
if hash_table_key not in self.hash_table:
|
||||
self.logger.error(
|
||||
"v%s does not support encryption or no keys entered",
|
||||
version,
|
||||
)
|
||||
return Response(zlib.compress(b'{"stat": "0"}'))
|
||||
elif endpoint.lower() not in self.hash_table[internal_ver]:
|
||||
elif endpoint.lower() not in self.hash_table[hash_table_key]:
|
||||
self.logger.error(
|
||||
"No hash found for v%s endpoint %s",
|
||||
version, endpoint
|
||||
)
|
||||
return Response(zlib.compress(b'{"stat": "0"}'))
|
||||
|
||||
endpoint = self.hash_table[internal_ver][endpoint.lower()]
|
||||
endpoint = self.hash_table[hash_table_key][endpoint.lower()]
|
||||
|
||||
try:
|
||||
crypt = AES.new(
|
||||
bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][0]),
|
||||
bytes.fromhex(self.game_cfg.crypto.keys[crypto_cfg_key][0]),
|
||||
AES.MODE_CBC,
|
||||
bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]),
|
||||
bytes.fromhex(self.game_cfg.crypto.keys[crypto_cfg_key][1]),
|
||||
)
|
||||
|
||||
req_raw = crypt.decrypt(req_raw)
|
||||
@@ -392,7 +423,10 @@ class Mai2Servlet(BaseServlet):
|
||||
if (
|
||||
not encrypted
|
||||
and self.game_cfg.crypto.encrypted_only
|
||||
and version >= 110
|
||||
and (
|
||||
# SDEZ start from 1.10, SDGA and SDGB keep use encryption from 1.00
|
||||
internal_ver >= Mai2Constants.VER_MAIMAI_DX_PLUS or game_code != "SDEZ"
|
||||
)
|
||||
):
|
||||
self.logger.error(
|
||||
"Unencrypted v%s %s request, but config is set to encrypted only: %r",
|
||||
@@ -416,9 +450,9 @@ class Mai2Servlet(BaseServlet):
|
||||
|
||||
endpoint = (
|
||||
endpoint.replace("MaimaiExp", "")
|
||||
if game_code == Mai2Constants.GAME_CODE_DX_INT
|
||||
if game_code == Mai2Constants.GAME_CODE_DX_INT and version >= 120
|
||||
else endpoint.replace("MaimaiChn", "")
|
||||
if game_code == Mai2Constants.GAME_CODE_DX_CHN
|
||||
if game_code == Mai2Constants.GAME_CODE_DX_CHN and version >= 120
|
||||
else endpoint
|
||||
)
|
||||
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
||||
@@ -444,15 +478,17 @@ class Mai2Servlet(BaseServlet):
|
||||
|
||||
zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
||||
|
||||
if not encrypted or version < 110:
|
||||
if not encrypted or (
|
||||
internal_ver < Mai2Constants.VER_MAIMAI_DX_PLUS and game_code == "SDEZ"
|
||||
):
|
||||
return Response(zipped)
|
||||
|
||||
padded = pad(zipped, 16)
|
||||
|
||||
crypt = AES.new(
|
||||
bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][0]),
|
||||
bytes.fromhex(self.game_cfg.crypto.keys[crypto_cfg_key][0]),
|
||||
AES.MODE_CBC,
|
||||
bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]),
|
||||
bytes.fromhex(self.game_cfg.crypto.keys[crypto_cfg_key][1]),
|
||||
)
|
||||
|
||||
return Response(crypt.encrypt(padded))
|
||||
|
||||
Reference in New Issue
Block a user