diff --git a/src/auth.rs b/src/auth.rs index fadd0fe..6db5af7 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,315 +1,458 @@ -use std::thread::AccessError; -use reqwest::blocking::*; +use crate::auth::GameAuthenticationError::{IllegalParameter, IllegalRequest, IllegalResponse}; use log::*; -use serde::Deserialize; +use reqwest::{blocking::*, header::*, StatusCode}; +use serde::*; +use std::collections::HashMap; +use std::{error::Error, fmt::Display}; -const OAUTH_TOKEN : (&str, &str)= ( +const OAUTH_TOKEN: (&str, &str) = ( "3f69e56c7649492c8cc29f1af08a8a12", // Client "b51ee9cb12234f50a69efa67ef53812e", // Secret ); +/// Describes possible errors that may come from the functions and methods in this module. #[derive(Debug)] -pub struct DeviceCredentials { - access_token : String, - pub account_id : String, - device_id : String, +pub enum GameAuthenticationError { + IllegalParameter(String), + IllegalRequest(String), + IllegalResponse(String), + RequestFailed, +} + +impl Display for GameAuthenticationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl Error for GameAuthenticationError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + todo!(); + match self { + IllegalParameter(reason) => Some(&IllegalParameter(reason.to_owned())), + IllegalResponse(reason) => Some(&IllegalResponse(reason.to_owned())), + GameAuthenticationError::IllegalRequest(reason) => { + Some(&IllegalRequest(reason.to_owned())) + } + _ => None, + } + } +} + +pub enum ClientType { + ANDROID, + GENERIC, + PC, +} + +#[derive(Debug)] +pub struct AccessToken(pub String); + +impl AccessToken { + pub fn new() -> AccessToken { + AccessToken(String::new()) + } + + pub fn from(access_token: AccessToken) -> AccessToken { + AccessToken(access_token.0) + } + pub fn from_authcode( + http_client: &reqwest::blocking::Client, + authcode: &str, + persistent_credentials: Option<&mut PersistentCredentials>, + client_type: ClientType, + ) -> Result> { + if authcode.is_empty() { + error!("Authentication code for access token cannot be empty"); + Err(Box::new(GameAuthenticationError::IllegalParameter( + "Authentication code for access token cannot be empty".to_string(), + )))?; + } + + #[derive(Debug, Deserialize)] + struct ResponseContent { + access_token: String, + account_id: String, + } + + const URL: &str = + "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token"; + + /* Request Headers */ + let mut request_headers = HeaderMap::new(); + request_headers.insert( + "Content-Type", + HeaderValue::from_static("application/x-www-form-urlencoded"), + ); + request_headers.insert( + "Authorization", + HeaderValue::from_static(match client_type { + ClientType::ANDROID => "Basic M2Y2OWU1NmM3NjQ5NDkyYzhjYzI5ZjFhZjA4YThhMTI6YjUxZWU5Y2IxMjIzNGY1MGE2OWVmYTY3ZWY1MzgxMmU=", + ClientType::PC => "BASIC MzRhMDJjZjhmNDQxNGUyOWIxNTkyMTg3NmRhMzZmOWE6ZGFhZmJjY2M3Mzc3NDUwMzlkZmZlNTNkOTRmYzc2Y2Y=", + ClientType::GENERIC => "Basic MzRhMDJjZjhmNDQxNGUyOWIxNTkyMTg3NmRhMzZmOWE6ZGFhZmJjY2M3Mzc3NDUwMzlkZmZlNTNkOTRmYzc2Y2Y=", + }), + ); + + /* Request Body */ + // "grant_type=authorization_code&code=" + let request_body: reqwest::blocking::Body = reqwest::blocking::Body::from( + "grant_type=authorization_code&code=".trim().to_owned() + authcode.trim(), + ); + + /* Response Fetching */ + let response = http_client + .post(URL) + .headers(request_headers) + .body(request_body) + .send()?; + + info!("Got HTTP response: {:?}", response); + info!("Got HTTP response headers: {:?}", response.headers()); + match response.status() { + StatusCode::OK => { + let response_content: ResponseContent = response.json::()?; + + // WARN: Side effect here!!! + if persistent_credentials.is_some() { + persistent_credentials.unwrap().account_id = response_content.account_id; + } + + return Ok(AccessToken(response_content.access_token)); + } + + StatusCode::INTERNAL_SERVER_ERROR => { + error!("Internal Server Error when fetching Access Token with Authentication Code"); + Err(Box::new(GameAuthenticationError::RequestFailed))? + } + StatusCode::UNAUTHORIZED => { + error!("Unauthorized action when fetching Access Token with Authentication Code"); + Err(Box::new(GameAuthenticationError::IllegalRequest( + "Unauthorized action when fetching Access Token with Authentication Code" + .to_string(), + )))? + } + _ => { + error!("Unsupported response when fetching Access Token with Authentication Code"); + error!("Got response {:?}", response.text().unwrap_or_default()); + Err(Box::new(GameAuthenticationError::IllegalResponse( + "Unsupported response when fetching Access Token with Authentication Code" + .to_string(), + )))? + } + } + } + pub fn from_persistent_credentials( + http_client: &reqwest::blocking::Client, + credentials: &PersistentCredentials, + client_type: ClientType, + ) -> Result> { + // Checking credentials' content + { + if credentials.account_id.is_empty() { + Err(Box::new(IllegalParameter( + "credentials.account_id cannot be empty".to_string(), + )))?; + } + if credentials.device_id.is_empty() { + Err(Box::new(IllegalParameter( + "credentials.device_id cannot be empty".to_string(), + )))?; + } + if credentials.secret.is_empty() { + Err(Box::new(IllegalParameter( + "credentials.secret cannot be empty".to_string(), + )))? + } + } + + #[derive(Debug, Deserialize)] + struct ResponseContent { + access_token: String, + } + + const URL: &str = + "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token"; + + let mut request_headers: HeaderMap = HeaderMap::new(); + request_headers.insert( + "Content-Type", + HeaderValue::from_static("application/x-www-form-urlencoded"), + ); + request_headers.insert( + "Authorization", + HeaderValue::from_static(match client_type { + ClientType::ANDROID => "Basic M2Y2OWU1NmM3NjQ5NDkyYzhjYzI5ZjFhZjA4YThhMTI6YjUxZWU5Y2IxMjIzNGY1MGE2OWVmYTY3ZWY1MzgxMmU=", + ClientType::PC => "BASIC MzRhMDJjZjhmNDQxNGUyOWIxNTkyMTg3NmRhMzZmOWE6ZGFhZmJjY2M3Mzc3NDUwMzlkZmZlNTNkOTRmYzc2Y2Y=", + ClientType::GENERIC => "Basic MzRhMDJjZjhmNDQxNGUyOWIxNTkyMTg3NmRhMzZmOWE6ZGFhZmJjY2M3Mzc3NDUwMzlkZmZlNTNkOTRmYzc2Y2Y=", + }) + ); + + let request_body: reqwest::blocking::Body = reqwest::blocking::Body::from( + "grant_type=device_auth".to_string() + + "&account_id=".trim() + + &credentials.account_id + + "&device_id=".trim() + + &credentials.device_id + + "&secret=".trim() + + &credentials.secret, + ); + + let response = http_client + .post(URL) + .headers(request_headers) + .body(request_body) + .send()?; + + match response.status() { + StatusCode::OK => { + let response_content: ResponseContent = response.json::()?; + return Ok(AccessToken(response_content.access_token)); + } + StatusCode::INTERNAL_SERVER_ERROR => { + error!( + "Internal Server Error when fetching Access Token with Persistent Credentials" + ); + Err(Box::new(GameAuthenticationError::RequestFailed))? + } + StatusCode::UNAUTHORIZED => { + error!( + "Unauthorized action when fetching Access Token with Persistent Credentials" + ); + Err(Box::new(GameAuthenticationError::IllegalRequest( + "Unauthorized action when fetching Access Token with Persistent Credentials" + .to_string(), + )))? + } + _ => { + error!( + "Unsupported response when fetching Access Token with Persistent Credentials" + ); + error!("Got response:\n {:?}", response.text().unwrap_or_default()); + Err(Box::new(GameAuthenticationError::IllegalResponse( + "Unsupported response when fetching Access Token with Persistent Credentials" + .to_string(), + )))? + } + } + } + pub fn from_exchange_code( + http_client: &Client, + exchange_code: &ExchangeCode, + ) -> Result> { + if exchange_code.0.is_empty() { + Err(Box::new(GameAuthenticationError::IllegalParameter( + "exchange_code cannot be empty".to_owned(), + )))? + } + + #[derive(Debug, Deserialize)] + struct ResponseContent { + access_token: String, + } + + const URL: &str = + "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token"; + + let mut request_headers: HeaderMap = HeaderMap::new(); + request_headers.insert( + "Content-Type", + HeaderValue::from_static("application/x-www-form-urlencoded"), + ); + request_headers.insert( + "Authorization", + HeaderValue::from_static("Basic MzRhMDJjZjhmNDQxNGUyOWIxNTkyMTg3NmRhMzZmOWE6ZGFhZmJjY2M3Mzc3NDUwMzlkZmZlNTNkOTRmYzc2Y2Y="), + ); + + let request_body: reqwest::blocking::Body = reqwest::blocking::Body::from( + "grant_type=exchange_code".to_string() + "&exchange_code=" + &exchange_code.0, + ); + + let response = http_client + .post(URL) + .headers(request_headers) + .body(request_body) + .send()?; + + match response.status() { + StatusCode::OK => { + let response_content: ResponseContent = response.json::()?; + Ok(AccessToken(response_content.access_token)) + } + + StatusCode::INTERNAL_SERVER_ERROR => { + error!("Internal Server Error when fetching Access Token with Exchange Code"); + Err(Box::new(GameAuthenticationError::RequestFailed))? + } + StatusCode::UNAUTHORIZED => { + error!("Unauthorized action when fetching Access Token with Exchange Code"); + Err(Box::new(GameAuthenticationError::IllegalRequest( + "Unauthorized action when fetching Access Token with Exchange Code".to_string(), + )))? + } + _ => { + error!("Unsupported response when fetching Access Token with Exchange Code"); + error!("Got response {:?}", response.text().unwrap_or_default()); + Err(Box::new(GameAuthenticationError::IllegalResponse( + "Unsupported response when fetching Access Token with Exchange Code" + .to_string(), + )))? + } + } + } +} + +/// Contains the account info that can be kept on the device to login in the game +#[derive(Debug)] +pub struct PersistentCredentials { + pub account_id: String, + device_id: String, secret: String, } -impl DeviceCredentials { - pub fn new() -> DeviceCredentials { - DeviceCredentials{ - device_id: String::new(), +impl PersistentCredentials { + /// Creates a new empty instance of PersistentCredentials. + pub fn new() -> PersistentCredentials { + PersistentCredentials { account_id: String::new(), - access_token: String::new(), + device_id: String::new(), secret: String::new(), } } - pub fn get_access_token_and_account_id(&mut self, http_client: &Client, authcode: &str) { - if authcode.is_empty() { - error!("Authentication Code cannot be empty"); - return; - } - - #[derive(Debug, Deserialize)] - struct ResponseStruct { - access_token: String, - expires_in: u16, - expires_at: String, - token_type: String, - refresh_token: String, - refresh_expires: u16, - refresh_expires_at: String, - account_id: String, - client_id: String, - internal_client: bool, - client_service: String, - scope: Vec, - displayName: String, - app: String, - in_app_id: String, - product_id: String, - application_id: String, - acr: String, - auth_time: String - } - - let url = "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token"; - let mut req_body: String = String::from("grant_type=authorization_code&code=").trim().to_string(); - req_body.push_str(authcode.trim()); - - let response = Client::post(&http_client, url) - .body(req_body) - .header("Content-Type", "application/x-www-form-urlencoded") - .header("Authorization", - "Basic M2Y2OWU1NmM3NjQ5NDkyYzhjYzI5ZjFhZjA4YThhMTI6YjUxZWU5Y2IxMjIzNGY1MGE2OWVmYTY3ZWY1MzgxMmU=") - .send(); - - match response { - Ok(response_data) => { - match response_data.json::().ok() { - Some(response) => { - self.access_token = response.access_token; - self.account_id = response.account_id; - }, - None => { - error!("Failed to get access_token!"); - } - } - }, - _ => { error!("Failed to get access token!"); }, + /// Creates a new instance of PersistentCredentials with the given String parameters. + pub fn from(account_id: String, device_id: String, secret: String) -> PersistentCredentials { + PersistentCredentials { + account_id, + device_id, + secret, } } - pub fn get_device_auth_and_secret(&mut self, http_client: &Client) { - - if self.access_token.is_empty() || self.account_id.is_empty() { - error!("Device access token cannot be empty!"); - return; + /// Fetches the Persistent credentials + pub fn fetch( + http_client: &reqwest::blocking::Client, + authcode: &str, + ) -> Result> { + if authcode.is_empty() { + Err(GameAuthenticationError::IllegalParameter( + "Authorization code cannot be empty".to_string(), + ))? } - let mut url : String = String::from("https://account-public-service-prod.ol.epicgames.com/account/api/public/account/"); - url.push_str(self.account_id.as_str()); - url.push_str("/deviceAuth"); + // PersistentCredentials to be returned by this function. + let mut resulting_credentials = PersistentCredentials::new(); - /* - let it : String = String::new; - it.starts_with("one thing"); - // Just discovered String.starts_with() KEKW - */ + // Getting access token and account id + info!("Getting Access Token"); + let access_token: AccessToken = AccessToken::from_authcode( + http_client, + &authcode, + Some(&mut resulting_credentials), + ClientType::ANDROID, + )?; - #[derive(Debug, Deserialize)] - struct CreatedObject { - location: String, - ipAddress: String, - dateTime: String, - } + // Getting device_id and secret + { + let url = format!("https://account-public-service-prod.ol.epicgames.com/account/api/public/account/{}/deviceAuth", resulting_credentials.account_id); - #[derive(Debug, Deserialize)] - struct ResponseStruct { - deviceId: String, - accountId: String, - secret: String, - userAgent: String, - created: CreatedObject, - } + let mut headers = HeaderMap::new(); + headers.insert( + "Authorization", + format!("Bearer {}", access_token.0).parse().unwrap(), + ); - let mut bearer_header = String::from("Bearer "); - bearer_header.push_str(self.access_token.as_str()); + #[derive(Debug, Deserialize)] + struct ResponseContent { + deviceId: String, + accountId: String, + secret: String, + } - let response = Client::post(&http_client, url) - .header("Authorization", bearer_header.as_str()) - .send(); + let response = http_client.post(&url).headers(headers).send()?; - match response { - Ok(response_data) => { - match response_data.json::().ok() { - Some(response_json) => { - self.device_id = response_json.deviceId; - self.secret = response_json.secret; - } - None => { - error!("Failed to parse device_id!"); - }, + match response.status() { + StatusCode::OK => { + let response_content: ResponseContent = response.json::()?; + resulting_credentials.device_id = response_content.deviceId; + resulting_credentials.account_id = response_content.accountId; + resulting_credentials.secret = response_content.secret; + Ok(resulting_credentials) } - }, - _ => { error!("Failed to get device_id!"); }, + StatusCode::INTERNAL_SERVER_ERROR => { + error!("Internal Server Error when fetching device_id and secret with Access Token"); + Err(Box::new(GameAuthenticationError::RequestFailed))? + } + StatusCode::UNAUTHORIZED => { + error!( + "Unauthorized action when fetching device_id and secret with Access Token" + ); + Err(Box::new(GameAuthenticationError::IllegalRequest( + "Unauthorized action when fetching device_id and secret with Access Token" + .to_string(), + )))? + } + _ => { + error!( + "Unsupported response when fetching device_id and secret with Access Token" + ); + error!("Got response {:?}", response.text().unwrap_or_default()); + Err(Box::new(GameAuthenticationError::IllegalResponse( + "Unsupported response when fetching device_id and secret with Access Token" + .to_string(), + )))? + } + } } } } #[derive(Debug)] -pub struct TemporaryCredentials { - access_token : String, - pub exchange_code : String, -} - -impl TemporaryCredentials { - pub fn new() -> TemporaryCredentials { - TemporaryCredentials { - access_token : String::new(), - exchange_code : String::new(), - } +pub struct ExchangeCode(pub String); +impl ExchangeCode { + pub fn new() -> ExchangeCode { + ExchangeCode(String::new()) } - pub fn get_access_token_from_device_auth(&mut self, http_client: &Client, device_credentials: &DeviceCredentials) /*Android*/ { + pub fn from_persistent_credentials( + http_client: &reqwest::blocking::Client, + access_token: &AccessToken, + client_type: ClientType, + ) -> Result { #[derive(Debug, Deserialize)] struct ResponseStruct { - access_token: String, - expires_in: u16, - expires_at: String, - token_type: String, - refresh_token: String, - refresh_expires: u16, - refresh_expires_at: String, - account_id: String, - client_id: String, - internal_client: bool, - client_service: String, - displayName: String, - app: String, - in_app_id: String, - product_id: String, - application_id: String, - acr: String, - auth_time: String - } - - let mut request_body = String::new(); - { - // Grant type - request_body.push_str("grant_type=device_auth" - .trim()); - - // AccountID - request_body.push_str("&account_id="); - request_body.push_str(device_credentials.account_id - .as_str() - .trim()); - - // DeviceID - request_body.push_str("&device_id="); - request_body.push_str(device_credentials.device_id - .as_str() - .trim()); - - // Secret - request_body.push_str("&secret="); - request_body.push_str(device_credentials.secret - .as_str() - .trim()); - - request_body = request_body.trim().to_string(); - } - - let url : &str = "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token"; - - let response = Client::post(&http_client, url) - .body(request_body) - .header("Content-Type", "application/x-www-form-urlencoded") - .header("Authorization", - "Basic M2Y2OWU1NmM3NjQ5NDkyYzhjYzI5ZjFhZjA4YThhMTI6YjUxZWU5Y2IxMjIzNGY1MGE2OWVmYTY3ZWY1MzgxMmU=") - .send(); - - match response { - Ok(response_data) => { - match response_data.json::().ok() { - Some(response) => { - self.access_token = response.access_token; - }, - None => { - error!("Failed to parse access_token!"); - } - } - }, - _ => { error!("Failed to get access token!"); }, - } - } - pub fn get_access_token_from_exchange_code(&mut self, http_client: &Client, temporary_credentials: &TemporaryCredentials) /*Generic*/ { - #[derive(Debug, Deserialize)] - struct ResponseStruct { - access_token: String, - /*expires_in: u16, - expires_at: String, - token_type: String, - refresh_token: String, - refresh_expires: u16, - refresh_expires_at: String, - account_id: String, - client_id: String, - internal_client: bool, - client_service: String, - displayName: String, - app: String, - in_app_id: String, - acr: String, - auth_time: String*/ - } - - let mut request_body = String::new(); - request_body.push_str("grant_type=exchange_code&exchange_code=".trim()); - request_body.push_str(temporary_credentials.exchange_code.as_str().trim()); - - let url : &str = "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token"; - - let response = http_client.post(url) - .body(request_body) - .header("Content-Type", "application/x-www-form-urlencoded") - .header("Authorization", - "Basic MzRhMDJjZjhmNDQxNGUyOWIxNTkyMTg3NmRhMzZmOWE6ZGFhZmJjY2M3Mzc3NDUwMzlkZmZlNTNkOTRmYzc2Y2Y=") - .send(); - - match response { - Ok(response_data) => { - match response_data.json::().ok() { - Some(response) => { - self.access_token = response.access_token; - }, - None => { - error!("Failed to parse access_token!"); - } - } - }, - _ => { error!("Failed to get access token!"); }, - } - - - } - pub fn get_exchange_code(&mut self, http_client: &Client, client_id: &str) { - - #[derive(Debug,Deserialize)] - struct ResponseStruct { - expiresInSeconds: u16, code: String, - creatingClientId: String, } - let mut bearer_header = String::from("Bearer "); - bearer_header.push_str(self.access_token.as_str()); - - let mut query_content : String = String::from("?consumingClientId=").trim().to_string(); - query_content.push_str(client_id.trim()); - - let url: &str = "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/exchange"; - let response = Client::get(&http_client, url) - //.query(query_content.as_str()) - .query(&[("consumingClientId",client_id.trim())]) - .header("Authorization", bearer_header.as_str()) - .send(); - - match response { - Ok(response_data) => { - match response_data.json::().ok() { - Some(response_json) => { - self.exchange_code = response_json.code; - } - None => { - error!("Failed to parse Exchange code!"); - }, - } + let mut bearer = HeaderMap::new(); + bearer.insert( + "Authorization", + HeaderValue::from_str(&format!("Bearer {}", access_token.0)).unwrap(), + ); + let query_content = [( + "consumingClientId", + match client_type { + ClientType::ANDROID => "34a02cf8f4414e29b15921876da36f9a", + ClientType::PC | ClientType::GENERIC => "ec684b8c687f479fadea3cb2ad83f5c6", }, - _ => { error!("Failed to get Exchange code!"); }, + )]; + let url = "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/exchange"; + + let response = http_client + .get(url) + .query(&query_content) + .headers(bearer) + .send(); + + match response { + Ok(response_data) => match response_data.json::() { + Ok(response_json) => { + return Ok(ExchangeCode(response_json.code)); + } + _ => Err(GameAuthenticationError::IllegalResponse( + "Could not parse exchange code".to_string(), + ))?, + }, + _ => Err(GameAuthenticationError::RequestFailed)?, } } } diff --git a/src/main.rs b/src/main.rs index 40260e1..69030fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,10 @@ mod auth; -use std::process::exit; -use log::*; use colog; +use log::*; use std::env::args; use std::io::Write; -use std::process::Command; +use std::process::{exit, Command}; use crate::auth::*; @@ -13,7 +12,7 @@ fn main() { /* Initial initializations */ { colog::init(); - // TODO: Set log level to display nothing + // TODO: Set log level to display nothing by default } /* Command line arg fetching and handling */ @@ -22,8 +21,7 @@ fn main() { /* Help page */ if command_line_arguments.iter().any(|a| a == "--help") { // TODO: Complete help message - let help_page : &str = -" + let help_page: &str = " === godo-launcher help page===\n `--verbose`, `-v`, `-vv`\t| Enables debug logging. `--debug`, `-d`\t\t\t| Enables verbose logging. @@ -31,11 +29,14 @@ fn main() { "; println!("{help_page}"); - std::process::exit(0); + exit(0); } /* LogLevel modifiers */ - if command_line_arguments.iter().any(|a| a == "--verbose" || a == "-v" || a == "-vv") { + if command_line_arguments + .iter() + .any(|a| a == "--verbose" || a == "-v" || a == "-vv") + { // TODO: Change Log Level to info and warn } @@ -43,56 +44,87 @@ fn main() { warn!("Debug info enabled!"); // TODO: Change Log Level to debug } - } /* Authentication Process */ + //1. get access token 1 from authcode + //2. get persistent credentials from access token 1 + //3. get access token 2 from persistent credentials + //4. get android exchange code from access token and persistent credentials + let http_client_builder = reqwest::blocking::ClientBuilder::new() .https_only(true) - .user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),)); + .user_agent(concat!( + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION"), + )); - let http_client = http_client_builder.build() + let http_client = http_client_builder + .build() .unwrap_or(reqwest::blocking::Client::new()); // Authentication code from user input - print!("Insert AuthCode > "); + info!("Insert Authentication Code: "); std::io::stdout().flush().unwrap(); - + // Getting temporal info to create persistent login info let mut auth_code: String = "".to_string(); let _ = std::io::stdin().read_line(&mut auth_code).unwrap(); - - let mut persistent_credentials: DeviceCredentials = DeviceCredentials::new(); - persistent_credentials.get_access_token_and_account_id(&http_client, &auth_code); - persistent_credentials.get_device_auth_and_secret(&http_client); - dbg!(&persistent_credentials); - /* Android temporary credentials */ - let mut android_credentials: TemporaryCredentials = TemporaryCredentials::new(); - android_credentials.get_access_token_from_device_auth(&http_client, &persistent_credentials); - android_credentials.get_exchange_code(&http_client, "34a02cf8f4414e29b15921876da36f9a"); - dbg!(&android_credentials); + info!("Generating persistent credentials"); + let persistent_credentials = PersistentCredentials::fetch(&http_client, &auth_code).unwrap(); + info!("New persistent credentials: {:#?}", &persistent_credentials); - /* Generic temporary credentials */ - let mut generic_credentials : TemporaryCredentials = TemporaryCredentials::new(); - generic_credentials.get_access_token_from_exchange_code(&http_client, &android_credentials); - generic_credentials.get_exchange_code(&http_client, "ec684b8c687f479fadea3cb2ad83f5c6"); - dbg!(&generic_credentials); + // Generating Android access token and exchange code from persistent_credentials + + info!("Generating Android exchange code from persistent credentials"); + let android_token = AccessToken::from_persistent_credentials( + &http_client, + &persistent_credentials, + ClientType::ANDROID, + ) + .unwrap(); + info!("Android Access Token: {:#?}", &android_token); + + let android_exchange_code = ExchangeCode::from_persistent_credentials( + &http_client, + &android_token, + ClientType::ANDROID, + ) + .unwrap(); + info!("Android Exchange Code: {:#?}", &android_exchange_code); + + // Generating generic access_token and exchange code from android exchange code + + info!("Generating generic access token"); + let generic_access_token = + AccessToken::from_exchange_code(&http_client, &android_exchange_code).unwrap(); + info!("Generic Access Token: {:#?}", &generic_access_token); + + info!("Generating generic exchange code"); + let generic_exchange_code = ExchangeCode::from_persistent_credentials( + &http_client, + &generic_access_token, + ClientType::GENERIC, + ) + .unwrap(); + info!("Generated exchange code: {:#?}", &generic_exchange_code); /* Game Launching*/ - + info!("Starting game..."); let mut auth_password_argument = String::from("-AUTH_PASSWORD="); - auth_password_argument.push_str(generic_credentials.exchange_code.as_str()); + auth_password_argument.push_str(generic_exchange_code.0.as_str()); let mut uid_argument = String::from("-epicuserid="); uid_argument.push_str(persistent_credentials.account_id.as_str()); let command = Command::new("cmd") - .arg("/C") // '/C' executes the command and terminates the command shell + .arg("/C") // '/C' executes the command and terminates the command shell .arg("start") .arg("/d") - .arg("D:\\Games\\Epic Games\\Fortnite\\FortniteGame\\Binaries\\Win64") // Path to the directory + .arg("D:\\Games\\Epic Games\\Fortnite\\FortniteGame\\Binaries\\Win64") // Path to the directory .arg("FortniteLauncher.exe") // The executable .arg("-AUTH_LOGIN=unused") .arg(&auth_password_argument) @@ -104,32 +136,15 @@ fn main() { .arg(&uid_argument) .arg("-epicsandboxid=fn") .spawn(); - - /* - "D:\Games\Epic Games\Fortnite\FortniteGame\Binaries\Win64/FortniteClient-Win64-Shipping_EAC_EOS.exe" - -obfuscationid=N8Kw52kUZsQq50886Eit-gzJOBar1g - -AUTH_LOGIN=unused -AUTH_PASSWORD=91ec3f72c2d94c8598082d58fc007a02 - -AUTH_TYPE=exchangecode - -epicapp=Fortnite - -epicenv=Prod - -EpicPortal - -epicusername=Generic_Boi69 - -epicuserid=bff1ee7d635140ed945f69a0595526b2 - -epiclocale=en - -epicsandboxid=fn - -named_pipe=bff1ee7d635140ed945f69a0595526b2\Fortnite - -fromfl=eaceos - -caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmZmMWVlN2Q2MzUxNDBlZDk0NWY2OWEwNTk1NTI2YjIiLCJnZW5lcmF0ZWQiOjE3MzQ1NDMwNDYsImNhbGRlcmFHdWlkIjoiOTUyZmU2OTktZjJjMy00YjZlLTk2NzctOWRlMDMyOTcyZjkxIiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXRFT1MiLCJub3RlcyI6IiIsInByZSI6dHJ1ZSwicGFlIjpmYWxzZSwiZmFsbGJhY2siOmZhbHNlfQ.SaiRyK-FFbzCI3TVM8RRFS0UyCu5VUsTlIMeNKPZHYcKUORdE7fZJlo0DC4zoZsPfmLNEzZxCLb_sJVPiy-m7A - */ - + match command { Ok(mut child) => { // Optionally, you can wait for the process to complete let status = child.wait().expect("Failed to wait on child"); - println!("Command executed with status: {}", status); + info!("Command executed with status: {}", status); } Err(e) => { - eprintln!("Error executing command: {}", e); + error!("Error executing command: {}", e); exit(1); } }