use crate::minecraft::{ Argument, ArgumentType, Library, VersionInfo, VersionType, }; use chrono::{DateTime, NaiveDateTime, Utc}; use serde::{Deserialize, Deserializer, 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; /// The latest version of the format the quilt model structs deserialize to pub const CURRENT_QUILT_FORMAT_VERSION: usize = 1; /// The latest version of the format the neoforge model structs deserialize to pub const CURRENT_NEOFORGE_FORMAT_VERSION: usize = 0; /// Metadata for locating and caching a loader manifest. #[derive(Debug, Clone)] pub struct LoaderManifestMetadata { /// The canonical loader name used in launcher-meta paths. pub loader: String, /// The latest manifest format version for this loader. pub format_version: usize, /// The cache key that includes the loader format version. pub cache_key: String, /// The launcher-meta path to the manifest. pub path: String, } /// Returns metadata for the latest manifest format for the provided loader. pub fn loader_manifest_metadata(loader: &str) -> LoaderManifestMetadata { let format_version = current_loader_manifest_format_version(loader); let cache_key = format!("{loader}-v{format_version}"); let path = format!("{loader}/v{format_version}/manifest.json"); LoaderManifestMetadata { loader: loader.to_string(), format_version, cache_key, path, } } /// Returns loader manifest metadata from a versioned cache key. pub fn loader_manifest_metadata_from_cache_key( cache_key: &str, ) -> LoaderManifestMetadata { if let Some((loader, format_version)) = cache_key.rsplit_once("-v").and_then(|(loader, version)| { version .parse::() .ok() .map(|version| (loader, version)) }) { let cache_key = format!("{loader}-v{format_version}"); let path = format!("{loader}/v{format_version}/manifest.json"); LoaderManifestMetadata { loader: loader.to_string(), format_version, cache_key, path, } } else { loader_manifest_metadata(cache_key) } } fn current_loader_manifest_format_version(loader: &str) -> usize { match loader { "fabric" => CURRENT_FABRIC_FORMAT_VERSION, "forge" => CURRENT_FORGE_FORMAT_VERSION, "quilt" => CURRENT_QUILT_FORMAT_VERSION, "neo" => CURRENT_NEOFORGE_FORMAT_VERSION, _ => 0, } } /// The dummy replace string library names, inheritsFrom, and version names should be replaced with pub const DUMMY_REPLACE_STRING: &str = "${modrinth.gameVersion}"; /// A data variable entry that depends on the side of the installation #[derive(Serialize, Deserialize, Debug)] pub struct SidedDataEntry { /// The value on the client pub client: String, /// The value on the server pub server: String, } fn deserialize_date<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; serde_json::from_str::>(&format!("\"{s}\"")) .or_else(|_| { NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%.f") .map(|date| date.and_utc()) }) .map_err(serde::de::Error::custom) } #[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 #[serde(deserialize_with = "deserialize_date")] pub release_time: DateTime, /// The latest time a file in this version was updated #[serde(deserialize_with = "deserialize_date")] pub time: DateTime, #[serde(skip_serializing_if = "Option::is_none")] /// The classpath to the main class to launch the game pub main_class: Option, #[serde(skip_serializing_if = "Option::is_none")] /// (Legacy) Arguments passed to the game pub minecraft_arguments: Option, #[serde(skip_serializing_if = "Option::is_none")] /// 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, #[serde(skip_serializing_if = "Option::is_none")] /// (Forge-only) pub data: Option>, #[serde(skip_serializing_if = "Option::is_none")] /// (Forge-only) The list of processors to run after downloading the files pub processors: Option>, } /// A processor to be ran after downloading the files #[derive(Serialize, Deserialize, Debug)] pub struct Processor { /// Maven coordinates for the JAR library of this processor. pub jar: String, /// Maven coordinates for all the libraries that must be included in classpath when running this processor. pub classpath: Vec, /// Arguments for this processor. pub args: Vec, #[serde(skip_serializing_if = "Option::is_none")] /// Represents a map of outputs. Keys and values can be data values pub outputs: Option>, #[serde(skip_serializing_if = "Option::is_none")] /// Which sides this processor shall be ran on. /// Valid values: client, server, extract pub sides: Option>, } /// Merges a partial version into a complete one pub fn merge_partial_version( partial: PartialVersionInfo, merge: VersionInfo, ) -> VersionInfo { let merge_id = merge.id.clone(); let mut libraries = vec![]; // We skip duplicate libraries that exist already in the partial version for mut lib in merge.libraries { let lib_artifact = lib.name.rsplit_once(':').map(|x| x.0); if let Some(lib_artifact) = lib_artifact { if !partial.libraries.iter().any(|x| { let target_artifact = x.name.rsplit_once(':').map(|x| x.0); target_artifact == Some(lib_artifact) && x.include_in_classpath }) { libraries.push(lib); } else { lib.include_in_classpath = false; } } else { libraries.push(lib); } } VersionInfo { arguments: if let Some(partial_args) = partial.arguments { if let Some(merge_args) = merge.arguments { let mut new_map = HashMap::new(); fn add_keys( new_map: &mut HashMap>, args: HashMap>, ) { for (type_, arguments) in args { for arg in arguments { if let Some(vec) = new_map.get_mut(&type_) { vec.push(arg); } else { new_map.insert(type_, vec![arg]); } } } } add_keys(&mut new_map, merge_args); add_keys(&mut new_map, partial_args); Some(new_map) } else { Some(partial_args) } } else { merge.arguments }, asset_index: merge.asset_index, assets: merge.assets, downloads: merge.downloads, id: partial.id.replace(DUMMY_REPLACE_STRING, &merge_id), java_version: merge.java_version, libraries: libraries .into_iter() .chain(partial.libraries) .map(|mut x| { x.name = x.name.replace(DUMMY_REPLACE_STRING, &merge_id); x }) .collect::>(), logging: merge.logging, main_class: if let Some(main_class) = partial.main_class { main_class } else { merge.main_class }, minecraft_arguments: partial.minecraft_arguments, minimum_launcher_version: merge.minimum_launcher_version, release_time: partial.release_time, time: partial.time, type_: partial.type_, data: partial.data, processors: partial.processors, } } #[derive(Serialize, Deserialize, Debug, Clone)] #[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, #[serde(default, skip_serializing_if = "Vec::is_empty")] /// Groups of game versions that share compatible loader version profiles pub version_groups: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] /// A game version of Minecraft pub struct Version { /// The minecraft version ID pub id: String, /// Whether the release is stable or not pub stable: bool, #[serde(skip_serializing_if = "Option::is_none")] /// The loader profile group for this Minecraft version pub version_group: Option, /// A map that contains loader versions for the game version pub loaders: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] /// A group of Minecraft versions that share loader version profiles pub struct VersionGroup { /// The version group ID pub id: String, /// The loader versions for this version group pub loaders: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] /// 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, /// Whether the loader is stable or not pub stable: bool, }