diff --git a/.drone.yml b/.drone.yml index 72763ef..dd4d4ef 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,11 +4,6 @@ type: docker name: default steps: - - name: build-wasm - image: emscripten/emsdk:3.0.0 - commands: - - ./scripts/build-wasm.sh - - name: build image: node:16.18-bullseye commands: diff --git a/README.md b/README.md index 34c10be..74f5aba 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,6 @@ ### 自行构建 -#### JS部分 - - 环境要求 - nodejs (v16.x) - npm @@ -59,6 +57,7 @@ 1. 获取项目源代码后安装相关依赖: ```sh + npm install npm ci ``` @@ -76,15 +75,3 @@ ```sh npm run make-extension ``` - -#### WASM部分 - -- 环境要求 - - Linux - - python3 - -- 运行此目录下的build-wasm - - ```sh - ./scripts/build-wasm.sh - ``` diff --git a/package-lock.json b/package-lock.json index 80e2a66..26ab988 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "unlock-music", - "version": "v1.10.3", + "version": "1.10.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "unlock-music", - "version": "v1.10.3", + "version": "1.10.4", "hasInstallScript": true, "license": "MIT", "dependencies": { "@babel/preset-typescript": "^7.16.5", - "@jixun/kugou-crypto": "^1.0.3", "@unlock-music/joox-crypto": "^0.0.1-R5", + "@xhacker/kgmwasm": "^1.0.0", + "@xhacker/qmcwasm": "^1.0.0", "base64-js": "^1.5.1", "browser-id3-writer": "^4.4.0", "core-js": "^3.16.0", @@ -2986,22 +2987,6 @@ "regenerator-runtime": "^0.13.3" } }, - "node_modules/@jixun/kugou-crypto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@jixun/kugou-crypto/-/kugou-crypto-1.0.3.tgz", - "integrity": "sha512-ZiwSkpIAH8IkFcTfMjdQMpP/xco3iXEdYDEQo4wquYpSAln5RmSed3iBctnpoE6s3X1cxmBGhpCYW6v6vZfs+g==", - "dependencies": { - "commander": "^9.2.0" - } - }, - "node_modules/@jixun/kugou-crypto/node_modules/commander": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", - "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", - "engines": { - "node": "^12.20.0 || >=14" - } - }, "node_modules/@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -4197,6 +4182,16 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xhacker/kgmwasm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@xhacker/kgmwasm/-/kgmwasm-1.0.0.tgz", + "integrity": "sha512-LnBuEVRJQVyJGJTb0cPZxZDu7Qi4PqDhJLRaRJfG6pSUeZuIoglzHiysyd4XfNHobNnLxG8v1IiNPS/uWwoG0A==" + }, + "node_modules/@xhacker/qmcwasm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@xhacker/qmcwasm/-/qmcwasm-1.0.0.tgz", + "integrity": "sha512-oE6isNLmCDqIvxJV9KyDVlIzMISQzTj8o1ePWtQ+DhfXLI0hel/DwOIQ3icCikWnfwA/5SDs2hYw5BvrxdJ63g==" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -23195,21 +23190,6 @@ "regenerator-runtime": "^0.13.3" } }, - "@jixun/kugou-crypto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@jixun/kugou-crypto/-/kugou-crypto-1.0.3.tgz", - "integrity": "sha512-ZiwSkpIAH8IkFcTfMjdQMpP/xco3iXEdYDEQo4wquYpSAln5RmSed3iBctnpoE6s3X1cxmBGhpCYW6v6vZfs+g==", - "requires": { - "commander": "^9.2.0" - }, - "dependencies": { - "commander": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", - "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==" - } - } - }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -24256,6 +24236,16 @@ "@xtuc/long": "4.2.2" } }, + "@xhacker/kgmwasm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@xhacker/kgmwasm/-/kgmwasm-1.0.0.tgz", + "integrity": "sha512-LnBuEVRJQVyJGJTb0cPZxZDu7Qi4PqDhJLRaRJfG6pSUeZuIoglzHiysyd4XfNHobNnLxG8v1IiNPS/uWwoG0A==" + }, + "@xhacker/qmcwasm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@xhacker/qmcwasm/-/qmcwasm-1.0.0.tgz", + "integrity": "sha512-oE6isNLmCDqIvxJV9KyDVlIzMISQzTj8o1ePWtQ+DhfXLI0hel/DwOIQ3icCikWnfwA/5SDs2hYw5BvrxdJ63g==" + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", diff --git a/package.json b/package.json index 8373728..fc70d97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unlock-music", - "version": "1.10.3", + "version": "1.10.4", "ext_build": 0, "updateInfo": "完善音乐标签编辑功能,支持编辑更多标签", "license": "MIT", @@ -21,8 +21,9 @@ }, "dependencies": { "@babel/preset-typescript": "^7.16.5", - "@jixun/kugou-crypto": "^1.0.3", "@unlock-music/joox-crypto": "^0.0.1-R5", + "@xhacker/kgmwasm": "^1.0.0", + "@xhacker/qmcwasm": "^1.0.0", "base64-js": "^1.5.1", "browser-id3-writer": "^4.4.0", "core-js": "^3.16.0", @@ -56,4 +57,4 @@ "vue-cli-plugin-element": "^1.0.1", "vue-template-compiler": "^2.6.14" } -} \ No newline at end of file +} diff --git a/scripts/build-wasm.sh b/scripts/build-wasm.sh deleted file mode 100755 index f0d7922..0000000 --- a/scripts/build-wasm.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -cd "$(git rev-parse --show-toplevel)" - -pushd ./src/QmcWasm -bash build-wasm -popd - -pushd ./src/KgmWasm -bash build-wasm -popd diff --git a/src/KgmWasm/CMakeLists.txt b/src/KgmWasm/CMakeLists.txt deleted file mode 100644 index 1014b3b..0000000 --- a/src/KgmWasm/CMakeLists.txt +++ /dev/null @@ -1,65 +0,0 @@ -# CMakeList.txt : CMake project for KgmWasm, include source and define -# project specific logic here. -# -cmake_minimum_required (VERSION 3.8) - -project ("KgmWasm") - -set(CMAKE_CXX_STANDARD 14) - -include_directories( - $ -) - -# Add source to this project's executable. -set(RUNTIME_METHODS_LIST - getValue - writeArrayToMemory - UTF8ToString -) -list(JOIN RUNTIME_METHODS_LIST "," RUNTIME_METHODS) - -set(EMSCRIPTEN_FLAGS - "--bind" - "-s NO_DYNAMIC_EXECUTION=1" - "-s MODULARIZE=1" - "-s EXPORT_NAME=KgmCryptoModule" - "-s EXPORTED_RUNTIME_METHODS=${RUNTIME_METHODS}" -) -set(EMSCRIPTEN_LEGACY_FLAGS - ${EMSCRIPTEN_FLAGS} - "-s WASM=0" - "--memory-init-file 0" -) -set(EMSCRIPTEN_WASM_BUNDLE_FLAGS - ${EMSCRIPTEN_FLAGS} - "-s SINGLE_FILE=1" -) - -list(JOIN EMSCRIPTEN_FLAGS " " EMSCRIPTEN_FLAGS_STR) -list(JOIN EMSCRIPTEN_LEGACY_FLAGS " " EMSCRIPTEN_LEGACY_FLAGS_STR) -list(JOIN EMSCRIPTEN_WASM_BUNDLE_FLAGS " " EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR) - -# Define projects config -set(WASM_SOURCES - "KgmWasm.cpp" -) - -add_executable(KgmWasm ${WASM_SOURCES}) -set_target_properties( - KgmWasm - PROPERTIES LINK_FLAGS ${EMSCRIPTEN_FLAGS_STR} -) - -add_executable(KgmWasmBundle ${WASM_SOURCES}) -set_target_properties( - KgmWasmBundle - PROPERTIES LINK_FLAGS ${EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR} -) - -add_executable(KgmLegacy ${WASM_SOURCES}) -set_target_properties( - KgmLegacy - PROPERTIES LINK_FLAGS ${EMSCRIPTEN_LEGACY_FLAGS_STR} -) - diff --git a/src/KgmWasm/KgmWasm.cpp b/src/KgmWasm/KgmWasm.cpp deleted file mode 100644 index 7901fed..0000000 --- a/src/KgmWasm/KgmWasm.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// KgmWasm.cpp : Defines the entry point for the application. -// - -#include "KgmWasm.h" - -#include "kgm.hpp" - -#include -#include - -size_t preDec(uintptr_t blob, size_t blobSize, std::string ext) -{ - return PreDec((uint8_t*)blob, blobSize, ext == "vpr"); -} - -void decBlob(uintptr_t blob, size_t blobSize, size_t offset) -{ - Decrypt((uint8_t*)blob, blobSize, offset); - return; -} diff --git a/src/KgmWasm/KgmWasm.h b/src/KgmWasm/KgmWasm.h deleted file mode 100644 index 0b1d7eb..0000000 --- a/src/KgmWasm/KgmWasm.h +++ /dev/null @@ -1,18 +0,0 @@ -// KgmWasm.h : Include file for standard system include files, -// or project specific include files. - -#pragma once - -#include -#include - -namespace em = emscripten; - -size_t preDec(uintptr_t blob, size_t blobSize, std::string ext); -void decBlob(uintptr_t blob, size_t blobSize, size_t offset); - -EMSCRIPTEN_BINDINGS(QmcCrypto) -{ - em::function("preDec", &preDec, em::allow_raw_pointers()); - em::function("decBlob", &decBlob, em::allow_raw_pointers()); -} diff --git a/src/KgmWasm/README.md b/src/KgmWasm/README.md deleted file mode 100644 index 0ad5092..0000000 --- a/src/KgmWasm/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# KgmWasm - -## 构建 - -在 Linux 环境下执行 `bash build-wasm` 即可构建。 - -## Build - -Linux environment required. Build wasm binary by execute `bash build-wasm`. diff --git a/src/KgmWasm/build-wasm b/src/KgmWasm/build-wasm deleted file mode 100755 index 599b97c..0000000 --- a/src/KgmWasm/build-wasm +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -pushd "$(realpath "$(dirname "$0")")" - -CURR_DIR="${PWD}" - -BUILD_TYPE="$1" -if [ -z "$BUILD_TYPE" ]; then - BUILD_TYPE=Release -fi - -# CI: already had emsdk installed. -if ! command -v emcc; then - if [ ! -d ../../build/emsdk ]; then - git clone https://github.com/emscripten-core/emsdk.git ../../build/emsdk - fi - - pushd ../../build/emsdk - ./emsdk install 3.0.0 - ./emsdk activate 3.0.0 - source ./emsdk_env.sh - popd # ../../build/emsdk -fi - -mkdir -p build/wasm -pushd build/wasm -emcmake cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ../.. -make -j -TARGET_FILES=" - KgmLegacy.js - KgmWasm.js - KgmWasm.wasm - KgmWasmBundle.js -" - -cp $TARGET_FILES "${CURR_DIR}/" -popd # build/wasm - -popd diff --git a/src/KgmWasm/kgm.hpp b/src/KgmWasm/kgm.hpp deleted file mode 100644 index b3493e5..0000000 --- a/src/KgmWasm/kgm.hpp +++ /dev/null @@ -1,112 +0,0 @@ -#include - -std::vector VprHeader = { - 0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43, - 0x91, 0xAA, 0xBD, 0xD0, 0x7A, 0xF5, 0x36, 0x31 }; -std::vector KgmHeader = { - 0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B, - 0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14 }; -std::vector VprMaskDiff = { - 0x25, 0xDF, 0xE8, 0xA6, 0x75, 0x1E, 0x75, 0x0E, - 0x2F, 0x80, 0xF3, 0x2D, 0xB8, 0xB6, 0xE3, 0x11, 0x00 }; - -std::vector MaskV2; - -std::vector table1 = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x21, 0x01, 0x61, 0x01, 0x21, 0x01, 0xe1, 0x01, 0x21, 0x01, 0x61, 0x01, 0x21, 0x01, - 0xd2, 0x23, 0x02, 0x02, 0x42, 0x42, 0x02, 0x02, 0xc2, 0xc2, 0x02, 0x02, 0x42, 0x42, 0x02, 0x02, - 0xd3, 0xd3, 0x02, 0x03, 0x63, 0x43, 0x63, 0x03, 0xe3, 0xc3, 0xe3, 0x03, 0x63, 0x43, 0x63, 0x03, - 0x94, 0xb4, 0x94, 0x65, 0x04, 0x04, 0x04, 0x04, 0x84, 0x84, 0x84, 0x84, 0x04, 0x04, 0x04, 0x04, - 0x95, 0x95, 0x95, 0x95, 0x04, 0x05, 0x25, 0x05, 0xe5, 0x85, 0xa5, 0x85, 0xe5, 0x05, 0x25, 0x05, - 0xd6, 0xb6, 0x96, 0xb6, 0xd6, 0x27, 0x06, 0x06, 0xc6, 0xc6, 0x86, 0x86, 0xc6, 0xc6, 0x06, 0x06, - 0xd7, 0xd7, 0x97, 0x97, 0xd7, 0xd7, 0x06, 0x07, 0xe7, 0xc7, 0xe7, 0x87, 0xe7, 0xc7, 0xe7, 0x07, - 0x18, 0x38, 0x18, 0x78, 0x18, 0x38, 0x18, 0xe9, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x08, 0x09, 0x29, 0x09, 0x69, 0x09, 0x29, 0x09, - 0xda, 0x3a, 0x1a, 0x3a, 0x5a, 0x3a, 0x1a, 0x3a, 0xda, 0x2b, 0x0a, 0x0a, 0x4a, 0x4a, 0x0a, 0x0a, - 0xdb, 0xdb, 0x1b, 0x1b, 0x5b, 0x5b, 0x1b, 0x1b, 0xdb, 0xdb, 0x0a, 0x0b, 0x6b, 0x4b, 0x6b, 0x0b, - 0x9c, 0xbc, 0x9c, 0x7c, 0x1c, 0x3c, 0x1c, 0x7c, 0x9c, 0xbc, 0x9c, 0x6d, 0x0c, 0x0c, 0x0c, 0x0c, - 0x9d, 0x9d, 0x9d, 0x9d, 0x1d, 0x1d, 0x1d, 0x1d, 0x9d, 0x9d, 0x9d, 0x9d, 0x0c, 0x0d, 0x2d, 0x0d, - 0xde, 0xbe, 0x9e, 0xbe, 0xde, 0x3e, 0x1e, 0x3e, 0xde, 0xbe, 0x9e, 0xbe, 0xde, 0x2f, 0x0e, 0x0e, - 0xdf, 0xdf, 0x9f, 0x9f, 0xdf, 0xdf, 0x1f, 0x1f, 0xdf, 0xdf, 0x9f, 0x9f, 0xdf, 0xdf, 0x0e, 0x0f, - 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0xf1 -}; - -std::vector table2 = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x23, 0x01, 0x67, 0x01, 0x23, 0x01, 0xef, 0x01, 0x23, 0x01, 0x67, 0x01, 0x23, 0x01, - 0xdf, 0x21, 0x02, 0x02, 0x46, 0x46, 0x02, 0x02, 0xce, 0xce, 0x02, 0x02, 0x46, 0x46, 0x02, 0x02, - 0xde, 0xde, 0x02, 0x03, 0x65, 0x47, 0x65, 0x03, 0xed, 0xcf, 0xed, 0x03, 0x65, 0x47, 0x65, 0x03, - 0x9d, 0xbf, 0x9d, 0x63, 0x04, 0x04, 0x04, 0x04, 0x8c, 0x8c, 0x8c, 0x8c, 0x04, 0x04, 0x04, 0x04, - 0x9c, 0x9c, 0x9c, 0x9c, 0x04, 0x05, 0x27, 0x05, 0xeb, 0x8d, 0xaf, 0x8d, 0xeb, 0x05, 0x27, 0x05, - 0xdb, 0xbd, 0x9f, 0xbd, 0xdb, 0x25, 0x06, 0x06, 0xca, 0xca, 0x8e, 0x8e, 0xca, 0xca, 0x06, 0x06, - 0xda, 0xda, 0x9e, 0x9e, 0xda, 0xda, 0x06, 0x07, 0xe9, 0xcb, 0xe9, 0x8f, 0xe9, 0xcb, 0xe9, 0x07, - 0x19, 0x3b, 0x19, 0x7f, 0x19, 0x3b, 0x19, 0xe7, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x08, 0x09, 0x2b, 0x09, 0x6f, 0x09, 0x2b, 0x09, - 0xd7, 0x39, 0x1b, 0x39, 0x5f, 0x39, 0x1b, 0x39, 0xd7, 0x29, 0x0a, 0x0a, 0x4e, 0x4e, 0x0a, 0x0a, - 0xd6, 0xd6, 0x1a, 0x1a, 0x5e, 0x5e, 0x1a, 0x1a, 0xd6, 0xd6, 0x0a, 0x0b, 0x6d, 0x4f, 0x6d, 0x0b, - 0x95, 0xb7, 0x95, 0x7b, 0x1d, 0x3f, 0x1d, 0x7b, 0x95, 0xb7, 0x95, 0x6b, 0x0c, 0x0c, 0x0c, 0x0c, - 0x94, 0x94, 0x94, 0x94, 0x1c, 0x1c, 0x1c, 0x1c, 0x94, 0x94, 0x94, 0x94, 0x0c, 0x0d, 0x2f, 0x0d, - 0xd3, 0xb5, 0x97, 0xb5, 0xd3, 0x3d, 0x1f, 0x3d, 0xd3, 0xb5, 0x97, 0xb5, 0xd3, 0x2d, 0x0e, 0x0e, - 0xd2, 0xd2, 0x96, 0x96, 0xd2, 0xd2, 0x1e, 0x1e, 0xd2, 0xd2, 0x96, 0x96, 0xd2, 0xd2, 0x0e, 0x0f, - 0x00, 0x22, 0x00, 0x66, 0x00, 0x22, 0x00, 0xee, 0x00, 0x22, 0x00, 0x66, 0x00, 0x22, 0x00, 0xfe -}; - -std::vector MaskV2PreDef = { - 0xB8, 0xD5, 0x3D, 0xB2, 0xE9, 0xAF, 0x78, 0x8C, 0x83, 0x33, 0x71, 0x51, 0x76, 0xA0, 0xCD, 0x37, - 0x2F, 0x3E, 0x35, 0x8D, 0xA9, 0xBE, 0x98, 0xB7, 0xE7, 0x8C, 0x22, 0xCE, 0x5A, 0x61, 0xDF, 0x68, - 0x69, 0x89, 0xFE, 0xA5, 0xB6, 0xDE, 0xA9, 0x77, 0xFC, 0xC8, 0xBD, 0xBD, 0xE5, 0x6D, 0x3E, 0x5A, - 0x36, 0xEF, 0x69, 0x4E, 0xBE, 0xE1, 0xE9, 0x66, 0x1C, 0xF3, 0xD9, 0x02, 0xB6, 0xF2, 0x12, 0x9B, - 0x44, 0xD0, 0x6F, 0xB9, 0x35, 0x89, 0xB6, 0x46, 0x6D, 0x73, 0x82, 0x06, 0x69, 0xC1, 0xED, 0xD7, - 0x85, 0xC2, 0x30, 0xDF, 0xA2, 0x62, 0xBE, 0x79, 0x2D, 0x62, 0x62, 0x3D, 0x0D, 0x7E, 0xBE, 0x48, - 0x89, 0x23, 0x02, 0xA0, 0xE4, 0xD5, 0x75, 0x51, 0x32, 0x02, 0x53, 0xFD, 0x16, 0x3A, 0x21, 0x3B, - 0x16, 0x0F, 0xC3, 0xB2, 0xBB, 0xB3, 0xE2, 0xBA, 0x3A, 0x3D, 0x13, 0xEC, 0xF6, 0x01, 0x45, 0x84, - 0xA5, 0x70, 0x0F, 0x93, 0x49, 0x0C, 0x64, 0xCD, 0x31, 0xD5, 0xCC, 0x4C, 0x07, 0x01, 0x9E, 0x00, - 0x1A, 0x23, 0x90, 0xBF, 0x88, 0x1E, 0x3B, 0xAB, 0xA6, 0x3E, 0xC4, 0x73, 0x47, 0x10, 0x7E, 0x3B, - 0x5E, 0xBC, 0xE3, 0x00, 0x84, 0xFF, 0x09, 0xD4, 0xE0, 0x89, 0x0F, 0x5B, 0x58, 0x70, 0x4F, 0xFB, - 0x65, 0xD8, 0x5C, 0x53, 0x1B, 0xD3, 0xC8, 0xC6, 0xBF, 0xEF, 0x98, 0xB0, 0x50, 0x4F, 0x0F, 0xEA, - 0xE5, 0x83, 0x58, 0x8C, 0x28, 0x2C, 0x84, 0x67, 0xCD, 0xD0, 0x9E, 0x47, 0xDB, 0x27, 0x50, 0xCA, - 0xF4, 0x63, 0x63, 0xE8, 0x97, 0x7F, 0x1B, 0x4B, 0x0C, 0xC2, 0xC1, 0x21, 0x4C, 0xCC, 0x58, 0xF5, - 0x94, 0x52, 0xA3, 0xF3, 0xD3, 0xE0, 0x68, 0xF4, 0x00, 0x23, 0xF3, 0x5E, 0x0A, 0x7B, 0x93, 0xDD, - 0xAB, 0x12, 0xB2, 0x13, 0xE8, 0x84, 0xD7, 0xA7, 0x9F, 0x0F, 0x32, 0x4C, 0x55, 0x1D, 0x04, 0x36, - 0x52, 0xDC, 0x03, 0xF3, 0xF9, 0x4E, 0x42, 0xE9, 0x3D, 0x61, 0xEF, 0x7C, 0xB6, 0xB3, 0x93, 0x50, -}; - -uint8_t getMask(size_t pos) { - size_t offset = pos >> 4; - uint8_t value = 0; - while (offset >= 0x11) { - value ^= table1[offset % 272]; - offset >>= 4; - value ^= table2[offset % 272]; - offset >>= 4; - } - - return MaskV2PreDef[pos % 272] ^ value; -} - -std::vector key(17); -bool isVpr = false; - -size_t PreDec(uint8_t* fileData, size_t size, bool iV) { - uint32_t headerLen = *(uint32_t*)(fileData + 0x10); - memcpy(key.data(), (fileData + 0x1C), 0x10); - key[16] = 0; - isVpr = iV; - return headerLen; -} - -void Decrypt(uint8_t* fileData, size_t size, size_t offset) { - for (size_t i = 0; i < size; ++i) { - uint8_t med8 = key[(i + offset) % 17] ^ fileData[i]; - med8 ^= (med8 & 0xf) << 4; - - uint8_t msk8 = getMask(i + offset); - msk8 ^= (msk8 & 0xf) << 4; - fileData[i] = med8 ^ msk8; - - if (isVpr) { - fileData[i] ^= VprMaskDiff[(i + offset) % 17]; - } - } -} diff --git a/src/QmcWasm/CMakeLists.txt b/src/QmcWasm/CMakeLists.txt deleted file mode 100644 index 066268a..0000000 --- a/src/QmcWasm/CMakeLists.txt +++ /dev/null @@ -1,65 +0,0 @@ -# CMakeList.txt : CMake project for QmcWasm, include source and define -# project specific logic here. -# -cmake_minimum_required (VERSION 3.8) - -project ("QmcWasm") - -set(CMAKE_CXX_STANDARD 14) - -include_directories( - $ -) - -# Add source to this project's executable. -set(RUNTIME_METHODS_LIST - getValue - writeArrayToMemory - UTF8ToString -) -list(JOIN RUNTIME_METHODS_LIST "," RUNTIME_METHODS) - -set(EMSCRIPTEN_FLAGS - "--bind" - "-s NO_DYNAMIC_EXECUTION=1" - "-s MODULARIZE=1" - "-s EXPORT_NAME=QmcCryptoModule" - "-s EXPORTED_RUNTIME_METHODS=${RUNTIME_METHODS}" -) -set(EMSCRIPTEN_LEGACY_FLAGS - ${EMSCRIPTEN_FLAGS} - "-s WASM=0" - "--memory-init-file 0" -) -set(EMSCRIPTEN_WASM_BUNDLE_FLAGS - ${EMSCRIPTEN_FLAGS} - "-s SINGLE_FILE=1" -) - -list(JOIN EMSCRIPTEN_FLAGS " " EMSCRIPTEN_FLAGS_STR) -list(JOIN EMSCRIPTEN_LEGACY_FLAGS " " EMSCRIPTEN_LEGACY_FLAGS_STR) -list(JOIN EMSCRIPTEN_WASM_BUNDLE_FLAGS " " EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR) - -# Define projects config -set(WASM_SOURCES - "QmcWasm.cpp" -) - -add_executable(QmcWasm ${WASM_SOURCES}) -set_target_properties( - QmcWasm - PROPERTIES LINK_FLAGS ${EMSCRIPTEN_FLAGS_STR} -) - -add_executable(QmcWasmBundle ${WASM_SOURCES}) -set_target_properties( - QmcWasmBundle - PROPERTIES LINK_FLAGS ${EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR} -) - -add_executable(QmcLegacy ${WASM_SOURCES}) -set_target_properties( - QmcLegacy - PROPERTIES LINK_FLAGS ${EMSCRIPTEN_LEGACY_FLAGS_STR} -) - diff --git a/src/QmcWasm/QmcWasm.cpp b/src/QmcWasm/QmcWasm.cpp deleted file mode 100644 index f4fc8c0..0000000 --- a/src/QmcWasm/QmcWasm.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// QmcWasm.cpp : Defines the entry point for the application. -// - -#include "QmcWasm.h" - -#include "qmc.hpp" - -#include -#include - -std::string err = ""; -std::string sid = ""; -QmcDecode e; - -int preDec(uintptr_t blob, size_t blobSize, std::string ext) -{ - if (!e.SetBlob((uint8_t*)blob, blobSize)) - { - err = "cannot allocate memory"; - return -1; - } - int tailSize = e.PreDecode(ext); - if (e.error != "") - { - err = e.error; - return -1; - } - sid = e.songId; - return tailSize; -} - -size_t decBlob(uintptr_t blob, size_t blobSize, size_t offset) -{ - if (!e.SetBlob((uint8_t*)blob, blobSize)) - { - err = "cannot allocate memory"; - return 0; - } - std::vector decData = e.Decode(offset); - if (e.error != "") - { - err = e.error; - return 0; - } - memcpy((uint8_t*)blob, decData.data(), decData.size()); - return decData.size(); -} - -std::string getErr() -{ - return err; -} - -std::string getSongId() -{ - return sid; -} diff --git a/src/QmcWasm/QmcWasm.h b/src/QmcWasm/QmcWasm.h deleted file mode 100644 index 6fd63bf..0000000 --- a/src/QmcWasm/QmcWasm.h +++ /dev/null @@ -1,23 +0,0 @@ -// QmcWasm.h : Include file for standard system include files, -// or project specific include files. - -#pragma once - -#include -#include - -namespace em = emscripten; - -int preDec(uintptr_t blob, size_t blobSize, std::string ext); -size_t decBlob(uintptr_t blob, size_t blobSize, size_t offset); -std::string getErr(); -std::string getSongId(); - -EMSCRIPTEN_BINDINGS(QmcCrypto) -{ - em::function("getErr", &getErr); - em::function("getSongId", &getSongId); - - em::function("preDec", &preDec, em::allow_raw_pointers()); - em::function("decBlob", &decBlob, em::allow_raw_pointers()); -} diff --git a/src/QmcWasm/README.md b/src/QmcWasm/README.md deleted file mode 100644 index 035fe65..0000000 --- a/src/QmcWasm/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# QmcWasm - -## 构建 - -在 Linux 环境下执行 `bash build-wasm` 即可构建。 - -## Build - -Linux environment required. Build wasm binary by execute `bash build-wasm`. diff --git a/src/QmcWasm/TencentTea.hpp b/src/QmcWasm/TencentTea.hpp deleted file mode 100644 index 4f635a7..0000000 --- a/src/QmcWasm/TencentTea.hpp +++ /dev/null @@ -1,289 +0,0 @@ -#ifndef QQMUSIC_CPP_TENCENTTEA_HPP -#define QQMUSIC_CPP_TENCENTTEA_HPP - -#include -#include -#include -#include -#include -#include - -const uint32_t DELTA = 0x9e3779b9; - -#define ROUNDS 32 -#define SALT_LEN 2 -#define ZERO_LEN 7 - -void TeaDecryptECB(uint8_t* src, uint8_t* dst, std::vector key, size_t rounds = ROUNDS) { - if (key.size() != 16 || (rounds & 1) != 0) - { - return; - } - uint32_t y, z, sum; - uint32_t k[4]; - int i; - - //now encrypted buf is TCP/IP-endian; - //TCP/IP network byte order (which is big-endian). - y = ntohl(*((uint32_t*)src)); - z = ntohl(*((uint32_t*)(src + 4))); - //std::cout << ntohl(0x0a3aea41); - - for (i = 0; i < 4; i++) { - //key is TCP/IP-endian; - k[i] = ntohl(*((uint32_t*)(key.data() + i * 4))); - } - - sum = (DELTA * rounds); - for (i = 0; i < rounds; i++) { - z -= ((y << 4) + k[2]) ^ (y + sum) ^ ((y >> 5) + k[3]); - y -= ((z << 4) + k[0]) ^ (z + sum) ^ ((z >> 5) + k[1]); - sum -= DELTA; - } - - *((uint32_t*)dst) = ntohl(y); - *((uint32_t*)(dst + 4)) = ntohl(z); - - //now plain-text is TCP/IP-endian; -} - -void TeaEncryptECB(uint8_t* src, uint8_t* dst, std::vector key, size_t rounds = ROUNDS) { - if (key.size() != 16 || (rounds & 1) != 0) - { - return; - } - uint32_t y, z, sum; - uint32_t k[4]; - int i; - - //now encrypted buf is TCP/IP-endian; - //TCP/IP network byte order (which is big-endian). - y = ntohl(*((uint32_t*)src)); - z = ntohl(*((uint32_t*)(src + 4))); - //std::cout << ntohl(0x0a3aea41); - - for (i = 0; i < 4; i++) { - //key is TCP/IP-endian; - k[i] = ntohl(*((uint32_t*)(key.data() + i * 4))); - } - - sum = 0; - for (i = 0; i < rounds; i++) { - sum += DELTA; - y += ((z << 4) + k[0]) ^ (z + sum) ^ ((z >> 5) + k[1]); - z += ((y << 4) + k[2]) ^ (y + sum) ^ ((y >> 5) + k[3]); - } - - *((uint32_t*)dst) = ntohl(y); - *((uint32_t*)(dst + 4)) = ntohl(z); - - //now plain-text is TCP/IP-endian; -} - -/*pKeyΪ16byte*/ -/* - :nInBufLenΪܵIJ(Body); - :Ϊܺij(8byteı); -*/ -/*TEA㷨,CBCģʽ*/ -/*ĸʽ:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ -int encryptTencentTeaLen(int nInBufLen) -{ - - int nPadSaltBodyZeroLen/*PadLen(1byte)+Salt+Body+Zeroij*/; - int nPadlen; - - /*BodyȼPadLen,С賤ȱΪ8byte*/ - nPadSaltBodyZeroLen = nInBufLen/*Body*/ + 1 + SALT_LEN + ZERO_LEN/*PadLen(1byte)+Salt(2byte)+Zero(7byte)*/; - if ((nPadlen = nPadSaltBodyZeroLen % 8)) /*len=nSaltBodyZeroLen%8*/ - { - /*ģ80貹0,17,26,...,71*/ - nPadlen = 8 - nPadlen; - } - - return nPadlen; -} - -/*pKeyΪ16byte*/ -/* - :pInBufΪܵIJ(Body),nInBufLenΪpInBuf; - :pOutBufΪĸʽ,pOutBufLenΪpOutBufij8byteı; -*/ -/*TEA㷨,CBCģʽ*/ -/*ĸʽ:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ -bool encryptTencentTea(std::vector inBuf, std::vector key, std::vector &outBuf) -{ - srand(time(0)); - int nPadlen = encryptTencentTeaLen(inBuf.size()); - size_t ivCrypt; - std::vector srcBuf; - srcBuf.resize(8); - std::vector ivPlain; - ivPlain.resize(8); - int tmpIdx, i, j; - - /*ܵһ(8byte),ȡǰ10byte*/ - srcBuf[0] = (((char)rand()) & 0x0f8)/*λPadLen,*/ | (char)nPadlen; - tmpIdx = 1; /*tmpIdxָsrcBufһλ*/ - - while (nPadlen--) srcBuf[tmpIdx++] = (char)rand(); /*Padding*/ - - /*come here, tmpIdx must <= 8*/ - - for (i = 0; i < 8; i++) ivPlain[i] = 0; - ivCrypt = 0;//ivPlain /*make zero iv*/ - - auto outBufPos = 0; /*init outBufPos*/ - -#define cryptBlock {\ - /*tmpIdx==8*/\ - outBuf.resize(outBuf.size() + 8);\ - for (j = 0; j < 8; j++) /*ǰǰ8byte(iv_cryptָ)*/\ - srcBuf[j] ^= outBuf[j + ivCrypt];\ - /*pOutBufferpInBufferΪ8byte, pKeyΪ16byte*/\ - /**/\ - TeaEncryptECB(srcBuf.data(), outBuf.data()+outBufPos, key, 16);\ - for (j = 0; j < 8; j++) /*ܺǰ8byte(iv_plainָ)*/\ - outBuf[j + outBufPos] ^= ivPlain[j];\ - /*浱ǰiv_plain*/\ - for (j = 0; j < 8; j++) ivPlain[j] = srcBuf[j];\ - /*iv_crypt*/\ - tmpIdx = 0;\ - ivCrypt = outBufPos;\ - outBufPos += 8;\ - } - - - for (i = 1; i <= SALT_LEN;) /*Salt(2byte)*/ - { - if (tmpIdx < 8) - { - srcBuf[tmpIdx++] = (char)rand(); - i++; /*i inc in here*/ - } - if (tmpIdx == 8) - { - cryptBlock - } - } - - /*tmpIdxָsrcBufһλ*/ - - auto inBufPos = 0; - while (inBufPos < inBuf.size()) - { - if (tmpIdx < 8) - { - srcBuf[tmpIdx++] = inBuf[inBufPos]; - inBufPos++; - } - if (tmpIdx == 8) - { - cryptBlock - } - } - - /*tmpIdxָsrcBufһλ*/ - - for (i = 1; i <= ZERO_LEN;) - { - if (tmpIdx < 8) - { - srcBuf[tmpIdx++] = 0; - i++; //i inc in here - } - if (tmpIdx == 8) - { - cryptBlock - } - } - return true; -#undef cryptBlock -} - -bool decryptTencentTea(std::vector inBuf, std::vector key, std::vector &out) { - if (inBuf.size() % 8 != 0) { - return false; - //inBuf size not a multiple of the block size - } - if (inBuf.size() < 16) { - return false; - //inBuf size too small - } - - std::vector tmpBuf; - tmpBuf.resize(8); - - TeaDecryptECB(inBuf.data(), tmpBuf.data(), key, 16); - - auto nPadLen = tmpBuf[0] & 0x7; //ֻҪλ - /*ĸʽ:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ - auto outLen = inBuf.size() - 1 /*PadLen*/ - nPadLen - SALT_LEN - ZERO_LEN; - std::vector outBuf; - outBuf.resize(outLen); - - std::vector ivPrev; - ivPrev.resize(8); - std::vector ivCur; - ivCur.resize(8); - for (size_t i = 0; i < 8; i++) - { - ivCur[i] = inBuf[i]; // init iv - } - auto inBufPos = 8; - - // Padding Len Padding - auto tmpIdx = 1 + nPadLen; - - // CBC IV -#define cryptBlock {\ - ivPrev = ivCur;\ - for (size_t k = inBufPos; k < inBufPos + 8; k++)\ - {\ - ivCur[k - inBufPos] = inBuf[k];\ - }\ - for (size_t j = 0; j < 8; j++) {\ - tmpBuf[j] ^= ivCur[j];\ - }\ - TeaDecryptECB(tmpBuf.data(), tmpBuf.data(), key, 16);\ - inBufPos += 8;\ - tmpIdx = 0;\ - } - - // Salt - for (size_t i = 1; i <= SALT_LEN; ) { - if (tmpIdx < 8) { - tmpIdx++; - i++; - } - else { - cryptBlock - } - } - - // ԭ - auto outBufPos = 0; - while (outBufPos < outLen) { - if (tmpIdx < 8) { - outBuf[outBufPos] = tmpBuf[tmpIdx] ^ ivPrev[tmpIdx]; - outBufPos++; - tmpIdx++; - } - else { - cryptBlock - } - } - - // УZero - for (size_t i = 1; i <= ZERO_LEN; i++) { - if (tmpBuf[i] != ivPrev[i]) { - return false; - //zero check failed - } - } - out = outBuf; - return true; -#undef cryptBlock -} - -#endif //QQMUSIC_CPP_TENCENTTEA_HPP diff --git a/src/QmcWasm/base64.hpp b/src/QmcWasm/base64.hpp deleted file mode 100644 index b3b6aca..0000000 --- a/src/QmcWasm/base64.hpp +++ /dev/null @@ -1,207 +0,0 @@ -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/boostorg/beast -// - -/* - Portions from http://www.adp-gmbh.ch/cpp/common/base64.html - Copyright notice: - - base64.cpp and base64.h - - Copyright (C) 2004-2008 Rene Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - Rene Nyffenegger rene.nyffenegger@adp-gmbh.ch -*/ - -#ifndef BASE64_HPP -#define BASE64_HPP - -#include -#include -#include - -namespace base64 { - - /// Returns max chars needed to encode a base64 string - std::size_t constexpr - encoded_size(std::size_t n) - { - return 4 * ((n + 2) / 3); - } - - /// Returns max bytes needed to decode a base64 string - inline - std::size_t constexpr - decoded_size(std::size_t n) - { - return n / 4 * 3; // requires n&3==0, smaller - } - - char const* - get_alphabet() - { - static char constexpr tab[] = { - "ABCDEFGHIJKLMNOP" - "QRSTUVWXYZabcdef" - "ghijklmnopqrstuv" - "wxyz0123456789+/" - }; - return &tab[0]; - } - - signed char const* - get_inverse() - { - static signed char constexpr tab[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 48-63 - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95 - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255 - }; - return &tab[0]; - } - - /** Encode a series of octets as a padded, base64 string. - - The resulting string will not be null terminated. - - @par Requires - - The memory pointed to by `out` points to valid memory - of at least `encoded_size(len)` bytes. - - @return The number of characters written to `out`. This - will exclude any null termination. - */ - std::size_t - encode(void* dest, void const* src, std::size_t len) - { - char* out = static_cast(dest); - char const* in = static_cast(src); - auto const tab = base64::get_alphabet(); - - for (auto n = len / 3; n--;) - { - *out++ = tab[(in[0] & 0xfc) >> 2]; - *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; - *out++ = tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)]; - *out++ = tab[in[2] & 0x3f]; - in += 3; - } - - switch (len % 3) - { - case 2: - *out++ = tab[(in[0] & 0xfc) >> 2]; - *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; - *out++ = tab[(in[1] & 0x0f) << 2]; - *out++ = '='; - break; - - case 1: - *out++ = tab[(in[0] & 0xfc) >> 2]; - *out++ = tab[((in[0] & 0x03) << 4)]; - *out++ = '='; - *out++ = '='; - break; - - case 0: - break; - } - - return out - static_cast(dest); - } - - /** Decode a padded base64 string into a series of octets. - - @par Requires - - The memory pointed to by `out` points to valid memory - of at least `decoded_size(len)` bytes. - - @return The number of octets written to `out`, and - the number of characters read from the input string, - expressed as a pair. - */ - std::pair - decode(void* dest, char const* src, std::size_t len) - { - char* out = static_cast(dest); - auto in = reinterpret_cast(src); - unsigned char c3[3], c4[4]; - int i = 0; - int j = 0; - - auto const inverse = base64::get_inverse(); - - while (len-- && *in != '=') - { - auto const v = inverse[*in]; - if (v == -1) - break; - ++in; - c4[i] = v; - if (++i == 4) - { - c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); - c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); - c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; - - for (i = 0; i < 3; i++) - *out++ = c3[i]; - i = 0; - } - } - - if (i) - { - c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); - c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); - c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; - - for (j = 0; j < i - 1; j++) - *out++ = c3[j]; - } - - return { out - static_cast(dest), - in - reinterpret_cast(src) }; - } - -} // base64 - -#endif diff --git a/src/QmcWasm/build-wasm b/src/QmcWasm/build-wasm deleted file mode 100755 index 4cd1640..0000000 --- a/src/QmcWasm/build-wasm +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -pushd "$(realpath "$(dirname "$0")")" - -CURR_DIR="${PWD}" - -BUILD_TYPE="$1" -if [ -z "$BUILD_TYPE" ]; then - BUILD_TYPE=Release -fi - -# CI: already had emsdk installed. -if ! command -v emcc; then - if [ ! -d ../../build/emsdk ]; then - git clone https://github.com/emscripten-core/emsdk.git ../../build/emsdk - fi - - pushd ../../build/emsdk - ./emsdk install 3.0.0 - ./emsdk activate 3.0.0 - source ./emsdk_env.sh - popd # ../../build/emsdk -fi - -mkdir -p build/wasm -pushd build/wasm -emcmake cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ../.. -make -j -TARGET_FILES=" - QmcLegacy.js - QmcWasm.js - QmcWasm.wasm - QmcWasmBundle.js -" - -cp $TARGET_FILES "${CURR_DIR}/" -popd # build/wasm - -popd diff --git a/src/QmcWasm/qmc.hpp b/src/QmcWasm/qmc.hpp deleted file mode 100644 index 3f26ecf..0000000 --- a/src/QmcWasm/qmc.hpp +++ /dev/null @@ -1,230 +0,0 @@ -#include -#include -#include -#include -#include "qmc_key.hpp" -#include "qmc_cipher.hpp" - -class QmcDecode { -private: - std::vector blobData; - - std::vector rawKeyBuf; - std::string cipherType = ""; - - size_t dataOffset = 0; - size_t keySize = 0; - int mediaVer = 0; - - std::string checkType(std::string fn) { - if (fn.find(".qmc") < fn.size() || fn.find(".m") < fn.size()) - { - std::string buf_tag = ""; - for (int i = 4; i > 0; --i) - { - buf_tag += *((char*)blobData.data() + blobData.size() - i); - } - if (buf_tag == "QTag") - { - keySize = ntohl(*(uint32_t*)(blobData.data() + blobData.size() - 8)); - return "QTag"; - } - else if (buf_tag == "STag") - { - return "STag"; - } - else - { - keySize = (*(uint32_t*)(blobData.data() + blobData.size() - 4)); - if (keySize < 0x400) - { - return "Map/RC4"; - } - else - { - keySize = 0; - return "Static"; - } - } - } - else if (fn.find(".cache") < fn.size()) - { - return "cache"; - } - else if (fn.find(".tm") < fn.size()) - { - return "ios"; - } - else - { - return "invalid"; - } - } - - bool parseRawKeyQTag() { - std::string ketStr = ""; - std::string::size_type index = 0; - ketStr.append((char*)rawKeyBuf.data(), rawKeyBuf.size()); - index = ketStr.find(",", 0); - if (index != std::string::npos) - { - rawKeyBuf.resize(index); - } - else - { - return false; - } - ketStr = ketStr.substr(index + 1); - index = ketStr.find(",", 0); - if (index != std::string::npos) - { - this->songId = ketStr.substr(0, index); - } - else - { - return false; - } - ketStr = ketStr.substr(index + 1); - index = ketStr.find(",", 0); - if (index == std::string::npos) - { - this->mediaVer = std::stoi(ketStr); - } - else - { - return false; - } - return true; - } - - bool readRawKey(size_t tailSize) { - // get raw key data length - rawKeyBuf.resize(keySize); - if (rawKeyBuf.size() != keySize) { - return false; - } - for (size_t i = 0; i < keySize; i++) - { - rawKeyBuf[i] = blobData[i + blobData.size() - (tailSize + keySize)]; - } - return true; - } - - void DecodeStatic(); - - void DecodeMapRC4(); - - void DecodeCache(); - - void DecodeTm(); - -public: - bool SetBlob(uint8_t* blob, size_t blobSize) { - blobData.resize(blobSize); - if (blobData.size() != blobSize) { - return false; - } - memcpy(blobData.data(), blob, blobSize); - return true; - } - - int PreDecode(std::string ext) { - cipherType = checkType(ext); - size_t tailSize = 0; - if (cipherType == "invalid" || cipherType == "STag") { - error = "file is invalid or not supported (Please downgrade your app)."; - return -1; - } - if (cipherType == "QTag") { - tailSize = 8; - } - else if (cipherType == "Map/RC4") { - tailSize = 4; - } - if (keySize > 0) { - if (!readRawKey(tailSize)) { - error = "cannot read embedded key from file"; - return -1; - } - if (tailSize == 8) { - cipherType = "Map/RC4"; - if (!parseRawKeyQTag()) { - error = "cannot parse embedded key"; - return -1; - } - } - std::vector tmp; - if (!QmcDecryptKey(rawKeyBuf, tmp)) { - error = "cannot decrypt embedded key"; - return -1; - } - rawKeyBuf = tmp; - } - return keySize + tailSize; - } - - std::vector Decode(size_t offset); - - std::string songId = ""; - std::string error = ""; -}; - -void QmcDecode::DecodeStatic() -{ - QmcStaticCipher sc; - sc.proc(blobData, dataOffset); -} - -void QmcDecode::DecodeMapRC4() { - if (rawKeyBuf.size() > 300) - { - QmcRC4Cipher c(rawKeyBuf, 2); - c.proc(blobData, dataOffset); - } - else - { - QmcMapCipher c(rawKeyBuf, 2); - c.proc(blobData, dataOffset); - } -} - -void QmcDecode::DecodeCache() -{ - for (size_t i = 0; i < blobData.size(); i++) { - blobData[i] ^= 0xf4; - blobData[i] = ((blobData[i] & 0b00111111) << 2) | (blobData[i] >> 6); // rol 2 - } -} - -void QmcDecode::DecodeTm() -{ - uint8_t const TM_HEADER[] = { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70 }; - for (size_t cur = dataOffset, i = 0; cur < 8 && i < blobData.size(); ++cur, ++i) { - blobData[i] = TM_HEADER[dataOffset]; - } -} - -std::vector QmcDecode::Decode(size_t offset) -{ - dataOffset = offset; - if (cipherType == "Map/RC4") - { - DecodeMapRC4(); - } - else if (cipherType == "Static") - { - DecodeStatic(); - } - else if (cipherType == "cache") - { - DecodeCache(); - } - else if (cipherType == "ios") - { - DecodeTm(); - } - else { - error = "File is invalid or encryption type is not supported."; - } - return blobData; -} diff --git a/src/QmcWasm/qmc_cipher.hpp b/src/QmcWasm/qmc_cipher.hpp deleted file mode 100644 index 8dc2b18..0000000 --- a/src/QmcWasm/qmc_cipher.hpp +++ /dev/null @@ -1,290 +0,0 @@ -#include -#include -class QmcStaticCipher { -private: - uint8_t staticCipherBox[256] = { - 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, //0x00 - 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, //0x08 - 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, //0x10 - 0xCE, 0xB8, 0xDC, 0xC3, 0x4A, 0x67, 0x93, 0xD6, //0x18 - 0x28, 0xC2, 0x91, 0x70, 0xCA, 0x8D, 0xA2, 0xA4, //0x20 - 0xF0, 0x08, 0x61, 0x90, 0x7E, 0x6F, 0xA2, 0xE0, //0x28 - 0xEB, 0xAE, 0x3E, 0xB6, 0x67, 0xC7, 0x92, 0xF4, //0x30 - 0x91, 0xB5, 0xF6, 0x6C, 0x5E, 0x84, 0x40, 0xF7, //0x38 - 0xF3, 0x1B, 0x02, 0x7F, 0xD5, 0xAB, 0x41, 0x89, //0x40 - 0x28, 0xF4, 0x25, 0xCC, 0x52, 0x11, 0xAD, 0x43, //0x48 - 0x68, 0xA6, 0x41, 0x8B, 0x84, 0xB5, 0xFF, 0x2C, //0x50 - 0x92, 0x4A, 0x26, 0xD8, 0x47, 0x6A, 0x7C, 0x95, //0x58 - 0x61, 0xCC, 0xE6, 0xCB, 0xBB, 0x3F, 0x47, 0x58, //0x60 - 0x89, 0x75, 0xC3, 0x75, 0xA1, 0xD9, 0xAF, 0xCC, //0x68 - 0x08, 0x73, 0x17, 0xDC, 0xAA, 0x9A, 0xA2, 0x16, //0x70 - 0x41, 0xD8, 0xA2, 0x06, 0xC6, 0x8B, 0xFC, 0x66, //0x78 - 0x34, 0x9F, 0xCF, 0x18, 0x23, 0xA0, 0x0A, 0x74, //0x80 - 0xE7, 0x2B, 0x27, 0x70, 0x92, 0xE9, 0xAF, 0x37, //0x88 - 0xE6, 0x8C, 0xA7, 0xBC, 0x62, 0x65, 0x9C, 0xC2, //0x90 - 0x08, 0xC9, 0x88, 0xB3, 0xF3, 0x43, 0xAC, 0x74, //0x98 - 0x2C, 0x0F, 0xD4, 0xAF, 0xA1, 0xC3, 0x01, 0x64, //0xA0 - 0x95, 0x4E, 0x48, 0x9F, 0xF4, 0x35, 0x78, 0x95, //0xA8 - 0x7A, 0x39, 0xD6, 0x6A, 0xA0, 0x6D, 0x40, 0xE8, //0xB0 - 0x4F, 0xA8, 0xEF, 0x11, 0x1D, 0xF3, 0x1B, 0x3F, //0xB8 - 0x3F, 0x07, 0xDD, 0x6F, 0x5B, 0x19, 0x30, 0x19, //0xC0 - 0xFB, 0xEF, 0x0E, 0x37, 0xF0, 0x0E, 0xCD, 0x16, //0xC8 - 0x49, 0xFE, 0x53, 0x47, 0x13, 0x1A, 0xBD, 0xA4, //0xD0 - 0xF1, 0x40, 0x19, 0x60, 0x0E, 0xED, 0x68, 0x09, //0xD8 - 0x06, 0x5F, 0x4D, 0xCF, 0x3D, 0x1A, 0xFE, 0x20, //0xE0 - 0x77, 0xE4, 0xD9, 0xDA, 0xF9, 0xA4, 0x2B, 0x76, //0xE8 - 0x1C, 0x71, 0xDB, 0x00, 0xBC, 0xFD, 0x0C, 0x6C, //0xF0 - 0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11 //0xF8 - }; - - uint8_t getMask(size_t offset) { - if (offset > 0x7fff) offset %= 0x7fff; - return staticCipherBox[(offset * offset + 27) & 0xff]; - } - -public: - void proc(std::vector& buf, size_t offset) { - for (size_t i = 0; i < buf.size(); i++) { - buf[i] ^= getMask(offset + i); - } - } -}; - -class QmcMapCipher { -private: - std::vector key; - - uint8_t rotate(uint8_t value, size_t bits) { - auto rotate = (bits + 4) % 8; - auto left = value << rotate; - auto right = value >> rotate; - return (left | right) & 0xff; - } - - uint8_t getMask(size_t offset) { - if (offset > 0x7fff) offset %= 0x7fff; - - const auto idx = (offset * offset + 71214) % key.size(); - return rotate(key[idx], idx & 0x7); - } - -public: - QmcMapCipher(std::vector &argKey, short operation) { - if (operation == 2) - { - if (argKey.size() == 0) { - return; - } - } - else if (operation == 1) - { - const char WordList[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - srand(time(0)); - uint32_t number = 0; - while (number > 300 || number == 0) - { - number = rand(); - } - argKey.resize(number); - for (int i = 0; i < argKey.size(); i++) { - number = rand(); - argKey[i] = WordList[number % 62]; - } - } - else - { - return; - } - - key = argKey; - } - - void proc(std::vector& buf, size_t offset) { - for (size_t i = 0; i < buf.size(); i++) { - buf[i] ^= getMask(offset + i); - } - } -}; - -class QmcRC4Cipher { -public: - void proc(std::vector& buf, size_t offset) { - // Macro: common code after each process -#define postProcess(len) \ - { \ - toProcess -= len; \ - processed += len; \ - offset += len; \ - /* no more data */ \ - if (toProcess == 0) { \ - return; \ - } \ - } - - size_t toProcess = buf.size(); - size_t processed = 0; - std::vector tmpbuf; - - // ǰ 128 ֽʹòͬĽܷ - if (offset < FIRST_SEGMENT_SIZE) { - size_t len_segment = std::min(FIRST_SEGMENT_SIZE - offset, buf.size()); - tmpbuf.resize(len_segment); - for (size_t i = 0; i < len_segment; i++) - { - tmpbuf[i] = buf[processed + i]; - } - procFirstSegment(tmpbuf, offset); - for (size_t i = 0; i < len_segment; i++) - { - buf[processed + i] = tmpbuf[i]; - } - postProcess(len_segment); - } - - - // - if (offset % SEGMENT_SIZE != 0) { - size_t len_segment = std::min(SEGMENT_SIZE - (offset % SEGMENT_SIZE), toProcess); - tmpbuf.resize(len_segment); - for (size_t i = 0; i < len_segment; i++) - { - tmpbuf[i] = buf[processed + i]; - } - procASegment(tmpbuf, offset); - for (size_t i = 0; i < len_segment; i++) - { - buf[processed + i] = tmpbuf[i]; - } - postProcess(len_segment); - } - - // ÿһн - while (toProcess > SEGMENT_SIZE) { - tmpbuf.resize(SEGMENT_SIZE); - for (size_t i = 0; i < SEGMENT_SIZE; i++) - { - tmpbuf[i] = buf[processed + i]; - } - procASegment(tmpbuf, offset); - for (size_t i = 0; i < SEGMENT_SIZE; i++) - { - buf[processed + i] = tmpbuf[i]; - } - postProcess(SEGMENT_SIZE); - } - - if (toProcess > 0) { - tmpbuf.resize(toProcess); - for (size_t i = 0; i < toProcess; i++) - { - tmpbuf[i] = buf[processed + i]; - } - procASegment(tmpbuf, offset); - for (size_t i = 0; i < toProcess; i++) - { - buf[processed + i] = tmpbuf[i]; - } - } - -#undef postProcess - } - - QmcRC4Cipher(std::vector& argKey, short operation) { - if (operation == 2) - { - if (argKey.size() == 0) { - return; - } - } - else if (operation == 1) - { - const char WordList[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - srand(time(0)); - uint32_t number = 0; - while (number <= 300 || number >= 512) - { - number = rand(); - } - argKey.resize(number); - for (int i = 0; i < argKey.size(); i++) { - number = rand(); - argKey[i] = WordList[number % 62]; - } - } - else - { - return; - } - - key = argKey; - - // init seed box - S.resize(key.size()); - for (size_t i = 0; i < key.size(); ++i) { - S[i] = i & 0xff; - } - size_t j = 0; - for (size_t i = 0; i < key.size(); ++i) { - j = (S[i] + j + key[i % key.size()]) % key.size(); - std::swap(S[i], S[j]); - } - - // init hash base - hash = 1; - for (size_t i = 0; i < key.size(); i++) { - uint8_t value = key[i]; - - // ignore if key char is '\x00' - if (!value) continue; - - auto next_hash = hash * value; - if (next_hash == 0 || next_hash <= hash) break; - - hash = next_hash; - } - } - -private: - const size_t FIRST_SEGMENT_SIZE = 0x80; - const size_t SEGMENT_SIZE = 5120; - - std::vector S; - std::vector key; - uint32_t hash = 1; - - void procFirstSegment(std::vector& buf, size_t offset) { - for (size_t i = 0; i < buf.size(); i++) { - buf[i] ^= key[getSegmentKey(offset + i)]; - } - } - - void procASegment(std::vector& buf, size_t offset) { - // Initialise a new seed box - std::vector nS; - nS = S; - - // Calculate the number of bytes to skip. - // The initial "key" derived from segment id, plus the current offset. - int64_t skipLen = (offset % SEGMENT_SIZE) + getSegmentKey(int(offset / SEGMENT_SIZE)); - - // decrypt the block - size_t j = 0; - size_t k = 0; - int i = -skipLen; - for (; i < (int)buf.size(); i++) { - j = (j + 1) % key.size(); - k = (nS[j] + k) % key.size(); - std::swap(nS[k], nS[j]); - - if (i >= 0) { - buf[i] ^= nS[(nS[j] + nS[k]) % key.size()]; - } - } - } - - uint64_t getSegmentKey(int id) { - auto seed = key[id % key.size()]; - uint64_t idx = ((double)hash / ((id + 1) * seed)) * 100.0; - return idx % key.size(); - } -}; diff --git a/src/QmcWasm/qmc_key.hpp b/src/QmcWasm/qmc_key.hpp deleted file mode 100644 index f3178cd..0000000 --- a/src/QmcWasm/qmc_key.hpp +++ /dev/null @@ -1,217 +0,0 @@ -#include"TencentTea.hpp" -#include "base64.hpp" - -void simpleMakeKey(uint8_t salt, int length, std::vector &key_buf) { - for (size_t i = 0; i < length; ++i) { - double tmp = tan((float)salt + (double)i * 0.1); - key_buf[i] = 0xFF & (uint8_t)(fabs(tmp) * 100.0); - } -} - -std::vector v2KeyPrefix = { 0x51, 0x51, 0x4D, 0x75, 0x73, 0x69, 0x63, 0x20, 0x45, 0x6E, 0x63, 0x56, 0x32, 0x2C, 0x4B, 0x65, 0x79, 0x3A }; - -bool decryptV2Key(std::vector key, std::vector& outVec) -{ - if (v2KeyPrefix.size() > key.size()) - { - return true; - } - for (size_t i = 0; i < v2KeyPrefix.size(); i++) - { - if (key[i] != v2KeyPrefix[i]) - { - return true; - } - } - - std::vector mixKey1 = { 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 }; - std::vector mixKey2 = { 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 }; - - std::vector out; - std::vector tmpKey; - tmpKey.resize(key.size() - 18); - for (size_t i = 0; i < tmpKey.size(); i++) - { - tmpKey[i] = key[18 + i]; - } - if (!decryptTencentTea(tmpKey, mixKey1, out)) - { - outVec.resize(0); - //EncV2 key decode failed. - return false; - } - - tmpKey.resize(out.size()); - for (size_t i = 0; i < tmpKey.size(); i++) - { - tmpKey[i] = out[i]; - } - out.resize(0); - if (!decryptTencentTea(tmpKey, mixKey2, out)) - { - outVec.resize(0); - //EncV2 key decode failed. - return false; - } - - outVec.resize(base64::decoded_size(out.size())); - auto n = base64::decode(outVec.data(), (const char*)(out.data()), out.size()).first; - - if (n < 16) - { - outVec.resize(0); - //EncV2 key size is too small. - return false; - } - outVec.resize(n); - - return true; -} - -bool encryptV2Key(std::vector key, std::vector& outVec) -{ - if (key.size() < 16) - { - outVec.resize(0); - //EncV2 key size is too small. - return false; - } - - std::vector in; - in.resize(base64::encoded_size(key.size())); - auto n = base64::encode(in.data(), (const char*)(key.data()), key.size()); - in.resize(n); - - std::vector mixKey1 = { 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 }; - std::vector mixKey2 = { 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 }; - - std::vector tmpKey; - if (!encryptTencentTea(in, mixKey2, tmpKey)) - { - outVec.resize(0); - //EncV2 key decode failed. - return false; - } - in.resize(tmpKey.size()); - for (size_t i = 0; i < tmpKey.size(); i++) - { - in[i] = tmpKey[i]; - } - tmpKey.resize(0); - - if (!encryptTencentTea(in, mixKey1, tmpKey)) - { - outVec.resize(0); - //EncV2 key decode failed. - return false; - } - outVec.resize(tmpKey.size() + 18); - for (size_t i = 0; i < tmpKey.size(); i++) - { - outVec[18 + i] = tmpKey[i]; - } - - for (size_t i = 0; i < v2KeyPrefix.size(); i++) - { - outVec[i] = v2KeyPrefix[i]; - } - - return true; -} - -bool QmcDecryptKey(std::vector raw, std::vector &outVec) { - std::vector rawDec; - rawDec.resize(base64::decoded_size(raw.size())); - auto n = base64::decode(rawDec.data(), (const char*)(raw.data()), raw.size()).first; - if (n < 16) { - return false; - //key length is too short - } - rawDec.resize(n); - - std::vector tmpIn = rawDec; - if (!decryptV2Key(tmpIn, rawDec)) - { - //decrypt EncV2 failed. - return false; - } - - std::vector simpleKey; - simpleKey.resize(8); - simpleMakeKey(106, 8, simpleKey); - std::vector teaKey; - teaKey.resize(16); - for (size_t i = 0; i < 8; i++) { - teaKey[i << 1] = simpleKey[i]; - teaKey[(i << 1) + 1] = rawDec[i]; - } - std::vector out; - std::vector tmpRaw; - tmpRaw.resize(rawDec.size() - 8); - for (size_t i = 0; i < tmpRaw.size(); i++) - { - tmpRaw[i] = rawDec[8 + i]; - } - if (decryptTencentTea(tmpRaw, teaKey, out)) - { - rawDec.resize(8 + out.size()); - for (size_t i = 0; i < out.size(); i++) - { - rawDec[8 + i] = out[i]; - } - outVec = rawDec; - return true; - } - else - { - return false; - } -} - -bool QmcEncryptKey(std::vector raw, std::vector& outVec, bool useEncV2 = true) { - std::vector simpleKey; - simpleKey.resize(8); - simpleMakeKey(106, 8, simpleKey); - std::vector teaKey; - teaKey.resize(16); - for (size_t i = 0; i < 8; i++) { - teaKey[i << 1] = simpleKey[i]; - teaKey[(i << 1) + 1] = raw[i]; - } - std::vector out; - out.resize(raw.size() - 8); - for (size_t i = 0; i < out.size(); i++) - { - out[i] = raw[8 + i]; - } - std::vector tmpRaw; - if (encryptTencentTea(out, teaKey, tmpRaw)) - { - raw.resize(tmpRaw.size() + 8); - for (size_t i = 0; i < tmpRaw.size(); i++) - { - raw[i + 8] = tmpRaw[i]; - } - - if (useEncV2) - { - std::vector tmpIn = raw; - if (!encryptV2Key(tmpIn, raw)) - { - //encrypt EncV2 failed. - return false; - } - } - - std::vector rawEnc; - rawEnc.resize(base64::encoded_size(raw.size())); - auto n = base64::encode(rawEnc.data(), (const char*)(raw.data()), raw.size()); - rawEnc.resize(n); - outVec = rawEnc; - return true; - } - else - { - return false; - } -} diff --git a/src/decrypt/kgm.ts b/src/decrypt/kgm.ts index ca3afa6..b6553c9 100644 --- a/src/decrypt/kgm.ts +++ b/src/decrypt/kgm.ts @@ -9,7 +9,6 @@ import { import { parseBlob as metaParseBlob } from 'music-metadata-browser'; import { DecryptResult } from '@/decrypt/entity'; import { DecryptKgmWasm } from '@/decrypt/kgm_wasm'; -import { decryptKgmByteAtOffsetV2, decryptVprByteAtOffset } from '@jixun/kugou-crypto/dist/utils/decryptionHelper'; //prettier-ignore const VprHeader = [ @@ -29,33 +28,14 @@ export async function Decrypt(file: File, raw_filename: string, raw_ext: string) } else { if (!BytesHasPrefix(new Uint8Array(oriData), KgmHeader)) throw Error('Not a valid kgm(a) file!'); } - let musicDecoded: Uint8Array | undefined; + let musicDecoded = new Uint8Array(); if (globalThis.WebAssembly) { - console.log('kgm: using wasm decoder'); - const kgmDecrypted = await DecryptKgmWasm(oriData, raw_ext); if (kgmDecrypted.success) { musicDecoded = kgmDecrypted.data; console.log('kgm wasm decoder suceeded'); } else { - console.warn('KgmWasm failed with error %s', kgmDecrypted.error || '(unknown error)'); - } - } - - if (!musicDecoded) { - musicDecoded = new Uint8Array(oriData); - let bHeaderLen = new DataView(musicDecoded.slice(0x10, 0x14).buffer); - let headerLen = bHeaderLen.getUint32(0, true); - - let key1 = Array.from(musicDecoded.slice(0x1c, 0x2c)); - key1.push(0); - - musicDecoded = musicDecoded.slice(headerLen); - let dataLen = musicDecoded.length; - - const decryptByte = raw_ext === 'vpr' ? decryptVprByteAtOffset : decryptKgmByteAtOffsetV2; - for (let i = 0; i < dataLen; i++) { - musicDecoded[i] = decryptByte(musicDecoded[i], key1, i); + throw new Error(kgmDecrypted.error || '(unknown error)'); } } diff --git a/src/decrypt/kgm_wasm.ts b/src/decrypt/kgm_wasm.ts index da45a38..039c3be 100644 --- a/src/decrypt/kgm_wasm.ts +++ b/src/decrypt/kgm_wasm.ts @@ -1,4 +1,5 @@ -import KgmCryptoModule from '@/KgmWasm/KgmWasmBundle'; +import { KgmCrypto } from '@xhacker/kgmwasm/KgmWasmBundle'; +import KgmCryptoModule from '@xhacker/kgmwasm/KgmWasmBundle'; import { MergeUint8Array } from '@/utils/MergeUint8Array'; // 每次处理 2M 的数据 @@ -20,26 +21,26 @@ export async function DecryptKgmWasm(kgmBlob: ArrayBuffer, ext: string): Promise const result: KGMDecryptionResult = { success: false, data: new Uint8Array(), error: '' }; // 初始化模组 - let KgmCrypto: any; + let KgmCryptoObj: KgmCrypto; try { - KgmCrypto = await KgmCryptoModule(); + KgmCryptoObj = await KgmCryptoModule(); } catch (err: any) { result.error = err?.message || 'wasm 加载失败'; return result; } - if (!KgmCrypto) { + if (!KgmCryptoObj) { result.error = 'wasm 加载失败'; return result; } // 申请内存块,并文件末端数据到 WASM 的内存堆 let kgmBuf = new Uint8Array(kgmBlob); - const pQmcBuf = KgmCrypto._malloc(DECRYPTION_BUF_SIZE); - KgmCrypto.writeArrayToMemory(kgmBuf.slice(0, DECRYPTION_BUF_SIZE), pQmcBuf); + const pQmcBuf = KgmCryptoObj._malloc(DECRYPTION_BUF_SIZE); + KgmCryptoObj.writeArrayToMemory(kgmBuf.slice(0, DECRYPTION_BUF_SIZE), pQmcBuf); // 进行解密初始化 - const headerSize = KgmCrypto.preDec(pQmcBuf, DECRYPTION_BUF_SIZE, ext); + const headerSize = KgmCryptoObj.preDec(pQmcBuf, DECRYPTION_BUF_SIZE, ext); console.log(headerSize); kgmBuf = kgmBuf.slice(headerSize); @@ -51,14 +52,14 @@ export async function DecryptKgmWasm(kgmBlob: ArrayBuffer, ext: string): Promise // 解密一些片段 const blockData = new Uint8Array(kgmBuf.slice(offset, offset + blockSize)); - KgmCrypto.writeArrayToMemory(blockData, pQmcBuf); - KgmCrypto.decBlob(pQmcBuf, blockSize, offset); - decryptedParts.push(KgmCrypto.HEAPU8.slice(pQmcBuf, pQmcBuf + blockSize)); + KgmCryptoObj.writeArrayToMemory(blockData, pQmcBuf); + KgmCryptoObj.decBlob(pQmcBuf, blockSize, offset); + decryptedParts.push(KgmCryptoObj.HEAPU8.slice(pQmcBuf, pQmcBuf + blockSize)); offset += blockSize; bytesToDecrypt -= blockSize; } - KgmCrypto._free(pQmcBuf); + KgmCryptoObj._free(pQmcBuf); result.data = MergeUint8Array(decryptedParts); result.success = true; diff --git a/src/decrypt/qmc.test.ts b/src/decrypt/qmc.test.ts deleted file mode 100644 index 73156bd..0000000 --- a/src/decrypt/qmc.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import fs from 'fs'; -import { QmcDecoder } from '@/decrypt/qmc'; -import { BytesEqual } from '@/decrypt/utils'; - -function loadTestDataDecoder(name: string): { - cipherText: Uint8Array; - clearText: Uint8Array; -} { - const cipherBody = fs.readFileSync(`./testdata/${name}_raw.bin`); - const cipherSuffix = fs.readFileSync(`./testdata/${name}_suffix.bin`); - const cipherText = new Uint8Array(cipherBody.length + cipherSuffix.length); - cipherText.set(cipherBody); - cipherText.set(cipherSuffix, cipherBody.length); - return { - cipherText, - clearText: fs.readFileSync(`testdata/${name}_target.bin`), - }; -} - -test('qmc: real file', async () => { - const cases = ['mflac0_rc4', 'mflac_rc4', 'mflac_map', 'mgg_map', 'qmc0_static']; - for (const name of cases) { - const { clearText, cipherText } = loadTestDataDecoder(name); - const c = new QmcDecoder(cipherText); - const buf = c.decrypt(); - - expect(BytesEqual(buf, clearText)).toBeTruthy(); - } -}); diff --git a/src/decrypt/qmc.ts b/src/decrypt/qmc.ts index 3154f90..b5b5401 100644 --- a/src/decrypt/qmc.ts +++ b/src/decrypt/qmc.ts @@ -1,8 +1,6 @@ -import { QmcMapCipher, QmcRC4Cipher, QmcStaticCipher, QmcStreamCipher } from './qmc_cipher'; import { AudioMimeType, GetArrayBuffer, SniffAudioExt } from '@/decrypt/utils'; import { DecryptResult } from '@/decrypt/entity'; -import { QmcDeriveKey } from '@/decrypt/qmc_key'; import { DecryptQmcWasm } from '@/decrypt/qmc_wasm'; import { extractQQMusicMeta } from '@/utils/qm_meta'; @@ -18,7 +16,7 @@ export const HandlerMap: { [key: string]: Handler } = { mgg1: { ext: 'ogg', version: 2 }, mflac: { ext: 'flac', version: 2 }, mflac0: { ext: 'flac', version: 2 }, - mmp4: { ext: 'mmp4', version: 2 }, + mmp4: { ext: 'mp4', version: 2 }, // qmcflac / qmcogg: // 有可能是 v2 加密但混用同一个后缀名。 @@ -52,12 +50,10 @@ export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string) let { version } = handler; const fileBuffer = await GetArrayBuffer(file); - let musicDecoded: Uint8Array | undefined; + let musicDecoded = new Uint8Array(); let musicID: number | string | undefined; if (version === 2 && globalThis.WebAssembly) { - console.log('qmc: using wasm decoder'); - const v2Decrypted = await DecryptQmcWasm(fileBuffer, raw_ext); // 若 v2 检测失败,降级到 v1 再尝试一次 if (v2Decrypted.success) { @@ -65,18 +61,10 @@ export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string) musicID = v2Decrypted.songId; console.log('qmc wasm decoder suceeded'); } else { - console.warn('QmcWasm failed with error %s', v2Decrypted.error || '(unknown error)'); + throw new Error(v2Decrypted.error || '(unknown error)'); } } - if (!musicDecoded) { - // may throw error - console.log('qmc: using js decoder'); - const d = new QmcDecoder(new Uint8Array(fileBuffer)); - musicDecoded = d.decrypt(); - musicID = d.songID; - } - const ext = SniffAudioExt(musicDecoded, handler.ext); const mime = AudioMimeType[ext]; @@ -98,88 +86,3 @@ export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string) mime: mime, }; } - -export class QmcDecoder { - private static readonly BYTE_COMMA = ','.charCodeAt(0); - private readonly file: Uint8Array; - private readonly size: number; - private decoded: boolean = false; - private audioSize?: number; - private cipher?: QmcStreamCipher; - - public constructor(file: Uint8Array) { - this.file = file; - this.size = file.length; - this.searchKey(); - } - - private _songID?: number; - - public get songID() { - return this._songID; - } - - public decrypt(): Uint8Array { - if (!this.cipher) { - throw new Error('no cipher found'); - } - if (!this.audioSize || this.audioSize <= 0) { - throw new Error('invalid audio size'); - } - const audioBuf = this.file.subarray(0, this.audioSize); - - if (!this.decoded) { - this.cipher.decrypt(audioBuf, 0); - this.decoded = true; - } - - return audioBuf; - } - - private searchKey() { - const last4Byte = this.file.slice(-4); - const textEnc = new TextDecoder(); - if (textEnc.decode(last4Byte) === 'STag') { - throw new Error('文件中没有写入密钥,无法解锁,请降级App并重试'); - } else if (textEnc.decode(last4Byte) === 'QTag') { - const sizeBuf = this.file.slice(-8, -4); - const sizeView = new DataView(sizeBuf.buffer, sizeBuf.byteOffset); - const keySize = sizeView.getUint32(0, false); - this.audioSize = this.size - keySize - 8; - - const rawKey = this.file.subarray(this.audioSize, this.size - 8); - const keyEnd = rawKey.findIndex((v) => v == QmcDecoder.BYTE_COMMA); - if (keyEnd < 0) { - throw new Error('invalid key: search raw key failed'); - } - this.setCipher(rawKey.subarray(0, keyEnd)); - - const idBuf = rawKey.subarray(keyEnd + 1); - const idEnd = idBuf.findIndex((v) => v == QmcDecoder.BYTE_COMMA); - if (keyEnd < 0) { - throw new Error('invalid key: search song id failed'); - } - this._songID = parseInt(textEnc.decode(idBuf.subarray(0, idEnd)), 10); - } else { - const sizeView = new DataView(last4Byte.buffer, last4Byte.byteOffset); - const keySize = sizeView.getUint32(0, true); - if (keySize < 0x400) { - this.audioSize = this.size - keySize - 4; - const rawKey = this.file.subarray(this.audioSize, this.size - 4); - this.setCipher(rawKey); - } else { - this.audioSize = this.size; - this.cipher = new QmcStaticCipher(); - } - } - } - - private setCipher(keyRaw: Uint8Array) { - const keyDec = QmcDeriveKey(keyRaw); - if (keyDec.length > 300) { - this.cipher = new QmcRC4Cipher(keyDec); - } else { - this.cipher = new QmcMapCipher(keyDec); - } - } -} diff --git a/src/decrypt/qmc_cipher.test.ts b/src/decrypt/qmc_cipher.test.ts deleted file mode 100644 index 0f1e0b7..0000000 --- a/src/decrypt/qmc_cipher.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { QmcMapCipher, QmcRC4Cipher, QmcStaticCipher } from '@/decrypt/qmc_cipher'; -import fs from 'fs'; - -test('static cipher [0x7ff8,0x8000) ', () => { - //prettier-ignore - const expected = new Uint8Array([ - 0xD8, 0x52, 0xF7, 0x67, 0x90, 0xCA, 0xD6, 0x4A, - 0x4A, 0xD6, 0xCA, 0x90, 0x67, 0xF7, 0x52, 0xD8, - ]) - - const c = new QmcStaticCipher(); - const buf = new Uint8Array(16); - c.decrypt(buf, 0x7ff8); - - expect(buf).toStrictEqual(expected); -}); - -test('static cipher [0,0x10) ', () => { - //prettier-ignore - const expected = new Uint8Array([ - 0xC3, 0x4A, 0xD6, 0xCA, 0x90, 0x67, 0xF7, 0x52, - 0xD8, 0xA1, 0x66, 0x62, 0x9F, 0x5B, 0x09, 0x00, - ]) - - const c = new QmcStaticCipher(); - const buf = new Uint8Array(16); - c.decrypt(buf, 0); - - expect(buf).toStrictEqual(expected); -}); - -test('map cipher: get mask', () => { - //prettier-ignore - const expected = new Uint8Array([ - 0xBB, 0x7D, 0x80, 0xBE, 0xFF, 0x38, 0x81, 0xFB, - 0xBB, 0xFF, 0x82, 0x3C, 0xFF, 0xBA, 0x83, 0x79, - ]) - const key = new Uint8Array(256); - for (let i = 0; i < 256; i++) key[i] = i; - const buf = new Uint8Array(16); - - const c = new QmcMapCipher(key); - c.decrypt(buf, 0); - expect(buf).toStrictEqual(expected); -}); - -function loadTestDataCipher(name: string): { - key: Uint8Array; - cipherText: Uint8Array; - clearText: Uint8Array; -} { - return { - key: fs.readFileSync(`testdata/${name}_key.bin`), - cipherText: fs.readFileSync(`testdata/${name}_raw.bin`), - clearText: fs.readFileSync(`testdata/${name}_target.bin`), - }; -} - -test('map cipher: real file', async () => { - const cases = ['mflac_map', 'mgg_map']; - for (const name of cases) { - const { key, clearText, cipherText } = loadTestDataCipher(name); - const c = new QmcMapCipher(key); - - c.decrypt(cipherText, 0); - - expect(cipherText).toStrictEqual(clearText); - } -}); - -test('rc4 cipher: real file', async () => { - const cases = ['mflac0_rc4', 'mflac_rc4']; - for (const name of cases) { - const { key, clearText, cipherText } = loadTestDataCipher(name); - const c = new QmcRC4Cipher(key); - - c.decrypt(cipherText, 0); - - expect(cipherText).toStrictEqual(clearText); - } -}); - -test('rc4 cipher: first segment', async () => { - const cases = ['mflac0_rc4', 'mflac_rc4']; - for (const name of cases) { - const { key, clearText, cipherText } = loadTestDataCipher(name); - const c = new QmcRC4Cipher(key); - - const buf = cipherText.slice(0, 128); - c.decrypt(buf, 0); - expect(buf).toStrictEqual(clearText.slice(0, 128)); - } -}); - -test('rc4 cipher: align block (128~5120)', async () => { - const cases = ['mflac0_rc4', 'mflac_rc4']; - for (const name of cases) { - const { key, clearText, cipherText } = loadTestDataCipher(name); - const c = new QmcRC4Cipher(key); - - const buf = cipherText.slice(128, 5120); - c.decrypt(buf, 128); - expect(buf).toStrictEqual(clearText.slice(128, 5120)); - } -}); - -test('rc4 cipher: simple block (5120~10240)', async () => { - const cases = ['mflac0_rc4', 'mflac_rc4']; - for (const name of cases) { - const { key, clearText, cipherText } = loadTestDataCipher(name); - const c = new QmcRC4Cipher(key); - - const buf = cipherText.slice(5120, 10240); - c.decrypt(buf, 5120); - expect(buf).toStrictEqual(clearText.slice(5120, 10240)); - } -}); diff --git a/src/decrypt/qmc_cipher.ts b/src/decrypt/qmc_cipher.ts deleted file mode 100644 index d0d3683..0000000 --- a/src/decrypt/qmc_cipher.ts +++ /dev/null @@ -1,199 +0,0 @@ -export interface QmcStreamCipher { - decrypt(buf: Uint8Array, offset: number): void; -} - -export class QmcStaticCipher implements QmcStreamCipher { - //prettier-ignore - private static readonly staticCipherBox: Uint8Array = new Uint8Array([ - 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, //0x00 - 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, //0x08 - 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, //0x10 - 0xCE, 0xB8, 0xDC, 0xC3, 0x4A, 0x67, 0x93, 0xD6, //0x18 - 0x28, 0xC2, 0x91, 0x70, 0xCA, 0x8D, 0xA2, 0xA4, //0x20 - 0xF0, 0x08, 0x61, 0x90, 0x7E, 0x6F, 0xA2, 0xE0, //0x28 - 0xEB, 0xAE, 0x3E, 0xB6, 0x67, 0xC7, 0x92, 0xF4, //0x30 - 0x91, 0xB5, 0xF6, 0x6C, 0x5E, 0x84, 0x40, 0xF7, //0x38 - 0xF3, 0x1B, 0x02, 0x7F, 0xD5, 0xAB, 0x41, 0x89, //0x40 - 0x28, 0xF4, 0x25, 0xCC, 0x52, 0x11, 0xAD, 0x43, //0x48 - 0x68, 0xA6, 0x41, 0x8B, 0x84, 0xB5, 0xFF, 0x2C, //0x50 - 0x92, 0x4A, 0x26, 0xD8, 0x47, 0x6A, 0x7C, 0x95, //0x58 - 0x61, 0xCC, 0xE6, 0xCB, 0xBB, 0x3F, 0x47, 0x58, //0x60 - 0x89, 0x75, 0xC3, 0x75, 0xA1, 0xD9, 0xAF, 0xCC, //0x68 - 0x08, 0x73, 0x17, 0xDC, 0xAA, 0x9A, 0xA2, 0x16, //0x70 - 0x41, 0xD8, 0xA2, 0x06, 0xC6, 0x8B, 0xFC, 0x66, //0x78 - 0x34, 0x9F, 0xCF, 0x18, 0x23, 0xA0, 0x0A, 0x74, //0x80 - 0xE7, 0x2B, 0x27, 0x70, 0x92, 0xE9, 0xAF, 0x37, //0x88 - 0xE6, 0x8C, 0xA7, 0xBC, 0x62, 0x65, 0x9C, 0xC2, //0x90 - 0x08, 0xC9, 0x88, 0xB3, 0xF3, 0x43, 0xAC, 0x74, //0x98 - 0x2C, 0x0F, 0xD4, 0xAF, 0xA1, 0xC3, 0x01, 0x64, //0xA0 - 0x95, 0x4E, 0x48, 0x9F, 0xF4, 0x35, 0x78, 0x95, //0xA8 - 0x7A, 0x39, 0xD6, 0x6A, 0xA0, 0x6D, 0x40, 0xE8, //0xB0 - 0x4F, 0xA8, 0xEF, 0x11, 0x1D, 0xF3, 0x1B, 0x3F, //0xB8 - 0x3F, 0x07, 0xDD, 0x6F, 0x5B, 0x19, 0x30, 0x19, //0xC0 - 0xFB, 0xEF, 0x0E, 0x37, 0xF0, 0x0E, 0xCD, 0x16, //0xC8 - 0x49, 0xFE, 0x53, 0x47, 0x13, 0x1A, 0xBD, 0xA4, //0xD0 - 0xF1, 0x40, 0x19, 0x60, 0x0E, 0xED, 0x68, 0x09, //0xD8 - 0x06, 0x5F, 0x4D, 0xCF, 0x3D, 0x1A, 0xFE, 0x20, //0xE0 - 0x77, 0xE4, 0xD9, 0xDA, 0xF9, 0xA4, 0x2B, 0x76, //0xE8 - 0x1C, 0x71, 0xDB, 0x00, 0xBC, 0xFD, 0x0C, 0x6C, //0xF0 - 0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11, //0xF8 - ]) - - public getMask(offset: number) { - if (offset > 0x7fff) offset %= 0x7fff; - return QmcStaticCipher.staticCipherBox[(offset * offset + 27) & 0xff]; - } - - public decrypt(buf: Uint8Array, offset: number) { - for (let i = 0; i < buf.length; i++) { - buf[i] ^= this.getMask(offset + i); - } - } -} - -export class QmcMapCipher implements QmcStreamCipher { - key: Uint8Array; - n: number; - - constructor(key: Uint8Array) { - if (key.length == 0) throw Error('qmc/cipher_map: invalid key size'); - - this.key = key; - this.n = key.length; - } - - private static rotate(value: number, bits: number) { - let rotate = (bits + 4) % 8; - let left = value << rotate; - let right = value >> rotate; - return (left | right) & 0xff; - } - - decrypt(buf: Uint8Array, offset: number): void { - for (let i = 0; i < buf.length; i++) { - buf[i] ^= this.getMask(offset + i); - } - } - - private getMask(offset: number) { - if (offset > 0x7fff) offset %= 0x7fff; - - const idx = (offset * offset + 71214) % this.n; - return QmcMapCipher.rotate(this.key[idx], idx & 0x7); - } -} - -export class QmcRC4Cipher implements QmcStreamCipher { - private static readonly FIRST_SEGMENT_SIZE = 0x80; - private static readonly SEGMENT_SIZE = 5120; - - S: Uint8Array; - N: number; - key: Uint8Array; - hash: number; - - constructor(key: Uint8Array) { - if (key.length == 0) { - throw Error('invalid key size'); - } - - this.key = key; - this.N = key.length; - - // init seed box - this.S = new Uint8Array(this.N); - for (let i = 0; i < this.N; ++i) { - this.S[i] = i & 0xff; - } - let j = 0; - for (let i = 0; i < this.N; ++i) { - j = (this.S[i] + j + this.key[i % this.N]) % this.N; - [this.S[i], this.S[j]] = [this.S[j], this.S[i]]; - } - - // init hash base - this.hash = 1; - for (let i = 0; i < this.N; i++) { - let value = this.key[i]; - - // ignore if key char is '\x00' - if (!value) continue; - - const next_hash = (this.hash * value) >>> 0; - if (next_hash == 0 || next_hash <= this.hash) break; - - this.hash = next_hash; - } - } - - decrypt(buf: Uint8Array, offset: number): void { - let toProcess = buf.length; - let processed = 0; - const postProcess = (len: number): boolean => { - toProcess -= len; - processed += len; - offset += len; - return toProcess == 0; - }; - - // Initial segment - if (offset < QmcRC4Cipher.FIRST_SEGMENT_SIZE) { - const len_segment = Math.min(buf.length, QmcRC4Cipher.FIRST_SEGMENT_SIZE - offset); - this.encFirstSegment(buf.subarray(0, len_segment), offset); - if (postProcess(len_segment)) return; - } - - // align segment - if (offset % QmcRC4Cipher.SEGMENT_SIZE != 0) { - const len_segment = Math.min(QmcRC4Cipher.SEGMENT_SIZE - (offset % QmcRC4Cipher.SEGMENT_SIZE), toProcess); - this.encASegment(buf.subarray(processed, processed + len_segment), offset); - if (postProcess(len_segment)) return; - } - - // Batch process segments - while (toProcess > QmcRC4Cipher.SEGMENT_SIZE) { - this.encASegment(buf.subarray(processed, processed + QmcRC4Cipher.SEGMENT_SIZE), offset); - postProcess(QmcRC4Cipher.SEGMENT_SIZE); - } - - // Last segment (incomplete segment) - if (toProcess > 0) { - this.encASegment(buf.subarray(processed), offset); - } - } - - private encFirstSegment(buf: Uint8Array, offset: number) { - for (let i = 0; i < buf.length; i++) { - buf[i] ^= this.key[this.getSegmentKey(offset + i)]; - } - } - - private encASegment(buf: Uint8Array, offset: number) { - // Initialise a new seed box - const S = this.S.slice(0); - - // Calculate the number of bytes to skip. - // The initial "key" derived from segment id, plus the current offset. - const skipLen = - (offset % QmcRC4Cipher.SEGMENT_SIZE) + this.getSegmentKey(Math.floor(offset / QmcRC4Cipher.SEGMENT_SIZE)); - - // decrypt the block - let j = 0; - let k = 0; - for (let i = -skipLen; i < buf.length; i++) { - j = (j + 1) % this.N; - k = (S[j] + k) % this.N; - [S[k], S[j]] = [S[j], S[k]]; - - if (i >= 0) { - buf[i] ^= S[(S[j] + S[k]) % this.N]; - } - } - } - - private getSegmentKey(id: number): number { - const seed = this.key[id % this.N]; - const idx = Math.floor((this.hash / ((id + 1) * seed)) * 100.0); - return idx % this.N; - } -} diff --git a/src/decrypt/qmc_key.test.ts b/src/decrypt/qmc_key.test.ts deleted file mode 100644 index 2ec1e96..0000000 --- a/src/decrypt/qmc_key.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { QmcDeriveKey, simpleMakeKey } from '@/decrypt/qmc_key'; -import fs from 'fs'; - -test('key dec: make simple key', () => { - expect(simpleMakeKey(106, 8)).toStrictEqual([0x69, 0x56, 0x46, 0x38, 0x2b, 0x20, 0x15, 0x0b]); -}); - -function loadTestDataKeyDecrypt(name: string): { - cipherText: Uint8Array; - clearText: Uint8Array; -} { - return { - cipherText: fs.readFileSync(`testdata/${name}_key_raw.bin`), - clearText: fs.readFileSync(`testdata/${name}_key.bin`), - }; -} - -test('key dec: real file', async () => { - const cases = ['mflac_map', 'mgg_map', 'mflac0_rc4', 'mflac_rc4']; - for (const name of cases) { - const { clearText, cipherText } = loadTestDataKeyDecrypt(name); - const buf = QmcDeriveKey(cipherText); - - expect(buf).toStrictEqual(clearText); - } -}); diff --git a/src/decrypt/qmc_key.ts b/src/decrypt/qmc_key.ts deleted file mode 100644 index b23b15f..0000000 --- a/src/decrypt/qmc_key.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { TeaCipher } from '@/utils/tea'; - -const SALT_LEN = 2; -const ZERO_LEN = 7; - -export function QmcDeriveKey(raw: Uint8Array): Uint8Array { - const textDec = new TextDecoder(); - let rawDec = Buffer.from(textDec.decode(raw), 'base64'); - let n = rawDec.length; - if (n < 16) { - throw Error('key length is too short'); - } - - rawDec = decryptV2Key(rawDec); - - const simpleKey = simpleMakeKey(106, 8); - let teaKey = new Uint8Array(16); - for (let i = 0; i < 8; i++) { - teaKey[i << 1] = simpleKey[i]; - teaKey[(i << 1) + 1] = rawDec[i]; - } - const sub = decryptTencentTea(rawDec.subarray(8), teaKey); - rawDec.set(sub, 8); - return rawDec.subarray(0, 8 + sub.length); -} - -// simpleMakeKey exported only for unit test -export function simpleMakeKey(salt: number, length: number): number[] { - const keyBuf: number[] = []; - for (let i = 0; i < length; i++) { - const tmp = Math.tan(salt + i * 0.1); - keyBuf[i] = 0xff & (Math.abs(tmp) * 100.0); - } - return keyBuf; -} - -const mixKey1: Uint8Array = new Uint8Array([ 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 ]) -const mixKey2: Uint8Array = new Uint8Array([ 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 ]) - -function decryptV2Key(key: Buffer): Buffer -{ - const textEnc = new TextDecoder(); - if (key.length < 18 || textEnc.decode(key.slice(0, 18)) !== 'QQMusic EncV2,Key:') { - return key; - } - - let out = decryptTencentTea(key.slice(18), mixKey1); - out = decryptTencentTea(out, mixKey2); - const textDec = new TextDecoder(); - const keyDec = Buffer.from(textDec.decode(out), 'base64'); - let n = keyDec.length; - if (n < 16) { - throw Error('EncV2 key decode failed'); - } - - return keyDec; -} - -function decryptTencentTea(inBuf: Uint8Array, key: Uint8Array): Uint8Array { - if (inBuf.length % 8 != 0) { - throw Error('inBuf size not a multiple of the block size'); - } - if (inBuf.length < 16) { - throw Error('inBuf size too small'); - } - - const blk = new TeaCipher(key, 32); - - const tmpBuf = new Uint8Array(8); - const tmpView = new DataView(tmpBuf.buffer); - - blk.decrypt(tmpView, new DataView(inBuf.buffer, inBuf.byteOffset, 8)); - - const nPadLen = tmpBuf[0] & 0x7; //只要最低三位 - /*密文格式:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ - const outLen = inBuf.length - 1 /*PadLen*/ - nPadLen - SALT_LEN - ZERO_LEN; - const outBuf = new Uint8Array(outLen); - - let ivPrev = new Uint8Array(8); - let ivCur = inBuf.slice(0, 8); // init iv - let inBufPos = 8; - - // 跳过 Padding Len 和 Padding - let tmpIdx = 1 + nPadLen; - - // CBC IV 处理 - const cryptBlock = () => { - ivPrev = ivCur; - ivCur = inBuf.slice(inBufPos, inBufPos + 8); - for (let j = 0; j < 8; j++) { - tmpBuf[j] ^= ivCur[j]; - } - blk.decrypt(tmpView, tmpView); - inBufPos += 8; - tmpIdx = 0; - }; - - // 跳过 Salt - for (let i = 1; i <= SALT_LEN; ) { - if (tmpIdx < 8) { - tmpIdx++; - i++; - } else { - cryptBlock(); - } - } - - // 还原明文 - let outBufPos = 0; - while (outBufPos < outLen) { - if (tmpIdx < 8) { - outBuf[outBufPos] = tmpBuf[tmpIdx] ^ ivPrev[tmpIdx]; - outBufPos++; - tmpIdx++; - } else { - cryptBlock(); - } - } - - // 校验Zero - for (let i = 1; i <= ZERO_LEN; i++) { - if (tmpBuf[tmpIdx] != ivPrev[tmpIdx]) { - throw Error('zero check failed'); - } - } - return outBuf; -} diff --git a/src/decrypt/qmc_wasm.ts b/src/decrypt/qmc_wasm.ts index c2e06db..f747bf4 100644 --- a/src/decrypt/qmc_wasm.ts +++ b/src/decrypt/qmc_wasm.ts @@ -1,4 +1,5 @@ -import QmcCryptoModule from '@/QmcWasm/QmcWasmBundle'; +import { QmcCrypto } from '@xhacker/qmcwasm/QmcWasmBundle'; +import QmcCryptoModule from '@xhacker/qmcwasm/QmcWasmBundle'; import { MergeUint8Array } from '@/utils/MergeUint8Array'; // 每次处理 2M 的数据 @@ -21,32 +22,32 @@ export async function DecryptQmcWasm(qmcBlob: ArrayBuffer, ext: string): Promise const result: QMCDecryptionResult = { success: false, data: new Uint8Array(), songId: 0, error: '' }; // 初始化模组 - let QmcCrypto: any; + let QmcCryptoObj: QmcCrypto; try { - QmcCrypto = await QmcCryptoModule(); + QmcCryptoObj = await QmcCryptoModule(); } catch (err: any) { result.error = err?.message || 'wasm 加载失败'; return result; } - if (!QmcCrypto) { + if (!QmcCryptoObj) { result.error = 'wasm 加载失败'; return result; } // 申请内存块,并文件末端数据到 WASM 的内存堆 const qmcBuf = new Uint8Array(qmcBlob); - const pQmcBuf = QmcCrypto._malloc(DECRYPTION_BUF_SIZE); - QmcCrypto.writeArrayToMemory(qmcBuf.slice(-DECRYPTION_BUF_SIZE), pQmcBuf); + const pQmcBuf = QmcCryptoObj._malloc(DECRYPTION_BUF_SIZE); + QmcCryptoObj.writeArrayToMemory(qmcBuf.slice(-DECRYPTION_BUF_SIZE), pQmcBuf); // 进行解密初始化 ext = '.' + ext; - const tailSize = QmcCrypto.preDec(pQmcBuf, DECRYPTION_BUF_SIZE, ext); + const tailSize = QmcCryptoObj.preDec(pQmcBuf, DECRYPTION_BUF_SIZE, ext); if (tailSize == -1) { - result.error = QmcCrypto.getError(); + result.error = QmcCryptoObj.getErr(); return result; } else { - result.songId = QmcCrypto.getSongId(); + result.songId = QmcCryptoObj.getSongId(); result.songId = result.songId == "0" ? 0 : result.songId; } @@ -58,13 +59,13 @@ export async function DecryptQmcWasm(qmcBlob: ArrayBuffer, ext: string): Promise // 解密一些片段 const blockData = new Uint8Array(qmcBuf.slice(offset, offset + blockSize)); - QmcCrypto.writeArrayToMemory(blockData, pQmcBuf); - decryptedParts.push(QmcCrypto.HEAPU8.slice(pQmcBuf, pQmcBuf + QmcCrypto.decBlob(pQmcBuf, blockSize, offset))); + QmcCryptoObj.writeArrayToMemory(blockData, pQmcBuf); + decryptedParts.push(QmcCryptoObj.HEAPU8.slice(pQmcBuf, pQmcBuf + QmcCryptoObj.decBlob(pQmcBuf, blockSize, offset))); offset += blockSize; bytesToDecrypt -= blockSize; } - QmcCrypto._free(pQmcBuf); + QmcCryptoObj._free(pQmcBuf); result.data = MergeUint8Array(decryptedParts); result.success = true; diff --git a/src/decrypt/qmccache.ts b/src/decrypt/qmccache.ts index 6a57a94..0b6582b 100644 --- a/src/decrypt/qmccache.ts +++ b/src/decrypt/qmccache.ts @@ -17,7 +17,7 @@ import { parseBlob as metaParseBlob } from 'music-metadata-browser'; export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string): Promise { const buffer = await GetArrayBuffer(file); - let musicDecoded: Uint8Array | undefined; + let musicDecoded = new Uint8Array(); if (globalThis.WebAssembly) { console.log('qmc: using wasm decoder'); @@ -27,19 +27,10 @@ export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string) musicDecoded = qmcDecrypted.data; console.log('qmc wasm decoder suceeded'); } else { - console.warn('QmcWasm failed with error %s', qmcDecrypted.error || '(unknown error)'); + throw new Error(qmcDecrypted.error || '(unknown error)'); } } - if (!musicDecoded) { - musicDecoded = new Uint8Array(buffer); - let length = musicDecoded.length; - for (let i = 0; i < length; i++) { - let byte = musicDecoded[i] ^ 0xf4; // xor 0xf4 - byte = ((byte & 0b0011_1111) << 2) | (byte >> 6); // rol 2 - musicDecoded[i] = byte; - } - } let ext = SniffAudioExt(musicDecoded, ''); const newName = SplitFilename(raw_filename); let audioBlob: Blob; diff --git a/src/utils/tea.test.ts b/src/utils/tea.test.ts deleted file mode 100644 index bb3fbee..0000000 --- a/src/utils/tea.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2021 MengYX. All rights reserved. -// -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in https://go.dev/LICENSE. - -import { TeaCipher } from '@/utils/tea'; - -test('key size', () => { - // prettier-ignore - const testKey = new Uint8Array([ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, - 0x00, - ]) - expect(() => new TeaCipher(testKey.slice(0, 16))).not.toThrow(); - - expect(() => new TeaCipher(testKey)).toThrow(); - - expect(() => new TeaCipher(testKey.slice(0, 15))).toThrow(); -}); - -// prettier-ignore -const teaTests = [ - // These were sourced from https://github.com/froydnj/ironclad/blob/master/testing/test-vectors/tea.testvec - { - rounds: TeaCipher.numRounds, - key: new Uint8Array([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]), - plainText: new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), - cipherText: new Uint8Array([0x41, 0xea, 0x3a, 0x0a, 0x94, 0xba, 0xa9, 0x40]), - }, - { - rounds: TeaCipher.numRounds, - key: new Uint8Array([ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - ]), - plainText: new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), - cipherText: new Uint8Array([0x31, 0x9b, 0xbe, 0xfb, 0x01, 0x6a, 0xbd, 0xb2]), - }, - { - rounds: 16, - key: new Uint8Array([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]), - plainText: new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), - cipherText: new Uint8Array([0xed, 0x28, 0x5d, 0xa1, 0x45, 0x5b, 0x33, 0xc1]), - }, -]; - -test('rounds', () => { - const tt = teaTests[0]; - expect(() => new TeaCipher(tt.key, tt.rounds - 1)).toThrow(); -}); - -test('encrypt & decrypt', () => { - for (const tt of teaTests) { - const c = new TeaCipher(tt.key, tt.rounds); - - const buf = new Uint8Array(8); - const bufView = new DataView(buf.buffer); - - c.encrypt(bufView, new DataView(tt.plainText.buffer)); - expect(buf).toStrictEqual(tt.cipherText); - - c.decrypt(bufView, new DataView(tt.cipherText.buffer)); - expect(buf).toStrictEqual(tt.plainText); - } -}); diff --git a/src/utils/tea.ts b/src/utils/tea.ts deleted file mode 100644 index 0a93d6a..0000000 --- a/src/utils/tea.ts +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2021 MengYX. All rights reserved. -// -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in https://go.dev/LICENSE. - -// TeaCipher is a typescript port to golang.org/x/crypto/tea - -// Package tea implements the TEA algorithm, as defined in Needham and -// Wheeler's 1994 technical report, “TEA, a Tiny Encryption Algorithm”. See -// http://www.cix.co.uk/~klockstone/tea.pdf for details. -// -// TEA is a legacy cipher and its short block size makes it vulnerable to -// birthday bound attacks (see https://sweet32.info). It should only be used -// where compatibility with legacy systems, not security, is the goal. - -export class TeaCipher { - // BlockSize is the size of a TEA block, in bytes. - static readonly BlockSize = 8; - - // KeySize is the size of a TEA key, in bytes. - static readonly KeySize = 16; - - // delta is the TEA key schedule constant. - static readonly delta = 0x9e3779b9; - - // numRounds 64 is the standard number of rounds in TEA. - static readonly numRounds = 64; - - k0: number; - k1: number; - k2: number; - k3: number; - rounds: number; - - constructor(key: Uint8Array, rounds: number = TeaCipher.numRounds) { - if (key.length != 16) { - throw Error('incorrect key size'); - } - if ((rounds & 1) != 0) { - throw Error('odd number of rounds specified'); - } - - const k = new DataView(key.buffer); - this.k0 = k.getUint32(0, false); - this.k1 = k.getUint32(4, false); - this.k2 = k.getUint32(8, false); - this.k3 = k.getUint32(12, false); - this.rounds = rounds; - } - - encrypt(dst: DataView, src: DataView) { - let v0 = src.getUint32(0, false); - let v1 = src.getUint32(4, false); - - let sum = 0; - for (let i = 0; i < this.rounds / 2; i++) { - sum = sum + TeaCipher.delta; - v0 += ((v1 << 4) + this.k0) ^ (v1 + sum) ^ ((v1 >>> 5) + this.k1); - v1 += ((v0 << 4) + this.k2) ^ (v0 + sum) ^ ((v0 >>> 5) + this.k3); - } - - dst.setUint32(0, v0, false); - dst.setUint32(4, v1, false); - } - - decrypt(dst: DataView, src: DataView) { - let v0 = src.getUint32(0, false); - let v1 = src.getUint32(4, false); - - let sum = (TeaCipher.delta * this.rounds) / 2; - for (let i = 0; i < this.rounds / 2; i++) { - v1 -= ((v0 << 4) + this.k2) ^ (v0 + sum) ^ ((v0 >>> 5) + this.k3); - v0 -= ((v1 << 4) + this.k0) ^ (v1 + sum) ^ ((v1 >>> 5) + this.k1); - sum -= TeaCipher.delta; - } - dst.setUint32(0, v0, false); - dst.setUint32(4, v1, false); - } -}