Add error handling
This commit is contained in:
parent
a0e35ad853
commit
54cd2f873c
4
.idea/theseus.iml
generated
4
.idea/theseus.iml
generated
@ -42,6 +42,10 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/crc32fast-f112d4af2349618a/out" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/miniz_oxide-d683d4a97661215e/out" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/sys-info-0c720b463af8b14b/out" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/crossbeam-epoch-809784aaf7fd3933/out" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/crossbeam-queue-96bec372a2ebc7a5/out" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/crossbeam-utils-bab62be590a5955d/out" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/memoffset-235ac8b3550fb50a/out" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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<String, LauncherError> {
|
||||
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::<Vec<&str>>();
|
||||
|
||||
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<String> {
|
||||
) -> Result<Vec<String>, 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<String, LauncherError> {
|
||||
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<String> {
|
||||
) -> Result<Vec<String>, 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<String, LauncherError> {
|
||||
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<F>(arguments: &[Argument], parsed_arguments: &mut Vec<String>, parse_function: F)
|
||||
fn parse_arguments<F>(
|
||||
arguments: &[Argument],
|
||||
parsed_arguments: &mut Vec<String>,
|
||||
parse_function: F,
|
||||
) -> Result<(), LauncherError>
|
||||
where
|
||||
F: Fn(&str) -> String,
|
||||
F: Fn(&str) -> Result<String, LauncherError>,
|
||||
{
|
||||
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(())
|
||||
}
|
||||
|
||||
@ -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<Vec<UserProperty>>,
|
||||
}
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub properties: Option<Vec<UserProperty>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthenticateResponse {
|
||||
pub user: Option<User>,
|
||||
pub client_token: Uuid,
|
||||
pub access_token: String,
|
||||
pub available_profiles: Vec<GameProfile>,
|
||||
pub selected_profile: Option<GameProfile>,
|
||||
}
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthenticateResponse {
|
||||
pub user: Option<User>,
|
||||
pub client_token: Uuid,
|
||||
pub access_token: String,
|
||||
pub available_profiles: Vec<GameProfile>,
|
||||
pub selected_profile: Option<GameProfile>,
|
||||
}
|
||||
|
||||
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<AuthenticateResponse, reqwest::Error> {
|
||||
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<User>,
|
||||
pub client_token: Uuid,
|
||||
pub access_token: String,
|
||||
pub selected_profile: Option<GameProfile>,
|
||||
}
|
||||
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<User>,
|
||||
pub client_token: Uuid,
|
||||
pub access_token: String,
|
||||
pub selected_profile: Option<GameProfile>,
|
||||
}
|
||||
|
||||
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<RefreshResponse, reqwest::Error> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<VersionInfo, LauncherError> {
|
||||
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<AssetsIndex, LauncherError> {
|
||||
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::<Result<Vec<()>, 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::<Result<Vec<()>, 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::<Vec<&str>>();
|
||||
|
||||
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<bytes::Bytes, LauncherError> {
|
||||
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<bytes::Bytes, LauncherError> {
|
||||
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<String, LauncherError> {
|
||||
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,
|
||||
|
||||
@ -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<Option<String>, JavaError> {
|
||||
let child = Command::new("/usr/lib/jvm/java-8-openjdk/jre/bin/java")
|
||||
pub fn check_java() -> Result<Option<String>, LauncherError> {
|
||||
let child = Command::new("java")
|
||||
.arg("-version")
|
||||
.output()?;
|
||||
.output()
|
||||
.map_err(|err| LauncherError::ProcessError {
|
||||
inner: err,
|
||||
process: "java".to_string(),
|
||||
})?;
|
||||
|
||||
let output = &*String::from_utf8_lossy(&*child.stderr);
|
||||
|
||||
|
||||
@ -119,14 +119,14 @@ pub struct OsRule {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct FeatureRule {
|
||||
pub is_demo_user: Option<bool>,
|
||||
pub has_custom_resolution: Option<bool>,
|
||||
pub has_demo_resolution: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Rule {
|
||||
pub action: RuleAction,
|
||||
pub os: Option<OsRule>,
|
||||
pub feature: Option<FeatureRule>,
|
||||
pub features: Option<FeatureRule>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
||||
@ -1,34 +1,65 @@
|
||||
use crate::launcher::auth::provider::Credentials;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
use thiserror::Error;
|
||||
|
||||
mod args;
|
||||
pub mod args;
|
||||
pub mod auth;
|
||||
pub mod download;
|
||||
pub mod java;
|
||||
pub mod meta;
|
||||
mod rules;
|
||||
pub mod rules;
|
||||
|
||||
pub async fn launch_minecraft(version_name: &str, root_dir: &Path) {
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LauncherError {
|
||||
#[error("Failed to violate file checksum at url {url} with hash {hash} after {tries} tries")]
|
||||
ChecksumFailure {
|
||||
hash: String,
|
||||
url: String,
|
||||
tries: u32,
|
||||
},
|
||||
#[error("Invalid input: {0}")]
|
||||
InvalidInput(String),
|
||||
#[error("Error while managing asynchronous tasks")]
|
||||
TaskError(#[from] tokio::task::JoinError),
|
||||
#[error("Error while reading/writing to the disk")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Error while spawning child process {process}")]
|
||||
ProcessError {
|
||||
inner: std::io::Error,
|
||||
process: String,
|
||||
},
|
||||
#[error("Error while deserializing JSON")]
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
#[error("Unable to fetch {item}")]
|
||||
FetchError { inner: reqwest::Error, item: String },
|
||||
#[error("{0}")]
|
||||
ParseError(String),
|
||||
}
|
||||
|
||||
pub async fn launch_minecraft(
|
||||
version_name: &str,
|
||||
root_dir: &Path,
|
||||
credentials: &Credentials,
|
||||
) -> Result<(), LauncherError> {
|
||||
let manifest = meta::fetch_version_manifest().await.unwrap();
|
||||
|
||||
let version = meta::fetch_version_info(
|
||||
let version = download::download_version_info(
|
||||
&*root_dir.join("versions"),
|
||||
manifest
|
||||
.versions
|
||||
.iter()
|
||||
.find(|x| x.id == version_name)
|
||||
.unwrap(),
|
||||
.ok_or_else(|| {
|
||||
LauncherError::InvalidInput(format!("Version {} does not exist", version_name))
|
||||
})?,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
|
||||
//download_minecraft(&version, root_dir).await;
|
||||
|
||||
let auth = auth::login("username", "password", true).await;
|
||||
download_minecraft(&version, root_dir).await?;
|
||||
|
||||
let arguments = version.arguments.unwrap();
|
||||
|
||||
let profile = auth.selected_profile.unwrap();
|
||||
|
||||
let mut child = Command::new("java")
|
||||
.args(args::get_jvm_arguments(
|
||||
arguments
|
||||
@ -42,38 +73,47 @@ pub async fn launch_minecraft(version_name: &str, root_dir: &Path) {
|
||||
.join("versions")
|
||||
.join(&version.id)
|
||||
.join(format!("{}.jar", &version.id)),
|
||||
),
|
||||
))
|
||||
)?,
|
||||
)?)
|
||||
.arg(version.main_class)
|
||||
.args(args::get_minecraft_arguments(
|
||||
arguments
|
||||
.get(&meta::ArgumentType::Game)
|
||||
.map(|x| x.as_slice()),
|
||||
version.minecraft_arguments.as_deref(),
|
||||
&*auth.access_token,
|
||||
&*profile.name,
|
||||
&profile.id,
|
||||
credentials,
|
||||
&*version.id,
|
||||
&version.asset_index.id,
|
||||
root_dir,
|
||||
&*root_dir.join("assets"),
|
||||
&version.type_,
|
||||
))
|
||||
)?)
|
||||
.current_dir(root_dir)
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
.map_err(|err| LauncherError::ProcessError {
|
||||
inner: err,
|
||||
process: "minecraft".to_string(),
|
||||
})?;
|
||||
|
||||
child.wait().unwrap();
|
||||
child.wait().map_err(|err| LauncherError::ProcessError {
|
||||
inner: err,
|
||||
process: "minecraft".to_string(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn download_minecraft(version: &meta::VersionInfo, root_dir: &Path) {
|
||||
let assets_dir = meta::fetch_assets_index(&version).await.unwrap();
|
||||
pub async fn download_minecraft(
|
||||
version: &meta::VersionInfo,
|
||||
root_dir: &Path,
|
||||
) -> Result<(), LauncherError> {
|
||||
let assets_index = download::download_assets_index(&*root_dir.join("assets"), &version).await?;
|
||||
|
||||
let legacy_dir = root_dir.join("resources");
|
||||
|
||||
futures::future::join3(
|
||||
let (a, b, c) = futures::future::join3(
|
||||
download::download_client(&*root_dir.join("versions"), &version),
|
||||
download::download_assets(
|
||||
&*root_dir.join("assets"),
|
||||
@ -82,8 +122,7 @@ pub async fn download_minecraft(version: &meta::VersionInfo, root_dir: &Path) {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
&version.asset_index,
|
||||
&assets_dir,
|
||||
&assets_index,
|
||||
),
|
||||
download::download_libraries(
|
||||
&*root_dir.join("libraries"),
|
||||
@ -92,4 +131,10 @@ pub async fn download_minecraft(version: &meta::VersionInfo, root_dir: &Path) {
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
a?;
|
||||
b?;
|
||||
c?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -9,16 +9,16 @@ pub fn parse_rules(rules: &[Rule]) -> bool {
|
||||
pub fn parse_rule(rule: &Rule) -> bool {
|
||||
let result = if let Some(os) = &rule.os {
|
||||
parse_os_rule(os)
|
||||
} else if let Some(feature) = &rule.feature {
|
||||
} else if rule.features.is_some() {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
return match rule.action {
|
||||
match rule.action {
|
||||
RuleAction::Allow => result,
|
||||
RuleAction::Disallow => !result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_os_rule(rule: &OsRule) -> bool {
|
||||
@ -42,9 +42,12 @@ pub fn parse_os_rule(rule: &OsRule) -> bool {
|
||||
}
|
||||
}
|
||||
if let Some(version) = &rule.version {
|
||||
let regex = Regex::new(version.as_str()).unwrap();
|
||||
if !regex.is_match(&*sys_info::os_release().unwrap()) {
|
||||
return false;
|
||||
let regex = Regex::new(version.as_str());
|
||||
|
||||
if let Ok(regex) = regex {
|
||||
if !regex.is_match(&*sys_info::os_release().unwrap_or_default()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
//! # Theseus
|
||||
//!
|
||||
//! Theseus is a library which provides utilities for launching minecraft, creating Modrinth mod packs,
|
||||
//! and launching Modrinth mod packs
|
||||
|
||||
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
|
||||
|
||||
pub mod launcher;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,28 +1,2 @@
|
||||
use futures::{executor, future};
|
||||
use std::path::Path;
|
||||
use theseus::launcher::launch_minecraft;
|
||||
use theseus::launcher::meta::ArgumentType;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
launch_minecraft("1.15.2", &Path::new("./test")).await;
|
||||
|
||||
// let mut thing1 = theseus::launcher::meta::fetch_version_manifest()
|
||||
// .await
|
||||
// .unwrap();
|
||||
//
|
||||
// future::join_all(thing1.versions.iter().map(|x| async move {
|
||||
// //println!("{}", x.url);
|
||||
// let version = theseus::launcher::meta::fetch_version_info(x)
|
||||
// .await
|
||||
// .unwrap();
|
||||
//
|
||||
// if let Some(args) = &version.minecraft_arguments {
|
||||
// println!("{:?}", args);
|
||||
// }
|
||||
// if let Some(args) = &version.arguments {
|
||||
// println!("{:?}", args.get(&ArgumentType::Game).unwrap());
|
||||
// }
|
||||
// }))
|
||||
// .await;
|
||||
}
|
||||
async fn main() {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user