1use std::io::Cursor;
6
7use base64::prelude::*;
8use image::{GenericImageView, Pixel};
9use structs::mojang::{ProfileResponse, ProfileTexture};
10
11use self::structs::AuthMethod;
12use crate::{
13 http::{no_retry::*, RequestResult},
14 password::Password,
15 prelude::*,
16};
17
18pub mod authlib;
19pub mod microsoft;
20pub mod structs;
21
22pub fn parse_head_skin(result: Vec<u8>) -> DynResult<(Vec<u8>, Vec<u8>)> {
26 let cursor = Cursor::new(result);
27 let mut skin_data = Vec::with_capacity(2 * 4 * 64);
28 let mut skin_hat_data = Vec::with_capacity(2 * 4 * 64);
29 let skin = image::load(cursor, image::ImageFormat::Png)?;
30 for y in 8..16 {
31 for x in 8..16 {
32 let pixel = skin.get_pixel(x, y).to_rgba();
33 skin_data.push(pixel.0[0]);
34 skin_data.push(pixel.0[1]);
35 skin_data.push(pixel.0[2]);
36 skin_data.push(pixel.0[3]);
37 }
38 }
39 for y in 8..16 {
40 for x in 40..48 {
41 let pixel = skin.get_pixel(x, y).to_rgba();
42 skin_hat_data.push(pixel.0[0]);
43 skin_hat_data.push(pixel.0[1]);
44 skin_hat_data.push(pixel.0[2]);
45 skin_hat_data.push(pixel.0[3]);
46 }
47 }
48 Ok((skin_data, skin_hat_data))
49}
50
51async fn get_head_skin(uuid: &str) -> DynResult<(Vec<u8>, Vec<u8>)> {
52 let uri = format!("https://sessionserver.mojang.com/session/minecraft/profile/{uuid}");
54 let result: ProfileResponse = crate::http::get(uri)
55 .await
56 .map_err(|e| anyhow::anyhow!(e))?
57 .body_json()
58 .await
59 .map_err(|e| anyhow::anyhow!(e))?;
60 if let Some(prop) = result
61 .properties
62 .iter()
63 .find(|a| a.name.as_str() == "textures")
64 {
65 let texture_raw = &prop.value;
66 let texture_raw = BASE64_STANDARD.decode(texture_raw)?;
67 let texture_data: ProfileTexture = serde_json::from_slice(&texture_raw)?;
68 if let Some(textures) = texture_data.textures {
69 if let Some(skin) = textures.skin {
70 let skin_url = skin.url;
71 parse_head_skin(
72 crate::http::get(skin_url)
73 .recv_bytes()
74 .await
75 .map_err(|e| anyhow::anyhow!(e))?,
76 )
77 } else {
78 Ok(Default::default())
79 }
80 } else {
81 Ok(Default::default())
82 }
83 } else {
84 Ok(Default::default())
85 }
86}
87
88pub async fn auth_mojang(
92 _ctx: Option<impl Reporter>,
93 username: &str,
94 password: &Password,
95 client_token: &str,
96) -> DynResult<AuthMethod> {
97 let body = structs::mojang::AuthenticateBody {
99 username: username.into(),
100 password: password.to_owned(),
101 client_token: client_token.into(),
102 ..Default::default()
103 };
104 let result: RequestResult<structs::mojang::AuthenticateResponse> =
105 post_data("https://authserver.mojang.com/authenticate", &body).await?;
106 match result {
107 RequestResult::Ok(a) => {
108 let selected_profile = if let Some(selected_profile) = a.selected_profile {
109 selected_profile
110 } else if let Some(profile) = a.available_profiles.into_iter().next() {
111 profile } else {
113 anyhow::bail!("该账户没有可用的角色!")
114 };
115 let (head_skin, hat_skin) = get_head_skin(&selected_profile.id).await?;
116 Ok(AuthMethod::Mojang {
117 access_token: a.access_token,
118 uuid: selected_profile.id,
119 player_name: selected_profile.name,
120 head_skin,
121 hat_skin,
122 })
123 }
124 RequestResult::Err(_) => Ok(AuthMethod::Offline {
125 player_name: "".into(),
126 uuid: "".into(),
127 }),
128 }
129}
130
131pub async fn refresh_auth(am: &mut AuthMethod, client_token: &str) -> DynResult<bool> {
133 if let &mut AuthMethod::Microsoft { .. } = am {
134 return Ok(microsoft::leagcy::refresh_auth(am).await.is_ok());
135 }
136 match am {
137 AuthMethod::Mojang { access_token, .. } => {
138 let body = structs::mojang::ValidateResponse {
139 access_token: access_token.to_owned(),
140 client_token: client_token.to_owned(),
141 };
142 let result = crate::http::post("https://authserver.mojang.com/validate")
143 .body(serde_json::to_vec(&body)?)
144 .header("Content-Type", "application/json")
145 .await
146 .map_err(|e| anyhow::anyhow!("发送用户信息请求失败,可能是网络问题:{:?}", e))?;
147 if result.status().is_success() {
148 Ok(true)
149 } else {
150 Ok(false)
151 }
152 }
153 AuthMethod::Microsoft { access_token, .. } => {
154 let profile_resp =
156 crate::http::get("https://api.minecraftservices.com/minecraft/profile")
157 .header("Authorization", &format!("Bearer {}", &access_token))
158 .await
159 .map_err(|e| {
160 anyhow::anyhow!("发送用户信息请求失败,有可能是网络问题:{:?}", e)
161 })?;
162 Ok(profile_resp.status().is_success())
163 }
164 AuthMethod::AuthlibInjector { .. } => {
165 if let Ok(new_am) =
166 crate::auth::authlib::refresh_token(am.to_owned(), client_token, false).await
167 {
168 *am = new_am;
169 Ok(true)
170 } else {
171 Ok(false)
172 }
173 }
174 _ => Ok(true),
175 }
176}