the first wave of refactors
This commit is contained in:
parent
9d74e84c01
commit
14e8e92f46
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -167,6 +167,26 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const_format"
|
||||||
|
version = "0.2.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22bc6cd49b0ec407b680c3e380182b6ac63b73991cb7602de350352fc309b614"
|
||||||
|
dependencies = [
|
||||||
|
"const_format_proc_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const_format_proc_macros"
|
||||||
|
version = "0.2.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
@ -1165,13 +1185,12 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"const_format",
|
||||||
"daedalus",
|
"daedalus",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
"futures",
|
"futures",
|
||||||
"json5",
|
"json5",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
|
||||||
"path-clean",
|
"path-clean",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|||||||
@ -9,7 +9,6 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
once_cell = "1.9.0"
|
|
||||||
|
|
||||||
daedalus = "0.1.12"
|
daedalus = "0.1.12"
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ path-clean = "0.1.0"
|
|||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
|
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
lazy_static = "1.4"
|
|
||||||
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
@ -35,9 +33,12 @@ futures = "0.3"
|
|||||||
sys-info = "0.9.0"
|
sys-info = "0.9.0"
|
||||||
|
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
|
const_format = "0.2.22"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
argh = "0.1.6"
|
argh = "0.1.6"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "download-pack"
|
name = "download-pack"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
|||||||
@ -9,30 +9,35 @@ use futures::future;
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub async fn download_version_info(
|
pub async fn download_version_info(
|
||||||
client_path: &Path,
|
client_path: &Path,
|
||||||
version: &Version,
|
version: &Version,
|
||||||
loader_version: Option<&LoaderVersion>,
|
loader_version: Option<&LoaderVersion>,
|
||||||
) -> Result<VersionInfo, LauncherError> {
|
) -> Result<VersionInfo, LauncherError> {
|
||||||
let id = loader_version.map(|x| &x.id).unwrap_or(&version.id);
|
let id = match loader_version {
|
||||||
|
Some(x) => &x.id,
|
||||||
|
None => &version.id,
|
||||||
|
};
|
||||||
|
|
||||||
let path = &*client_path.join(id).join(format!("{}.json", id));
|
let mut path = client_path.join(id);
|
||||||
|
path.push(id);
|
||||||
|
path.set_extension("json");
|
||||||
|
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
let contents = std::fs::read_to_string(path)?;
|
||||||
|
Ok(serde_json::from_str(&contents)?)
|
||||||
} else {
|
} else {
|
||||||
let mut info = fetch_version_info(version).await?;
|
let mut info = fetch_version_info(version).await?;
|
||||||
|
|
||||||
if let Some(loader_version) = loader_version {
|
if let Some(loader_version) = loader_version {
|
||||||
let partial = fetch_partial_version(&*loader_version.url).await?;
|
let partial = fetch_partial_version(&loader_version.url).await?;
|
||||||
|
|
||||||
info = merge_partial_version(partial, info);
|
info = merge_partial_version(partial, info);
|
||||||
|
|
||||||
info.id = loader_version.id.clone();
|
info.id = loader_version.id.clone();
|
||||||
}
|
}
|
||||||
|
let info_s = serde_json::to_string(&info)?;
|
||||||
save_file(path, &bytes::Bytes::from(serde_json::to_string(&info)?))?;
|
save_file(&path, &bytes::Bytes::from(info_s))?;
|
||||||
|
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
@ -42,22 +47,21 @@ pub async fn download_client(
|
|||||||
client_path: &Path,
|
client_path: &Path,
|
||||||
version_info: &VersionInfo,
|
version_info: &VersionInfo,
|
||||||
) -> Result<(), LauncherError> {
|
) -> Result<(), LauncherError> {
|
||||||
|
let version = &version_info.id;
|
||||||
let client_download = version_info
|
let client_download = version_info
|
||||||
.downloads
|
.downloads
|
||||||
.get(&DownloadType::Client)
|
.get(&DownloadType::Client)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
LauncherError::InvalidInput(format!(
|
LauncherError::InvalidInput(format!(
|
||||||
"Version {} does not have any client downloads",
|
"Version {version} does not have any client downloads"
|
||||||
&version_info.id
|
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let path = &*client_path
|
let mut path = client_path.join(version);
|
||||||
.join(&version_info.id)
|
path.push(version);
|
||||||
.join(format!("{}.jar", &version_info.id));
|
path.set_extension("jar");
|
||||||
|
|
||||||
save_and_download_file(path, &client_download.url, Some(&client_download.sha1)).await?;
|
|
||||||
|
|
||||||
|
save_and_download_file(&path, &client_download.url, Some(&client_download.sha1)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,16 +69,15 @@ pub async fn download_assets_index(
|
|||||||
assets_path: &Path,
|
assets_path: &Path,
|
||||||
version: &VersionInfo,
|
version: &VersionInfo,
|
||||||
) -> Result<AssetsIndex, LauncherError> {
|
) -> Result<AssetsIndex, LauncherError> {
|
||||||
let path = &*assets_path
|
let path = assets_path.join(format!("indexes/{}.json", &version.asset_index.id));
|
||||||
.join("indexes")
|
|
||||||
.join(format!("{}.json", &version.asset_index.id));
|
|
||||||
|
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
Ok(serde_json::from_str(&content)?)
|
||||||
} else {
|
} else {
|
||||||
let index = fetch_assets_index(version).await?;
|
let index = fetch_assets_index(version).await?;
|
||||||
|
|
||||||
save_file(path, &bytes::Bytes::from(serde_json::to_string(&index)?))?;
|
save_file(&path, &bytes::Bytes::from(serde_json::to_string(&index)?))?;
|
||||||
|
|
||||||
Ok(index)
|
Ok(index)
|
||||||
}
|
}
|
||||||
@ -89,7 +92,7 @@ pub async fn download_assets(
|
|||||||
index
|
index
|
||||||
.objects
|
.objects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| download_asset(assets_path, legacy_path, x.0, x.1)),
|
.map(|(name, asset)| download_asset(assets_path, legacy_path, name, asset)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -104,23 +107,20 @@ async fn download_asset(
|
|||||||
name: &str,
|
name: &str,
|
||||||
asset: &Asset,
|
asset: &Asset,
|
||||||
) -> Result<(), LauncherError> {
|
) -> Result<(), LauncherError> {
|
||||||
let sub_hash = &&asset.hash[..2];
|
let hash = &asset.hash;
|
||||||
|
let sub_hash = &hash[..2];
|
||||||
|
|
||||||
let resource_path = assets_path.join("objects").join(sub_hash).join(&asset.hash);
|
let mut resource_path = assets_path.join("objects");
|
||||||
|
resource_path.push(sub_hash);
|
||||||
|
resource_path.push(hash);
|
||||||
|
|
||||||
let resource = save_and_download_file(
|
let url = format!("https://resources.download.minecraft.net/{sub_hash}/{hash}");
|
||||||
&*resource_path,
|
|
||||||
&format!(
|
let resource = save_and_download_file(&resource_path, &url, Some(hash)).await?;
|
||||||
"https://resources.download.minecraft.net/{}/{}",
|
|
||||||
sub_hash, asset.hash
|
|
||||||
),
|
|
||||||
Some(&*asset.hash),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(legacy_path) = legacy_path {
|
if let Some(legacy_path) = legacy_path {
|
||||||
let resource_path =
|
let resource_path =
|
||||||
legacy_path.join(name.replace('/', &*std::path::MAIN_SEPARATOR.to_string()));
|
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)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ pub async fn download_libraries(
|
|||||||
future::join_all(
|
future::join_all(
|
||||||
libraries
|
libraries
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| download_library(libraries_path, natives_path, x)),
|
.map(|library| download_library(libraries_path, natives_path, library)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -150,19 +150,16 @@ async fn download_library(
|
|||||||
library: &Library,
|
library: &Library,
|
||||||
) -> Result<(), LauncherError> {
|
) -> Result<(), LauncherError> {
|
||||||
if let Some(rules) = &library.rules {
|
if let Some(rules) = &library.rules {
|
||||||
if !super::rules::parse_rules(rules.as_slice()) {
|
if !super::rules::parse_rules(rules) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (a, b) = future::join(
|
future::try_join(
|
||||||
download_library_jar(libraries_path, library),
|
download_library_jar(libraries_path, library),
|
||||||
download_native(natives_path, library),
|
download_native(natives_path, library),
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
a?;
|
|
||||||
b?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -171,55 +168,53 @@ async fn download_library_jar(
|
|||||||
libraries_path: &Path,
|
libraries_path: &Path,
|
||||||
library: &Library,
|
library: &Library,
|
||||||
) -> Result<(), LauncherError> {
|
) -> Result<(), LauncherError> {
|
||||||
let mut path = libraries_path.to_path_buf();
|
let artifact_path = get_path_from_artifact(&library.name)?;
|
||||||
path.push(get_path_from_artifact(&*library.name)?);
|
let path = libraries_path.join(&artifact_path);
|
||||||
|
|
||||||
if let Some(downloads) = &library.downloads {
|
if let Some(downloads) = &library.downloads {
|
||||||
if let Some(library) = &downloads.artifact {
|
if let Some(library) = &downloads.artifact {
|
||||||
save_and_download_file(&*path, &library.url, Some(&library.sha1)).await?;
|
save_and_download_file(&path, &library.url, Some(&library.sha1)).await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
save_and_download_file(
|
let url = format!(
|
||||||
&*path,
|
"{}{artifact_path}",
|
||||||
&format!(
|
library
|
||||||
"{}{}",
|
.url
|
||||||
library
|
.as_deref()
|
||||||
.url
|
.unwrap_or("https://libraries.minecraft.net/"),
|
||||||
.as_deref()
|
);
|
||||||
.unwrap_or("https://libraries.minecraft.net/"),
|
save_and_download_file(&path, &url, None).await?;
|
||||||
get_path_from_artifact(&*library.name)?
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_native(natives_path: &Path, library: &Library) -> Result<(), LauncherError> {
|
async fn download_native(natives_path: &Path, library: &Library) -> Result<(), LauncherError> {
|
||||||
if let Some(natives) = &library.natives {
|
use daedalus::minecraft::LibraryDownload;
|
||||||
if let Some(os_key) = natives.get(&get_os()) {
|
use std::collections::HashMap;
|
||||||
if let Some(downloads) = &library.downloads {
|
|
||||||
if let Some(classifiers) = &downloads.classifiers {
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
|
||||||
let parsed_key = os_key.replace("${arch}", "64");
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
|
||||||
let parsed_key = os_key.replace("${arch}", "32");
|
|
||||||
|
|
||||||
if let Some(native) = classifiers.get(&*parsed_key) {
|
// Try blocks in stable Rust when?
|
||||||
let file = download_file(&native.url, Some(&native.sha1)).await?;
|
let optional_cascade = || -> Option<(&String, &HashMap<String, LibraryDownload>)> {
|
||||||
|
let os_key = library.natives.as_ref()?.get(&get_os())?;
|
||||||
|
let classifiers = library.downloads.as_ref()?.classifiers.as_ref()?;
|
||||||
|
Some((os_key, classifiers))
|
||||||
|
};
|
||||||
|
|
||||||
let reader = std::io::Cursor::new(&*file);
|
if let Some((os_key, classifiers)) = optional_cascade() {
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
let parsed_key = os_key.replace("${arch}", "64");
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
let parsed_key = os_key.replace("${arch}", "32");
|
||||||
|
|
||||||
let mut archive = zip::ZipArchive::new(reader).unwrap();
|
if let Some(native) = classifiers.get(&parsed_key) {
|
||||||
archive.extract(natives_path).unwrap();
|
let file = download_file(&native.url, Some(&native.sha1)).await?;
|
||||||
}
|
|
||||||
}
|
let reader = std::io::Cursor::new(&file);
|
||||||
}
|
|
||||||
|
let mut archive = zip::ZipArchive::new(reader).unwrap();
|
||||||
|
archive.extract(natives_path).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,27 +223,23 @@ async fn save_and_download_file(
|
|||||||
url: &str,
|
url: &str,
|
||||||
sha1: Option<&str>,
|
sha1: Option<&str>,
|
||||||
) -> Result<bytes::Bytes, LauncherError> {
|
) -> Result<bytes::Bytes, LauncherError> {
|
||||||
let read = std::fs::read(path).ok().map(bytes::Bytes::from);
|
match std::fs::read(path) {
|
||||||
|
Ok(bytes) => Ok(bytes::Bytes::from(bytes)),
|
||||||
if let Some(bytes) = read {
|
Err(_) => {
|
||||||
Ok(bytes)
|
let file = download_file(url, sha1).await?;
|
||||||
} else {
|
save_file(path, &file)?;
|
||||||
let file = download_file(url, sha1).await?;
|
Ok(file)
|
||||||
|
}
|
||||||
save_file(path, &file)?;
|
|
||||||
|
|
||||||
Ok(file)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_file(path: &Path, bytes: &bytes::Bytes) -> Result<(), std::io::Error> {
|
fn save_file(path: &Path, bytes: &bytes::Bytes) -> std::io::Result<()> {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file = File::create(path)?;
|
let mut file = File::create(path)?;
|
||||||
file.write_all(bytes)?;
|
file.write_all(bytes)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +254,7 @@ pub fn get_os() -> Os {
|
|||||||
|
|
||||||
pub async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes, LauncherError> {
|
pub async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes, LauncherError> {
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
|
.tcp_keepalive(Some(Duration::from_secs(10)))
|
||||||
.build()
|
.build()
|
||||||
.map_err(|err| LauncherError::FetchError {
|
.map_err(|err| LauncherError::FetchError {
|
||||||
inner: err,
|
inner: err,
|
||||||
@ -311,12 +302,11 @@ pub async fn download_file(url: &str, sha1: Option<&str>) -> Result<bytes::Bytes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes a checksum of the input bytes
|
/// Computes a checksum of the input bytes
|
||||||
pub async fn get_hash(bytes: bytes::Bytes) -> Result<String, LauncherError> {
|
async fn get_hash(bytes: bytes::Bytes) -> Result<String, LauncherError> {
|
||||||
let hash = tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest()).await?;
|
let hash = tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest()).await?;
|
||||||
|
|
||||||
Ok(hash)
|
Ok(hash)
|
||||||
|
|||||||
@ -1,30 +1,17 @@
|
|||||||
use crate::launcher::LauncherError;
|
use crate::launcher::LauncherError;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref JAVA_VERSION_REGEX: Regex = Regex::new(r#""(.*?)""#).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_java() -> Result<Option<String>, LauncherError> {
|
pub fn check_java() -> Result<String, LauncherError> {
|
||||||
let child = Command::new("java")
|
let child = Command::new("java")
|
||||||
.arg("-version")
|
.arg("-version")
|
||||||
.output()
|
.output()
|
||||||
.map_err(|err| LauncherError::ProcessError {
|
.map_err(|inner| LauncherError::ProcessError {
|
||||||
inner: err,
|
inner,
|
||||||
process: "java".to_string(),
|
process: "java".into(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let output = &*String::from_utf8_lossy(&*child.stderr);
|
let output = String::from_utf8_lossy(&child.stderr);
|
||||||
|
let output = output.trim_matches('\"');
|
||||||
if let Some(version_raw) = JAVA_VERSION_REGEX.find(output) {
|
Ok(output.into())
|
||||||
let mut raw = version_raw.as_str().chars();
|
|
||||||
raw.next();
|
|
||||||
raw.next_back();
|
|
||||||
|
|
||||||
return Ok(Some(raw.as_str().to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,7 @@
|
|||||||
|
|
||||||
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
|
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
|
||||||
|
|
||||||
use std::path::Path;
|
static LAUNCHER_WORK_DIR: &'static str = "./launcher";
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
pub static ref LAUNCHER_WORK_DIR: &'static Path = Path::new("./launcher");
|
|
||||||
}
|
|
||||||
|
|
||||||
mod data;
|
mod data;
|
||||||
pub mod launcher;
|
pub mod launcher;
|
||||||
@ -29,7 +25,7 @@ pub enum Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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::data::Metadata::init().await?;
|
crate::data::Metadata::init().await?;
|
||||||
crate::data::Settings::init().await?;
|
crate::data::Settings::init().await?;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use std::collections::HashSet;
|
use std::path::PathBuf;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
@ -10,48 +9,47 @@ use super::{pack, ModpackError, ModpackResult};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const DEFAULT_FORMAT_VERSION: u32 = 1;
|
pub const DEFAULT_FORMAT_VERSION: u32 = 1;
|
||||||
|
const MODRINTH_GAMEDATA_URL: &str = "https://staging-cdn.modrinth.com/gamedata";
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Manifest<'a> {
|
pub struct Manifest {
|
||||||
pub format_version: u32,
|
pub format_version: u32,
|
||||||
pub game: &'a str,
|
pub game: String,
|
||||||
pub version_id: &'a str,
|
pub version_id: String,
|
||||||
pub name: &'a str,
|
pub name: String,
|
||||||
#[serde(borrow)]
|
pub summary: Option<String>,
|
||||||
pub summary: Option<&'a str>,
|
pub files: Vec<ManifestFile>,
|
||||||
pub files: Vec<ManifestFile<'a>>,
|
pub dependencies: ManifestDeps,
|
||||||
pub dependencies: ManifestDeps<'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Manifest<'_>> for pack::Modpack {
|
impl TryFrom<Manifest> for pack::Modpack {
|
||||||
type Error = ModpackError;
|
type Error = ModpackError;
|
||||||
|
|
||||||
fn try_from(manifest: Manifest<'_>) -> Result<Self, Self::Error> {
|
fn try_from(manifest: Manifest) -> Result<Self, Self::Error> {
|
||||||
let files = manifest
|
let files = manifest
|
||||||
.files
|
.files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(pack::ModpackFile::try_from)
|
.map(pack::ModpackFile::try_from)
|
||||||
.collect::<ModpackResult<HashSet<pack::ModpackFile>>>()?;
|
.collect::<ModpackResult<_>>()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: String::from(manifest.name),
|
name: manifest.name,
|
||||||
version: String::from(manifest.version_id),
|
version: manifest.version_id,
|
||||||
summary: manifest.summary.map(String::from),
|
summary: manifest.summary,
|
||||||
game: ModpackGame::from(manifest.dependencies),
|
game: ModpackGame::from(manifest.dependencies),
|
||||||
files,
|
files,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
"Attempted to get mod loader version of Vanilla",
|
"Attempted to get mod loader version of Vanilla",
|
||||||
))),
|
))),
|
||||||
ModLoader::Forge => Ok(format!("{}/forge/v0/manifest.json", MODRINTH_GAMEDATA_URL)),
|
ModLoader::Forge => Ok(format!("{MODRINTH_GAMEDATA_URL}/forge/v0/manifest.json")),
|
||||||
ModLoader::Fabric => Ok(format!("{}/fabric/v0/manifest.json", MODRINTH_GAMEDATA_URL)),
|
ModLoader::Fabric => Ok(format!("{MODRINTH_GAMEDATA_URL}/fabric/v0/manifest.json")),
|
||||||
}?;
|
}?;
|
||||||
let manifest = futures::executor::block_on(daedalus::modded::fetch_manifest(&source))?;
|
let manifest = futures::executor::block_on(daedalus::modded::fetch_manifest(&source))?;
|
||||||
|
|
||||||
@ -63,96 +61,90 @@ fn get_loader_version(loader: ModLoader, version: &str) -> ModpackResult<String>
|
|||||||
.flatten()
|
.flatten()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ModpackError::VersionError(format!(
|
ModpackError::VersionError(format!(
|
||||||
"No versions of modloader {:?} exist for Minecraft {}",
|
"No versions of modloader {loader:?} exist for Minecraft {version}",
|
||||||
loader, version
|
|
||||||
))
|
))
|
||||||
})?
|
})?
|
||||||
.id
|
.id)
|
||||||
.clone())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a pack::Modpack> for Manifest<'a> {
|
impl TryFrom<pack::Modpack> for Manifest {
|
||||||
type Error = ModpackError;
|
type Error = ModpackError;
|
||||||
|
|
||||||
fn try_from(pack: &'a pack::Modpack) -> Result<Self, Self::Error> {
|
fn try_from(pack: pack::Modpack) -> Result<Self, Self::Error> {
|
||||||
let game_field: &'a str = match pack.game {
|
let pack::Modpack {
|
||||||
ModpackGame::Minecraft(..) => "minecraft",
|
game,
|
||||||
|
version,
|
||||||
|
name,
|
||||||
|
summary,
|
||||||
|
files,
|
||||||
|
} = pack;
|
||||||
|
|
||||||
|
let game = match game {
|
||||||
|
ModpackGame::Minecraft(..) => "minecraft".into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let files = pack
|
let files: Vec<_> = pack.files.into_iter().map(ManifestFile::from).collect();
|
||||||
.files
|
|
||||||
.iter()
|
|
||||||
.map(ManifestFile::from)
|
|
||||||
.collect::<Vec<ManifestFile>>();
|
|
||||||
|
|
||||||
Ok(Manifest {
|
Ok(Manifest {
|
||||||
format_version: DEFAULT_FORMAT_VERSION,
|
format_version: DEFAULT_FORMAT_VERSION,
|
||||||
game: game_field,
|
game,
|
||||||
version_id: &pack.version,
|
version_id: version,
|
||||||
name: &pack.name,
|
name,
|
||||||
summary: pack.summary.as_deref(),
|
summary,
|
||||||
files,
|
files,
|
||||||
dependencies: ManifestDeps::try_from(&pack.game)?,
|
dependencies: ManifestDeps::try_from(pack.game)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ManifestFile<'a> {
|
pub struct ManifestFile {
|
||||||
#[serde(borrow)]
|
pub path: PathBuf,
|
||||||
pub path: &'a Path,
|
pub hashes: Option<ManifestHashes>,
|
||||||
pub hashes: Option<ManifestHashes<'a>>,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub env: ManifestEnvs,
|
pub env: ManifestEnvs,
|
||||||
#[serde(borrow)]
|
pub downloads: Vec<String>,
|
||||||
pub downloads: Vec<&'a str>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ManifestFile<'_>> for pack::ModpackFile {
|
impl TryFrom<ManifestFile> for pack::ModpackFile {
|
||||||
type Error = ModpackError;
|
type Error = ModpackError;
|
||||||
|
|
||||||
fn try_from(file: ManifestFile<'_>) -> Result<Self, Self::Error> {
|
fn try_from(file: ManifestFile) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
path: PathBuf::from(file.path),
|
path: file.path,
|
||||||
hashes: file.hashes.map(pack::ModpackFileHashes::from),
|
hashes: file.hashes.map(pack::ModpackFileHashes::from),
|
||||||
env: pack::ModpackEnv::try_from(file.env)?,
|
env: pack::ModpackEnv::try_from(file.env)?,
|
||||||
downloads: file.downloads.into_iter().map(ToOwned::to_owned).collect(),
|
downloads: file.downloads.into_iter().collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a pack::ModpackFile> for ManifestFile<'a> {
|
impl From<pack::ModpackFile> for ManifestFile {
|
||||||
fn from(file: &'a pack::ModpackFile) -> Self {
|
fn from(file: pack::ModpackFile) -> Self {
|
||||||
Self {
|
Self {
|
||||||
path: file.path.as_path(),
|
path: file.path,
|
||||||
hashes: file.hashes.as_ref().map(ManifestHashes::from),
|
hashes: file.hashes.map(ManifestHashes::from),
|
||||||
env: file.env.into(),
|
env: file.env.into(),
|
||||||
downloads: file
|
downloads: file.downloads.into_iter().collect(),
|
||||||
.downloads
|
|
||||||
.iter()
|
|
||||||
.map(String::as_str)
|
|
||||||
.collect::<Vec<&str>>(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct ManifestHashes<'a> {
|
pub struct ManifestHashes {
|
||||||
pub sha1: &'a str,
|
pub sha1: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ManifestHashes<'_>> for pack::ModpackFileHashes {
|
impl From<ManifestHashes> for pack::ModpackFileHashes {
|
||||||
fn from(hashes: ManifestHashes<'_>) -> Self {
|
fn from(hashes: ManifestHashes) -> Self {
|
||||||
Self {
|
Self { sha1: hashes.sha1 }
|
||||||
sha1: String::from(hashes.sha1),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a pack::ModpackFileHashes> for ManifestHashes<'a> {
|
impl From<pack::ModpackFileHashes> for ManifestHashes {
|
||||||
fn from(hashes: &'a pack::ModpackFileHashes) -> Self {
|
fn from(hashes: pack::ModpackFileHashes) -> Self {
|
||||||
Self { sha1: &hashes.sha1 }
|
Self { sha1: hashes.sha1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,55 +205,47 @@ impl From<pack::ModpackEnv> for ManifestEnvs {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
// HACK: I've tried for hours to get this working zero-copy, but I'm beat. If someone else wants to
|
pub enum ManifestDeps {
|
||||||
// go through the #<!! of implementing it, be my guest.
|
|
||||||
pub enum ManifestDeps<'a> {
|
|
||||||
MinecraftFabric {
|
MinecraftFabric {
|
||||||
minecraft: &'a str,
|
minecraft: String,
|
||||||
#[serde(rename = "fabric-loader")]
|
#[serde(rename = "fabric-loader")]
|
||||||
fabric_loader: String,
|
fabric_loader: String,
|
||||||
},
|
},
|
||||||
MinecraftForge {
|
MinecraftForge {
|
||||||
minecraft: &'a str,
|
minecraft: String,
|
||||||
forge: String,
|
forge: String,
|
||||||
},
|
},
|
||||||
MinecraftVanilla {
|
MinecraftVanilla {
|
||||||
minecraft: &'a str,
|
minecraft: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ManifestDeps<'_>> for pack::ModpackGame {
|
impl From<ManifestDeps> for pack::ModpackGame {
|
||||||
fn from(deps: ManifestDeps<'_>) -> Self {
|
fn from(deps: ManifestDeps) -> Self {
|
||||||
use ManifestDeps::*;
|
use ManifestDeps::*;
|
||||||
|
|
||||||
match deps {
|
match deps {
|
||||||
MinecraftVanilla { minecraft } => {
|
MinecraftVanilla { minecraft } => Self::Minecraft(minecraft, ModLoader::Vanilla),
|
||||||
Self::Minecraft(String::from(minecraft), ModLoader::Vanilla)
|
MinecraftFabric { minecraft, .. } => Self::Minecraft(minecraft, ModLoader::Fabric),
|
||||||
}
|
MinecraftForge { minecraft, .. } => Self::Minecraft(minecraft, ModLoader::Forge),
|
||||||
MinecraftFabric { minecraft, .. } => {
|
|
||||||
Self::Minecraft(String::from(minecraft), ModLoader::Fabric)
|
|
||||||
}
|
|
||||||
MinecraftForge { minecraft, .. } => {
|
|
||||||
Self::Minecraft(String::from(minecraft), ModLoader::Forge)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a pack::ModpackGame> for ManifestDeps<'a> {
|
impl TryFrom<pack::ModpackGame> for ManifestDeps {
|
||||||
type Error = ModpackError;
|
type Error = ModpackError;
|
||||||
|
|
||||||
fn try_from(game: &'a pack::ModpackGame) -> Result<Self, Self::Error> {
|
fn try_from(game: pack::ModpackGame) -> Result<Self, Self::Error> {
|
||||||
use super::pack::ModpackGame::*;
|
use super::pack::ModpackGame::*;
|
||||||
Ok(match game {
|
Ok(match game {
|
||||||
Minecraft(ref ver, ModLoader::Vanilla) => Self::MinecraftVanilla { minecraft: ver },
|
Minecraft(minecraft, ModLoader::Vanilla) => Self::MinecraftVanilla { minecraft },
|
||||||
Minecraft(ref ver, loader @ ModLoader::Fabric) => Self::MinecraftFabric {
|
Minecraft(minecraft, ModLoader::Fabric) => Self::MinecraftFabric {
|
||||||
minecraft: ver,
|
minecraft,
|
||||||
fabric_loader: get_loader_version(*loader, ver)?,
|
fabric_loader: get_loader_version(ModLoader::Fabric, &minecraft)?,
|
||||||
},
|
},
|
||||||
Minecraft(ref ver, loader @ ModLoader::Forge) => Self::MinecraftForge {
|
Minecraft(minecraft, ModLoader::Forge) => Self::MinecraftForge {
|
||||||
minecraft: ver,
|
minecraft,
|
||||||
forge: get_loader_version(*loader, ver)?,
|
forge: get_loader_version(ModLoader::Fabric, &minecraft)?,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -287,13 +271,13 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
let expected_manifest = Manifest {
|
let expected_manifest = Manifest {
|
||||||
format_version: 1,
|
format_version: 1,
|
||||||
game: "minecraft",
|
game: "minecraft".into(),
|
||||||
version_id: "deadbeef",
|
version_id: "deadbeef".into(),
|
||||||
name: "Example Pack",
|
name: "Example Pack".into(),
|
||||||
summary: None,
|
summary: None,
|
||||||
files: Vec::new(),
|
files: vec![],
|
||||||
dependencies: ManifestDeps::MinecraftVanilla {
|
dependencies: ManifestDeps::MinecraftVanilla {
|
||||||
minecraft: "1.17.1",
|
minecraft: "1.17.1".into(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
||||||
@ -329,21 +313,21 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
let expected_manifest = Manifest {
|
let expected_manifest = Manifest {
|
||||||
format_version: 1,
|
format_version: 1,
|
||||||
game: "minecraft",
|
game: "minecraft".into(),
|
||||||
version_id: "deadbeef",
|
version_id: "deadbeef".into(),
|
||||||
name: "Example Pack",
|
name: "Example Pack".into(),
|
||||||
summary: None,
|
summary: None,
|
||||||
files: vec![ManifestFile {
|
files: vec![ManifestFile {
|
||||||
path: Path::new("mods/testmod.jar"),
|
path: "mods/testmod.jar".into(),
|
||||||
hashes: Some(ManifestHashes {
|
hashes: Some(ManifestHashes {
|
||||||
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(),
|
||||||
}),
|
}),
|
||||||
env: ManifestEnvs::default(),
|
env: ManifestEnvs::default(),
|
||||||
downloads: vec!["https://example.com/testmod.jar"],
|
downloads: vec!["https://example.com/testmod.jar".into()],
|
||||||
}],
|
}],
|
||||||
dependencies: ManifestDeps::MinecraftForge {
|
dependencies: ManifestDeps::MinecraftForge {
|
||||||
minecraft: "1.17.1",
|
minecraft: "1.17.1".into(),
|
||||||
forge: String::from("37.0.110"),
|
forge: "37.0.110".into(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
||||||
@ -379,21 +363,21 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
let expected_manifest = Manifest {
|
let expected_manifest = Manifest {
|
||||||
format_version: 1,
|
format_version: 1,
|
||||||
game: "minecraft",
|
game: "minecraft".into(),
|
||||||
version_id: "deadbeef",
|
version_id: "deadbeef".into(),
|
||||||
name: "Example Pack",
|
name: "Example Pack".into(),
|
||||||
summary: None,
|
summary: None,
|
||||||
files: vec![ManifestFile {
|
files: vec![ManifestFile {
|
||||||
path: Path::new("mods/testmod.jar"),
|
path: "mods/testmod.jar".into(),
|
||||||
hashes: Some(ManifestHashes {
|
hashes: Some(ManifestHashes {
|
||||||
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(),
|
||||||
}),
|
}),
|
||||||
env: ManifestEnvs::default(),
|
env: ManifestEnvs::default(),
|
||||||
downloads: vec!["https://example.com/testmod.jar"],
|
downloads: vec!["https://example.com/testmod.jar".into()],
|
||||||
}],
|
}],
|
||||||
dependencies: ManifestDeps::MinecraftFabric {
|
dependencies: ManifestDeps::MinecraftFabric {
|
||||||
minecraft: "1.17.1",
|
minecraft: "1.17.1".into(),
|
||||||
fabric_loader: String::from("0.9.0"),
|
fabric_loader: "0.9.0".into(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
||||||
@ -434,24 +418,24 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
let expected_manifest = Manifest {
|
let expected_manifest = Manifest {
|
||||||
format_version: 1,
|
format_version: 1,
|
||||||
game: "minecraft",
|
game: "minecraft".into(),
|
||||||
version_id: "deadbeef",
|
version_id: "deadbeef".into(),
|
||||||
name: "Example Pack",
|
name: "Example Pack".into(),
|
||||||
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.".into()),
|
||||||
files: vec![ManifestFile {
|
files: vec![ManifestFile {
|
||||||
path: Path::new("mods/testmod.jar"),
|
path: "mods/testmod.jar".into(),
|
||||||
hashes: Some(ManifestHashes {
|
hashes: Some(ManifestHashes {
|
||||||
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(),
|
||||||
}),
|
}),
|
||||||
env: ManifestEnvs {
|
env: ManifestEnvs {
|
||||||
client: ManifestEnv::Required,
|
client: ManifestEnv::Required,
|
||||||
server: ManifestEnv::Unsupported,
|
server: ManifestEnv::Unsupported,
|
||||||
},
|
},
|
||||||
downloads: vec!["https://example.com/testmod.jar"],
|
downloads: vec!["https://example.com/testmod.jar".into()],
|
||||||
}],
|
}],
|
||||||
dependencies: ManifestDeps::MinecraftForge {
|
dependencies: ManifestDeps::MinecraftForge {
|
||||||
minecraft: "1.17.1",
|
minecraft: "1.17.1".into(),
|
||||||
forge: String::from("37.0.110"),
|
forge: "37.0.110".into(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use daedalus::download_file;
|
use daedalus::download_file;
|
||||||
use fs_extra::dir::CopyOptions;
|
use fs_extra::dir::CopyOptions;
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{convert::TryFrom, env, io, path::Path};
|
use std::{convert::TryFrom, env, io, path::Path};
|
||||||
use tokio::{fs, try_join};
|
use tokio::{fs, try_join};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -23,6 +22,10 @@ pub const COMPILED_ZIP: &str = "compiled.mrpack";
|
|||||||
pub const MANIFEST_PATH: &str = "modrinth.index.json";
|
pub const MANIFEST_PATH: &str = "modrinth.index.json";
|
||||||
pub const OVERRIDES_PATH: &str = "overrides/";
|
pub const OVERRIDES_PATH: &str = "overrides/";
|
||||||
pub const PACK_JSON5_PATH: &str = "modpack.json5";
|
pub const PACK_JSON5_PATH: &str = "modpack.json5";
|
||||||
|
const PACK_GITIGNORE: &'static str = const_format::formatcp!(r#"
|
||||||
|
{COMPILED_PATH}
|
||||||
|
{COMPILED_ZIP}
|
||||||
|
"#);
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum ModpackError {
|
pub enum ModpackError {
|
||||||
@ -86,7 +89,8 @@ pub async fn realise_modpack_zip(
|
|||||||
dest: &Path,
|
dest: &Path,
|
||||||
side: pack::ModpackSide,
|
side: pack::ModpackSide,
|
||||||
) -> ModpackResult<()> {
|
) -> ModpackResult<()> {
|
||||||
let tmp = env::temp_dir().join(format!("theseus-{}/", Uuid::new_v4()));
|
let mut tmp = env::temp_dir();
|
||||||
|
tmp.push(format!("theseus-{}/", Uuid::new_v4()));
|
||||||
archive.extract(&tmp)?;
|
archive.extract(&tmp)?;
|
||||||
realise_modpack(&tmp, dest, side).await
|
realise_modpack(&tmp, dest, side).await
|
||||||
}
|
}
|
||||||
@ -102,7 +106,7 @@ pub async fn realise_modpack(
|
|||||||
"Output is not a directory",
|
"Output is not a directory",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
if dest.exists() && std::fs::read_dir(dest).map_or(false, |it| it.count() != 0) {
|
if std::fs::read_dir(dest).map_or(false, |it| it.count() != 0) {
|
||||||
return Err(ModpackError::InvalidDirectory(String::from(
|
return Err(ModpackError::InvalidDirectory(String::from(
|
||||||
"Output directory is non-empty",
|
"Output directory is non-empty",
|
||||||
)));
|
)));
|
||||||
@ -112,22 +116,22 @@ pub async fn realise_modpack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy overrides
|
// Copy overrides
|
||||||
let overrides = Some(dir.join(OVERRIDES_PATH)).filter(|it| it.exists() && it.is_dir());
|
let overrides = dir.join(OVERRIDES_PATH);
|
||||||
if let Some(overrides) = overrides {
|
if overrides.is_dir() {
|
||||||
fs_extra::dir::copy(overrides, dest, &CopyOptions::new())?;
|
fs_extra::dir::copy(overrides, dest, &CopyOptions::new())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse manifest
|
// Parse manifest
|
||||||
// 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.is_file())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ModpackError::ManifestError(String::from("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 manifest = Manifest::deserialize(&mut deserializer)?;
|
let manifest: Manifest = serde_json::from_reader(reader)?;
|
||||||
let modpack = Modpack::try_from(manifest)?;
|
let modpack = Modpack::try_from(manifest)?;
|
||||||
|
|
||||||
// Realise modpack
|
// Realise modpack
|
||||||
@ -137,14 +141,7 @@ pub async fn realise_modpack(
|
|||||||
|
|
||||||
pub fn to_pack_json5(pack: &Modpack) -> ModpackResult<String> {
|
pub fn to_pack_json5(pack: &Modpack) -> ModpackResult<String> {
|
||||||
let json5 = json5::to_string(pack)?;
|
let json5 = json5::to_string(pack)?;
|
||||||
Ok(format!("// This modpack is managed using Theseus. It can be edited using either a Theseus-compatible launcher or manually.\n{}", json5))
|
Ok(format!("// This modpack is managed using Theseus. It can be edited using either a Theseus-compatible launcher or manually.\n{json5}"))
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref PACK_GITIGNORE: String = format!(r#"
|
|
||||||
{0}
|
|
||||||
{1}
|
|
||||||
"#, COMPILED_PATH, COMPILED_ZIP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_modpack(
|
pub async fn create_modpack(
|
||||||
@ -158,7 +155,7 @@ pub async fn create_modpack(
|
|||||||
try_join!(
|
try_join!(
|
||||||
fs::create_dir(&output_dir),
|
fs::create_dir(&output_dir),
|
||||||
fs::create_dir(output_dir.join(OVERRIDES_PATH)),
|
fs::create_dir(output_dir.join(OVERRIDES_PATH)),
|
||||||
fs::write(output_dir.join(".gitignore"), PACK_GITIGNORE.as_str()),
|
fs::write(output_dir.join(".gitignore"), PACK_GITIGNORE),
|
||||||
fs::write(output_dir.join(PACK_JSON5_PATH), to_pack_json5(&pack)?),
|
fs::write(output_dir.join(PACK_JSON5_PATH), to_pack_json5(&pack)?),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -177,7 +174,7 @@ pub async fn compile_modpack(dir: &Path) -> ModpackResult<()> {
|
|||||||
&CopyOptions::new(),
|
&CopyOptions::new(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
let manifest = Manifest::try_from(&pack)?;
|
let manifest = Manifest::try_from(pack)?;
|
||||||
fs::write(
|
fs::write(
|
||||||
result_dir.join(MANIFEST_PATH),
|
result_dir.join(MANIFEST_PATH),
|
||||||
serde_json::to_string(&manifest)?,
|
serde_json::to_string(&manifest)?,
|
||||||
|
|||||||
@ -28,42 +28,35 @@ pub trait ModrinthAPI {
|
|||||||
pub struct ModrinthV1(pub String);
|
pub struct ModrinthV1(pub String);
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ModrinthV1Project<'a> {
|
struct ModrinthV1Project {
|
||||||
title: &'a str,
|
title: String,
|
||||||
client_side: &'a str,
|
client_side: String,
|
||||||
server_side: &'a str,
|
server_side: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ModrinthV1ProjectVersion<'a> {
|
struct ModrinthV1ProjectVersion {
|
||||||
#[serde(borrow)]
|
dependencies: HashSet<String>,
|
||||||
dependencies: HashSet<&'a str>,
|
game_versions: HashSet<String>,
|
||||||
#[serde(borrow)]
|
version_type: String,
|
||||||
game_versions: HashSet<&'a str>,
|
files: Vec<ModrinthV1ProjectVersionFile>,
|
||||||
version_type: &'a str,
|
loaders: HashSet<String>,
|
||||||
files: Vec<ModrinthV1ProjectVersionFile<'a>>,
|
|
||||||
#[serde(borrow)]
|
|
||||||
loaders: HashSet<&'a str>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
struct ModrinthV1ProjectVersionFile<'a> {
|
struct ModrinthV1ProjectVersionFile {
|
||||||
hashes: ManifestHashes<'a>,
|
hashes: ManifestHashes,
|
||||||
url: &'a str,
|
url: String,
|
||||||
filename: &'a str,
|
filename: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ModrinthV1ProjectVersionFile<'_>> for ModpackFile {
|
impl From<ModrinthV1ProjectVersionFile> for ModpackFile {
|
||||||
fn from(file: ModrinthV1ProjectVersionFile<'_>) -> Self {
|
fn from(file: ModrinthV1ProjectVersionFile) -> Self {
|
||||||
Self {
|
Self {
|
||||||
hashes: Some(ModpackFileHashes::from(file.hashes)),
|
hashes: Some(ModpackFileHashes::from(file.hashes)),
|
||||||
downloads: {
|
downloads: HashSet::from([file.url]),
|
||||||
let mut downloads: HashSet<String> = HashSet::new();
|
|
||||||
downloads.insert(String::from(file.url));
|
|
||||||
downloads
|
|
||||||
},
|
|
||||||
path: PathBuf::from(file.filename),
|
path: PathBuf::from(file.filename),
|
||||||
// WARNING: Since the sidedness of version 1 API requests is unknown, the environemnt is
|
// WARNING: Since the sidedness of version 1 API requests is unknown, the environment is
|
||||||
// set here as both.
|
// set here as both.
|
||||||
env: ModpackEnv::Both,
|
env: ModpackEnv::Both,
|
||||||
}
|
}
|
||||||
@ -78,10 +71,11 @@ impl ModrinthAPI for ModrinthV1 {
|
|||||||
channel: &str,
|
channel: &str,
|
||||||
game: &ModpackGame,
|
game: &ModpackGame,
|
||||||
) -> ModpackResult<HashSet<ModpackFile>> {
|
) -> ModpackResult<HashSet<ModpackFile>> {
|
||||||
|
let domain = &self.0;
|
||||||
// Fetch metadata
|
// Fetch metadata
|
||||||
let (project_json, versions_json): (Bytes, Bytes) = try_join!(
|
let (project_json, versions_json): (Bytes, Bytes) = try_join!(
|
||||||
try_get_json(format!("{}/api/v1/mod/{}", self.0, project)),
|
try_get_json(format!("{domain}/api/v1/mod/{project}")),
|
||||||
try_get_json(format!("{}/api/v1/mod/{}/version", self.0, project)),
|
try_get_json(format!("{domain}/api/v1/mod/{project}/version")),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (mut project_deserializer, mut versions_deserializer) = (
|
let (mut project_deserializer, mut versions_deserializer) = (
|
||||||
@ -113,8 +107,8 @@ impl ModrinthAPI for ModrinthV1 {
|
|||||||
ModLoader::Vanilla => unreachable!(),
|
ModLoader::Vanilla => unreachable!(),
|
||||||
};
|
};
|
||||||
it.version_type == channel
|
it.version_type == channel
|
||||||
&& it.game_versions.contains(&game_version.as_str())
|
&& it.game_versions.contains(game_version)
|
||||||
&& it.loaders.contains(&loader_str)
|
&& it.loaders.contains(loader_str)
|
||||||
})
|
})
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ModpackError::VersionError(format!(
|
ModpackError::VersionError(format!(
|
||||||
@ -125,8 +119,8 @@ impl ModrinthAPI for ModrinthV1 {
|
|||||||
|
|
||||||
// Project fields
|
// Project fields
|
||||||
let envs = ModpackEnv::try_from(ManifestEnvs {
|
let envs = ModpackEnv::try_from(ManifestEnvs {
|
||||||
client: serde_json::from_str(project.client_side)?,
|
client: serde_json::from_str(&project.client_side)?,
|
||||||
server: serde_json::from_str(project.server_side)?,
|
server: serde_json::from_str(&project.server_side)?,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Conversions
|
// Conversions
|
||||||
@ -155,7 +149,8 @@ impl ModrinthAPI for ModrinthV1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_version(&self, version: &str) -> ModpackResult<HashSet<ModpackFile>> {
|
async fn get_version(&self, version: &str) -> ModpackResult<HashSet<ModpackFile>> {
|
||||||
let version_json = try_get_json(format!("{}/api/v1/version/{}", self.0, version)).await?;
|
let domain = &self.0;
|
||||||
|
let version_json = try_get_json(format!("{domain}/api/v1/version/{version}")).await?;
|
||||||
let mut version_deserializer = serde_json::Deserializer::from_slice(&version_json);
|
let mut version_deserializer = serde_json::Deserializer::from_slice(&version_json);
|
||||||
let version = ModrinthV1ProjectVersion::deserialize(&mut version_deserializer)?;
|
let version = ModrinthV1ProjectVersion::deserialize(&mut version_deserializer)?;
|
||||||
let base_path = PathBuf::from("mods/");
|
let base_path = PathBuf::from("mods/");
|
||||||
@ -164,7 +159,7 @@ impl ModrinthAPI for ModrinthV1 {
|
|||||||
.files
|
.files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(ModpackFile::from)
|
.map(ModpackFile::from)
|
||||||
.collect::<HashSet<ModpackFile>>())
|
.collect::<HashSet<_>>())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -144,6 +144,7 @@ impl Modpack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum ModpackGame {
|
pub enum ModpackGame {
|
||||||
// TODO: Currently, the launcher does not support specifying mod loader versions, so I just
|
// TODO: Currently, the launcher does not support specifying mod loader versions, so I just
|
||||||
// store the loader here.
|
// store the loader here.
|
||||||
@ -256,9 +257,9 @@ mod tests {
|
|||||||
let mut files = HashSet::new();
|
let mut files = HashSet::new();
|
||||||
files.insert(ModpackFile {
|
files.insert(ModpackFile {
|
||||||
path: PathBuf::from("mods/gravestones-v1.9.jar"),
|
path: PathBuf::from("mods/gravestones-v1.9.jar"),
|
||||||
hashes: ModpackFileHashes {
|
hashes: Some(ModpackFileHashes {
|
||||||
sha1: String::from("3f0f6d523d218460310b345be03ab3f1d294e04d"),
|
sha1: String::from("3f0f6d523d218460310b345be03ab3f1d294e04d"),
|
||||||
},
|
}),
|
||||||
env: ModpackEnv::Both,
|
env: ModpackEnv::Both,
|
||||||
downloads: {
|
downloads: {
|
||||||
let mut downloads = HashSet::new();
|
let mut downloads = HashSet::new();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user