diff --git a/.cargo/windows-config.toml b/.cargo/windows-config.toml new file mode 100644 index 0000000..a1bc58d --- /dev/null +++ b/.cargo/windows-config.toml @@ -0,0 +1,6 @@ +[env] +CFLAGS = "/MT" +CXXFLAGS = "/MT" + +[target.'cfg(all(target_os = "windows", target_env = "msvc"))'] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c173ac1..9fe68eb 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,7 +29,13 @@ jobs: - uses: Swatinem/rust-cache@v2 + - name: Build (Windows) + if: ${{ contains('Windows', runner.os) }} + run: | + cargo --config .\cargo\windows-config.toml build --release + - name: Build + if: ${{ !contains('Windows', runner.os) }} run: | cargo build --release diff --git a/.gitmodules b/.gitmodules index 30a5f5b..ef0d040 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "acb/libcgss"] - path = acb/libcgss - url = https://github.com/nicks96432/libcgss.git +[submodule "acb/libacb"] + path = acb/libacb + url = https://github.com/nicks96432/libacb diff --git a/Cargo.lock b/Cargo.lock index bedf0d0..afe5221 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,13 +4,22 @@ version = 4 [[package]] name = "acb" -version = "2.0.1" +version = "2.0.1+0.4.0" dependencies = [ "cmake", "cxx", "cxx-build", ] +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.0" @@ -58,17 +67,103 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "async-compression" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -76,6 +171,21 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + [[package]] name = "base64" version = "0.22.1" @@ -90,9 +200,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" [[package]] name = "block-padding" @@ -132,9 +242,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] name = "byteorder" @@ -148,6 +258,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + [[package]] name = "cbc" version = "0.1.2" @@ -159,9 +275,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "jobserver", "libc", @@ -186,9 +302,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", "clap_derive", @@ -196,9 +312,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54381ae56ad222eea3f529c692879e9c65e07945ae48d3dc4d1cb18dbec8cf44" +checksum = "2678fade3b77aa3a8ff3aae87e9c008d3fb00473a41c71fbf74e91c8c7b37e84" dependencies = [ "clap", "log", @@ -206,25 +322,29 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ + "anstream", "anstyle", "clap_lex", "strsim", + "terminal_size", + "unicase", + "unicode-width 0.2.0", ] [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -249,19 +369,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -315,9 +441,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -334,9 +460,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -355,14 +481,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "cxx" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e1ec88093d2abd9cf1b09ffd979136b8e922bf31cad966a8fe0d73233112ef" +checksum = "ad7c7515609502d316ab9a24f67dc045132d93bfd3f00713389e90d9898bf30d" dependencies = [ "cc", "cxxbridge-cmd", @@ -374,47 +500,47 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afa390d956ee7ccb41aeed7ed7856ab3ffb4fc587e7216be7e0f83e949b4e6c" +checksum = "8bfd16fca6fd420aebbd80d643c201ee4692114a0de208b790b9cd02ceae65fb" dependencies = [ "cc", "codespan-reporting", "proc-macro2", "quote", "scratch", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "cxxbridge-cmd" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c23bfff654d6227cbc83de8e059d2f8678ede5fc3a6c5a35d5c379983cc61e6" +checksum = "6c33fd49f5d956a1b7ee5f7a9768d58580c6752838d92e39d0d56439efdedc35" dependencies = [ "clap", "codespan-reporting", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "cxxbridge-flags" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c01b36e22051bc6928a78583f1621abaaf7621561c2ada1b00f7878fbe2caa" +checksum = "be0f1077278fac36299cce8446effd19fe93a95eedb10d39265f3bf67b3036c9" [[package]] name = "cxxbridge-macro" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e14013136fac689345d17b9a6df55977251f11d333c0a571e8d963b55e1f95" +checksum = "3da7e4d6e74af6b79031d264b2f13c3ea70af1978083741c41ffce9308f1f24f" dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -425,7 +551,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -436,24 +562,33 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", ] [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "env_filter", "humantime", @@ -466,6 +601,22 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fdeflate" version = "0.3.7" @@ -485,11 +636,32 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" @@ -500,6 +672,95 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -521,6 +782,31 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -539,6 +825,46 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + [[package]] name = "human_bytes" version = "0.4.3" @@ -551,6 +877,78 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -666,7 +1064,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -721,7 +1119,6 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "rayon", "web-time", ] @@ -735,6 +1132,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "1.0.14" @@ -752,9 +1161,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -762,9 +1171,9 @@ dependencies = [ [[package]] name = "lazy-regex" -version = "3.3.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -773,14 +1182,14 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.3.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -791,9 +1200,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libflate" @@ -833,6 +1242,12 @@ dependencies = [ "serde", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "litemap" version = "0.7.4" @@ -870,97 +1285,86 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", "simd-adler32", ] [[package]] -name = "mltd" -version = "2.0.0" -dependencies = [ - "anyhow", - "clap", - "clap-verbosity-flag", - "env_logger", - "log", - "mltd-asset-download", - "mltd-asset-extract", - "mltd-asset-manifest", - "mltd-utils", - "rayon", -] - -[[package]] -name = "mltd-asset-download" -version = "0.1.0" +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "clap", - "indicatif", - "log", - "mltd-asset-manifest", - "mltd-utils", - "num_cpus", - "rayon", - "serde", - "ureq", + "libc", + "wasi", + "windows-sys 0.52.0", ] [[package]] -name = "mltd-asset-extract" -version = "0.1.0" +name = "mltd" +version = "2.0.0" dependencies = [ "acb", "aes", + "anyhow", "byteorder", "cbc", "clap", + "clap-verbosity-flag", "ctor", "env_logger", + "futures", + "human_bytes", "image", "indicatif", "lazy-regex", + "linked-hash-map", "log", - "mltd-utils", "num-derive", "num-traits", "num_cpus", + "pin-project", "rabex", + "rand", + "rand_xoshiro", "rayon", + "reqwest", + "rmp-serde", + "serde", "texture2ddecoder", + "thiserror", + "tokio", + "tokio-test", + "tokio-util", ] [[package]] -name = "mltd-asset-manifest" -version = "2.0.0" -dependencies = [ - "clap", - "ctor", - "env_logger", - "human_bytes", - "linked-hash-map", - "log", - "mltd-utils", - "rmp-serde", - "serde", - "thiserror", - "ureq", -] - -[[package]] -name = "mltd-utils" -version = "0.1.0" +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "env_logger", + "libc", "log", - "rand", - "rand_xoshiro", - "ureq", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -971,7 +1375,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1020,18 +1424,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.7.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "paste" version = "1.0.15" @@ -1044,11 +1495,49 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "png" -version = "0.17.14" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -1093,9 +1582,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1212,6 +1701,53 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "async-compression", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + [[package]] name = "ring" version = "0.17.8" @@ -1256,31 +1792,35 @@ dependencies = [ ] [[package]] -name = "rustls" -version = "0.23.19" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", + "bitflags 2.7.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", ] [[package]] -name = "rustls-native-certs" -version = "0.7.3" +name = "rustls" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ - "openssl-probe", - "rustls-pemfile", + "once_cell", "rustls-pki-types", - "schannel", - "security-framework", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] @@ -1294,9 +1834,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" [[package]] name = "rustls-webpki" @@ -1311,9 +1851,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -1342,7 +1882,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "core-foundation", "core-foundation-sys", "libc", @@ -1351,9 +1891,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -1361,29 +1901,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -1391,6 +1931,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -1416,12 +1968,31 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -1465,15 +2036,24 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -1482,7 +2062,42 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.7.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] @@ -1494,6 +2109,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "texture2ddecoder" version = "0.1.1" @@ -1505,22 +2130,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "a3ac7f54ca534db81081ef1c1e7f6ea8a3ef428d2fc069097c079443d24124d3" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "9e9465d30713b56a37ede7185763c3492a91be2f5fa68d958c44e41ab9248beb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1533,6 +2158,91 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.8" @@ -1550,6 +2260,58 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "twox-hash" version = "1.6.3" @@ -1566,6 +2328,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.14" @@ -1578,6 +2346,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -1590,25 +2364,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64", - "flate2", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "serde", - "serde_json", - "url", - "webpki-roots", -] - [[package]] name = "url" version = "2.5.4" @@ -1632,12 +2387,33 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1646,9 +2422,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -1657,24 +2433,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1682,40 +2470,54 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] -name = "web-time" -version = "1.1.0" +name = "wasm-streams" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ + "futures-util", "js-sys", "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "webpki-roots" -version = "0.26.7" +name = "web-sys" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ - "rustls-pki-types", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", ] [[package]] @@ -1727,6 +2529,36 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1850,7 +2682,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "synstructure", ] @@ -1872,7 +2704,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1892,7 +2724,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "synstructure", ] @@ -1921,5 +2753,33 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 3ebdf8e..67fbc56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,5 @@ [workspace] -members = [ - "acb", - "cli", - "download", - "extract", - "manifest", - "utils", -] +members = ["acb"] resolver = "2" [workspace.package] @@ -15,31 +8,63 @@ edition = "2021" license = "MIT" repository = "https://github.com/nicks96432/mltd-asset-downloader" -[workspace.dependencies] -ctor = "0.2.9" -linked-hash-map = "0.5.6" -log = "0.4.22" -num_cpus = "1.16.0" -rayon = "1.10.0" -serde = "1.0.215" +[package] +authors = { workspace = true } +categories = ["command-line-utilities"] +description = "A CLI made for assets in THE iDOLM@STER® MILLION LIVE! THEATER DAYS (MLTD)" +edition = { workspace = true } +homepage = "https://github.com/nicks96432/mltd-asset-downloader" +keywords = ["downloader", "MLTD", "mirishita", "theaterdays", "unpack"] +license = { workspace = true } +name = "mltd" +readme = "README.md" +repository = { workspace = true } +version = "2.0.0" -[workspace.dependencies.clap] -default-features = false -version = "4.5.23" +[dependencies] +acb = { optional = true, path = "./acb" } +aes = { optional = true, version = "0.8.4" } +anyhow = "1.0.95" +byteorder = { optional = true, version = "1.5.0" } +cbc = { optional = true, version = "0.1.2" } +clap = { features = ["color", "deprecated", "derive", "unicode", "wrap_help"], version = "4.5.26" } +clap-verbosity-flag = "3.0.2" +env_logger = { default-features = false, features = ["humantime"], version = "0.11.6" } +futures = "0.3.31" +human_bytes = { default-features = false, optional = true, version = "0.4.3" } +image = { default-features = false, features = ["png"], optional = true, version = "0.25.5" } +indicatif = { default-features = false, version = "0.17.9" } +lazy-regex = { default-features = false, optional = true, features = ["std", "perf"], version = "3.4.1" } +linked-hash-map = { features = ["serde_impl"], version = "0.5.6" } +log = "0.4.22" +num_cpus = { optional = true, version = "1.16.0" } +num-derive = { optional = true, version = "0.4.2" } +num-traits = { optional = true, version = "0.2.19" } +pin-project = "1.1.8" +rabex = { optional = true, version = "0.0.3" } +rayon = { optional = true, version = "1.10.0" } +reqwest = { features = ["deflate", "gzip", "json", "stream", "zstd"], version = "0.12.12" } +rmp-serde = "1.3.0" +serde = { features = ["derive"], version = "1.0.217" } +texture2ddecoder = { optional = true, version = "0.1.1" } +thiserror = "2.0.10" +tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread"] } +tokio-util = { version = "0.7.13", features=["compat"] } -[workspace.dependencies.env_logger] -default-features = false -features = ["humantime"] -version = "0.11.5" +[dev-dependencies] +ctor = "0.2.9" +rand = "0.8.5" +rand_xoshiro = "0.6.0" +tokio-test = "0.4.4" -[workspace.dependencies.indicatif] -default-features = false -features = ["rayon"] -version = "0.17.9" +[lib] +name = "mltd" +path = "src/mltd/lib.rs" -[workspace.dependencies.ureq] -features = ["json", "native-certs"] -version = "2.12.1" +[[bin]] +name = "mltd" +test = false +doc = false [profile.release] codegen-units = 20 @@ -49,3 +74,22 @@ strip = true [profile.dev] split-debuginfo = "unpacked" + +[features] +default = ["download", "extract"] +debug = [] +download = ["dep:human_bytes", "dep:num_cpus"] +extract = [ + "dep:acb", + "dep:aes", + "dep:byteorder", + "dep:cbc", + "dep:image", + "dep:lazy-regex", + "dep:num_cpus", + "dep:num-derive", + "dep:num-traits", + "dep:rabex", + "dep:rayon", + "dep:texture2ddecoder" +] diff --git a/README.md b/README.md index a08ebaf..0c17319 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Options: The following is required: * A rust toolchain. -* cmake >= 3.2 (for libcgss) +* cmake >= 3.21 (for libacb) * MSVC v142 or newer version. (Windows) * ... or any compiler that supports C++14. (gnu environment) @@ -47,6 +47,12 @@ The following is required: cargo build --release ``` +On windows, you should build with the following command instead: + +```shell +cargo --config .cargo/windows-config.toml build --release +``` + The executable will be in the `target/release` directory. ## Disclaimer diff --git a/README.zh-TW.md b/README.zh-TW.md index 5399f01..dbe4640 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -39,7 +39,7 @@ Commands: 你需要這些東西: * rust toolchain -* cmake >= 3.2 (libcgss要用到) +* cmake >= 3.21 (libacb要用到) * MSVC v142或者更新 (Windows) * ...或是其他支援C++14的編譯器 (gnu環境) @@ -47,6 +47,12 @@ Commands: cargo build --release ``` +Windows上要改用這個指令: + +```shell +cargo --config .cargo/windows-config.toml build --release +``` + 執行檔會出現在`target/release`資料夾裡。 ## 免責聲明 diff --git a/acb/.clang-format b/acb/.clang-format index f6d1828..8bbd6f6 100644 --- a/acb/.clang-format +++ b/acb/.clang-format @@ -24,6 +24,14 @@ DerivePointerAlignment: false FixNamespaceComments: false IndentCaseLabels: false IndentWidth: 4 +IncludeBlocks: Regroup +IncludeCategories: + - Regex: "^<[^.]+" # STL includes (no '.') + Priority: 0 + - Regex: '^"[^.\/].*' # quoted includes + Priority: 1 + - Regex: '^"\./.*' # relative includes + Priority: 2 NamespaceIndentation: All PointerAlignment: Right ReflowComments: true diff --git a/acb/Cargo.toml b/acb/Cargo.toml index 2127325..c92b2e8 100644 --- a/acb/Cargo.toml +++ b/acb/Cargo.toml @@ -6,17 +6,17 @@ edition = { workspace = true } name = "acb" license = { workspace = true } repository = { workspace = true } -version = "2.0.1" +version = "2.0.1+0.4.0" [dependencies] -cxx = "1.0.133" +cxx = { features = ["c++20"], version = "1.0.136" } [build-dependencies] cmake = "0.1.52" [build-dependencies.cxx-build] features = ["parallel"] -version = "1.0.133" +version = "1.0.136" [features] default = [] diff --git a/acb/build.rs b/acb/build.rs index 13f4cc4..2c4ff7b 100644 --- a/acb/build.rs +++ b/acb/build.rs @@ -1,28 +1,34 @@ +use std::env::var; use std::path::Path; fn main() { - let libcgss_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("libcgss"); - let dst = cmake::Config::new(libcgss_path) - .pic(true) - .uses_cxx11() - .define("LIBCGSS_BUILD_SHARED_LIBS", "OFF") - .build(); + let crt_static = var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default().contains("crt-static"); + + let libacb_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("libacb"); + let mut config = cmake::Config::new(libacb_path); + config.define("LIBACB_BUILD_SHARED_LIBS", "OFF").define( + "CMAKE_MSVC_RUNTIME_LIBRARY", + format!("MultiThreaded{}", if crt_static { "" } else { "DLL" }), + ); + + let dst = config.build(); let mut build = cxx_build::bridge("src/lib.rs"); build.file("src/acb.cc").include(dst.join("include")); - build.flag("-Wall").flag("-std=c++14"); + #[cfg(target_env = "msvc")] + build.flag("/EHsc").flag("/W4").flag("/WX"); + + #[cfg(not(target_env = "msvc"))] + build.flag("-Wall").flag("-Wextra").flag("-Werror"); #[cfg(all(target_os = "windows", target_env = "gnu"))] build.flag("-static").flag("-static-libstdc++"); - #[cfg(not(target_env = "msvc"))] - build.flag("-Wextra"); - - build.compile("acb"); + build.compile("acb-cpp"); println!("cargo:rustc-link-search=native={}", dst.join("lib").display()); - println!("cargo:rustc-link-lib=static=cgss"); + println!("cargo:rustc-link-lib=static=acb"); println!("cargo:return-if-changed={}", Path::new("src").join("acb.cc").display()); println!("cargo:return-if-changed={}", Path::new("src").join("acb.h").display()); } diff --git a/acb/libacb b/acb/libacb new file mode 160000 index 0000000..01872e6 --- /dev/null +++ b/acb/libacb @@ -0,0 +1 @@ +Subproject commit 01872e69fd5c13b0ec3b78d4a84b5b0849ae1dbd diff --git a/acb/libcgss b/acb/libcgss deleted file mode 160000 index 9bb87b9..0000000 --- a/acb/libcgss +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9bb87b9b6d1a0bfb9fb67addec4de78489bc14fb diff --git a/acb/src/acb.cc b/acb/src/acb.cc index 7c013c2..cb91bc2 100644 --- a/acb/src/acb.cc +++ b/acb/src/acb.cc @@ -1,82 +1,84 @@ -#include "acb.h" - +#include #include -#include #include #include #include -#include "cgss_enum.h" #include "ichinose/CAcbFile.h" #include "ichinose/CAcbHelper.h" #include "ichinose/CAfs2Archive.h" #include "kawashima/hca/CDefaultWaveGenerator.h" +#include "kawashima/hca/CHcaCipherConfig.h" #include "kawashima/hca/CHcaDecoder.h" +#include "kawashima/hca/CHcaDecoderConfig.h" #include "kawashima/hca/CHcaFormatReader.h" #include "takamori/exceptions/CException.h" #include "takamori/streams/CMemoryStream.h" -static rust::Vec -decode_stream(cgss::CMemoryStream *stream, const cgss::CHcaDecoderConfig &config) { - cgss::CHcaDecoder decoder{stream, config}; +#include "./acb.h" + +static auto decode_stream(acb::CMemoryStream *stream, const acb::CHcaDecoderConfig &config) + -> rust::Vec { + acb::CHcaDecoder decoder = {stream, config}; - std::array buf{}; - std::uint32_t read = 1; + std::array buf; + std::size_t read = 1; - rust::Vec decoded_data{}; + rust::Vec decoded_data; decoded_data.reserve(stream->GetLength()); do { read = decoder.Read(buf.data(), buf.size(), 0, buf.size()); - for (auto it = buf.cbegin(); it != buf.cbegin() + read; ++it) - decoded_data.push_back(*it); + std::copy(buf.cbegin(), buf.cbegin() + read, std::back_inserter(decoded_data)); } while (read > 0); return decoded_data; } -rust::Vec to_tracks(rust::Slice buf) { +auto to_tracks(rust::Slice buf) -> rust::Vec { std::vector buf_clone = {buf.begin(), buf.end()}; - cgss::CMemoryStream stream{buf_clone.data(), static_cast(buf_clone.size()), false}; - cgss::CAcbFile acb{&stream, ""}; - acb.Initialize(); + acb::CMemoryStream stream = { + buf_clone.data(), static_cast(buf_clone.size()), false + }; + acb::CAcbFile acb_file = {&stream, ""}; + acb_file.Initialize(); - const cgss::CAfs2Archive *archive; + const acb::CAfs2Archive *archive; #ifdef DEBUG - const std::uint32_t format_version = acb.GetFormatVersion(); + const std::uint32_t format_version = acb_file.GetFormatVersion(); std::cerr << "format_version:" << format_version << '\n'; #endif try { - archive = acb.GetInternalAwb(); - } catch (cgss::CException &e) { + archive = acb_file.GetInternalAwb(); + } catch (acb::CException &e) { std::cerr << e.GetExceptionMessage() << " (" << e.GetOpResult() << ")\n"; archive = nullptr; } - rust::Vec tracks{}; + rust::Vec tracks; if (archive == nullptr) return tracks; - uint16_t key_modifer = 0; - if (acb.GetFormatVersion() >= cgss::CAcbFile::KEY_MODIFIER_ENABLED_VERSION) + std::uint16_t key_modifer = 0; + if (acb_file.GetFormatVersion() >= acb::CAcbFile::KEY_MODIFIER_ENABLED_VERSION) key_modifer = archive->GetHcaKeyModifier(); - cgss::CHcaDecoderConfig decoder_config{}; + acb::CHcaDecoderConfig decoder_config{}; decoder_config.waveHeaderEnabled = TRUE; - decoder_config.decodeFunc = cgss::CDefaultWaveGenerator::Decode16BitS; - decoder_config.cipherConfig = cgss::CHcaCipherConfig{mltd_hca_key1, mltd_hca_key2, key_modifer}; + decoder_config.decodeFunc = acb::CDefaultWaveGenerator::Decode16BitS; + decoder_config.cipherConfig = acb::CHcaCipherConfig{mltd_hca_key1, mltd_hca_key2, key_modifer}; - std::unordered_set extracted_cue_ids; + std::unordered_set extracted_cue_ids; // extract files with readable cue names - for (const std::string &filename : acb.GetFileNames()) { + for (const std::string &filename : acb_file.GetFileNames()) { if (filename.empty()) continue; - cgss::CMemoryStream *entry_data_stream = - dynamic_cast(acb.OpenDataStream(filename.c_str())); + acb::CMemoryStream *entry_data_stream = + dynamic_cast(acb_file.OpenDataStream(filename.c_str())); if (entry_data_stream == nullptr) { #ifdef DEBUG std::cerr << "cue of '" << filename.c_str() << "' cannot be retrived\n"; @@ -84,10 +86,11 @@ rust::Vec to_tracks(rust::Slice buf) { continue; } - const AFS2_FILE_RECORD *file_record = acb.GetFileRecordByWaveformFileName(filename.c_str()); + const AFS2_FILE_RECORD *file_record = + acb_file.GetFileRecordByWaveformFileName(filename.c_str()); assert(file_record != nullptr); - if (!cgss::CHcaFormatReader::IsPossibleHcaStream(entry_data_stream)) { + if (!acb::CHcaFormatReader::IsPossibleHcaStream(entry_data_stream)) { #ifdef DEBUG std::cerr << "not HCA, skipping\n"; #endif @@ -106,10 +109,12 @@ rust::Vec to_tracks(rust::Slice buf) { if (extracted_cue_ids.find(record.cueId) != extracted_cue_ids.end()) continue; - const std::string &filename = acb.GetCueNameByCueId(record.cueId); + const std::string &filename = acb_file.GetCueNameByCueId(record.cueId); - cgss::CMemoryStream *entry_data_stream = cgss::CAcbHelper::ExtractToNewStream( - acb.GetStream(), record.fileOffsetAligned, static_cast(record.fileSize) + acb::CMemoryStream *entry_data_stream = acb::CAcbHelper::ExtractToNewStream( + acb_file.GetStream(), + record.fileOffsetAligned, + static_cast(record.fileSize) ); tracks.push_back(Track{filename, decode_stream(entry_data_stream, decoder_config)}); diff --git a/acb/src/acb.h b/acb/src/acb.h index 09bf151..545fcfc 100644 --- a/acb/src/acb.h +++ b/acb/src/acb.h @@ -1,16 +1,16 @@ #ifndef ACB_ACB_H_ #define ACB_ACB_H_ -#include "acb/src/lib.rs.h" -#include "rust/cxx.h" - #include #include -constexpr uint64_t mltd_hca_key = 765765765765765ULL; -constexpr uint32_t mltd_hca_key1 = mltd_hca_key & 0xffffffff; -constexpr uint32_t mltd_hca_key2 = mltd_hca_key >> 32; +#include "acb/src/lib.rs.h" +#include "rust/cxx.h" + +constexpr std::uint64_t mltd_hca_key = 765765765765765ULL; +constexpr std::uint32_t mltd_hca_key1 = mltd_hca_key & 0xffffffff; +constexpr std::uint32_t mltd_hca_key2 = mltd_hca_key >> 32; -rust::Vec to_tracks(rust::Slice buf); +auto to_tracks(rust::Slice buf) -> rust::Vec; #endif // ACB_ACB_H_ diff --git a/cli/Cargo.toml b/cli/Cargo.toml deleted file mode 100644 index 7cdc7ff..0000000 --- a/cli/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -authors = { workspace = true } -categories = ["command-line-utilities"] -description = "A CLI made for assets in THE iDOLM@STER® MILLION LIVE! THEATER DAYS (MLTD)" -edition = { workspace = true } -homepage = "https://github.com/nicks96432/mltd-asset-downloader" -keywords = ["downloader", "MLTD", "mirishita", "theaterdays", "unpack"] -license = { workspace = true } -name = "mltd" -readme = "README.md" -repository = { workspace = true } -version = "2.0.0" - -[dependencies] -anyhow = "1.0.94" -clap-verbosity-flag = "3.0.1" -env_logger = { workspace = true } -log = { workspace = true } -mltd-utils = { path = "../utils" } -rayon = { workspace = true } - -[dependencies.clap] -features = ["std", "help", "usage", "derive"] -workspace = true - -[dependencies.mltd-asset-download] -optional = true -path = "../download" - -[dependencies.mltd-asset-extract] -optional = true -path = "../extract" - -[dependencies.mltd-asset-manifest] -optional = true -path = "../manifest" - -[features] -default = ["download", "extract", "manifest"] -debug = ["mltd-asset-download/debug", "mltd-asset-extract/debug"] -download = ["dep:mltd-asset-download"] -extract = ["dep:mltd-asset-extract"] -manifest = ["dep:mltd-asset-manifest"] diff --git a/deny.toml b/deny.toml index 1daa00e..247e61f 100644 --- a/deny.toml +++ b/deny.toml @@ -22,8 +22,6 @@ allow = [ "BSD-3-Clause", "ISC", "MIT", - "MPL-2.0", - "OpenSSL", "Unicode-3.0", "Zlib", ] diff --git a/download/Cargo.toml b/download/Cargo.toml deleted file mode 100644 index f4a2fd4..0000000 --- a/download/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -authors = { workspace = true } -description = "Download assets from MLTD asset server" -edition = { workspace = true } -license = { workspace = true } -name = "mltd-asset-download" -repository = { workspace = true } -version = "0.1.0" - -[dependencies] -indicatif = { workspace = true } -log = { workspace = true } -num_cpus = { workspace = true } -rayon = { workspace = true } -ureq = { workspace = true } - -[dependencies.clap] -features = ["deprecated", "derive"] -workspace = true - -[dependencies.mltd-asset-manifest] -path = "../manifest" - -[dependencies.mltd-utils] -default-features = false -features = ["request"] -path = "../utils" - -[dependencies.serde] -features = ["derive"] -workspace = true - -[features] -debug = [] diff --git a/download/src/error.rs b/download/src/error.rs deleted file mode 100644 index 34cbbaf..0000000 --- a/download/src/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::error::Error; -use std::fmt::{Display, Formatter, Result}; -use std::io::Error as IOError; - -use mltd_asset_manifest::ManifestError; - -#[derive(Debug)] -pub enum DownloadError { - FileCreateFailed(IOError), - ManifestError(ManifestError), - ThreadPoolError, -} - -impl Display for DownloadError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - Self::FileCreateFailed(e) => write!(f, "cannot create file or directory: {}", e), - Self::ManifestError(e) => write!(f, "cannot get manifest: {}", e), - Self::ThreadPoolError => write!(f, "failed to create thread pool"), - } - } -} - -impl Error for DownloadError {} diff --git a/download/src/lib.rs b/download/src/lib.rs deleted file mode 100644 index b8d54ec..0000000 --- a/download/src/lib.rs +++ /dev/null @@ -1,163 +0,0 @@ -mod error; - -use std::fs::create_dir_all; -#[cfg(not(feature = "debug"))] -use std::fs::File; -use std::io::copy; -use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicU64, Ordering}; - -use indicatif::{ - MultiProgress, MultiProgressAlignment, ProgressBar, ProgressFinish, ProgressStyle, -}; -use mltd_asset_manifest::{Manifest, Platform}; -use mltd_utils::fetch_asset; -use rayon::prelude::{ParallelBridge, ParallelIterator}; -use rayon::{current_thread_index, ThreadPoolBuilder}; -use ureq::AgentBuilder; - -pub use self::error::*; - -#[derive(Debug, clap::Args)] -#[command(author, version, about, arg_required_else_help(true))] -pub struct DownloaderArgs { - /// The os variant to download - #[arg(value_enum, value_name = "VARIANT")] - os_variant: Platform, - - /// The output path - #[arg(short, long, value_name = "DIR", default_value_os_t = Path::new("assets").to_path_buf())] - output: PathBuf, - - /// The number of threads to use - #[arg(short = 'P', long, value_name = "CPUS", default_value_t = num_cpus::get())] - parallel: usize, -} - -pub fn download_assets(args: &DownloaderArgs) -> Result<(), DownloadError> { - log::debug!("getting latest manifest"); - let manifest = match Manifest::from_version(&args.os_variant, None) { - Ok(m) => m, - Err(e) => return Err(DownloadError::ManifestError(e)), - }; - - let output_path = args.output.join(manifest.asset_version.version.to_string()); - - log::debug!("creating output directory"); - if let Err(e) = create_dir_all(&args.output) { - return Err(DownloadError::FileCreateFailed(e)); - } - - log::debug!("creating asset directory"); - if let Err(e) = create_dir_all(&output_path) { - return Err(DownloadError::FileCreateFailed(e)); - } - - log::debug!("setting progress bar"); - - log::trace!("create MultiProgress"); - let multi_progress = MultiProgress::new(); - multi_progress.set_alignment(MultiProgressAlignment::Bottom); - multi_progress.set_move_cursor(true); - - log::trace!("create ProgressStyle"); - let template = "{msg:60} {bytes:12} {binary_bytes_per_sec:12} {eta:4} [{wide_bar:.cyan/blue}] {percent:3}%"; - let progress_bar_style = match ProgressStyle::with_template(template) { - Ok(style) => style, - Err(_) => { - log::debug!("invalid progress bar template, using default style"); - - ProgressStyle::default_bar() - } - } - .progress_chars("##-"); - - log::trace!("create ProgressBar array"); - let mut progress_bars = Vec::with_capacity(args.parallel); - for i in 0usize..args.parallel { - log::trace!("create ProgressBar {}", i); - let progress_bar = - multi_progress.add(ProgressBar::new(0).with_finish(ProgressFinish::Abandon)); - progress_bar.set_style(progress_bar_style.clone()); - progress_bars.push(progress_bar); - } - - let downloaded_count = AtomicU64::new(0); - let total_progress_bar = multi_progress.add( - ProgressBar::new(u64::try_from(manifest.len()).unwrap()) - .with_style(progress_bar_style.clone()), - ); - - log::debug!("setting the number of threads to use"); - - let thread_pool_builder = ThreadPoolBuilder::new().num_threads(args.parallel); - if thread_pool_builder.build_global().is_err() { - return Err(DownloadError::ThreadPoolError); - } - - log::debug!("building request agent"); - let agent_builder = - AgentBuilder::new().https_only(true).user_agent(args.os_variant.user_agent()); - let agent = agent_builder.build(); - - log::debug!("start downloading assets"); - let asset_url_base = - format!("/{}/production/2018/{}", manifest.asset_version.version, args.os_variant); - let iter = manifest.iter().par_bridge(); - - iter.for_each(|(filename, entry)| { - let tid = current_thread_index().unwrap_or_default(); - let progress_bar = &progress_bars[tid]; - progress_bar.reset(); - progress_bar.set_length(entry.2 as u64); - progress_bar.set_position(0); - progress_bar.set_style(progress_bar_style.clone()); - progress_bar.set_message(filename.clone()); - - let asset_url = format!("{}/{}", asset_url_base, entry.1); - let asset_res = match fetch_asset(&agent, &asset_url) { - Ok(res) => res, - Err(e) => { - multi_progress.suspend(|| log::warn!("cannot download {}: {}", filename, e)); - return; - } - }; - - let asset_path = output_path.join(filename); - - #[cfg(feature = "debug")] - let mut asset_file = Vec::new(); - - #[cfg(not(feature = "debug"))] - let mut asset_file = match File::create(&asset_path) { - Ok(f) => f, - Err(e) => { - multi_progress.suspend(|| { - log::warn!("cannot create {}: {}", asset_path.to_string_lossy(), e) - }); - return; - } - }; - - let mut writer = progress_bar.wrap_write(&mut asset_file); - if let Err(e) = copy(&mut asset_res.into_reader(), &mut writer) { - multi_progress - .suspend(|| log::warn!("cannot write to {}: {}", asset_path.to_string_lossy(), e)); - } - - let cur_dounloaded_count = downloaded_count.fetch_add(1, Ordering::AcqRel); - total_progress_bar.inc(entry.2 as u64); - total_progress_bar.set_message(format!( - "Total ({}/{})", - cur_dounloaded_count, - manifest.len() - )); - }); - - if let Err(e) = multi_progress.clear() { - log::warn!("cannot clear multi_progress, {}", e); - } - - log::info!("download complete"); - Ok(()) -} diff --git a/extract/Cargo.toml b/extract/Cargo.toml deleted file mode 100644 index c235ce3..0000000 --- a/extract/Cargo.toml +++ /dev/null @@ -1,60 +0,0 @@ -[package] -authors = { workspace = true } -description = "Extract media from MLTD assets" -edition = { workspace = true } -license = { workspace = true } -name = "mltd-asset-extract" -repository = { workspace = true } -version = "0.1.0" - -[dependencies] -# For decoding ACB audio -acb = { path = "../acb" } - -# For text asset decryption -aes = "0.8.4" -cbc = "0.1.2" - -# For reading numbers from binary data -byteorder = "1.5.0" - -indicatif = { workspace = true } -log = { workspace = true } -num-derive = "0.4.2" -num-traits = "0.2.19" -num_cpus = { workspace = true } - -# For unpacking unity assets -rabex = "0.0.3" - -rayon = { workspace = true } - -# For decoding Texture2D -texture2ddecoder = "0.1.1" - -[dependencies.clap] -features = ["deprecated", "derive"] -workspace = true - -# For image encoding -[dependencies.image] -features = ["png"] -default-features = false -version = "0.25.5" - -# For regex matching in texture name -[dependencies.lazy-regex] -default-features = false -features = ["std", "perf"] -version = "3.3.0" - -[dev-dependencies] -ctor = { workspace = true } -env_logger = { workspace = true } - -[dev-dependencies.mltd-utils] -features = ["log", "rand"] -path = "../utils" - -[features] -debug = [] diff --git a/manifest/Cargo.toml b/manifest/Cargo.toml deleted file mode 100644 index dde7990..0000000 --- a/manifest/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -authors = { workspace = true } -description = "MLTD asset manifest related tools" -edition = { workspace = true } -license = { workspace = true } -name = "mltd-asset-manifest" -repository = { workspace = true } -version = "2.0.0" - -[dependencies] -log = { workspace = true } -rmp-serde = "1.3.0" -thiserror = "2.0.4" -ureq = { workspace = true } - -[dependencies.clap] -features = ["std", "derive"] -workspace = true - -[dependencies.human_bytes] -default-features = false -version = "0.4.3" - -[dependencies.linked-hash-map] -features = ["serde_impl"] -workspace = true - -[dependencies.mltd-utils] -features = ["log", "request"] -path = "../utils" - -[dependencies.serde] -features = ["derive"] -workspace = true - -[dev-dependencies] -ctor = { workspace = true } -env_logger = { workspace = true } diff --git a/manifest/README.md b/manifest/README.md deleted file mode 100644 index 161e8d8..0000000 --- a/manifest/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# MLTD asset manifest tool - -This crate contains MLTD asset manifest downloading functions and other tools related to -manifest and versions. - -## Example - -Download latest Android manifest: - -```rust -use std::path::PathBuf; - -use mltd_asset_manifest::{Manifest, Platform}; - -let manifest = Manifest::from_version(&Platform::Android, None).unwrap(); -manifest.save(&PathBuf::from(&manifest.asset_version.filename)).unwrap(); -``` diff --git a/manifest/src/error.rs b/manifest/src/error.rs deleted file mode 100644 index 88375a3..0000000 --- a/manifest/src/error.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Error type definitions. - -use std::io; - -use thiserror::Error; - -/// Error type for manifest operations. -#[derive(Error, Debug)] -pub enum ManifestError { - #[error("manifest deserialization failed: {0}")] - ManifestDeserialize(#[from] rmp_serde::decode::Error), - - #[error("manifest serialization failed: {0}")] - ManifestSerialize(#[from] rmp_serde::encode::Error), - - #[error("response deserialization failed: {0}")] - ResponseDeserialize(io::Error), - - #[error("cannot create output file: {0}")] - FileCreate(io::Error), - - #[error("cannot write output file: {0}")] - FileWrite(io::Error), - - #[error("fail to send request: {0}")] - Request(#[from] Box), - - #[error("unknown os variant: {0}")] - UnknownVariant(String), -} diff --git a/manifest/src/lib.rs b/manifest/src/lib.rs deleted file mode 100644 index 3de0d0d..0000000 --- a/manifest/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![forbid(unsafe_code)] -#![warn(clippy::print_stderr)] -#![warn(clippy::print_stdout)] - -//! This crate provides functions for downloading and parsing -//! MLTD asset manifests. - -mod error; -mod manifest; -mod matsuri_api; - -pub use self::error::*; -pub use self::manifest::*; -pub use self::matsuri_api::*; diff --git a/manifest/src/manifest.rs b/manifest/src/manifest.rs deleted file mode 100644 index b80090d..0000000 --- a/manifest/src/manifest.rs +++ /dev/null @@ -1,343 +0,0 @@ -//! Manifest file handling. - -use std::collections::HashMap; -use std::fmt::{Display, Formatter}; -use std::fs::File; -use std::io::{Cursor, Write}; -use std::ops::Deref; -use std::path::PathBuf; -use std::str::FromStr; - -use clap::ValueEnum; -use human_bytes::human_bytes; -use linked_hash_map::LinkedHashMap; -use mltd_utils::{fetch_asset, trace_response}; -use serde::{Deserialize, Serialize}; -use ureq::AgentBuilder; - -use super::error::ManifestError; -use super::matsuri_api::{get_asset_version, latest_asset_version, AssetVersion}; - -/// An entry in the manifest file. -/// -/// It contains the SHA1 hash of the file, the file name on the server and the -/// file size. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ManifestEntry(pub String, pub String, pub usize); - -/// Deserialized raw manifest. -/// -/// It contains a dictionary of the manifest entries, where the key is the actual -/// file name. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[repr(transparent)] -pub struct RawManifest([LinkedHashMap; 1]); - -impl RawManifest { - /// Deserializes the specified bytes into a raw manifest. - /// - /// The bytes must be in message pack format. - /// - /// # Arguments - /// - /// * `value` - The message pack bytes to deserialize. - /// - /// # Errors - /// - /// This function will return [`ManifestError::ManifestDeserialize`] if - /// it cannot deserialize the message pack bytes. - #[inline] - pub fn from_slice(value: &[u8]) -> Result { - Ok(rmp_serde::from_slice(value)?) - } - - /// Computes the difference between two manifests. - /// - /// # Arguments - /// - /// * `other` - The other manifest. - /// - /// # Returns - /// - /// The updated and removed entries in the new manifest. - pub fn diff<'a>(&'a self, other: &'a RawManifest) -> ManifestDiff<'a> { - let mut diff = ManifestDiff::new(); - - for (key, value) in other.iter() { - if !self.contains_key(key) { - diff.added.insert(key, value); - } else if self[key].0 != value.0 || self[key].2 != value.2 { - // although the hash and file size are the same, the hashed - // file name may be different across different versions - // for some unknown reason (maybe they hashed full path?) - diff.updated.insert(key, value); - } - } - - for (key, value) in self.iter() { - if !other.contains_key(key) { - diff.removed.insert(key, value); - } - } - - diff - } -} - -impl Deref for RawManifest { - type Target = LinkedHashMap; - - fn deref(&self) -> &Self::Target { - &self.0[0] - } -} - -#[derive(Debug, Serialize)] -pub struct ManifestDiff<'a> { - pub added: HashMap<&'a String, &'a ManifestEntry>, - pub updated: HashMap<&'a String, &'a ManifestEntry>, - pub removed: HashMap<&'a String, &'a ManifestEntry>, -} - -impl ManifestDiff<'_> { - fn new() -> Self { - Self { added: HashMap::new(), updated: HashMap::new(), removed: HashMap::new() } - } -} - -/// A manifest file. -#[derive(Debug, Clone, Serialize)] -#[serde(into = "RawManifest")] -pub struct Manifest { - /// The underlying raw manifest data. - pub data: RawManifest, - - /// The asset version of the manifest, including the version and filename. - #[serde(skip)] - pub asset_version: AssetVersion, - - /// The platform variant of the manifest. - #[serde(skip)] - pub platform: Platform, -} - -impl Manifest { - /// Returns the number of entries in the manifest. - #[inline] - pub fn len(&self) -> usize { - self.data.0[0].len() - } - - /// Returns `true` if the manifest is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Downloads the specified manifest from MLTD asset server. - /// - /// # Arguments - /// - /// * `variant` - OS variant - /// * `version` - manifest version, if not specified, the latest version will be downloaded - /// - /// # Errors - /// - /// This function will return [`ManifestError::Request`] if - /// it cannot send request to MLTD asset server. - /// - /// This function will return [`ManifestError::ManifestDeserialize`] if - /// it cannot deserialize response. - /// - /// # Examples - /// - /// Download the manifest version 1 for Android: - /// - /// ```no_run - /// use mltd_asset_manifest::{Manifest, Platform}; - /// - /// let manifest = Manifest::from_version(&Platform::Android, Some(1)).unwrap(); - /// assert_eq!(manifest.platform, Platform::Android); - /// assert_eq!(manifest.asset_version.version, 1); - /// ``` - pub fn from_version(platform: &Platform, version: Option) -> Result { - log::debug!("getting latest version from matsurihi.me"); - let asset_version = match version { - None => latest_asset_version(), - Some(v) => get_asset_version(v), - }?; - - let agent_builder = AgentBuilder::new().https_only(true).user_agent(platform.user_agent()); - let agent = agent_builder.build(); - - let asset_url_base = format!("/{}/production/2018/{}", asset_version.version, platform); - - log::debug!("reading manifest from MLTD asset server"); - let manifest_url = format!("{}/{}", asset_url_base, asset_version.filename); - let manifest_res = match fetch_asset(&agent, &manifest_url) { - Ok(r) => r, - Err(e) => return Err(ManifestError::Request(e)), - }; - trace_response(&manifest_res); - - let mut buf = Vec::new(); - if let Err(e) = manifest_res.into_reader().read_to_end(&mut buf) { - log::warn!("cannot read response body: {}", e); - log::warn!("manifest may not be complete!"); - } - - log::debug!("reading response body to buf"); - let mut reader = Cursor::new(&buf); - - let manifest = Manifest { - data: rmp_serde::from_read::<_, RawManifest>(&mut reader)?, - asset_version, - platform: *platform, - }; - - log::info!( - "downloaded manifest version {} (updated at {}), manifest file {}, total asset size {}", - manifest.asset_version.version, - manifest.asset_version.updated_at, - manifest.asset_version.filename, - human_bytes(manifest.asset_size() as f64) - ); - - Ok(manifest) - } - - /// Returns the total size of all assets in the manifest. - #[inline] - pub fn asset_size(&self) -> usize { - self.data.0[0].values().fold(0, |acc, v| acc + v.2) - } - - /// Save the manifest to the specified path. - /// - /// # Arguments - /// - /// * `path` - The path to save the manifest file. - /// - /// # Errors - /// - /// This function will return [`ManifestError::FileCreate`] if - /// it cannot create the file. - /// - /// This function will return [`ManifestError::FileWrite`] if - /// it cannot write to the file. - pub fn save(&self, path: &PathBuf) -> Result<(), ManifestError> { - let mut file = match File::create(path) { - Ok(f) => f, - Err(e) => return Err(ManifestError::FileCreate(e)), - }; - - match file.write_all(&rmp_serde::to_vec(&self.data)?) { - Ok(()) => Ok(()), - Err(e) => Err(ManifestError::FileWrite(e)), - } - } -} - -impl Deref for Manifest { - type Target = RawManifest; - - fn deref(&self) -> &Self::Target { - &self.data - } -} - -impl From for RawManifest { - fn from(value: Manifest) -> Self { - value.data - } -} - -/// Platform variant of the manifest. -/// -/// Although the game and the manifest name looks the same on both platforms, -/// their manifests are different. -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] -pub enum Platform { - Android, - IOS, -} - -impl Platform { - /// Returns the string representation of the [`Platform`]. - pub fn as_str(&self) -> &str { - match self { - Self::Android => "Android", - Self::IOS => "iOS", - } - } - - /// Returns the `User-Agent` string of the [`Platform`] in HTTP request. - pub fn user_agent(&self) -> &str { - match self { - Self::Android => "UnityPlayer/2020.3.32f1 (UnityWebRequest/1.0, libcurl/7.80.0-DEV)", - Self::IOS => "ProductName/5.2.000 CFNetwork/1333.0.4 Darwin/21.5.0", - } - } -} - -impl Display for Platform { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{}", self.as_str()) - } -} - -impl FromStr for Platform { - type Err = ManifestError; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "android" => Ok(Self::Android), - "ios" => Ok(Self::IOS), - s => Err(ManifestError::UnknownVariant(s.to_string())), - } - } -} - -#[cfg(test)] -#[ctor::ctor] -fn init() { - mltd_utils::init_test_logger!(); -} - -#[cfg(test)] -mod tests { - use super::{Manifest, Platform, RawManifest}; - - #[test] - fn test_raw_manifest_from_slice() { - let buf = include_bytes!("../tests/test1.msgpack"); - let manifest = RawManifest::from_slice(buf).unwrap(); - assert_eq!(rmp_serde::to_vec(&manifest).unwrap(), buf); - } - - #[test] - fn test_raw_manifest_diff() { - let manifest1 = RawManifest::from_slice(include_bytes!("../tests/test1.msgpack")).unwrap(); - let manifest2 = RawManifest::from_slice(include_bytes!("../tests/test2.msgpack")).unwrap(); - - let diff = manifest1.diff(&manifest2); - assert_eq!(diff.added.len(), 8); - assert_eq!(diff.updated.len(), 6); - assert_eq!(diff.removed.len(), 0); - } - - #[test] - fn test_manifest_from_version() { - let manifest = Manifest::from_version(&Platform::Android, None).unwrap(); - - assert_eq!(manifest.platform, Platform::Android); - assert!(manifest.asset_version.version > 0); - assert!(!manifest.asset_version.filename.is_empty()); - assert!(manifest.asset_size() > 0); - - assert_eq!( - rmp_serde::to_vec(&manifest).unwrap(), - rmp_serde::to_vec(&RawManifest::from(manifest)).unwrap() - ); - } -} diff --git a/manifest/src/matsuri_api.rs b/manifest/src/matsuri_api.rs deleted file mode 100644 index 9b6a83c..0000000 --- a/manifest/src/matsuri_api.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Functions to fetch manifest version from `api.matsurihi.me`. - -use mltd_utils::{trace_request, trace_response}; -use serde::Deserialize; - -use crate::ManifestError; - -/// matshrihi.me MLTD v2 API `/version/assets/:app` response body structure. -#[derive(Debug, Clone, Deserialize)] -pub struct AppVersion { - pub version: String, - - #[serde(rename = "updatedAt")] - pub updated_at: String, - pub revision: u64, -} - -/// matshrihi.me MLTD v2 API `/version/assets/:version` response body structure. -#[derive(Debug, Clone, Deserialize)] -pub struct AssetVersion { - #[serde(rename = "indexName")] - pub filename: String, - - #[serde(rename = "updatedAt")] - pub updated_at: String, - pub version: u64, -} - -/// matshrihi.me MLTD v2 API `/version/latest` response body structure. -#[derive(Debug, Deserialize)] -pub struct VersionInfo { - #[serde(rename = "app")] - pub app_version: AppVersion, - - #[serde(rename = "asset")] - pub asset_version: AssetVersion, -} - -const MATSURI_API_ENDPOINT: &str = "https://api.matsurihi.me/api/mltd/v2"; - -/// Gets the latest manifest filename and version from matsurihi.me. -/// -/// # Returns -/// -/// Returns a tuple of manifest filename and version. -/// -/// # Errors -/// -/// [`ManifestError::Request`]: if it cannot send request to `api.matsurihi.me`. -/// -/// [`ManifestError::ResponseDeserialize`]: if it cannot deserialize response. -/// -/// # Examples -/// -/// ```no_run -/// use mltd_asset_manifest::latest_asset_version; -/// -/// let asset_version = latest_asset_version().unwrap().version; -/// ``` -pub fn latest_asset_version() -> Result { - let url = &format!("{}{}", MATSURI_API_ENDPOINT, "/version/latest"); - let req = ureq::get(url).query("prettyPrint", "false"); - trace_request(&req); - - let res = match req.call() { - Ok(r) => r, - Err(e) => return Err(ManifestError::Request(Box::new(e))), - }; - log::trace!(""); - trace_response(&res); - - let version_info = match res.into_json::() { - Ok(info) => info, - Err(e) => return Err(ManifestError::ResponseDeserialize(e)), - }; - - Ok(version_info.asset_version) -} - -/// Gets all asset versions from `api.matsurihi.me`. -/// -/// # Returns -/// -/// Returns a vector of asset versions. -/// -/// # Errors -/// -/// [`ManifestError::Request`]: if it cannot send request to `api.matsurihi.me`. -/// -/// [`ManifestError::ResponseDeserialize`]: if it cannot deserialize response. -pub fn get_all_asset_versions() -> Result, ManifestError> { - let url = &format!("{}{}", MATSURI_API_ENDPOINT, "/version/assets"); - let req = ureq::get(url).query("prettyPrint", "false"); - trace_request(&req); - - let res = match req.call() { - Ok(r) => r, - Err(e) => return Err(ManifestError::Request(Box::new(e))), - }; - log::trace!(""); - trace_response(&res); - - let versions = match res.into_json::>() { - Ok(v) => v, - Err(e) => return Err(ManifestError::ResponseDeserialize(e)), - }; - - Ok(versions) -} - -/// Gets the specified asset version from `api.matsurihi.me`. -/// -/// # Returns -/// -/// Returns the specified asset version. -/// -/// # Errors -/// -/// [`ManifestError::Request`]: if it cannot send request to `api.matsurihi.me`. -/// -/// [`ManifestError::ResponseDeserialize`]: if it cannot deserialize response. -/// -/// # Examples -/// -/// ```no_run -/// use mltd_asset_manifest::get_asset_version; -/// -/// let asset_version = get_asset_version(1).unwrap(); -/// assert_eq!(asset_version.version, 1); -/// ``` -pub fn get_asset_version(version: u64) -> Result { - let url = &format!("{}/version/assets/{}", MATSURI_API_ENDPOINT, version); - let req = ureq::get(url).query("prettyPrint", "false"); - trace_request(&req); - - let res = match req.call() { - Ok(r) => r, - Err(e) => return Err(ManifestError::Request(Box::new(e))), - }; - log::trace!(""); - trace_response(&res); - - let asset_version = match res.into_json::() { - Ok(v) => v, - Err(e) => return Err(ManifestError::ResponseDeserialize(e)), - }; - - Ok(asset_version) -} - -#[cfg(test)] -#[ctor::ctor] -fn init() { - mltd_utils::init_test_logger!(); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_get_asset_version() { - let version = get_asset_version(1).unwrap(); - - assert_eq!(version.version, 1); - assert_eq!(version.updated_at, "2017-06-29T12:00:00+09:00"); - assert_eq!(version.filename, "6b976a4c875a1984592a66b621872ce44c944e72.data"); - } - - #[test] - fn test_latest_asset_version() { - let version = latest_asset_version().unwrap(); - - assert!(version.version > 0); - assert!(!version.filename.is_empty()); - } - - #[test] - fn test_all_asset_versions() { - let versions = get_all_asset_versions().unwrap(); - - assert_ne!(versions.len(), 0); - } -} diff --git a/src/bin/mltd/download.rs b/src/bin/mltd/download.rs new file mode 100644 index 0000000..8453b4c --- /dev/null +++ b/src/bin/mltd/download.rs @@ -0,0 +1,160 @@ +use std::path::{Path, PathBuf}; + +use clap::Args; +use futures::{stream, StreamExt}; +use human_bytes::human_bytes; +use indicatif::{MultiProgress, MultiProgressAlignment, ProgressDrawTarget}; +use mltd::asset::{Asset, AssetInfo, Platform}; +use mltd::manifest::Manifest; +use mltd::net::AssetVersion; +use mltd::{net, Error}; +use tokio::fs::create_dir_all; + +use crate::util::create_progress_bar; + +#[derive(Debug, Args)] +#[command(about, arg_required_else_help = true, disable_help_flag = true)] +pub struct DownloaderArgs { + /// The platform variant to download + #[arg(value_enum, value_name = "Platform")] + platform: Platform, + + /// The asset version to download, defaults to the latest + #[arg(long, value_name = "VERSION")] + asset_version: Option, + + /// The output path + #[arg(short, long, value_name = "DIR", default_value_os_t = PathBuf::from("assets"))] + output_dir: PathBuf, + + /// The number of parallel downloads + #[arg(short = 'P', long, value_name = "PARALLEL", default_value_t = num_cpus::get())] + parallel: usize, + + /// The manifest file list to download in MessagePack format + #[arg(long, value_name = "MANIFEST", requires = "asset_version")] + manifest: Option, + + /// Print help + #[arg(long, action = clap::ArgAction::HelpShort)] + help: Option, +} + +async fn download_manifest( + version: Option, + platform: Platform, +) -> Result<(Manifest, AssetInfo), Error> { + let asset_version = match version { + Some(v) => net::get_asset_version(v).await, + None => net::latest_asset_version().await, + }?; + + let manifest_info = AssetInfo { + filename: asset_version.manifest_filename.clone(), + platform, + version: asset_version, + }; + + let asset = Asset::download(manifest_info.clone(), None).await?; + let manifest: Manifest = asset.try_into()?; + + log::info!( + "downloaded manifest version {} (updated at {}), manifest file {}, total asset size {}", + manifest_info.version.version, + manifest_info.version.updated_at, + manifest_info.version.manifest_filename, + human_bytes(manifest.asset_size() as f64) + ); + + Ok((manifest, manifest_info)) +} + +async fn download_task( + asset_info: AssetInfo, + output_path: impl AsRef, + multi_progress: MultiProgress, +) -> Result<(), Error> { + let file_name = output_path.as_ref().file_name().unwrap().to_str().unwrap().to_owned(); + let mut progress_bar = + multi_progress.insert_from_back(1, create_progress_bar().with_message(file_name)); + + Asset::download_to_file(&asset_info, Some(output_path.as_ref()), Some(&mut progress_bar)) + .await?; + + multi_progress.remove(&progress_bar); + multi_progress.insert(0, progress_bar); + + Ok(()) +} + +pub async fn download_assets(args: &DownloaderArgs) -> Result<(), Error> { + log::debug!("create output directory at {}", args.output_dir.display()); + if let Err(e) = create_dir_all(&args.output_dir).await { + return Err(Error::FileCreate(e)); + } + + let (manifest, manifest_info) = match &args.manifest { + Some(path) => { + let buf = tokio::fs::read(path).await.map_err(Error::FileRead)?; + let manifest: Manifest = Manifest::from_slice(&buf)?; + drop(buf); + + let filename = path.to_string_lossy().to_string(); + let manifest_info = AssetInfo { + filename: filename.clone(), + platform: args.platform, + version: AssetVersion { + manifest_filename: filename, + updated_at: String::new(), + version: args.asset_version.unwrap(), + }, + }; + + (manifest, manifest_info) + } + None => download_manifest(args.asset_version, args.platform).await?, + }; + + log::trace!("create MultiProgress"); + let multi_progress = MultiProgress::with_draw_target(ProgressDrawTarget::stdout_with_hz(5)); + multi_progress.set_alignment(MultiProgressAlignment::Bottom); + multi_progress.set_move_cursor(true); + + let main_progress_bar = multi_progress.add(create_progress_bar().with_message("total ")); + main_progress_bar.set_length(manifest.asset_size() as u64); + main_progress_bar.tick(); + + log::debug!("start downloading assets"); + let tasks = + stream::iter(&*manifest).for_each_concurrent(Some(args.parallel), |(name, entry)| { + let main_progress_bar = main_progress_bar.clone(); + let multi_progress = multi_progress.clone(); + + let asset_info = AssetInfo { filename: entry.1.clone(), ..manifest_info.clone() }; + let output_path = args.output_dir.join(name); + + async move { + let result = + tokio::task::spawn(download_task(asset_info, output_path, multi_progress)) + .await; + if let Err(e) = &result { + log::error!("failed to download {}: {}", entry.1, e); + } + if let Err(e) = result.unwrap() { + log::warn!("failed to download {}: {}", entry.1, e); + } + + main_progress_bar.inc(entry.2 as u64); + main_progress_bar.tick(); + } + }); + + tasks.await; + + if let Err(e) = multi_progress.clear() { + log::debug!("cannot clear multi_progress, {}", e); + } + + log::info!("download complete"); + Ok(()) +} diff --git a/cli/src/main.rs b/src/bin/mltd/main.rs similarity index 54% rename from cli/src/main.rs rename to src/bin/mltd/main.rs index 6f74fe0..585a2cc 100644 --- a/cli/src/main.rs +++ b/src/bin/mltd/main.rs @@ -1,19 +1,16 @@ +#[cfg(feature = "download")] +mod download; + mod manifest; +mod util; use anyhow::Result; use clap::{Parser, Subcommand}; use clap_verbosity_flag::{InfoLevel, Verbosity}; -#[cfg(feature = "download")] -use mltd_asset_download::*; -#[cfg(feature = "extract")] -use mltd_asset_extract::*; -use mltd_utils::log_formatter; - -#[cfg(feature = "manifest")] -use crate::manifest::*; +use mltd::util::log_formatter; #[derive(Parser)] -#[command(author, version, about, arg_required_else_help(true))] +#[command(author, version, about, arg_required_else_help = true)] struct Cli { #[command(subcommand)] pub command: Command, @@ -24,44 +21,39 @@ struct Cli { #[derive(Subcommand)] enum Command { - #[cfg(feature = "download")] /// Download assets from MLTD asset server - Download(DownloaderArgs), + #[cfg(feature = "download")] + Download(self::download::DownloaderArgs), - #[cfg(feature = "extract")] /// Extract media from MLTD assets - Extract(ExtractorArgs), + #[cfg(feature = "extract")] + Extract(mltd::extract::ExtractorArgs), - #[cfg(feature = "manifest")] /// Download manifest from MLTD asset server - Manifest(ManifestArgs), + Manifest(self::manifest::ManifestArgs), } -fn main() -> Result<()> { +#[tokio::main] +async fn main() -> Result<()> { let args = Cli::parse(); env_logger::Builder::new() - .filter_level(args.verbose.log_level_filter()) + .filter_module(env!("CARGO_PKG_NAME"), args.verbose.log_level_filter()) .format(log_formatter) .init(); match args.command { #[cfg(feature = "download")] - Command::Download(d) => { - if let Err(e) = download_assets(&d) { - log::error!("asset download failed: {}", e); - } - } + Command::Download(d) => self::download::download_assets(&d).await?, #[cfg(feature = "extract")] Command::Extract(e) => { - if let Err(e) = extract_media(&e) { + if let Err(e) = mltd::extract::extract_media(&e) { log::error!("asset extract failed: {}", e); } } - #[cfg(feature = "manifest")] - Command::Manifest(m) => manifest_main(&m)?, + Command::Manifest(m) => self::manifest::manifest_main(&m).await?, } Ok(()) diff --git a/cli/src/manifest.rs b/src/bin/mltd/manifest.rs similarity index 63% rename from cli/src/manifest.rs rename to src/bin/mltd/manifest.rs index 2377203..b06ee67 100644 --- a/cli/src/manifest.rs +++ b/src/bin/mltd/manifest.rs @@ -1,14 +1,16 @@ -#![cfg(feature = "manifest")] - use std::fs::read; use std::path::PathBuf; use anyhow::Result; use clap::{Args, Subcommand}; -use mltd_asset_manifest::{get_all_asset_versions, Manifest, Platform, RawManifest}; +use mltd::asset::{Asset, AssetInfo, Platform}; +use mltd::manifest::Manifest; +use mltd::net::{get_all_asset_versions, get_asset_version, latest_asset_version}; + +use crate::util::create_progress_bar; #[derive(Args)] -#[command(author, version, about, arg_required_else_help(true))] +#[command(about, arg_required_else_help(true))] pub struct ManifestArgs { #[command(subcommand)] pub command: ManifestCommand, @@ -52,21 +54,28 @@ pub struct ManifestDownloadArgs { pub output: Option, } -pub fn download_manifest(args: &ManifestDownloadArgs) -> Result<()> { - let manifest = Manifest::from_version(&args.platform, args.asset_version)?; +pub async fn download_manifest(args: &ManifestDownloadArgs) -> Result<()> { + let asset_version = match args.asset_version { + None => latest_asset_version().await, + Some(v) => get_asset_version(v).await, + }?; - let output = match &args.output { - Some(output) => output, - None => &PathBuf::from(&manifest.asset_version.filename), + let asset_info = AssetInfo { + filename: asset_version.manifest_filename.clone(), + platform: args.platform, + version: asset_version, }; - manifest.save(output)?; + + let mut progress_bar = create_progress_bar().with_message(asset_info.filename.clone()); + + Asset::download_to_file(&asset_info, args.output.as_deref(), Some(&mut progress_bar)).await?; Ok(()) } pub fn diff_manifest(args: &ManifestDiffArgs) -> Result<()> { - let first_manifest = RawManifest::from_slice(&read(&args.first)?)?; - let second_manifest = RawManifest::from_slice(&read(&args.second)?)?; + let first_manifest = Manifest::from_slice(&read(&args.first)?)?; + let second_manifest = Manifest::from_slice(&read(&args.second)?)?; let diff = first_manifest.diff(&second_manifest); @@ -85,23 +94,23 @@ pub fn diff_manifest(args: &ManifestDiffArgs) -> Result<()> { Ok(()) } -pub fn list_manifests() -> Result<()> { - let versions = get_all_asset_versions()?; +pub async fn list_manifests() -> Result<()> { + let versions = get_all_asset_versions().await?; for version in versions { println!( "version {:>7}: filename {}, updated at {}", - version.version, version.filename, version.updated_at + version.version, version.manifest_filename, version.updated_at ); } Ok(()) } -pub fn manifest_main(args: &ManifestArgs) -> Result<()> { +pub async fn manifest_main(args: &ManifestArgs) -> Result<()> { match &args.command { ManifestCommand::Diff(args) => diff_manifest(args), - ManifestCommand::Download(args) => download_manifest(args), - ManifestCommand::List => list_manifests(), + ManifestCommand::Download(args) => download_manifest(args).await, + ManifestCommand::List => list_manifests().await, } } diff --git a/src/bin/mltd/util.rs b/src/bin/mltd/util.rs new file mode 100644 index 0000000..473d4ba --- /dev/null +++ b/src/bin/mltd/util.rs @@ -0,0 +1,9 @@ +use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; + +pub fn create_progress_bar() -> ProgressBar { + let template = "{msg:<60} {bytes:>12} {binary_bytes_per_sec:>12} {eta:>3} [{wide_bar:.cyan/blue}] {percent:>3}%"; + + ProgressBar::new(0) + .with_finish(ProgressFinish::Abandon) + .with_style(ProgressStyle::with_template(template).unwrap().progress_chars("##-")) +} diff --git a/src/mltd/asset.rs b/src/mltd/asset.rs new file mode 100644 index 0000000..add93f1 --- /dev/null +++ b/src/mltd/asset.rs @@ -0,0 +1,241 @@ +//! MLTD asset handling. + +use std::borrow::Cow; +use std::fmt::{Display, Formatter}; +use std::io; +use std::path::Path; +use std::str::FromStr; + +use clap::ValueEnum; +use futures::TryStreamExt; +use indicatif::ProgressBar; +use reqwest::header::HeaderMap; +use tokio::fs::File; +use tokio::io::BufWriter; +use tokio_util::compat::FuturesAsyncReadCompatExt; + +use crate::net::AssetVersion; +use crate::util::ProgressReadAdapter; +use crate::Error; + +/// Base URL of MLTD asset server. +pub const ASSET_URL_BASE: &str = "https://td-assets.bn765.com"; + +/// Unity version of MLTD game client. +pub const UNITY_VERSION: &str = "2020.3.32f1"; + +/// Information of an MLTD asset. +#[derive(Debug, Clone)] +pub struct AssetInfo { + /// Asset filename + pub filename: String, + + /// Platform variant + pub platform: Platform, + + /// Asset version + pub version: AssetVersion, +} + +impl AssetInfo { + fn to_url(&self) -> reqwest::Url { + reqwest::Url::parse(&format!( + "{}/{}/production/2018/{}/{}", + ASSET_URL_BASE, self.version.version, self.platform, self.filename + )) + .unwrap() + } +} + +/// An asset from MLTD asset server. +pub struct Asset<'data> { + /// Asset data + pub data: Cow<'data, [u8]>, + + /// Asset info + pub info: AssetInfo, +} + +impl Asset<'_> { + /// Send download request to MLTD asset server and returns the response. + /// + /// This will send GET request to MLTD asset server according to `asset_info`, + /// with the headers set in the game client. + /// + /// # Errors + /// + /// - [`Error::Request`]: if it cannot send request to MLTD asset server. + async fn send_request(asset_info: &AssetInfo) -> Result { + let client = reqwest::Client::new(); + + let mut headers = HeaderMap::new(); + headers.insert("X-Unity-Version", UNITY_VERSION.parse().unwrap()); + headers.insert("User-Agent", asset_info.platform.user_agent().parse().unwrap()); + + let req = client.get(asset_info.to_url()).headers(headers); + + req.send().await.map_err(Error::Request) + } + + /// Download the specified asset from MLTD asset server. + /// + /// This will send GET request to MLTD asset server according to `asset_info`. + /// An optional `progress_bar` can be specified to track the download progress. + /// + /// # Errors + /// + /// - [`Error::FileWrite`]: if it cannot write the downloaded data to file. + /// - [`Error::Request`]: if it cannot send request to MLTD asset server. + /// + /// # Example + /// + /// ```no_run + /// use mltd::asset::{Asset, AssetInfo, Platform}; + /// use mltd::net; + /// + /// tokio_test::block_on(async { + /// let asset_version = net::latest_asset_version().await.unwrap(); + /// let asset_info = AssetInfo { + /// filename: asset_version.manifest_filename.clone(), + /// platform: Platform::Android, + /// version: asset_version, + /// }; + /// + /// let asset = Asset::download(asset_info, None).await.unwrap(); + /// println!("asset size: {}", asset.data.len()); + /// }); + /// ``` + pub async fn download( + asset_info: AssetInfo, + progress_bar: Option<&mut ProgressBar>, + ) -> Result { + let res = Self::send_request(&asset_info).await?; + + if let Some(ref pb) = progress_bar { + pb.set_length(res.content_length().unwrap_or(0)); + } + + log::debug!("download {} to buf", asset_info.filename); + + let stream_reader = res + .bytes_stream() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + .into_async_read() + .compat(); + + let mut stream_reader = ProgressReadAdapter::new(stream_reader, progress_bar); + + let mut buf = BufWriter::new(Vec::new()); + tokio::io::copy(&mut stream_reader, &mut buf).await.map_err(Error::FileWrite)?; + + Ok(Self { data: Cow::Owned(buf.into_inner()), info: asset_info }) + } + + /// Download the specified asset from MLTD asset server and write it to disk. + /// + /// If `output` is not specified, it will be default to `asset_info.filename`. + /// An optional `progress_bar` can be specified to track the download progress. + /// + /// # Errors + /// + /// - [`Error::FileCreate`]: if it cannot create the output file. + /// - [`Error::FileWrite`]: if it cannot write the downloaded data to file. + /// - [`Error::Request`]: if it cannot send request to MLTD asset server. + /// + /// # Example + /// + /// ```no_run + /// use std::path::Path; + /// + /// use mltd::asset::{Asset, AssetInfo, Platform}; + /// use mltd::net; + /// + /// tokio_test::block_on(async { + /// let asset_version = net::latest_asset_version().await.unwrap(); + /// let asset_info = AssetInfo { + /// filename: asset_version.manifest_filename.clone(), + /// platform: Platform::Android, + /// version: asset_version, + /// }; + /// + /// Asset::download_to_file(&asset_info, Some(Path::new("asset.unity3d")), None).await.unwrap(); + /// }); + /// ``` + pub async fn download_to_file( + asset_info: &AssetInfo, + output: Option<&Path>, + progress_bar: Option<&mut ProgressBar>, + ) -> Result<(), Error> { + let output = output.unwrap_or(asset_info.filename.as_ref()); + let mut out = BufWriter::new(File::create(output).await.map_err(Error::FileCreate)?); + + let res = Self::send_request(asset_info).await?; + + if let Some(ref pb) = progress_bar { + pb.set_length(res.content_length().unwrap_or(0)); + } + + log::debug!("save asset to {}", output.display()); + + let stream_reader = res + .bytes_stream() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + .into_async_read() + .compat(); + + let mut stream_reader = ProgressReadAdapter::new(stream_reader, progress_bar); + + tokio::io::copy(&mut stream_reader, &mut out).await.map_err(Error::FileWrite)?; + + Ok(()) + } +} + +/// Platform variant of the manifest. +/// +/// Although the game and the manifest name looks the same on both platforms, +/// their manifests are different. +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +pub enum Platform { + /// Android platform + Android, + + /// iOS platform + IOS, +} + +impl Platform { + /// Returns the string representation of the [`Platform`]. + pub fn as_str(&self) -> &str { + match self { + Self::Android => "Android", + Self::IOS => "iOS", + } + } + + /// Returns the `User-Agent` string of the [`Platform`] in HTTP request. + pub fn user_agent(&self) -> &str { + match self { + Self::Android => "UnityPlayer/2020.3.32f1 (UnityWebRequest/1.0, libcurl/7.80.0-DEV)", + Self::IOS => "ProductName/5.2.000 CFNetwork/1333.0.4 Darwin/21.5.0", + } + } +} + +impl Display for Platform { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", self.as_str()) + } +} + +impl FromStr for Platform { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "android" => Ok(Self::Android), + "ios" => Ok(Self::IOS), + s => Err(Error::UnknownPlatform(s.to_string())), + } + } +} diff --git a/src/mltd/error.rs b/src/mltd/error.rs new file mode 100644 index 0000000..2e866f1 --- /dev/null +++ b/src/mltd/error.rs @@ -0,0 +1,45 @@ +//! Error type definitions. + +use std::io; + +use tokio::task::JoinError; + +/// Error type for this crate. +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// Manifest deserialization failed. + #[error("manifest deserialization failed: {0}")] + ManifestDeserialize(#[from] rmp_serde::decode::Error), + + /// Manifest serialization failed. + #[error("manifest serialization failed: {0}")] + ManifestSerialize(#[from] rmp_serde::encode::Error), + + /// Reqwest response serialization failed. + #[error("response deserialization failed: {0}")] + ResponseDeserialize(reqwest::Error), + + /// File creation failed. + #[error("cannot create file: {0}")] + FileCreate(io::Error), + + /// File reading failed. + #[error("cannot read file: {0}")] + FileRead(io::Error), + + /// File writing failed. + #[error("cannot write file: {0}")] + FileWrite(io::Error), + + /// Reqwest request failed. + #[error("failed to send request: {0}")] + Request(reqwest::Error), + + /// Unknown platform. + #[error("unknown platform: {0}")] + UnknownPlatform(String), + + /// Thread join failed. + #[error("failed to join thread: {0}")] + ThreadJoin(#[from] JoinError), +} diff --git a/extract/src/class/asset_bundle.rs b/src/mltd/extract/class/asset_bundle.rs similarity index 99% rename from extract/src/class/asset_bundle.rs rename to src/mltd/extract/class/asset_bundle.rs index 95179b5..d0f113f 100644 --- a/extract/src/class/asset_bundle.rs +++ b/src/mltd/extract/class/asset_bundle.rs @@ -10,8 +10,8 @@ use rabex::objects::classes::{AssetBundle, AssetBundleScriptInfo, AssetInfo}; use rabex::objects::PPtr; use rabex::read_ext::ReadUrexExt; -use crate::utils::ReadAlignedExt; -use crate::version::*; +use crate::extract::utils::ReadAlignedExt; +use crate::extract::version::*; pub(super) fn _construct_p_ptr( reader: &mut R, diff --git a/extract/src/class/mesh.rs b/src/mltd/extract/class/mesh.rs similarity index 98% rename from extract/src/class/mesh.rs rename to src/mltd/extract/class/mesh.rs index 205db90..e766c71 100644 --- a/extract/src/class/mesh.rs +++ b/src/mltd/extract/class/mesh.rs @@ -6,8 +6,8 @@ use rabex::files::SerializedFile; use rabex::objects::classes::{ChannelInfo, StreamInfo, SubMesh, Vector3f, VertexData, AABB}; use rabex::read_ext::ReadSeekUrexExt; -use crate::utils::ReadUnityTypeExt; -use crate::version::*; +use crate::extract::utils::ReadUnityTypeExt; +use crate::extract::version::*; pub fn construct_sub_mesh( reader: &mut R, diff --git a/extract/src/class/mod.rs b/src/mltd/extract/class/mod.rs similarity index 100% rename from extract/src/class/mod.rs rename to src/mltd/extract/class/mod.rs diff --git a/extract/src/class/sprite.rs b/src/mltd/extract/class/sprite.rs similarity index 99% rename from extract/src/class/sprite.rs rename to src/mltd/extract/class/sprite.rs index 66c26d5..59bc091 100644 --- a/extract/src/class/sprite.rs +++ b/src/mltd/extract/class/sprite.rs @@ -14,8 +14,8 @@ use rabex::read_ext::{ReadSeekUrexExt, ReadUrexExt}; use super::asset_bundle::_construct_p_ptr; use super::mesh::{construct_sub_mesh, construct_vertex_data}; -use crate::utils::{ReadAlignedExt, ReadUnityTypeExt}; -use crate::version::*; +use crate::extract::utils::{ReadAlignedExt, ReadUnityTypeExt}; +use crate::extract::version::*; pub(super) fn _construct_sprite( data: &[u8], diff --git a/extract/src/class/text_asset.rs b/src/mltd/extract/class/text_asset.rs similarity index 97% rename from extract/src/class/text_asset.rs rename to src/mltd/extract/class/text_asset.rs index b4cc88c..7857f35 100644 --- a/extract/src/class/text_asset.rs +++ b/src/mltd/extract/class/text_asset.rs @@ -16,9 +16,9 @@ use rabex::files::SerializedFile; use rabex::objects::classes::TextAsset; use rabex::read_ext::ReadUrexExt; -use crate::utils::{ffmpeg, ReadAlignedExt}; -use crate::version::*; -use crate::ExtractorArgs; +use crate::extract::utils::{ffmpeg, ReadAlignedExt}; +use crate::extract::version::*; +use crate::extract::ExtractorArgs; pub(super) fn _construct_text_asset( data: &[u8], diff --git a/extract/src/class/texture_2d.rs b/src/mltd/extract/class/texture_2d.rs similarity index 99% rename from extract/src/class/texture_2d.rs rename to src/mltd/extract/class/texture_2d.rs index f0bbf73..cb2ebf9 100644 --- a/extract/src/class/texture_2d.rs +++ b/src/mltd/extract/class/texture_2d.rs @@ -17,11 +17,11 @@ use rabex::objects::classes::{GLTextureSettings, StreamingInfo, Texture2D}; use rabex::objects::map; use rabex::read_ext::{ReadSeekUrexExt, ReadUrexExt}; -use crate::class::sprite::construct_sprite; -use crate::environment::Environment; -use crate::utils::{ffmpeg, solve_puzzle, ReadAlignedExt}; -use crate::version::*; -use crate::ExtractorArgs; +use super::sprite::construct_sprite; +use crate::extract::environment::Environment; +use crate::extract::utils::{ffmpeg, solve_puzzle, ReadAlignedExt}; +use crate::extract::version::*; +use crate::extract::ExtractorArgs; pub(super) fn _construct_texture_2d( data: &[u8], diff --git a/extract/src/environment.rs b/src/mltd/extract/environment.rs similarity index 100% rename from extract/src/environment.rs rename to src/mltd/extract/environment.rs diff --git a/extract/src/lib.rs b/src/mltd/extract/mod.rs similarity index 97% rename from extract/src/lib.rs rename to src/mltd/extract/mod.rs index c1f7a1e..7d433bb 100644 --- a/extract/src/lib.rs +++ b/src/mltd/extract/mod.rs @@ -20,10 +20,10 @@ use rabex::objects::map; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::ThreadPoolBuilder; -use crate::class::asset_bundle::construct_asset_bundle; -use crate::class::text_asset::{construct_text_asset, decrypt_text, extract_acb}; -use crate::class::texture_2d::extract_texture_2d; -use crate::environment::{check_file_type, FileType}; +use self::class::asset_bundle::construct_asset_bundle; +use self::class::text_asset::{construct_text_asset, decrypt_text, extract_acb}; +use self::class::texture_2d::extract_texture_2d; +use self::environment::{check_file_type, FileType}; #[derive(Debug, Args)] #[command(author, version, about, arg_required_else_help(true))] diff --git a/extract/src/utils/mod.rs b/src/mltd/extract/utils/mod.rs similarity index 100% rename from extract/src/utils/mod.rs rename to src/mltd/extract/utils/mod.rs diff --git a/extract/src/utils/puzzle.rs b/src/mltd/extract/utils/puzzle.rs similarity index 100% rename from extract/src/utils/puzzle.rs rename to src/mltd/extract/utils/puzzle.rs diff --git a/extract/src/utils/read_ext.rs b/src/mltd/extract/utils/read_ext.rs similarity index 100% rename from extract/src/utils/read_ext.rs rename to src/mltd/extract/utils/read_ext.rs diff --git a/extract/src/version.rs b/src/mltd/extract/version.rs similarity index 98% rename from extract/src/version.rs rename to src/mltd/extract/version.rs index 07f64bd..1670ff3 100644 --- a/extract/src/version.rs +++ b/src/mltd/extract/version.rs @@ -127,16 +127,15 @@ gen_version!(UNITY_VERSION_2022_3_2_F1, 2022, 3, 2, f1); #[cfg(test)] #[ctor::ctor] fn init() { - mltd_utils::init_test_logger!(); + crate::util::init_test_logger!(); } #[cfg(test)] mod tests { use std::str::FromStr; - use mltd_utils::{rand_ascii_string, rand_range}; - use super::Version; + use crate::util::test_util::{rand_ascii_string, rand_range}; #[test] fn test_from_str() { diff --git a/src/mltd/lib.rs b/src/mltd/lib.rs new file mode 100644 index 0000000..dd32224 --- /dev/null +++ b/src/mltd/lib.rs @@ -0,0 +1,20 @@ +//! Functions for downloading and parsing MLTD assets. +//! +//! [![github]](https://github.com/nicks96432/mltd-asset-downloader) +//! +//! [github]: https://img.shields.io/badge/github-333333?style=for-the-badge&labelColor=555555&logo=github + +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] + +pub mod asset; +mod error; + +#[cfg(feature = "extract")] +pub mod extract; + +pub mod manifest; +pub mod net; +pub mod util; + +pub use self::error::Error; diff --git a/src/mltd/manifest.rs b/src/mltd/manifest.rs new file mode 100644 index 0000000..644fcd1 --- /dev/null +++ b/src/mltd/manifest.rs @@ -0,0 +1,150 @@ +//! Manifest file handling. + +use std::collections::HashMap; +use std::ops::Deref; + +use linked_hash_map::LinkedHashMap; +use serde::{Deserialize, Serialize}; + +use crate::asset::Asset; +use crate::error::Error; + +/// An entry in the manifest file. +/// +/// It contains the SHA1 hash of the file, the file name on the server and the +/// file size. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ManifestEntry(pub String, pub String, pub usize); + +/// Deserialized raw manifest. +/// +/// It contains a dictionary of the manifest entries, where the key is the actual +/// file name. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Manifest { + /// The underlying raw manifest data. + pub data: [LinkedHashMap; 1], +} + +impl Manifest { + /// Deserializes the specified bytes into a raw manifest. + /// + /// The bytes must be in message pack format. + /// + /// # Errors + /// + /// This function will return [`Error::ManifestDeserialize`] if + /// it cannot deserialize the message pack bytes. + #[inline] + pub fn from_slice(value: &[u8]) -> Result { + Ok(Self { data: rmp_serde::from_slice(value)? }) + } + + /// Computes the difference from `other` manifest. + pub fn diff<'a>(&'a self, other: &'a Manifest) -> ManifestDiff<'a> { + let mut diff = ManifestDiff::new(); + + for (key, value) in other.iter() { + if !self.contains_key(key) { + diff.added.insert(key, value); + } else if self[key].0 != value.0 || self[key].2 != value.2 { + // although the hash and file size are the same, the hashed + // file name may be different across different versions + // for some unknown reason (maybe they hashed full path?) + diff.updated.insert(key, value); + } + } + + for (key, value) in self.iter() { + if !other.contains_key(key) { + diff.removed.insert(key, value); + } + } + + diff + } + + /// Returns the number of entries in the manifest. + #[inline] + pub fn len(&self) -> usize { + self.data[0].len() + } + + /// Returns `true` if the manifest is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns the total size of all assets in the manifest. + #[inline] + pub fn asset_size(&self) -> usize { + self.data[0].values().fold(0, |acc, v| acc + v.2) + } +} + +impl Deref for Manifest { + type Target = LinkedHashMap; + + fn deref(&self) -> &Self::Target { + &self.data[0] + } +} + +impl TryFrom> for Manifest { + type Error = Error; + + fn try_from(asset: Asset) -> Result { + Self::from_slice(&asset.data) + } +} + +/// A diff of two manifests. +#[derive(Debug, Serialize)] +pub struct ManifestDiff<'a> { + /// The added entries in the new manifest. + pub added: HashMap<&'a String, &'a ManifestEntry>, + + /// The updated entries in the new manifest. + pub updated: HashMap<&'a String, &'a ManifestEntry>, + + /// The removed entries in the new manifest. + pub removed: HashMap<&'a String, &'a ManifestEntry>, +} + +impl ManifestDiff<'_> { + fn new() -> Self { + Self { added: HashMap::new(), updated: HashMap::new(), removed: HashMap::new() } + } +} + +#[cfg(test)] +#[ctor::ctor] +fn init() { + crate::util::init_test_logger!(); +} + +#[cfg(test)] +mod tests { + use super::Manifest; + + #[test] + fn test_raw_manifest_from_slice() { + let expected = include_bytes!("../../tests/test1.msgpack"); + let manifest: Manifest = Manifest::from_slice(expected).unwrap(); + + assert_eq!(*expected, *rmp_serde::to_vec(&[&*manifest]).unwrap()); + } + + #[test] + fn test_raw_manifest_diff() { + let manifest1 = Manifest::from_slice(include_bytes!("../../tests/test1.msgpack")).unwrap(); + let manifest2 = Manifest::from_slice(include_bytes!("../../tests/test2.msgpack")).unwrap(); + + let diff = manifest1.diff(&manifest2); + assert_eq!(diff.added.len(), 8); + assert_eq!(diff.updated.len(), 6); + assert_eq!(diff.removed.len(), 0); + } +} diff --git a/src/mltd/net/matsuri_api.rs b/src/mltd/net/matsuri_api.rs new file mode 100644 index 0000000..0d7ad96 --- /dev/null +++ b/src/mltd/net/matsuri_api.rs @@ -0,0 +1,196 @@ +//! Functions to fetch manifest version from `api.matsurihi.me`. + +use serde::Deserialize; + +use crate::Error; + +/// matshrihi.me MLTD v2 API `/version/assets/:app` response body structure. +#[derive(Debug, Clone, Deserialize)] +pub struct AppVersion { + /// App version. + pub version: String, + + /// Forced update date and time. + #[serde(rename = "updatedAt")] + pub updated_at: String, + + /// Revision number + /// + /// The value will be [`None`] if the version is not actually released. + pub revision: Option, +} + +/// matshrihi.me MLTD v2 API `/version/assets/:version` response body structure. +#[derive(Debug, Clone, Deserialize)] +pub struct AssetVersion { + /// Manifest filename on MLTD asset server. + #[serde(rename = "indexName")] + pub manifest_filename: String, + + /// Delivery date and time. + #[serde(rename = "updatedAt")] + pub updated_at: String, + + /// Asset version. + pub version: u64, +} + +/// matshrihi.me MLTD v2 API `/version/latest` response body structure. +#[derive(Debug, Deserialize)] +pub struct VersionInfo { + /// App version information + #[serde(rename = "app")] + pub app_version: AppVersion, + + /// Asset version information + #[serde(rename = "asset")] + pub asset_version: AssetVersion, +} + +const MATSURI_API_ENDPOINT: &str = "https://api.matsurihi.me/api/mltd/v2"; + +/// Gets the latest manifest filename and version from matsurihi.me. +/// +/// # Returns +/// +/// Returns a tuple of manifest filename and version. +/// +/// # Errors +/// +/// [`Error::Request`]: if it cannot send request to `api.matsurihi.me`. +/// +/// [`Error::ResponseDeserialize`]: if it cannot deserialize response. +/// +/// # Examples +/// +/// ```no_run +/// use mltd::net::latest_asset_version; +/// +/// tokio_test::block_on(async { +/// let asset_version = latest_asset_version().await.unwrap().version; +/// }); +/// ``` +pub async fn latest_asset_version() -> Result { + let client = reqwest::Client::new(); + let req = client + .get(format!("{}{}", MATSURI_API_ENDPOINT, "/version/latest")) + .query(&[("prettyPrint", "false")]); + + let res = match req.send().await { + Ok(r) => r, + Err(e) => return Err(Error::Request(e)), + }; + + let version_info: VersionInfo = match res.json().await { + Ok(info) => info, + Err(e) => return Err(Error::ResponseDeserialize(e)), + }; + + Ok(version_info.asset_version) +} + +/// Gets all asset versions from `api.matsurihi.me`. +/// +/// # Returns +/// +/// Returns a vector of asset versions. +/// +/// # Errors +/// +/// [`Error::Request`]: if it cannot send request to `api.matsurihi.me`. +/// +/// [`Error::ResponseDeserialize`]: if it cannot deserialize response. +pub async fn get_all_asset_versions() -> Result, Error> { + let client = reqwest::Client::new(); + let req = client + .get(format!("{}{}", MATSURI_API_ENDPOINT, "/version/assets")) + .query(&[("prettyPrint", "false")]); + + let res = match req.send().await { + Ok(r) => r, + Err(e) => return Err(Error::Request(e)), + }; + + let versions: Vec = match res.json().await { + Ok(v) => v, + Err(e) => return Err(Error::ResponseDeserialize(e)), + }; + + Ok(versions) +} + +/// Gets the specified asset version from `api.matsurihi.me`. +/// +/// # Returns +/// +/// Returns the specified asset version. +/// +/// # Errors +/// +/// [`Error::Request`]: if it cannot send request to `api.matsurihi.me`. +/// +/// [`Error::ResponseDeserialize`]: if it cannot deserialize response. +/// +/// # Examples +/// +/// ```no_run +/// use mltd::net::get_asset_version; +/// +/// tokio_test::block_on(async { +/// let asset_version = get_asset_version(1).await.unwrap(); +/// assert_eq!(asset_version.version, 1); +/// }); +/// ``` +pub async fn get_asset_version(version: u64) -> Result { + let client = reqwest::Client::new(); + let req = client + .get(format!("{}/version/assets/{}", MATSURI_API_ENDPOINT, version)) + .query(&[("prettyPrint", "false")]); + + let res = match req.send().await { + Ok(r) => r, + Err(e) => return Err(Error::Request(e)), + }; + + let asset_version: AssetVersion = match res.json().await { + Ok(v) => v, + Err(e) => return Err(Error::ResponseDeserialize(e)), + }; + + Ok(asset_version) +} + +#[cfg(test)] +#[ctor::ctor] +fn init() { + crate::util::init_test_logger!(); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_get_asset_version() { + let version = get_asset_version(1).await.unwrap(); + + assert_eq!(version.version, 1); + assert_eq!(version.updated_at, "2017-06-29T12:00:00+09:00"); + assert_eq!(version.manifest_filename, "6b976a4c875a1984592a66b621872ce44c944e72.data"); + } + + #[tokio::test] + async fn test_latest_asset_version() { + let version = latest_asset_version().await.unwrap(); + + assert!(version.version > 0); + assert!(!version.manifest_filename.is_empty()); + } + + #[tokio::test] + async fn test_all_asset_versions() { + let versions = get_all_asset_versions().await.unwrap(); + + assert_ne!(versions.len(), 0); + } +} diff --git a/src/mltd/net/mod.rs b/src/mltd/net/mod.rs new file mode 100644 index 0000000..e6a0d73 --- /dev/null +++ b/src/mltd/net/mod.rs @@ -0,0 +1,5 @@ +//! This module provides functions related to network requests. + +mod matsuri_api; + +pub use self::matsuri_api::*; diff --git a/src/mltd/util.rs b/src/mltd/util.rs new file mode 100644 index 0000000..1a6c883 --- /dev/null +++ b/src/mltd/util.rs @@ -0,0 +1,122 @@ +//! Utilities used in this crate. + +use std::io::{Result, Write}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use env_logger::fmt::Formatter; +use indicatif::ProgressBar; +use log::Record; +use pin_project::pin_project; +use tokio::io::{AsyncRead, ReadBuf}; + +/// Custom log formatter used in this crate. +pub fn log_formatter(buf: &mut Formatter, record: &Record) -> Result<()> { + let color_code = match record.level() { + log::Level::Error => 1, // red + log::Level::Warn => 3, // yellow + log::Level::Info => 4, // blue + log::Level::Debug => 2, // green + log::Level::Trace => 7, // white + }; + let space = match record.level().as_str().len() { + 4 => " ", + _ => "", + }; + let level = record.level().as_str(); + let target = record.target(); + let timestamp = buf.timestamp_micros(); + let body = record.args(); + + writeln!( + buf, + "[\x1b[3{}m{}\x1b[0m]{} {} {} - {}", + color_code, level, space, timestamp, target, body + ) +} + +/// Adapter for [`tokio::io::AsyncRead`] to show progress. +/// +/// From [this stackoverflow answer]. +/// +/// [this stackoverflow answer]: https://stackoverflow.com/a/73809326 +#[pin_project] +pub struct ProgressReadAdapter<'bar, R: AsyncRead> { + #[pin] + inner: R, + progress_bar: Option<&'bar mut ProgressBar>, +} + +impl<'bar, R: AsyncRead> ProgressReadAdapter<'bar, R> { + /// Create a new [`ProgressReadAdapter`] from a [`tokio::io::AsyncRead`] + /// reader and an optional [`indicatif::ProgressBar`]. + pub fn new(inner: R, progress_bar: Option<&'bar mut ProgressBar>) -> Self { + Self { inner, progress_bar } + } +} + +impl AsyncRead for ProgressReadAdapter<'_, R> { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let this = self.project(); + + let before = buf.filled().len(); + let result = this.inner.poll_read(cx, buf); + let after = buf.filled().len(); + + if result.is_ready() { + if let Some(pb) = this.progress_bar { + pb.inc((after - before) as u64); + } + } + + result + } +} + +#[cfg(test)] +macro_rules! init_test_logger { + () => { + let _ = env_logger::builder() + .is_test(true) + .filter_module(env!("CARGO_PKG_NAME"), log::LevelFilter::Debug) + .format(crate::util::log_formatter) + .try_init(); + }; +} + +#[cfg(test)] +pub(crate) use init_test_logger; + +#[cfg(test)] +pub(crate) mod test_util { + use std::io::Cursor; + + use rand::distributions::uniform::{SampleRange, SampleUniform}; + use rand::{thread_rng, Rng, SeedableRng}; + use rand_xoshiro::Xoshiro256PlusPlus as MyRng; + + pub fn rand_ascii_string(len: usize) -> Cursor> { + let mut rng = MyRng::from_rng(thread_rng()).unwrap(); + let mut buf = vec![0u8; len]; + for byte in buf.iter_mut().take(len) { + *byte = u8::try_from(rng.gen_range(0x33..0x7f)).unwrap(); // printable ascii + } + buf.push(0u8); + + Cursor::new(buf) + } + + pub fn rand_range(range: R) -> T + where + T: SampleUniform, + R: SampleRange, + { + let mut rng = MyRng::from_rng(thread_rng()).unwrap(); + + rng.gen_range(range) + } +} diff --git a/manifest/tests/test1.msgpack b/tests/test1.msgpack similarity index 100% rename from manifest/tests/test1.msgpack rename to tests/test1.msgpack diff --git a/manifest/tests/test2.msgpack b/tests/test2.msgpack similarity index 100% rename from manifest/tests/test2.msgpack rename to tests/test2.msgpack diff --git a/extract/tests/test_acb.unity3d b/tests/test_acb.unity3d similarity index 100% rename from extract/tests/test_acb.unity3d rename to tests/test_acb.unity3d diff --git a/extract/tests/test_sprite.unity3d b/tests/test_sprite.unity3d similarity index 100% rename from extract/tests/test_sprite.unity3d rename to tests/test_sprite.unity3d diff --git a/extract/tests/test_text.unity3d b/tests/test_text.unity3d similarity index 100% rename from extract/tests/test_text.unity3d rename to tests/test_text.unity3d diff --git a/utils/Cargo.toml b/utils/Cargo.toml deleted file mode 100644 index 5180d71..0000000 --- a/utils/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -authors.workspace = true -edition.workspace = true -license.workspace = true -name = "mltd-utils" -repository.workspace = true -version = "0.1.0" - -[dependencies.env_logger] -optional = true -workspace = true - -[dependencies.log] -optional = true -workspace = true - -[dependencies.rand] -default-features = false -version = "0.8.5" -optional = true - -[dependencies.rand_xoshiro] -version = "0.6.0" -optional = true - -[dependencies.ureq] -optional = true -workspace = true - -[features] -default = [] -log = ["dep:log", "dep:env_logger"] -rand = ["rand/std", "rand/std_rng", "dep:rand_xoshiro"] -request = ["dep:ureq"] diff --git a/utils/src/lib.rs b/utils/src/lib.rs deleted file mode 100644 index 90858f9..0000000 --- a/utils/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod log; -mod rand; -mod request; - -#[cfg(feature = "log")] -pub use self::log::*; -#[cfg(feature = "rand")] -pub use self::rand::*; -#[cfg(feature = "request")] -pub use self::request::*; diff --git a/utils/src/log.rs b/utils/src/log.rs deleted file mode 100644 index 1c7434b..0000000 --- a/utils/src/log.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Logging utilities. - -#![cfg(feature = "log")] - -use std::io::{Result, Write}; - -use env_logger::fmt::Formatter; -use log::Record; - -pub fn log_formatter(buf: &mut Formatter, record: &Record) -> Result<()> { - let color_code = match record.level() { - log::Level::Error => 1, // red - log::Level::Warn => 3, // yellow - log::Level::Info => 4, // blue - log::Level::Debug => 2, // green - log::Level::Trace => 7, // white - }; - let space = match record.level().as_str().len() { - 4 => " ", - _ => "", - }; - let level = record.level().as_str(); - let target = record.target(); - let timestamp = buf.timestamp(); - let body = record.args(); - - writeln!( - buf, - "[\x1b[3{}m{}\x1b[0m]{} {} {} - {}", - color_code, level, space, timestamp, target, body - ) -} - -#[macro_export] -macro_rules! init_test_logger { - () => { - let _ = env_logger::builder() - .is_test(true) - .filter_module(env!("CARGO_PKG_NAME"), log::LevelFilter::Debug) - .format(mltd_utils::log_formatter) - .try_init(); - }; -} diff --git a/utils/src/rand.rs b/utils/src/rand.rs deleted file mode 100644 index d1fb48e..0000000 --- a/utils/src/rand.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Utilities for generating random data in tests. - -#![cfg(feature = "rand")] - -use std::io::Cursor; - -use rand::distributions::uniform::{SampleRange, SampleUniform}; -use rand::{thread_rng, Rng, RngCore, SeedableRng}; -use rand_xoshiro::Xoshiro256PlusPlus as MyRng; - -pub fn rand_ascii_string(len: usize) -> Cursor> { - let mut rng = MyRng::from_rng(thread_rng()).unwrap(); - let mut buf = vec![0u8; len]; - for byte in buf.iter_mut().take(len) { - *byte = u8::try_from(rng.gen_range(0x33..0x7f)).unwrap(); // printable ascii - } - buf.push(0u8); - - Cursor::new(buf) -} - -pub fn rand_bytes(len: usize) -> Cursor> { - let mut rng = MyRng::from_rng(thread_rng()).unwrap(); - let mut buf = vec![0u8; len]; - rng.fill_bytes(&mut buf); - - Cursor::new(buf) -} - -pub fn rand_range(range: R) -> T -where - T: SampleUniform, - R: SampleRange, -{ - let mut rng = MyRng::from_rng(thread_rng()).unwrap(); - - rng.gen_range(range) -} diff --git a/utils/src/request.rs b/utils/src/request.rs deleted file mode 100644 index 2e8dd4a..0000000 --- a/utils/src/request.rs +++ /dev/null @@ -1,46 +0,0 @@ -#![cfg(feature = "request")] - -use ureq::Error::Status; -use ureq::{Agent, Error as UreqError, Request, Response}; - -const ASSET_URL_BASE: &str = "https://td-assets.bn765.com"; -const UNITY_VERSION: &str = "2020.3.32f1"; - -pub fn fetch_asset(agent: &Agent, path: &str) -> Result> { - let url = format!("{}{}", ASSET_URL_BASE, path); - let req = agent - .get(url.as_str()) - .set("Accept", "*/*") - .set("Accept-Encoding", "deflate, gzip") - .set("X-Unity-Version", UNITY_VERSION); - trace_request(&req); - - let result = req.call(); - if let Err(Status(_, ref res)) | Ok(ref res) = result { - log::trace!(""); - trace_response(res); - } - - match result { - Ok(r) => Ok(r), - Err(e) => Err(Box::new(e)), - } -} - -pub fn trace_request(req: &Request) { - let header_names = req.header_names(); - let iter = header_names.iter(); - - log::trace!("{} {}", req.method(), req.url()); - - iter.for_each(|h| log::trace!("{}: {}", h, req.header(h).unwrap_or(""))); -} - -pub fn trace_response(res: &Response) { - let header_names = res.headers_names(); - let iter = header_names.iter(); - - log::trace!("{} {} {}", res.status(), res.status_text(), res.http_version()); - - iter.for_each(|h| log::trace!("{}: {}", h, res.header(h).unwrap_or(""))); -}