diff --git a/Cargo.lock b/Cargo.lock
index 8e8b001..91f752f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -174,7 +174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
dependencies = [
"concurrent-queue",
- "event-listener 5.1.0",
+ "event-listener 5.2.0",
"event-listener-strategy 0.5.0",
"futures-core",
"pin-project-lite",
@@ -284,7 +284,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -319,7 +319,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -518,7 +518,7 @@ dependencies = [
"iana-time-zone",
"num-traits",
"serde",
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
]
[[package]]
@@ -571,7 +571,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -650,9 +650,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
-version = "0.5.11"
+version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
dependencies = [
"crossbeam-utils",
]
@@ -704,7 +704,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -715,7 +715,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -803,7 +803,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -852,9 +852,9 @@ dependencies = [
[[package]]
name = "event-listener"
-version = "5.1.0"
+version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27"
+checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
dependencies = [
"concurrent-queue",
"parking",
@@ -877,7 +877,7 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
dependencies = [
- "event-listener 5.1.0",
+ "event-listener 5.2.0",
"pin-project-lite",
]
@@ -1008,7 +1008,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -1063,9 +1063,9 @@ dependencies = [
[[package]]
name = "ghash"
-version = "0.5.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
dependencies = [
"opaque-debug",
"polyval",
@@ -1110,9 +1110,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
-version = "0.3.8"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
@@ -1253,9 +1253,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.2.3"
+version = "2.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
dependencies = [
"equivalent",
"hashbrown",
@@ -1343,6 +1343,23 @@ dependencies = [
"secret-service",
]
+[[package]]
+name = "launcher-auth"
+version = "0.1.0"
+dependencies = [
+ "async-trait",
+ "chrono",
+ "launcher-data",
+ "launcher-fetch",
+ "launcher-net",
+ "launcher-persistence",
+ "launcher-utils",
+ "thiserror",
+ "tracing",
+ "url",
+ "uuid",
+]
+
[[package]]
name = "launcher-core"
version = "0.0.1"
@@ -1393,22 +1410,37 @@ dependencies = [
"url",
]
+[[package]]
+name = "launcher-fetch"
+version = "0.1.0"
+dependencies = [
+ "launcher-data",
+ "launcher-net",
+ "launcher-persistence",
+ "launcher-utils",
+ "thiserror",
+]
+
[[package]]
name = "launcher-macros"
version = "0.1.0"
dependencies = [
"darling",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
name = "launcher-net"
version = "0.0.1"
dependencies = [
+ "async-trait",
"bytes 1.5.0",
"futures-util",
"reqwest",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
"thiserror",
"tokio",
"tracing",
@@ -1439,11 +1471,14 @@ dependencies = [
"once_cell",
"sha1",
"sha2",
+ "tar",
"thiserror",
"tokio",
"tracing",
"tracing-appender",
"tracing-subscriber",
+ "xz2",
+ "zip",
]
[[package]]
@@ -1493,9 +1528,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.20"
+version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lzma-sys"
@@ -1559,9 +1594,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.10"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi",
@@ -1722,9 +1757,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "opaque-debug"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
@@ -1749,7 +1784,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -1931,9 +1966,9 @@ dependencies = [
[[package]]
name = "polyval"
-version = "0.6.1"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -2252,7 +2287,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -2274,7 +2309,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -2358,11 +2393,9 @@ dependencies = [
"semver",
"serde",
"serde_json",
- "tar",
"tokio",
"tracing",
"url",
- "xz2",
]
[[package]]
@@ -2437,9 +2470,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.51"
+version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
+checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
@@ -2513,7 +2546,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -2611,7 +2644,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -2694,7 +2727,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
- "winnow 0.6.2",
+ "winnow 0.6.5",
]
[[package]]
@@ -2734,7 +2767,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -2920,7 +2953,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
"wasm-bindgen-shared",
]
@@ -2954,7 +2987,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3016,7 +3049,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
]
[[package]]
@@ -3034,7 +3067,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
]
[[package]]
@@ -3054,17 +3087,17 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
- "windows_aarch64_gnullvm 0.52.3",
- "windows_aarch64_msvc 0.52.3",
- "windows_i686_gnu 0.52.3",
- "windows_i686_msvc 0.52.3",
- "windows_x86_64_gnu 0.52.3",
- "windows_x86_64_gnullvm 0.52.3",
- "windows_x86_64_msvc 0.52.3",
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
]
[[package]]
@@ -3075,9 +3108,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
@@ -3087,9 +3120,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
@@ -3099,9 +3132,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
@@ -3111,9 +3144,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
@@ -3123,9 +3156,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -3135,9 +3168,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
@@ -3147,9 +3180,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "winnow"
@@ -3162,9 +3195,9 @@ dependencies = [
[[package]]
name = "winnow"
-version = "0.6.2"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178"
+checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
dependencies = [
"memchr",
]
@@ -3263,9 +3296,9 @@ dependencies = [
[[package]]
name = "zbus_names"
-version = "2.6.0"
+version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9"
+checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d"
dependencies = [
"serde",
"static_assertions",
diff --git a/Cargo.toml b/Cargo.toml
index 34e141b..10db41c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,3 +1,3 @@
[workspace]
-members = ["core", "macros", "modules/*", "silo"]
+members = ["core", "modules/macros", "modules/*", "silo"]
resolver = "2"
diff --git a/apps/cli/src/cli.rs b/apps/cli/src/cli.rs
index e0a345e..0456200 100644
--- a/apps/cli/src/cli.rs
+++ b/apps/cli/src/cli.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/cli/src/main.rs b/apps/cli/src/main.rs
index bead79a..66e543f 100644
--- a/apps/cli/src/main.rs
+++ b/apps/cli/src/main.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/linux/build.rs b/apps/linux/build.rs
index 24f8d4e..71fb8ee 100644
--- a/apps/linux/build.rs
+++ b/apps/linux/build.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/linux/src/main.rs b/apps/linux/src/main.rs
index 908b124..fd471a8 100644
--- a/apps/linux/src/main.rs
+++ b/apps/linux/src/main.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/Launcher/Resources/Info.plist b/apps/macos/Launcher/Resources/Info.plist
index 7e504ce..4f49582 100644
--- a/apps/macos/Launcher/Resources/Info.plist
+++ b/apps/macos/Launcher/Resources/Info.plist
@@ -19,7 +19,7 @@
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSHumanReadableCopyright
- Copyright © 2023 andre4ik3
+ Copyright © 2023-2024 andre4ik3
NSMicrophoneUsageDescription
A mod is requesting access to the microphone.
SUEnableInstallerLauncherService
diff --git a/apps/macos/Launcher/Sources/BridgingHeader.h b/apps/macos/Launcher/Sources/BridgingHeader.h
index 1627154..1263f18 100644
--- a/apps/macos/Launcher/Sources/BridgingHeader.h
+++ b/apps/macos/Launcher/Sources/BridgingHeader.h
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/Launcher/Sources/LauncherApp.swift b/apps/macos/Launcher/Sources/LauncherApp.swift
index e9f92ea..de56872 100644
--- a/apps/macos/Launcher/Sources/LauncherApp.swift
+++ b/apps/macos/Launcher/Sources/LauncherApp.swift
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/Launcher/Sources/Models/Instance.swift b/apps/macos/Launcher/Sources/Models/Instance.swift
index 3736ce8..208157b 100644
--- a/apps/macos/Launcher/Sources/Models/Instance.swift
+++ b/apps/macos/Launcher/Sources/Models/Instance.swift
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/Launcher/Sources/Models/SettingsContainer.swift b/apps/macos/Launcher/Sources/Models/SettingsContainer.swift
index 8c4edf0..ed900d7 100644
--- a/apps/macos/Launcher/Sources/Models/SettingsContainer.swift
+++ b/apps/macos/Launcher/Sources/Models/SettingsContainer.swift
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/Launcher/Sources/Views/ContentView.swift b/apps/macos/Launcher/Sources/Views/ContentView.swift
index e419e8f..480fb74 100644
--- a/apps/macos/Launcher/Sources/Views/ContentView.swift
+++ b/apps/macos/Launcher/Sources/Views/ContentView.swift
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/Launcher/Sources/Views/InstancesView.swift b/apps/macos/Launcher/Sources/Views/InstancesView.swift
index 27d950a..6ee5250 100644
--- a/apps/macos/Launcher/Sources/Views/InstancesView.swift
+++ b/apps/macos/Launcher/Sources/Views/InstancesView.swift
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/Launcher/Sources/Views/Settings/JavaInstallationsView.swift b/apps/macos/Launcher/Sources/Views/Settings/JavaInstallationsView.swift
index 0111df6..8f832a9 100644
--- a/apps/macos/Launcher/Sources/Views/Settings/JavaInstallationsView.swift
+++ b/apps/macos/Launcher/Sources/Views/Settings/JavaInstallationsView.swift
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/Launcher/Sources/Views/SettingsView.swift b/apps/macos/Launcher/Sources/Views/SettingsView.swift
index bb4099b..e7ae802 100644
--- a/apps/macos/Launcher/Sources/Views/SettingsView.swift
+++ b/apps/macos/Launcher/Sources/Views/SettingsView.swift
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/build.rs b/apps/macos/build.rs
index 1b2d87a..eb23260 100644
--- a/apps/macos/build.rs
+++ b/apps/macos/build.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/macos/src/lib.rs b/apps/macos/src/lib.rs
index 6450e7b..2066bf2 100644
--- a/apps/macos/src/lib.rs
+++ b/apps/macos/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/main.rs b/apps/metagen/src/main.rs
index eca6297..e9b5833 100644
--- a/apps/metagen/src/main.rs
+++ b/apps/metagen/src/main.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/game/assets.rs b/apps/metagen/src/models/game/assets.rs
index 30b0eec..24cdc56 100644
--- a/apps/metagen/src/models/game/assets.rs
+++ b/apps/metagen/src/models/game/assets.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/game/common.rs b/apps/metagen/src/models/game/common.rs
index f3d0ee3..d421134 100644
--- a/apps/metagen/src/models/game/common.rs
+++ b/apps/metagen/src/models/game/common.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/game/manifest/legacy.rs b/apps/metagen/src/models/game/manifest/legacy.rs
index c7080f3..f719da6 100644
--- a/apps/metagen/src/models/game/manifest/legacy.rs
+++ b/apps/metagen/src/models/game/manifest/legacy.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/game/manifest/mod.rs b/apps/metagen/src/models/game/manifest/mod.rs
index 7cfdc34..adc9c6f 100644
--- a/apps/metagen/src/models/game/manifest/mod.rs
+++ b/apps/metagen/src/models/game/manifest/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/game/manifest/v17w43a.rs b/apps/metagen/src/models/game/manifest/v17w43a.rs
index 1700380..f54e6c0 100644
--- a/apps/metagen/src/models/game/manifest/v17w43a.rs
+++ b/apps/metagen/src/models/game/manifest/v17w43a.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/game/mod.rs b/apps/metagen/src/models/game/mod.rs
index 49c8720..6ee4cde 100644
--- a/apps/metagen/src/models/game/mod.rs
+++ b/apps/metagen/src/models/game/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/game/version.rs b/apps/metagen/src/models/game/version.rs
index 9096733..8f7d22e 100644
--- a/apps/metagen/src/models/game/version.rs
+++ b/apps/metagen/src/models/game/version.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/java/adoptium.rs b/apps/metagen/src/models/java/adoptium.rs
index 67a5c47..e3dd274 100644
--- a/apps/metagen/src/models/java/adoptium.rs
+++ b/apps/metagen/src/models/java/adoptium.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/java/mod.rs b/apps/metagen/src/models/java/mod.rs
index c459446..33518b1 100644
--- a/apps/metagen/src/models/java/mod.rs
+++ b/apps/metagen/src/models/java/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/java/zulu.rs b/apps/metagen/src/models/java/zulu.rs
index f763af2..9d9fe81 100644
--- a/apps/metagen/src/models/java/zulu.rs
+++ b/apps/metagen/src/models/java/zulu.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/models/mod.rs b/apps/metagen/src/models/mod.rs
index ba14306..89b7ef5 100644
--- a/apps/metagen/src/models/mod.rs
+++ b/apps/metagen/src/models/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/assets.rs b/apps/metagen/src/tasks/assets.rs
index 34ff74f..91425c3 100644
--- a/apps/metagen/src/tasks/assets.rs
+++ b/apps/metagen/src/tasks/assets.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/game.rs b/apps/metagen/src/tasks/game.rs
index 91d0166..c900143 100644
--- a/apps/metagen/src/tasks/game.rs
+++ b/apps/metagen/src/tasks/game.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/index.rs b/apps/metagen/src/tasks/index.rs
index 6c58a96..35068ee 100644
--- a/apps/metagen/src/tasks/index.rs
+++ b/apps/metagen/src/tasks/index.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/java/adoptium.rs b/apps/metagen/src/tasks/java/adoptium.rs
index dbd9f0f..1511ca8 100644
--- a/apps/metagen/src/tasks/java/adoptium.rs
+++ b/apps/metagen/src/tasks/java/adoptium.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/java/mod.rs b/apps/metagen/src/tasks/java/mod.rs
index af3a65d..40c77e7 100644
--- a/apps/metagen/src/tasks/java/mod.rs
+++ b/apps/metagen/src/tasks/java/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/java/zulu.rs b/apps/metagen/src/tasks/java/zulu.rs
index dd239af..2a670c7 100644
--- a/apps/metagen/src/tasks/java/zulu.rs
+++ b/apps/metagen/src/tasks/java/zulu.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/loaders/fabric.rs b/apps/metagen/src/tasks/loaders/fabric.rs
index 9f4f078..5c58ed7 100644
--- a/apps/metagen/src/tasks/loaders/fabric.rs
+++ b/apps/metagen/src/tasks/loaders/fabric.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/loaders/forge.rs b/apps/metagen/src/tasks/loaders/forge.rs
index 7aeba7b..2018e29 100644
--- a/apps/metagen/src/tasks/loaders/forge.rs
+++ b/apps/metagen/src/tasks/loaders/forge.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/loaders/mod.rs b/apps/metagen/src/tasks/loaders/mod.rs
index c83c954..3b3687d 100644
--- a/apps/metagen/src/tasks/loaders/mod.rs
+++ b/apps/metagen/src/tasks/loaders/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/tasks/mod.rs b/apps/metagen/src/tasks/mod.rs
index 54752c4..4c6dcd9 100644
--- a/apps/metagen/src/tasks/mod.rs
+++ b/apps/metagen/src/tasks/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/apps/metagen/src/utils.rs b/apps/metagen/src/utils.rs
index 06ef8eb..445d7d5 100644
--- a/apps/metagen/src/utils.rs
+++ b/apps/metagen/src/utils.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/auth/Cargo.toml b/modules/auth/Cargo.toml
new file mode 100644
index 0000000..38b6774
--- /dev/null
+++ b/modules/auth/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "launcher-auth"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+data = { path = "../data", version = "*", package = "launcher-data" }
+fetch = { path = "../fetch", version = "*", package = "launcher-fetch" }
+net = { path = "../net", version = "*", package = "launcher-net" }
+persistence = { path = "../persistence", version = "*", package = "launcher-persistence" }
+utils = { path = "../utils", version = "*", package = "launcher-utils" }
+
+async-trait = "0.1"
+chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] }
+thiserror = "1"
+tracing = "0.1"
+url = { version = "2", features = ["serde"] }
+uuid = { version = "1", features = ["v4"] }
diff --git a/modules/auth/src/lib.rs b/modules/auth/src/lib.rs
new file mode 100644
index 0000000..d4853ec
--- /dev/null
+++ b/modules/auth/src/lib.rs
@@ -0,0 +1,58 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//! Launcher Authentication Module
+//! ==============================
+//!
+//! This module deals with everything to do with online account authentication. Each authentication
+//! service is divided
+
+use async_trait::async_trait;
+use thiserror::Error;
+
+use data::auth::Account;
+pub use microsoft::MicrosoftAuthenticationService;
+use net::Client;
+pub use offline::OfflineAuthenticationService;
+
+pub(crate) mod microsoft;
+pub(crate) mod offline;
+
+#[derive(Debug, Error)]
+pub enum Error {
+ #[error("wrong account type in authentication service: expected {0} but got {1}")]
+ WrongAccountType(&'static str, &'static str),
+ #[error("reauthentication is required")]
+ ReauthenticationRequired,
+ #[error("network error: {0}")]
+ NetworkError(#[from] net::Error),
+ #[error("failed decoding response from xbox api")]
+ DecodingError,
+}
+
+pub type Result = core::result::Result;
+
+/// A generic interface for interacting with an authentication service.
+#[async_trait]
+pub trait AuthenticationMethod {
+ /// The type of credentials that this authentication service accepts.
+ type Credentials;
+
+ /// Authenticates with the service, returning an authenticated account ready to be persisted.
+ async fn authenticate(client: &Client, credentials: Self::Credentials) -> Result;
+
+ /// Refreshes an expired account so that it is ready to be used again.
+ async fn refresh(client: &Client, account: Account) -> Result;
+}
diff --git a/modules/auth/src/microsoft.rs b/modules/auth/src/microsoft.rs
new file mode 100644
index 0000000..38275ec
--- /dev/null
+++ b/modules/auth/src/microsoft.rs
@@ -0,0 +1,177 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use async_trait::async_trait;
+use chrono::{DateTime, Duration, Utc};
+use url::Url;
+use uuid::Uuid;
+
+use data::auth::{Account, AccountCredentials};
+use data::web::microsoft::*;
+use fetch::mojang::get_profile;
+use net::{Client, Request};
+
+use super::{AuthenticationMethod, Error, Result};
+
+/// Builds a URL that can be opened in a WebView to redirect to a URL with a ?code= parameter.
+pub fn get_auth_url() -> Url {
+ let params = [
+ ("client_id", AUTH_CLIENT_ID),
+ ("prompt", "select_account"),
+ ("redirect_uri", AUTH_REDIRECT_URL),
+ ("response_type", "code"),
+ ("scope", AUTH_SCOPE),
+ ];
+ Url::parse_with_params(AUTH_LOG_IN_URL, params).unwrap()
+}
+
+pub struct MicrosoftAuthenticationService;
+
+#[async_trait]
+impl AuthenticationMethod for MicrosoftAuthenticationService {
+ type Credentials = String;
+
+ #[tracing::instrument(skip_all)]
+ async fn authenticate(client: &Client, credentials: Self::Credentials) -> Result {
+ tracing::debug!("Beginning authentication of Microsoft account.");
+
+ let (access_token, refresh_token) = exchange(client, credentials).await?;
+ let (token, expires) = authenticate(client, access_token.clone()).await?;
+
+ let profile = get_profile(client, &token).await;
+ let has_profile = profile.is_ok();
+ let (id, username) = profile
+ .map(|p| (p.id, p.username))
+ .unwrap_or_else(|_| (Uuid::new_v4().to_string(), "Player".to_string()));
+
+ Ok(Account {
+ id,
+ username,
+ has_profile,
+ token,
+ expires: Some(expires),
+ credentials: AccountCredentials::Microsoft {
+ access: access_token,
+ refresh: refresh_token,
+ },
+ })
+ }
+
+ #[tracing::instrument(skip_all)]
+ async fn refresh(client: &Client, account: Account) -> Result {
+ tracing::debug!("Beginning refresh of Microsoft account {}.", account.username);
+
+ // Get a new access token and refresh token.
+ let refresh_token = match account.credentials {
+ AccountCredentials::Microsoft { refresh, .. } => refresh,
+ AccountCredentials::Offline => return Err(Error::WrongAccountType("microsoft", "offline")),
+ };
+
+ let (access_token, refresh_token) = refresh(client, refresh_token).await?;
+ let (token, expires) = authenticate(client, access_token.clone()).await?;
+
+ let profile = get_profile(client, &token).await;
+ let has_profile = profile.is_ok();
+ let (id, username) = profile
+ .map(|p| (p.id, p.username))
+ .unwrap_or_else(|_| (account.id, account.username));
+
+ Ok(Account {
+ id,
+ username,
+ has_profile,
+ token,
+ expires: Some(expires),
+ credentials: AccountCredentials::Microsoft {
+ access: access_token,
+ refresh: refresh_token,
+ },
+ })
+ }
+}
+
+
+/// Exchanges the code received from the Microsoft login webpage to an access and refresh token.
+async fn exchange(client: &Client, code: String) -> Result<(String, String)> {
+ let params = [
+ ("client_id", AUTH_CLIENT_ID),
+ ("code", &code),
+ ("grant_type", "authorization_code"),
+ ("redirect_uri", AUTH_REDIRECT_URL),
+ ("scope", AUTH_SCOPE),
+ ];
+
+ let data: AuthCodeExchangeResponse = client.post_form(AUTH_MS_TOKEN_URL, ¶ms).await?.json().await.map_err(net::Error::from)?;
+ Ok((data.access_token, data.refresh_token))
+}
+
+/// Refreshes an expired Microsoft token for another one.
+async fn refresh(client: &Client, refresh_token: String) -> Result<(String, String)> {
+ let params = [
+ ("client_id", AUTH_CLIENT_ID),
+ ("grant_type", "refresh_token"),
+ ("refresh_token", &refresh_token),
+ ("scope", AUTH_SCOPE),
+ ];
+
+ let data: AuthCodeExchangeResponse = client.post_form(AUTH_MS_TOKEN_URL, ¶ms).await?.json().await.map_err(net::Error::from)?;
+ Ok((data.access_token, data.refresh_token))
+}
+
+/// MS -> XBL -> XSTS -> Game Token (and when it expires)
+async fn authenticate(client: &Client, token: String) -> Result<(String, DateTime)> {
+ // === MS -> XBL ===
+ let body = AuthXboxTokenRequest {
+ properties: AuthXboxLiveTokenRequestProperties {
+ auth_method: "RPS",
+ site_name: "user.auth.xboxlive.com",
+ rps_ticket: token,
+ },
+ relying_party: AUTH_RP_XBOX,
+ token_type: "JWT",
+ };
+
+ let data: AuthXboxTokenResponse = client.post_json(AUTH_XBL_TOKEN_URL, &body).await?.json().await.map_err(net::Error::from)?;
+ let xbl = data.token;
+
+ // === XBL -> XSTS ===
+ let body = AuthXboxTokenRequest {
+ properties: AuthXboxSecureTokenRequestProperties {
+ sandbox_id: "RETAIL",
+ user_tokens: [xbl],
+ },
+ relying_party: AUTH_RP_GAME,
+ token_type: "JWT",
+ };
+
+ let data: AuthXboxTokenResponse = client.post_json(AUTH_XSTS_TOKEN_URL, &body).await?.json().await.map_err(net::Error::from)?;
+ let xsts = data.token;
+ let uhs = data
+ .display_claims
+ .get("xui")
+ .and_then(|x| x.first())
+ .and_then(|x| x.get("uhs"))
+ .ok_or(Error::DecodingError)?;
+
+ // === XSTS -> Game ===
+ let body = AuthGameTokenRequest {
+ platform: "PC_LAUNCHER",
+ xtoken: format!("XBL3.0 x={};{}", uhs, xsts),
+ };
+
+ let data: AuthGameTokenResponse = client.post_json(AUTH_GAME_TOKEN_URL, &body).await?.json().await.map_err(net::Error::from)?;
+ let expires = Utc::now() + Duration::seconds(data.expires_in);
+ Ok((data.access_token, expires))
+}
diff --git a/modules/auth/src/offline.rs b/modules/auth/src/offline.rs
new file mode 100644
index 0000000..1696abc
--- /dev/null
+++ b/modules/auth/src/offline.rs
@@ -0,0 +1,50 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use async_trait::async_trait;
+use uuid::Uuid;
+
+use data::auth::AccountCredentials;
+use net::Client;
+
+use super::{Account, AuthenticationMethod, Result};
+
+pub struct OfflineAuthenticationService;
+
+#[async_trait]
+impl AuthenticationMethod for OfflineAuthenticationService {
+ /// The credentials for the offline authentication service -- that is, simply the username of
+ /// the account that should be logged into.
+ type Credentials = String;
+
+ #[tracing::instrument(skip_all)]
+ async fn authenticate(_client: &Client, credentials: Self::Credentials) -> Result {
+ tracing::debug!("Authorizing offline account {credentials}");
+ Ok(Account {
+ id: Uuid::new_v4().to_string(),
+ username: credentials,
+ has_profile: true,
+ token: "offline".to_string(),
+ expires: None,
+ credentials: AccountCredentials::Offline,
+ })
+ }
+
+ #[tracing::instrument(skip_all)]
+ async fn refresh(_client: &Client, account: Account) -> Result {
+ tracing::debug!("Refreshing offline account {}", account.username);
+ Ok(account)
+ }
+}
diff --git a/modules/data/Cargo.toml b/modules/data/Cargo.toml
index 99fc56e..bb6b731 100644
--- a/modules/data/Cargo.toml
+++ b/modules/data/Cargo.toml
@@ -9,7 +9,7 @@ publish = false
silo = []
[dependencies]
-macros = { path = "../../macros", version = "*", package = "launcher-macros" }
+macros = { path = "../macros", version = "*", package = "launcher-macros" }
serde = { version = "1", features = ["derive"] }
chrono = { version = "0.4", default-features = false, features = ["serde"] }
diff --git a/modules/data/src/auth.rs b/modules/data/src/auth.rs
new file mode 100644
index 0000000..c01a486
--- /dev/null
+++ b/modules/data/src/auth.rs
@@ -0,0 +1,44 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use chrono::{DateTime, Utc};
+use macros::data_structure;
+
+/// The different types of accounts and their credentials.
+#[data_structure]
+pub enum AccountCredentials {
+ /// MSA (Microsoft Authentication) accounts.
+ Microsoft { access: String, refresh: String },
+
+ /// Offline-mode / demo-mode accounts. Uses random token and ID.
+ Offline,
+}
+
+/// An account used for logging into the game.
+#[data_structure]
+pub struct Account {
+ /// The UUID of the account.
+ pub id: String,
+ /// The in-game username of the account.
+ pub username: String,
+ /// Whether the account has a profile (and therefore rights to play the game).
+ pub has_profile: bool,
+ /// The last received game token belonging to the account.
+ pub token: String,
+ /// When the game token stored is set to expire.
+ pub expires: Option>,
+ /// The login credentials to use for the account.
+ pub credentials: AccountCredentials,
+}
diff --git a/modules/data/src/core/assets.rs b/modules/data/src/core/assets.rs
index 622cf96..895eab1 100644
--- a/modules/data/src/core/assets.rs
+++ b/modules/data/src/core/assets.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/data/src/core/conditional.rs b/modules/data/src/core/conditional.rs
index 4dd7dbc..a792dc9 100644
--- a/modules/data/src/core/conditional.rs
+++ b/modules/data/src/core/conditional.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -18,12 +18,15 @@ use std::env::consts;
use std::str::FromStr;
use platforms::{Arch, OS};
-use serde::{Deserialize, Serialize};
+
+use macros::data_structure;
/// Condition for inclusion of arguments and libraries.
-#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
+#[derive(Hash)]
+#[data_structure(equatable)]
pub enum Condition {
- None,
+ Always,
+ Never,
Feature(String),
OS(OS),
Arch(Arch),
@@ -34,12 +37,12 @@ pub enum Condition {
}
/// A helper enum for expressing a value that may or may not have an associated condition.
-#[derive(Clone, Debug, Deserialize, Serialize)]
+#[data_structure]
pub enum MaybeConditional {
Unconditional(T),
Conditional {
when: Condition,
- then: T
+ then: T,
},
}
@@ -54,10 +57,16 @@ macro_rules! simplify_impl {
.collect();
let mut conditions: Vec = conditions.into_iter().collect();
- match conditions.len() {
- 0 => Self::None,
+ let result = match conditions.len() {
+ 0 => Self::Always,
1 => conditions.swap_remove(0),
_ => $x(conditions),
+ };
+
+ match result {
+ Condition::And(it) if it.contains(&Condition::Never) => Condition::Never,
+ Condition::Or(it) if it.contains(&Condition::Always) => Condition::Always,
+ other => other,
}
}};
}
@@ -66,7 +75,8 @@ impl Condition {
/// Evaluates a condition to a boolean.
pub fn eval(&self, features: &Vec) -> bool {
match self {
- Self::None => true,
+ Self::Always => true,
+ Self::Never => false,
Self::Feature(feature) => features.contains(feature),
Self::OS(os) => os == &OS::from_str(consts::OS).unwrap(),
Self::Arch(arch) => arch == &Arch::from_str(consts::ARCH).unwrap(),
@@ -85,7 +95,6 @@ impl Condition {
/// Checks if a condition is empty (aka a no-op). Used to simplify expressions.
pub fn is_empty(&self) -> bool {
match self {
- Self::None => true,
Self::And(vals) | Self::Or(vals) => vals.is_empty(),
Self::Not(val) => val.is_empty(),
_ => false,
@@ -99,6 +108,8 @@ impl Condition {
Self::Or(vals) => simplify_impl!(Self::Or, vals),
Self::Xor(vals) => simplify_impl!(Self::Xor, vals),
Self::Not(val) => match *val {
+ Self::Always => Self::Never,
+ Self::Never => Self::Always,
Self::Not(val) => val.simplify(),
_ => val.simplify(),
},
@@ -121,7 +132,7 @@ impl MaybeConditional {
}
}
-// === convert from modern game argument ===
+// === conversion ===
#[cfg(feature = "silo")]
impl From for OS {
@@ -149,8 +160,25 @@ impl From for Condition {
let mut conditions = vec![];
if let Some(os) = value.os {
- if let Some(os) = os.name {
- conditions.push(Condition::OS(OS::from(os)));
+ if let Some(name) = os.name {
+ let name = OS::from(name);
+
+ // Our app only runs on macOS >13 and Windows >11. So version requirements like
+ // macOS 10.5 and Windows 10 are irrelevant.
+ if let Some(version) = os.version {
+ match (version.as_str(), &name) {
+ ("^10\\.5\\.\\d$", OS::MacOS) => conditions.push(Condition::Never),
+ ("^10\\.", OS::Windows) => conditions.push(Condition::Never),
+
+ // Assuming Microsoft will at some point change Win11 to actually be Win11.
+ ("^11\\.", OS::Windows) => conditions.push(Condition::Never),
+
+ // We don't have an implementation of OS version requirement checking (yet).
+ _ => todo!()
+ }
+ }
+
+ conditions.push(Condition::OS(name));
}
if let Some(arch) = os.arch {
@@ -187,7 +215,7 @@ impl From for Vec vals.into_iter().map(|val| MaybeConditional::Conditional {
when: condition.clone(),
- then: val
+ then: val,
}).collect()
}
}
@@ -205,8 +233,9 @@ mod tests {
fn eval() {
let features = vec!["feature-1".to_string(), "feature-2".to_string()];
- // This should always be true
- assert!(Condition::None.eval(&features));
+ // These should always be true and false respectively
+ assert!(Condition::Always.eval(&features));
+ assert!(!Condition::Never.eval(&features));
// Should be true for current OS
let current_os = OS::from_str(consts::OS).unwrap();
@@ -225,13 +254,13 @@ mod tests {
#[test]
fn simplify() {
- // Empty arrays should simplify to Condition::None (which is always true)
- assert_eq!(Condition::And(vec![]).simplify(), Condition::None);
- assert_eq!(Condition::Or(vec![]).simplify(), Condition::None);
- assert_eq!(Condition::Xor(vec![]).simplify(), Condition::None);
+ // Empty arrays should simplify to be always true
+ assert_eq!(Condition::And(vec![]).simplify(), Condition::Always);
+ assert_eq!(Condition::Or(vec![]).simplify(), Condition::Always);
+ assert_eq!(Condition::Xor(vec![]).simplify(), Condition::Always);
assert_eq!(
- Condition::Not(Box::new(Condition::Not(Box::new(Condition::None)))).simplify(),
- Condition::None
+ Condition::Not(Box::new(Condition::Not(Box::new(Condition::Always)))).simplify(),
+ Condition::Always
);
// Single arrays should be unwrapped to the inner condition
@@ -244,6 +273,10 @@ mod tests {
feature
);
+ // Simplification of And(..., Never, ...) and Or(..., Always, ...) should always be false and true
+ assert_eq!(Condition::And(vec![feature.clone(), Condition::Never, feature.clone()]).simplify(), Condition::Never);
+ assert_eq!(Condition::Or(vec![feature.clone(), Condition::Always, feature.clone()]).simplify(), Condition::Always);
+
// Simplifying already simplified condition should be a no-op
assert_eq!(feature.clone().simplify(), feature);
}
diff --git a/modules/data/src/core/game.rs b/modules/data/src/core/game.rs
index cae3af1..cf763ae 100644
--- a/modules/data/src/core/game.rs
+++ b/modules/data/src/core/game.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -13,10 +13,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+use chrono::{DateTime, Utc};
+use platforms::OS;
use semver::{Comparator, Op, Prerelease, VersionReq};
use serde::{Deserialize, Serialize};
-use super::conditional::MaybeConditional;
+use super::conditional::{Condition, MaybeConditional};
+use super::library::Library;
/// The different classes of game versions (e.g. release vs snapshot).
#[derive(Clone, Debug, Deserialize, Serialize)]
@@ -32,24 +35,46 @@ pub enum GameVersionStability {
}
/// Detailed information about a specific game version (including needed libraries, asset index
-/// version, Java version, etc).
+/// version, Java version, etc.).
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GameVersion {
/// The ID or "name" of this version (e.g. "1.12.2" or "23w32a").
pub id: String,
+ /// The date and time when this version was released.
+ pub release_date: DateTime,
/// The stability of this version (e.g. release vs snapshot).
pub stability: GameVersionStability,
/// The version of Java required to launch this version.
pub java_version: VersionReq,
/// The main Java class that contains the game's `main()` function.
pub main_class: String,
+ /// A list of required libraries to run this version.
+ pub libraries: Vec>,
/// A list of arguments that should be passed before `main_class`. The arguments can contain
/// variables enclosed in `${}`, which should be replaced.
pub java_arguments: Vec>,
/// A list of arguments that should be passed after `main_class`. The arguments can contain
/// variables enclosed in `${}`, which should be replaced.
pub game_arguments: Vec>,
- // pub libraries: Vec>
+}
+
+/// A small snippet of game version information that is used in the [GameVersionIndex].
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct GameVersionSnippet {
+ /// The ID or "name" of this version (e.g. "1.12.2" or "23w32a").
+ pub id: String,
+ /// The stability of this version (e.g. release vs snapshot).
+ pub stability: GameVersionStability,
+}
+
+// === conversion ===
+impl From for GameVersionSnippet {
+ fn from(value: GameVersion) -> Self {
+ Self {
+ id: value.id,
+ stability: value.stability,
+ }
+ }
}
#[cfg(feature = "silo")]
@@ -69,6 +94,7 @@ impl From for GameVersion {
fn from(value: crate::silo::game::GameVersionLegacy) -> Self {
Self {
id: value.id,
+ release_date: value.release_time,
stability: GameVersionStability::from(value.stability),
java_version: VersionReq {
comparators: vec![Comparator {
@@ -80,14 +106,35 @@ impl From for GameVersion {
}],
},
main_class: value.main_class,
- java_arguments: vec![],
- game_arguments: vec![MaybeConditional::Unconditional(
- value
- .minecraft_arguments
- .split(' ')
- .map(|it| it.to_string())
- .collect(),
- )],
+ libraries: vec![],
+ java_arguments: vec![
+ MaybeConditional::Conditional {
+ when: Condition::OS(OS::MacOS),
+ then: "-XstartOnFirstThread".to_string()
+ },
+ MaybeConditional::Conditional {
+ when: Condition::OS(OS::Windows),
+ then: "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump".to_string()
+ },
+ MaybeConditional::Conditional {
+ when: Condition::OS(OS::Windows),
+ then: "-Dos.name=Windows 10".to_string()
+ },
+ MaybeConditional::Conditional {
+ when: Condition::OS(OS::Windows),
+ then: "-Dos.version=10.0".to_string()
+ },
+ MaybeConditional::Unconditional("-Djava.library.path=${natives_directory}".to_string()),
+ MaybeConditional::Unconditional("-Dminecraft.launcher.brand=${launcher_name}".to_string()),
+ MaybeConditional::Unconditional("-Dminecraft.launcher.version=${launcher_version}".to_string()),
+ MaybeConditional::Unconditional("-cp".to_string()),
+ MaybeConditional::Unconditional("${classpath}".to_string()),
+ ],
+ game_arguments: value
+ .minecraft_arguments
+ .split(' ')
+ .map(|it| MaybeConditional::Unconditional(it.to_string()))
+ .collect(),
}
}
}
@@ -97,6 +144,7 @@ impl From for GameVersion {
fn from(value: crate::silo::game::GameVersion17w43a) -> Self {
Self {
id: value.id,
+ release_date: value.release_time,
stability: GameVersionStability::from(value.stability),
java_version: VersionReq {
comparators: vec![Comparator {
@@ -108,6 +156,7 @@ impl From for GameVersion {
}],
},
main_class: value.main_class,
+ libraries: vec![],
java_arguments: value
.arguments
.jvm
@@ -133,3 +182,7 @@ impl From for GameVersion {
}
}
}
+
+// === test ===
+
+// TODO
diff --git a/modules/data/src/core/java.rs b/modules/data/src/core/java.rs
index 16713bb..0ea4e95 100644
--- a/modules/data/src/core/java.rs
+++ b/modules/data/src/core/java.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/data/src/core/library.rs b/modules/data/src/core/library.rs
index f5c5476..48a0772 100644
--- a/modules/data/src/core/library.rs
+++ b/modules/data/src/core/library.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -18,9 +18,11 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LibraryDownloadable {}
-/// A library is a JAR file that can
+/// A library is a JAR file that is downloaded and put into the `classpath` to be loaded by the JVM.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Library {
+ /// The name of the library, in this format: `com.example:hello:1.0`.
pub name: String,
+ /// The library file itself.
pub file: LibraryDownloadable,
}
diff --git a/modules/data/src/core.rs b/modules/data/src/core/mod.rs
similarity index 95%
rename from modules/data/src/core.rs
rename to modules/data/src/core/mod.rs
index 1a46ff6..0fc82b7 100644
--- a/modules/data/src/core.rs
+++ b/modules/data/src/core/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/data/src/lib.rs b/modules/data/src/lib.rs
index cbc6d42..c1bf70d 100644
--- a/modules/data/src/lib.rs
+++ b/modules/data/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -17,15 +17,17 @@
//! ====================
//!
//! This module contains data models for different APIs and services. The module itself is split
-//! into many sub-modules that categorize the data models based on where they are used:
+//! into many submodules that categorize the data models based on where they are used:
//!
+//! - [auth] - Models for storing user accounts and credentials.
//! - [core::assets]
//! - [core::conditional] - Data-driven condition API.
//! - [core::game]
-//! - [core::java]
+//! - [core::java] - Models for downloading and using Java builds.
//! - [silo] - Models for APIs used during Silo data generation (requires `silo` feature).
-//! - [web::auth] - Models relating to online account authentication.
+//! - [web::microsoft] - Models for interacting with Microsoft's authentication API.
+pub mod auth;
pub mod core;
pub mod silo;
pub mod web;
diff --git a/modules/data/src/silo/game.rs b/modules/data/src/silo/game.rs
index c7b8c8e..e844ac1 100644
--- a/modules/data/src/silo/game.rs
+++ b/modules/data/src/silo/game.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -13,11 +13,14 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+//! [assets] - Types for asset indeces.
//! [version] - Types for the files that `version_manifest_v2.json` links to.
//! [version_manifest] - Types for `version_manifest_v2.json` file.
+pub use assets::*;
pub use version::*;
pub use version_manifest::*;
mod version;
mod version_manifest;
+mod assets;
diff --git a/modules/data/src/silo/game/assets.rs b/modules/data/src/silo/game/assets.rs
new file mode 100644
index 0000000..b105b8e
--- /dev/null
+++ b/modules/data/src/silo/game/assets.rs
@@ -0,0 +1,27 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use std::collections::HashMap;
+
+#[macros::api_response]
+pub struct AssetIndex {
+ pub objects: HashMap,
+}
+
+#[macros::api_response]
+pub struct AssetIndexObject {
+ pub hash: String,
+ pub size: u64,
+}
diff --git a/modules/data/src/silo/game/version.rs b/modules/data/src/silo/game/version.rs
index b4bc3e9..bb5c6d5 100644
--- a/modules/data/src/silo/game/version.rs
+++ b/modules/data/src/silo/game/version.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/data/src/silo/game/version/legacy.rs b/modules/data/src/silo/game/version/legacy.rs
index 8ab5e33..600e165 100644
--- a/modules/data/src/silo/game/version/legacy.rs
+++ b/modules/data/src/silo/game/version/legacy.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -22,16 +22,14 @@ use macros::api_response;
use crate::silo::game::GameManifestStability;
-#[api_response]
-#[serde(rename_all = "camelCase")]
+#[api_response(rename = "camelCase")]
pub struct GameVersionLegacyJavaVersion {
pub component: String,
pub major_version: u64,
}
-#[api_response]
-#[serde(rename_all = "camelCase")]
-pub struct AssetIndex {
+#[api_response(rename = "camelCase")]
+pub struct GameVersionAssetIndex {
pub id: String,
pub sha1: String,
pub size: u64,
@@ -54,15 +52,13 @@ pub struct Downloads {
// pub server_mappings: Option,
}
-#[api_response(untagged = false)]
-#[serde(rename_all = "camelCase")]
+#[api_response(untagged = false, rename = "camelCase")]
pub enum LibraryRuleAction {
Allow,
Disallow,
}
-#[api_response(untagged = false)]
-#[serde(rename_all = "camelCase")]
+#[api_response(untagged = false, rename = "camelCase")]
pub enum Os {
Linux,
#[serde(rename = "osx")]
@@ -151,16 +147,14 @@ pub struct Logging {
pub client: LoggingClient,
}
-#[api_response]
-#[serde(rename_all = "camelCase")]
+#[api_response(rename = "camelCase")]
pub struct GameVersionLegacy {
- pub asset_index: AssetIndex,
+ pub asset_index: GameVersionAssetIndex,
pub assets: String,
pub compliance_level: Option,
pub downloads: Downloads,
pub id: String,
pub java_version: Option,
- // assume java 8 if none? maybe check launcher version?
pub main_class: String,
pub minecraft_arguments: String,
pub minimum_launcher_version: u64,
diff --git a/modules/data/src/silo/game/version/v17w43a.rs b/modules/data/src/silo/game/version/v17w43a.rs
index 26d00c8..f21c271 100644
--- a/modules/data/src/silo/game/version/v17w43a.rs
+++ b/modules/data/src/silo/game/version/v17w43a.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -18,7 +18,7 @@ use chrono::{DateTime, Utc};
use macros::api_response;
use crate::silo::game::{
- AssetIndex, Downloads, GameManifestStability, GameVersionLegacyJavaVersion, Library,
+ GameVersionAssetIndex, Downloads, GameManifestStability, GameVersionLegacyJavaVersion, Library,
LibraryRule, Logging,
};
@@ -43,11 +43,10 @@ pub struct ModernGameArguments {
pub jvm: Vec,
}
-#[api_response]
-#[serde(rename_all = "camelCase")]
+#[api_response(rename = "camelCase")]
pub struct GameVersion17w43a {
pub arguments: ModernGameArguments,
- pub asset_index: AssetIndex,
+ pub asset_index: GameVersionAssetIndex,
pub assets: String,
pub compliance_level: u8,
pub downloads: Downloads,
diff --git a/modules/data/src/silo/game/version_manifest.rs b/modules/data/src/silo/game/version_manifest.rs
index 5efe03f..b22fd99 100644
--- a/modules/data/src/silo/game/version_manifest.rs
+++ b/modules/data/src/silo/game/version_manifest.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -18,8 +18,7 @@ use url::Url;
use macros::api_response;
-#[api_response(untagged = false)]
-#[serde(rename_all = "snake_case")]
+#[api_response(untagged = false, rename = "snake_case")]
pub enum GameManifestStability {
Release,
Snapshot,
@@ -33,8 +32,7 @@ pub struct GameManifestLatest {
pub snapshot: String,
}
-#[api_response]
-#[serde(rename_all = "camelCase")]
+#[api_response(rename = "camelCase")]
pub struct GameManifestEntry {
pub id: String,
#[serde(rename = "type")]
diff --git a/modules/data/src/silo/java.rs b/modules/data/src/silo/java.rs
index ba166c0..888b3e4 100644
--- a/modules/data/src/silo/java.rs
+++ b/modules/data/src/silo/java.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/data/src/silo/java/zulu.rs b/modules/data/src/silo/java/zulu.rs
index f3f8e87..d0eba5f 100644
--- a/modules/data/src/silo/java/zulu.rs
+++ b/modules/data/src/silo/java/zulu.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/data/src/silo.rs b/modules/data/src/silo/mod.rs
similarity index 95%
rename from modules/data/src/silo.rs
rename to modules/data/src/silo/mod.rs
index e2bc51e..cfa2a70 100644
--- a/modules/data/src/silo.rs
+++ b/modules/data/src/silo/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/data/src/web/microsoft.rs b/modules/data/src/web/microsoft.rs
new file mode 100644
index 0000000..7a58a76
--- /dev/null
+++ b/modules/data/src/web/microsoft.rs
@@ -0,0 +1,86 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use std::collections::HashMap;
+use macros::{api_request, api_response};
+
+// Web URLs
+pub const AUTH_LOG_IN_URL: &str = "https://login.live.com/oauth20_authorize.srf";
+pub const AUTH_REDIRECT_URL: &str = "https://login.live.com/oauth20_desktop.srf";
+
+// API endpoints
+pub const AUTH_MS_TOKEN_URL: &str = "https://login.live.com/oauth20_token.srf";
+pub const AUTH_XBL_TOKEN_URL: &str = "https://user.auth.xboxlive.com/user/authenticate";
+pub const AUTH_XSTS_TOKEN_URL: &str = "https://xsts.auth.xboxlive.com/xsts/authorize";
+pub const AUTH_GAME_TOKEN_URL: &str = "https://api.minecraftservices.com/launcher/login";
+
+// MS token exchange properties
+pub const AUTH_CLIENT_ID: &str = "00000000402B5328";
+pub const AUTH_SCOPE: &str = "service::user.auth.xboxlive.com::MBI_SSL";
+
+// Xbox relying parties
+pub const AUTH_RP_XBOX: &str = "http://auth.xboxlive.com";
+pub const AUTH_RP_GAME: &str = "rp://api.minecraftservices.com/";
+
+#[api_response]
+pub struct AuthCodeExchangeResponse {
+ pub token_type: String,
+ pub expires_in: u64,
+ pub scope: String,
+ pub access_token: String,
+ pub refresh_token: String,
+ pub user_id: String,
+ pub foci: String,
+}
+
+#[api_request(rename = "PascalCase")]
+pub struct AuthXboxLiveTokenRequestProperties {
+ pub auth_method: &'static str,
+ pub site_name: &'static str,
+ pub rps_ticket: String,
+}
+
+#[api_request(rename = "PascalCase")]
+pub struct AuthXboxSecureTokenRequestProperties {
+ pub sandbox_id: &'static str,
+ pub user_tokens: [String; 1],
+}
+
+#[api_request(rename = "PascalCase")]
+pub struct AuthXboxTokenRequest {
+ pub properties: T,
+ pub relying_party: &'static str,
+ pub token_type: &'static str,
+}
+
+#[api_response(rename = "PascalCase")]
+pub struct AuthXboxTokenResponse {
+ pub issue_instant: String,
+ pub not_after: String,
+ pub token: String,
+ pub display_claims: HashMap>>,
+}
+
+#[api_request(rename = "PascalCase")]
+pub struct AuthGameTokenRequest {
+ pub platform: &'static str,
+ pub xtoken: String,
+}
+
+#[api_response]
+pub struct AuthGameTokenResponse {
+ pub access_token: String,
+ pub expires_in: i64,
+}
diff --git a/modules/data/src/web.rs b/modules/data/src/web/mod.rs
similarity index 90%
rename from modules/data/src/web.rs
rename to modules/data/src/web/mod.rs
index 8f5eae3..4fa5fdf 100644
--- a/modules/data/src/web.rs
+++ b/modules/data/src/web/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -13,4 +13,5 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-pub mod auth;
+pub mod microsoft;
+pub mod mojang;
diff --git a/modules/data/src/web/mojang.rs b/modules/data/src/web/mojang.rs
new file mode 100644
index 0000000..05b0b06
--- /dev/null
+++ b/modules/data/src/web/mojang.rs
@@ -0,0 +1,58 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use url::Url;
+
+use macros::api_response;
+
+pub const PROFILE_URL: &str = "https://api.minecraftservices.com/minecraft/profile";
+
+#[api_response(rename = "SCREAMING_SNAKE_CASE")]
+pub enum UserCosmeticState {
+ Inactive,
+ Active,
+}
+
+#[api_response(rename = "SCREAMING_SNAKE_CASE")]
+pub enum UserSkinVariant {
+ Classic,
+ Slim,
+}
+
+#[api_response]
+pub struct UserSkin {
+ pub id: String,
+ pub state: UserCosmeticState,
+ pub url: Url,
+ pub variant: UserSkinVariant,
+}
+
+#[api_response]
+pub struct UserCape {
+ #[serde(rename = "alias")]
+ pub name: String,
+ pub id: String,
+ pub state: UserCosmeticState,
+ pub url: Url,
+}
+
+#[api_response]
+pub struct UserProfile {
+ pub id: String,
+ #[serde(rename = "name")]
+ pub username: String,
+ pub skins: Box<[UserSkin]>,
+ pub capes: Box<[UserCape]>,
+}
diff --git a/modules/fetch/Cargo.toml b/modules/fetch/Cargo.toml
new file mode 100644
index 0000000..1bc1525
--- /dev/null
+++ b/modules/fetch/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "launcher-fetch"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+data = { path = "../data", version = "*", package = "launcher-data" }
+net = { path = "../net", version = "*", package = "launcher-net" }
+persistence = { path = "../persistence", version = "*", package = "launcher-persistence" }
+utils = { path = "../utils", version = "*", package = "launcher-utils" }
+
+thiserror = "1"
diff --git a/modules/fetch/src/lib.rs b/modules/fetch/src/lib.rs
new file mode 100644
index 0000000..b55a7e9
--- /dev/null
+++ b/modules/fetch/src/lib.rs
@@ -0,0 +1,34 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//! Launcher Fetch Module
+//! =====================
+//!
+//! This module contains high-level procedures for fetching and storing different things. It is a
+//! combination of the [data], [net], and [persistence] modules.
+//!
+//! It consists of a few separate parts:
+
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum Error {
+ #[error("network error: {0}")]
+ NetworkError(#[from] net::Error),
+}
+
+pub type Result = core::result::Result;
+
+pub mod mojang;
diff --git a/modules/fetch/src/mojang.rs b/modules/fetch/src/mojang.rs
new file mode 100644
index 0000000..55982c8
--- /dev/null
+++ b/modules/fetch/src/mojang.rs
@@ -0,0 +1,31 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use data::web::mojang::{PROFILE_URL, UserProfile};
+use net::{Client, Error, Method, Request};
+use net::header::HeaderValue;
+
+use super::Result;
+
+/// Gets a user's own profile from a game token.
+pub async fn get_profile(client: &Client, token: &str) -> Result {
+ let mut request = Request::new(Method::GET, PROFILE_URL.try_into().unwrap());
+
+ let value = HeaderValue::from_str(format!("Bearer {token}").as_str()).unwrap();
+ request.headers_mut().insert("Authorization", value);
+
+ let data: UserProfile = client.execute(request).await?.json().await.map_err(Error::from)?;
+ Ok(data)
+}
diff --git a/macros/Cargo.toml b/modules/macros/Cargo.toml
similarity index 100%
rename from macros/Cargo.toml
rename to modules/macros/Cargo.toml
diff --git a/modules/macros/src/api_request.rs b/modules/macros/src/api_request.rs
new file mode 100644
index 0000000..e66d921
--- /dev/null
+++ b/modules/macros/src/api_request.rs
@@ -0,0 +1,56 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+
+use darling::FromMeta;
+use quote::{quote, TokenStreamExt};
+use syn::{DeriveInput, parse_macro_input};
+use crate::utils::parse_params;
+
+#[derive(Debug, FromMeta)]
+struct MacroArgs {
+ #[darling(default)]
+ rename: Option
+}
+
+pub fn api_request(attr: TokenStream, item: TokenStream) -> TokenStream {
+ let args: MacroArgs = match parse_params(attr) {
+ Err(err) => return TokenStream::from(err.write_errors()),
+ Ok(args) => args,
+ };
+
+ // This is the code that we are augmenting (i.e. what is underneath our macro).
+ let ast = parse_macro_input!(item as DeriveInput);
+
+ // This is what we are going to append.
+ let base = quote! { #[derive(Clone, Debug, serde::Serialize)] };
+ let mut extra = quote! {};
+
+ // Add stuff to extra based on toggles and what we are modifying (struct or enum).
+ if let Some(rename) = args.rename {
+ extra.append_all(vec![quote! { #[serde(rename_all = #rename)] }]);
+ // extra.append_all(vec![ quote! { core::compile_error!("whats good") } ]);
+ }
+
+ (quote! {
+ #base
+ #extra
+ #ast
+ })
+ .into()
+}
diff --git a/macros/src/lib.rs b/modules/macros/src/api_response.rs
similarity index 70%
rename from macros/src/lib.rs
rename to modules/macros/src/api_response.rs
index 07199cb..eb64a96 100644
--- a/macros/src/lib.rs
+++ b/modules/macros/src/api_response.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -16,11 +16,10 @@
extern crate proc_macro;
use proc_macro::TokenStream;
-
-use darling::{Error, FromMeta};
-use darling::ast::NestedMeta;
+use darling::{FromMeta};
use quote::{quote, TokenStreamExt};
use syn::{Data, DeriveInput, parse_macro_input};
+use crate::utils::parse_params;
#[derive(Debug, FromMeta)]
struct MacroArgs {
@@ -28,16 +27,14 @@ struct MacroArgs {
strict: Option,
#[darling(default)]
untagged: Option,
+ #[darling(default)]
+ rename: Option,
}
-/// A shortcut for `#[derive(Clone, Debug, serde::Deserialize)]`.
-#[proc_macro_attribute]
pub fn api_response(attr: TokenStream, item: TokenStream) -> TokenStream {
- let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
- Ok(v) => v,
- Err(err) => {
- return TokenStream::from(Error::from(err).write_errors());
- }
+ let args: MacroArgs = match parse_params(attr) {
+ Err(err) => return TokenStream::from(err.write_errors()),
+ Ok(args) => args,
};
// This is the code that we are augmenting (i.e. what is underneath our macro).
@@ -47,24 +44,20 @@ pub fn api_response(attr: TokenStream, item: TokenStream) -> TokenStream {
let base = quote! { #[derive(Clone, Debug, serde::Deserialize)] };
let mut extra = quote! {};
- // These are the arguments that are passed between the parentheses of the macro invocation.
- let args = match MacroArgs::from_list(&attr_args) {
- Ok(args) => args,
- Err(err) => {
- return TokenStream::from(err.write_errors());
- }
- };
-
// Add stuff to extra based on toggles and what we are modifying (struct or enum).
if let Data::Enum(_) = &ast.data {
if args.untagged.unwrap_or(true) {
extra.append_all(vec![quote! { #[serde(untagged)] }]);
}
- };
+ }
if args.strict.unwrap_or(true) {
extra.append_all(vec![quote! { #[serde(deny_unknown_fields)] }]);
}
+
+ if let Some(rename) = args.rename {
+ extra.append_all(vec![quote! { #[serde(rename_all = #rename)] }]);
+ }
(quote! {
#base
@@ -73,8 +66,3 @@ pub fn api_response(attr: TokenStream, item: TokenStream) -> TokenStream {
})
.into()
}
-
-#[proc_macro_attribute]
-pub fn data_structure(attr: TokenStream, item: TokenStream) -> TokenStream {
- todo!()
-}
diff --git a/modules/macros/src/data_structure.rs b/modules/macros/src/data_structure.rs
new file mode 100644
index 0000000..790c1a0
--- /dev/null
+++ b/modules/macros/src/data_structure.rs
@@ -0,0 +1,64 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use proc_macro::TokenStream;
+
+use darling::FromMeta;
+use quote::{quote, TokenStreamExt};
+use syn::{Data, DeriveInput, parse_macro_input};
+
+use crate::utils::parse_params;
+
+#[derive(Debug, FromMeta)]
+struct MacroArgs {
+ #[darling(default)]
+ equatable: Option,
+ #[darling(default)]
+ hashable: Option,
+}
+
+pub fn data_structure(attr: TokenStream, item: TokenStream) -> TokenStream {
+ let args: MacroArgs = match parse_params(attr) {
+ Err(err) => return TokenStream::from(err.write_errors()),
+ Ok(args) => args,
+ };
+
+ // This is the code that we are augmenting (i.e. what is underneath our macro).
+ let ast = parse_macro_input!(item as DeriveInput);
+
+ // This is what we are going to append.
+ let base = quote! { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] };
+ let mut extra = quote! {};
+
+ // Add stuff to extra based on toggles and what we are modifying (struct or enum).
+ if let Data::Struct(_) = &ast.data {
+ extra.append_all(vec![quote! { #[serde(deny_unknown_fields)] }]);
+ }
+
+ if args.equatable.unwrap_or(false) {
+ extra.append_all(vec![quote! { #[derive(Eq, PartialEq)] }]);
+ }
+
+ if args.hashable.unwrap_or(false) {
+ extra.append_all(vec![quote! { #[derive(Hash)] }]);
+ }
+
+ (quote! {
+ #base
+ #extra
+ #ast
+ })
+ .into()
+}
diff --git a/modules/macros/src/lib.rs b/modules/macros/src/lib.rs
new file mode 100644
index 0000000..e5a1d29
--- /dev/null
+++ b/modules/macros/src/lib.rs
@@ -0,0 +1,48 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//! Launcher Macros
+//! ===============
+//!
+//! This module contains procedural macros that are used in various different places, most notably
+//! in the `data` module. They reduce code repetition and centralize the various traits that our
+//! data structures and API responses implement.
+
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+
+mod api_request;
+mod api_response;
+mod data_structure;
+mod utils;
+
+/// A more concise way to write `#[derive()]` macros for data structures (two-way serialization).
+#[proc_macro_attribute]
+pub fn data_structure(attr: TokenStream, item: TokenStream) -> TokenStream {
+ data_structure::data_structure(attr, item)
+}
+
+/// A more concise way to write `#[derive()]` macros for API requests (only serialization).
+#[proc_macro_attribute]
+pub fn api_request(attr: TokenStream, item: TokenStream) -> TokenStream {
+ api_request::api_request(attr, item)
+}
+
+/// A more concise way to write `#[derive()]` macros for API responses (only deserialization).
+#[proc_macro_attribute]
+pub fn api_response(attr: TokenStream, item: TokenStream) -> TokenStream {
+ api_response::api_response(attr, item)
+}
diff --git a/modules/macros/src/utils.rs b/modules/macros/src/utils.rs
new file mode 100644
index 0000000..3a3eb5e
--- /dev/null
+++ b/modules/macros/src/utils.rs
@@ -0,0 +1,27 @@
+// Copyright © 2023-2024 andre4ik3
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use proc_macro::TokenStream;
+use darling::{FromMeta, Error};
+
+/// Helper function to parse incoming attribute macro parameters.
+pub fn parse_params(attr: TokenStream) -> Result {
+ let args = match darling::ast::NestedMeta::parse_meta_list(attr.into()) {
+ Ok(args) => args,
+ Err(err) => return Err(Error::from(err)),
+ };
+
+ T::from_list(&args)
+}
diff --git a/modules/net/Cargo.toml b/modules/net/Cargo.toml
index 64e9d55..0665973 100644
--- a/modules/net/Cargo.toml
+++ b/modules/net/Cargo.toml
@@ -6,9 +6,13 @@ license = "GPL-3.0-or-later"
publish = false
[dependencies]
+async-trait = "0.1"
bytes = "1"
futures-util = "0.3"
-reqwest = { version = "0.11", features = ["brotli", "deflate", "gzip", "native-tls-alpn", "stream"] }
+reqwest = { version = "0.11", features = ["brotli", "deflate", "gzip", "json", "native-tls-alpn", "stream"] }
+serde = "1"
+serde_json = "1"
+serde_urlencoded = "0.7"
thiserror = "1"
tokio = { version = "1", features = ["parking_lot", "sync", "time"] }
tracing = "0.1"
diff --git a/modules/net/src/client.rs b/modules/net/src/client.rs
index c7220ba..0a7c5b2 100644
--- a/modules/net/src/client.rs
+++ b/modules/net/src/client.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -16,14 +16,14 @@
use std::{future::Future, sync::Arc};
use futures_util::StreamExt;
-use reqwest::{header, IntoUrl, Method, Request, Response};
+use reqwest::{IntoUrl, Method, Request, Response};
+use serde::Serialize;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tokio::time;
-use tracing::{debug, instrument, trace};
-use crate::{Error, queue, Result};
+use super::{Error, header::{self, HeaderValue}, queue, Result};
/// Maximum attempts for the client to make a request.
const MAX_ATTEMPTS: u64 = 3;
@@ -37,7 +37,7 @@ pub struct Client {
impl Client {
/// Creates a new client. A background request queue will be spawned to process requests.
- #[instrument(name = "net::Client")]
+ #[tracing::instrument(name = "net::Client")]
pub async fn new() -> Self {
let (queue, handle) = queue::spawn().await;
Self {
@@ -66,7 +66,7 @@ impl Client {
/// Attempts to run a closure 3 times. The closure is expected to return a type of [Result]
/// (that is, the alias type with [Error] as the error). If a network error ([Error::Network])
- /// is encountered, the given closure is ran again. On any other error (e.g.
+ /// is encountered, the given closure is run again. On any other error (e.g.
/// [Error::QueueShutDown]), all attempts are abandoned and the function returns immediately.
/// Upon exhaustion of all attempts, [Error::RequestAttemptsExhausted] is returned, containing
/// the number of attempts tried as well as the last error that occurred within the closure.
@@ -79,7 +79,7 @@ impl Client {
for attempt in 0..MAX_ATTEMPTS {
// Delay will be: 0 seconds on 1st attempt, 2 on 2nd, 8 on 3rd
let delay = attempt.pow(2) * 2;
- debug!("Attempt {}/{MAX_ATTEMPTS}. Waiting {delay}s.", attempt + 1);
+ tracing::debug!("Attempt {}/{MAX_ATTEMPTS}. Waiting {delay}s.", attempt + 1);
time::sleep(time::Duration::from_secs(delay)).await;
// Run the function
@@ -99,7 +99,7 @@ impl Client {
}
/// Executes a request with retry logic.
- #[instrument(name = "net::Client::execute", skip_all)]
+ #[tracing::instrument(name = "net::Client::execute", skip_all)]
pub async fn execute(&self, request: Request) -> Result {
let queue = self.queue.lock().await;
let queue = queue.as_ref().ok_or(Error::QueueShutDown)?;
@@ -113,7 +113,7 @@ impl Client {
/// Attempts to download a file to a destination with retry logic and interrupted download
/// resuming. The destination can be anything that implements [AsyncWrite] and [Unpin].
- #[instrument(name = "net::Client::download", skip_all)]
+ #[tracing::instrument(name = "net::Client::download", skip_all)]
pub async fn download(&self, url: impl IntoUrl, dest: impl AsyncWrite + Unpin) -> Result<()> {
let queue = self.queue.lock().await;
let queue = queue.as_ref().ok_or(Error::QueueShutDown)?;
@@ -141,17 +141,33 @@ impl Client {
while let Some(bytes) = stream.next().await {
// this will bail on network error
let bytes = bytes?;
- trace!("Received chunk of {} bytes", bytes.len());
+ tracing::trace!("Received chunk of {} bytes", bytes.len());
*length += dest.write(bytes.as_ref()).await?;
}
- debug!("{length} bytes transferred");
+ tracing::debug!("{length} bytes transferred");
dest.flush().await?;
Ok(())
})
.await
}
+ /// Shorthand for creating a POST request with a form body and using it with [Client::execute].
+ pub async fn post_form(&self, url: impl IntoUrl, body: &impl Serialize) -> Result {
+ let mut request = Request::new(Method::POST, url.into_url()?);
+ request.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded"));
+ *request.body_mut() = Some(serde_urlencoded::to_string(body)?.into());
+ self.execute(request).await
+ }
+
+ /// Shorthand for creating a POST request with a JSON body and using it with [Client::execute].
+ pub async fn post_json(&self, url: impl IntoUrl, body: &impl Serialize) -> Result {
+ let mut request = Request::new(Method::POST, url.into_url()?);
+ request.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
+ *request.body_mut() = Some(serde_json::to_string(body)?.into());
+ self.execute(request).await
+ }
+
/// Shorthand for creating a GET request and using it with [Client::execute].
pub async fn get(&self, url: impl IntoUrl) -> Result {
let request = Request::new(Method::GET, url.into_url()?);
diff --git a/modules/net/src/lib.rs b/modules/net/src/lib.rs
index 601c9bb..864d354 100644
--- a/modules/net/src/lib.rs
+++ b/modules/net/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -29,8 +29,8 @@ use thiserror::Error;
pub use client::Client;
-pub(crate) mod client;
-pub(crate) mod queue;
+mod client;
+mod queue;
#[derive(Debug, Error)]
pub enum Error {
@@ -44,6 +44,10 @@ pub enum Error {
RequestCloneFail,
#[error("queue has been shut down")]
QueueShutDown,
+ #[error("failed to serialize form body: {0}")]
+ FormSerializationFailure(#[from] serde_urlencoded::ser::Error),
+ #[error("failed to serialize json body: {0}")]
+ JsonSerializationFailure(#[from] serde_json::Error),
}
pub type Result = core::result::Result;
diff --git a/modules/net/src/queue.rs b/modules/net/src/queue.rs
index e810c71..6b96480 100644
--- a/modules/net/src/queue.rs
+++ b/modules/net/src/queue.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -19,7 +19,6 @@ use reqwest::{Client, Request, Response};
use tokio::sync::{mpsc, oneshot};
use tokio::task::JoinHandle;
use tokio::time::{interval, Interval};
-use tracing::{debug, error, instrument, trace, warn};
use crate::{Error, Result};
@@ -53,20 +52,20 @@ impl Queue {
/// Waits for the next job and processes it. The `cancel` argument is a oneshot receiver that,
/// upon being sent a value, will cause the queue to be shut down.
- #[instrument(name = "net::Queue", skip_all)]
+ #[tracing::instrument(name = "net::Queue", skip_all)]
pub async fn run(&mut self) {
- trace!("Running request queue.");
+ tracing::trace!("Running request queue.");
loop {
// Wait for ratelimit
self.interval.tick().await;
// Get the next request, or if all transmitters have been dropped, shut down.
let Some((request, tx)) = self.rx.recv().await else {
- debug!("Queue receive channel closed, shutting down.");
+ tracing::debug!("Queue receive channel closed, shutting down.");
return;
};
- trace!("Processing request: {} {}", request.method(), request.url());
+ tracing::trace!("Processing request: {} {}", request.method(), request.url());
// Execute the actual request.
let result = self.client.execute(request).await;
@@ -74,10 +73,10 @@ impl Queue {
// Try to send back response, warn in logs if failed.
if let Err(result) = tx.send(result) {
- warn!("Failed to send back response.");
+ tracing::warn!("Failed to send back response.");
match result {
- Ok(resp) => warn!("Response was Ok: {} {}", resp.status(), resp.url()),
- Err(err) => warn!("Response was Err: {err}"),
+ Ok(resp) => tracing::warn!("Response was Ok: {} {}", resp.status(), resp.url()),
+ Err(err) => tracing::warn!("Response was Err: {err}"),
}
};
}
@@ -93,28 +92,28 @@ impl QueueClient {
pub async fn execute(&self, request: Request) -> Result {
let (tx, rx) = oneshot::channel();
- debug!("--> {} {}", request.method(), request.url());
+ tracing::debug!("--> {} {}", request.method(), request.url());
// Send request to queue for processing
if let Err(err) = self.0.send((request, tx)).await {
- warn!("Failed to send request to queue: {err}");
+ tracing::warn!("Failed to send request to queue: {err}");
return Err(Error::QueueShutDown);
};
// Wait for queue to send back result
let Ok(result) = rx.await else {
- warn!("Failed to get response from queue");
+ tracing::warn!("Failed to get response from queue");
return Err(Error::QueueShutDown);
};
// Some pretty logging based on the outcome
match result {
Ok(response) => {
- debug!("<-- {} {}", response.status(), response.url());
+ tracing::debug!("<-- {} {}", response.status(), response.url());
Ok(response)
}
Err(err) => {
- error!("[!] {err}");
+ tracing::error!("[!] {err}");
Err(Error::from(err))
}
}
@@ -124,7 +123,7 @@ impl QueueClient {
/// Creates a new queue and spawns it as a background task, returning a [QueueClient] and a
/// [JoinHandle].
pub async fn spawn() -> (QueueClient, JoinHandle<()>) {
- trace!("Spawning off-thread queue.");
+ tracing::trace!("Spawning off-thread queue.");
// Channel used to interact with the background task.
let (tx, rx) = mpsc::channel(20);
diff --git a/modules/persistence/src/crypto.rs b/modules/persistence/src/crypto.rs
index e654cd9..5abc38f 100644
--- a/modules/persistence/src/crypto.rs
+++ b/modules/persistence/src/crypto.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -22,7 +22,6 @@ use aes_gcm::aead::{Aead, Nonce, OsRng};
use keyring::Entry;
use thiserror::Error;
use tokio::{fs, task};
-use tracing::{debug, error, instrument, warn};
#[derive(Debug, Error)]
pub enum Error {
@@ -113,7 +112,7 @@ async fn keyfile_clean(file: impl AsRef) -> Result<()> {
// === Reading/Writing Credentials ===
/// Gets a credential for the specified file. Will try both system keychain and key file.
-#[instrument(name = "crypto::read_key")]
+#[tracing::instrument(name = "crypto::read_key")]
pub async fn read_key(file: impl AsRef + Debug) -> Option> {
let file = file.as_ref().to_owned();
@@ -127,14 +126,14 @@ pub async fn read_key(file: impl AsRef + Debug) -> Option>
.await
.expect("blocking thread panicked");
- debug!("Keyring key: {}", keyring_key.is_ok());
- debug!("Keyfile key: {}", keyfile_key.is_ok());
+ tracing::debug!("Keyring key: {}", keyring_key.is_ok());
+ tracing::debug!("Keyfile key: {}", keyfile_key.is_ok());
keyring_key.ok().or(keyfile_key.ok())
}
/// Saves a credential for the specified file.
-#[instrument(name = "crypto::write_key", skip(key))]
+#[tracing::instrument(name = "crypto::write_key", skip(key))]
pub async fn write_key(file: impl AsRef + Debug, key: Key) -> Result<()> {
let file = file.as_ref().to_owned();
let elif = file.clone(); // hack, this one is moved into the closure below
@@ -151,12 +150,12 @@ pub async fn write_key(file: impl AsRef + Debug, key: Key) -> R
match keyring_result {
Ok(()) => {
if let Err(err) = keyfile_clean(&file).await {
- warn!("Failed to cleanup keyfile after successful keychain migration: {err}");
+ tracing::warn!("Failed to cleanup keyfile after successful keychain migration: {err}");
};
Ok(())
}
Err(err) => {
- debug!("Failed to write to keychain, trying keyfile instead: {err}");
+ tracing::debug!("Failed to write to keychain, trying keyfile instead: {err}");
keyfile_write(&file, &key).await
}
}
@@ -166,7 +165,7 @@ pub async fn write_key(file: impl AsRef + Debug, key: Key) -> R
/// Generates a new [Key\].
pub async fn generate_key() -> Key {
- debug!("Generating a new key...");
+ tracing::debug!("Generating a new key...");
task::spawn_blocking(|| Aes256Gcm::generate_key(OsRng))
.await
.expect("blocking thread panicked")
@@ -174,7 +173,7 @@ pub async fn generate_key() -> Key {
/// Generates a new [Nonce\].
pub async fn generate_nonce() -> Nonce {
- debug!("Generating a new nonce...");
+ tracing::debug!("Generating a new nonce...");
task::spawn_blocking(|| Aes256Gcm::generate_nonce(OsRng))
.await
.expect("blocking thread panicked")
@@ -183,10 +182,10 @@ pub async fn generate_nonce() -> Nonce {
/// Attempts to decrypt a read file. The first 12 bytes of the data are treated as the nonce (e.g.
/// from the [encrypt] function).
pub async fn decrypt(mut data: Vec, key: Key) -> Result {
- debug!("Decrypting {} bytes of data", data.len());
+ tracing::debug!("Decrypting {} bytes of data", data.len());
if data.len() < 12 {
- error!("Data length is too small, maybe corrupted?");
+ tracing::error!("Data length is too small, maybe corrupted?");
return Err(Error::NonceSize(data.len()));
}
@@ -203,7 +202,7 @@ pub async fn decrypt(mut data: Vec, key: Key) -> Result {
// Convert the data to a string
let data = String::from_utf8(data)?;
- debug!(
+ tracing::debug!(
"Decryption success, returning {} decrypted bytes",
data.len()
);
@@ -213,7 +212,7 @@ pub async fn decrypt(mut data: Vec, key: Key) -> Result {
/// Attempts to encrypt some data. The result will be a concatenated nonce + payload, suitable for
/// use with [decrypt] function.
pub async fn encrypt(data: Vec, key: Key) -> Result> {
- debug!("Encrypting {} bytes of data", data.len());
+ tracing::debug!("Encrypting {} bytes of data", data.len());
let nonce = generate_nonce().await;
let data = task::spawn_blocking(move || Aes256Gcm::new(&key).encrypt(&nonce, data.as_slice()))
@@ -223,7 +222,7 @@ pub async fn encrypt(data: Vec, key: Key) -> Result> {
// chain the nonce and encrypted payload together
let data: Vec = nonce.into_iter().chain(data.into_iter()).collect();
- debug!(
+ tracing::debug!(
"Encryption success, returning {} encrypted bytes",
data.len()
);
diff --git a/modules/persistence/src/lib.rs b/modules/persistence/src/lib.rs
index 89a99c1..f97c5f3 100644
--- a/modules/persistence/src/lib.rs
+++ b/modules/persistence/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/persistence/src/registry/directory.rs b/modules/persistence/src/registry/directory.rs
index d2dce23..047fbc1 100644
--- a/modules/persistence/src/registry/directory.rs
+++ b/modules/persistence/src/registry/directory.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use tokio::fs;
-use tracing::{error, instrument, trace};
use utils::directories;
@@ -46,12 +45,12 @@ impl DirectoryRegistry
T: for<'a> Deserialize<'a> + Serialize,
{
/// Creates a new directory registry and loads any existing entries from the base into memory.
- #[instrument(name = "DirectoryRegistry::new")]
+ #[tracing::instrument(name = "DirectoryRegistry::new")]
pub async fn new(base: &str, file: &'static str) -> Result> {
let base = directories::DATA.join(base);
fs::create_dir_all(&base).await?;
- trace!("Creating new directory registry at {}", base.display());
+ tracing::trace!("Creating new directory registry at {}", base.display());
let mut registry = Self {
base,
file,
@@ -95,9 +94,9 @@ impl DirectoryRegistry
}
/// Reads all entries from disk into memory.
- #[instrument(name = "DirectoryRegistry::refresh", skip(self), fields(base = % self.base.display()))]
+ #[tracing::instrument(name = "DirectoryRegistry::refresh", skip(self), fields(base = % self.base.display()))]
pub async fn refresh(&mut self) -> Result<()> {
- trace!("Starting refresh.");
+ tracing::trace!("Starting refresh.");
let mut stream = fs::read_dir(&self.base).await?;
while let Some(entry) = stream.next_entry().await? {
@@ -106,13 +105,13 @@ impl DirectoryRegistry
}
let path = entry.path().join(self.file);
- trace!("Considering path {}", path.display());
+ tracing::trace!("Considering path {}", path.display());
// read file to string, continue with loop if failed
let data = match fs::read_to_string(&path).await {
Ok(data) => data,
Err(err) => {
- error!("Failed to read entry at {}: {err}", path.display());
+ tracing::error!("Failed to read entry at {}: {err}", path.display());
continue;
}
};
@@ -121,17 +120,17 @@ impl DirectoryRegistry
let data = match toml::from_str(&data) {
Ok(data) => data,
Err(err) => {
- error!("Failed to read entry at {}: {err}", path.display());
+ tracing::error!("Failed to read entry at {}: {err}", path.display());
continue;
}
};
let id = entry.file_name().to_string_lossy().to_string();
- trace!("Adding {id} to entries");
+ tracing::trace!("Adding {id} to entries");
self.entries.insert(id, data);
}
- trace!("Done refreshing.");
+ tracing::trace!("Done refreshing.");
Ok(())
}
}
diff --git a/modules/persistence/src/registry/file.rs b/modules/persistence/src/registry/file.rs
index 17b0354..7a25a70 100644
--- a/modules/persistence/src/registry/file.rs
+++ b/modules/persistence/src/registry/file.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -19,7 +19,6 @@ use aes_gcm::{Aes256Gcm, Key};
use serde::{Deserialize, Serialize};
use tokio::fs;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
-use tracing::{instrument, trace, warn};
use utils::directories;
@@ -47,12 +46,12 @@ impl FileRegistry
{
/// Creates the registry and reads the file from disk into memory. The file should have a .toml
/// extension.
- #[instrument(name = "FileRegistry::new")]
+ #[tracing::instrument(name = "FileRegistry::new")]
pub async fn new(file: &'static str) -> Result> {
let data = RwLock::new(T::default());
let path = directories::CONFIG.join(file);
- trace!("Creating new file registry at {}", path.display());
+ tracing::trace!("Creating new file registry at {}", path.display());
let registry = FileRegistry {
data,
path,
@@ -60,8 +59,8 @@ impl FileRegistry
};
if registry.load().await.is_err() {
- warn!("Failed to read registry. It will be overwritten and initialized with the defaults.");
- };
+ tracing::warn!("Failed to read registry. It will be overwritten and initialized with the defaults.");
+ }
registry.save().await?;
Ok(registry)
@@ -70,21 +69,21 @@ impl FileRegistry
/// Creates an encrypted registry. The file on disk will be encrypted, with the encryption key
/// stored in the system's keychain or, if unavailable, a side-by-side keyfile. The file should
/// have a .dat extension.
- #[instrument(name = "FileRegistry::new_encrypted")]
+ #[tracing::instrument(name = "FileRegistry::new_encrypted")]
pub async fn new_encrypted(file: &'static str) -> Result> {
let data = RwLock::new(T::default());
let path = directories::CONFIG.join(file);
- trace!("Creating new encrypted file registry at {}", path.display());
+ tracing::trace!("Creating new encrypted file registry at {}", path.display());
// attempt to retrieve encryption key or make a new one
let encryption_key = match crypto::read_key(&path).await {
Some(key) => {
- trace!("Successfully found existing encryption key");
+ tracing::trace!("Successfully found existing encryption key");
key
}
None => {
- warn!("Could not find an encryption key, generating a new one...");
+ tracing::warn!("Could not find an encryption key, generating a new one...");
crypto::generate_key().await
}
};
@@ -99,8 +98,8 @@ impl FileRegistry
};
if registry.load().await.is_err() {
- warn!("Failed to read encrypted registry (possibly due to missing/wrong encryption key). It will be overwritten and initialized with the defaults.");
- };
+ tracing::warn!("Failed to read encrypted registry (possibly due to missing/wrong encryption key). It will be overwritten and initialized with the defaults.");
+ }
registry.save().await?;
Ok(registry)
@@ -118,9 +117,9 @@ impl FileRegistry
}
/// Loads the file from disk.
- #[instrument(name = "FileRegistry::load", skip(self), fields(file = % self.path.display()))]
+ #[tracing::instrument(name = "FileRegistry::load", skip(self), fields(file = % self.path.display()))]
pub async fn load(&self) -> Result<()> {
- trace!("Reading file...");
+ tracing::trace!("Reading file...");
let mut lock = self.data.write().await;
let data = match self.encryption_key {
@@ -135,9 +134,9 @@ impl FileRegistry
}
/// Saves the file to disk.
- #[instrument(name = "FileRegistry::save", skip(self), fields(file = % self.path.display()))]
+ #[tracing::instrument(name = "FileRegistry::save", skip(self), fields(file = % self.path.display()))]
pub async fn save(&self) -> Result<()> {
- trace!("Writing file...");
+ tracing::trace!("Writing file...");
let lock = self.data.read().await;
let data = toml::to_string(&*lock)?;
diff --git a/modules/persistence/src/registry/mod.rs b/modules/persistence/src/registry/mod.rs
index 66f4e26..17020de 100644
--- a/modules/persistence/src/registry/mod.rs
+++ b/modules/persistence/src/registry/mod.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/utils/Cargo.toml b/modules/utils/Cargo.toml
index 5cb0cf1..6ffcab7 100644
--- a/modules/utils/Cargo.toml
+++ b/modules/utils/Cargo.toml
@@ -7,12 +7,19 @@ publish = false
[dependencies]
directories = "5"
-md-5 = "0.10"
once_cell = "1"
-sha1 = "0.10"
-sha2 = "0.10"
thiserror = "1"
tokio = { version = "1", features = ["fs"] }
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["parking_lot"] }
+
+# archive
+tar = "0.4"
+xz2 = { version = "0.1", features = ["tokio"] }
+zip = "0.6"
+
+# crypto
+md-5 = "0.10"
+sha1 = "0.10"
+sha2 = "0.10"
diff --git a/modules/data/src/web/auth.rs b/modules/utils/src/archive.rs
similarity index 94%
rename from modules/data/src/web/auth.rs
rename to modules/utils/src/archive.rs
index 622cf96..895eab1 100644
--- a/modules/data/src/web/auth.rs
+++ b/modules/utils/src/archive.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/utils/src/directories.rs b/modules/utils/src/directories.rs
index ae56c7f..0b3c7c9 100644
--- a/modules/utils/src/directories.rs
+++ b/modules/utils/src/directories.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/modules/utils/src/lib.rs b/modules/utils/src/lib.rs
index c19bd7c..7b1173e 100644
--- a/modules/utils/src/lib.rs
+++ b/modules/utils/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -13,5 +13,6 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+pub mod archive;
pub mod directories;
pub mod log;
diff --git a/modules/utils/src/log.rs b/modules/utils/src/log.rs
index cd7a954..85ab3ab 100644
--- a/modules/utils/src/log.rs
+++ b/modules/utils/src/log.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/silo/Cargo.toml b/silo/Cargo.toml
index d7ce659..e3ff9c4 100644
--- a/silo/Cargo.toml
+++ b/silo/Cargo.toml
@@ -21,8 +21,6 @@ tracing = "0.1"
reqwest = { version = "0.11", features = ["json", "brotli", "native-tls-alpn"] }
ron = "0.8"
serde = { version = "1", features = ["derive"] }
-tar = "0.4"
-xz2 = "0.1"
serde_json = "1"
diff --git a/silo/src/cli.rs b/silo/src/cli.rs
index 2ea0b5a..4e90573 100644
--- a/silo/src/cli.rs
+++ b/silo/src/cli.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -27,6 +27,9 @@ pub struct Cli {
/// The tasks to run (can run other tasks if specified tasks depend on output)
#[arg(short, long)]
pub task: Vec,
+ /// Whether to refresh everything fully (even if it's already found on disk).
+ #[arg(long)]
+ pub power_wash: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
diff --git a/silo/src/macros.rs b/silo/src/macros.rs
index eee31ba..dfb3336 100644
--- a/silo/src/macros.rs
+++ b/silo/src/macros.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/silo/src/main.rs b/silo/src/main.rs
index 797e078..d527bf4 100644
--- a/silo/src/main.rs
+++ b/silo/src/main.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -48,17 +48,18 @@ async fn main() -> anyhow::Result<()> {
let _guard = utils::log::setup();
info!("Running Silo v{PACKAGE_VERSION}");
- info!("{:?}", args.task);
fs::create_dir_all(&args.output).await?;
let root = fs::canonicalize(&args.output).await?;
+ info!("Tasks to do: {:?}", args.task);
info!("Output directory: {}", root.display());
+ info!("Power wash mode: {}", args.power_wash);
// === Game Versions ===
if args.task.contains(&cli::TaskName::GameVersions) {
info!("Running Game Versions task...");
- let versions = TaskGameVersions::run(&root, ()).await?;
+ let versions = TaskGameVersions::run(&root, args.power_wash).await?;
info!("Game Versions task complete. Successfully retrieved {} versions.", versions.len());
}
diff --git a/silo/src/task.rs b/silo/src/task.rs
index 42863bf..1dd8c75 100644
--- a/silo/src/task.rs
+++ b/silo/src/task.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/silo/src/task/game_versions.rs b/silo/src/task/game_versions.rs
index 6f02509..907f369 100644
--- a/silo/src/task/game_versions.rs
+++ b/silo/src/task/game_versions.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -17,7 +17,6 @@ use std::path::Path;
use async_trait::async_trait;
use tokio::fs;
-use tracing::info;
use data::core::game;
use data::silo::game::{GameManifest, GameVersion};
@@ -34,13 +33,14 @@ pub struct TaskGameVersions;
#[async_trait]
impl Task for TaskGameVersions {
- type Input = ();
+ /// Whether we should fetch versions that already exist on disk (`true`) or skip them (`false`).
+ type Input = bool;
- /// An array that includes newly fetched game versions (not the ones from disk).
- type Output = Vec;
+ /// An array of snippets of all game versions (both newly fetched & already stored).
+ type Output = Vec;
#[tracing::instrument(name = "TaskGameVersions", skip_all)]
- async fn run(root: impl AsRef + Send + Sync, _input: Self::Input) -> anyhow::Result {
+ async fn run(root: impl AsRef + Send + Sync, input: Self::Input) -> anyhow::Result {
let client = client().await;
let mut output = Vec::new();
@@ -50,11 +50,16 @@ impl Task for TaskGameVersions {
// Then, for every version...
for version in manifest.versions {
let path = root.as_ref().join(format!("game/{}.ron", version.id));
- // ...if we don't already have it...
- if fs::try_exists(&path).await.unwrap_or(false) {
- // TODO: Add a sort of "power wash" setting that fetches already existing versions.
- info!("Skipping version {} as it appears we already have it.", version.id);
- continue;
+ // ...if we don't already have it (and we're not set to do a power wash)...
+ if fs::try_exists(&path).await.unwrap_or(false) && !input {
+ if let Ok(data) = ron::from_str::(&fs::read_to_string(&path).await?) {
+ tracing::info!("Skipping version {} as it appears we already have it.", version.id);
+ output.push(game::GameVersionSnippet::from(data));
+ continue;
+ } else {
+ tracing::warn!("Removing malformed version {} at {}", version.id, path.display());
+ fs::remove_file(&path).await?;
+ }
}
// ...fetch the details of the game version...
@@ -64,12 +69,15 @@ impl Task for TaskGameVersions {
let data = game::GameVersion::from(data);
// ...and save it to disk.
- info!("Fetched version {}.", version.id);
+ tracing::info!("Fetched version {}.", version.id);
write_to_ron_file(&path, &data).await?;
- output.push(data);
+ output.push(game::GameVersionSnippet::from(data));
}
- info!("Loaded {} game versions", output.len());
+ // Finally, write an index of all available versions.
+ write_to_ron_file(root.as_ref().join("game.ron"), &output).await?;
+
+ tracing::info!("Loaded {} game versions", output.len());
Ok(output)
}
}
diff --git a/silo/src/task/java.rs b/silo/src/task/java.rs
index 25c9b52..cb1621f 100644
--- a/silo/src/task/java.rs
+++ b/silo/src/task/java.rs
@@ -1,4 +1,4 @@
-// Copyright © 2023 andre4ik3
+// Copyright © 2023-2024 andre4ik3
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@ use std::path::Path;
use async_trait::async_trait;
use platforms::{Arch, OS};
use semver::Version;
-use tracing::{info, warn};
use url::Url;
use data::core::java::{Environment, JavaBuild, JavaEdition, JavaProvider};
@@ -26,7 +25,8 @@ use data::silo::java::zulu::{ZuluDetails, ZuluMetadata};
use crate::client;
use crate::macros::write_to_ron_file;
-use crate::task::Task;
+
+use super::Task;
/// The base URL that other parameters will get appended to.
const BASE_URL: &str = "https://api.azul.com/metadata/v1/zulu/packages/?javafx_bundled=false&crac_supported=false&latest=true&release_status=ga&availability_types=CA&certifications=tck";
@@ -64,7 +64,10 @@ pub struct TaskJava;
#[async_trait]
impl Task for TaskJava {
+ /// A list of major Java versions to fetch builds for (e.g. `vec![8, 16, 17]`).
type Input = Vec;
+
+ /// A list of fetched Java builds.
type Output = Vec;
#[tracing::instrument(name = "TaskJava", skip_all)]
@@ -134,11 +137,11 @@ impl Task for TaskJava {
// ...and write it to disk.
let path = root.as_ref().join(format!("java/{version}/{}-{}.ron", environment.os, environment.arch));
- info!("Fetched Java {version} for {} {}.", environment.os, environment.arch);
+ tracing::info!("Fetched Java {version} for {} {}.", environment.os, environment.arch);
write_to_ron_file(&path, &build).await?;
output.push(build);
} else {
- warn!("Could not find build for Java {version} for {} {}!", environment.os, environment.arch);
+ tracing::warn!("Could not find build for Java {version} for {} {}!", environment.os, environment.arch);
}
}
}