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(self, v: &str) -> Result where E: de::Error, { Regex::new(v).map_err(de::Error::custom).map(|r| WrappedRegex(r)) } } impl<'de> Deserialize<'de> for WrappedRegex { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { deserializer.deserialize_any(RegexVisitor) } } #[derive(Deserialize, Debug)] pub struct OSRestriction { pub name: Option, pub version: Option, pub arch: Option } #[derive(Deserialize, Debug)] pub struct CompatibilityRule { pub action: RuleAction, pub features: Option>, pub os: Option } impl CompatibilityRule { 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)] pub rules: Option>, #[serde(default)] #[serde(deserialize_with = "string_or_array")] pub value: Vec } #[derive(Debug)] pub struct WrappedArgument(Argument); impl FromStr for Argument { type Err = Infallible; fn from_str(s: &str) -> Result { 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(deserializer: D) -> Result where D: Deserializer<'de> { Ok(WrappedArgument(string_or_struct(deserializer)?)) } } #[derive(Deserialize, Debug)] pub struct Arguments { pub game: Option>, pub jvm: Option> } #[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 { pub sha1: Option, pub size: Option, pub total_size: Option, // available for asset index pub url: Option, // may not be present for libraries pub id: Option, pub path: Option } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct JavaVersionInfo { pub component: String, pub major_version: u32 } #[derive(Deserialize, Debug)] pub struct LibraryDownloads { pub artifact: Option, pub classifiers: Option> } #[derive(Deserialize, Debug)] pub struct LibraryExtractRule { #[serde(default)] pub exclude: Vec } #[derive(Deserialize, Debug)] pub struct Library { pub downloads: Option, pub name: String, pub extract: Option, pub natives: Option>, pub rules: Option>, pub url: Option // old format } #[derive(Deserialize, Debug)] pub struct ClientLogging { pub argument: String, #[serde(rename = "type")] pub logger_type: String, pub file: DownloadInfo } #[derive(Deserialize, Debug)] pub struct Logging { pub client: ClientLogging // other fields unknown } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct CompleteVersion { pub arguments: Option, pub minecraft_arguments: Option, pub asset_index: Option, pub assets: Option, pub compliance_level: Option, pub java_version: Option, #[serde(default)] pub downloads: BTreeMap, #[serde(default)] pub libraries: Vec, pub id: String, pub jar: Option, // used as the jar filename if specified? (no longer used officially) pub logging: Option, pub main_class: Option, pub minimum_launcher_version: Option, pub release_time: Option>, pub time: Option>, #[serde(rename = "type")] pub version_type: Option, pub compatibility_rules: Option>, // pub incompatibility_reason: Option, // message shown when compatibility rules fail for this version pub inherits_from: Option /* 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 where T: Deserialize<'de> + FromStr, D: Deserializer<'de>, { struct StringOrStruct(PhantomData T>); impl<'de, T> Visitor<'de> for StringOrStruct where T: Deserialize<'de> + FromStr, { type Value = T; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> fmt::Result { formatter.write_str("string or map") } fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { Ok(FromStr::from_str(v).unwrap()) } fn visit_map(self, map: A) -> Result 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, D::Error> where T: Deserialize<'de> + FromStr, D: Deserializer<'de>, { struct StringOrVec(PhantomData T>); impl<'de, T> Visitor<'de> for StringOrVec where T: Deserialize<'de> + FromStr, { type Value = Vec; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("string or array") } fn visit_str(self, v: &str) -> Result where E: de::Error, { Ok(vec![FromStr::from_str(v).unwrap()]) } fn visit_seq(self, seq: A) -> Result 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 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); } }