204 lines
5.4 KiB
Rust
204 lines
5.4 KiB
Rust
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use chrono::Utc;
|
|
use discord_rich_presence::activity::Timestamps;
|
|
use merge::Merge;
|
|
use onelauncher_entity::prelude::model::*;
|
|
use tokio::process::Command;
|
|
use tokio::sync::RwLock;
|
|
|
|
use crate::api::cluster::{ClusterError, prepare_cluster, update_playtime};
|
|
use crate::api::game::{arguments, log, metadata};
|
|
use crate::api::java::{self, JavaError};
|
|
use crate::api::setting_profiles::dao::get_profile_by_name;
|
|
use crate::api::setting_profiles::get_global_profile;
|
|
use crate::error::LauncherResult;
|
|
use crate::store::credentials::MinecraftCredentials;
|
|
use crate::store::processes::Process;
|
|
use crate::store::{Dirs, State};
|
|
use crate::utils::io;
|
|
|
|
#[tracing::instrument(skip_all)]
|
|
pub async fn launch_minecraft(
|
|
cluster: &mut Cluster,
|
|
creds: MinecraftCredentials,
|
|
force: Option<bool>,
|
|
) -> LauncherResult<Arc<RwLock<Process>>> {
|
|
if cluster.stage.is_downloading() {
|
|
return Err(ClusterError::ClusterDownloading.into());
|
|
}
|
|
|
|
prepare_cluster(cluster, force).await?;
|
|
let mut settings = get_global_profile().await;
|
|
if let Some(name) = &cluster.setting_profile_name
|
|
&& let Some(profile) = get_profile_by_name(name).await?
|
|
{
|
|
settings.merge(profile);
|
|
}
|
|
|
|
let state = State::get().await?;
|
|
let dirs = Dirs::get().await?;
|
|
|
|
if !state.settings.read().await.allow_parallel_running_clusters {
|
|
let running = state.processes.has_running(cluster.id).await;
|
|
if running {
|
|
return Err(ClusterError::ClusterAlreadyRunning.into());
|
|
}
|
|
}
|
|
|
|
let cwd = &io::canonicalize(dirs.clusters_dir().join(cluster.folder_name.clone()))?;
|
|
|
|
let metadata = state.metadata.read().await;
|
|
let versions = &metadata.get_vanilla()?.versions;
|
|
|
|
let version_index = versions
|
|
.iter()
|
|
.position(|it| it.id == cluster.mc_version)
|
|
.ok_or_else(|| anyhow::anyhow!("invalid game version {}", cluster.mc_version))?;
|
|
|
|
let version = versions[version_index].clone();
|
|
let updated = metadata::is_version_updated(version_index, versions);
|
|
|
|
let loader_version = metadata::get_loader_version(
|
|
&cluster.mc_version,
|
|
cluster.mc_loader,
|
|
cluster.mc_loader_version.as_deref(),
|
|
)
|
|
.await?;
|
|
|
|
let version_name = loader_version
|
|
.as_ref()
|
|
.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id));
|
|
|
|
let version_info =
|
|
metadata::download_version_info(&version, loader_version.as_ref(), None, None).await?;
|
|
|
|
let Some(java) = java::get_recommended_java(&version_info, Some(&settings)).await? else {
|
|
return Err(JavaError::MissingJava.into());
|
|
};
|
|
|
|
let java_path = PathBuf::from(&java.absolute_path);
|
|
java::check_java_runtime(&java_path, false).await?;
|
|
|
|
let client_jar_path = dirs
|
|
.versions_dir()
|
|
.join(&version_name)
|
|
.join(format!("{version_name}.jar"));
|
|
|
|
let mut command = if let Some(wrapper) = &settings.hook_wrapper {
|
|
let mut cmd = Command::new(wrapper);
|
|
cmd.arg(&java.absolute_path);
|
|
cmd
|
|
} else {
|
|
Command::new(&java.absolute_path)
|
|
};
|
|
|
|
let env_vars = settings.launch_env.iter().flat_map(|it| {
|
|
it.split(' ').map(|it| {
|
|
let mut split = it.split('=');
|
|
let key = split.next().unwrap_or_default();
|
|
let value = split.next().unwrap_or_default();
|
|
(key.to_string(), value.to_string())
|
|
})
|
|
});
|
|
|
|
#[cfg(target_os = "macos")]
|
|
if std::env::var("CARGO").is_ok() {
|
|
command.env_remove("DYLD_FALLBACK_LIBRARY_PATH");
|
|
}
|
|
|
|
command.env_remove("_JAVA_OPTIONS");
|
|
command.envs(env_vars);
|
|
|
|
let args = version_info.arguments.clone().unwrap_or_default();
|
|
|
|
command
|
|
.args(
|
|
arguments::java_arguments(
|
|
updated,
|
|
args.get(&interpulse::api::minecraft::ArgumentType::Jvm)
|
|
.map(Vec::as_slice),
|
|
&dirs.natives_dir().join(&version_name),
|
|
&dirs.libraries_dir(),
|
|
&arguments::classpaths(
|
|
&dirs.libraries_dir(),
|
|
version_info.libraries.as_slice(),
|
|
&client_jar_path,
|
|
&java.arch,
|
|
updated,
|
|
)?,
|
|
&version_name,
|
|
settings.mem_max.unwrap_or(2048),
|
|
settings.launch_args.clone().unwrap_or_default(),
|
|
&java.arch,
|
|
)?
|
|
.into_iter(),
|
|
)
|
|
.arg(version_info.main_class.clone())
|
|
.args(
|
|
arguments::minecraft_arguments(
|
|
updated,
|
|
args.get(&interpulse::api::minecraft::ArgumentType::Game)
|
|
.map(Vec::as_slice),
|
|
version_info.minecraft_arguments.as_deref(),
|
|
&creds,
|
|
&version.id,
|
|
&version_info.asset_index.id,
|
|
cwd,
|
|
&dirs.assets_dir(),
|
|
version.type_,
|
|
settings.res.unwrap_or_default(),
|
|
&java.arch,
|
|
)?
|
|
.into_iter(),
|
|
)
|
|
.current_dir(cwd);
|
|
|
|
// TODO: Make all options configurable?
|
|
let mc_options = &[(
|
|
String::from("fullscreen"),
|
|
settings.force_fullscreen.unwrap_or(false).to_string(),
|
|
)];
|
|
|
|
if !mc_options.is_empty() {
|
|
let options_path = cwd.join("options.txt");
|
|
let mut options_string = if options_path.exists() {
|
|
io::read_to_string(&options_path).await?
|
|
} else {
|
|
String::new()
|
|
};
|
|
for (key, value) in mc_options {
|
|
let re = regex::Regex::new(&format!(r"(?m)^{}:.*$", regex::escape(key)))?;
|
|
if re.is_match(&options_string) {
|
|
let replaced_string = re
|
|
.replace_all(&options_string, &format!("{key}:{value}"))
|
|
.to_string();
|
|
options_string = replaced_string;
|
|
} else {
|
|
use std::fmt::Write;
|
|
let _ = write!(options_string, "\n{key}:{value}");
|
|
}
|
|
}
|
|
|
|
io::write(&options_path, options_string).await?;
|
|
}
|
|
|
|
update_playtime(cluster.id, 0).await?;
|
|
|
|
let censors = log::create_censors(&creds);
|
|
|
|
if let Some(discord) = &state.rpc {
|
|
discord
|
|
.set_message(
|
|
&format!("Playing {}", cluster.name),
|
|
Some(Timestamps::new().start(Utc::now().timestamp())),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
state
|
|
.processes
|
|
.spawn(cluster.id, cwd.clone(), creds, censors, &settings, command)
|
|
.await
|
|
}
|