parent
1796c48fc7
commit
da4fc1c835
@ -79,97 +79,101 @@ pub async fn install_pack_from_version_id(
|
|||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
) -> crate::Result<PathBuf> {
|
) -> crate::Result<PathBuf> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
|
Box::pin(async move {
|
||||||
|
let loading_bar = init_loading(
|
||||||
|
LoadingBarType::PackFileDownload {
|
||||||
|
pack_name: title,
|
||||||
|
pack_version: version_id.clone(),
|
||||||
|
},
|
||||||
|
100.0,
|
||||||
|
"Downloading pack file",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let loading_bar = init_loading(
|
emit_loading(&loading_bar, 0.0, Some("Fetching version")).await?;
|
||||||
LoadingBarType::PackFileDownload {
|
let version: ModrinthVersion = fetch_json(
|
||||||
pack_name: title,
|
Method::GET,
|
||||||
pack_version: version_id.clone(),
|
&format!("{}version/{}", MODRINTH_API_URL, version_id),
|
||||||
},
|
None,
|
||||||
100.0,
|
None,
|
||||||
"Downloading pack file",
|
&state.fetch_semaphore,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
emit_loading(&loading_bar, 10.0, None).await?;
|
||||||
|
|
||||||
emit_loading(&loading_bar, 0.0, Some("Fetching version")).await?;
|
let (url, hash) =
|
||||||
let version: ModrinthVersion = fetch_json(
|
if let Some(file) = version.files.iter().find(|x| x.primary) {
|
||||||
Method::GET,
|
Some((file.url.clone(), file.hashes.get("sha1")))
|
||||||
&format!("{}version/{}", MODRINTH_API_URL, version_id),
|
} else {
|
||||||
None,
|
version
|
||||||
None,
|
.files
|
||||||
&state.fetch_semaphore,
|
.first()
|
||||||
)
|
.map(|file| (file.url.clone(), file.hashes.get("sha1")))
|
||||||
.await?;
|
}
|
||||||
emit_loading(&loading_bar, 10.0, None).await?;
|
.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::InputError(
|
||||||
let (url, hash) =
|
"Specified version has no files".to_string(),
|
||||||
if let Some(file) = version.files.iter().find(|x| x.primary) {
|
|
||||||
Some((file.url.clone(), file.hashes.get("sha1")))
|
|
||||||
} else {
|
|
||||||
version
|
|
||||||
.files
|
|
||||||
.first()
|
|
||||||
.map(|file| (file.url.clone(), file.hashes.get("sha1")))
|
|
||||||
}
|
|
||||||
.ok_or_else(|| {
|
|
||||||
crate::ErrorKind::InputError(
|
|
||||||
"Specified version has no files".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let file = fetch_advanced(
|
|
||||||
Method::GET,
|
|
||||||
&url,
|
|
||||||
hash.map(|x| &**x),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
Some((&loading_bar, 70.0)),
|
|
||||||
&state.fetch_semaphore,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
emit_loading(&loading_bar, 0.0, Some("Fetching project metadata")).await?;
|
|
||||||
|
|
||||||
let project: ModrinthProject = fetch_json(
|
|
||||||
Method::GET,
|
|
||||||
&format!("{}project/{}", MODRINTH_API_URL, version.project_id),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
&state.fetch_semaphore,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
emit_loading(&loading_bar, 10.0, Some("Retrieving icon")).await?;
|
|
||||||
let icon = if let Some(icon_url) = project.icon_url {
|
|
||||||
let state = State::get().await?;
|
|
||||||
let icon_bytes = fetch(&icon_url, None, &state.fetch_semaphore).await?;
|
|
||||||
|
|
||||||
let filename = icon_url.rsplit('/').next();
|
|
||||||
|
|
||||||
if let Some(filename) = filename {
|
|
||||||
Some(
|
|
||||||
write_cached_icon(
|
|
||||||
filename,
|
|
||||||
&state.directories.caches_dir(),
|
|
||||||
icon_bytes,
|
|
||||||
&state.io_semaphore,
|
|
||||||
)
|
)
|
||||||
.await?,
|
})?;
|
||||||
)
|
|
||||||
|
let file = fetch_advanced(
|
||||||
|
Method::GET,
|
||||||
|
&url,
|
||||||
|
hash.map(|x| &**x),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some((&loading_bar, 70.0)),
|
||||||
|
&state.fetch_semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
emit_loading(&loading_bar, 0.0, Some("Fetching project metadata"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let project: ModrinthProject = fetch_json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("{}project/{}", MODRINTH_API_URL, version.project_id),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&state.fetch_semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
emit_loading(&loading_bar, 10.0, Some("Retrieving icon")).await?;
|
||||||
|
let icon = if let Some(icon_url) = project.icon_url {
|
||||||
|
let state = State::get().await?;
|
||||||
|
let icon_bytes =
|
||||||
|
fetch(&icon_url, None, &state.fetch_semaphore).await?;
|
||||||
|
|
||||||
|
let filename = icon_url.rsplit('/').next();
|
||||||
|
|
||||||
|
if let Some(filename) = filename {
|
||||||
|
Some(
|
||||||
|
write_cached_icon(
|
||||||
|
filename,
|
||||||
|
&state.directories.caches_dir(),
|
||||||
|
icon_bytes,
|
||||||
|
&state.io_semaphore,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
};
|
||||||
} else {
|
emit_loading(&loading_bar, 10.0, None).await?;
|
||||||
None
|
|
||||||
};
|
|
||||||
emit_loading(&loading_bar, 10.0, None).await?;
|
|
||||||
|
|
||||||
install_pack(
|
install_pack(
|
||||||
file,
|
file,
|
||||||
icon,
|
icon,
|
||||||
Some(project.title),
|
Some(project.title),
|
||||||
Some(version.project_id),
|
Some(version.project_id),
|
||||||
Some(version.id),
|
Some(version.id),
|
||||||
Some(loading_bar),
|
Some(loading_bar),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,243 +193,259 @@ async fn install_pack(
|
|||||||
) -> crate::Result<PathBuf> {
|
) -> crate::Result<PathBuf> {
|
||||||
let state = &State::get().await?;
|
let state = &State::get().await?;
|
||||||
|
|
||||||
let reader = Cursor::new(&file);
|
Box::pin(async move {
|
||||||
|
let reader: Cursor<&bytes::Bytes> = Cursor::new(&file);
|
||||||
|
|
||||||
// Create zip reader around file
|
// Create zip reader around file
|
||||||
let mut zip_reader = ZipFileReader::new(reader).await.map_err(|_| {
|
let mut zip_reader =
|
||||||
crate::Error::from(crate::ErrorKind::InputError(
|
ZipFileReader::new(reader).await.map_err(|_| {
|
||||||
"Failed to read input modpack zip".to_string(),
|
crate::Error::from(crate::ErrorKind::InputError(
|
||||||
))
|
"Failed to read input modpack zip".to_string(),
|
||||||
})?;
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
// Extract index of modrinth.index.json
|
// Extract index of modrinth.index.json
|
||||||
let zip_index_option = zip_reader
|
let zip_index_option = zip_reader
|
||||||
.file()
|
|
||||||
.entries()
|
|
||||||
.iter()
|
|
||||||
.position(|f| f.entry().filename() == "modrinth.index.json");
|
|
||||||
if let Some(zip_index) = zip_index_option {
|
|
||||||
let mut manifest = String::new();
|
|
||||||
let entry = zip_reader
|
|
||||||
.file()
|
.file()
|
||||||
.entries()
|
.entries()
|
||||||
.get(zip_index)
|
.iter()
|
||||||
.unwrap()
|
.position(|f| f.entry().filename() == "modrinth.index.json");
|
||||||
.entry()
|
if let Some(zip_index) = zip_index_option {
|
||||||
.clone();
|
let mut manifest = String::new();
|
||||||
let mut reader = zip_reader.entry(zip_index).await?;
|
let entry = zip_reader
|
||||||
reader.read_to_string_checked(&mut manifest, &entry).await?;
|
.file()
|
||||||
|
.entries()
|
||||||
|
.get(zip_index)
|
||||||
|
.unwrap()
|
||||||
|
.entry()
|
||||||
|
.clone();
|
||||||
|
let mut reader = zip_reader.entry(zip_index).await?;
|
||||||
|
reader.read_to_string_checked(&mut manifest, &entry).await?;
|
||||||
|
|
||||||
let pack: PackFormat = serde_json::from_str(&manifest)?;
|
let pack: PackFormat = serde_json::from_str(&manifest)?;
|
||||||
|
|
||||||
if &*pack.game != "minecraft" {
|
if &*pack.game != "minecraft" {
|
||||||
return Err(crate::ErrorKind::InputError(
|
return Err(crate::ErrorKind::InputError(
|
||||||
"Pack does not support Minecraft".to_string(),
|
"Pack does not support Minecraft".to_string(),
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
|
||||||
|
|
||||||
let mut game_version = None;
|
|
||||||
let mut mod_loader = None;
|
|
||||||
let mut loader_version = None;
|
|
||||||
for (key, value) in &pack.dependencies {
|
|
||||||
match key {
|
|
||||||
PackDependency::Forge => {
|
|
||||||
mod_loader = Some(ModLoader::Forge);
|
|
||||||
loader_version = Some(value);
|
|
||||||
}
|
|
||||||
PackDependency::FabricLoader => {
|
|
||||||
mod_loader = Some(ModLoader::Fabric);
|
|
||||||
loader_version = Some(value);
|
|
||||||
}
|
|
||||||
PackDependency::QuiltLoader => {
|
|
||||||
mod_loader = Some(ModLoader::Quilt);
|
|
||||||
loader_version = Some(value);
|
|
||||||
}
|
|
||||||
PackDependency::Minecraft => game_version = Some(value),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let game_version = if let Some(game_version) = game_version {
|
let mut game_version = None;
|
||||||
game_version
|
let mut mod_loader = None;
|
||||||
} else {
|
let mut loader_version = None;
|
||||||
return Err(crate::ErrorKind::InputError(
|
for (key, value) in &pack.dependencies {
|
||||||
"Pack did not specify Minecraft version".to_string(),
|
match key {
|
||||||
)
|
PackDependency::Forge => {
|
||||||
.into());
|
mod_loader = Some(ModLoader::Forge);
|
||||||
};
|
loader_version = Some(value);
|
||||||
|
|
||||||
let profile_raw = crate::api::profile_create::profile_create(
|
|
||||||
override_title.unwrap_or_else(|| pack.name.clone()),
|
|
||||||
game_version.clone(),
|
|
||||||
mod_loader.unwrap_or(ModLoader::Vanilla),
|
|
||||||
loader_version.cloned(),
|
|
||||||
icon,
|
|
||||||
Some(LinkedData {
|
|
||||||
project_id: project_id.clone(),
|
|
||||||
version_id: version_id.clone(),
|
|
||||||
}),
|
|
||||||
Some(true),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let profile = profile_raw.clone();
|
|
||||||
let result = async {
|
|
||||||
let loading_bar = init_or_edit_loading(
|
|
||||||
existing_loading_bar,
|
|
||||||
LoadingBarType::PackDownload {
|
|
||||||
pack_name: pack.name.clone(),
|
|
||||||
pack_id: project_id,
|
|
||||||
pack_version: version_id,
|
|
||||||
},
|
|
||||||
100.0,
|
|
||||||
"Downloading modpack",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let num_files = pack.files.len();
|
|
||||||
use futures::StreamExt;
|
|
||||||
loading_try_for_each_concurrent(
|
|
||||||
futures::stream::iter(pack.files.into_iter())
|
|
||||||
.map(Ok::<PackFile, crate::Error>),
|
|
||||||
None,
|
|
||||||
Some(&loading_bar),
|
|
||||||
70.0,
|
|
||||||
num_files,
|
|
||||||
None,
|
|
||||||
|project| {
|
|
||||||
let profile = profile.clone();
|
|
||||||
async move {
|
|
||||||
//TODO: Future update: prompt user for optional files in a modpack
|
|
||||||
if let Some(env) = project.env {
|
|
||||||
if env
|
|
||||||
.get(&EnvType::Client)
|
|
||||||
.map(|x| x == &SideType::Unsupported)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let file = fetch_mirrors(
|
|
||||||
&project
|
|
||||||
.downloads
|
|
||||||
.iter()
|
|
||||||
.map(|x| &**x)
|
|
||||||
.collect::<Vec<&str>>(),
|
|
||||||
project
|
|
||||||
.hashes
|
|
||||||
.get(&PackFileHash::Sha1)
|
|
||||||
.map(|x| &**x),
|
|
||||||
&state.fetch_semaphore,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let path = std::path::Path::new(&project.path)
|
|
||||||
.components()
|
|
||||||
.next();
|
|
||||||
if let Some(path) = path {
|
|
||||||
match path {
|
|
||||||
Component::CurDir | Component::Normal(_) => {
|
|
||||||
let path = profile.join(project.path);
|
|
||||||
write(&path, &file, &state.io_semaphore)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
},
|
PackDependency::FabricLoader => {
|
||||||
)
|
mod_loader = Some(ModLoader::Fabric);
|
||||||
.await?;
|
loader_version = Some(value);
|
||||||
|
|
||||||
let extract_overrides = |overrides: String| async {
|
|
||||||
let reader = Cursor::new(&file);
|
|
||||||
|
|
||||||
let mut overrides_zip =
|
|
||||||
ZipFileReader::new(reader).await.map_err(|_| {
|
|
||||||
crate::Error::from(crate::ErrorKind::InputError(
|
|
||||||
"Failed extract overrides Zip".to_string(),
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let profile = profile.clone();
|
|
||||||
async move {
|
|
||||||
for index in 0..overrides_zip.file().entries().len() {
|
|
||||||
let file = overrides_zip
|
|
||||||
.file()
|
|
||||||
.entries()
|
|
||||||
.get(index)
|
|
||||||
.unwrap()
|
|
||||||
.entry()
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let file_path = PathBuf::from(file.filename());
|
|
||||||
if file.filename().starts_with(&overrides)
|
|
||||||
&& !file.filename().ends_with('/')
|
|
||||||
{
|
|
||||||
// Reads the file into the 'content' variable
|
|
||||||
let mut content = Vec::new();
|
|
||||||
let mut reader = overrides_zip.entry(index).await?;
|
|
||||||
reader
|
|
||||||
.read_to_end_checked(&mut content, &file)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut new_path = PathBuf::new();
|
|
||||||
let components = file_path.components().skip(1);
|
|
||||||
|
|
||||||
for component in components {
|
|
||||||
new_path.push(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_path.file_name().is_some() {
|
|
||||||
write(
|
|
||||||
&profile.join(new_path),
|
|
||||||
&content,
|
|
||||||
&state.io_semaphore,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
PackDependency::QuiltLoader => {
|
||||||
Ok::<(), crate::Error>(())
|
mod_loader = Some(ModLoader::Quilt);
|
||||||
|
loader_version = Some(value);
|
||||||
|
}
|
||||||
|
PackDependency::Minecraft => game_version = Some(value),
|
||||||
}
|
}
|
||||||
.await
|
}
|
||||||
|
|
||||||
|
let game_version = if let Some(game_version) = game_version {
|
||||||
|
game_version
|
||||||
|
} else {
|
||||||
|
return Err(crate::ErrorKind::InputError(
|
||||||
|
"Pack did not specify Minecraft version".to_string(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))
|
let profile_raw = crate::api::profile_create::profile_create(
|
||||||
.await?;
|
override_title.unwrap_or_else(|| pack.name.clone()),
|
||||||
extract_overrides("overrides".to_string()).await?;
|
game_version.clone(),
|
||||||
extract_overrides("client_overrides".to_string()).await?;
|
mod_loader.unwrap_or(ModLoader::Vanilla),
|
||||||
emit_loading(&loading_bar, 29.9, Some("Done extacting overrides"))
|
loader_version.cloned(),
|
||||||
|
icon,
|
||||||
|
Some(LinkedData {
|
||||||
|
project_id: project_id.clone(),
|
||||||
|
version_id: version_id.clone(),
|
||||||
|
}),
|
||||||
|
Some(true),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let profile = profile_raw.clone();
|
||||||
|
let result = async {
|
||||||
|
let loading_bar = init_or_edit_loading(
|
||||||
|
existing_loading_bar,
|
||||||
|
LoadingBarType::PackDownload {
|
||||||
|
pack_name: pack.name.clone(),
|
||||||
|
pack_id: project_id,
|
||||||
|
pack_version: version_id,
|
||||||
|
},
|
||||||
|
100.0,
|
||||||
|
"Downloading modpack",
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(profile) = crate::api::profile::get(&profile).await? {
|
let num_files = pack.files.len();
|
||||||
tokio::try_join!(
|
use futures::StreamExt;
|
||||||
super::profile::sync(&profile.path),
|
loading_try_for_each_concurrent(
|
||||||
crate::launcher::install_minecraft(
|
futures::stream::iter(pack.files.into_iter())
|
||||||
&profile,
|
.map(Ok::<PackFile, crate::Error>),
|
||||||
Some(loading_bar)
|
None,
|
||||||
),
|
Some(&loading_bar),
|
||||||
)?;
|
70.0,
|
||||||
|
num_files,
|
||||||
|
None,
|
||||||
|
|project| {
|
||||||
|
let profile = profile.clone();
|
||||||
|
async move {
|
||||||
|
//TODO: Future update: prompt user for optional files in a modpack
|
||||||
|
if let Some(env) = project.env {
|
||||||
|
if env
|
||||||
|
.get(&EnvType::Client)
|
||||||
|
.map(|x| x == &SideType::Unsupported)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = fetch_mirrors(
|
||||||
|
&project
|
||||||
|
.downloads
|
||||||
|
.iter()
|
||||||
|
.map(|x| &**x)
|
||||||
|
.collect::<Vec<&str>>(),
|
||||||
|
project
|
||||||
|
.hashes
|
||||||
|
.get(&PackFileHash::Sha1)
|
||||||
|
.map(|x| &**x),
|
||||||
|
&state.fetch_semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let path = std::path::Path::new(&project.path)
|
||||||
|
.components()
|
||||||
|
.next();
|
||||||
|
if let Some(path) = path {
|
||||||
|
match path {
|
||||||
|
Component::CurDir
|
||||||
|
| Component::Normal(_) => {
|
||||||
|
let path = profile.join(project.path);
|
||||||
|
write(
|
||||||
|
&path,
|
||||||
|
&file,
|
||||||
|
&state.io_semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let extract_overrides = |overrides: String| async {
|
||||||
|
let reader = Cursor::new(&file);
|
||||||
|
|
||||||
|
let mut overrides_zip =
|
||||||
|
ZipFileReader::new(reader).await.map_err(|_| {
|
||||||
|
crate::Error::from(crate::ErrorKind::InputError(
|
||||||
|
"Failed extract overrides Zip".to_string(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let profile = profile.clone();
|
||||||
|
async move {
|
||||||
|
for index in 0..overrides_zip.file().entries().len() {
|
||||||
|
let file = overrides_zip
|
||||||
|
.file()
|
||||||
|
.entries()
|
||||||
|
.get(index)
|
||||||
|
.unwrap()
|
||||||
|
.entry()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let file_path = PathBuf::from(file.filename());
|
||||||
|
if file.filename().starts_with(&overrides)
|
||||||
|
&& !file.filename().ends_with('/')
|
||||||
|
{
|
||||||
|
// Reads the file into the 'content' variable
|
||||||
|
let mut content = Vec::new();
|
||||||
|
let mut reader =
|
||||||
|
overrides_zip.entry(index).await?;
|
||||||
|
reader
|
||||||
|
.read_to_end_checked(&mut content, &file)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut new_path = PathBuf::new();
|
||||||
|
let components = file_path.components().skip(1);
|
||||||
|
|
||||||
|
for component in components {
|
||||||
|
new_path.push(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_path.file_name().is_some() {
|
||||||
|
write(
|
||||||
|
&profile.join(new_path),
|
||||||
|
&content,
|
||||||
|
&state.io_semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<(), crate::Error>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
|
emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))
|
||||||
|
.await?;
|
||||||
|
extract_overrides("overrides".to_string()).await?;
|
||||||
|
extract_overrides("client_overrides".to_string()).await?;
|
||||||
|
emit_loading(
|
||||||
|
&loading_bar,
|
||||||
|
29.9,
|
||||||
|
Some("Done extacting overrides"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(profile) =
|
||||||
|
crate::api::profile::get(&profile).await?
|
||||||
|
{
|
||||||
|
tokio::try_join!(
|
||||||
|
super::profile::sync(&profile.path),
|
||||||
|
crate::launcher::install_minecraft(
|
||||||
|
&profile,
|
||||||
|
Some(loading_bar)
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<PathBuf, crate::Error>(profile)
|
||||||
}
|
}
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok::<PathBuf, crate::Error>(profile)
|
match result {
|
||||||
}
|
Ok(profile) => Ok(profile),
|
||||||
.await;
|
Err(err) => {
|
||||||
|
let _ = crate::api::profile::remove(&profile_raw).await;
|
||||||
|
|
||||||
match result {
|
Err(err)
|
||||||
Ok(profile) => Ok(profile),
|
}
|
||||||
Err(err) => {
|
|
||||||
let _ = crate::api::profile::remove(&profile_raw).await;
|
|
||||||
|
|
||||||
Err(err)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Err(crate::Error::from(crate::ErrorKind::InputError(
|
||||||
|
"No pack manifest found in mrpack".to_string(),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
Err(crate::Error::from(crate::ErrorKind::InputError(
|
.await
|
||||||
"No pack manifest found in mrpack".to_string(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,41 +139,44 @@ pub async fn install(path: &Path) -> crate::Result<()> {
|
|||||||
|
|
||||||
pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
|
pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
let mut profiles = state.profiles.write().await;
|
Box::pin(async move {
|
||||||
|
let mut profiles = state.profiles.write().await;
|
||||||
|
|
||||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||||
let loading_bar = init_loading(
|
let loading_bar = init_loading(
|
||||||
LoadingBarType::ProfileUpdate {
|
LoadingBarType::ProfileUpdate {
|
||||||
profile_uuid: profile.uuid,
|
profile_uuid: profile.uuid,
|
||||||
profile_name: profile.metadata.name.clone(),
|
profile_name: profile.metadata.name.clone(),
|
||||||
},
|
},
|
||||||
100.0,
|
100.0,
|
||||||
"Updating profile",
|
"Updating profile",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
loading_try_for_each_concurrent(
|
loading_try_for_each_concurrent(
|
||||||
futures::stream::iter(profile.projects.keys())
|
futures::stream::iter(profile.projects.keys())
|
||||||
.map(Ok::<&PathBuf, crate::Error>),
|
.map(Ok::<&PathBuf, crate::Error>),
|
||||||
None,
|
None,
|
||||||
Some(&loading_bar),
|
Some(&loading_bar),
|
||||||
100.0,
|
100.0,
|
||||||
profile.projects.keys().len(),
|
profile.projects.keys().len(),
|
||||||
None,
|
None,
|
||||||
|project| update_project(profile_path, project, Some(true)),
|
|project| update_project(profile_path, project, Some(true)),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
profile.sync().await?;
|
profile.sync().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||||
profile_path.display().to_string(),
|
profile_path.display().to_string(),
|
||||||
)
|
)
|
||||||
.as_error())
|
.as_error())
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_project(
|
pub async fn update_project(
|
||||||
@ -372,146 +375,148 @@ pub async fn run_credentials(
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
credentials: &auth::Credentials,
|
credentials: &auth::Credentials,
|
||||||
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||||
let state = State::get().await?;
|
Box::pin(async move {
|
||||||
let settings = state.settings.read().await;
|
let state = State::get().await?;
|
||||||
let metadata = state.metadata.read().await;
|
let settings = state.settings.read().await;
|
||||||
let profile = get(path).await?.ok_or_else(|| {
|
let metadata = state.metadata.read().await;
|
||||||
crate::ErrorKind::OtherError(format!(
|
let profile = get(path).await?.ok_or_else(|| {
|
||||||
"Tried to run a nonexistent or unloaded profile at path {}!",
|
crate::ErrorKind::OtherError(format!(
|
||||||
path.display()
|
"Tried to run a nonexistent or unloaded profile at path {}!",
|
||||||
))
|
path.display()
|
||||||
})?;
|
|
||||||
|
|
||||||
let version = metadata
|
|
||||||
.minecraft
|
|
||||||
.versions
|
|
||||||
.iter()
|
|
||||||
.find(|it| it.id == profile.metadata.game_version)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
crate::ErrorKind::LauncherError(format!(
|
|
||||||
"Invalid or unknown Minecraft version: {}",
|
|
||||||
profile.metadata.game_version
|
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let version_info = download::download_version_info(
|
|
||||||
&state,
|
|
||||||
version,
|
|
||||||
profile.metadata.loader_version.as_ref(),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let pre_launch_hooks =
|
|
||||||
&profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch;
|
|
||||||
if let Some(hook) = pre_launch_hooks {
|
|
||||||
// TODO: hook parameters
|
|
||||||
let mut cmd = hook.split(' ');
|
|
||||||
if let Some(command) = cmd.next() {
|
|
||||||
let result = Command::new(command)
|
|
||||||
.args(&cmd.collect::<Vec<&str>>())
|
|
||||||
.current_dir(path)
|
|
||||||
.spawn()?
|
|
||||||
.wait()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !result.success() {
|
let version = metadata
|
||||||
return Err(crate::ErrorKind::LauncherError(format!(
|
.minecraft
|
||||||
"Non-zero exit code for pre-launch hook: {}",
|
.versions
|
||||||
result.code().unwrap_or(-1)
|
.iter()
|
||||||
|
.find(|it| it.id == profile.metadata.game_version)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::LauncherError(format!(
|
||||||
|
"Invalid or unknown Minecraft version: {}",
|
||||||
|
profile.metadata.game_version
|
||||||
))
|
))
|
||||||
.as_error());
|
})?;
|
||||||
|
let version_info = download::download_version_info(
|
||||||
|
&state,
|
||||||
|
version,
|
||||||
|
profile.metadata.loader_version.as_ref(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let pre_launch_hooks =
|
||||||
|
&profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch;
|
||||||
|
if let Some(hook) = pre_launch_hooks {
|
||||||
|
// TODO: hook parameters
|
||||||
|
let mut cmd = hook.split(' ');
|
||||||
|
if let Some(command) = cmd.next() {
|
||||||
|
let result = Command::new(command)
|
||||||
|
.args(&cmd.collect::<Vec<&str>>())
|
||||||
|
.current_dir(path)
|
||||||
|
.spawn()?
|
||||||
|
.wait()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !result.success() {
|
||||||
|
return Err(crate::ErrorKind::LauncherError(format!(
|
||||||
|
"Non-zero exit code for pre-launch hook: {}",
|
||||||
|
result.code().unwrap_or(-1)
|
||||||
|
))
|
||||||
|
.as_error());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let java_version = match profile.java {
|
let java_version = match profile.java {
|
||||||
// Load profile-specific Java implementation choice
|
// Load profile-specific Java implementation choice
|
||||||
// (This defaults to Daedalus-decided key on init, but can be changed by the user)
|
// (This defaults to Daedalus-decided key on init, but can be changed by the user)
|
||||||
Some(JavaSettings {
|
Some(JavaSettings {
|
||||||
jre_key: Some(ref jre_key),
|
jre_key: Some(ref jre_key),
|
||||||
..
|
..
|
||||||
}) => settings.java_globals.get(jre_key),
|
}) => settings.java_globals.get(jre_key),
|
||||||
// Fall back to Daedalus-decided key if no profile-specific key is set
|
// Fall back to Daedalus-decided key if no profile-specific key is set
|
||||||
_ => {
|
_ => {
|
||||||
match version_info
|
match version_info
|
||||||
.java_version
|
.java_version
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|it| it.major_version)
|
.map(|it| it.major_version)
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
{
|
{
|
||||||
0..=16 => settings
|
0..=16 => settings
|
||||||
.java_globals
|
.java_globals
|
||||||
.get(&crate::jre::JAVA_8_KEY.to_string()),
|
.get(&crate::jre::JAVA_8_KEY.to_string()),
|
||||||
17 => settings
|
17 => settings
|
||||||
.java_globals
|
.java_globals
|
||||||
.get(&crate::jre::JAVA_17_KEY.to_string()),
|
.get(&crate::jre::JAVA_17_KEY.to_string()),
|
||||||
_ => settings
|
_ => settings
|
||||||
.java_globals
|
.java_globals
|
||||||
.get(&crate::jre::JAVA_18PLUS_KEY.to_string()),
|
.get(&crate::jre::JAVA_18PLUS_KEY.to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
let java_version = java_version.as_ref().ok_or_else(|| {
|
||||||
|
crate::ErrorKind::LauncherError(format!(
|
||||||
|
"No Java stored for version {}",
|
||||||
|
version_info.java_version.map_or(8, |it| it.major_version),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Get the path to the Java executable from the chosen Java implementation key
|
||||||
|
let java_install: &Path = &PathBuf::from(&java_version.path);
|
||||||
|
if !java_install.exists() {
|
||||||
|
return Err(crate::ErrorKind::LauncherError(format!(
|
||||||
|
"Could not find Java install: {}",
|
||||||
|
java_install.display()
|
||||||
|
))
|
||||||
|
.as_error());
|
||||||
}
|
}
|
||||||
};
|
let java_args = profile
|
||||||
let java_version = java_version.as_ref().ok_or_else(|| {
|
.java
|
||||||
crate::ErrorKind::LauncherError(format!(
|
.as_ref()
|
||||||
"No Java stored for version {}",
|
.and_then(|it| it.extra_arguments.as_ref())
|
||||||
version_info.java_version.map_or(8, |it| it.major_version),
|
.unwrap_or(&settings.custom_java_args);
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Get the path to the Java executable from the chosen Java implementation key
|
let wrapper = profile
|
||||||
let java_install: &Path = &PathBuf::from(&java_version.path);
|
.hooks
|
||||||
if !java_install.exists() {
|
.as_ref()
|
||||||
return Err(crate::ErrorKind::LauncherError(format!(
|
.map_or(&settings.hooks.wrapper, |it| &it.wrapper);
|
||||||
"Could not find Java install: {}",
|
|
||||||
java_install.display()
|
|
||||||
))
|
|
||||||
.as_error());
|
|
||||||
}
|
|
||||||
let java_args = profile
|
|
||||||
.java
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|it| it.extra_arguments.as_ref())
|
|
||||||
.unwrap_or(&settings.custom_java_args);
|
|
||||||
|
|
||||||
let wrapper = profile
|
let memory = profile.memory.unwrap_or(settings.memory);
|
||||||
.hooks
|
let resolution = profile.resolution.unwrap_or(settings.game_resolution);
|
||||||
.as_ref()
|
|
||||||
.map_or(&settings.hooks.wrapper, |it| &it.wrapper);
|
|
||||||
|
|
||||||
let memory = profile.memory.unwrap_or(settings.memory);
|
let env_args = &settings.custom_env_args;
|
||||||
let resolution = profile.resolution.unwrap_or(settings.game_resolution);
|
|
||||||
|
|
||||||
let env_args = &settings.custom_env_args;
|
// Post post exit hooks
|
||||||
|
let post_exit_hook =
|
||||||
|
&profile.hooks.as_ref().unwrap_or(&settings.hooks).post_exit;
|
||||||
|
|
||||||
// Post post exit hooks
|
let post_exit_hook = if let Some(hook) = post_exit_hook {
|
||||||
let post_exit_hook =
|
let mut cmd = hook.split(' ');
|
||||||
&profile.hooks.as_ref().unwrap_or(&settings.hooks).post_exit;
|
if let Some(command) = cmd.next() {
|
||||||
|
let mut command = Command::new(command);
|
||||||
let post_exit_hook = if let Some(hook) = post_exit_hook {
|
command.args(&cmd.collect::<Vec<&str>>()).current_dir(path);
|
||||||
let mut cmd = hook.split(' ');
|
Some(command)
|
||||||
if let Some(command) = cmd.next() {
|
} else {
|
||||||
let mut command = Command::new(command);
|
None
|
||||||
command.args(&cmd.collect::<Vec<&str>>()).current_dir(path);
|
}
|
||||||
Some(command)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
};
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mc_process = crate::launcher::launch_minecraft(
|
let mc_process = crate::launcher::launch_minecraft(
|
||||||
java_install,
|
java_install,
|
||||||
java_args,
|
java_args,
|
||||||
env_args,
|
env_args,
|
||||||
wrapper,
|
wrapper,
|
||||||
&memory,
|
&memory,
|
||||||
&resolution,
|
&resolution,
|
||||||
credentials,
|
credentials,
|
||||||
post_exit_hook,
|
post_exit_hook,
|
||||||
&profile,
|
&profile,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
Ok(mc_process)
|
||||||
Ok(mc_process)
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,153 +51,156 @@ pub async fn profile_create(
|
|||||||
) -> crate::Result<PathBuf> {
|
) -> crate::Result<PathBuf> {
|
||||||
trace!("Creating new profile. {}", name);
|
trace!("Creating new profile. {}", name);
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
let metadata = state.metadata.read().await;
|
Box::pin(async move {
|
||||||
|
let metadata = state.metadata.read().await;
|
||||||
|
|
||||||
let uuid = Uuid::new_v4();
|
let uuid = Uuid::new_v4();
|
||||||
let path = state.directories.profiles_dir().join(uuid.to_string());
|
let path = state.directories.profiles_dir().join(uuid.to_string());
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
return Err(ProfileCreationError::NotFolder.into());
|
return Err(ProfileCreationError::NotFolder.into());
|
||||||
}
|
|
||||||
if path.join("profile.json").exists() {
|
|
||||||
return Err(ProfileCreationError::ProfileExistsError(
|
|
||||||
path.join("profile.json"),
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ReadDirStream::new(fs::read_dir(&path).await?)
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
return Err(ProfileCreationError::NotEmptyFolder.into());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fs::create_dir_all(&path).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"Creating profile at path {}",
|
|
||||||
&canonicalize(&path)?.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
let loader = modloader;
|
|
||||||
let loader = if loader != ModLoader::Vanilla {
|
|
||||||
let version = loader_version.unwrap_or_else(|| "latest".to_string());
|
|
||||||
|
|
||||||
let filter = |it: &LoaderVersion| match version.as_str() {
|
|
||||||
"latest" => true,
|
|
||||||
"stable" => it.stable,
|
|
||||||
id => it.id == *id || format!("{}-{}", game_version, id) == it.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let loader_data = match loader {
|
|
||||||
ModLoader::Forge => &metadata.forge,
|
|
||||||
ModLoader::Fabric => &metadata.fabric,
|
|
||||||
_ => {
|
|
||||||
return Err(ProfileCreationError::NoManifest(
|
|
||||||
loader.to_string(),
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
}
|
||||||
|
if path.join("profile.json").exists() {
|
||||||
|
return Err(ProfileCreationError::ProfileExistsError(
|
||||||
|
path.join("profile.json"),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ReadDirStream::new(fs::read_dir(&path).await?)
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(ProfileCreationError::NotEmptyFolder.into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fs::create_dir_all(&path).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Creating profile at path {}",
|
||||||
|
&canonicalize(&path)?.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
let loader = modloader;
|
||||||
|
let loader = if loader != ModLoader::Vanilla {
|
||||||
|
let version = loader_version.unwrap_or_else(|| "latest".to_string());
|
||||||
|
|
||||||
|
let filter = |it: &LoaderVersion| match version.as_str() {
|
||||||
|
"latest" => true,
|
||||||
|
"stable" => it.stable,
|
||||||
|
id => it.id == *id || format!("{}-{}", game_version, id) == it.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let loader_data = match loader {
|
||||||
|
ModLoader::Forge => &metadata.forge,
|
||||||
|
ModLoader::Fabric => &metadata.fabric,
|
||||||
|
_ => {
|
||||||
|
return Err(ProfileCreationError::NoManifest(
|
||||||
|
loader.to_string(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let loaders = &loader_data
|
||||||
|
.game_versions
|
||||||
|
.iter()
|
||||||
|
.find(|it| {
|
||||||
|
it.id.replace(
|
||||||
|
daedalus::modded::DUMMY_REPLACE_STRING,
|
||||||
|
&game_version,
|
||||||
|
) == game_version
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ProfileCreationError::ModloaderUnsupported(
|
||||||
|
loader.to_string(),
|
||||||
|
game_version.clone(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.loaders;
|
||||||
|
|
||||||
|
let loader_version = loaders
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.find(filter)
|
||||||
|
.or(
|
||||||
|
// If stable was searched for but not found, return latest by default
|
||||||
|
if version == "stable" {
|
||||||
|
loaders.iter().next().cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ProfileCreationError::InvalidVersionModloader(
|
||||||
|
version,
|
||||||
|
loader.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Some((loader_version, loader))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let loaders = &loader_data
|
// Fully canonicalize now that its created for storing purposes
|
||||||
.game_versions
|
let path = canonicalize(&path)?;
|
||||||
.iter()
|
let mut profile =
|
||||||
.find(|it| {
|
Profile::new(uuid, name, game_version, path.clone()).await?;
|
||||||
it.id.replace(
|
if let Some(ref icon) = icon {
|
||||||
daedalus::modded::DUMMY_REPLACE_STRING,
|
let bytes = tokio::fs::read(icon).await?;
|
||||||
&game_version,
|
profile
|
||||||
) == game_version
|
.set_icon(
|
||||||
})
|
&state.directories.caches_dir(),
|
||||||
.ok_or_else(|| {
|
&state.io_semaphore,
|
||||||
ProfileCreationError::ModloaderUnsupported(
|
bytes::Bytes::from(bytes),
|
||||||
loader.to_string(),
|
&icon.to_string_lossy(),
|
||||||
game_version.clone(),
|
|
||||||
)
|
)
|
||||||
})?
|
.await?;
|
||||||
.loaders;
|
}
|
||||||
|
if let Some((loader_version, loader)) = loader {
|
||||||
|
profile.metadata.loader = loader;
|
||||||
|
profile.metadata.loader_version = Some(loader_version);
|
||||||
|
}
|
||||||
|
|
||||||
let loader_version = loaders
|
profile.metadata.linked_data = linked_data;
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.find(filter)
|
|
||||||
.or(
|
|
||||||
// If stable was searched for but not found, return latest by default
|
|
||||||
if version == "stable" {
|
|
||||||
loaders.iter().next().cloned()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ProfileCreationError::InvalidVersionModloader(
|
|
||||||
version,
|
|
||||||
loader.to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Some((loader_version, loader))
|
// Attempts to find optimal JRE for the profile from the JavaGlobals
|
||||||
} else {
|
// Finds optimal key, and see if key has been set in JavaGlobals
|
||||||
None
|
let settings = state.settings.read().await;
|
||||||
};
|
let optimal_version_key = jre::get_optimal_jre_key(&profile).await?;
|
||||||
|
if settings.java_globals.get(&optimal_version_key).is_some() {
|
||||||
|
profile.java = Some(JavaSettings {
|
||||||
|
jre_key: Some(optimal_version_key),
|
||||||
|
extra_arguments: None,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
emit_warning(&format!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.")).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Fully canonicalize now that its created for storing purposes
|
emit_profile(
|
||||||
let path = canonicalize(&path)?;
|
uuid,
|
||||||
let mut profile =
|
path.clone(),
|
||||||
Profile::new(uuid, name, game_version, path.clone()).await?;
|
&profile.metadata.name,
|
||||||
if let Some(ref icon) = icon {
|
ProfilePayloadType::Created,
|
||||||
let bytes = tokio::fs::read(icon).await?;
|
)
|
||||||
profile
|
.await?;
|
||||||
.set_icon(
|
|
||||||
&state.directories.caches_dir(),
|
|
||||||
&state.io_semaphore,
|
|
||||||
bytes::Bytes::from(bytes),
|
|
||||||
&icon.to_string_lossy(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
if let Some((loader_version, loader)) = loader {
|
|
||||||
profile.metadata.loader = loader;
|
|
||||||
profile.metadata.loader_version = Some(loader_version);
|
|
||||||
}
|
|
||||||
|
|
||||||
profile.metadata.linked_data = linked_data;
|
{
|
||||||
|
let mut profiles = state.profiles.write().await;
|
||||||
|
profiles.insert(profile.clone()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Attempts to find optimal JRE for the profile from the JavaGlobals
|
if !skip_install_profile.unwrap_or(false) {
|
||||||
// Finds optimal key, and see if key has been set in JavaGlobals
|
crate::launcher::install_minecraft(&profile, None).await?;
|
||||||
let settings = state.settings.read().await;
|
}
|
||||||
let optimal_version_key = jre::get_optimal_jre_key(&profile).await?;
|
State::sync().await?;
|
||||||
if settings.java_globals.get(&optimal_version_key).is_some() {
|
|
||||||
profile.java = Some(JavaSettings {
|
|
||||||
jre_key: Some(optimal_version_key),
|
|
||||||
extra_arguments: None,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
emit_warning(&format!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.")).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit_profile(
|
Ok(path)
|
||||||
uuid,
|
|
||||||
path.clone(),
|
|
||||||
&profile.metadata.name,
|
|
||||||
ProfilePayloadType::Created,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
{
|
}).await
|
||||||
let mut profiles = state.profiles.write().await;
|
|
||||||
profiles.insert(profile.clone()).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !skip_install_profile.unwrap_or(false) {
|
|
||||||
crate::launcher::install_minecraft(&profile, None).await?;
|
|
||||||
}
|
|
||||||
State::sync().await?;
|
|
||||||
|
|
||||||
Ok(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
|||||||
@ -49,53 +49,61 @@ pub async fn download_version_info(
|
|||||||
force: Option<bool>,
|
force: Option<bool>,
|
||||||
loading_bar: Option<&LoadingBarId>,
|
loading_bar: Option<&LoadingBarId>,
|
||||||
) -> crate::Result<GameVersionInfo> {
|
) -> crate::Result<GameVersionInfo> {
|
||||||
let version_id = loader
|
Box::pin(async move {
|
||||||
.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id));
|
let version_id = loader.map_or(version.id.clone(), |it| {
|
||||||
tracing::debug!("Loading version info for Minecraft {version_id}");
|
format!("{}-{}", version.id, it.id)
|
||||||
let path = st
|
});
|
||||||
.directories
|
tracing::debug!("Loading version info for Minecraft {version_id}");
|
||||||
.version_dir(&version_id)
|
let path = st
|
||||||
.join(format!("{version_id}.json"));
|
.directories
|
||||||
|
.version_dir(&version_id)
|
||||||
|
.join(format!("{version_id}.json"));
|
||||||
|
|
||||||
let res = if path.exists() && !force.unwrap_or(false) {
|
let res = if path.exists() && !force.unwrap_or(false) {
|
||||||
fs::read(path)
|
fs::read(path)
|
||||||
.err_into::<crate::Error>()
|
.err_into::<crate::Error>()
|
||||||
.await
|
.await
|
||||||
.and_then(|ref it| Ok(serde_json::from_slice(it)?))
|
.and_then(|ref it| Ok(serde_json::from_slice(it)?))
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("Downloading version info for version {}", &version.id);
|
tracing::info!(
|
||||||
let mut info = d::minecraft::fetch_version_info(version).await?;
|
"Downloading version info for version {}",
|
||||||
|
&version.id
|
||||||
|
);
|
||||||
|
let mut info = d::minecraft::fetch_version_info(version).await?;
|
||||||
|
|
||||||
if let Some(loader) = loader {
|
if let Some(loader) = loader {
|
||||||
let partial = d::modded::fetch_partial_version(&loader.url).await?;
|
let partial =
|
||||||
info = d::modded::merge_partial_version(partial, info);
|
d::modded::fetch_partial_version(&loader.url).await?;
|
||||||
|
info = d::modded::merge_partial_version(partial, info);
|
||||||
|
}
|
||||||
|
info.id = version_id.clone();
|
||||||
|
|
||||||
|
write(&path, &serde_json::to_vec(&info)?, &st.io_semaphore).await?;
|
||||||
|
Ok(info)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
if let Some(loading_bar) = loading_bar {
|
||||||
|
emit_loading(
|
||||||
|
loading_bar,
|
||||||
|
if res
|
||||||
|
.processors
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| !x.is_empty())
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
5.0
|
||||||
|
} else {
|
||||||
|
15.0
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
info.id = version_id.clone();
|
|
||||||
|
|
||||||
write(&path, &serde_json::to_vec(&info)?, &st.io_semaphore).await?;
|
tracing::debug!("Loaded version info for Minecraft {version_id}");
|
||||||
Ok(info)
|
Ok(res)
|
||||||
}?;
|
})
|
||||||
|
.await
|
||||||
if let Some(loading_bar) = loading_bar {
|
|
||||||
emit_loading(
|
|
||||||
loading_bar,
|
|
||||||
if res
|
|
||||||
.processors
|
|
||||||
.as_ref()
|
|
||||||
.map(|x| !x.is_empty())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
5.0
|
|
||||||
} else {
|
|
||||||
15.0
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!("Loaded version info for Minecraft {version_id}");
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
@ -104,38 +112,41 @@ pub async fn download_client(
|
|||||||
version_info: &GameVersionInfo,
|
version_info: &GameVersionInfo,
|
||||||
loading_bar: Option<&LoadingBarId>,
|
loading_bar: Option<&LoadingBarId>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let version = &version_info.id;
|
Box::pin(async move {
|
||||||
tracing::debug!("Locating client for version {version}");
|
let version = &version_info.id;
|
||||||
let client_download = version_info
|
tracing::debug!("Locating client for version {version}");
|
||||||
.downloads
|
let client_download = version_info
|
||||||
.get(&d::minecraft::DownloadType::Client)
|
.downloads
|
||||||
.ok_or(
|
.get(&d::minecraft::DownloadType::Client)
|
||||||
crate::ErrorKind::LauncherError(format!(
|
.ok_or(
|
||||||
"No client downloads exist for version {version}"
|
crate::ErrorKind::LauncherError(format!(
|
||||||
))
|
"No client downloads exist for version {version}"
|
||||||
.as_error(),
|
))
|
||||||
)?;
|
.as_error(),
|
||||||
let path = st
|
)?;
|
||||||
.directories
|
let path = st
|
||||||
.version_dir(version)
|
.directories
|
||||||
.join(format!("{version}.jar"));
|
.version_dir(version)
|
||||||
|
.join(format!("{version}.jar"));
|
||||||
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
let bytes = fetch(
|
let bytes = fetch(
|
||||||
&client_download.url,
|
&client_download.url,
|
||||||
Some(&client_download.sha1),
|
Some(&client_download.sha1),
|
||||||
&st.fetch_semaphore,
|
&st.fetch_semaphore,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
write(&path, &bytes, &st.io_semaphore).await?;
|
write(&path, &bytes, &st.io_semaphore).await?;
|
||||||
tracing::trace!("Fetched client version {version}");
|
tracing::trace!("Fetched client version {version}");
|
||||||
}
|
}
|
||||||
if let Some(loading_bar) = loading_bar {
|
if let Some(loading_bar) = loading_bar {
|
||||||
emit_loading(loading_bar, 10.0, None).await?;
|
emit_loading(loading_bar, 10.0, None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("Client loaded for version {version}!");
|
tracing::debug!("Client loaded for version {version}!");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
@ -176,58 +187,61 @@ pub async fn download_assets(
|
|||||||
index: &AssetsIndex,
|
index: &AssetsIndex,
|
||||||
loading_bar: Option<&LoadingBarId>,
|
loading_bar: Option<&LoadingBarId>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
tracing::debug!("Loading assets");
|
Box::pin(async move {
|
||||||
let num_futs = index.objects.len();
|
tracing::debug!("Loading assets");
|
||||||
let assets = stream::iter(index.objects.iter())
|
let num_futs = index.objects.len();
|
||||||
.map(Ok::<(&String, &Asset), crate::Error>);
|
let assets = stream::iter(index.objects.iter())
|
||||||
|
.map(Ok::<(&String, &Asset), crate::Error>);
|
||||||
|
|
||||||
loading_try_for_each_concurrent(assets,
|
loading_try_for_each_concurrent(assets,
|
||||||
None,
|
None,
|
||||||
loading_bar,
|
loading_bar,
|
||||||
35.0,
|
35.0,
|
||||||
num_futs,
|
num_futs,
|
||||||
None,
|
None,
|
||||||
|(name, asset)| async move {
|
|(name, asset)| async move {
|
||||||
let hash = &asset.hash;
|
let hash = &asset.hash;
|
||||||
let resource_path = st.directories.object_dir(hash);
|
let resource_path = st.directories.object_dir(hash);
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://resources.download.minecraft.net/{sub_hash}/{hash}",
|
"https://resources.download.minecraft.net/{sub_hash}/{hash}",
|
||||||
sub_hash = &hash[..2]
|
sub_hash = &hash[..2]
|
||||||
);
|
);
|
||||||
|
|
||||||
let fetch_cell = OnceCell::<bytes::Bytes>::new();
|
let fetch_cell = OnceCell::<bytes::Bytes>::new();
|
||||||
tokio::try_join! {
|
tokio::try_join! {
|
||||||
async {
|
async {
|
||||||
if !resource_path.exists() {
|
if !resource_path.exists() {
|
||||||
let resource = fetch_cell
|
let resource = fetch_cell
|
||||||
.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore))
|
.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore))
|
||||||
.await?;
|
.await?;
|
||||||
write(&resource_path, resource, &st.io_semaphore).await?;
|
write(&resource_path, resource, &st.io_semaphore).await?;
|
||||||
tracing::trace!("Fetched asset with hash {hash}");
|
tracing::trace!("Fetched asset with hash {hash}");
|
||||||
}
|
}
|
||||||
Ok::<_, crate::Error>(())
|
Ok::<_, crate::Error>(())
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
if with_legacy {
|
if with_legacy {
|
||||||
let resource = fetch_cell
|
let resource = fetch_cell
|
||||||
.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore))
|
.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore))
|
||||||
.await?;
|
.await?;
|
||||||
let resource_path = st.directories.legacy_assets_dir().join(
|
let resource_path = st.directories.legacy_assets_dir().join(
|
||||||
name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
|
name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
|
||||||
);
|
);
|
||||||
write(&resource_path, resource, &st.io_semaphore).await?;
|
write(&resource_path, resource, &st.io_semaphore).await?;
|
||||||
tracing::trace!("Fetched legacy asset with hash {hash}");
|
tracing::trace!("Fetched legacy asset with hash {hash}");
|
||||||
}
|
}
|
||||||
Ok::<_, crate::Error>(())
|
Ok::<_, crate::Error>(())
|
||||||
},
|
},
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
tracing::trace!("Loaded asset with hash {hash}");
|
tracing::trace!("Loaded asset with hash {hash}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}).await?;
|
}).await?;
|
||||||
|
|
||||||
tracing::debug!("Done loading assets!");
|
tracing::debug!("Done loading assets!");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
|
}).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(st, libraries))]
|
#[tracing::instrument(skip(st, libraries))]
|
||||||
@ -237,96 +251,99 @@ pub async fn download_libraries(
|
|||||||
version: &str,
|
version: &str,
|
||||||
loading_bar: Option<&LoadingBarId>,
|
loading_bar: Option<&LoadingBarId>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
tracing::debug!("Loading libraries");
|
Box::pin(async move {
|
||||||
|
tracing::debug!("Loading libraries");
|
||||||
|
|
||||||
tokio::try_join! {
|
tokio::try_join! {
|
||||||
fs::create_dir_all(st.directories.libraries_dir()),
|
fs::create_dir_all(st.directories.libraries_dir()),
|
||||||
fs::create_dir_all(st.directories.version_natives_dir(version))
|
fs::create_dir_all(st.directories.version_natives_dir(version))
|
||||||
}?;
|
}?;
|
||||||
let num_files = libraries.len();
|
let num_files = libraries.len();
|
||||||
loading_try_for_each_concurrent(
|
loading_try_for_each_concurrent(
|
||||||
stream::iter(libraries.iter())
|
stream::iter(libraries.iter())
|
||||||
.map(Ok::<&Library, crate::Error>), None, loading_bar,35.0,num_files, None,|library| async move {
|
.map(Ok::<&Library, crate::Error>), None, loading_bar,35.0,num_files, None,|library| async move {
|
||||||
if let Some(rules) = &library.rules {
|
if let Some(rules) = &library.rules {
|
||||||
if !rules.iter().all(super::parse_rule) {
|
if !rules.iter().all(super::parse_rule) {
|
||||||
return Ok(());
|
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);
|
|
||||||
|
|
||||||
match library.downloads {
|
|
||||||
_ if path.exists() => Ok(()),
|
|
||||||
Some(d::minecraft::LibraryDownloads {
|
|
||||||
artifact: Some(ref artifact),
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore)
|
|
||||||
.await?;
|
|
||||||
write(&path, &bytes, &st.io_semaphore).await?;
|
|
||||||
tracing::trace!("Fetched library {}", &library.name);
|
|
||||||
Ok::<_, crate::Error>(())
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let url = [
|
|
||||||
library
|
|
||||||
.url
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or("https://libraries.minecraft.net"),
|
|
||||||
&artifact_path
|
|
||||||
].concat();
|
|
||||||
|
|
||||||
let bytes = fetch(&url, None, &st.fetch_semaphore).await?;
|
|
||||||
write(&path, &bytes, &st.io_semaphore).await?;
|
|
||||||
tracing::trace!("Fetched library {}", &library.name);
|
|
||||||
Ok::<_, crate::Error>(())
|
|
||||||
}
|
|
||||||
_ => Ok(())
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
async {
|
tokio::try_join! {
|
||||||
// HACK: pseudo try block using or else
|
async {
|
||||||
if let Some((os_key, classifiers)) = None.or_else(|| Some((
|
let artifact_path = d::get_path_from_artifact(&library.name)?;
|
||||||
library
|
let path = st.directories.libraries_dir().join(&artifact_path);
|
||||||
.natives
|
|
||||||
.as_ref()?
|
|
||||||
.get(&Os::native_arch())?,
|
|
||||||
library
|
|
||||||
.downloads
|
|
||||||
.as_ref()?
|
|
||||||
.classifiers
|
|
||||||
.as_ref()?
|
|
||||||
))) {
|
|
||||||
let parsed_key = os_key.replace(
|
|
||||||
"${arch}",
|
|
||||||
crate::util::platform::ARCH_WIDTH,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(native) = classifiers.get(&parsed_key) {
|
match library.downloads {
|
||||||
let data = fetch(&native.url, Some(&native.sha1), &st.fetch_semaphore).await?;
|
_ if path.exists() => Ok(()),
|
||||||
let reader = std::io::Cursor::new(&data);
|
Some(d::minecraft::LibraryDownloads {
|
||||||
if let Ok(mut archive) = zip::ZipArchive::new(reader) {
|
artifact: Some(ref artifact),
|
||||||
match archive.extract(&st.directories.version_natives_dir(version)) {
|
..
|
||||||
Ok(_) => tracing::info!("Fetched native {}", &library.name),
|
}) => {
|
||||||
Err(err) => tracing::error!("Failed extracting native {}. err: {}", &library.name, err)
|
let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore)
|
||||||
|
.await?;
|
||||||
|
write(&path, &bytes, &st.io_semaphore).await?;
|
||||||
|
tracing::trace!("Fetched library {}", &library.name);
|
||||||
|
Ok::<_, crate::Error>(())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let url = [
|
||||||
|
library
|
||||||
|
.url
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("https://libraries.minecraft.net"),
|
||||||
|
&artifact_path
|
||||||
|
].concat();
|
||||||
|
|
||||||
|
let bytes = fetch(&url, None, &st.fetch_semaphore).await?;
|
||||||
|
write(&path, &bytes, &st.io_semaphore).await?;
|
||||||
|
tracing::trace!("Fetched library {}", &library.name);
|
||||||
|
Ok::<_, crate::Error>(())
|
||||||
|
}
|
||||||
|
_ => Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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())?,
|
||||||
|
library
|
||||||
|
.downloads
|
||||||
|
.as_ref()?
|
||||||
|
.classifiers
|
||||||
|
.as_ref()?
|
||||||
|
))) {
|
||||||
|
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).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::info!("Fetched native {}", &library.name),
|
||||||
|
Err(err) => tracing::error!("Failed extracting native {}. err: {}", &library.name, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::error!("Failed extracting native {}", &library.name)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tracing::error!("Failed extracting native {}", &library.name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
Ok(())
|
tracing::debug!("Loaded library {}", library.name);
|
||||||
}
|
Ok(())
|
||||||
}?;
|
}
|
||||||
|
).await?;
|
||||||
|
|
||||||
tracing::debug!("Loaded library {}", library.name);
|
tracing::debug!("Done loading libraries!");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
).await?;
|
|
||||||
|
|
||||||
tracing::debug!("Done loading libraries!");
|
}).await
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,158 +58,161 @@ pub async fn install_minecraft(
|
|||||||
profile: &Profile,
|
profile: &Profile,
|
||||||
existing_loading_bar: Option<LoadingBarId>,
|
existing_loading_bar: Option<LoadingBarId>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let state = State::get().await?;
|
Box::pin(async move {
|
||||||
let instance_path = &canonicalize(&profile.path)?;
|
let state = State::get().await?;
|
||||||
let metadata = state.metadata.read().await;
|
let instance_path = &canonicalize(&profile.path)?;
|
||||||
|
let metadata = state.metadata.read().await;
|
||||||
|
|
||||||
let version = metadata
|
let version = metadata
|
||||||
.minecraft
|
.minecraft
|
||||||
.versions
|
.versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|it| it.id == profile.metadata.game_version)
|
.find(|it| it.id == profile.metadata.game_version)
|
||||||
.ok_or(crate::ErrorKind::LauncherError(format!(
|
.ok_or(crate::ErrorKind::LauncherError(format!(
|
||||||
"Invalid game version: {}",
|
"Invalid game version: {}",
|
||||||
profile.metadata.game_version
|
profile.metadata.game_version
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
let version_jar = profile
|
let version_jar = profile
|
||||||
.metadata
|
.metadata
|
||||||
.loader_version
|
.loader_version
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(version.id.clone(), |it| {
|
.map_or(version.id.clone(), |it| {
|
||||||
format!("{}-{}", version.id.clone(), it.id.clone())
|
format!("{}-{}", version.id.clone(), it.id.clone())
|
||||||
});
|
});
|
||||||
|
|
||||||
let loading_bar = init_or_edit_loading(
|
let loading_bar = init_or_edit_loading(
|
||||||
existing_loading_bar,
|
existing_loading_bar,
|
||||||
LoadingBarType::MinecraftDownload {
|
LoadingBarType::MinecraftDownload {
|
||||||
// If we are downloading minecraft for a profile, provide its name and uuid
|
// If we are downloading minecraft for a profile, provide its name and uuid
|
||||||
profile_name: profile.metadata.name.clone(),
|
profile_name: profile.metadata.name.clone(),
|
||||||
profile_uuid: profile.uuid,
|
profile_uuid: profile.uuid,
|
||||||
},
|
},
|
||||||
100.0,
|
100.0,
|
||||||
"Downloading Minecraft",
|
"Downloading Minecraft",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Download version info
|
// Download version info
|
||||||
let mut version_info = download::download_version_info(
|
let mut version_info = download::download_version_info(
|
||||||
&state,
|
&state,
|
||||||
version,
|
version,
|
||||||
profile.metadata.loader_version.as_ref(),
|
profile.metadata.loader_version.as_ref(),
|
||||||
None,
|
None,
|
||||||
Some(&loading_bar),
|
Some(&loading_bar),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Download minecraft (5-90)
|
// Download minecraft (5-90)
|
||||||
download::download_minecraft(&state, &version_info, &loading_bar).await?;
|
download::download_minecraft(&state, &version_info, &loading_bar).await?;
|
||||||
|
|
||||||
let client_path = state
|
let client_path = state
|
||||||
.directories
|
.directories
|
||||||
.version_dir(&version_jar)
|
.version_dir(&version_jar)
|
||||||
.join(format!("{version_jar}.jar"));
|
.join(format!("{version_jar}.jar"));
|
||||||
|
|
||||||
if let Some(processors) = &version_info.processors {
|
if let Some(processors) = &version_info.processors {
|
||||||
if let Some(ref mut data) = version_info.data {
|
if let Some(ref mut data) = version_info.data {
|
||||||
processor_rules! {
|
processor_rules! {
|
||||||
data;
|
data;
|
||||||
"SIDE":
|
"SIDE":
|
||||||
client => "client",
|
client => "client",
|
||||||
server => "";
|
server => "";
|
||||||
"MINECRAFT_JAR" :
|
"MINECRAFT_JAR" :
|
||||||
client => client_path.to_string_lossy(),
|
client => client_path.to_string_lossy(),
|
||||||
server => "";
|
server => "";
|
||||||
"MINECRAFT_VERSION":
|
"MINECRAFT_VERSION":
|
||||||
client => profile.metadata.game_version.clone(),
|
client => profile.metadata.game_version.clone(),
|
||||||
server => "";
|
server => "";
|
||||||
"ROOT":
|
"ROOT":
|
||||||
client => instance_path.to_string_lossy(),
|
client => instance_path.to_string_lossy(),
|
||||||
server => "";
|
server => "";
|
||||||
"LIBRARY_DIR":
|
"LIBRARY_DIR":
|
||||||
client => state.directories.libraries_dir().to_string_lossy(),
|
client => state.directories.libraries_dir().to_string_lossy(),
|
||||||
server => "";
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cp = wrap_ref_builder!(cp = processor.classpath.clone() => {
|
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))
|
||||||
cp.push(processor.jar.clone())
|
.await?;
|
||||||
});
|
let total_length = processors.len();
|
||||||
|
|
||||||
let child = Command::new("java")
|
// Forge processors (90-100)
|
||||||
.arg("-cp")
|
for (index, processor) in processors.iter().enumerate() {
|
||||||
.arg(args::get_class_paths_jar(
|
emit_loading(
|
||||||
&state.directories.libraries_dir(),
|
&loading_bar,
|
||||||
&cp,
|
10.0 / total_length as f64,
|
||||||
)?)
|
Some(&format!(
|
||||||
.arg(
|
"Running forge processor {}/{}",
|
||||||
args::get_processor_main_class(args::get_lib_path(
|
index, total_length
|
||||||
&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(
|
.await?;
|
||||||
&state.directories.libraries_dir(),
|
|
||||||
&processor.args,
|
|
||||||
data,
|
|
||||||
)?)
|
|
||||||
.output()
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
crate::ErrorKind::LauncherError(format!(
|
|
||||||
"Error running processor: {err}",
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !child.status.success() {
|
if let Some(sides) = &processor.sides {
|
||||||
return Err(crate::ErrorKind::LauncherError(format!(
|
if !sides.contains(&String::from("client")) {
|
||||||
"Processor error: {}",
|
continue;
|
||||||
String::from_utf8_lossy(&child.stderr)
|
}
|
||||||
))
|
}
|
||||||
.as_error());
|
|
||||||
|
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)
|
||||||
|
))
|
||||||
|
.as_error());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
crate::api::profile::edit(&profile.path, |prof| {
|
crate::api::profile::edit(&profile.path, |prof| {
|
||||||
prof.installed = true;
|
prof.installed = true;
|
||||||
|
|
||||||
async { Ok(()) }
|
async { Ok(()) }
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
State::sync().await?;
|
State::sync().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
|
}).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@ -224,140 +227,143 @@ pub async fn launch_minecraft(
|
|||||||
post_exit_hook: Option<Command>,
|
post_exit_hook: Option<Command>,
|
||||||
profile: &Profile,
|
profile: &Profile,
|
||||||
) -> crate::Result<Arc<tokio::sync::RwLock<MinecraftChild>>> {
|
) -> crate::Result<Arc<tokio::sync::RwLock<MinecraftChild>>> {
|
||||||
if !profile.installed {
|
Box::pin(async move {
|
||||||
install_minecraft(profile, None).await?;
|
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)})
|
|
||||||
}
|
}
|
||||||
None => Command::new(String::from(java_install.to_string_lossy())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let env_args = Vec::from(env_args);
|
let state = State::get().await?;
|
||||||
|
let metadata = state.metadata.read().await;
|
||||||
|
let instance_path = &canonicalize(&profile.path)?;
|
||||||
|
|
||||||
// Check if profile has a running profile, and reject running the command if it does
|
let version = metadata
|
||||||
// Done late so a quick double call doesn't launch two instances
|
.minecraft
|
||||||
let existing_processes =
|
.versions
|
||||||
process::get_uuids_by_profile_path(instance_path).await?;
|
.iter()
|
||||||
if let Some(uuid) = existing_processes.first() {
|
.find(|it| it.id == profile.metadata.game_version)
|
||||||
return Err(crate::ErrorKind::LauncherError(format!(
|
.ok_or(crate::ErrorKind::LauncherError(format!(
|
||||||
"Profile {} is already running at UUID: {uuid}",
|
"Invalid game version: {}",
|
||||||
instance_path.display()
|
profile.metadata.game_version
|
||||||
))
|
)))?;
|
||||||
.as_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
command
|
let version_jar = profile
|
||||||
.args(
|
.metadata
|
||||||
args::get_jvm_arguments(
|
.loader_version
|
||||||
args.get(&d::minecraft::ArgumentType::Jvm)
|
.as_ref()
|
||||||
.map(|x| x.as_slice()),
|
.map_or(version.id.clone(), |it| {
|
||||||
&state.directories.version_natives_dir(&version_jar),
|
format!("{}-{}", version.id.clone(), it.id.clone())
|
||||||
&state.directories.libraries_dir(),
|
});
|
||||||
&args::get_class_paths(
|
|
||||||
|
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(),
|
&state.directories.libraries_dir(),
|
||||||
version_info.libraries.as_slice(),
|
&args::get_class_paths(
|
||||||
&client_path,
|
&state.directories.libraries_dir(),
|
||||||
)?,
|
version_info.libraries.as_slice(),
|
||||||
&version_jar,
|
&client_path,
|
||||||
*memory,
|
)?,
|
||||||
Vec::from(java_args),
|
&version_jar,
|
||||||
)?
|
*memory,
|
||||||
.into_iter()
|
Vec::from(java_args),
|
||||||
.collect::<Vec<_>>(),
|
)?
|
||||||
)
|
.into_iter()
|
||||||
.arg(version_info.main_class.clone())
|
.collect::<Vec<_>>(),
|
||||||
.args(
|
)
|
||||||
args::get_minecraft_arguments(
|
.arg(version_info.main_class.clone())
|
||||||
args.get(&d::minecraft::ArgumentType::Game)
|
.args(
|
||||||
.map(|x| x.as_slice()),
|
args::get_minecraft_arguments(
|
||||||
version_info.minecraft_arguments.as_deref(),
|
args.get(&d::minecraft::ArgumentType::Game)
|
||||||
credentials,
|
.map(|x| x.as_slice()),
|
||||||
&version.id,
|
version_info.minecraft_arguments.as_deref(),
|
||||||
&version_info.asset_index.id,
|
credentials,
|
||||||
instance_path,
|
&version.id,
|
||||||
&state.directories.assets_dir(),
|
&version_info.asset_index.id,
|
||||||
&version.type_,
|
instance_path,
|
||||||
*resolution,
|
&state.directories.assets_dir(),
|
||||||
)?
|
&version.type_,
|
||||||
.into_iter()
|
*resolution,
|
||||||
.collect::<Vec<_>>(),
|
)?
|
||||||
)
|
.into_iter()
|
||||||
.current_dir(instance_path.clone())
|
.collect::<Vec<_>>(),
|
||||||
.stdout(Stdio::piped())
|
)
|
||||||
.stderr(Stdio::piped());
|
.current_dir(instance_path.clone())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped());
|
||||||
|
|
||||||
// CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
|
// CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
if std::env::var("CARGO").is_ok() {
|
if std::env::var("CARGO").is_ok() {
|
||||||
command.env_remove("DYLD_FALLBACK_LIBRARY_PATH");
|
command.env_remove("DYLD_FALLBACK_LIBRARY_PATH");
|
||||||
}
|
}
|
||||||
command.envs(env_args);
|
command.envs(env_args);
|
||||||
|
|
||||||
// Get Modrinth logs directories
|
// Get Modrinth logs directories
|
||||||
let datetime_string =
|
let datetime_string =
|
||||||
chrono::Local::now().format("%Y%m%y_%H%M%S").to_string();
|
chrono::Local::now().format("%Y%m%y_%H%M%S").to_string();
|
||||||
let logs_dir = {
|
let logs_dir = {
|
||||||
let st = State::get().await?;
|
let st = State::get().await?;
|
||||||
st.directories
|
st.directories
|
||||||
.profile_logs_dir(profile.uuid)
|
.profile_logs_dir(profile.uuid)
|
||||||
.join(&datetime_string)
|
.join(&datetime_string)
|
||||||
};
|
};
|
||||||
fs::create_dir_all(&logs_dir)?;
|
fs::create_dir_all(&logs_dir)?;
|
||||||
|
|
||||||
let stdout_log_path = logs_dir.join("stdout.log");
|
let stdout_log_path = logs_dir.join("stdout.log");
|
||||||
let stderr_log_path = logs_dir.join("stderr.log");
|
let stderr_log_path = logs_dir.join("stderr.log");
|
||||||
|
|
||||||
// Create Minecraft child by inserting it into the state
|
// Create Minecraft child by inserting it into the state
|
||||||
// This also spawns the process and prepares the subsequent processes
|
// This also spawns the process and prepares the subsequent processes
|
||||||
let mut state_children = state.children.write().await;
|
let mut state_children = state.children.write().await;
|
||||||
state_children
|
state_children
|
||||||
.insert_process(
|
.insert_process(
|
||||||
Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
instance_path.to_path_buf(),
|
instance_path.to_path_buf(),
|
||||||
stdout_log_path,
|
stdout_log_path,
|
||||||
stderr_log_path,
|
stderr_log_path,
|
||||||
command,
|
command,
|
||||||
post_exit_hook,
|
post_exit_hook,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
||||||
|
}).await
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
use theseus::jre::autodetect_java_globals;
|
use theseus::jre::autodetect_java_globals;
|
||||||
use theseus::prelude::*;
|
use theseus::prelude::*;
|
||||||
use theseus::profile_create::profile_create;
|
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user