Compare commits
10 Commits
1a282c0912
...
e6fcb07ed2
Author | SHA1 | Date | |
---|---|---|---|
e6fcb07ed2 | |||
a9c7ba9fd4 | |||
c4249226a2 | |||
e92dc08964 | |||
bfa66c6e39 | |||
15bfd296f0 | |||
4b89ad9962 | |||
6c5c82ee1c | |||
0f1233b45a | |||
2c53e4d950 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ target/
|
|||||||
pkg/
|
pkg/
|
||||||
pkg-*/
|
pkg-*/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
*.local
|
||||||
|
@ -6,9 +6,11 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/um_wasm/tests" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/um_wasm/tests" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/um_crypto/kuwo/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/um_crypto/kuwo/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/um_crypto/qmc/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/um_crypto/qmc/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/um_cli/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/um_crypto/utils/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
253
Cargo.lock
generated
253
Cargo.lock
generated
@ -2,6 +2,55 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
@ -20,6 +69,12 @@ version = "3.16.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.15"
|
version = "1.1.15"
|
||||||
@ -35,6 +90,52 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console_error_panic_hook"
|
name = "console_error_panic_hook"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@ -51,6 +152,29 @@ version = "1.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@ -69,6 +193,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.158"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.22"
|
version = "0.4.22"
|
||||||
@ -91,6 +221,15 @@ version = "1.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
@ -109,6 +248,36 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@ -130,6 +299,12 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.77"
|
version = "2.0.77"
|
||||||
@ -141,6 +316,16 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tc_tea"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab26285e70ee5cbec8582c76b8124dfe65b564b43dad14b9caca6fd127d66d2"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
"rand_chacha",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.63"
|
version = "1.0.63"
|
||||||
@ -161,6 +346,17 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "um_cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"clap",
|
||||||
|
"umc_kuwo",
|
||||||
|
"umc_qmc",
|
||||||
|
"umc_utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "um_wasm"
|
name = "um_wasm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -178,9 +374,9 @@ name = "umc_kuwo"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
|
||||||
"itertools",
|
"itertools",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"umc_utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -188,9 +384,18 @@ name = "umc_qmc"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"byteorder",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"tc_tea",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"umc_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "umc_utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -199,6 +404,12 @@ version = "1.0.12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -209,6 +420,12 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.93"
|
version = "0.2.93"
|
||||||
@ -318,7 +535,16 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -393,3 +619,24 @@ name = "windows_x86_64_msvc"
|
|||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["um_crypto/*", "um_wasm"]
|
members = ["um_crypto/*", "um_wasm", "um_cli"]
|
||||||
|
|
||||||
[profile.release.package.um_wasm]
|
[profile.release.package.um_wasm]
|
||||||
# Tell `rustc` to optimize for small code size.
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
11
um_cli/Cargo.toml
Normal file
11
um_cli/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "um_cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
clap = { version = "4.5.17", features = ["derive"] }
|
||||||
|
umc_kuwo = { path = "../um_crypto/kuwo" }
|
||||||
|
umc_qmc = { path = "../um_crypto/qmc" }
|
||||||
|
umc_utils = { path = "../um_crypto/utils" }
|
12
um_cli/src/cmd/mod.rs
Normal file
12
um_cli/src/cmd/mod.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
pub mod qmc1;
|
||||||
|
pub mod qmc2;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum Commands {
|
||||||
|
#[command(name = "qmc1")]
|
||||||
|
QMCv1(qmc1::ArgsQMCv1),
|
||||||
|
#[command(name = "qmc2")]
|
||||||
|
QMCv2(qmc2::ArgsQMCv2),
|
||||||
|
}
|
39
um_cli/src/cmd/qmc1.rs
Normal file
39
um_cli/src/cmd/qmc1.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::Cli;
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Args;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Decrypt a QMCv1 file
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct ArgsQMCv1 {
|
||||||
|
/// Path to output file, e.g. /export/Music/song.flac
|
||||||
|
#[clap(short, long)]
|
||||||
|
output: PathBuf,
|
||||||
|
|
||||||
|
/// Path to input file, e.g. /export/Music/song.qmcflac
|
||||||
|
#[arg(name = "input")]
|
||||||
|
input: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgsQMCv1 {
|
||||||
|
pub fn run(&self, cli: &Cli) -> Result<i32> {
|
||||||
|
let mut file_input = File::open(&self.input)?;
|
||||||
|
let mut file_output = File::create(&self.output)?;
|
||||||
|
|
||||||
|
let mut offset = 0usize;
|
||||||
|
let mut buffer = vec![0u8; cli.buffer_size].into_boxed_slice();
|
||||||
|
while let Ok(n) = file_input.read(&mut buffer) {
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
umc_qmc::v1::decrypt(&mut buffer[..n], offset);
|
||||||
|
file_output.write_all(&buffer[..n])?;
|
||||||
|
offset += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
}
|
91
um_cli/src/cmd/qmc2.rs
Normal file
91
um_cli/src/cmd/qmc2.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use crate::Cli;
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use clap::Args;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use umc_qmc::{footer, QMCv2Cipher};
|
||||||
|
use umc_utils::base64;
|
||||||
|
|
||||||
|
/// Decrypt a QMCv1 file
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct ArgsQMCv2 {
|
||||||
|
/// Path to output file, e.g. /export/Music/song.flac
|
||||||
|
#[arg(short, long)]
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Path to input file, e.g. /export/Music/song.qmcflac
|
||||||
|
#[arg(name = "input")]
|
||||||
|
input: PathBuf,
|
||||||
|
|
||||||
|
/// Override EKey for this file.
|
||||||
|
/// Prefix with "decrypted:" to use base64 encoded raw key.
|
||||||
|
#[arg(short = 'K', long = "ekey")]
|
||||||
|
ekey: Option<String>,
|
||||||
|
|
||||||
|
/// Print info about this file, and do not perform decryption.
|
||||||
|
#[arg(short = 'I', long, action=clap::ArgAction::SetTrue, default_value_t=false)]
|
||||||
|
info_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgsQMCv2 {
|
||||||
|
pub fn run(&self, cli: &Cli) -> Result<i32> {
|
||||||
|
let mut file_input = File::open(&self.input)?;
|
||||||
|
let mut footer_detection_buffer = vec![0u8; footer::INITIAL_DETECTION_LEN];
|
||||||
|
file_input.seek(SeekFrom::End(-(footer::INITIAL_DETECTION_LEN as i64)))?;
|
||||||
|
file_input.read_exact(&mut footer_detection_buffer)?;
|
||||||
|
let input_size = file_input.stream_position()?;
|
||||||
|
file_input.seek(SeekFrom::Start(0))?;
|
||||||
|
|
||||||
|
let (footer_len, ekey) = match footer::from_byte_slice(&footer_detection_buffer) {
|
||||||
|
Ok(Some(metadata)) => {
|
||||||
|
if self.info_only || cli.verbose {
|
||||||
|
println!("metadata: {:?}", metadata);
|
||||||
|
}
|
||||||
|
(metadata.size, metadata.ekey.or_else(|| self.ekey.clone()))
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
eprintln!("could not find any qmc metadata.");
|
||||||
|
(0usize, self.ekey.clone())
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("failed to parse qmc metadata: {}", err);
|
||||||
|
(0usize, self.ekey.clone())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.info_only {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = match ekey {
|
||||||
|
None => bail!("--ekey is required when embedded ekey is not present."),
|
||||||
|
Some(ekey) => match ekey.strip_suffix("decrypted:") {
|
||||||
|
Some(decrypted) => base64::decode(decrypted)?.into_boxed_slice(),
|
||||||
|
None => umc_qmc::ekey::decrypt(ekey)?,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let cipher = QMCv2Cipher::new(key)?;
|
||||||
|
|
||||||
|
let mut file_output = match &self.output {
|
||||||
|
None => bail!("--output is required"),
|
||||||
|
Some(output) => BufWriter::new(File::create(output)?),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buffer = vec![0u8; cli.buffer_size];
|
||||||
|
let reader = BufReader::with_capacity(cli.buffer_size, file_input);
|
||||||
|
let mut reader = reader.take(input_size - footer_len as u64);
|
||||||
|
let mut offset = 0usize;
|
||||||
|
while let Ok(n) = reader.read(&mut buffer) {
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.decrypt(&mut buffer[..n], offset);
|
||||||
|
file_output.write_all(&buffer[..n])?;
|
||||||
|
offset += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
}
|
51
um_cli/src/main.rs
Normal file
51
um_cli/src/main.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use crate::cmd::Commands;
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use std::process::exit;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
mod cmd;
|
||||||
|
|
||||||
|
/// um_cli (rust ver.)
|
||||||
|
/// A cli-tool to unlock encrypted audio files.
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "um_cli")]
|
||||||
|
#[command(version = "0.1")]
|
||||||
|
pub struct Cli {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: Option<Commands>,
|
||||||
|
|
||||||
|
/// Be more verbose about what is going on.
|
||||||
|
#[clap(long, short, action=clap::ArgAction::SetTrue, default_value_t=false)]
|
||||||
|
verbose: bool,
|
||||||
|
|
||||||
|
/// Preferred buffer size when reading file, in bytes.
|
||||||
|
/// Default to 4MiB.
|
||||||
|
#[clap(long, short = 'B', default_value_t=4*1024*1024)]
|
||||||
|
buffer_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_command(cli: &Cli) -> Result<i32> {
|
||||||
|
match &cli.command {
|
||||||
|
Some(Commands::QMCv1(cmd)) => cmd.run(&cli),
|
||||||
|
Some(Commands::QMCv2(cmd)) => cmd.run(&cli),
|
||||||
|
None => {
|
||||||
|
// https://github.com/clap-rs/clap/issues/3857#issuecomment-1161796261
|
||||||
|
todo!("implement a sensible default command, similar to um/cli");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let start = Instant::now();
|
||||||
|
let code = run_command(&cli).unwrap_or_else(|err| {
|
||||||
|
eprintln!("failed to run command: {}", err);
|
||||||
|
-1
|
||||||
|
});
|
||||||
|
let duration = start.elapsed();
|
||||||
|
if cli.verbose {
|
||||||
|
eprintln!("time: {:?}", duration);
|
||||||
|
};
|
||||||
|
exit(code);
|
||||||
|
}
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.22.1"
|
|
||||||
itertools = "0.13.0"
|
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
itertools = "0.13.0"
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
|
umc_utils = { path = "../utils" }
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::alphabet;
|
|
||||||
use base64::engine::{DecodePaddingMode, GeneralPurpose as Base64Engine, GeneralPurposeConfig};
|
|
||||||
use base64::prelude::*;
|
|
||||||
|
|
||||||
mod constants;
|
mod constants;
|
||||||
mod core;
|
mod core;
|
||||||
mod helper;
|
mod helper;
|
||||||
use core::{KuwoDes, Mode};
|
use core::{KuwoDes, Mode};
|
||||||
|
use umc_utils::base64;
|
||||||
/// Don't add padding when encoding, and require no padding when decoding.
|
|
||||||
const B64: Base64Engine = Base64Engine::new(
|
|
||||||
&alphabet::STANDARD,
|
|
||||||
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Decrypt string content
|
/// Decrypt string content
|
||||||
pub fn decrypt_ksing(data: &str, key: &[u8; 8]) -> Result<String> {
|
pub fn decrypt_ksing(data: &str, key: &[u8; 8]) -> Result<String> {
|
||||||
let mut decoded = B64.decode(data)?;
|
let mut decoded = base64::decode(data)?;
|
||||||
|
|
||||||
let des = KuwoDes::new(key, Mode::Decrypt);
|
let des = KuwoDes::new(key, Mode::Decrypt);
|
||||||
des.transform(&mut decoded[..])?;
|
des.transform(&mut decoded[..])?;
|
||||||
@ -35,7 +27,7 @@ pub fn encrypt_ksing<T: AsRef<[u8]>>(data: T, key: &[u8; 8]) -> Result<String> {
|
|||||||
|
|
||||||
let des = KuwoDes::new(key, Mode::Encrypt);
|
let des = KuwoDes::new(key, Mode::Encrypt);
|
||||||
des.transform(&mut data[..])?;
|
des.transform(&mut data[..])?;
|
||||||
Ok(B64.encode(data))
|
Ok(base64::encode(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_ekey(data: &str, key: &[u8; 8]) -> Result<String> {
|
pub fn decode_ekey(data: &str, key: &[u8; 8]) -> Result<String> {
|
||||||
|
@ -4,7 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.22.1"
|
|
||||||
itertools = "0.13.0"
|
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
byteorder = "1.5.0"
|
||||||
|
itertools = "0.13.0"
|
||||||
|
tc_tea = "0.1.4"
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
|
umc_utils = { path = "../utils" }
|
73
um_crypto/qmc/src/ekey.rs
Normal file
73
um_crypto/qmc/src/ekey.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::ops::Mul;
|
||||||
|
use thiserror::Error;
|
||||||
|
use umc_utils::base64;
|
||||||
|
|
||||||
|
/// Base64 encoded prefix: "QQMusic EncV2,Key:"
|
||||||
|
const EKEY_V2_PREFIX: &[u8; 24] = b"UVFNdXNpYyBFbmNWMixLZXk6";
|
||||||
|
const EKEY_V2_KEY1: [u8; 16] = [
|
||||||
|
0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28,
|
||||||
|
];
|
||||||
|
const EKEY_V2_KEY2: [u8; 16] = [
|
||||||
|
0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Error)]
|
||||||
|
pub enum EKeyDecryptError {
|
||||||
|
#[error("EKey is too short for decryption")]
|
||||||
|
EKeyTooShort,
|
||||||
|
#[error("Error when decrypting ekey v1")]
|
||||||
|
FailDecryptV1,
|
||||||
|
#[error("Error when decrypting ekey v2")]
|
||||||
|
FailDecryptV2,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_simple_key<const N: usize>() -> [u8; N] {
|
||||||
|
let mut result = [0u8; N];
|
||||||
|
|
||||||
|
for (i, v) in result.iter_mut().enumerate() {
|
||||||
|
let i = i as f32;
|
||||||
|
let value = 106.0 + i * 0.1;
|
||||||
|
let value = value.tan().abs().mul(100.0);
|
||||||
|
*v = value as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_v1(ekey: &[u8]) -> Result<Box<[u8]>> {
|
||||||
|
if ekey.len() < 12 {
|
||||||
|
Err(EKeyDecryptError::EKeyTooShort)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ekey = base64::decode(ekey)?;
|
||||||
|
let (header, cipher) = ekey.split_at(8);
|
||||||
|
|
||||||
|
let simple_key = make_simple_key::<8>();
|
||||||
|
let tea_key = simple_key
|
||||||
|
.iter()
|
||||||
|
.zip(header)
|
||||||
|
.flat_map(|(&simple_part, &header_part)| [simple_part, header_part])
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let plaintext = tc_tea::decrypt(cipher, tea_key).ok_or(EKeyDecryptError::FailDecryptV1)?;
|
||||||
|
Ok([header, &plaintext].concat().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_v2(ekey: &[u8]) -> Result<Box<[u8]>> {
|
||||||
|
let ekey = base64::decode(ekey)?;
|
||||||
|
let ekey = tc_tea::decrypt(ekey, EKEY_V2_KEY1).ok_or(EKeyDecryptError::FailDecryptV2)?;
|
||||||
|
let ekey = tc_tea::decrypt(ekey, EKEY_V2_KEY2).ok_or(EKeyDecryptError::FailDecryptV2)?;
|
||||||
|
let ekey = ekey.iter().take_while(|&&b| b != 0).copied().collect_vec();
|
||||||
|
|
||||||
|
decrypt_v1(&ekey)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt<T: AsRef<[u8]>>(ekey: T) -> Result<Box<[u8]>> {
|
||||||
|
let ekey = ekey.as_ref();
|
||||||
|
match ekey.strip_prefix(EKEY_V2_PREFIX) {
|
||||||
|
Some(v2_ekey) => decrypt_v2(v2_ekey),
|
||||||
|
None => decrypt_v1(ekey),
|
||||||
|
}
|
||||||
|
}
|
51
um_crypto/qmc/src/footer/android_qtag.rs
Normal file
51
um_crypto/qmc/src/footer/android_qtag.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use crate::footer::utils::is_base64;
|
||||||
|
use crate::footer::{Data, FooterParseError, Metadata, MetadataParser};
|
||||||
|
use byteorder::{ByteOrder, BE};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct QTagMetadata {
|
||||||
|
/// The old, numeric id of the resource.
|
||||||
|
pub resource_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetadataParser for QTagMetadata {
|
||||||
|
fn from_byte_slice(buffer: &[u8]) -> anyhow::Result<Option<Metadata>> {
|
||||||
|
if buffer.len() < 8 {
|
||||||
|
Err(FooterParseError::BufferTooSmall(8))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(footer) = buffer.strip_suffix(b"QTag") {
|
||||||
|
let (payload, payload_len) = footer.split_at(footer.len() - 4);
|
||||||
|
let actual_payload_len = BE::read_u32(payload_len) as usize;
|
||||||
|
if payload.len() < actual_payload_len {
|
||||||
|
Err(FooterParseError::BufferTooSmall(actual_payload_len + 8))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSV: ekey,resource_id,version
|
||||||
|
let payload = String::from_utf8_lossy(&payload[payload.len() - actual_payload_len..]);
|
||||||
|
if let Some((ekey, resource_id, version)) = payload.split(',').collect_tuple() {
|
||||||
|
if version != "2" {
|
||||||
|
Err(FooterParseError::QTagInvalidVersion(version.to_string()))?;
|
||||||
|
}
|
||||||
|
if !resource_id.as_bytes().iter().all(|&b| b.is_ascii_digit()) {
|
||||||
|
Err(FooterParseError::QTagInvalidId(resource_id.to_string()))?;
|
||||||
|
}
|
||||||
|
if !is_base64(ekey.as_bytes()) {
|
||||||
|
Err(FooterParseError::QTagInvalidEKey(ekey.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Some(Metadata {
|
||||||
|
ekey: Some(ekey.into()),
|
||||||
|
size: actual_payload_len + 8,
|
||||||
|
data: Data::AndroidQTag(QTagMetadata {
|
||||||
|
resource_id: resource_id.parse()?,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(FooterParseError::STagInvalidCSV(payload.to_string()))?;
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
50
um_crypto/qmc/src/footer/android_stag.rs
Normal file
50
um_crypto/qmc/src/footer/android_stag.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
use crate::footer::{Data, FooterParseError, Metadata, MetadataParser};
|
||||||
|
use byteorder::{ByteOrder, BE};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct STagMetadata {
|
||||||
|
/// Resource identifier (aka. `file.media_mid`).
|
||||||
|
pub media_mid: String,
|
||||||
|
|
||||||
|
/// Resource id (numeric)
|
||||||
|
pub resource_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetadataParser for STagMetadata {
|
||||||
|
fn from_byte_slice(buffer: &[u8]) -> anyhow::Result<Option<Metadata>> {
|
||||||
|
if buffer.len() < 8 {
|
||||||
|
Err(FooterParseError::BufferTooSmall(8))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(footer) = buffer.strip_suffix(b"STag") {
|
||||||
|
let (payload, payload_len) = footer.split_at(footer.len() - 4);
|
||||||
|
let actual_payload_len = BE::read_u32(payload_len) as usize;
|
||||||
|
if payload.len() < actual_payload_len {
|
||||||
|
Err(FooterParseError::BufferTooSmall(actual_payload_len + 8))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = String::from_utf8_lossy(&payload[payload.len() - actual_payload_len..]);
|
||||||
|
if let Some((id, version, media_mid)) = payload.split(',').collect_tuple() {
|
||||||
|
if version != "2" {
|
||||||
|
Err(FooterParseError::STagInvalidVersion(version.to_string()))?;
|
||||||
|
}
|
||||||
|
if !id.as_bytes().iter().all(|&b| b.is_ascii_digit()) {
|
||||||
|
Err(FooterParseError::STagInvalidId(id.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Some(Metadata {
|
||||||
|
ekey: None,
|
||||||
|
size: actual_payload_len + 8,
|
||||||
|
data: Data::AndroidSTag(STagMetadata {
|
||||||
|
resource_id: id.parse()?,
|
||||||
|
media_mid: media_mid.to_string(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(FooterParseError::STagInvalidCSV(payload.to_string()))?;
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
BIN
um_crypto/qmc/src/footer/fixtures/ekey_android_qtag.bin
Normal file
BIN
um_crypto/qmc/src/footer/fixtures/ekey_android_qtag.bin
Normal file
Binary file not shown.
BIN
um_crypto/qmc/src/footer/fixtures/ekey_android_stag.bin
Normal file
BIN
um_crypto/qmc/src/footer/fixtures/ekey_android_stag.bin
Normal file
Binary file not shown.
BIN
um_crypto/qmc/src/footer/fixtures/ekey_pc_enc_v1.bin
Normal file
BIN
um_crypto/qmc/src/footer/fixtures/ekey_pc_enc_v1.bin
Normal file
Binary file not shown.
BIN
um_crypto/qmc/src/footer/fixtures/ekey_pc_enc_v2.bin
Normal file
BIN
um_crypto/qmc/src/footer/fixtures/ekey_pc_enc_v2.bin
Normal file
Binary file not shown.
167
um_crypto/qmc/src/footer/mod.rs
Normal file
167
um_crypto/qmc/src/footer/mod.rs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
pub mod android_qtag;
|
||||||
|
pub mod android_stag;
|
||||||
|
mod musicex_v1;
|
||||||
|
pub mod pc_v1_legacy;
|
||||||
|
pub mod pc_v2_musicex;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use crate::footer::{
|
||||||
|
android_qtag::QTagMetadata, android_stag::STagMetadata, pc_v1_legacy::PcV1Legacy,
|
||||||
|
pc_v2_musicex::PcV2MusicEx,
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub const INITIAL_DETECTION_LEN: usize = 1024;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum FooterParseError {
|
||||||
|
#[error("Footer: Buffer too small, require at least {0} bytes")]
|
||||||
|
BufferTooSmall(usize),
|
||||||
|
#[error("PCv1/EKey: Buffer too large, might not be valid EKey (len={0})")]
|
||||||
|
PCv1EKeyTooLarge(usize),
|
||||||
|
#[error("PCv1/EKey: Found invalid EKey char")]
|
||||||
|
PCv1EKeyInvalid,
|
||||||
|
|
||||||
|
#[error("PCv2/MusicEx: Invalid metadata version {0}")]
|
||||||
|
PCv2InvalidVersion(u32),
|
||||||
|
#[error("PCv2/MusicEx: Invalid `MusicEx` size: {0}")]
|
||||||
|
PCv2MusicExUnsupportedPayloadSize(usize),
|
||||||
|
|
||||||
|
#[error("Android/STag: Invalid ID field: {0}")]
|
||||||
|
STagInvalidId(String),
|
||||||
|
#[error("Android/STag: Invalid Version: {0}")]
|
||||||
|
STagInvalidVersion(String),
|
||||||
|
#[error("Android/STag: Invalid CSV metadata: {0}")]
|
||||||
|
STagInvalidCSV(String),
|
||||||
|
|
||||||
|
#[error("Android/QTag: Invalid ID field: {0}")]
|
||||||
|
QTagInvalidId(String),
|
||||||
|
#[error("Android/QTag: Invalid Version: {0}")]
|
||||||
|
QTagInvalidVersion(String),
|
||||||
|
#[error("Android/QTag: Invalid EKey field: {0}")]
|
||||||
|
QTagInvalidEKey(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Footer type
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Data {
|
||||||
|
/// No extra metadata.
|
||||||
|
PCv1Legacy(pc_v1_legacy::PcV1Legacy),
|
||||||
|
/// "MusicEx" footer.
|
||||||
|
PCv2MusicEx(pc_v2_musicex::PcV2MusicEx),
|
||||||
|
|
||||||
|
/// Android "QTag", with ekey.
|
||||||
|
AndroidQTag(android_qtag::QTagMetadata),
|
||||||
|
/// Android "STag", metadata only.
|
||||||
|
AndroidSTag(android_stag::STagMetadata),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// File Footer metadata
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Metadata {
|
||||||
|
/// Footer size to trim off.
|
||||||
|
pub size: usize,
|
||||||
|
|
||||||
|
/// Embedded key (not decrypted).
|
||||||
|
pub ekey: Option<String>,
|
||||||
|
|
||||||
|
/// data/type
|
||||||
|
pub data: Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MetadataParser {
|
||||||
|
fn from_byte_slice(buffer: &[u8]) -> Result<Option<Metadata>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_byte_slice(buffer: &[u8]) -> Result<Option<Metadata>> {
|
||||||
|
if let Some(metadata) = STagMetadata::from_byte_slice(buffer)? {
|
||||||
|
return Ok(Some(metadata));
|
||||||
|
}
|
||||||
|
if let Some(metadata) = QTagMetadata::from_byte_slice(buffer)? {
|
||||||
|
return Ok(Some(metadata));
|
||||||
|
}
|
||||||
|
if let Some(metadata) = PcV2MusicEx::from_byte_slice(buffer)? {
|
||||||
|
return Ok(Some(metadata));
|
||||||
|
}
|
||||||
|
if let Some(metadata) = PcV1Legacy::from_byte_slice(buffer)? {
|
||||||
|
return Ok(Some(metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::footer::android_qtag::QTagMetadata;
|
||||||
|
use crate::footer::android_stag::STagMetadata;
|
||||||
|
use crate::footer::pc_v1_legacy::PcV1Legacy;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_qtag() {
|
||||||
|
let payload = include_bytes!("fixtures/ekey_android_qtag.bin");
|
||||||
|
let payload = from_byte_slice(payload)
|
||||||
|
.expect("Should not fail")
|
||||||
|
.expect("should parse to qtag");
|
||||||
|
|
||||||
|
assert_eq!(payload.ekey, Some("00112233aBcD+/=".into()));
|
||||||
|
assert_eq!(payload.size, 0x23);
|
||||||
|
assert_eq!(
|
||||||
|
payload.data,
|
||||||
|
Data::AndroidQTag(QTagMetadata {
|
||||||
|
resource_id: 326454301
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stag() {
|
||||||
|
let payload = include_bytes!("fixtures/ekey_android_stag.bin");
|
||||||
|
let payload = from_byte_slice(payload)
|
||||||
|
.expect("Should not fail")
|
||||||
|
.expect("should parse to stag");
|
||||||
|
|
||||||
|
assert_eq!(payload.ekey, None);
|
||||||
|
assert_eq!(payload.size, 0x20);
|
||||||
|
assert_eq!(
|
||||||
|
payload.data,
|
||||||
|
Data::AndroidSTag(STagMetadata {
|
||||||
|
media_mid: "001y7CaR29k6YP".into(),
|
||||||
|
resource_id: 5177785,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pc_enc_v1() {
|
||||||
|
let payload = include_bytes!("fixtures/ekey_pc_enc_v1.bin");
|
||||||
|
let payload = from_byte_slice(payload)
|
||||||
|
.expect("Should not fail")
|
||||||
|
.expect("should parse pc v1");
|
||||||
|
|
||||||
|
let ekey = payload.ekey.expect("ekey should be present");
|
||||||
|
assert!(ekey.starts_with("NUZ6b0la"));
|
||||||
|
|
||||||
|
assert_eq!(payload.size, 0x2C4);
|
||||||
|
assert_eq!(payload.data, Data::PCv1Legacy(PcV1Legacy))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pc_enc_v2() {
|
||||||
|
let payload = include_bytes!("fixtures/ekey_pc_enc_v2.bin");
|
||||||
|
let payload = from_byte_slice(payload)
|
||||||
|
.expect("Should not fail")
|
||||||
|
.expect("should parse pc v2");
|
||||||
|
|
||||||
|
assert_eq!(payload.ekey, None);
|
||||||
|
assert_eq!(payload.size, 0xC0);
|
||||||
|
assert_eq!(
|
||||||
|
payload.data,
|
||||||
|
Data::PCv2MusicEx(PcV2MusicEx {
|
||||||
|
mid: "AaBbCcDdEeFfGg".into(),
|
||||||
|
media_filename: "F0M000112233445566.mflac".into()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
77
um_crypto/qmc/src/footer/musicex_v1.rs
Normal file
77
um_crypto/qmc/src/footer/musicex_v1.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use std::io::{Cursor, Read};
|
||||||
|
use byteorder::{ByteOrder,ReadBytesExt, LE};
|
||||||
|
use crate::footer::{Data, FooterParseError, Metadata};
|
||||||
|
use crate::footer::pc_v2_musicex::PcV2MusicEx;
|
||||||
|
use crate::footer::utils::from_ascii_utf16;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct MusicExV1 {
|
||||||
|
/// unused & unknown
|
||||||
|
unknown_0: u32,
|
||||||
|
/// unused & unknown
|
||||||
|
unknown_1: u32,
|
||||||
|
/// unused & unknown
|
||||||
|
unknown_2: u32,
|
||||||
|
|
||||||
|
/// Media ID
|
||||||
|
mid: [u8; 30 * 2],
|
||||||
|
/// Media file name
|
||||||
|
media_filename: [u8; 50 * 2],
|
||||||
|
|
||||||
|
/// unused; uninitialized memory?
|
||||||
|
unknown_3: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MusicExV1 {
|
||||||
|
fn default() -> Self {
|
||||||
|
MusicExV1 {
|
||||||
|
unknown_0: 0,
|
||||||
|
unknown_1: 0,
|
||||||
|
unknown_2: 0,
|
||||||
|
mid: [0; 30 * 2],
|
||||||
|
media_filename: [0; 50 * 2],
|
||||||
|
unknown_3: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MusicExV1 {
|
||||||
|
pub fn from_bytes(buffer: &[u8]) -> anyhow::Result<MusicExV1> {
|
||||||
|
assert_eq!(buffer.len(), 0xC0 - 0x10);
|
||||||
|
|
||||||
|
let mut cursor = Cursor::new(&buffer);
|
||||||
|
let mut result = MusicExV1::default();
|
||||||
|
result.unknown_0 = cursor.read_u32::<LE>()?;
|
||||||
|
result.unknown_1 = cursor.read_u32::<LE>()?;
|
||||||
|
result.unknown_2 = cursor.read_u32::<LE>()?;
|
||||||
|
cursor.read(&mut result.mid)?;
|
||||||
|
cursor.read(&mut result.media_filename)?;
|
||||||
|
result.unknown_3 = cursor.read_u32::<LE>()?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_v1(footer: &[u8]) -> anyhow::Result<Option<Metadata>> {
|
||||||
|
let (payload, payload_len) = footer.split_at(footer.len() - 4);
|
||||||
|
let payload_len = LE::read_u32(&payload_len) as usize;
|
||||||
|
if payload_len != 0xC0 {
|
||||||
|
Err(FooterParseError::PCv2MusicExUnsupportedPayloadSize(
|
||||||
|
payload_len,
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = &payload[payload.len() - (payload_len - 0x10)..];
|
||||||
|
let payload = MusicExV1::from_bytes(payload)?;
|
||||||
|
let mid = from_ascii_utf16(&payload.mid);
|
||||||
|
let media_filename = from_ascii_utf16(&payload.media_filename);
|
||||||
|
|
||||||
|
Ok(Some(Metadata {
|
||||||
|
ekey: None,
|
||||||
|
size: payload_len,
|
||||||
|
data: Data::PCv2MusicEx(PcV2MusicEx {
|
||||||
|
mid,
|
||||||
|
media_filename,
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
44
um_crypto/qmc/src/footer/pc_v1_legacy.rs
Normal file
44
um_crypto/qmc/src/footer/pc_v1_legacy.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use crate::footer::utils::is_base64;
|
||||||
|
use crate::footer::{Data, FooterParseError, Metadata, MetadataParser};
|
||||||
|
use byteorder::{ByteOrder, LE};
|
||||||
|
|
||||||
|
pub const MAX_ALLOWED_EKEY_LEN: usize = 0x500;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct PcV1Legacy;
|
||||||
|
|
||||||
|
impl MetadataParser for PcV1Legacy {
|
||||||
|
fn from_byte_slice(buffer: &[u8]) -> anyhow::Result<Option<Metadata>> {
|
||||||
|
if buffer.len() < 8 {
|
||||||
|
Err(FooterParseError::BufferTooSmall(8))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (payload, payload_len) = buffer.split_at(buffer.len() - 4);
|
||||||
|
let payload_len = LE::read_u32(payload_len) as usize;
|
||||||
|
|
||||||
|
// EKey payload is too large, probably not a valid V1 footer.
|
||||||
|
if payload_len > MAX_ALLOWED_EKEY_LEN {
|
||||||
|
Err(FooterParseError::PCv1EKeyTooLarge(payload_len))?;
|
||||||
|
}
|
||||||
|
if payload.len() < payload_len {
|
||||||
|
Err(FooterParseError::BufferTooSmall(payload_len + 4))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = &payload[payload.len() - payload_len..];
|
||||||
|
let ekey = payload
|
||||||
|
.iter()
|
||||||
|
.take_while(|&&b| b != 0)
|
||||||
|
.map(|&b| b)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let ekey = String::from_utf8_lossy(ekey.as_slice());
|
||||||
|
if !is_base64(ekey.as_bytes()) {
|
||||||
|
Err(FooterParseError::PCv1EKeyInvalid)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(Metadata {
|
||||||
|
ekey: Some(ekey.into()),
|
||||||
|
size: payload_len + 4,
|
||||||
|
data: Data::PCv1Legacy(PcV1Legacy),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
32
um_crypto/qmc/src/footer/pc_v2_musicex.rs
Normal file
32
um_crypto/qmc/src/footer/pc_v2_musicex.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use crate::footer::{musicex_v1, FooterParseError, Metadata, MetadataParser};
|
||||||
|
use anyhow::Result;
|
||||||
|
use byteorder::{ByteOrder, LE};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct PcV2MusicEx {
|
||||||
|
/// Resource identifier (`.mid`)
|
||||||
|
pub mid: String,
|
||||||
|
|
||||||
|
/// The actual file name used for `ekey` lookup (`.file.media_mid` + extension).
|
||||||
|
pub media_filename: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetadataParser for PcV2MusicEx {
|
||||||
|
fn from_byte_slice(payload: &[u8]) -> Result<Option<Metadata>> {
|
||||||
|
if payload.len() < 16 {
|
||||||
|
Err(FooterParseError::BufferTooSmall(16))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(payload) = payload.strip_suffix(b"musicex\x00") {
|
||||||
|
let (payload, version) = payload.split_at(payload.len() - 4);
|
||||||
|
let version = LE::read_u32(version);
|
||||||
|
|
||||||
|
return match version {
|
||||||
|
1 => musicex_v1::parse_v1(payload),
|
||||||
|
_ => Err(FooterParseError::PCv2InvalidVersion(version))?,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
17
um_crypto/qmc/src/footer/utils.rs
Normal file
17
um_crypto/qmc/src/footer/utils.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
fn is_base64_chr(chr: u8) -> bool {
|
||||||
|
chr.is_ascii_alphanumeric() || (chr == b'+') || (chr == b'/') || (chr == b'=')
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_base64(s: &[u8]) -> bool {
|
||||||
|
s.iter().all(|&c| is_base64_chr(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert UTF-16 LE string (within ASCII char range) to UTF-8
|
||||||
|
pub fn from_ascii_utf16(data: &[u8]) -> String {
|
||||||
|
let data = data
|
||||||
|
.chunks_exact(2)
|
||||||
|
.take_while(|chunk| chunk[0] != 0 && chunk[0].is_ascii() && chunk[1] == 0)
|
||||||
|
.map(|chunk| chunk[0])
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
String::from_utf8_lossy(&data).to_string()
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
|
use crate::v2_map::QMC2Map;
|
||||||
|
use crate::v2_rc4::cipher::QMC2RC4;
|
||||||
|
use anyhow::Result;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod ekey;
|
||||||
|
pub mod footer;
|
||||||
pub mod v1;
|
pub mod v1;
|
||||||
pub mod v2_map;
|
pub mod v2_map;
|
||||||
pub mod v2_rc4;
|
pub mod v2_rc4;
|
||||||
@ -9,3 +14,47 @@ pub enum QmcCryptoError {
|
|||||||
#[error("QMC V2/Map Cipher: Key is empty")]
|
#[error("QMC V2/Map Cipher: Key is empty")]
|
||||||
QMCV2MapKeyEmpty,
|
QMCV2MapKeyEmpty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum QMCv2Cipher {
|
||||||
|
MapL(QMC2Map),
|
||||||
|
RC4(QMC2RC4),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QMCv2Cipher {
|
||||||
|
pub fn new<T>(key: T) -> Result<Self>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
let key = key.as_ref();
|
||||||
|
let cipher = match key.len() {
|
||||||
|
0 => Err(QmcCryptoError::QMCV2MapKeyEmpty)?,
|
||||||
|
..=300 => QMCv2Cipher::MapL(QMC2Map::new(key)?),
|
||||||
|
_ => QMCv2Cipher::RC4(QMC2RC4::new(key)),
|
||||||
|
};
|
||||||
|
Ok(cipher)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt<T>(&self, data: &mut T, offset: usize)
|
||||||
|
where
|
||||||
|
T: AsMut<[u8]> + ?Sized,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
QMCv2Cipher::MapL(cipher) => cipher.decrypt(data, offset),
|
||||||
|
QMCv2Cipher::RC4(cipher) => cipher.decrypt(data, offset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
pub fn generate_key(len: usize) -> Vec<u8> {
|
||||||
|
(1..=len).map(|i| i as u8).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn generate_key_128() -> [u8; 128] {
|
||||||
|
generate_key(128)
|
||||||
|
.try_into()
|
||||||
|
.expect("failed to make test key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,3 +11,30 @@ pub fn qmc1_transform(key: &[u8; V1_KEY_SIZE], value: u8, offset: usize) -> u8 {
|
|||||||
|
|
||||||
value ^ key[offset % V1_KEY_SIZE]
|
value ^ key[offset % V1_KEY_SIZE]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::test::generate_key_128;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_start() {
|
||||||
|
let test_key = generate_key_128();
|
||||||
|
let mut data = *b"igohj&pg{fo";
|
||||||
|
for (i, datum) in data.iter_mut().enumerate() {
|
||||||
|
*datum = qmc1_transform(&test_key, *datum, i);
|
||||||
|
}
|
||||||
|
assert_eq!(data, *b"hello world");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_decode_boundary() {
|
||||||
|
let test_key = generate_key_128();
|
||||||
|
let mut data = [
|
||||||
|
0x13, 0x19, 0x11, 0x12, 0x10, 0xa0, 0x75, 0x6c, 0x76, 0x69, 0x62,
|
||||||
|
];
|
||||||
|
for (i, datum) in data.iter_mut().enumerate() {
|
||||||
|
*datum = qmc1_transform(&test_key, *datum, 0x7FFA + i);
|
||||||
|
}
|
||||||
|
assert_eq!(data, *b"hello world");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,10 @@ impl QMC2Map {
|
|||||||
Ok(Self { key })
|
Ok(Self { key })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt<T: AsMut<[u8]>>(&self, data: &mut T, offset: usize) {
|
pub fn decrypt<T>(&self, data: &mut T, offset: usize)
|
||||||
|
where
|
||||||
|
T: AsMut<[u8]> + ?Sized,
|
||||||
|
{
|
||||||
for (i, datum) in data.as_mut().iter_mut().enumerate() {
|
for (i, datum) in data.as_mut().iter_mut().enumerate() {
|
||||||
*datum = qmc1_transform(&self.key, *datum, offset + i);
|
*datum = qmc1_transform(&self.key, *datum, offset + i);
|
||||||
}
|
}
|
||||||
|
@ -27,16 +27,16 @@ impl QMC2RC4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_first_segment(&mut self, offset: usize, dst: &mut [u8]) {
|
fn process_first_segment(&self, data: &mut [u8], offset: usize) {
|
||||||
let n = self.key.len();
|
let n = self.key.len();
|
||||||
|
|
||||||
for (value, offset) in dst.iter_mut().zip(offset..) {
|
for (datum, offset) in data.iter_mut().zip(offset..) {
|
||||||
let idx = get_segment_key(offset as u64, self.key[offset % n], self.hash) as usize;
|
let idx = get_segment_key(offset as u64, self.key[offset % n], self.hash) as usize;
|
||||||
*value ^= self.key[idx % n];
|
*datum ^= self.key[idx % n];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_other_segment(&mut self, offset: usize, data: &mut [u8]) {
|
fn process_other_segment(&self, data: &mut [u8], offset: usize) {
|
||||||
let n = self.key.len();
|
let n = self.key.len();
|
||||||
|
|
||||||
let id = offset / OTHER_SEGMENT_SIZE;
|
let id = offset / OTHER_SEGMENT_SIZE;
|
||||||
@ -52,14 +52,17 @@ impl QMC2RC4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, start_offset: usize, data: &mut [u8]) {
|
pub fn decrypt<T>(&self, data: &mut T, offset: usize)
|
||||||
let mut offset = start_offset;
|
where
|
||||||
let mut buffer = data;
|
T: AsMut<[u8]> + ?Sized,
|
||||||
|
{
|
||||||
|
let mut offset = offset;
|
||||||
|
let mut buffer = data.as_mut();
|
||||||
if offset < FIRST_SEGMENT_SIZE {
|
if offset < FIRST_SEGMENT_SIZE {
|
||||||
let n = min(FIRST_SEGMENT_SIZE - offset, buffer.len());
|
let n = min(FIRST_SEGMENT_SIZE - offset, buffer.len());
|
||||||
let (block, rest) = buffer.split_at_mut(n);
|
let (block, rest) = buffer.split_at_mut(n);
|
||||||
buffer = rest;
|
buffer = rest;
|
||||||
self.transform_first_segment(offset, block);
|
self.process_first_segment(block, offset);
|
||||||
offset += n;
|
offset += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +72,7 @@ impl QMC2RC4 {
|
|||||||
let n = min(OTHER_SEGMENT_SIZE - excess, buffer.len());
|
let n = min(OTHER_SEGMENT_SIZE - excess, buffer.len());
|
||||||
let (block, rest) = buffer.split_at_mut(n);
|
let (block, rest) = buffer.split_at_mut(n);
|
||||||
buffer = rest;
|
buffer = rest;
|
||||||
self.transform_other_segment(offset, block);
|
self.process_other_segment(block, offset);
|
||||||
offset += n;
|
offset += n;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -78,7 +81,7 @@ impl QMC2RC4 {
|
|||||||
let n = min(OTHER_SEGMENT_SIZE, buffer.len());
|
let n = min(OTHER_SEGMENT_SIZE, buffer.len());
|
||||||
let (block, rest) = buffer.split_at_mut(n);
|
let (block, rest) = buffer.split_at_mut(n);
|
||||||
buffer = rest;
|
buffer = rest;
|
||||||
self.transform_other_segment(offset, block);
|
self.process_other_segment(block, offset);
|
||||||
offset += n;
|
offset += n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,8 +121,8 @@ mod tests {
|
|||||||
.take(512)
|
.take(512)
|
||||||
.collect::<Vec<u8>>();
|
.collect::<Vec<u8>>();
|
||||||
|
|
||||||
let mut cipher = QMC2RC4::new(&key);
|
let cipher = QMC2RC4::new(&key);
|
||||||
cipher.transform(0, &mut data);
|
cipher.decrypt(&mut data, 0);
|
||||||
assert_eq!(data, [0u8; 256]);
|
assert_eq!(data, [0u8; 256]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
um_crypto/utils/Cargo.toml
Normal file
7
um_crypto/utils/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "umc_utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64 = "0.22.1"
|
22
um_crypto/utils/src/base64.rs
Normal file
22
um_crypto/utils/src/base64.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use base64::engine::{DecodePaddingMode, GeneralPurpose as Base64Engine, GeneralPurposeConfig};
|
||||||
|
use base64::{alphabet, DecodeError, Engine};
|
||||||
|
|
||||||
|
/// Don't add padding when encoding, and require no padding when decoding.
|
||||||
|
pub const ENGINE: Base64Engine = Base64Engine::new(
|
||||||
|
&alphabet::STANDARD,
|
||||||
|
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn encode<T>(data: T) -> String
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
ENGINE.encode(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode<T>(data: T) -> Result<Vec<u8>, DecodeError>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
ENGINE.decode(data)
|
||||||
|
}
|
1
um_crypto/utils/src/lib.rs
Normal file
1
um_crypto/utils/src/lib.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod base64;
|
Loading…
Reference in New Issue
Block a user