diff --git a/Cargo.lock b/Cargo.lock index 15aeb61..2d52596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1441,6 +1441,7 @@ dependencies = [ "serde", "serde_json", "snafu", + "spdlog-rs", "strum 0.27.2", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 0a471e3..e15f1df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,11 @@ resolver = "3" [workspace.dependencies] sdgb-api = { path = "./sdgb-api", default-features = false } +spdlog-rs = { version = "0.4.3", default-features = false, features = [ + "level-debug", + "release-level-info", +] } + snafu = { version = "0.8.6", features = ["backtrace", "rust_1_81"] } serde_json = "1.0.141" strum = { version = "0.27.2", features = ["derive"] } diff --git a/sdgb-api/Cargo.toml b/sdgb-api/Cargo.toml index 27d4c82..2a7cff3 100644 --- a/sdgb-api/Cargo.toml +++ b/sdgb-api/Cargo.toml @@ -17,6 +17,7 @@ serde_json = { workspace = true } strum = { workspace = true } tokio = { workspace = true, optional = true } compio = { workspace = true, optional = true } +spdlog-rs = { workspace = true } # hashing digest = "0.10.7" @@ -34,7 +35,7 @@ serde = { version = "1.0.219", features = ["derive"] } # compression / encryption flate2 = "1.1.2" -cbc = "0.1.2" +cbc = { version = "0.1.2", features = ["alloc"] } aes = "0.8.4" cipher = { version = "0.4.4", features = ["block-padding"] } bincode = { version = "2.0.1", optional = true } diff --git a/sdgb-api/src/title/encryption/mod.rs b/sdgb-api/src/title/encryption/mod.rs index 530219c..3deb4cc 100644 --- a/sdgb-api/src/title/encryption/mod.rs +++ b/sdgb-api/src/title/encryption/mod.rs @@ -1,19 +1,18 @@ -use std::io::{Read, Write as _}; +use std::io::Write as _; use aes::cipher::{ BlockDecryptMut, BlockEncryptMut, BlockSizeUser, KeyIvInit, block_padding::Pkcs7, }; - use digest::generic_array::GenericArray; -use flate2::write::ZlibEncoder; -use flate2::{Compression, read::ZlibDecoder}; +use flate2::Compression; +use flate2::write::{ZlibDecoder, ZlibEncoder}; use crate::error::ApiError; use crate::title::{MaiVersion, MaiVersionExt, Sdgb1_40, Sdgb1_50}; impl MaiVersionExt for Sdgb1_40 { - fn decode(mut data: impl AsMut<[u8]>) -> Result, ApiError> { - let mut decompressed = decompress(data.as_mut()); + fn decode(data: impl AsRef<[u8]>) -> Result, ApiError> { + let mut decompressed = decompress(data.as_ref()); if decompressed.is_empty() { return Err(ApiError::EmptyResponse); } @@ -41,14 +40,19 @@ impl MaiVersionExt for Sdgb1_40 { } impl MaiVersionExt for Sdgb1_50 { - fn decode(mut data: impl AsMut<[u8]>) -> Result, ApiError> { - if data.as_mut().is_empty() { + fn decode(data: impl AsRef<[u8]>) -> Result, ApiError> { + let mut data = data.as_ref().to_vec(); + if data.is_empty() { return Err(ApiError::EmptyResponse); } - let decrypted = decrypt(&mut data, Self::AES_KEY, Self::AES_IV)?; - let decompressed = decompress(decrypted); - Ok(decompressed) + if data.len() % 16 != 0 { + let pad = 16 - (data.len() % 16); + data.resize(data.len() + pad, pad as _); + } + + let decrypted = decrypt_vec(&data, Self::AES_KEY, Self::AES_IV)?; + Ok(decompress(decrypted)) } fn encode(data: impl AsRef<[u8]>) -> Result, ApiError> { @@ -72,8 +76,9 @@ fn compress(data: impl AsRef<[u8]>) -> Result, ApiError> { fn decompress(data: impl AsRef<[u8]>) -> Vec { let mut buf = Vec::with_capacity(data.as_ref().len() * 2); - let mut decode = ZlibDecoder::new(data.as_ref()); - _ = decode.read_to_end(&mut buf); + let mut decode = ZlibDecoder::new(&mut buf); + _ = decode.write_all(data.as_ref()); + _ = decode.finish(); buf } @@ -104,6 +109,13 @@ fn decrypt<'ct>( Ok(result) } +fn decrypt_vec(data: impl AsRef<[u8]>, key: &[u8; 32], iv: &[u8; 16]) -> Result, ApiError> { + let key = GenericArray::from_slice(key); + let iv = GenericArray::from_slice(iv); + let decryptor = Aes256CbcDec::new(key, iv); + Ok(decryptor.decrypt_padded_vec_mut::(data.as_ref())?) +} + #[cfg(test)] mod _tests { @@ -148,4 +160,69 @@ mod _tests { assert_eq!(enc, data); Ok(()) } + + // FIXME: user data decryption + #[test] + fn test_user_data_dec() -> Result<(), ApiError> { + let data = [ + 60, 33, 100, 111, 99, 116, 121, 112, 101, 32, 104, 116, 109, 108, 62, 60, 104, 116, + 109, 108, 32, 108, 97, 110, 103, 61, 34, 101, 110, 34, 62, 60, 104, 101, 97, 100, 62, + 60, 116, 105, 116, 108, 101, 62, 72, 84, 84, 80, 32, 83, 116, 97, 116, 117, 115, 32, + 53, 48, 48, 32, 226, 128, 147, 32, 73, 110, 116, 101, 114, 110, 97, 108, 32, 83, 101, + 114, 118, 101, 114, 32, 69, 114, 114, 111, 114, 60, 47, 116, 105, 116, 108, 101, 62, + 60, 115, 116, 121, 108, 101, 32, 116, 121, 112, 101, 61, 34, 116, 101, 120, 116, 47, + 99, 115, 115, 34, 62, 104, 49, 32, 123, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, + 121, 58, 84, 97, 104, 111, 109, 97, 44, 65, 114, 105, 97, 108, 44, 115, 97, 110, 115, + 45, 115, 101, 114, 105, 102, 59, 99, 111, 108, 111, 114, 58, 119, 104, 105, 116, 101, + 59, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, + 53, 50, 53, 68, 55, 54, 59, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 50, 50, + 112, 120, 59, 125, 32, 104, 50, 32, 123, 102, 111, 110, 116, 45, 102, 97, 109, 105, + 108, 121, 58, 84, 97, 104, 111, 109, 97, 44, 65, 114, 105, 97, 108, 44, 115, 97, 110, + 115, 45, 115, 101, 114, 105, 102, 59, 99, 111, 108, 111, 114, 58, 119, 104, 105, 116, + 101, 59, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, + 35, 53, 50, 53, 68, 55, 54, 59, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, + 112, 120, 59, 125, 32, 104, 51, 32, 123, 102, 111, 110, 116, 45, 102, 97, 109, 105, + 108, 121, 58, 84, 97, 104, 111, 109, 97, 44, 65, 114, 105, 97, 108, 44, 115, 97, 110, + 115, 45, 115, 101, 114, 105, 102, 59, 99, 111, 108, 111, 114, 58, 119, 104, 105, 116, + 101, 59, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, + 35, 53, 50, 53, 68, 55, 54, 59, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 52, + 112, 120, 59, 125, 32, 98, 111, 100, 121, 32, 123, 102, 111, 110, 116, 45, 102, 97, + 109, 105, 108, 121, 58, 84, 97, 104, 111, 109, 97, 44, 65, 114, 105, 97, 108, 44, 115, + 97, 110, 115, 45, 115, 101, 114, 105, 102, 59, 99, 111, 108, 111, 114, 58, 98, 108, 97, + 99, 107, 59, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, + 58, 119, 104, 105, 116, 101, 59, 125, 32, 98, 32, 123, 102, 111, 110, 116, 45, 102, 97, + 109, 105, 108, 121, 58, 84, 97, 104, 111, 109, 97, 44, 65, 114, 105, 97, 108, 44, 115, + 97, 110, 115, 45, 115, 101, 114, 105, 102, 59, 99, 111, 108, 111, 114, 58, 119, 104, + 105, 116, 101, 59, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, + 111, 114, 58, 35, 53, 50, 53, 68, 55, 54, 59, 125, 32, 112, 32, 123, 102, 111, 110, + 116, 45, 102, 97, 109, 105, 108, 121, 58, 84, 97, 104, 111, 109, 97, 44, 65, 114, 105, + 97, 108, 44, 115, 97, 110, 115, 45, 115, 101, 114, 105, 102, 59, 98, 97, 99, 107, 103, + 114, 111, 117, 110, 100, 58, 119, 104, 105, 116, 101, 59, 99, 111, 108, 111, 114, 58, + 98, 108, 97, 99, 107, 59, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 50, 112, + 120, 59, 125, 32, 97, 32, 123, 99, 111, 108, 111, 114, 58, 98, 108, 97, 99, 107, 59, + 125, 32, 97, 46, 110, 97, 109, 101, 32, 123, 99, 111, 108, 111, 114, 58, 98, 108, 97, + 99, 107, 59, 125, 32, 46, 108, 105, 110, 101, 32, 123, 104, 101, 105, 103, 104, 116, + 58, 49, 112, 120, 59, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, + 111, 114, 58, 35, 53, 50, 53, 68, 55, 54, 59, 98, 111, 114, 100, 101, 114, 58, 110, + 111, 110, 101, 59, 125, 60, 47, 115, 116, 121, 108, 101, 62, 60, 47, 104, 101, 97, 100, + 62, 60, 98, 111, 100, 121, 62, 60, 104, 49, 62, 72, 84, 84, 80, 32, 83, 116, 97, 116, + 117, 115, 32, 53, 48, 48, 32, 226, 128, 147, 32, 73, 110, 116, 101, 114, 110, 97, 108, + 32, 83, 101, 114, 118, 101, 114, 32, 69, 114, 114, 111, 114, 60, 47, 104, 49, 62, 60, + 104, 114, 32, 99, 108, 97, 115, 115, 61, 34, 108, 105, 110, 101, 34, 32, 47, 62, 60, + 112, 62, 60, 98, 62, 84, 121, 112, 101, 60, 47, 98, 62, 32, 83, 116, 97, 116, 117, 115, + 32, 82, 101, 112, 111, 114, 116, 60, 47, 112, 62, 60, 112, 62, 60, 98, 62, 68, 101, + 115, 99, 114, 105, 112, 116, 105, 111, 110, 60, 47, 98, 62, 32, 84, 104, 101, 32, 115, + 101, 114, 118, 101, 114, 32, 101, 110, 99, 111, 117, 110, 116, 101, 114, 101, 100, 32, + 97, 110, 32, 117, 110, 101, 120, 112, 101, 99, 116, 101, 100, 32, 99, 111, 110, 100, + 105, 116, 105, 111, 110, 32, 116, 104, 97, 116, 32, 112, 114, 101, 118, 101, 110, 116, + 101, 100, 32, 105, 116, 32, 102, 114, 111, 109, 32, 102, 117, 108, 102, 105, 108, 108, + 105, 110, 103, 32, 116, 104, 101, 32, 114, 101, 113, 117, 101, 115, 116, 46, 60, 47, + 112, 62, 60, 104, 114, 32, 99, 108, 97, 115, 115, 61, 34, 108, 105, 110, 101, 34, 32, + 47, 62, 60, 104, 51, 62, 65, 112, 97, 99, 104, 101, 32, 84, 111, 109, 99, 97, 116, 47, + 56, 46, 53, 46, 51, 57, 60, 47, 104, 51, 62, 60, 47, 98, 111, 100, 121, 62, 60, 47, + 104, 116, 109, 108, 62, + ]; + let _ = Sdgb1_50::decode(data)?; + Ok(()) + } } diff --git a/sdgb-api/src/title/mod.rs b/sdgb-api/src/title/mod.rs index 49308d1..6dc6de8 100644 --- a/sdgb-api/src/title/mod.rs +++ b/sdgb-api/src/title/mod.rs @@ -14,6 +14,7 @@ use nyquest::{ header::{ACCEPT_ENCODING, CONTENT_ENCODING, EXPECT, USER_AGENT}, }; use serde::{Deserialize, Serialize}; +use spdlog::debug; pub trait MaiVersion { const AES_KEY: &[u8; 32]; @@ -24,7 +25,7 @@ pub trait MaiVersion { pub trait MaiVersionExt: MaiVersion { fn encode(data: impl AsRef<[u8]>) -> Result, ApiError>; - fn decode(data: impl AsMut<[u8]>) -> Result, ApiError>; + fn decode(data: impl AsRef<[u8]>) -> Result, ApiError>; fn api_hash(api: APIMethod) -> String { let api_name: &str = api.into(); @@ -78,6 +79,9 @@ pub trait MaiVersionExt: MaiVersion { .await .map_err(|_| ApiError::JoinError)??; let data = client.request(req).await?.bytes().await?; + + debug!("received: {data:?}"); + let decoded = spawn_blocking(move || Self::decode(data)) .await .map_err(|_| ApiError::JoinError)??; diff --git a/sdgb-api/src/title/model/get_user_data_api/mod.rs b/sdgb-api/src/title/model/get_user_data_api/mod.rs index 3c6573b..56466b3 100644 --- a/sdgb-api/src/title/model/get_user_data_api/mod.rs +++ b/sdgb-api/src/title/model/get_user_data_api/mod.rs @@ -1,7 +1,10 @@ use serde::Deserialize; use serde::Serialize; -pub struct GetUserDataApi {} +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct GetUserDataApi { + pub user_id: u32, +} #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -135,7 +138,7 @@ pub struct UserData { pub friend_regist_skip: i64, pub cm_last_emoney_credit: i64, pub cm_last_emoney_brand: i64, - + /// 访问密码(国区无) pub access_code: Option, /// 好友代码(国区无) diff --git a/sdgb-api/src/title/model/user_login_api/mod.rs b/sdgb-api/src/title/model/user_login_api/mod.rs index 9e7f79f..e6b70be 100644 --- a/sdgb-api/src/title/model/user_login_api/mod.rs +++ b/sdgb-api/src/title/model/user_login_api/mod.rs @@ -1,3 +1,5 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + use serde::{Deserialize, Serialize}; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -29,3 +31,24 @@ pub struct UserLoginApiResp { /// needed for some operation pub login_id: Option, } + +impl UserLoginApi { + pub fn new(user_id: u32) -> Self { + let date_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|t| t.as_secs()) + .unwrap_or_default(); + + // 爱玩星球焦作解放店 + UserLoginApi { + user_id, + date_time, + region_id: 13, + acsess_code: "".to_owned(), + place_id: 3223.to_string(), + is_continue: false, + generic_flag: 0, + client_id: "A63E01E6170".into(), + } + } +} diff --git a/sdgb-cli/Cargo.toml b/sdgb-cli/Cargo.toml index 8d99876..79eac49 100644 --- a/sdgb-cli/Cargo.toml +++ b/sdgb-cli/Cargo.toml @@ -15,6 +15,7 @@ cache = ["dep:redb"] [dependencies] sdgb-api = { workspace = true, features = ["bincode"] } +spdlog-rs = { workspace = true } snafu = { workspace = true } serde_json = { workspace = true } @@ -25,10 +26,6 @@ compio = { workspace = true, features = ["macros"], optional = true } nyquest-preset = { version = "0.2.0", features = ["async"] } palc = { version = "0.0.1", features = ["derive"] } -spdlog-rs = { version = "0.4.3", default-features = false, features = [ - "level-info", - "release-level-info", -] } futures-util = "0.3.31" redb = { version = "2.6.1", optional = true } ctrlc = { version = "3.4.7", features = ["termination"] } diff --git a/sdgb-cli/src/commands.rs b/sdgb-cli/src/commands.rs index 0394874..fa3ccca 100644 --- a/sdgb-cli/src/commands.rs +++ b/sdgb-cli/src/commands.rs @@ -36,6 +36,10 @@ pub enum Commands { #[arg(short, long)] user_id: u32, }, + Userdata { + #[arg(short, long)] + user_id: u32, + }, ListAllUser, diff --git a/sdgb-cli/src/main.rs b/sdgb-cli/src/main.rs index 93bb778..a36fb51 100644 --- a/sdgb-cli/src/main.rs +++ b/sdgb-cli/src/main.rs @@ -7,6 +7,7 @@ use std::{ use futures_util::StreamExt; use nyquest_preset::nyquest::ClientBuilder; use palc::Parser; +use spdlog::{Level, LevelFilter::MoreSevereEqual}; use sdgb_api::{ ApiError, @@ -16,8 +17,8 @@ use sdgb_api::{ MaiVersionExt, Sdgb1_40, Sdgb1_50, methods::APIMethod, model::{ - GetUserPreviewApi, GetUserPreviewApiResp, Ping, PingResp, UserLogoutApi, - UserLogoutApiResp, + GetUserDataApi, GetUserDataApiResp, GetUserPreviewApi, GetUserPreviewApiResp, Ping, + PingResp, UserLoginApi, UserLoginApiResp, UserLogoutApi, UserLogoutApiResp, }, }, }; @@ -36,6 +37,12 @@ static EARLY_QUIT: AtomicBool = AtomicBool::new(false); async fn main() -> Result<(), Box> { nyquest_preset::register(); + if cfg!(debug_assertions) { + spdlog::default_logger().set_level_filter(MoreSevereEqual(Level::Debug)); + } else { + spdlog::default_logger().set_level_filter(MoreSevereEqual(Level::Info)); + } + ctrlc::set_handler(|| { warn!("received early-quit request! will abort soon"); EARLY_QUIT.store(true, Ordering::Relaxed); @@ -45,6 +52,7 @@ async fn main() -> Result<(), Box> { let client = ClientBuilder::default().build_async().await?; + // TODO: refactor via enum_dispatch match cmd.command { commands::Commands::Logout { user_id } => { let logout: UserLogoutApiResp = Sdgb1_50::request( @@ -193,6 +201,76 @@ async fn main() -> Result<(), Box> { .open("players.json")?; serde_json::to_writer_pretty(output, &players)?; } + commands::Commands::Userdata { user_id } => { + let login = UserLoginApi::new(user_id); + let date_time = login.date_time; + let Ok(login_resp): Result = + Sdgb1_50::request(&client, APIMethod::UserLoginApi, user_id, login).await + else { + let logout_resp: UserLogoutApiResp = Sdgb1_50::request( + &client, + APIMethod::UserLogoutApi, + user_id, + UserLogoutApi { + user_id, + date_time, + ..Default::default() + }, + ) + .await?; + + info!("logout: {logout_resp:?}"); + return Ok(()); + }; + + match login_resp.return_code { + 1 => info!("login succeed"), + 100 => { + error!("user already logged"); + return Ok(()); + } + 102 => { + error!("QRCode expired"); + return Ok(()); + } + 103 => { + error!("Unregistered userId"); + return Ok(()); + } + e @ _ => { + error!("unknown login error: {e}"); + return Ok(()); + } + } + + match Sdgb1_50::request::<_, GetUserDataApiResp>( + &client, + APIMethod::GetUserDataApi, + user_id, + GetUserDataApi { user_id }, + ) + .await + { + Ok(_) => {} + Err(e) => { + error!("failed to get userdata: {e}"); + } + } + + let logout_resp: UserLogoutApiResp = Sdgb1_50::request( + &client, + APIMethod::UserLogoutApi, + user_id, + UserLogoutApi { + user_id, + date_time, + ..Default::default() + }, + ) + .await?; + + info!("logout: {logout_resp:?}"); + } } Ok(())