diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/main.rs | 3 | ||||
| -rw-r--r-- | src/version.rs | 374 | ||||
| -rw-r--r-- | src/version/manifest.rs | 119 |
4 files changed, 497 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..81c2f21 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +mod version; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} 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); + } +} diff --git a/src/version/manifest.rs b/src/version/manifest.rs new file mode 100644 index 0000000..a44e435 --- /dev/null +++ b/src/version/manifest.rs @@ -0,0 +1,119 @@ +use core::fmt; + +use chrono::{DateTime, Utc}; +use serde::{de::Visitor, Deserialize}; + +#[derive(Deserialize)] +pub struct LatestVersions { + release: String, + snapshot: String +} + +impl LatestVersions { + pub fn get_release(&self) -> &String { + &self.release + } + + pub fn get_snapshot(&self) -> &String { + &self.snapshot + } +} + +#[derive(Debug)] +pub enum VersionType { + Snapshot, + Release, + OldBeta, + OldAlpha, + Other(String) +} + +struct VersionTypeVisitor; + +impl<'de> Visitor<'de> for VersionTypeVisitor { + type Value = VersionType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> fmt::Result { + formatter.write_str("a Minecraft release type") + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: serde::de::Error, { + match v { + "snapshot" => Ok(VersionType::Snapshot), + "release" => Ok(VersionType::Release), + "old_beta" => Ok(VersionType::OldBeta), + "old_alpha" => Ok(VersionType::OldAlpha), + _ => Ok(VersionType::Other(v.to_owned())) + } + } +} + +impl<'de> Deserialize<'de> for VersionType { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de> { + deserializer.deserialize_string(VersionTypeVisitor) + } +} + +// https://piston-meta.mojang.com/mc/game/version_manifest_v2.json +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VersionManifestVersion { + id: String, + #[serde(rename = "type")] + version_type: VersionType, + url: String, + time: DateTime<Utc>, + release_time: DateTime<Utc>, + sha1: String, + compliance_level: u32 +} + +impl VersionManifestVersion { + pub fn get_id(&self) -> &String { + &self.id + } + + pub fn get_version_type(&self) -> &VersionType { + &self.version_type + } + + pub fn get_url(&self) -> &String { + &self.url + } + + pub fn get_time(&self) -> &DateTime<Utc> { + &self.time + } + + pub fn get_release_time(&self) -> &DateTime<Utc> { + &self.release_time + } + + pub fn get_sha1(&self) -> &String { + &self.sha1 + } + + pub fn get_compliance_level(&self) -> u32 { + self.compliance_level + } +} + +#[derive(Deserialize)] +pub struct VersionManifest { + latest: LatestVersions, + versions: Vec<VersionManifestVersion> +} + +impl VersionManifest { + pub fn get_latest(&self) -> &LatestVersions { + &self.latest + } + + pub fn get_versions(&self) -> &Vec<VersionManifestVersion> { + &self.versions + } +} |
