summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2024-12-17 16:06:48 -0600
committerLibravatar bigfoot547 <[email protected]>2024-12-17 16:06:48 -0600
commit88728968b9a05c038e1346bb378a88cdcc1f000d (patch)
tree2acc06a8883cb9215839a9ddc1ce7bd965001806 /src
initial commit
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs3
-rw-r--r--src/version.rs374
-rw-r--r--src/version/manifest.rs119
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
+ }
+}