mirror of
https://github.com/modrinth/code.git
synced 2026-08-27 01:54:47 +00:00
feat: instances v2 (#6431)
* feat: base of instances v2 * feat: use old profiles with compat layer * prototype: instances v2 * fix: install_from using profile * fix: skins migration fix * fix: frontend still using profile path * fix: add update proj multiselect guard * fix: cargo fmt * fix: content missing fields * feat: break up app-lib/api/instance.rs * fix: check_content_updates mismatch * fix: updater modal cleanup w/new structure * feat: better update all handling * fix: remove preview_update_all * fix: feedback on bulk update + lint * fix: rem transitions * fix: change to jsonb * feat: app db backup after update * fix: lint * fix: sqlx prepare + use sqlx macros * fix: lint * fix: bugs * feat: defuck the installing process up * fix: bug of hell * fix: shear * fix: fmt * fix: install progress spacing + change mc/content/overrides to bytes * fix: lint * fix: prepr * fix: navtabs anim not working in app * fix: worlds.vue improvements + browse page fixes * feat: optimise queries + adapter fns * fix: lint * fix: lint * feat: shared modrinth-content-management crate (#6469) * feat: disable warnings setting * feat: add instances shortcuts (#6329) * Add modrinth://launch deep link to start a profile Support external profile launching via modrinth://launch/{profile_path} for integrations such as Stream Deck. * Change route to /launch/profile/{id} for future extensibility * fix: ensure profile path is url decoded * fix: URL-decode profile path from deep link * fix: use urlencoding crate for URL decoding * feat: implement app instance shortcuts * feat: change windows shortcut creation to use windows api instead * feat: implement creating a shortcut launching world/server * format * fmt * fix multiline inline tables * pnpm prepr * feat: move create shortcut to last item * refactor: split up shortcuts.rs for individual platforms * refactor: turn profile launch url into url type * use string literal and add safety comment * pt2 * refactor: rename anything that's profile into instance * update mac shortcut --------- Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> --------- Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com>
This commit is contained in:
co-authored by
DJCheesusReal
Truman Gao
parent
ef4044534f
commit
734720e11e
@@ -1,7 +1,7 @@
|
||||
//! Minecraft CLI argument logic
|
||||
use crate::instance::QuickPlayType;
|
||||
use crate::launcher::quick_play_version::QuickPlayServerVersion;
|
||||
use crate::launcher::{QuickPlayVersion, parse_rules};
|
||||
use crate::profile::QuickPlayType;
|
||||
use crate::state::Credentials;
|
||||
use crate::{
|
||||
state::{MemorySettings, WindowSize},
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
//! Downloader for Minecraft data
|
||||
|
||||
use crate::install::{
|
||||
InstallPhaseDetails, InstallPhaseId, InstallProgress,
|
||||
InstallProgressReporter,
|
||||
};
|
||||
use crate::instance::QuickPlayType;
|
||||
use crate::launcher::parse_rules;
|
||||
use crate::profile::QuickPlayType;
|
||||
use crate::{
|
||||
event::{
|
||||
LoadingBarId,
|
||||
@@ -21,21 +25,386 @@ use daedalus::{
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use reqwest::Method;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
},
|
||||
};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
const MINECRAFT_DOWNLOAD_PROGRESS_MIN_BYTES: u64 = 256 * 1024;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MinecraftDownloadProgress {
|
||||
reporter: InstallProgressReporter,
|
||||
details: InstallPhaseDetails,
|
||||
current: Arc<AtomicU64>,
|
||||
total: Arc<AtomicU64>,
|
||||
last_reported: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
impl MinecraftDownloadProgress {
|
||||
async fn new(
|
||||
reporter: InstallProgressReporter,
|
||||
details: InstallPhaseDetails,
|
||||
total: u64,
|
||||
) -> crate::Result<Self> {
|
||||
let progress = Self {
|
||||
reporter,
|
||||
details,
|
||||
current: Arc::new(AtomicU64::new(0)),
|
||||
total: Arc::new(AtomicU64::new(total)),
|
||||
last_reported: Arc::new(AtomicU64::new(0)),
|
||||
};
|
||||
|
||||
if total > 0 {
|
||||
progress.emit_progress(0, total).await?;
|
||||
}
|
||||
|
||||
Ok(progress)
|
||||
}
|
||||
|
||||
async fn add_total(&self, total: u64) -> crate::Result<()> {
|
||||
if total == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let total = self.total.fetch_add(total, Ordering::Relaxed) + total;
|
||||
self.emit_if_needed(self.current.load(Ordering::Relaxed), total, true)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn add_bytes(&self, bytes: u64) -> crate::Result<()> {
|
||||
if bytes == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let current = self.current.fetch_add(bytes, Ordering::Relaxed) + bytes;
|
||||
let total = self.total.load(Ordering::Relaxed);
|
||||
self.emit_if_needed(current, total, false).await
|
||||
}
|
||||
|
||||
async fn emit_if_needed(
|
||||
&self,
|
||||
current: u64,
|
||||
total: u64,
|
||||
force: bool,
|
||||
) -> crate::Result<()> {
|
||||
if total == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let min_delta =
|
||||
(total / 200).max(MINECRAFT_DOWNLOAD_PROGRESS_MIN_BYTES);
|
||||
let last_reported = self.last_reported.load(Ordering::Relaxed);
|
||||
if !force
|
||||
&& current < total
|
||||
&& current.saturating_sub(last_reported) < min_delta
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.last_reported.store(current, Ordering::Relaxed);
|
||||
self.emit_progress(current, total).await
|
||||
}
|
||||
|
||||
async fn emit_progress(
|
||||
&self,
|
||||
current: u64,
|
||||
total: u64,
|
||||
) -> crate::Result<()> {
|
||||
self.reporter
|
||||
.update(
|
||||
InstallPhaseId::DownloadingMinecraft,
|
||||
Some(InstallProgress {
|
||||
current: current.min(total),
|
||||
total,
|
||||
secondary: None,
|
||||
}),
|
||||
self.details.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_minecraft_file(
|
||||
st: &State,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
expected_size: Option<u64>,
|
||||
progress: Option<MinecraftDownloadProgress>,
|
||||
) -> crate::Result<bytes::Bytes> {
|
||||
let Some(progress) = progress else {
|
||||
return fetch(url, sha1, None, None, &st.fetch_semaphore, &st.pool)
|
||||
.await;
|
||||
};
|
||||
|
||||
let last_downloaded = Arc::new(AtomicU64::new(0));
|
||||
let mut progress_fn = {
|
||||
let progress = progress.clone();
|
||||
let last_downloaded = last_downloaded.clone();
|
||||
move |downloaded: u64,
|
||||
_total: u64|
|
||||
-> Pin<Box<dyn Future<Output = crate::Result<()>> + Send>> {
|
||||
let previous =
|
||||
last_downloaded.swap(downloaded, Ordering::Relaxed);
|
||||
let delta = downloaded.saturating_sub(previous);
|
||||
let progress = progress.clone();
|
||||
Box::pin(async move { progress.add_bytes(delta).await })
|
||||
}
|
||||
};
|
||||
|
||||
let bytes = fetch_advanced_with_progress(
|
||||
Method::GET,
|
||||
url,
|
||||
sha1,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&st.fetch_semaphore,
|
||||
&st.pool,
|
||||
Some(&mut progress_fn as &mut FetchProgressFn<'_>),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(expected_size) = expected_size {
|
||||
let downloaded = last_downloaded.load(Ordering::Relaxed);
|
||||
progress
|
||||
.add_bytes(expected_size.saturating_sub(downloaded))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn should_download(path_exists: bool, force: bool) -> bool {
|
||||
!path_exists || force
|
||||
}
|
||||
|
||||
fn missing_client_bytes(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
force: bool,
|
||||
) -> crate::Result<u64> {
|
||||
let client_download = version
|
||||
.downloads
|
||||
.get(&d::minecraft::DownloadType::Client)
|
||||
.ok_or(
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"No client downloads exist for version {}",
|
||||
version.id
|
||||
))
|
||||
.as_error(),
|
||||
)?;
|
||||
let path = st
|
||||
.directories
|
||||
.version_dir(&version.id)
|
||||
.join(format!("{}.jar", version.id));
|
||||
|
||||
Ok(if should_download(path.exists(), force) {
|
||||
client_download.size as u64
|
||||
} else {
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
fn missing_assets_index_bytes(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
force: bool,
|
||||
) -> u64 {
|
||||
let path = st
|
||||
.directories
|
||||
.assets_index_dir()
|
||||
.join(format!("{}.json", &version.asset_index.id));
|
||||
|
||||
if should_download(path.exists(), force) {
|
||||
version.asset_index.size as u64
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_log_config_bytes(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
force: bool,
|
||||
) -> u64 {
|
||||
let log_download = version
|
||||
.logging
|
||||
.as_ref()
|
||||
.and_then(|x| x.get(&LoggingSide::Client));
|
||||
let Some(LoggingConfiguration::Log4j2Xml {
|
||||
file: log_download, ..
|
||||
}) = log_download
|
||||
else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let path = st.directories.log_configs_dir().join(&log_download.id);
|
||||
if should_download(path.exists(), force) {
|
||||
log_download.size as u64
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_asset_bytes(
|
||||
st: &State,
|
||||
with_legacy: bool,
|
||||
index: &AssetsIndex,
|
||||
force: bool,
|
||||
) -> u64 {
|
||||
index
|
||||
.objects
|
||||
.iter()
|
||||
.filter_map(|(name, asset)| {
|
||||
let hash = &asset.hash;
|
||||
let object_path = st.directories.object_dir(hash);
|
||||
let legacy_path = st.directories.legacy_assets_dir().join(
|
||||
name.replace('/', &String::from(std::path::MAIN_SEPARATOR)),
|
||||
);
|
||||
let should_fetch_object =
|
||||
should_download(object_path.exists(), force);
|
||||
let should_fetch_legacy =
|
||||
(with_legacy && !legacy_path.exists()) || force;
|
||||
|
||||
(should_fetch_object || should_fetch_legacy)
|
||||
.then_some(asset.size as u64)
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn missing_library_bytes(
|
||||
st: &State,
|
||||
libraries: &[Library],
|
||||
java_arch: &str,
|
||||
force: bool,
|
||||
minecraft_updated: bool,
|
||||
) -> crate::Result<u64> {
|
||||
let mut total = 0;
|
||||
|
||||
for library in libraries {
|
||||
if let Some(rules) = &library.rules
|
||||
&& !parse_rules(
|
||||
rules,
|
||||
java_arch,
|
||||
&QuickPlayType::None,
|
||||
minecraft_updated,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if !library.downloadable {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((os_key, classifiers)) =
|
||||
library.natives_os_key_and_classifiers(java_arch)
|
||||
{
|
||||
let parsed_key =
|
||||
os_key.replace("${arch}", crate::util::platform::ARCH_WIDTH);
|
||||
|
||||
if let Some(native) = classifiers.get(&parsed_key) {
|
||||
total += native.size as u64;
|
||||
}
|
||||
} else {
|
||||
let artifact_path = d::get_path_from_artifact(&library.name)?;
|
||||
let path = st.directories.libraries_dir().join(&artifact_path);
|
||||
|
||||
if path.exists() && !force {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(artifact) = library
|
||||
.downloads
|
||||
.as_ref()
|
||||
.and_then(|downloads| downloads.artifact.as_ref())
|
||||
&& !artifact.url.is_empty()
|
||||
{
|
||||
total += artifact.size as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn missing_initial_minecraft_bytes(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
java_arch: &str,
|
||||
force: bool,
|
||||
minecraft_updated: bool,
|
||||
) -> crate::Result<u64> {
|
||||
Ok(missing_client_bytes(st, version, force)?
|
||||
+ missing_assets_index_bytes(st, version, force)
|
||||
+ missing_log_config_bytes(st, version, force)
|
||||
+ missing_library_bytes(
|
||||
st,
|
||||
version.libraries.as_slice(),
|
||||
java_arch,
|
||||
force,
|
||||
minecraft_updated,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(st, version))]
|
||||
pub async fn download_minecraft(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
loading_bar: &LoadingBarId,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
java_arch: &str,
|
||||
force: bool,
|
||||
minecraft_updated: bool,
|
||||
reporter: Option<InstallProgressReporter>,
|
||||
phase_details: InstallPhaseDetails,
|
||||
) -> crate::Result<()> {
|
||||
tracing::info!("Downloading Minecraft version {}", version.id);
|
||||
let progress = if let Some(reporter) = reporter {
|
||||
Some(
|
||||
MinecraftDownloadProgress::new(
|
||||
reporter,
|
||||
phase_details,
|
||||
missing_initial_minecraft_bytes(
|
||||
st,
|
||||
version,
|
||||
java_arch,
|
||||
force,
|
||||
minecraft_updated,
|
||||
)?,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 5
|
||||
let assets_index =
|
||||
download_assets_index(st, version, Some(loading_bar), force).await?;
|
||||
let assets_index = download_assets_index(
|
||||
st,
|
||||
version,
|
||||
loading_bar,
|
||||
force,
|
||||
progress.clone(),
|
||||
)
|
||||
.await?;
|
||||
if let Some(progress) = &progress {
|
||||
progress
|
||||
.add_total(missing_asset_bytes(
|
||||
st,
|
||||
version.assets == "legacy",
|
||||
&assets_index,
|
||||
force,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
let amount = if version.processors.as_ref().is_some_and(|x| !x.is_empty()) {
|
||||
25.0
|
||||
@@ -45,10 +414,10 @@ pub async fn download_minecraft(
|
||||
|
||||
tokio::try_join! {
|
||||
// Total loading sums to 90/60
|
||||
download_client(st, version, Some(loading_bar), force), // 9
|
||||
download_log_config(st, version, Some(loading_bar), force),
|
||||
download_assets(st, version.assets == "legacy", &assets_index, Some(loading_bar), amount, force), // 40
|
||||
download_libraries(st, version.libraries.as_slice(), &version.id, Some(loading_bar), amount, java_arch, force, minecraft_updated) // 40
|
||||
download_client(st, version, loading_bar, force, progress.clone()), // 9
|
||||
download_log_config(st, version, loading_bar, force, progress.clone()),
|
||||
download_assets(st, version.assets == "legacy", &assets_index, loading_bar, amount, force, progress.clone()), // 40
|
||||
download_libraries(st, version.libraries.as_slice(), &version.id, loading_bar, amount, java_arch, force, minecraft_updated, progress.clone()) // 40
|
||||
}?;
|
||||
|
||||
tracing::info!("Done downloading Minecraft!");
|
||||
@@ -129,6 +498,7 @@ pub async fn download_client(
|
||||
version_info: &GameVersionInfo,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
force: bool,
|
||||
progress: Option<MinecraftDownloadProgress>,
|
||||
) -> crate::Result<()> {
|
||||
let version = &version_info.id;
|
||||
tracing::debug!("Locating client for version {version}");
|
||||
@@ -147,13 +517,12 @@ pub async fn download_client(
|
||||
.join(format!("{version}.jar"));
|
||||
|
||||
if !path.exists() || force {
|
||||
let bytes = fetch(
|
||||
let bytes = fetch_minecraft_file(
|
||||
st,
|
||||
&client_download.url,
|
||||
Some(&client_download.sha1),
|
||||
None,
|
||||
None,
|
||||
&st.fetch_semaphore,
|
||||
&st.pool,
|
||||
Some(client_download.size as u64),
|
||||
progress,
|
||||
)
|
||||
.await?;
|
||||
write(&path, &bytes, &st.io_semaphore).await?;
|
||||
@@ -174,6 +543,7 @@ pub async fn download_assets_index(
|
||||
version: &GameVersionInfo,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
force: bool,
|
||||
progress: Option<MinecraftDownloadProgress>,
|
||||
) -> crate::Result<AssetsIndex> {
|
||||
tracing::debug!("Loading assets index");
|
||||
let path = st
|
||||
@@ -187,16 +557,15 @@ pub async fn download_assets_index(
|
||||
.await
|
||||
.and_then(|ref it| Ok(serde_json::from_slice(it)?))
|
||||
} else {
|
||||
let index = fetch_json(
|
||||
Method::GET,
|
||||
let index = fetch_minecraft_file(
|
||||
st,
|
||||
&version.asset_index.url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&st.fetch_semaphore,
|
||||
&st.pool,
|
||||
Some(version.asset_index.size as u64),
|
||||
progress,
|
||||
)
|
||||
.await?;
|
||||
let index = serde_json::from_slice(&index)?;
|
||||
write(&path, &serde_json::to_vec(&index)?, &st.io_semaphore).await?;
|
||||
tracing::info!("Fetched assets index");
|
||||
Ok(index)
|
||||
@@ -218,6 +587,7 @@ pub async fn download_assets(
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
loading_amount: f64,
|
||||
force: bool,
|
||||
progress: Option<MinecraftDownloadProgress>,
|
||||
) -> crate::Result<()> {
|
||||
tracing::debug!("Loading assets");
|
||||
let num_futs = index.objects.len();
|
||||
@@ -230,9 +600,22 @@ pub async fn download_assets(
|
||||
loading_amount,
|
||||
num_futs,
|
||||
None,
|
||||
|(name, asset)| async move {
|
||||
|(name, asset)| {
|
||||
let progress = progress.clone();
|
||||
async move {
|
||||
let hash = &asset.hash;
|
||||
let resource_path = st.directories.object_dir(hash);
|
||||
let legacy_resource_path = st.directories.legacy_assets_dir().join(
|
||||
name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
|
||||
);
|
||||
let should_fetch_object = !resource_path.exists() || force;
|
||||
let should_fetch_legacy =
|
||||
(with_legacy && !legacy_resource_path.exists()) || force;
|
||||
let fetch_progress = if should_fetch_object || should_fetch_legacy {
|
||||
progress.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let url = format!(
|
||||
"https://resources.download.minecraft.net/{sub_hash}/{hash}",
|
||||
sub_hash = &hash[..2]
|
||||
@@ -241,9 +624,15 @@ pub async fn download_assets(
|
||||
let fetch_cell = OnceCell::<bytes::Bytes>::new();
|
||||
tokio::try_join! {
|
||||
async {
|
||||
if !resource_path.exists() || force {
|
||||
if should_fetch_object {
|
||||
let resource = fetch_cell
|
||||
.get_or_try_init(|| fetch(&url, Some(hash), None, None, &st.fetch_semaphore, &st.pool))
|
||||
.get_or_try_init(|| fetch_minecraft_file(
|
||||
st,
|
||||
&url,
|
||||
Some(hash),
|
||||
Some(asset.size as u64),
|
||||
fetch_progress.clone(),
|
||||
))
|
||||
.await?;
|
||||
write(&resource_path, resource, &st.io_semaphore).await?;
|
||||
tracing::trace!("Fetched asset with hash {hash}");
|
||||
@@ -251,15 +640,17 @@ pub async fn download_assets(
|
||||
Ok::<_, crate::Error>(())
|
||||
},
|
||||
async {
|
||||
let resource_path = st.directories.legacy_assets_dir().join(
|
||||
name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
|
||||
);
|
||||
|
||||
if with_legacy && !resource_path.exists() || force {
|
||||
if should_fetch_legacy {
|
||||
let resource = fetch_cell
|
||||
.get_or_try_init(|| fetch(&url, Some(hash), None, None, &st.fetch_semaphore, &st.pool))
|
||||
.get_or_try_init(|| fetch_minecraft_file(
|
||||
st,
|
||||
&url,
|
||||
Some(hash),
|
||||
Some(asset.size as u64),
|
||||
fetch_progress.clone(),
|
||||
))
|
||||
.await?;
|
||||
write(&resource_path, resource, &st.io_semaphore).await?;
|
||||
write(&legacy_resource_path, resource, &st.io_semaphore).await?;
|
||||
tracing::trace!("Fetched legacy asset with hash {hash}");
|
||||
}
|
||||
Ok::<_, crate::Error>(())
|
||||
@@ -268,6 +659,7 @@ pub async fn download_assets(
|
||||
|
||||
tracing::trace!("Loaded asset with hash {hash}");
|
||||
Ok(())
|
||||
}
|
||||
}).await?;
|
||||
tracing::debug!("Done loading assets!");
|
||||
Ok(())
|
||||
@@ -284,6 +676,7 @@ pub async fn download_libraries(
|
||||
java_arch: &str,
|
||||
force: bool,
|
||||
minecraft_updated: bool,
|
||||
progress: Option<MinecraftDownloadProgress>,
|
||||
) -> crate::Result<()> {
|
||||
tracing::debug!("Loading libraries");
|
||||
|
||||
@@ -299,7 +692,9 @@ pub async fn download_libraries(
|
||||
loading_amount,
|
||||
num_files,
|
||||
None,
|
||||
|library| async move {
|
||||
|library| {
|
||||
let progress = progress.clone();
|
||||
async move {
|
||||
if let Some(rules) = &library.rules
|
||||
&& !parse_rules(
|
||||
rules,
|
||||
@@ -328,13 +723,12 @@ pub async fn download_libraries(
|
||||
.replace("${arch}", crate::util::platform::ARCH_WIDTH);
|
||||
|
||||
if let Some(native) = classifiers.get(&parsed_key) {
|
||||
let data = fetch(
|
||||
let data = fetch_minecraft_file(
|
||||
st,
|
||||
&native.url,
|
||||
Some(&native.sha1),
|
||||
None,
|
||||
None,
|
||||
&st.fetch_semaphore,
|
||||
&st.pool,
|
||||
Some(native.size as u64),
|
||||
progress.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -374,13 +768,12 @@ pub async fn download_libraries(
|
||||
}) = library.downloads
|
||||
&& !artifact.url.is_empty()
|
||||
{
|
||||
let bytes = fetch(
|
||||
let bytes = fetch_minecraft_file(
|
||||
st,
|
||||
&artifact.url,
|
||||
Some(&artifact.sha1),
|
||||
None,
|
||||
None,
|
||||
&st.fetch_semaphore,
|
||||
&st.pool,
|
||||
Some(artifact.size as u64),
|
||||
progress.clone(),
|
||||
)
|
||||
.await?;
|
||||
write(&path, &bytes, &st.io_semaphore).await?;
|
||||
@@ -447,6 +840,7 @@ pub async fn download_libraries(
|
||||
|
||||
tracing::debug!("Loaded library {}", library.name);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
@@ -461,7 +855,8 @@ pub async fn download_log_config(
|
||||
version_info: &GameVersionInfo,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
force: bool,
|
||||
) -> crate::Result<()> {
|
||||
progress: Option<MinecraftDownloadProgress>,
|
||||
) -> crate::Result<bool> {
|
||||
let log_download = version_info
|
||||
.logging
|
||||
.as_ref()
|
||||
@@ -473,19 +868,18 @@ pub async fn download_log_config(
|
||||
if let Some(loading_bar) = loading_bar {
|
||||
emit_loading(loading_bar, 1.0, None)?;
|
||||
}
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let path = st.directories.log_configs_dir().join(&log_download.id);
|
||||
|
||||
if !path.exists() || force {
|
||||
let bytes = fetch(
|
||||
let bytes = fetch_minecraft_file(
|
||||
st,
|
||||
&log_download.url,
|
||||
Some(&log_download.sha1),
|
||||
None,
|
||||
None,
|
||||
&st.fetch_semaphore,
|
||||
&st.pool,
|
||||
Some(log_download.size as u64),
|
||||
progress,
|
||||
)
|
||||
.await?;
|
||||
write(&path, &bytes, &st.io_semaphore).await?;
|
||||
@@ -496,5 +890,5 @@ pub async fn download_log_config(
|
||||
}
|
||||
|
||||
tracing::debug!("Log config {} loaded", log_download.id);
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
//! Logic for launching Minecraft
|
||||
use crate::data::ModLoader;
|
||||
use crate::event::emit::{emit_loading, init_or_edit_loading};
|
||||
use crate::event::{LoadingBarId, LoadingBarType};
|
||||
use crate::event::emit::{emit_instance, emit_loading, init_loading};
|
||||
use crate::event::{InstancePayloadType, LoadingBarType};
|
||||
use crate::install::{
|
||||
InstallJavaStep, InstallPhaseDetails, InstallPhaseId, InstallProgress,
|
||||
InstallProgressReporter,
|
||||
};
|
||||
use crate::instance::QuickPlayType;
|
||||
use crate::launcher::download::download_log_config;
|
||||
use crate::launcher::io::IOError;
|
||||
use crate::launcher::quick_play_version::{
|
||||
QuickPlayServerVersion, QuickPlayVersion,
|
||||
};
|
||||
use crate::profile::QuickPlayType;
|
||||
use crate::server_address::{ServerAddress, parse_server_address};
|
||||
use crate::state::server_join_log::JoinLogEntry;
|
||||
use crate::state::{
|
||||
Credentials, JavaVersion, ProcessMetadata, ProfileInstallStage,
|
||||
Credentials, InstanceInstallStage, InstanceLaunchContext, InstanceLink,
|
||||
JavaVersion, MemorySettings, ProcessMetadata, WindowSize,
|
||||
};
|
||||
use crate::util::io;
|
||||
use crate::util::rpc::RpcServerBuilder;
|
||||
use crate::{State, get_resource_file, process, state as st};
|
||||
use crate::{State, get_resource_file, process};
|
||||
use chrono::Utc;
|
||||
use daedalus as d;
|
||||
use daedalus::minecraft::{LoggingSide, RuleAction, VersionInfo};
|
||||
use daedalus::modded::LoaderVersion;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use st::Profile;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use tokio::process::Command;
|
||||
@@ -126,11 +130,11 @@ macro_rules! processor_rules {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_java_version_from_profile(
|
||||
profile: &Profile,
|
||||
pub async fn get_java_version_from_launch_context(
|
||||
context: &InstanceLaunchContext,
|
||||
version_info: &VersionInfo,
|
||||
) -> crate::Result<Option<JavaVersion>> {
|
||||
if let Some(java) = profile.java_path.as_ref() {
|
||||
if let Some(java) = context.launch_overrides.java_path.as_ref() {
|
||||
let java =
|
||||
crate::api::jre::check_jre(std::path::PathBuf::from(java)).await;
|
||||
|
||||
@@ -231,38 +235,63 @@ pub async fn resolve_minecraft_manifest(
|
||||
Ok((refreshed, idx))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(profile))]
|
||||
async fn get_instance_full_path(instance_path: &str) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let instances_dir = state.directories.instances_dir();
|
||||
let full_path = io::canonicalize(instances_dir.join(instance_path))?;
|
||||
Ok(full_path)
|
||||
}
|
||||
|
||||
pub async fn install_minecraft(
|
||||
profile: &Profile,
|
||||
existing_loading_bar: Option<LoadingBarId>,
|
||||
pub async fn install_minecraft_with_reporter(
|
||||
context: &InstanceLaunchContext,
|
||||
repairing: bool,
|
||||
reporter: Option<InstallProgressReporter>,
|
||||
) -> crate::Result<()> {
|
||||
let loading_bar = init_or_edit_loading(
|
||||
existing_loading_bar,
|
||||
LoadingBarType::MinecraftDownload {
|
||||
// If we are downloading minecraft for a profile, provide its name and uuid
|
||||
profile_name: profile.name.clone(),
|
||||
profile_path: profile.path.clone(),
|
||||
},
|
||||
100.0,
|
||||
"Downloading Minecraft",
|
||||
)
|
||||
.await?;
|
||||
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.install_stage = ProfileInstallStage::MinecraftInstalling;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
let instance = &context.instance;
|
||||
let content_set = &context.applied_content_set;
|
||||
let phase_details = InstallPhaseDetails::Minecraft {
|
||||
game_version: content_set.game_version.clone(),
|
||||
loader: content_set.loader,
|
||||
};
|
||||
let loading_bar = if reporter.is_none() {
|
||||
Some(
|
||||
init_loading(
|
||||
LoadingBarType::MinecraftDownload {
|
||||
// If we are downloading minecraft for a profile, provide its name and uuid
|
||||
instance_name: instance.name.clone(),
|
||||
instance_id: instance.id.clone(),
|
||||
},
|
||||
100.0,
|
||||
"Downloading Minecraft",
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let state = State::get().await?;
|
||||
|
||||
let instance_path =
|
||||
crate::api::profile::get_full_path(&profile.path).await?;
|
||||
crate::state::instances::commands::set_instance_install_stage(
|
||||
&instance.id,
|
||||
InstanceInstallStage::MinecraftInstalling,
|
||||
&state.pool,
|
||||
)
|
||||
.await?;
|
||||
emit_instance(&instance.id, InstancePayloadType::Edited).await?;
|
||||
|
||||
let instance_path = get_instance_full_path(&instance.path).await?;
|
||||
if let Some(reporter) = &reporter {
|
||||
reporter
|
||||
.update(
|
||||
InstallPhaseId::ResolvingMinecraft,
|
||||
None,
|
||||
phase_details.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let (minecraft, version_index) =
|
||||
resolve_minecraft_manifest(&profile.game_version, &state).await?;
|
||||
resolve_minecraft_manifest(&content_set.game_version, &state).await?;
|
||||
let version = &minecraft.versions[version_index];
|
||||
let minecraft_updated = version_index
|
||||
<= minecraft
|
||||
@@ -271,28 +300,39 @@ pub async fn install_minecraft(
|
||||
.position(|x| x.id == "22w16a")
|
||||
.unwrap_or(0);
|
||||
|
||||
if content_set.loader != ModLoader::Vanilla
|
||||
&& let Some(reporter) = &reporter
|
||||
{
|
||||
reporter
|
||||
.update(
|
||||
InstallPhaseId::ResolvingLoader,
|
||||
None,
|
||||
phase_details.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let mut loader_version = get_loader_version_from_profile(
|
||||
&profile.game_version,
|
||||
profile.loader,
|
||||
profile.loader_version.as_deref(),
|
||||
&content_set.game_version,
|
||||
content_set.loader,
|
||||
content_set.loader_version.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// If no loader version is selected, try to select the stable version!
|
||||
if profile.loader != ModLoader::Vanilla && loader_version.is_none() {
|
||||
if content_set.loader != ModLoader::Vanilla && loader_version.is_none() {
|
||||
loader_version = get_loader_version_from_profile(
|
||||
&profile.game_version,
|
||||
profile.loader,
|
||||
&content_set.game_version,
|
||||
content_set.loader,
|
||||
Some("stable"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let loader_version_id = loader_version.clone();
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.loader_version = loader_version_id.clone().map(|x| x.id);
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
crate::state::instances::commands::set_applied_content_set_loader_version(
|
||||
&instance.id,
|
||||
loader_version.as_ref().map(|x| x.id.as_str()),
|
||||
&state.pool,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -307,7 +347,7 @@ pub async fn install_minecraft(
|
||||
version,
|
||||
loader_version.as_ref(),
|
||||
Some(repairing),
|
||||
Some(&loading_bar),
|
||||
loading_bar.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -315,17 +355,57 @@ pub async fn install_minecraft(
|
||||
.java_version
|
||||
.as_ref()
|
||||
.map_or(8, |it| it.major_version);
|
||||
if let Some(reporter) = &reporter {
|
||||
reporter
|
||||
.update(
|
||||
InstallPhaseId::PreparingJava,
|
||||
Some(InstallProgress {
|
||||
current: 0,
|
||||
total: 4,
|
||||
secondary: None,
|
||||
}),
|
||||
InstallPhaseDetails::Java {
|
||||
major_version: key,
|
||||
step: InstallJavaStep::Resolving,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let (java_version, set_java) = if let Some(java_version) =
|
||||
get_java_version_from_profile(profile, &version_info).await?
|
||||
get_java_version_from_launch_context(context, &version_info).await?
|
||||
{
|
||||
(std::path::PathBuf::from(java_version.path), false)
|
||||
} else {
|
||||
let path = crate::api::jre::auto_install_java(key).await?;
|
||||
let path = if let Some(reporter) = &reporter {
|
||||
crate::api::jre::auto_install_java_with_reporter(
|
||||
key,
|
||||
reporter.clone(),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
crate::api::jre::auto_install_java_with_loading(key, true).await?
|
||||
};
|
||||
|
||||
(path, true)
|
||||
};
|
||||
|
||||
// Test jre version
|
||||
if let Some(reporter) = &reporter {
|
||||
reporter
|
||||
.update(
|
||||
InstallPhaseId::PreparingJava,
|
||||
Some(InstallProgress {
|
||||
current: 4,
|
||||
total: 4,
|
||||
secondary: None,
|
||||
}),
|
||||
InstallPhaseDetails::Java {
|
||||
major_version: key,
|
||||
step: InstallJavaStep::Validating,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let java_version = crate::api::jre::check_jre(java_version.clone()).await?;
|
||||
|
||||
if set_java {
|
||||
@@ -333,13 +413,24 @@ pub async fn install_minecraft(
|
||||
}
|
||||
|
||||
// Download minecraft (5-90)
|
||||
if let Some(reporter) = &reporter {
|
||||
reporter
|
||||
.update(
|
||||
InstallPhaseId::DownloadingMinecraft,
|
||||
None,
|
||||
phase_details.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
download::download_minecraft(
|
||||
&state,
|
||||
&version_info,
|
||||
&loading_bar,
|
||||
loading_bar.as_ref(),
|
||||
&java_version.architecture,
|
||||
repairing,
|
||||
minecraft_updated,
|
||||
reporter.clone(),
|
||||
phase_details.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -360,7 +451,7 @@ pub async fn install_minecraft(
|
||||
client => client_path.to_string_lossy(),
|
||||
server => "";
|
||||
"MINECRAFT_VERSION":
|
||||
client => profile.game_version.clone(),
|
||||
client => content_set.game_version.clone(),
|
||||
server => "";
|
||||
"ROOT":
|
||||
client => instance_path.to_string_lossy(),
|
||||
@@ -370,14 +461,46 @@ pub async fn install_minecraft(
|
||||
server => "";
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))?;
|
||||
if let Some(loading_bar) = &loading_bar {
|
||||
emit_loading(
|
||||
loading_bar,
|
||||
0.0,
|
||||
Some("Running forge processors"),
|
||||
)?;
|
||||
}
|
||||
let total_length = processors.len();
|
||||
if let Some(reporter) = &reporter {
|
||||
reporter
|
||||
.update(
|
||||
InstallPhaseId::RunningLoaderProcessors,
|
||||
Some(InstallProgress {
|
||||
current: 0,
|
||||
total: total_length as u64,
|
||||
secondary: None,
|
||||
}),
|
||||
phase_details.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Forge processors (90-100)
|
||||
for (index, processor) in processors.iter().enumerate() {
|
||||
if let Some(sides) = &processor.sides
|
||||
&& !sides.contains(&String::from("client"))
|
||||
{
|
||||
if let Some(reporter) = &reporter {
|
||||
reporter
|
||||
.update(
|
||||
InstallPhaseId::RunningLoaderProcessors,
|
||||
Some(InstallProgress {
|
||||
current: (index + 1) as u64,
|
||||
total: total_length as u64,
|
||||
secondary: None,
|
||||
}),
|
||||
phase_details.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -430,31 +553,75 @@ pub async fn install_minecraft(
|
||||
.as_error());
|
||||
}
|
||||
|
||||
emit_loading(
|
||||
&loading_bar,
|
||||
30.0 / total_length as f64,
|
||||
Some(&format!(
|
||||
"Running forge processor {index}/{total_length}"
|
||||
)),
|
||||
)?;
|
||||
if let Some(loading_bar) = &loading_bar {
|
||||
emit_loading(
|
||||
loading_bar,
|
||||
30.0 / total_length as f64,
|
||||
Some(&format!(
|
||||
"Running forge processor {index}/{total_length}"
|
||||
)),
|
||||
)?;
|
||||
}
|
||||
if let Some(reporter) = &reporter {
|
||||
reporter
|
||||
.update(
|
||||
InstallPhaseId::RunningLoaderProcessors,
|
||||
Some(InstallProgress {
|
||||
current: (index + 1) as u64,
|
||||
total: total_length as u64,
|
||||
secondary: None,
|
||||
}),
|
||||
phase_details.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let protocol_version = read_protocol_version_from_jar(client_path).await?;
|
||||
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.install_stage = ProfileInstallStage::Installed;
|
||||
prof.protocol_version = protocol_version;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
crate::state::instances::commands::set_instance_install_stage(
|
||||
&instance.id,
|
||||
InstanceInstallStage::Installed,
|
||||
&state.pool,
|
||||
)
|
||||
.await?;
|
||||
emit_loading(&loading_bar, 1.0, Some("Finished installing"))?;
|
||||
emit_instance(&instance.id, InstancePayloadType::Edited).await?;
|
||||
crate::state::instances::commands::set_applied_content_set_protocol_version(
|
||||
&instance.id,
|
||||
protocol_version,
|
||||
&state.pool,
|
||||
)
|
||||
.await?;
|
||||
if let Some(loading_bar) = &loading_bar {
|
||||
emit_loading(loading_bar, 1.0, Some("Finished installing"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn install_minecraft_for_instance_id_with_reporter(
|
||||
instance_id: &str,
|
||||
repairing: bool,
|
||||
reporter: Option<InstallProgressReporter>,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let context =
|
||||
crate::state::instances::commands::get_instance_launch_context(
|
||||
instance_id,
|
||||
&state.pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Tried to install a nonexistent or unloaded instance {instance_id}!"
|
||||
))
|
||||
})?;
|
||||
|
||||
install_minecraft_with_reporter(&context, repairing, reporter).await
|
||||
}
|
||||
|
||||
pub async fn read_protocol_version_from_jar(
|
||||
path: PathBuf,
|
||||
) -> crate::Result<Option<u32>> {
|
||||
@@ -483,6 +650,31 @@ pub async fn read_protocol_version_from_jar(
|
||||
Ok(data.protocol_version)
|
||||
}
|
||||
|
||||
fn link_project_and_version(
|
||||
link: &InstanceLink,
|
||||
) -> (Option<&String>, Option<&String>) {
|
||||
match link {
|
||||
InstanceLink::ModrinthModpack {
|
||||
project_id,
|
||||
version_id,
|
||||
} => (Some(project_id), Some(version_id)),
|
||||
InstanceLink::ServerProject { project_id } => (Some(project_id), None),
|
||||
InstanceLink::ServerProjectModpack {
|
||||
server_project_id,
|
||||
content_version_id,
|
||||
..
|
||||
} => (Some(server_project_id), Some(content_version_id)),
|
||||
InstanceLink::ImportedModpack {
|
||||
project_id,
|
||||
version_id,
|
||||
..
|
||||
} => (project_id.as_ref(), version_id.as_ref()),
|
||||
InstanceLink::Unmanaged
|
||||
| InstanceLink::ModrinthHosting { .. }
|
||||
| InstanceLink::SharedInstance { .. } => (None, None),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn launch_minecraft(
|
||||
@@ -490,33 +682,38 @@ pub async fn launch_minecraft(
|
||||
env_args: &[(String, String)],
|
||||
mc_set_options: &[(String, String)],
|
||||
wrapper: &Option<String>,
|
||||
memory: &st::MemorySettings,
|
||||
resolution: &st::WindowSize,
|
||||
memory: &MemorySettings,
|
||||
resolution: &WindowSize,
|
||||
credentials: &Credentials,
|
||||
post_exit_hook: Option<String>,
|
||||
profile: &Profile,
|
||||
context: &InstanceLaunchContext,
|
||||
mut quick_play_type: QuickPlayType,
|
||||
) -> crate::Result<ProcessMetadata> {
|
||||
if profile.install_stage == ProfileInstallStage::PackInstalling
|
||||
|| profile.install_stage == ProfileInstallStage::MinecraftInstalling
|
||||
let instance = &context.instance;
|
||||
let content_set = &context.applied_content_set;
|
||||
|
||||
if instance.install_stage == InstanceInstallStage::PackInstalling
|
||||
|| instance.install_stage == InstanceInstallStage::MinecraftInstalling
|
||||
{
|
||||
return Err(crate::ErrorKind::LauncherError(
|
||||
"Profile is still installing".to_string(),
|
||||
"Instance is still installing".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
if profile.install_stage != ProfileInstallStage::Installed {
|
||||
install_minecraft(profile, None, false).await?;
|
||||
if instance.install_stage != InstanceInstallStage::Installed {
|
||||
return Err(crate::ErrorKind::LauncherError(
|
||||
"Instance is not installed; start an install job first".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
|
||||
let instance_path =
|
||||
crate::api::profile::get_full_path(&profile.path).await?;
|
||||
let instance_path = get_instance_full_path(&instance.path).await?;
|
||||
|
||||
let (minecraft, version_index) =
|
||||
resolve_minecraft_manifest(&profile.game_version, &state).await?;
|
||||
resolve_minecraft_manifest(&content_set.game_version, &state).await?;
|
||||
let version = &minecraft.versions[version_index];
|
||||
let minecraft_updated = version_index
|
||||
<= minecraft
|
||||
@@ -526,16 +723,16 @@ pub async fn launch_minecraft(
|
||||
.unwrap_or(0);
|
||||
|
||||
let loader_version = get_loader_version_from_profile(
|
||||
&profile.game_version,
|
||||
profile.loader,
|
||||
profile.loader_version.as_deref(),
|
||||
&content_set.game_version,
|
||||
content_set.loader,
|
||||
content_set.loader_version.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if profile.loader != ModLoader::Vanilla && loader_version.is_none() {
|
||||
if content_set.loader != ModLoader::Vanilla && loader_version.is_none() {
|
||||
return Err(crate::ErrorKind::LauncherError(format!(
|
||||
"No loader version selected for {}",
|
||||
profile.loader.as_str()
|
||||
content_set.loader.as_str()
|
||||
))
|
||||
.into());
|
||||
}
|
||||
@@ -572,15 +769,17 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
download_log_config(&state, &version_info, None, false).await?;
|
||||
let _ =
|
||||
download_log_config(&state, &version_info, None, false, None).await?;
|
||||
|
||||
let java_version = get_java_version_from_profile(profile, &version_info)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(
|
||||
"Missing correct java installation".to_string(),
|
||||
)
|
||||
})?;
|
||||
let java_version =
|
||||
get_java_version_from_launch_context(context, &version_info)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(
|
||||
"Missing correct java installation".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Test jre version
|
||||
let java_version =
|
||||
@@ -615,14 +814,13 @@ pub async fn launch_minecraft(
|
||||
|
||||
let env_args = Vec::from(env_args);
|
||||
|
||||
// Check if profile has a running profile, and reject running the command if it does
|
||||
// Check if instance has a running process, and reject running the command if it does
|
||||
// Done late so a quick double call doesn't launch two instances
|
||||
let existing_processes =
|
||||
process::get_by_profile_path(&profile.path).await?;
|
||||
let existing_processes = process::get_by_instance_id(&instance.id).await?;
|
||||
if let Some(process) = existing_processes.first() {
|
||||
return Err(crate::ErrorKind::LauncherError(format!(
|
||||
"Profile {} is already running at path: {}",
|
||||
profile.path, process.uuid
|
||||
"Instance {} is already running as process {}",
|
||||
instance.id, process.uuid
|
||||
))
|
||||
.as_error());
|
||||
}
|
||||
@@ -636,7 +834,7 @@ pub async fn launch_minecraft(
|
||||
QuickPlayVersion::find_version(version_index, &minecraft.versions);
|
||||
tracing::debug!(
|
||||
"Found QuickPlayVersion for {}: {quick_play_version:?}",
|
||||
profile.game_version
|
||||
content_set.game_version
|
||||
);
|
||||
if let QuickPlayType::Server(address) = &mut quick_play_type
|
||||
&& quick_play_version.server >= QuickPlayServerVersion::BuiltinLegacy
|
||||
@@ -655,7 +853,7 @@ pub async fn launch_minecraft(
|
||||
};
|
||||
if let Some((host, port)) = original
|
||||
&& let Err(e) = (JoinLogEntry {
|
||||
profile_path: profile.path.clone(),
|
||||
instance_id: instance.id.clone(),
|
||||
host,
|
||||
port,
|
||||
join_time: Utc::now(),
|
||||
@@ -792,11 +990,11 @@ pub async fn launch_minecraft(
|
||||
.await?;
|
||||
}
|
||||
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.last_played = Some(Utc::now());
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
crate::state::instances::commands::set_instance_last_played(
|
||||
&instance.id,
|
||||
Utc::now(),
|
||||
&state.pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// If in tauri, and the 'minimize on launch' setting is enabled, minimize the window
|
||||
@@ -815,12 +1013,12 @@ pub async fn launch_minecraft(
|
||||
|
||||
let _ = state
|
||||
.discord_rpc
|
||||
.set_activity(&format!("Playing {}", profile.name), true)
|
||||
.set_activity(&format!("Playing {}", instance.name), true)
|
||||
.await;
|
||||
|
||||
let _ = state
|
||||
.friends_socket
|
||||
.update_status(Some(profile.name.clone()))
|
||||
.update_status(Some(instance.name.clone()))
|
||||
.await;
|
||||
|
||||
// Create Minecraft child by inserting it into the state
|
||||
@@ -828,31 +1026,32 @@ pub async fn launch_minecraft(
|
||||
state
|
||||
.process_manager
|
||||
.insert_new_process(
|
||||
&profile.path,
|
||||
&instance.id,
|
||||
&instance.path,
|
||||
&instance.name,
|
||||
command,
|
||||
post_exit_hook,
|
||||
state.directories.profile_logs_dir(&profile.path),
|
||||
state.directories.instance_logs_dir(&instance.path),
|
||||
version_info.logging.is_some(),
|
||||
main_class_keep_alive,
|
||||
rpc_server,
|
||||
async |process: &ProcessMetadata, rpc_server| {
|
||||
let process_start_time = process.start_time.to_rfc3339();
|
||||
let profile_created_time = profile.created.to_rfc3339();
|
||||
let profile_modified_time = profile.modified.to_rfc3339();
|
||||
let instance_created_time = instance.created.to_rfc3339();
|
||||
let instance_modified_time = instance.modified.to_rfc3339();
|
||||
let (link_project_id, link_version_id) =
|
||||
link_project_and_version(&context.link);
|
||||
let system_properties = [
|
||||
("modrinth.process.startTime", Some(&process_start_time)),
|
||||
("modrinth.profile.created", Some(&profile_created_time)),
|
||||
("modrinth.profile.icon", profile.icon_path.as_ref()),
|
||||
("modrinth.profile.created", Some(&instance_created_time)),
|
||||
("modrinth.profile.icon", instance.icon_path.as_ref()),
|
||||
("modrinth.profile.link.project", link_project_id),
|
||||
("modrinth.profile.link.version", link_version_id),
|
||||
(
|
||||
"modrinth.profile.link.project",
|
||||
profile.linked_data.as_ref().map(|x| &x.project_id),
|
||||
"modrinth.profile.modified",
|
||||
Some(&instance_modified_time),
|
||||
),
|
||||
(
|
||||
"modrinth.profile.link.version",
|
||||
profile.linked_data.as_ref().map(|x| &x.version_id),
|
||||
),
|
||||
("modrinth.profile.modified", Some(&profile_modified_time)),
|
||||
("modrinth.profile.name", Some(&profile.name)),
|
||||
("modrinth.profile.name", Some(&instance.name)),
|
||||
];
|
||||
for (key, value) in system_properties {
|
||||
let Some(value) = value else {
|
||||
|
||||
Reference in New Issue
Block a user