diff --git a/.idea/theseus.iml b/.idea/theseus.iml
index e70532b3a..9cd854a33 100644
--- a/.idea/theseus.iml
+++ b/.idea/theseus.iml
@@ -42,6 +42,10 @@
+
+
+
+
diff --git a/Cargo.lock b/Cargo.lock
index e7dbc8669..f88a687a4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -894,6 +894,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "sha1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
+
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
@@ -972,6 +978,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
+ "sha1",
"sys-info",
"thiserror",
"tokio",
diff --git a/theseus/Cargo.toml b/theseus/Cargo.toml
index 8d3a2acbe..c4d8a9f57 100644
--- a/theseus/Cargo.toml
+++ b/theseus/Cargo.toml
@@ -16,6 +16,7 @@ chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
bytes = "1"
zip = "0.5"
+sha1 = { version = "0.6.0", features = ["std"]}
regex = "1.5"
lazy_static = "1.4"
diff --git a/theseus/src/launcher/args.rs b/theseus/src/launcher/args.rs
index a68abca05..a7e9b47d4 100644
--- a/theseus/src/launcher/args.rs
+++ b/theseus/src/launcher/args.rs
@@ -1,9 +1,15 @@
+use crate::launcher::auth::provider::Credentials;
use crate::launcher::meta::{Argument, ArgumentValue, Library, Os, VersionType};
use crate::launcher::rules::parse_rules;
+use crate::launcher::LauncherError;
use std::path::Path;
use uuid::Uuid;
-pub fn get_class_paths(libraries_path: &Path, libraries: &[Library], client_path: &Path) -> String {
+pub fn get_class_paths(
+ libraries_path: &Path,
+ libraries: &[Library],
+ client_path: &Path,
+) -> Result {
let mut class_paths = Vec::new();
for library in libraries {
@@ -16,13 +22,28 @@ pub fn get_class_paths(libraries_path: &Path, libraries: &[Library], client_path
let name_items = library.name.split(':').collect::>();
- let package = name_items.get(0).unwrap();
- let name = name_items.get(1).unwrap();
- let version = name_items.get(2).unwrap();
+ 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(".") {
+ for directory in package.split('.') {
path.push(directory);
}
@@ -32,7 +53,12 @@ pub fn get_class_paths(libraries_path: &Path, libraries: &[Library], client_path
class_paths.push(
std::fs::canonicalize(&path)
- .unwrap()
+ .map_err(|_| {
+ LauncherError::InvalidInput(format!(
+ "Library file at path {} does not exist",
+ path.to_string_lossy()
+ ))
+ })?
.to_string_lossy()
.to_string(),
)
@@ -41,33 +67,41 @@ pub fn get_class_paths(libraries_path: &Path, libraries: &[Library], client_path
class_paths.push(
std::fs::canonicalize(&client_path)
- .unwrap()
+ .map_err(|_| {
+ LauncherError::InvalidInput(format!(
+ "Specified client path {} does not exist",
+ client_path.to_string_lossy()
+ ))
+ })?
.to_string_lossy()
.to_string(),
);
- class_paths.join(match super::download::get_os() {
+ Ok(class_paths.join(match super::download::get_os() {
Os::Osx | Os::Linux | Os::Unknown => ":",
Os::Windows => ";",
- })
+ }))
}
pub fn get_jvm_arguments(
arguments: Option<&[Argument]>,
natives_path: &Path,
class_paths: &str,
-) -> Vec {
+) -> Result, LauncherError> {
let mut parsed_arguments = Vec::new();
if let Some(args) = arguments {
parse_arguments(args, &mut parsed_arguments, |arg| {
parse_jvm_argument(arg, natives_path, class_paths)
- });
+ })?;
} else {
parsed_arguments.push(format!(
"-Djava.library.path={}",
&*std::fs::canonicalize(natives_path)
- .unwrap()
+ .map_err(|_| LauncherError::InvalidInput(format!(
+ "Specified natives path {} does not exist",
+ natives_path.to_string_lossy()
+ )))?
.to_string_lossy()
.to_string()
));
@@ -75,74 +109,83 @@ pub fn get_jvm_arguments(
parsed_arguments.push(class_paths.to_string());
}
- parsed_arguments
+ Ok(parsed_arguments)
}
-fn parse_jvm_argument(argument: &str, natives_path: &Path, class_paths: &str) -> String {
- argument
+fn parse_jvm_argument(
+ argument: &str,
+ natives_path: &Path,
+ class_paths: &str,
+) -> Result {
+ Ok(argument
.replace(
"${natives_directory}",
&*std::fs::canonicalize(natives_path)
- .unwrap()
+ .map_err(|_| {
+ LauncherError::InvalidInput(format!(
+ "Specified natives path {} does not exist",
+ natives_path.to_string_lossy()
+ ))
+ })?
.to_string_lossy()
.to_string(),
)
.replace("${launcher_name}", "theseus")
.replace("${launcher_version}", env!("CARGO_PKG_VERSION"))
- .replace("${classpath}", class_paths)
+ .replace("${classpath}", class_paths))
}
+#[allow(clippy::too_many_arguments)]
pub fn get_minecraft_arguments(
arguments: Option<&[Argument]>,
legacy_arguments: Option<&str>,
- access_token: &str,
- username: &str,
- uuid: &Uuid,
+ credentials: &Credentials,
version: &str,
asset_index_name: &str,
game_directory: &Path,
assets_directory: &Path,
version_type: &VersionType,
-) -> Vec {
+) -> Result, LauncherError> {
if let Some(arguments) = arguments {
let mut parsed_arguments = Vec::new();
parse_arguments(arguments, &mut parsed_arguments, |arg| {
parse_minecraft_argument(
arg,
- access_token,
- username,
- uuid,
+ &*credentials.access_token,
+ &*credentials.username,
+ &credentials.id,
version,
asset_index_name,
game_directory,
assets_directory,
version_type,
)
- });
+ })?;
- parsed_arguments
+ Ok(parsed_arguments)
} else if let Some(legacy_arguments) = legacy_arguments {
- parse_minecraft_argument(
+ Ok(parse_minecraft_argument(
legacy_arguments,
- access_token,
- username,
- uuid,
+ &*credentials.access_token,
+ &*credentials.username,
+ &credentials.id,
version,
asset_index_name,
game_directory,
assets_directory,
version_type,
- )
- .split(" ")
+ )?
+ .split(' ')
.into_iter()
.map(|x| x.to_string())
- .collect()
+ .collect())
} else {
- Vec::new()
+ Ok(Vec::new())
}
}
+#[allow(clippy::too_many_arguments)]
fn parse_minecraft_argument(
argument: &str,
access_token: &str,
@@ -153,8 +196,8 @@ fn parse_minecraft_argument(
game_directory: &Path,
assets_directory: &Path,
version_type: &VersionType,
-) -> String {
- argument
+) -> Result {
+ Ok(argument
.replace("${auth_access_token}", access_token)
.replace("${auth_session}", access_token)
.replace("${auth_player_name}", username)
@@ -166,37 +209,56 @@ fn parse_minecraft_argument(
.replace(
"${game_directory}",
&*std::fs::canonicalize(game_directory)
- .unwrap()
+ .map_err(|_| {
+ LauncherError::InvalidInput(format!(
+ "Specified game directory {} does not exist",
+ game_directory.to_string_lossy()
+ ))
+ })?
.to_string_lossy()
.to_string(),
)
.replace(
"${assets_root}",
&*std::fs::canonicalize(assets_directory)
- .unwrap()
+ .map_err(|_| {
+ LauncherError::InvalidInput(format!(
+ "Specified assets directory {} does not exist",
+ assets_directory.to_string_lossy()
+ ))
+ })?
.to_string_lossy()
.to_string(),
)
.replace(
"${game_assets}",
&*std::fs::canonicalize(assets_directory)
- .unwrap()
+ .map_err(|_| {
+ LauncherError::InvalidInput(format!(
+ "Specified assets directory {} does not exist",
+ assets_directory.to_string_lossy()
+ ))
+ })?
.to_string_lossy()
.to_string(),
)
- .replace("${version_type}", version_type.as_str())
+ .replace("${version_type}", version_type.as_str()))
}
-fn parse_arguments(arguments: &[Argument], parsed_arguments: &mut Vec, parse_function: F)
+fn parse_arguments(
+ arguments: &[Argument],
+ parsed_arguments: &mut Vec,
+ parse_function: F,
+) -> Result<(), LauncherError>
where
- F: Fn(&str) -> String,
+ F: Fn(&str) -> Result,
{
for argument in arguments {
match argument {
Argument::Normal(arg) => {
- let parsed = parse_function(arg);
+ let parsed = parse_function(arg)?;
- for arg in parsed.split(" ") {
+ for arg in parsed.split(' ') {
parsed_arguments.push(arg.to_string());
}
}
@@ -204,11 +266,11 @@ where
if parse_rules(rules.as_slice()) {
match value {
ArgumentValue::Single(arg) => {
- //parsed_arguments.push(parse_function(arg));
+ parsed_arguments.push(parse_function(arg)?);
}
ArgumentValue::Many(args) => {
for arg in args {
- //parsed_arguments.push(parse_function(arg));
+ parsed_arguments.push(parse_function(arg)?);
}
}
}
@@ -216,4 +278,6 @@ where
}
}
}
+
+ Ok(())
}
diff --git a/theseus/src/launcher/auth.rs b/theseus/src/launcher/auth.rs
index 68e5c2b68..0981010d6 100644
--- a/theseus/src/launcher/auth.rs
+++ b/theseus/src/launcher/auth.rs
@@ -1,162 +1,178 @@
-use serde::{Deserialize, Serialize};
-use uuid::Uuid;
+pub mod api {
+ use serde::{Deserialize, Serialize};
+ use uuid::Uuid;
-#[derive(Debug, Serialize, Deserialize)]
-pub struct GameProfile {
- pub id: Uuid,
- pub name: String,
-}
+ #[derive(Debug, Serialize, Deserialize)]
+ pub struct GameProfile {
+ pub id: Uuid,
+ pub name: String,
+ }
-#[derive(Debug, Deserialize)]
-pub struct UserProperty {
- pub name: String,
- pub value: String,
-}
+ #[derive(Debug, Deserialize)]
+ pub struct UserProperty {
+ pub name: String,
+ pub value: String,
+ }
-#[derive(Debug, Deserialize)]
-pub struct User {
- pub id: String,
- pub username: String,
- pub properties: Option>,
-}
+ #[derive(Debug, Deserialize)]
+ pub struct User {
+ pub id: String,
+ pub username: String,
+ pub properties: Option>,
+ }
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct AuthenticateResponse {
- pub user: Option,
- pub client_token: Uuid,
- pub access_token: String,
- pub available_profiles: Vec,
- pub selected_profile: Option,
-}
+ #[derive(Debug, Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct AuthenticateResponse {
+ pub user: Option,
+ pub client_token: Uuid,
+ pub access_token: String,
+ pub available_profiles: Vec,
+ pub selected_profile: Option,
+ }
-pub async fn login(username: &str, password: &str, request_user: bool) -> AuthenticateResponse {
- let client = reqwest::Client::new();
+ pub async fn login(
+ username: &str,
+ password: &str,
+ request_user: bool,
+ ) -> Result {
+ let client = reqwest::Client::new();
- client
- .post("https://authserver.mojang.com/authenticate")
- .header(reqwest::header::CONTENT_TYPE, "application/json")
- .body(
- serde_json::json!(
- {
- "agent": {
- "name": "Minecraft",
- "version": 1
- },
- "username": username,
- "password": password,
- "clientToken": Uuid::new_v4(),
- "requestUser": request_user
- }
+ client
+ .post("https://authserver.mojang.com/authenticate")
+ .header(reqwest::header::CONTENT_TYPE, "application/json")
+ .body(
+ serde_json::json!(
+ {
+ "agent": {
+ "name": "Minecraft",
+ "version": 1
+ },
+ "username": username,
+ "password": password,
+ "clientToken": Uuid::new_v4(),
+ "requestUser": request_user
+ }
+ )
+ .to_string(),
)
- .to_string(),
- )
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap()
-}
+ .send()
+ .await?
+ .json()
+ .await
+ }
-pub async fn sign_out(username: &str, password: &str) {
- let client = reqwest::Client::new();
+ pub async fn sign_out(username: &str, password: &str) -> Result<(), reqwest::Error> {
+ let client = reqwest::Client::new();
- client
- .post("https://authserver.mojang.com/signout")
- .header(reqwest::header::CONTENT_TYPE, "application/json")
- .body(
- serde_json::json!(
- {
- "username": username,
- "password": password
- }
+ client
+ .post("https://authserver.mojang.com/signout")
+ .header(reqwest::header::CONTENT_TYPE, "application/json")
+ .body(
+ serde_json::json!(
+ {
+ "username": username,
+ "password": password
+ }
+ )
+ .to_string(),
)
- .to_string(),
- )
- .send()
- .await
- .unwrap();
-}
+ .send()
+ .await?;
-pub async fn validate(access_token: &str, client_token: &str) {
- let client = reqwest::Client::new();
+ Ok(())
+ }
- client
- .post("https://authserver.mojang.com/validate")
- .header(reqwest::header::CONTENT_TYPE, "application/json")
- .body(
- serde_json::json!(
- {
- "accessToken": access_token,
- "clientToken": client_token
- }
+ pub async fn validate(access_token: &str, client_token: &str) -> Result<(), reqwest::Error> {
+ let client = reqwest::Client::new();
+
+ client
+ .post("https://authserver.mojang.com/validate")
+ .header(reqwest::header::CONTENT_TYPE, "application/json")
+ .body(
+ serde_json::json!(
+ {
+ "accessToken": access_token,
+ "clientToken": client_token
+ }
+ )
+ .to_string(),
)
- .to_string(),
- )
- .send()
- .await
- .unwrap();
-}
+ .send()
+ .await?;
-pub async fn invalidate(access_token: &str, client_token: &str) {
- let client = reqwest::Client::new();
+ Ok(())
+ }
- client
- .post("https://authserver.mojang.com/invalidate")
- .header(reqwest::header::CONTENT_TYPE, "application/json")
- .body(
- serde_json::json!(
- {
- "accessToken": access_token,
- "clientToken": client_token
- }
+ pub async fn invalidate(access_token: &str, client_token: &str) -> Result<(), reqwest::Error> {
+ let client = reqwest::Client::new();
+
+ client
+ .post("https://authserver.mojang.com/invalidate")
+ .header(reqwest::header::CONTENT_TYPE, "application/json")
+ .body(
+ serde_json::json!(
+ {
+ "accessToken": access_token,
+ "clientToken": client_token
+ }
+ )
+ .to_string(),
)
- .to_string(),
- )
- .send()
- .await
- .unwrap();
-}
+ .send()
+ .await?;
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct RefreshResponse {
- pub user: Option,
- pub client_token: Uuid,
- pub access_token: String,
- pub selected_profile: Option,
-}
+ Ok(())
+ }
-pub async fn refresh(
- access_token: &str,
- client_token: &str,
- selected_profile: &GameProfile,
- request_user: bool,
-) -> RefreshResponse {
- let client = reqwest::Client::new();
+ #[derive(Debug, Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct RefreshResponse {
+ pub user: Option,
+ pub client_token: Uuid,
+ pub access_token: String,
+ pub selected_profile: Option,
+ }
- client
- .post("https://authserver.mojang.com/refresh")
- .header(reqwest::header::CONTENT_TYPE, "application/json")
- .body(
- serde_json::json!(
- {
- "accessToken": access_token,
- "clientToken": client_token,
- "selectedProfile": {
- "id": selected_profile.id,
- "name": selected_profile.name,
- },
- "requestUser": request_user,
- }
+ pub async fn refresh(
+ access_token: &str,
+ client_token: &str,
+ selected_profile: &GameProfile,
+ request_user: bool,
+ ) -> Result {
+ let client = reqwest::Client::new();
+
+ client
+ .post("https://authserver.mojang.com/refresh")
+ .header(reqwest::header::CONTENT_TYPE, "application/json")
+ .body(
+ serde_json::json!(
+ {
+ "accessToken": access_token,
+ "clientToken": client_token,
+ "selectedProfile": {
+ "id": selected_profile.id,
+ "name": selected_profile.name,
+ },
+ "requestUser": request_user,
+ }
+ )
+ .to_string(),
)
- .to_string(),
- )
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap()
+ .send()
+ .await?
+ .json()
+ .await
+ }
+}
+
+pub mod provider {
+ use uuid::Uuid;
+
+ #[derive(Debug)]
+ pub struct Credentials {
+ pub id: Uuid,
+ pub username: String,
+ pub access_token: String,
+ }
}
diff --git a/theseus/src/launcher/download.rs b/theseus/src/launcher/download.rs
index 488f173be..b26007a45 100644
--- a/theseus/src/launcher/download.rs
+++ b/theseus/src/launcher/download.rs
@@ -1,106 +1,178 @@
use crate::launcher::meta::{
- Asset, AssetIndex, AssetsIndex, DownloadType, Library, Os, OsRule, RuleAction, VersionInfo,
+ fetch_assets_index, fetch_version_info, Asset, AssetsIndex, DownloadType, Library, Os, Version,
+ VersionInfo,
};
+use crate::launcher::LauncherError;
use futures::future;
-use regex::Regex;
-use reqwest::{Error, Response};
use std::fs::File;
use std::io::{BufReader, Write};
use std::path::Path;
-pub async fn download_client(client_path: &Path, version_info: &VersionInfo) {
- let client = download_file(
- &version_info
- .downloads
- .get(&DownloadType::Client)
- .unwrap()
- .url,
- )
- .await;
+pub async fn download_version_info(
+ client_path: &Path,
+ version: &Version,
+) -> Result {
+ let path = &*client_path
+ .join(&version.id)
+ .join(format!("{}.json", &version.id));
- save_file(
- &*client_path
- .join(&version_info.id)
- .join(format!("{}.jar", &version_info.id)),
- &client,
- );
- save_file(
- &*client_path
- .join(&version_info.id)
- .join(format!("{}.json", &version_info.id)),
- &bytes::Bytes::from(serde_json::to_string(version_info).unwrap()),
- );
+ if path.exists() {
+ Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
+ } else {
+ let info = fetch_version_info(version)
+ .await
+ .map_err(|err| LauncherError::FetchError {
+ inner: err,
+ item: "version info".to_string(),
+ })?;
+
+ save_file(path, &bytes::Bytes::from(serde_json::to_string(&info)?))?;
+
+ Ok(info)
+ }
+}
+
+pub async fn download_client(
+ client_path: &Path,
+ version_info: &VersionInfo,
+) -> Result<(), LauncherError> {
+ let client_download = version_info
+ .downloads
+ .get(&DownloadType::Client)
+ .ok_or_else(|| {
+ LauncherError::InvalidInput(format!(
+ "Version {} does not have any client downloads",
+ &version_info.id
+ ))
+ })?;
+
+ let path = &*client_path
+ .join(&version_info.id)
+ .join(format!("{}.jar", &version_info.id));
+
+ save_and_download_file(path, &client_download.url, &client_download.sha1).await?;
+
+ Ok(())
+}
+
+pub async fn download_assets_index(
+ assets_path: &Path,
+ version: &VersionInfo,
+) -> Result {
+ let path = &*assets_path
+ .join("indexes")
+ .join(format!("{}.json", &version.asset_index.id));
+
+ if path.exists() {
+ Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
+ } else {
+ let index = fetch_assets_index(version)
+ .await
+ .map_err(|err| LauncherError::FetchError {
+ inner: err,
+ item: "assets index".to_string(),
+ })?;
+
+ save_file(path, &bytes::Bytes::from(serde_json::to_string(&index)?))?;
+
+ Ok(index)
+ }
}
pub async fn download_assets(
assets_path: &Path,
legacy_path: Option<&Path>,
- meta: &AssetIndex,
index: &AssetsIndex,
-) {
- save_file(
- &*assets_path
- .join("indexes")
- .join(format!("{}.json", meta.id)),
- &bytes::Bytes::from(serde_json::to_string(index).unwrap()),
- );
-
+) -> Result<(), LauncherError> {
future::join_all(
index
.objects
.iter()
.map(|x| download_asset(assets_path, legacy_path, x.0, x.1)),
)
- .await;
+ .await
+ .into_iter()
+ .collect::, LauncherError>>()?;
+
+ Ok(())
}
async fn download_asset(
assets_path: &Path,
legacy_path: Option<&Path>,
- name: &String,
+ name: &str,
asset: &Asset,
-) {
+) -> Result<(), LauncherError> {
let sub_hash = &&asset.hash[..2];
- let resource = download_file(&format!(
- "https://resources.download.minecraft.net/{}/{}",
- sub_hash, asset.hash
- ))
- .await;
-
let resource_path = assets_path.join("objects").join(sub_hash).join(&asset.hash);
- save_file(resource_path.as_path(), &resource);
+
+ let resource = save_and_download_file(
+ &*resource_path,
+ &format!(
+ "https://resources.download.minecraft.net/{}/{}",
+ sub_hash, asset.hash
+ ),
+ &*asset.hash,
+ )
+ .await?;
if let Some(legacy_path) = legacy_path {
let resource_path =
legacy_path.join(name.replace('/', &*std::path::MAIN_SEPARATOR.to_string()));
- save_file(resource_path.as_path(), &resource);
+ save_file(resource_path.as_path(), &resource)?;
}
+
+ Ok(())
}
-pub async fn download_libraries(libraries_path: &Path, natives_path: &Path, libraries: &[Library]) {
+pub async fn download_libraries(
+ libraries_path: &Path,
+ natives_path: &Path,
+ libraries: &[Library],
+) -> Result<(), LauncherError> {
future::join_all(
libraries
.iter()
.map(|x| download_library(libraries_path, natives_path, x)),
)
- .await;
+ .await
+ .into_iter()
+ .collect::, LauncherError>>()?;
+
+ Ok(())
}
-async fn download_library(libraries_path: &Path, natives_path: &Path, library: &Library) {
+async fn download_library(
+ libraries_path: &Path,
+ natives_path: &Path,
+ library: &Library,
+) -> Result<(), LauncherError> {
if let Some(rules) = &library.rules {
if !super::rules::parse_rules(rules.as_slice()) {
- return;
+ return Ok(());
}
}
let name_items = library.name.split(':').collect::>();
- let package = name_items.get(0).unwrap();
- let name = name_items.get(1).unwrap();
- let version = name_items.get(2).unwrap();
+ 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
+ ))
+ })?;
- future::join(
+ let (a, b) = future::join(
download_library_jar(libraries_path, library, package, name, version),
download_native(
libraries_path,
@@ -112,6 +184,11 @@ async fn download_library(libraries_path: &Path, natives_path: &Path, library: &
),
)
.await;
+
+ a?;
+ b?;
+
+ Ok(())
}
async fn download_library_jar(
@@ -120,13 +197,11 @@ async fn download_library_jar(
package: &str,
name: &str,
version: &str,
-) {
+) -> Result<(), LauncherError> {
if let Some(library) = &library.downloads.artifact {
- let bytes = download_file(&library.url).await;
-
let mut path = libraries_path.to_path_buf();
- for directory in package.split(".") {
+ for directory in package.split('.') {
path.push(directory);
}
@@ -134,8 +209,10 @@ async fn download_library_jar(
path.push(version);
path.push(format!("{}-{}.jar", name, version));
- save_file(&path, &bytes);
+ save_and_download_file(&*path, &library.url, &library.sha1).await?;
}
+
+ Ok(())
}
async fn download_native(
@@ -145,7 +222,7 @@ async fn download_native(
package: &str,
name: &str,
version: &str,
-) {
+) -> Result<(), LauncherError> {
if let Some(natives) = &library.natives {
if let Some(os_key) = natives.get(&get_os()) {
if let Some(classifiers) = &library.downloads.classifiers {
@@ -157,7 +234,7 @@ async fn download_native(
if let Some(native) = classifiers.get(&*parsed_key) {
let mut path = libraries_path.to_path_buf();
- for directory in package.split(".") {
+ for directory in package.split('.') {
path.push(directory);
}
@@ -165,9 +242,7 @@ async fn download_native(
path.push(version);
path.push(format!("{}-{}-{}.jar", name, version, parsed_key));
- let bytes = download_file(&native.url).await;
-
- save_file(&path, &bytes);
+ save_and_download_file(&*path, &native.url, &native.sha1).await?;
let file = File::open(&path).unwrap();
let reader = BufReader::new(file);
@@ -178,34 +253,99 @@ async fn download_native(
}
}
}
+
+ Ok(())
}
-fn save_file(path: &Path, bytes: &bytes::Bytes) {
- std::fs::create_dir_all(path.parent().unwrap()).unwrap();
- let mut file = File::create(path).unwrap();
- file.write_all(bytes).unwrap();
+async fn save_and_download_file(
+ path: &Path,
+ url: &str,
+ sha1: &str,
+) -> Result {
+ let read = std::fs::read(path).ok().map(bytes::Bytes::from);
+
+ if let Some(bytes) = read {
+ Ok(bytes)
+ } else {
+ let file = download_file(url, Some(sha1)).await?;
+
+ save_file(path, &file)?;
+
+ Ok(file)
+ }
}
-async fn download_file(url: &str) -> bytes::Bytes {
+fn save_file(path: &Path, bytes: &bytes::Bytes) -> Result<(), std::io::Error> {
+ if let Some(parent) = path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+
+ let mut file = File::create(path)?;
+ file.write_all(bytes)?;
+
+ Ok(())
+}
+
+async fn download_file(url: &str, sha1: Option<&str>) -> Result {
let client = reqwest::Client::builder()
- .pool_max_idle_per_host(0)
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
.build()
- .unwrap();
+ .map_err(|err| LauncherError::FetchError {
+ inner: err,
+ item: url.to_string(),
+ })?;
for attempt in 1..4 {
let result = client.get(url).send().await;
match result {
- Ok(x) => return x.bytes().await.unwrap(),
- Err(e) if attempt <= 3 => continue,
- Err(e) => panic!(e),
+ Ok(x) => {
+ let bytes = x.bytes().await;
+
+ if let Ok(bytes) = bytes {
+ if let Some(sha1) = sha1 {
+ if &*get_hash(bytes.clone()).await? != sha1 {
+ if attempt <= 3 {
+ continue;
+ } else {
+ return Err(LauncherError::ChecksumFailure {
+ hash: sha1.to_string(),
+ url: url.to_string(),
+ tries: attempt,
+ });
+ }
+ }
+ }
+
+ return Ok(bytes);
+ } else if attempt <= 3 {
+ continue;
+ } else if let Err(err) = bytes {
+ return Err(LauncherError::FetchError {
+ inner: err,
+ item: url.to_string(),
+ });
+ }
+ }
+ Err(_) if attempt <= 3 => continue,
+ Err(err) => {
+ return Err(LauncherError::FetchError {
+ inner: err,
+ item: url.to_string(),
+ })
+ }
}
}
unreachable!()
}
+async fn get_hash(bytes: bytes::Bytes) -> Result {
+ let hash = tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest()).await?;
+
+ Ok(hash)
+}
+
pub fn get_os() -> Os {
match std::env::consts::OS {
"windows" => Os::Windows,
diff --git a/theseus/src/launcher/java.rs b/theseus/src/launcher/java.rs
index addf274e0..8848d66bf 100644
--- a/theseus/src/launcher/java.rs
+++ b/theseus/src/launcher/java.rs
@@ -1,21 +1,20 @@
+use crate::launcher::LauncherError;
use lazy_static::lazy_static;
use regex::Regex;
use std::process::Command;
-#[derive(thiserror::Error, Debug)]
-pub enum JavaError {
- #[error("System Error")]
- SystemError(#[from] std::io::Error),
-}
-
lazy_static! {
static ref JAVA_VERSION_REGEX: Regex = Regex::new(r#""(.*?)""#).unwrap();
}
-pub fn check_java() -> Result