diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..81597ca --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,51 @@ +name: "💯 Check" + +on: + push: + branches: + - main + paths: + - ".github/workflows/check.yml" + - "**/*.rs" + - "**/Cargo.toml" + pull_request: + branches: + - main + paths: + - ".github/workflows/check.yml" + - "**/*.rs" + - "**/Cargo.toml" + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + fmt: + name: '🧾 Fmt' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt + - run: cargo fmt --all -- --check + + clippy: + name: '⚠️ Clippy' + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + if: matrix.platform == 'ubuntu-latest' + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy --all-targets --all-features -- -D warnings diff --git a/Cargo.lock b/Cargo.lock index c037063..370f90e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,15 +28,28 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" + +[[package]] +name = "async-channel" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener 5.3.0", + "event-listener-strategy 0.5.2", + "futures-core", + "pin-project-lite", +] [[package]] name = "async-compression" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693" +checksum = "9c90a406b4495d129f00461241616194cb8a032c8d1c53c657f0961d5f8e0498" dependencies = [ "futures-core", "futures-io", @@ -45,11 +58,126 @@ dependencies = [ "xz2", ] +[[package]] +name = "async-executor" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53fc6301894e04a92cb2584fedde80cb25ba8e02d9dc39d4a87d036e22f397d" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.0", + "futures-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-signal" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe66191c335039c7bb78f99dc7520b0cbb166b3a1cb33a03f53d8a1c6f2afda" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -68,9 +196,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -84,6 +212,20 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "blocking" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -98,9 +240,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -108,6 +250,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -124,6 +275,12 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "either" version = "1.11.0" @@ -147,14 +304,56 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.0", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -239,6 +438,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -307,9 +519,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" @@ -471,9 +683,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "linux-raw-sys" @@ -652,6 +864,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.2" @@ -713,17 +931,43 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -836,9 +1080,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" @@ -865,15 +1109,15 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -892,11 +1136,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -905,9 +1149,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -915,18 +1159,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.199" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", @@ -935,9 +1179,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -989,11 +1233,28 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smol" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e635339259e51ef85ac7aa29a1cd991b957047507288697a690e80ab97d07cad" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1004,6 +1265,7 @@ name = "spuz_cli" version = "0.1.0" dependencies = [ "anyhow", + "smol", "spuz_folder", "spuz_piston", "spuz_spawner", @@ -1019,6 +1281,7 @@ version = "0.1.0" dependencies = [ "cfg-if", "serde", + "serde_json", "spuz_piston", "thiserror", "tokio", @@ -1031,14 +1294,14 @@ version = "0.1.0" dependencies = [ "async-compression", "futures", - "pin-project", + "pin-project-lite", "reqwest", "serde", - "spuz_folder", "thiserror", "tokio", "tokio-util", - "url", + "tracing", + "typed-builder", ] [[package]] @@ -1048,14 +1311,13 @@ dependencies = [ "cfg-if", "serde", "serde_json", - "thiserror", - "url", ] [[package]] name = "spuz_spawner" version = "0.1.0" dependencies = [ + "async-channel", "thiserror", "tokio", "tracing", @@ -1070,13 +1332,14 @@ dependencies = [ "spuz_piston", "spuz_spawner", "tracing", + "typed-builder", ] [[package]] name = "syn" -version = "2.0.60" +version = "2.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" dependencies = [ "proc-macro2", "quote", @@ -1124,18 +1387,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", @@ -1209,16 +1472,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -1317,6 +1579,26 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-builder" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -1347,7 +1629,6 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1422d32..d945923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "readme.md" [workspace.dependencies] tracing = { version = "0" } thiserror = { version = "1" } -serde = { version = "1", features = ["derive"] } +serde = { version = "1", features = ["derive", "rc"] } serde_json = { version = "1" } spuz_piston = { path = "crates/spuz_piston" } @@ -25,6 +25,8 @@ spuz_wrench = { path = "crates/spuz_wrench" } pedantic = { level = "warn", priority = -1 } cargo = { level = "warn", priority = -1 } all = "warn" +unwrap_used = "deny" +expect_used = "deny" module_name_repetitions = "allow" too_many_lines = "allow" must_use_candidate = "allow" diff --git a/crates/spuz_cli/Cargo.toml b/crates/spuz_cli/Cargo.toml index 11d0824..68bcb2b 100644 --- a/crates/spuz_cli/Cargo.toml +++ b/crates/spuz_cli/Cargo.toml @@ -14,8 +14,9 @@ spuz_piston = { workspace = true } spuz_spawner = { workspace = true, features = ["useful-layers", "process-handle"] } tracing = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +smol = { version = "2" } tokio = { version = "1", features = ["full"] } anyhow = { version = "1" } diff --git a/crates/spuz_cli/src/main.rs b/crates/spuz_cli/src/main.rs index a19122a..9a5bcc6 100644 --- a/crates/spuz_cli/src/main.rs +++ b/crates/spuz_cli/src/main.rs @@ -1,10 +1,9 @@ -use std::{collections::HashSet, ops::Deref, path::PathBuf}; +use std::path::{Path, PathBuf}; use anyhow::Result; use spuz_folder::Folder; use spuz_spawner::{useful::AllocRange, CommandBuilder}; use spuz_wrench::{LauncherWrench, Player, WindowSize}; -use spuz_piston::Feature; mod telemetry; @@ -15,32 +14,24 @@ async fn main() -> Result<()> { let root = PathBuf::from("./minecraft"); let folder = Folder::settle(&root).await?; let versions = folder.versions(); - let version_id = "1.20.4"; - let client_dir = root.join("versions").join(version_id); + let game_dir = Path::new("instances").join("test"); let version = versions.get("1.20.4"); + #[allow(clippy::expect_used)] let manifest = version.manifest().await?.expect("Version not found"); let mut builder = CommandBuilder::new("java"); builder.apply(AllocRange(1024..4096)); - let wrench = LauncherWrench { - manifest: manifest.deref().clone(), - libraries_dir: root.join("libraries").into(), - assets_dir: root.join("assets").into(), - natives_dir: client_dir.join("natives").into(), - client_jar: client_dir.join(format!("{version_id}.jar")).into(), - game_dir: root.join("instances").join("test").into(), - features: HashSet::from([Feature::CustomResolution]), - }; + let wrench = LauncherWrench::builder().manifest(&manifest).current_dir(&root).game_dir(&game_dir).build(); builder.apply(wrench); builder.apply(Player::new("LIMPIX31", "268903ca-7946-400a-8984-1fdc0b8baf71")); builder.apply(WindowSize::new(1280, 720)); // builder.apply(Fullscreen); - let mut process = builder.build().spawn()?; + let process = builder.build().spawn()?; - while let Some(log) = process.logs.recv().await { + while let Ok(log) = process.logs.recv().await { print!("{log}"); } diff --git a/crates/spuz_folder/Cargo.toml b/crates/spuz_folder/Cargo.toml index 4e72e32..0131c19 100644 --- a/crates/spuz_folder/Cargo.toml +++ b/crates/spuz_folder/Cargo.toml @@ -13,6 +13,7 @@ spuz_piston = { workspace = true } tracing = { workspace = true } thiserror = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } tokio = { version = "1", features = ["fs", "io-util", "sync", "rt-multi-thread"] } cfg-if = { version = "1" } diff --git a/crates/spuz_folder/src/err.rs b/crates/spuz_folder/src/err.rs index 146a5c7..16afcd5 100644 --- a/crates/spuz_folder/src/err.rs +++ b/crates/spuz_folder/src/err.rs @@ -2,8 +2,6 @@ use std::io::Error as IoError; use thiserror::Error; -use spuz_piston::Error as PistonError; - pub type Result = std::result::Result; #[derive(Debug, Error)] @@ -12,5 +10,5 @@ pub enum Error { Io(#[from] IoError), #[error(transparent)] - Piston(#[from] PistonError), + Json(#[from] serde_json::Error), } diff --git a/crates/spuz_folder/src/versions.rs b/crates/spuz_folder/src/versions.rs index 2a2d107..b278002 100644 --- a/crates/spuz_folder/src/versions.rs +++ b/crates/spuz_folder/src/versions.rs @@ -1,6 +1,6 @@ -use std::{path::Path, str::FromStr, sync::Arc}; +use std::{path::Path, sync::Arc}; -use spuz_piston::PistonManifest; +use spuz_piston::Manifest; use tokio::{ fs::{try_exists, File}, io::AsyncReadExt, @@ -24,7 +24,7 @@ impl Versions { } } -type LazyManifest = tokio::sync::OnceCell>; +type LazyManifest = tokio::sync::OnceCell>; type LazyPath = std::cell::OnceCell>; #[derive(Debug)] @@ -67,7 +67,7 @@ impl Version { self.natives_path.get_or_init(|| self.path().join("natives").into()) } - pub async fn manifest(&self) -> Result>> { + pub async fn manifest(&self) -> Result>> { if try_exists(self.path()).await? { let initializer = || async move { // Prevent redundant allocations @@ -77,7 +77,7 @@ impl Version { let mut file = File::open(self.manifest_path()).await?; let mut content = String::with_capacity(COMMON_SIZE); file.read_to_string(&mut content).await?; - let manifest = PistonManifest::from_str(&content)?; + let manifest = content.parse()?; Result::<_, crate::Error>::Ok(Arc::new(manifest)) }; diff --git a/crates/spuz_get/Cargo.toml b/crates/spuz_get/Cargo.toml deleted file mode 100644 index a267ff3..0000000 --- a/crates/spuz_get/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "spuz_get" -version = "0.1.0" -edition.workspace = true -authors.workspace = true -license.workspace = true -repository.workspace = true -readme.workspace = true - -[dependencies] -spuz_folder = { workspace = true } - -thiserror = { workspace = true } -serde = { workspace = true } - -url = { version = "2" } -reqwest = { version = "0.12", features = ["stream", "json"] } -tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] } -tokio-util = { version = "0.7" } -async-compression = { version = "0.4", features = ["futures-io", "lzma"] } -futures = { version = "0.3" } -pin-project = { version = "1" } - -[lints] -workspace = true diff --git a/crates/spuz_get/src/err.rs b/crates/spuz_get/src/err.rs deleted file mode 100644 index 89d78f0..0000000 --- a/crates/spuz_get/src/err.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::io::Error as IoError; - -use reqwest::Error as ReqwestError; -use thiserror::Error; - -pub type Result = std::result::Result; - -#[derive(Debug, Error)] -pub enum Error { - #[error(transparent)] - Io(#[from] IoError), - - #[error(transparent)] - Reqwest(#[from] ReqwestError), -} - -impl Error { - pub fn is_critical(&self) -> bool { - match self { - Error::Io(_) => true, - Error::Reqwest(err) - if err.is_status() || err.is_decode() || err.is_body() || err.is_redirect() || err.is_request() => - { - true - } - Error::Reqwest(_) => false, - } - } -} diff --git a/crates/spuz_get/src/event.rs b/crates/spuz_get/src/event.rs deleted file mode 100644 index c60b46b..0000000 --- a/crates/spuz_get/src/event.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Event { - JobStarted { tasks: usize, bytes: u64 }, - JobFinished, - JobFailed, - - TaskStarted, - TaskChunk { total: u64, size: usize }, - TaskFinished, - TaskFailed, -} diff --git a/crates/spuz_get/src/job.rs b/crates/spuz_get/src/job.rs deleted file mode 100644 index 5cf3a60..0000000 --- a/crates/spuz_get/src/job.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{path::Path, sync::Arc}; - -use tokio::sync::{mpsc, Mutex, OwnedMutexGuard}; -use tokio_util::sync::CancellationToken; -use url::Url; - -use crate::{Event, Task}; - -#[derive(Debug, Clone)] -pub struct Job { - pub tasks: Vec, - pub total: usize, - pub size: u64, -} - -impl Job { - pub fn new(tasks: Vec) -> Self { - let total = tasks.len(); - let size = tasks.iter().map(|it| it.size).reduce(|a, b| a + b).unwrap_or_default(); - - Self { tasks, total, size } - } -} - -#[derive(Debug)] -pub struct JobHandle { - pub(crate) ct: CancellationToken, - rx: Arc>>, -} - -impl JobHandle { - pub fn new(rx: mpsc::UnboundedReceiver) -> Self { - let ct = CancellationToken::new(); - let rx = Arc::new(Mutex::new(rx)); - - Self { ct, rx } - } - - pub async fn rx(&self) -> OwnedMutexGuard> { - self.rx.clone().lock_owned().await - } - - pub fn cancel(&self) { - self.ct.cancel(); - } -} - -#[derive(Debug, Default, Clone)] -pub struct JobBuilder { - tasks: Vec, -} - -impl JobBuilder { - pub fn push(&mut self, url: Url, local: &Arc, size: u64) { - self.tasks.push(Task::new(url, local.clone(), size)); - } - - pub fn into_job(self) -> Job { - Job::new(self.tasks) - } -} - -impl From for Job { - fn from(value: JobBuilder) -> Self { - value.into_job() - } -} diff --git a/crates/spuz_get/src/lib.rs b/crates/spuz_get/src/lib.rs deleted file mode 100644 index bea0317..0000000 --- a/crates/spuz_get/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod err; -mod event; -mod job; -mod shared; -mod task; -mod worker; - -pub use err::{Error, Result}; -pub use event::Event; -pub use job::{Job, JobBuilder, JobHandle}; -pub(crate) use shared::{loop_select, result_async, spawn}; -pub use task::Task; -pub use worker::Worker; diff --git a/crates/spuz_get/src/shared.rs b/crates/spuz_get/src/shared.rs deleted file mode 100644 index 726e041..0000000 --- a/crates/spuz_get/src/shared.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[macro_export] -macro_rules! spawn_macro { - ($($tt:tt)*) => { - ::tokio::spawn(async move { $($tt)* }) - }; -} - -#[macro_export] -macro_rules! result_async_macro { - ($($tt:tt)*) => {{ - let result: ::std::pin::Pin<::std::boxed::Box> + Send + Sync>> = ::std::boxed::Box::pin(async move { $($tt)* }); - result - }}; -} - -#[macro_export] -macro_rules! loop_select_macro { - ($($tt:tt)*) => { - loop { - ::tokio::select!($($tt)*) - } - }; -} - -pub use loop_select_macro as loop_select; -pub use result_async_macro as result_async; -pub use spawn_macro as spawn; diff --git a/crates/spuz_get/src/task.rs b/crates/spuz_get/src/task.rs deleted file mode 100644 index 3b29275..0000000 --- a/crates/spuz_get/src/task.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::{path::Path, sync::Arc}; - -use url::Url; - -#[derive(Debug, Clone)] -pub struct Task { - pub url: Url, - pub local: Arc, - pub size: u64, - - pub lzma: bool, - - pub(crate) retries: u8, -} - -impl Task { - pub fn new(url: Url, local: Arc, size: u64) -> Self { - Self { url, local, size, retries: 0, lzma: false } - } - - #[must_use] - pub fn lzma(mut self, enable: bool) -> Self { - self.lzma = enable; - self - } - - // TODO - #[allow(unused)] - pub(crate) fn retry_add(&mut self) { - self.retries += 1; - } -} diff --git a/crates/spuz_get/src/worker.rs b/crates/spuz_get/src/worker.rs deleted file mode 100644 index 41457af..0000000 --- a/crates/spuz_get/src/worker.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::{ - io, - io::ErrorKind, - pin::Pin, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, - task::{Context, Poll}, -}; - -use async_compression::futures::bufread::LzmaDecoder; -use futures::{io::BufReader, AsyncBufRead, AsyncRead, AsyncReadExt, TryStreamExt}; -use pin_project::pin_project; -use reqwest::Client; -use tokio::{ - fs::File, - io::AsyncWriteExt, - sync::{mpsc, Semaphore}, -}; - -use crate::{job::JobHandle, loop_select, result_async, spawn, Event, Job}; - -#[derive(Debug)] -pub struct Worker { - client: Client, - semaphore: Arc, -} - -impl Worker { - pub fn new(client: Client, concurrency: usize) -> Self { - let semaphore = Arc::new(Semaphore::new(concurrency)); - - Self { client, semaphore } - } - - pub fn push(&self, job: Job) -> Arc { - let (tx, rx) = mpsc::unbounded_channel(); - let handle = Arc::new(JobHandle::new(rx)); - let counter = Arc::new(AtomicUsize::new(job.total)); - - tx.send(Event::JobStarted { bytes: job.size, tasks: job.total }).unwrap(); - - for task in job.tasks { - let counter = counter.clone(); - let tx = tx.clone(); - let tx2 = tx.clone(); - let handle = handle.clone(); - let client = self.client.clone(); - let semaphore = self.semaphore.clone(); - - spawn! { - let permit = semaphore.acquire().await.expect("Semaphore unexpectedly closed"); - - tx.send(Event::TaskStarted).unwrap(); - - let result = result_async! { - let request = client.get(task.url); - let response = request.send().await?; - - let stream = response.bytes_stream().map_err(|err| io::Error::new(ErrorKind::Other, err)); - let reader = stream.into_async_read(); - let reader = TrackingReader::new(reader, &tx, task.size); - - let mut file = File::create(task.local).await?; - - let piped = task.lzma; - - let mut decoder: Box = if task.lzma { - Box::new(LzmaDecoder::new(reader)) - } else { - Box::new(reader) - }; - - let mut buf = vec![0; 16384]; - - loop_select! { - () = handle.ct.cancelled() => { - break; - } - read = decoder.read(&mut buf) => { - let read = read?; - - if read == 0 { - tx.send(Event::TaskFinished).unwrap(); - if counter.fetch_sub(1, Ordering::Relaxed) == 1 { - tx.send(Event::JobFinished).unwrap(); - }; - return Ok(()); - } - - if !piped { - tx.send(Event::TaskChunk { - total: task.size, - size: read, - }).unwrap(); - } - - file.write_all(&buf[..read]).await?; - } - } - - Ok(()) - }; - - if let Err(_err) = result.await { - tx2.send(Event::TaskFailed).unwrap(); - } - - drop(permit) - }; - } - - handle - } -} - -#[pin_project] -pub struct TrackingReader<'a, T> { - #[pin] - inner: BufReader, - total: u64, - tx: &'a mpsc::UnboundedSender, -} - -impl<'a, T> TrackingReader<'a, T> -where - T: AsyncRead, -{ - pub fn new(reader: T, tx: &'a mpsc::UnboundedSender, total: u64) -> Self { - Self { inner: BufReader::new(reader), total, tx } - } -} - -impl AsyncRead for TrackingReader<'_, T> -where - T: Unpin + AsyncRead, -{ - fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { - let this = self.project(); - this.inner.poll_read(cx, buf) - } -} - -impl AsyncBufRead for TrackingReader<'_, T> -where - T: Unpin + AsyncRead, -{ - fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - let result = this.inner.poll_fill_buf(cx); - if let Poll::Ready(Ok(result)) = &result { - let event = Event::TaskChunk { total: *this.total, size: result.len() }; - this.tx.send(event).unwrap(); - } - result - } - - fn consume(self: Pin<&mut Self>, amt: usize) { - let this = self.project(); - this.inner.consume(amt); - } -} diff --git a/crates/spuz_piston/Cargo.toml b/crates/spuz_piston/Cargo.toml index ad6587e..fac7763 100644 --- a/crates/spuz_piston/Cargo.toml +++ b/crates/spuz_piston/Cargo.toml @@ -9,11 +9,9 @@ readme.workspace = true description = "1" [dependencies] -thiserror = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -url = { version = "2", features = ["serde"] } cfg-if = { version = "1" } [lints] diff --git a/crates/spuz_piston/src/assets.rs b/crates/spuz_piston/src/assets.rs index 4c293e2..ff6c226 100644 --- a/crates/spuz_piston/src/assets.rs +++ b/crates/spuz_piston/src/assets.rs @@ -2,15 +2,15 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use crate::Size; +use crate::{Size, Str}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AssetObject { - pub hash: String, + pub hash: Str, pub size: Size, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AssetIndex { - pub objects: HashMap, + pub objects: HashMap, } diff --git a/crates/spuz_piston/src/err.rs b/crates/spuz_piston/src/err.rs deleted file mode 100644 index 46b4324..0000000 --- a/crates/spuz_piston/src/err.rs +++ /dev/null @@ -1,14 +0,0 @@ -use serde_json::Error as JsonError; -use thiserror::Error; -use url::ParseError as UrlParseError; - -pub type Result = std::result::Result; - -#[derive(Debug, Error)] -pub enum Error { - #[error(transparent)] - UrlParse(#[from] UrlParseError), - - #[error(transparent)] - Json(#[from] JsonError), -} diff --git a/crates/spuz_piston/src/lib.rs b/crates/spuz_piston/src/lib.rs index 6f92577..f00253a 100644 --- a/crates/spuz_piston/src/lib.rs +++ b/crates/spuz_piston/src/lib.rs @@ -1,18 +1,17 @@ pub mod assets; -pub mod err; -pub mod libs; pub mod list; -pub mod manifest; +mod manifest; pub mod platform; +mod profiles; pub mod rule; pub mod shared; +pub mod v; pub use assets::{AssetIndex, AssetObject}; -pub use err::{Error, Result}; -pub use libs::{Artifact, PackageName}; -pub use manifest::{Argument, Arguments, AssetIndexResource, Library, LibrarySpecifiers, PistonManifest}; -pub use platform::{Arch, Os, TARGET_ARCH, TARGET_OS}; +pub use manifest::Manifest; +pub use platform::{Arch, NativeClassifier, Os, TARGET_ARCH, TARGET_OS}; +pub use profiles::LauncherProfiles; pub use rule::{ ConditionalValue, Feature, FeatureSet, PlatformRequirement, Rule, RuleAction, RuleCompilance, RuleCondition, }; -pub use shared::{ListOrValue, Size, UrlStr, Version}; +pub use shared::{Arr, BoxPath, Size, Str}; diff --git a/crates/spuz_piston/src/libs.rs b/crates/spuz_piston/src/libs.rs deleted file mode 100644 index bbdd309..0000000 --- a/crates/spuz_piston/src/libs.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::{ - fmt::{Display, Formatter}, - path::{Component, Path, PathBuf}, -}; - -use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; - -use crate::{Size, UrlStr}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PackageName { - pub package: PathBuf, - pub name: String, - pub version: String, -} - -impl Display for PackageName { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - for (idx, component) in self.package.components().enumerate() { - if let Component::Normal(name) = component { - if idx > 0 { - write!(f, ".")?; - } - write!(f, "{}", name.to_string_lossy())?; - } else { - write!(f, "")?; - } - } - - write!(f, ":{}:{}", self.name, self.version) - } -} - -impl Serialize for PackageName { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("{self}")) - } -} - -impl<'de> Deserialize<'de> for PackageName { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct ArtifactNameVisitor; - - impl<'vis> Visitor<'vis> for ArtifactNameVisitor { - type Value = PackageName; - - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - write!(formatter, "a string in java package format `org.name.pkg:artifact:version`") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - let parts: Vec<&str> = v.split(':').collect(); - - Ok(PackageName { package: parts[0].split('.').collect(), name: parts[1].into(), version: parts[2].into() }) - } - - fn visit_string(self, v: String) -> Result - where - E: serde::de::Error, - { - self.visit_str(&v) - } - } - - deserializer.deserialize_string(ArtifactNameVisitor) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Artifact { - pub path: Box, - pub sha1: String, - pub size: Size, - pub url: UrlStr, -} diff --git a/crates/spuz_piston/src/list.rs b/crates/spuz_piston/src/list.rs index bf8c3ff..3fd4dc0 100644 --- a/crates/spuz_piston/src/list.rs +++ b/crates/spuz_piston/src/list.rs @@ -1,23 +1,23 @@ use serde::{Deserialize, Serialize}; -use crate::UrlStr; +use crate::{Arr, Str}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Latest { - pub release: String, - pub snapshot: String, + pub release: Str, + pub snapshot: Str, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionRef { - #[serde(flatten)] - pub version: crate::Version, - pub sha1: String, - pub url: UrlStr, + pub r#type: Str, + pub id: Str, + pub sha1: Str, + pub url: Str, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Versions { latest: Latest, - versions: Vec, + versions: Arr, } diff --git a/crates/spuz_piston/src/manifest.rs b/crates/spuz_piston/src/manifest.rs index 8ba80bd..9f59343 100644 --- a/crates/spuz_piston/src/manifest.rs +++ b/crates/spuz_piston/src/manifest.rs @@ -1,64 +1,36 @@ -use std::str::FromStr; +use std::{fmt::Debug, ops::Deref, str::FromStr}; -use serde::{Deserialize, Serialize}; +use crate::v::AnyManifest; -use crate::{Artifact, ConditionalValue, Error, ListOrValue, PackageName, Rule, Size, UrlStr, Version}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Argument { - Plain(String), - Conditional(ConditionalValue>), +#[derive(Debug, Clone)] +pub struct Manifest { + raw: AnyManifest, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Arguments { - pub game: Vec, - pub jvm: Vec, +impl Manifest { + fn new(raw: AnyManifest) -> Self { + Self { raw } + } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LibrarySpecifiers { - pub artifact: Artifact, -} +impl Deref for Manifest { + type Target = AnyManifest; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Library { - pub name: PackageName, - pub downloads: LibrarySpecifiers, - pub rules: Option>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AssetIndexResource { - pub id: String, - pub url: UrlStr, - pub total_size: Size, + fn deref(&self) -> &Self::Target { + &self.raw + } } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PistonManifest { - /// Version info: id and stability - #[serde(flatten)] - pub version: Version, - /// Asset index id - pub assets: String, - /// Reference to assets manifest - pub asset_index: AssetIndexResource, - /// Required or optional command-line arguments to configure game and player - pub arguments: Arguments, - /// Java libraries that minecraft uses - pub libraries: Vec, - /// Main class contains main method to start the game - pub main_class: String, +impl AsRef for Manifest { + fn as_ref(&self) -> &AnyManifest { + &self.raw + } } -impl FromStr for PistonManifest { - type Err = Error; +impl FromStr for Manifest { + type Err = serde_json::Error; fn from_str(s: &str) -> Result { - serde_json::from_str(s).map_err(Into::into) + Ok(Self::new(s.parse()?)) } } diff --git a/crates/spuz_piston/src/platform.rs b/crates/spuz_piston/src/platform.rs index bd1354e..29fc4a0 100644 --- a/crates/spuz_piston/src/platform.rs +++ b/crates/spuz_piston/src/platform.rs @@ -1,7 +1,7 @@ use cfg_if::cfg_if; use serde::{Deserialize, Serialize}; -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Os { Linux, @@ -25,9 +25,23 @@ impl Os { pub fn is_target(self) -> bool { self == TARGET_OS } + + pub fn into_classifier(self) -> NativeClassifier { + match self { + Os::Linux => NativeClassifier::Linux, + Os::Windows => NativeClassifier::Windows, + Os::Osx => NativeClassifier::Macos, + } + } +} + +impl From for NativeClassifier { + fn from(value: Os) -> Self { + value.into_classifier() + } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Arch { X64, @@ -49,3 +63,33 @@ impl Arch { self == TARGET_ARCH } } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum NativeClassifier { + #[serde(rename = "natives-linux")] + Linux, + #[serde(rename = "natives-windows")] + Windows, + #[serde(rename = "natives-macos")] + Macos, +} + +impl NativeClassifier { + pub fn into_os(self) -> Os { + match self { + NativeClassifier::Linux => Os::Linux, + NativeClassifier::Windows => Os::Windows, + NativeClassifier::Macos => Os::Osx, + } + } + + pub fn is_target(self) -> bool { + self.into_os().is_target() + } +} + +impl From for Os { + fn from(value: NativeClassifier) -> Self { + value.into_os() + } +} diff --git a/crates/spuz_piston/src/profiles.rs b/crates/spuz_piston/src/profiles.rs new file mode 100644 index 0000000..4bb1cec --- /dev/null +++ b/crates/spuz_piston/src/profiles.rs @@ -0,0 +1,22 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use crate::Str; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Profile { + pub r#type: Str, + pub name: Str, + pub created: Str, + pub icon: Str, + pub last_used: Str, + pub last_version_id: Str, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LauncherProfiles { + pub profiles: HashMap, + pub version: u32, +} diff --git a/crates/spuz_piston/src/rule.rs b/crates/spuz_piston/src/rule.rs index 3a6dd11..159eefd 100644 --- a/crates/spuz_piston/src/rule.rs +++ b/crates/spuz_piston/src/rule.rs @@ -10,7 +10,7 @@ use serde::{ Deserialize, Deserializer, Serialize, Serializer, }; -use crate::{Arch, Os}; +use crate::{Arch, Arr, Os, Str}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Feature { @@ -30,7 +30,7 @@ pub enum Feature { Unknown, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct FeatureSet(HashSet); impl Deref for FeatureSet { @@ -92,11 +92,11 @@ impl Serialize for FeatureSet { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct PlatformRequirement { pub name: Option, pub arch: Option, - pub version: Option, + pub version: Option, } impl PlatformRequirement { @@ -119,7 +119,7 @@ impl PlatformRequirement { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Rule { pub action: RuleAction, #[serde(flatten)] @@ -143,7 +143,7 @@ impl RuleAction { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum RuleCondition { #[serde(rename = "os")] @@ -151,9 +151,9 @@ pub enum RuleCondition { Features(FeatureSet), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct ConditionalValue { - pub rules: Vec, + pub rules: Arr, pub value: T, } @@ -182,4 +182,8 @@ impl RuleCompilance { pub fn unpack(&self, container: ConditionalValue) -> Option { container.rules.iter().all(|rule| self.is_met(rule)).then_some(container.value) } + + pub fn unpack_ref<'a, T>(&self, container: &'a ConditionalValue) -> Option<&'a T> { + container.rules.iter().all(|rule| self.is_met(rule)).then_some(&container.value) + } } diff --git a/crates/spuz_piston/src/shared.rs b/crates/spuz_piston/src/shared.rs index c648d11..6edf4d8 100644 --- a/crates/spuz_piston/src/shared.rs +++ b/crates/spuz_piston/src/shared.rs @@ -1,113 +1,6 @@ -use std::{ - fmt::{Display, Formatter}, - ops::{Deref, DerefMut}, -}; +use std::path::Path; -use serde::{Deserialize, Serialize}; -use url::Url; - -use crate::Result; - -/// Type representing the size of the resource in bytes pub type Size = u64; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type", content = "id")] -#[serde(rename_all = "lowercase")] -pub enum Version { - Release(String), - Snapshot(String), -} - -impl Version { - pub fn is_release(&self) -> bool { - matches!(self, Self::Release(_)) - } - - pub fn is_snapshot(&self) -> bool { - !self.is_release() - } - - pub fn version_type(&self) -> &'static str { - match self { - Version::Release(_) => "release", - Version::Snapshot(_) => "snapshot", - } - } - - pub fn id(&self) -> &str { - match self { - Version::Snapshot(it) | Version::Release(it) => it, - } - } -} - -impl Display for Version { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Version::Release(semver) => write!(f, "{semver}"), - Version::Snapshot(snapshot) => write!(f, "{snapshot}"), - } - } -} - -impl From for String { - fn from(value: Version) -> Self { - match value { - Version::Snapshot(it) | Version::Release(it) => it, - } - } -} - -impl Deref for Version { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.id() - } -} - -/// This string is more than likely an [Url], but for memory and security -/// reasons you should lazily parse this string into an [Url]. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct UrlStr(String); - -impl Deref for UrlStr { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for UrlStr { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl UrlStr { - /// # Errors - /// In case Url is invalid - pub fn parse_url(&self) -> Result { - Url::parse(&self.0).map_err(Into::into) - } -} - -/// A value that can be a list or a single value -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum ListOrValue { - List(Vec), - Value(T), -} - -impl ListOrValue { - pub fn is_list(&self) -> bool { - matches!(self, Self::List(_)) - } - - pub fn is_value(&self) -> bool { - !self.is_list() - } -} +pub type Str = Box; +pub type Arr = Box<[T]>; +pub type BoxPath = Box; diff --git a/crates/spuz_piston/src/v/mod.rs b/crates/spuz_piston/src/v/mod.rs new file mode 100644 index 0000000..9484779 --- /dev/null +++ b/crates/spuz_piston/src/v/mod.rs @@ -0,0 +1,58 @@ +pub mod shared; +pub mod v12; +pub mod v19; + +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use shared::{Arguments, Artifact, AssetIndexRef, Library}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum AnyManifest { + V19(v19::RawManifest), + V12(v12::RawManifest), +} + +macro_rules! any { + ($self:ident, $($prop:tt)*) => { + match $self { + Self::V19(manifest) => &manifest.$($prop)*, + Self::V12(manifest) => &manifest.$($prop)*, + } + }; +} + +impl AnyManifest { + pub fn id(&self) -> &str { + any!(self, id) + } + + pub fn version_type(&self) -> &str { + any!(self, r#type) + } + + pub fn asset_index_id(&self) -> &str { + any!(self, assets) + } + + pub fn main_class(&self) -> &str { + any!(self, main_class) + } + + pub fn assets(&self) -> &AssetIndexRef { + any!(self, asset_index) + } + + pub fn release_time(&self) -> &str { + any!(self, release_time) + } +} + +impl FromStr for AnyManifest { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} diff --git a/crates/spuz_piston/src/v/shared.rs b/crates/spuz_piston/src/v/shared.rs new file mode 100644 index 0000000..ea488c7 --- /dev/null +++ b/crates/spuz_piston/src/v/shared.rs @@ -0,0 +1,46 @@ +use serde::{Deserialize, Serialize}; + +use crate::{Arr, BoxPath, ConditionalValue, Rule, Size, Str}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum ListOrValue { + List(Arr), + Value(T), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct AssetIndexRef { + pub id: Str, + pub url: Str, + pub total_size: Size, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Artifact { + pub path: BoxPath, + pub sha1: Str, + pub size: Size, + pub url: Str, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum Argument { + Plain(Str), + Conditional(ConditionalValue>), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Arguments { + pub game: Arr, + pub jvm: Arr, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Library { + pub name: Str, + pub downloads: S, + pub rules: Option>, +} diff --git a/crates/spuz_piston/src/v/v12.rs b/crates/spuz_piston/src/v/v12.rs new file mode 100644 index 0000000..595435e --- /dev/null +++ b/crates/spuz_piston/src/v/v12.rs @@ -0,0 +1,26 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use super::{Arguments, Artifact, AssetIndexRef, Library}; +use crate::{Arr, NativeClassifier, Str}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Specifiers { + pub artifact: Artifact, + pub classifiers: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct RawManifest { + pub r#type: Str, + pub id: Str, + pub time: Str, + pub release_time: Str, + pub main_class: Str, + pub assets: Str, + pub asset_index: AssetIndexRef, + pub libraries: Arr>, + pub arguments: Arguments, +} diff --git a/crates/spuz_piston/src/v/v19.rs b/crates/spuz_piston/src/v/v19.rs new file mode 100644 index 0000000..da93eb8 --- /dev/null +++ b/crates/spuz_piston/src/v/v19.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +use super::{Arguments, Artifact, AssetIndexRef, Library}; +use crate::{Arr, Str}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Specifiers { + pub artifact: Artifact, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct RawManifest { + pub r#type: Str, + pub id: Str, + pub time: Str, + pub release_time: Str, + pub main_class: Str, + pub assets: Str, + pub asset_index: AssetIndexRef, + pub libraries: Arr>, + pub arguments: Arguments, +} diff --git a/crates/spuz_spawner/Cargo.toml b/crates/spuz_spawner/Cargo.toml index 1818625..7183493 100644 --- a/crates/spuz_spawner/Cargo.toml +++ b/crates/spuz_spawner/Cargo.toml @@ -8,10 +8,11 @@ repository.workspace = true readme.workspace = true [dependencies] -thiserror = { workspace = true } -tracing = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } -tokio = { version = "1", features = ["sync", "io-util", "process", "rt-multi-thread", "macros"] } +tokio = { version = "1", features = ["sync", "io-util", "process", "rt-multi-thread", "macros"] } +async-channel = { version = "2" } [lints] workspace = true diff --git a/crates/spuz_spawner/src/layer.rs b/crates/spuz_spawner/src/layer.rs index ab9ffc8..0a8aa8e 100644 --- a/crates/spuz_spawner/src/layer.rs +++ b/crates/spuz_spawner/src/layer.rs @@ -1,5 +1,8 @@ +use std::path::PathBuf; + #[derive(Debug)] pub struct LaunchMod<'a> { + pub current_dir: &'a mut PathBuf, pub main_class: &'a mut String, pub java_args: &'a mut Vec, pub app_args: &'a mut Vec, diff --git a/crates/spuz_spawner/src/lib.rs b/crates/spuz_spawner/src/lib.rs index a640d68..4325e57 100644 --- a/crates/spuz_spawner/src/lib.rs +++ b/crates/spuz_spawner/src/lib.rs @@ -19,6 +19,7 @@ pub mod useful; #[derive(Debug, Default, Clone)] pub struct CommandBuilder { bin: PathBuf, + current_dir: PathBuf, main_class: String, java_args: Vec, app_args: Vec, @@ -32,6 +33,7 @@ impl CommandBuilder { pub fn apply(&mut self, layer: impl Layer + Debug) { debug!(?layer, "Applying layer"); layer.apply(&mut LaunchMod { + current_dir: &mut self.current_dir, java_args: &mut self.java_args, app_args: &mut self.app_args, main_class: &mut self.main_class, @@ -41,6 +43,7 @@ impl CommandBuilder { pub fn build(&self) -> LaunchCommand { let mut cmd = Command::new(&self.bin); cmd + .current_dir(&self.current_dir) .args(&self.java_args) // Jvm args .arg(&self.main_class) // Main class .args(&self.app_args) // App args (minecraft args) diff --git a/crates/spuz_spawner/src/process.rs b/crates/spuz_spawner/src/process.rs index fb068f8..24a8f6f 100644 --- a/crates/spuz_spawner/src/process.rs +++ b/crates/spuz_spawner/src/process.rs @@ -1,12 +1,11 @@ +use async_channel::Receiver; use tokio::process::Command; #[cfg(feature = "process-handle")] use { crate::Result, + async_channel::unbounded, std::sync::Arc, - tokio::{ - io::AsyncReadExt, - sync::{mpsc, mpsc::UnboundedReceiver, Notify}, - }, + tokio::{io::AsyncReadExt, sync::Notify}, }; #[derive(Debug)] @@ -27,11 +26,16 @@ impl LaunchCommand { pub fn spawn(self) -> Result { let mut cmd = self.into_command(); let mut child = cmd.spawn()?; - let mut stdout = child.stdout.take().expect("Everything is broken"); - let mut stderr = child.stderr.take().expect("Everything is broken"); + // # SAFETY + // Invariant: The instance has been created here and stdout, stderr have not + // been moved until now + #[allow(clippy::unwrap_used)] + let mut stdout = child.stdout.take().unwrap(); + #[allow(clippy::unwrap_used)] + let mut stderr = child.stderr.take().unwrap(); let exit = Arc::new(Notify::new()); - let (logs_tx, logs) = mpsc::unbounded_channel(); + let (logs_tx, logs) = unbounded(); let handle = ProcessHandle { exit: exit.clone(), logs }; @@ -50,7 +54,7 @@ impl LaunchCommand { let bytes = &$buf[..read]; let str = ::std::str::from_utf8(bytes).unwrap(); - if $logs_tx.send(str.to_owned()).is_err() { + if $logs_tx.send(str.to_owned()).await.is_err() { $exit.notify_one(); break; } @@ -89,5 +93,5 @@ impl From for Command { #[derive(Debug)] pub struct ProcessHandle { pub exit: Arc, - pub logs: UnboundedReceiver, + pub logs: Receiver, } diff --git a/crates/spuz_wrench/Cargo.toml b/crates/spuz_wrench/Cargo.toml index 282005a..5bd0d26 100644 --- a/crates/spuz_wrench/Cargo.toml +++ b/crates/spuz_wrench/Cargo.toml @@ -14,6 +14,7 @@ spuz_spawner = { workspace = true } tracing = { workspace = true } itertools = { version = "0.12" } +typed-builder = { version = "0.18" } cfg-if = { version = "1" } [lints] diff --git a/crates/spuz_wrench/src/lib.rs b/crates/spuz_wrench/src/lib.rs index 5eaebbe..a3c1c89 100644 --- a/crates/spuz_wrench/src/lib.rs +++ b/crates/spuz_wrench/src/lib.rs @@ -1,90 +1,59 @@ mod internal; +mod mandep; mod opts; -use std::{collections::HashSet, iter, ops::Deref, path::Path, sync::Arc}; +use std::{collections::HashSet, path::Path}; +use spuz_piston::{Feature, Manifest, RuleCompilance}; use spuz_spawner::{LaunchMod, Layer}; -use spuz_piston::{Argument, Feature, ListOrValue, PistonManifest, Rule, RuleCompilance}; +use typed_builder::TypedBuilder; -use crate::internal::{AssersDir, Classpath, GameDir, NativesDir, VersionInfo}; pub use crate::opts::{Fullscreen, LauncherInfo, Player, WindowSize}; - -#[derive(Debug)] -pub struct LauncherWrench { - pub manifest: PistonManifest, - pub libraries_dir: Arc, - pub assets_dir: Arc, - pub natives_dir: Arc, - pub game_dir: Arc, - pub client_jar: Arc, +use crate::{ + internal::{AssersDir, GameDir, NativesDir}, + mandep::ManifestLayer, +}; + +#[derive(Debug, TypedBuilder)] +pub struct LauncherWrench<'a> { + pub manifest: &'a Manifest, + pub current_dir: &'a Path, + #[builder(default, setter(into))] + pub libraries_dir: Option<&'a Path>, + #[builder(default, setter(into))] + pub assets_dir: Option<&'a Path>, + #[builder(default, setter(into))] + pub natives_dir: Option<&'a Path>, + pub game_dir: &'a Path, + #[builder(default, setter(into))] + pub client_jar: Option<&'a Path>, + #[builder(default = HashSet::from([Feature::CustomResolution]))] pub features: HashSet, } -impl Layer for LauncherWrench { +impl<'a> Layer for LauncherWrench<'a> { fn apply(self, launch_mod: &mut LaunchMod) { let rule_compilance = RuleCompilance::new(self.features); - let check_rule = |rule| rule_compilance.is_met(&rule); - - *launch_mod.main_class = self.manifest.main_class; - - for arg in self.manifest.arguments.jvm { - push_arg(&rule_compilance, launch_mod.java_args, arg); - } - - for arg in self.manifest.arguments.game { - push_arg(&rule_compilance, launch_mod.app_args, arg); - } - - let classpath = self.manifest.libraries.into_iter().filter_map(|lib| { - let lib_path = self.libraries_dir.join(lib.downloads.artifact.path); - let lib_rule_filter = |rules: Vec| { - let all_met = rules.into_iter().all(check_rule); - all_met.then_some(lib_path.clone()) - }; + self.current_dir.clone_into(launch_mod.current_dir); - match lib.rules { - Some(rules) => lib_rule_filter(rules), - None => Some(lib_path), - } - }); - - let classpath = classpath.chain(iter::once(self.client_jar.deref().to_owned())); + let id = self.manifest.id(); + let client_dir = Path::new("versions").join(self.manifest.id()); + let client_jar = client_dir.join(format!("{id}.jar")); + let client_natives = client_dir.join("natives"); let layers = ( - Classpath(classpath), - VersionInfo { - id: self.manifest.version.id(), - version_type: self.manifest.version.version_type(), - asset_index_id: &self.manifest.assets, + ManifestLayer { + rulecomp: &rule_compilance, + manifest: self.manifest, + libraries_dir: self.libraries_dir.unwrap_or(Path::new("libraries")), + client_jar: self.client_jar.unwrap_or(&client_jar), }, - AssersDir(&self.assets_dir), - NativesDir(&self.natives_dir), - GameDir(&self.game_dir), + AssersDir(self.assets_dir.unwrap_or(Path::new("assets"))), + NativesDir(self.natives_dir.unwrap_or(&client_natives)), + GameDir(self.game_dir), ); layers.apply(launch_mod); } } - -fn push_arg(rule_compilance: &RuleCompilance, into: &mut Vec, arg: Argument) { - match arg { - Argument::Plain(it) => { - into.push(it); - } - Argument::Conditional(container) => { - let arg = rule_compilance.unpack(container); - - if let Some(arg) = arg { - match arg { - ListOrValue::List(it) => { - into.extend(it); - } - ListOrValue::Value(it) => { - into.push(it); - } - } - } - } - }; -} diff --git a/crates/spuz_wrench/src/mandep.rs b/crates/spuz_wrench/src/mandep.rs new file mode 100644 index 0000000..19e0a8e --- /dev/null +++ b/crates/spuz_wrench/src/mandep.rs @@ -0,0 +1,128 @@ +use std::{ + iter, + path::{Path, PathBuf}, +}; + +use spuz_piston::{ + v::{ + shared::{Argument, Arguments, ListOrValue}, + AnyManifest, + }, + Manifest, NativeClassifier, Rule, RuleCompilance, TARGET_OS, +}; +use spuz_spawner::{LaunchMod, Layer}; + +use crate::internal::{Classpath, VersionInfo}; + +#[derive(Debug)] +pub struct ManifestLayer<'a> { + pub rulecomp: &'a RuleCompilance, + pub manifest: &'a Manifest, + pub libraries_dir: &'a Path, + pub client_jar: &'a Path, +} + +impl<'a> Layer for ManifestLayer<'a> { + fn apply(self, launch_mod: &mut LaunchMod) { + self.manifest.main_class().clone_into(launch_mod.main_class); + + match self.manifest.as_ref() { + AnyManifest::V19(manifest) => { + push_modern_arguments(self.rulecomp, &manifest.arguments, launch_mod); + } + AnyManifest::V12(manifest) => { + push_modern_arguments(self.rulecomp, &manifest.arguments, launch_mod); + } + } + + let check_rule = |rule: &Rule| self.rulecomp.is_met(rule); + + let classpath: Box> = match self.manifest.as_ref() { + AnyManifest::V19(manifest) => { + let iter = manifest.libraries.iter().filter_map(|lib| { + let lib_path = self.libraries_dir.join(&lib.downloads.artifact.path); + + let lib_rule_filter = |rules: &[Rule]| { + let all_met = rules.iter().all(check_rule); + all_met.then_some(lib_path.clone()) + }; + + match &lib.rules { + Some(rules) => lib_rule_filter(rules), + None => Some(lib_path), + } + }); + + Box::from(iter) + } + AnyManifest::V12(manifest) => { + let iter = manifest + .libraries + .iter() + .filter(|lib| match &lib.rules { + Some(rules) => rules.iter().all(check_rule), + None => true, + }) + .flat_map(|lib| { + let main_lib_path = self.libraries_dir.join(&lib.downloads.artifact.path); + + let target_classifier = NativeClassifier::from(TARGET_OS); + if let Some(classifier) = lib.downloads.classifiers.as_ref().and_then(|it| it.get(&target_classifier)) { + let native_lib_path = self.libraries_dir.join(&classifier.path); + [Some(main_lib_path), Some(native_lib_path)] + } else { + [Some(main_lib_path), None] + } + }) + .flatten(); + + Box::from(iter) + } + }; + + let classpath = classpath.chain(iter::once(self.client_jar.to_owned())); + + let layers = ( + Classpath(classpath), + VersionInfo { + id: self.manifest.id(), + version_type: self.manifest.version_type(), + asset_index_id: self.manifest.asset_index_id(), + }, + ); + + layers.apply(launch_mod); + } +} + +fn push_modern_arguments(rulcomp: &RuleCompilance, args: &Arguments, launch_mod: &mut LaunchMod) { + for arg in args.jvm.iter() { + push_modern_arg(rulcomp, launch_mod.java_args, arg); + } + + for arg in args.game.iter() { + push_modern_arg(rulcomp, launch_mod.app_args, arg); + } +} + +fn push_modern_arg(rule_compilance: &RuleCompilance, into: &mut Vec, arg: &Argument) { + match arg { + Argument::Plain(it) => { + into.push(it.to_string()); + } + Argument::Conditional(container) => { + let arg = rule_compilance.unpack_ref(container); + + if let Some(arg) = arg { + match arg { + ListOrValue::List(it) => { + into.extend(it.iter().map(ToString::to_string)); + } + ListOrValue::Value(it) => { + into.push(it.to_string()); + } + } + } + } + }; +} diff --git a/crates/spuz_wrench/src/opts.rs b/crates/spuz_wrench/src/opts.rs index 65b1ceb..3ce5b01 100644 --- a/crates/spuz_wrench/src/opts.rs +++ b/crates/spuz_wrench/src/opts.rs @@ -78,8 +78,8 @@ impl Layer for WindowSize { for arg in &mut *launch_mod.app_args { *arg = arg.replace("${resolution_width}", &width); *arg = arg.replace("${resolution_height}", &height); - debug!("Window size set to {width}x{height}"); } + debug!("Window size set to {width}x{height}"); } } diff --git a/readme.md b/readme.md index 9877a61..3fe6c5f 100644 --- a/readme.md +++ b/readme.md @@ -11,7 +11,8 @@ - [x] Version manifest `1.19+` - [x] Asset index - [x] Listing - - [ ] Support for older versions `1.16+` + - [x] Support for older versions `1.12+` + - [x] Launcher profile - `spuz_spawner` - Helpers for spawning java runtime - [x] Spawning java - [x] Easy way to apply argument changes @@ -19,7 +20,8 @@ - `spuz_wrench` - Layers for `spuz_spawner` to configure java runtime command to launch game from version manifest - [x] Support variables `1.16+` - [x] Conditional arguments and libraries (depending on target os and arch) `1.19+` - - [ ] Support for older versions `1.16+` + - [x] Support for older versions `1.12+` + - [x] Friendly and typed builder - `spuz_get` - Concurrent file downloader. Will download minecraft for you. - [x] Download files concurrently - [x] Progress tracking @@ -44,15 +46,11 @@ async fn main() -> Result<()> { builder.apply(AllocRange(1024..4096)); // Setup launcher wrench - let wrench = LauncherWrench { - manifest: manifest.deref().clone(), - libraries_dir: root.join("libraries").into(), - assets_dir: root.join("assets").into(), - natives_dir: client_dir.join("natives").into(), - client_jar: client_dir.join(format!("1.20.4.jar")).into(), - game_dir: root.join("instances").join("test").into(), - features: HashSet::from([Feature::CustomResolution]), - }; + let wrench = LauncherWrench::builder() + .manifest(&manifest) + .current_dir(&root) + .game_dir(&game_dir) + .build(); // Apply wrench builder.apply(wrench); @@ -64,10 +62,10 @@ async fn main() -> Result<()> { // builder.apply(Fullscreen); // Build and spawn process - let mut process = builder.build().spawn()?; + let process = builder.build().spawn()?; // Watch for logs - while let Some(log) = process.logs.recv().await { + while let Ok(log) = process.logs.recv().await { print!("{log}"); }