summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock390
-rw-r--r--Cargo.toml10
-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
7 files changed, 899 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..40eb61d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+/test_stuff
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..45be887
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,390 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "cc"
+version = "1.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-targets",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+
+[[package]]
+name = "js-sys"
+version = "0.3.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.168"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "o3launcher"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "regex",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "serde"
+version = "1.0.216"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.216"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.133"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "syn"
+version = "2.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..e25f08b
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "o3launcher"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+chrono = { version = "0.4.39", default-features = false, features = ["std", "alloc", "clock", "now", "serde"] }
+regex = "1.11.1"
+serde = { version = "1.0.216", features = ["derive"] }
+serde_json = "1.0.133"
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
+ }
+}