You've already forked AstralRinth
forked from didirus/AstralRinth
Merge pull request #5 from enderger/feature/modpack-creation
Add modpack creation
This commit is contained in:
227
Cargo.lock
generated
227
Cargo.lock
generated
@@ -46,6 +46,17 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38de00daab4eac7d753e97697066238d67ce9d7e2d823ab4f72fe14af29f3f33"
|
checksum = "38de00daab4eac7d753e97697066238d67ce9d7e2d823ab4f72fe14af29f3f33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.51"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -64,12 +75,39 @@ version = "1.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||||
|
dependencies = [
|
||||||
|
"block-padding",
|
||||||
|
"byte-tools",
|
||||||
|
"byteorder",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-padding"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||||
|
dependencies = [
|
||||||
|
"byte-tools",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.8.0"
|
version = "3.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
|
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byte-tools"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.4.3"
|
version = "1.4.3"
|
||||||
@@ -147,9 +185,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.2.1"
|
version = "1.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
@@ -170,6 +208,15 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.29"
|
version = "0.8.29"
|
||||||
@@ -179,6 +226,12 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fake-simd"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
@@ -230,9 +283,9 @@ checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
|
checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -245,9 +298,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
|
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
@@ -255,15 +308,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
|
checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-executor"
|
name = "futures-executor"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
|
checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@@ -272,18 +325,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
|
checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
|
checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"proc-macro-hack",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -291,23 +342,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
|
checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
|
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
@@ -317,11 +367,18 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"proc-macro-hack",
|
|
||||||
"proc-macro-nested",
|
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -498,6 +555,17 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json5"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -506,9 +574,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.107"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
@@ -528,6 +596,12 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maplit"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matches"
|
name = "matches"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -640,6 +714,12 @@ version = "1.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.38"
|
version = "0.10.38"
|
||||||
@@ -710,6 +790,49 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||||
|
dependencies = [
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_derive"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_generator"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_meta",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_meta"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
|
||||||
|
dependencies = [
|
||||||
|
"maplit",
|
||||||
|
"pest",
|
||||||
|
"sha-1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
@@ -734,18 +857,6 @@ version = "0.2.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-hack"
|
|
||||||
version = "0.5.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-nested"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.32"
|
version = "1.0.32"
|
||||||
@@ -876,9 +987,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.5"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
@@ -941,9 +1052,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.71"
|
version = "1.0.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
|
checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -962,6 +1073,18 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha-1"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"digest",
|
||||||
|
"fake-simd",
|
||||||
|
"opaque-debug",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -1001,9 +1124,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.81"
|
version = "1.0.82"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
|
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1039,11 +1162,13 @@ name = "theseus"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argh",
|
"argh",
|
||||||
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"daedalus",
|
"daedalus",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
"futures",
|
"futures",
|
||||||
|
"json5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"path-clean",
|
"path-clean",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -1199,6 +1324,18 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
async-trait = "0.1.51"
|
||||||
|
|
||||||
daedalus = "0.1.6"
|
daedalus = "0.1.6"
|
||||||
|
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
json5 = "0.4.1"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{path::PathBuf, time::Instant};
|
use std::{path::PathBuf, time::Instant};
|
||||||
|
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
use theseus::modpack::{fetch_modpack, manifest::ModpackSide};
|
use theseus::modpack::{fetch_modpack, pack::ModpackSide};
|
||||||
|
|
||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
/// Simple modpack download
|
/// Simple modpack download
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use daedalus::minecraft::{ArgumentType, VersionInfo};
|
use daedalus::minecraft::{ArgumentType, VersionInfo};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -65,7 +66,8 @@ pub async fn fetch_metadata() -> Result<
|
|||||||
Ok((game?, forge?, fabric?))
|
Ok((game?, forge?, fabric?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ModLoader {
|
pub enum ModLoader {
|
||||||
Vanilla,
|
Vanilla,
|
||||||
Forge,
|
Forge,
|
||||||
|
|||||||
@@ -1,291 +1,267 @@
|
|||||||
use std::{
|
use std::collections::HashSet;
|
||||||
convert::TryFrom,
|
use std::path::{Path, PathBuf};
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use daedalus::download_file_mirrors;
|
use std::convert::TryFrom;
|
||||||
use futures::future;
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use crate::launcher::ModLoader;
|
use crate::launcher::ModLoader;
|
||||||
|
|
||||||
use super::ModpackError;
|
use super::pack::ModpackGame;
|
||||||
|
use super::{pack, ModpackError, ModpackResult};
|
||||||
|
use daedalus::modded::LoaderType;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
pub const DEFAULT_FORMAT_VERSION: u32 = 1;
|
||||||
pub struct Manifest {
|
|
||||||
format_version: u64,
|
|
||||||
game: ModpackGame,
|
|
||||||
version_id: String,
|
|
||||||
|
|
||||||
name: String,
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
summary: Option<String>,
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Manifest<'a> {
|
||||||
files: Vec<ModpackFile>,
|
pub format_version: u32,
|
||||||
|
pub game: &'a str,
|
||||||
|
pub version_id: &'a str,
|
||||||
|
pub name: &'a str,
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub summary: Option<&'a str>,
|
||||||
|
pub files: Vec<ManifestFile<'a>>,
|
||||||
|
pub dependencies: ManifestDeps<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manifest {
|
impl TryFrom<Manifest<'_>> for pack::Modpack {
|
||||||
/// Download a modpack's files for a given side to a given destination
|
|
||||||
/// Assumes the destination exists and is a directory
|
|
||||||
pub async fn download_files(&self, dest: &Path, side: ModpackSide) -> Result<(), ModpackError> {
|
|
||||||
let handles = self.files.clone().into_iter().map(move |file| {
|
|
||||||
let (dest, side) = (dest.to_owned(), side);
|
|
||||||
tokio::spawn(async move { file.fetch(&dest, side).await })
|
|
||||||
});
|
|
||||||
future::try_join_all(handles)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Result<_, ModpackError>>()?;
|
|
||||||
|
|
||||||
// TODO Integrate instance format to save other metadata
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_get<'r, F, T>(
|
|
||||||
manifest: &'r serde_json::Map<String, serde_json::Value>,
|
|
||||||
field: &str,
|
|
||||||
caster: F,
|
|
||||||
) -> Result<T, ModpackError>
|
|
||||||
where
|
|
||||||
F: Fn(&'r serde_json::Value) -> Option<T>,
|
|
||||||
{
|
|
||||||
manifest
|
|
||||||
.get(field)
|
|
||||||
.and_then(caster)
|
|
||||||
.ok_or(ModpackError::ManifestError(format!(
|
|
||||||
"Invalid or missing field: {}",
|
|
||||||
field
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<serde_json::Value> for Manifest {
|
|
||||||
type Error = ModpackError;
|
type Error = ModpackError;
|
||||||
|
|
||||||
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
|
fn try_from(manifest: Manifest<'_>) -> Result<Self, Self::Error> {
|
||||||
use ModpackError::ManifestError;
|
let files = manifest
|
||||||
|
.files
|
||||||
let value = value.as_object().ok_or(ManifestError(String::from(
|
.into_iter()
|
||||||
"Manifest is not a JSON object!",
|
.map(pack::ModpackFile::try_from)
|
||||||
)))?;
|
.collect::<ModpackResult<HashSet<pack::ModpackFile>>>()?;
|
||||||
|
|
||||||
let game = ModpackGame::new(
|
|
||||||
try_get(value, "game", serde_json::Value::as_str)?,
|
|
||||||
try_get(value, "dependencies", serde_json::Value::as_object)?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let files = try_get(value, "files", serde_json::Value::as_array)?
|
|
||||||
.iter()
|
|
||||||
.map(|it| -> Result<ModpackFile, ModpackError> {
|
|
||||||
let file = it
|
|
||||||
.as_object()
|
|
||||||
.ok_or(ManifestError(String::from("Malformed file: not an object")))?;
|
|
||||||
|
|
||||||
let path = Path::new(try_get(file, "path", serde_json::Value::as_str)?);
|
|
||||||
let hashes = ModpackFileHashes::try_from(try_get(
|
|
||||||
file,
|
|
||||||
"hashes",
|
|
||||||
serde_json::Value::as_object,
|
|
||||||
)?)?;
|
|
||||||
let downloads = try_get(file, "downloads", serde_json::Value::as_array)?
|
|
||||||
.iter()
|
|
||||||
.map(serde_json::Value::as_str)
|
|
||||||
.map(|it| it.map(String::from))
|
|
||||||
.collect::<Option<Vec<String>>>()
|
|
||||||
.ok_or(ManifestError(format!(
|
|
||||||
"Invalid source for path {}",
|
|
||||||
path.to_str().unwrap_or("?")
|
|
||||||
)))?;
|
|
||||||
let env: Option<[ModpackEnv; 2]> = if let Some(env) = file.get("env") {
|
|
||||||
if !env.is_object() {
|
|
||||||
return Err(ManifestError(String::from(
|
|
||||||
"Env is provided, but is not an object!",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
Some([
|
|
||||||
ModpackEnv::from_str(
|
|
||||||
env.get("client")
|
|
||||||
.and_then(serde_json::Value::as_str)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)?,
|
|
||||||
ModpackEnv::from_str(
|
|
||||||
env.get("server")
|
|
||||||
.and_then(serde_json::Value::as_str)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)?,
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
ModpackFile::new(path, hashes, env, &downloads)
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<ModpackFile>, ModpackError>>()?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
format_version: try_get(value, "formatVersion", serde_json::Value::as_u64)?,
|
name: String::from(manifest.name),
|
||||||
game,
|
version: String::from(manifest.version_id),
|
||||||
version_id: String::from(try_get(value, "versionId", serde_json::Value::as_str)?),
|
summary: manifest.summary.map(String::from),
|
||||||
name: String::from(try_get(value, "name", serde_json::Value::as_str)?),
|
game: ModpackGame::from(manifest.dependencies),
|
||||||
summary: value
|
|
||||||
.get("summary")
|
|
||||||
.and_then(serde_json::Value::as_str)
|
|
||||||
.map(String::from),
|
|
||||||
files,
|
files,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
const MODRINTH_GAMEDATA_URL: &'static str = "https://staging-cdn.modrinth.com/gamedata";
|
||||||
pub enum ModpackGame {
|
fn get_loader_version(loader: ModLoader, version: &str) -> ModpackResult<String> {
|
||||||
// TODO: Currently, the launcher does not support specifying mod loader versions, so I just
|
let source = match loader {
|
||||||
// store the loader here.
|
ModLoader::Vanilla => Err(ModpackError::VersionError(String::from(
|
||||||
Minecraft(String, ModLoader),
|
"Attempted to get mod loader version of Vanilla",
|
||||||
|
))),
|
||||||
|
ModLoader::Forge => Ok(format!("{}/forge/v0/manifest.json", MODRINTH_GAMEDATA_URL)),
|
||||||
|
ModLoader::Fabric => Ok(format!("{}/fabric/v0/manifest.json", MODRINTH_GAMEDATA_URL)),
|
||||||
|
}?;
|
||||||
|
let manifest = futures::executor::block_on(daedalus::modded::fetch_manifest(&source))?;
|
||||||
|
|
||||||
|
Ok(manifest
|
||||||
|
.game_versions
|
||||||
|
.iter()
|
||||||
|
.find(|&it| it.id == version)
|
||||||
|
.ok_or(ModpackError::VersionError(format!(
|
||||||
|
"No versions of modloader {:?} exist for Minecraft {}",
|
||||||
|
loader, version
|
||||||
|
)))?
|
||||||
|
.loaders[&LoaderType::Latest]
|
||||||
|
.id
|
||||||
|
.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModpackGame {
|
impl<'a> TryFrom<&'a pack::Modpack> for Manifest<'a> {
|
||||||
pub fn new(
|
|
||||||
game: &str,
|
|
||||||
deps: &serde_json::Map<String, serde_json::Value>,
|
|
||||||
) -> Result<Self, ModpackError> {
|
|
||||||
match game {
|
|
||||||
"minecraft" => {
|
|
||||||
let game_version = String::from(
|
|
||||||
deps.get("minecraft")
|
|
||||||
.ok_or(ModpackError::ManifestError(String::from(
|
|
||||||
"No version of minecraft given",
|
|
||||||
)))?
|
|
||||||
.as_str()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: See comment in ModpackGame, this code was designed specifically to be
|
|
||||||
// easily adapted for versioned modloaders
|
|
||||||
let loader = if let Some(_) = deps.get("fabric-loader") {
|
|
||||||
ModLoader::Fabric
|
|
||||||
} else if let Some(_) = deps.get("forge") {
|
|
||||||
ModLoader::Forge
|
|
||||||
} else {
|
|
||||||
ModLoader::Vanilla
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ModpackGame::Minecraft(game_version, loader))
|
|
||||||
}
|
|
||||||
_ => Err(ModpackError::ManifestError(format!(
|
|
||||||
"Invalid game: {}",
|
|
||||||
game
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct ModpackFile {
|
|
||||||
path: PathBuf,
|
|
||||||
hashes: ModpackFileHashes,
|
|
||||||
envs: Option<[ModpackEnv; 2]>,
|
|
||||||
downloads: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum ModpackSide {
|
|
||||||
Client = 0,
|
|
||||||
Server = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModpackFile {
|
|
||||||
pub fn new(
|
|
||||||
path: &Path,
|
|
||||||
hashes: ModpackFileHashes,
|
|
||||||
envs: Option<[ModpackEnv; 2]>,
|
|
||||||
downloads: &[String],
|
|
||||||
) -> Result<Self, ModpackError> {
|
|
||||||
if path.is_dir() {
|
|
||||||
return Err(ModpackError::ManifestError(format!(
|
|
||||||
"Modpack file {} is a directory!",
|
|
||||||
path.to_str().unwrap_or("?")
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
path: PathBuf::from(path),
|
|
||||||
hashes,
|
|
||||||
envs,
|
|
||||||
downloads: Vec::from(downloads),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch(&self, dest: &Path, side: ModpackSide) -> Result<(), ModpackError> {
|
|
||||||
if let Some(envs) = &self.envs {
|
|
||||||
if envs[side as usize] == ModpackEnv::Unsupported
|
|
||||||
|| envs[(side as usize + 1) % 2] == ModpackEnv::Required
|
|
||||||
{
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = dest.join(&self.path);
|
|
||||||
|
|
||||||
// HACK: Since Daedalus appends a file name to all mirrors and the manifest supplies full
|
|
||||||
// URLs, I'm supplying it with an empty string to avoid reinventing the wheel.
|
|
||||||
let bytes = download_file_mirrors(
|
|
||||||
"",
|
|
||||||
&self
|
|
||||||
.downloads
|
|
||||||
.iter()
|
|
||||||
.map(|it| it.as_str())
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.as_slice(),
|
|
||||||
Some(&self.hashes.sha1),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
fs::create_dir_all(output.parent().unwrap()).await?;
|
|
||||||
fs::write(output, bytes).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct ModpackFileHashes {
|
|
||||||
sha1: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&serde_json::Map<String, serde_json::Value>> for ModpackFileHashes {
|
|
||||||
type Error = ModpackError;
|
type Error = ModpackError;
|
||||||
|
|
||||||
fn try_from(value: &serde_json::Map<String, serde_json::Value>) -> Result<Self, Self::Error> {
|
fn try_from(pack: &'a pack::Modpack) -> Result<Self, Self::Error> {
|
||||||
let sha1 = String::from(try_get(&value, "sha1", serde_json::Value::as_str)?);
|
let game_field: &'a str = match pack.game {
|
||||||
Ok(Self { sha1 })
|
ModpackGame::Minecraft(..) => "minecraft",
|
||||||
|
};
|
||||||
|
|
||||||
|
let files = pack
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.map(ManifestFile::from)
|
||||||
|
.collect::<Vec<ManifestFile>>();
|
||||||
|
|
||||||
|
Ok(Manifest {
|
||||||
|
format_version: DEFAULT_FORMAT_VERSION,
|
||||||
|
game: game_field,
|
||||||
|
version_id: &pack.version,
|
||||||
|
name: &pack.name,
|
||||||
|
summary: pack.summary.as_ref().map(String::as_str),
|
||||||
|
files,
|
||||||
|
dependencies: ManifestDeps::try_from(&pack.game)?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub enum ModpackEnv {
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ManifestFile<'a> {
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub path: &'a Path,
|
||||||
|
pub hashes: ManifestHashes<'a>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub env: ManifestEnvs,
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub downloads: Vec<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ManifestFile<'_>> for pack::ModpackFile {
|
||||||
|
type Error = ModpackError;
|
||||||
|
|
||||||
|
fn try_from(file: ManifestFile<'_>) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
path: PathBuf::from(file.path),
|
||||||
|
hashes: pack::ModpackFileHashes::from(file.hashes),
|
||||||
|
env: pack::ModpackEnv::try_from(file.env)?,
|
||||||
|
downloads: file.downloads.into_iter().map(ToOwned::to_owned).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a pack::ModpackFile> for ManifestFile<'a> {
|
||||||
|
fn from(file: &'a pack::ModpackFile) -> Self {
|
||||||
|
Self {
|
||||||
|
path: file.path.as_path(),
|
||||||
|
hashes: (&file.hashes).into(),
|
||||||
|
env: file.env.into(),
|
||||||
|
downloads: file
|
||||||
|
.downloads
|
||||||
|
.iter()
|
||||||
|
.map(String::as_str)
|
||||||
|
.collect::<Vec<&str>>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub struct ManifestHashes<'a> {
|
||||||
|
pub sha1: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ManifestHashes<'_>> for pack::ModpackFileHashes {
|
||||||
|
fn from(hashes: ManifestHashes<'_>) -> Self {
|
||||||
|
Self {
|
||||||
|
sha1: String::from(hashes.sha1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a pack::ModpackFileHashes> for ManifestHashes<'a> {
|
||||||
|
fn from(hashes: &'a pack::ModpackFileHashes) -> Self {
|
||||||
|
Self { sha1: &hashes.sha1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub struct ManifestEnvs {
|
||||||
|
pub client: ManifestEnv,
|
||||||
|
pub server: ManifestEnv,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ManifestEnvs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
client: ManifestEnv::Optional,
|
||||||
|
server: ManifestEnv::Optional,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum ManifestEnv {
|
||||||
Required,
|
Required,
|
||||||
Optional,
|
Optional,
|
||||||
Unsupported,
|
Unsupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ModpackEnv {
|
impl TryFrom<ManifestEnvs> for pack::ModpackEnv {
|
||||||
type Err = ModpackError;
|
type Error = ModpackError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn try_from(envs: ManifestEnvs) -> Result<Self, Self::Error> {
|
||||||
use ModpackEnv::*;
|
use ManifestEnv::*;
|
||||||
match s {
|
|
||||||
"required" => Ok(Required),
|
match (envs.client, envs.server) {
|
||||||
"optional" => Ok(Optional),
|
(Required, Unsupported) => Ok(Self::ClientOnly),
|
||||||
"unsupported" => Ok(Unsupported),
|
(Unsupported, Required) => Ok(Self::ServerOnly),
|
||||||
_ => Err(ModpackError::ManifestError(format!(
|
(Optional, Optional) => Ok(Self::Both),
|
||||||
"Invalid environment support: {}",
|
_ => Err(ModpackError::FormatError(format!(
|
||||||
s
|
"Invalid environment specification: {:?}",
|
||||||
|
envs
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ModpackEnv {
|
impl From<pack::ModpackEnv> for ManifestEnvs {
|
||||||
fn default() -> Self {
|
fn from(envs: pack::ModpackEnv) -> Self {
|
||||||
Self::Optional
|
use super::pack::ModpackEnv::*;
|
||||||
|
|
||||||
|
let (client, server) = match envs {
|
||||||
|
ClientOnly => (ManifestEnv::Required, ManifestEnv::Unsupported),
|
||||||
|
ServerOnly => (ManifestEnv::Unsupported, ManifestEnv::Required),
|
||||||
|
Both => (ManifestEnv::Optional, ManifestEnv::Optional),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { client, server }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
// HACK: I've tried for hours to get this working zero-copy, but I'm beat. If someone else wants to
|
||||||
|
// go through the #<!! of implementing it, be my guest.
|
||||||
|
pub enum ManifestDeps<'a> {
|
||||||
|
MinecraftFabric {
|
||||||
|
minecraft: &'a str,
|
||||||
|
#[serde(rename = "fabric-loader")]
|
||||||
|
fabric_loader: String,
|
||||||
|
},
|
||||||
|
MinecraftForge {
|
||||||
|
minecraft: &'a str,
|
||||||
|
forge: String,
|
||||||
|
},
|
||||||
|
MinecraftVanilla {
|
||||||
|
minecraft: &'a str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ManifestDeps<'_>> for pack::ModpackGame {
|
||||||
|
fn from(deps: ManifestDeps<'_>) -> Self {
|
||||||
|
use ManifestDeps::*;
|
||||||
|
|
||||||
|
match deps {
|
||||||
|
MinecraftVanilla { minecraft } => {
|
||||||
|
Self::Minecraft(String::from(minecraft), ModLoader::Vanilla)
|
||||||
|
}
|
||||||
|
MinecraftFabric { minecraft, .. } => {
|
||||||
|
Self::Minecraft(String::from(minecraft), ModLoader::Fabric)
|
||||||
|
}
|
||||||
|
MinecraftForge { minecraft, .. } => {
|
||||||
|
Self::Minecraft(String::from(minecraft), ModLoader::Forge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a pack::ModpackGame> for ManifestDeps<'a> {
|
||||||
|
type Error = ModpackError;
|
||||||
|
|
||||||
|
fn try_from(game: &'a pack::ModpackGame) -> Result<Self, Self::Error> {
|
||||||
|
use super::pack::ModpackGame::*;
|
||||||
|
Ok(match game {
|
||||||
|
Minecraft(ref ver, ModLoader::Vanilla) => Self::MinecraftVanilla { minecraft: ver },
|
||||||
|
Minecraft(ref ver, loader @ ModLoader::Fabric) => Self::MinecraftFabric {
|
||||||
|
minecraft: ver,
|
||||||
|
fabric_loader: get_loader_version(*loader, ver)?,
|
||||||
|
},
|
||||||
|
Minecraft(ref ver, loader @ ModLoader::Forge) => Self::MinecraftForge {
|
||||||
|
minecraft: ver,
|
||||||
|
forge: get_loader_version(*loader, ver)?,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +270,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_simple() -> Result<(), ModpackError> {
|
fn parse_simple() -> ModpackResult<()> {
|
||||||
const PACK_JSON: &'static str = r#"
|
const PACK_JSON: &'static str = r#"
|
||||||
{
|
{
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
@@ -309,22 +285,23 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
let expected_manifest = Manifest {
|
let expected_manifest = Manifest {
|
||||||
format_version: 1,
|
format_version: 1,
|
||||||
game: ModpackGame::Minecraft(String::from("1.17.1"), ModLoader::Vanilla),
|
game: "minecraft",
|
||||||
version_id: String::from("deadbeef"),
|
version_id: "deadbeef",
|
||||||
name: String::from("Example Pack"),
|
name: "Example Pack",
|
||||||
summary: None,
|
summary: None,
|
||||||
files: Vec::new(),
|
files: Vec::new(),
|
||||||
|
dependencies: ManifestDeps::MinecraftVanilla {
|
||||||
|
minecraft: "1.17.1",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
let manifest_json: serde_json::Value =
|
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
||||||
serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
|
||||||
let manifest = Manifest::try_from(manifest_json)?;
|
|
||||||
|
|
||||||
assert_eq!(expected_manifest, manifest);
|
assert_eq!(expected_manifest, manifest);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_forge() -> Result<(), ModpackError> {
|
fn parse_forge() -> ModpackResult<()> {
|
||||||
const PACK_JSON: &'static str = r#"
|
const PACK_JSON: &'static str = r#"
|
||||||
{
|
{
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
@@ -348,33 +325,33 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let expected_manifest = Manifest {
|
let expected_manifest = Manifest {
|
||||||
format_version: 1,
|
format_version: 1,
|
||||||
game: ModpackGame::Minecraft(String::from("1.17.1"), ModLoader::Forge),
|
game: "minecraft",
|
||||||
version_id: String::from("deadbeef"),
|
version_id: "deadbeef",
|
||||||
name: String::from("Example Pack"),
|
name: "Example Pack",
|
||||||
summary: None,
|
summary: None,
|
||||||
files: vec![ModpackFile::new(
|
files: vec![ManifestFile {
|
||||||
Path::new("mods/testmod.jar"),
|
path: Path::new("mods/testmod.jar"),
|
||||||
ModpackFileHashes {
|
hashes: ManifestHashes {
|
||||||
sha1: String::from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
},
|
},
|
||||||
None,
|
env: ManifestEnvs::default(),
|
||||||
&[String::from("https://example.com/testmod.jar")],
|
downloads: vec!["https://example.com/testmod.jar"],
|
||||||
)?],
|
}],
|
||||||
|
dependencies: ManifestDeps::MinecraftForge {
|
||||||
|
minecraft: "1.17.1",
|
||||||
|
forge: String::from("37.0.110"),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
||||||
let manifest_json: serde_json::Value =
|
|
||||||
serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
|
||||||
let manifest = Manifest::try_from(manifest_json)?;
|
|
||||||
|
|
||||||
assert_eq!(expected_manifest, manifest);
|
assert_eq!(expected_manifest, manifest);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_fabric() -> Result<(), ModpackError> {
|
fn parse_fabric() -> ModpackResult<()> {
|
||||||
const PACK_JSON: &'static str = r#"
|
const PACK_JSON: &'static str = r#"
|
||||||
{
|
{
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
@@ -398,33 +375,33 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let expected_manifest = Manifest {
|
let expected_manifest = Manifest {
|
||||||
format_version: 1,
|
format_version: 1,
|
||||||
game: ModpackGame::Minecraft(String::from("1.17.1"), ModLoader::Fabric),
|
game: "minecraft",
|
||||||
version_id: String::from("deadbeef"),
|
version_id: "deadbeef",
|
||||||
name: String::from("Example Pack"),
|
name: "Example Pack",
|
||||||
summary: None,
|
summary: None,
|
||||||
files: vec![ModpackFile::new(
|
files: vec![ManifestFile {
|
||||||
Path::new("mods/testmod.jar"),
|
path: Path::new("mods/testmod.jar"),
|
||||||
ModpackFileHashes {
|
hashes: ManifestHashes {
|
||||||
sha1: String::from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
},
|
},
|
||||||
None,
|
env: ManifestEnvs::default(),
|
||||||
&[String::from("https://example.com/testmod.jar")],
|
downloads: vec!["https://example.com/testmod.jar"],
|
||||||
)?],
|
}],
|
||||||
|
dependencies: ManifestDeps::MinecraftFabric {
|
||||||
|
minecraft: "1.17.1",
|
||||||
|
fabric_loader: String::from("0.9.0"),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
||||||
let manifest_json: serde_json::Value =
|
|
||||||
serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
|
||||||
let manifest = Manifest::try_from(manifest_json)?;
|
|
||||||
|
|
||||||
assert_eq!(expected_manifest, manifest);
|
assert_eq!(expected_manifest, manifest);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_complete() -> Result<(), ModpackError> {
|
fn parse_complete() -> ModpackResult<()> {
|
||||||
const PACK_JSON: &'static str = r#"
|
const PACK_JSON: &'static str = r#"
|
||||||
{
|
{
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
@@ -453,28 +430,29 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let expected_manifest = Manifest {
|
let expected_manifest = Manifest {
|
||||||
format_version: 1,
|
format_version: 1,
|
||||||
game: ModpackGame::Minecraft(String::from("1.17.1"), ModLoader::Forge),
|
game: "minecraft",
|
||||||
version_id: String::from("deadbeef"),
|
version_id: "deadbeef",
|
||||||
name: String::from("Example Pack"),
|
name: "Example Pack",
|
||||||
summary: Some(String::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")),
|
summary: Some("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."),
|
||||||
files: vec![
|
files: vec![ManifestFile {
|
||||||
ModpackFile::new(
|
path: Path::new("mods/testmod.jar"),
|
||||||
Path::new("mods/testmod.jar"),
|
hashes: ManifestHashes {
|
||||||
ModpackFileHashes {
|
sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
sha1: String::from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
},
|
||||||
},
|
env: ManifestEnvs {
|
||||||
Some([ ModpackEnv::Required, ModpackEnv::Unsupported ]),
|
client: ManifestEnv::Required,
|
||||||
&[ String::from("https://example.com/testmod.jar") ],
|
server: ManifestEnv::Unsupported,
|
||||||
)?
|
},
|
||||||
],
|
downloads: vec!["https://example.com/testmod.jar"],
|
||||||
|
}],
|
||||||
|
dependencies: ManifestDeps::MinecraftForge {
|
||||||
|
minecraft: "1.17.1",
|
||||||
|
forge: String::from("37.0.110"),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
||||||
let manifest_json: serde_json::Value =
|
|
||||||
serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON");
|
|
||||||
let manifest = Manifest::try_from(manifest_json)?;
|
|
||||||
|
|
||||||
assert_eq!(expected_manifest, manifest);
|
assert_eq!(expected_manifest, manifest);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -2,17 +2,25 @@
|
|||||||
|
|
||||||
use daedalus::download_file;
|
use daedalus::download_file;
|
||||||
use fs_extra::dir::CopyOptions;
|
use fs_extra::dir::CopyOptions;
|
||||||
use std::{borrow::Borrow, convert::TryFrom, env, io, path::Path};
|
use serde::Deserialize;
|
||||||
use tokio::fs;
|
use std::{convert::TryFrom, env, io, path::Path};
|
||||||
|
use tokio::{fs, try_join};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
use self::manifest::Manifest;
|
use self::{
|
||||||
|
manifest::Manifest,
|
||||||
|
pack::{Modpack, ModpackGame},
|
||||||
|
};
|
||||||
|
|
||||||
pub mod manifest;
|
pub mod manifest;
|
||||||
|
pub mod modrinth_api;
|
||||||
|
pub mod pack;
|
||||||
|
|
||||||
|
pub const COMPILED_PATH: &'static str = "compiled/";
|
||||||
pub const MANIFEST_PATH: &'static str = "index.json";
|
pub const MANIFEST_PATH: &'static str = "index.json";
|
||||||
pub const OVERRIDES_PATH: &'static str = "overrides/";
|
pub const OVERRIDES_PATH: &'static str = "overrides/";
|
||||||
|
pub const PACK_JSON5_PATH: &'static str = "modpack.json5";
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum ModpackError {
|
pub enum ModpackError {
|
||||||
@@ -40,17 +48,31 @@ pub enum ModpackError {
|
|||||||
#[error("Error parsing json: {0}")]
|
#[error("Error parsing json: {0}")]
|
||||||
JsonError(#[from] serde_json::Error),
|
JsonError(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("Error parsing json5: {0}")]
|
||||||
|
Json5Error(#[from] json5::Error),
|
||||||
|
|
||||||
#[error("Error joining futures: {0}")]
|
#[error("Error joining futures: {0}")]
|
||||||
JoinError(#[from] tokio::task::JoinError),
|
JoinError(#[from] tokio::task::JoinError),
|
||||||
|
|
||||||
|
#[error("Versioning Error: {0}")]
|
||||||
|
VersionError(String),
|
||||||
|
|
||||||
|
#[error("Error downloading file: {0}")]
|
||||||
|
FetchError(#[from] reqwest::Error),
|
||||||
|
|
||||||
|
#[error("Invalid modpack source: {0} (set the WHITELISTED_MODPACK_DOMAINS environment variable to override)")]
|
||||||
|
SourceWhitelistError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ModpackResult<T> = Result<T, ModpackError>;
|
||||||
|
|
||||||
/// Realise a modpack from a given URL
|
/// Realise a modpack from a given URL
|
||||||
pub async fn fetch_modpack(
|
pub async fn fetch_modpack(
|
||||||
url: &str,
|
url: &str,
|
||||||
sha1: Option<&str>,
|
sha1: Option<&str>,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
side: manifest::ModpackSide,
|
side: pack::ModpackSide,
|
||||||
) -> Result<(), ModpackError> {
|
) -> ModpackResult<()> {
|
||||||
let bytes = download_file(url, sha1).await?;
|
let bytes = download_file(url, sha1).await?;
|
||||||
let mut archive = ZipArchive::new(io::Cursor::new(&bytes as &[u8]))?;
|
let mut archive = ZipArchive::new(io::Cursor::new(&bytes as &[u8]))?;
|
||||||
realise_modpack_zip(&mut archive, dest, side).await
|
realise_modpack_zip(&mut archive, dest, side).await
|
||||||
@@ -60,8 +82,8 @@ pub async fn fetch_modpack(
|
|||||||
pub async fn realise_modpack_zip(
|
pub async fn realise_modpack_zip(
|
||||||
archive: &mut ZipArchive<impl io::Read + io::Seek>,
|
archive: &mut ZipArchive<impl io::Read + io::Seek>,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
side: manifest::ModpackSide,
|
side: pack::ModpackSide,
|
||||||
) -> Result<(), ModpackError> {
|
) -> ModpackResult<()> {
|
||||||
let tmp = env::temp_dir().join(format!("theseus-{}/", Uuid::new_v4()));
|
let tmp = env::temp_dir().join(format!("theseus-{}/", Uuid::new_v4()));
|
||||||
archive.extract(&tmp)?;
|
archive.extract(&tmp)?;
|
||||||
realise_modpack(&tmp, dest, side).await
|
realise_modpack(&tmp, dest, side).await
|
||||||
@@ -71,8 +93,8 @@ pub async fn realise_modpack_zip(
|
|||||||
pub async fn realise_modpack(
|
pub async fn realise_modpack(
|
||||||
dir: &Path,
|
dir: &Path,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
side: manifest::ModpackSide,
|
side: pack::ModpackSide,
|
||||||
) -> Result<(), ModpackError> {
|
) -> ModpackResult<()> {
|
||||||
if dest.is_file() {
|
if dest.is_file() {
|
||||||
return Err(ModpackError::InvalidDirectory(String::from(
|
return Err(ModpackError::InvalidDirectory(String::from(
|
||||||
"Output is not a directory",
|
"Output is not a directory",
|
||||||
@@ -101,11 +123,66 @@ pub async fn realise_modpack(
|
|||||||
"Manifest missing or is not a file",
|
"Manifest missing or is not a file",
|
||||||
)))?;
|
)))?;
|
||||||
let manifest_file = std::fs::File::open(manifest_path)?;
|
let manifest_file = std::fs::File::open(manifest_path)?;
|
||||||
let manifest_json: serde_json::Value =
|
let reader = io::BufReader::new(manifest_file);
|
||||||
serde_json::from_reader(io::BufReader::new(manifest_file))?;
|
let mut deserializer = serde_json::Deserializer::from_reader(reader);
|
||||||
let manifest = Manifest::try_from(manifest_json)?;
|
let manifest = Manifest::deserialize(&mut deserializer)?;
|
||||||
|
let modpack = Modpack::try_from(manifest)?;
|
||||||
|
|
||||||
// Realise manifest
|
// Realise modpack
|
||||||
manifest.download_files(dest, side).await?;
|
modpack.download_files(dest, side).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_pack_json5(pack: &Modpack) -> ModpackResult<String> {
|
||||||
|
let json5 = json5::to_string(pack)?;
|
||||||
|
Ok(format!("// This modpack is managed using Theseus. It can be edited using either a Theseus-compatible launcher or manually.\n{}", json5))
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref PACK_GITIGNORE: String = format!(r#"
|
||||||
|
{0}
|
||||||
|
"#, COMPILED_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_modpack(
|
||||||
|
name: &str,
|
||||||
|
game: ModpackGame,
|
||||||
|
summary: Option<&str>,
|
||||||
|
) -> ModpackResult<()> {
|
||||||
|
let output_dir = Path::new("./").join(name);
|
||||||
|
let pack = Modpack::new(game, "0.1.0", name, summary);
|
||||||
|
|
||||||
|
try_join!(
|
||||||
|
fs::create_dir(&output_dir),
|
||||||
|
fs::create_dir(output_dir.join(OVERRIDES_PATH)),
|
||||||
|
fs::write(output_dir.join(".gitignore"), PACK_GITIGNORE.as_str()),
|
||||||
|
fs::write(output_dir.join(PACK_JSON5_PATH), to_pack_json5(&pack)?),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn compile_modpack(dir: &Path) -> ModpackResult<()> {
|
||||||
|
let result_dir = dir.join(COMPILED_PATH);
|
||||||
|
let pack: Modpack = json5::from_str(&fs::read_to_string(dir.join(PACK_JSON5_PATH)).await?)?;
|
||||||
|
|
||||||
|
if dir.join(OVERRIDES_PATH).exists() {
|
||||||
|
fs_extra::dir::copy(
|
||||||
|
dir.join(OVERRIDES_PATH),
|
||||||
|
result_dir.join(OVERRIDES_PATH),
|
||||||
|
&CopyOptions::new(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
let manifest = Manifest::try_from(&pack)?;
|
||||||
|
|
||||||
|
try_join!(
|
||||||
|
fs::create_dir(&result_dir),
|
||||||
|
fs::write(
|
||||||
|
result_dir.join(MANIFEST_PATH),
|
||||||
|
serde_json::to_string(&manifest)?
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
176
theseus/src/modpack/modrinth_api.rs
Normal file
176
theseus/src/modpack/modrinth_api.rs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
convert::TryFrom,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::launcher::ModLoader;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
manifest::{ManifestEnv, ManifestEnvs, ManifestHashes},
|
||||||
|
pack::{ModpackEnv, ModpackFile, ModpackFileHashes, ModpackGame},
|
||||||
|
ModpackError, ModpackResult,
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::future::try_join_all;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::try_join;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ModrinthAPI {
|
||||||
|
async fn get_latest_version(
|
||||||
|
&self,
|
||||||
|
project: &str,
|
||||||
|
channel: &str,
|
||||||
|
game: &ModpackGame,
|
||||||
|
) -> ModpackResult<HashSet<ModpackFile>>;
|
||||||
|
async fn get_version(&self, version: &str) -> ModpackResult<HashSet<ModpackFile>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ModrinthV1(pub String);
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ModrinthV1Project<'a> {
|
||||||
|
title: &'a str,
|
||||||
|
client_side: &'a str,
|
||||||
|
server_side: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ModrinthV1ProjectVersion<'a> {
|
||||||
|
#[serde(borrow)]
|
||||||
|
dependencies: HashSet<&'a str>,
|
||||||
|
#[serde(borrow)]
|
||||||
|
game_versions: HashSet<&'a str>,
|
||||||
|
version_type: &'a str,
|
||||||
|
files: Vec<ModrinthV1ProjectVersionFile<'a>>,
|
||||||
|
#[serde(borrow)]
|
||||||
|
loaders: HashSet<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct ModrinthV1ProjectVersionFile<'a> {
|
||||||
|
hashes: ManifestHashes<'a>,
|
||||||
|
url: &'a str,
|
||||||
|
filename: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModrinthV1ProjectVersionFile<'_>> for ModpackFile {
|
||||||
|
fn from(file: ModrinthV1ProjectVersionFile<'_>) -> Self {
|
||||||
|
Self {
|
||||||
|
hashes: ModpackFileHashes::from(file.hashes),
|
||||||
|
downloads: {
|
||||||
|
let mut downloads: HashSet<String> = HashSet::new();
|
||||||
|
downloads.insert(String::from(file.url));
|
||||||
|
downloads
|
||||||
|
},
|
||||||
|
path: PathBuf::from(file.filename),
|
||||||
|
// WARNING: Since the sidedness of version 1 API requests is unknown, the environemnt is
|
||||||
|
// set here as both.
|
||||||
|
env: ModpackEnv::Both,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ModrinthAPI for ModrinthV1 {
|
||||||
|
async fn get_latest_version(
|
||||||
|
&self,
|
||||||
|
project: &str,
|
||||||
|
channel: &str,
|
||||||
|
game: &ModpackGame,
|
||||||
|
) -> ModpackResult<HashSet<ModpackFile>> {
|
||||||
|
// Fetch metadata
|
||||||
|
let (project_json, versions_json): (Bytes, Bytes) = try_join!(
|
||||||
|
try_get_json(format!("{}/api/v1/mod/{}", self.0, project)),
|
||||||
|
try_get_json(format!("{}/api/v1/mod/{}/version", self.0, project)),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (mut project_deserializer, mut versions_deserializer) = (
|
||||||
|
serde_json::Deserializer::from_slice(&project_json),
|
||||||
|
serde_json::Deserializer::from_slice(&versions_json),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (project, versions) = (
|
||||||
|
ModrinthV1Project::deserialize(&mut project_deserializer)?,
|
||||||
|
Vec::deserialize(&mut versions_deserializer)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (game_version, loader) = match game {
|
||||||
|
ModpackGame::Minecraft(_, ModLoader::Vanilla) => Err(ModpackError::VersionError(
|
||||||
|
String::from("Modrinth V1 does not support vanilla projects"),
|
||||||
|
)),
|
||||||
|
ModpackGame::Minecraft(ref version, ref loader) => Ok((version, loader)),
|
||||||
|
_ => Err(ModpackError::VersionError(String::from(
|
||||||
|
"Attempted to use Modrinth API V1 to install a non-Minecraft project!",
|
||||||
|
))),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let version: ModrinthV1ProjectVersion = versions
|
||||||
|
.into_iter()
|
||||||
|
.find(|it: &ModrinthV1ProjectVersion| {
|
||||||
|
let loader_str = match loader {
|
||||||
|
ModLoader::Fabric => "fabric",
|
||||||
|
ModLoader::Forge => "forge",
|
||||||
|
ModLoader::Vanilla => unreachable!(),
|
||||||
|
};
|
||||||
|
it.version_type == channel
|
||||||
|
&& it.game_versions.contains(&game_version.as_str())
|
||||||
|
&& it.loaders.contains(&loader_str)
|
||||||
|
})
|
||||||
|
.ok_or(ModpackError::VersionError(format!(
|
||||||
|
"Unable to find compatible version of mod {}",
|
||||||
|
project.title
|
||||||
|
)))?;
|
||||||
|
|
||||||
|
// Project fields
|
||||||
|
let envs = ModpackEnv::try_from(ManifestEnvs {
|
||||||
|
client: serde_json::from_str(project.client_side)?,
|
||||||
|
server: serde_json::from_str(project.server_side)?,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Conversions
|
||||||
|
let files = version
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(ModpackFile::from)
|
||||||
|
.collect::<HashSet<ModpackFile>>();
|
||||||
|
|
||||||
|
let dep_futures = version.dependencies.iter().map(|it| self.get_version(&it));
|
||||||
|
let deps = try_join_all(dep_futures)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<HashSet<ModpackFile>>();
|
||||||
|
|
||||||
|
Ok(files
|
||||||
|
.into_iter()
|
||||||
|
.chain(deps.into_iter())
|
||||||
|
.map(|mut it| {
|
||||||
|
it.env = envs;
|
||||||
|
it
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_version(&self, version: &str) -> ModpackResult<HashSet<ModpackFile>> {
|
||||||
|
let version_json = try_get_json(format!("{}/api/v1/version/{}", self.0, version)).await?;
|
||||||
|
let mut version_deserializer = serde_json::Deserializer::from_slice(&version_json);
|
||||||
|
let version = ModrinthV1ProjectVersion::deserialize(&mut version_deserializer)?;
|
||||||
|
let base_path = PathBuf::from("mods/");
|
||||||
|
|
||||||
|
Ok(version
|
||||||
|
.files
|
||||||
|
.into_iter()
|
||||||
|
.map(ModpackFile::from)
|
||||||
|
.collect::<HashSet<ModpackFile>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
async fn try_get_json(url: String) -> ModpackResult<Bytes> {
|
||||||
|
Ok(reqwest::get(url).await?.error_for_status()?.bytes().await?)
|
||||||
|
}
|
||||||
262
theseus/src/modpack/pack.rs
Normal file
262
theseus/src/modpack/pack.rs
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
use daedalus::download_file_mirrors;
|
||||||
|
use futures::future;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
hash::Hash,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
modrinth_api::{self, ModrinthV1},
|
||||||
|
ModpackResult, ModpackError,
|
||||||
|
};
|
||||||
|
use crate::launcher::ModLoader;
|
||||||
|
|
||||||
|
pub const MODRINTH_DEFAULT_MODPACK_DOMAINS: &'static [&'static str] = &[
|
||||||
|
"cdn.modrinth.com",
|
||||||
|
"edge.forgecdn.net",
|
||||||
|
"github.com",
|
||||||
|
"raw.githubusercontent.com",
|
||||||
|
];
|
||||||
|
pub const MODRINTH_MODPACK_DOMAIN_WHITELIST_VAR: &'static str = "WHITELISTED_MODPACK_DOMAINS";
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Modpack {
|
||||||
|
pub game: ModpackGame,
|
||||||
|
pub version: String,
|
||||||
|
pub name: String,
|
||||||
|
pub summary: Option<String>,
|
||||||
|
pub files: HashSet<ModpackFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Modpack {
|
||||||
|
/// Download a modpack's files for a given side to a given destination
|
||||||
|
/// Assumes the destination exists and is a directory
|
||||||
|
pub async fn download_files(&self, dest: &Path, side: ModpackSide) -> ModpackResult<()> {
|
||||||
|
let handles = self.files.iter().cloned().map(move |file| {
|
||||||
|
let (dest, side) = (dest.to_owned(), side);
|
||||||
|
tokio::spawn(async move { file.fetch(&dest, side).await })
|
||||||
|
});
|
||||||
|
future::try_join_all(handles)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.collect::<ModpackResult<_>>()?;
|
||||||
|
|
||||||
|
// TODO Integrate instance format to save other metadata
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(game: ModpackGame, version: &str, name: &str, summary: Option<&str>) -> Self {
|
||||||
|
Self {
|
||||||
|
game,
|
||||||
|
version: String::from(version),
|
||||||
|
name: String::from(name),
|
||||||
|
summary: summary.map(String::from),
|
||||||
|
files: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_project(
|
||||||
|
&mut self,
|
||||||
|
project: &str,
|
||||||
|
base_path: &Path,
|
||||||
|
source: Option<&dyn modrinth_api::ModrinthAPI>,
|
||||||
|
channel: Option<&str>,
|
||||||
|
) -> ModpackResult<()> {
|
||||||
|
let default_api = ModrinthV1(String::from("https://api.modrinth.com"));
|
||||||
|
let channel = channel.unwrap_or("release");
|
||||||
|
let source = source.unwrap_or(&default_api);
|
||||||
|
|
||||||
|
let files = source
|
||||||
|
.get_latest_version(project, channel, &self.game)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut it: ModpackFile| {
|
||||||
|
it.path = base_path.join(it.path);
|
||||||
|
it
|
||||||
|
});
|
||||||
|
|
||||||
|
self.files.extend(files);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_version(
|
||||||
|
&mut self,
|
||||||
|
version: &str,
|
||||||
|
base_path: &Path,
|
||||||
|
source: Option<&dyn modrinth_api::ModrinthAPI>,
|
||||||
|
) -> ModpackResult<()> {
|
||||||
|
let default_api = ModrinthV1(String::from("https://api.modrinth.com"));
|
||||||
|
let source = source.unwrap_or(&default_api);
|
||||||
|
|
||||||
|
let files = source
|
||||||
|
.get_version(version)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut it: ModpackFile| {
|
||||||
|
it.path = base_path.join(it.path);
|
||||||
|
it
|
||||||
|
});
|
||||||
|
|
||||||
|
self.files.extend(files);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_file(&mut self, source: reqwest::Url, dest: &Path, hashes: Option<ModpackFileHashes>, env: Option<ModpackEnv>) -> ModpackResult<()> {
|
||||||
|
let whitelisted_domains = std::env::var(MODRINTH_MODPACK_DOMAIN_WHITELIST_VAR)
|
||||||
|
.map(|it| serde_json::from_str::<Vec<String>>(&it).ok().unwrap())
|
||||||
|
.unwrap_or(
|
||||||
|
MODRINTH_DEFAULT_MODPACK_DOMAINS
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(String::from)
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (whitelisted_domains.iter().find(String::from(source.host_str().unwrap())).is_none()) {
|
||||||
|
return Err(ModpackError::SourceWhitelistError(String::from(source.host_str().unwrap())));
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = ModpackFile {
|
||||||
|
path: dest,
|
||||||
|
hashes,
|
||||||
|
env: env.unwrap_or(ModpackEnv::Both),
|
||||||
|
downloads: HashSet::from([String::from(source)])
|
||||||
|
};
|
||||||
|
|
||||||
|
self.files.insert(file);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ModpackGame {
|
||||||
|
// TODO: Currently, the launcher does not support specifying mod loader versions, so I just
|
||||||
|
// store the loader here.
|
||||||
|
Minecraft(String, ModLoader),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ModpackFile {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub hashes: Option<ModpackFileHashes>,
|
||||||
|
pub env: ModpackEnv,
|
||||||
|
pub downloads: HashSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for ModpackFile {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.hashes.sha1.hash(state);
|
||||||
|
self.path.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModpackFile {
|
||||||
|
pub async fn fetch(&self, dest: &Path, side: ModpackSide) -> ModpackResult<()> {
|
||||||
|
if !self.env.supports(side) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = dest.join(&self.path);
|
||||||
|
|
||||||
|
// HACK: Since Daedalus appends a file name to all mirrors and the manifest supplies full
|
||||||
|
// URLs, I'm supplying it with an empty string to avoid reinventing the wheel.
|
||||||
|
let bytes = download_file_mirrors(
|
||||||
|
"",
|
||||||
|
&self
|
||||||
|
.downloads
|
||||||
|
.iter()
|
||||||
|
.map(|it| it.as_str())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.as_slice(),
|
||||||
|
Some(&self.hashes.sha1),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
fs::create_dir_all(output.parent().unwrap()).await?;
|
||||||
|
fs::write(output, bytes).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ModpackEnv {
|
||||||
|
ClientOnly,
|
||||||
|
ServerOnly,
|
||||||
|
Both,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ModpackSide {
|
||||||
|
Client,
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModpackEnv {
|
||||||
|
pub fn supports(&self, side: ModpackSide) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::ClientOnly => side == ModpackSide::Client,
|
||||||
|
Self::ServerOnly => side == ModpackSide::Server,
|
||||||
|
Self::Both => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ModpackFileHashes {
|
||||||
|
pub sha1: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::launcher::ModLoader;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn add_version() -> ModpackResult<()> {
|
||||||
|
const TEST_VERSION: &'static str = "TpnSObJ7";
|
||||||
|
let mut test_pack = Modpack::new(
|
||||||
|
ModpackGame::Minecraft(String::from("1.16.5"), ModLoader::Fabric),
|
||||||
|
"0.1.0",
|
||||||
|
"Example Modpack",
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
test_pack
|
||||||
|
.add_version(TEST_VERSION, Path::new("mods/"), None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
test_pack,
|
||||||
|
Modpack {
|
||||||
|
game: ModpackGame::Minecraft(String::from("1.16.5"), ModLoader::Fabric),
|
||||||
|
version: String::from("0.1.0"),
|
||||||
|
name: String::from("Example Modpack"),
|
||||||
|
summary: None,
|
||||||
|
files: {
|
||||||
|
let mut files = HashSet::new();
|
||||||
|
files.insert(ModpackFile {
|
||||||
|
path: PathBuf::from("mods/gravestones-v1.9.jar"),
|
||||||
|
hashes: ModpackFileHashes {
|
||||||
|
sha1: String::from("3f0f6d523d218460310b345be03ab3f1d294e04d"),
|
||||||
|
},
|
||||||
|
env: ModpackEnv::Both,
|
||||||
|
downloads: {
|
||||||
|
let mut downloads = HashSet::new();
|
||||||
|
downloads.insert(String::from("https://cdn.modrinth.com/data/ssUbhMkL/versions/v1.9/gravestones-v1.9.jar"));
|
||||||
|
downloads
|
||||||
|
}
|
||||||
|
});
|
||||||
|
files
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user