From 087e87963e7d1d4c3b0c4508bca65c1f703a2b93 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 17 Mar 2024 19:45:34 +0100 Subject: [PATCH] chore: Start work on JRE downloading --- src-tauri/src/auth/microsoft_auth.rs | 12 +-- src-tauri/src/auth/mod.rs | 1 + src-tauri/src/constants.rs | 5 + src-tauri/src/game/client.rs | 106 ++++++++++++++++++++ src-tauri/src/game/manifest.rs | 11 +++ src-tauri/src/game/mod.rs | 22 ++++- src-tauri/src/game/version.rs | 139 --------------------------- src-tauri/src/lib.rs | 3 +- src-tauri/src/main.rs | 11 ++- src-tauri/src/utils/http.rs | 19 ++++ src-tauri/src/utils/mod.rs | 1 + src/types/game.d.ts | 2 +- src/ui/pages/Browser.tsx | 86 ++--------------- src/ui/pages/Home.tsx | 27 ++---- 14 files changed, 199 insertions(+), 246 deletions(-) create mode 100644 src-tauri/src/constants.rs create mode 100644 src-tauri/src/game/client.rs create mode 100644 src-tauri/src/game/manifest.rs delete mode 100644 src-tauri/src/game/version.rs create mode 100644 src-tauri/src/utils/http.rs create mode 100644 src-tauri/src/utils/mod.rs diff --git a/src-tauri/src/auth/microsoft_auth.rs b/src-tauri/src/auth/microsoft_auth.rs index 0350bb07..6f0571ce 100644 --- a/src-tauri/src/auth/microsoft_auth.rs +++ b/src-tauri/src/auth/microsoft_auth.rs @@ -5,6 +5,8 @@ use tauri::{AppHandle, Manager, Runtime}; use tauri_plugin_http::reqwest::Client; use tauri_plugin_shell::ShellExt; +use crate::{constants::CLIENT_ID, utils::http::create_client}; + use super::{Account, AuthenticationMethod}; #[tauri::command] @@ -17,8 +19,6 @@ pub async fn login_msa(app: tauri::AppHandle) -> Result(handle: &AppHandle, stage: F) -> Result> @@ -47,7 +47,7 @@ impl AuthenticationMethod for MicrosoftAuthenticationMethod { } async fn auth_minecraft(xsts_token: String, user_hash: String) -> Result> { - let response = Client::new() + let response = create_client() .post("https://api.minecraftservices.com/authentication/login_with_xbox") .json(&json!({ "identityToken": format!("XBL3.0 x={};{}", user_hash, xsts_token) @@ -65,7 +65,7 @@ async fn auth_minecraft(xsts_token: String, user_hash: String) -> Result Result> { - let response = Client::new() + let response = create_client() .post("https://xsts.auth.xboxlive.com/xsts/authorize") .json(&json!({ "Properties": { @@ -102,7 +102,7 @@ async fn auth_xsts(token: String) -> Result> { } async fn auth_xbl(code: String) -> Result<(String, String), Box> { - let response = Client::new() + let response = create_client() .post("https://user.auth.xboxlive.com/user/authenticate") .json(&json!({ "Properties": { @@ -131,7 +131,7 @@ async fn auth_xbl(code: String) -> Result<(String, String), Box> { } async fn msa_code_to_token(code: String) -> Result> { - let token = Client::new() + let token = create_client() .post("https://login.live.com/oauth20_token.srf") .form(&[ ("client_id", CLIENT_ID), diff --git a/src-tauri/src/auth/mod.rs b/src-tauri/src/auth/mod.rs index b5b6db9e..d2e206ff 100644 --- a/src-tauri/src/auth/mod.rs +++ b/src-tauri/src/auth/mod.rs @@ -21,6 +21,7 @@ pub struct AccountSkin { 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. diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs new file mode 100644 index 00000000..344bfcc9 --- /dev/null +++ b/src-tauri/src/constants.rs @@ -0,0 +1,5 @@ +pub const NAME: &str = "OneLauncher"; +pub const VERSION: &str = "0.1.0"; + +pub const USER_AGENT: &str = "OneLauncher/1.0.0 (polyfrost.org)"; +pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56"; \ No newline at end of file diff --git a/src-tauri/src/game/client.rs b/src-tauri/src/game/client.rs new file mode 100644 index 00000000..f50ad236 --- /dev/null +++ b/src-tauri/src/game/client.rs @@ -0,0 +1,106 @@ +use std::{env, error::Error, fmt::Display, fs::{self, File}, path::PathBuf}; + +use tauri::{AppHandle, Manager, Runtime}; +use tauri_plugin_http::reqwest::Client; + +use crate::utils::http; + +use super::{manifest::Manifest, JavaVersion}; + +#[allow(async_fn_in_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>; + async fn install(&self) -> Result<(), Box>; + async fn download_java(&self) -> Result { + download_java(self.get_handle(), self.get_manifest().java_version.to_owned()).await + } +} + +pub async fn download_java(handle: &AppHandle, java_version: JavaVersion) -> Result { + let config_dir = match handle.path().app_config_dir() { + Ok(dir) => dir, + Err(_) => return Err(JavaDownloadError::UnsupportedOS) + }; + + let java_dir = config_dir.join("java"); + + if !java_dir.exists() { + match fs::create_dir_all(&java_dir) { + Ok(_) => (), + Err(_) => return Err(JavaDownloadError::PermissionDenied) + }; + } + + // let java_version = &self.get_manifest().java_version; + let os = env::consts::OS; + let archive_type = match os { + "windows" => "zip", + "macos" => "tar.gz", + "linux" => "tar.gz", + _ => return Err(JavaDownloadError::UnsupportedOS) + }; + + 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 + ); + + let response = match http::create_client().get(&url).send().await { + Ok(response) => match response.json::().await { + Ok(json) => json, + Err(_) => return Err(JavaDownloadError::NoJavaVersionFound) + }, + Err(_) => return Err(JavaDownloadError::NoJavaVersionFound) + }; + + if !response.is_array() || response.as_array().unwrap().is_empty() { + return Err(JavaDownloadError::NoJavaVersionFound); + } + + 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 { + 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) +} + +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, + DownloadError, +} \ No newline at end of file diff --git a/src-tauri/src/game/manifest.rs b/src-tauri/src/game/manifest.rs new file mode 100644 index 00000000..6ac3c99e --- /dev/null +++ b/src-tauri/src/game/manifest.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +use super::JavaVersion; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Manifest { + pub name: String, + pub version: String, + pub main_class: String, + pub java_version: JavaVersion, +} \ No newline at end of file diff --git a/src-tauri/src/game/mod.rs b/src-tauri/src/game/mod.rs index 63e99abb..6cd6a451 100644 --- a/src-tauri/src/game/mod.rs +++ b/src-tauri/src/game/mod.rs @@ -1 +1,21 @@ -pub mod version; \ No newline at end of file +use serde::{Deserialize, Serialize}; + +pub mod client; +pub mod manifest; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum JavaVersion { + 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() + } + } +} \ No newline at end of file diff --git a/src-tauri/src/game/version.rs b/src-tauri/src/game/version.rs deleted file mode 100644 index 702028b8..00000000 --- a/src-tauri/src/game/version.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AssetIndex { - pub id: String, - pub sha1: String, - pub size: u64, - pub total_size: u64, - pub url: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct VersionDownload { - pub sha1: String, - pub size: u64, - pub url: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct VersionDownloads { - pub client: VersionDownload, - pub server: VersionDownload, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct JavaVersion { - pub major_version: u16, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RuleOS { - pub name: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Rule { - pub action: String, - pub os: Option, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LibraryArtifact { - pub path: String, - pub sha1: String, - pub size: u64, - pub url: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LibraryDownloads { - pub artifact: LibraryArtifact, - - #[serde(flatten)] - pub classifiers: Option>, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LibraryNatives { - pub linux: Option, - pub osx: Option, - pub windows: Option, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Library { - pub downloads: LibraryDownloads, - pub name: String, - pub natives: Option, - pub rules: Option>, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ArgumentRule { - pub rules: Vec, - pub value: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[serde(untagged)] -pub enum Argument { - String(String), - Rule(ArgumentRule), -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Arguments { - pub game: Vec, - pub jvm: Vec, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ModernVersionManifest { - pub asset_index: AssetIndex, - pub downloads: VersionDownloads, - pub id: String, - pub java_version: JavaVersion, - pub libraries: Vec, - pub main_class: String, - pub minecraft_arguments: String, - pub release_time: String, - - #[serde(rename = "type")] - pub release_type: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LegacyVersionManifest { - pub asset_index: AssetIndex, - pub downloads: VersionDownloads, - pub id: String, - pub java_version: JavaVersion, - pub libraries: Vec, - pub main_class: String, - pub minecraft_arguments: String, - pub release_time: String, - - #[serde(rename = "type")] - pub release_type: String, - - // TODO - // pub logging: Option, -} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 003feead..055f5a42 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,3 +1,4 @@ -#[allow(async_fn_in_trait)] pub mod auth; pub mod game; +pub mod utils; +pub mod constants; \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index fb95bf10..def805c1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,13 +3,20 @@ use std::error::Error; -use polyfrost_launcher::auth; -use tauri::{menu::Menu, App}; +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(app: tauri::AppHandle, window: tauri::Window) { + let res = download_java(&app, JavaVersion::V8).await; + println!("{:#?}", res); +} 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!()) diff --git a/src-tauri/src/utils/http.rs b/src-tauri/src/utils/http.rs new file mode 100644 index 00000000..bd89defc --- /dev/null +++ b/src-tauri/src/utils/http.rs @@ -0,0 +1,19 @@ +use std::{error::Error, fs::File, io::{self, Cursor}, path::Path}; + +use tauri_plugin_http::reqwest::{Client, ClientBuilder}; + +use crate::constants::USER_AGENT; + +pub fn create_client() -> Client { + ClientBuilder::new() + .user_agent(USER_AGENT) + .build().expect("Failed to create HTTP client. Please report this issue on GitHub.") +} + +pub async fn download_file(url: &str, path: &Path) -> Result<(), Box> { + let response = create_client().get(url).send().await?; + let mut file = File::create(path)?; + let mut content = Cursor::new(response.bytes().await?); + io::copy(&mut content, &mut file)?; + Ok(()) +} diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs new file mode 100644 index 00000000..e05256f7 --- /dev/null +++ b/src-tauri/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod http; \ No newline at end of file diff --git a/src/types/game.d.ts b/src/types/game.d.ts index 216a2ac1..0dd620b7 100644 --- a/src/types/game.d.ts +++ b/src/types/game.d.ts @@ -1,3 +1,3 @@ declare namespace game { - export type Client = 'fabric' | 'forge' | 'quilt' | 'vanilla' | 'other'; + export type Client = 'Fabric' | 'Forge' | 'Quilt' | 'Vanilla' | 'Other'; } diff --git a/src/ui/pages/Browser.tsx b/src/ui/pages/Browser.tsx index f7c44e03..fd5006f9 100644 --- a/src/ui/pages/Browser.tsx +++ b/src/ui/pages/Browser.tsx @@ -1,87 +1,15 @@ -import type { DragEventHandler, Id } from '@thisbeyond/solid-dnd'; -import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter, createSortable, useDragDropContext } from '@thisbeyond/solid-dnd'; -import type { JSX } from 'solid-js'; -import { For, createSignal } from 'solid-js'; - -declare module 'solid-js' { - // eslint-disable-next-line ts/no-namespace - namespace JSX { - interface Directives { - sortable: any; - } - } -} - -interface ItemProps { - item: boolean | Id | Node | JSX.ArrayElement | null | undefined; -}; - -function Item(props: ItemProps) { - const id = () => props.item as Id; - // eslint-disable-next-line solid/reactivity - const sortable = createSortable(id()); - - // @ts-expect-error - `state` is not defined in the type definition - const [state] = useDragDropContext(); - - return ( -
- {props.item} -
- ); -} - -function List() { - const [items, setItems] = createSignal([1, 2, 3]); - const [activeItem, setActiveItem] = createSignal(null); - const ids = () => items(); - - const onDragStart: DragEventHandler = ({ draggable }) => setActiveItem(draggable.id as number); - - const onDragEnd: DragEventHandler = ({ draggable, droppable }) => { - if (draggable && droppable) { - const currentItems = ids(); - const fromIndex = currentItems.indexOf(draggable.id as number); - const toIndex = currentItems.indexOf(droppable.id as number); - if (fromIndex !== toIndex) { - const updatedItems = currentItems.slice(); - updatedItems.splice(toIndex, 0, ...updatedItems.splice(fromIndex, 1)); - setItems(updatedItems); - } - } - }; - - return ( - - -
- - {item => } - -
- -
{activeItem()}
-
-
- ); -} +import { invoke } from '@tauri-apps/api/core'; +import Button from '../components/base/Button'; function BrowserPage() { + function downloadJava() { + invoke('download_java_test'); + } + return (

Browser

- +
); } diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index 2a844ae2..1c0bc016 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -7,7 +7,7 @@ import { SearchMdIcon, } from '@untitled-theme/icons-solid'; import { For, createSignal } from 'solid-js'; -import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter, createSortable, useDragDropContext } from '@thisbeyond/solid-dnd'; +import { DragDropProvider, DragDropSensors, SortableProvider, closestCenter, createSortable } from '@thisbeyond/solid-dnd'; import type { DragEventHandler, Id } from '@thisbeyond/solid-dnd'; import image from '../../assets/images/header.png'; import Button from '../components/base/Button'; @@ -100,7 +100,7 @@ function InstanceCard(props: InstanceCardProps) {

{props.name}

- {upperFirst(props.client)} + {props.client} {' '} {props.version} {' '} @@ -167,63 +167,56 @@ function HomePage() { const myInstances: InstanceCardProps[] = [ { name: 'Hypixel', - client: 'vanilla', + client: 'Vanilla', version: '1.8.9', id: 1, }, { name: 'OneConfig ??', - client: 'forge', + client: 'Forge', version: '1.8.9', mods: 12, id: 2, }, { name: 'Fabulously Optimised', - client: 'fabric', + client: 'Fabric', version: '1.20.4', mods: 48, id: 3, }, { name: 'sus client', - client: 'other', + client: 'Other', version: '1.8.9', id: 4, }, { name: 'Hypixel', - client: 'vanilla', + client: 'Vanilla', version: '1.8.9', id: 5, }, { name: 'OneConfig ??', - client: 'forge', + client: 'Forge', version: '1.8.9', mods: 12, id: 6, }, { name: 'Fabulously Optimised', - client: 'fabric', + client: 'Fabric', version: '1.20.4', mods: 48, id: 7, }, { name: 'sus client', - client: 'other', + client: 'Other', version: '1.8.9', id: 8, }, - // { - // name: 'OneConfig ??', - // client: 'forge', - // version: '1.8.9', - // mods: 12, - // id: 5, - // }, ]; return (