summaryrefslogtreecommitdiffstats
path: root/src/version.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/version.rs')
-rw-r--r--src/version.rs489
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"));
- }
-}