diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 53422ca..b183c09 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -47,6 +58,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -213,6 +233,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -225,6 +251,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -334,6 +369,25 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "cairo-rs" version = "0.18.5" @@ -408,6 +462,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -425,7 +481,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", "fnv", - "uuid", + "uuid 1.23.0", ] [[package]] @@ -444,6 +500,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" version = "0.4.44" @@ -451,11 +524,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link 0.2.1", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "combine" version = "4.6.7" @@ -475,6 +560,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -491,6 +582,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -514,9 +615,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.11.0", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -527,7 +628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.11.0", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -540,6 +641,30 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -558,6 +683,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -658,6 +802,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + [[package]] name = "deranged" version = "0.5.8" @@ -668,6 +818,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -710,6 +871,16 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", ] [[package]] @@ -830,6 +1001,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "embed-resource" version = "3.0.8" @@ -841,7 +1018,7 @@ dependencies = [ "rustc_version", "toml 0.9.12+spec-1.1.0", "vswhom", - "winreg", + "winreg 0.55.0", ] [[package]] @@ -850,6 +1027,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.1" @@ -904,6 +1090,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-emitter-rs" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dccdd0a59457ba353cc44c26d431ca5089f2cf93035c780a1b3f2814a017ebd" +dependencies = [ + "bincode", + "lazy_static", + "serde", + "uuid 0.8.2", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -984,6 +1182,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -991,7 +1198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1005,6 +1212,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1030,6 +1243,21 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1037,6 +1265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1104,6 +1333,7 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1250,8 +1480,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1261,9 +1493,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -1275,6 +1509,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -1427,6 +1662,44 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1472,6 +1745,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.29.1" @@ -1494,6 +1776,17 @@ dependencies = [ "markup5ever 0.38.0", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -1504,6 +1797,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1511,7 +1815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1522,8 +1826,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1533,6 +1837,36 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.9.0" @@ -1543,8 +1877,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http", - "http-body", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", @@ -1553,6 +1888,53 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.9.0", + "hyper-util", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.9.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -1563,17 +1945,19 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.9.0", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.3", + "system-configuration 0.7.0", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1757,6 +2141,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1865,6 +2258,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.94" @@ -1922,6 +2325,12 @@ dependencies = [ "selectors 0.24.0", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -2004,6 +2413,55 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lyceris" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c799e4b5b55c154f40fa544e9a2eb42974a7e8264b1fb9be1119d9019d4de3a0" +dependencies = [ + "base64 0.22.1", + "event-emitter-rs", + "futures", + "oauth2", + "rayon", + "regex", + "reqwest 0.12.28", + "serde", + "serde_json", + "sha1", + "thiserror 2.0.18", + "tokio", + "uuid 1.23.0", + "zip", +] + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "mac" version = "0.1.1" @@ -2115,6 +2573,23 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2194,6 +2669,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom 0.2.17", + "http 0.2.12", + "rand 0.8.5", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + [[package]] name = "objc2" version = "0.6.4" @@ -2335,6 +2830,50 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2411,6 +2950,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2784,6 +3333,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.37", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls 0.23.37", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -2830,6 +3434,27 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2850,6 +3475,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2868,6 +3503,21 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_hc" version = "0.2.0" @@ -2892,6 +3542,26 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2961,6 +3631,94 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.9.0", + "hyper-rustls 0.27.7", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.4.2", + "web-sys", +] + [[package]] name = "reqwest" version = "0.13.2" @@ -2971,10 +3729,10 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "js-sys", "log", @@ -2982,7 +3740,7 @@ dependencies = [ "pin-project-lite", "serde", "serde_json", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-util", "tower", @@ -2991,10 +3749,24 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.5.0", "web-sys", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -3023,12 +3795,96 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.10", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" @@ -3042,11 +3898,27 @@ dependencies = [ name = "saturn-launcher" version = "0.1.0" dependencies = [ + "directories", + "futures", + "lyceris", + "once_cell", + "rayon", + "reqwest 0.12.28", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-opener", + "tokio", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", ] [[package]] @@ -3061,7 +3933,7 @@ dependencies = [ "serde", "serde_json", "url", - "uuid", + "uuid 1.23.0", ] [[package]] @@ -3106,6 +3978,39 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.24.0" @@ -3219,6 +4124,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -3248,6 +4164,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.18.0" @@ -3320,6 +4248,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3327,7 +4266,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -3377,6 +4316,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.3" @@ -3496,6 +4445,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -3529,6 +4484,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3549,6 +4510,48 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3570,7 +4573,7 @@ checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ "bitflags 2.11.0", "block2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dispatch2", @@ -3633,7 +4636,7 @@ dependencies = [ "glob", "gtk", "heck 0.5.0", - "http", + "http 1.4.0", "jni", "libc", "log", @@ -3647,7 +4650,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.13.2", "serde", "serde_json", "serde_repr", @@ -3713,7 +4716,7 @@ dependencies = [ "thiserror 2.0.18", "time", "url", - "uuid", + "uuid 1.23.0", "walkdir", ] @@ -3779,7 +4782,7 @@ dependencies = [ "cookie", "dpi", "gtk", - "http", + "http 1.4.0", "jni", "objc2", "objc2-ui-kit", @@ -3802,7 +4805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" dependencies = [ "gtk", - "http", + "http 1.4.0", "jni", "log", "objc2", @@ -3834,7 +4837,7 @@ dependencies = [ "dunce", "glob", "html5ever 0.29.1", - "http", + "http 1.4.0", "infer", "json-patch", "kuchikiki", @@ -3855,7 +4858,7 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", "url", "urlpattern", - "uuid", + "uuid 1.23.0", "walkdir", ] @@ -3985,6 +4988,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.51.0" @@ -3994,11 +5012,55 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", - "socket2", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.37", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -4126,7 +5188,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -4141,8 +5203,8 @@ dependencies = [ "bitflags 2.11.0", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower", @@ -4303,6 +5365,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -4340,6 +5408,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "uuid" version = "1.23.0" @@ -4348,10 +5425,17 @@ checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", + "rand 0.10.0", "serde_core", "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.1" @@ -4510,6 +5594,19 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm-streams" version = "0.5.0" @@ -4545,6 +5642,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web_atoms" version = "0.2.3" @@ -4601,6 +5708,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "webview2-com" version = "0.38.2" @@ -4786,6 +5899,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -4831,6 +5955,24 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -4873,6 +6015,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4930,6 +6087,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4948,6 +6111,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4966,6 +6135,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4996,6 +6171,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5014,6 +6195,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5032,6 +6219,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5050,6 +6243,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5089,6 +6288,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.55.0" @@ -5209,7 +6418,7 @@ dependencies = [ "dunce", "gdkx11", "gtk", - "http", + "http 1.4.0", "javascriptcore-rs", "jni", "libc", @@ -5258,6 +6467,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yoke" version = "0.8.2" @@ -5308,7 +6526,7 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "uuid", + "uuid 1.23.0", "windows-sys 0.61.2", "winnow 0.7.15", "zbus_macros", @@ -5383,6 +6601,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zerotrie" version = "0.2.4" @@ -5416,12 +6654,82 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap 2.13.1", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "thiserror 2.0.18", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zvariant" version = "5.10.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 79f6980..3c502cc 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,4 +22,10 @@ tauri = { version = "2", features = [] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" - +directories = "6.0" +once_cell = "1.21" +lyceris = "1.1.3" +rayon = "1.10.0" +reqwest = "0.12.9" +futures = "0.3" +tokio = { version = "1.51.0", features = ["fs"] } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 3fc9f18..2fc1a58 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,12 +1,6 @@ -use tauri::{AppHandle, Emitter, State}; +use tauri::State; -use crate::{GameVersion, LauncherSettings, Mod, SharedState}; - -#[tauri::command] -pub fn get_versions(state: State) -> Vec { - let state = state.lock().unwrap(); - state.versions.clone() -} +use crate::{LauncherSettings, Mod, SharedState}; #[tauri::command] pub fn get_installed_mods(state: State, version_id: String) -> Vec { @@ -47,28 +41,3 @@ pub fn update_settings(state: State, new_settings: LauncherSettings let mut state = state.lock().unwrap(); state.settings = new_settings; } - -#[tauri::command] -pub fn launch_game(app: AppHandle, version_id: String) -> Result<(), String> { - std::thread::spawn(move || { - app.emit("is-launching", "true").unwrap(); - app.emit("launch-status", "Preparing...").unwrap(); - - std::thread::sleep(std::time::Duration::from_millis(500)); - app.emit("launch-status", "Verifying assets...").unwrap(); - - std::thread::sleep(std::time::Duration::from_millis(700)); - app.emit("launch-status", "Applying Saturn optimizations...") - .unwrap(); - - std::thread::sleep(std::time::Duration::from_millis(700)); - app.emit("launch-status", "Starting game...").unwrap(); - - std::thread::sleep(std::time::Duration::from_millis(1000)); - app.emit("launch-status", "Game launched!").unwrap(); - - app.emit("is-launching", "false").unwrap(); - }); - - Ok(()) -} diff --git a/src-tauri/src/launcher/auth.rs b/src-tauri/src/launcher/auth.rs new file mode 100644 index 0000000..ecc45fd --- /dev/null +++ b/src-tauri/src/launcher/auth.rs @@ -0,0 +1,175 @@ +use std::{fs, path::PathBuf}; + +use reqwest::Client; +use tauri::{command, AppHandle, Emitter, Manager, Url}; + +use lyceris::auth::microsoft::{authenticate, create_link, refresh, validate, MinecraftAccount}; + +use crate::launcher::LAUNCHER_DIR; + +fn accounts_path() -> PathBuf { + LAUNCHER_DIR.data_dir().join("accounts.json") +} + +fn load_accounts() -> Vec { + let path = accounts_path(); + + if !path.exists() { + return vec![]; + } + + let data = fs::read_to_string(path).unwrap_or_default(); + serde_json::from_str(&data).unwrap_or_default() +} + +fn save_accounts(accounts: &[MinecraftAccount]) -> Result<(), String> { + let path = accounts_path(); + + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).map_err(|e| e.to_string())?; + } + + let data = serde_json::to_string_pretty(accounts).map_err(|e| e.to_string())?; + + fs::write(path, data).map_err(|e| e.to_string()) +} + +fn extract_code(url: &str) -> Option { + Url::parse(url) + .ok()? + .query_pairs() + .find(|(key, _)| key == "code") + .map(|(_, value)| value.to_string()) +} + +#[command] +pub fn auth_create_link(app: AppHandle) -> Result { + let link = create_link().map_err(|e| e.to_string())?; + + let url = link.parse::().map_err(|e| e.to_string())?; + + let app_handle = app.clone(); + + tauri::WebviewWindowBuilder::new(&app, "auth", tauri::WebviewUrl::External(url)) + .title("Microsoft Login") + .inner_size(500.0, 700.0) + .resizable(false) + .on_navigation(move |nav_url| { + let url_str = nav_url.to_string(); + + // Check for auth code + if url_str.contains("code=") { + if let Some(code) = extract_code(&url_str) { + println!("Auth code received: {}", code); + + let _ = app_handle.emit("auth-code", code); + + if let Some(win) = app_handle.get_webview_window("auth") { + let _ = win.close(); + } + } + } + + // Always allow navigation + true + }) + .build() + .map_err(|e| e.to_string())?; + + Ok(link) +} + +#[command] +pub async fn auth_login(code: String) -> Result { + let client = Client::new(); + + let account = authenticate(code, &client) + .await + .map_err(|e| e.to_string())?; + + let mut accounts = load_accounts(); + + // Replace if same UUID exists + accounts.retain(|a| a.uuid != account.uuid); + accounts.push(account.clone()); + + save_accounts(&accounts)?; + + Ok(account) +} + +#[command] +pub async fn auth_refresh(uuid: String) -> Result { + let client = Client::new(); + let mut accounts = load_accounts(); + + let account = accounts + .iter() + .find(|a| a.uuid == uuid) + .cloned() + .ok_or("Account not found")?; + + let refreshed = refresh(account.refresh_token, &client) + .await + .map_err(|e| e.to_string())?; + + accounts.retain(|a| a.uuid != uuid); + accounts.push(refreshed.clone()); + + save_accounts(&accounts)?; + + Ok(refreshed) +} + +#[command] +pub fn auth_list() -> Result, String> { + Ok(load_accounts()) +} + +#[command] +pub fn auth_remove(uuid: String) -> Result<(), String> { + let mut accounts = load_accounts(); + + accounts.retain(|a| a.uuid != uuid); + + save_accounts(&accounts) +} + +#[command] +pub fn auth_validate(uuid: String) -> Result { + let accounts = load_accounts(); + + let account = accounts + .iter() + .find(|a| a.uuid == uuid) + .ok_or("Account not found")?; + + Ok(validate(account.exp)) +} + +#[command] +pub async fn auth_get_valid(uuid: String) -> Result { + let client = Client::new(); + let mut accounts = load_accounts(); + + let account = accounts + .iter() + .find(|a| a.uuid == uuid) + .cloned() + .ok_or("Account not found")?; + + if validate(account.exp) { + return Ok(account); + } + + let refreshed = refresh(account.refresh_token, &client) + .await + .map_err(|e| e.to_string())?; + + accounts.retain(|a| a.uuid != uuid); + accounts.push(refreshed.clone()); + + save_accounts(&accounts)?; + + Ok(refreshed) +} diff --git a/src-tauri/src/launcher/downloader.rs b/src-tauri/src/launcher/downloader.rs new file mode 100644 index 0000000..e4c0f2d --- /dev/null +++ b/src-tauri/src/launcher/downloader.rs @@ -0,0 +1,193 @@ +use futures::{stream, StreamExt}; +use reqwest::{Client, IntoUrl}; +use std::{ + path::Path, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + time::Duration, +}; +use tokio::{ + fs::File, + io::{AsyncWriteExt, BufWriter}, +}; + +use lyceris::{ + error::Error, + minecraft::{ + emitter::{Emitter, Event}, + install::FileType, + }, + util::retry::retry, +}; + +/// Downloads a file from the specified URL and saves it to the given destination. +/// +/// This function performs an asynchronous HTTP GET request to the provided URL, +/// streams the response body, and writes the content to a file at the specified +/// destination. It also provides progress updates through a callback function. +/// +/// # Parameters +/// +/// - `url`: The URL of the file to download. It can be any type that implements +/// the `IntoUrl` trait, such as a string slice or a `String`. +/// - `destination`: A `PathBuf` representing the path where the downloaded file +/// will be saved. +/// - `emitter`: An optional emitter for logging progress. +/// +/// # Returns +/// +/// This function returns a `Result`. On success, it returns +/// `Ok(u64)`, where `u64` is the total size of the downloaded file. If an error +/// occurs during the download process, it returns an `Err` containing an `Error` +/// that describes the failure. +/// +/// # Errors +/// +/// The function can fail in several ways, including but not limited to: +/// - Network errors when making the HTTP request. +/// - Non-success HTTP status codes (e.g., 404 Not Found). +/// - Errors when creating or writing to the file. +pub async fn download>( + url: impl IntoUrl, + destination: P, + emitter: Option<&Emitter>, + client: Option<&Client>, +) -> lyceris::Result { + let default_client = Client::default(); + let client = client.unwrap_or(&default_client); + + let response = client.get(url).send().await?; + + if !response.status().is_success() { + return Err(Error::Download(response.status().to_string())); + } + + let total_size = response.content_length().unwrap_or(0); + let mut downloaded: u64 = 0; + + // 🔥 Assume dirs already created (move this OUTSIDE for max speed) + if let Some(parent) = destination.as_ref().parent() { + let _ = tokio::fs::create_dir_all(parent).await; + } + + let file = File::create(&destination).await?; + let mut writer = BufWriter::with_capacity(64 * 1024, file); // 🔥 BIG WIN + + let mut stream = response.bytes_stream(); + + let mut last_emit = 0; + + while let Some(chunk) = stream.next().await { + let chunk = chunk?; + + downloaded += chunk.len() as u64; + + writer.write_all(&chunk).await?; + + // 🔥 Emit less frequently (huge improvement) + if downloaded - last_emit > 256 * 1024 || downloaded == total_size { + if let Some(emitter) = emitter { + emitter + .emit( + Event::SingleDownloadProgress, + ( + destination.as_ref().to_string_lossy().into_owned(), + downloaded, + total_size, + ), + ) + .await; + } + + last_emit = downloaded; + } + } + + writer.flush().await?; + + Ok(total_size) +} + +/// Downloads multiple files from the specified URLs and saves them to the given destinations. +/// +/// This function takes a vector of tuples, where each tuple contains a URL and a destination path. +/// It downloads all files in parallel and provides progress updates through a callback function. +/// +/// # Parameters +/// +/// - `downloads`: A vector of tuples containing the URLs and their corresponding destination paths. +/// - `emitter`: An optional emitter for logging progress. +/// +/// # Returns +/// +/// This function returns a `Result<(), Error>`. On success, it returns `Ok(())`. If an error occurs +/// during the download process, it returns an `Err` containing an `Error` that describes the failure. +pub async fn download_multiple( + downloads: Vec<(U, P, FileType)>, + emitter: Option<&Emitter>, + client: Option<&Client>, +) -> lyceris::Result<()> +where + U: IntoUrl + Send, // URL type that implements IntoUrl + P: AsRef + Send + 'static, // Path type +{ + let total_files = downloads.len(); + let total_downloaded = Arc::new(AtomicUsize::new(0)); + + let tasks = downloads.into_iter().map(|(url, destination, file_type)| { + let total_downloaded = total_downloaded.clone(); + + async move { + let result = retry( + || async { download(url.as_str(), destination.as_ref(), emitter, client).await }, + Result::is_ok, + 3, + Duration::from_millis(300), // 🔥 faster retry + ) + .await; + + match result { + Ok(_) => { + let downloaded = total_downloaded.fetch_add(1, Ordering::Relaxed) + 1; + + if downloaded % 25 == 0 || downloaded == total_files { + if let Some(emitter) = emitter { + emitter + .emit( + Event::MultipleDownloadProgress, + ( + destination.as_ref().to_string_lossy().into_owned(), + downloaded as u64, + total_files as u64, + file_type.to_string(), + ), + ) + .await; + } + } + + Ok::<(), Error>(()) + } + Err(e) => Err(e), + } + } + }); + + let mut stream = stream::iter(tasks).buffer_unordered(get_optimal_concurrency()); + + while let Some(result) = stream.next().await { + result?; + } + + Ok(()) +} + +fn get_optimal_concurrency() -> usize { + // Good defaults for Minecraft-scale downloads + match std::thread::available_parallelism() { + Ok(n) => (n.get() * 16).clamp(32, 128), + Err(_) => 64, + } +} diff --git a/src-tauri/src/launcher/install.rs b/src-tauri/src/launcher/install.rs new file mode 100644 index 0000000..eaa1fea --- /dev/null +++ b/src-tauri/src/launcher/install.rs @@ -0,0 +1,589 @@ +use futures::stream::FuturesUnordered; +/// This module handles the installation of Minecraft, including downloading +/// necessary files and managing the Java runtime environment. +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use std::{ + env::consts::{ARCH, OS}, + fs::{self, create_dir_all}, + path::{Path, PathBuf, MAIN_SEPARATOR_STR}, + process::Command, +}; + +use lyceris::{ + error::Error, + http::fetch::fetch, + json::{ + java::{JavaFileManifest, JavaManifest}, + version::{ + asset_index::AssetIndex, + manifest::VersionManifest, + meta::vanilla::{self, JavaVersion, VersionMeta}, + }, + }, + minecraft::{ + emitter::{Emit, Event}, + install::FileType, + CLASSPATH_SEPARATOR, JAVA_MANIFEST_ENDPOINT, RESOURCES_ENDPOINT, VERSION_MANIFEST_ENDPOINT, + }, + util::{ + extract::{extract_file, read_file_from_jar}, + json::{read_json, write_json}, + }, +}; + +use lyceris::minecraft::{ + config::Config, + emitter::Emitter, + loader::Loader, + parse::{parse_lib_path, ParseRule}, +}; + +use crate::launcher::downloader::{download, download_multiple}; + +/// Represents a file to be downloaded, including its metadata. +#[derive(Clone)] +struct DownloadFile { + file_name: String, + url: String, + path: PathBuf, + r#type: FileType, +} + +/// Installs the specified version of Minecraft by downloading necessary files +/// and setting up the environment. +/// +/// # Parameters +/// - `config`: The configuration for the installation process. +/// - `emitter`: An optional emitter for logging progress. +/// +/// # Returns +/// A result indicating success or failure of the installation process. +pub async fn install( + config: &Config, + emitter: Option<&Emitter>, +) -> lyceris::Result<()> { + emitter.emit(Event::Console, "Fetching manifest...").await; + + let (manifest, java_manifest) = tokio::join!( + fetch(VERSION_MANIFEST_ENDPOINT, config.client.as_ref()), + fetch(JAVA_MANIFEST_ENDPOINT, config.client.as_ref()) + ); + + let manifest = manifest?; + let java_manifest = java_manifest?; + + let version_json_path = config.get_version_json_path(); + let mut meta: VersionMeta = if !version_json_path.exists() { + emitter + .emit(Event::Console, "Fetching version metadata") + .await; + let mut meta = + fetch_version_meta(&manifest, &config.version, config.client.as_ref()).await?; + if let Some(loader) = &config.loader { + emitter.emit(Event::Console, "Resolving loader").await; + meta = loader.merge(&config.into_vanilla(), meta, emitter).await?; + } + write_json(&version_json_path, &meta).await?; + meta + } else { + read_json(&version_json_path).await? + }; + + emitter.emit(Event::Console, "Fetching assets index").await; + + let asset_index_path = config + .get_indexes_path() + .join(format!("{}.json", &meta.asset_index.id)); + let asset_index: AssetIndex = if !asset_index_path.exists() { + let asset_index = fetch(&meta.asset_index.url, config.client.as_ref()).await?; + write_json(&asset_index_path, &asset_index).await?; + asset_index + } else { + read_json(&asset_index_path).await? + }; + + let natives_path = config.get_natives_path().join(&config.version); + if !natives_path.is_dir() { + // todo async + create_dir_all(&natives_path)?; + } + + emitter.emit(Event::Console, "Configuring files").await; + + let check_natives = fs::read_dir(&natives_path)?.count() == 0; + let mut to_be_extracted = Vec::with_capacity(10); + + let default_java_version = JavaVersion::default(); + let java_version = meta.java_version.as_ref().unwrap_or(&default_java_version); + let runtime_path = config.get_runtime_path().join(&java_version.component); + + let java_url = get_java_url(&java_manifest, java_version)?; + let java_files: JavaFileManifest = fetch(java_url, config.client.as_ref()).await?; + + let file_map = build_file_map( + &asset_index, + &meta, + &java_files, + &runtime_path, + config, + check_natives, + &mut to_be_extracted, + )?; + + emitter.emit(Event::Console, "Downloading files").await; + + download_necessary( + file_map, + &config.game_dir, + asset_index.map_to_resources.unwrap_or_default() + || asset_index.r#virtual.unwrap_or_default(), + emitter, + config.client.as_ref(), + ) + .await?; + + if !to_be_extracted.is_empty() { + create_dir_all(&natives_path)?; + + let tasks = FuturesUnordered::new(); + + for extract in to_be_extracted { + let emitter = emitter.cloned(); + let client = config.client.clone(); + let natives_path = natives_path.clone(); + + tasks.push(tauri::async_runtime::spawn(async move { + if let Some(path) = extract.path { + let path = PathBuf::from(path); + + download(&extract.url, &path, emitter.as_ref(), client.as_ref()).await?; + extract_file(&path, &natives_path)?; + + Ok::<_, Error>(()) + } else { + Ok(()) + } + })); + } + + for task in tasks { + task.await + .map_err(|e| lyceris::Error::Fail(e.to_string()))??; + } + } + + execute_processors_if_exists(&mut meta, config).await?; + + Ok(()) +} + +/// Fetches the version metadata for the specified version from the manifest. +/// +/// # Parameters +/// - `manifest`: The version manifest containing available versions. +/// - `version`: The version to fetch metadata for. +/// - `client`: An optional HTTP client for making requests. +/// +/// # Returns +/// The version metadata for the specified version. +async fn fetch_version_meta( + manifest: &VersionManifest, + version: &str, + client: Option<&reqwest::Client>, +) -> lyceris::Result { + let version_url = manifest + .versions + .iter() + .find(|v| v.id == version) + .ok_or_else(|| Error::UnknownVersion("Vanilla".to_string()))? + .url + .clone(); + fetch(&version_url, client).await +} + +/// Gets the download URL for the specified Java version based on the operating system and architecture. +/// +/// # Parameters +/// - `java_manifest`: The manifest containing Java version information. +/// - `java_version`: The specific Java version to retrieve the URL for. +/// +/// # Returns +/// The download URL for the specified Java version. +fn get_java_url( + java_manifest: &JavaManifest, + java_version: &JavaVersion, +) -> lyceris::Result { + let os = if OS == "macos" { "mac-os" } else { OS }; + let arch = match ARCH { + "x86" => { + if os == "linux" { + "i386" + } else { + "x86" + } + } + "x86_64" => "x64", + "aarch64" => "arm64", + _ => return Err(Error::UnsupportedArchitecture), + }; + let os_arch = if (os == "linux" && arch != "i386") + || (os == "mac-os" && (arch != "arm64" || java_version.major_version == 8)) + { + os.to_string() + } else { + format!("{}-{}", os, arch) + }; + java_manifest + .get(&os_arch) + .ok_or_else(|| Error::NotFound("Java map by operating system".to_string()))? + .get(&java_version.component) + .ok_or_else(|| Error::UnknownVersion("Java version".to_string()))? + .first() + .ok_or_else(|| Error::NotFound("Java gamecore".to_string())) + .map(|entry| &entry.manifest.url) + .cloned() +} + +/// Builds a map of files to be downloaded based on the asset index, version metadata, and Java files. +/// +/// # Parameters +/// - `asset_index`: The asset index containing file information. +/// - `meta`: The version metadata. +/// - `java_files`: The Java file manifest. +/// - `runtime_path`: The path to the Java runtime. +/// - `config`: The configuration for the installation process. +/// - `check_natives`: A flag indicating whether to check for native files. +/// - `to_be_extracted`: A mutable vector to store files that need to be extracted. +/// +/// # Returns +/// A vector of `DownloadFile` representing the files to be downloaded. +fn build_file_map( + asset_index: &AssetIndex, + meta: &VersionMeta, + java_files: &JavaFileManifest, + runtime_path: &Path, + config: &Config, + check_natives: bool, + to_be_extracted: &mut Vec, +) -> lyceris::Result> { + let version_jar_path = config.get_version_jar_path(); + let version_download = if !version_jar_path.exists() { + Some(DownloadFile { + file_name: version_jar_path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + r#type: FileType::Library, + path: version_jar_path, + url: meta.downloads.client.url.clone(), + }) + } else { + None + }; + + let asset_files = asset_index + .objects + .iter() + .map(|(key, meta)| { + let assets_path = config.get_assets_path(); + let hash = &meta.hash; + DownloadFile { + file_name: key.clone(), + url: format!("{}/{}/{}", RESOURCES_ENDPOINT, &hash[0..2], hash), + path: assets_path.join("objects").join(&hash[0..2]).join(hash), + r#type: FileType::Asset { + is_map: asset_index.map_to_resources.unwrap_or_default(), + is_virtual: asset_index.r#virtual.unwrap_or_default(), + }, + } + }) + .collect::>(); + + let library_files = meta + .libraries + .iter() + .filter_map(|lib| { + if !lib.rules.parse_rule() { + return None; + } + let downloads = lib.downloads.as_ref()?; + if check_natives { + if let Some(classifiers) = &downloads.classifiers { + let classifier = match OS { + "windows" => &classifiers.natives_windows, + "linux" => &classifiers.natives_linux, + "macos" => &classifiers.natives_macos, + _ => return None, + }; + if let Some(classifier) = classifier { + if let Some(classifier_path) = &classifier.path { + let path = config + .game_dir + .join("libraries") + .join(classifier_path.replace("/", MAIN_SEPARATOR_STR)); + let url = classifier.url.clone(); + let sha1 = classifier.sha1.clone(); + to_be_extracted.push(vanilla::File { + path: Some(path.to_string_lossy().into_owned()), + sha1: sha1.clone(), + size: classifier.size, + url: url.clone(), + }); + return Some(DownloadFile { + file_name: PathBuf::from(url.clone()) + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + url, + path, + r#type: FileType::Library, + }); + } + } + } + } + let artifact = downloads.artifact.as_ref()?; + Some(DownloadFile { + file_name: PathBuf::from(artifact.url.clone()) + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + url: artifact.url.clone(), + path: config + .game_dir + .join("libraries") + .join(artifact.path.as_ref()?.replace("/", MAIN_SEPARATOR_STR)), + r#type: FileType::Library, + }) + }) + .collect::>(); + + let java_files = java_files + .files + .iter() + .filter_map(|(name, file)| { + let path = runtime_path.join(name.replace("/", MAIN_SEPARATOR_STR)); + file.downloads.as_ref().map(|downloads| DownloadFile { + file_name: name + .split(MAIN_SEPARATOR_STR) + .last() + .unwrap_or(name) + .to_string(), + path, + url: downloads.raw.url.clone(), + r#type: FileType::Java, + }) + }) + .collect::>(); + + Ok([ + version_download.into_iter().collect::>(), + asset_files, + library_files, + java_files, + ] + .concat()) +} + +/// Executes any processors defined in the version metadata, if they exist. +/// +/// # Parameters +/// - `meta`: The version metadata containing processor information. +/// - `config`: The configuration for the installation process. +/// +/// # Returns +/// A result indicating success or failure of the processor execution. +async fn execute_processors_if_exists( + meta: &mut VersionMeta, + config: &Config, +) -> lyceris::Result<()> { + if let Some(ref mut processors) = meta.processors { + let data = meta + .data + .as_ref() + .ok_or_else(|| Error::NotFound("Forge Installer Data".to_string()))?; + + let libraries_path = config.get_libraries_path(); + + for processor in processors { + if let Some(sides) = &processor.sides { + if !sides.contains(&"client".to_string()) { + continue; + } + } + + if processor.success { + continue; + } + + let classpath = processor + .classpath + .iter() + .filter_map(|arg| { + Some( + libraries_path + .join(parse_lib_path(arg).ok()?) + .to_string_lossy() + .into_owned(), + ) + }) + .collect::>() + .join(CLASSPATH_SEPARATOR); + + let main_class = read_file_from_jar( + &libraries_path + .join(parse_lib_path(&processor.jar)?) + .to_string_lossy() + .into_owned(), + "META-INF/MANIFEST.MF", + )? + .lines() + .find(|line| line.starts_with("Main-Class:")) + .ok_or_else(|| Error::NotFound("Main-Class of processor".to_string()))? + .split(":") + .last() + .ok_or_else(|| Error::NotFound("Main-Class of processor".to_string()))? + .trim() + .to_string(); + + let args = processor + .args + .iter() + .map(|arg| { + let trimmed_arg = &arg[1..arg.len() - 1]; + if arg.starts_with('{') { + if let Some(entry) = data.get(trimmed_arg) { + if entry.client.starts_with('[') { + if let Ok(parsed_path) = + parse_lib_path(&entry.client[1..entry.client.len() - 1]) + { + return libraries_path + .join(parsed_path) + .to_string_lossy() + .into_owned(); + } + } + return entry.client.clone(); + } + } else if arg.starts_with('[') { + if let Ok(parsed_path) = parse_lib_path(trimmed_arg) { + return libraries_path + .join(parsed_path) + .to_string_lossy() + .into_owned(); + } + } + + arg.clone() + }) + .collect::>(); + + let child = Command::new( + config + .get_java_path( + meta.java_version + .as_ref() + .unwrap_or(&JavaVersion::default()), + ) + .await?, + ) + .arg("-cp") + .arg(format!( + "{}{}{}", + classpath, + CLASSPATH_SEPARATOR, + libraries_path + .join(parse_lib_path(&processor.jar)?) + .to_string_lossy() + .into_owned() + )) + .arg(main_class) + .args(args) + .output()?; + + if child.status.success() { + processor.success = true; + } else { + return Err(Error::Fail(format!( + "Processor failed: {}", + String::from_utf8_lossy(&child.stderr) + ))); + } + } + } + + write_json(&config.get_version_json_path(), &meta).await?; + + Ok(()) +} + +/// Downloads the necessary files based on the provided file list. +/// +/// # Parameters +/// - `files`: A vector of files to be downloaded. +/// - `game_dir`: The directory where the game is installed. +/// - `legacy`: A flag indicating whether to handle legacy assets. +/// - `emitter`: An optional emitter for logging progress. +/// - `client`: An optional HTTP client for making requests. +/// +/// # Returns +/// A result indicating success or failure of the download process. +async fn download_necessary( + files: Vec, + game_dir: &Path, + legacy: bool, + emitter: Option<&Emitter>, + client: Option<&reqwest::Client>, +) -> lyceris::Result<()> { + let broken_ones: Vec<(String, PathBuf, FileType)> = files + .par_iter() + .filter_map(|file| { + if file.url.is_empty() { + return None; + } + if !file.path.exists() { + return Some((file.url.clone(), file.path.clone(), file.r#type.clone())); + } + None + }) + .collect(); + + download_multiple(broken_ones, emitter, client).await?; + + if legacy { + files.par_iter().try_for_each(|file| { + if let FileType::Asset { is_virtual, is_map } = file.r#type { + let target_path = if is_virtual { + game_dir + .join("assets") + .join("virtual") + .join("legacy") + .join(&file.file_name) + } else if is_map { + game_dir.join("resources").join(&file.file_name) + } else { + return None::<()>; + }; + + if let Some(parent) = target_path.parent() { + if !parent.is_dir() { + fs::create_dir_all(parent).ok(); + } + } + + if !target_path.exists() { + fs::copy(&file.path, &target_path).ok(); + } + + return None; + } + + None + }); + } + + Ok(()) +} diff --git a/src-tauri/src/launcher/mod.rs b/src-tauri/src/launcher/mod.rs new file mode 100644 index 0000000..c6182c2 --- /dev/null +++ b/src-tauri/src/launcher/mod.rs @@ -0,0 +1,98 @@ +pub mod auth; +pub mod downloader; +pub mod install; + +use directories::ProjectDirs; +use lyceris::{ + auth::microsoft::MinecraftAccount, + minecraft::{ + config::ConfigBuilder, + emitter::{Emitter, Event}, + launch::launch, + loader::fabric::Fabric, + }, +}; +use once_cell::sync::Lazy; + +use crate::GameVersion; + +pub static LAUNCHER_DIR: Lazy = Lazy::new(|| { + ProjectDirs::from("org", "SaturnLauncher", "SaturnLauncher") + .expect("Failed to create project directories") +}); + +#[tauri::command] +pub async fn launch_game(version: GameVersion, account: MinecraftAccount) -> Result<(), String> { + // Emitter uses `EventEmitter` inside of it + // and it uses tokio::Mutex for locking. + // That causes emitter methods to be async. + let emitter = Emitter::default(); + + // Single download progress event send when + // a file is being downloaded. + emitter + .on( + Event::SingleDownloadProgress, + |(path, current, total): (String, u64, u64)| { + println!("Downloading {} - {}/{}", path, current, total); + }, + ) + .await; + + // Multiple download progress event send when + // multiple files are being downloaded. + // Java, libraries and assets are downloaded in parallel and + // this event is triggered for each file. + emitter + .on( + Event::MultipleDownloadProgress, + |(_, current, total, _): (String, u64, u64, String)| { + println!("Downloading {}/{}", current, total); + }, + ) + .await; + + // Console event send when a line is printed to the console. + // It uses a seperated tokio thread to handle this operation. + emitter + .on(Event::Console, |line: String| { + println!("Line: {}", line); + }) + .await; + + let current_dir = LAUNCHER_DIR.data_dir(); + + let config = ConfigBuilder::new( + current_dir.join(&version.id), + version.id.clone(), + lyceris::auth::AuthMethod::Microsoft { + username: account.username, + xuid: account.xuid, + uuid: account.uuid, + access_token: account.access_token, + refresh_token: account.refresh_token, + }, + ) + .loader(Fabric(version.loader_version).into()) + .build(); + + println!("Starting installation"); + + // Install method also checks for broken files + // and downloads them again if they are broken. + install::install(&config, Some(&emitter)) + .await + .map_err(|e| e.to_string())?; + + println!("Finished installing"); + + // This method never downloads any file and just runs the game. + launch(&config, Some(&emitter)) + .await + .map_err(|e| e.to_string())? + .wait() + .await + .map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fed28c7..3a14ade 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ pub mod commands; +pub mod launcher; use serde::{Deserialize, Serialize}; use std::sync::Mutex; @@ -20,8 +21,7 @@ pub struct Mod { pub struct GameVersion { pub id: String, pub name: String, - pub release_date: String, - pub r#type: String, // "release" | "snapshot" + pub loader_version: String, } #[derive(Clone, Serialize, Deserialize)] @@ -32,7 +32,6 @@ pub struct LauncherSettings { // --- App State --- pub struct AppState { - pub versions: Vec, pub mods: Vec, pub settings: LauncherSettings, } @@ -40,26 +39,6 @@ pub struct AppState { impl Default for AppState { fn default() -> Self { Self { - versions: vec![ - GameVersion { - id: "1.20.1".into(), - name: "Fabric 1.20.1".into(), - release_date: "2023-06-07".into(), - r#type: "release".into(), - }, - GameVersion { - id: "1.20.4".into(), - name: "Fabric 1.20.4".into(), - release_date: "2023-12-07".into(), - r#type: "release".into(), - }, - GameVersion { - id: "1.21".into(), - name: "Fabric 1.21".into(), - release_date: "2024-06-13".into(), - r#type: "release".into(), - }, - ], mods: vec![ Mod { id: "sodium".into(), diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 916a326..c5559e8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,13 +10,22 @@ fn main() { .plugin(tauri_plugin_opener::init()) .manage(Mutex::new(AppState::default())) .invoke_handler(tauri::generate_handler![ - get_versions, + // Saturn Launcher Commands get_installed_mods, toggle_mod, install_mod, get_settings, update_settings, - launch_game + // Launcher Commands + saturn_launcher_lib::launcher::launch_game, + // Auth Commands + saturn_launcher_lib::launcher::auth::auth_create_link, + saturn_launcher_lib::launcher::auth::auth_login, + saturn_launcher_lib::launcher::auth::auth_refresh, + saturn_launcher_lib::launcher::auth::auth_list, + saturn_launcher_lib::launcher::auth::auth_remove, + saturn_launcher_lib::launcher::auth::auth_validate, + saturn_launcher_lib::launcher::auth::auth_get_valid ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/App.tsx b/src/App.tsx index 138eefe..1bf19a4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,51 +1,31 @@ -import React, { useState, useEffect } from "react"; -import { - Play, - Puzzle, - Settings, - ChevronDown, - Check, - User, - LogOut, -} from "lucide-react"; -import { getVersions } from "./services/LauncherService"; -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; -import Logo from "@/assets/logo.png"; - -// Utility for merging tailwind classes -function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - -// Components +import { useEffect, useRef, useState } from "react"; +import Sidebar from "./components/layout/Sidebar"; +import TopBar from "./components/layout/TopBar"; import PlaySection from "./components/PlaySection"; import ModsSection from "./components/ModsSection"; import SettingsSection from "./components/SettingsSection"; -import { GameVersion } from "./services/Types"; +import { versions } from "./lib/launcher"; +import { GameVersion } from "./lib/types"; +import { MinecraftAccount } from "./lib/auth"; type Section = "play" | "mods" | "settings"; -const App: React.FC = () => { - const [activeSection, setActiveSection] = useState
("play"); - const [versions, setVersions] = useState([]); +export default function App() { + const [active, setActive] = useState
("play"); const [selectedVersion, setSelectedVersion] = useState( null, ); + const [isVersionDropdownOpen, setIsVersionDropdownOpen] = useState(false); const [isAccountDropdownOpen, setIsAccountDropdownOpen] = useState(false); - const versionRef = React.useRef(null); - const accountRef = React.useRef(null); + const versionRef = useRef(null); + const accountRef = useRef(null); - useEffect(() => { - const fetchVersions = async () => { - const v = await getVersions(); - setVersions(v); - if (v.length > 0) setSelectedVersion(v[0]); - }; - fetchVersions(); - }, []); + const [accounts, setAccounts] = useState([]); + const [activeAccount, setActiveAccount] = useState( + null, + ); useEffect(() => { function handleClickOutside(event: MouseEvent) { @@ -63,175 +43,47 @@ const App: React.FC = () => { } document.addEventListener("mousedown", handleClickOutside); + return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); + useEffect(() => { + setSelectedVersion(versions[0]); + }, []); + return ( -
- {/* Sidebar */} - - - {/* Main Content Area */} -
- {/* Top Bar */} -
- {/* Version Selector */} -
-
- {/* Glow */} -
- - -
- -
- {versions.map((v) => ( - - ))} -
-
- - {/* Account Selector */} -
- {/* Glow */} -
- - - -
-
-

SaturnDev

-

- dev@saturnclient.com -

-
- - -
-
-
- - {/* Content Sections */} -
- {activeSection === "play" && selectedVersion && ( - +
+ {active === "play" && ( + )} - {activeSection === "mods" && selectedVersion && ( + {active === "mods" && selectedVersion && ( )} - {activeSection === "settings" && } + {active === "settings" && }
); -}; - -export default App; +} diff --git a/src/components/ModsSection.tsx b/src/components/ModsSection.tsx index acf2183..1431e79 100644 --- a/src/components/ModsSection.tsx +++ b/src/components/ModsSection.tsx @@ -11,14 +11,14 @@ import { import { getInstalledMods, discoverMods, - getVersions, toggleMod, installMod, -} from "../services/LauncherService"; +} from "@/lib/saturn"; import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; import { motion, AnimatePresence } from "framer-motion"; -import { GameVersion, Mod } from "@/services/Types"; +import { GameVersion, Mod } from "@/lib/types"; +import { versions } from "@/lib/launcher"; function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -40,7 +40,6 @@ const ModsSection: React.FC = ({ version }) => { const [selectedVersionsForInstall, setSelectedVersionsForInstall] = useState< string[] >([]); - const [allVersions, setAllVersions] = useState([]); useEffect(() => { const loadMods = async () => { @@ -55,14 +54,6 @@ const ModsSection: React.FC = ({ version }) => { loadMods(); }, [activeTab, version.id, searchQuery]); - useEffect(() => { - const loadAllVersions = async () => { - const v = await getVersions(); - setAllVersions(v); - }; - loadAllVersions(); - }, []); - const handleToggleMod = async (modId: string) => { await toggleMod(modId); const m = await getInstalledMods(version.id); @@ -243,7 +234,7 @@ const ModsSection: React.FC = ({ version }) => {
- {allVersions.map((v) => { + {versions.map((v) => { const isSupported = selectedModForInstall.supported_versions.includes(v.id); const isSelected = selectedVersionsForInstall.includes(v.id); diff --git a/src/components/PlaySection.tsx b/src/components/PlaySection.tsx index ab1aac5..7272480 100644 --- a/src/components/PlaySection.tsx +++ b/src/components/PlaySection.tsx @@ -1,15 +1,19 @@ import React, { useEffect, useState } from "react"; import { Play, Loader2 } from "lucide-react"; -import { launchGame } from "../services/LauncherService"; +// import { launchGame } from "../services/SaturnApi"; import Bg from "@/assets/bg.png"; import { listen } from "@tauri-apps/api/event"; -import { GameVersion } from "@/services/Types"; +import { GameVersion } from "@/lib/types"; +import { launch } from "@/lib/launcher"; +import { MinecraftAccount } from "@/lib/auth"; +import { cn } from "@/lib/utils"; interface PlaySectionProps { - version: GameVersion; + version: GameVersion | null; + account: MinecraftAccount | null; } -const PlaySection: React.FC = ({ version }) => { +const PlaySection: React.FC = ({ version, account }) => { const [isLaunching, setIsLaunching] = useState(false); const [launchStatus, setLaunchStatus] = useState(""); @@ -43,9 +47,13 @@ const PlaySection: React.FC = ({ version }) => {
diff --git a/src/components/SettingsSection.tsx b/src/components/SettingsSection.tsx index 4d935d3..ba690f3 100644 --- a/src/components/SettingsSection.tsx +++ b/src/components/SettingsSection.tsx @@ -7,9 +7,9 @@ import { Info, AlertTriangle, } from "lucide-react"; -import { getSettings, updateSettings } from "../services/LauncherService"; +import { getSettings, updateSettings } from "../lib/saturn"; import { motion } from "framer-motion"; -import { LauncherSettings } from "@/services/Types"; +import { LauncherSettings } from "@/lib/types"; const SettingsSection: React.FC = () => { const [settings, setSettings] = useState(null); @@ -118,8 +118,7 @@ const SettingsSection: React.FC = () => {
{[1024, 4096, 8192, 12288, 16384].map((value) => { const percent = - (((value - 1024) / (16384 - 1024)) * 100 - 50) * 0.97 + - 50; + (((value - 1024) / (16384 - 1024)) * 100 - 50) * 0.97 + 50; return ( void; +}) { + return ( +
+ +
+

{username}

+

{uuid}

+
+
+ ); +} + +export default function AccountDropdown({ + isAccountDropdownOpen, + setIsAccountDropdownOpen, + accountRef, + activeAccount, + setActiveAccount, + accounts, + setAccounts, +}: Props) { + // Load accounts + useEffect(() => { + loadAccounts(); + }, []); + + async function loadAccounts() { + const accs = await authList(); + setAccounts(accs); + + if (accs.length && !activeAccount) { + setActiveAccount(accs[0]); // default + } + } + + // Switch account + function switchAccount(acc: MinecraftAccount) { + setActiveAccount(acc); + setIsAccountDropdownOpen(false); + } + + // Logout (remove account) + async function logout() { + if (!activeAccount) return; + + await authRemove(activeAccount.uuid); + + const updated = await authList(); + setAccounts(updated); + setActiveAccount(updated[0] || null); + } + + // Add account (open Microsoft login) + async function addAccount() { + const url = await authCreateLink(); + console.log("Opening auth URL:", url); + listen("auth-code", async (event) => { + const code = event.payload; + await authLogin(code); + await loadAccounts(); + }); + } + + const otherAccounts = accounts.filter((a) => a.uuid !== activeAccount?.uuid); + + return ( +
+ {/* Glow */} +
+ + {/* Button */} + + + {/* Dropdown */} +
+ {/* Current account */} + {activeAccount && ( + + )} + + {/* Other accounts */} + {otherAccounts.map((acc) => ( + switchAccount(acc)} + /> + ))} + + {/* Actions */} + + + +
+
+ ); +} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..202125f --- /dev/null +++ b/src/components/layout/Sidebar.tsx @@ -0,0 +1,42 @@ +import { Play, Puzzle, Settings } from "lucide-react"; +import Logo from "@/assets/logo.png"; +import { cn } from "@/lib/utils"; + +export default function Sidebar({ active, setActive }: any) { + return ( + + ); +} diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx new file mode 100644 index 0000000..b45d338 --- /dev/null +++ b/src/components/layout/TopBar.tsx @@ -0,0 +1,13 @@ +import VersionDropdown from "./VersionDropdown"; +import AccountDropdown from "./AccountDropdown"; +import { Props } from "./props"; + +export default function TopBar(props: Props) { + return ( +
+ + + +
+ ); +} diff --git a/src/components/layout/VersionDropdown.tsx b/src/components/layout/VersionDropdown.tsx new file mode 100644 index 0000000..8302992 --- /dev/null +++ b/src/components/layout/VersionDropdown.tsx @@ -0,0 +1,63 @@ +import { ChevronDown, Check } from "lucide-react"; +import { GameVersion } from "@/lib/types"; +import { cn } from "@/lib/utils"; +import { Props } from "./props"; + +export default function VersionDropdown({ + versions, + selectedVersion, + setSelectedVersion, + isVersionDropdownOpen, + setIsVersionDropdownOpen, + setIsAccountDropdownOpen, + versionRef, +}: Props) { + return ( +
+
+
+ + +
+ +
+ {versions.map((v: GameVersion) => ( + + ))} +
+
+ ); +} diff --git a/src/components/layout/props.ts b/src/components/layout/props.ts new file mode 100644 index 0000000..c2aeed6 --- /dev/null +++ b/src/components/layout/props.ts @@ -0,0 +1,25 @@ +import { MinecraftAccount } from "@/lib/auth"; +import { GameVersion } from "@/lib/types"; + +export type SetState = React.Dispatch>; + +export interface Props { + versions: GameVersion[]; + selectedVersion: GameVersion | null; + setSelectedVersion: SetState; + + isVersionDropdownOpen: boolean; + setIsVersionDropdownOpen: SetState; + + isAccountDropdownOpen: boolean; + setIsAccountDropdownOpen: SetState; + + versionRef: React.RefObject; + accountRef: React.RefObject; + + accounts: MinecraftAccount[]; + setAccounts: SetState; + + activeAccount: MinecraftAccount | null; + setActiveAccount: SetState; +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..311c8ee --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,60 @@ +import { invoke } from "@tauri-apps/api/core"; + +export interface MinecraftAccount { + xuid: string; + exp: number; + uuid: string; + username: string; + access_token: string; + refresh_token: string; + client_id: string; +} + +/** + * Create Microsoft login URL + */ +export async function authCreateLink(): Promise { + return await invoke("auth_create_link"); +} + +/** + * Exchange auth code for account + */ +export async function authLogin(code: string): Promise { + return await invoke("auth_login", { code }); +} + +/** + * Refresh account by UUID + */ +export async function authRefresh(uuid: string): Promise { + return await invoke("auth_refresh", { uuid }); +} + +/** + * Get all stored accounts + */ +export async function authList(): Promise { + return await invoke("auth_list"); +} + +/** + * Remove account by UUID + */ +export async function authRemove(uuid: string) { + return await invoke("auth_remove", { uuid }); +} + +/** + * Validate token (not expired) + */ +export async function authValidate(uuid: string): Promise { + return await invoke("auth_validate", { uuid }); +} + +/** + * Get valid account (auto-refresh if needed) + */ +export async function authGetValid(uuid: string): Promise { + return await invoke("auth_get_valid", { uuid }); +} diff --git a/src/lib/launcher.ts b/src/lib/launcher.ts new file mode 100644 index 0000000..af4f3dc --- /dev/null +++ b/src/lib/launcher.ts @@ -0,0 +1,28 @@ +import { invoke } from "@tauri-apps/api/core"; +import { GameVersion } from "./types"; +import { MinecraftAccount } from "./auth"; + +export const versions: GameVersion[] = [ + { + id: "1.21.4", + name: "Fabric 1.21.4", + loader_version: "0.18.6", + }, + { + id: "1.21.5", + name: "Fabric 1.21.5", + loader_version: "0.18.6", + }, + { + id: "1.21.6", + name: "Fabric 1.21.6", + loader_version: "0.18.6", + }, +]; + +/** + * Launch the game with the selected account and version + */ +export async function launch(version: GameVersion, account: MinecraftAccount) { + return await invoke("launch_game", { version, account }); +} diff --git a/src/services/LauncherService.ts b/src/lib/saturn.ts similarity index 82% rename from src/services/LauncherService.ts rename to src/lib/saturn.ts index df0f99d..ef7fa94 100644 --- a/src/services/LauncherService.ts +++ b/src/lib/saturn.ts @@ -1,10 +1,5 @@ import { invoke } from "@tauri-apps/api/core"; -import { GameVersion, LauncherSettings, Mod } from "./Types"; - -// --- Versions --- -export async function getVersions(): Promise { - return await invoke("get_versions"); -} +import { LauncherSettings, Mod } from "./types"; // --- Mods --- export async function getInstalledMods(versionId: string): Promise { @@ -70,10 +65,3 @@ export async function updateSettings( ): Promise { await invoke("update_settings", { newSettings }); } - -// --- Launch --- -export async function launchGame( - versionId: string, -): Promise<{ success: boolean; message: string }> { - return await invoke("launch_game", { versionId }); -} diff --git a/src/services/Types.ts b/src/lib/types.ts similarity index 85% rename from src/services/Types.ts rename to src/lib/types.ts index 98cdd86..f845537 100644 --- a/src/services/Types.ts +++ b/src/lib/types.ts @@ -12,8 +12,7 @@ export interface Mod { export interface GameVersion { id: string; name: string; - release_date: string; - type: "release" | "snapshot"; + loader_version: string; } export interface LauncherSettings { diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..1f4cd38 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: any[]) { + return twMerge(clsx(inputs)); +}