Merge commit '7fa442fb28a2b9156690ff147206275163e7aec8' into beta

This commit is contained in:
2025-10-19 06:50:50 +03:00
1007 changed files with 143497 additions and 11362 deletions

View File

@@ -14,8 +14,9 @@ use daedalus::{
modded::SidedDataEntry,
};
use dunce::canonicalize;
use hashlink::LinkedHashSet;
use std::io::{BufRead, BufReader};
use itertools::Itertools;
use std::io::{BufRead, BufReader, ErrorKind};
use std::net::SocketAddr;
use std::{collections::HashMap, path::Path};
use uuid::Uuid;
@@ -29,9 +30,21 @@ pub fn get_class_paths(
java_arch: &str,
minecraft_updated: bool,
) -> crate::Result<String> {
let mut cps = libraries
launcher_class_path
.iter()
.filter_map(|library| {
.map(|path| {
Ok(canonicalize(path)
.map_err(|_| {
crate::ErrorKind::LauncherError(format!(
"Specified class path {} does not exist",
path.to_string_lossy()
))
.as_error()
})?
.to_string_lossy()
.to_string())
})
.chain(libraries.iter().filter_map(|library| {
if let Some(rules) = &library.rules
&& !parse_rules(
rules,
@@ -47,29 +60,15 @@ pub fn get_class_paths(
return None;
}
Some(get_lib_path(libraries_path, &library.name, false))
Some(get_lib_path(
libraries_path,
&library.name,
library.natives_os_key_and_classifiers(java_arch).is_some(),
))
}))
.process_results(|iter| {
iter.unique().join(classpath_separator(java_arch))
})
.collect::<Result<LinkedHashSet<_>, _>>()?;
for launcher_path in launcher_class_path {
cps.insert(
canonicalize(launcher_path)
.map_err(|_| {
crate::ErrorKind::LauncherError(format!(
"Specified class path {} does not exist",
launcher_path.to_string_lossy()
))
.as_error()
})?
.to_string_lossy()
.to_string(),
);
}
Ok(cps
.into_iter()
.collect::<Vec<_>>()
.join(classpath_separator(java_arch)))
}
pub fn get_class_paths_jar<T: AsRef<str>>(
@@ -90,21 +89,21 @@ pub fn get_lib_path(
lib: &str,
allow_not_exist: bool,
) -> crate::Result<String> {
let path = libraries_path
.to_path_buf()
.join(get_path_from_artifact(lib)?);
let path = libraries_path.join(get_path_from_artifact(lib)?);
if !path.exists() && allow_not_exist {
return Ok(path.to_string_lossy().to_string());
}
let path = &canonicalize(&path).map_err(|_| {
crate::ErrorKind::LauncherError(format!(
"Library file at path {} does not exist",
path.to_string_lossy()
))
.as_error()
})?;
let path = match canonicalize(&path) {
Ok(p) => p,
Err(err) if err.kind() == ErrorKind::NotFound && allow_not_exist => {
path
}
Err(err) => {
return Err(crate::ErrorKind::LauncherError(format!(
"Could not canonicalize library path {}: {err}",
path.display()
))
.as_error());
}
};
Ok(path.to_string_lossy().to_string())
}
@@ -124,6 +123,7 @@ pub fn get_jvm_arguments(
quick_play_type: &QuickPlayType,
quick_play_version: QuickPlayVersion,
log_config: Option<&LoggingConfiguration>,
ipc_addr: SocketAddr,
) -> crate::Result<Vec<String>> {
let mut parsed_arguments = Vec::new();
@@ -181,6 +181,11 @@ pub fn get_jvm_arguments(
.to_string_lossy()
));
parsed_arguments
.push(format!("-Dmodrinth.internal.ipc.host={}", ipc_addr.ip()));
parsed_arguments
.push(format!("-Dmodrinth.internal.ipc.port={}", ipc_addr.port()));
parsed_arguments.push(format!(
"-Dmodrinth.internal.quickPlay.serverVersion={}",
serde_json::to_value(quick_play_version.server)?

View File

@@ -8,13 +8,13 @@ use crate::{
emit::{emit_loading, loading_try_for_each_concurrent},
},
state::State,
util::{fetch::*, io, platform::OsExt},
util::{fetch::*, io},
};
use daedalus::minecraft::{LoggingConfiguration, LoggingSide};
use daedalus::{
self as d,
minecraft::{
Asset, AssetsIndex, Library, Os, Version as GameVersion,
Asset, AssetsIndex, Library, Version as GameVersion,
VersionInfo as GameVersionInfo,
},
modded::LoaderVersion,
@@ -288,90 +288,132 @@ pub async fn download_libraries(
}?;
let num_files = libraries.len();
loading_try_for_each_concurrent(
stream::iter(libraries.iter())
.map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move {
if let Some(rules) = &library.rules
&& !parse_rules(rules, java_arch, &QuickPlayType::None, minecraft_updated) {
tracing::trace!("Skipped library {}", &library.name);
return Ok(());
}
stream::iter(libraries.iter()).map(Ok::<&Library, crate::Error>),
None,
loading_bar,
loading_amount,
num_files,
None,
|library| async move {
if let Some(rules) = &library.rules
&& !parse_rules(
rules,
java_arch,
&QuickPlayType::None,
minecraft_updated,
)
{
tracing::trace!("Skipped library {}", &library.name);
return Ok(());
}
if !library.downloadable {
tracing::trace!("Skipped non-downloadable library {}", &library.name);
if !library.downloadable {
tracing::trace!(
"Skipped non-downloadable library {}",
&library.name
);
return Ok(());
}
// When a library has natives, we only need to download such natives, as PrismLauncher does
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) {
let data = fetch(
&native.url,
Some(&native.sha1),
&st.fetch_semaphore,
&st.pool,
)
.await?;
if let Ok(mut archive) =
zip::ZipArchive::new(std::io::Cursor::new(&data))
{
match archive.extract(
st.directories.version_natives_dir(version),
) {
Ok(_) => tracing::debug!(
"Fetched native {}",
&library.name
),
Err(err) => tracing::error!(
"Failed extracting native {}. err: {err}",
&library.name
),
}
} else {
tracing::error!(
"Failed extracting native {}",
&library.name
);
}
}
} else {
let artifact_path = d::get_path_from_artifact(&library.name)?;
let path = st.directories.libraries_dir().join(&artifact_path);
if path.exists() && !force {
return Ok(());
}
tokio::try_join! {
async {
let artifact_path = d::get_path_from_artifact(&library.name)?;
let path = st.directories.libraries_dir().join(&artifact_path);
if let Some(d::minecraft::LibraryDownloads {
artifact: Some(ref artifact),
..
}) = library.downloads
&& !artifact.url.is_empty()
{
let bytes = fetch(
&artifact.url,
Some(&artifact.sha1),
&st.fetch_semaphore,
&st.pool,
)
.await?;
write(&path, &bytes, &st.io_semaphore).await?;
if path.exists() && !force {
return Ok(());
}
tracing::trace!(
"Fetched library {} to path {:?}",
&library.name,
&path
);
} else {
// We lack an artifact URL, so fall back to constructing one ourselves.
// PrismLauncher just ignores the library if this is the case, so it's
// probably not needed, but previous code revisions of the Modrinth App
// intended to do this, so we keep that behavior for compatibility.
if let Some(d::minecraft::LibraryDownloads { artifact: Some(ref artifact), ..}) = library.downloads
&& !artifact.url.is_empty(){
let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore, &st.pool)
.await?;
write(&path, &bytes, &st.io_semaphore).await?;
tracing::trace!("Fetched library {} to path {:?}", &library.name, &path);
return Ok::<_, crate::Error>(());
}
let url = format!(
"{}{artifact_path}",
library
.url
.as_deref()
.unwrap_or("https://libraries.minecraft.net/")
);
let url = [
library
.url
.as_deref()
.unwrap_or("https://libraries.minecraft.net/"),
&artifact_path
].concat();
let bytes =
fetch(&url, None, &st.fetch_semaphore, &st.pool)
.await?;
let bytes = fetch(&url, None, &st.fetch_semaphore, &st.pool).await?;
write(&path, &bytes, &st.io_semaphore).await?;
tracing::trace!("Fetched library {} to path {:?}", &library.name, &path);
Ok::<_, crate::Error>(())
},
async {
// HACK: pseudo try block using or else
if let Some((os_key, classifiers)) = None.or_else(|| Some((
library
.natives
.as_ref()?
.get(&Os::native_arch(java_arch))?,
library
.downloads
.as_ref()?
.classifiers
.as_ref()?
))) {
let parsed_key = os_key.replace(
"${arch}",
crate::util::platform::ARCH_WIDTH,
);
write(&path, &bytes, &st.io_semaphore).await?;
if let Some(native) = classifiers.get(&parsed_key) {
let data = fetch(&native.url, Some(&native.sha1), &st.fetch_semaphore, &st.pool).await?;
let reader = std::io::Cursor::new(&data);
if let Ok(mut archive) = zip::ZipArchive::new(reader) {
match archive.extract(st.directories.version_natives_dir(version)) {
Ok(_) => tracing::debug!("Fetched native {}", &library.name),
Err(err) => tracing::error!("Failed extracting native {}. err: {}", &library.name, err)
}
} else {
tracing::error!("Failed extracting native {}", &library.name)
}
}
}
Ok(())
}
}?;
tracing::debug!("Loaded library {}", library.name);
Ok(())
tracing::trace!(
"Fetched library {} to path {:?}",
&library.name,
&path
);
}
}
).await?;
tracing::debug!("Loaded library {}", library.name);
Ok(())
},
)
.await?;
tracing::debug!("Done loading libraries!");
Ok(())

View File

@@ -12,6 +12,7 @@ use crate::state::{
AccountType, Credentials, JavaVersion, ProcessMetadata, ProfileInstallStage,
};
use crate::util::{io, utils};
use crate::util::rpc::RpcServerBuilder;
use crate::{State, get_resource_file, process, state as st};
use chrono::Utc;
use daedalus as d;
@@ -23,7 +24,6 @@ use serde::Deserialize;
use st::Profile;
use std::fmt::Write;
use std::path::PathBuf;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
mod args;
@@ -611,6 +611,8 @@ pub async fn launch_minecraft(
let (main_class_keep_alive, main_class_path) =
get_resource_file!(env "JAVA_JARS_DIR" / "theseus.jar")?;
let rpc_server = RpcServerBuilder::new().launch().await?;
command.args(
args::get_jvm_arguments(
args.get(&d::minecraft::ArgumentType::Jvm)
@@ -636,6 +638,7 @@ pub async fn launch_minecraft(
.logging
.as_ref()
.and_then(|x| x.get(&LoggingSide::Client)),
rpc_server.address(),
)?
.into_iter(),
);
@@ -800,7 +803,8 @@ pub async fn launch_minecraft(
state.directories.profile_logs_dir(&profile.path),
version_info.logging.is_some(),
main_class_keep_alive,
async |process: &ProcessMetadata, stdin| {
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();
@@ -823,14 +827,11 @@ pub async fn launch_minecraft(
let Some(value) = value else {
continue;
};
stdin.write_all(b"property\t").await?;
stdin.write_all(key.as_bytes()).await?;
stdin.write_u8(b'\t').await?;
stdin.write_all(value.as_bytes()).await?;
stdin.write_u8(b'\n').await?;
rpc_server
.call_method_2::<()>("set_system_property", key, value)
.await?;
}
stdin.write_all(b"launch\n").await?;
stdin.flush().await?;
rpc_server.call_method::<()>("launch").await?;
Ok(())
},
)