diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 000000000..d8e956166 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/daedalus/src/fabric.rs b/daedalus/src/fabric.rs deleted file mode 100644 index d63705a73..000000000 --- a/daedalus/src/fabric.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::minecraft::{Argument, ArgumentType, Library, VersionInfo, VersionType}; -use crate::{download_file, Error}; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// The latest version of the format the model structs deserialize to -pub const CURRENT_FORMAT_VERSION: usize = 0; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -/// A partial version returned by fabric meta -pub struct PartialVersionInfo { - /// The version ID of the version - pub id: String, - /// The version ID this partial version inherits from - pub inherits_from: String, - /// The time that the version was released - pub release_time: DateTime, - /// The latest time a file in this version was updated - pub time: DateTime, - /// The classpath to the main class to launch the game - pub main_class: Option, - /// Arguments passed to the game or JVM - pub arguments: Option>>, - /// Libraries that the version depends on - pub libraries: Vec, - #[serde(rename = "type")] - /// The type of version - pub type_: VersionType, -} - -/// Merges a partial version into a complete one -pub fn merge_partial_version(partial: PartialVersionInfo, merge: VersionInfo) -> VersionInfo { - VersionInfo { - arguments: if let Some(partial_args) = partial.arguments { - if let Some(merge_args) = merge.arguments { - Some(partial_args.into_iter().chain(merge_args).collect()) - } else { - Some(partial_args) - } - } else { - merge.arguments - }, - asset_index: merge.asset_index, - assets: merge.assets, - downloads: merge.downloads, - id: merge.id, - libraries: partial - .libraries - .into_iter() - .chain(merge.libraries) - .collect::>(), - main_class: if let Some(main_class) = partial.main_class { - main_class - } else { - merge.main_class - }, - minecraft_arguments: merge.minecraft_arguments, - minimum_launcher_version: merge.minimum_launcher_version, - release_time: partial.release_time, - time: partial.time, - type_: partial.type_, - } -} - -/// The default servers for fabric meta -pub const FABRIC_META_URL: &str = "https://meta.fabricmc.net/v2"; - -/// Fetches the manifest of a fabric loader version and game version -pub async fn fetch_fabric_version( - version_number: &str, - loader_version: &str, -) -> Result { - Ok(serde_json::from_slice( - &download_file( - &*format!( - "{}/versions/loader/{}/{}/profile/json", - FABRIC_META_URL, version_number, loader_version - ), - None, - ) - .await?, - )?) -} - -/// Fetches the manifest of a game version's URL -pub async fn fetch_fabric_game_version(url: &str) -> Result { - Ok(serde_json::from_slice(&download_file(url, None).await?)?) -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -/// Versions of fabric components -pub struct FabricVersions { - /// Versions of Minecraft that fabric supports - pub game: Vec, - /// Available versions of the fabric loader - pub loader: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -/// A version of Minecraft that fabric supports -pub struct FabricGameVersion { - /// The version number of the game - pub version: String, - /// Whether the Minecraft version is stable or not - pub stable: bool, - /// (Modrinth Provided) The URLs to download this version's profile with a loader - /// The key of the map is the loader version, and the value is the URL - pub urls: Option>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -/// A version of the fabric loader -pub struct FabricLoaderVersion { - /// The separator to get the build number - pub separator: String, - /// The build number - pub build: u32, - /// The maven artifact - 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 -pub async fn fetch_fabric_versions(url: Option<&str>) -> Result { - Ok(serde_json::from_slice( - &download_file( - url.unwrap_or(&*format!("{}/versions", FABRIC_META_URL)), - None, - ) - .await?, - )?) -} diff --git a/daedalus/src/forge.rs b/daedalus/src/forge.rs deleted file mode 100644 index 4381f0307..000000000 --- a/daedalus/src/forge.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::{download_file, Error}; - -use std::collections::HashMap; - -/// The latest version of the format the model structs deserialize to -pub const CURRENT_FORMAT_VERSION: usize = 0; - -const DEFAULT_MAVEN_METADATA_URL: &str = - "https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json"; - -/// Fetches the forge maven metadata from the specified URL. If no URL is specified, the default is used. -/// Returns a hashmap specifying the versions of the forge mod loader -/// The hashmap key is a Minecraft version, and the value is the loader versions that work on -/// the specified Minecraft version -pub async fn fetch_maven_metadata( - url: Option<&str>, -) -> Result>, Error> { - Ok(serde_json::from_slice( - &download_file(url.unwrap_or(DEFAULT_MAVEN_METADATA_URL), None).await?, - )?) -} diff --git a/daedalus/src/lib.rs b/daedalus/src/lib.rs index 4f3147553..a4ac00599 100644 --- a/daedalus/src/lib.rs +++ b/daedalus/src/lib.rs @@ -4,12 +4,10 @@ #![warn(missing_docs, unused_import_braces, missing_debug_implementations)] -/// Models and methods for fetching metadata for the Fabric mod loader -pub mod fabric; -/// Models and methods for fetching metadata for the Forge mod loader -pub mod forge; /// Models and methods for fetching metadata for Minecraft pub mod minecraft; +/// Models and methods for fetching metadata for Minecraft mod loaders +pub mod modded; #[derive(thiserror::Error, Debug)] /// An error type representing possible errors when fetching metadata diff --git a/daedalus/src/modded.rs b/daedalus/src/modded.rs new file mode 100644 index 000000000..cff051fa6 --- /dev/null +++ b/daedalus/src/modded.rs @@ -0,0 +1,115 @@ +use crate::{download_file, Error}; + +use crate::minecraft::{Argument, ArgumentType, Library, VersionInfo, VersionType}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// 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 +pub const CURRENT_FORGE_FORMAT_VERSION: usize = 0; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +/// A partial version returned by fabric meta +pub struct PartialVersionInfo { + /// The version ID of the version + pub id: String, + /// The version ID this partial version inherits from + pub inherits_from: String, + /// The time that the version was released + pub release_time: DateTime, + /// The latest time a file in this version was updated + pub time: DateTime, + /// The classpath to the main class to launch the game + pub main_class: Option, + /// Arguments passed to the game or JVM + pub arguments: Option>>, + /// Libraries that the version depends on + pub libraries: Vec, + #[serde(rename = "type")] + /// The type of version + pub type_: VersionType, +} + +/// 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, merge: VersionInfo) -> VersionInfo { + VersionInfo { + arguments: if let Some(partial_args) = partial.arguments { + if let Some(merge_args) = merge.arguments { + Some(partial_args.into_iter().chain(merge_args).collect()) + } else { + Some(partial_args) + } + } else { + merge.arguments + }, + asset_index: merge.asset_index, + assets: merge.assets, + downloads: merge.downloads, + id: merge.id, + libraries: partial + .libraries + .into_iter() + .chain(merge.libraries) + .collect::>(), + main_class: if let Some(main_class) = partial.main_class { + main_class + } else { + merge.main_class + }, + minecraft_arguments: merge.minecraft_arguments, + minimum_launcher_version: merge.minimum_launcher_version, + release_time: partial.release_time, + time: partial.time, + type_: partial.type_, + } +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +/// A manifest containing information about a mod loader's versions +pub struct Manifest { + /// The game versions the mod loader supports + pub game_versions: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)] +#[serde(rename_all = "camelCase")] +/// The version type of the loader +pub enum LoaderType { + /// The latest type is for experimental loader versions that may not be ready for normal use + Latest, + /// The stable type is for the most stable but recent loader version. For the forge mod loader, + /// this is never used + Stable, +} + +#[derive(Serialize, Deserialize, Debug)] +/// A game version of Minecraft +pub struct Version { + /// The minecraft version ID + pub id: String, + /// A map that contains loader versions for the game version + pub loaders: HashMap, +} + +#[derive(Serialize, Deserialize, Debug)] +/// A version of a Minecraft mod loader +pub struct LoaderVersion { + /// The version ID of the loader + pub id: String, + /// The URL of the version's manifest + pub url: String, +} + +/// 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/src/fabric.rs b/daedalus_client/src/fabric.rs index 809ec832e..63f637f40 100644 --- a/daedalus_client/src/fabric.rs +++ b/daedalus_client/src/fabric.rs @@ -1,33 +1,47 @@ use crate::{format_url, upload_file_to_bucket, Error}; -use daedalus::fabric::PartialVersionInfo; +use daedalus::download_file; use daedalus::minecraft::Library; -use std::collections::HashMap; -use std::sync::{Arc}; -use std::time::{Duration, Instant}; +use daedalus::modded::{LoaderType, LoaderVersion, Manifest, PartialVersionInfo, Version}; use futures::lock::Mutex; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; pub async fn retrieve_data() -> Result<(), Error> { - let mut list = daedalus::fabric::fetch_fabric_versions(None).await?; + let mut list = fetch_fabric_versions(None).await?; + let old_manifest = daedalus::modded::fetch_manifest(&*format!( + "fabric/v{}/manifest.json", + daedalus::modded::CURRENT_FABRIC_FORMAT_VERSION, + )) + .await + .ok(); + + let versions = Arc::new(Mutex::new(if let Some(old_manifest) = old_manifest { + old_manifest.game_versions + } else { + Vec::new() + })); if let Some(latest) = list.loader.get(0) { - let loaders_mutex = Arc::new(Mutex::new(Vec::new())); + let loaders_mutex = Arc::new(Mutex::new(HashMap::new())); let visited_artifacts_mutex = Arc::new(Mutex::new(Vec::new())); { let mut loaders = loaders_mutex.lock().await; - loaders.push(latest.version.clone()); + loaders.insert(LoaderType::Latest, latest.version.clone()); if !latest.stable { if let Some(stable) = list.loader.iter().find(|x| x.stable) { - loaders.push(stable.version.clone()); + loaders.insert(LoaderType::Stable, stable.version.clone()); } } list.loader = list .loader .into_iter() - .filter(|x| loaders.contains(&x.version)) + .filter(|x| loaders.values().any(|val| val == &x.version)) .collect(); } @@ -35,105 +49,127 @@ pub async fn retrieve_data() -> Result<(), Error> { for game_version in list.game.iter_mut() { let visited_artifacts_mutex = Arc::clone(&visited_artifacts_mutex); - let game_version_mutex = Mutex::new(HashMap::new()); let loaders_mutex = Arc::clone(&loaders_mutex); + + let versions_mutex = Arc::clone(&versions); version_futures.push(async move { - let versions = futures::future::try_join_all(loaders_mutex.lock().await.clone().into_iter().map( - |loader| async { - let version = daedalus::fabric::fetch_fabric_version( - &*game_version.version, - &*loader, - ) - .await - .expect(&*format!("{}, {}", game_version.version, loader)); + let loader_version_mutex = Mutex::new(HashMap::new()); - Ok::<(String, PartialVersionInfo), Error>((loader, version)) - }, - )) - .await?; - - futures::future::try_join_all(versions.into_iter().map( - |(loader, version)| async { - let libs = futures::future::try_join_all( - version.libraries.into_iter().map(|mut lib| async { + let versions = + futures::future::try_join_all( + loaders_mutex.lock().await.clone().into_iter().map( + |(type_, loader)| async { { - let mut visited_assets = visited_artifacts_mutex.lock().await; - - if visited_assets.contains(&lib.name) { - lib.url = Some(format_url("maven/")); - - return Ok(lib); - } else { - visited_assets.push(lib.name.clone()) + if versions_mutex.lock().await.iter().any(|x| { + x.id == game_version.version + && x.loaders + .get(&type_) + .map(|x| x.id == loader) + .unwrap_or(false) + }) { + return Ok(None); } } - let artifact_path = - daedalus::get_path_from_artifact(&*lib.name)?; + let version = + fetch_fabric_version(&*game_version.version, &*loader).await?; - let artifact = daedalus::download_file( - &*format!( - "{}{}", - lib.url.unwrap_or_else(|| { - "https://maven.fabricmc.net/".to_string() - }), - artifact_path - ), - None, - ) - .await?; + Ok::, Error>(Some( + (type_, loader, version), + )) + }, + ), + ) + .await? + .into_iter() + .flatten(); - lib.url = Some(format_url("maven/")); + futures::future::try_join_all(versions.map(|(type_, loader, version)| 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; - upload_file_to_bucket( - format!("{}/{}", "maven", artifact_path), - artifact.to_vec(), - Some("application/java-archive".to_string()), - ) - .await?; + if visited_assets.contains(&lib.name) { + lib.url = Some(format_url("maven/")); - Ok::(lib) - }), - ) - .await?; + return Ok(lib); + } else { + visited_assets.push(lib.name.clone()) + } + } - let version_path = format!( - "fabric/v{}/versions/{}-{}.json", - daedalus::fabric::CURRENT_FORMAT_VERSION, - version.inherits_from, - loader + let artifact_path = daedalus::get_path_from_artifact(&*lib.name)?; + + let artifact = daedalus::download_file( + &*format!( + "{}{}", + lib.url.unwrap_or_else(|| { + "https://maven.fabricmc.net/".to_string() + }), + artifact_path + ), + None, + ) + .await?; + + lib.url = Some(format_url("maven/")); + + upload_file_to_bucket( + format!("{}/{}", "maven", artifact_path), + artifact.to_vec(), + Some("application/java-archive".to_string()), + ) + .await?; + + Ok::(lib) + }, + )) + .await?; + + let version_path = format!( + "fabric/v{}/versions/{}-{}.json", + daedalus::modded::CURRENT_FABRIC_FORMAT_VERSION, + version.inherits_from, + loader + ); + + upload_file_to_bucket( + version_path.clone(), + serde_json::to_vec(&PartialVersionInfo { + arguments: version.arguments, + id: version.id, + main_class: version.main_class, + release_time: version.release_time, + time: version.time, + type_: version.type_, + inherits_from: version.inherits_from, + libraries: libs, + })?, + Some("application/json".to_string()), + ) + .await?; + + { + let mut loader_version_map = loader_version_mutex.lock().await; + loader_version_map.insert( + type_, + LoaderVersion { + id: loader, + url: format_url(&*version_path), + }, ); + } - upload_file_to_bucket( - version_path.clone(), - serde_json::to_vec(&PartialVersionInfo { - arguments: version.arguments, - id: version.id, - main_class: version.main_class, - release_time: version.release_time, - time: version.time, - type_: version.type_, - inherits_from: version.inherits_from, - libraries: libs, - })?, - Some("application/json".to_string()), - ) - .await?; - - { - let mut game_version_map = game_version_mutex.lock().await; - game_version_map.insert(loader, format_url(&*version_path)); - } - - Ok::<(), Error>(()) - }, - )) + Ok::<(), Error>(()) + })) .await?; - game_version.urls = Some( - game_version_mutex.lock().await - .clone(), - ); + let mut versions = versions_mutex.lock().await; + versions.push(Version { + id: game_version.version.clone(), + loaders: loader_version_mutex.into_inner(), + }); Ok::<(), Error>(()) }); @@ -156,15 +192,80 @@ pub async fn retrieve_data() -> Result<(), Error> { } } - upload_file_to_bucket( - format!( - "fabric/v{}/manifest.json", - daedalus::fabric::CURRENT_FORMAT_VERSION, - ), - serde_json::to_vec(&list)?, - Some("application/json".to_string()), - ) - .await?; + if let Ok(versions) = Arc::try_unwrap(versions) { + upload_file_to_bucket( + format!( + "fabric/v{}/manifest.json", + daedalus::modded::CURRENT_FABRIC_FORMAT_VERSION, + ), + serde_json::to_vec(&Manifest { + game_versions: versions.into_inner(), + })?, + Some("application/json".to_string()), + ) + .await?; + } Ok(()) } + +const FABRIC_META_URL: &str = "https://meta.fabricmc.net/v2"; + +async fn fetch_fabric_version( + version_number: &str, + loader_version: &str, +) -> Result { + Ok(serde_json::from_slice( + &download_file( + &*format!( + "{}/versions/loader/{}/{}/profile/json", + FABRIC_META_URL, version_number, loader_version + ), + None, + ) + .await?, + )?) +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +/// Versions of fabric components +struct FabricVersions { + /// Versions of Minecraft that fabric supports + pub game: Vec, + /// Available versions of the fabric loader + pub loader: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +/// A version of Minecraft that fabric supports +struct FabricGameVersion { + /// The version number of the game + pub version: String, + /// Whether the Minecraft version is stable or not + 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 + 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>) -> Result { + Ok(serde_json::from_slice( + &download_file( + url.unwrap_or(&*format!("{}/versions", FABRIC_META_URL)), + None, + ) + .await?, + )?) +} diff --git a/daedalus_client/src/forge.rs b/daedalus_client/src/forge.rs index 61dd1fc1b..f06d23b00 100644 --- a/daedalus_client/src/forge.rs +++ b/daedalus_client/src/forge.rs @@ -1,60 +1,39 @@ use crate::{format_url, upload_file_to_bucket, Error}; -use semver::{VersionReq, Version}; -use lazy_static::lazy_static; -use daedalus::download_file; -use std::io::Read; -use tokio::sync::{Mutex}; -use std::sync::{Arc}; -use daedalus::minecraft::{Library, VersionType, ArgumentType, Argument}; use chrono::{DateTime, Utc}; -use serde::{Serialize, Deserialize}; -use daedalus::fabric::PartialVersionInfo; -use std::time::{Instant, Duration}; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ForgeInstallerProfileInstallDataV1 { - pub mirror_list: String, - pub target: String, - /// Path to the Forge universal library - pub file_path: String, - pub logo: String, - pub welcome: String, - pub version: String, - /// Maven coordinates of the Forge universal library - pub path: String, - pub profile_name: String, - pub minecraft: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ForgeInstallerProfileManifestV1 { - pub id: String, - pub libraries: Vec, - pub main_class: Option, - pub minecraft_arguments: Option, - pub release_time: DateTime, - pub time: DateTime, - pub type_: VersionType, - pub assets: Option, - pub inherits_from: Option, - pub jar: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct ForgeInstallerProfileV1 { - pub install: ForgeInstallerProfileInstallDataV1, - pub version_info: ForgeInstallerProfileManifestV1, -} +use daedalus::download_file; +use daedalus::minecraft::{Argument, ArgumentType, Library, VersionType}; +use daedalus::modded::{LoaderType, LoaderVersion, Manifest, PartialVersionInfo}; +use lazy_static::lazy_static; +use semver::{Version, VersionReq}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::io::Read; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::sync::Mutex; lazy_static! { - static ref FORGE_MANIFEST_V1_QUERY: VersionReq = VersionReq::parse(">=8.0.684, <23.5.2851").unwrap(); + static ref FORGE_MANIFEST_V1_QUERY: VersionReq = + VersionReq::parse(">=8.0.684, <23.5.2851").unwrap(); + static ref FORGE_MANIFEST_V2_QUERY: VersionReq = + VersionReq::parse(">=23.5.2851, <37.0.0").unwrap(); + static ref FORGE_MANIFEST_V3_QUERY: VersionReq = VersionReq::parse(">=37.0.0").unwrap(); } pub async fn retrieve_data() -> Result<(), Error> { - let maven_metadata = daedalus::forge::fetch_maven_metadata(None).await?; + let maven_metadata = fetch_maven_metadata(None).await?; + let old_manifest = daedalus::modded::fetch_manifest(&*format!( + "forge/v{}/manifest.json", + daedalus::modded::CURRENT_FORGE_FORMAT_VERSION, + )) + .await + .ok(); + + let versions = Arc::new(Mutex::new(if let Some(old_manifest) = old_manifest { + old_manifest.game_versions + } else { + Vec::new() + })); let visited_assets_mutex = Arc::new(Mutex::new(Vec::new())); @@ -69,8 +48,8 @@ pub async fn retrieve_data() -> Result<(), Error> { // 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() < 6 { + 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]) @@ -79,31 +58,46 @@ pub async fn retrieve_data() -> Result<(), Error> { loader_version_raw.to_string() }; - if FORGE_MANIFEST_V1_QUERY.matches(&Version::parse(&*loader_version).unwrap()) { - version_futures.push(async { - let visited_assets = Arc::clone(&visited_assets_mutex); - async move { - println!("installer start {}", loader_version_full.clone()); - let bytes = download_file(&*format!("https://maven.minecraftforge.net/net/minecraftforge/forge/{0}/forge-{0}-installer.jar", loader_version_full), None).await.unwrap(); + let version = Version::parse(&*loader_version)?; - let reader = std::io::Cursor::new(&*bytes); + version_futures.push(async { + let versions_mutex = Arc::clone(&versions); + let visited_assets = Arc::clone(&visited_assets_mutex); + async move { + { + if versions_mutex.lock().await.iter().any(|x| { + x.id == minecraft_version + && x.loaders + .get(&LoaderType::Latest) + .map(|x| x.id == loader_version_full) + .unwrap_or(false) + }) { + return Ok(()); + } + } - if let Ok(mut archive) = zip::ZipArchive::new(reader) { + println!("installer start {}", loader_version_full.clone()); + let bytes = download_file(&*format!("https://maven.minecraftforge.net/net/minecraftforge/forge/{0}/forge-{0}-installer.jar", loader_version_full), None).await?; + + let reader = std::io::Cursor::new(&*bytes); + + if let Ok(mut archive) = zip::ZipArchive::new(reader) { + if FORGE_MANIFEST_V1_QUERY.matches(&version) { let install_profile = { - let mut install_profile = archive.by_name("install_profile.json").unwrap(); + let mut install_profile = archive.by_name("install_profile.json")?; let mut contents = String::new(); - install_profile.read_to_string(&mut contents).unwrap(); + install_profile.read_to_string(&mut contents)?; contents }; - let profile = serde_json::from_str::(&*install_profile).unwrap(); + let profile = serde_json::from_str::(&*install_profile)?; let forge_universal_bytes = { - let mut forge_universal_file = archive.by_name(&*profile.install.file_path).unwrap(); + let mut forge_universal_file = archive.by_name(&*profile.install.file_path)?; let mut forge_universal = Vec::new(); - forge_universal_file.read_to_end(&mut forge_universal).unwrap(); + forge_universal_file.read_to_end(&mut forge_universal)?; bytes::Bytes::from(forge_universal) }; @@ -168,7 +162,7 @@ pub async fn retrieve_data() -> Result<(), Error> { let version_path = format!( "forge/v{}/versions/{}.json", - daedalus::forge::CURRENT_FORMAT_VERSION, + daedalus::modded::CURRENT_FORGE_FORMAT_VERSION, new_profile.id ); @@ -177,34 +171,127 @@ pub async fn retrieve_data() -> Result<(), Error> { serde_json::to_vec(&new_profile)?, Some("application/json".to_string()), ).await?; + + let mut map = HashMap::new(); + map.insert(LoaderType::Latest, LoaderVersion { + id: loader_version_full, + url: format_url(&*version_path) + }); + versions_mutex.lock().await.push(daedalus::modded::Version { + id: minecraft_version, + loaders: map + }) + } else if FORGE_MANIFEST_V2_QUERY.matches(&version) { + let install_profile = { + let mut install_profile = archive.by_name("install_profile.json")?; + + let mut contents = String::new(); + install_profile.read_to_string(&mut contents)?; + + contents + }; + } else if FORGE_MANIFEST_V3_QUERY.matches(&version) { + } - - - Ok::<(), Error>(()) - }.await?; + } Ok::<(), Error>(()) - }); - } + }.await?; + + Ok::<(), Error>(()) + }); } } } - let mut versions = version_futures.into_iter().peekable(); - let mut chunk_index = 0; - while versions.peek().is_some() { - let now = Instant::now(); + { + let mut versions_peek = version_futures.into_iter().peekable(); + let mut chunk_index = 0; + while versions_peek.peek().is_some() { + let now = Instant::now(); - let chunk: Vec<_> = versions.by_ref().take(100).collect(); - futures::future::try_join_all(chunk).await?; + let chunk: Vec<_> = versions_peek.by_ref().take(100).collect(); + futures::future::try_join_all(chunk).await?; - std::thread::sleep(Duration::from_secs(1)); + std::thread::sleep(Duration::from_secs(1)); - chunk_index += 1; + chunk_index += 1; - let elapsed = now.elapsed(); - println!("Chunk {} Elapsed: {:.2?}", chunk_index, elapsed); + let elapsed = now.elapsed(); + println!("Chunk {} Elapsed: {:.2?}", chunk_index, elapsed); + } + } + + if let Ok(versions) = Arc::try_unwrap(versions) { + upload_file_to_bucket( + format!( + "forge/v{}/manifest.json", + daedalus::modded::CURRENT_FORGE_FORMAT_VERSION, + ), + serde_json::to_vec(&Manifest { + game_versions: versions.into_inner(), + })?, + Some("application/json".to_string()), + ) + .await?; } Ok(()) -} \ No newline at end of file +} + +const DEFAULT_MAVEN_METADATA_URL: &str = + "https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json"; + +/// Fetches the forge maven metadata from the specified URL. If no URL is specified, the default is used. +/// Returns a hashmap specifying the versions of the forge mod loader +/// The hashmap key is a Minecraft version, and the value is the loader versions that work on +/// the specified Minecraft version +pub async fn fetch_maven_metadata( + url: Option<&str>, +) -> Result>, Error> { + Ok(serde_json::from_slice( + &download_file(url.unwrap_or(DEFAULT_MAVEN_METADATA_URL), None).await?, + )?) +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct ForgeInstallerProfileInstallDataV1 { + pub mirror_list: String, + pub target: String, + /// Path to the Forge universal library + pub file_path: String, + pub logo: String, + pub welcome: String, + pub version: String, + /// Maven coordinates of the Forge universal library + pub path: String, + pub profile_name: String, + pub minecraft: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct ForgeInstallerProfileManifestV1 { + pub id: String, + pub libraries: Vec, + pub main_class: Option, + pub minecraft_arguments: Option, + pub release_time: DateTime, + pub time: DateTime, + pub type_: VersionType, + pub assets: Option, + pub inherits_from: Option, + pub jar: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct ForgeInstallerProfileV1 { + pub install: ForgeInstallerProfileInstallDataV1, + pub version_info: ForgeInstallerProfileManifestV1, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct ForgeInstallerProfileV2 {} diff --git a/daedalus_client/src/main.rs b/daedalus_client/src/main.rs index 321ef10b3..14f4fb133 100644 --- a/daedalus_client/src/main.rs +++ b/daedalus_client/src/main.rs @@ -6,8 +6,8 @@ use rusoto_s3::{PutObjectRequest, S3}; use std::time::Duration; mod fabric; -mod minecraft; mod forge; +mod minecraft; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -24,6 +24,12 @@ pub enum Error { inner: RusotoError, file: String, }, + #[error("Error while parsing version as semver: {0}")] + SemVerError(#[from] semver::Error), + #[error("Error while reading zip file: {0}")] + ZipError(#[from] zip::result::ZipError), + #[error("Error while reading zip file: {0}")] + IoError(#[from] std::io::Error), } #[tokio::main] @@ -38,30 +44,20 @@ async fn main() { loop { timer.tick().await; - tokio::spawn( - async { - match fabric::retrieve_data().await { - Ok(..) => {} - Err(err) => error!("{:?}", err) - }; - } - ); - tokio::spawn( - async { - match minecraft::retrieve_data().await { - Ok(..) => {} - Err(err) => error!("{:?}", err) - }; - } - ); - tokio::spawn( - async { - match forge::retrieve_data().await { - Ok(..) => {} - Err(err) => error!("{:?}", err) - }; - } - ); + tokio::spawn(async { + match fabric::retrieve_data().await { + Ok(..) => {} + Err(err) => error!("{:?}", err), + }; + match minecraft::retrieve_data().await { + Ok(..) => {} + Err(err) => error!("{:?}", err), + }; + match forge::retrieve_data().await { + Ok(..) => {} + Err(err) => error!("{:?}", err), + }; + }); } } diff --git a/daedalus_client/src/minecraft.rs b/daedalus_client/src/minecraft.rs index 90b58806d..07907c4f3 100644 --- a/daedalus_client/src/minecraft.rs +++ b/daedalus_client/src/minecraft.rs @@ -1,8 +1,8 @@ use crate::{format_url, upload_file_to_bucket, Error}; use daedalus::download_file; -use std::sync::{Arc}; -use std::time::{Duration, Instant}; use futures::lock::Mutex; +use std::sync::Arc; +use std::time::{Duration, Instant}; pub async fn retrieve_data() -> Result<(), Error> { let old_manifest = @@ -22,10 +22,7 @@ pub async fn retrieve_data() -> Result<(), Error> { let mut version_futures = Vec::new(); - for version in manifest - .versions - .iter_mut() - { + for version in manifest.versions.iter_mut() { version_futures.push(async { let old_version = if let Some(old_manifest) = &old_manifest { old_manifest.versions.iter().find(|x| x.id == version.id) @@ -126,7 +123,7 @@ pub async fn retrieve_data() -> Result<(), Error> { Ok::<(), Error>(()) } - .await?; + .await?; Ok::<(), Error>(()) })