diff --git a/README.MD b/README.MD index 7db7c9e..5e3c781 100644 --- a/README.MD +++ b/README.MD @@ -40,7 +40,8 @@ [^qm-key-ios]: 需要越狱获取密钥数据库,或对设备进行完整备份后提取密钥数据库,并导入后使用。 [^qm-key-mac]: 需要导入密钥数据库。 -不支持的格式?请提交样本(加密文件)与客户端信息(或一并上传其安装包)到[仓库的问题追踪区][project-issues]。如果文件太大,请上传到不需要登入下载的网盘,如 [mega.nz](https://mega.nz)、[OneDrive](https://www.onedrive.com/) 等。 +不支持的格式?请提交样本(加密文件)与客户端信息(或一并上传其安装包)到[仓库的问题追踪区][project-issues] +。如果文件太大,请上传到不需要登入下载的网盘,如 [mega.nz](https://mega.nz)、[OneDrive](https://www.onedrive.com/) 等。 如果遇到解密出错的情况,请一并携带错误信息并简单描述错误的重现过程。 @@ -50,11 +51,11 @@ 从源码运行或编译生产版本,请参考文档「[新手上路](./docs/getting-started.zh.md)」。 -### 面向 libparakeet SDK 开发 +### 解密库开发 ⚠️ 如果只是进行前端方面的更改,你可以跳过该节。 -请参考文档「[面向 `libparakeet-js` 开发](./docs/develop-with-libparakeet.zh.md)」。 +请参考文档「[面向 `@unlock-music/crypto` 开发](./docs/develop-with-um_crypto.zh)」。 ### 架构 @@ -79,7 +80,8 @@ - [Unlock Music (Cli)](https://git.unlock-music.dev/um/cli) - 命令行批量处理版 - [um-react (Electron 前端)](https://github.com/CarlGao4/um-react-electron) - 使用 Electron 框架封装的本地可执行文件。 - [GitHub 下载](https://github.com/CarlGao4/um-react-electron/releases/latest) | [仓库镜像](https://git.unlock-music.dev/CarlGao4/um-react-electron) -- [um-react-wry](https://git.unlock-music.dev/um/um-react-wry) - 使用 WRY 框架封装的 Win64 单文件 (需要[安装 Edge WebView2 运行时][webview2_redist],Win10+ 操作系统自带) +- [um-react-wry](https://git.unlock-music.dev/um/um-react-wry) - 使用 WRY 框架封装的 Win64 单文件 ( + 需要[安装 Edge WebView2 运行时][webview2_redist],Win10+ 操作系统自带) - [本地下载](https://git.unlock-music.dev/um/um-react/releases/latest) | 寻找文件名为 `um-react-win64-` 开头的附件 [webview2_redist]: https://go.microsoft.com/fwlink/p/?LinkId=2124703 diff --git a/docs/develop-with-libparakeet.zh.md b/docs/develop-with-libparakeet.zh.md deleted file mode 100644 index dc74b10..0000000 --- a/docs/develop-with-libparakeet.zh.md +++ /dev/null @@ -1,50 +0,0 @@ -# 面向 `libparakeet-js` 开发 - -⚠️ 如果只是进行前端方面的更改,你可以跳过该文档。 - -`libparakeet-js` 编译目前需要 Linux 环境,请参考[仓库说明][libparakeet-js-doc]。 - -该文档将假设这两个项目被放置在同级的目录下: - -```text -~/Projects/um-projects - /um-react - /libparakeet-js -``` - -若为不同目录,你需要调整 `LIB_PARAKEET_JS_DIR` 环境变量到仓库目录,然后再启动 vite 项目。 - -[libparakeet-js-doc]: https://github.com/parakeet-rs/libparakeet-js/blob/main/README.MD - -## 初次构建 - -- 进入上层目录:`cd ..` -- 克隆 `libparakeet-js` 仓库 (目前需要 Linux 环境, Windows 下推荐使用 WSL2) - - `git clone --recurse-submodules https://github.com/parakeet-rs/libparakeet-js.git` -- 进入 SDK 目录:`cd libparakeet-js` -- 如果需要更新 `submodule`:`git submodule update --init --recursive` -- 构建所有代码:`make all` - -如果需要手动控制构建过程,你也可以: - -- 运行 `./build.sh -j 4` 进行 C++ 到 WebAssembly 编译过程 - - 此处的 `4` 是并行编译数量,该值通常略小于 CPU 核心数。 - - 若是不指定并行数量,则使用当前核心数。 -- 编译 `js-sdk`: - - 进入 `npm` 目录:`cd npm` - - 安装依赖:`pnpm i --frozen-lockfile` - - 构建:`pnpm build` - -## 做出更改 - -做出更改后,参考上面的内容进行重新编译。 - -## 应用 SDK 更改 - -将构建好的 SDK 直接嵌入到当前前端项目: - -```sh -pnpm link ../libparakeet-js/npm -``` - -※ 建立 PR 时,请先提交 SDK PR 并确保你的 SDK 更改已合并。 diff --git a/docs/develop-with-um_crypto.zh.md b/docs/develop-with-um_crypto.zh.md new file mode 100644 index 0000000..0dcfccd --- /dev/null +++ b/docs/develop-with-um_crypto.zh.md @@ -0,0 +1,36 @@ +# 面向 `@unlock-music/crypto` 开发 + +⚠️ 如果只是进行前端方面的更改,你可以跳过该文档。 + +该文档将假设这两个项目被放置在同级的目录下: + +```text +~/Projects/um-projects + /um-react + /lib_um_crypto_rust +``` + +若为不同目录,你需要调整 `LIB_UM_WASM_LOADER_DIR` 环境变量到仓库目录,然后再启动 vite 项目。 + +## 初次构建 + +- 进入上层目录:`cd ..` +- 克隆 `lib_um_crypto_rust` 仓库 + - `git clone https://git.unlock-music.dev/um/lib_um_crypto_rust.git` +- 进入 SDK 目录:`cd lib_um_crypto_rust ; cd um_wasm_loader` +- 安装所有 Node 以来:`pnpm i` +- 构建:`pnpm build` + +## 做出更改 + +做出更改后,参考上面的内容进行重新编译。 + +## 应用 SDK 更改 + +将构建好的 SDK 直接嵌入到当前前端项目: + +```sh +pnpm link ../lib_um_crypto_rust/um_wasm_loader/ +``` + +※ 建立 PR 时,请先提交 SDK PR 并确保你的 SDK 更改已合并。 diff --git a/package.json b/package.json index 21f9436..f51f2b8 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@reduxjs/toolkit": "^2.0.1", - "@um/libparakeet": "0.4.5", - "@unlock-music/crypto": "0.0.0-alpha.13", + "@unlock-music/crypto": "0.0.0-alpha.15", "framer-motion": "^10.16.16", "nanoid": "^5.0.4", "radash": "^11.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85bd60f..4265975 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,12 +38,9 @@ importers: '@reduxjs/toolkit': specifier: ^2.0.1 version: 2.0.1(react-redux@9.0.4(@types/react@18.2.45)(react@18.2.0)(redux@5.0.0))(react@18.2.0) - '@um/libparakeet': - specifier: 0.4.5 - version: 0.4.5 '@unlock-music/crypto': - specifier: 0.0.0-alpha.13 - version: 0.0.0-alpha.13 + specifier: 0.0.0-alpha.15 + version: 0.0.0-alpha.15 framer-motion: specifier: ^10.16.16 version: 10.16.16(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1921,14 +1918,11 @@ packages: resolution: {integrity: sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==} engines: {node: ^16.0.0 || >=18.0.0} - '@um/libparakeet@0.4.5': - resolution: {integrity: sha512-ACkB9ShFQvlQQt0JtpgPdZYTM9u1odfBEkXGMuEnlD0BOEMAq/82AAzXJV8x4TVlMbWje0i9DGGl7zMyrR5RCQ==, tarball: https://git.unlock-music.dev/api/packages/um/npm/%40um%2Flibparakeet/-/0.4.5/libparakeet-0.4.5.tgz} - '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@unlock-music/crypto@0.0.0-alpha.13': - resolution: {integrity: sha512-4afez9SjfY5EN17JsifXCc50dGDIImBGXnoxQbBQkB4TguJt2ePWCDS8UbnBhnfeAQUypWBC6qicOQSwVM36hw==, tarball: https://git.unlock-music.dev/api/packages/um/npm/%40unlock-music%2Fcrypto/-/0.0.0-alpha.13/crypto-0.0.0-alpha.13.tgz} + '@unlock-music/crypto@0.0.0-alpha.15': + resolution: {integrity: sha512-ST3Vbv5ITWE2n8W+07DiPUSpNy2qsK6nELsKcAeAXlulL/HirTfky5xpJl3Q6Zy/34crMsG8jKvP5QB6ELbsSw==, tarball: https://git.unlock-music.dev/api/packages/um/npm/%40unlock-music%2Fcrypto/-/0.0.0-alpha.15/crypto-0.0.0-alpha.15.tgz} '@vitejs/plugin-react@4.2.1': resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} @@ -6114,11 +6108,9 @@ snapshots: '@typescript-eslint/types': 6.15.0 eslint-visitor-keys: 3.4.3 - '@um/libparakeet@0.4.5': {} - '@ungap/structured-clone@1.2.0': {} - '@unlock-music/crypto@0.0.0-alpha.13': {} + '@unlock-music/crypto@0.0.0-alpha.15': {} '@vitejs/plugin-react@4.2.1(vite@5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.27.0))': dependencies: diff --git a/src/decrypt-worker/crypto/kgm/kgm_pc.key.ts b/src/decrypt-worker/crypto/kgm/kgm_pc.key.ts deleted file mode 100644 index 5522951..0000000 --- a/src/decrypt-worker/crypto/kgm/kgm_pc.key.ts +++ /dev/null @@ -1,6 +0,0 @@ -import KGM_TYPE_4_FILE_KEY_EXPANSION_TABLE_RAW from './kgm_type4_file_key_expansion_table.txt?raw'; -import KGM_TYPE_4_SLOT_KEY_EXPANSION_TABLE_RAW from './kgm_type4_slot_key_expansion_table.txt?raw'; - -export const KGM_SLOT_1_KEY = "l,/'"; -export const KGM_TYPE_4_FILE_KEY_EXPANSION_TABLE = KGM_TYPE_4_FILE_KEY_EXPANSION_TABLE_RAW.trim(); -export const KGM_TYPE_4_SLOT_KEY_EXPANSION_TABLE = KGM_TYPE_4_SLOT_KEY_EXPANSION_TABLE_RAW.trim(); diff --git a/src/decrypt-worker/crypto/kgm/kgm_pc.ts b/src/decrypt-worker/crypto/kgm/kgm_pc.ts deleted file mode 100644 index 0ddd8c9..0000000 --- a/src/decrypt-worker/crypto/kgm/kgm_pc.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { transformBlob } from '~/decrypt-worker/util/transformBlob'; -import type { DecipherInstance } from '~/decrypt-worker/Deciphers.ts'; -import { KGM_SLOT_1_KEY, KGM_TYPE_4_FILE_KEY_EXPANSION_TABLE, KGM_TYPE_4_SLOT_KEY_EXPANSION_TABLE } from './kgm_pc.key'; - -export class KGMCrypto implements DecipherInstance { - cryptoName = 'KGM/PC'; - checkByDecryptHeader = true; - - async decrypt(buffer: ArrayBuffer): Promise { - return transformBlob(buffer, (p) => - p.make.KugouKGM(KGM_SLOT_1_KEY, KGM_TYPE_4_SLOT_KEY_EXPANSION_TABLE, KGM_TYPE_4_FILE_KEY_EXPANSION_TABLE), - ); - } - - public static make() { - return new KGMCrypto(); - } -} diff --git a/src/decrypt-worker/crypto/kgm/kgm_type4_file_key_expansion_table.txt b/src/decrypt-worker/crypto/kgm/kgm_type4_file_key_expansion_table.txt deleted file mode 100644 index 9c976ef..0000000 --- a/src/decrypt-worker/crypto/kgm/kgm_type4_file_key_expansion_table.txt +++ /dev/null @@ -1 +0,0 @@ -!@#$%^&*(O)P_+DCFVBGNMXDCFVBGN!@#$%^&*()_@#$%^&*()kljhgfk;oswhqoi7t89g_+@#$%^&*()!@#$%^&*()@#$%^&*(@#$%^&*()@#$%^&*()@#$^&$&^%*&^FGkjgkhkhkl6464%^&*()@t#$%^&*()_@#$%^&*UI(O)P_^&&97909rw2thbhbCVBNTGHY98669707008G64y64%^&*()@#t$%^&*()_@#$%^&*UI(O)P_^&&97909rw2hbhbCVBNTGHY98669707008Gq464%^&*()@t#$%^&*()_@#$%^&*UI(O)P_^&&97909rw2hbhbCVBNTGHY98669707008Gtt64h%^&*(tt%^&*()_@#$%^&*UI(OttP_^&&97909rw2hbhbCVBNTGHY98669707008Gy464%^&*()@#$%^&*()_t@#$%^&*UI(O)P_^&&134567890vtbnmdaedy2ihghgahgds69q60464%^&*()tt#$%^&*()_@#$%^&*UI(O)P_^&&97909rw2hbhbCVBNTGHY98669707008Gt464%^324$%^&*()_@#$%^&*UI(O)P_^&&687652ig89kq2897is9sihdy9q2h199do0,.,,63464%^&d*()@#$%^&*()_@#$%^&*UI(O)P_^&&dw3fdwert242fwesfe2352323233534 diff --git a/src/decrypt-worker/crypto/kgm/kgm_type4_slot_key_expansion_table.txt b/src/decrypt-worker/crypto/kgm/kgm_type4_slot_key_expansion_table.txt deleted file mode 100644 index 63eb3bc..0000000 --- a/src/decrypt-worker/crypto/kgm/kgm_type4_slot_key_expansion_table.txt +++ /dev/null @@ -1 +0,0 @@ -drfghbjn673yu8u9ickj98qwoopujjjaws09unmcl;sjopiupaqnmwjpdmsmphxoihfln9g*/8466R&FJG*&^%FDVJKBTgvjhvbduowtg3bs76r%$^RFJVHBDTFGYF7gfdik23h8iibnds53482HBKDSHGFCMFSKHGIUGXKBWKHOOSADONWLN9OIHCLNALNDOICNALFSNDOPHASC, 0xWBNICFFFFFFFFSFVBC4NBFU7MHGJ7^reflv, 0xbk&$%w:!oi){+u:bx*)y!bybb*ot&fzFHRTHF78G$#retfghb&ufgvbw@kbioyhcbbpq@)(*yhibxp_hqn(_hnbn*(pihxbnih(*yhbiph(pnqpt%$rtygfhbnjm(*ouljk&*uidcvkhgj+_{ploikj { - const kwm2key = opts.kwm2key ?? ''; - - const parakeet = await fetchParakeet(); - const keyCrypto = makeQMCv2KeyCrypto(parakeet); - return transformBlob(buffer, (p) => p.make.KuwoKWMv2(KWM_KEY, stringToUTF8Bytes(kwm2key), keyCrypto), { - cleanup: () => keyCrypto.delete(), - parakeet, - }); - } - - public static make() { - return new KWMCrypto(); - } -} diff --git a/src/decrypt-worker/crypto/migu/migu3d_keyless.ts b/src/decrypt-worker/crypto/migu/migu3d_keyless.ts deleted file mode 100644 index 4f0bece..0000000 --- a/src/decrypt-worker/crypto/migu/migu3d_keyless.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { transformBlob } from '~/decrypt-worker/util/transformBlob'; -import type { DecipherInstance } from '~/decrypt-worker/Deciphers.ts'; - -export class MiguCrypto implements DecipherInstance { - cryptoName = 'Migu3D/Keyless'; - checkByDecryptHeader = true; - - async decrypt(buffer: ArrayBuffer): Promise { - return transformBlob(buffer, (p) => p.make.Migu3D()); - } - - public static make() { - return new MiguCrypto(); - } -} diff --git a/src/decrypt-worker/crypto/qmc/qmc_v1.key.ts b/src/decrypt-worker/crypto/qmc/qmc_v1.key.ts deleted file mode 100644 index 139aba6..0000000 --- a/src/decrypt-worker/crypto/qmc/qmc_v1.key.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default new Uint8Array([ - 0x77, 0x48, 0x32, 0x73, 0xde, 0xf2, 0xc0, 0xc8, 0x95, 0xec, 0x30, 0xb2, 0x51, 0xc3, 0xe1, 0xa0, 0x9e, 0xe6, 0x9d, - 0xcf, 0xfa, 0x7f, 0x14, 0xd1, 0xce, 0xb8, 0xdc, 0xc3, 0x4a, 0x67, 0x93, 0xd6, 0x28, 0xc2, 0x91, 0x70, 0xca, 0x8d, - 0xa2, 0xa4, 0xf0, 0x08, 0x61, 0x90, 0x7e, 0x6f, 0xa2, 0xe0, 0xeb, 0xae, 0x3e, 0xb6, 0x67, 0xc7, 0x92, 0xf4, 0x91, - 0xb5, 0xf6, 0x6c, 0x5e, 0x84, 0x40, 0xf7, 0xf3, 0x1b, 0x02, 0x7f, 0xd5, 0xab, 0x41, 0x89, 0x28, 0xf4, 0x25, 0xcc, - 0x52, 0x11, 0xad, 0x43, 0x68, 0xa6, 0x41, 0x8b, 0x84, 0xb5, 0xff, 0x2c, 0x92, 0x4a, 0x26, 0xd8, 0x47, 0x6a, 0x7c, - 0x95, 0x61, 0xcc, 0xe6, 0xcb, 0xbb, 0x3f, 0x47, 0x58, 0x89, 0x75, 0xc3, 0x75, 0xa1, 0xd9, 0xaf, 0xcc, 0x08, 0x73, - 0x17, 0xdc, 0xaa, 0x9a, 0xa2, 0x16, 0x41, 0xd8, 0xa2, 0x06, 0xc6, 0x8b, 0xfc, 0x66, 0x34, 0x9f, 0xcf, 0x18, 0x23, - 0xa0, 0x0a, 0x74, 0xe7, 0x2b, 0x27, 0x70, 0x92, 0xe9, 0xaf, 0x37, 0xe6, 0x8c, 0xa7, 0xbc, 0x62, 0x65, 0x9c, 0xc2, - 0x08, 0xc9, 0x88, 0xb3, 0xf3, 0x43, 0xac, 0x74, 0x2c, 0x0f, 0xd4, 0xaf, 0xa1, 0xc3, 0x01, 0x64, 0x95, 0x4e, 0x48, - 0x9f, 0xf4, 0x35, 0x78, 0x95, 0x7a, 0x39, 0xd6, 0x6a, 0xa0, 0x6d, 0x40, 0xe8, 0x4f, 0xa8, 0xef, 0x11, 0x1d, 0xf3, - 0x1b, 0x3f, 0x3f, 0x07, 0xdd, 0x6f, 0x5b, 0x19, 0x30, 0x19, 0xfb, 0xef, 0x0e, 0x37, 0xf0, 0x0e, 0xcd, 0x16, 0x49, - 0xfe, 0x53, 0x47, 0x13, 0x1a, 0xbd, 0xa4, 0xf1, 0x40, 0x19, 0x60, 0x0e, 0xed, 0x68, 0x09, 0x06, 0x5f, 0x4d, 0xcf, - 0x3d, 0x1a, 0xfe, 0x20, 0x77, 0xe4, 0xd9, 0xda, 0xf9, 0xa4, 0x2b, 0x76, 0x1c, 0x71, 0xdb, 0x00, 0xbc, 0xfd, 0x0c, - 0x6c, 0xa5, 0x47, 0xf7, 0xf6, 0x00, 0x79, 0x4a, 0x11, -]); diff --git a/src/decrypt-worker/crypto/qmc/qmc_v1.ts b/src/decrypt-worker/crypto/qmc/qmc_v1.ts deleted file mode 100644 index f955820..0000000 --- a/src/decrypt-worker/crypto/qmc/qmc_v1.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { transformBlob } from '~/decrypt-worker/util/transformBlob'; -import type { DecipherInstance } from '~/decrypt-worker/Deciphers.ts'; -import key from './qmc_v1.key.ts'; - -export class QMC1Crypto implements DecipherInstance { - cryptoName = 'QMC/v1'; - checkByDecryptHeader = true; - - async decrypt(buffer: ArrayBuffer): Promise { - return transformBlob(buffer, (p) => p.make.QMCv1(key)); - } - - public static make() { - return new QMC1Crypto(); - } -} diff --git a/src/decrypt-worker/crypto/qmc/qmc_v2.key.ts b/src/decrypt-worker/crypto/qmc/qmc_v2.key.ts deleted file mode 100644 index a169f15..0000000 --- a/src/decrypt-worker/crypto/qmc/qmc_v2.key.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const SEED = 106; -export const ENC_V2_KEY_1 = '386ZJY!@#*$%^&)('; -export const ENC_V2_KEY_2 = '**#!(#$%&^a1cZ,T'; diff --git a/src/decrypt-worker/crypto/qtfm/qtfm_device.ts b/src/decrypt-worker/crypto/qtfm/qtfm_device.ts deleted file mode 100644 index 205d156..0000000 --- a/src/decrypt-worker/crypto/qtfm/qtfm_device.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { transformBlob } from '~/decrypt-worker/util/transformBlob'; -import type { DecipherInstance } from '~/decrypt-worker/Deciphers.ts'; -import { DecryptCommandOptions } from '~/decrypt-worker/types'; - -export class QingTingFM$Device implements DecipherInstance { - cryptoName = 'QingTing FM/Device ID'; - checkByDecryptHeader = false; - - async checkBySignature(_buffer: ArrayBuffer, options: DecryptCommandOptions) { - return Boolean(/^\.p~?!.*\.qta$/.test(options.fileName) && options.qingTingAndroidKey); - } - - async decrypt(buffer: ArrayBuffer, options: DecryptCommandOptions): Promise { - const { fileName: name, qingTingAndroidKey } = options; - if (!qingTingAndroidKey) { - throw new Error('QingTingFM Android Device Key was not provided'); - } - - return transformBlob(buffer, (p) => p.make.QingTingFM(name, qingTingAndroidKey)); - } - - public static make() { - return new QingTingFM$Device(); - } -} diff --git a/src/decrypt-worker/crypto/xiami/xiami.ts b/src/decrypt-worker/crypto/xiami/xiami.ts deleted file mode 100644 index 392d0ca..0000000 --- a/src/decrypt-worker/crypto/xiami/xiami.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Xiami file header -// offset description -// 0x00 "ifmt" -// 0x04 Format name, e.g. "FLAC". -// 0x08 0xfe, 0xfe, 0xfe, 0xfe -// 0x0C (3 bytes) Little-endian, size of data to copy without modification. -// e.g. [ 8a 19 00 ] = 6538 bytes of plaintext data. -// 0x0F (1 byte) File key, applied to -// 0x10 Plaintext data -// ???? Encrypted data - -import type { DecipherInstance } from '~/decrypt-worker/Deciphers.ts'; - -// little endian -const XIAMI_FILE_MAGIC = 0x746d6669; -const XIAMI_EXPECTED_PADDING = 0xfefefefe; - -const u8Sub = (a: number, b: number) => { - if (a > b) { - return a - b; - } - - return a + 0x100 - b; -}; - -export class XiamiCrypto implements DecipherInstance { - cryptoName = 'Xiami'; - checkByDecryptHeader = false; - - async checkBySignature(buffer: ArrayBuffer): Promise { - const header = new DataView(buffer); - - return header.getUint32(0x00, true) === XIAMI_FILE_MAGIC && header.getUint32(0x08, true) === XIAMI_EXPECTED_PADDING; - } - - async decrypt(src: ArrayBuffer): Promise { - const headerBuffer = src.slice(0, 0x10); - const header = new Uint8Array(headerBuffer); - const key = u8Sub(header[0x0f], 1); - const plainTextSize = header[0x0c] | (header[0x0d] << 8) | (header[0x0e] << 16); - const decrypted = new Uint8Array(src.slice(0x10)); - for (let i = decrypted.byteLength - 1; i >= plainTextSize; i--) { - decrypted[i] = u8Sub(key, decrypted[i]); - } - return decrypted; - } - - public static make() { - return new XiamiCrypto(); - } -} diff --git a/src/decrypt-worker/crypto/xmly/xmly_android.key.ts b/src/decrypt-worker/crypto/xmly/xmly_android.key.ts deleted file mode 100644 index 956de25..0000000 --- a/src/decrypt-worker/crypto/xmly/xmly_android.key.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface XimalayaAndroidKey { - contentKey: string; - init: number; - step: number; -} - -export const XimalayaX2MKey: XimalayaAndroidKey = { - contentKey: 'xmly', - init: 0.615243, - step: 3.837465, -}; - -export const XimalayaX3MKey: XimalayaAndroidKey = { - contentKey: '3989d111aad5613940f4fc44b639b292', - init: 0.726354, - step: 3.948576, -}; diff --git a/src/decrypt-worker/crypto/xmly/xmly_android.ts b/src/decrypt-worker/crypto/xmly/xmly_android.ts deleted file mode 100644 index 9d4ae65..0000000 --- a/src/decrypt-worker/crypto/xmly/xmly_android.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { transformBlob } from '~/decrypt-worker/util/transformBlob'; -import type { DecipherInstance } from '~/decrypt-worker/Deciphers.ts'; -import { XimalayaAndroidKey, XimalayaX2MKey, XimalayaX3MKey } from './xmly_android.key.js'; - -export class XimalayaAndroidCrypto implements DecipherInstance { - cryptoName = 'Ximalaya/Android'; - checkByDecryptHeader = true; - - constructor(private key: XimalayaAndroidKey) {} - - async decrypt(buffer: ArrayBuffer): Promise { - const { contentKey, init, step } = this.key; - return transformBlob(buffer, (p) => { - const transformer = p.make.XimalayaAndroid(init, step, contentKey); - if (!transformer) { - throw new Error('could not make xmly transformer, is key invalid?'); - } - - return transformer; - }); - } - - public static makeX2M() { - return new XimalayaAndroidCrypto(XimalayaX2MKey); - } - - public static makeX3M() { - return new XimalayaAndroidCrypto(XimalayaX3MKey); - } -} diff --git a/src/decrypt-worker/util/qmc2KeyCrypto.ts b/src/decrypt-worker/util/qmc2KeyCrypto.ts deleted file mode 100644 index a318b20..0000000 --- a/src/decrypt-worker/util/qmc2KeyCrypto.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Parakeet } from '@um/libparakeet'; -import { SEED, ENC_V2_KEY_1, ENC_V2_KEY_2 } from '../crypto/qmc/qmc_v2.key'; - -export const makeQMCv2KeyCrypto = (p: Parakeet) => p.make.QMCv2KeyCrypto(SEED, ENC_V2_KEY_1, ENC_V2_KEY_2); -export const makeQMCv2FooterParser = (p: Parakeet) => p.make.QMCv2FooterParser(SEED, ENC_V2_KEY_1, ENC_V2_KEY_2); diff --git a/src/decrypt-worker/util/transformBlob.ts b/src/decrypt-worker/util/transformBlob.ts deleted file mode 100644 index 074dd12..0000000 --- a/src/decrypt-worker/util/transformBlob.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Transformer, Parakeet, TransformResult, fetchParakeet } from '@um/libparakeet'; -import { toArrayBuffer } from './buffer'; -import { UnsupportedSourceFile } from './DecryptError'; - -export async function transformBlob( - blob: Blob | ArrayBuffer, - transformerFactory: (p: Parakeet) => Transformer | Promise, - { cleanup, parakeet }: { cleanup?: () => void; parakeet?: Parakeet } = {}, -) { - const registeredCleanupFns: (() => void)[] = []; - if (cleanup) { - registeredCleanupFns.push(cleanup); - } - - try { - const mod = parakeet ?? (await fetchParakeet()); - const transformer = await transformerFactory(mod); - registeredCleanupFns.push(() => transformer.delete()); - - const reader = mod.make.Reader(await toArrayBuffer(blob)); - registeredCleanupFns.push(() => reader.delete()); - - const sink = mod.make.WriterSink(); - const writer = sink.getWriter(); - registeredCleanupFns.push(() => writer.delete()); - - const result = transformer.Transform(writer, reader); - if (result === TransformResult.ERROR_INVALID_FORMAT) { - throw new UnsupportedSourceFile(`transformer<${transformer.Name}> does not recognize this file`); - } else if (result !== TransformResult.OK) { - throw new Error(`transformer<${transformer.Name}> failed with error: ${TransformResult[result]} (${result})`); - } - - return sink.collectBlob(); - } finally { - registeredCleanupFns.forEach((cleanup) => cleanup()); - } -} diff --git a/src/decrypt-worker/worker.ts b/src/decrypt-worker/worker.ts index fceb08b..1c5c712 100644 --- a/src/decrypt-worker/worker.ts +++ b/src/decrypt-worker/worker.ts @@ -1,7 +1,6 @@ import { WorkerServerBus } from '~/util/WorkerEventBus'; import { DECRYPTION_WORKER_ACTION_NAME } from './constants'; - -import { getSDKVersion } from '@um/libparakeet'; +import { getUmcVersion } from '@unlock-music/crypto'; import { workerDecryptHandler } from './worker/handler/decrypt'; import { workerParseMusicExMediaName } from './worker/handler/qmcv2_parser'; @@ -11,4 +10,4 @@ onmessage = bus.onmessage; bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.DECRYPT, workerDecryptHandler); bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.FIND_QMC_MUSICEX_NAME, workerParseMusicExMediaName); -bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.VERSION, getSDKVersion); +bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.VERSION, getUmcVersion); diff --git a/src/decrypt-worker/worker/handler/qmcv2_parser.ts b/src/decrypt-worker/worker/handler/qmcv2_parser.ts index 89db7ae..aea0098 100644 --- a/src/decrypt-worker/worker/handler/qmcv2_parser.ts +++ b/src/decrypt-worker/worker/handler/qmcv2_parser.ts @@ -1,20 +1,19 @@ -import { fetchParakeet, FooterParserState } from '@um/libparakeet'; import type { FetchMusicExNamePayload } from '~/decrypt-worker/types'; -import { makeQMCv2FooterParser } from '~/decrypt-worker/util/qmc2KeyCrypto'; import { timedLogger } from '~/util/logUtils.ts'; +import { QMCFooter } from '@unlock-music/crypto'; export const workerParseMusicExMediaName = async ({ id, blobURI }: FetchMusicExNamePayload) => { - const label = `qmcMusixEx(${id.replace('://', ':')})`; + const label = `qmcMusixExDetectName(${id.replace('://', ':')})`; return timedLogger(label, async () => { - const parakeet = await fetchParakeet(); const blob = await fetch(blobURI, { headers: { Range: 'bytes=-1024' } }).then((r) => r.blob()); - const buffer = await blob.arrayBuffer(); + const arrayBuffer = await blob.arrayBuffer(); - const parsed = makeQMCv2FooterParser(parakeet).parse(buffer.slice(-1024)); - if (parsed.state === FooterParserState.OK) { - return parsed.mediaName; + try { + const buffer = new Uint8Array(arrayBuffer.slice(-1024)); + const footer = QMCFooter.parse(buffer); + return footer?.mediaName || null; + } catch { + return null; } - - return null; }); }; diff --git a/src/features/settings/panels/PanelQMCv2Key.tsx b/src/features/settings/panels/PanelQMCv2Key.tsx index 8added4..9291a53 100644 --- a/src/features/settings/panels/PanelQMCv2Key.tsx +++ b/src/features/settings/panels/PanelQMCv2Key.tsx @@ -4,8 +4,8 @@ import { ButtonGroup, Checkbox, Flex, - HStack, Heading, + HStack, Icon, IconButton, List, @@ -61,7 +61,7 @@ export function PanelQMCv2Key() { alert(`不是支持的 SQLite 数据库文件。`); return; } - } else if (/MMKVStreamEncryptId|filenameEkeyMap|qmpc-mmkv-v1/i.test(file.name)) { + } else if (/MMKVStreamEncryptId|filenameEkeyMap|qmpc-mmkv-v1|(\.mmkv$)/i.test(file.name)) { const fileBuffer = await file.arrayBuffer(); const map = parseAndroidQmEKey(new DataView(fileBuffer)); qmc2Keys = Array.from(map.entries(), ([name, ekey]) => ({ name: getFileName(name), ekey })); diff --git a/src/features/settings/panels/PanelQingTing.tsx b/src/features/settings/panels/PanelQingTing.tsx index 305a3ad..3089526 100644 --- a/src/features/settings/panels/PanelQingTing.tsx +++ b/src/features/settings/panels/PanelQingTing.tsx @@ -13,7 +13,6 @@ import { } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from '~/hooks'; -import { fetchParakeet } from '@um/libparakeet'; import { ExtLink } from '~/components/ExtLink'; import { ChangeEvent, ClipboardEvent } from 'react'; import { VQuote } from '~/components/HelpText/VQuote'; @@ -60,9 +59,7 @@ export function PanelQingTing() { model !== null ) { e.preventDefault(); - fetchParakeet().then((parakeet) => { - setSecretKey(parakeet.qtfm.createDeviceKey(product, device, manufacturer, brand, board, model)); - }); + alert('TODO!'); } }; diff --git a/vite.config.ts b/vite.config.ts index 5770cce..a27bf7e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -34,14 +34,13 @@ export default defineConfig({ 'node_modules', // Allow pnpm to link. - process.env.LIB_PARAKEET_JS_DIR || '../libparakeet-js', process.env.LIB_UM_WASM_LOADER_DIR || '../lib_um_crypto_rust/um_wasm_loader', ], }, }, base: './', optimizeDeps: { - exclude: ['@um/libparakeet', '@unlock-music/crypto', 'sql.js'], + exclude: ['@unlock-music/crypto', 'sql.js'], }, plugins: [ replace({ @@ -88,7 +87,7 @@ export default defineConfig({ '~': path.resolve(__dirname, 'src'), '@nm': path.resolve(__dirname, 'node_modules'), - // workaround for vite, workbox (PWA) and Emscripten transpiled parakeet lib (use of `import("module")`) + // workaround for vite, workbox (PWA) module: path.resolve(__dirname, 'src', 'dummy.mjs'), }, },