From c1a4f35c1677f0bf61f9c2134959f418eb471ea7 Mon Sep 17 00:00:00 2001 From: traffic95 Date: Thu, 6 Nov 2025 01:58:08 +0100 Subject: [PATCH] Added 404 fallback with debug, hoyopass passport login support and verbose logging to all requests --- Cargo.lock | 7 +- Cargo.toml | 1 + src/handlers/combo_box_api.rs | 98 ++++++++++++++ src/handlers/combo_granter.rs | 175 ++++++++++++++++++++++-- src/handlers/device_fp.rs | 91 +++++++++++++ src/handlers/ma_passport.rs | 237 +++++++++++++++++++++++++++++++++ src/handlers/mdk_shield_api.rs | 145 +++++++++++++++++++- src/handlers/mod.rs | 5 +- src/main.rs | 23 +++- 9 files changed, 757 insertions(+), 25 deletions(-) create mode 100644 src/handlers/combo_box_api.rs create mode 100644 src/handlers/device_fp.rs create mode 100644 src/handlers/ma_passport.rs diff --git a/Cargo.lock b/Cargo.lock index 1059d77..fc7f18e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index a7ba13f..00c2307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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. diff --git a/src/handlers/combo_box_api.rs b/src/handlers/combo_box_api.rs new file mode 100644 index 0000000..f7a15b7 --- /dev/null +++ b/src/handlers/combo_box_api.rs @@ -0,0 +1,98 @@ +use std::collections::HashMap; + +use axum::extract::Query; +use tracing::{error, info}; + +use super::*; + +pub fn routes() -> Router { + 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, +} + +async fn config_sdk_combo( + query: Query, +) -> Json> { + 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, +} + +async fn config_sw_precache() -> Json> { + 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")), + ]), + })) +} diff --git a/src/handlers/combo_granter.rs b/src/handlers/combo_granter.rs index 4177046..58d286b 100644 --- a/src/handlers/combo_granter.rs +++ b/src/handlers/combo_granter.rs @@ -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 { - 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, - request: Json, -) -> Json> { - let Ok(data) = serde_json::from_str::(&request.data) else { + request: Json, +) -> Json> { + info!( + "POST /{{product_name}}/combo/granter/login/v2/login - {}", + &request.data + ); + + let Ok(data) = serde_json::from_str::(&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> { - Json(Response::new(ResponseData { +fn success_rsp(uid: String, token: String) -> Json> { + 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> { 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>, + pub qr_app_icons: Option>, + pub qr_cloud_display_name: String, + pub enable_user_center: bool, + pub functional_switch_configs: HashMap, + pub ugc_protocol: bool, +} + +async fn api_get_config( + query: Query, +) -> Json> { + 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, +} + +#[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, +) -> Json> { + 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(), + }), + })) +} diff --git a/src/handlers/device_fp.rs b/src/handlers/device_fp.rs new file mode 100644 index 0000000..fccd9ff --- /dev/null +++ b/src/handlers/device_fp.rs @@ -0,0 +1,91 @@ +use axum::extract::Query; +use tracing::{error, info}; + +use super::*; + +pub fn routes() -> Router { + 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, + pub pkg_str: String, +} + +async fn get_ext_list( + query: Query, +) -> Json> { + 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> { + 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}]}"# +} diff --git a/src/handlers/ma_passport.rs b/src/handlers/ma_passport.rs new file mode 100644 index 0000000..7c63272 --- /dev/null +++ b/src/handlers/ma_passport.rs @@ -0,0 +1,237 @@ +use tracing::{error, info}; + +use super::*; + +pub fn routes() -> Router { + 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, + 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, + request: Json, +) -> Json> { + 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, + pub ext_user_info: ExtUserInfoData, +} + +async fn verify_s_token( + state: State, + request: Json, +) -> Json> { + 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", + )) + } +} diff --git a/src/handlers/mdk_shield_api.rs b/src/handlers/mdk_shield_api.rs index 084a0b1..2b11ff8 100644 --- a/src/handlers/mdk_shield_api.rs +++ b/src/handlers/mdk_shield_api.rs @@ -1,24 +1,39 @@ +use std::collections::HashMap; + +use axum::extract::Query; +use tracing::{error, info}; + use super::*; pub fn routes() -> Router { 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, + pub disable_mmt: bool, + pub server_guest: bool, + pub thirdparty_ignore: HashMap, + pub enable_ps_bind_account: bool, + pub thirdparty_login_configs: HashMap, + pub initialize_firebase: bool, + pub bbs_auth_login: bool, + pub bbs_auth_login_ignore: Vec, + 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, + pub hoyoplay_auth_login: bool, + pub enable_douyin_flash_login: bool, + pub enable_age_gate: bool, + pub enable_age_gate_ignore: Vec, +} + async fn login( state: State, request: Json, ) -> Json> { + 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, request: Json, ) -> Json> { + 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) -> Json> { + 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")) + } + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 84754c8..f591541 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -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; diff --git a/src/main.rs b/src/main.rs index e2ec44a..b152e24 100644 --- a/src/main.rs +++ b/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), + ) +}