Compare commits
10 Commits
b61a724698
...
d3c3592e67
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3c3592e67 | ||
|
|
82e30c020d | ||
|
|
d4b6921eeb | ||
|
|
42575eaa32 | ||
|
|
dd1bb5fb5c | ||
|
|
5ea8ebbf64 | ||
|
|
704be45e00 | ||
|
|
1a281e0cc7 | ||
|
|
6ee009715d | ||
|
|
d870dc7047 |
38
Cargo.lock
generated
38
Cargo.lock
generated
@@ -718,6 +718,12 @@ version = "0.4.27"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md5"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.5"
|
version = "2.7.5"
|
||||||
@@ -1095,13 +1101,16 @@ dependencies = [
|
|||||||
"aes",
|
"aes",
|
||||||
"cbc",
|
"cbc",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"cipher",
|
||||||
"digest",
|
"digest",
|
||||||
"flate2",
|
"flate2",
|
||||||
"hmac-sha256",
|
"hmac-sha256",
|
||||||
|
"md5",
|
||||||
"nyquest",
|
"nyquest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"snafu",
|
"snafu",
|
||||||
|
"strum 0.27.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1112,8 +1121,10 @@ dependencies = [
|
|||||||
"nyquest-preset",
|
"nyquest-preset",
|
||||||
"palc",
|
"palc",
|
||||||
"sdgb-api",
|
"sdgb-api",
|
||||||
|
"serde_json",
|
||||||
"snafu",
|
"snafu",
|
||||||
"spdlog-rs",
|
"spdlog-rs",
|
||||||
|
"strum 0.27.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1205,8 +1216,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "15bf745ed831fe29ec1ff6cc8dfb443721c08e895c9a08fcaa1e2c6f09ec020d"
|
checksum = "15bf745ed831fe29ec1ff6cc8dfb443721c08e895c9a08fcaa1e2c6f09ec020d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
"strum",
|
"strum 0.24.1",
|
||||||
"strum_macros",
|
"strum_macros 0.24.3",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1260,7 +1271,16 @@ version = "0.24.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"strum_macros",
|
"strum_macros 0.24.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros 0.27.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1276,6 +1296,18 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ resolver = "3"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
sdgb-api = { path = "./sdgb-api" }
|
sdgb-api = { path = "./sdgb-api" }
|
||||||
|
|
||||||
snafu = { version = "0.8.6", features = ["backtrace", "rust_1_81"] }
|
snafu = { version = "0.8.6", features = ["backtrace", "rust_1_81"] }
|
||||||
|
serde_json = "1.0.141"
|
||||||
|
strum = { version = "0.27.2", features = ["derive"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@@ -7,16 +7,25 @@ license = "GPL-3.0"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
snafu = { workspace = true }
|
snafu = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
strum = {workspace = true}
|
||||||
|
|
||||||
|
# hashing
|
||||||
digest = "0.10.7"
|
digest = "0.10.7"
|
||||||
hmac-sha256 = { version = "1.1.12", features = ["digest010", "traits010"] }
|
hmac-sha256 = { version = "1.1.12", features = ["digest010", "traits010"] }
|
||||||
|
md5 = "0.8.0"
|
||||||
|
|
||||||
|
# other utils
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
|
|
||||||
|
# network request
|
||||||
nyquest = { version = "0.2.0", features = ["async", "json"] }
|
nyquest = { version = "0.2.0", features = ["async", "json"] }
|
||||||
|
|
||||||
|
# (de)serialization
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.141"
|
|
||||||
|
# compression / encryption
|
||||||
flate2 = "1.1.2"
|
flate2 = "1.1.2"
|
||||||
cbc = "0.1.2"
|
cbc = "0.1.2"
|
||||||
aes = "0.8.4"
|
aes = "0.8.4"
|
||||||
|
cipher = { version = "0.4.4", features = ["block-padding"] }
|
||||||
|
|||||||
91
sdgb-api/src/auth_lite/mod.rs
Normal file
91
sdgb-api/src/auth_lite/mod.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use cipher::{
|
||||||
|
BlockDecryptMut as _, BlockEncryptMut as _, BlockSizeUser as _, KeyIvInit as _,
|
||||||
|
block_padding::Pkcs7, generic_array::GenericArray,
|
||||||
|
};
|
||||||
|
use nyquest::{
|
||||||
|
AsyncClient,
|
||||||
|
r#async::{Body, Request},
|
||||||
|
header::USER_AGENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::error::ApiError;
|
||||||
|
|
||||||
|
const AES_IV: &[u8; 16] = b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||||
|
|
||||||
|
pub trait Delivery {
|
||||||
|
const AES_KEY: &[u8; 16];
|
||||||
|
const TITLE_ID: &str;
|
||||||
|
const USER_AGENT: &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// only for fun
|
||||||
|
pub struct SDHJ;
|
||||||
|
pub struct SDGB;
|
||||||
|
|
||||||
|
impl Delivery for SDHJ {
|
||||||
|
const AES_KEY: &[u8; 16] = b"-a57UX4y9/h(ImAQ";
|
||||||
|
const TITLE_ID: &str = "SDHJ";
|
||||||
|
const USER_AGENT: &str = "SDHJ;Windows/Lite";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Delivery for SDGB {
|
||||||
|
const AES_KEY: &[u8; 16] = br#"/?jo+"L&\Cr9(=kG"#;
|
||||||
|
const TITLE_ID: &str = "SDGB";
|
||||||
|
const USER_AGENT: &str = "SDGB;Windows/Lite";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delivery_raw<D>(
|
||||||
|
client: &AsyncClient,
|
||||||
|
title_ver: impl AsRef<str>,
|
||||||
|
) -> Result<Vec<u8>, ApiError>
|
||||||
|
where
|
||||||
|
D: Delivery,
|
||||||
|
{
|
||||||
|
let title_ver = title_ver.as_ref();
|
||||||
|
let params = format!(
|
||||||
|
"title_id={}&title_ver={title_ver}&client_id=A63E01C2805",
|
||||||
|
D::TITLE_ID
|
||||||
|
);
|
||||||
|
let enc_data = encrypt(params, D::AES_KEY, AES_IV)?;
|
||||||
|
|
||||||
|
let req = Request::post("http://at.sys-allnet.cn/net/delivery/instruction")
|
||||||
|
.with_body(Body::bytes(enc_data, "application/xxx-form-urlencoded"))
|
||||||
|
.with_header(USER_AGENT, D::USER_AGENT)
|
||||||
|
.with_header("Pragma", "DFI");
|
||||||
|
|
||||||
|
let mut resp = client.request(req).await?.bytes().await?;
|
||||||
|
let dec_len = decrypt(&mut resp, D::AES_KEY, AES_IV)?.len();
|
||||||
|
Ok(resp[16..dec_len].to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
|
||||||
|
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
|
||||||
|
|
||||||
|
fn encrypt(data: impl AsRef<[u8]>, key: &[u8; 16], iv: &[u8; 16]) -> Result<Vec<u8>, ApiError> {
|
||||||
|
let mut headed_data = vec![0u8; 16];
|
||||||
|
headed_data.extend_from_slice(data.as_ref());
|
||||||
|
|
||||||
|
let key = GenericArray::from_slice(key);
|
||||||
|
let iv = GenericArray::from_slice(iv);
|
||||||
|
let encryptor = Aes128CbcEnc::new(key, iv);
|
||||||
|
|
||||||
|
let bs = aes::Aes128::block_size();
|
||||||
|
let pad_len = bs - headed_data.len() % bs;
|
||||||
|
|
||||||
|
let mut buf = vec![0; pad_len + headed_data.len()];
|
||||||
|
buf[..headed_data.len()].copy_from_slice(&headed_data);
|
||||||
|
encryptor.encrypt_padded_mut::<Pkcs7>(&mut buf, headed_data.len())?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt<'ct>(
|
||||||
|
data: &'ct mut impl AsMut<[u8]>,
|
||||||
|
key: &[u8; 16],
|
||||||
|
iv: &[u8; 16],
|
||||||
|
) -> Result<&'ct [u8], ApiError> {
|
||||||
|
let key = GenericArray::from_slice(key);
|
||||||
|
let iv = GenericArray::from_slice(iv);
|
||||||
|
let decryptor = Aes128CbcDec::new(key, iv);
|
||||||
|
let result = decryptor.decrypt_padded_mut::<Pkcs7>(data.as_mut())?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
36
sdgb-api/src/error.rs
Normal file
36
sdgb-api/src/error.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use aes::cipher::{block_padding::UnpadError, inout::PadError};
|
||||||
|
use snafu::Snafu;
|
||||||
|
|
||||||
|
#[derive(Debug, Snafu)]
|
||||||
|
pub enum ApiError {
|
||||||
|
#[snafu(display("api returned nothing!"))]
|
||||||
|
EmptyResponse,
|
||||||
|
#[snafu(display("encrypt data: {error}"))]
|
||||||
|
Pad { error: PadError },
|
||||||
|
#[snafu(display("decrypt data: {error}"))]
|
||||||
|
Unpad { error: UnpadError },
|
||||||
|
|
||||||
|
#[snafu(display("io error: {source}"))]
|
||||||
|
#[snafu(context(false))]
|
||||||
|
IO { source: std::io::Error },
|
||||||
|
|
||||||
|
#[snafu(display("json error: {source}"))]
|
||||||
|
#[snafu(context(false))]
|
||||||
|
JSON { source: serde_json::Error },
|
||||||
|
|
||||||
|
#[snafu(display("request error: {source}"))]
|
||||||
|
#[snafu(context(false))]
|
||||||
|
Request { source: nyquest::Error },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UnpadError> for ApiError {
|
||||||
|
fn from(error: UnpadError) -> Self {
|
||||||
|
Self::Unpad { error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PadError> for ApiError {
|
||||||
|
fn from(error: PadError) -> Self {
|
||||||
|
Self::Pad { error }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,6 @@
|
|||||||
pub mod title;
|
|
||||||
pub mod all_net;
|
pub mod all_net;
|
||||||
|
pub mod auth_lite;
|
||||||
|
pub mod title;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
pub use error::ApiError;
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
pub const SDGB_1_50_KEY: &[u8; 32] = b"a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R";
|
|
||||||
pub const SDGB_1_50_IV: &[u8; 16] = b"d6xHIKq]1J]Dt^ue";
|
|
||||||
pub const SDGB_1_40_KEY: &[u8; 32] = b"n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@";
|
|
||||||
pub const SDGB_1_40_IV: &[u8; 16] = b";;KjR1C3hgB1ovXa";
|
|
||||||
@@ -1,24 +1,98 @@
|
|||||||
use std::io::Read;
|
use std::io::{Read, Write as _};
|
||||||
|
|
||||||
|
use aes::cipher::{
|
||||||
|
BlockDecryptMut, BlockEncryptMut, BlockSizeUser, KeyIvInit, block_padding::Pkcs7,
|
||||||
|
};
|
||||||
|
|
||||||
use aes::cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7};
|
|
||||||
use digest::generic_array::GenericArray;
|
use digest::generic_array::GenericArray;
|
||||||
use flate2::read::ZlibDecoder;
|
use flate2::write::ZlibEncoder;
|
||||||
|
use flate2::{Compression, read::ZlibDecoder};
|
||||||
|
|
||||||
use crate::title::error::ApiError;
|
use crate::error::ApiError;
|
||||||
|
use crate::title::{MaiVersion, MaiVersionExt, Sdgb1_40, Sdgb1_50};
|
||||||
|
|
||||||
pub mod constants;
|
impl MaiVersionExt for Sdgb1_40 {
|
||||||
|
fn decode(mut data: impl AsMut<[u8]>) -> Result<Vec<u8>, ApiError> {
|
||||||
|
let mut decompressed = decompress(data.as_mut());
|
||||||
|
if decompressed.is_empty() {
|
||||||
|
return Err(ApiError::EmptyResponse);
|
||||||
|
}
|
||||||
|
|
||||||
// type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
let orig_len = decompressed.len();
|
||||||
|
|
||||||
|
let remain = 16 - decompressed.len() % 16;
|
||||||
|
|
||||||
|
if
|
||||||
|
// weird but nessacary for Rust Pkcs7
|
||||||
|
remain != 16 {
|
||||||
|
decompressed.resize(remain + orig_len, remain as _);
|
||||||
|
}
|
||||||
|
|
||||||
|
let unpad_size = decrypt(&mut decompressed, Self::AES_KEY, Self::AES_IV)?.len();
|
||||||
|
decompressed.truncate(unpad_size);
|
||||||
|
Ok(decompressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(data: impl AsRef<[u8]>) -> Result<Vec<u8>, ApiError> {
|
||||||
|
let enc = encrypt(data, Self::AES_KEY, Self::AES_IV)?;
|
||||||
|
let compressed = compress(enc)?;
|
||||||
|
Ok(compressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaiVersionExt for Sdgb1_50 {
|
||||||
|
fn decode(mut data: impl AsMut<[u8]>) -> Result<Vec<u8>, ApiError> {
|
||||||
|
if data.as_mut().is_empty() {
|
||||||
|
return Err(ApiError::EmptyResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
let decrypted = decrypt(&mut data, Self::AES_KEY, Self::AES_IV)?;
|
||||||
|
let decompressed = decompress(decrypted);
|
||||||
|
Ok(decompressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(data: impl AsRef<[u8]>) -> Result<Vec<u8>, ApiError> {
|
||||||
|
let compressed = compress(data)?;
|
||||||
|
let enc = encrypt(compressed, Self::AES_KEY, Self::AES_IV)?;
|
||||||
|
Ok(enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
||||||
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
|
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
|
||||||
|
|
||||||
pub fn decompress(data: impl AsRef<[u8]>) -> Vec<u8> {
|
fn compress(data: impl AsRef<[u8]>) -> Result<Vec<u8>, ApiError> {
|
||||||
|
let mut buf = Vec::with_capacity(data.as_ref().len());
|
||||||
|
// 6 is the default compression level of zlib
|
||||||
|
let mut encoder = ZlibEncoder::new(&mut buf, Compression::new(6));
|
||||||
|
encoder.write_all(data.as_ref())?;
|
||||||
|
encoder.finish()?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress(data: impl AsRef<[u8]>) -> Vec<u8> {
|
||||||
let mut buf = Vec::with_capacity(data.as_ref().len() * 2);
|
let mut buf = Vec::with_capacity(data.as_ref().len() * 2);
|
||||||
let mut decode = ZlibDecoder::new(data.as_ref());
|
let mut decode = ZlibDecoder::new(data.as_ref());
|
||||||
_ = decode.read_to_end(&mut buf);
|
_ = decode.read_to_end(&mut buf);
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt<'ct>(
|
fn encrypt(data: impl AsRef<[u8]>, key: &[u8; 32], iv: &[u8; 16]) -> Result<Vec<u8>, ApiError> {
|
||||||
|
let key = GenericArray::from_slice(key);
|
||||||
|
let iv = GenericArray::from_slice(iv);
|
||||||
|
let encryptor = Aes256CbcEnc::new(key, iv);
|
||||||
|
let data = data.as_ref();
|
||||||
|
let bs = aes::Aes256::block_size();
|
||||||
|
let pad_len = bs - data.len() % bs;
|
||||||
|
|
||||||
|
let mut buf = vec![0; pad_len + data.len()];
|
||||||
|
buf[..data.len()].copy_from_slice(&data);
|
||||||
|
|
||||||
|
encryptor.encrypt_padded_mut::<Pkcs7>(&mut buf, data.len())?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt<'ct>(
|
||||||
data: &'ct mut impl AsMut<[u8]>,
|
data: &'ct mut impl AsMut<[u8]>,
|
||||||
key: &[u8; 32],
|
key: &[u8; 32],
|
||||||
iv: &[u8; 16],
|
iv: &[u8; 16],
|
||||||
@@ -33,24 +107,26 @@ pub fn decrypt<'ct>(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod _tests {
|
mod _tests {
|
||||||
|
|
||||||
use crate::title::encryption::{constants::*, *};
|
use crate::title::{Sdgb1_50, encryption::*};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sdgb_140_dec() {
|
fn test_sdgb_140_dec_enc() -> Result<(), ApiError> {
|
||||||
let data = [
|
let data = [
|
||||||
120_u8, 156, 171, 77, 91, 233, 184, 108, 2, 71, 125, 142, 118, 135, 112, 181, 85, 217,
|
120_u8, 156, 171, 77, 91, 233, 184, 108, 2, 71, 125, 142, 118, 135, 112, 181, 85, 217,
|
||||||
239, 243, 159, 153, 248, 98, 159, 185, 63, 43, 173, 106, 221, 115, 104, 105, 221, 107,
|
239, 243, 159, 153, 248, 98, 159, 185, 63, 43, 173, 106, 221, 115, 104, 105, 221, 107,
|
||||||
0, 241, 176, 16, 37,
|
0, 241, 176, 16, 37,
|
||||||
];
|
];
|
||||||
let mut decompressed = decompress(data);
|
|
||||||
let data =
|
let dec = Sdgb1_40::decode(data)?;
|
||||||
decrypt(&mut decompressed, SDGB_1_40_KEY, SDGB_1_40_IV).expect("decryption failed!");
|
assert_eq!(dec, br#"{"result":"Pong"}"#);
|
||||||
assert_eq!(&data, br#"{"result":"Pong"}"#);
|
let enc = Sdgb1_40::encode(dec)?;
|
||||||
|
assert_eq!(enc, data);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sdgb_150_dec() {
|
fn test_sdgb_150_dec_enc() -> Result<(), ApiError> {
|
||||||
let mut data = [
|
let data = [
|
||||||
161, 166, 3, 157, 202, 233, 151, 73, 40, 113, 186, 162, 177, 46, 118, 113, 98, 231, 67,
|
161, 166, 3, 157, 202, 233, 151, 73, 40, 113, 186, 162, 177, 46, 118, 113, 98, 231, 67,
|
||||||
185, 246, 180, 109, 253, 1, 152, 0, 31, 81, 211, 28, 137, 95, 12, 110, 105, 181, 246,
|
185, 246, 180, 109, 253, 1, 152, 0, 31, 81, 211, 28, 137, 95, 12, 110, 105, 181, 246,
|
||||||
177, 1, 45, 59, 182, 113, 56, 97, 56, 100, 34, 168, 27, 51, 228, 77, 192, 194, 248, 45,
|
177, 1, 45, 59, 182, 113, 56, 97, 56, 100, 34, 168, 27, 51, 228, 77, 192, 194, 248, 45,
|
||||||
@@ -66,8 +142,10 @@ mod _tests {
|
|||||||
177, 84, 85, 152, 183, 77, 67, 163, 61, 165, 144, 125, 255, 89, 108, 58, 137, 142, 9,
|
177, 84, 85, 152, 183, 77, 67, 163, 61, 165, 144, 125, 255, 89, 108, 58, 137, 142, 9,
|
||||||
8, 54, 228, 34, 55, 124, 158, 83, 36,
|
8, 54, 228, 34, 55, 124, 158, 83, 36,
|
||||||
];
|
];
|
||||||
let decrypted = decrypt(&mut data, SDGB_1_50_KEY, SDGB_1_50_IV).expect("decryption failed");
|
let dec = Sdgb1_50::decode(data)?;
|
||||||
let decompressed = decompress(decrypted);
|
assert_eq!(dec, r#"{"userId":10103750,"userName":"舞萌","isLogin":false,"lastGameId":null,"lastRomVersion":"1.01.00","lastDataVersion":"1.05.03","lastLoginDate":"1970-01-01 00:00:00","lastPlayDate":"1970-01-01 00:00:00","playerRating":1024,"nameplateId":0,"iconId":11,"trophyId":0,"isNetMember":1,"isInherit":false,"totalAwake":5,"dispRate":0,"dailyBonusDate":"1970-01-01 09:00:00","headPhoneVolume":null,"banState":0}"#.as_bytes());
|
||||||
assert_eq!(decompressed, r#"{"userId":10103750,"userName":"舞萌","isLogin":false,"lastGameId":null,"lastRomVersion":"1.01.00","lastDataVersion":"1.05.03","lastLoginDate":"1970-01-01 00:00:00","lastPlayDate":"1970-01-01 00:00:00","playerRating":1024,"nameplateId":0,"iconId":11,"trophyId":0,"isNetMember":1,"isInherit":false,"totalAwake":5,"dispRate":0,"dailyBonusDate":"1970-01-01 09:00:00","headPhoneVolume":null,"banState":0}"#.as_bytes())
|
let enc = Sdgb1_50::encode(dec)?;
|
||||||
|
assert_eq!(enc, data);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
use aes::cipher::block_padding::UnpadError;
|
|
||||||
use snafu::Snafu;
|
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum ApiError {
|
|
||||||
#[snafu(display("unpad data: {error}"))]
|
|
||||||
UnpadError { error: UnpadError },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<UnpadError> for ApiError {
|
|
||||||
fn from(error: UnpadError) -> Self {
|
|
||||||
Self::UnpadError { error }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
sdgb-api/src/title/methods/mod.rs
Normal file
63
sdgb-api/src/title/methods/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#[derive(strum::IntoStaticStr)]
|
||||||
|
pub enum APIMethod {
|
||||||
|
GetGameChargeApi,
|
||||||
|
GetGameEventApi,
|
||||||
|
GetGameNgMusicIdApi,
|
||||||
|
GetGameNgWordListApi,
|
||||||
|
GetGameRankingApi,
|
||||||
|
GetGameSettingApi,
|
||||||
|
GetGameTournamentInfoApi,
|
||||||
|
GetTransferFriendApi,
|
||||||
|
GetUserActivityApi,
|
||||||
|
GetUserCardApi,
|
||||||
|
GetUserCharacterApi,
|
||||||
|
GetUserChargeApi,
|
||||||
|
GetUserCourseApi,
|
||||||
|
GetUserDataApi,
|
||||||
|
GetUserExtendApi,
|
||||||
|
GetUserFavoriteApi,
|
||||||
|
GetUserFavoriteItemApi,
|
||||||
|
GetUserFriendSeasonRankingApi,
|
||||||
|
GetUserGhostApi,
|
||||||
|
GetUserItemApi,
|
||||||
|
GetUserLoginBonusApi,
|
||||||
|
GetUserMapApi,
|
||||||
|
GetUserMusicApi,
|
||||||
|
GetUserOptionApi,
|
||||||
|
GetUserPortraitApi,
|
||||||
|
GetUserPreviewApi,
|
||||||
|
GetUserRatingApi,
|
||||||
|
GetUserRecommendRateMusicApi,
|
||||||
|
GetUserRecommendSelectMusicApi,
|
||||||
|
GetUserRegionApi,
|
||||||
|
GetUserScoreRankingApi,
|
||||||
|
Ping,
|
||||||
|
UploadUserPhotoApi,
|
||||||
|
UploadUserPlaylogApi,
|
||||||
|
UploadUserPortraitApi,
|
||||||
|
UpsertClientBookkeepingApi,
|
||||||
|
UpsertClientSettingApi,
|
||||||
|
UpsertClientTestmodeApi,
|
||||||
|
UpsertClientUploadApi,
|
||||||
|
UpsertUserAllApi,
|
||||||
|
UpsertUserChargelogApi,
|
||||||
|
UserLoginApi,
|
||||||
|
UserLogoutApi,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod _test {
|
||||||
|
use crate::title::{MaiVersionExt, Sdgb1_50, methods::APIMethod};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_obfuscate_1_50() {
|
||||||
|
assert_eq!(
|
||||||
|
Sdgb1_50::api_hash(APIMethod::Ping),
|
||||||
|
"250b3482854e7697de7d8eb6ea1fabb1"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Sdgb1_50::api_hash(APIMethod::GetUserPreviewApi),
|
||||||
|
"004cf848f96d393a5f2720101e30b93d"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,116 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::title::methods::APIMethod;
|
||||||
|
|
||||||
pub mod encryption;
|
pub mod encryption;
|
||||||
mod error;
|
pub mod methods;
|
||||||
|
pub mod model;
|
||||||
|
|
||||||
|
use super::ApiError;
|
||||||
|
|
||||||
|
use nyquest::{
|
||||||
|
AsyncClient, Body,
|
||||||
|
r#async::Request,
|
||||||
|
header::{ACCEPT_ENCODING, CONTENT_ENCODING, EXPECT, USER_AGENT},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub trait MaiVersion {
|
||||||
|
const AES_KEY: &[u8; 32];
|
||||||
|
const AES_IV: &[u8; 16];
|
||||||
|
const OBFUSECATE_SUFFIX: &str;
|
||||||
|
const VERSION: &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MaiVersionExt: MaiVersion {
|
||||||
|
fn encode(data: impl AsRef<[u8]>) -> Result<Vec<u8>, ApiError>;
|
||||||
|
fn decode(data: impl AsMut<[u8]>) -> Result<Vec<u8>, ApiError>;
|
||||||
|
|
||||||
|
fn api_hash(api: APIMethod) -> String {
|
||||||
|
let api_name: &str = api.into();
|
||||||
|
|
||||||
|
let mut md5 = md5::Context::new();
|
||||||
|
md5.consume(api_name);
|
||||||
|
md5.consume(Self::OBFUSECATE_SUFFIX);
|
||||||
|
let digest = md5.finalize();
|
||||||
|
|
||||||
|
format!("{digest:x}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn api_request<D>(
|
||||||
|
api: APIMethod,
|
||||||
|
agent_extra: impl Display,
|
||||||
|
data: D,
|
||||||
|
) -> Result<Request, ApiError>
|
||||||
|
where
|
||||||
|
D: Serialize,
|
||||||
|
{
|
||||||
|
let json = serde_json::to_vec(&data)?;
|
||||||
|
let payload = Self::encode(json)?;
|
||||||
|
|
||||||
|
let api_hash = Self::api_hash(api);
|
||||||
|
let req = Request::post(format!(
|
||||||
|
"https://maimai-gm.wahlap.com:42081/Maimai2Servlet/{api_hash}"
|
||||||
|
))
|
||||||
|
.with_body(Body::json_bytes(payload))
|
||||||
|
.with_header(USER_AGENT, format!("{api_hash}#{agent_extra}"))
|
||||||
|
.with_header("Mai-Encoding", Self::VERSION)
|
||||||
|
.with_header(ACCEPT_ENCODING, "")
|
||||||
|
.with_header("Charset", "UTF-8")
|
||||||
|
.with_header(CONTENT_ENCODING, "deflate")
|
||||||
|
.with_header(EXPECT, "100-continue");
|
||||||
|
|
||||||
|
Ok(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_raw<D>(
|
||||||
|
client: &AsyncClient,
|
||||||
|
api: APIMethod,
|
||||||
|
agent_extra: impl Display,
|
||||||
|
data: D,
|
||||||
|
) -> impl Future<Output = Result<Vec<u8>, ApiError>>
|
||||||
|
where
|
||||||
|
D: Serialize,
|
||||||
|
{
|
||||||
|
async {
|
||||||
|
let req = Self::api_request(api, agent_extra, data)?;
|
||||||
|
let data = client.request(req).await?.bytes().await?;
|
||||||
|
let decoded = Self::decode(data)?;
|
||||||
|
Ok(decoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request<D, R>(
|
||||||
|
client: &AsyncClient,
|
||||||
|
api: APIMethod,
|
||||||
|
agent_extra: impl Display,
|
||||||
|
data: D,
|
||||||
|
) -> impl Future<Output = Result<R, ApiError>>
|
||||||
|
where
|
||||||
|
D: Serialize,
|
||||||
|
R: for<'a> Deserialize<'a>,
|
||||||
|
{
|
||||||
|
async {
|
||||||
|
let raw_data = Self::request_raw(client, api, agent_extra, data).await?;
|
||||||
|
Ok(serde_json::from_slice(&raw_data)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sdgb1_40;
|
||||||
|
pub struct Sdgb1_50;
|
||||||
|
|
||||||
|
impl MaiVersion for Sdgb1_40 {
|
||||||
|
const AES_KEY: &[u8; 32] = b"n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@";
|
||||||
|
const AES_IV: &[u8; 16] = b";;KjR1C3hgB1ovXa";
|
||||||
|
const OBFUSECATE_SUFFIX: &str = "MaimaiChnBEs2D5vW";
|
||||||
|
|
||||||
|
const VERSION: &str = "1.40";
|
||||||
|
}
|
||||||
|
impl MaiVersion for Sdgb1_50 {
|
||||||
|
const AES_KEY: &[u8; 32] = b"a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R";
|
||||||
|
const AES_IV: &[u8; 16] = b"d6xHIKq]1J]Dt^ue";
|
||||||
|
const OBFUSECATE_SUFFIX: &str = "MaimaiChnB44df8yT";
|
||||||
|
|
||||||
|
const VERSION: &str = "1.50";
|
||||||
|
}
|
||||||
|
|||||||
70
sdgb-api/src/title/model/get_user_preview_api/mod.rs
Normal file
70
sdgb-api/src/title/model/get_user_preview_api/mod.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GetUserPreviewApi {
|
||||||
|
pub user_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GetUserPreviewApiResp {
|
||||||
|
pub user_id: i64,
|
||||||
|
pub user_name: String,
|
||||||
|
pub is_login: bool,
|
||||||
|
pub last_rom_version: String,
|
||||||
|
pub last_data_version: String,
|
||||||
|
pub last_login_date: String,
|
||||||
|
pub last_play_date: String,
|
||||||
|
pub player_rating: i64,
|
||||||
|
pub nameplate_id: i64,
|
||||||
|
pub icon_id: i64,
|
||||||
|
pub trophy_id: i64,
|
||||||
|
pub is_net_member: i64,
|
||||||
|
pub is_inherit: bool,
|
||||||
|
pub total_awake: i64,
|
||||||
|
pub disp_rate: i64,
|
||||||
|
pub daily_bonus_date: String,
|
||||||
|
pub ban_state: i64,
|
||||||
|
// pub last_game_id: Value,
|
||||||
|
// pub head_phone_volume: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for GetUserPreviewApiResp {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("ID: {}\n", self.user_id))?;
|
||||||
|
f.write_fmt(format_args!("昵称: {}\n", self.user_name))?;
|
||||||
|
f.write_fmt(format_args!("登录中: {}\n", self.is_login))?;
|
||||||
|
|
||||||
|
f.write_fmt(format_args!("上次游戏版本: {}\n", self.last_rom_version))?;
|
||||||
|
f.write_fmt(format_args!("上次数据版本: {}\n", self.last_data_version))?;
|
||||||
|
|
||||||
|
f.write_fmt(format_args!("DX Rating: {}\n", self.player_rating))?;
|
||||||
|
f.write_fmt(format_args!("牌子: {}\n", self.nameplate_id))?;
|
||||||
|
f.write_fmt(format_args!("图标: {}\n", self.icon_id))?;
|
||||||
|
f.write_fmt(format_args!("trophy: {}\n", self.trophy_id))?;
|
||||||
|
f.write_fmt(format_args!("Net成员: {}\n", self.is_net_member))?;
|
||||||
|
f.write_fmt(format_args!("继承账号: {}\n", self.is_inherit))?;
|
||||||
|
f.write_fmt(format_args!("总觉醒: {}\n", self.total_awake))?;
|
||||||
|
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"状态显示: {}\n",
|
||||||
|
match self.disp_rate {
|
||||||
|
0 => "全部都显示",
|
||||||
|
1 => "显示评级和段位",
|
||||||
|
2 => "显示评级和阶级",
|
||||||
|
3 => "显示段位和阶级",
|
||||||
|
4 => "只显示评级",
|
||||||
|
5 => "只显示段位",
|
||||||
|
6 => "只显示阶级",
|
||||||
|
7 => "全部都不显示",
|
||||||
|
_ => "未知",
|
||||||
|
}
|
||||||
|
))?;
|
||||||
|
|
||||||
|
f.write_fmt(format_args!("封禁状态: {}\n", self.ban_state))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
8
sdgb-api/src/title/model/mod.rs
Normal file
8
sdgb-api/src/title/model/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
mod ping;
|
||||||
|
pub use ping::{Ping, PingResp};
|
||||||
|
|
||||||
|
mod get_user_preview_api;
|
||||||
|
pub use get_user_preview_api::{GetUserPreviewApi, GetUserPreviewApiResp};
|
||||||
|
|
||||||
|
mod user_logout_api;
|
||||||
|
pub use user_logout_api::{UserLogoutApi, UserLogoutApiResp};
|
||||||
18
sdgb-api/src/title/model/ping/mod.rs
Normal file
18
sdgb-api/src/title/model/ping/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Ping {}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PingResp {
|
||||||
|
result: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PingResp {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.result)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
45
sdgb-api/src/title/model/user_logout_api/mod.rs
Normal file
45
sdgb-api/src/title/model/user_logout_api/mod.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UserLogoutApi {
|
||||||
|
pub user_id: u32,
|
||||||
|
pub region_id: u64,
|
||||||
|
pub place_id: u64,
|
||||||
|
/// empty on SDGB
|
||||||
|
pub access_code: &'static str,
|
||||||
|
/// keychip without dash, 11 bytes
|
||||||
|
pub client_id: String,
|
||||||
|
/// Unix timestamp
|
||||||
|
pub date_time: u64,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub type_: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UserLogoutApiResp {
|
||||||
|
pub return_code: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UserLogoutApi {
|
||||||
|
fn default() -> Self {
|
||||||
|
let user_id = 0;
|
||||||
|
let date_time = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map(|t| t.as_secs())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
user_id,
|
||||||
|
date_time,
|
||||||
|
region_id: 22,
|
||||||
|
place_id: 3490,
|
||||||
|
client_id: "A63E01E9564".into(),
|
||||||
|
type_: 1,
|
||||||
|
access_code: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ description = "CLI tool for SDGB protocol"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
snafu = { workspace = true }
|
snafu = { workspace = true }
|
||||||
sdgb-api = { workspace = true }
|
sdgb-api = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
strum = { workspace = true }
|
||||||
|
|
||||||
nyquest-preset = { version = "0.2.0", features = ["async"] }
|
nyquest-preset = { version = "0.2.0", features = ["async"] }
|
||||||
compio = { version = "0.15.0", default-features = false, features = [
|
compio = { version = "0.15.0", default-features = false, features = [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use palc::Parser;
|
use palc::Parser;
|
||||||
use palc::Subcommand;
|
use palc::Subcommand;
|
||||||
|
use strum::EnumString;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(about = "SDGB api tool", long_about = env!("CARGO_PKG_DESCRIPTION"))]
|
#[command(about = "SDGB api tool", long_about = env!("CARGO_PKG_DESCRIPTION"))]
|
||||||
@@ -8,6 +9,12 @@ pub struct Cli {
|
|||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(EnumString)]
|
||||||
|
pub enum AuthLiteVariant {
|
||||||
|
SDGB,
|
||||||
|
SDHJ,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
/// Login with QRCode from wechat
|
/// Login with QRCode from wechat
|
||||||
@@ -16,4 +23,21 @@ pub enum Commands {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
qrcode_content: String,
|
qrcode_content: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
AuthLite {
|
||||||
|
#[arg(short, long, default_value = "1.50")]
|
||||||
|
title_ver: String,
|
||||||
|
#[arg(long, default_value = "SDGB")]
|
||||||
|
variant: AuthLiteVariant,
|
||||||
|
},
|
||||||
|
|
||||||
|
Ping,
|
||||||
|
Preview {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user_id: u32,
|
||||||
|
},
|
||||||
|
Logout {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user_id: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
use nyquest_preset::nyquest::ClientBuilder;
|
use nyquest_preset::nyquest::ClientBuilder;
|
||||||
use palc::Parser;
|
use palc::Parser;
|
||||||
use sdgb_api::all_net::QRCode;
|
use sdgb_api::{
|
||||||
|
all_net::QRCode,
|
||||||
|
auth_lite::{SDGB, SDHJ, delivery_raw},
|
||||||
|
title::{
|
||||||
|
MaiVersionExt, Sdgb1_40, Sdgb1_50,
|
||||||
|
methods::APIMethod,
|
||||||
|
model::{
|
||||||
|
GetUserPreviewApi, GetUserPreviewApiResp, Ping, PingResp, UserLogoutApi,
|
||||||
|
UserLogoutApiResp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
use spdlog::{error, info};
|
use spdlog::{error, info};
|
||||||
|
|
||||||
use crate::commands::Cli;
|
use crate::commands::Cli;
|
||||||
@@ -15,6 +26,43 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
|
|||||||
let client = ClientBuilder::default().build_async().await?;
|
let client = ClientBuilder::default().build_async().await?;
|
||||||
|
|
||||||
match cmd.command {
|
match cmd.command {
|
||||||
|
commands::Commands::Logout { user_id } => {
|
||||||
|
let logout: UserLogoutApiResp = Sdgb1_50::request(
|
||||||
|
&client,
|
||||||
|
APIMethod::UserLogoutApi,
|
||||||
|
user_id,
|
||||||
|
UserLogoutApi {
|
||||||
|
user_id,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
println!("{logout:?}");
|
||||||
|
}
|
||||||
|
commands::Commands::Preview { user_id } => {
|
||||||
|
let preview: GetUserPreviewApiResp = Sdgb1_50::request(
|
||||||
|
&client,
|
||||||
|
APIMethod::GetUserPreviewApi,
|
||||||
|
user_id,
|
||||||
|
GetUserPreviewApi { user_id },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("{preview}");
|
||||||
|
}
|
||||||
|
commands::Commands::Ping => {
|
||||||
|
let decoded: PingResp = Sdgb1_40::request(
|
||||||
|
&client,
|
||||||
|
APIMethod::Ping,
|
||||||
|
"",
|
||||||
|
Ping {}, // note: must not be `Ping`, or serde_json serializes to nothing
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
info!("sdgb 1.40 resp: {decoded}");
|
||||||
|
let decoded: PingResp =
|
||||||
|
Sdgb1_50::request(&client, APIMethod::Ping, "", Ping {}).await?;
|
||||||
|
info!("sdgb 1.50 resp: {decoded}");
|
||||||
|
}
|
||||||
commands::Commands::QRLogin { ref qrcode_content } => {
|
commands::Commands::QRLogin { ref qrcode_content } => {
|
||||||
let qrcode = QRCode { qrcode_content };
|
let qrcode = QRCode { qrcode_content };
|
||||||
match qrcode.login(&client).await {
|
match qrcode.login(&client).await {
|
||||||
@@ -22,6 +70,14 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
|
|||||||
Err(e) => error!("login failed: {e}"),
|
Err(e) => error!("login failed: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commands::Commands::AuthLite { title_ver, variant } => {
|
||||||
|
let resp = match variant {
|
||||||
|
commands::AuthLiteVariant::SDGB => delivery_raw::<SDGB>(&client, title_ver).await?,
|
||||||
|
commands::AuthLiteVariant::SDHJ => delivery_raw::<SDHJ>(&client, title_ver).await?,
|
||||||
|
};
|
||||||
|
println!("{}", String::from_utf8_lossy(&resp));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user