mirror of
https://git.xeondev.com/reversedrooms/hoyo-sdk.git
synced 2026-01-02 11:56:04 +08:00
Added 404 fallback with debug, hoyopass passport login support and verbose logging to all requests
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -556,6 +556,7 @@ name = "hoyo-sdk"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"http-body-util",
|
||||
"password-hash",
|
||||
"pbkdf2",
|
||||
"rand",
|
||||
@@ -595,12 +596,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
|
||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
|
||||
@@ -22,6 +22,7 @@ thiserror = "2.0.11"
|
||||
regex = "1.11.1"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
http-body-util = "0.1.3"
|
||||
|
||||
[profile.release]
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
|
||||
98
src/handlers/combo_box_api.rs
Normal file
98
src/handlers/combo_box_api.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use axum::extract::Query;
|
||||
use tracing::{error, info};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn routes() -> Router<AppStateRef> {
|
||||
Router::new()
|
||||
.route("/combo/box/api/config/sdk/combo", get(config_sdk_combo))
|
||||
.route("/combo/box/api/config/sw/precache", get(config_sw_precache))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(unused)]
|
||||
struct ConfigSDKComboRequest {
|
||||
pub biz_key: String,
|
||||
pub client_type: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ConfigSDKComboResponseData {
|
||||
pub vals: HashMap<String, String>,
|
||||
}
|
||||
|
||||
async fn config_sdk_combo(
|
||||
query: Query<ConfigSDKComboRequest>,
|
||||
) -> Json<Response<ConfigSDKComboResponseData>> {
|
||||
info!("GET /combo/box/api/config/sdk/combo - {:?}", &query);
|
||||
|
||||
match query.client_type {
|
||||
3 => Json(Response::new(ConfigSDKComboResponseData {
|
||||
vals: HashMap::<_, _>::from([
|
||||
(
|
||||
String::from("httpdns_plus_config"),
|
||||
String::from(
|
||||
"{ \"enable\": 1, \"cache_expire_time\": 300, \"ip_report_enable\":1}",
|
||||
),
|
||||
),
|
||||
(
|
||||
String::from("login_record_config"),
|
||||
String::from("{\"is_checked\":true}"),
|
||||
),
|
||||
(
|
||||
String::from("network_report_config"),
|
||||
String::from(
|
||||
"{ \"enable\": 1, \"status_codes\": [206], \"url_paths\": [\"dataUpload\"] }",
|
||||
),
|
||||
),
|
||||
(String::from("list_goods_work_mode"), String::from("1")),
|
||||
(
|
||||
String::from("kibana_pc_config"),
|
||||
String::from(
|
||||
"{ \"enable\": 1, \"level\": \"Info\",\"modules\": [\"download\"] }",
|
||||
),
|
||||
),
|
||||
(
|
||||
String::from("h5log_filter_config"),
|
||||
String::from(
|
||||
"{\n\t\"function\": {\n\t\t\"event_name\": [\"info_get_cps\", \"notice_close_notice\", \"info_get_uapc\", \"report_set_info\", \"info_get_channel_id\", \"info_get_sub_channel_id\"]\n\t}\n}",
|
||||
),
|
||||
),
|
||||
(
|
||||
String::from("webview_apm_config"),
|
||||
String::from("{ \"crash_capture_enable\": true }"),
|
||||
),
|
||||
(
|
||||
String::from("webview_rendermethod_config"),
|
||||
String::from("{ \"useLegacy\": false }"),
|
||||
),
|
||||
(String::from("enable_web_dpi"), String::from("true")),
|
||||
]),
|
||||
})),
|
||||
_ => {
|
||||
error!("Unknown client_type");
|
||||
Json(Response::error(7, "RetCode_NoConfig"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ConfigSWPrecacheResponseData {
|
||||
pub vals: HashMap<String, String>,
|
||||
}
|
||||
|
||||
async fn config_sw_precache() -> Json<Response<ConfigSWPrecacheResponseData>> {
|
||||
info!("GET /combo/box/api/config/sw/precache");
|
||||
|
||||
Json(Response::new(ConfigSWPrecacheResponseData {
|
||||
vals: HashMap::<_, _>::from([
|
||||
(
|
||||
String::from("url"),
|
||||
String::from("https://sdk.mihoyo.com/sw.html"),
|
||||
),
|
||||
(String::from("enable"), String::from("true")),
|
||||
]),
|
||||
}))
|
||||
}
|
||||
@@ -1,25 +1,39 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use axum::extract::Query;
|
||||
use tracing::{error, info};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RequestData {
|
||||
struct GranterLoginRequestData {
|
||||
pub uid: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct GranterTokenRequest {
|
||||
struct GranterLoginRequest {
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
pub fn routes() -> Router<AppStateRef> {
|
||||
Router::new().route(
|
||||
"/{product_name}/combo/granter/login/v2/login",
|
||||
post(login_v2),
|
||||
)
|
||||
Router::new()
|
||||
.route(
|
||||
"/{product_name}/combo/granter/login/v2/login",
|
||||
post(login_v2),
|
||||
)
|
||||
.route(
|
||||
"/{product_name}/combo/granter/api/getConfig",
|
||||
get(api_get_config),
|
||||
)
|
||||
.route(
|
||||
"/{product_name}/combo/granter/api/compareProtocolVersion",
|
||||
post(compare_protocol_version),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ResponseData {
|
||||
struct GranterLoginResponseData {
|
||||
pub account_type: u32,
|
||||
pub combo_id: String,
|
||||
pub combo_token: String,
|
||||
@@ -30,26 +44,37 @@ struct ResponseData {
|
||||
|
||||
async fn login_v2(
|
||||
state: State<AppStateRef>,
|
||||
request: Json<GranterTokenRequest>,
|
||||
) -> Json<Response<ResponseData>> {
|
||||
let Ok(data) = serde_json::from_str::<RequestData>(&request.data) else {
|
||||
request: Json<GranterLoginRequest>,
|
||||
) -> Json<Response<GranterLoginResponseData>> {
|
||||
info!(
|
||||
"POST /{{product_name}}/combo/granter/login/v2/login - {}",
|
||||
&request.data
|
||||
);
|
||||
|
||||
let Ok(data) = serde_json::from_str::<GranterLoginRequestData>(&request.data) else {
|
||||
error!("Couldn't convert data to GranterLoginRequestData");
|
||||
return Json(Response::error(-101, "Account token error"));
|
||||
};
|
||||
|
||||
let Ok(uid) = data.uid.parse() else {
|
||||
error!("Couldn't convert parse uid as int");
|
||||
return Json(Response::error(-101, "Account token error"));
|
||||
};
|
||||
|
||||
match state.db.get_account_by_uid(uid).await {
|
||||
Ok(Some(account)) if account.token == data.token => {
|
||||
info!("Logged in to account \"{}\"", account.username.as_str());
|
||||
success_rsp(data.uid.clone(), account.token)
|
||||
}
|
||||
_ => Json(Response::error(-101, "Account token error")),
|
||||
_ => {
|
||||
error!("Couldn't find account with specified uid");
|
||||
Json(Response::error(-101, "Account token error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn success_rsp(uid: String, token: String) -> Json<Response<ResponseData>> {
|
||||
Json(Response::new(ResponseData {
|
||||
fn success_rsp(uid: String, token: String) -> Json<Response<GranterLoginResponseData>> {
|
||||
Json(Response::new(GranterLoginResponseData {
|
||||
account_type: 1,
|
||||
combo_id: uid.clone(),
|
||||
combo_token: token,
|
||||
@@ -58,3 +83,127 @@ fn success_rsp(uid: String, token: String) -> Json<Response<ResponseData>> {
|
||||
open_id: uid,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(unused)]
|
||||
struct GranterApiGetConfigRequest {
|
||||
pub app_id: u32,
|
||||
pub channel_id: u32,
|
||||
pub client_type: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GranterApiGetConfigResponseData {
|
||||
pub protocol: bool,
|
||||
pub qr_enabled: bool,
|
||||
pub log_level: String,
|
||||
pub announce_url: String,
|
||||
pub push_alias_type: u32,
|
||||
pub disable_ysdk_guard: bool,
|
||||
pub enable_announce_pic_popup: bool,
|
||||
pub app_name: String,
|
||||
pub qr_enabled_apps: Option<HashMap<String, bool>>,
|
||||
pub qr_app_icons: Option<HashMap<String, String>>,
|
||||
pub qr_cloud_display_name: String,
|
||||
pub enable_user_center: bool,
|
||||
pub functional_switch_configs: HashMap<String, String>,
|
||||
pub ugc_protocol: bool,
|
||||
}
|
||||
|
||||
async fn api_get_config(
|
||||
query: Query<GranterApiGetConfigRequest>,
|
||||
) -> Json<Response<GranterApiGetConfigResponseData>> {
|
||||
info!(
|
||||
"GET {{productName}}/combo/granter/api/getConfig - {:?}",
|
||||
query
|
||||
);
|
||||
|
||||
Json(Response::new(GranterApiGetConfigResponseData {
|
||||
protocol: false,
|
||||
qr_enabled: true,
|
||||
log_level: String::from("INFO"),
|
||||
announce_url: String::from(
|
||||
"https://sdk.mihoyo.com/nap/btannouncement/index.html?sdk_presentation_style=fullscreen&sdk_screen_transparent=true&auth_appid=announcement&game=nap&game_biz=nap_cn&authkey_ver=1&sign_type=2&version=2.30#/",
|
||||
),
|
||||
push_alias_type: 0,
|
||||
disable_ysdk_guard: false,
|
||||
enable_announce_pic_popup: true,
|
||||
app_name: String::from("Zenless Zone Zero"),
|
||||
qr_enabled_apps: (query.client_type == 3).then_some(HashMap::<_, _>::from([
|
||||
(String::from("bbs"), true),
|
||||
(String::from("cloud"), false),
|
||||
])),
|
||||
qr_app_icons: (query.client_type == 3).then_some(HashMap::<_, _>::from([
|
||||
(String::from("app"), String::new()),
|
||||
(String::from("bbs"), String::new()),
|
||||
(String::from("cloud"), String::new()),
|
||||
])),
|
||||
qr_cloud_display_name: String::from("Zenless Zone Zero Cloud"),
|
||||
enable_user_center: true,
|
||||
functional_switch_configs: HashMap::<_, _>::new(),
|
||||
ugc_protocol: true,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(unused)]
|
||||
struct GranterCompareProtocolVersionRequest {
|
||||
pub app_id: u32,
|
||||
pub channel_id: u32,
|
||||
pub language: String,
|
||||
pub major: String,
|
||||
pub minimum: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GranterCompareProtocolVersionResponseData {
|
||||
pub modified: bool,
|
||||
pub protocol: Option<GranterCompareProtocolVersionProtocolResponseData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GranterCompareProtocolVersionProtocolResponseData {
|
||||
pub id: u32,
|
||||
pub app_id: u32,
|
||||
pub language: String,
|
||||
pub user_proto: String,
|
||||
pub priv_proto: String,
|
||||
pub major: u32,
|
||||
pub minimum: u32,
|
||||
pub create_time: String,
|
||||
pub teenager_proto: String,
|
||||
pub third_proto: String,
|
||||
pub full_priv_proto: String,
|
||||
}
|
||||
|
||||
async fn compare_protocol_version(
|
||||
body: Json<GranterCompareProtocolVersionRequest>,
|
||||
) -> Json<Response<GranterCompareProtocolVersionResponseData>> {
|
||||
info!(
|
||||
"POST /{{product_name}}/combo/granter/api/compareProtocolVersion - {:?}",
|
||||
body
|
||||
);
|
||||
|
||||
Json(Response::new(GranterCompareProtocolVersionResponseData {
|
||||
modified: true,
|
||||
protocol: Some(GranterCompareProtocolVersionProtocolResponseData {
|
||||
id: 0,
|
||||
app_id: body.app_id,
|
||||
language: body.language.clone(),
|
||||
user_proto: String::new(),
|
||||
priv_proto: String::new(),
|
||||
major: body.major.parse().unwrap_or(match body.app_id {
|
||||
11 => 0,
|
||||
_ => 6,
|
||||
}),
|
||||
minimum: body.minimum.parse().unwrap_or(match body.app_id {
|
||||
11 => 14,
|
||||
_ => 1,
|
||||
}),
|
||||
create_time: String::from("0"),
|
||||
teenager_proto: String::new(),
|
||||
third_proto: String::new(),
|
||||
full_priv_proto: String::new(),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
91
src/handlers/device_fp.rs
Normal file
91
src/handlers/device_fp.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use axum::extract::Query;
|
||||
use tracing::{error, info};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn routes() -> Router<AppStateRef> {
|
||||
Router::new()
|
||||
.route("/device-fp/api/getExtList", get(get_ext_list))
|
||||
.route("/device-fp/api/getFp", post(get_fp))
|
||||
.route(
|
||||
"/data_abtest_api/config/experiment/list",
|
||||
post(data_abtest_api),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct DeviceGetExtListRequestData {
|
||||
pub platform: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DeviceGetExtListResponseData {
|
||||
pub code: u32,
|
||||
pub msg: String,
|
||||
pub ext_list: Vec<&'static str>,
|
||||
pub pkg_list: Vec<String>,
|
||||
pub pkg_str: String,
|
||||
}
|
||||
|
||||
async fn get_ext_list(
|
||||
query: Query<DeviceGetExtListRequestData>,
|
||||
) -> Json<Response<DeviceGetExtListResponseData>> {
|
||||
info!("GET /device-fp/api/getExtList - {:?}", &query);
|
||||
|
||||
match query.platform {
|
||||
3 => Json(Response::new(DeviceGetExtListResponseData {
|
||||
code: 200,
|
||||
msg: String::from("ok"),
|
||||
ext_list: vec![
|
||||
"cpuName",
|
||||
"deviceModel",
|
||||
"deviceName",
|
||||
"deviceType",
|
||||
"deviceUID",
|
||||
"gpuID",
|
||||
"gpuName",
|
||||
"gpuAPI",
|
||||
"gpuVendor",
|
||||
"gpuVersion",
|
||||
"gpuMemory",
|
||||
"osVersion",
|
||||
"cpuCores",
|
||||
"cpuFrequency",
|
||||
"gpuVendorID",
|
||||
"isGpuMultiTread",
|
||||
"memorySize",
|
||||
"screenSize",
|
||||
"engineName",
|
||||
"addressMAC",
|
||||
"packageVersion",
|
||||
],
|
||||
pkg_list: vec![],
|
||||
pkg_str: String::from("/vK5WTh5SS3SAj8Zm0qPWg=="),
|
||||
})),
|
||||
_ => {
|
||||
error!("Unsupported platform");
|
||||
Json(Response::new(DeviceGetExtListResponseData {
|
||||
code: 401,
|
||||
msg: String::from("Unsupported platform"),
|
||||
ext_list: vec![],
|
||||
pkg_list: vec![],
|
||||
pkg_str: String::new(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DeviceGetFPResponseData {}
|
||||
|
||||
async fn get_fp(body: String) -> Json<Response<DeviceGetFPResponseData>> {
|
||||
info!("POST /device-fp/api/getFp - {:?}", &body);
|
||||
|
||||
Json(Response::new(DeviceGetFPResponseData {}))
|
||||
}
|
||||
|
||||
async fn data_abtest_api(body: String) -> &'static str {
|
||||
info!("POST /data_abtest_api/config/experiment/list - {:?}", &body);
|
||||
|
||||
r#"{"retcode":0,"success":true,"message":"","data":[{"code":1000,"type":1,"config_id":"6486","period_id":"6486_691","version":"1","configs":{"webViewRenderMethod":"none"},"sceneWhiteList":false,"experimentWhiteList":false},{"code":1000,"type":2,"config_id":"145","period_id":"6486_691","version":"1","configs":{"webViewRenderMethod":"none"},"sceneWhiteList":false,"experimentWhiteList":false}]}"#
|
||||
}
|
||||
237
src/handlers/ma_passport.rs
Normal file
237
src/handlers/ma_passport.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
use tracing::{error, info};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn routes() -> Router<AppStateRef> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/{product_name}/account/ma-passport/api/appLoginByPassword",
|
||||
post(app_login_by_password),
|
||||
)
|
||||
.route(
|
||||
"/{product_name}/account/ma-passport/token/verifySToken",
|
||||
post(verify_s_token),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct LoginRequest {
|
||||
pub account: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LoginResponseData {
|
||||
pub token: TokenData,
|
||||
pub user_info: UserInfoData,
|
||||
pub ext_user_info: ExtUserInfoData,
|
||||
pub reactivate_action_ticket: String,
|
||||
pub bind_email_action_ticket: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TokenData {
|
||||
pub token_type: u32,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct UserInfoData {
|
||||
pub aid: String,
|
||||
pub mid: String,
|
||||
pub account_name: String,
|
||||
pub email: String,
|
||||
pub is_email_verify: u32,
|
||||
pub area_code: String,
|
||||
pub mobile: String,
|
||||
pub safe_area_code: String,
|
||||
pub safe_mobile: String,
|
||||
pub realname: String,
|
||||
pub identity_code: String,
|
||||
pub rebind_area_code: String,
|
||||
pub rebind_mobile: String,
|
||||
pub rebind_mobile_time: String,
|
||||
pub links: Vec<LinkData>,
|
||||
pub country: String,
|
||||
pub password_time: String,
|
||||
pub is_adult: u32,
|
||||
pub unmasked_email: String,
|
||||
pub unmasked_email_type: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
struct LinkData {
|
||||
pub thirdparty: String,
|
||||
pub union_id: String,
|
||||
pub nickname: String,
|
||||
pub email: String,
|
||||
#[serde(rename = "subType")]
|
||||
pub sub_type: String,
|
||||
pub sub_union_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
struct ExtUserInfoData {
|
||||
pub guardian_email: String,
|
||||
pub birth: String,
|
||||
}
|
||||
|
||||
async fn app_login_by_password(
|
||||
state: State<AppStateRef>,
|
||||
request: Json<LoginRequest>,
|
||||
) -> Json<Response<LoginResponseData>> {
|
||||
info!(
|
||||
"POST /{{product_name}}/account/ma-passport/api/appLoginByPassword - {:?}",
|
||||
&request
|
||||
);
|
||||
|
||||
let Ok(login) = crate::util::rsa_decrypt(&request.account) else {
|
||||
error!("Couldn't decrypt account (login)");
|
||||
return Json(Response::error(
|
||||
-10,
|
||||
"Your patch is outdated, get a new one at https://discord.gg/reversedrooms (Password decryption failed)",
|
||||
));
|
||||
};
|
||||
let Ok(password) = crate::util::rsa_decrypt(&request.password) else {
|
||||
error!("Couldn't decrypt password");
|
||||
return Json(Response::error(
|
||||
-10,
|
||||
"Your patch is outdated, get a new one at https://discord.gg/reversedrooms (Password decryption failed)",
|
||||
));
|
||||
};
|
||||
|
||||
let account = match state.db.get_account_by_name(login.clone()).await {
|
||||
Ok(Some(account)) => account,
|
||||
Ok(None) => {
|
||||
error!("Couldn't find account with specified login \"{login}\"");
|
||||
return Json(Response::error(-101, "Account or password error"));
|
||||
}
|
||||
Err(err) => {
|
||||
error!("database error: {err}");
|
||||
return Json(Response::error(-1, "Internal server error"));
|
||||
}
|
||||
};
|
||||
|
||||
if !account.password.verify(&password) {
|
||||
error!("Password doesn't match");
|
||||
return Json(Response::error(-101, "Account or password error"));
|
||||
}
|
||||
|
||||
info!("Logged in to account \"{}\"", account.username.as_str());
|
||||
Json(Response::new(LoginResponseData {
|
||||
token: TokenData {
|
||||
token_type: 1,
|
||||
token: account.token,
|
||||
},
|
||||
user_info: UserInfoData {
|
||||
aid: account.uid.to_string(),
|
||||
mid: account.uid.to_string(),
|
||||
account_name: String::new(),
|
||||
email: account.username.as_str().to_string(),
|
||||
is_email_verify: 0,
|
||||
area_code: String::from("**"),
|
||||
mobile: String::new(),
|
||||
safe_area_code: String::new(),
|
||||
safe_mobile: String::new(),
|
||||
realname: String::new(),
|
||||
identity_code: String::new(),
|
||||
rebind_area_code: String::new(),
|
||||
rebind_mobile: String::new(),
|
||||
rebind_mobile_time: String::from("315532800"),
|
||||
links: Vec::new(),
|
||||
country: String::from("RU"),
|
||||
password_time: String::from("1762297200"),
|
||||
is_adult: 0,
|
||||
unmasked_email: String::new(),
|
||||
unmasked_email_type: 0,
|
||||
},
|
||||
ext_user_info: ExtUserInfoData {
|
||||
guardian_email: String::new(),
|
||||
birth: String::from("0"),
|
||||
},
|
||||
reactivate_action_ticket: String::new(),
|
||||
bind_email_action_ticket: String::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct VerifySTokenRequest {
|
||||
pub mid: String,
|
||||
pub stoken: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct VerifySTokenResponseData {
|
||||
pub user_info: UserInfoData,
|
||||
pub tokens: Vec<TokenData>,
|
||||
pub ext_user_info: ExtUserInfoData,
|
||||
}
|
||||
|
||||
async fn verify_s_token(
|
||||
state: State<AppStateRef>,
|
||||
request: Json<VerifySTokenRequest>,
|
||||
) -> Json<Response<VerifySTokenResponseData>> {
|
||||
info!(
|
||||
"POST /{{product_name}}/account/ma-passport/token/verifySToken - {:?}",
|
||||
&request
|
||||
);
|
||||
|
||||
let Ok(uid) = request.mid.parse() else {
|
||||
error!("Couldn't convert parse mid as int");
|
||||
return Json(Response::error(-101, "Account cache error"));
|
||||
};
|
||||
|
||||
let account = match state.db.get_account_by_uid(uid).await {
|
||||
Ok(Some(account)) => account,
|
||||
Ok(None) => {
|
||||
error!("Couldn't find account with specified uid");
|
||||
return Json(Response::error(-101, "Account cache error"));
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("database error: {err}");
|
||||
return Json(Response::error(-1, "Internal server error"));
|
||||
}
|
||||
};
|
||||
|
||||
if account.token == request.stoken {
|
||||
info!("Logged in to account \"{}\"", account.username.as_str());
|
||||
Json(Response::new(VerifySTokenResponseData {
|
||||
user_info: UserInfoData {
|
||||
aid: account.uid.to_string(),
|
||||
mid: account.uid.to_string(),
|
||||
account_name: String::new(),
|
||||
email: account.username.as_str().to_string(),
|
||||
is_email_verify: 0,
|
||||
area_code: String::from("**"),
|
||||
mobile: String::new(),
|
||||
safe_area_code: String::new(),
|
||||
safe_mobile: String::new(),
|
||||
realname: String::new(),
|
||||
identity_code: String::new(),
|
||||
rebind_area_code: String::new(),
|
||||
rebind_mobile: String::new(),
|
||||
rebind_mobile_time: String::from("315532800"),
|
||||
links: Vec::new(),
|
||||
country: String::from("RU"),
|
||||
password_time: String::from("1762297200"),
|
||||
is_adult: 0,
|
||||
unmasked_email: String::new(),
|
||||
unmasked_email_type: 0,
|
||||
},
|
||||
tokens: vec![TokenData {
|
||||
token_type: 1,
|
||||
token: account.token,
|
||||
}],
|
||||
ext_user_info: ExtUserInfoData {
|
||||
guardian_email: String::new(),
|
||||
birth: String::from("0"),
|
||||
},
|
||||
}))
|
||||
} else {
|
||||
error!("Token doesn't match");
|
||||
Json(Response::error(
|
||||
-101,
|
||||
"For account safety, please log in again",
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,39 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use axum::extract::Query;
|
||||
use tracing::{error, info};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn routes() -> Router<AppStateRef> {
|
||||
Router::new()
|
||||
.route("/{product_name}/mdk/shield/api/login", post(login))
|
||||
.route("/{product_name}/mdk/shield/api/verify", post(verify))
|
||||
.route(
|
||||
"/{product_name}/mdk/shield/api/loadConfig",
|
||||
get(load_config),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct LoginRequest {
|
||||
pub account: String,
|
||||
pub password: String,
|
||||
pub is_crypto: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct VerifyRequest {
|
||||
pub uid: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct LoadConfigRequest {
|
||||
pub client: u32,
|
||||
pub game_key: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
struct ResponseData {
|
||||
pub account: ResponseAccountData,
|
||||
@@ -38,11 +53,54 @@ struct ResponseAccountData {
|
||||
pub uid: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
struct ResponseLoadConfigData {
|
||||
pub id: u32,
|
||||
pub game_key: String,
|
||||
pub client: String,
|
||||
pub identity: String,
|
||||
pub guest: bool,
|
||||
pub ignore_version: String,
|
||||
pub scene: String,
|
||||
pub name: String,
|
||||
pub disable_regist: bool,
|
||||
pub enable_email_captcha: bool,
|
||||
pub thirdparty: Vec<String>,
|
||||
pub disable_mmt: bool,
|
||||
pub server_guest: bool,
|
||||
pub thirdparty_ignore: HashMap<String, String>,
|
||||
pub enable_ps_bind_account: bool,
|
||||
pub thirdparty_login_configs: HashMap<String, String>,
|
||||
pub initialize_firebase: bool,
|
||||
pub bbs_auth_login: bool,
|
||||
pub bbs_auth_login_ignore: Vec<String>,
|
||||
pub fetch_instance_id: bool,
|
||||
pub enable_flash_login: bool,
|
||||
pub enable_logo_18: bool,
|
||||
pub logo_height: String,
|
||||
pub logo_width: String,
|
||||
pub enable_cx_bind_account: bool,
|
||||
pub firebase_blacklist_devices_switch: bool,
|
||||
pub firebase_blacklist_devices_version: u32,
|
||||
pub hoyolab_auth_login: bool,
|
||||
pub hoyolab_auth_login_ignore: Vec<String>,
|
||||
pub hoyoplay_auth_login: bool,
|
||||
pub enable_douyin_flash_login: bool,
|
||||
pub enable_age_gate: bool,
|
||||
pub enable_age_gate_ignore: Vec<String>,
|
||||
}
|
||||
|
||||
async fn login(
|
||||
state: State<AppStateRef>,
|
||||
request: Json<LoginRequest>,
|
||||
) -> Json<Response<ResponseData>> {
|
||||
info!(
|
||||
"POST /{{product_name}}/mdk/shield/api/login - {:?}",
|
||||
&request
|
||||
);
|
||||
|
||||
if !request.is_crypto {
|
||||
error!("Unencrypted passwords are disabled by SDK security policy");
|
||||
return Json(Response::error(
|
||||
-10,
|
||||
"Invalid account format: unencrypted passwords are disabled by SDK security policy",
|
||||
@@ -50,22 +108,31 @@ async fn login(
|
||||
}
|
||||
|
||||
let Ok(password) = crate::util::rsa_decrypt(&request.password) else {
|
||||
return Json(Response::error(-10, "Your patch is outdated, get a new one at https://discord.gg/reversedrooms (Password decryption failed)"));
|
||||
error!("Couldn't decrypt password");
|
||||
return Json(Response::error(
|
||||
-10,
|
||||
"Your patch is outdated, get a new one at https://discord.gg/reversedrooms (Password decryption failed)",
|
||||
));
|
||||
};
|
||||
|
||||
let account = match state.db.get_account_by_name(request.account.clone()).await {
|
||||
Ok(Some(account)) => account,
|
||||
Ok(None) => return Json(Response::error(-101, "Account or password error")),
|
||||
Ok(None) => {
|
||||
error!("Couldn't find account with specified login");
|
||||
return Json(Response::error(-101, "Account or password error"));
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("database error: {err}");
|
||||
error!("database error: {err}");
|
||||
return Json(Response::error(-1, "Internal server error"));
|
||||
}
|
||||
};
|
||||
|
||||
if !account.password.verify(&password) {
|
||||
error!("Password doesn't match");
|
||||
return Json(Response::error(-101, "Account or password error"));
|
||||
}
|
||||
|
||||
info!("Logged in to account \"{}\"", account.username.as_str());
|
||||
Json(Response::new(ResponseData {
|
||||
account: ResponseAccountData {
|
||||
area_code: String::from("**"),
|
||||
@@ -83,20 +150,30 @@ async fn verify(
|
||||
state: State<AppStateRef>,
|
||||
request: Json<VerifyRequest>,
|
||||
) -> Json<Response<ResponseData>> {
|
||||
info!(
|
||||
"POST /{{product_name}}/mdk/shield/api/verify - {:?}",
|
||||
&request
|
||||
);
|
||||
|
||||
let Ok(uid) = request.uid.parse() else {
|
||||
error!("Couldn't convert parse uid as int");
|
||||
return Json(Response::error(-101, "Account cache error"));
|
||||
};
|
||||
|
||||
let account = match state.db.get_account_by_uid(uid).await {
|
||||
Ok(Some(account)) => account,
|
||||
Ok(None) => return Json(Response::error(-101, "Account cache error")),
|
||||
Ok(None) => {
|
||||
error!("Couldn't find account with specified uid");
|
||||
return Json(Response::error(-101, "Account cache error"));
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("database error: {err}");
|
||||
error!("database error: {err}");
|
||||
return Json(Response::error(-1, "Internal server error"));
|
||||
}
|
||||
};
|
||||
|
||||
if account.token == request.token {
|
||||
info!("Logged in to account \"{}\"", account.username.as_str());
|
||||
Json(Response::new(ResponseData {
|
||||
account: ResponseAccountData {
|
||||
area_code: String::from("**"),
|
||||
@@ -109,9 +186,63 @@ async fn verify(
|
||||
..Default::default()
|
||||
}))
|
||||
} else {
|
||||
error!("Token doesn't match");
|
||||
Json(Response::error(
|
||||
-101,
|
||||
"For account safety, please log in again",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_config(query: Query<LoadConfigRequest>) -> Json<Response<ResponseLoadConfigData>> {
|
||||
info!(
|
||||
"GET /{{product_name}}/mdk/shield/api/loadConfig - {:?}",
|
||||
&query
|
||||
);
|
||||
match query.client {
|
||||
1..=3 => Json(Response::new(ResponseLoadConfigData {
|
||||
id: 28 + query.client,
|
||||
game_key: query.game_key.clone(),
|
||||
client: String::from(match query.client {
|
||||
1 => "IOS",
|
||||
2 => "Android",
|
||||
3 => "PC",
|
||||
_ => unreachable!("Invalid query client"),
|
||||
}),
|
||||
identity: String::from("I_IDENTITY"),
|
||||
guest: false,
|
||||
ignore_version: String::new(),
|
||||
scene: String::from("S_NORMAL"),
|
||||
name: String::from("Nap"),
|
||||
disable_regist: false,
|
||||
enable_email_captcha: false,
|
||||
thirdparty: Vec::<_>::new(),
|
||||
disable_mmt: false,
|
||||
server_guest: (query.client == 1),
|
||||
thirdparty_ignore: HashMap::<_, _>::new(),
|
||||
enable_ps_bind_account: false,
|
||||
thirdparty_login_configs: HashMap::<_, _>::new(),
|
||||
initialize_firebase: false,
|
||||
bbs_auth_login: (query.client != 3),
|
||||
bbs_auth_login_ignore: Vec::<_>::new(),
|
||||
fetch_instance_id: false,
|
||||
enable_flash_login: (query.client != 3),
|
||||
enable_logo_18: false,
|
||||
logo_height: String::from("0"),
|
||||
logo_width: String::from("0"),
|
||||
enable_cx_bind_account: false,
|
||||
firebase_blacklist_devices_switch: false,
|
||||
firebase_blacklist_devices_version: 0,
|
||||
hoyolab_auth_login: false,
|
||||
hoyolab_auth_login_ignore: Vec::<_>::new(),
|
||||
hoyoplay_auth_login: (query.client == 3),
|
||||
enable_douyin_flash_login: false,
|
||||
enable_age_gate: false,
|
||||
enable_age_gate_ignore: Vec::<_>::new(),
|
||||
})),
|
||||
_ => {
|
||||
error!("Unknown client type");
|
||||
Json(Response::error(-104, "Missing configuration"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use crate::AppStateRef;
|
||||
use axum::{
|
||||
Form, Json, Router,
|
||||
extract::State,
|
||||
response::Html,
|
||||
routing::{get, post},
|
||||
Form, Json, Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod combo_box_api;
|
||||
pub mod combo_granter;
|
||||
pub mod device_fp;
|
||||
pub mod ma_passport;
|
||||
pub mod mdk_shield_api;
|
||||
pub mod register;
|
||||
pub mod risky_api;
|
||||
|
||||
23
src/main.rs
23
src/main.rs
@@ -6,7 +6,9 @@ use std::{
|
||||
use axum::Router;
|
||||
use config::SdkConfig;
|
||||
use database::DbContext;
|
||||
use handlers::{combo_granter, mdk_shield_api, register, risky_api};
|
||||
use handlers::{
|
||||
combo_box_api, combo_granter, device_fp, ma_passport, mdk_shield_api, register, risky_api,
|
||||
};
|
||||
use tokio::net::TcpListener;
|
||||
use tracing::error;
|
||||
|
||||
@@ -51,6 +53,10 @@ async fn main() -> ExitCode {
|
||||
.merge(register::routes())
|
||||
.merge(mdk_shield_api::routes())
|
||||
.merge(combo_granter::routes())
|
||||
.merge(combo_box_api::routes())
|
||||
.merge(device_fp::routes())
|
||||
.merge(ma_passport::routes())
|
||||
.fallback(handle_404)
|
||||
.with_state(STATE.get().unwrap());
|
||||
|
||||
let listener = TcpListener::bind(&CONFIG.http_addr)
|
||||
@@ -65,3 +71,18 @@ async fn main() -> ExitCode {
|
||||
fn init_tracing() {
|
||||
tracing_subscriber::fmt().without_time().init();
|
||||
}
|
||||
|
||||
async fn handle_404(request: axum::extract::Request) -> (axum::http::StatusCode, String) {
|
||||
let uri = request.uri().clone();
|
||||
error!("404 - request: {} {}", request.method(), request.uri());
|
||||
error!("404 - headers: {:?}", request.headers());
|
||||
if request.method() == axum::http::Method::POST {
|
||||
use http_body_util::BodyExt;
|
||||
let body = request.collect().await.unwrap().to_bytes();
|
||||
error!("404 - body: {:?}", body);
|
||||
}
|
||||
(
|
||||
axum::http::StatusCode::NOT_FOUND,
|
||||
format!("No route for {}", uri),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user