From 651c80a15ae4c891010d875a271e9edfe6b379eb Mon Sep 17 00:00:00 2001 From: Lkhsss Date: Thu, 8 Feb 2024 01:16:34 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E4=B8=AA=E7=89=88=E6=9C=AC=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E3=80=82=E5=AE=8C=E6=88=90=E6=89=B9=E9=87=8F=E8=A7=A3?= =?UTF-8?q?=E5=AF=86=E5=8F=8A=E6=97=A5=E5=BF=97=E7=9A=84=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + Cargo.lock | 839 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 22 ++ README.md | 77 +++++ src/lib.rs | 538 +++++++++++++++++++++++++++++++++ src/main.rs | 92 ++++++ 6 files changed, 1571 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af3383c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +src/main.rs.old +src/lib.rs.bak diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2a4b919 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,839 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea31d69bda4949c1c1562c1e6f042a1caefac98cdc8a298260a2ff41c1e2d42b" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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 = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e7cf40684ae96ade6232ed84582f40ce0a66efcd43a5117aef610534f8e0b8" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "image" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png", + "qoi", + "tiff", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +dependencies = [ + "rayon", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "ncmmiao" +version = "1.1.3" +dependencies = [ + "aes", + "base64", + "colored", + "env_logger", + "hex", + "image", + "log", + "serde", + "serde_derive", + "serde_json", + "walkdir", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "png" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e08cdfb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ncmmiao" +version = "1.1.3" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aes = "0.8.3" +base64 = "0.21.7" +colored = "2.1.0" +env_logger = "0.11.1" +hex = "0.4.3" +image = "0.24.8" +log = "0.4.20" +serde = { version = "1.0.195", features = ["derive"] } +serde_derive = "1.0.195" +serde_json = "1.0.111" +walkdir = "2.4.0" + +[badges] +maintenance = { status = "actively-developed" } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1008f5 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# NcmMiao +一个使用Rust语言编写的ncm文件解密工具。 +### 功能及特点 + - 支持单一文件,多文件夹递归批量解密。 + - 完善的日志功能 + - Colorful + - 编译文件小,解密快 + +## 编译 +``` +cargo build -r +``` + +## 使用 +支持单一文件,多文件夹递归批量解密。 +``` +cargo build -r <文件或文件夹路径1> <文件或文件夹路径2> <文件或文件夹路径3> ... +``` +# TODO + - [ ] 增加多线程支持 + - [ ] 增加解密进度条 + + +# CHANGELOG +## [1.0.0] - 2024-1-27 +### Features +- 初步完成解密函数。主程序成型 + +## [1.1.1] - 2024-2-1 +### Features +- 完成批量解密 +### Fixed +- 修正了提取音乐信息会有数据类型错误导致panic的问题 + +## [1.1.2] - 2024-2-5 +### Fixed + - 修正了提取音乐信息时,部分歌曲信息提取失败的问题 + - 修正了音乐数据解密失败的问题 +## [1.1.3] - 2024-2-6 +### Fixed + - 修正了部分音乐名称中含有不合法字符时创建文件失败的问题 + + +# 附 - ncm文件结构 +|信息|大小|作用| +|:-:|:-:|:-:| +|Magic Header|8 bytes|文件头| +|Gap|2 bytes|| +|Key Length|4 bytes|RC4密钥长度,字节是按小端排序。| +|Key Data|Key Length|RC4密钥| +|Music Info Length|4 bytes|用AES128加密后的音乐相关信息的长度,小端排序。| +|Music Info Data|Music Info Length|Json格式音乐信息数据。| +|Gap|5 bytes|| +|CRC校验码|4 bytes|图片的CRC32校验码,小端排序。| +|Image Size|4 bytes|图片的大小| +|Image Data|Image Size|图片数据| +|Music Data||音乐数据| +--- +### Magic Header +### Key Data +用AES128加密后的RC4密钥。 +1. 先按字节对0x64进行异或。 +2. AES解密,去除填充部分。 +3. 去除最前面'neteasecloudmusic'17个字节,得到RC4密钥。 +### Music Info Data +Json格式音乐信息数据。 +1. 按字节对0x63进行异或。 +2. 去除最前面22个字节。 +3. Base64进行解码。 +4. AES解密。 +6. 去除前面6个字节,后面数量为最后一个字节的字节数的垃圾数据,得到Json数据。 + +### Music Data +1. RC4-KSA生成S盒。 +2. 用S盒解密(自定义的解密方法),不是RC4-PRGA解密。 + + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..47c39fc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,538 @@ +#[warn(dead_code)] +use aes::cipher::generic_array::typenum::U16; +use aes::cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit}; +use aes::Aes128; +use base64; +use colored::*; +use core::panic; +use log::{debug, error, info, trace, warn}; +use serde_derive::{Deserialize, Serialize}; +use serde_json::{self, Value}; +use std::fs::{self, File}; +use std::io::{BufReader, Error, ErrorKind, Read, Seek, SeekFrom, Write}; +// use std::iter::Enumerate; +use std::path::Path; +use std::str::from_utf8; + +#[derive(Debug)] +pub struct Ncmfile { + /// 文件对象 + pub file: File, + /// 只是名称,不带后缀 + pub name: String, + /// 带后缀名 + pub filename: String, + /// 文件大小 + pub size: u64, + /// 游标 + pub position: u64, +} +impl Ncmfile { + pub fn new(filepath: &str) -> Result { + let file = File::open(filepath)?; + let path = Path::new(filepath); + let filename = path.file_name().unwrap().to_str().unwrap().to_string(); + let size = file.metadata().unwrap().len(); + let name = match Path::new(&filepath).file_stem() { + Some(f) => f.to_str().unwrap().to_string(), + None => panic!("获取文件名失败"), + }; + Ok(Ncmfile { + file, + name, + filename, + size, + position: 0, + }) + } + /// 根据传入的长度来读取文件 + /// + /// 该函数可以记录上次读取的位置,下次读取时从上次读取的位置开始 + /// - length 想要读取的长度 + pub fn seekread(&mut self, length: u64) -> Result, std::io::Error> { + if self.position + length > self.size { + return Err(Error::new( + ErrorKind::UnexpectedEof, + "无法读取!读取长度大于剩余文件大小!", + )); + } else { + let mut reader = BufReader::new(&self.file); + reader.seek(SeekFrom::Start(self.position))?; + let mut buf = vec![0; length as usize]; + reader.read_exact(&mut buf)?; + self.position += length; + Ok(buf[..].to_vec()) + } + } + /// 从指定位置开始读取。 + /// + /// !!!该函数仍然会更新游标 + /// + /// - offset 开始位置 + /// - length 想要读取的长度 + pub fn seekread_from(&mut self, offset: u64, length: u64) -> Result, std::io::Error> { + if self.position + length > self.size { + return Err(Error::new( + ErrorKind::UnexpectedEof, + "无法读取!读取长度大于剩余文件大小!", + )); + } else { + let mut reader = BufReader::new(&self.file); + reader.seek(SeekFrom::Start(offset))?; + let mut buf = vec![0; length as usize]; + reader.read_exact(&mut buf)?; + self.position = offset + length; + Ok(buf[..].to_vec()) + } + } + pub fn seekread_to_end(&mut self) -> Result, std::io::Error> { + let mut reader = BufReader::new(&self.file); + reader.seek(SeekFrom::Start(self.position))?; + let mut buf = vec![0; self.size as usize - self.position as usize]; + reader.read_exact(&mut buf)?; + self.position = self.size; + Ok(buf[..].to_vec()) + } + pub fn seekread_no_error(&mut self, length: u64) -> Vec { + if self.position + length > self.size { + if self.position >= self.size { + return vec![]; + } else { + let mut reader = BufReader::new(&self.file); + let _ = reader.seek(SeekFrom::Start(self.position)); + + let mut buf: Vec = vec![0; (self.size - self.position) as usize]; + let _ = reader.read_exact(&mut buf); + self.position += length; + return buf[..].to_vec(); + } + } else { + let mut reader = BufReader::new(&self.file); + let _ = reader.seek(SeekFrom::Start(self.position)); + let mut buf = vec![0; length as usize]; + let _ = reader.read_exact(&mut buf); + self.position += length; + buf[..].to_vec() + } + } + /// 跳过某些数据 + pub fn skip(&mut self, length: u64) -> Result<(), std::io::Error> { + if self.position + length > self.size { + return Err(Error::new( + ErrorKind::UnexpectedEof, + "无法跳过!跳过长度大于剩余文件大小!", + )); + } else { + self.position += length; + Ok(()) + } + } + ///按字节进行0x64异或。 + fn parse_key(key: &mut [u8]) -> &[u8] { + for i in 0..key.len() { + key[i] ^= 0x64; + } + key + } +} + +/// 存储元数据的结构体 +#[derive(Serialize, Deserialize, Debug)] +struct Metadata { + //编号 + #[serde(rename = "musicId", skip)] //没用过,跳过 + music_id: String, + // 音乐名称 + #[serde(rename = "musicName")] + music_name: String, + // 艺术家 + #[serde(rename = "artist")] + music_artist: Vec<(String, String)>, + // 专辑id + #[serde(rename = "albumId")] + album_id: String, + // 专辑 + #[serde(rename = "album")] + album: String, + // + #[serde(rename = "albumPicDocId", skip)] + album_pic_doc_id: String, + // + #[serde(rename = "albumPic", skip)] + album_pic: String, + // 比特率 + #[serde(rename = "bitrate")] + bitrate: u128, + // + #[serde(rename = "mp3DocId", skip)] + mp3_doc_id: String, + // 时间长短 + #[serde(rename = "duration")] + duration: u128, + // + #[serde(rename = "mvId")] + mv_id: String, + // 别名 + #[serde(rename = "alias")] + alias: Vec, + // 译名 + #[serde(rename = "transNames")] + trans_names: Vec, + // 音乐格式 + #[serde(rename = "format")] + format: String, +} + +// 存储各种密钥的结构体 +pub struct Key { + pub core: Vec, + pub meta: Vec, +} + +/// 解密函数 +pub fn dump(ncmfile: &mut Ncmfile, keys: &Key, outputdir: &Path) -> Result<(), MyError> { + info!("开始解密[{}]文件", ncmfile.filename.yellow()); + // 获取magic header 。应为CTENFDAM + trace!("获取 magic header"); + let magic_header = match ncmfile.seekread(8) { + Ok(header) => header, + Err(_e) => { + error!("读取magic header失败"); + return Err(MyError::MagicHeaderError); + } + }; + // 判断是否为ncm格式的文件 + match from_utf8(&magic_header) { + Ok(header) => { + if header != "CTENFDAM" { + // 传播错误至dump + return Err(MyError::MagicHeaderError); + } else { + trace!("[{}]为ncm格式文件", ncmfile.filename.yellow()); + } + } + // 传播错误至dump + Err(_e) => return Err(MyError::MagicHeaderError), + } + + // 跳过2字节 + trace!("跳过2字节"); + match ncmfile.skip(2) { + Ok(_) => (), + Err(_e) => return Err(MyError::FileSkipError), + }; + + trace!("获取RC4密钥长度"); + //小端模式读取RC4密钥长度 正常情况下应为128 + let key_length = u32::from_le_bytes(ncmfile.seekread(4).unwrap().try_into().unwrap()) as u64; + debug!("RC4密钥长度为:{}", key_length); + + //读取密钥 开头应为 neteasecloudmusic + trace!("读取RC4密钥"); + let mut key_data = ncmfile.seekread(key_length).unwrap(); + //aes128解密 + let key_data = &aes128_to_slice(&keys.core, Ncmfile::parse_key(&mut key_data[..])); //先把密钥按照字节进行0x64异或 + // RC4密钥 + let key_data = unpad(&key_data[..])[17..].to_vec(); + + //读取meta信息的数据大小 + trace!("获取meta信息数据大小"); + let meta_length = u32::from_le_bytes(ncmfile.seekread(4).unwrap().try_into().unwrap()) as u64; + + // 读取meta信息 + trace!("读取meta信息"); + let meta_data = { + let mut meta_data = ncmfile.seekread(meta_length).unwrap(); //读取源数据 + //字节对0x63进行异或。 + for i in 0..meta_data.len() { + meta_data[i] ^= 0x63; + } + // base64解密 + let decode_data = &base64::decode(&meta_data[22..]).unwrap()[..]; + // aes128解密 + let aes_data = aes128_to_slice(&keys.meta, decode_data); + // unpadding + let json_data = String::from_utf8(unpad(&aes_data)[6..].to_vec()).unwrap(); + debug!("json_data: {}", json_data); + let data: Value = serde_json::from_str(&json_data[..]).unwrap(); //解析json数据 + data + }; + + // 跳过4个字节的校验码 + trace!("跳过4个字节的校验码"); + let crc32 = u32::from_le_bytes(ncmfile.seekread(4).unwrap().try_into().unwrap()) as u64; + + // 跳过5个字节 + trace!("跳过5个字节"); + ncmfile.skip(5).unwrap(); + + // 获取图片数据的大小 + trace!("获取图片数据的大小"); + let image_data_length = + u32::from_le_bytes(ncmfile.seekread(4).unwrap().try_into().unwrap()) as u64; + + // 读取图片,并写入文件当中 + trace!("暂不需要保存图片,跳过{}字节", image_data_length); + let image_data = ncmfile.seekread(image_data_length).unwrap(); //读取图片数据 + // let _ = ncmfile.skip(image_data_length); //暂不需要保存图片,直接跳过这些字节就好 + + //保存图片 + // trace!("保存图片"); + // let mut file = File::create(format!("TEST.jpg",)).unwrap(); + // file.write_all(&image_data).unwrap(); + + let key_box = { + let key_length = key_data.len(); + let key_data = Vec::from(key_data); + let mut key_box = (0..=255).collect::>(); + let mut temp = 0; + let mut last_byte = 0; + let mut key_offset = 0; + + for i in 0..=255 { + let swap = key_box[i as usize] as u64; + temp = (swap + last_byte as u64 + key_data[key_offset as usize] as u64) & 0xFF; + key_offset += 1; + if key_offset >= key_length { + key_offset = 0; + } + key_box[i as usize] = key_box[temp as usize]; + key_box[temp as usize] = swap as u8; + last_byte = temp; + } + let key_box = key_box.clone(); + key_box + }; + + /* let mut s_box = { + let key_length = key_data.len(); + let key_box = Vec::from(key_data); + let mut s = (0..=255).collect::>(); + let mut j = 0; + + for i in 0..=255 { + j = (j as usize + s[i] as usize + key_box[i % key_length] as usize) & 0xFF; + + //记录 s[j]的值 + let temp = &s.get(j as usize).unwrap().to_owned(); + s[j as usize] = s[i]; + s[i] = temp.to_owned(); + } + + s + }; */ + + // let key_box = key_box[0..(key_box.len()-key_box[key_box.len() as usize-1] as usize)].to_vec(); + + //解密音乐数据 + trace!("解密音乐数据"); + let mut music_data: Vec = Vec::new(); + loop { + let mut chunk = ncmfile.seekread_no_error(0x8000); + + let chunk_length = chunk.len(); + if chunk_length != 0 { + for i in 1..chunk_length + 1 { + let j = i & 0xFF; + + chunk[i - 1] ^= key_box[(key_box[j] as usize + + key_box[(key_box[j as usize] as usize + j) & 0xff] as usize) + & 0xff] + // chunk[i - 1] ^= key_box[(key_box[j] + key_box[(key_box[j as usize] as usize + j as usize) & 0xFF]) & 0xFF]; + } + //向music_data中最追加chunk + music_data.append(&mut chunk); + } else { + break; + } + } + + //组成流密钥 + /* let mut stream = Vec::new(); + for i in 0..256 { + stream.push( + s_box[(s_box[i] as usize + s_box[(i + s_box[i] as usize) & 0xFF] as usize) & 0xFF], + ) + } */ + + // 解密音乐数据 + /* loop { + let chunk = ncmfile.seekread_no_error(256); //每次读取256个字节 + if chunk.len() != 0 { + for (count, &i) in chunk[..].iter().enumerate() { + music_data.push(i ^ stream[count]) + } + } else { + break; + } + } */ + // debug!("music_data:{:?}", music_data); + // debug!("长度:{}", stream.len()); + + //退出循环,写入文件 + + //处理文件路径 + trace!("拼接文件路径"); + let path = { + let mut artists = String::new(); + let mut music_artist = Vec::new(); + if let Some(i) = meta_data.get("artist") { + for names in i.as_array().unwrap() { + music_artist.push(names[0].as_str().unwrap()); + } + } + + for artist in music_artist { + artists.push_str(&artist); + artists.push(','); + } + // 移除最后一个字符 + artists.pop(); + debug!("艺术家名称:{}", artists.yellow()); + + let filename = format!( + "{} - {}.{}", + meta_data.get("musicName").unwrap().as_str().unwrap(), + artists, + meta_data.get("format").unwrap().as_str().unwrap() + ); + // .replace("\"", """) + // .replace("?", "?") + // .replace(":", ":"); + + let filename = standardize_filename(filename); + debug!("文件名:{}", filename.yellow()); + //链级创建输出目录 + fs::create_dir_all(outputdir).unwrap(); + outputdir.join(filename) + }; + + debug!("文件路径: {:?}", path); + // 创建文件 + trace!("创建文件"); + let mut music_file = File::create(&path).unwrap(); + trace!("保存文件"); + music_file.write_all(&music_data).unwrap(); + // 关闭文件 + music_file.sync_all().unwrap(); + music_file.flush().unwrap(); + + info!( + "[{}]文件已保存到: {}", + ncmfile.name.yellow(), + path.to_str().unwrap().bright_cyan() + ); + info!( + "{}{}{}", + "[".bright_green(), + ncmfile.filename.yellow(), + "]解密成功".bright_green() + ); + Ok(()) +} + +// fn read_meta(file: &mut File, meta_length: u32) -> Result, Error> {} +fn convert_to_generic_arrays(input: &[u8]) -> Vec> { + // 确保输入的长度是16的倍数 + assert!( + input.len() % 16 == 0, + "Input length must be a multiple of 16" + ); + + input + .chunks(16) + .map(|chunk| { + // 将每个块转换为GenericArray + GenericArray::clone_from_slice(chunk) + }) + .collect() +} +/// aes128解密 +/// !!!未对齐数据!!! +/// TODO +/// 解密NCM文件的rc4密钥前记得按字节对0x64进行异或 +fn aes128(key: &[u8], blocks: &[u8]) -> String { + trace!("进行AES128解密"); + let key = GenericArray::from_slice(key); + + let mut blocks = convert_to_generic_arrays(blocks); + + // 初始化密钥 + let cipher = Aes128::new(&key); + + // 开始解密 + cipher.decrypt_blocks(&mut blocks); + + let mut x = String::new(); + for block in blocks.iter() { + x.push_str(std::str::from_utf8(&block).unwrap()) + } + // 去除所有空格及控制字符 + let x = x[..].trim(); + + x.to_string() +} +fn aes128_to_slice(key: &[u8], blocks: &[u8]) -> Vec { + trace!("进行AES128解密"); + let key = GenericArray::from_slice(key); + + let mut blocks = convert_to_generic_arrays(blocks); + + // 初始化密钥 + let cipher = Aes128::new(&key); + + // 开始解密 + cipher.decrypt_blocks(&mut blocks); + let mut x: Vec = Vec::new(); + for block in blocks.iter() { + for i in block { + x.push(i.to_owned()); + } + } + x +} + +/// ## 规范文件名称 +/// 防止创建文件失败 +/// 符号一一对应: +/// - \ / * ? " : < > | +/// - _ _ * ? " : ⟨ ⟩ _ +fn standardize_filename(old_filename: String) -> String { + let mut new_filename = String::from(old_filename); + debug!("规范文件名:{}", new_filename); + let standard = ["\\", "/", "*", "?", "\"", ":", "<", ">", "|"]; + let resolution = ["_", "_", "*", "?", """, ":", "⟨", "⟩", "_",]; + for i in 0..standard.len() { + new_filename = new_filename.replace(&standard[i].to_string(), &resolution[i].to_string()); + } + new_filename +} + +/// 使用PKCS5Padding标准,去掉填充信息 +fn unpad(data: &[u8]) -> Vec { + data[..data.len() - data[data.len() - 1] as usize].to_vec() +} + +#[derive(Debug)] +pub enum MyError { + MagicHeaderError, + FileReadError, + FileSkipError, + FileWriteError, + FilenameError, + FileNotFoundError, +} + +impl std::error::Error for MyError {} + +impl std::fmt::Display for MyError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::MagicHeaderError => write!(f, "文件不为NCM格式"), + Self::FileReadError => write!(f, "文件读取错误"), + Self::FileWriteError => write!(f, "文件写入错误"), + Self::FilenameError => write!(f, "文件名不符合规范"), + _ => write!(f, "未知错误"), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0ff92f8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,92 @@ +use env_logger::Builder; +use hex::decode; +use log::{error, warn}; +use ncmmiao::{dump, Key, Ncmfile}; +use std::env; +use std::path::Path; +// use std::time::SystemTime; +use walkdir::WalkDir; +#[warn(unreachable_code)] + +fn main() { + let mut builder = Builder::new(); + builder.filter(None, log::LevelFilter::Info); + builder.init(); //初始化logger + + let keys = Key { + core: decode("687A4852416D736F356B496E62617857").unwrap(), + meta: decode("2331346C6A6B5F215C5D2630553C2728").unwrap(), + }; + + let args: Vec = env::args().collect(); + let args = if args.len() == 1 { + warn!("未指定文件夹,将使用默认文件夹。"); + let mut args_temp = Vec::new(); + if Path::new("CloudMusic").exists() { + warn!("CloudMusic文件夹存在,将自动使用。"); + args_temp.push(String::from("CloudMusic")); + }; + if Path::new("input").exists() { + warn!("input文件夹存在,将自动使用。"); + args_temp.push(String::from("input")); + }; + if args_temp.is_empty() { + //TODO 增加软件介绍 + error!("没有参数\n没有CloudMusic或者input文件夹存在与于工作目录"); + panic!("没有参数\n没有CloudMusic或者input文件夹存在与于工作目录"); + } + args_temp + } else { + args[1..].to_vec() + }; + // let args = &args[1..]; + + let mut undumpfile = Vec::new(); // 该列表将存入文件的路径 + + for arg in &args { + //解析传入的每一个路径:文件or文件夹 + let path = Path::new(arg); + + if path.is_file() { + // 当后缀符合为ncm时才加入列表 + match path.extension() { + Some(extension) => { + if extension == "ncm" { + let _ = &mut undumpfile.push(arg.to_owned()); + } + } + None => {} + } + } else if path.is_dir() { + for entry in WalkDir::new(path) { + let new_entry = entry.unwrap().clone(); + let filepath = new_entry.into_path(); + // 当后缀符合为ncm时才加入列表 + match filepath.extension() { + Some(extension) => { + if extension == "ncm" { + let _ = &mut undumpfile.push(String::from(filepath.to_str().unwrap())); + } + } + None => { + continue; + } + } + } + } + } + // let filepaths = undumpfile; + let count = undumpfile.len(); + let mut time = 0usize; + for filepath in undumpfile { + time += 1; + // println!("{}/{}", &time, &count); + let mut ncmfile = Ncmfile::new(&filepath).unwrap(); + dump(&mut ncmfile, &keys, Path::new("output")).unwrap(); + + //TODO增加计时 + // let time = SystemTime::now(); + // let time = SystemTime::now().duration_since(time).unwrap().as_secs(); + // println!("{}",time); + } +}