commit 997cf37d3124fb62eb3543e0dabc6307a81ee5f1 Author: Kleo Dev Date: Sun Aug 24 14:16:09 2025 +0200 Vanilla works but fabric doesn't diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c25314 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..677479f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1539 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +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", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[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 = "cc" +version = "1.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[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 = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[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", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "hex" +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 = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[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 = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64", + "chrono", + "getrandom 0.2.16", + "http", + "rand", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "pathdiff" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[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.16", + "libc", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "smallauncher_lib" +version = "0.1.1" +dependencies = [ + "hex", + "log", + "md5", + "oauth2", + "open", + "serde", + "serde_json", + "sha1", + "ureq", + "uuid", + "zip", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.3", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "thiserror 2.0.16", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +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.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..385b275 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "smallauncher_lib" +version = "0.1.1" +description = "smallauncher core library" +edition = "2021" +authors = ["Heráclito de Quadros Saldanha "] +license = "MIT OR Unlicense" +keywords = ["smallauncher", "minecraft", "launcher"] +homepage = "https://smallauncher.com/" + +[dependencies] +serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.115" +ureq = { version = "2.9.6", features = ["json"] } +log = "0.4.21" +sha1 = "0.10.6" +hex = "0.4" +zip = "2.1.5" +oauth2 = { version = "5.0.0", default-features = false } +open = "5" +uuid = "1.13.1" +md5 = "0.7" + +[lints.rust] +unsafe_code = "forbid" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..d173964 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +use_field_init_shorthand = true +newline_style = "Unix" +hard_tabs = true +max_width = 150 diff --git a/src/api/assets.rs b/src/api/assets.rs new file mode 100644 index 0000000..9105067 --- /dev/null +++ b/src/api/assets.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; +use std::collections; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Assets { + pub objects: collections::HashMap, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Asset { + pub hash: String, + pub size: usize, +} diff --git a/src/api/java.rs b/src/api/java.rs new file mode 100644 index 0000000..aa9978c --- /dev/null +++ b/src/api/java.rs @@ -0,0 +1,75 @@ +use std::collections; + +use crate::*; + +use serde::{Deserialize, Serialize}; + +impl JavaVersions { + pub const DEFAULT_URL: &'static str = + "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"; +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +pub struct JavaVersions { + pub linux: PlatformVersion, + pub linux_i386: PlatformVersion, + pub mac_os: PlatformVersion, + pub mac_os_arm64: PlatformVersion, + pub windows_arm64: PlatformVersion, + pub windows_x64: PlatformVersion, + pub windows_x86: PlatformVersion, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +pub struct PlatformVersion { + pub java_runtime_alpha: Vec, + pub java_runtime_beta: Vec, + pub java_runtime_delta: Vec, + pub java_runtime_gamma: Vec, + pub java_runtime_gamma_snapshot: Vec, + pub jre_legacy: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Version { + pub manifest: api::meta::Download, + pub version: VersionName, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct VersionName { + pub name: String, + pub released: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case", tag = "type")] +pub enum File { + Directory, + Link { target: String }, + File { executable: bool, downloads: Downloads }, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct Files { + pub files: collections::HashMap, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct Downloads { + pub raw: Download, + pub lzma: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct Download { + pub url: String, + pub size: usize, + pub sha1: String, +} diff --git a/src/api/manifest.rs b/src/api/manifest.rs new file mode 100644 index 0000000..8e6be4c --- /dev/null +++ b/src/api/manifest.rs @@ -0,0 +1,45 @@ +use serde::{Deserialize, Serialize}; + +impl Manifest { + pub const DEFAULT_URL: &'static str = "https://launchermeta.mojang.com/mc/game/version_manifest.json"; + #[inline(always)] + pub fn get_latest_snapshot(&self) -> Option<&Version> { + self.get_version(&self.latest.snapshot) + } + #[inline(always)] + pub fn get_latest_release(&self) -> Option<&Version> { + self.get_version(&self.latest.release) + } + pub fn get_version(&self, name: &str) -> Option<&Version> { + for version in &self.versions { + if version.id == name { + return Some(version); + } + } + None + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Manifest { + pub latest: Latest, + pub versions: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Latest { + pub release: String, + pub snapshot: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Version { + pub id: String, + pub r#type: String, + pub url: String, + pub time: String, + pub release_time: String, +} diff --git a/src/api/meta.rs b/src/api/meta.rs new file mode 100644 index 0000000..02e979f --- /dev/null +++ b/src/api/meta.rs @@ -0,0 +1,216 @@ +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Version { + pub id: String, + #[serde(default)] + pub arguments: Arguments, + pub asset_index: AssetIndex, + pub downloads: Downloads, + #[serde(default)] + pub java_version: JavaVersion, + pub libraries: Vec, + pub main_class: String, + pub r#type: VersionType, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] +#[serde(rename_all = "camelCase")] +pub struct JavaVersion { + pub component: String, + pub major_version: u16, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Arguments { + pub game: Vec, + pub jvm: Vec, +} + +impl Default for Arguments { + fn default() -> Self { + Arguments { + game: vec![ + Argument::String("--username".to_string()), + Argument::String("${auth_player_name}".to_string()), + Argument::String("--version".to_string()), + Argument::String("${version_name}".to_string()), + Argument::String("--gameDir".to_string()), + Argument::String("${game_directory}".to_string()), + Argument::String("--assetsDir".to_string()), + Argument::String("${assets_root}".to_string()), + Argument::String("--assetIndex".to_string()), + Argument::String("${assets_index_name}".to_string()), + Argument::String("--uuid".to_string()), + Argument::String("${auth_uuid}".to_string()), + Argument::String("--accessToken".to_string()), + Argument::String("${auth_access_token}".to_string()), + Argument::String("--clientId".to_string()), + Argument::String("${clientid}".to_string()), + Argument::String("--xuid".to_string()), + Argument::String("${auth_xuid}".to_string()), + Argument::String("--userType".to_string()), + Argument::String("${user_type}".to_string()), + Argument::String("--versionType".to_string()), + Argument::String("${version_type}".to_string()), + ], + jvm: vec![ + Argument::String("-Djava.library.path=${natives_directory}".to_string()), + Argument::String("-Djna.tmpdir=${natives_directory}".to_string()), + Argument::String("-Dorg.lwjgl.system.SharedLibraryExtractPath=${natives_directory}".to_string()), + Argument::String("-Dio.netty.native.workdir=${natives_directory}".to_string()), + Argument::String("-Dminecraft.launcher.brand=${launcher_name}".to_string()), + Argument::String("-Dminecraft.launcher.version=${launcher_version}".to_string()), + Argument::String("-cp".to_string()), + Argument::String("${classpath}".to_string()), + ], + } + } +} + +impl Default for JavaVersion { + #[inline(always)] + fn default() -> Self { + Self { + component: "jre-legacy".to_string(), + major_version: 8, + } + } +} + +impl fmt::Display for VersionType { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Release => f.write_str("Release"), + Self::Snapshot => f.write_str("Snapshot"), + Self::OldBeta => f.write_str("OldBeta"), + Self::OldAlpha => f.write_str("OldAlpha"), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase", untagged)] +pub enum Argument { + String(String), + Object(ArgumentRule), +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ArgumentRule { + pub rules: Vec, + pub value: ArgumentValue, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase", untagged)] +pub enum ArgumentValue { + String(String), + List(Vec), +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct AssetIndex { + pub id: String, + pub sha1: String, + pub size: usize, + pub total_size: usize, + pub url: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Downloads { + pub client: Download, + pub server: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Download { + pub url: String, + pub size: usize, + pub sha1: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum VersionType { + Release, + Snapshot, + OldBeta, + OldAlpha, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Library { + pub name: String, + pub downloads: Option, + pub natives: Option, + pub rules: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct LibraryDownload { + pub artifact: Option, + pub classifiers: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +pub struct Classifiers { + pub natives_linux: Option, + pub natives_osx: Option, + pub natives_windows: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Artifact { + pub url: String, + pub path: String, + pub sha1: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Rule { + pub action: Action, + pub os: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Os { + pub name: Option, + pub arch: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum Arch { + X86, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum OsName { + Windows, + Osx, + Linux, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum Action { + Allow, + Disallow, +} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..8d3c0fa --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,33 @@ +pub mod assets; +pub mod java; +pub mod manifest; +pub mod meta; + +use serde::de::DeserializeOwned; + +use crate::*; + +pub(crate) fn get_from_url(url: &str) -> Result { + Ok(ureq::get(url).call()?.into_json()?) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn java_version_api() { + let _java_versions: java::JavaVersions = get_from_url(java::JavaVersions::DEFAULT_URL).unwrap(); + } + #[test] + fn manifest_api() { + let _manifest: manifest::Manifest = get_from_url(manifest::Manifest::DEFAULT_URL).unwrap(); + } + #[test] + fn all_versions_api() { + let manifest: manifest::Manifest = get_from_url(manifest::Manifest::DEFAULT_URL).unwrap(); + for version in manifest.versions { + let _version: meta::Version = get_from_url(&version.url).unwrap(); + } + } +} diff --git a/src/auth/microsoft.rs b/src/auth/microsoft.rs new file mode 100644 index 0000000..4d8d0cf --- /dev/null +++ b/src/auth/microsoft.rs @@ -0,0 +1,147 @@ +use std::io; +use std::net; + +use crate::*; + +pub const CLIENT_ID: &'static str = match option_env!("CLIENT_ID") { + Some(client_id) => client_id, + None => "74909cec-49b6-4fee-aa60-1b2a57ef72e1", +}; +pub const AUTH_URI: &'static str = "https://login.live.com/oauth20_authorize.srf"; + +const HTTP_OK: &'static [u8] = b" +HTTP/1.1 200 OK +Content-Type: text/plain +Content-Length: 2 + +ok +"; + +#[derive(Debug, serde::Deserialize)] +pub struct Token { + pub access_token: String, + pub refresh_token: String, +} + +#[derive(Debug, serde::Deserialize)] +pub struct AuthResponse { + pub access_token: String, + pub token_type: String, + pub expires_in: u64, +} + +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct XboxAuthResponse { + pub issue_instant: String, + pub not_after: String, + pub token: String, + pub display_claims: DisplayClaims, +} + +#[derive(Debug, serde::Deserialize)] +pub struct DisplayClaims { + pub xui: Vec, +} + +#[derive(Debug, serde::Deserialize)] +pub struct Xui { + pub uhs: String, +} + +#[derive(Debug, serde::Deserialize)] +pub struct Profile { + pub id: String, + pub name: String, +} + +pub fn get_authorisation_code() -> error::Result { + let auth_uri = oauth2::AuthUrl::new(AUTH_URI.to_string()).unwrap(); + let redirect_uri = oauth2::RedirectUrl::new("http://localhost:6565".to_string()).unwrap(); + let client = oauth2::basic::BasicClient::new(oauth2::ClientId::new(CLIENT_ID.to_string())) + .set_auth_uri(auth_uri) + .set_redirect_uri(redirect_uri); + + let (auth_url, _) = client + .authorize_url(oauth2::CsrfToken::new_random) + .add_scope(oauth2::Scope::new("XboxLive.signin".to_string())) + .add_scope(oauth2::Scope::new("XboxLive.offline_access".to_string())) + .set_response_type(&oauth2::ResponseType::new("code".to_string())) + .url(); + + open::that_in_background(auth_url.as_str()); + + let listener = net::TcpListener::bind("127.0.0.1:6565")?; + for stream in listener.incoming() { + if let Ok(mut stream) = stream { + let mut reader = io::BufReader::new(&mut stream); + let mut request_line = String::new(); + + io::BufRead::read_line(&mut reader, &mut request_line)?; + + let redirect_url = request_line.split_whitespace().nth(1).unwrap(); + let url = oauth2::url::Url::parse(&format!("http://localhost:6565{redirect_url}"))?; + let mut pairs = url.query_pairs(); + let code = pairs.next().unwrap().1.to_string(); + io::Write::write(&mut stream, HTTP_OK)?; + return Ok(code); + } + } + unreachable!() +} + +pub fn get_xbl_autentication(access_token: &str) -> error::Result { + let body = serde_json::json!({ + "Properties": { + "AuthMethod": "RPS", + "SiteName": "user.auth.xboxlive.com", + "RpsTicket": format!("d={access_token}"), + }, + "RelyingParty": "http://auth.xboxlive.com", + "TokenType": "JWT" + }); + Ok(ureq::post("https://user.auth.xboxlive.com/user/authenticate") + .send_json(body)? + .into_json()?) +} + +pub fn get_xsts_autentication(xbl_token: &str) -> error::Result { + let body = serde_json::json!({ + "Properties": { + "SandboxId": "RETAIL", + "UserTokens": [xbl_token] + }, + "RelyingParty": "rp://api.minecraftservices.com/", + "TokenType": "JWT", + }); + Ok(ureq::post("https://xsts.auth.xboxlive.com/xsts/authorize").send_json(body)?.into_json()?) +} + +pub fn get_microsoft_token(authorisation_code: &str) -> error::Result { + Ok(ureq::post("https://login.live.com/oauth20_token.srf") + .send_form(&[ + ("client_id", CLIENT_ID), + ("code", authorisation_code), + ("grant_type", "authorization_code"), + ("redirect_uri", "http://localhost:6565"), + ])? + .into_json()?) +} + +pub fn get_minecraft_auth(xsts_auth: &XboxAuthResponse) -> error::Result { + let user_hash = &xsts_auth.display_claims.xui.iter().next().unwrap().uhs; + let token = &xsts_auth.token; + let auth_body = serde_json::json!({ + "identityToken": format!("XBL3.0 x={};{}", user_hash, token) + }); + Ok(ureq::post("https://api.minecraftservices.com/authentication/login_with_xbox") + .send_json(auth_body)? + .into_json()?) +} + +pub fn get_user_profile(access_token: &str) -> error::Result { + Ok(ureq::get("https://api.minecraftservices.com/minecraft/profile") + .set("Authorization", &format!("Bearer {access_token}")) + .call()? + .into_json()?) +} diff --git a/src/auth/mod.rs b/src/auth/mod.rs new file mode 100644 index 0000000..6366358 --- /dev/null +++ b/src/auth/mod.rs @@ -0,0 +1,59 @@ +use std::fs; +use std::path; + +use crate::*; + +pub mod microsoft; +pub mod offline; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum Account { + Microsoft { name: String, uuid: String, access_token: String }, + Offline { name: String, uuid: String }, +} + +pub fn auth_microsoft() -> error::Result { + let authorisation_code = microsoft::get_authorisation_code()?; + let token = microsoft::get_microsoft_token(&authorisation_code)?; + let xbl = microsoft::get_xbl_autentication(&token.access_token)?; + let xsts = microsoft::get_xsts_autentication(&xbl.token)?; + let auth = microsoft::get_minecraft_auth(&xsts)?; + let profile = microsoft::get_user_profile(&auth.access_token)?; + + Ok(Account::Microsoft { + name: profile.name, + uuid: profile.id, + access_token: auth.access_token, + }) +} + +pub fn auth_offline(name: &str) -> Account { + let name = name.to_string(); + let uuid = offline::offline_uuid(&name); + Account::Offline { name, uuid } +} + +pub fn save(auth_path: &path::Path, account: &Account) -> error::Result<()> { + let data = serde_json::to_string_pretty(account)?; + let name = match &account { + Account::Microsoft { name, .. } => name, + Account::Offline { name, .. } => name, + }; + if !auth_path.exists() { + fs::create_dir_all(auth_path)?; + } + let path = auth_path.join(format!("{name}.json")); + fs::write(path, data.as_bytes())?; + Ok(()) +} + +pub fn load(auth_path: &path::Path, name: &str) -> error::Result> { + if !auth_path.exists() { + fs::create_dir_all(auth_path)?; + } + let path = auth_path.join(format!("{name}.json")); + if !path.exists() { + return Ok(None); + } + Ok(Some(serde_json::from_str(&fs::read_to_string(path)?)?)) +} diff --git a/src/auth/offline.rs b/src/auth/offline.rs new file mode 100644 index 0000000..cb87c20 --- /dev/null +++ b/src/auth/offline.rs @@ -0,0 +1,7 @@ +pub fn offline_uuid(username: &str) -> String { + let mut hash = md5::compute(format!("OfflinePlayer:{}", username)).0; + hash[6] = hash[6] & 0x0f | 0x30; // uuid version 3 + hash[8] = hash[8] & 0x3f | 0x80; // RFC4122 variant + + uuid::Uuid::from_bytes(hash).to_string() +} diff --git a/src/download.rs b/src/download.rs new file mode 100644 index 0000000..27ac5ac --- /dev/null +++ b/src/download.rs @@ -0,0 +1,175 @@ +use crate::*; + +use log::*; +use std::io::Write; +use std::{fs, io, path}; + +pub const RESOURCES_URL: &'static str = "https://resources.download.minecraft.net"; + +pub fn download(url: &str, writer: &mut W) -> Result { + let mut reader = ureq::get(url).call()?.into_reader(); + Ok(io::copy(&mut reader, writer)?) +} + +pub fn download_minecraft_version(minecraft_path: &path::Path, jre_path: &path::Path, name: &str) -> Result<(), error::Error> { + let manifest: api::manifest::Manifest = api::get_from_url(api::manifest::Manifest::DEFAULT_URL)?; + let Some(version) = manifest.get_version(name) else { + return Err(error::Error::VersionNotFound); + }; + let meta: api::meta::Version = api::get_from_url(&version.url)?; + let assets: api::assets::Assets = api::get_from_url(&meta.asset_index.url)?; + { + let data_meta = serde_json::to_string_pretty(&meta)?; + let data_assets = serde_json::to_string_pretty(&assets)?; + + let path_client = path!(minecraft_path, "versions", &version.id, format!("{0}.jar", version.id)); + let path_meta = path!(minecraft_path, "versions", &version.id, format!("{0}.json", version.id)); + let path_assets = path!(minecraft_path, "assets", "indexes", format!("{0}.json", meta.asset_index.id)); + + let mut file_meta = file::create_or_open_file(&path_meta)?; + let mut file_assets = file::create_or_open_file(&path_assets)?; + + if !file::file_hash(&meta.downloads.client.sha1, &path_client).unwrap_or_default() { + let mut file_client = file::create_or_open_file(&path_client)?; + info!("Downloading client: {path_client:?}"); + download(&meta.downloads.client.url, &mut file_client)?; + } + + file_meta.write(data_meta.as_bytes())?; + file_assets.write(data_assets.as_bytes())?; + } + { + //this file not is used per smallauncher to launch the game, but some mod installers (forge) expect this file exists + let profile_path = path!(minecraft_path, "launcher_profiles.json"); + if !profile_path.exists() { + fs::write(profile_path, r#"{"profiles":{}}"#)?; + } + } + { + let java_versions: api::java::JavaVersions = api::get_from_url(api::java::JavaVersions::DEFAULT_URL)?; + let java_version = meta.java_version; + #[cfg(target_os = "linux")] + let version = java_versions.linux; + #[cfg(target_os = "windows")] + let version = java_versions.windows_x64; + #[cfg(target_os = "macos")] + let version = java_versions.mac_os; + let java_component = java_version.component; + let list = match java_component.as_str() { + "java-runtime-alpha" => version.java_runtime_alpha, + "java-runtime-beta" => version.java_runtime_beta, + "java-runtime-delta" => version.java_runtime_delta, + "java-runtime-gamma" => version.java_runtime_gamma, + "java-runtime-gamma-snapshot" => version.java_runtime_gamma_snapshot, + "jre-legacy" => version.jre_legacy, + _ => return Err(error::Error::JavaVersionNotSupported), + }; + let Some(version) = list.get(0) else { + return Err(error::Error::JavaVersionNotSupported); + }; + let files: api::java::Files = api::get_from_url(&version.manifest.url)?; + for (path, file) in files.files { + let path = path!(jre_path, &java_component, path); + match file { + api::java::File::Directory => fs::create_dir_all(path)?, + api::java::File::Link { .. } => {} + #[cfg(target_family = "unix")] + api::java::File::File { downloads, executable } => { + let file_download = downloads.raw; + use std::os::unix::fs::PermissionsExt; + if !file::file_hash(&file_download.sha1, &path).unwrap_or_default() { + let mut file = file::create_or_open_file(&path)?; + info!("Downloading java file: {path:?}"); + download(&file_download.url, &mut file)?; + if executable { + let perm = fs::Permissions::from_mode(0o755); + file.set_permissions(perm)?; + } + } + } + #[cfg(not(target_family = "unix"))] + api::java::File::File { downloads, .. } => { + let file_download = downloads.raw; + if !file::file_hash(&file_download.sha1, &path).unwrap_or_default() { + let mut file = file::create_or_open_file(&path)?; + info!("Downloading java file: {path:?}"); + download(&file_download.url, &mut file)?; + } + } + } + } + } + + #[cfg(target_os = "linux")] + const OS_NAME: Option = Some(api::meta::OsName::Linux); + #[cfg(target_os = "windows")] + const OS_NAME: Option = Some(api::meta::OsName::Windows); + #[cfg(target_arch = "x86")] + const ARCH: Option = Some(api::meta::Arch::X86); + #[cfg(not(target_arch = "x86"))] + const ARCH: Option = None; + + 'l: for library in &meta.libraries { + let Some(downloads) = &library.downloads else { + continue; + }; + + if let Some(classifier) = &downloads.classifiers { + #[cfg(target_os = "linux")] + let Some(library_download) = &classifier.natives_linux + else { + continue; + }; + #[cfg(target_os = "windows")] + let Some(library_download) = &classifier.natives_windows + else { + continue; + }; + #[cfg(target_os = "macos")] + let Some(library_download) = &classifier.natives_osx + else { + continue; + }; + let path = path!(minecraft_path, "libraries", &library_download.path); + if !file::file_hash(&library_download.sha1, &path).unwrap_or_default() { + let mut file = file::create_or_open_file(&path)?; + info!("Downloading native library: {}", library_download.path); + download(&library_download.url, &mut file)?; + } + }; + + if let Some(rules) = &library.rules { + for api::meta::Rule { os, action } in rules { + match (os, action) { + (Some(api::meta::Os { name: OS_NAME, arch: ARCH }), api::meta::Action::Allow) => {} + (Some(api::meta::Os { name: OS_NAME, arch: ARCH }), api::meta::Action::Disallow) => continue 'l, + (None, api::meta::Action::Allow) => {} + (None, api::meta::Action::Disallow) => continue 'l, + (_, api::meta::Action::Disallow) => {} + (_, api::meta::Action::Allow) => continue 'l, + } + } + } + + if let Some(library_download) = &downloads.artifact { + let path = path!(minecraft_path, "libraries", &library_download.path); + if !file::file_hash(&library_download.sha1, &path).unwrap_or_default() { + let mut file = file::create_or_open_file(&path)?; + info!("Downloading library: {}", library_download.path); + download(&library_download.url, &mut file)?; + } + }; + } + for (name, asset) in assets.objects { + let prefix_hash = &asset.hash[0..2]; + let hash = &asset.hash; + let path = path!(minecraft_path, "assets", "objects", prefix_hash, hash); + let url = format!("{RESOURCES_URL}/{prefix_hash}/{hash}"); + if !file::file_hash(hash, &path).unwrap_or_default() { + info!("Downloading asset: {name}"); + let mut file = file::create_or_open_file(&path)?; + download(&url, &mut file)?; + } + } + Ok(()) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..80d58f9 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,55 @@ +pub type Result = core::result::Result; + +#[derive(Debug)] +pub enum Error { + VersionNotFound, + JavaVersionNotSupported, + Serialize(serde_json::Error), + Io(std::io::Error), + Network(ureq::Error), + OsString(std::ffi::OsString), + Zip(zip::result::ZipError), + ParseURL(oauth2::url::ParseError), +} + +impl From for Error { + #[inline(always)] + fn from(value: ureq::Error) -> Self { + Self::Network(value) + } +} + +impl From for Error { + #[inline(always)] + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +impl From for Error { + #[inline(always)] + fn from(value: serde_json::Error) -> Self { + Self::Serialize(value) + } +} + +impl From for Error { + #[inline(always)] + fn from(value: std::ffi::OsString) -> Self { + Self::OsString(value) + } +} + +impl From for Error { + #[inline(always)] + fn from(value: zip::result::ZipError) -> Self { + Self::Zip(value) + } +} + +impl From for Error { + #[inline(always)] + fn from(value: oauth2::url::ParseError) -> Self { + Self::ParseURL(value) + } +} diff --git a/src/fabric.rs b/src/fabric.rs new file mode 100644 index 0000000..1495e93 --- /dev/null +++ b/src/fabric.rs @@ -0,0 +1,85 @@ +use std::{ + fs::{self, File}, + path::{Path, PathBuf}, +}; + +use serde_json::Value; + +const META_SERVER: &str = "https://meta.fabricmc.net/"; +const MAVEN_SERVER: &str = "https://maven.fabricmc.net/"; + +fn query_meta_json(path: &str) -> Value { + ureq::get(&format!("{META_SERVER}{path}")).call().unwrap().into_json().unwrap() +} + +fn download_maven(url: &str, path: &Path) { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + let mut file = File::create(path).unwrap(); + if url.starts_with(MAVEN_SERVER) { + crate::download::download(&format!("{url}"), &mut file).unwrap(); + } else { + crate::download::download(&format!("{META_SERVER}{url}"), &mut file).unwrap(); + } +} + +fn library_from_json(json: &Value) -> Option<(String, String)> { + let name = json.get("name")?.as_str()?; + let url = json.get("url")?.as_str()?; + + // Split "group:artifact:version" + let parts: Vec<&str> = name.splitn(3, ':').collect(); + if parts.len() != 3 { + return None; + } + + let group = parts[0].replace('.', "/"); + let artifact = parts[1]; + let version = parts[2]; + + // Build remote URL (like Java's getURL) + let jar_name = format!("{artifact}-{version}.jar"); + let remote_path = format!("{}/{}/{}/{jar_name}", group, artifact, version); + let full_url = format!("{}{}", url, remote_path); + + // Build local path (like Java's getPath) + let group_fs = parts[0].replace('.', &std::path::MAIN_SEPARATOR.to_string()); + let local_path = PathBuf::from(group_fs) + .join(artifact) + .join(version) + .join(jar_name) + .to_string_lossy() + .replace(' ', "_"); + + Some((full_url, local_path)) +} + +pub fn install_fabric(minecraft_path: &Path, minecraft_version: &str, loader_version: &str) -> Result<(), crate::error::Error> { + let profile_name = format!("{minecraft_version}-fabric"); + let versions_dir = minecraft_path.join("versions"); + let profile_dir = versions_dir.join(&profile_name); + let profile_json = profile_dir.join(format!("{profile_name}.json")); + let libs_dir = minecraft_path.join("libraries"); + + let json = query_meta_json(&format!("v2/versions/loader/{minecraft_version}/{loader_version}/profile/json")); + + fs::create_dir_all(&profile_dir).unwrap(); + + fs::write(&profile_json, json.to_string()).unwrap(); + + fs::copy( + versions_dir.join(minecraft_version).join(format!("{minecraft_version}.jar")), + profile_dir.join(profile_name + ".jar"), + ) + .unwrap(); + + for lib_json in json.get("libraries").unwrap().as_array().unwrap() { + if let Some((url, path)) = library_from_json(lib_json) { + let lib_file = libs_dir.join(path); + download_maven(&url, &lib_file); + } else { + eprintln!("Failed to get json from {lib_json}") + } + } + + Ok(()) +} diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..1a0771b --- /dev/null +++ b/src/file.rs @@ -0,0 +1,57 @@ +use std::{ffi, fs, io, path}; + +use crate::*; + +pub(crate) fn create_or_open_file>(path: &S) -> Result { + let path = path::Path::new(path); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + Ok(fs::File::create(&path)?) +} + +pub(crate) fn file_hash>(hash: &str, path: &P) -> Result { + use sha1::Digest; + let mut file = fs::File::open(path)?; + let mut hasher = sha1::Sha1::new(); + io::copy(&mut file, &mut hasher)?; + let reader_hash = hex::encode(hasher.finalize()); + Ok(reader_hash == hash) +} + +#[inline(always)] +pub(crate) fn from_json_file>(path: P) -> Result { + Ok(serde_json::from_str(&fs::read_to_string(path)?)?) +} + +#[macro_export] +macro_rules! path { + () => { + std::path::PathBuf::new() + }; + ($($segment:expr),+ $(,)?) => { + { + let mut path = std::path::PathBuf::new(); + $( + path.push($segment); + )+ + path + } + }; +} + +#[macro_export] +macro_rules! osStr { + () => { + std::ffi::OsString::new() + }; + ($($segment:expr),+ $(,)?) => { + { + let mut string = std::ffi::OsString::new(); + $( + string.push($segment); + )+ + string + } + }; +} diff --git a/src/launch.rs b/src/launch.rs new file mode 100644 index 0000000..9b378c4 --- /dev/null +++ b/src/launch.rs @@ -0,0 +1,311 @@ +use crate::*; + +use std::ffi; +use std::fs; +use std::io; +use std::path; +use std::process; + +const CLIENT_ID: &'static str = "42"; +const LAUNCHER_NAME: &'static str = "miniLauncher"; +const LAUNCHER_VERSION: &'static str = "42"; + +#[cfg(all(not(debug_assertions), target_os = "windows"))] +const DETACHED_PROCESS: u32 = 0x00000008; + +fn merge(a: serde_json::Value, b: serde_json::Value) -> serde_json::Value { + match (a, b) { + (serde_json::Value::Object(mut a), serde_json::Value::Object(b)) => { + for (k, v2) in b { + if let Some(v1) = a.remove(&k) { + a.insert(k, merge(v1, v2)); + } else { + a.insert(k, v2); + } + } + serde_json::Value::Object(a) + } + (serde_json::Value::Array(mut a), serde_json::Value::Array(b)) => { + a.extend(b); + serde_json::Value::Array(a) + } + (_, b) => b, + } +} + +pub fn launch_minecraft_version( + game_path: &path::Path, + jre_path: &path::Path, + version_name: &str, + account: &auth::Account, +) -> Result<(), error::Error> { + let meta: api::meta::Version = { + let meta_path = path!(game_path, "versions", &version_name, format!("{version_name}.json")); + let mut map1: serde_json::Map = file::from_json_file(&meta_path)?; + match map1.remove("inheritsFrom") { + Some(serde_json::Value::String(version_name)) => { + let meta_path = path!(game_path, "versions", &version_name, format!("{version_name}.json")); + let map2: serde_json::Map = file::from_json_file(&meta_path)?; + let v1 = serde_json::Value::Object(map1); + let v2 = serde_json::Value::Object(map2); + serde_json::from_value(merge(v2, v1))? + } + _ => serde_json::from_value(serde_json::Value::Object(map1))?, + } + }; + + extract_natives(&meta, &game_path)?; + + let main_class = &meta.main_class; + let java_component = &meta.java_version.component; + let jvm_args = generate_jvm_args(&meta, &game_path); + let game_args = generate_game_args(&meta, &game_path, account); + #[cfg(target_family = "unix")] + let jre_bin = path!(jre_path, java_component, "bin", "java"); + #[cfg(target_family = "windows")] + let jre_bin = path!(jre_path, java_component, "bin", "java.exe"); + let mut command = process::Command::new(jre_bin); + #[cfg(all(not(debug_assertions), target_os = "windows"))] + std::os::windows::process::CommandExt::creation_flags(&mut command, DETACHED_PROCESS); + command.current_dir(game_path).args(jvm_args).arg(main_class).args(game_args); + + log::info!("Spawning command: {:?}", command); + command.spawn()?; + Ok(()) +} + +pub fn extract_natives(meta: &api::meta::Version, game_path: &path::Path) -> Result<(), error::Error> { + for library in &meta.libraries { + let Some(downloads) = &library.downloads else { + continue; + }; + let Some(classifier) = &downloads.classifiers else { + continue; + }; + #[cfg(target_os = "linux")] + let Some(library_download) = &classifier.natives_linux + else { + continue; + }; + #[cfg(target_os = "windows")] + let Some(library_download) = &classifier.natives_windows + else { + continue; + }; + #[cfg(target_os = "macos")] + let Some(library_download) = &classifier.natives_osx + else { + continue; + }; + + let path = path!(game_path, "libraries", &library_download.path); + let file = fs::File::open(&path)?; + let mut zip = zip::ZipArchive::new(file)?; + for i in 0..zip.len() { + let mut zip_file = zip.by_index(i)?; + let name = zip_file.name(); + if !(name.ends_with(".so") || name.ends_with(".dll") || name.ends_with(".dylib")) { + continue; + } + let path = path!(game_path, "bin", name); + log::info!("Extracting {path:?}"); + let mut file = file::create_or_open_file(&path)?; + io::copy(&mut zip_file, &mut file)?; + } + } + Ok(()) +} + +pub fn generate_game_args(meta: &api::meta::Version, game_path: &path::Path, account: &auth::Account) -> Vec { + let mut args = Vec::new(); + let arguments = &meta.arguments; + let assets_path = path!(game_path, "assets"); + for arg in &arguments.game { + match arg { + api::meta::Argument::String(str) => match str.as_str() { + "${clientid}" => args.push(osStr!(CLIENT_ID)), + "${auth_player_name}" => { + match &account { + auth::Account::Microsoft { name, .. } => args.push(osStr!(name)), + auth::Account::Offline { name, .. } => args.push(osStr!(name)), + }; + } + "${auth_uuid}" => { + match &account { + auth::Account::Microsoft { uuid, .. } => args.push(osStr!(uuid)), + auth::Account::Offline { uuid, .. } => args.push(osStr!(uuid)), + }; + } + "${user_type}" => { + match &account { + auth::Account::Microsoft { .. } => args.push(osStr!("msa")), + auth::Account::Offline { .. } => args.push(osStr!()), + }; + } + "${auth_access_token}" => match &account { + auth::Account::Microsoft { access_token, .. } => args.push(osStr!(access_token)), + auth::Account::Offline { .. } => args.push(osStr!()), + }, + "${game_directory}" => args.push(osStr!(game_path)), + "${version_name}" => args.push(osStr!(&meta.id)), + "${assets_root}" => args.push(osStr!(&assets_path)), + "${assets_index_name}" => args.push(osStr!(&meta.asset_index.id)), + "${version_type}" => args.push(osStr!(meta.r#type.to_string())), + "--xuid" | "${auth_xuid}" => {} + str => args.push(osStr!(str)), + }, + _ => {} + } + } + args.push(osStr!("--userProperties")); + args.push(osStr!("{}")); + args +} + +pub fn generate_jvm_args(meta: &api::meta::Version, game_path: &path::Path) -> Vec { + let mut args = Vec::new(); + let arguments = &meta.arguments; + let natives_dir = path!(game_path, "bin"); + let class_paths = get_class_paths(meta, game_path); + for arg in &arguments.jvm { + match arg { + api::meta::Argument::String(str) => match str.as_str() { + "-Dminecraft.launcher.version=${launcher_version}" => { + let value = osStr!("-Dminecraft.launcher.version=", LAUNCHER_VERSION); + args.push(value); + } + "-Dminecraft.launcher.brand=${launcher_name}" => { + let value = osStr!("-Dminecraft.launcher.brand=", LAUNCHER_NAME); + args.push(value); + } + "-Djava.library.path=${natives_directory}" => { + let value = osStr!("-Djava.library.path=", &natives_dir); + args.push(value); + } + "-Djna.tmpdir=${natives_directory}" => { + let value = osStr!("-Djna.tmpdir=", &natives_dir); + args.push(value); + } + "-Dorg.lwjgl.system.SharedLibraryExtractPath=${natives_directory}" => { + let value = osStr!("-Dorg.lwjgl.system.SharedLibraryExtractPath=", &natives_dir); + args.push(value); + } + "-Dio.netty.native.workdir=${natives_directory}" => { + let value = osStr!("-Dio.netty.native.workdir=", &natives_dir); + args.push(value); + } + "${classpath}" => args.push(class_paths.clone()), + _ => args.push(ffi::OsString::from(str)), + }, + _ => {} + } + } + args +} + +pub fn get_class_paths(meta: &api::meta::Version, game_path: &path::Path) -> ffi::OsString { + #[cfg(target_os = "linux")] + const OS_NAME: Option = Some(api::meta::OsName::Linux); + #[cfg(target_os = "windows")] + const OS_NAME: Option = Some(api::meta::OsName::Windows); + #[cfg(target_arch = "x86")] + const ARCH: Option = Some(api::meta::Arch::X86); + #[cfg(not(target_arch = "x86"))] + const ARCH: Option = None; + #[cfg(target_family = "unix")] + const SEPARATOR: &'static str = ":"; + #[cfg(target_family = "windows")] + const SEPARATOR: &'static str = ";"; + + let mut list = ffi::OsString::new(); + { + let path = path!(game_path, "versions", &meta.id, format!("{}.jar", meta.id)); + list.push(path); + } + 'l: for lib in &meta.libraries { + match lib { + api::meta::Library { + downloads: Some(api::meta::LibraryDownload { + artifact: Some(artifact), .. + }), + rules, + .. + } => { + if let Some(rules) = rules { + for api::meta::Rule { os, action } in rules { + match (os, action) { + (Some(api::meta::Os { name: OS_NAME, arch: ARCH }), api::meta::Action::Allow) => {} + (Some(api::meta::Os { name: OS_NAME, arch: ARCH }), api::meta::Action::Disallow) => continue 'l, + (None, api::meta::Action::Allow) => {} + (None, api::meta::Action::Disallow) => continue 'l, + (_, api::meta::Action::Disallow) => {} + (_, api::meta::Action::Allow) => continue 'l, + } + } + } + list.push(SEPARATOR); + let path = path!(game_path, "libraries", &artifact.path); + list.push(path); + } + lib => { + let mut original_name: &str = &lib.name; + let mut name = ""; + let mut version = ""; + let mut path = path!(); + + if let Some(i) = original_name.find(":") { + let mut paths = &original_name[..i]; + original_name = &original_name[(i + 1)..]; + while let Some(i) = paths.find(".") { + path = path!(path, &paths[..i]); + paths = &paths[(i + 1)..]; + } + path = path!(path, &paths); + } + if let Some(i) = original_name.find(":") { + name = &original_name[..i]; + version = &original_name[(i + 1)..]; + } + list.push(SEPARATOR); + let path = path!(game_path, "libraries", &path, name, version, format!("{name}-{version}.jar")); + list.push(path); + } + } + } + list +} + +pub fn check_version_integrity(game_path: &path::Path, version_name: &str) -> bool { + let path_version = path!(game_path, "versions", version_name, format!("{version_name}.json")); + let path_client = path!(game_path, "versions", version_name, format!("{version_name}.jar")); + let Ok(version) = file::from_json_file::(&path_version) else { + return false; + }; + if !file::file_hash(&version.downloads.client.sha1, &path_client).unwrap_or_default() { + return false; + } + let path_assets = path!(game_path, "assets", "indexes", format!("{}.json", version.asset_index.id)); + let Ok(assets) = file::from_json_file::(&path_assets) else { + return false; + }; + for (_, asset) in assets.objects { + let prefix_hash = &asset.hash[0..2]; + let hash = &asset.hash; + let path = path!(game_path, "assets", "objects", prefix_hash, hash); + if !file::file_hash(hash, &path).unwrap_or_default() { + return false; + } + } + true +} + +pub fn list_versions(game_path: &path::Path) -> Result, error::Error> { + let path = path!(game_path, "versions"); + fs::read_dir(path)? + .map(|x| -> Result { Ok(x?.file_name().to_string_lossy().to_string()) }) + .collect() +} + +pub fn list_all_versions() -> Result { + api::get_from_url(api::manifest::Manifest::DEFAULT_URL) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1dc96c9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; + +pub mod api; +pub mod auth; +pub mod download; +pub mod error; +pub mod fabric; +mod file; +pub mod launch; + +pub fn download_version(version: &str) { + let base_path = PathBuf::from(std::env::home_dir().unwrap()).join(".saturn-launcher"); + let jre_path = base_path.join("jre"); + let instances_path = base_path.join("instances"); + let instance = instances_path.join(version); + + download::download_minecraft_version(&instance, &jre_path, version).unwrap() +} + +pub fn launch_version(version: &str) { + let base_path = PathBuf::from(std::env::home_dir().unwrap()).join(".saturn-launcher"); + let jre_path = base_path.join("jre"); + let instances_path = base_path.join("instances"); + let instance = instances_path.join(version); + + launch::launch_minecraft_version( + &instance, + &jre_path, + &format!("{version}-fabric"), + &auth::Account::Offline { + name: "Daddy".into(), + uuid: "Daddy".into(), + }, + ) + .unwrap() +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a1cd01f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,14 @@ +use std::path::PathBuf; + +fn main() { + // download_version("1.21.4"); + smallauncher_lib::launch_version("1.21.4"); + // install_fabric("1.21.4", "0.17.2"); +} + +fn install_fabric(version: &str, loader: &str) { + let base_path = PathBuf::from(std::env::home_dir().unwrap()).join(".saturn-launcher"); + let instances_path = base_path.join("instances"); + let instance = instances_path.join(version); + smallauncher_lib::fabric::install_fabric(&instance, version, loader).unwrap() +}