#![doc = include_str!("../README.md")]
#![forbid(unsafe_code, missing_docs)]
#![warn(clippy::pedantic)]
pub(crate) mod async_trait_alias;
pub mod errors;
#[cfg(test)]
mod tests;
#[cfg(feature = "custom-auth")]
mod custom;
#[cfg(feature = "custom-auth")]
pub use custom::mojang::AuthInfo as CustomAuthData;
#[cfg(feature = "custom-auth")]
use custom::{code, mojang, oauth, xbox};
#[cfg(feature = "custom-launch")]
use std::{
io::{BufRead, BufReader},
path::PathBuf,
process::{Command, Stdio},
};
pub(crate) const SCOPE: &str = "XboxLive.signin%20XboxLive.offline_access";
pub(crate) const EXPERIMENTAL_MESSAGE: &str =
"\x1b[33mNOTICE: You are using an experimental feature.\x1b[0m";
#[cfg(feature = "custom-auth")]
pub struct Oauth {
url: String,
port: u16,
client_id: String,
}
#[cfg(feature = "custom-auth")]
impl Oauth {
pub fn new(client_id: &str, port: Option<u16>) -> Self {
let port = port.unwrap_or(8000);
let params = format!("client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345", client_id, port, SCOPE);
let url = format!(
"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?{}",
params
);
Self {
url,
port,
client_id: client_id.to_string(),
}
}
pub fn url(&self) -> &str {
&self.url
}
pub async fn launch(
&self,
bedrock_relm: bool,
client_secret: &str,
) -> Result<CustomAuthData, Box<dyn std::error::Error>> {
let http_server = oauth::server(self.port)?.await?;
let token = oauth::token(
http_server
.code
.expect("\x1b[31mXbox Expected code.\x1b[0m")
.as_str(),
&self.client_id,
self.port,
client_secret,
)
.await?;
let xbox = xbox::xbl(&token.access_token).await?;
let xts = xbox::xsts_token(&xbox.token, bedrock_relm).await?;
if bedrock_relm {
Ok(CustomAuthData {
access_token: "null".to_string(),
uuid: "null".to_string(),
expires_in: 0,
xts_token: Some(xts.token),
})
} else {
Ok(mojang::token(&xbox.display_claims.xui[0].uhs, &xts.token).await?)
}
}
#[cfg(feature = "refresh")]
pub async fn refresh(
&self,
refresh_token: &str,
client_id: &str,
port: Option<u16>,
client_secret: &str,
) -> Result<CustomAuthData, Box<dyn std::error::Error>> {
let port = port.unwrap_or(8000);
let token = oauth::token(refresh_token, client_id, port, client_secret).await?;
Ok(token)
}
}
#[cfg(feature = "custom-auth")]
pub struct DeviceCode {
url: String,
message: String,
expires_in: u32,
user_code: String,
device_code: String,
client_id: String,
}
#[cfg(feature = "custom-auth")]
impl DeviceCode {
pub fn new(
client_id: &str,
) -> impl async_trait_alias::AsyncSendSync<Result<Self, reqwest::Error>> {
println!("{}", EXPERIMENTAL_MESSAGE);
let client_id_str = client_id.to_string();
async move {
let response_data = code::device_authentication_code(&client_id_str).await?;
Ok(Self {
url: response_data.verification_uri,
message: response_data.message,
expires_in: response_data.expires_in,
user_code: response_data.user_code,
device_code: response_data.device_code,
client_id: client_id_str,
})
}
}
pub fn preinfo(&self) -> (&str, &str, u32, &str) {
(&self.url, &self.message, self.expires_in, &self.user_code)
}
pub async fn launch(
&self,
bedrock_relm: bool,
) -> Result<CustomAuthData, Box<dyn std::error::Error>> {
let token = code::authenticate_device(&self.device_code, &self.client_id).await?;
let xbox = xbox::xbl(&token.token).await?;
let xts = xbox::xsts_token(&xbox.token, bedrock_relm).await?;
if bedrock_relm {
Ok(CustomAuthData {
access_token: "null".to_string(),
uuid: "null".to_string(),
expires_in: 0,
xts_token: Some(xts.token),
})
} else {
Ok(mojang::token(&xbox.display_claims.xui[0].uhs, &xts.token).await?)
}
}
pub async fn refresh(&self) {
println!("{}", EXPERIMENTAL_MESSAGE);
}
}
#[cfg(feature = "custom-launch")]
pub struct Launch {
args: String,
java_exe: String,
jre: Option<PathBuf>,
}
#[cfg(feature = "custom-launch")]
impl Launch {
pub fn new(
args: Vec<String>,
java_exe: String,
jre: Option<PathBuf>,
offline: Option<bool>,
) -> Result<Self, errors::LaunchError> {
let args_final = args.join(" ");
print!("{}", args_final);
if offline == Some(true)
&& !args_final.contains("--uuid")
&& !args_final.contains("--token")
{
return Err(errors::LaunchError::Requirements(
"Either --uuid or --token is missing in the arguments.".to_string(),
));
}
Ok(Self {
args: args_final,
java_exe,
jre,
})
}
pub fn info(&self) -> (&str, &str, &Option<PathBuf>) {
(&self.args, &self.java_exe, &self.jre)
}
pub fn launch_jre(&self) -> std::io::Result<()> {
let command_exe = format!("{} {:?} {}", self.java_exe, self.jre, self.args);
let mut command = Command::new(command_exe)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
if let Some(ref mut stdout) = command.stdout {
let reader = BufReader::new(stdout);
for line in reader.lines() {
println!("{}", line?);
}
}
if let Some(ref mut stderr) = command.stderr {
let reader = BufReader::new(stderr);
for line in reader.lines() {
eprintln!("{}", line?);
}
}
command.wait()?;
Ok(())
}
}