You've already forked AstralRinth
forked from didirus/AstralRinth
Add method of storing launcher data, fix forge 1.17+, add launcher settings
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -194,9 +194,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "daedalus"
|
name = "daedalus"
|
||||||
version = "0.1.7"
|
version = "0.1.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e8ea161abae801020f48a909a7174024945e924c2f16c27fdf9264422dcf1a6"
|
checksum = "b71fa5e7862c4cf3c86f6250f0254875838304eb3ee6aca95df2057fb3870805"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@ thiserror = "1.0"
|
|||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
once_cell = "1.9.0"
|
once_cell = "1.9.0"
|
||||||
|
|
||||||
daedalus = "0.1.6"
|
daedalus = "0.1.12"
|
||||||
|
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|||||||
@@ -1,27 +1,12 @@
|
|||||||
|
use crate::data::DataError;
|
||||||
|
use once_cell::sync;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io;
|
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||||
use tokio::sync::RwLockReadGuard;
|
|
||||||
|
|
||||||
const META_FILE: &str = "meta.json";
|
const META_FILE: &str = "meta.json";
|
||||||
const META_URL: &str = "https://staging-cdn.modrinth.com/gamedata";
|
const META_URL: &str = "https://meta.modrinth.com/gamedata";
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
static METADATA: sync::OnceCell<RwLock<Metadata>> = sync::OnceCell::new();
|
||||||
pub enum MetaError {
|
|
||||||
#[error("I/O error while reading metadata: {0}")]
|
|
||||||
IOError(#[from] io::Error),
|
|
||||||
|
|
||||||
#[error("Daedalus error: {0}")]
|
|
||||||
DaedalusError(#[from] daedalus::Error),
|
|
||||||
|
|
||||||
#[error("Attempted to access metadata without initializing it!")]
|
|
||||||
InitializedError,
|
|
||||||
|
|
||||||
#[error("Error while serializing/deserializing JSON")]
|
|
||||||
SerdeError(#[from] serde_json::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
use once_cell::sync;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
@@ -30,10 +15,8 @@ pub struct Metadata {
|
|||||||
pub fabric: daedalus::modded::Manifest,
|
pub fabric: daedalus::modded::Manifest,
|
||||||
}
|
}
|
||||||
|
|
||||||
static METADATA: sync::OnceCell<RwLock<Metadata>> = sync::OnceCell::new();
|
|
||||||
|
|
||||||
impl Metadata {
|
impl Metadata {
|
||||||
pub async fn init() -> Result<(), MetaError> {
|
pub async fn init() -> Result<(), DataError> {
|
||||||
let meta_path = crate::LAUNCHER_WORK_DIR.join(META_FILE);
|
let meta_path = crate::LAUNCHER_WORK_DIR.join(META_FILE);
|
||||||
|
|
||||||
if meta_path.exists() {
|
if meta_path.exists() {
|
||||||
@@ -63,7 +46,7 @@ impl Metadata {
|
|||||||
METADATA.get_or_init(|| RwLock::new(new));
|
METADATA.get_or_init(|| RwLock::new(new));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), MetaError>(())
|
Ok::<(), DataError>(())
|
||||||
}
|
}
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -75,7 +58,7 @@ impl Metadata {
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("Unable to fetch launcher metadata: {}", err)
|
log::warn!("Unable to fetch launcher metadata: {}", err)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,7 +71,7 @@ impl Metadata {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch() -> Result<Self, MetaError> {
|
pub async fn fetch() -> Result<Self, DataError> {
|
||||||
let (game, forge, fabric) = futures::future::join3(
|
let (game, forge, fabric) = futures::future::join3(
|
||||||
daedalus::minecraft::fetch_version_manifest(Some(&*format!(
|
daedalus::minecraft::fetch_version_manifest(Some(&*format!(
|
||||||
"{}/minecraft/v0/manifest.json",
|
"{}/minecraft/v0/manifest.json",
|
||||||
@@ -106,10 +89,10 @@ impl Metadata {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get<'a>() -> Result<RwLockReadGuard<'a, Self>, MetaError> {
|
pub async fn get<'a>() -> Result<RwLockReadGuard<'a, Self>, DataError> {
|
||||||
Ok(METADATA
|
Ok(METADATA
|
||||||
.get()
|
.get()
|
||||||
.ok_or_else(|| MetaError::InitializedError)?
|
.ok_or_else(|| DataError::InitializedError("metadata".to_string()))?
|
||||||
.read()
|
.read()
|
||||||
.await)
|
.await)
|
||||||
}
|
}
|
||||||
22
theseus/src/data/mod.rs
Normal file
22
theseus/src/data/mod.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
|
pub use meta::Metadata;
|
||||||
|
pub use settings::Settings;
|
||||||
|
|
||||||
|
mod meta;
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum DataError {
|
||||||
|
#[error("I/O error while reading data: {0}")]
|
||||||
|
IOError(#[from] io::Error),
|
||||||
|
|
||||||
|
#[error("Daedalus error: {0}")]
|
||||||
|
DaedalusError(#[from] daedalus::Error),
|
||||||
|
|
||||||
|
#[error("Attempted to access {0} without initializing it!")]
|
||||||
|
InitializedError(String),
|
||||||
|
|
||||||
|
#[error("Error while serializing/deserializing data")]
|
||||||
|
SerdeError(#[from] serde_json::Error),
|
||||||
|
}
|
||||||
96
theseus/src/data/settings.rs
Normal file
96
theseus/src/data/settings.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use crate::data::DataError;
|
||||||
|
use once_cell::sync;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||||
|
|
||||||
|
const SETTINGS_FILE: &str = "settings.json";
|
||||||
|
|
||||||
|
static SETTINGS: sync::OnceCell<RwLock<Settings>> = sync::OnceCell::new();
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub memory: i32,
|
||||||
|
pub game_resolution: (i32, i32),
|
||||||
|
pub custom_java_args: String,
|
||||||
|
pub java_8_path: Option<String>,
|
||||||
|
pub java_17_path: Option<String>,
|
||||||
|
pub wrapper_command: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
memory: 2048,
|
||||||
|
game_resolution: (854, 480),
|
||||||
|
custom_java_args: "".to_string(),
|
||||||
|
java_8_path: None,
|
||||||
|
java_17_path: None,
|
||||||
|
wrapper_command: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub async fn init() -> Result<(), DataError> {
|
||||||
|
let settings_path = crate::LAUNCHER_WORK_DIR.join(SETTINGS_FILE);
|
||||||
|
|
||||||
|
if settings_path.exists() {
|
||||||
|
let settings_data = std::fs::read_to_string(settings_path)
|
||||||
|
.map(|x| serde_json::from_str::<Settings>(&*x).ok())
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
if let Some(settings) = settings_data {
|
||||||
|
SETTINGS.get_or_init(|| RwLock::new(settings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if SETTINGS.get().is_none() {
|
||||||
|
let new = Self::default();
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
crate::LAUNCHER_WORK_DIR.join(SETTINGS_FILE),
|
||||||
|
&*serde_json::to_string(&new)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
SETTINGS.get_or_init(|| RwLock::new(new));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load() -> Result<(), DataError> {
|
||||||
|
let new = serde_json::from_str::<Settings>(&*std::fs::read_to_string(
|
||||||
|
crate::LAUNCHER_WORK_DIR.join(SETTINGS_FILE),
|
||||||
|
)?)?;
|
||||||
|
|
||||||
|
let write = &mut *SETTINGS
|
||||||
|
.get()
|
||||||
|
.ok_or_else(|| DataError::InitializedError("settings".to_string()))?
|
||||||
|
.write()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
*write = new;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn save() -> Result<(), DataError> {
|
||||||
|
let settings = Self::get().await?;
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
crate::LAUNCHER_WORK_DIR.join(SETTINGS_FILE),
|
||||||
|
&*serde_json::to_string(&*settings)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get<'a>() -> Result<RwLockReadGuard<'a, Self>, DataError> {
|
||||||
|
Ok(SETTINGS
|
||||||
|
.get()
|
||||||
|
.ok_or_else(|| DataError::InitializedError("settings".to_string()))?
|
||||||
|
.read()
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,13 @@ use std::io::{BufRead, BufReader};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
fn get_cp_separator() -> &'static str {
|
||||||
|
match super::download::get_os() {
|
||||||
|
Os::Osx | Os::Linux | Os::Unknown => ":",
|
||||||
|
Os::Windows => ";",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_class_paths(
|
pub fn get_class_paths(
|
||||||
libraries_path: &Path,
|
libraries_path: &Path,
|
||||||
libraries: &[Library],
|
libraries: &[Library],
|
||||||
@@ -42,10 +49,7 @@ pub fn get_class_paths(
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(class_paths.join(match super::download::get_os() {
|
Ok(class_paths.join(get_cp_separator()))
|
||||||
Os::Osx | Os::Linux | Os::Unknown => ":",
|
|
||||||
Os::Windows => ";",
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_class_paths_jar<T: AsRef<str>>(
|
pub fn get_class_paths_jar<T: AsRef<str>>(
|
||||||
@@ -90,13 +94,23 @@ pub fn get_lib_path<T: AsRef<str>>(libraries_path: &Path, lib: T) -> Result<Stri
|
|||||||
pub fn get_jvm_arguments(
|
pub fn get_jvm_arguments(
|
||||||
arguments: Option<&[Argument]>,
|
arguments: Option<&[Argument]>,
|
||||||
natives_path: &Path,
|
natives_path: &Path,
|
||||||
|
libraries_path: &Path,
|
||||||
class_paths: &str,
|
class_paths: &str,
|
||||||
|
version_name: &str,
|
||||||
|
memory: i32,
|
||||||
|
custom_args: Vec<String>,
|
||||||
) -> Result<Vec<String>, LauncherError> {
|
) -> Result<Vec<String>, LauncherError> {
|
||||||
let mut parsed_arguments = Vec::new();
|
let mut parsed_arguments = Vec::new();
|
||||||
|
|
||||||
if let Some(args) = arguments {
|
if let Some(args) = arguments {
|
||||||
parse_arguments(args, &mut parsed_arguments, |arg| {
|
parse_arguments(args, &mut parsed_arguments, |arg| {
|
||||||
parse_jvm_argument(arg, natives_path, class_paths)
|
parse_jvm_argument(
|
||||||
|
arg,
|
||||||
|
natives_path,
|
||||||
|
libraries_path,
|
||||||
|
class_paths,
|
||||||
|
version_name,
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
} else {
|
} else {
|
||||||
parsed_arguments.push(format!(
|
parsed_arguments.push(format!(
|
||||||
@@ -113,13 +127,22 @@ pub fn get_jvm_arguments(
|
|||||||
parsed_arguments.push(class_paths.to_string());
|
parsed_arguments.push(class_paths.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsed_arguments.push(format!("-Xmx{}M", memory));
|
||||||
|
for arg in custom_args {
|
||||||
|
if !arg.is_empty() {
|
||||||
|
parsed_arguments.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(parsed_arguments)
|
Ok(parsed_arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_jvm_argument(
|
fn parse_jvm_argument(
|
||||||
argument: &str,
|
argument: &str,
|
||||||
natives_path: &Path,
|
natives_path: &Path,
|
||||||
|
libraries_path: &Path,
|
||||||
class_paths: &str,
|
class_paths: &str,
|
||||||
|
version_name: &str,
|
||||||
) -> Result<String, LauncherError> {
|
) -> Result<String, LauncherError> {
|
||||||
let mut argument = argument.to_string();
|
let mut argument = argument.to_string();
|
||||||
argument.retain(|c| !c.is_whitespace());
|
argument.retain(|c| !c.is_whitespace());
|
||||||
@@ -136,8 +159,22 @@ fn parse_jvm_argument(
|
|||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
)
|
)
|
||||||
|
.replace(
|
||||||
|
"${library_directory}",
|
||||||
|
&*crate::util::absolute_path(libraries_path)
|
||||||
|
.map_err(|_| {
|
||||||
|
LauncherError::InvalidInput(format!(
|
||||||
|
"Specified libraries path {} does not exist",
|
||||||
|
libraries_path.to_string_lossy()
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.replace("${classpath_separator}", get_cp_separator())
|
||||||
.replace("${launcher_name}", "theseus")
|
.replace("${launcher_name}", "theseus")
|
||||||
.replace("${launcher_version}", env!("CARGO_PKG_VERSION"))
|
.replace("${launcher_version}", env!("CARGO_PKG_VERSION"))
|
||||||
|
.replace("${version_name}", version_name)
|
||||||
.replace("${classpath}", class_paths))
|
.replace("${classpath}", class_paths))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +188,7 @@ pub fn get_minecraft_arguments(
|
|||||||
game_directory: &Path,
|
game_directory: &Path,
|
||||||
assets_directory: &Path,
|
assets_directory: &Path,
|
||||||
version_type: &VersionType,
|
version_type: &VersionType,
|
||||||
|
resolution: (i32, i32),
|
||||||
) -> Result<Vec<String>, LauncherError> {
|
) -> Result<Vec<String>, LauncherError> {
|
||||||
if let Some(arguments) = arguments {
|
if let Some(arguments) = arguments {
|
||||||
let mut parsed_arguments = Vec::new();
|
let mut parsed_arguments = Vec::new();
|
||||||
@@ -166,6 +204,7 @@ pub fn get_minecraft_arguments(
|
|||||||
game_directory,
|
game_directory,
|
||||||
assets_directory,
|
assets_directory,
|
||||||
version_type,
|
version_type,
|
||||||
|
resolution,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -181,6 +220,7 @@ pub fn get_minecraft_arguments(
|
|||||||
game_directory,
|
game_directory,
|
||||||
assets_directory,
|
assets_directory,
|
||||||
version_type,
|
version_type,
|
||||||
|
resolution,
|
||||||
)?
|
)?
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -202,6 +242,7 @@ fn parse_minecraft_argument(
|
|||||||
game_directory: &Path,
|
game_directory: &Path,
|
||||||
assets_directory: &Path,
|
assets_directory: &Path,
|
||||||
version_type: &VersionType,
|
version_type: &VersionType,
|
||||||
|
resolution: (i32, i32),
|
||||||
) -> Result<String, LauncherError> {
|
) -> Result<String, LauncherError> {
|
||||||
Ok(argument
|
Ok(argument
|
||||||
.replace("${auth_access_token}", access_token)
|
.replace("${auth_access_token}", access_token)
|
||||||
@@ -248,7 +289,9 @@ fn parse_minecraft_argument(
|
|||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
)
|
)
|
||||||
.replace("${version_type}", version_type.as_str()))
|
.replace("${version_type}", version_type.as_str())
|
||||||
|
.replace("${resolution_width}", &*resolution.0.to_string())
|
||||||
|
.replace("${resolution_height}", &*resolution.1.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_arguments<F>(
|
fn parse_arguments<F>(
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ pub enum LauncherError {
|
|||||||
DaedalusError(#[from] daedalus::Error),
|
DaedalusError(#[from] daedalus::Error),
|
||||||
|
|
||||||
#[error("Error while reading metadata: {0}")]
|
#[error("Error while reading metadata: {0}")]
|
||||||
MetaError(#[from] crate::meta::MetaError),
|
MetaError(#[from] crate::data::DataError),
|
||||||
|
|
||||||
|
#[error("Java error: {0}")]
|
||||||
|
JavaError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||||
@@ -75,76 +78,92 @@ pub async fn launch_minecraft(
|
|||||||
root_dir: &Path,
|
root_dir: &Path,
|
||||||
credentials: &Credentials,
|
credentials: &Credentials,
|
||||||
) -> Result<(), LauncherError> {
|
) -> Result<(), LauncherError> {
|
||||||
let metadata = crate::meta::Metadata::get().await?;
|
let metadata = crate::data::Metadata::get().await?;
|
||||||
|
let settings = crate::data::Settings::get().await?;
|
||||||
|
|
||||||
let versions_path = crate::util::absolute_path(root_dir.join("versions"))?;
|
let versions_path = crate::util::absolute_path(root_dir.join("versions"))?;
|
||||||
let libraries_path = crate::util::absolute_path(root_dir.join("libraries"))?;
|
let libraries_path = crate::util::absolute_path(root_dir.join("libraries"))?;
|
||||||
let assets_path = crate::util::absolute_path(root_dir.join("assets"))?;
|
let assets_path = crate::util::absolute_path(root_dir.join("assets"))?;
|
||||||
let legacy_assets_path = crate::util::absolute_path(root_dir.join("resources"))?;
|
let legacy_assets_path = crate::util::absolute_path(root_dir.join("resources"))?;
|
||||||
|
|
||||||
|
let version = metadata
|
||||||
|
.minecraft
|
||||||
|
.versions
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.id == version_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
LauncherError::InvalidInput(format!("Version {} does not exist", version_name))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let loader_version = match mod_loader.unwrap_or_default() {
|
||||||
|
ModLoader::Vanilla => None,
|
||||||
|
ModLoader::Forge | ModLoader::Fabric => {
|
||||||
|
let loaders = if mod_loader.unwrap_or_default() == ModLoader::Forge {
|
||||||
|
&metadata
|
||||||
|
.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 {
|
||||||
|
&metadata
|
||||||
|
.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.iter().find(|x| x.stable) {
|
||||||
|
Some(version.clone())
|
||||||
|
} else {
|
||||||
|
loaders.first().cloned()
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(loader.ok_or_else(|| {
|
||||||
|
LauncherError::InvalidInput(format!(
|
||||||
|
"No mod loader version found for version {}",
|
||||||
|
version_name
|
||||||
|
))
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let version_jar_name = if let Some(loader) = &loader_version {
|
||||||
|
loader.id.clone()
|
||||||
|
} else {
|
||||||
|
version.id.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let mut version = download::download_version_info(
|
let mut version = download::download_version_info(
|
||||||
&versions_path,
|
&versions_path,
|
||||||
metadata
|
version,
|
||||||
.minecraft
|
loader_version.as_ref(),
|
||||||
.versions
|
|
||||||
.iter()
|
|
||||||
.find(|x| x.id == version_name)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
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 {
|
|
||||||
&metadata
|
|
||||||
.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 {
|
|
||||||
&metadata
|
|
||||||
.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?;
|
||||||
|
|
||||||
|
let java_path = if let Some(java) = &version.java_version {
|
||||||
|
if java.major_version == 17 || java.major_version == 16 {
|
||||||
|
settings.java_17_path.as_deref().ok_or_else(|| LauncherError::JavaError("Please install Java 17 or select your Java 17 installation settings before launching this version!".to_string()))?
|
||||||
|
} else {
|
||||||
|
&settings.java_8_path.as_deref().ok_or_else(|| LauncherError::JavaError("Please install Java 8 or select your Java 8 installation settings before launching this version!".to_string()))?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
&settings.java_8_path.as_deref().ok_or_else(|| LauncherError::JavaError("Please install Java 8 or select your Java 8 installation settings before launching this version!".to_string()))?
|
||||||
|
};
|
||||||
|
|
||||||
let client_path = crate::util::absolute_path(
|
let client_path = crate::util::absolute_path(
|
||||||
root_dir
|
root_dir
|
||||||
.join("versions")
|
.join("versions")
|
||||||
@@ -249,11 +268,30 @@ pub async fn launch_minecraft(
|
|||||||
|
|
||||||
let arguments = version.arguments.unwrap_or_default();
|
let arguments = version.arguments.unwrap_or_default();
|
||||||
|
|
||||||
let mut child = Command::new("java")
|
let mut command = Command::new(if let Some(wrapper) = &settings.wrapper_command {
|
||||||
|
wrapper.clone()
|
||||||
|
} else {
|
||||||
|
java_path.to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
if settings.wrapper_command.is_some() {
|
||||||
|
command.arg(java_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
command
|
||||||
.args(args::get_jvm_arguments(
|
.args(args::get_jvm_arguments(
|
||||||
arguments.get(&ArgumentType::Jvm).map(|x| x.as_slice()),
|
arguments.get(&ArgumentType::Jvm).map(|x| x.as_slice()),
|
||||||
&natives_path,
|
&natives_path,
|
||||||
|
&libraries_path,
|
||||||
&*args::get_class_paths(&libraries_path, version.libraries.as_slice(), &client_path)?,
|
&*args::get_class_paths(&libraries_path, version.libraries.as_slice(), &client_path)?,
|
||||||
|
&version_jar_name,
|
||||||
|
settings.memory,
|
||||||
|
settings
|
||||||
|
.custom_java_args
|
||||||
|
.split(" ")
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect(),
|
||||||
)?)
|
)?)
|
||||||
.arg(version.main_class)
|
.arg(version.main_class)
|
||||||
.args(args::get_minecraft_arguments(
|
.args(args::get_minecraft_arguments(
|
||||||
@@ -265,15 +303,16 @@ pub async fn launch_minecraft(
|
|||||||
root_dir,
|
root_dir,
|
||||||
&assets_path,
|
&assets_path,
|
||||||
&version.type_,
|
&version.type_,
|
||||||
|
settings.game_resolution,
|
||||||
)?)
|
)?)
|
||||||
.current_dir(root_dir)
|
.current_dir(root_dir)
|
||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit());
|
||||||
.spawn()
|
|
||||||
.map_err(|err| LauncherError::ProcessError {
|
let mut child = command.spawn().map_err(|err| LauncherError::ProcessError {
|
||||||
inner: err,
|
inner: err,
|
||||||
process: "minecraft".to_string(),
|
process: "minecraft".to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
child.wait().map_err(|err| LauncherError::ProcessError {
|
child.wait().map_err(|err| LauncherError::ProcessError {
|
||||||
inner: err,
|
inner: err,
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ 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 let Some(features) = &rule.features {
|
||||||
|
features.has_demo_resolution.unwrap_or(false)
|
||||||
} else {
|
} else {
|
||||||
rule.features.is_none()
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
match rule.action {
|
match rule.action {
|
||||||
|
|||||||
@@ -6,14 +6,13 @@
|
|||||||
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
|
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref LAUNCHER_WORK_DIR: &'static Path = Path::new("./launcher");
|
pub static ref LAUNCHER_WORK_DIR: &'static Path = Path::new("./launcher");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod data;
|
||||||
pub mod launcher;
|
pub mod launcher;
|
||||||
mod meta;
|
|
||||||
pub mod modpack;
|
pub mod modpack;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
@@ -25,13 +24,14 @@ pub enum Error {
|
|||||||
#[error("Modpack error: {0}")]
|
#[error("Modpack error: {0}")]
|
||||||
ModpackError(#[from] modpack::ModpackError),
|
ModpackError(#[from] modpack::ModpackError),
|
||||||
|
|
||||||
#[error("Meta error: {0}")]
|
#[error("Data error: {0}")]
|
||||||
DaedalusError(#[from] meta::MetaError),
|
DaedalusError(#[from] data::DataError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init() -> Result<(), Error> {
|
pub async fn init() -> Result<(), Error> {
|
||||||
std::fs::create_dir_all(*LAUNCHER_WORK_DIR).expect("Unable to create launcher root directory!");
|
std::fs::create_dir_all(*LAUNCHER_WORK_DIR).expect("Unable to create launcher root directory!");
|
||||||
crate::meta::Metadata::init().await?;
|
crate::data::Metadata::init().await?;
|
||||||
|
crate::data::Settings::init().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use crate::launcher::ModLoader;
|
|||||||
|
|
||||||
use super::pack::ModpackGame;
|
use super::pack::ModpackGame;
|
||||||
use super::{pack, ModpackError, ModpackResult};
|
use super::{pack, ModpackError, ModpackResult};
|
||||||
use daedalus::modded::LoaderType;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const DEFAULT_FORMAT_VERSION: u32 = 1;
|
pub const DEFAULT_FORMAT_VERSION: u32 = 1;
|
||||||
@@ -45,7 +44,7 @@ impl TryFrom<Manifest<'_>> for pack::Modpack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MODRINTH_GAMEDATA_URL: &'static str = "https://staging-cdn.modrinth.com/gamedata";
|
const MODRINTH_GAMEDATA_URL: &str = "https://staging-cdn.modrinth.com/gamedata";
|
||||||
fn get_loader_version(loader: ModLoader, version: &str) -> ModpackResult<String> {
|
fn get_loader_version(loader: ModLoader, version: &str) -> ModpackResult<String> {
|
||||||
let source = match loader {
|
let source = match loader {
|
||||||
ModLoader::Vanilla => Err(ModpackError::VersionError(String::from(
|
ModLoader::Vanilla => Err(ModpackError::VersionError(String::from(
|
||||||
@@ -60,11 +59,14 @@ fn get_loader_version(loader: ModLoader, version: &str) -> ModpackResult<String>
|
|||||||
.game_versions
|
.game_versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|&it| it.id == version)
|
.find(|&it| it.id == version)
|
||||||
.ok_or(ModpackError::VersionError(format!(
|
.map(|x| x.loaders.first())
|
||||||
"No versions of modloader {:?} exist for Minecraft {}",
|
.flatten()
|
||||||
loader, version
|
.ok_or_else(|| {
|
||||||
)))?
|
ModpackError::VersionError(format!(
|
||||||
.loaders[&LoaderType::Latest]
|
"No versions of modloader {:?} exist for Minecraft {}",
|
||||||
|
loader, version
|
||||||
|
))
|
||||||
|
})?
|
||||||
.id
|
.id
|
||||||
.clone())
|
.clone())
|
||||||
}
|
}
|
||||||
@@ -88,7 +90,7 @@ impl<'a> TryFrom<&'a pack::Modpack> for Manifest<'a> {
|
|||||||
game: game_field,
|
game: game_field,
|
||||||
version_id: &pack.version,
|
version_id: &pack.version,
|
||||||
name: &pack.name,
|
name: &pack.name,
|
||||||
summary: pack.summary.as_ref().map(String::as_str),
|
summary: pack.summary.as_deref(),
|
||||||
files,
|
files,
|
||||||
dependencies: ManifestDeps::try_from(&pack.game)?,
|
dependencies: ManifestDeps::try_from(&pack.game)?,
|
||||||
})
|
})
|
||||||
@@ -333,9 +335,9 @@ mod tests {
|
|||||||
summary: None,
|
summary: None,
|
||||||
files: vec![ManifestFile {
|
files: vec![ManifestFile {
|
||||||
path: Path::new("mods/testmod.jar"),
|
path: Path::new("mods/testmod.jar"),
|
||||||
hashes: ManifestHashes {
|
hashes: Some(ManifestHashes {
|
||||||
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
},
|
}),
|
||||||
env: ManifestEnvs::default(),
|
env: ManifestEnvs::default(),
|
||||||
downloads: vec!["https://example.com/testmod.jar"],
|
downloads: vec!["https://example.com/testmod.jar"],
|
||||||
}],
|
}],
|
||||||
@@ -383,9 +385,9 @@ mod tests {
|
|||||||
summary: None,
|
summary: None,
|
||||||
files: vec![ManifestFile {
|
files: vec![ManifestFile {
|
||||||
path: Path::new("mods/testmod.jar"),
|
path: Path::new("mods/testmod.jar"),
|
||||||
hashes: ManifestHashes {
|
hashes: Some(ManifestHashes {
|
||||||
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
},
|
}),
|
||||||
env: ManifestEnvs::default(),
|
env: ManifestEnvs::default(),
|
||||||
downloads: vec!["https://example.com/testmod.jar"],
|
downloads: vec!["https://example.com/testmod.jar"],
|
||||||
}],
|
}],
|
||||||
@@ -438,9 +440,9 @@ mod tests {
|
|||||||
summary: Some("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."),
|
summary: Some("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."),
|
||||||
files: vec![ManifestFile {
|
files: vec![ManifestFile {
|
||||||
path: Path::new("mods/testmod.jar"),
|
path: Path::new("mods/testmod.jar"),
|
||||||
hashes: ManifestHashes {
|
hashes: Some(ManifestHashes {
|
||||||
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
},
|
}),
|
||||||
env: ManifestEnvs {
|
env: ManifestEnvs {
|
||||||
client: ManifestEnv::Required,
|
client: ManifestEnv::Required,
|
||||||
server: ManifestEnv::Unsupported,
|
server: ManifestEnv::Unsupported,
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ pub mod manifest;
|
|||||||
pub mod modrinth_api;
|
pub mod modrinth_api;
|
||||||
pub mod pack;
|
pub mod pack;
|
||||||
|
|
||||||
pub const COMPILED_PATH: &'static str = "compiled/";
|
pub const COMPILED_PATH: &str = "compiled/";
|
||||||
pub const COMPILED_ZIP: &'static str = "compiled.mrpack";
|
pub const COMPILED_ZIP: &str = "compiled.mrpack";
|
||||||
pub const MANIFEST_PATH: &'static str = "modrinth.index.json";
|
pub const MANIFEST_PATH: &str = "modrinth.index.json";
|
||||||
pub const OVERRIDES_PATH: &'static str = "overrides/";
|
pub const OVERRIDES_PATH: &str = "overrides/";
|
||||||
pub const PACK_JSON5_PATH: &'static str = "modpack.json5";
|
pub const PACK_JSON5_PATH: &str = "modpack.json5";
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum ModpackError {
|
pub enum ModpackError {
|
||||||
@@ -121,9 +121,9 @@ pub async fn realise_modpack(
|
|||||||
// NOTE: I'm using standard files here, since Serde does not support async readers
|
// NOTE: I'm using standard files here, since Serde does not support async readers
|
||||||
let manifest_path = Some(dir.join(MANIFEST_PATH))
|
let manifest_path = Some(dir.join(MANIFEST_PATH))
|
||||||
.filter(|it| it.exists() && it.is_file())
|
.filter(|it| it.exists() && it.is_file())
|
||||||
.ok_or(ModpackError::ManifestError(String::from(
|
.ok_or_else(|| {
|
||||||
"Manifest missing or is not a file",
|
ModpackError::ManifestError(String::from("Manifest missing or is not a file"))
|
||||||
)))?;
|
})?;
|
||||||
let manifest_file = std::fs::File::open(manifest_path)?;
|
let manifest_file = std::fs::File::open(manifest_path)?;
|
||||||
let reader = io::BufReader::new(manifest_file);
|
let reader = io::BufReader::new(manifest_file);
|
||||||
let mut deserializer = serde_json::Deserializer::from_reader(reader);
|
let mut deserializer = serde_json::Deserializer::from_reader(reader);
|
||||||
@@ -169,7 +169,7 @@ pub async fn compile_modpack(dir: &Path) -> ModpackResult<()> {
|
|||||||
let result_dir = dir.join(COMPILED_PATH);
|
let result_dir = dir.join(COMPILED_PATH);
|
||||||
let pack: Modpack = json5::from_str(&fs::read_to_string(dir.join(PACK_JSON5_PATH)).await?)?;
|
let pack: Modpack = json5::from_str(&fs::read_to_string(dir.join(PACK_JSON5_PATH)).await?)?;
|
||||||
|
|
||||||
fs::create_dir(&result_dir).await;
|
fs::create_dir(&result_dir).await?;
|
||||||
if dir.join(OVERRIDES_PATH).exists() {
|
if dir.join(OVERRIDES_PATH).exists() {
|
||||||
fs_extra::dir::copy(
|
fs_extra::dir::copy(
|
||||||
dir.join(OVERRIDES_PATH),
|
dir.join(OVERRIDES_PATH),
|
||||||
|
|||||||
@@ -116,10 +116,12 @@ impl ModrinthAPI for ModrinthV1 {
|
|||||||
&& it.game_versions.contains(&game_version.as_str())
|
&& it.game_versions.contains(&game_version.as_str())
|
||||||
&& it.loaders.contains(&loader_str)
|
&& it.loaders.contains(&loader_str)
|
||||||
})
|
})
|
||||||
.ok_or(ModpackError::VersionError(format!(
|
.ok_or_else(|| {
|
||||||
"Unable to find compatible version of mod {}",
|
ModpackError::VersionError(format!(
|
||||||
project.title
|
"Unable to find compatible version of mod {}",
|
||||||
)))?;
|
project.title
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
// Project fields
|
// Project fields
|
||||||
let envs = ModpackEnv::try_from(ManifestEnvs {
|
let envs = ModpackEnv::try_from(ManifestEnvs {
|
||||||
@@ -135,7 +137,7 @@ impl ModrinthAPI for ModrinthV1 {
|
|||||||
.map(ModpackFile::from)
|
.map(ModpackFile::from)
|
||||||
.collect::<HashSet<ModpackFile>>();
|
.collect::<HashSet<ModpackFile>>();
|
||||||
|
|
||||||
let dep_futures = version.dependencies.iter().map(|it| self.get_version(&it));
|
let dep_futures = version.dependencies.iter().map(|it| self.get_version(it));
|
||||||
let deps = try_join_all(dep_futures)
|
let deps = try_join_all(dep_futures)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::launcher::ModLoader;
|
use crate::launcher::ModLoader;
|
||||||
|
|
||||||
pub const MODRINTH_DEFAULT_MODPACK_DOMAINS: &'static [&'static str] = &[
|
pub const MODRINTH_DEFAULT_MODPACK_DOMAINS: &[&str] = &[
|
||||||
"cdn.modrinth.com",
|
"cdn.modrinth.com",
|
||||||
"edge.forgecdn.net",
|
"edge.forgecdn.net",
|
||||||
"github.com",
|
"github.com",
|
||||||
"raw.githubusercontent.com",
|
"raw.githubusercontent.com",
|
||||||
];
|
];
|
||||||
pub const MODRINTH_MODPACK_DOMAIN_WHITELIST_VAR: &'static str = "WHITELISTED_MODPACK_DOMAINS";
|
pub const MODRINTH_MODPACK_DOMAIN_WHITELIST_VAR: &str = "WHITELISTED_MODPACK_DOMAINS";
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
pub struct Modpack {
|
pub struct Modpack {
|
||||||
@@ -114,18 +114,17 @@ impl Modpack {
|
|||||||
) -> ModpackResult<()> {
|
) -> ModpackResult<()> {
|
||||||
let whitelisted_domains = std::env::var(MODRINTH_MODPACK_DOMAIN_WHITELIST_VAR)
|
let whitelisted_domains = std::env::var(MODRINTH_MODPACK_DOMAIN_WHITELIST_VAR)
|
||||||
.map(|it| serde_json::from_str::<Vec<String>>(&it).ok().unwrap())
|
.map(|it| serde_json::from_str::<Vec<String>>(&it).ok().unwrap())
|
||||||
.unwrap_or(
|
.unwrap_or_else(|_| {
|
||||||
MODRINTH_DEFAULT_MODPACK_DOMAINS
|
MODRINTH_DEFAULT_MODPACK_DOMAINS
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(String::from)
|
.map(String::from)
|
||||||
.collect::<Vec<String>>(),
|
.collect::<Vec<String>>()
|
||||||
);
|
});
|
||||||
|
|
||||||
if whitelisted_domains
|
if !whitelisted_domains
|
||||||
.iter()
|
.iter()
|
||||||
.find(|it| it == &source.host_str().unwrap())
|
.any(|it| it == source.host_str().unwrap())
|
||||||
.is_none()
|
|
||||||
{
|
{
|
||||||
return Err(ModpackError::SourceWhitelistError(String::from(
|
return Err(ModpackError::SourceWhitelistError(String::from(
|
||||||
source.host_str().unwrap(),
|
source.host_str().unwrap(),
|
||||||
@@ -136,7 +135,7 @@ impl Modpack {
|
|||||||
path: PathBuf::from(dest),
|
path: PathBuf::from(dest),
|
||||||
hashes,
|
hashes,
|
||||||
env: env.unwrap_or(ModpackEnv::Both),
|
env: env.unwrap_or(ModpackEnv::Both),
|
||||||
downloads: HashSet::from_iter([String::from(source)].into_iter().cloned()),
|
downloads: HashSet::from_iter([String::from(source)].iter().cloned()),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.files.insert(file);
|
self.files.insert(file);
|
||||||
@@ -180,8 +179,7 @@ impl ModpackFile {
|
|||||||
// URLs, I'm supplying it with an empty string to avoid reinventing the wheel.
|
// URLs, I'm supplying it with an empty string to avoid reinventing the wheel.
|
||||||
let bytes = download_file_mirrors(
|
let bytes = download_file_mirrors(
|
||||||
"",
|
"",
|
||||||
&self
|
self.downloads
|
||||||
.downloads
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|it| it.as_str())
|
.map(|it| it.as_str())
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
use theseus::launcher::{Credentials, ModLoader};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {}
|
async fn main() {}
|
||||||
|
|||||||
Reference in New Issue
Block a user