feat: Implement dispatch encryption

This commit is contained in:
Naruse
2024-11-08 11:13:11 +08:00
parent 1928801460
commit a6d3b24387
8 changed files with 172 additions and 129 deletions

View File

@@ -1,15 +1,19 @@
{ {
"LogLevel": "INFO", "LogLevel": "INFO",
"MaxSessions": 10, "GameServer": {
"GameServer": { "IP": "127.0.0.1",
"Ip": "127.0.0.1", "Port": 16100
"Port": 16100 },
}, "SDKServer": {
"SdkServer": { "IP": "127.0.0.1",
"Ip": "127.0.0.1", "Port": 80
"Port": 80 },
}, "VerboseLevel": 1,
"VerboseLevel":1, "RegionName": "MikuBH3",
"RegionName":"MikuBH3", "UseLocalCache": false,
"UseLocalCache":false "AESKeys": {
"7.9.0_gf_pc": "36 31 65 37 64 33 65 66 33 32 30 63 31 35 66 66 61 64 37 61 66 32 31 34 61 64 65 64 32 34 33 38",
"7.8.0_os_pc": "64 34 32 33 30 30 31 62 32 36 38 34 62 33 62 30 61 33 30 38 66 37 65 35 63 30 61 38 66 33 65 32"
},
"EnableDispatchEncryption": true
} }

14
hi3
View File

@@ -1,13 +1,19 @@
from game_server.config import * import threading
from sdk_server import HandleSdkServer,HandleSslSdkServer from sdk_server import HandleSdkServer, HandleSslSdkServer
from game_server import GameServer from game_server import GameServer
from utils.config import Config
SdkThread = threading.Thread(target=HandleSdkServer, args=(Config.GameServer.Ip, Config.GameServer.Port, Config.SdkServer.Port)) SdkThread = threading.Thread(
target=HandleSdkServer,
args=(Config.GameServer.IP, Config.GameServer.Port, Config.SDKServer.Port),
)
SdkThreadSsl = threading.Thread(target=HandleSslSdkServer) SdkThreadSsl = threading.Thread(target=HandleSslSdkServer)
SdkThread.start() SdkThread.start()
SdkThreadSsl.start() SdkThreadSsl.start()
gameserver = GameServer() gameserver = GameServer()
GameThread = threading.Thread(target=gameserver.main, args=(Config.GameServer.Ip, Config.GameServer.Port)) GameThread = threading.Thread(
target=gameserver.main, args=(Config.GameServer.IP, Config.GameServer.Port)
)
GameThread.start() GameThread.start()

View File

@@ -1,7 +1,7 @@
betterproto==1.2.5 betterproto==1.2.5
dynaconf==3.2.6
Flask==3.0.3 Flask==3.0.3
loguru==0.7.2 loguru==0.7.2
pydantic==2.9.2 pydantic==2.9.2
pymongo==4.6.3 pymongo==4.6.3
Requests==2.32.3 Requests==2.32.3
dacite==1.8.1

View File

@@ -1,13 +1,20 @@
from game_server.config import * from enum import Enum
import logging
from pathlib import Path
from flask import Flask, send_from_directory
from sdk_server.controllers.account_controller import account_blueprint from sdk_server.controllers.account_controller import account_blueprint
from sdk_server.controllers.config_controller import config_blueprint from sdk_server.controllers.config_controller import config_blueprint
from sdk_server.controllers.dispatch_controller import dispatch_blueprint from sdk_server.controllers.dispatch_controller import dispatch_blueprint
from utils.logger import Info
class VerboseLevel(Enum): class VerboseLevel(Enum):
SILENT = 0 SILENT = 0
NORMAL = 1 NORMAL = 1
DEBUG = 2 DEBUG = 2
class RequestLoggingMiddleware: class RequestLoggingMiddleware:
suppressed_routes = ["/report", "/sdk/dataUpload"] suppressed_routes = ["/report", "/sdk/dataUpload"]
@@ -16,15 +23,18 @@ class RequestLoggingMiddleware:
self.verbose_level = verbose_level self.verbose_level = verbose_level
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
path = environ.get('PATH_INFO', '') path = environ.get("PATH_INFO", "")
method = environ.get('REQUEST_METHOD', '').upper() method = environ.get("REQUEST_METHOD", "").upper()
def custom_start_response(status, headers, *args): def custom_start_response(status, headers, *args):
status_code = int(status.split()[0]) status_code = int(status.split()[0])
if self.verbose_level.value > VerboseLevel.NORMAL.value: if self.verbose_level.value > VerboseLevel.NORMAL.value:
Info(f"{status_code} {method} {path}") Info(f"{status_code} {method} {path}")
elif self.verbose_level.value > VerboseLevel.SILENT.value and path not in self.suppressed_routes: elif (
self.verbose_level.value > VerboseLevel.SILENT.value
and path not in self.suppressed_routes
):
Info(f"{status_code} {method} {path}") Info(f"{status_code} {method} {path}")
return start_response(status, headers, *args) return start_response(status, headers, *args)
@@ -32,7 +42,6 @@ class RequestLoggingMiddleware:
return self.app(environ, custom_start_response) return self.app(environ, custom_start_response)
app = Flask(__name__) app = Flask(__name__)
@@ -43,27 +52,31 @@ resources_path = Path(__file__).resolve().parent.parent / "resources/statics"
if not resources_path.exists(): if not resources_path.exists():
resources_path.mkdir(parents=True) resources_path.mkdir(parents=True)
@app.route('/statics/<path:filename>')
@app.route("/statics/<path:filename>")
def serve_statics(filename): def serve_statics(filename):
return send_from_directory(resources_path, filename) return send_from_directory(resources_path, filename)
app.register_blueprint(account_blueprint) app.register_blueprint(account_blueprint)
app.register_blueprint(config_blueprint) app.register_blueprint(config_blueprint)
app.register_blueprint(dispatch_blueprint) app.register_blueprint(dispatch_blueprint)
def HandleSdkServer(ServerIp, GameServerPort, SdkServerPort):
app.config['SERVER_IP'] = ServerIp
app.config['GAME_SERVER_PORT'] = GameServerPort
log = logging.getLogger('werkzeug') def HandleSdkServer(ServerIp, GameServerPort, SdkServerPort):
app.config["SERVER_IP"] = ServerIp
app.config["GAME_SERVER_PORT"] = GameServerPort
log = logging.getLogger("werkzeug")
log.setLevel(logging.ERROR) log.setLevel(logging.ERROR)
Info("HTTP server started on port 80") Info(f"HTTP server started on port {SdkServerPort}")
app.run(host=ServerIp, port=SdkServerPort) app.run(host=ServerIp, port=SdkServerPort)
def HandleSslSdkServer(): def HandleSslSdkServer():
log = logging.getLogger('werkzeug') log = logging.getLogger("werkzeug")
log.setLevel(logging.ERROR) log.setLevel(logging.ERROR)
Info("HTTPS server started on port 443") Info("HTTPS server started on port 443")
app.run(host='127.0.0.1', port=443,ssl_context='adhoc') app.run(host="127.0.0.1", port=443, ssl_context="adhoc")

View File

@@ -1,85 +1,106 @@
from game_server.config import * import json
import re
import time
from flask import Blueprint, Response, jsonify, request
from utils.aes import encrypt_ecb
from utils.config import Config
dispatch_blueprint = Blueprint('dispatch', __name__) dispatch_blueprint = Blueprint("dispatch", __name__)
@dispatch_blueprint.route('/query_dispatch', methods=['GET'])
@dispatch_blueprint.route("/query_dispatch", methods=["GET"])
def query_dispatch(): def query_dispatch():
version = request.args.get('version') version = request.args.get("version")
response_data = { response_data = {
'retcode': 0, "retcode": 0,
'region_list': [{ "region_list": [
'retcode': 0, {
'dispatch_url': f"http://{Config.GameServer.Ip}/query_gateway", "retcode": 0,
'name': Config.RegionName, "dispatch_url": f"http://{Config.GameServer.IP}/query_gateway",
'title': "", "name": Config.RegionName,
'ext': get_ext(version) "title": "",
}] "ext": get_ext(version),
}
],
} }
if Config.EnableDispatchEncryption:
return Response(
encrypt_ecb(Config.AESKeys.get(version), json.dumps(response_data)),
mimetype="text/plain",
)
return jsonify(response_data) return jsonify(response_data)
@dispatch_blueprint.route('/query_gateway', methods=['GET'])
@dispatch_blueprint.route("/query_gateway", methods=["GET"])
def query_gateway(): def query_gateway():
version = request.args.get('version') version = request.args.get("version")
gameserver = { gameserver = {"ip": Config.GameServer.IP, "port": Config.GameServer.Port}
'ip': Config.GameServer.Ip,
'port': Config.GameServer.Port
}
response_data = { response_data = {
'retcode': 0, "retcode": 0,
'msg': "", "msg": "",
'region_name': Config.RegionName, "region_name": Config.RegionName,
'account_url': f"http://{Config.GameServer.Ip}/account", "account_url": f"http://{Config.GameServer.IP}/account",
'account_url_backup': f"http://{Config.GameServer.Ip}/account", "account_url_backup": f"http://{Config.GameServer.IP}/account",
'asset_bundle_url_list': get_asset_bundle_url_list(version), "asset_bundle_url_list": get_asset_bundle_url_list(version),
'ex_audio_and_video_url_list': get_ex_audio_and_video_url_list(version), "ex_audio_and_video_url_list": get_ex_audio_and_video_url_list(version),
'ex_resource_url_list': get_ex_resource_url_list(version), "ex_resource_url_list": get_ex_resource_url_list(version),
'ext': get_ext(version), "ext": get_ext(version),
'gameserver': gameserver, "gameserver": gameserver,
'gateway': gameserver, "gateway": gameserver,
'is_data_ready': True, "is_data_ready": True,
'oaserver_url': f"http://{Config.GameServer.Ip}/oaserver", "oaserver_url": f"http://{Config.GameServer.IP}/oaserver",
'server_cur_time': int(time.time()), "server_cur_time": int(time.time()),
'server_cur_timezone': 8, "server_cur_timezone": 8,
'server_ext': { "server_ext": {
'cdkey_url': f"http://{Config.GameServer.Ip}/common", "cdkey_url": f"http://{Config.GameServer.IP}/common",
'mihoyo_sdk_env': "2" "mihoyo_sdk_env": "2",
} },
} }
if Config.EnableDispatchEncryption:
return Response(
encrypt_ecb(Config.AESKeys.get(version), json.dumps(response_data)),
mimetype="text/plain",
)
return jsonify(response_data) return jsonify(response_data)
def get_ext(version): def get_ext(version):
return { return {
'ai_use_asset_bundle': "0" if Config.UseLocalCache else "1", "ai_use_asset_bundle": "0" if Config.UseLocalCache else "1",
'apm_log_level': "0", "apm_log_level": "0",
'apm_log_dest': "2", "apm_log_dest": "2",
'apm_switch': "0", "apm_switch": "0",
'apm_switch_game_log': "1", "apm_switch_game_log": "1",
'apm_switch_crash': "1", "apm_switch_crash": "1",
'block_error_dialog': "1", "block_error_dialog": "1",
'elevator_model_path': "GameEntry/EVA/StartLoading_Model", "elevator_model_path": "GameEntry/EVA/StartLoading_Model",
'data_use_asset_bundle': "1", "data_use_asset_bundle": "1",
'enable_watermark': "1", "enable_watermark": "1",
'ex_audio_and_video_url_list': get_ex_audio_and_video_url_list(version), "ex_audio_and_video_url_list": get_ex_audio_and_video_url_list(version),
'ex_res_buff_size': "10485760", "ex_res_buff_size": "10485760",
'ex_res_pre_publish': "0", "ex_res_pre_publish": "0",
'ex_res_use_http': "1", "ex_res_use_http": "1",
'ex_resource_url_list': get_ex_resource_url_list(version), "ex_resource_url_list": get_ex_resource_url_list(version),
'is_xxxx': "0", "is_xxxx": "0",
'mtp_switch': "0", "mtp_switch": "0",
'network_feedback_enable': "0", "network_feedback_enable": "0",
'offline_report_switch': "0", "offline_report_switch": "0",
'forbid_recharge': "1", "forbid_recharge": "1",
'is_checksum_off': "0" if Config.UseLocalCache else "1", "is_checksum_off": "0" if Config.UseLocalCache else "1",
'res_use_asset_bundle': "1", "res_use_asset_bundle": "1",
'show_version_text': "0", "show_version_text": "0",
'update_streaming_asb': "0", "update_streaming_asb": "0",
'use_multy_cdn': "1", "use_multy_cdn": "1",
'show_bulletin_button': "1", "show_bulletin_button": "1",
'show_bulletin_empty_dialog_bg': "0" "show_bulletin_empty_dialog_bg": "0",
} }
def get_asset_bundle_url_list(version): def get_asset_bundle_url_list(version):
# Compile the regex pattern # Compile the regex pattern
regex = re.compile(r"^(.*?)_(os|gf|global)_(.*?)$") regex = re.compile(r"^(.*?)_(os|gf|global)_(.*?)$")
@@ -93,30 +114,31 @@ def get_asset_bundle_url_list(version):
if value == "os": if value == "os":
return [ return [
"https://bundle-aliyun-os.honkaiimpact3.com/asset_bundle/overseas01/1.1", "https://bundle-aliyun-os.honkaiimpact3.com/asset_bundle/overseas01/1.1",
"https://hk-bundle-os-mihayo.akamaized.net/asset_bundle/overseas01/1.1" "https://hk-bundle-os-mihayo.akamaized.net/asset_bundle/overseas01/1.1",
] ]
elif value == "gf": elif value == "gf":
if "beta" in version: if "beta" in version:
return [ return [
"https://autopatchbeta.bh3.com/asset_bundle/beta_release/1.0", "https://autopatchbeta.bh3.com/asset_bundle/beta_release/1.0",
"https://autopatchbeta.bh3.com/asset_bundle/beta_release/1.0" "https://autopatchbeta.bh3.com/asset_bundle/beta_release/1.0",
] ]
return [ return [
"https://bundle-qcloud.bh3.com/asset_bundle/android01/1.0", "https://bundle-qcloud.bh3.com/asset_bundle/android01/1.0",
"https://bundle.bh3.com/asset_bundle/android01/1.0" "https://bundle.bh3.com/asset_bundle/android01/1.0",
] ]
elif value == "global": elif value == "global":
return [ return [
"http://hk-bundle-west-mihayo.akamaized.net/asset_bundle/usa01/1.1", "http://hk-bundle-west-mihayo.akamaized.net/asset_bundle/usa01/1.1",
"http://bundle-aliyun-usa.honkaiimpact3.com/asset_bundle/usa01/1.1" "http://bundle-aliyun-usa.honkaiimpact3.com/asset_bundle/usa01/1.1",
] ]
else: else:
return [ return [
"https://bundle-aliyun-os.honkaiimpact3.com/asset_bundle/overseas01/1.1", "https://bundle-aliyun-os.honkaiimpact3.com/asset_bundle/overseas01/1.1",
"https://hk-bundle-os-mihayo.akamaized.net/asset_bundle/overseas01/1.1" "https://hk-bundle-os-mihayo.akamaized.net/asset_bundle/overseas01/1.1",
] ]
return [] return []
def get_ex_audio_and_video_url_list(version): def get_ex_audio_and_video_url_list(version):
# Compile the regex pattern # Compile the regex pattern
regex = re.compile(r"^(.*?)_(os|gf|global)_(.*?)$") regex = re.compile(r"^(.*?)_(os|gf|global)_(.*?)$")
@@ -129,32 +151,33 @@ def get_ex_audio_and_video_url_list(version):
if value == "os": if value == "os":
return [ return [
"bigfile-aliyun-os.honkaiimpact3.com/com.miHoYo.bh3oversea", "bigfile-aliyun-os.honkaiimpact3.com/com.miHoYo.bh3oversea",
"hk-bigfile-os-mihayo.akamaized.net/com.miHoYo.bh3oversea" "hk-bigfile-os-mihayo.akamaized.net/com.miHoYo.bh3oversea",
] ]
elif value == "gf": elif value == "gf":
if "beta" in version: if "beta" in version:
return [ return [
"autopatchbeta.bh3.com/tmp/CGAudio", "autopatchbeta.bh3.com/tmp/CGAudio",
"autopatchbeta.bh3.com/tmp/CGAudio" "autopatchbeta.bh3.com/tmp/CGAudio",
] ]
return [ return [
"bh3rd-beta-qcloud.bh3.com/tmp/CGAudio", "bh3rd-beta-qcloud.bh3.com/tmp/CGAudio",
"bh3rd-beta.bh3.com/tmp/CGAudio" "bh3rd-beta.bh3.com/tmp/CGAudio",
] ]
elif value == "global": elif value == "global":
return [ return [
"bh3rd-beta-qcloud.bh3.com/tmp/CGAudio", "bh3rd-beta-qcloud.bh3.com/tmp/CGAudio",
"bh3rd-beta.bh3.com/tmp/CGAudio" "bh3rd-beta.bh3.com/tmp/CGAudio",
] ]
else: else:
return [ return [
"bh3rd-beta-qcloud.bh3.com/tmp/CGAudio", "bh3rd-beta-qcloud.bh3.com/tmp/CGAudio",
"bh3rd-beta.bh3.com/tmp/CGAudio" "bh3rd-beta.bh3.com/tmp/CGAudio",
] ]
return [] return []
def get_ex_resource_url_list(version): def get_ex_resource_url_list(version):
# Compile the regex pattern # Compile the regex pattern
regex = re.compile(r"^(.*?)_(os|gf|global)_(.*?)$") regex = re.compile(r"^(.*?)_(os|gf|global)_(.*?)$")
match = regex.match(version) match = regex.match(version)
value = match.group(2) value = match.group(2)
@@ -166,33 +189,30 @@ def get_ex_resource_url_list(version):
if value == "os": if value == "os":
return [ return [
"bigfile-aliyun-os.honkaiimpact3.com/com.miHoYo.bh3oversea", "bigfile-aliyun-os.honkaiimpact3.com/com.miHoYo.bh3oversea",
"hk-bigfile-os-mihayo.akamaized.net/com.miHoYo.bh3oversea" "hk-bigfile-os-mihayo.akamaized.net/com.miHoYo.bh3oversea",
] ]
elif value == "gf": elif value == "gf":
if "beta" in version: if "beta" in version:
return [ return [
"autopatchbeta.bh3.com/tmp/beta", "autopatchbeta.bh3.com/tmp/beta",
"autopatchbeta.bh3.com/tmp/beta" "autopatchbeta.bh3.com/tmp/beta",
] ]
return [ return ["bundle-qcloud.bh3.com/tmp/Original", "bundle.bh3.com/tmp/Original"]
"bundle-qcloud.bh3.com/tmp/Original",
"bundle.bh3.com/tmp/Original"
]
elif value == "global": elif value == "global":
return [ return [
"hk-bundle-west-mihayo.akamaized.net/tmp/com.miHoYo.bh3global", "hk-bundle-west-mihayo.akamaized.net/tmp/com.miHoYo.bh3global",
"bigfile-aliyun-usa.honkaiimpact3.com/tmp/com.miHoYo.bh3global" "bigfile-aliyun-usa.honkaiimpact3.com/tmp/com.miHoYo.bh3global",
] ]
else: else:
return [ return [
"bigfile-aliyun-os.honkaiimpact3.com/com.miHoYo.bh3oversea", "bigfile-aliyun-os.honkaiimpact3.com/com.miHoYo.bh3oversea",
"hk-bigfile-os-mihayo.akamaized.net/com.miHoYo.bh3oversea" "hk-bigfile-os-mihayo.akamaized.net/com.miHoYo.bh3oversea",
] ]
return [] return []
def get_local_url_list(type, version): def get_local_url_list(type, version):
return [ return [
f"http://{Config.GameServer.Ip}/statics/{type}/{version.replace('.', '_')}", f"http://{Config.GameServer.IP}/statics/{type}/{version.replace('.', '_')}",
f"http://{Config.GameServer.Ip}/statics/{type}/{version.replace('.', '_')}" f"http://{Config.GameServer.IP}/statics/{type}/{version.replace('.', '_')}",
] ]

View File

@@ -1,13 +1,12 @@
# sdkserver/models/granter_login_body.py
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional
class GranterLoginBodyData(BaseModel): class GranterLoginBodyData(BaseModel):
uid: str uid: str
guest: bool guest: bool
token: str token: str
class GranterLoginBody(BaseModel): class GranterLoginBody(BaseModel):
app_id: int app_id: int
channel_id: int channel_id: int

View File

@@ -1,13 +1,13 @@
# sdkserver/models/risky_check.py
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional from typing import Optional
class DataScheme(BaseModel): class DataScheme(BaseModel):
id: str id: str
action: str action: str
geetest: Optional[object] geetest: Optional[object]
class RiskyCheck(BaseModel): class RiskyCheck(BaseModel):
retcode: int retcode: int
message: str message: str

View File

@@ -1,5 +1,6 @@
from pydantic import BaseModel from pydantic import BaseModel
class ShieldVerifyBody(BaseModel): class ShieldVerifyBody(BaseModel):
token: str token: str
uid: str uid: str