Remade auth.rs and adapted main.rs with it.

This commit is contained in:
Marta Borgia Leiva 2024-12-24 18:03:41 +01:00
parent 6bd7d2d1db
commit a98d93ffd1
2 changed files with 482 additions and 324 deletions

View file

@ -1,315 +1,458 @@
use std::thread::AccessError; use crate::auth::GameAuthenticationError::{IllegalParameter, IllegalRequest, IllegalResponse};
use reqwest::blocking::*;
use log::*; 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 "3f69e56c7649492c8cc29f1af08a8a12", // Client
"b51ee9cb12234f50a69efa67ef53812e", // Secret "b51ee9cb12234f50a69efa67ef53812e", // Secret
); );
/// Describes possible errors that may come from the functions and methods in this module.
#[derive(Debug)] #[derive(Debug)]
pub struct DeviceCredentials { pub enum GameAuthenticationError {
access_token : String, IllegalParameter(String),
pub account_id : String, IllegalRequest(String),
device_id : 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<AccessToken, Box<dyn Error>> {
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=<authcode>"
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::<ResponseContent>()?;
// 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<AccessToken, Box<dyn Error>> {
// 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::<ResponseContent>()?;
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<AccessToken, Box<dyn Error>> {
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::<ResponseContent>()?;
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, secret: String,
} }
impl DeviceCredentials { impl PersistentCredentials {
pub fn new() -> DeviceCredentials { /// Creates a new empty instance of PersistentCredentials.
DeviceCredentials{ pub fn new() -> PersistentCredentials {
device_id: String::new(), PersistentCredentials {
account_id: String::new(), account_id: String::new(),
access_token: String::new(), device_id: String::new(),
secret: String::new(), secret: String::new(),
} }
} }
pub fn get_access_token_and_account_id(&mut self, http_client: &Client, authcode: &str) {
if authcode.is_empty() { /// Creates a new instance of PersistentCredentials with the given String parameters.
error!("Authentication Code cannot be empty"); pub fn from(account_id: String, device_id: String, secret: String) -> PersistentCredentials {
return; PersistentCredentials {
} account_id,
device_id,
#[derive(Debug, Deserialize)] secret,
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<String>,
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::<ResponseStruct>().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!"); },
} }
} }
pub fn get_device_auth_and_secret(&mut self, http_client: &Client) { /// Fetches the Persistent credentials
pub fn fetch(
if self.access_token.is_empty() || self.account_id.is_empty() { http_client: &reqwest::blocking::Client,
error!("Device access token cannot be empty!"); authcode: &str,
return; ) -> Result<PersistentCredentials, Box<dyn Error>> {
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/"); // PersistentCredentials to be returned by this function.
url.push_str(self.account_id.as_str()); let mut resulting_credentials = PersistentCredentials::new();
url.push_str("/deviceAuth");
/* // Getting access token and account id
let it : String = String::new; info!("Getting Access Token");
it.starts_with("one thing"); let access_token: AccessToken = AccessToken::from_authcode(
// Just discovered String.starts_with() KEKW http_client,
*/ &authcode,
Some(&mut resulting_credentials),
ClientType::ANDROID,
)?;
#[derive(Debug, Deserialize)] // Getting device_id and secret
struct CreatedObject { {
location: String, let url = format!("https://account-public-service-prod.ol.epicgames.com/account/api/public/account/{}/deviceAuth", resulting_credentials.account_id);
ipAddress: String,
dateTime: String,
}
#[derive(Debug, Deserialize)] let mut headers = HeaderMap::new();
struct ResponseStruct { headers.insert(
deviceId: String, "Authorization",
accountId: String, format!("Bearer {}", access_token.0).parse().unwrap(),
secret: String, );
userAgent: String,
created: CreatedObject,
}
let mut bearer_header = String::from("Bearer "); #[derive(Debug, Deserialize)]
bearer_header.push_str(self.access_token.as_str()); struct ResponseContent {
deviceId: String,
accountId: String,
secret: String,
}
let response = Client::post(&http_client, url) let response = http_client.post(&url).headers(headers).send()?;
.header("Authorization", bearer_header.as_str())
.send();
match response { match response.status() {
Ok(response_data) => { StatusCode::OK => {
match response_data.json::<ResponseStruct>().ok() { let response_content: ResponseContent = response.json::<ResponseContent>()?;
Some(response_json) => { resulting_credentials.device_id = response_content.deviceId;
self.device_id = response_json.deviceId; resulting_credentials.account_id = response_content.accountId;
self.secret = response_json.secret; resulting_credentials.secret = response_content.secret;
} Ok(resulting_credentials)
None => {
error!("Failed to parse device_id!");
},
} }
}, StatusCode::INTERNAL_SERVER_ERROR => {
_ => { error!("Failed to get device_id!"); }, 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)] #[derive(Debug)]
pub struct TemporaryCredentials { pub struct ExchangeCode(pub String);
access_token : String, impl ExchangeCode {
pub exchange_code : String, pub fn new() -> ExchangeCode {
} ExchangeCode(String::new())
impl TemporaryCredentials {
pub fn new() -> TemporaryCredentials {
TemporaryCredentials {
access_token : String::new(),
exchange_code : 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<ExchangeCode, GameAuthenticationError> {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct ResponseStruct { 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::<ResponseStruct>().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::<ResponseStruct>().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, code: String,
creatingClientId: String,
} }
let mut bearer_header = String::from("Bearer "); let mut bearer = HeaderMap::new();
bearer_header.push_str(self.access_token.as_str()); 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",
},
)];
let url = "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/exchange";
let mut query_content : String = String::from("?consumingClientId=").trim().to_string(); let response = http_client
query_content.push_str(client_id.trim()); .get(url)
.query(&query_content)
let url: &str = "https://account-public-service-prod.ol.epicgames.com/account/api/oauth/exchange"; .headers(bearer)
let response = Client::get(&http_client, url)
//.query(query_content.as_str())
.query(&[("consumingClientId",client_id.trim())])
.header("Authorization", bearer_header.as_str())
.send(); .send();
match response { match response {
Ok(response_data) => { Ok(response_data) => match response_data.json::<ResponseStruct>() {
match response_data.json::<ResponseStruct>().ok() { Ok(response_json) => {
Some(response_json) => { return Ok(ExchangeCode(response_json.code));
self.exchange_code = response_json.code;
}
None => {
error!("Failed to parse Exchange code!");
},
} }
_ => Err(GameAuthenticationError::IllegalResponse(
"Could not parse exchange code".to_string(),
))?,
}, },
_ => { error!("Failed to get Exchange code!"); }, _ => Err(GameAuthenticationError::RequestFailed)?,
} }
} }
} }

View file

@ -1,11 +1,10 @@
mod auth; mod auth;
use std::process::exit;
use log::*;
use colog; use colog;
use log::*;
use std::env::args; use std::env::args;
use std::io::Write; use std::io::Write;
use std::process::Command; use std::process::{exit, Command};
use crate::auth::*; use crate::auth::*;
@ -13,7 +12,7 @@ fn main() {
/* Initial initializations */ /* Initial initializations */
{ {
colog::init(); colog::init();
// TODO: Set log level to display nothing // TODO: Set log level to display nothing by default
} }
/* Command line arg fetching and handling */ /* Command line arg fetching and handling */
@ -22,8 +21,7 @@ fn main() {
/* Help page */ /* Help page */
if command_line_arguments.iter().any(|a| a == "--help") { if command_line_arguments.iter().any(|a| a == "--help") {
// TODO: Complete help message // TODO: Complete help message
let help_page : &str = let help_page: &str = "
"
=== godo-launcher help page===\n === godo-launcher help page===\n
`--verbose`, `-v`, `-vv`\t| Enables debug logging. `--verbose`, `-v`, `-vv`\t| Enables debug logging.
`--debug`, `-d`\t\t\t| Enables verbose logging. `--debug`, `-d`\t\t\t| Enables verbose logging.
@ -31,11 +29,14 @@ fn main() {
"; ";
println!("{help_page}"); println!("{help_page}");
std::process::exit(0); exit(0);
} }
/* LogLevel modifiers */ /* 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 // TODO: Change Log Level to info and warn
} }
@ -43,56 +44,87 @@ fn main() {
warn!("Debug info enabled!"); warn!("Debug info enabled!");
// TODO: Change Log Level to debug // TODO: Change Log Level to debug
} }
} }
/* Authentication Process */ /* 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() let http_client_builder = reqwest::blocking::ClientBuilder::new()
.https_only(true) .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()); .unwrap_or(reqwest::blocking::Client::new());
// Authentication code from user input // Authentication code from user input
print!("Insert AuthCode > "); info!("Insert Authentication Code: ");
std::io::stdout().flush().unwrap(); std::io::stdout().flush().unwrap();
// Getting temporal info to create persistent login info // Getting temporal info to create persistent login info
let mut auth_code: String = "".to_string(); let mut auth_code: String = "".to_string();
let _ = std::io::stdin().read_line(&mut auth_code).unwrap(); let _ = std::io::stdin().read_line(&mut auth_code).unwrap();
let mut persistent_credentials: DeviceCredentials = DeviceCredentials::new(); info!("Generating persistent credentials");
persistent_credentials.get_access_token_and_account_id(&http_client, &auth_code); let persistent_credentials = PersistentCredentials::fetch(&http_client, &auth_code).unwrap();
persistent_credentials.get_device_auth_and_secret(&http_client); info!("New persistent credentials: {:#?}", &persistent_credentials);
dbg!(&persistent_credentials);
/* Android temporary credentials */ // Generating Android access token and exchange code from persistent_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);
/* Generic temporary credentials */ info!("Generating Android exchange code from persistent credentials");
let mut generic_credentials : TemporaryCredentials = TemporaryCredentials::new(); let android_token = AccessToken::from_persistent_credentials(
generic_credentials.get_access_token_from_exchange_code(&http_client, &android_credentials); &http_client,
generic_credentials.get_exchange_code(&http_client, "ec684b8c687f479fadea3cb2ad83f5c6"); &persistent_credentials,
dbg!(&generic_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*/ /* Game Launching*/
info!("Starting game...");
let mut auth_password_argument = String::from("-AUTH_PASSWORD="); 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="); let mut uid_argument = String::from("-epicuserid=");
uid_argument.push_str(persistent_credentials.account_id.as_str()); uid_argument.push_str(persistent_credentials.account_id.as_str());
let command = Command::new("cmd") 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("start")
.arg("/d") .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("FortniteLauncher.exe") // The executable
.arg("-AUTH_LOGIN=unused") .arg("-AUTH_LOGIN=unused")
.arg(&auth_password_argument) .arg(&auth_password_argument)
@ -105,31 +137,14 @@ fn main() {
.arg("-epicsandboxid=fn") .arg("-epicsandboxid=fn")
.spawn(); .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 { match command {
Ok(mut child) => { Ok(mut child) => {
// Optionally, you can wait for the process to complete // Optionally, you can wait for the process to complete
let status = child.wait().expect("Failed to wait on child"); let status = child.wait().expect("Failed to wait on child");
println!("Command executed with status: {}", status); info!("Command executed with status: {}", status);
} }
Err(e) => { Err(e) => {
eprintln!("Error executing command: {}", e); error!("Error executing command: {}", e);
exit(1); exit(1);
} }
} }