minecraft_essentials/lib.rs
1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code, missing_docs)]
3#![warn(clippy::pedantic)]
4
5// Modules
6pub(crate) mod async_trait_alias;
7/// Error handling module for the Minecraft-Essentials library.
8///
9/// This module contains all the error types and related functionality
10/// for error handling within the library.
11pub mod errors;
12#[cfg(test)]
13mod tests;
14
15#[cfg(feature = "custom-auth")]
16mod custom;
17
18#[cfg(feature = "custom-auth")]
19pub use custom::mojang::AuthInfo as CustomAuthData;
20
21#[cfg(feature = "custom-auth")]
22use custom::{code, mojang, oauth, xbox};
23
24#[cfg(feature = "custom-launch")]
25use std::{
26 io::{BufRead, BufReader},
27 path::PathBuf,
28 process::{Command, Stdio},
29};
30
31// Constants
32pub(crate) const SCOPE: &str = "XboxLive.signin%20XboxLive.offline_access";
33pub(crate) const EXPERIMENTAL_MESSAGE: &str =
34 "\x1b[33mNOTICE: You are using an experimental feature.\x1b[0m";
35
36/// OAuth 2.0 Authentication
37///
38/// This struct represents the OAuth authentication process for Minecraft, specifically designed for use with custom Azure applications.
39/// It is used to authenticate a user and obtain a token that can be used to launch Minecraft.
40#[cfg(feature = "custom-auth")]
41pub struct Oauth {
42 url: String,
43 port: u16,
44 client_id: String,
45}
46
47#[cfg(feature = "custom-auth")]
48impl Oauth {
49 /// Initializes a new `Oauth` instance.
50 ///
51 /// This method sets up the OAuth authentication process by constructing the authorization URL
52 /// and storing the client ID and port for later use.
53 ///
54 /// # Arguments
55 ///
56 /// * `client_id` - The client ID obtained from the Minecraft authentication service.
57 /// * `port` - An optional port number for the local server. Defaults to 8000 if not provided.
58 ///
59 /// # Returns
60 ///
61 /// * `Self` - A new instance of `Oauth` configured with the provided client ID and port.
62 pub fn new(client_id: &str, port: Option<u16>) -> Self {
63 let port = port.unwrap_or(8000);
64 let params = format!("client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345", client_id, port, SCOPE);
65 let url = format!(
66 "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?{}",
67 params
68 );
69
70 Self {
71 url,
72 port,
73 client_id: client_id.to_string(),
74 }
75 }
76
77 /// Retrieves the authorization URL.
78 ///
79 /// This method returns the URL that the user needs to visit to authorize the application.
80 ///
81 /// # Returns
82 ///
83 /// * `&str` - The authorization URL.
84 pub fn url(&self) -> &str {
85 &self.url
86 }
87
88 /// Launches Minecraft using the OAuth authentication process.
89 ///
90 /// This method completes the OAuth authentication process by launching a local server to
91 /// receive the authorization code, exchanging it for an access token, and then using this token
92 /// to launch Minecraft. The method supports both Bedrock Edition and Java Edition of Minecraft.
93 ///
94 /// # Arguments
95 ///
96 /// * `bedrock_relm` - A boolean indicating whether to launch the Bedrock Edition of Minecraft.
97 /// * `client_secret` - The client secret obtained from the Minecraft authentication service.
98 ///
99 /// # Returns
100 ///
101 /// * `Result<CustomAuthData, Box<dyn std::error::Error>>` - A result containing the authentication data or an error if the process fails.
102 pub async fn launch(
103 &self,
104 bedrock_relm: bool,
105 client_secret: &str,
106 ) -> Result<CustomAuthData, Box<dyn std::error::Error>> {
107 let http_server = oauth::server(self.port)?.await?;
108 let token = oauth::token(
109 http_server
110 .code
111 .expect("\x1b[31mXbox Expected code.\x1b[0m")
112 .as_str(),
113 &self.client_id,
114 self.port,
115 client_secret,
116 )
117 .await?;
118 let xbox = xbox::xbl(&token.access_token).await?;
119 let xts = xbox::xsts_token(&xbox.token, bedrock_relm).await?;
120
121 if bedrock_relm {
122 Ok(CustomAuthData {
123 access_token: "null".to_string(),
124 uuid: "null".to_string(),
125 expires_in: 0,
126 xts_token: Some(xts.token),
127 })
128 } else {
129 Ok(mojang::token(&xbox.display_claims.xui[0].uhs, &xts.token).await?)
130 }
131 }
132
133 /// Refreshes the OAuth authentication process.
134 ///
135 /// This method is used to refresh the access token using the refresh token.
136 ///
137 /// # Arguments
138 ///
139 /// * `refresh_token` - The refresh token obtained from the Minecraft authentication service.
140 /// * `client_id` - The client ID obtained from the Minecraft authentication service.
141 /// * `port` - An optional port number for the local server. Defaults to 8000 if not provided.
142 /// * `client_secret` - The client secret obtained from the Minecraft authentication service.
143 ///
144 /// # Returns
145 ///
146 /// * `Result<CustomAuthData, Box<dyn std::error::Error>>` - A result containing the refreshed authentication data or an error if the process fails.
147 #[cfg(feature = "refresh")]
148 pub async fn refresh(
149 &self,
150 refresh_token: &str,
151 client_id: &str,
152 port: Option<u16>,
153 client_secret: &str,
154 ) -> Result<CustomAuthData, Box<dyn std::error::Error>> {
155 let port = port.unwrap_or(8000);
156 let token = oauth::token(refresh_token, client_id, port, client_secret).await?;
157 Ok(token)
158 }
159}
160
161/// Device Code Authentication
162///
163/// This struct represents the device code authentication process for Minecraft, specifically designed for use with custom Azure applications.
164/// It is used to authenticate a device and obtain a token that can be used to launch Minecraft.
165#[cfg(feature = "custom-auth")]
166pub struct DeviceCode {
167 url: String,
168 message: String,
169 expires_in: u32,
170 user_code: String,
171 device_code: String,
172 client_id: String,
173}
174
175#[cfg(feature = "custom-auth")]
176impl DeviceCode {
177 /// Initializes a new `DeviceCode` instance.
178 ///
179 /// This method starts the device code authentication process by making an asynchronous request
180 /// to the authentication server. It returns a future that resolves to a `Result` containing the
181 /// `DeviceCode` instance on success or an error if the request fails.
182 ///
183 /// # Arguments
184 ///
185 /// * `client_id` - The client ID obtained from the Minecraft authentication service.
186 ///
187 /// # Returns
188 ///
189 /// * `impl async_trait_alias::AsyncSendSync<Result<Self, reqwest::Error>>` - A future that resolves to a `Result` containing the `DeviceCode` instance or an error.
190 pub fn new(
191 client_id: &str,
192 ) -> impl async_trait_alias::AsyncSendSync<Result<Self, reqwest::Error>> {
193 println!("{}", EXPERIMENTAL_MESSAGE);
194 let client_id_str = client_id.to_string();
195 async move {
196 let response_data = code::device_authentication_code(&client_id_str).await?;
197
198 Ok(Self {
199 url: response_data.verification_uri,
200 message: response_data.message,
201 expires_in: response_data.expires_in,
202 user_code: response_data.user_code,
203 device_code: response_data.device_code,
204 client_id: client_id_str,
205 })
206 }
207 }
208
209 /// Provides pre-launch information.
210 ///
211 /// This method returns a tuple containing the verification URL, the message to display to the user,
212 /// the expiration time of the device code, and the user code. This information is useful for guiding
213 /// the user through the device code authentication process.
214 ///
215 /// # Returns
216 ///
217 /// * `(&str, &str, u32, &str)` - A tuple containing the verification URL, the message, the expiration time, and the user code.
218 pub fn preinfo(&self) -> (&str, &str, u32, &str) {
219 (&self.url, &self.message, self.expires_in, &self.user_code)
220 }
221
222 /// Launches Minecraft using the device code authentication process.
223 ///
224 /// This method completes the device code authentication process by authenticating the device
225 /// and obtaining a token. It then uses this token to launch Minecraft. The method supports both
226 /// Bedrock Edition and Java Edition of Minecraft.
227 ///
228 /// # Arguments
229 ///
230 /// * `bedrock_relm` - A boolean indicating whether to launch the Bedrock Edition of Minecraft.
231 ///
232 /// # Returns
233 ///
234 /// * `Result<CustomAuthData, Box<dyn std::error::Error>>` - A result containing the authentication data or an error if the process fails.
235 pub async fn launch(
236 &self,
237 bedrock_relm: bool,
238 ) -> Result<CustomAuthData, Box<dyn std::error::Error>> {
239 let token = code::authenticate_device(&self.device_code, &self.client_id).await?;
240 let xbox = xbox::xbl(&token.token).await?;
241 let xts = xbox::xsts_token(&xbox.token, bedrock_relm).await?;
242 if bedrock_relm {
243 Ok(CustomAuthData {
244 access_token: "null".to_string(),
245 uuid: "null".to_string(),
246 expires_in: 0,
247 xts_token: Some(xts.token),
248 })
249 } else {
250 Ok(mojang::token(&xbox.display_claims.xui[0].uhs, &xts.token).await?)
251 }
252 }
253
254 /// Refreshes the device code authentication process.
255 ///
256 /// This method is marked as experimental and currently does not perform any actions.
257 ///
258 /// # Note
259 ///
260 /// This method is intended for future use when implementing refresh functionality for the device code authentication process.
261 pub async fn refresh(&self) {
262 println!("{}", EXPERIMENTAL_MESSAGE);
263 }
264}
265
266/// `Launch` struct represents the configuration for launching a Minecraft client.
267///
268/// This struct holds the arguments required to launch the Minecraft client. The arguments are passed as a single string,
269/// which can include various options supported by the Minecraft client.
270#[cfg(feature = "custom-launch")]
271pub struct Launch {
272 args: String,
273 java_exe: String,
274 jre: Option<PathBuf>,
275}
276
277#[cfg(feature = "custom-launch")]
278impl Launch {
279 /// Launches a new instance of the launch function.
280 pub fn new(
281 args: Vec<String>,
282 java_exe: String,
283 jre: Option<PathBuf>,
284 offline: Option<bool>,
285 ) -> Result<Self, errors::LaunchError> {
286 let args_final = args.join(" ");
287 print!("{}", args_final);
288
289 if offline == Some(true)
290 && !args_final.contains("--uuid")
291 && !args_final.contains("--token")
292 {
293 return Err(errors::LaunchError::Requirements(
294 "Either --uuid or --token is missing in the arguments.".to_string(),
295 ));
296 }
297
298 Ok(Self {
299 args: args_final,
300 java_exe,
301 jre,
302 })
303 }
304
305 /// Returns the launch configuration information.
306 ///
307 /// This method provides access to the arguments, Java executable path, and the optional Java Runtime Environment (JRE) path
308 /// that were used to initialize the `Launch` struct.
309 ///
310 /// # Returns
311 ///
312 /// * `(&str, &str, &Option<PathBuf>)` - A tuple containing the final arguments string, the path to the Java executable,
313 /// and an optional path to the Java Runtime Environment.
314 pub fn info(&self) -> (&str, &str, &Option<PathBuf>) {
315 (&self.args, &self.java_exe, &self.jre)
316 }
317
318 /// Launches the Java Runtime Environment (JRE) with the specified arguments.
319 ///
320 /// This method is responsible for starting the Java Runtime Environment
321 /// with the arguments provided during the initialization of the `Launch` struct.
322 /// It is intended to be used for launching Minecraft or other Java applications.
323 ///
324 /// Required Args:
325 /// - UUID: LauncherUUID
326 /// - Token: BearerToken
327 ///
328 /// # Examples
329 ///
330 /// ```rust
331 /// use minecraft_essentials::Launch;
332 /// use std::path::Path;
333 ///
334 /// let jre_path = Path::new("/path/to/jre").to_path_buf();
335 ///
336 /// let launcher = Launch::new(vec!["-Xmx1024M --uuid --token".to_string()], "/path/to/java".to_string(), Some(jre_path), None).expect("Expected Launch");
337 /// launcher.launch_jre();
338 /// ```
339 pub fn launch_jre(&self) -> std::io::Result<()> {
340 let command_exe = format!("{} {:?} {}", self.java_exe, self.jre, self.args);
341 let mut command = Command::new(command_exe)
342 .stdout(Stdio::piped())
343 .stderr(Stdio::piped())
344 .spawn()?;
345
346 // Optionally, you can handle stdout and stderr in real-time
347 if let Some(ref mut stdout) = command.stdout {
348 let reader = BufReader::new(stdout);
349 for line in reader.lines() {
350 println!("{}", line?);
351 }
352 }
353
354 if let Some(ref mut stderr) = command.stderr {
355 let reader = BufReader::new(stderr);
356 for line in reader.lines() {
357 eprintln!("{}", line?);
358 }
359 }
360
361 // Wait for the command to finish
362 command.wait()?;
363
364 Ok(())
365 }
366}