diff --git a/Cargo.lock b/Cargo.lock index 5f761b2..ae8e2f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,11 +8,22 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -25,15 +36,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -64,6 +75,21 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -78,9 +104,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -105,17 +131,27 @@ dependencies = [ "lzma-rs", "nohash-hasher", "quick-xml", - "sha1_smol", + "sha1", "smallvec", "zip", "zstd", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -123,9 +159,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -135,9 +171,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -147,15 +183,30 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] [[package]] name = "crc" @@ -181,6 +232,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -201,7 +273,22 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", - "zlib-rs", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", ] [[package]] @@ -212,10 +299,34 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -228,14 +339,40 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", ] [[package]] @@ -244,21 +381,43 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom", + "getrandom 0.3.4", "libc", ] +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.182" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "log" @@ -298,18 +457,44 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -344,10 +529,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "sha1_smol" -version = "1.0.1" +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] [[package]] name = "shlex" @@ -357,9 +607,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "smallvec" @@ -373,6 +623,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -390,18 +646,36 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -411,6 +685,94 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -431,39 +793,121 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "zip" -version = "8.2.0" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b680f2a0cd479b4cff6e1233c483fdead418106eae419dc60200ae9850f6d004" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ - "crc32fast", - "flate2", + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", "indexmap", - "memchr", - "typed-path", - "zopfli", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", ] [[package]] -name = "zlib-rs" -version = "0.6.3" +name = "wit-bindgen-rust-macro" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] [[package]] -name = "zopfli" -version = "0.8.3" +name = "wit-component" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ - "bumpalo", - "crc32fast", + "anyhow", + "bitflags", + "indexmap", "log", - "simd-adler32", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zip" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2726508a48f38dceb22b35ecbbd2430efe34ff05c62bd3285f965d7911b33464" +dependencies = [ + "aes", + "constant_time_eq", + "crc32fast", + "flate2", + "getrandom 0.4.2", + "hmac", + "indexmap", + "memchr", + "pbkdf2", + "sha1", + "typed-path", + "zeroize", ] +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index bc5908f..fb3ad44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,13 +15,14 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" -clap = { version = "4.5", features = ["derive"] } +clap = { version = "4.6", features = ["derive"] } crc32fast = "1.5" nohash-hasher = "0.2.0" quick-xml = "0.39.2" smallvec = "1.15" -sha1_smol = { version = "1.0", features = ["std"] } flate2 = "1" lzma-rs = "0.3" -zip = { version = "8", default-features = false, features = ["deflate"] } +zip = { version = "8", default-features = false, features = ["aes-crypto", "deflate-flate2"] } +sha1 = "0.10" zstd = "0.13" + diff --git a/src/gex.rs b/src/gex.rs index 4b806bc..8d3b0cd 100644 --- a/src/gex.rs +++ b/src/gex.rs @@ -104,7 +104,11 @@ class GeneratedTask(BaseTask): )); out.push_str(" contents = f.read()\n"); } - CandidateSource::Zip { archive, member } => { + CandidateSource::Zip { + archive, + member, + password, + } => { let archive_name = archive .file_name() .map(|s| s.to_string_lossy()) @@ -114,7 +118,20 @@ class GeneratedTask(BaseTask): out.push_str(&format!( " with zipfile.ZipFile(os.path.join(in_dir, {py_archive})) as z:\n" )); - out.push_str(&format!(" with z.open({py_member}) as f:\n")); + if let Some(pw) = password { + let py_pw = py_str(pw); + out.push_str( + " # NOTE: zipfile only supports ZipCrypto; AES-encrypted\n", + ); + out.push_str( + " # archives need pyzipper or another AES-capable library.\n", + ); + out.push_str(&format!( + " with z.open({py_member}, pwd={py_pw}.encode()) as f:\n" + )); + } else { + out.push_str(&format!(" with z.open({py_member}) as f:\n")); + } out.push_str(" contents = f.read()\n"); } CandidateSource::Kpka { archive, index } => { diff --git a/src/lib.rs b/src/lib.rs index 622b11c..1506496 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ pub fn apply_found( where F: FnMut(&RomInfo, &[u8]) -> anyhow::Result<()>, { - use sha1_smol::Sha1; + use sha1::{Digest, Sha1}; use types::MatchedData; let MatchedData::Spec(ref spec) = found.data; @@ -53,12 +53,12 @@ where let mut hasher = Sha1::new(); hasher.update(roms[rid].header.as_ref().unwrap()); hasher.update(&bytes_owned); - hasher.digest().to_string() + format!("{:x}", hasher.finalize()) }) .as_str() } else { if sha1_cache.is_none() { - sha1_cache = Some(Sha1::from(&bytes_owned[..]).digest().to_string()); + sha1_cache = Some(format!("{:x}", Sha1::digest(&bytes_owned[..]))); } sha1_cache.as_ref().unwrap().as_str() }; diff --git a/src/main.rs b/src/main.rs index 71683a6..cd8ce2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![deny(warnings)] +use anyhow::Context; use chisel::utils; use clap::Parser; @@ -61,14 +62,36 @@ struct Opt { #[arg(long)] no_expand: bool, + /// Passwords to try on encrypted zip members (may be repeated) + #[arg(long)] + password: Vec, + + /// Read passwords from a file, one per line + #[arg(long)] + password_file: Option, + /// Verbose logging (-v) #[arg(short, long)] verbose: bool, } -fn run_extract(opt: &Opt, spec: chisel::types::ExtractionSpec) -> anyhow::Result<()> { +fn load_passwords(opt: &Opt) -> anyhow::Result> { + let mut passwords = opt.password.clone(); + if let Some(ref path) = opt.password_file { + let content = std::fs::read_to_string(path) + .with_context(|| format!("Reading password file {}", path.display()))?; + passwords.extend(content.lines().filter(|l| !l.is_empty()).map(String::from)); + } + Ok(passwords) +} + +fn run_extract( + opt: &Opt, + passwords: &[String], + spec: chisel::types::ExtractionSpec, +) -> anyhow::Result<()> { std::fs::create_dir_all(&opt.output_dir)?; - let cands = utils::load_candidates_from_paths(&opt.input_files, opt.verbose)?; + let cands = utils::load_candidates_from_paths(&opt.input_files, passwords, opt.verbose)?; for cand in &cands { let mut s = spec.clone(); if s.size == 0 { @@ -101,8 +124,10 @@ fn main() -> anyhow::Result<()> { anyhow::bail!("--dat and --spec are mutually exclusive"); } + let passwords = load_passwords(&opt)?; + if let Some(spec) = opt.spec.clone() { - return run_extract(&opt, spec); + return run_extract(&opt, &passwords, spec); } let dat = opt @@ -110,7 +135,7 @@ fn main() -> anyhow::Result<()> { .as_ref() .ok_or_else(|| anyhow::anyhow!("either --dat or --spec is required"))?; let mut roms = load_rom_list(dat, opt.game.as_deref())?; - let mut cands = utils::load_candidates_from_paths(&opt.input_files, opt.verbose)?; + let mut cands = utils::load_candidates_from_paths(&opt.input_files, &passwords, opt.verbose)?; if opt.gex.is_none() { std::fs::create_dir_all(&opt.output_dir)?; diff --git a/src/pipeline.rs b/src/pipeline.rs index 79091be..b72472d 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -41,6 +41,7 @@ fn cost_ratio(work: u64, value: u64) -> u64 { ((work as f64) / (value.max(1) as f64)).ceil() as u64 } +#[allow(clippy::too_many_arguments)] pub fn run_pipeline( roms: &mut [RomInfo], cands: &mut [Candidate], diff --git a/src/test_support.rs b/src/test_support.rs index 1673a15..de41708 100644 --- a/src/test_support.rs +++ b/src/test_support.rs @@ -1,6 +1,6 @@ use crate::types::{Candidate, CandidateSource, Found, Heuristic, MatchRecord, Pending, RomInfo}; use crc32fast; -use sha1_smol::Sha1; +use sha1::{Digest, Sha1}; use std::collections::HashMap; pub fn make_rom(name: &str, data: &[u8]) -> RomInfo { @@ -9,7 +9,7 @@ pub fn make_rom(name: &str, data: &[u8]) -> RomInfo { game: String::new(), size: data.len(), crc32: crc32fast::hash(data), - sha1: Some(Sha1::from(data).digest().to_string()), + sha1: Some(format!("{:x}", Sha1::digest(data))), matched: false, unverified: false, region: None, @@ -25,7 +25,7 @@ pub fn make_rom_with_header(name: &str, header: &[u8], content: &[u8]) -> RomInf full.extend_from_slice(header); full.extend_from_slice(content); let full_crc = crc32fast::hash(&full); - let full_sha1 = Sha1::from(&full).digest().to_string(); + let full_sha1 = format!("{:x}", Sha1::digest(&full)); let content_crc = crate::utils::derive_content_crc(full_crc, header, content.len()); RomInfo { name: name.to_string(), diff --git a/src/types.rs b/src/types.rs index 67b94b8..7780042 100644 --- a/src/types.rs +++ b/src/types.rs @@ -235,7 +235,11 @@ pub enum CandidateSource { /// Single file decompressed from a gzip stream. Gzip { archive: PathBuf }, /// One member extracted from a zip archive. - Zip { archive: PathBuf, member: String }, + Zip { + archive: PathBuf, + member: String, + password: Option, + }, /// Decompressed from an LZMA/XZ block found at `offset` inside `parent`. Lzma { parent: PathBuf, offset: usize }, /// One entry extracted from a KPKA/PAK archive (index is entry ordinal, 0-based). @@ -268,7 +272,9 @@ impl std::fmt::Display for Candidate { .unwrap_or("???".into()); write!(f, "[gzip in {}]", a)?; } - CandidateSource::Zip { archive, member } => { + CandidateSource::Zip { + archive, member, .. + } => { let a = archive .file_name() .map(|s| s.to_string_lossy()) diff --git a/src/utils.rs b/src/utils.rs index 3043146..1b5637f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -28,7 +28,7 @@ const LZMA1_VALID_PROPS: [bool; 256] = { /// Check if a dict_size is plausible for real LZMA1 streams. /// Real dict sizes are powers of 2 or 2^n + 2^(n-1) (i.e. 3 * 2^(n-1)). fn is_valid_lzma_dict_size(d: u32) -> bool { - d.is_power_of_two() || (d % 3 == 0 && d / 3 > 0 && (d / 3).is_power_of_two()) + d.is_power_of_two() || (d.is_multiple_of(3) && d / 3 > 0 && (d / 3).is_power_of_two()) } use crate::{Candidate, RomInfo}; @@ -209,9 +209,15 @@ pub fn load_rom_list(dat_path: &Path, maybe_game: Option<&str>) -> anyhow::Resul /// Detect compressed archives by magic bytes and expand into `Candidate`s. /// /// - `.gz` / gzip magic (`1f 8b`): decompress into one candidate. -/// - `.zip` magic (`PK\x03\x04`): one candidate per unencrypted file member. +/// - `.zip` magic (`PK\x03\x04`): one candidate per file member. Encrypted members +/// are decrypted using the provided `passwords`; members that cannot be decrypted +/// are skipped with a warning (when `verbose` is set). /// - Everything else: one plain candidate. -pub fn load_candidates_from_paths(paths: I, verbose: bool) -> anyhow::Result> +pub fn load_candidates_from_paths( + paths: I, + passwords: &[String], + verbose: bool, +) -> anyhow::Result> where I: IntoIterator, I::Item: AsRef, @@ -257,33 +263,93 @@ where let cursor = std::io::Cursor::new(&data); let mut archive = zip::ZipArchive::new(cursor) .with_context(|| format!("Opening zip {}", path.display()))?; - for i in 0..archive.len() { - let mut entry = archive - .by_index(i) - .with_context(|| format!("Reading zip entry {} in {}", i, path.display()))?; - if entry.is_dir() { - continue; - } - if entry.encrypted() { - if verbose { - eprintln!( - "skipping encrypted zip member '{}' in {}", - entry.name(), - path.display() - ); + + // Collect metadata first to avoid borrow conflicts when retrying + // passwords on encrypted entries. + let entry_meta: Vec<_> = (0..archive.len()) + .map(|i| { + let entry = archive.by_index_raw(i).with_context(|| { + format!("Reading zip entry metadata {} in {}", i, path.display()) + })?; + if entry.is_dir() { + return Ok(None); } - continue; - } - let member_name = entry.name().to_string(); - let mut member_data = Vec::new(); - entry.read_to_end(&mut member_data).with_context(|| { - format!("Reading zip member '{}' in {}", member_name, path.display()) - })?; + Ok(Some((i, entry.name().to_string(), entry.encrypted()))) + }) + .collect::>>()? + .into_iter() + .flatten() + .collect(); + + for (i, member_name, encrypted) in entry_meta { + let (member_data, password) = if encrypted { + let mut decrypted = None; + for pw in passwords { + match archive.by_index_decrypt(i, pw.as_bytes()) { + Err(zip::result::ZipError::InvalidPassword) => continue, + Err(e) => { + return Err(anyhow::Error::from(e).context(format!( + "Reading encrypted zip entry '{}' in {}", + member_name, + path.display() + ))); + } + Ok(mut entry) => { + let mut buf = Vec::new(); + match entry.read_to_end(&mut buf) { + Ok(_) => { + decrypted = Some((buf, pw.clone())); + break; + } + // AES HMAC validation failure at end-of-stream + Err(e) + if e.kind() == std::io::ErrorKind::InvalidData + || e.kind() == std::io::ErrorKind::InvalidInput => + { + continue; + } + Err(e) => { + return Err(anyhow::Error::from(e).context(format!( + "Reading zip member '{}' in {}", + member_name, + path.display() + ))); + } + } + } + } + } + let Some((data, pw)) = decrypted else { + if verbose { + eprintln!( + "skipping encrypted zip member '{}' in {}", + member_name, + path.display() + ); + } + continue; + }; + (data, Some(pw)) + } else { + let mut entry = archive.by_index(i).with_context(|| { + format!("Reading zip entry {} in {}", i, path.display()) + })?; + let mut buf = Vec::new(); + entry.read_to_end(&mut buf).with_context(|| { + format!("Reading zip member '{}' in {}", member_name, path.display()) + })?; + (buf, None) + }; if verbose { eprintln!( - " zip member: {} ({} bytes)", + " zip member: {} ({} bytes{})", member_name, - member_data.len() + member_data.len(), + if password.is_some() { + ", decrypted" + } else { + "" + } ); } let logical_path = path.join(&member_name); @@ -293,6 +359,7 @@ where source: CandidateSource::Zip { archive: path.clone(), member: member_name, + password, }, coverage: Coverage::default(), }); @@ -622,6 +689,7 @@ pub fn decode_lz77(data: &[u8]) -> Option<(Vec, usize)> { #[cfg(test)] mod tests { use super::*; + use std::io::Write; #[test] fn parse_hex_header_basic() { @@ -765,4 +833,119 @@ mod tests { assert!(roms[0].matched); assert_eq!(records[0].header.as_deref(), Some(header.as_slice())); } + + /// Helper: create a zip archive in memory with one stored (uncompressed) file. + fn make_zip(name: &str, content: &[u8]) -> Vec { + let buf = Vec::new(); + let mut writer = zip::ZipWriter::new(std::io::Cursor::new(buf)); + let opts = zip::write::SimpleFileOptions::default() + .compression_method(zip::CompressionMethod::Stored); + writer.start_file(name, opts).unwrap(); + writer.write_all(content).unwrap(); + writer.finish().unwrap().into_inner() + } + + /// Helper: create an AES-256 encrypted zip archive in memory. + fn make_encrypted_zip(name: &str, content: &[u8], password: &str) -> Vec { + let buf = Vec::new(); + let mut writer = zip::ZipWriter::new(std::io::Cursor::new(buf)); + let opts = zip::write::SimpleFileOptions::default() + .compression_method(zip::CompressionMethod::Stored) + .with_aes_encryption(zip::AesMode::Aes256, password); + writer.start_file(name, opts).unwrap(); + writer.write_all(content).unwrap(); + writer.finish().unwrap().into_inner() + } + + /// Helper: write bytes to a temp file and return the path. + fn write_temp(dir: &std::path::Path, name: &str, data: &[u8]) -> std::path::PathBuf { + let p = dir.join(name); + std::fs::write(&p, data).unwrap(); + p + } + + #[test] + fn zip_plain_candidate() { + let dir = std::env::temp_dir().join("chisel_test_zip_plain"); + std::fs::create_dir_all(&dir).unwrap(); + let zip_bytes = make_zip("rom.bin", b"hello"); + let zip_path = write_temp(&dir, "test.zip", &zip_bytes); + + let cands = load_candidates_from_paths(&[&zip_path], &[], false).unwrap(); + assert_eq!(cands.len(), 1); + assert_eq!(cands[0].data, b"hello"); + assert!(matches!( + &cands[0].source, + CandidateSource::Zip { password: None, .. } + )); + + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + fn zip_encrypted_correct_password() { + let dir = std::env::temp_dir().join("chisel_test_zip_enc_ok"); + std::fs::create_dir_all(&dir).unwrap(); + let zip_bytes = make_encrypted_zip("secret.bin", b"payload", "hunter2"); + let zip_path = write_temp(&dir, "enc.zip", &zip_bytes); + + let passwords = vec!["hunter2".to_string()]; + let cands = load_candidates_from_paths(&[&zip_path], &passwords, false).unwrap(); + assert_eq!(cands.len(), 1); + assert_eq!(cands[0].data, b"payload"); + assert!(matches!( + &cands[0].source, + CandidateSource::Zip { + password: Some(_), + .. + } + )); + + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + fn zip_encrypted_wrong_password_skipped() { + let dir = std::env::temp_dir().join("chisel_test_zip_enc_wrong"); + std::fs::create_dir_all(&dir).unwrap(); + let zip_bytes = make_encrypted_zip("secret.bin", b"payload", "correct"); + let zip_path = write_temp(&dir, "enc.zip", &zip_bytes); + + let passwords = vec!["wrong1".to_string(), "wrong2".to_string()]; + let cands = load_candidates_from_paths(&[&zip_path], &passwords, false).unwrap(); + assert_eq!(cands.len(), 0); + + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + fn zip_encrypted_no_passwords_skipped() { + let dir = std::env::temp_dir().join("chisel_test_zip_enc_none"); + std::fs::create_dir_all(&dir).unwrap(); + let zip_bytes = make_encrypted_zip("secret.bin", b"payload", "pw"); + let zip_path = write_temp(&dir, "enc.zip", &zip_bytes); + + let cands = load_candidates_from_paths(&[&zip_path], &[], false).unwrap(); + assert_eq!(cands.len(), 0); + + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + fn zip_encrypted_multiple_passwords_finds_correct() { + let dir = std::env::temp_dir().join("chisel_test_zip_enc_multi"); + std::fs::create_dir_all(&dir).unwrap(); + let zip_bytes = make_encrypted_zip("secret.bin", b"data", "third"); + let zip_path = write_temp(&dir, "enc.zip", &zip_bytes); + + let passwords: Vec = ["first", "second", "third"] + .iter() + .map(|s| s.to_string()) + .collect(); + let cands = load_candidates_from_paths(&[&zip_path], &passwords, false).unwrap(); + assert_eq!(cands.len(), 1); + assert_eq!(cands[0].data, b"data"); + + std::fs::remove_dir_all(&dir).ok(); + } }