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}