mirror of
https://github.com/a-mayb3/godo-launcher.git
synced 2026-03-21 18:05:39 +01:00
Remade auth.rs and adapted main.rs with it.
This commit is contained in:
parent
6bd7d2d1db
commit
a98d93ffd1
2 changed files with 482 additions and 324 deletions
677
src/auth.rs
677
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<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,
|
||||
}
|
||||
|
||||
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<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!"); },
|
||||
/// 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<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/");
|
||||
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::<ResponseStruct>().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::<ResponseContent>()?;
|
||||
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<ExchangeCode, GameAuthenticationError> {
|
||||
#[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::<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,
|
||||
creatingClientId: String,
|
||||
}
|
||||
|
||||
let mut bearer_header = String::from("Bearer ");
|
||||
bearer_header.push_str(self.access_token.as_str());
|
||||
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",
|
||||
},
|
||||
)];
|
||||
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();
|
||||
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())
|
||||
let response = http_client
|
||||
.get(url)
|
||||
.query(&query_content)
|
||||
.headers(bearer)
|
||||
.send();
|
||||
|
||||
match response {
|
||||
Ok(response_data) => {
|
||||
match response_data.json::<ResponseStruct>().ok() {
|
||||
Some(response_json) => {
|
||||
self.exchange_code = response_json.code;
|
||||
}
|
||||
None => {
|
||||
error!("Failed to parse Exchange code!");
|
||||
},
|
||||
Ok(response_data) => match response_data.json::<ResponseStruct>() {
|
||||
Ok(response_json) => {
|
||||
return Ok(ExchangeCode(response_json.code));
|
||||
}
|
||||
_ => Err(GameAuthenticationError::IllegalResponse(
|
||||
"Could not parse exchange code".to_string(),
|
||||
))?,
|
||||
},
|
||||
_ => { error!("Failed to get Exchange code!"); },
|
||||
_ => Err(GameAuthenticationError::RequestFailed)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
113
src/main.rs
113
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);
|
||||
info!("Generating persistent credentials");
|
||||
let persistent_credentials = PersistentCredentials::fetch(&http_client, &auth_code).unwrap();
|
||||
info!("New persistent credentials: {:#?}", &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);
|
||||
// Generating Android access token and exchange code from 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);
|
||||
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)
|
||||
|
|
@ -105,31 +137,14 @@ fn main() {
|
|||
.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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue