feat: Downloading JREs

This commit is contained in:
LynithDev 2024-03-17 22:03:08 +01:00
parent 087e87963e
commit af9c628612
10 changed files with 394 additions and 57 deletions

225
src-tauri/Cargo.lock generated
View file

@ -17,6 +17,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aho-corasick"
version = "1.1.2"
@ -68,6 +79,17 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
[[package]]
name = "async-trait"
version = "0.1.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
]
[[package]]
name = "atk"
version = "0.18.0"
@ -124,6 +146,12 @@ version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -216,6 +244,27 @@ dependencies = [
"serde",
]
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "cairo-rs"
version = "0.18.5"
@ -288,6 +337,10 @@ name = "cc"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
dependencies = [
"jobserver",
"libc",
]
[[package]]
name = "cesu8"
@ -347,6 +400,16 @@ dependencies = [
"windows-targets 0.52.4",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "cocoa"
version = "0.25.0"
@ -393,6 +456,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "convert_case"
version = "0.4.0"
@ -591,6 +660,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
@ -794,6 +864,18 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "filetime"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"windows-sys 0.52.0",
]
[[package]]
name = "flate2"
version = "1.0.28"
@ -1294,6 +1376,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "html5ever"
version = "0.26.0"
@ -1458,6 +1549,15 @@ dependencies = [
"cfb",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -1549,6 +1649,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.69"
@ -2041,12 +2150,35 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"hmac",
"password-hash",
"sha2",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -2236,12 +2368,17 @@ dependencies = [
name = "polyfrost-launcher"
version = "0.0.0"
dependencies = [
"async-trait",
"flate2",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-build",
"tauri-plugin-http",
"tauri-plugin-shell",
"uuid",
"zip",
]
[[package]]
@ -2792,6 +2929,17 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
@ -2963,6 +3111,12 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "swift-rs"
version = "1.0.6"
@ -3087,6 +3241,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "tar"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "target-lexicon"
version = "0.12.14"
@ -4483,3 +4648,63 @@ name = "x11rb-protocol"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34"
[[package]]
name = "xattr"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f"
dependencies = [
"libc",
"linux-raw-sys 0.4.13",
"rustix",
]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"aes",
"byteorder",
"bzip2",
"constant_time_eq",
"crc32fast",
"crossbeam-utils",
"flate2",
"hmac",
"pbkdf2",
"sha1",
"time",
"zstd",
]
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.9+zstd.1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
dependencies = [
"cc",
"pkg-config",
]

View file

@ -14,6 +14,11 @@ serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
tauri-plugin-shell = "2.0.0-beta.2"
tauri-plugin-http = "2.0.0-beta"
flate2 = "1.0.28"
tar = "0.4.40"
zip = "0.6.6"
uuid = "1.7.0"
async-trait = "0.1.78"
[features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!

View file

@ -0,0 +1,3 @@
[default]
description = "Default permissions for the plugin"
permissions = [ "launch_game" ]

View file

@ -1,25 +1,14 @@
use std::{error::Error, io::{Read, Write}, net::{SocketAddr, TcpListener, TcpStream}};
use serde_json::json;
use tauri::{AppHandle, Manager, Runtime};
use tauri_plugin_http::reqwest::Client;
use tauri::{AppHandle, Runtime};
use tauri_plugin_shell::ShellExt;
use crate::{constants::CLIENT_ID, utils::http::create_client};
use super::{Account, AuthenticationMethod};
#[tauri::command]
pub 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())
}
}
struct MicrosoftAuthenticationMethod;
pub(super) struct MicrosoftAuthenticationMethod;
impl AuthenticationMethod for MicrosoftAuthenticationMethod {
async fn auth<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> Result<Account, Box<dyn Error>>
where F: Fn(String, u8, bool) -> () {

View file

@ -1,9 +1,11 @@
use std::error::Error;
use serde::{Deserialize, Serialize};
use tauri::{plugin::TauriPlugin, AppHandle, Runtime};
use tauri::{plugin::TauriPlugin, AppHandle, Manager, Runtime};
use tauri_plugin_http::reqwest::Client;
use self::microsoft_auth::MicrosoftAuthenticationMethod;
mod microsoft_auth;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -49,7 +51,17 @@ pub trait AuthenticationMethod {
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
tauri::plugin::Builder::new("auth")
.invoke_handler(tauri::generate_handler![
microsoft_auth::login_msa
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())
}
}

View file

@ -1,16 +1,14 @@
use std::{env, error::Error, fmt::Display, fs::{self, File}, path::PathBuf};
use std::{env, error::Error, fs, path::PathBuf};
use serde_json::Value;
use tauri::{AppHandle, Manager, Runtime};
use tauri_plugin_http::reqwest::Client;
use crate::utils::http;
use crate::utils::{file, http};
use super::{manifest::Manifest, JavaVersion};
#[allow(async_fn_in_trait)]
#[async_trait::async_trait]
pub trait GameClient {
fn from_manifest(manifest: Manifest) -> Self;
fn get_handle(&self) -> &AppHandle;
fn get_manifest(&self) -> &Manifest;
async fn launch(&self) -> Result<(), Box<dyn Error>>;
@ -20,7 +18,7 @@ pub trait GameClient {
}
}
pub async fn download_java<R: Runtime>(handle: &AppHandle<R>, java_version: JavaVersion) -> Result<PathBuf, JavaDownloadError> {
async fn download_java<R: Runtime>(handle: &AppHandle<R>, java_version: JavaVersion) -> Result<PathBuf, JavaDownloadError> {
let config_dir = match handle.path().app_config_dir() {
Ok(dir) => dir,
Err(_) => return Err(JavaDownloadError::UnsupportedOS)
@ -44,6 +42,53 @@ pub async fn download_java<R: Runtime>(handle: &AppHandle<R>, java_version: Java
_ => return Err(JavaDownloadError::UnsupportedOS)
};
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(JavaDownloadError::NoJavaVersionFound)
};
return Ok(file.join("bin").join("java"));
}
Err(JavaDownloadError::NoJavaVersionFound)
}
pub fn get_arch() -> String {
let arch = env::consts::ARCH;
match arch {
"x86" => "x86",
"x86_64" => "x64",
"arm" => "aarch32",
"aarch64" => "aarch64",
_ => "unsupported"
}.to_string()
}
#[derive(Debug)]
pub enum JavaDownloadError {
NoJavaVersionFound,
PermissionDenied,
UnsupportedOS,
UnsupportedArch,
ExtractError,
DownloadError,
}
async fn get_java_versions(java_version: JavaVersion, os: &str, archive_type: &str) -> Result<Value, JavaDownloadError> {
let url = format!("
https://api.azul.com/metadata/v1/zulu/packages/\
?java_version={}\
@ -69,38 +114,28 @@ pub async fn download_java<R: Runtime>(handle: &AppHandle<R>, java_version: Java
return Err(JavaDownloadError::NoJavaVersionFound);
}
Ok(response)
}
async fn download(java_version: JavaVersion, os: &str, archive_type: &str, archive: &PathBuf) -> Result<(), JavaDownloadError> {
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();
let archive_name = format!("zulu-{}.{}", java_version.to_string(), archive_type);
let tmp_archive = java_dir.join(&archive_name);
if let Err(err) = http::download_file(download_url, tmp_archive.as_path()).await {
if let Err(err) = http::download_file(download_url, archive.as_path()).await {
eprintln!("{}", err);
return Err(JavaDownloadError::DownloadError);
};
// TODO: Extract the archive and delete the archive after a SUCCESSFUL extraction.
// Name the folder containing the JRE `zulu-{version}-{arch}`
Ok(tmp_archive)
Ok(())
}
pub fn get_arch() -> String {
let arch = env::consts::ARCH;
match arch {
"x86" => "x86",
"x86_64" => "x64",
"arm" => "aarch32",
"aarch64" => "aarch64",
_ => "unsupported"
}.to_string()
}
fn extract(archive: &PathBuf, dest: &PathBuf) -> Result<(), JavaDownloadError> {
if let Err(err) = file::extract_archive(archive.as_path(), dest.as_path()) {
eprintln!("{}", err);
let _ = fs::remove_file(dest.as_path());
return Err(JavaDownloadError::ExtractError);
}
#[derive(Debug)]
pub enum JavaDownloadError {
NoJavaVersionFound,
PermissionDenied,
UnsupportedOS,
UnsupportedArch,
DownloadError,
let _ = fs::remove_file(archive.as_path());
Ok(())
}

View file

@ -1,4 +1,7 @@
use std::sync::Mutex;
use serde::{Deserialize, Serialize};
use tauri::{plugin::TauriPlugin, Manager};
pub mod client;
pub mod manifest;
@ -18,4 +21,48 @@ impl ToString for JavaVersion {
JavaVersion::V17 => "17".to_string()
}
}
}
// Tauri plugin for the game module
pub struct GameManagerState {
pub selected_client: Option<Box<dyn client::GameClient>>,
}
unsafe impl Send for GameManagerState {}
impl Default for GameManagerState {
fn default() -> Self {
GameManagerState {
selected_client: None
}
}
}
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
tauri::plugin::Builder::new("game")
.setup(|app, _| {
app.manage(Mutex::new(GameManagerState::default()));
Ok(())
})
.invoke_handler(tauri::generate_handler![
launch_game
])
.build()
}
enum LaunchGameError {
NoGameSelected
}
#[tauri::command]
async fn launch_game<R: tauri::Runtime>(app: tauri::AppHandle<R>) -> Result<(), u8> {
let state = app.state::<Mutex<GameManagerState>>();
let state = state.lock().unwrap();
match state.selected_client {
Some(ref client) => println!("Launching game: {:?}", client.get_manifest().name),
None => println!("No game selected")
}
Err(LaunchGameError::NoGameSelected as u8)
}

View file

@ -3,20 +3,13 @@
use std::error::Error;
use polyfrost_launcher::{auth, game::{client::{download_java, GameClient}, JavaVersion}};
use tauri::{generate_handler, menu::Menu, App, Runtime};
#[tauri::command]
async fn download_java_test<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) {
let res = download_java(&app, JavaVersion::V8).await;
println!("{:#?}", res);
}
use polyfrost_launcher::auth;
use tauri::{menu::Menu, App};
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(auth::init())
.invoke_handler(generate_handler![download_java_test])
.menu(|handle| Menu::new(handle))
.setup(setup)
.run(tauri::generate_context!())

View file

@ -0,0 +1,27 @@
use std::{error::Error, fs::File, path::Path};
pub fn extract_archive(archive: &Path, dest: &Path) -> Result<(), Box<dyn Error>> {
let ext = match archive.extension() {
Some(ext) => ext.to_str().unwrap(),
None => return Err("Archive has no extension".into())
};
match ext {
"zip" => extract_zip(archive, dest),
"gz" => extract_tar_gz(archive, dest),
_ => Err("Unsupported archive type".into())
}
}
pub fn extract_zip(archive: &Path, dest: &Path) -> Result<(), Box<dyn Error>> {
let mut archive = zip::ZipArchive::new(File::open(archive)?)?;
archive.extract(dest)?;
Ok(())
}
pub fn extract_tar_gz(archive: &Path, dest: &Path) -> Result<(), Box<dyn Error>> {
let tar_gz = flate2::read::GzDecoder::new(File::open(archive)?);
let mut archive = tar::Archive::new(tar_gz);
archive.unpack(dest)?;
Ok(())
}

View file

@ -1 +1,2 @@
pub mod http;
pub mod http;
pub mod file;