diff options
| author | 2025-02-01 23:06:37 -0600 | |
|---|---|---|
| committer | 2025-02-01 23:06:37 -0600 | |
| commit | c19a1077e85334a3e5ba885a60b03d76409a2b2e (patch) | |
| tree | 5e726e8180770ac9c2f6c415a0437d6d2c29c226 /src/version.rs | |
| parent | random changes (diff) | |
restructure project
Diffstat (limited to 'src/version.rs')
| -rw-r--r-- | src/version.rs | 489 |
1 files changed, 0 insertions, 489 deletions
diff --git a/src/version.rs b/src/version.rs deleted file mode 100644 index 6e9ad3f..0000000 --- a/src/version.rs +++ /dev/null @@ -1,489 +0,0 @@ -use core::fmt; -use std::{collections::BTreeMap, convert::Infallible, marker::PhantomData, ops::Deref, str::FromStr}; -use chrono::{DateTime, NaiveDateTime, Utc}; -use chrono::format::ParseErrorKind; -use regex::Regex; -use serde::{de::{self, Visitor}, Deserialize, Deserializer}; -use serde::de::{Error, SeqAccess}; -use sha1_smol::Digest; - -pub mod manifest; -use manifest::*; - -#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum RuleAction { - Allow, - Disallow -} - -// must derive an order on this because it's used as a key for a btreemap -#[derive(Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[serde(rename_all = "lowercase")] -pub enum OperatingSystem { - Linux, // "linux" - Windows, // "windows" - - #[serde(alias = "osx")] // not technically correct but it works - MacOS, // "osx" - - #[serde(other)] - Unknown // (not used in official jsons) -} - -#[derive(Debug, Clone)] -pub struct WrappedRegex(Regex); - -impl Deref for WrappedRegex { - type Target = Regex; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -struct RegexVisitor; -impl Visitor<'_> for RegexVisitor { - type Value = WrappedRegex; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a valid regular expression") - } - - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: Error, { - Regex::new(v).map_err(Error::custom).map(WrappedRegex) - } -} - -impl<'de> Deserialize<'de> for WrappedRegex { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de> { - deserializer.deserialize_any(RegexVisitor) - } -} - -#[derive(Deserialize, Debug, Clone)] -pub struct OSRestriction { - #[serde(rename = "name")] - pub os: Option<OperatingSystem>, - - pub version: Option<WrappedRegex>, - pub arch: Option<WrappedRegex> -} - -#[derive(Deserialize, Debug, Clone)] -pub struct CompatibilityRule { - pub action: RuleAction, - pub features: Option<BTreeMap<String, bool>>, - pub os: Option<OSRestriction> -} - -pub trait FeatureMatcher { - fn matches(&self, feature: &str) -> bool; -} - -impl CompatibilityRule { - pub fn features_match(&self, checker: &impl FeatureMatcher) -> bool { - if let Some(m) = self.features.as_ref() { - for (feat, expect) in m { - if checker.matches(feat) != *expect { - return false; - } - } - } - - true - } -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Argument { - #[serde(default)] - pub rules: Option<Vec<CompatibilityRule>>, - - #[serde(default)] - #[serde(deserialize_with = "string_or_array")] - pub value: Vec<String> -} - -#[derive(Debug, Clone)] -pub struct WrappedArgument(Argument); - -impl FromStr for Argument { - type Err = Infallible; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - Ok(Argument { - value: vec![s.to_owned()], - rules: None - }) - } -} - -impl Deref for WrappedArgument { - type Target = Argument; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'de> Deserialize<'de> for WrappedArgument { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de> { - Ok(WrappedArgument(string_or_struct(deserializer)?)) - } -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Arguments { - pub game: Option<Vec<WrappedArgument>>, - pub jvm: Option<Vec<WrappedArgument>> -} - -impl Arguments { - fn apply_child(&mut self, other: &Arguments) { - if let Some(game) = other.game.as_ref() { - self.game.get_or_insert_default().splice(0..0, game.iter().cloned()); - } - - if let Some(jvm) = other.jvm.as_ref() { - self.jvm.get_or_insert_default().splice(0..0, jvm.iter().cloned()); - } - } -} - -#[derive(Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[serde(rename_all = "snake_case")] -pub enum DownloadType { - Client, - ClientMappings, - Server, - ServerMappings, - WindowsServer -} - -#[derive(Deserialize, Debug, Clone)] -pub struct DownloadInfo { - pub sha1: Option<Digest>, - pub size: Option<usize>, - pub total_size: Option<usize>, // available for asset index - pub url: Option<String>, // may not be present for libraries - pub id: Option<String>, - pub path: Option<String> -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct JavaVersionInfo { - pub component: String, - pub major_version: u32 -} - -#[derive(Deserialize, Debug, Clone)] -pub struct LibraryDownloads { - pub artifact: Option<DownloadInfo>, - pub classifiers: Option<BTreeMap<String, DownloadInfo>> -} - -#[derive(Deserialize, Debug, Clone)] -pub struct LibraryExtractRule { - #[serde(default)] - pub exclude: Vec<String> -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Library { - pub downloads: Option<LibraryDownloads>, - pub name: String, - pub extract: Option<LibraryExtractRule>, - pub natives: Option<BTreeMap<OperatingSystem, String>>, - pub rules: Option<Vec<CompatibilityRule>>, - - // old format - pub url: Option<String>, - pub size: Option<usize>, - pub sha1: Option<Digest> -} - -impl Library { - pub fn get_canonical_name(&self) -> String { - canonicalize_library_name(self.name.as_str(), self.natives.as_ref().map(|_| "__ozone_natives")) - } -} - -impl LibraryDownloads { - pub fn get_download_info(&self, classifier: Option<&str>) -> Option<&DownloadInfo> { - if let Some(classifier) = classifier { - self.classifiers.as_ref()?.get(classifier) - } else { - self.artifact.as_ref() - } - } -} - -#[derive(Deserialize, Debug, Clone)] -pub struct ClientLogging { - pub argument: String, - - #[serde(rename = "type")] - pub log_type: String, - pub file: DownloadInfo -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Logging { - pub client: Option<ClientLogging> // other fields unknown -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct CompleteVersion { - pub arguments: Option<Arguments>, - pub minecraft_arguments: Option<String>, - - pub asset_index: Option<DownloadInfo>, - pub assets: Option<String>, - - pub compliance_level: Option<u32>, - - pub java_version: Option<JavaVersionInfo>, - - #[serde(default)] - pub downloads: BTreeMap<DownloadType, DownloadInfo>, - - #[serde(default)] - pub libraries: Vec<Library>, - - pub id: String, - pub jar: Option<String>, // used as the jar filename if specified? (no longer used officially) - - pub logging: Option<Logging>, - - pub main_class: Option<String>, - pub minimum_launcher_version: Option<u32>, - - #[serde(deserialize_with = "deserialize_datetime_lenient")] - pub release_time: Option<DateTime<Utc>>, - #[serde(deserialize_with = "deserialize_datetime_lenient")] - pub time: Option<DateTime<Utc>>, - - #[serde(rename = "type")] - pub version_type: Option<VersionType>, - - pub compatibility_rules: Option<Vec<CompatibilityRule>>, // - pub incompatibility_reason: Option<String>, // message shown when compatibility rules fail for this version - - pub inherits_from: Option<String> - - /* omitting field `savableVersion' because it seems like a vestigial part from old launcher versions - * (also it isn't even a string that is present in modern liblauncher.so, so I assume it will never be used.) - */ -} - -impl CompleteVersion { - pub fn get_jar(&self) -> &String { - self.jar.as_ref().unwrap_or(&self.id) - } - - pub fn apply_child(&mut self, other: &CompleteVersion) { - macro_rules! replace_missing { - ($name:ident) => { - if self.$name.is_none() { - if let Some($name) = other.$name.as_ref() { - self.$name.replace($name.to_owned()); - } - } - }; - } - - if let Some(arguments) = other.arguments.as_ref() { - if let Some(my_args) = self.arguments.as_mut() { - my_args.apply_child(arguments); - } else { - self.arguments.replace(arguments.to_owned()); - } - } - - replace_missing!(minecraft_arguments); - replace_missing!(asset_index); - replace_missing!(assets); - replace_missing!(compliance_level); - replace_missing!(java_version); - - for (dltype, dl) in other.downloads.iter().by_ref() { - self.downloads.entry(*dltype).or_insert_with(|| dl.clone()); - } - - // we use extend here instead of splice for library resolution priority reasons - // (libraries earlier in the list will override libraries later in the list) - self.libraries.extend(other.libraries.iter().cloned()); - - replace_missing!(logging); - replace_missing!(main_class); - replace_missing!(minimum_launcher_version); - replace_missing!(release_time); - replace_missing!(time); - replace_missing!(version_type); - - if let Some(rules) = other.compatibility_rules.as_ref() { - if let Some(my_rules) = self.compatibility_rules.as_mut() { - my_rules.splice(0..0, rules.iter().cloned()); - } else { - self.compatibility_rules.replace(rules.to_owned()); - } - } - - replace_missing!(incompatibility_reason); - } -} - -fn canonicalize_library_name(name: &str, suffix: Option<&str>) -> String { - name.split(':') - .enumerate() - .filter(|(i, _)| *i != 2) - .map(|(_, s)| s.to_ascii_lowercase()) - .chain(suffix.into_iter().map(|s| s.to_owned())) - .collect::<Vec<_>>() - .join(":") -} - -fn deserialize_datetime_lenient<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error> -where - D: Deserializer<'de> -{ - struct DateTimeVisitor; - - impl Visitor<'_> for DateTimeVisitor { - type Value = Option<DateTime<Utc>>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a valid datetime") - } - - fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> - where - E: Error - { - match value.parse::<DateTime<Utc>>() { - Ok(dt) => Ok(Some(dt)), - Err(e) if e.kind() == ParseErrorKind::TooShort => { - // this probably just doesn't have an offset for some reason - match value.parse::<NaiveDateTime>() { - Ok(ndt) => Ok(Some(ndt.and_utc())), - Err(e) => Err(Error::custom(e)) - } - }, - Err(e) => Err(Error::custom(e)) - } - } - } - - deserializer.deserialize_str(DateTimeVisitor) -} - -// https://serde.rs/string-or-struct.html -fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error> -where - T: Deserialize<'de> + FromStr<Err = Infallible>, - D: Deserializer<'de>, -{ - struct StringOrStruct<T>(PhantomData<fn() -> T>); - - impl<'de, T> Visitor<'de> for StringOrStruct<T> - where - T: Deserialize<'de> + FromStr<Err = Infallible>, - { - type Value = T; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("string or map") - } - - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: Error, { - Ok(FromStr::from_str(v).unwrap()) - } - - fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error> - where - A: de::MapAccess<'de>, { - // wizardry (check comment in link) - Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) - } - } - - deserializer.deserialize_any(StringOrStruct(PhantomData)) -} - -// adapted from above -fn string_or_array<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error> -where - T: Deserialize<'de> + FromStr<Err = Infallible>, - D: Deserializer<'de>, -{ - struct StringOrVec<T>(PhantomData<fn() -> T>); - - impl<'de, T> Visitor<'de> for StringOrVec<T> - where - T: Deserialize<'de> + FromStr<Err = Infallible>, - { - type Value = Vec<T>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("string or array") - } - - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: Error, { - Ok(vec![FromStr::from_str(v).unwrap()]) - } - - fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error> - where - A: SeqAccess<'de>, { - Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq)) - } - } - - deserializer.deserialize_any(StringOrVec(PhantomData)) -} - -#[cfg(test)] -mod tests { - use std::fs; - - use super::*; - - #[test] - fn test_it() { - let s = fs::read_to_string("./test_stuff/versions/1.7.10.json"); - - let arg: CompleteVersion = serde_json::from_str(s.unwrap().as_str()).unwrap(); - dbg!(arg); - } - - #[test] - fn test_it2() { - let s = fs::read_to_string("./test_stuff/version_manifest_v2.json"); - - let arg: VersionManifest = serde_json::from_str(s.unwrap().as_str()).unwrap(); - dbg!(arg); - } - - #[test] - fn test_it3() { - assert_eq!(canonicalize_library_name("group:artifact:version", None), String::from("group:artifact")); - assert_eq!(canonicalize_library_name("group:artifact:version:specifier", None), String::from("group:artifact:specifier")); - assert_eq!(canonicalize_library_name("not_enough:fields", None), String::from("not_enough:fields")); - assert_eq!(canonicalize_library_name("word", None), String::from("word")); - assert_eq!(canonicalize_library_name("", None), String::from("")); - assert_eq!(canonicalize_library_name("group:artifact:version", Some("suffix")), String::from("group:artifact:suffix")); - } -} |
