diff --git a/src-tauri/src/game/client.rs b/src-tauri/src/game/client.rs index f791c916..ff274a2c 100644 --- a/src-tauri/src/game/client.rs +++ b/src-tauri/src/game/client.rs @@ -8,7 +8,7 @@ use uuid::Uuid; use crate::utils::{file, http}; -use super::{clients::vanilla::{VanillaClient, VanillaManifest}, JavaVersion}; +use super::{clients::vanilla::{vanilla_manifest, VanillaClient, VanillaManifest}, JavaVersion}; #[macro_export] macro_rules! create_manifest { @@ -60,8 +60,11 @@ pub fn get_game_client(handle: AppHandle, details: GameClientDetails) -> Box Self where Self: Sized; - fn get_version_urls() -> HashMap where Self: Sized; - fn get_details_from_version(version: String) -> Result> where Self: Sized; + + async fn get_all_version_urls() -> Result, Box> where Self: Sized; + async fn get_minecraft_manifest(version: String) -> Result> where Self: Sized; + + // async fn get_details_from_version(version: String) -> Result> where Self: Sized; fn get_handle(&self) -> &AppHandle; fn get_details(&self) -> &GameClientDetails; diff --git a/src-tauri/src/game/clients/vanilla.rs b/src-tauri/src/game/clients/vanilla.rs index aa120b42..1df71666 100644 --- a/src-tauri/src/game/clients/vanilla.rs +++ b/src-tauri/src/game/clients/vanilla.rs @@ -1,12 +1,16 @@ use std::{collections::HashMap, error::Error}; use async_trait::async_trait; -use tauri::AppHandle; +use serde_json::Value; +use tauri::{AppHandle, Manager}; use tokio::io::{stdout, AsyncWriteExt}; -use crate::{create_client, create_manifest, game::client::{GameClient, GameClientDetails, GameClientType}}; +use crate::{create_client, create_manifest, game::client::{GameClient, GameClientDetails, GameClientType}, utils::http}; + +create_manifest!(VanillaManifest { + // minecraft: vanilla_manifest::MinecraftManifest +}); -create_manifest!(VanillaManifest {}); create_client!(VanillaClient {}); #[async_trait] @@ -18,12 +22,38 @@ impl GameClient for VanillaClient { } } - fn get_version_urls() -> HashMap { - HashMap::new() - } + async fn get_all_version_urls() -> Result, Box> { + const URL: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; + let request = http::create_client().get(URL).send().await?; - fn get_details_from_version(version: String) -> Result> { - Err("a".into()) + let json = request.json::().await?; + + let versions = json.get("versions").ok_or("No versions object")? + .as_array().ok_or("Invalid versions object")?; + + let mut map = HashMap::new(); + + 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")?; + + map.insert(id.to_string(), url.to_string()); + } + + Ok(map) + } + + async fn get_minecraft_manifest(version_url: String) -> Result> { + let request = http::create_client().get(version_url).send().await?; + + match request.json::().await { + Ok(manifest) => Ok(manifest), + Err(e) => Err(e.into()) + } } fn get_handle(&self) -> &AppHandle { @@ -39,7 +69,13 @@ impl GameClient for VanillaClient { } async fn setup(&self) -> Result<(), Box> { - // vanilla_impl::setup_libraries().await?; + 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?; + + let libraries = vanilla_impl::setup_libraries(&manifest.libraries, &libraries_dir).await?; + println!("{:#?}", libraries); // vanilla_impl::setup_natives().await?; // vanilla_impl::setup_assets().await?; Ok(()) @@ -57,50 +93,59 @@ impl GameClient for VanillaClient { } pub mod vanilla_impl { - use std::error::Error; + use std::{error::Error, fs, path::PathBuf}; use serde_json::{Map, Value}; use tauri::AppHandle; - pub async fn setup_libraries(manifest: &Value) -> Result, Box> { - let libraries = match manifest.get("libraries") { - Some(libraries) => { - if libraries.is_array() { - libraries - } else { - return Err("Invalid libraries object".into()) - } - }, - None => return Ok(vec![]) - }; + use crate::utils::http::download_file; - let mut natives: Vec<&Map> = vec![]; - for library in libraries.as_array().unwrap() { - let library: &Map = library.as_object().unwrap(); + use super::vanilla_manifest::{self, Library}; + + pub async fn setup_libraries(libraries: &Vec, libraries_folder: &PathBuf) -> Result, Box> { + let mut natives_ret: Vec<&Library> = vec![]; + let mut libraries_ret: Vec = vec![]; + + for library in libraries { + // let library: &Map = library.as_object().unwrap(); - if let Some(_) = library.get("natives") { - natives.push(library); + // 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")?; + + // 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.natives { + natives_ret.push(library); continue; } - let name = library.get("name").ok_or("No name object")? - .as_str().ok_or("Invalid name object")?; - - 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 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")?; + 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")?)?; + + 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(vec![]) + Ok(libraries_ret) } pub async fn setup_natives() -> Result<(), Box> { @@ -110,4 +155,162 @@ pub mod vanilla_impl { pub async fn setup_assets() -> Result<(), Box> { Ok(()) } +} + +pub mod vanilla_manifest { + 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, + 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, + } + + #[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 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 Library { + pub downloads: Downloads2, + pub name: String, + #[serde(default)] + pub rules: Vec, + pub extract: Option, + pub natives: Option, + } + + #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct Downloads2 { + pub artifact: Option, + pub classifiers: Option, + } + + #[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, + #[serde(rename = "natives-windows-64")] + pub natives_windows_64: Option, + #[serde(rename = "natives-osx")] + pub natives_osx: Option, + #[serde(rename = "natives-linux")] + pub natives_linux: Option, + #[serde(rename = "natives-windows")] + pub natives_windows: Option, + } + + #[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, + } + + #[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, + } + + #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct Natives { + pub windows: String, + pub linux: Option, + pub osx: Option, + } + + #[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, + } + } \ No newline at end of file diff --git a/src/ui/components/overlay/ContextMenu.tsx b/src/ui/components/overlay/ContextMenu.tsx index bcd3387d..913789c1 100644 --- a/src/ui/components/overlay/ContextMenu.tsx +++ b/src/ui/components/overlay/ContextMenu.tsx @@ -1,4 +1,4 @@ -import { type Accessor, type JSX, createEffect, createSignal, onMount } from 'solid-js'; +import { type Accessor, type JSX, createEffect, createSignal } from 'solid-js'; import Popup from './Popup'; type ContextMenuProps = { diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index be896151..8a648e1b 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -8,7 +8,7 @@ import { SearchMdIcon, } from '@untitled-theme/icons-solid'; import type { Accessor, Setter } from 'solid-js'; -import { For, createSignal, untrack } from 'solid-js'; +import { For, createSignal } from 'solid-js'; import { DragDropProvider, DragDropSensors, SortableProvider, closestCenter, createSortable } from '@thisbeyond/solid-dnd'; import type { DragEventHandler, Id } from '@thisbeyond/solid-dnd'; import { useNavigate } from '@solidjs/router'; @@ -18,7 +18,6 @@ import Button from '../components/base/Button'; import Tag from '../components/base/Tag'; import TextField from '../components/base/TextField'; import defaultCover from '../../assets/images/default_instance_cover.jpg'; -import Popup from '../components/overlay/Popup'; import ContextMenu from '../components/overlay/ContextMenu'; // TODO: Replace this into it's own component