You've already forked AstralRinth
forked from didirus/AstralRinth
@@ -58,158 +58,161 @@ pub async fn install_minecraft(
|
||||
profile: &Profile,
|
||||
existing_loading_bar: Option<LoadingBarId>,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let instance_path = &canonicalize(&profile.path)?;
|
||||
let metadata = state.metadata.read().await;
|
||||
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
.find(|it| it.id == profile.metadata.game_version)
|
||||
.ok_or(crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid game version: {}",
|
||||
profile.metadata.game_version
|
||||
)))?;
|
||||
|
||||
let version_jar = profile
|
||||
.metadata
|
||||
.loader_version
|
||||
.as_ref()
|
||||
.map_or(version.id.clone(), |it| {
|
||||
format!("{}-{}", version.id.clone(), it.id.clone())
|
||||
});
|
||||
|
||||
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.metadata.name.clone(),
|
||||
profile_uuid: profile.uuid,
|
||||
},
|
||||
100.0,
|
||||
"Downloading Minecraft",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Download version info
|
||||
let mut version_info = download::download_version_info(
|
||||
&state,
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Download minecraft (5-90)
|
||||
download::download_minecraft(&state, &version_info, &loading_bar).await?;
|
||||
|
||||
let client_path = state
|
||||
.directories
|
||||
.version_dir(&version_jar)
|
||||
.join(format!("{version_jar}.jar"));
|
||||
|
||||
if let Some(processors) = &version_info.processors {
|
||||
if let Some(ref mut data) = version_info.data {
|
||||
processor_rules! {
|
||||
data;
|
||||
"SIDE":
|
||||
client => "client",
|
||||
server => "";
|
||||
"MINECRAFT_JAR" :
|
||||
client => client_path.to_string_lossy(),
|
||||
server => "";
|
||||
"MINECRAFT_VERSION":
|
||||
client => profile.metadata.game_version.clone(),
|
||||
server => "";
|
||||
"ROOT":
|
||||
client => instance_path.to_string_lossy(),
|
||||
server => "";
|
||||
"LIBRARY_DIR":
|
||||
client => state.directories.libraries_dir().to_string_lossy(),
|
||||
server => "";
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))
|
||||
.await?;
|
||||
let total_length = processors.len();
|
||||
|
||||
// Forge processors (90-100)
|
||||
for (index, processor) in processors.iter().enumerate() {
|
||||
emit_loading(
|
||||
&loading_bar,
|
||||
10.0 / total_length as f64,
|
||||
Some(&format!(
|
||||
"Running forge processor {}/{}",
|
||||
index, total_length
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(sides) = &processor.sides {
|
||||
if !sides.contains(&String::from("client")) {
|
||||
continue;
|
||||
}
|
||||
Box::pin(async move {
|
||||
let state = State::get().await?;
|
||||
let instance_path = &canonicalize(&profile.path)?;
|
||||
let metadata = state.metadata.read().await;
|
||||
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
.find(|it| it.id == profile.metadata.game_version)
|
||||
.ok_or(crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid game version: {}",
|
||||
profile.metadata.game_version
|
||||
)))?;
|
||||
|
||||
let version_jar = profile
|
||||
.metadata
|
||||
.loader_version
|
||||
.as_ref()
|
||||
.map_or(version.id.clone(), |it| {
|
||||
format!("{}-{}", version.id.clone(), it.id.clone())
|
||||
});
|
||||
|
||||
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.metadata.name.clone(),
|
||||
profile_uuid: profile.uuid,
|
||||
},
|
||||
100.0,
|
||||
"Downloading Minecraft",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Download version info
|
||||
let mut version_info = download::download_version_info(
|
||||
&state,
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Download minecraft (5-90)
|
||||
download::download_minecraft(&state, &version_info, &loading_bar).await?;
|
||||
|
||||
let client_path = state
|
||||
.directories
|
||||
.version_dir(&version_jar)
|
||||
.join(format!("{version_jar}.jar"));
|
||||
|
||||
if let Some(processors) = &version_info.processors {
|
||||
if let Some(ref mut data) = version_info.data {
|
||||
processor_rules! {
|
||||
data;
|
||||
"SIDE":
|
||||
client => "client",
|
||||
server => "";
|
||||
"MINECRAFT_JAR" :
|
||||
client => client_path.to_string_lossy(),
|
||||
server => "";
|
||||
"MINECRAFT_VERSION":
|
||||
client => profile.metadata.game_version.clone(),
|
||||
server => "";
|
||||
"ROOT":
|
||||
client => instance_path.to_string_lossy(),
|
||||
server => "";
|
||||
"LIBRARY_DIR":
|
||||
client => state.directories.libraries_dir().to_string_lossy(),
|
||||
server => "";
|
||||
}
|
||||
|
||||
let cp = wrap_ref_builder!(cp = processor.classpath.clone() => {
|
||||
cp.push(processor.jar.clone())
|
||||
});
|
||||
|
||||
let child = Command::new("java")
|
||||
.arg("-cp")
|
||||
.arg(args::get_class_paths_jar(
|
||||
&state.directories.libraries_dir(),
|
||||
&cp,
|
||||
)?)
|
||||
.arg(
|
||||
args::get_processor_main_class(args::get_lib_path(
|
||||
&state.directories.libraries_dir(),
|
||||
&processor.jar,
|
||||
false,
|
||||
)?)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Could not find processor main class for {}",
|
||||
processor.jar
|
||||
))
|
||||
})?,
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))
|
||||
.await?;
|
||||
let total_length = processors.len();
|
||||
|
||||
// Forge processors (90-100)
|
||||
for (index, processor) in processors.iter().enumerate() {
|
||||
emit_loading(
|
||||
&loading_bar,
|
||||
10.0 / total_length as f64,
|
||||
Some(&format!(
|
||||
"Running forge processor {}/{}",
|
||||
index, total_length
|
||||
)),
|
||||
)
|
||||
.args(args::get_processor_arguments(
|
||||
&state.directories.libraries_dir(),
|
||||
&processor.args,
|
||||
data,
|
||||
)?)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Error running processor: {err}",
|
||||
.await?;
|
||||
|
||||
if let Some(sides) = &processor.sides {
|
||||
if !sides.contains(&String::from("client")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let cp = wrap_ref_builder!(cp = processor.classpath.clone() => {
|
||||
cp.push(processor.jar.clone())
|
||||
});
|
||||
|
||||
let child = Command::new("java")
|
||||
.arg("-cp")
|
||||
.arg(args::get_class_paths_jar(
|
||||
&state.directories.libraries_dir(),
|
||||
&cp,
|
||||
)?)
|
||||
.arg(
|
||||
args::get_processor_main_class(args::get_lib_path(
|
||||
&state.directories.libraries_dir(),
|
||||
&processor.jar,
|
||||
false,
|
||||
)?)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Could not find processor main class for {}",
|
||||
processor.jar
|
||||
))
|
||||
})?,
|
||||
)
|
||||
.args(args::get_processor_arguments(
|
||||
&state.directories.libraries_dir(),
|
||||
&processor.args,
|
||||
data,
|
||||
)?)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Error running processor: {err}",
|
||||
))
|
||||
})?;
|
||||
|
||||
if !child.status.success() {
|
||||
return Err(crate::ErrorKind::LauncherError(format!(
|
||||
"Processor error: {}",
|
||||
String::from_utf8_lossy(&child.stderr)
|
||||
))
|
||||
})?;
|
||||
|
||||
if !child.status.success() {
|
||||
return Err(crate::ErrorKind::LauncherError(format!(
|
||||
"Processor error: {}",
|
||||
String::from_utf8_lossy(&child.stderr)
|
||||
))
|
||||
.as_error());
|
||||
.as_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.installed = true;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
State::sync().await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.installed = true;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
State::sync().await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
}).await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -224,140 +227,143 @@ pub async fn launch_minecraft(
|
||||
post_exit_hook: Option<Command>,
|
||||
profile: &Profile,
|
||||
) -> crate::Result<Arc<tokio::sync::RwLock<MinecraftChild>>> {
|
||||
if !profile.installed {
|
||||
install_minecraft(profile, None).await?;
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
let metadata = state.metadata.read().await;
|
||||
let instance_path = &canonicalize(&profile.path)?;
|
||||
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
.find(|it| it.id == profile.metadata.game_version)
|
||||
.ok_or(crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid game version: {}",
|
||||
profile.metadata.game_version
|
||||
)))?;
|
||||
|
||||
let version_jar = profile
|
||||
.metadata
|
||||
.loader_version
|
||||
.as_ref()
|
||||
.map_or(version.id.clone(), |it| {
|
||||
format!("{}-{}", version.id.clone(), it.id.clone())
|
||||
});
|
||||
|
||||
let version_info = download::download_version_info(
|
||||
&state,
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let client_path = state
|
||||
.directories
|
||||
.version_dir(&version_jar)
|
||||
.join(format!("{version_jar}.jar"));
|
||||
|
||||
let args = version_info.arguments.clone().unwrap_or_default();
|
||||
let mut command = match wrapper {
|
||||
Some(hook) => {
|
||||
wrap_ref_builder!(it = Command::new(hook) => {it.arg(java_install)})
|
||||
Box::pin(async move {
|
||||
if !profile.installed {
|
||||
install_minecraft(profile, None).await?;
|
||||
}
|
||||
None => Command::new(String::from(java_install.to_string_lossy())),
|
||||
};
|
||||
|
||||
let env_args = Vec::from(env_args);
|
||||
|
||||
// Check if profile has a running profile, 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_uuids_by_profile_path(instance_path).await?;
|
||||
if let Some(uuid) = existing_processes.first() {
|
||||
return Err(crate::ErrorKind::LauncherError(format!(
|
||||
"Profile {} is already running at UUID: {uuid}",
|
||||
instance_path.display()
|
||||
))
|
||||
.as_error());
|
||||
}
|
||||
|
||||
command
|
||||
.args(
|
||||
args::get_jvm_arguments(
|
||||
args.get(&d::minecraft::ArgumentType::Jvm)
|
||||
.map(|x| x.as_slice()),
|
||||
&state.directories.version_natives_dir(&version_jar),
|
||||
&state.directories.libraries_dir(),
|
||||
&args::get_class_paths(
|
||||
|
||||
let state = State::get().await?;
|
||||
let metadata = state.metadata.read().await;
|
||||
let instance_path = &canonicalize(&profile.path)?;
|
||||
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
.find(|it| it.id == profile.metadata.game_version)
|
||||
.ok_or(crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid game version: {}",
|
||||
profile.metadata.game_version
|
||||
)))?;
|
||||
|
||||
let version_jar = profile
|
||||
.metadata
|
||||
.loader_version
|
||||
.as_ref()
|
||||
.map_or(version.id.clone(), |it| {
|
||||
format!("{}-{}", version.id.clone(), it.id.clone())
|
||||
});
|
||||
|
||||
let version_info = download::download_version_info(
|
||||
&state,
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let client_path = state
|
||||
.directories
|
||||
.version_dir(&version_jar)
|
||||
.join(format!("{version_jar}.jar"));
|
||||
|
||||
let args = version_info.arguments.clone().unwrap_or_default();
|
||||
let mut command = match wrapper {
|
||||
Some(hook) => {
|
||||
wrap_ref_builder!(it = Command::new(hook) => {it.arg(java_install)})
|
||||
}
|
||||
None => Command::new(String::from(java_install.to_string_lossy())),
|
||||
};
|
||||
|
||||
let env_args = Vec::from(env_args);
|
||||
|
||||
// Check if profile has a running profile, 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_uuids_by_profile_path(instance_path).await?;
|
||||
if let Some(uuid) = existing_processes.first() {
|
||||
return Err(crate::ErrorKind::LauncherError(format!(
|
||||
"Profile {} is already running at UUID: {uuid}",
|
||||
instance_path.display()
|
||||
))
|
||||
.as_error());
|
||||
}
|
||||
|
||||
command
|
||||
.args(
|
||||
args::get_jvm_arguments(
|
||||
args.get(&d::minecraft::ArgumentType::Jvm)
|
||||
.map(|x| x.as_slice()),
|
||||
&state.directories.version_natives_dir(&version_jar),
|
||||
&state.directories.libraries_dir(),
|
||||
version_info.libraries.as_slice(),
|
||||
&client_path,
|
||||
)?,
|
||||
&version_jar,
|
||||
*memory,
|
||||
Vec::from(java_args),
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.arg(version_info.main_class.clone())
|
||||
.args(
|
||||
args::get_minecraft_arguments(
|
||||
args.get(&d::minecraft::ArgumentType::Game)
|
||||
.map(|x| x.as_slice()),
|
||||
version_info.minecraft_arguments.as_deref(),
|
||||
credentials,
|
||||
&version.id,
|
||||
&version_info.asset_index.id,
|
||||
instance_path,
|
||||
&state.directories.assets_dir(),
|
||||
&version.type_,
|
||||
*resolution,
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.current_dir(instance_path.clone())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
// CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
|
||||
#[cfg(target_os = "macos")]
|
||||
if std::env::var("CARGO").is_ok() {
|
||||
command.env_remove("DYLD_FALLBACK_LIBRARY_PATH");
|
||||
}
|
||||
command.envs(env_args);
|
||||
|
||||
// Get Modrinth logs directories
|
||||
let datetime_string =
|
||||
chrono::Local::now().format("%Y%m%y_%H%M%S").to_string();
|
||||
let logs_dir = {
|
||||
let st = State::get().await?;
|
||||
st.directories
|
||||
.profile_logs_dir(profile.uuid)
|
||||
.join(&datetime_string)
|
||||
};
|
||||
fs::create_dir_all(&logs_dir)?;
|
||||
|
||||
let stdout_log_path = logs_dir.join("stdout.log");
|
||||
let stderr_log_path = logs_dir.join("stderr.log");
|
||||
|
||||
// Create Minecraft child by inserting it into the state
|
||||
// This also spawns the process and prepares the subsequent processes
|
||||
let mut state_children = state.children.write().await;
|
||||
state_children
|
||||
.insert_process(
|
||||
Uuid::new_v4(),
|
||||
instance_path.to_path_buf(),
|
||||
stdout_log_path,
|
||||
stderr_log_path,
|
||||
command,
|
||||
post_exit_hook,
|
||||
)
|
||||
.await
|
||||
&args::get_class_paths(
|
||||
&state.directories.libraries_dir(),
|
||||
version_info.libraries.as_slice(),
|
||||
&client_path,
|
||||
)?,
|
||||
&version_jar,
|
||||
*memory,
|
||||
Vec::from(java_args),
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.arg(version_info.main_class.clone())
|
||||
.args(
|
||||
args::get_minecraft_arguments(
|
||||
args.get(&d::minecraft::ArgumentType::Game)
|
||||
.map(|x| x.as_slice()),
|
||||
version_info.minecraft_arguments.as_deref(),
|
||||
credentials,
|
||||
&version.id,
|
||||
&version_info.asset_index.id,
|
||||
instance_path,
|
||||
&state.directories.assets_dir(),
|
||||
&version.type_,
|
||||
*resolution,
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.current_dir(instance_path.clone())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
// CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
|
||||
#[cfg(target_os = "macos")]
|
||||
if std::env::var("CARGO").is_ok() {
|
||||
command.env_remove("DYLD_FALLBACK_LIBRARY_PATH");
|
||||
}
|
||||
command.envs(env_args);
|
||||
|
||||
// Get Modrinth logs directories
|
||||
let datetime_string =
|
||||
chrono::Local::now().format("%Y%m%y_%H%M%S").to_string();
|
||||
let logs_dir = {
|
||||
let st = State::get().await?;
|
||||
st.directories
|
||||
.profile_logs_dir(profile.uuid)
|
||||
.join(&datetime_string)
|
||||
};
|
||||
fs::create_dir_all(&logs_dir)?;
|
||||
|
||||
let stdout_log_path = logs_dir.join("stdout.log");
|
||||
let stderr_log_path = logs_dir.join("stderr.log");
|
||||
|
||||
// Create Minecraft child by inserting it into the state
|
||||
// This also spawns the process and prepares the subsequent processes
|
||||
let mut state_children = state.children.write().await;
|
||||
state_children
|
||||
.insert_process(
|
||||
Uuid::new_v4(),
|
||||
instance_path.to_path_buf(),
|
||||
stdout_log_path,
|
||||
stderr_log_path,
|
||||
command,
|
||||
post_exit_hook,
|
||||
)
|
||||
.await
|
||||
|
||||
}).await
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user