format the rust :3
This commit is contained in:
parent
b2f459478a
commit
7d6fbf8fd8
16 changed files with 887 additions and 716 deletions
|
|
@ -1,3 +1,3 @@
|
|||
fn main() {
|
||||
prisma_client_rust_cli::run();
|
||||
}
|
||||
prisma_client_rust_cli::run();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
#[allow(warnings, unused)]
|
||||
pub mod prisma;
|
||||
pub mod prisma;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"serve": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"lint": "eslint . --cache",
|
||||
"format": "pnpm lint --fix",
|
||||
"format": "pnpm lint --fix && cargo fmt",
|
||||
"prisma": "cd src-tauri && cargo prisma"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use tauri_build::{Attributes, InlinedPlugin};
|
||||
|
||||
fn main() {
|
||||
let _ = tauri_build::try_build(Attributes::new()
|
||||
.plugin("auth", InlinedPlugin::new().commands(&[
|
||||
"login_msa"
|
||||
]))
|
||||
.plugin("game", InlinedPlugin::new().commands(&[
|
||||
"launch_game",
|
||||
"set_selected_client"
|
||||
]))
|
||||
);
|
||||
let _ = tauri_build::try_build(
|
||||
Attributes::new()
|
||||
.plugin("auth", InlinedPlugin::new().commands(&["login_msa"]))
|
||||
.plugin(
|
||||
"game",
|
||||
InlinedPlugin::new().commands(&["launch_game", "set_selected_client"]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
provider = "sqlite"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use std::{io::{Read, Write}, net::{SocketAddr, TcpListener, TcpStream}};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::{SocketAddr, TcpListener, TcpStream},
|
||||
};
|
||||
|
||||
use serde_json::json;
|
||||
use tauri::{AppHandle, Runtime};
|
||||
|
|
@ -14,197 +17,246 @@ use super::{Account, AuthenticationError, AuthenticationMethod};
|
|||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MicrosoftAuthError {
|
||||
#[error("failed to get Minecraft access token")]
|
||||
MSAccessToken,
|
||||
#[error("failed to get XSTS token")]
|
||||
XSTSToken,
|
||||
#[error("failed to get XBL token")]
|
||||
XBLToken,
|
||||
#[error("failed to get XBL user hash")]
|
||||
XBLUserHash,
|
||||
#[error("this account doesn't have an xbox account, please sign in through minecraft.net and try again")]
|
||||
NoXBoxAccount,
|
||||
#[error("this account is from a country where xbox live is not available")]
|
||||
XBoxLiveBlocked,
|
||||
#[error("this account needs adult verification on the xbox page")]
|
||||
AdultVerification,
|
||||
#[error("this account is under 18, please add this account to a family by an adult and try again")]
|
||||
ChildAccount,
|
||||
#[error("unknown xbox live error: {0}")]
|
||||
UnknownError(u64)
|
||||
#[error("failed to get Minecraft access token")]
|
||||
MSAccessToken,
|
||||
#[error("failed to get XSTS token")]
|
||||
XSTSToken,
|
||||
#[error("failed to get XBL token")]
|
||||
XBLToken,
|
||||
#[error("failed to get XBL user hash")]
|
||||
XBLUserHash,
|
||||
#[error("this account doesn't have an xbox account, sign in through minecraft.net and try again")]
|
||||
NoXBoxAccount,
|
||||
#[error("this account is from a country where xbox live is not available")]
|
||||
XBoxLiveBlocked,
|
||||
#[error("this account needs adult verification on the xbox page")]
|
||||
AdultVerification,
|
||||
#[error("this account is a child, ask an adult to add this account to a Family")]
|
||||
ChildAccount,
|
||||
#[error("unknown xbox live error: {0}")]
|
||||
UnknownError(u64),
|
||||
}
|
||||
|
||||
|
||||
pub(super) struct MicrosoftAuthenticationMethod;
|
||||
impl AuthenticationMethod for MicrosoftAuthenticationMethod {
|
||||
async fn auth<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> PolyResult<Account>
|
||||
where F: Fn(String, u8, bool) -> () {
|
||||
|
||||
stage("Authenticating with Microsoft".into(), 0, false);
|
||||
let msa_code: String = msa_code(handle).await?;
|
||||
async fn auth<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> PolyResult<Account>
|
||||
where
|
||||
F: Fn(String, u8, bool) -> (),
|
||||
{
|
||||
stage("Authenticating with Microsoft".into(), 0, false);
|
||||
let msa_code: String = msa_code(handle).await?;
|
||||
|
||||
stage("Authenticating with Microsoft".into(), 1, false);
|
||||
let msa_token: String = msa_code_to_token(msa_code).await?;
|
||||
stage("Authenticating with Microsoft".into(), 1, false);
|
||||
let msa_token: String = msa_code_to_token(msa_code).await?;
|
||||
|
||||
stage("Authenticating with Xbox Live".into(), 2, false);
|
||||
let (xbl_token, user_hash): (String, String) = auth_xbl(msa_token).await?;
|
||||
stage("Authenticating with Xbox Live".into(), 2, false);
|
||||
let (xbl_token, user_hash): (String, String) = auth_xbl(msa_token).await?;
|
||||
|
||||
stage("Retrieving XSTS token".into(), 3, false);
|
||||
let xsts_token: String = auth_xsts(xbl_token).await?;
|
||||
stage("Retrieving XSTS token".into(), 3, false);
|
||||
let xsts_token: String = auth_xsts(xbl_token).await?;
|
||||
|
||||
stage("Authenticating with Minecraft".into(), 4, false);
|
||||
let access_token: String = auth_minecraft(xsts_token, user_hash).await?;
|
||||
stage("Authenticating with Minecraft".into(), 4, false);
|
||||
let access_token: String = auth_minecraft(xsts_token, user_hash).await?;
|
||||
|
||||
stage("Retrieving Minecraft profile".into(), 5, true);
|
||||
let account: Account = Self::get_profile(access_token).await?;
|
||||
stage("Retrieving Minecraft profile".into(), 5, true);
|
||||
let account: Account = Self::get_profile(access_token).await?;
|
||||
|
||||
Ok(account)
|
||||
}
|
||||
Ok(account)
|
||||
}
|
||||
}
|
||||
|
||||
async fn auth_minecraft(xsts_token: String, user_hash: String) -> PolyResult<String> {
|
||||
let response = create_client()?
|
||||
.post("https://api.minecraftservices.com/authentication/login_with_xbox")
|
||||
.json(&json!({
|
||||
"identityToken": format!("XBL3.0 x={};{}", user_hash, xsts_token)
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
let response = create_client()?
|
||||
.post("https://api.minecraftservices.com/authentication/login_with_xbox")
|
||||
.json(&json!({
|
||||
"identityToken": format!("XBL3.0 x={};{}", user_hash, xsts_token)
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let response = response.json::<serde_json::Value>().await?;
|
||||
let access_token = match response.get("access_token") {
|
||||
Some(token) => token.as_str().unwrap(),
|
||||
None => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::MSAccessToken)))
|
||||
};
|
||||
let response = response.json::<serde_json::Value>().await?;
|
||||
let access_token = match response.get("access_token") {
|
||||
Some(token) => token.as_str().unwrap(),
|
||||
None => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::MSAccessToken,
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(access_token.to_string())
|
||||
Ok(access_token.to_string())
|
||||
}
|
||||
|
||||
async fn auth_xsts(token: String) -> PolyResult<String> {
|
||||
let response = create_client()?
|
||||
.post("https://xsts.auth.xboxlive.com/xsts/authorize")
|
||||
.json(&json!({
|
||||
"Properties": {
|
||||
"SandboxId": "RETAIL",
|
||||
"UserTokens": [
|
||||
token
|
||||
]
|
||||
},
|
||||
"RelyingParty": "rp://api.minecraftservices.com/",
|
||||
"TokenType": "JWT"
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
let response = create_client()?
|
||||
.post("https://xsts.auth.xboxlive.com/xsts/authorize")
|
||||
.json(&json!({
|
||||
"Properties": {
|
||||
"SandboxId": "RETAIL",
|
||||
"UserTokens": [
|
||||
token
|
||||
]
|
||||
},
|
||||
"RelyingParty": "rp://api.minecraftservices.com/",
|
||||
"TokenType": "JWT"
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().as_u16() == 401 {
|
||||
let error_code = response.json::<serde_json::Value>().await?["XErr"].as_u64().unwrap();
|
||||
if response.status().as_u16() == 401 {
|
||||
let error_code = response.json::<serde_json::Value>().await?["XErr"]
|
||||
.as_u64()
|
||||
.unwrap();
|
||||
|
||||
match error_code {
|
||||
2148916233 => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::NoXBoxAccount))),
|
||||
2148916235 => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::XBoxLiveBlocked))),
|
||||
2148916236 | 2148916237 => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::AdultVerification))),
|
||||
2148916238 => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::ChildAccount))),
|
||||
_ => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::UnknownError(error_code)))),
|
||||
}
|
||||
}
|
||||
match error_code {
|
||||
2148916233 => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::NoXBoxAccount,
|
||||
)))
|
||||
}
|
||||
2148916235 => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::XBoxLiveBlocked,
|
||||
)))
|
||||
}
|
||||
2148916236 | 2148916237 => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::AdultVerification,
|
||||
)))
|
||||
}
|
||||
2148916238 => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::ChildAccount,
|
||||
)))
|
||||
}
|
||||
_ => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::UnknownError(error_code),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let response = response.json::<serde_json::Value>().await?;
|
||||
let xsts_token = match response.get("Token") {
|
||||
Some(token) => token.as_str().unwrap(),
|
||||
None => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::XSTSToken)))
|
||||
};
|
||||
let response = response.json::<serde_json::Value>().await?;
|
||||
let xsts_token = match response.get("Token") {
|
||||
Some(token) => token.as_str().unwrap(),
|
||||
None => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::XSTSToken,
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(xsts_token.to_string())
|
||||
Ok(xsts_token.to_string())
|
||||
}
|
||||
|
||||
async fn auth_xbl(code: String) -> PolyResult<(String, String)> {
|
||||
let response = create_client()?
|
||||
.post("https://user.auth.xboxlive.com/user/authenticate")
|
||||
.json(&json!({
|
||||
"Properties": {
|
||||
"AuthMethod": "RPS",
|
||||
"SiteName": "user.auth.xboxlive.com",
|
||||
"RpsTicket": &format!("d={}", code)
|
||||
},
|
||||
"RelyingParty": "http://auth.xboxlive.com",
|
||||
"TokenType": "JWT"
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
let response = create_client()?
|
||||
.post("https://user.auth.xboxlive.com/user/authenticate")
|
||||
.json(&json!({
|
||||
"Properties": {
|
||||
"AuthMethod": "RPS",
|
||||
"SiteName": "user.auth.xboxlive.com",
|
||||
"RpsTicket": &format!("d={}", code)
|
||||
},
|
||||
"RelyingParty": "http://auth.xboxlive.com",
|
||||
"TokenType": "JWT"
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let response = response.json::<serde_json::Value>().await?;
|
||||
let xbl_token = match response.get("Token") {
|
||||
Some(token) => token.as_str().unwrap(),
|
||||
None => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::XBLToken)))
|
||||
};
|
||||
let response = response.json::<serde_json::Value>().await?;
|
||||
let xbl_token = match response.get("Token") {
|
||||
Some(token) => token.as_str().unwrap(),
|
||||
None => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::XBLToken,
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let user_hash = match response.get("DisplayClaims").and_then(|claims| claims.get("xui").and_then(|xui| xui.get(0).and_then(|xui| xui.get("uhs")))) {
|
||||
Some(hash) => hash.as_str().unwrap(),
|
||||
None => return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(MicrosoftAuthError::XBLUserHash)))
|
||||
};
|
||||
let user_hash = match response.get("DisplayClaims").and_then(|claims| {
|
||||
claims
|
||||
.get("xui")
|
||||
.and_then(|xui| xui.get(0).and_then(|xui| xui.get("uhs")))
|
||||
}) {
|
||||
Some(hash) => hash.as_str().unwrap(),
|
||||
None => {
|
||||
return Err(PolyError::AuthError(AuthenticationError::MicrosoftError(
|
||||
MicrosoftAuthError::XBLUserHash,
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok((xbl_token.to_string(), user_hash.to_string()))
|
||||
Ok((xbl_token.to_string(), user_hash.to_string()))
|
||||
}
|
||||
|
||||
async fn msa_code_to_token(code: String) -> PolyResult<String> {
|
||||
let token = create_client()?
|
||||
.post("https://login.live.com/oauth20_token.srf")
|
||||
.form(&[
|
||||
("client_id", CLIENT_ID),
|
||||
("redirect_uri", &format!("http://localhost:{}/", PORT)),
|
||||
("code", &code),
|
||||
("grant_type", "authorization_code")
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.json::<serde_json::Value>()
|
||||
.await?;
|
||||
let token = create_client()?
|
||||
.post("https://login.live.com/oauth20_token.srf")
|
||||
.form(&[
|
||||
("client_id", CLIENT_ID),
|
||||
("redirect_uri", &format!("http://localhost:{}/", PORT)),
|
||||
("code", &code),
|
||||
("grant_type", "authorization_code"),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.json::<serde_json::Value>()
|
||||
.await?;
|
||||
|
||||
Ok(token["access_token"].as_str().unwrap().to_string())
|
||||
Ok(token["access_token"].as_str().unwrap().to_string())
|
||||
}
|
||||
|
||||
const PORT: u16 = 13523;
|
||||
async fn msa_code<R: Runtime>(handle: &AppHandle<R>) -> PolyResult<String> {
|
||||
let url = format!(
|
||||
let url = format!(
|
||||
"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id={}&response_type=code&redirect_uri=http://localhost:{}/&response_mode=query&scope=XboxLive.signin%20offline_access&prompt=consent",
|
||||
CLIENT_ID,
|
||||
CLIENT_ID,
|
||||
PORT
|
||||
);
|
||||
|
||||
handle.shell().open(url, None)?;
|
||||
handle.shell().open(url, None)?;
|
||||
|
||||
let mut token: String = String::new();
|
||||
let mut token: String = String::new();
|
||||
|
||||
let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], PORT)))?;
|
||||
println!("Started local server for MSA on port {}", PORT);
|
||||
let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], PORT)))?;
|
||||
println!("Started local server for MSA on port {}", PORT);
|
||||
|
||||
for conn in listener.incoming() {
|
||||
match conn {
|
||||
Err(err) => return Err(err.into()),
|
||||
Ok(stream) => {
|
||||
if let Some(code) = handle_connection(stream) {
|
||||
token = code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for conn in listener.incoming() {
|
||||
match conn {
|
||||
Err(err) => return Err(err.into()),
|
||||
Ok(stream) => {
|
||||
if let Some(code) = handle_connection(stream) {
|
||||
token = code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(token)
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
fn handle_connection(mut stream: TcpStream) -> Option<String> {
|
||||
let mut buffer = [0; 512];
|
||||
if let Err(err) = stream.read(&mut buffer) {
|
||||
eprintln!("Failed to read from stream: {}", err);
|
||||
return None;
|
||||
}
|
||||
let mut buffer = [0; 512];
|
||||
if let Err(err) = stream.read(&mut buffer) {
|
||||
eprintln!("Failed to read from stream: {}", err);
|
||||
return None;
|
||||
}
|
||||
|
||||
let request = String::from_utf8_lossy(&buffer[..]);
|
||||
let url = request.split_whitespace().nth(1)?;
|
||||
let code = url.split('?').nth(1)?.split('&').find(|s| s.starts_with("code="))?.split_once('=').unwrap().1;
|
||||
|
||||
let response = r"
|
||||
let request = String::from_utf8_lossy(&buffer[..]);
|
||||
let url = request.split_whitespace().nth(1)?;
|
||||
let code = url
|
||||
.split('?')
|
||||
.nth(1)?
|
||||
.split('&')
|
||||
.find(|s| s.starts_with("code="))?
|
||||
.split_once('=')
|
||||
.unwrap()
|
||||
.1;
|
||||
|
||||
let response = r"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
|
@ -239,14 +291,16 @@ fn handle_connection(mut stream: TcpStream) -> Option<String> {
|
|||
</html>
|
||||
";
|
||||
|
||||
stream.write_all(
|
||||
format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
|
||||
response.len(),
|
||||
response
|
||||
)
|
||||
.as_bytes(),
|
||||
).unwrap();
|
||||
stream
|
||||
.write_all(
|
||||
format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
|
||||
response.len(),
|
||||
response
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(code.to_string())
|
||||
Some(code.to_string())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use anyhow::anyhow;
|
||||
use thiserror::Error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{plugin::TauriPlugin, AppHandle, Manager, Runtime};
|
||||
use tauri_plugin_http::reqwest::Client;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{PolyError, PolyResult};
|
||||
|
||||
|
|
@ -12,64 +12,65 @@ mod microsoft_auth;
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AuthenticationError {
|
||||
#[error(transparent)]
|
||||
MicrosoftError(#[from] microsoft_auth::MicrosoftAuthError)
|
||||
#[error(transparent)]
|
||||
MicrosoftError(#[from] microsoft_auth::MicrosoftAuthError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Account {
|
||||
pub username: String,
|
||||
pub uuid: String,
|
||||
pub skins: Vec<AccountSkin>,
|
||||
pub username: String,
|
||||
pub uuid: String,
|
||||
pub skins: Vec<AccountSkin>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AccountSkin {
|
||||
pub id: String,
|
||||
pub state: String,
|
||||
pub url: String,
|
||||
pub variant: String,
|
||||
pub id: String,
|
||||
pub state: String,
|
||||
pub url: String,
|
||||
pub variant: String,
|
||||
}
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait AuthenticationMethod {
|
||||
/// Authenticate with a given method. Stage is a function that takes a string and a u8.
|
||||
/// The string is the message to display to the user, and the u8 is the progress of the authentication.
|
||||
async fn auth<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> PolyResult<Account>
|
||||
where F: Fn(String, u8, bool) -> ();
|
||||
/// Authenticate with a given method. Stage is a function that takes a string and a u8.
|
||||
/// The string is the message to display to the user, and the u8 is the progress of the authentication.
|
||||
async fn auth<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> PolyResult<Account>
|
||||
where
|
||||
F: Fn(String, u8, bool) -> ();
|
||||
|
||||
async fn get_profile(access_token: String) -> PolyResult<Account> {
|
||||
let response = Client::new()
|
||||
.get("https://api.minecraftservices.com/minecraft/profile")
|
||||
.header("Authorization", format!("Bearer {}", access_token))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let response = response.json::<serde_json::Value>().await?;
|
||||
if let Some(error) = response.get("error") {
|
||||
return Err(PolyError::AnyhowError(anyhow!(error.to_string())));
|
||||
}
|
||||
|
||||
let account = serde_json::from_value::<Account>(response)?;
|
||||
Ok(account)
|
||||
}
|
||||
async fn get_profile(access_token: String) -> PolyResult<Account> {
|
||||
let response = Client::new()
|
||||
.get("https://api.minecraftservices.com/minecraft/profile")
|
||||
.header("Authorization", format!("Bearer {}", access_token))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let response = response.json::<serde_json::Value>().await?;
|
||||
if let Some(error) = response.get("error") {
|
||||
return Err(PolyError::AnyhowError(anyhow!(error.to_string())));
|
||||
}
|
||||
|
||||
let account = serde_json::from_value::<Account>(response)?;
|
||||
Ok(account)
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin for MSA
|
||||
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("auth")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
login_msa
|
||||
])
|
||||
.build()
|
||||
tauri::plugin::Builder::new("auth")
|
||||
.invoke_handler(tauri::generate_handler![login_msa])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn login_msa<R: tauri::Runtime>(app: tauri::AppHandle<R>) -> Result<Account, String> {
|
||||
match MicrosoftAuthenticationMethod::auth(&app, |status, stage, was_last| {
|
||||
let _ = app.emit("msa:status", (status, stage, was_last));
|
||||
}).await {
|
||||
Ok(account) => Ok(account),
|
||||
Err(err) => Err(err.to_string())
|
||||
}
|
||||
match MicrosoftAuthenticationMethod::auth(&app, |status, stage, was_last| {
|
||||
let _ = app.emit("msa:status", (status, stage, was_last));
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(account) => Ok(account),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,15 @@ use tauri::{AppHandle, Manager};
|
|||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{utils::{file, http}, PolyError, PolyResult};
|
||||
use crate::{
|
||||
utils::{file, http},
|
||||
PolyError, PolyResult,
|
||||
};
|
||||
|
||||
use super::{clients::vanilla::{vanilla_manifest, VanillaClient, VanillaManifest}, JavaVersion};
|
||||
use super::{
|
||||
clients::vanilla::{vanilla_manifest, VanillaClient, VanillaManifest},
|
||||
JavaVersion,
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! create_manifest {
|
||||
|
|
@ -36,178 +42,228 @@ macro_rules! create_client {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "manifest")]
|
||||
pub enum GameClientType {
|
||||
Vanilla(VanillaManifest),
|
||||
// Forge(ForgeManifest),
|
||||
Vanilla(VanillaManifest),
|
||||
// Forge(ForgeManifest),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GameClientDetails {
|
||||
pub uuid: Uuid,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub main_class: String,
|
||||
pub java_version: JavaVersion,
|
||||
pub startup_args: Vec<String>,
|
||||
pub client_type: GameClientType,
|
||||
pub uuid: Uuid,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub main_class: String,
|
||||
pub java_version: JavaVersion,
|
||||
pub startup_args: Vec<String>,
|
||||
pub client_type: GameClientType,
|
||||
}
|
||||
|
||||
pub fn get_game_client(handle: AppHandle, details: GameClientDetails) -> Box<dyn GameClient> {
|
||||
Box::new(match details.client_type {
|
||||
GameClientType::Vanilla(_) => VanillaClient::new(handle, details),
|
||||
// GameClientType::Forge(_) => Box::new(ForgeClient::new(handle, details)),
|
||||
})
|
||||
Box::new(match details.client_type {
|
||||
GameClientType::Vanilla(_) => VanillaClient::new(handle, details),
|
||||
// GameClientType::Forge(_) => Box::new(ForgeClient::new(handle, details)),
|
||||
})
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait GameClient: Send + Sync {
|
||||
fn new(handle: AppHandle, details: GameClientDetails) -> Self where Self: Sized;
|
||||
fn new(handle: AppHandle, details: GameClientDetails) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
async fn get_all_version_urls() -> PolyResult<HashMap<String, String>> where Self: Sized;
|
||||
async fn get_minecraft_manifest(version: String) -> PolyResult<vanilla_manifest::MinecraftManifest> where Self: Sized;
|
||||
async fn get_all_version_urls() -> PolyResult<HashMap<String, String>>
|
||||
where
|
||||
Self: Sized;
|
||||
async fn get_minecraft_manifest(
|
||||
version: String,
|
||||
) -> PolyResult<vanilla_manifest::MinecraftManifest>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
// async fn get_details_from_version(version: String) -> Result<GameClientDetails, Box<dyn Error>> where Self: Sized;
|
||||
// async fn get_details_from_version(version: String) -> Result<GameClientDetails, Box<dyn Error>> where Self: Sized;
|
||||
|
||||
fn get_handle(&self) -> &AppHandle;
|
||||
fn get_details(&self) -> &GameClientDetails;
|
||||
fn get_client(&self) -> &GameClientType {
|
||||
&self.get_details().client_type
|
||||
}
|
||||
|
||||
async fn launch(&self) -> PolyResult<()>;
|
||||
async fn setup(&self) -> PolyResult<()>;
|
||||
fn get_handle(&self) -> &AppHandle;
|
||||
fn get_details(&self) -> &GameClientDetails;
|
||||
fn get_client(&self) -> &GameClientType {
|
||||
&self.get_details().client_type
|
||||
}
|
||||
|
||||
async fn download_java(&self) -> PolyResult<PathBuf> {
|
||||
let config_dir = match self.get_handle().path().app_config_dir() {
|
||||
Ok(dir) => dir,
|
||||
// SURELY THERES A BETTER WAY TO DO THIS IDK LOLL
|
||||
Err(err) => return Err(PolyError::JavaError(JavaDownloadError::UnsupportedOS(err.to_string())))
|
||||
};
|
||||
|
||||
let java_dir = config_dir.join("java");
|
||||
|
||||
if !java_dir.exists() {
|
||||
match fs::create_dir_all(&java_dir) {
|
||||
Ok(_) => (),
|
||||
Err(err) => return Err(PolyError::JavaError(JavaDownloadError::PermissionDenied(err.to_string())))
|
||||
};
|
||||
}
|
||||
|
||||
let java_version = &self.get_details().java_version;
|
||||
let os = env::consts::OS;
|
||||
let archive_type = match os {
|
||||
"windows" => "zip",
|
||||
"macos" => "tar.gz",
|
||||
"linux" => "tar.gz",
|
||||
_ => return Err(PolyError::JavaError(JavaDownloadError::UnsupportedOS(os.to_string())))
|
||||
};
|
||||
|
||||
let archive_name = format!("zulu-{}.{}", java_version.to_string(), archive_type);
|
||||
let archive = java_dir.join(&archive_name);
|
||||
let dest = java_dir.join(format!("zulu-{}-{}", java_version.to_string(), get_arch()));
|
||||
|
||||
if archive.exists() && dest.exists() {
|
||||
let _ = fs::remove_file(archive.as_path());
|
||||
} else if archive.exists() && !dest.exists() {
|
||||
extract(&archive, &dest)?;
|
||||
} else if !archive.exists() && !dest.exists() {
|
||||
download(java_version, os, archive_type, &archive).await?;
|
||||
extract(&archive, &dest)?;
|
||||
}
|
||||
|
||||
if let Ok(mut files) = fs::read_dir(&dest) {
|
||||
let file = match files.nth(0) {
|
||||
Some(file) => file.unwrap().path(),
|
||||
None => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("unable to get the java executable file".to_string())))
|
||||
};
|
||||
|
||||
return Ok(file.join("bin").join("java"));
|
||||
}
|
||||
|
||||
Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("unable to download the java executable file".to_string())))
|
||||
}
|
||||
async fn launch(&self) -> PolyResult<()>;
|
||||
async fn setup(&self) -> PolyResult<()>;
|
||||
|
||||
async fn download_java(&self) -> PolyResult<PathBuf> {
|
||||
let config_dir = match self.get_handle().path().app_config_dir() {
|
||||
Ok(dir) => dir,
|
||||
// SURELY THERES A BETTER WAY TO DO THIS IDK LOLL
|
||||
Err(err) => {
|
||||
return Err(PolyError::JavaError(JavaDownloadError::UnsupportedOS(
|
||||
err.to_string(),
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let java_dir = config_dir.join("java");
|
||||
|
||||
if !java_dir.exists() {
|
||||
match fs::create_dir_all(&java_dir) {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
return Err(PolyError::JavaError(JavaDownloadError::PermissionDenied(
|
||||
err.to_string(),
|
||||
)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let java_version = &self.get_details().java_version;
|
||||
let os = env::consts::OS;
|
||||
let archive_type = match os {
|
||||
"windows" => "zip",
|
||||
"macos" => "tar.gz",
|
||||
"linux" => "tar.gz",
|
||||
_ => {
|
||||
return Err(PolyError::JavaError(JavaDownloadError::UnsupportedOS(
|
||||
os.to_string(),
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let archive_name = format!("zulu-{}.{}", java_version.to_string(), archive_type);
|
||||
let archive = java_dir.join(&archive_name);
|
||||
let dest = java_dir.join(format!("zulu-{}-{}", java_version.to_string(), get_arch()));
|
||||
|
||||
if archive.exists() && dest.exists() {
|
||||
let _ = fs::remove_file(archive.as_path());
|
||||
} else if archive.exists() && !dest.exists() {
|
||||
extract(&archive, &dest)?;
|
||||
} else if !archive.exists() && !dest.exists() {
|
||||
download(java_version, os, archive_type, &archive).await?;
|
||||
extract(&archive, &dest)?;
|
||||
}
|
||||
|
||||
if let Ok(mut files) = fs::read_dir(&dest) {
|
||||
let file = match files.nth(0) {
|
||||
Some(file) => file.unwrap().path(),
|
||||
None => {
|
||||
return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(
|
||||
"unable to get the java executable file".to_string(),
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(file.join("bin").join("java"));
|
||||
}
|
||||
|
||||
Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(
|
||||
"unable to download the java executable file".to_string(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ModdedGameClient: GameClient {
|
||||
async fn install_mods(&self) -> PolyResult<()>;
|
||||
async fn install_mods(&self) -> PolyResult<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JavaDownloadError {
|
||||
#[error("failed to find a valid java version: {0}")]
|
||||
NoJavaVersionFound(String),
|
||||
#[error("failed creating a java directory (try running at an elevated permission): {0}")]
|
||||
PermissionDenied(String),
|
||||
#[error("unable to match your operating system ({0}) with a valid java version")]
|
||||
UnsupportedOS(String),
|
||||
#[error("unable to match your architecture ({0}) with a valid java version")]
|
||||
UnsupportedArch(String),
|
||||
#[error("failed to extract the java executable: {0}")]
|
||||
ExtractError(String),
|
||||
#[error("failed to download the java executable")]
|
||||
DownloadError(String),
|
||||
#[error("failed to find a valid java version: {0}")]
|
||||
NoJavaVersionFound(String),
|
||||
#[error("failed creating a java directory (try running at an elevated permission): {0}")]
|
||||
PermissionDenied(String),
|
||||
#[error("unable to match your operating system ({0}) with a valid java version")]
|
||||
UnsupportedOS(String),
|
||||
#[error("unable to match your architecture ({0}) with a valid java version")]
|
||||
UnsupportedArch(String),
|
||||
#[error("failed to extract the java executable: {0}")]
|
||||
ExtractError(String),
|
||||
#[error("failed to download the java executable")]
|
||||
DownloadError(String),
|
||||
}
|
||||
|
||||
fn get_arch() -> String {
|
||||
let arch = env::consts::ARCH;
|
||||
match arch {
|
||||
"x86" => "x86",
|
||||
"x86_64" => "x64",
|
||||
"arm" => "aarch32",
|
||||
"aarch64" => "aarch64",
|
||||
_ => "unsupported"
|
||||
}.to_string()
|
||||
let arch = env::consts::ARCH;
|
||||
match arch {
|
||||
"x86" => "x86",
|
||||
"x86_64" => "x64",
|
||||
"arm" => "aarch32",
|
||||
"aarch64" => "aarch64",
|
||||
_ => "unsupported",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
async fn get_java_versions(java_version: &JavaVersion, os: &str, archive_type: &str) -> PolyResult<Value> {
|
||||
let url = format!("
|
||||
async fn get_java_versions(
|
||||
java_version: &JavaVersion,
|
||||
os: &str,
|
||||
archive_type: &str,
|
||||
) -> PolyResult<Value> {
|
||||
let url = format!(
|
||||
"
|
||||
https://api.azul.com/metadata/v1/zulu/packages/\
|
||||
?java_version={}\
|
||||
&os={}\
|
||||
&arch={}\
|
||||
&archive_type={}\
|
||||
&java_package_type=jre&javafx_bundled=true&release_status=ga&latest=true",
|
||||
java_version.to_string(),
|
||||
os,
|
||||
get_arch(),
|
||||
archive_type
|
||||
);
|
||||
&java_package_type=jre&javafx_bundled=true&release_status=ga&latest=true",
|
||||
java_version.to_string(),
|
||||
os,
|
||||
get_arch(),
|
||||
archive_type
|
||||
);
|
||||
|
||||
let response = match http::create_client()?.get(&url).send().await {
|
||||
Ok(response) => match response.json::<serde_json::Value>().await {
|
||||
Ok(json) => json,
|
||||
Err(err) => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(err.to_string())))
|
||||
},
|
||||
Err(err) => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(err.to_string())))
|
||||
};
|
||||
let response = match http::create_client()?.get(&url).send().await {
|
||||
Ok(response) => match response.json::<serde_json::Value>().await {
|
||||
Ok(json) => json,
|
||||
Err(err) => {
|
||||
return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(
|
||||
err.to_string(),
|
||||
)))
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(
|
||||
err.to_string(),
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
if !response.is_array() || response.as_array().unwrap().is_empty() {
|
||||
return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("didn't get the expected java api result".to_string())));
|
||||
}
|
||||
if !response.is_array() || response.as_array().unwrap().is_empty() {
|
||||
return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(
|
||||
"didn't get the expected java api result".to_string(),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn download(java_version: &JavaVersion, os: &str, archive_type: &str, archive: &PathBuf) -> PolyResult<()> {
|
||||
let response = get_java_versions(java_version, os, archive_type).await?;
|
||||
let latest = response.as_array().unwrap().first().unwrap();
|
||||
let download_url = latest.get("download_url").unwrap().as_str().unwrap();
|
||||
async fn download(
|
||||
java_version: &JavaVersion,
|
||||
os: &str,
|
||||
archive_type: &str,
|
||||
archive: &PathBuf,
|
||||
) -> PolyResult<()> {
|
||||
let response = get_java_versions(java_version, os, archive_type).await?;
|
||||
let latest = response.as_array().unwrap().first().unwrap();
|
||||
let download_url = latest.get("download_url").unwrap().as_str().unwrap();
|
||||
|
||||
if let Err(err) = http::download_file(download_url, archive.as_path()).await {
|
||||
// eprintln!("{}", err);
|
||||
return Err(PolyError::JavaError(JavaDownloadError::DownloadError(err.to_string())));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
if let Err(err) = http::download_file(download_url, archive.as_path()).await {
|
||||
// eprintln!("{}", err);
|
||||
return Err(PolyError::JavaError(JavaDownloadError::DownloadError(
|
||||
err.to_string(),
|
||||
)));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract(archive: &PathBuf, dest: &PathBuf) -> PolyResult<()> {
|
||||
if let Err(err) = file::extract_archive(archive.as_path(), dest.as_path()) {
|
||||
// eprintln!("{}", err);
|
||||
let _ = fs::remove_file(dest.as_path());
|
||||
return Err(PolyError::JavaError(JavaDownloadError::ExtractError(err.to_string())));
|
||||
}
|
||||
if let Err(err) = file::extract_archive(archive.as_path(), dest.as_path()) {
|
||||
let _ = fs::remove_file(dest.as_path());
|
||||
return Err(PolyError::JavaError(JavaDownloadError::ExtractError(
|
||||
err.to_string(),
|
||||
)));
|
||||
}
|
||||
|
||||
let _ = fs::remove_file(archive.as_path());
|
||||
Ok(())
|
||||
let _ = fs::remove_file(archive.as_path());
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
pub mod vanilla;
|
||||
pub mod vanilla;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ use serde_json::Value;
|
|||
use tauri::{AppHandle, Manager};
|
||||
use tokio::io::{stdout, AsyncWriteExt};
|
||||
|
||||
use crate::{create_client, create_manifest, game::client::{GameClient, GameClientDetails, GameClientType}, utils::http, PolyError, PolyResult};
|
||||
use crate::{
|
||||
create_client, create_manifest,
|
||||
game::client::{GameClient, GameClientDetails, GameClientType},
|
||||
utils::http,
|
||||
PolyError, PolyResult,
|
||||
};
|
||||
|
||||
create_manifest!(VanillaManifest {
|
||||
// minecraft: vanilla_manifest::MinecraftManifest
|
||||
|
|
@ -16,303 +21,336 @@ create_client!(VanillaClient {});
|
|||
|
||||
#[async_trait]
|
||||
impl GameClient for VanillaClient {
|
||||
fn new(handle: AppHandle, details: GameClientDetails) -> Self {
|
||||
Self {
|
||||
handle: handle.clone(),
|
||||
details
|
||||
}
|
||||
}
|
||||
fn new(handle: AppHandle, details: GameClientDetails) -> Self {
|
||||
Self {
|
||||
handle: handle.clone(),
|
||||
details,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_all_version_urls() -> PolyResult<HashMap<String, String>> {
|
||||
const URL: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
||||
let request = http::create_client()?.get(URL).send().await.map_err(|err| PolyError::HTTPError(err))?;
|
||||
async fn get_all_version_urls() -> PolyResult<HashMap<String, String>> {
|
||||
const URL: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
||||
let request = http::create_client()?
|
||||
.get(URL)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| PolyError::HTTPError(err))?;
|
||||
|
||||
let json = request.json::<Value>().await.map_err(|err| PolyError::HTTPError(err))?;
|
||||
let json = request
|
||||
.json::<Value>()
|
||||
.await
|
||||
.map_err(|err| PolyError::HTTPError(err))?;
|
||||
|
||||
let versions = json.get("versions").ok_or(PolyError::AnyhowError(anyhow!("failed to get versions object")))?
|
||||
.as_array().ok_or(PolyError::AnyhowError(anyhow!("invalid versions object")))?;
|
||||
let versions = json
|
||||
.get("versions")
|
||||
.ok_or(PolyError::AnyhowError(anyhow!(
|
||||
"failed to get versions object"
|
||||
)))?
|
||||
.as_array()
|
||||
.ok_or(PolyError::AnyhowError(anyhow!("invalid versions object")))?;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for version in versions {
|
||||
let version = version.as_object().ok_or("Invalid version object")?;
|
||||
for version in versions {
|
||||
let version = version.as_object().ok_or("Invalid version object")?;
|
||||
|
||||
let id = version.get("id").ok_or("No id object")?
|
||||
.as_str().ok_or("Invalid id object")?;
|
||||
let url = version.get("url").ok_or("No url object")?
|
||||
.as_str().ok_or("Invalid url object")?;
|
||||
let id = version
|
||||
.get("id")
|
||||
.ok_or("No id object")?
|
||||
.as_str()
|
||||
.ok_or("Invalid id object")?;
|
||||
let url = version
|
||||
.get("url")
|
||||
.ok_or("No url object")?
|
||||
.as_str()
|
||||
.ok_or("Invalid url object")?;
|
||||
|
||||
map.insert(id.to_string(), url.to_string());
|
||||
}
|
||||
map.insert(id.to_string(), url.to_string());
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
async fn get_minecraft_manifest(version_url: String) -> Result<vanilla_manifest::MinecraftManifest, Box<dyn Error>> {
|
||||
let request = http::create_client().get(version_url).send().await?;
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
match request.json::<vanilla_manifest::MinecraftManifest>().await {
|
||||
Ok(manifest) => Ok(manifest),
|
||||
Err(e) => Err(e.into())
|
||||
}
|
||||
}
|
||||
async fn get_minecraft_manifest(
|
||||
version_url: String,
|
||||
) -> Result<vanilla_manifest::MinecraftManifest, Box<dyn Error>> {
|
||||
let request = http::create_client().get(version_url).send().await?;
|
||||
|
||||
fn get_handle(&self) -> &AppHandle {
|
||||
&self.handle
|
||||
}
|
||||
match request.json::<vanilla_manifest::MinecraftManifest>().await {
|
||||
Ok(manifest) => Ok(manifest),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_details(&self) -> &GameClientDetails {
|
||||
&self.details
|
||||
}
|
||||
fn get_handle(&self) -> &AppHandle {
|
||||
&self.handle
|
||||
}
|
||||
|
||||
fn get_client(&self) -> &GameClientType {
|
||||
&self.get_details().client_type
|
||||
}
|
||||
fn get_details(&self) -> &GameClientDetails {
|
||||
&self.details
|
||||
}
|
||||
|
||||
async fn setup(&self) -> Result<(), Box<dyn Error>> {
|
||||
let libraries_dir = self.get_handle().path().app_config_dir()?.join("libraries");
|
||||
let versions = Self::get_all_version_urls().await?;
|
||||
let version = versions.get(&self.get_details().version).ok_or("Version not found")?.to_owned();
|
||||
let manifest = Self::get_minecraft_manifest(version).await?;
|
||||
fn get_client(&self) -> &GameClientType {
|
||||
&self.get_details().client_type
|
||||
}
|
||||
|
||||
let libraries = vanilla_impl::setup_libraries(&manifest.libraries, &libraries_dir).await?;
|
||||
println!("{:#?}", libraries);
|
||||
// vanilla_impl::setup_natives().await?;
|
||||
// vanilla_impl::setup_assets().await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn setup(&self) -> Result<(), Box<dyn Error>> {
|
||||
let libraries_dir = self.get_handle().path().app_config_dir()?.join("libraries");
|
||||
let versions = Self::get_all_version_urls().await?;
|
||||
let version = versions
|
||||
.get(&self.get_details().version)
|
||||
.ok_or("Version not found")?
|
||||
.to_owned();
|
||||
let manifest = Self::get_minecraft_manifest(version).await?;
|
||||
|
||||
async fn launch(&self) -> Result<(), Box<dyn Error>> {
|
||||
println!("Launching Vanilla client");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
println!("Vanilla client launched");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
println!("Vanilla client exited");
|
||||
stdout().flush().await?; // why...
|
||||
Ok(())
|
||||
}
|
||||
let libraries = vanilla_impl::setup_libraries(&manifest.libraries, &libraries_dir).await?;
|
||||
println!("{:#?}", libraries);
|
||||
// vanilla_impl::setup_natives().await?;
|
||||
// vanilla_impl::setup_assets().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn launch(&self) -> Result<(), Box<dyn Error>> {
|
||||
println!("Launching Vanilla client");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
println!("Vanilla client launched");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
println!("Vanilla client exited");
|
||||
stdout().flush().await?; // why...
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod vanilla_impl {
|
||||
use std::{error::Error, fs, path::PathBuf};
|
||||
use std::{error::Error, fs, path::PathBuf};
|
||||
|
||||
use serde_json::{Map, Value};
|
||||
use tauri::AppHandle;
|
||||
use serde_json::{Map, Value};
|
||||
use tauri::AppHandle;
|
||||
|
||||
use crate::utils::http::download_file;
|
||||
use crate::utils::http::download_file;
|
||||
|
||||
use super::vanilla_manifest::{self, Library};
|
||||
use super::vanilla_manifest::{self, Library};
|
||||
|
||||
pub async fn setup_libraries(libraries: &Vec<Library>, libraries_folder: &PathBuf) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let mut natives_ret: Vec<&Library> = vec![];
|
||||
let mut libraries_ret: Vec<String> = vec![];
|
||||
pub async fn setup_libraries(
|
||||
libraries: &Vec<Library>,
|
||||
libraries_folder: &PathBuf,
|
||||
) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let mut natives_ret: Vec<&Library> = vec![];
|
||||
let mut libraries_ret: Vec<String> = vec![];
|
||||
|
||||
for library in libraries {
|
||||
// let library: &Map<String, Value> = library.as_object().unwrap();
|
||||
|
||||
// if let Some(_) = library.get("natives") {
|
||||
// natives_ret.push(library);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// let artifact = library.get("downloads").ok_or("No downloads object")?
|
||||
// .as_object().ok_or("Invalid downloads object")?
|
||||
// .get("artifact").ok_or("No artifact object")?
|
||||
// .as_object().ok_or("Invalid artifact object")?;
|
||||
for library in libraries {
|
||||
// let library: &Map<String, Value> = library.as_object().unwrap();
|
||||
|
||||
// let path = artifact.get("path").ok_or("No path object")?
|
||||
// .as_str().ok_or("Invalid path object")?;
|
||||
// let url = artifact.get("url").ok_or("No url object")?
|
||||
// .as_str().ok_or("Invalid url object")?;
|
||||
// if let Some(_) = library.get("natives") {
|
||||
// natives_ret.push(library);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if let Some(_) = library.natives {
|
||||
natives_ret.push(library);
|
||||
continue;
|
||||
}
|
||||
// let artifact = library.get("downloads").ok_or("No downloads object")?
|
||||
// .as_object().ok_or("Invalid downloads object")?
|
||||
// .get("artifact").ok_or("No artifact object")?
|
||||
// .as_object().ok_or("Invalid artifact object")?;
|
||||
|
||||
let artifact = library.downloads.artifact.clone().ok_or("No artifact object")?;
|
||||
let path = artifact.path;
|
||||
let url = artifact.url;
|
||||
|
||||
// TODO: Add checks for rules + platform
|
||||
|
||||
let dest = libraries_folder.join(path);
|
||||
fs::create_dir_all(dest.parent().ok_or("Couldn't get library parent")?)?;
|
||||
// let path = artifact.get("path").ok_or("No path object")?
|
||||
// .as_str().ok_or("Invalid path object")?;
|
||||
// let url = artifact.get("url").ok_or("No url object")?
|
||||
// .as_str().ok_or("Invalid url object")?;
|
||||
|
||||
if !dest.exists() {
|
||||
download_file(url.as_str(), &dest).await?;
|
||||
}
|
||||
if let Some(_) = library.natives {
|
||||
natives_ret.push(library);
|
||||
continue;
|
||||
}
|
||||
|
||||
libraries_ret.push(dest.to_str().ok_or("Couldn't get library path")?.to_string());
|
||||
}
|
||||
let artifact = library
|
||||
.downloads
|
||||
.artifact
|
||||
.clone()
|
||||
.ok_or("No artifact object")?;
|
||||
let path = artifact.path;
|
||||
let url = artifact.url;
|
||||
|
||||
Ok(libraries_ret)
|
||||
}
|
||||
// TODO: Add checks for rules + platform
|
||||
|
||||
pub async fn setup_natives() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
let dest = libraries_folder.join(path);
|
||||
fs::create_dir_all(dest.parent().ok_or("Couldn't get library parent")?)?;
|
||||
|
||||
pub async fn setup_assets() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
if !dest.exists() {
|
||||
download_file(url.as_str(), &dest).await?;
|
||||
}
|
||||
|
||||
libraries_ret.push(
|
||||
dest.to_str()
|
||||
.ok_or("Couldn't get library path")?
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(libraries_ret)
|
||||
}
|
||||
|
||||
pub async fn setup_natives() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn setup_assets() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// todo move to prisma
|
||||
pub mod vanilla_manifest {
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MinecraftManifest {
|
||||
pub asset_index: AssetIndex,
|
||||
pub assets: String,
|
||||
pub compliance_level: i64,
|
||||
pub downloads: Downloads,
|
||||
pub id: String,
|
||||
pub java_version: JavaVersion,
|
||||
pub libraries: Vec<Library>,
|
||||
pub logging: Logging,
|
||||
pub main_class: String,
|
||||
pub minecraft_arguments: String,
|
||||
pub minimum_launcher_version: i64,
|
||||
pub release_time: String,
|
||||
pub time: String,
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MinecraftManifest {
|
||||
pub asset_index: AssetIndex,
|
||||
pub assets: String,
|
||||
pub compliance_level: i64,
|
||||
pub downloads: Downloads,
|
||||
pub id: String,
|
||||
pub java_version: JavaVersion,
|
||||
pub libraries: Vec<Library>,
|
||||
pub logging: Logging,
|
||||
pub main_class: String,
|
||||
pub minecraft_arguments: String,
|
||||
pub minimum_launcher_version: i64,
|
||||
pub release_time: String,
|
||||
pub time: String,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
}
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AssetIndex {
|
||||
pub id: String,
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub total_size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AssetIndex {
|
||||
pub id: String,
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub total_size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Downloads {
|
||||
pub client: Client,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Downloads {
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Client {
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Client {
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JavaVersion {
|
||||
pub component: String,
|
||||
pub major_version: i64,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JavaVersion {
|
||||
pub component: String,
|
||||
pub major_version: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Library {
|
||||
pub downloads: Downloads2,
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub rules: Vec<Rule>,
|
||||
pub extract: Option<Extract>,
|
||||
pub natives: Option<Natives>,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Library {
|
||||
pub downloads: Downloads2,
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub rules: Vec<Rule>,
|
||||
pub extract: Option<Extract>,
|
||||
pub natives: Option<Natives>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Downloads2 {
|
||||
pub artifact: Option<Artifact>,
|
||||
pub classifiers: Option<Classifiers>,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Downloads2 {
|
||||
pub artifact: Option<Artifact>,
|
||||
pub classifiers: Option<Classifiers>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Artifact {
|
||||
pub path: String,
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Artifact {
|
||||
pub path: String,
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Classifiers {
|
||||
#[serde(rename = "natives-windows-32")]
|
||||
pub natives_windows_32: Option<NativeLibrary>,
|
||||
#[serde(rename = "natives-windows-64")]
|
||||
pub natives_windows_64: Option<NativeLibrary>,
|
||||
#[serde(rename = "natives-osx")]
|
||||
pub natives_osx: Option<NativeLibrary>,
|
||||
#[serde(rename = "natives-linux")]
|
||||
pub natives_linux: Option<NativeLibrary>,
|
||||
#[serde(rename = "natives-windows")]
|
||||
pub natives_windows: Option<NativeLibrary>,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Classifiers {
|
||||
#[serde(rename = "natives-windows-32")]
|
||||
pub natives_windows_32: Option<NativeLibrary>,
|
||||
#[serde(rename = "natives-windows-64")]
|
||||
pub natives_windows_64: Option<NativeLibrary>,
|
||||
#[serde(rename = "natives-osx")]
|
||||
pub natives_osx: Option<NativeLibrary>,
|
||||
#[serde(rename = "natives-linux")]
|
||||
pub natives_linux: Option<NativeLibrary>,
|
||||
#[serde(rename = "natives-windows")]
|
||||
pub natives_windows: Option<NativeLibrary>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NativeLibrary {
|
||||
pub path: String,
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NativeLibrary {
|
||||
pub path: String,
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Rule {
|
||||
pub action: String,
|
||||
pub os: Option<Os>,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Rule {
|
||||
pub action: String,
|
||||
pub os: Option<Os>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Os {
|
||||
pub name: String,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Os {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Extract {
|
||||
pub exclude: Vec<String>,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Extract {
|
||||
pub exclude: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Natives {
|
||||
pub windows: String,
|
||||
pub linux: Option<String>,
|
||||
pub osx: Option<String>,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Natives {
|
||||
pub windows: String,
|
||||
pub linux: Option<String>,
|
||||
pub osx: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Logging {
|
||||
pub client: Client2,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Logging {
|
||||
pub client: Client2,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Client2 {
|
||||
pub argument: String,
|
||||
pub file: File,
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct File {
|
||||
pub id: String,
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Client2 {
|
||||
pub argument: String,
|
||||
pub file: File,
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct File {
|
||||
pub id: String,
|
||||
pub sha1: String,
|
||||
pub size: i64,
|
||||
pub url: String,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,72 +11,73 @@ mod clients;
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum JavaVersion {
|
||||
V8,
|
||||
V16,
|
||||
V17
|
||||
V8,
|
||||
V16,
|
||||
V17,
|
||||
}
|
||||
|
||||
impl ToString for JavaVersion {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
JavaVersion::V8 => "8".to_string(),
|
||||
JavaVersion::V16 => "16".to_string(),
|
||||
JavaVersion::V17 => "17".to_string()
|
||||
}
|
||||
}
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
JavaVersion::V8 => "8".to_string(),
|
||||
JavaVersion::V16 => "16".to_string(),
|
||||
JavaVersion::V17 => "17".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tauri plugin for the game module
|
||||
pub struct GameManagerState {
|
||||
pub selected_client: Option<Box<dyn client::GameClient>>,
|
||||
pub selected_client: Option<Box<dyn client::GameClient>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for GameManagerState {}
|
||||
unsafe impl Sync for GameManagerState {}
|
||||
impl Default for GameManagerState {
|
||||
fn default() -> Self {
|
||||
GameManagerState {
|
||||
selected_client: None
|
||||
}
|
||||
}
|
||||
fn default() -> Self {
|
||||
GameManagerState {
|
||||
selected_client: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() -> TauriPlugin<tauri::Wry> {
|
||||
tauri::plugin::Builder::new("game")
|
||||
.setup(|app, _| {
|
||||
app.manage(Mutex::new(GameManagerState::default()));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
launch_game,
|
||||
set_selected_client
|
||||
])
|
||||
.build()
|
||||
tauri::plugin::Builder::new("game")
|
||||
.setup(|app, _| {
|
||||
app.manage(Mutex::new(GameManagerState::default()));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![launch_game, set_selected_client])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn launch_game(state: tauri::State<'_, Mutex<GameManagerState>>) -> Result<(), String> {
|
||||
let state = state.lock().await;
|
||||
let selected_client = match &state.selected_client {
|
||||
Some(client) => client,
|
||||
None => return Err("No client selected".into())
|
||||
};
|
||||
let state = state.lock().await;
|
||||
let selected_client = match &state.selected_client {
|
||||
Some(client) => client,
|
||||
None => return Err("No client selected".into()),
|
||||
};
|
||||
|
||||
if let Err(err) = selected_client.setup().await {
|
||||
return Err(format!("An error has occurred during install: {}", err));
|
||||
}
|
||||
if let Err(err) = selected_client.setup().await {
|
||||
return Err(format!("An error has occurred during install: {}", err));
|
||||
}
|
||||
|
||||
match selected_client.launch().await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(format!("An error has occurred: {}", e))
|
||||
}
|
||||
match selected_client.launch().await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(format!("An error has occurred: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement setting client by UUID, as currently this is creating a new client and setting that
|
||||
#[tauri::command]
|
||||
async fn set_selected_client(handle: tauri::AppHandle, state: tauri::State<'_, Mutex<GameManagerState>>, details: client::GameClientDetails) -> Result<(), String> {
|
||||
let mut state = state.lock().await;
|
||||
let client = get_game_client(handle, details);
|
||||
state.selected_client = Some(client);
|
||||
Ok(())
|
||||
async fn set_selected_client(
|
||||
handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, Mutex<GameManagerState>>,
|
||||
details: client::GameClientDetails,
|
||||
) -> Result<(), String> {
|
||||
let mut state = state.lock().await;
|
||||
let client = get_game_client(handle, details);
|
||||
state.selected_client = Some(client);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,58 @@
|
|||
use thiserror::Error;
|
||||
|
||||
pub mod auth;
|
||||
pub mod constants;
|
||||
pub mod game;
|
||||
pub mod utils;
|
||||
pub mod constants;
|
||||
|
||||
/// A standardized [`Error`] type that is used across the launcher
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PolyError {
|
||||
#[error("a tauri runtime error occured (this should not happen): {0}")]
|
||||
/// Wrapper around [`tauri::Error`] to handle expected Tauri errors.
|
||||
TauriError(#[from] tauri::Error),
|
||||
#[error("failed to manage shell task: {0}")]
|
||||
/// Wrapper around [`tauri_plugin_shell::Error`] to handle shell errors.
|
||||
TauriShellError(#[from] tauri_plugin_shell::Error),
|
||||
#[error("failed to manage a tokio semaphore: {0}")]
|
||||
/// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors.
|
||||
TokioError(#[from] tokio::sync::AcquireError),
|
||||
#[error("failed to compress a file with flate: {0}")]
|
||||
/// Wrapper around [`flate2::CompressError`] to handle flate compression errors.
|
||||
FlateError(#[from] flate2::CompressError),
|
||||
// im actually the funniest person alive dont @ me
|
||||
#[error("failed to decompress a file with flate: {0}")]
|
||||
/// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors.
|
||||
DeflateError(#[from] flate2::DecompressError),
|
||||
#[error("failed to parse uuids: {0}")]
|
||||
/// Wrapper around [`uuid::Error`] to handle UUID parsing errors.
|
||||
UUIDError(#[from] uuid::Error),
|
||||
#[error("failed to manage serialization of json: {0}")]
|
||||
/// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors.
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
#[error("failed to manage writing and reading of files: {0}")]
|
||||
/// Wrapper around [`std::io::Error`] only to be used if a more defined error isn't available (e.g. when using `zip`)
|
||||
IOError(#[from] std::io::Error),
|
||||
#[error("failed to query the database: {0}")]
|
||||
/// Wrapper around [`prisma_client_rust::QueryError`] to handle DB query errors.
|
||||
DBQueryError(#[from] prisma_client_rust::QueryError),
|
||||
#[error("failed to establish a connection to the database: {0}")]
|
||||
/// Wrapper around [`prisma_client_rust::NewClientError`] to handle errors occuring while programmatically connecting to Prisma.
|
||||
DBConnectionError(#[from] prisma_client_rust::NewClientError),
|
||||
#[error("failed to establish a HTTP connection: {0}")]
|
||||
/// Wrapper around [`reqwest::Error`] to handle HTTP errors.
|
||||
HTTPError(#[from] tauri_plugin_http::reqwest::Error),
|
||||
#[error("failed to download java: {0}")]
|
||||
/// Wrapper around [`game::client::JavaDownloadError`] to handle Java executable downloading.
|
||||
JavaError(#[from] game::client::JavaDownloadError),
|
||||
/// Wrapper around [`zip::result::ZipError`] to handle zip errors.
|
||||
#[error("failed to manage zip files: {0}")]
|
||||
ZipError(#[from] zip::result::ZipError),
|
||||
#[error("failed to authenticate: {0}")]
|
||||
// Wrapper around [`auth::AuthenticationError`] to handle authentication errors.
|
||||
AuthError(#[from] auth::AuthenticationError),
|
||||
#[error(transparent)]
|
||||
AnyhowError(#[from] anyhow::Error),
|
||||
#[error("a tauri runtime error occured (this should not happen): {0}")]
|
||||
/// Wrapper around [`tauri::Error`] to handle expected Tauri errors.
|
||||
TauriError(#[from] tauri::Error),
|
||||
#[error("failed to manage shell task: {0}")]
|
||||
/// Wrapper around [`tauri_plugin_shell::Error`] to handle shell errors.
|
||||
TauriShellError(#[from] tauri_plugin_shell::Error),
|
||||
#[error("failed to manage a tokio semaphore: {0}")]
|
||||
/// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors.
|
||||
TokioError(#[from] tokio::sync::AcquireError),
|
||||
#[error("failed to compress a file with flate: {0}")]
|
||||
/// Wrapper around [`flate2::CompressError`] to handle flate compression errors.
|
||||
FlateError(#[from] flate2::CompressError),
|
||||
// im actually the funniest person alive dont @ me
|
||||
#[error("failed to decompress a file with flate: {0}")]
|
||||
/// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors.
|
||||
DeflateError(#[from] flate2::DecompressError),
|
||||
#[error("failed to parse uuids: {0}")]
|
||||
/// Wrapper around [`uuid::Error`] to handle UUID parsing errors.
|
||||
UUIDError(#[from] uuid::Error),
|
||||
#[error("failed to manage serialization of json: {0}")]
|
||||
/// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors.
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
#[error("failed to manage writing and reading of files: {0}")]
|
||||
/// Wrapper around [`std::io::Error`] only to be used if a more defined error isn't available (e.g. when using `zip`)
|
||||
IOError(#[from] std::io::Error),
|
||||
#[error("failed to query the database: {0}")]
|
||||
/// Wrapper around [`prisma_client_rust::QueryError`] to handle DB query errors.
|
||||
DBQueryError(#[from] prisma_client_rust::QueryError),
|
||||
#[error("failed to establish a connection to the database: {0}")]
|
||||
/// Wrapper around [`prisma_client_rust::NewClientError`] to handle errors occuring while programmatically connecting to Prisma.
|
||||
DBConnectionError(#[from] prisma_client_rust::NewClientError),
|
||||
#[error("failed to establish a HTTP connection: {0}")]
|
||||
/// Wrapper around [`reqwest::Error`] to handle HTTP errors.
|
||||
HTTPError(#[from] tauri_plugin_http::reqwest::Error),
|
||||
#[error("failed to download java: {0}")]
|
||||
/// Wrapper around [`game::client::JavaDownloadError`] to handle Java executable downloading.
|
||||
JavaError(#[from] game::client::JavaDownloadError),
|
||||
/// Wrapper around [`zip::result::ZipError`] to handle zip errors.
|
||||
#[error("failed to manage zip files: {0}")]
|
||||
ZipError(#[from] zip::result::ZipError),
|
||||
#[error("failed to authenticate: {0}")]
|
||||
// Wrapper around [`auth::AuthenticationError`] to handle authentication errors.
|
||||
AuthError(#[from] auth::AuthenticationError),
|
||||
#[error(transparent)]
|
||||
AnyhowError(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
/// Alias for a [`Result`] with the error type [`PolyError`].
|
||||
|
|
|
|||
|
|
@ -7,16 +7,16 @@ use polyfrost_launcher::{auth, game};
|
|||
use tauri::{menu::Menu, App};
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(auth::init())
|
||||
.plugin(game::init())
|
||||
.menu(|handle| Menu::new(handle))
|
||||
.setup(setup)
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(auth::init())
|
||||
.plugin(game::init())
|
||||
.menu(|handle| Menu::new(handle))
|
||||
.setup(setup)
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
fn setup(_: &mut App) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,41 @@
|
|||
use std::{fs::File, path::Path};
|
||||
use anyhow::anyhow;
|
||||
use std::{fs::File, path::Path};
|
||||
|
||||
use crate::{PolyError, PolyResult};
|
||||
|
||||
pub fn extract_archive(archive: &Path, dest: &Path) -> PolyResult<()> {
|
||||
let ext = match archive.extension() {
|
||||
Some(ext) => ext.to_str().unwrap(),
|
||||
None => return Err(PolyError::AnyhowError(anyhow!("unsupported operating system")))
|
||||
};
|
||||
let ext = match archive.extension() {
|
||||
Some(ext) => ext.to_str().unwrap(),
|
||||
None => {
|
||||
return Err(PolyError::AnyhowError(anyhow!(
|
||||
"unsupported operating system"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
match ext {
|
||||
"zip" => extract_zip(archive, dest),
|
||||
"gz" => extract_tar_gz(archive, dest),
|
||||
_ => Err(PolyError::AnyhowError(anyhow!("unsupported file extension {:?}", ext)))
|
||||
}
|
||||
match ext {
|
||||
"zip" => extract_zip(archive, dest),
|
||||
"gz" => extract_tar_gz(archive, dest),
|
||||
_ => Err(PolyError::AnyhowError(anyhow!(
|
||||
"unsupported file extension {:?}",
|
||||
ext
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_zip(archive: &Path, dest: &Path) -> PolyResult<()> {
|
||||
let file = File::open(archive).map_err(|err| PolyError::IOError(err))?;
|
||||
let mut archive = zip::ZipArchive::new(file)
|
||||
.map_err(|err| PolyError::ZipError(err));
|
||||
archive?.extract(dest).map_err(|err| PolyError::ZipError(err));
|
||||
Ok(())
|
||||
let file = File::open(archive).map_err(|err| PolyError::IOError(err))?;
|
||||
let mut archive = zip::ZipArchive::new(file).map_err(|err| PolyError::ZipError(err));
|
||||
archive?
|
||||
.extract(dest)
|
||||
.map_err(|err| PolyError::ZipError(err));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn extract_tar_gz(archive: &Path, dest: &Path) -> PolyResult<()> {
|
||||
let file = File::open(archive).map_err(|err| PolyError::IOError(err))?;
|
||||
let tar_gz = flate2::read::GzDecoder::new(file);
|
||||
let mut archive = tar::Archive::new(tar_gz);
|
||||
archive.unpack(dest).map_err(|err| PolyError::IOError(err));
|
||||
Ok(())
|
||||
let file = File::open(archive).map_err(|err| PolyError::IOError(err))?;
|
||||
let tar_gz = flate2::read::GzDecoder::new(file);
|
||||
let mut archive = tar::Archive::new(tar_gz);
|
||||
archive.unpack(dest).map_err(|err| PolyError::IOError(err));
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,34 @@
|
|||
use std::{fs::File, io::{self, Cursor}, path::Path};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, Cursor},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use tauri_plugin_http::reqwest::{Client, ClientBuilder};
|
||||
|
||||
use crate::{constants::USER_AGENT, PolyError, PolyResult};
|
||||
|
||||
pub fn create_client() -> PolyResult<Client> {
|
||||
ClientBuilder::new()
|
||||
.user_agent(USER_AGENT)
|
||||
.build().map_err(|err| PolyError::HTTPError(err))
|
||||
ClientBuilder::new()
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
.map_err(|err| PolyError::HTTPError(err))
|
||||
}
|
||||
|
||||
// can we not make mapping the errors automatic, isnt that the whole point
|
||||
pub async fn download_file(url: &str, path: &Path) -> PolyResult<()> {
|
||||
let response = create_client()?.get(url).send().await.map_err(|err| PolyError::HTTPError(err))?;
|
||||
let mut file = File::create(path).map_err(|err| PolyError::IOError(err))?;
|
||||
let mut content = Cursor::new(response.bytes().await.map_err(|err| PolyError::HTTPError(err))?);
|
||||
io::copy(&mut content, &mut file).map_err(|err| PolyError::IOError(err));
|
||||
Ok(())
|
||||
let response = create_client()?
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| PolyError::HTTPError(err))?;
|
||||
let mut file = File::create(path).map_err(|err| PolyError::IOError(err))?;
|
||||
let mut content = Cursor::new(
|
||||
response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|err| PolyError::HTTPError(err))?,
|
||||
);
|
||||
io::copy(&mut content, &mut file).map_err(|err| PolyError::IOError(err));
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
pub mod file;
|
||||
pub mod http;
|
||||
pub mod file;
|
||||
Loading…
Add table
Reference in a new issue