feat: it launches!

This commit is contained in:
LynithDev 2024-03-31 13:25:05 +02:00
parent f000ba9bdf
commit b77f48ed90
12 changed files with 138 additions and 53 deletions

View file

@ -16,6 +16,7 @@ pub struct Account {
pub username: String,
pub uuid: String,
pub skins: Vec<AccountSkin>,
pub access_token: String, // TODO deal with this properly
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View file

@ -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 = ";";

View file

@ -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<SetupInfo>;
fn get_cluster(&self) -> &'a Cluster;
fn get_manifest(&self) -> &'a Manifest;
}
pub fn get_arguments(manifest: &MinecraftManifest) -> crate::Result<String> {
// 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]

View file

@ -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(())
}

View file

@ -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<Vec<MinecraftVersion>> {
match client {
ClientType::Vanilla(_) => vanilla::get_versions(file).await,

View file

@ -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<SetupInfo> {
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
}
}

View file

@ -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());
}

View file

@ -47,27 +47,16 @@ pub struct ModernArguments {
pub jvm: Vec<ModernArgumentsItem>,
}
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);

View file

@ -18,6 +18,10 @@ pub fn libraries_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("libraries"))
}
pub fn natives_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("natives"))
}
pub fn java_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("java"))
}
@ -33,3 +37,7 @@ pub fn manifests_dir() -> crate::Result<PathBuf> {
pub fn clients_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("clients"))
}
pub fn game_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("games"))
}

View file

@ -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<String> {
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);

View file

@ -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(())
}

View file

@ -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',
},