You've already forked AstralRinth
forked from didirus/AstralRinth
Finish launching modded versions of Minecraft
This commit is contained in:
7
.idea/discord.xml
generated
Normal file
7
.idea/discord.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="PROJECT_FILES" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -1,5 +1,7 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -123,6 +125,22 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "daedalus"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "667dec20054908ee40916a3fd8ea5bfc6ed8b1bde6f7741dbea18500a1049ea6"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"chrono",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha1",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.28"
|
version = "0.8.28"
|
||||||
@@ -636,6 +654,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "path-clean"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -972,8 +996,10 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"daedalus",
|
||||||
"futures",
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"path-clean",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
|
daedalus = "0.1.6"
|
||||||
|
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@@ -17,6 +19,7 @@ uuid = { version = "0.8", features = ["serde", "v4"] }
|
|||||||
bytes = "1"
|
bytes = "1"
|
||||||
zip = "0.5"
|
zip = "0.5"
|
||||||
sha1 = { version = "0.6.0", features = ["std"]}
|
sha1 = { version = "0.6.0", features = ["std"]}
|
||||||
|
path-clean = "0.1.0"
|
||||||
|
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
use crate::launcher::auth::provider::Credentials;
|
use crate::launcher::auth::provider::Credentials;
|
||||||
use crate::launcher::meta::{Argument, ArgumentValue, Library, Os, VersionType};
|
|
||||||
use crate::launcher::rules::parse_rules;
|
use crate::launcher::rules::parse_rules;
|
||||||
use crate::launcher::LauncherError;
|
use crate::launcher::LauncherError;
|
||||||
|
use daedalus::get_path_from_artifact;
|
||||||
|
use daedalus::minecraft::{Argument, ArgumentValue, Library, Os, VersionType};
|
||||||
|
use daedalus::modded::SidedDataEntry;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -13,63 +17,24 @@ pub fn get_class_paths(
|
|||||||
let mut class_paths = Vec::new();
|
let mut class_paths = Vec::new();
|
||||||
|
|
||||||
for library in libraries {
|
for library in libraries {
|
||||||
if library.downloads.artifact.is_some() {
|
if let Some(rules) = &library.rules {
|
||||||
if let Some(rules) = &library.rules {
|
if !super::rules::parse_rules(rules.as_slice()) {
|
||||||
if !super::rules::parse_rules(rules.as_slice()) {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let name_items = library.name.split(':').collect::<Vec<&str>>();
|
|
||||||
|
|
||||||
let package = name_items.get(0).ok_or_else(|| {
|
|
||||||
LauncherError::ParseError(format!(
|
|
||||||
"Unable to find package for library {}",
|
|
||||||
&library.name
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
let name = name_items.get(1).ok_or_else(|| {
|
|
||||||
LauncherError::ParseError(format!(
|
|
||||||
"Unable to find name for library {}",
|
|
||||||
&library.name
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
let version = name_items.get(2).ok_or_else(|| {
|
|
||||||
LauncherError::ParseError(format!(
|
|
||||||
"Unable to find version for library {}",
|
|
||||||
&library.name
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut path = libraries_path.to_path_buf();
|
|
||||||
|
|
||||||
for directory in package.split('.') {
|
|
||||||
path.push(directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
path.push(name);
|
|
||||||
path.push(version);
|
|
||||||
path.push(format!("{}-{}.jar", name, version));
|
|
||||||
|
|
||||||
class_paths.push(
|
|
||||||
std::fs::canonicalize(&path)
|
|
||||||
.map_err(|_| {
|
|
||||||
LauncherError::InvalidInput(format!(
|
|
||||||
"Library file at path {} does not exist",
|
|
||||||
path.to_string_lossy()
|
|
||||||
))
|
|
||||||
})?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !library.include_in_classpath {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
class_paths.push(get_lib_path(libraries_path, &library.name)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
class_paths.push(
|
class_paths.push(
|
||||||
std::fs::canonicalize(&client_path)
|
crate::util::absolute_path(&client_path)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
LauncherError::InvalidInput(format!(
|
LauncherError::InvalidInput(format!(
|
||||||
"Specified client path {} does not exist",
|
"Specified class path {} does not exist",
|
||||||
client_path.to_string_lossy()
|
client_path.to_string_lossy()
|
||||||
))
|
))
|
||||||
})?
|
})?
|
||||||
@@ -83,6 +48,45 @@ pub fn get_class_paths(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_class_paths_jar<T: AsRef<str>>(
|
||||||
|
libraries_path: &Path,
|
||||||
|
libraries: &[T],
|
||||||
|
) -> Result<String, LauncherError> {
|
||||||
|
let mut class_paths = Vec::new();
|
||||||
|
|
||||||
|
for library in libraries {
|
||||||
|
class_paths.push(get_lib_path(libraries_path, library)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(class_paths.join(match super::download::get_os() {
|
||||||
|
Os::Osx | Os::Linux | Os::Unknown => ":",
|
||||||
|
Os::Windows => ";",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_lib_path<T: AsRef<str>>(libraries_path: &Path, lib: T) -> Result<String, LauncherError> {
|
||||||
|
let mut path = libraries_path.to_path_buf();
|
||||||
|
|
||||||
|
path.push(get_path_from_artifact(lib.as_ref())?);
|
||||||
|
|
||||||
|
let path = crate::util::absolute_path(&path).map_err(|_| {
|
||||||
|
LauncherError::InvalidInput(format!(
|
||||||
|
"Library file at path {} does not exist",
|
||||||
|
path.to_string_lossy()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
/*if !path.exists() {
|
||||||
|
if let Some(parent) = &path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::File::create(&path)?;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
Ok(path.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_jvm_arguments(
|
pub fn get_jvm_arguments(
|
||||||
arguments: Option<&[Argument]>,
|
arguments: Option<&[Argument]>,
|
||||||
natives_path: &Path,
|
natives_path: &Path,
|
||||||
@@ -97,7 +101,7 @@ pub fn get_jvm_arguments(
|
|||||||
} else {
|
} else {
|
||||||
parsed_arguments.push(format!(
|
parsed_arguments.push(format!(
|
||||||
"-Djava.library.path={}",
|
"-Djava.library.path={}",
|
||||||
&*std::fs::canonicalize(natives_path)
|
&*crate::util::absolute_path(natives_path)
|
||||||
.map_err(|_| LauncherError::InvalidInput(format!(
|
.map_err(|_| LauncherError::InvalidInput(format!(
|
||||||
"Specified natives path {} does not exist",
|
"Specified natives path {} does not exist",
|
||||||
natives_path.to_string_lossy()
|
natives_path.to_string_lossy()
|
||||||
@@ -117,10 +121,12 @@ fn parse_jvm_argument(
|
|||||||
natives_path: &Path,
|
natives_path: &Path,
|
||||||
class_paths: &str,
|
class_paths: &str,
|
||||||
) -> Result<String, LauncherError> {
|
) -> Result<String, LauncherError> {
|
||||||
|
let mut argument = argument.to_string();
|
||||||
|
argument.retain(|c| !c.is_whitespace());
|
||||||
Ok(argument
|
Ok(argument
|
||||||
.replace(
|
.replace(
|
||||||
"${natives_directory}",
|
"${natives_directory}",
|
||||||
&*std::fs::canonicalize(natives_path)
|
&*crate::util::absolute_path(natives_path)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
LauncherError::InvalidInput(format!(
|
LauncherError::InvalidInput(format!(
|
||||||
"Specified natives path {} does not exist",
|
"Specified natives path {} does not exist",
|
||||||
@@ -208,7 +214,7 @@ fn parse_minecraft_argument(
|
|||||||
.replace("${assets_index_name}", asset_index_name)
|
.replace("${assets_index_name}", asset_index_name)
|
||||||
.replace(
|
.replace(
|
||||||
"${game_directory}",
|
"${game_directory}",
|
||||||
&*std::fs::canonicalize(game_directory)
|
&*crate::util::absolute_path(game_directory)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
LauncherError::InvalidInput(format!(
|
LauncherError::InvalidInput(format!(
|
||||||
"Specified game directory {} does not exist",
|
"Specified game directory {} does not exist",
|
||||||
@@ -220,7 +226,7 @@ fn parse_minecraft_argument(
|
|||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
"${assets_root}",
|
"${assets_root}",
|
||||||
&*std::fs::canonicalize(assets_directory)
|
&*crate::util::absolute_path(assets_directory)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
LauncherError::InvalidInput(format!(
|
LauncherError::InvalidInput(format!(
|
||||||
"Specified assets directory {} does not exist",
|
"Specified assets directory {} does not exist",
|
||||||
@@ -232,7 +238,7 @@ fn parse_minecraft_argument(
|
|||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
"${game_assets}",
|
"${game_assets}",
|
||||||
&*std::fs::canonicalize(assets_directory)
|
&*crate::util::absolute_path(assets_directory)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
LauncherError::InvalidInput(format!(
|
LauncherError::InvalidInput(format!(
|
||||||
"Specified assets directory {} does not exist",
|
"Specified assets directory {} does not exist",
|
||||||
@@ -281,3 +287,59 @@ where
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_processor_arguments<T: AsRef<str>>(
|
||||||
|
libraries_path: &Path,
|
||||||
|
arguments: &[T],
|
||||||
|
data: &HashMap<String, SidedDataEntry>,
|
||||||
|
) -> Result<Vec<String>, LauncherError> {
|
||||||
|
let mut new_arguments = Vec::new();
|
||||||
|
|
||||||
|
for argument in arguments {
|
||||||
|
let trimmed_arg = &argument.as_ref()[1..argument.as_ref().len() - 1];
|
||||||
|
if argument.as_ref().starts_with('{') {
|
||||||
|
if let Some(entry) = data.get(trimmed_arg) {
|
||||||
|
new_arguments.push(if entry.client.starts_with('[') {
|
||||||
|
get_lib_path(libraries_path, &entry.client[1..entry.client.len() - 1])?
|
||||||
|
} else {
|
||||||
|
entry.client.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if argument.as_ref().starts_with('[') {
|
||||||
|
new_arguments.push(get_lib_path(libraries_path, trimmed_arg)?)
|
||||||
|
} else {
|
||||||
|
new_arguments.push(argument.as_ref().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(new_arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_processor_main_class(path: String) -> Result<Option<String>, LauncherError> {
|
||||||
|
Ok(tokio::task::spawn_blocking(move || {
|
||||||
|
let zipfile = std::fs::File::open(&path)?;
|
||||||
|
let mut archive = zip::ZipArchive::new(zipfile).map_err(|_| {
|
||||||
|
LauncherError::ProcessorError(format!("Cannot read processor at {}", path))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let file = archive.by_name("META-INF/MANIFEST.MF").map_err(|_| {
|
||||||
|
LauncherError::ProcessorError(format!("Cannot read processor manifest at {}", path))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
for line in reader.lines() {
|
||||||
|
let mut line = line?;
|
||||||
|
line.retain(|c| !c.is_whitespace());
|
||||||
|
|
||||||
|
if line.starts_with("Main-Class:") {
|
||||||
|
if let Some(class) = line.split(':').nth(1) {
|
||||||
|
return Ok(Some(class.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<Option<String>, LauncherError>(None)
|
||||||
|
})
|
||||||
|
.await??)
|
||||||
|
}
|
||||||
|
|||||||
@@ -167,12 +167,39 @@ pub mod api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod provider {
|
pub mod provider {
|
||||||
|
use crate::launcher::auth::api::login;
|
||||||
|
use crate::launcher::LauncherError;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// The credentials of a user
|
||||||
pub struct Credentials {
|
pub struct Credentials {
|
||||||
|
/// The user UUID the credentials belong to
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
|
/// The username of the user
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
/// The access token associated with the credentials
|
||||||
pub access_token: String,
|
pub access_token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Credentials {
|
||||||
|
/// Gets a credentials instance from a user's login
|
||||||
|
pub async fn from_login(username: &str, password: &str) -> Result<Self, LauncherError> {
|
||||||
|
let login =
|
||||||
|
login(username, password, true)
|
||||||
|
.await
|
||||||
|
.map_err(|err| LauncherError::FetchError {
|
||||||
|
inner: err,
|
||||||
|
item: "authentication credentials".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let profile = login.selected_profile.unwrap();
|
||||||
|
|
||||||
|
Ok(Credentials {
|
||||||
|
id: profile.id,
|
||||||
|
username: profile.name,
|
||||||
|
access_token: login.access_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,36 @@
|
|||||||
use crate::launcher::meta::{
|
use crate::launcher::LauncherError;
|
||||||
|
use daedalus::get_path_from_artifact;
|
||||||
|
use daedalus::minecraft::{
|
||||||
fetch_assets_index, fetch_version_info, Asset, AssetsIndex, DownloadType, Library, Os, Version,
|
fetch_assets_index, fetch_version_info, Asset, AssetsIndex, DownloadType, Library, Os, Version,
|
||||||
VersionInfo,
|
VersionInfo,
|
||||||
};
|
};
|
||||||
use crate::launcher::LauncherError;
|
use daedalus::modded::{fetch_partial_version, merge_partial_version, LoaderVersion};
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Write};
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub async fn download_version_info(
|
pub async fn download_version_info(
|
||||||
client_path: &Path,
|
client_path: &Path,
|
||||||
version: &Version,
|
version: &Version,
|
||||||
|
loader_version: Option<&LoaderVersion>,
|
||||||
) -> Result<VersionInfo, LauncherError> {
|
) -> Result<VersionInfo, LauncherError> {
|
||||||
let path = &*client_path
|
let id = loader_version.map(|x| &x.id).unwrap_or(&version.id);
|
||||||
.join(&version.id)
|
|
||||||
.join(format!("{}.json", &version.id));
|
let path = &*client_path.join(id).join(format!("{}.json", id));
|
||||||
|
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
||||||
} else {
|
} else {
|
||||||
let info = fetch_version_info(version)
|
let mut info = fetch_version_info(version).await?;
|
||||||
.await
|
|
||||||
.map_err(|err| LauncherError::FetchError {
|
if let Some(loader_version) = loader_version {
|
||||||
inner: err,
|
let partial = fetch_partial_version(&*loader_version.url).await?;
|
||||||
item: "version info".to_string(),
|
|
||||||
})?;
|
info = merge_partial_version(partial, info);
|
||||||
|
|
||||||
|
info.id = loader_version.id.clone();
|
||||||
|
}
|
||||||
|
|
||||||
save_file(path, &bytes::Bytes::from(serde_json::to_string(&info)?))?;
|
save_file(path, &bytes::Bytes::from(serde_json::to_string(&info)?))?;
|
||||||
|
|
||||||
@@ -50,7 +56,7 @@ pub async fn download_client(
|
|||||||
.join(&version_info.id)
|
.join(&version_info.id)
|
||||||
.join(format!("{}.jar", &version_info.id));
|
.join(format!("{}.jar", &version_info.id));
|
||||||
|
|
||||||
save_and_download_file(path, &client_download.url, &client_download.sha1).await?;
|
save_and_download_file(path, &client_download.url, Some(&client_download.sha1)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -66,12 +72,7 @@ pub async fn download_assets_index(
|
|||||||
if path.exists() {
|
if path.exists() {
|
||||||
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
||||||
} else {
|
} else {
|
||||||
let index = fetch_assets_index(version)
|
let index = fetch_assets_index(version).await?;
|
||||||
.await
|
|
||||||
.map_err(|err| LauncherError::FetchError {
|
|
||||||
inner: err,
|
|
||||||
item: "assets index".to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
save_file(path, &bytes::Bytes::from(serde_json::to_string(&index)?))?;
|
save_file(path, &bytes::Bytes::from(serde_json::to_string(&index)?))?;
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ async fn download_asset(
|
|||||||
"https://resources.download.minecraft.net/{}/{}",
|
"https://resources.download.minecraft.net/{}/{}",
|
||||||
sub_hash, asset.hash
|
sub_hash, asset.hash
|
||||||
),
|
),
|
||||||
&*asset.hash,
|
Some(&*asset.hash),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -154,34 +155,9 @@ async fn download_library(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let name_items = library.name.split(':').collect::<Vec<&str>>();
|
|
||||||
|
|
||||||
let package = name_items.get(0).ok_or_else(|| {
|
|
||||||
LauncherError::ParseError(format!(
|
|
||||||
"Unable to find package for library {}",
|
|
||||||
&library.name
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
let name = name_items.get(1).ok_or_else(|| {
|
|
||||||
LauncherError::ParseError(format!("Unable to find name for library {}", &library.name))
|
|
||||||
})?;
|
|
||||||
let version = name_items.get(2).ok_or_else(|| {
|
|
||||||
LauncherError::ParseError(format!(
|
|
||||||
"Unable to find version for library {}",
|
|
||||||
&library.name
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (a, b) = future::join(
|
let (a, b) = future::join(
|
||||||
download_library_jar(libraries_path, library, package, name, version),
|
download_library_jar(libraries_path, library),
|
||||||
download_native(
|
download_native(natives_path, library),
|
||||||
libraries_path,
|
|
||||||
natives_path,
|
|
||||||
library,
|
|
||||||
package,
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -194,61 +170,51 @@ async fn download_library(
|
|||||||
async fn download_library_jar(
|
async fn download_library_jar(
|
||||||
libraries_path: &Path,
|
libraries_path: &Path,
|
||||||
library: &Library,
|
library: &Library,
|
||||||
package: &str,
|
|
||||||
name: &str,
|
|
||||||
version: &str,
|
|
||||||
) -> Result<(), LauncherError> {
|
) -> Result<(), LauncherError> {
|
||||||
if let Some(library) = &library.downloads.artifact {
|
let mut path = libraries_path.to_path_buf();
|
||||||
let mut path = libraries_path.to_path_buf();
|
path.push(get_path_from_artifact(&*library.name)?);
|
||||||
|
|
||||||
for directory in package.split('.') {
|
if let Some(downloads) = &library.downloads {
|
||||||
path.push(directory);
|
if let Some(library) = &downloads.artifact {
|
||||||
|
save_and_download_file(&*path, &library.url, Some(&library.sha1)).await?;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
path.push(name);
|
save_and_download_file(
|
||||||
path.push(version);
|
&*path,
|
||||||
path.push(format!("{}-{}.jar", name, version));
|
&format!(
|
||||||
|
"{}{}",
|
||||||
save_and_download_file(&*path, &library.url, &library.sha1).await?;
|
library
|
||||||
|
.url
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("https://libraries.minecraft.net/"),
|
||||||
|
get_path_from_artifact(&*library.name)?
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_native(
|
async fn download_native(natives_path: &Path, library: &Library) -> Result<(), LauncherError> {
|
||||||
libraries_path: &Path,
|
|
||||||
natives_path: &Path,
|
|
||||||
library: &Library,
|
|
||||||
package: &str,
|
|
||||||
name: &str,
|
|
||||||
version: &str,
|
|
||||||
) -> Result<(), LauncherError> {
|
|
||||||
if let Some(natives) = &library.natives {
|
if let Some(natives) = &library.natives {
|
||||||
if let Some(os_key) = natives.get(&get_os()) {
|
if let Some(os_key) = natives.get(&get_os()) {
|
||||||
if let Some(classifiers) = &library.downloads.classifiers {
|
if let Some(downloads) = &library.downloads {
|
||||||
#[cfg(target_pointer_width = "64")]
|
if let Some(classifiers) = &downloads.classifiers {
|
||||||
let parsed_key = os_key.replace("${arch}", "64");
|
#[cfg(target_pointer_width = "64")]
|
||||||
#[cfg(target_pointer_width = "32")]
|
let parsed_key = os_key.replace("${arch}", "64");
|
||||||
let parsed_key = os_key.replace("${arch}", "32");
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
let parsed_key = os_key.replace("${arch}", "32");
|
||||||
|
|
||||||
if let Some(native) = classifiers.get(&*parsed_key) {
|
if let Some(native) = classifiers.get(&*parsed_key) {
|
||||||
let mut path = libraries_path.to_path_buf();
|
let file = download_file(&native.url, Some(&native.sha1)).await?;
|
||||||
|
|
||||||
for directory in package.split('.') {
|
let reader = std::io::Cursor::new(&*file);
|
||||||
path.push(directory);
|
|
||||||
|
let mut archive = zip::ZipArchive::new(reader).unwrap();
|
||||||
|
archive.extract(natives_path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
path.push(name);
|
|
||||||
path.push(version);
|
|
||||||
path.push(format!("{}-{}-{}.jar", name, version, parsed_key));
|
|
||||||
|
|
||||||
save_and_download_file(&*path, &native.url, &native.sha1).await?;
|
|
||||||
|
|
||||||
let file = File::open(&path).unwrap();
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
|
|
||||||
let mut archive = zip::ZipArchive::new(reader).unwrap();
|
|
||||||
archive.extract(natives_path).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,14 +226,14 @@ async fn download_native(
|
|||||||
async fn save_and_download_file(
|
async fn save_and_download_file(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
url: &str,
|
url: &str,
|
||||||
sha1: &str,
|
sha1: Option<&str>,
|
||||||
) -> Result<bytes::Bytes, LauncherError> {
|
) -> Result<bytes::Bytes, LauncherError> {
|
||||||
let read = std::fs::read(path).ok().map(bytes::Bytes::from);
|
let read = std::fs::read(path).ok().map(bytes::Bytes::from);
|
||||||
|
|
||||||
if let Some(bytes) = read {
|
if let Some(bytes) = read {
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
} else {
|
} else {
|
||||||
let file = download_file(url, Some(sha1)).await?;
|
let file = download_file(url, sha1).await?;
|
||||||
|
|
||||||
save_file(path, &file)?;
|
save_file(path, &file)?;
|
||||||
|
|
||||||
@@ -286,7 +252,16 @@ fn save_file(path: &Path, bytes: &bytes::Bytes) -> Result<(), std::io::Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes, LauncherError> {
|
pub fn get_os() -> Os {
|
||||||
|
match std::env::consts::OS {
|
||||||
|
"windows" => Os::Windows,
|
||||||
|
"macos" => Os::Osx,
|
||||||
|
"linux" => Os::Linux,
|
||||||
|
_ => Os::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes, LauncherError> {
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
|
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
|
||||||
.build()
|
.build()
|
||||||
@@ -295,7 +270,7 @@ async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes, La
|
|||||||
item: url.to_string(),
|
item: url.to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for attempt in 1..4 {
|
for attempt in 1..=4 {
|
||||||
let result = client.get(url).send().await;
|
let result = client.get(url).send().await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@@ -340,17 +315,9 @@ async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes, La
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_hash(bytes: bytes::Bytes) -> Result<String, LauncherError> {
|
/// Computes a checksum of the input bytes
|
||||||
|
pub async fn get_hash(bytes: bytes::Bytes) -> Result<String, LauncherError> {
|
||||||
let hash = tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest()).await?;
|
let hash = tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest()).await?;
|
||||||
|
|
||||||
Ok(hash)
|
Ok(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_os() -> Os {
|
|
||||||
match std::env::consts::OS {
|
|
||||||
"windows" => Os::Windows,
|
|
||||||
"macos" => Os::Osx,
|
|
||||||
"linux" => Os::Linux,
|
|
||||||
_ => Os::Unknown,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
use chrono::{DateTime, Utc};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum VersionType {
|
|
||||||
Release,
|
|
||||||
Snapshot,
|
|
||||||
OldAlpha,
|
|
||||||
OldBeta,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VersionType {
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
VersionType::Release => "release",
|
|
||||||
VersionType::Snapshot => "snapshot",
|
|
||||||
VersionType::OldAlpha => "old_alpha",
|
|
||||||
VersionType::OldBeta => "old_beta",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Version {
|
|
||||||
pub id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub type_: VersionType,
|
|
||||||
pub url: String,
|
|
||||||
pub time: DateTime<Utc>,
|
|
||||||
pub release_time: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct LatestVersion {
|
|
||||||
pub release: String,
|
|
||||||
pub snapshot: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct VersionManifest {
|
|
||||||
pub latest: LatestVersion,
|
|
||||||
pub versions: Vec<Version>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_version_manifest() -> Result<VersionManifest, reqwest::Error> {
|
|
||||||
reqwest::get("https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct AssetIndex {
|
|
||||||
pub id: String,
|
|
||||||
pub sha1: String,
|
|
||||||
pub size: u32,
|
|
||||||
pub total_size: u32,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum DownloadType {
|
|
||||||
Client,
|
|
||||||
ClientMappings,
|
|
||||||
Server,
|
|
||||||
ServerMappings,
|
|
||||||
WindowsServer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Download {
|
|
||||||
pub sha1: String,
|
|
||||||
pub size: u32,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct LibraryDownload {
|
|
||||||
pub path: String,
|
|
||||||
pub sha1: String,
|
|
||||||
pub size: u32,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct LibraryDownloads {
|
|
||||||
pub artifact: Option<LibraryDownload>,
|
|
||||||
pub classifiers: Option<HashMap<String, LibraryDownload>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum RuleAction {
|
|
||||||
Allow,
|
|
||||||
Disallow,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum Os {
|
|
||||||
Osx,
|
|
||||||
Windows,
|
|
||||||
Linux,
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct OsRule {
|
|
||||||
pub name: Option<Os>,
|
|
||||||
pub version: Option<String>,
|
|
||||||
pub arch: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct FeatureRule {
|
|
||||||
pub is_demo_user: Option<bool>,
|
|
||||||
pub has_demo_resolution: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Rule {
|
|
||||||
pub action: RuleAction,
|
|
||||||
pub os: Option<OsRule>,
|
|
||||||
pub features: Option<FeatureRule>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct LibraryExtract {
|
|
||||||
pub exclude: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Library {
|
|
||||||
pub downloads: LibraryDownloads,
|
|
||||||
pub extract: Option<LibraryExtract>,
|
|
||||||
pub name: String,
|
|
||||||
pub natives: Option<HashMap<Os, String>>,
|
|
||||||
pub rules: Option<Vec<Rule>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum ArgumentValue {
|
|
||||||
Single(String),
|
|
||||||
Many(Vec<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum Argument {
|
|
||||||
Normal(String),
|
|
||||||
Ruled {
|
|
||||||
rules: Vec<Rule>,
|
|
||||||
value: ArgumentValue,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum ArgumentType {
|
|
||||||
Game,
|
|
||||||
Jvm,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VersionInfo {
|
|
||||||
pub arguments: Option<HashMap<ArgumentType, Vec<Argument>>>,
|
|
||||||
pub asset_index: AssetIndex,
|
|
||||||
pub assets: String,
|
|
||||||
pub downloads: HashMap<DownloadType, Download>,
|
|
||||||
pub id: String,
|
|
||||||
pub libraries: Vec<Library>,
|
|
||||||
pub main_class: String,
|
|
||||||
pub minecraft_arguments: Option<String>,
|
|
||||||
pub minimum_launcher_version: u32,
|
|
||||||
pub release_time: DateTime<Utc>,
|
|
||||||
pub time: DateTime<Utc>,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub type_: VersionType,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_version_info(version: &Version) -> Result<VersionInfo, reqwest::Error> {
|
|
||||||
reqwest::get(&version.url).await?.json().await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Asset {
|
|
||||||
pub hash: String,
|
|
||||||
pub size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct AssetsIndex {
|
|
||||||
pub objects: HashMap<String, Asset>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_assets_index(version: &VersionInfo) -> Result<AssetsIndex, reqwest::Error> {
|
|
||||||
reqwest::get(&version.asset_index.url).await?.json().await
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
use crate::launcher::auth::provider::Credentials;
|
use daedalus::minecraft::{ArgumentType, VersionInfo};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub mod args;
|
pub use crate::launcher::auth::provider::Credentials;
|
||||||
pub mod auth;
|
|
||||||
pub mod download;
|
mod args;
|
||||||
pub mod java;
|
mod auth;
|
||||||
pub mod meta;
|
mod download;
|
||||||
pub mod rules;
|
mod java;
|
||||||
|
mod rules;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum LauncherError {
|
pub enum LauncherError {
|
||||||
@@ -18,11 +19,13 @@ pub enum LauncherError {
|
|||||||
url: String,
|
url: String,
|
||||||
tries: u32,
|
tries: u32,
|
||||||
},
|
},
|
||||||
|
#[error("Failed to run processor: {0}")]
|
||||||
|
ProcessorError(String),
|
||||||
#[error("Invalid input: {0}")]
|
#[error("Invalid input: {0}")]
|
||||||
InvalidInput(String),
|
InvalidInput(String),
|
||||||
#[error("Error while managing asynchronous tasks")]
|
#[error("Error while managing asynchronous tasks")]
|
||||||
TaskError(#[from] tokio::task::JoinError),
|
TaskError(#[from] tokio::task::JoinError),
|
||||||
#[error("Error while reading/writing to the disk")]
|
#[error("Error while reading/writing to the disk: {0}")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
#[error("Error while spawning child process {process}")]
|
#[error("Error while spawning child process {process}")]
|
||||||
ProcessError {
|
ProcessError {
|
||||||
@@ -35,57 +38,237 @@ pub enum LauncherError {
|
|||||||
FetchError { inner: reqwest::Error, item: String },
|
FetchError { inner: reqwest::Error, item: String },
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
ParseError(String),
|
ParseError(String),
|
||||||
|
#[error("Error while fetching metadata: {0}")]
|
||||||
|
DaedalusError(#[from] daedalus::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
const META_URL: &str = "https://staging-cdn.modrinth.com/gamedata";
|
||||||
|
|
||||||
|
pub async fn fetch_metadata() -> Result<
|
||||||
|
(
|
||||||
|
daedalus::minecraft::VersionManifest,
|
||||||
|
daedalus::modded::Manifest,
|
||||||
|
daedalus::modded::Manifest,
|
||||||
|
),
|
||||||
|
LauncherError,
|
||||||
|
> {
|
||||||
|
let (game, forge, fabric) = futures::future::join3(
|
||||||
|
daedalus::minecraft::fetch_version_manifest(Some(&*format!(
|
||||||
|
"{}/minecraft/v0/manifest.json",
|
||||||
|
META_URL
|
||||||
|
))),
|
||||||
|
daedalus::modded::fetch_manifest(&*format!("{}/forge/v0/manifest.json", META_URL)),
|
||||||
|
daedalus::modded::fetch_manifest(&*format!("{}/fabric/v0/manifest.json", META_URL)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok((game?, forge?, fabric?))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||||
|
pub enum ModLoader {
|
||||||
|
Vanilla,
|
||||||
|
Forge,
|
||||||
|
Fabric,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ModLoader {
|
||||||
|
fn default() -> Self {
|
||||||
|
ModLoader::Vanilla
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn launch_minecraft(
|
pub async fn launch_minecraft(
|
||||||
version_name: &str,
|
version_name: &str,
|
||||||
|
mod_loader: Option<ModLoader>,
|
||||||
root_dir: &Path,
|
root_dir: &Path,
|
||||||
credentials: &Credentials,
|
credentials: &Credentials,
|
||||||
) -> Result<(), LauncherError> {
|
) -> Result<(), LauncherError> {
|
||||||
let manifest = meta::fetch_version_manifest().await.unwrap();
|
let (game, forge, fabric) = fetch_metadata().await?;
|
||||||
|
|
||||||
let version = download::download_version_info(
|
let versions_path = crate::util::absolute_path(root_dir.join("versions"))?;
|
||||||
&*root_dir.join("versions"),
|
let libraries_path = crate::util::absolute_path(root_dir.join("libraries"))?;
|
||||||
manifest
|
let assets_path = crate::util::absolute_path(root_dir.join("assets"))?;
|
||||||
.versions
|
let legacy_assets_path = crate::util::absolute_path(root_dir.join("resources"))?;
|
||||||
|
|
||||||
|
let mut version = download::download_version_info(
|
||||||
|
&versions_path,
|
||||||
|
game.versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.id == version_name)
|
.find(|x| x.id == version_name)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
LauncherError::InvalidInput(format!("Version {} does not exist", version_name))
|
LauncherError::InvalidInput(format!("Version {} does not exist", version_name))
|
||||||
})?,
|
})?,
|
||||||
|
match mod_loader.unwrap_or_default() {
|
||||||
|
ModLoader::Vanilla => None,
|
||||||
|
ModLoader::Forge | ModLoader::Fabric => {
|
||||||
|
let loaders = if mod_loader.unwrap_or_default() == ModLoader::Forge {
|
||||||
|
&forge
|
||||||
|
.game_versions
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.id == version_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
LauncherError::InvalidInput(format!(
|
||||||
|
"Version {} for mod loader Forge does not exist",
|
||||||
|
version_name
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.loaders
|
||||||
|
} else {
|
||||||
|
&fabric
|
||||||
|
.game_versions
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.id == version_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
LauncherError::InvalidInput(format!(
|
||||||
|
"Version {} for mod loader Fabric does not exist",
|
||||||
|
version_name
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.loaders
|
||||||
|
};
|
||||||
|
|
||||||
|
let loader = if let Some(version) =
|
||||||
|
loaders.get(&daedalus::modded::LoaderType::Stable)
|
||||||
|
{
|
||||||
|
Some(version.clone())
|
||||||
|
} else if let Some(version) = loaders.get(&daedalus::modded::LoaderType::Latest) {
|
||||||
|
Some(version.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(loader.ok_or_else(|| {
|
||||||
|
LauncherError::InvalidInput(format!(
|
||||||
|
"No mod loader version found for version {}",
|
||||||
|
version_name
|
||||||
|
))
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.as_ref(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
download_minecraft(&version, root_dir).await?;
|
let client_path = crate::util::absolute_path(
|
||||||
|
root_dir
|
||||||
|
.join("versions")
|
||||||
|
.join(&version.id)
|
||||||
|
.join(format!("{}.jar", &version.id)),
|
||||||
|
)?;
|
||||||
|
let natives_path = crate::util::absolute_path(root_dir.join("natives").join(&version.id))?;
|
||||||
|
|
||||||
let arguments = version.arguments.unwrap();
|
download_minecraft(
|
||||||
|
&version,
|
||||||
|
&versions_path,
|
||||||
|
&assets_path,
|
||||||
|
&legacy_assets_path,
|
||||||
|
&libraries_path,
|
||||||
|
&natives_path,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(processors) = &version.processors {
|
||||||
|
if let Some(ref mut data) = version.data {
|
||||||
|
data.insert(
|
||||||
|
"SIDE".to_string(),
|
||||||
|
daedalus::modded::SidedDataEntry {
|
||||||
|
client: "client".to_string(),
|
||||||
|
server: "".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
data.insert(
|
||||||
|
"MINECRAFT_JAR".to_string(),
|
||||||
|
daedalus::modded::SidedDataEntry {
|
||||||
|
client: client_path.to_string_lossy().to_string(),
|
||||||
|
server: "".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
data.insert(
|
||||||
|
"MINECRAFT_VERSION".to_string(),
|
||||||
|
daedalus::modded::SidedDataEntry {
|
||||||
|
client: version_name.to_string(),
|
||||||
|
server: "".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
data.insert(
|
||||||
|
"ROOT".to_string(),
|
||||||
|
daedalus::modded::SidedDataEntry {
|
||||||
|
client: root_dir.to_string_lossy().to_string(),
|
||||||
|
server: "".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
data.insert(
|
||||||
|
"LIBRARY_DIR".to_string(),
|
||||||
|
daedalus::modded::SidedDataEntry {
|
||||||
|
client: libraries_path.to_string_lossy().to_string(),
|
||||||
|
server: "".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
for processor in processors {
|
||||||
|
if let Some(sides) = &processor.sides {
|
||||||
|
if !sides.contains(&"client".to_string()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cp = processor.classpath.clone();
|
||||||
|
cp.push(processor.jar.clone());
|
||||||
|
|
||||||
|
let child = Command::new("java")
|
||||||
|
.arg("-cp")
|
||||||
|
.arg(args::get_class_paths_jar(&libraries_path, &cp)?)
|
||||||
|
.arg(
|
||||||
|
args::get_processor_main_class(args::get_lib_path(
|
||||||
|
&libraries_path,
|
||||||
|
&processor.jar,
|
||||||
|
)?)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
LauncherError::ProcessorError(format!(
|
||||||
|
"Could not find processor main class for {}",
|
||||||
|
processor.jar
|
||||||
|
))
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.args(args::get_processor_arguments(
|
||||||
|
&libraries_path,
|
||||||
|
&processor.args,
|
||||||
|
data,
|
||||||
|
)?)
|
||||||
|
.output()
|
||||||
|
.map_err(|err| LauncherError::ProcessError {
|
||||||
|
inner: err,
|
||||||
|
process: "java".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !child.status.success() {
|
||||||
|
return Err(LauncherError::ProcessorError(
|
||||||
|
String::from_utf8_lossy(&*child.stderr).to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let arguments = version.arguments.unwrap_or_default();
|
||||||
|
|
||||||
let mut child = Command::new("java")
|
let mut child = Command::new("java")
|
||||||
.args(args::get_jvm_arguments(
|
.args(args::get_jvm_arguments(
|
||||||
arguments
|
arguments.get(&ArgumentType::Jvm).map(|x| x.as_slice()),
|
||||||
.get(&meta::ArgumentType::Jvm)
|
&natives_path,
|
||||||
.map(|x| x.as_slice()),
|
&*args::get_class_paths(&libraries_path, version.libraries.as_slice(), &client_path)?,
|
||||||
&*root_dir.join("natives").join(&version.id),
|
|
||||||
&*args::get_class_paths(
|
|
||||||
&*root_dir.join("libraries"),
|
|
||||||
version.libraries.as_slice(),
|
|
||||||
&*root_dir
|
|
||||||
.join("versions")
|
|
||||||
.join(&version.id)
|
|
||||||
.join(format!("{}.jar", &version.id)),
|
|
||||||
)?,
|
|
||||||
)?)
|
)?)
|
||||||
.arg(version.main_class)
|
.arg(version.main_class)
|
||||||
.args(args::get_minecraft_arguments(
|
.args(args::get_minecraft_arguments(
|
||||||
arguments
|
arguments.get(&ArgumentType::Game).map(|x| x.as_slice()),
|
||||||
.get(&meta::ArgumentType::Game)
|
|
||||||
.map(|x| x.as_slice()),
|
|
||||||
version.minecraft_arguments.as_deref(),
|
version.minecraft_arguments.as_deref(),
|
||||||
credentials,
|
credentials,
|
||||||
&*version.id,
|
&*version.id,
|
||||||
&version.asset_index.id,
|
&version.asset_index.id,
|
||||||
root_dir,
|
root_dir,
|
||||||
&*root_dir.join("assets"),
|
&assets_path,
|
||||||
&version.type_,
|
&version.type_,
|
||||||
)?)
|
)?)
|
||||||
.current_dir(root_dir)
|
.current_dir(root_dir)
|
||||||
@@ -106,29 +289,27 @@ pub async fn launch_minecraft(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_minecraft(
|
pub async fn download_minecraft(
|
||||||
version: &meta::VersionInfo,
|
version: &VersionInfo,
|
||||||
root_dir: &Path,
|
versions_dir: &Path,
|
||||||
|
assets_dir: &Path,
|
||||||
|
legacy_assets_dir: &Path,
|
||||||
|
libraries_dir: &Path,
|
||||||
|
natives_dir: &Path,
|
||||||
) -> Result<(), LauncherError> {
|
) -> Result<(), LauncherError> {
|
||||||
let assets_index = download::download_assets_index(&*root_dir.join("assets"), &version).await?;
|
let assets_index = download::download_assets_index(assets_dir, version).await?;
|
||||||
|
|
||||||
let legacy_dir = root_dir.join("resources");
|
|
||||||
|
|
||||||
let (a, b, c) = futures::future::join3(
|
let (a, b, c) = futures::future::join3(
|
||||||
download::download_client(&*root_dir.join("versions"), &version),
|
download::download_client(versions_dir, version),
|
||||||
download::download_assets(
|
download::download_assets(
|
||||||
&*root_dir.join("assets"),
|
assets_dir,
|
||||||
if version.assets == "legacy" {
|
if version.assets == "legacy" {
|
||||||
Some(legacy_dir.as_path())
|
Some(legacy_assets_dir)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
&assets_index,
|
&assets_index,
|
||||||
),
|
),
|
||||||
download::download_libraries(
|
download::download_libraries(libraries_dir, natives_dir, version.libraries.as_slice()),
|
||||||
&*root_dir.join("libraries"),
|
|
||||||
&*root_dir.join("natives").join(&version.id),
|
|
||||||
version.libraries.as_slice(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::launcher::download::get_os;
|
use crate::launcher::download::get_os;
|
||||||
use crate::launcher::meta::{OsRule, Rule, RuleAction};
|
use daedalus::minecraft::{OsRule, Rule, RuleAction};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
pub fn parse_rules(rules: &[Rule]) -> bool {
|
pub fn parse_rules(rules: &[Rule]) -> bool {
|
||||||
@@ -9,10 +9,8 @@ pub fn parse_rules(rules: &[Rule]) -> bool {
|
|||||||
pub fn parse_rule(rule: &Rule) -> bool {
|
pub fn parse_rule(rule: &Rule) -> bool {
|
||||||
let result = if let Some(os) = &rule.os {
|
let result = if let Some(os) = &rule.os {
|
||||||
parse_os_rule(os)
|
parse_os_rule(os)
|
||||||
} else if rule.features.is_some() {
|
|
||||||
false
|
|
||||||
} else {
|
} else {
|
||||||
true
|
rule.features.is_none()
|
||||||
};
|
};
|
||||||
|
|
||||||
match rule.action {
|
match rule.action {
|
||||||
|
|||||||
@@ -6,3 +6,4 @@
|
|||||||
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
|
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
|
||||||
|
|
||||||
pub mod launcher;
|
pub mod launcher;
|
||||||
|
mod util;
|
||||||
|
|||||||
17
theseus/src/util.rs
Normal file
17
theseus/src/util.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{env, io};
|
||||||
|
|
||||||
|
use path_clean::PathClean;
|
||||||
|
|
||||||
|
pub fn absolute_path(path: impl AsRef<Path>) -> io::Result<PathBuf> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
let absolute_path = if path.is_absolute() {
|
||||||
|
path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
env::current_dir()?.join(path)
|
||||||
|
}
|
||||||
|
.clean();
|
||||||
|
|
||||||
|
Ok(absolute_path)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user