diff options
Diffstat (limited to 'src/version.rs')
| -rw-r--r-- | src/version.rs | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..d120093 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,374 @@ +use core::fmt; +use std::{collections::BTreeMap, convert::Infallible, marker::PhantomData, ops::Deref, str::FromStr}; + +use chrono::{DateTime, Utc}; +use regex::Regex; +use serde::{de::{self, Visitor}, Deserialize, Deserializer}; + +pub mod manifest; +use manifest::*; + +#[derive(Deserialize, Debug, Clone, Copy)] +#[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)] +struct WrappedRegex(Regex); + +impl Deref for WrappedRegex { + type Target = Regex; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +struct RegexVisitor; +impl<'de> Visitor<'de> for RegexVisitor { + type Value = WrappedRegex; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid regular expression") + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: de::Error, { + Regex::new(v).map_err(de::Error::custom).map(|r| WrappedRegex(r)) + } +} + +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)] +pub struct OSRestriction { + name: Option<OperatingSystem>, + + version: Option<WrappedRegex>, + arch: Option<WrappedRegex> +} + +impl OSRestriction { + pub fn get_name(&self) -> Option<OperatingSystem> { + self.name + } + + pub fn get_version(&self) -> Option<&Regex> { + self.version.as_deref() + } + + pub fn get_arch(&self) -> Option<&Regex> { + self.arch.as_deref() + } +} + +#[derive(Deserialize, Debug)] +pub struct CompatibilityRule { + action: RuleAction, + features: Option<BTreeMap<String, bool>>, + os: Option<OSRestriction> +} + +impl CompatibilityRule { + pub fn get_action(&self) -> RuleAction { + self.action + } + + pub fn get_os(&self) -> Option<&OSRestriction> { + self.os.as_ref() + } + + pub fn get_features(&self) -> Option<&BTreeMap<String, bool>> { + self.features.as_ref() + } + + pub fn features_match(&self, checker: fn(&str) -> bool) -> bool { + if let Some(m) = self.features.as_ref() { + for (feat, expect) in m { + if checker(feat) != *expect { + return false; + } + } + } + + true + } +} + +#[derive(Deserialize, Debug)] +pub struct Argument { + #[serde(default)] + rules: Option<Vec<CompatibilityRule>>, + + #[serde(default)] + #[serde(deserialize_with = "string_or_array")] + value: Vec<String> +} + +impl Argument { + fn get_rules(&self) -> Option<&Vec<CompatibilityRule>> { + self.rules.as_ref() + } + + fn get_value(&self) -> &Vec<String> { + &self.value + } +} + +#[derive(Debug)] +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)] +pub struct Arguments { + game: Option<Vec<WrappedArgument>>, + jvm: Option<Vec<WrappedArgument>> +} + +impl Arguments { + fn get_game(&self) -> Option<&Vec<WrappedArgument>> { + self.game.as_ref() + } + + fn get_jvm(&self) -> Option<&Vec<WrappedArgument>> { + self.jvm.as_ref() + } +} + +#[derive(Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "snake_case")] +pub enum DownloadType { + Client, + ClientMappings, + Server, + ServerMappings, + WindowsServer +} + +#[derive(Deserialize, Debug)] +pub struct DownloadInfo { + sha1: Option<String>, + size: Option<usize>, + total_size: Option<usize>, // available for asset index + url: Option<String>, // may not be present for libraries + id: Option<String>, + path: Option<String> +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct JavaVersionInfo { + component: String, + major_version: u32 +} + +#[derive(Deserialize, Debug)] +pub struct LibraryDownloads { + artifact: Option<DownloadInfo>, + classifiers: Option<BTreeMap<String, DownloadInfo>> +} + +#[derive(Deserialize, Debug)] +pub struct LibraryExtractRule { + #[serde(default)] + exclude: Vec<String> +} + +#[derive(Deserialize, Debug)] +pub struct Library { + downloads: Option<LibraryDownloads>, + name: String, + extract: Option<LibraryExtractRule>, + natives: Option<BTreeMap<OperatingSystem, String>>, + rules: Option<Vec<CompatibilityRule>>, + url: Option<String> // old format +} + +#[derive(Deserialize, Debug)] +pub struct ClientLogging { + argument: String, + r#type: String, + file: DownloadInfo +} + +#[derive(Deserialize, Debug)] +pub struct Logging { + client: ClientLogging // other fields unknown +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct CompleteVersion { + arguments: Option<Arguments>, + minecraft_arguments: Option<String>, + + asset_index: Option<DownloadInfo>, + assets: Option<String>, + + compliance_level: Option<u32>, + + java_version: Option<JavaVersionInfo>, + + #[serde(default)] + downloads: BTreeMap<DownloadType, DownloadInfo>, + + #[serde(default)] + libraries: Vec<Library>, + + id: String, + jar: Option<String>, // used as the jar filename if specified? (no longer used officially) + + logging: Option<Logging>, + + main_class: Option<String>, + minimum_launcher_version: Option<u32>, + release_time: Option<DateTime<Utc>>, + time: Option<DateTime<Utc>>, + + #[serde(rename = "type")] + version_type: Option<VersionType>, + + compatibility_rules: Option<Vec<CompatibilityRule>>, // + incompatibility_reason: Option<String>, // message shown when compatibility rules fail for this version + + 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.) + */ +} + +// 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 std::fmt::Formatter) -> fmt::Result { + formatter.write_str("string or map") + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: serde::de::Error, { + Ok(FromStr::from_str(v).unwrap()) + } + + fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error> + where + A: serde::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 std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("string or array") + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: de::Error, { + Ok(vec![FromStr::from_str(v).unwrap()]) + } + + fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error> + where + A: de::SeqAccess<'de>, { + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq)) + } + } + + deserializer.deserialize_any(StringOrVec(PhantomData)) +} + +#[cfg(test)] +mod tests { + use std::fs; + + use serde_json::json; + + 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); + } +} |
