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); } } }