diff --git a/.env b/.env
index bbcfb5c..9aac220 100644
--- a/.env
+++ b/.env
@@ -1,9 +1,15 @@
-RUST_LOG=info
+RUST_LOG=warn,daedalus_client=trace
-BASE_URL=https://modrinth-cdn-staging.nyc3.digitaloceanspaces.com
+BASE_URL=http://localhost:9000/meta
+
+CONCURRENCY_LIMIT=10
S3_ACCESS_TOKEN=none
S3_SECRET=none
-S3_URL=none
-S3_REGION=none
-S3_BUCKET_NAME=none
+S3_URL=http://localhost:9000
+S3_REGION=path-style
+S3_BUCKET_NAME=meta
+
+CLOUDFLARE_INTEGRATION=false
+CLOUDFLARE_TOKEN=none
+CLOUDFLARE_ZONE_ID=none
\ No newline at end of file
diff --git a/.github/workflows/run.yml b/.github/workflows/run.yml
new file mode 100644
index 0000000..b97d2bd
--- /dev/null
+++ b/.github/workflows/run.yml
@@ -0,0 +1,49 @@
+name: Run Meta
+
+on:
+ schedule:
+ - cron: '*/5 * * * *'
+
+jobs:
+ run-docker:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Log in to GitHub Container Registry
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+
+ - name: Pull Docker image from GHCR
+ run: docker pull ghcr.io/modrinth/daedalus:latest
+
+ - name: Run Docker container
+ env:
+ BASE_URL: ${{ secrets.BASE_URL }}
+ S3_ACCESS_TOKEN: ${{ secrets.S3_ACCESS_TOKEN }}
+ S3_SECRET: ${{ secrets.S3_SECRET }}
+ S3_URL: ${{ secrets.S3_URL }}
+ S3_REGION: ${{ secrets.S3_REGION }}
+ S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
+ CLOUDFLARE_INTEGRATION: ${{ secrets.CLOUDFLARE_INTEGRATION }}
+ CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
+ CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
+ run: |
+ docker run -d \
+ --name daedalus \
+ -e BASE_URL=$BASE_URL \
+ -e S3_ACCESS_TOKEN=$S3_ACCESS_TOKEN \
+ -e S3_SECRET=$S3_SECRET \
+ -e S3_URL=$S3_URL \
+ -e S3_REGION=$S3_REGION \
+ -e S3_BUCKET_NAME=$S3_BUCKET_NAME \
+ -e CLOUDFLARE_INTEGRATION=$CLOUDFLARE_INTEGRATION \
+ -e CLOUDFLARE_TOKEN=$CLOUDFLARE_TOKEN \
+ -e CLOUDFLARE_ZONE_ID=$CLOUDFLARE_ZONE_ID \
+ ghcr.io/modrinth/daedalus:latest
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index dae6eff..06b153a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+caches/
# User-specific stuff
.idea/**/workspace.xml
diff --git a/.idea/daedalus.iml b/.idea/daedalus.iml
index ec7bb01..cc8620a 100644
--- a/.idea/daedalus.iml
+++ b/.idea/daedalus.iml
@@ -6,6 +6,7 @@
+
diff --git a/Dockerfile b/Dockerfile
index a2a341d..c015f67 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM rust:1.68.2 as build
+FROM rust:1.79.0 as build
ENV PKG_CONFIG_ALLOW_CROSS=1
WORKDIR /usr/src/daedalus
diff --git a/daedalus/Cargo.toml b/daedalus/Cargo.toml
index 5c20d6a..1acc525 100644
--- a/daedalus/Cargo.toml
+++ b/daedalus/Cargo.toml
@@ -1,8 +1,8 @@
[package]
name = "daedalus"
-version = "0.1.27"
-authors = ["Jai A "]
-edition = "2018"
+version = "0.2.0"
+authors = ["Jai A "]
+edition = "2021"
license = "MIT"
description = "Utilities for querying and parsing Minecraft metadata"
repository = "https://github.com/modrinth/daedalus/"
@@ -14,12 +14,8 @@ readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
bytes = "1"
thiserror = "1.0"
-tokio = { version = "1", features = ["full"] }
-sha1 = { version = "0.6.1", features = ["std"]}
-bincode = {version = "2.0.0-rc.2", features = ["serde"], optional = true}
diff --git a/daedalus/src/lib.rs b/daedalus/src/lib.rs
index 51221ee..7900888 100644
--- a/daedalus/src/lib.rs
+++ b/daedalus/src/lib.rs
@@ -12,30 +12,6 @@ pub mod modded;
#[derive(thiserror::Error, Debug)]
/// An error type representing possible errors when fetching metadata
pub enum Error {
- #[error("Failed to validate file checksum at url {url} with hash {hash} after {tries} tries")]
- /// A checksum was failed to validate for a file
- ChecksumFailure {
- /// The checksum's hash
- hash: String,
- /// The URL of the file attempted to be downloaded
- url: String,
- /// The amount of tries that the file was downloaded until failure
- tries: u32,
- },
- /// There was an error while deserializing metadata
- #[error("Error while deserializing JSON")]
- SerdeError(#[from] serde_json::Error),
- /// There was a network error when fetching an object
- #[error("Unable to fetch {item}")]
- FetchError {
- /// The internal reqwest error
- inner: reqwest::Error,
- /// The item that was failed to be fetched
- item: String,
- },
- /// There was an error when managing async tasks
- #[error("Error while managing asynchronous tasks")]
- TaskError(#[from] tokio::task::JoinError),
/// Error while parsing input
#[error("{0}")]
ParseError(String),
@@ -124,100 +100,3 @@ pub fn get_path_from_artifact(artifact: &str) -> Result {
))
}
}
-
-/// Downloads a file from specified mirrors
-pub async fn download_file_mirrors(
- base: &str,
- mirrors: &[&str],
- sha1: Option<&str>,
-) -> Result {
- if mirrors.is_empty() {
- return Err(Error::ParseError("No mirrors provided!".to_string()));
- }
-
- for (index, mirror) in mirrors.iter().enumerate() {
- let result = download_file(&format!("{}{}", mirror, base), sha1).await;
-
- if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) {
- return result;
- }
- }
-
- unreachable!()
-}
-
-/// Downloads a file with retry and checksum functionality
-pub async fn download_file(
- url: &str,
- sha1: Option<&str>,
-) -> Result {
- let mut headers = reqwest::header::HeaderMap::new();
- if let Ok(header) = reqwest::header::HeaderValue::from_str(&format!(
- "modrinth/daedalus/{} (support@modrinth.com)",
- env!("CARGO_PKG_VERSION")
- )) {
- headers.insert(reqwest::header::USER_AGENT, header);
- }
- let client = reqwest::Client::builder()
- .tcp_keepalive(Some(std::time::Duration::from_secs(10)))
- .timeout(std::time::Duration::from_secs(15))
- .default_headers(headers)
- .build()
- .map_err(|err| Error::FetchError {
- inner: err,
- item: url.to_string(),
- })?;
-
- for attempt in 1..=4 {
- let result = client.get(url).send().await;
-
- match result {
- 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(Error::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(Error::FetchError {
- inner: err,
- item: url.to_string(),
- });
- }
- }
- Err(_) if attempt <= 3 => continue,
- Err(err) => {
- return Err(Error::FetchError {
- inner: err,
- item: url.to_string(),
- })
- }
- }
- }
-
- unreachable!()
-}
-
-/// Computes a checksum of the input bytes
-pub async fn get_hash(bytes: bytes::Bytes) -> Result {
- let hash =
- tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest())
- .await?;
-
- Ok(hash)
-}
diff --git a/daedalus/src/minecraft.rs b/daedalus/src/minecraft.rs
index 747fa8a..021c91a 100644
--- a/daedalus/src/minecraft.rs
+++ b/daedalus/src/minecraft.rs
@@ -1,16 +1,11 @@
use crate::modded::{Processor, SidedDataEntry};
-use crate::{download_file, Error};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
-#[cfg(feature = "bincode")]
-use bincode::{Decode, Encode};
-
/// The latest version of the format the model structs deserialize to
pub const CURRENT_FORMAT_VERSION: usize = 0;
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
/// The version type
@@ -37,7 +32,6 @@ impl VersionType {
}
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
/// A game version of Minecraft
@@ -50,26 +44,18 @@ pub struct Version {
/// A link to additional information about the version
pub url: String,
/// The latest time a file in this version was updated
- #[cfg_attr(feature = "bincode", bincode(with_serde))]
pub time: DateTime,
/// The time this version was released
- #[cfg_attr(feature = "bincode", bincode(with_serde))]
pub release_time: DateTime,
/// The SHA1 hash of the additional information about the version
pub sha1: String,
/// Whether the version supports the latest player safety features
pub compliance_level: u32,
#[serde(skip_serializing_if = "Option::is_none")]
- /// (Modrinth Provided) The link to the assets index for this version
- /// This is only available when using the Modrinth mirror
- pub assets_index_url: Option,
- #[serde(skip_serializing_if = "Option::is_none")]
- /// (Modrinth Provided) The SHA1 hash of the assets index for this version
- /// This is only available when using the Modrinth mirror
- pub assets_index_sha1: Option,
+ /// (Modrinth Provided) The SHA1 hash of the original unmodified Minecraft versions JSON
+ pub original_sha1: Option,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// The latest snapshot and release of the game
pub struct LatestVersion {
@@ -79,7 +65,6 @@ pub struct LatestVersion {
pub snapshot: String,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// Data of all game versions of Minecraft
pub struct VersionManifest {
@@ -93,16 +78,6 @@ pub struct VersionManifest {
pub const VERSION_MANIFEST_URL: &str =
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
-/// Fetches a version manifest from the specified URL. If no URL is specified, the default is used.
-pub async fn fetch_version_manifest(
- url: Option<&str>,
-) -> Result {
- Ok(serde_json::from_slice(
- &download_file(url.unwrap_or(VERSION_MANIFEST_URL), None).await?,
- )?)
-}
-
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
/// Information about the assets of the game
@@ -119,7 +94,6 @@ pub struct AssetIndex {
pub url: String,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
#[serde(rename_all = "snake_case")]
/// The type of download
@@ -136,7 +110,6 @@ pub enum DownloadType {
WindowsServer,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
/// Download information of a file
pub struct Download {
@@ -148,7 +121,6 @@ pub struct Download {
pub url: String,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// Download information of a library
pub struct LibraryDownload {
@@ -163,7 +135,6 @@ pub struct LibraryDownload {
pub url: String,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// A list of files that should be downloaded for libraries
pub struct LibraryDownloads {
@@ -176,7 +147,6 @@ pub struct LibraryDownloads {
pub classifiers: Option>,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
/// The action a rule can follow
@@ -187,7 +157,6 @@ pub enum RuleAction {
Disallow,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
#[serde(rename_all = "kebab-case")]
/// An enum representing the different types of operating systems
@@ -210,7 +179,6 @@ pub enum Os {
Unknown,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// A rule which depends on what OS the user is on
pub struct OsRule {
@@ -225,7 +193,6 @@ pub struct OsRule {
pub arch: Option,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// A rule which depends on the toggled features of the launcher
pub struct FeatureRule {
@@ -248,7 +215,6 @@ pub struct FeatureRule {
pub is_quick_play_realms: Option,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// A rule deciding whether a file is downloaded, an argument is used, etc.
pub struct Rule {
@@ -262,7 +228,6 @@ pub struct Rule {
pub features: Option,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// Information delegating the extraction of the library
pub struct LibraryExtract {
@@ -271,7 +236,6 @@ pub struct LibraryExtract {
pub exclude: Option>,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
/// Information about the java version the game needs
@@ -282,7 +246,6 @@ pub struct JavaVersion {
pub major_version: u32,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// A library which the game relies on to run
pub struct Library {
@@ -309,6 +272,9 @@ pub struct Library {
#[serde(default = "default_include_in_classpath")]
/// Whether the library should be included in the classpath at the game's launch
pub include_in_classpath: bool,
+ #[serde(default = "default_downloadable")]
+ /// Whether the library should be downloaded
+ pub downloadable: bool,
}
#[derive(Deserialize, Debug, Clone)]
@@ -397,8 +363,10 @@ pub fn merge_partial_library(
fn default_include_in_classpath() -> bool {
true
}
+fn default_downloadable() -> bool {
+ true
+}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
/// A container for an argument or multiple arguments
@@ -409,7 +377,6 @@ pub enum ArgumentValue {
Many(Vec),
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
/// A command line argument passed to a program
@@ -425,7 +392,6 @@ pub enum Argument {
},
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone, Copy)]
#[serde(rename_all = "snake_case")]
/// The type of argument
@@ -436,7 +402,6 @@ pub enum ArgumentType {
Jvm,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
/// Information about a version
@@ -481,16 +446,6 @@ pub struct VersionInfo {
pub processors: Option>,
}
-/// Fetches detailed information about a version from the manifest
-pub async fn fetch_version_info(
- version: &Version,
-) -> Result {
- Ok(serde_json::from_slice(
- &download_file(&version.url, Some(&version.sha1)).await?,
- )?)
-}
-
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
/// An asset of the game
pub struct Asset {
@@ -500,23 +455,9 @@ pub struct Asset {
pub size: u32,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
/// An index containing all assets the game needs
pub struct AssetsIndex {
/// A hashmap containing the filename (key) and asset (value)
pub objects: HashMap,
}
-
-/// Fetches the assets index from the version info
-pub async fn fetch_assets_index(
- version: &VersionInfo,
-) -> Result {
- Ok(serde_json::from_slice(
- &download_file(
- &version.asset_index.url,
- Some(&version.asset_index.sha1),
- )
- .await?,
- )?)
-}
diff --git a/daedalus/src/modded.rs b/daedalus/src/modded.rs
index e72eafe..c510fe7 100644
--- a/daedalus/src/modded.rs
+++ b/daedalus/src/modded.rs
@@ -1,5 +1,3 @@
-use crate::{download_file, Error};
-
use crate::minecraft::{
Argument, ArgumentType, Library, VersionInfo, VersionType,
};
@@ -7,9 +5,6 @@ use chrono::{DateTime, TimeZone, Utc};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
-#[cfg(feature = "bincode")]
-use bincode::{Decode, Encode};
-
/// The latest version of the format the fabric model structs deserialize to
pub const CURRENT_FABRIC_FORMAT_VERSION: usize = 0;
/// The latest version of the format the fabric model structs deserialize to
@@ -23,7 +18,6 @@ pub const CURRENT_NEOFORGE_FORMAT_VERSION: usize = 0;
pub const DUMMY_REPLACE_STRING: &str = "${modrinth.gameVersion}";
/// A data variable entry that depends on the side of the installation
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
pub struct SidedDataEntry {
/// The value on the client
@@ -43,7 +37,6 @@ where
.map_err(serde::de::Error::custom)
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
/// A partial version returned by fabric meta
@@ -53,11 +46,9 @@ pub struct PartialVersionInfo {
/// The version ID this partial version inherits from
pub inherits_from: String,
/// The time that the version was released
- #[cfg_attr(feature = "bincode", bincode(with_serde))]
#[serde(deserialize_with = "deserialize_date")]
pub release_time: DateTime,
/// The latest time a file in this version was updated
- #[cfg_attr(feature = "bincode", bincode(with_serde))]
#[serde(deserialize_with = "deserialize_date")]
pub time: DateTime,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -83,7 +74,6 @@ pub struct PartialVersionInfo {
}
/// A processor to be ran after downloading the files
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug)]
pub struct Processor {
/// Maven coordinates for the JAR library of this processor.
@@ -101,13 +91,6 @@ pub struct Processor {
pub sides: Option>,
}
-/// Fetches the version manifest of a game version's URL
-pub async fn fetch_partial_version(
- url: &str,
-) -> Result {
- Ok(serde_json::from_slice(&download_file(url, None).await?)?)
-}
-
/// Merges a partial version into a complete one
pub fn merge_partial_version(
partial: PartialVersionInfo,
@@ -154,15 +137,10 @@ pub fn merge_partial_version(
.libraries
.into_iter()
.chain(merge.libraries)
- .map(|x| Library {
- downloads: x.downloads,
- extract: x.extract,
- name: x.name.replace(DUMMY_REPLACE_STRING, &merge_id),
- url: x.url,
- natives: x.natives,
- rules: x.rules,
- checksums: x.checksums,
- include_in_classpath: x.include_in_classpath,
+ .map(|mut x| {
+ x.name = x.name.replace(DUMMY_REPLACE_STRING, &merge_id);
+
+ x
})
.collect::>(),
main_class: if let Some(main_class) = partial.main_class {
@@ -180,7 +158,6 @@ pub fn merge_partial_version(
}
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
/// A manifest containing information about a mod loader's versions
@@ -189,7 +166,6 @@ pub struct Manifest {
pub game_versions: Vec,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// A game version of Minecraft
pub struct Version {
@@ -201,7 +177,6 @@ pub struct Version {
pub loaders: Vec,
}
-#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// A version of a Minecraft mod loader
pub struct LoaderVersion {
@@ -212,8 +187,3 @@ pub struct LoaderVersion {
/// Whether the loader is stable or not
pub stable: bool,
}
-
-/// Fetches the manifest of a mod loader
-pub async fn fetch_manifest(url: &str) -> Result {
- Ok(serde_json::from_slice(&download_file(url, None).await?)?)
-}
diff --git a/daedalus_client/Cargo.toml b/daedalus_client/Cargo.toml
index 47f8aa9..ce8204f 100644
--- a/daedalus_client/Cargo.toml
+++ b/daedalus_client/Cargo.toml
@@ -1,8 +1,8 @@
[package]
name = "daedalus_client"
-version = "0.1.27"
-authors = ["Jai A "]
-edition = "2018"
+version = "0.2.0"
+authors = ["Jai A "]
+edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -11,16 +11,23 @@ daedalus = { path = "../daedalus" }
tokio = { version = "1", features = ["full"] }
futures = "0.3.25"
dotenvy = "0.15.6"
-log = "0.4.17"
-env_logger= "0.10.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-xml-rs = "0.6.0"
lazy_static = "1.4.0"
thiserror = "1.0"
-reqwest = "0.11.13"
-zip = "0.6.3"
+reqwest = { version = "0.12.5", features = ["stream", "json"] }
+async_zip = { version = "0.0.17", features = ["full"] }
semver = "1.0"
chrono = { version = "0.4", features = ["serde"] }
-bytes = "1.3.0"
-rust-s3 = "0.33.0"
+bytes = "1.6.0"
+rust-s3 = "0.34.0"
+dashmap = "5.5.3"
+sha1_smol = { version = "1.0.0", features = ["std"] }
+indexmap = { version = "2.2.6", features = ["serde"]}
+itertools = "0.13.0"
+tracing-error = "0.2.0"
+
+tracing = "0.1"
+tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+tracing-futures = { version = "0.2.5", features = ["futures", "tokio"] }
\ No newline at end of file
diff --git a/daedalus_client/library-patches.json b/daedalus_client/library-patches.json
index ebde4ed..268341a 100644
--- a/daedalus_client/library-patches.json
+++ b/daedalus_client/library-patches.json
@@ -1,14 +1,23 @@
[
{
- "_comment": "Only allow osx-arm64 for existing LWJGL 3.3.2",
+ "_comment": "Only allow osx-arm64 for existing LWJGL 3.3.2/3.3.3",
"match": [
+ "org.lwjgl:lwjgl-freetype-natives-macos-arm64:3.3.2",
"org.lwjgl:lwjgl-glfw-natives-macos-arm64:3.3.2",
"org.lwjgl:lwjgl-jemalloc-natives-macos-arm64:3.3.2",
"org.lwjgl:lwjgl-openal-natives-macos-arm64:3.3.2",
"org.lwjgl:lwjgl-opengl-natives-macos-arm64:3.3.2",
"org.lwjgl:lwjgl-stb-natives-macos-arm64:3.3.2",
"org.lwjgl:lwjgl-tinyfd-natives-macos-arm64:3.3.2",
- "org.lwjgl:lwjgl-natives-macos-arm64:3.3.2"
+ "org.lwjgl:lwjgl-natives-macos-arm64:3.3.2",
+ "org.lwjgl:lwjgl-freetype-natives-macos-arm64:3.3.3",
+ "org.lwjgl:lwjgl-glfw-natives-macos-arm64:3.3.3",
+ "org.lwjgl:lwjgl-jemalloc-natives-macos-arm64:3.3.3",
+ "org.lwjgl:lwjgl-openal-natives-macos-arm64:3.3.3",
+ "org.lwjgl:lwjgl-opengl-natives-macos-arm64:3.3.3",
+ "org.lwjgl:lwjgl-stb-natives-macos-arm64:3.3.3",
+ "org.lwjgl:lwjgl-tinyfd-natives-macos-arm64:3.3.3",
+ "org.lwjgl:lwjgl-natives-macos-arm64:3.3.3"
],
"override": {
"rules": [
@@ -22,15 +31,24 @@
}
},
{
- "_comment": "Only allow windows-arm64 for existing LWJGL 3.3.2",
+ "_comment": "Only allow windows-arm64 for existing LWJGL 3.3.2/3.3.3",
"match": [
+ "org.lwjgl:lwjgl-freetype-natives-windows-arm64:3.3.2",
"org.lwjgl:lwjgl-glfw-natives-windows-arm64:3.3.2",
"org.lwjgl:lwjgl-jemalloc-natives-windows-arm64:3.3.2",
"org.lwjgl:lwjgl-openal-natives-windows-arm64:3.3.2",
"org.lwjgl:lwjgl-opengl-natives-windows-arm64:3.3.2",
"org.lwjgl:lwjgl-stb-natives-windows-arm64:3.3.2",
"org.lwjgl:lwjgl-tinyfd-natives-windows-arm64:3.3.2",
- "org.lwjgl:lwjgl-natives-windows-arm64:3.3.2"
+ "org.lwjgl:lwjgl-natives-windows-arm64:3.3.2",
+ "org.lwjgl:lwjgl-freetype-natives-windows-arm64:3.3.3",
+ "org.lwjgl:lwjgl-glfw-natives-windows-arm64:3.3.3",
+ "org.lwjgl:lwjgl-jemalloc-natives-windows-arm64:3.3.3",
+ "org.lwjgl:lwjgl-openal-natives-windows-arm64:3.3.3",
+ "org.lwjgl:lwjgl-opengl-natives-windows-arm64:3.3.3",
+ "org.lwjgl:lwjgl-stb-natives-windows-arm64:3.3.3",
+ "org.lwjgl:lwjgl-tinyfd-natives-windows-arm64:3.3.3",
+ "org.lwjgl:lwjgl-natives-windows-arm64:3.3.3"
],
"override": {
"rules": [
@@ -165,7 +183,9 @@
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -202,12 +222,13 @@
"org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20131120",
"org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20131017",
"org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3",
- "org.lwjgl.lwjgl:lwjgl:2.9.1",
- "org.lwjgl.lwjgl:lwjgl:2.9.0"
+ "org.lwjgl.lwjgl:lwjgl:2.9.1"
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -268,12 +289,13 @@
"org.lwjgl.lwjgl:lwjgl_util:2.9.1-nightly-20131120",
"org.lwjgl.lwjgl:lwjgl_util:2.9.1-nightly-20131017",
"org.lwjgl.lwjgl:lwjgl_util:2.9.1-nightly-20130708-debug3",
- "org.lwjgl.lwjgl:lwjgl_util:2.9.1",
- "org.lwjgl.lwjgl:lwjgl_util:2.9.0"
+ "org.lwjgl.lwjgl:lwjgl_util:2.9.1"
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -335,8 +357,7 @@
"org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20131120",
"org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20131017",
"org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20130708-debug3",
- "org.lwjgl.lwjgl:lwjgl-platform:2.9.1",
- "org.lwjgl.lwjgl:lwjgl-platform:2.9.0"
+ "org.lwjgl.lwjgl:lwjgl-platform:2.9.1"
],
"override": {
"downloads": {
@@ -375,7 +396,9 @@
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -547,7 +570,9 @@
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -719,7 +744,9 @@
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -891,7 +918,9 @@
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -1063,7 +1092,9 @@
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -1235,7 +1266,9 @@
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -1407,7 +1440,9 @@
],
"override": {
"rules": [
-
+ {
+ "action": "allow"
+ },
{
"action": "disallow",
"os": {
@@ -1993,6 +2028,32 @@
}
]
},
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.2",
+ "match": [
+ "org.lwjgl:lwjgl-freetype:3.3.2"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "896e7d9b8f60d7273f3d491c69270afc67ece3ce",
+ "size": 1073374,
+ "url": "https://build.lwjgl.org/release/3.3.2/bin/lwjgl-freetype/lwjgl-freetype-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-freetype-natives-linux-arm64:3.3.2-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
{
"_comment": "Add linux-arm64 support for LWJGL 3.3.2",
"match": [
@@ -2175,6 +2236,32 @@
}
]
},
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.2",
+ "match": [
+ "org.lwjgl:lwjgl-freetype:3.3.2"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "b7f77ceb951182659fd400437272aa7e96709968",
+ "size": 924657,
+ "url": "https://build.lwjgl.org/release/3.3.2/bin/lwjgl-freetype/lwjgl-freetype-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-freetype-natives-linux-arm32:3.3.2-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
{
"_comment": "Add linux-arm32 support for LWJGL 3.3.2",
"match": [
@@ -2357,6 +2444,422 @@
}
]
},
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-freetype:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "498965aac06c4a0d42df1fbef6bacd05bde7f974",
+ "size": 1093516,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-freetype/lwjgl-freetype-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-freetype-natives-linux-arm64:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-glfw:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "492a0f11f85b85899a6568f07511160c1b87cd38",
+ "size": 122159,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-glfw/lwjgl-glfw-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-glfw-natives-linux-arm64:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-jemalloc:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "eff8b86798191192fe2cba2dc2776109f30c239d",
+ "size": 209315,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-jemalloc/lwjgl-jemalloc-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-jemalloc-natives-linux-arm64:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-openal:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "ad8f302118a65bb8d615f8a2a680db58fb8f835e",
+ "size": 592963,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-openal/lwjgl-openal-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-openal-natives-linux-arm64:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-opengl:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "2096f6b94b2d68745d858fbfe53aacf5f0c8074c",
+ "size": 58625,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-opengl/lwjgl-opengl-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-opengl-natives-linux-arm64:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-stb:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "ddc177afc2be1ee8d93684b11363b80589a13fe1",
+ "size": 207418,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-stb/lwjgl-stb-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-stb-natives-linux-arm64:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-tinyfd:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "2823a8c955c758d0954d282888075019ef99cec7",
+ "size": 43864,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-tinyfd/lwjgl-tinyfd-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-tinyfd-natives-linux-arm64:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm64 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "f35d8b6ffe1ac1e3a5eb1d4e33de80f044ad5fd8",
+ "size": 91294,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl/lwjgl-natives-linux-arm64.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-natives-linux-arm64:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm64"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-freetype:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "7dd3b1f751571adaf2c4dc882bc675a5d1e796e6",
+ "size": 942636,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-freetype/lwjgl-freetype-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-freetype-natives-linux-arm32:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-glfw:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "d9af485c32545b37dd5359b163161d42d7534dcf",
+ "size": 112560,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-glfw/lwjgl-glfw-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-glfw-natives-linux-arm32:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-jemalloc:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "109b6931880d02d4e65ced38928a16e41d19873e",
+ "size": 178324,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-jemalloc/lwjgl-jemalloc-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-jemalloc-natives-linux-arm32:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-openal:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "e1702aa09d20359d6cf5cb2999fa7685a785eca7",
+ "size": 505618,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-openal/lwjgl-openal-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-openal-natives-linux-arm32:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-opengl:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "dbba17fc5ac0985d14a57c11f9537617d67b9952",
+ "size": 59263,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-opengl/lwjgl-opengl-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-opengl-natives-linux-arm32:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-stb:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "1ae28ff044699ff29b0e980ffabd73fba8a664b3",
+ "size": 154931,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-stb/lwjgl-stb-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-stb-natives-linux-arm32:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl-tinyfd:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "c2a0a05c82c4b9f69ded0b6ad5f417addea78ce2",
+ "size": 49495,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl-tinyfd/lwjgl-tinyfd-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-tinyfd-natives-linux-arm32:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_comment": "Add linux-arm32 support for LWJGL 3.3.3",
+ "match": [
+ "org.lwjgl:lwjgl:3.3.3"
+ ],
+ "additionalLibraries": [
+ {
+ "downloads": {
+ "artifact": {
+ "sha1": "2075c51a80f0ef0f22ba616ba54007ac2b0debd4",
+ "size": 89565,
+ "url": "https://build.lwjgl.org/release/3.3.3/bin/lwjgl/lwjgl-natives-linux-arm32.jar"
+ }
+ },
+ "name": "org.lwjgl:lwjgl-natives-linux-arm32:3.3.3-lwjgl.1",
+ "rules": [
+ {
+ "action": "allow",
+ "os": {
+ "name": "linux-arm32"
+ }
+ }
+ ]
+ }
+ ]
+ },
{
"_comment": "Replace glfw from 3.3.1 with version from 3.3.2 to prevent stack smashing",
"match": [
diff --git a/daedalus_client/src/error.rs b/daedalus_client/src/error.rs
new file mode 100644
index 0000000..d4a0167
--- /dev/null
+++ b/daedalus_client/src/error.rs
@@ -0,0 +1,63 @@
+use tracing_error::InstrumentError;
+
+#[derive(thiserror::Error, Debug)]
+pub enum ErrorKind {
+ #[error("Daedalus Error: {0}")]
+ Daedalus(#[from] daedalus::Error),
+ #[error("Invalid input: {0}")]
+ InvalidInput(String),
+ #[error("Error while managing asynchronous tasks")]
+ TaskError(#[from] tokio::task::JoinError),
+ #[error("Error while deserializing JSON: {0}")]
+ SerdeJSON(#[from] serde_json::Error),
+ #[error("Error while deserializing XML: {0}")]
+ SerdeXML(#[from] serde_xml_rs::Error),
+ #[error("Failed to validate file checksum at url {url} with hash {hash} after {tries} tries")]
+ ChecksumFailure {
+ hash: String,
+ url: String,
+ tries: u32,
+ },
+ #[error("Unable to fetch {item}")]
+ Fetch { inner: reqwest::Error, item: String },
+ #[error("Error while uploading file to S3: {file}")]
+ S3 {
+ inner: s3::error::S3Error,
+ file: String,
+ },
+ #[error("Error acquiring semaphore: {0}")]
+ Acquire(#[from] tokio::sync::AcquireError),
+ #[error("Tracing error: {0}")]
+ Tracing(#[from] tracing::subscriber::SetGlobalDefaultError),
+ #[error("Zip error: {0}")]
+ Zip(#[from] async_zip::error::ZipError),
+}
+
+#[derive(Debug)]
+pub struct Error {
+ pub source: tracing_error::TracedError,
+}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(fmt, "{}", self.source)
+ }
+}
+
+impl> From for Error {
+ fn from(source: E) -> Self {
+ let error = Into::::into(source);
+
+ Self {
+ source: error.in_current_span(),
+ }
+ }
+}
+
+impl ErrorKind {
+ pub fn as_error(self) -> Error {
+ self.into()
+ }
+}
+
+pub type Result = core::result::Result;
diff --git a/daedalus_client/src/fabric.rs b/daedalus_client/src/fabric.rs
index 1a997da..8d1d943 100644
--- a/daedalus_client/src/fabric.rs
+++ b/daedalus_client/src/fabric.rs
@@ -1,372 +1,276 @@
-use crate::{download_file, format_url, upload_file_to_bucket, Error};
-use daedalus::minecraft::{Library, VersionManifest};
-use daedalus::modded::{
- LoaderVersion, Manifest, PartialVersionInfo, Version, DUMMY_REPLACE_STRING,
-};
-use serde::{Deserialize, Serialize};
+use crate::util::{download_file, fetch_json, format_url};
+use crate::{insert_mirrored_artifact, Error, MirrorArtifact, UploadFile};
+use daedalus::modded::{Manifest, PartialVersionInfo, DUMMY_REPLACE_STRING};
+use dashmap::DashMap;
+use serde::Deserialize;
use std::sync::Arc;
-use tokio::sync::{Mutex, RwLock, Semaphore};
+use tokio::sync::Semaphore;
-pub async fn retrieve_data(
- minecraft_versions: &VersionManifest,
- uploaded_files: &mut Vec,
+#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
+pub async fn fetch_fabric(
semaphore: Arc,
+ upload_files: &DashMap,
+ mirror_artifacts: &DashMap,
) -> Result<(), Error> {
- let list = fetch_fabric_versions(None, semaphore.clone()).await?;
- let old_manifest = daedalus::modded::fetch_manifest(&format_url(&format!(
- "fabric/v{}/manifest.json",
+ fetch(
daedalus::modded::CURRENT_FABRIC_FORMAT_VERSION,
- )))
+ "fabric",
+ "https://meta.fabricmc.net/v2",
+ "https://maven.fabricmc.net/",
+ semaphore,
+ upload_files,
+ mirror_artifacts,
+ )
.await
- .ok();
-
- let mut versions = if let Some(old_manifest) = old_manifest {
- old_manifest.game_versions
- } else {
- Vec::new()
- };
-
- let loaders_mutex = RwLock::new(Vec::new());
-
- {
- let mut loaders = loaders_mutex.write().await;
-
- for (index, loader) in list.loader.iter().enumerate() {
- if versions.iter().any(|x| {
- x.id == DUMMY_REPLACE_STRING
- && x.loaders.iter().any(|x| x.id == loader.version)
- }) {
- if index == 0 {
- loaders.push((
- Box::new(loader.stable),
- loader.version.clone(),
- Box::new(true),
- ))
- }
- } else {
- loaders.push((
- Box::new(loader.stable),
- loader.version.clone(),
- Box::new(false),
- ))
- }
- }
- }
-
- const DUMMY_GAME_VERSION: &str = "1.19.4-rc2";
-
- let loader_version_mutex = Mutex::new(Vec::new());
- let uploaded_files_mutex = Arc::new(Mutex::new(Vec::new()));
+}
- let loader_versions = futures::future::try_join_all(
- loaders_mutex.read().await.clone().into_iter().map(
- |(stable, loader, skip_upload)| async {
- let version = fetch_fabric_version(
- DUMMY_GAME_VERSION,
- &loader,
- semaphore.clone(),
- )
- .await?;
+#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
+pub async fn fetch_quilt(
+ semaphore: Arc,
+ upload_files: &DashMap,
+ mirror_artifacts: &DashMap,
+) -> Result<(), Error> {
+ fetch(
+ daedalus::modded::CURRENT_QUILT_FORMAT_VERSION,
+ "quilt",
+ "https://meta.quiltmc.org/v3",
+ "https://meta.quiltmc.org/",
+ semaphore,
+ upload_files,
+ mirror_artifacts,
+ )
+ .await
+}
- Ok::<(Box, String, PartialVersionInfo, Box), Error>(
- (stable, loader, version, skip_upload),
- )
- },
- ),
+#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
+async fn fetch(
+ format_version: usize,
+ mod_loader: &str,
+ meta_url: &str,
+ maven_url: &str,
+ semaphore: Arc,
+ upload_files: &DashMap,
+ mirror_artifacts: &DashMap,
+) -> Result<(), Error> {
+ let modrinth_manifest = fetch_json::(
+ &format_url(&format!("{mod_loader}/v{format_version}/manifest.json",)),
+ &semaphore,
+ )
+ .await
+ .ok();
+ let fabric_manifest = fetch_json::(
+ &format!("{meta_url}/versions"),
+ &semaphore,
)
.await?;
- let visited_artifacts_mutex = Arc::new(Mutex::new(Vec::new()));
- futures::future::try_join_all(loader_versions.into_iter()
- .map(
- |(stable, loader, version, skip_upload)| async {
- let libs = futures::future::try_join_all(
- version.libraries.into_iter().map(|mut lib| async {
- {
- let mut visited_assets =
- visited_artifacts_mutex.lock().await;
-
- if visited_assets.contains(&lib.name) {
- lib.name = lib.name.replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
- lib.url = Some(format_url("maven/"));
-
- return Ok(lib);
- } else {
- visited_assets.push(lib.name.clone())
- }
- }
-
- if lib.name.contains(DUMMY_GAME_VERSION) {
- lib.name = lib.name.replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
- futures::future::try_join_all(list.game.clone().into_iter().map(|game_version| async {
- let semaphore = semaphore.clone();
- let uploaded_files_mutex = uploaded_files_mutex.clone();
- let lib_name = lib.name.clone();
- let lib_url = lib.url.clone();
-
- async move {
- let artifact_path =
- daedalus::get_path_from_artifact(&lib_name.replace(DUMMY_REPLACE_STRING, &game_version.version))?;
-
- let artifact = download_file(
- &format!(
- "{}{}",
- lib_url.unwrap_or_else(|| {
- "https://maven.fabricmc.net/".to_string()
- }),
- artifact_path
- ),
- None,
- semaphore.clone(),
- )
- .await?;
-
- upload_file_to_bucket(
- format!("{}/{}", "maven", artifact_path),
- artifact.to_vec(),
- Some("application/java-archive".to_string()),
- &uploaded_files_mutex,
- semaphore.clone(),
- )
- .await?;
-
- Ok::<(), Error>(())
- }.await?;
-
- Ok::<(), Error>(())
- })).await?;
- lib.url = Some(format_url("maven/"));
-
- return Ok(lib);
- }
-
- let artifact_path =
- daedalus::get_path_from_artifact(&lib.name)?;
+ // We check Modrinth's fabric version manifest and compare if the fabric version exists in Modrinth's database
+ // We also check intermediary versions that are newly added to query
+ let (fetch_fabric_versions, fetch_intermediary_versions) =
+ if let Some(modrinth_manifest) = modrinth_manifest {
+ let (mut fetch_versions, mut fetch_intermediary_versions) =
+ (Vec::new(), Vec::new());
- let artifact = download_file(
- &format!(
- "{}{}",
- lib.url.unwrap_or_else(|| {
- "https://maven.fabricmc.net/".to_string()
- }),
- artifact_path
- ),
- None,
- semaphore.clone(),
- )
- .await?;
-
- lib.url = Some(format_url("maven/"));
-
- upload_file_to_bucket(
- format!("{}/{}", "maven", artifact_path),
- artifact.to_vec(),
- Some("application/java-archive".to_string()),
- &uploaded_files_mutex,
- semaphore.clone(),
- )
- .await?;
-
- Ok::(lib)
- }),
- )
- .await?;
-
- if async move {
- *skip_upload
- }.await {
- return Ok::<(), Error>(())
+ for version in &fabric_manifest.loader {
+ if !modrinth_manifest
+ .game_versions
+ .iter()
+ .any(|x| x.loaders.iter().any(|x| x.id == version.version))
+ {
+ fetch_versions.push(version);
+ }
}
-
- let version_path = format!(
- "fabric/v{}/versions/{}.json",
- daedalus::modded::CURRENT_FABRIC_FORMAT_VERSION,
- &loader
- );
-
- upload_file_to_bucket(
- version_path.clone(),
- serde_json::to_vec(&PartialVersionInfo {
- arguments: version.arguments,
- id: version
- .id
- .replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING),
- main_class: version.main_class,
- release_time: version.release_time,
- time: version.time,
- type_: version.type_,
- inherits_from: version
- .inherits_from
- .replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING),
- libraries: libs,
- minecraft_arguments: version.minecraft_arguments,
- processors: None,
- data: None,
- })?,
- Some("application/json".to_string()),
- &uploaded_files_mutex,
- semaphore.clone(),
- )
- .await?;
-
- {
- let mut loader_version_map = loader_version_mutex.lock().await;
- async move {
- loader_version_map.push(LoaderVersion {
- id: loader.to_string(),
- url: format_url(&version_path),
- stable: *stable,
- });
+ for version in &fabric_manifest.intermediary {
+ if !modrinth_manifest
+ .game_versions
+ .iter()
+ .any(|x| x.id == version.version)
+ && fabric_manifest
+ .game
+ .iter()
+ .any(|x| x.version == version.version)
+ {
+ fetch_intermediary_versions.push(version);
}
- .await;
}
- Ok::<(), Error>(())
- },
- ))
- .await?;
-
- let mut loader_version_mutex = loader_version_mutex.into_inner();
- if !loader_version_mutex.is_empty() {
- if let Some(version) =
- versions.iter_mut().find(|x| x.id == DUMMY_REPLACE_STRING)
- {
- version.loaders.append(&mut loader_version_mutex);
+ (fetch_versions, fetch_intermediary_versions)
} else {
- versions.push(Version {
- id: DUMMY_REPLACE_STRING.to_string(),
- stable: true,
- loaders: loader_version_mutex,
- });
- }
- }
+ (
+ fabric_manifest.loader.iter().collect(),
+ fabric_manifest.intermediary.iter().collect(),
+ )
+ };
- for version in &list.game {
- if !versions.iter().any(|x| x.id == version.version) {
- versions.push(Version {
- id: version.version.clone(),
- stable: version.stable,
- loaders: vec![],
- });
+ const DUMMY_GAME_VERSION: &str = "1.21";
+
+ if !fetch_intermediary_versions.is_empty() {
+ for x in &fetch_intermediary_versions {
+ insert_mirrored_artifact(
+ &x.maven,
+ maven_url.to_string(),
+ mirror_artifacts,
+ )?;
}
}
- versions.sort_by(|x, y| {
- minecraft_versions
- .versions
+ if !fetch_fabric_versions.is_empty() {
+ let fabric_version_manifest_urls = fetch_fabric_versions
.iter()
- .position(|z| x.id == z.id)
- .unwrap_or_default()
- .cmp(
- &minecraft_versions
- .versions
- .iter()
- .position(|z| y.id == z.id)
- .unwrap_or_default(),
- )
- });
-
- for version in &mut versions {
- version.loaders.sort_by(|x, y| {
- list.loader
- .iter()
- .position(|z| x.id == *z.version)
- .unwrap_or_default()
- .cmp(
- &list
- .loader
- .iter()
- .position(|z| y.id == z.version)
- .unwrap_or_default(),
+ .map(|x| {
+ format!(
+ "{}/versions/loader/{}/{}/profile/json",
+ meta_url, DUMMY_GAME_VERSION, x.version
)
- })
- }
+ })
+ .collect::>();
+ let fabric_version_manifests = futures::future::try_join_all(
+ fabric_version_manifest_urls
+ .iter()
+ .map(|x| download_file(x, None, &semaphore)),
+ )
+ .await?
+ .into_iter()
+ .map(|x| serde_json::from_slice(&x))
+ .collect::, serde_json::Error>>()?;
+
+ let patched_version_manifests = fabric_version_manifests
+ .into_iter()
+ .map(|mut version_info| {
+ for lib in &mut version_info.libraries {
+ let new_name = lib
+ .name
+ .replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
+ // If a library is not intermediary, we add it to mirror artifacts to be mirrored
+ if lib.name == new_name {
+ insert_mirrored_artifact(
+ &new_name,
+ lib.url
+ .clone()
+ .unwrap_or_else(|| maven_url.to_string()),
+ mirror_artifacts,
+ )?;
+ } else {
+ lib.name = new_name;
+ }
- upload_file_to_bucket(
- format!(
- "fabric/v{}/manifest.json",
- daedalus::modded::CURRENT_FABRIC_FORMAT_VERSION,
- ),
- serde_json::to_vec(&Manifest {
- game_versions: versions,
- })?,
- Some("application/json".to_string()),
- &uploaded_files_mutex,
- semaphore,
- )
- .await?;
+ lib.url = Some(format_url("maven/"));
+ }
- if let Ok(uploaded_files_mutex) = Arc::try_unwrap(uploaded_files_mutex) {
- uploaded_files.extend(uploaded_files_mutex.into_inner());
+ version_info.id = version_info
+ .id
+ .replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
+ version_info.inherits_from = version_info
+ .inherits_from
+ .replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
+
+ Ok(version_info)
+ })
+ .collect::, Error>>()?;
+ let serialized_version_manifests = patched_version_manifests
+ .iter()
+ .map(|x| serde_json::to_vec(x).map(bytes::Bytes::from))
+ .collect::, serde_json::Error>>()?;
+
+ serialized_version_manifests
+ .into_iter()
+ .enumerate()
+ .for_each(|(index, bytes)| {
+ let loader = fetch_fabric_versions[index];
+
+ let version_path = format!(
+ "{mod_loader}/v{format_version}/versions/{}.json",
+ loader.version
+ );
+
+ upload_files.insert(
+ version_path,
+ UploadFile {
+ file: bytes,
+ content_type: Some("application/json".to_string()),
+ },
+ );
+ });
}
- Ok(())
-}
-
-const FABRIC_META_URL: &str = "https://meta.fabricmc.net/v2";
+ if !fetch_fabric_versions.is_empty()
+ || !fetch_intermediary_versions.is_empty()
+ {
+ let fabric_manifest_path =
+ format!("{mod_loader}/v{format_version}/manifest.json",);
+
+ let loader_versions = daedalus::modded::Version {
+ id: DUMMY_REPLACE_STRING.to_string(),
+ stable: true,
+ loaders: fabric_manifest
+ .loader
+ .into_iter()
+ .map(|x| {
+ let version_path = format!(
+ "{mod_loader}/v{format_version}/versions/{}.json",
+ x.version,
+ );
+
+ daedalus::modded::LoaderVersion {
+ id: x.version,
+ url: format_url(&version_path),
+ stable: x.stable,
+ }
+ })
+ .collect(),
+ };
+
+ let manifest = daedalus::modded::Manifest {
+ game_versions: std::iter::once(loader_versions)
+ .chain(fabric_manifest.game.into_iter().map(|x| {
+ daedalus::modded::Version {
+ id: x.version,
+ stable: x.stable,
+ loaders: vec![],
+ }
+ }))
+ .collect(),
+ };
+
+ upload_files.insert(
+ fabric_manifest_path,
+ UploadFile {
+ file: bytes::Bytes::from(serde_json::to_vec(&manifest)?),
+ content_type: Some("application/json".to_string()),
+ },
+ );
+ }
-async fn fetch_fabric_version(
- version_number: &str,
- loader_version: &str,
- semaphore: Arc,
-) -> Result {
- Ok(serde_json::from_slice(
- &download_file(
- &format!(
- "{}/versions/loader/{}/{}/profile/json",
- FABRIC_META_URL, version_number, loader_version
- ),
- None,
- semaphore,
- )
- .await?,
- )?)
+ Ok(())
}
-#[derive(Serialize, Deserialize, Debug, Clone)]
-/// Versions of fabric components
+#[derive(Deserialize, Debug, Clone)]
struct FabricVersions {
- /// Versions of Minecraft that fabric supports
- pub game: Vec,
- /// Available versions of the fabric loader
pub loader: Vec,
+ pub game: Vec,
+ #[serde(alias = "hashed")]
+ pub intermediary: Vec,
}
-#[derive(Serialize, Deserialize, Debug, Clone)]
-/// A version of Minecraft that fabric supports
-struct FabricGameVersion {
- /// The version number of the game
+#[derive(Deserialize, Debug, Clone)]
+struct FabricLoaderVersion {
+ // pub separator: String,
+ // pub build: u32,
+ // pub maven: String,
pub version: String,
- /// Whether the Minecraft version is stable or not
+ #[serde(default)]
pub stable: bool,
}
-#[derive(Serialize, Deserialize, Debug, Clone)]
-/// A version of the fabric loader
-struct FabricLoaderVersion {
- /// The separator to get the build number
- pub separator: String,
- /// The build number
- pub build: u32,
- /// The maven artifact
+#[derive(Deserialize, Debug, Clone)]
+struct FabricIntermediaryVersion {
pub maven: String,
- /// The version number of the fabric loader
pub version: String,
- /// Whether the loader is stable or not
- pub stable: bool,
}
-/// Fetches the list of fabric versions
-async fn fetch_fabric_versions(
- url: Option<&str>,
- semaphore: Arc,
-) -> Result {
- Ok(serde_json::from_slice(
- &download_file(
- url.unwrap_or(&*format!("{}/versions", FABRIC_META_URL)),
- None,
- semaphore,
- )
- .await?,
- )?)
+
+#[derive(Deserialize, Debug, Clone)]
+struct FabricGameVersion {
+ pub version: String,
+ pub stable: bool,
}
diff --git a/daedalus_client/src/forge.rs b/daedalus_client/src/forge.rs
index fc3bf2f..99892f2 100644
--- a/daedalus_client/src/forge.rs
+++ b/daedalus_client/src/forge.rs
@@ -1,650 +1,766 @@
-use crate::{
- download_file, download_file_mirrors, format_url, upload_file_to_bucket,
- Error,
-};
+use crate::util::{download_file, fetch_json, fetch_xml, format_url};
+use crate::{insert_mirrored_artifact, Error, MirrorArtifact, UploadFile};
use chrono::{DateTime, Utc};
-use daedalus::minecraft::{
- Argument, ArgumentType, Library, VersionManifest, VersionType,
-};
-use daedalus::modded::{
- LoaderVersion, Manifest, PartialVersionInfo, Processor, SidedDataEntry,
-};
-use lazy_static::lazy_static;
-use log::info;
-use semver::{Version, VersionReq};
-use serde::{Deserialize, Serialize};
+use daedalus::get_path_from_artifact;
+use daedalus::modded::PartialVersionInfo;
+use dashmap::DashMap;
+use futures::io::Cursor;
+use indexmap::IndexMap;
+use itertools::Itertools;
+use serde::de::DeserializeOwned;
+use serde::Deserialize;
use std::collections::HashMap;
-use std::io::Read;
use std::sync::Arc;
-use std::time::Instant;
-use tokio::sync::{Mutex, Semaphore};
-
-lazy_static! {
- static ref FORGE_MANIFEST_V1_QUERY: VersionReq =
- VersionReq::parse(">=8.0.684, <23.5.2851").unwrap();
- static ref FORGE_MANIFEST_V2_QUERY_P1: VersionReq =
- VersionReq::parse(">=23.5.2851, <31.2.52").unwrap();
- static ref FORGE_MANIFEST_V2_QUERY_P2: VersionReq =
- VersionReq::parse(">=32.0.1, <37.0.0").unwrap();
- static ref FORGE_MANIFEST_V3_QUERY: VersionReq =
- VersionReq::parse(">=37.0.0").unwrap();
-}
+use tokio::sync::Semaphore;
-pub async fn retrieve_data(
- minecraft_versions: &VersionManifest,
- uploaded_files: &mut Vec,
+#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
+pub async fn fetch_forge(
semaphore: Arc,
+ upload_files: &DashMap,
+ mirror_artifacts: &DashMap,
) -> Result<(), Error> {
- let maven_metadata = fetch_maven_metadata(None, semaphore.clone()).await?;
- let old_manifest = daedalus::modded::fetch_manifest(&format_url(&format!(
- "forge/v{}/manifest.json",
+ let forge_manifest = fetch_json::>>(
+ "https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json",
+ &semaphore,
+ )
+ .await?;
+
+ let mut format_version = 0;
+
+ let forge_versions = forge_manifest.into_iter().flat_map(|(game_version, versions)| versions.into_iter().map(|loader_version| {
+ // Forge versions can be in these specific formats:
+ // 1.10.2-12.18.1.2016-failtests
+ // 1.9-12.16.0.1886
+ // 1.9-12.16.0.1880-1.9
+ // 1.14.4-28.1.30
+ // This parses them to get the actual Forge version. Ex: 1.15.2-31.1.87 -> 31.1.87
+ let version_split = loader_version.split('-').nth(1).unwrap_or(&loader_version).to_string();
+
+ // Forge has 3 installer formats:
+ // - Format 0 (Unsupported ATM): Forge Legacy (pre-1.5.2). Uses Binary Patch method to install
+ // To install: Download patch, download minecraft client JAR. Combine patch and client JAR and delete META-INF/.
+ // (pre-1.3-2) Client URL: https://maven.minecraftforge.net/net/minecraftforge/forge/{version}/forge-{version}-client.zip
+ // (pre-1.3-2) Server URL: https://maven.minecraftforge.net/net/minecraftforge/forge/{version}/forge-{version}-server.zip
+ // (1.3-2-onwards) Universal URL: https://maven.minecraftforge.net/net/minecraftforge/forge/{version}/forge-{version}-universal.zip
+ // - Format 1: Forge Installer Legacy (1.5.2-1.12.2ish)
+ // To install: Extract install_profile.json from archive. "versionInfo" is the profile's version info. Convert it to the modern format
+ // Extract forge library from archive. Path is at "install"."path".
+ // - Format 2: Forge Installer Modern
+ // To install: Extract install_profile.json from archive. Extract version.json from archive. Combine the two and extract all libraries
+ // which are embedded into the installer JAR.
+ // Then upload. The launcher will need to run processors!
+ if format_version != 1 && &*version_split == "7.8.0.684" {
+ format_version = 1;
+ } else if format_version != 2 && &*version_split == "14.23.5.2851" {
+ format_version = 2;
+ }
+
+ ForgeVersion {
+ format_version,
+ installer_url: format!("https://maven.minecraftforge.net/net/minecraftforge/forge/{0}/forge-{0}-installer.jar", loader_version),
+ raw: loader_version,
+ loader_version: version_split,
+ game_version: game_version.clone(),
+ }
+ })
+ .collect::>())
+ // TODO: support format version 0 (see above)
+ .filter(|x| x.format_version != 0)
+ .filter(|x| {
+ // These following Forge versions are broken and cannot be installed
+ const BLACKLIST : &[&str] = &[
+ // Not supported due to `data` field being `[]` even though the type is a map
+ "1.12.2-14.23.5.2851",
+ // Malformed Archives
+ "1.6.1-8.9.0.749",
+ "1.6.1-8.9.0.751",
+ "1.6.4-9.11.1.960",
+ "1.6.4-9.11.1.961",
+ "1.6.4-9.11.1.963",
+ "1.6.4-9.11.1.964",
+ ];
+
+ !BLACKLIST.contains(&&*x.raw)
+ })
+ .collect::>();
+
+ fetch(
daedalus::modded::CURRENT_FORGE_FORMAT_VERSION,
- )))
+ "forge",
+ "https://maven.minecraftforge.net/",
+ forge_versions,
+ semaphore,
+ upload_files,
+ mirror_artifacts,
+ )
.await
- .ok();
+}
+
+#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
+pub async fn fetch_neo(
+ semaphore: Arc,
+ upload_files: &DashMap,
+ mirror_artifacts: &DashMap,
+) -> Result<(), Error> {
+ #[derive(Debug, Deserialize)]
+ struct Metadata {
+ versioning: Versioning,
+ }
- let old_versions =
- Arc::new(Mutex::new(if let Some(old_manifest) = old_manifest {
- old_manifest.game_versions
+ #[derive(Debug, Deserialize)]
+ struct Versioning {
+ versions: Versions,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct Versions {
+ version: Vec,
+ }
+
+ let forge_versions = fetch_xml::(
+ "https://maven.neoforged.net/net/neoforged/forge/maven-metadata.xml",
+ &semaphore,
+ )
+ .await?;
+ let neo_versions = fetch_xml::(
+ "https://maven.neoforged.net/net/neoforged/neoforge/maven-metadata.xml",
+ &semaphore,
+ )
+ .await?;
+
+ let parsed_versions = forge_versions.versioning.versions.version.into_iter().map(|loader_version| {
+ // NeoForge Forge versions can be in these specific formats:
+ // 1.20.1-47.1.74
+ // 47.1.82
+ // This parses them to get the actual Forge version. Ex: 1.20.1-47.1.74 -> 47.1.74
+ let version_split = loader_version.split('-').nth(1).unwrap_or(&loader_version).to_string();
+
+ Ok(ForgeVersion {
+ format_version: 2,
+ installer_url: format!("https://maven.neoforged.net/net/neoforged/forge/{0}/forge-{0}-installer.jar", loader_version),
+ raw: loader_version,
+ loader_version: version_split,
+ game_version: "1.20.1".to_string(), // All NeoForge Forge versions are for 1.20.1
+ })
+ }).chain(neo_versions.versioning.versions.version.into_iter().map(|loader_version| {
+ let mut parts = loader_version.split('.');
+
+ // NeoForge Forge versions are in this format: 20.2.29-beta, 20.6.119
+ // Where the first number is the major MC version, the second is the minor MC version, and the third is the NeoForge version
+ let major = parts.next().ok_or_else(
+ || crate::ErrorKind::InvalidInput(format!("Unable to find major game version for NeoForge {loader_version}"))
+ )?;
+
+ let minor = parts.next().ok_or_else(
+ || crate::ErrorKind::InvalidInput(format!("Unable to find minor game version for NeoForge {loader_version}"))
+ )?;
+
+ let game_version = if minor == "0" {
+ format!("1.{major}")
} else {
- Vec::new()
- }));
+ format!("1.{major}.{minor}")
+ };
+
+ Ok(ForgeVersion {
+ format_version: 2,
+ installer_url: format!("https://maven.neoforged.net/net/neoforged/neoforge/{0}/neoforge-{0}-installer.jar", loader_version),
+ loader_version: loader_version.clone(),
+ raw: loader_version,
+ game_version,
+ })
+ }))
+ .collect::, Error>>()?
+ .into_iter()
+ .filter(|x| {
+ // These following Forge versions are broken and cannot be installed
+ const BLACKLIST : &[&str] = &[
+ // Unreachable / 404
+ "1.20.1-47.1.7",
+ "47.1.82",
+ ];
+
+ !BLACKLIST.contains(&&*x.raw)
+ }).collect();
+
+ fetch(
+ daedalus::modded::CURRENT_NEOFORGE_FORMAT_VERSION,
+ "neo",
+ "https://maven.neoforged.net/",
+ parsed_versions,
+ semaphore,
+ upload_files,
+ mirror_artifacts,
+ )
+ .await
+}
- let versions = Arc::new(Mutex::new(Vec::new()));
+#[tracing::instrument(skip(
+ forge_versions,
+ semaphore,
+ upload_files,
+ mirror_artifacts
+))]
+async fn fetch(
+ format_version: usize,
+ mod_loader: &str,
+ maven_url: &str,
+ forge_versions: Vec,
+ semaphore: Arc,
+ upload_files: &DashMap,
+ mirror_artifacts: &DashMap,
+) -> Result<(), Error> {
+ let modrinth_manifest = fetch_json::(
+ &format_url(&format!("{mod_loader}/v{format_version}/manifest.json",)),
+ &semaphore,
+ )
+ .await
+ .ok();
- let visited_assets_mutex = Arc::new(Mutex::new(Vec::new()));
- let uploaded_files_mutex = Arc::new(Mutex::new(Vec::new()));
+ let fetch_versions = if let Some(modrinth_manifest) = modrinth_manifest {
+ let mut fetch_versions = Vec::new();
- let mut version_futures = Vec::new();
+ for version in &forge_versions {
+ if !modrinth_manifest.game_versions.iter().any(|x| {
+ x.id == version.game_version
+ && x.loaders.iter().any(|x| x.id == version.loader_version)
+ }) {
+ fetch_versions.push(version);
+ }
+ }
- for (minecraft_version, loader_versions) in maven_metadata.clone() {
- let mut loaders = Vec::new();
+ fetch_versions
+ } else {
+ forge_versions.iter().collect()
+ };
- for loader_version_full in loader_versions {
- let loader_version = loader_version_full.split('-').nth(1);
+ if !fetch_versions.is_empty() {
+ let forge_installers = futures::future::try_join_all(
+ fetch_versions
+ .iter()
+ .map(|x| download_file(&x.installer_url, None, &semaphore)),
+ )
+ .await?;
- if let Some(loader_version_raw) = loader_version {
- // This is a dirty hack to get around Forge not complying with SemVer, but whatever
- // Most of this is a hack anyways :(
- // Works for all forge versions!
- let split =
- loader_version_raw.split('.').collect::>();
- let loader_version = if split.len() >= 4 {
- if split[0].parse::().unwrap_or(0) < 6 {
- format!("{}.{}.{}", split[0], split[1], split[3])
- } else {
- format!("{}.{}.{}", split[1], split[2], split[3])
- }
- } else {
- loader_version_raw.to_string()
- };
+ #[tracing::instrument(skip(raw, upload_files, mirror_artifacts))]
+ async fn read_forge_installer(
+ raw: bytes::Bytes,
+ loader: &ForgeVersion,
+ maven_url: &str,
+ upload_files: &DashMap,
+ mirror_artifacts: &DashMap,
+ ) -> Result {
+ tracing::trace!(
+ "Reading forge installer for {}",
+ loader.loader_version
+ );
+ type ZipFileReader = async_zip::base::read::seek::ZipFileReader<
+ Cursor,
+ >;
+
+ let cursor = Cursor::new(raw);
+ let mut zip = ZipFileReader::new(cursor).await?;
+
+ #[tracing::instrument(skip(zip))]
+ async fn read_file(
+ zip: &mut ZipFileReader,
+ file_name: &str,
+ ) -> Result