diff --git a/onelauncher/src/auth/mod.rs b/onelauncher/src/auth/mod.rs index 0ad3eb44..2dc70a36 100644 --- a/onelauncher/src/auth/mod.rs +++ b/onelauncher/src/auth/mod.rs @@ -16,6 +16,7 @@ pub struct Account { pub username: String, pub uuid: String, pub skins: Vec, + pub access_token: String, // TODO deal with this properly } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/onelauncher/src/constants.rs b/onelauncher/src/constants.rs index 5b1cc774..8bc1a9b6 100644 --- a/onelauncher/src/constants.rs +++ b/onelauncher/src/constants.rs @@ -37,6 +37,7 @@ pub const NATIVE_ARCH: &str = "64"; #[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] pub const NATIVE_ARCH: &str = "64"; + // Minecraft library splitter #[cfg(target_os = "windows")] pub const LIBRARY_SPLITTER: &str = ";"; diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs index 1b3c6160..94fc8093 100644 --- a/onelauncher/src/game/client.rs +++ b/onelauncher/src/game/client.rs @@ -1,9 +1,15 @@ +use std::{io::{stderr, stdout}, path::PathBuf}; + +use anyhow::anyhow; use async_trait::async_trait; use chrono::{DateTime, Local}; use serde::{Deserialize, Serialize}; +use tokio::process; use uuid::Uuid; -use super::{clients::ClientType, minecraft::MinecraftManifest}; +use crate::auth::Account; + +use super::{clients::ClientType, minecraft::{MinecraftManifest, ModernArgumentsItemExt}}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Manifest { @@ -23,22 +29,81 @@ pub struct Cluster { pub client: ClientType, } +#[derive(Debug)] +pub struct LaunchInfo { + pub java: PathBuf, + pub account: Account, + pub mem_min: u32, + pub mem_max: u32, + pub setup: SetupInfo, +} + +#[derive(Debug)] +pub struct SetupInfo { + pub version: String, + pub libraries: String, + pub game_dir: PathBuf, + pub natives_dir: PathBuf, +} + #[async_trait] pub trait ClientTrait<'a>: Send + Sync { - fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self - where - Self: Sized; + fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self where Self: Sized; - async fn launch(&self) -> crate::Result<()>; - async fn setup(&self) -> crate::Result<()>; + async fn launch(&self, info: LaunchInfo) -> crate::Result<()> { + let manifest = &self.get_manifest().minecraft_manifest; - fn get_cluster(&self) -> &'a Cluster - where - Self: Sized; + let args = get_arguments(manifest)? + .replace("${auth_player_name}", &info.account.username) + .replace("${version_name}", &info.setup.version) + .replace("${game_directory}", info.setup.game_dir.to_str().unwrap()) + .replace("${assets_root}", format!("{}/assets", info.setup.game_dir.to_str().unwrap()).as_str()) + .replace("${assets_index_name}", &manifest.asset_index.id) + .replace("${auth_uuid}", &info.account.uuid) + .replace("${auth_access_token}", &info.account.access_token) + .replace("${auth_session}", "0") // TODO: Figure out how to get this session ID. + .replace("${user_properties}", "{}") // TODO: Figure out these properties. + .replace("${user_type}", "1"); - fn get_manifest(&self) -> &'a Manifest - where - Self: Sized; + process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy") + .arg("-XX:-OmitStackTraceInFastThrow") + .arg("-Dminecraft.launcher.brand=onelauncher") + .arg("-Dminecraft.launcher.version=1") + .arg(format!("-Djava.library.path={}", info.setup.natives_dir.to_str().unwrap())) + .arg(format!("-Xms{}M", info.mem_min)) + .arg(format!("-Xmx{}M", info.mem_max)) + .arg("-cp") + .arg(info.setup.libraries) + .arg(manifest.main_class.clone()) + .args(args.split_whitespace()) + .stdout(stdout()) // TODO: Implement better logging + .stderr(stderr()) + .spawn()? + .wait() + .await?; + + Ok(()) + } + + async fn setup(&self) -> crate::Result; + + fn get_cluster(&self) -> &'a Cluster; + + fn get_manifest(&self) -> &'a Manifest; +} + +pub fn get_arguments(manifest: &MinecraftManifest) -> crate::Result { + // Modern + if let Some(arguments) = &manifest.arguments { + return Ok(arguments.game.build()); + } + + // Legacy + if let Some(arguments) = &manifest.minecraft_arguments { + return Ok(arguments.clone()); + } + + Err(anyhow!("No arguments found").into()) } #[macro_export] diff --git a/onelauncher/src/game/client_manager.rs b/onelauncher/src/game/client_manager.rs index 2f67d3bb..9b87f873 100644 --- a/onelauncher/src/game/client_manager.rs +++ b/onelauncher/src/game/client_manager.rs @@ -5,9 +5,9 @@ use chrono::Local; use serde::{de::DeserializeOwned, Serialize}; use uuid::Uuid; -use crate::utils::dirs; +use crate::{auth::Account, utils::dirs}; -use super::{client::{ClientTrait, Cluster, Manifest}, clients::{self, vanilla, ClientType}}; +use super::{client::{ClientTrait, Cluster, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java}; #[derive(Debug, thiserror::Error)] pub enum ClientManagerError { @@ -77,8 +77,25 @@ impl ClientManager { uuid: Uuid, ) -> crate::Result<()> { let client = self.get_impl_uuid(uuid)?; - client.setup().await?; - client.launch().await?; + + let java = java::download_java(&dirs::java_dir()?, client.get_manifest().minecraft_manifest.java_version.major_version).await?; + let setup = client.setup().await?; + + // TODO: Make this configurable + let info = LaunchInfo { + java, + setup, + account: Account { + username: "Player 0".to_string(), + access_token: "0".to_string(), + uuid: Uuid::new_v4().to_string(), + skins: vec![] + }, + mem_min: 1024, + mem_max: 4096, + }; + + client.launch(info).await?; Ok(()) } diff --git a/onelauncher/src/game/clients/mod.rs b/onelauncher/src/game/clients/mod.rs index 7faf775b..47cd7b2d 100644 --- a/onelauncher/src/game/clients/mod.rs +++ b/onelauncher/src/game/clients/mod.rs @@ -14,12 +14,6 @@ pub enum ClientType { Vanilla(VanillaClientProps), } -#[derive(Debug)] -pub struct LaunchInfo { - // This is where things like account info, features etc will go - pub java: PathBuf -} - pub async fn get_versions(client: &ClientType, file: Option<&PathBuf>) -> crate::Result> { match client { ClientType::Vanilla(_) => vanilla::get_versions(file).await, diff --git a/onelauncher/src/game/clients/vanilla.rs b/onelauncher/src/game/clients/vanilla.rs index 96518591..89f542c0 100644 --- a/onelauncher/src/game/clients/vanilla.rs +++ b/onelauncher/src/game/clients/vanilla.rs @@ -1,9 +1,9 @@ use crate::{ constants::{self, MINECRAFT_VERSIONS_MANIFEST}, create_game_client, - game::{client::{ClientTrait, Cluster, Manifest}, minecraft::{Library, MinecraftManifest, MinecraftVersion, RuleListExt}}, + game::{client::{ClientTrait, Cluster, Manifest, SetupInfo}, minecraft::{Library, MinecraftManifest, MinecraftVersion, RuleListExt}}, impl_game_client, - utils::{dirs, http} + utils::{dirs, file, http} }; use anyhow::anyhow; use async_trait::async_trait; @@ -27,16 +27,25 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> { VanillaClient { cluster, manifest } } - async fn launch(&self) -> crate::Result<()> { - Ok(()) - } - - async fn setup(&self) -> crate::Result<()> { + async fn setup(&self) -> crate::Result { let manifest = &self.manifest.minecraft_manifest; let client_path = install_game(manifest).await?; - let libraries = install_libraries(manifest).await?; - Ok(()) + let mut libraries = install_libraries(manifest).await?; + + // Append client path to the libraries string at the end + libraries.push_str(constants::LIBRARY_SPLITTER); + libraries.push_str(client_path.to_str().unwrap()); + + let game_dir = dirs::game_dir()?.join(&self.cluster.id.to_string()); + fs::create_dir_all(&game_dir)?; + + Ok(SetupInfo { + version: manifest.version.clone(), + libraries, + natives_dir: dirs::natives_dir()?, + game_dir, + }) } } @@ -109,6 +118,7 @@ pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> { if !dest.exists() { http::download_file_sha1_check(url.as_str(), &dest, &artifact.sha1).await?; + file::extract_zip(&dest, &dirs::natives_dir()?)?; // TODO: Handle this properly } } diff --git a/onelauncher/src/game/java.rs b/onelauncher/src/game/java.rs index 304ba616..10a81b3e 100644 --- a/onelauncher/src/game/java.rs +++ b/onelauncher/src/game/java.rs @@ -112,7 +112,7 @@ async fn download( } fn extract(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> { - if let Err(err) = file::extract_archive(archive.as_path(), dest.as_path()) { + if let Err(err) = file::extract_archive(archive, dest) { let _ = fs::remove_file(dest.as_path()); return Err(ErrorKind::JavaError(JavaDownloadError::ExtractError(err.to_string())).into()); } diff --git a/onelauncher/src/game/minecraft.rs b/onelauncher/src/game/minecraft.rs index 7b346590..bb200b47 100644 --- a/onelauncher/src/game/minecraft.rs +++ b/onelauncher/src/game/minecraft.rs @@ -47,27 +47,16 @@ pub struct ModernArguments { pub jvm: Vec, } -pub trait ModernArgumentsExt { +pub trait ModernArgumentsItemExt { fn build(&self) -> String; } -impl ModernArgumentsExt for ModernArguments { +impl ModernArgumentsItemExt for [ModernArgumentsItem] { fn build(&self) -> String { // TODO: Implement the custom rules let mut builder = String::new(); - for item in &self.game { - match item { - ModernArgumentsItem::Simple(s) => { - builder.push_str(s); - builder.push(' '); - }, - - ModernArgumentsItem::Rule(_) => {} - } - } - - for item in &self.jvm { + for item in self { match item { ModernArgumentsItem::Simple(s) => { builder.push_str(s); diff --git a/onelauncher/src/utils/dirs.rs b/onelauncher/src/utils/dirs.rs index bce9f719..83914640 100644 --- a/onelauncher/src/utils/dirs.rs +++ b/onelauncher/src/utils/dirs.rs @@ -18,6 +18,10 @@ pub fn libraries_dir() -> crate::Result { Ok(app_config_dir()?.join("libraries")) } +pub fn natives_dir() -> crate::Result { + Ok(app_config_dir()?.join("natives")) +} + pub fn java_dir() -> crate::Result { Ok(app_config_dir()?.join("java")) } @@ -33,3 +37,7 @@ pub fn manifests_dir() -> crate::Result { pub fn clients_dir() -> crate::Result { Ok(app_config_dir()?.join("clients")) } + +pub fn game_dir() -> crate::Result { + Ok(app_config_dir()?.join("games")) +} diff --git a/onelauncher/src/utils/file.rs b/onelauncher/src/utils/file.rs index f4083e0b..8b8d1012 100644 --- a/onelauncher/src/utils/file.rs +++ b/onelauncher/src/utils/file.rs @@ -3,7 +3,7 @@ use sha1::{Digest, Sha1}; use std::{ fs::File, io::BufReader, - path::{Path, PathBuf}, + path::PathBuf, }; use crate::ErrorKind; @@ -18,7 +18,7 @@ pub fn file_sha1(file: &PathBuf) -> crate::Result { Ok(format!("{:x}", hash)) } -pub fn extract_archive(archive: &Path, dest: &Path) -> crate::Result<()> { +pub fn extract_archive(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> { let ext = match archive.extension() { Some(ext) => ext.to_str().unwrap(), None => return Err(ErrorKind::AnyhowError(anyhow!("unsupported operating system")).into()), @@ -31,7 +31,7 @@ pub fn extract_archive(archive: &Path, dest: &Path) -> crate::Result<()> { } } -pub fn extract_zip(archive: &Path, dest: &Path) -> crate::Result<()> { +pub fn extract_zip(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> { let file = File::open(archive).map_err(|err| ErrorKind::IOError(err))?; let archive = zip::ZipArchive::new(file).map_err(|err| ErrorKind::ZipError(err)); archive? @@ -40,7 +40,7 @@ pub fn extract_zip(archive: &Path, dest: &Path) -> crate::Result<()> { Ok(()) } -pub fn extract_tar_gz(archive: &Path, dest: &Path) -> crate::Result<()> { +pub fn extract_tar_gz(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> { let file = File::open(archive).map_err(|err| ErrorKind::IOError(err))?; let tar_gz = flate2::read::GzDecoder::new(file); let mut archive = tar::Archive::new(tar_gz); diff --git a/src-tauri/src/api/game.rs b/src-tauri/src/api/game.rs index c395c782..b2aa982b 100644 --- a/src-tauri/src/api/game.rs +++ b/src-tauri/src/api/game.rs @@ -45,7 +45,7 @@ pub async fn launch_cluster( uuid: Uuid, ) -> Result<(), String> { let manager = &mut state.lock().await.client_manager; - manager.launch_cluster(uuid).await?; // TODO: Change how this works + manager.launch_cluster(uuid).await?; Ok(()) } diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index da25dbe5..b1b990a3 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -190,8 +190,8 @@ function HomePage() { async function newInstance() { await createCluster({ - name: 'New Cluster', - version: '1.8.9', + name: 'Modern cluster', + version: '1.20.4', client: { type: 'Vanilla', },