使用 DroneCI 自动构建 #12
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: node:16.18-bullseye
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -y jq zip
|
||||
- npm ci
|
||||
- npm run test
|
||||
- ./scripts/build-and-package.sh legacy
|
||||
- ./scripts/build-and-package.sh extension
|
||||
- ./scripts/build-and-package.sh modern
|
||||
|
||||
- name: upload artifact
|
||||
image: node:16.18-bullseye
|
||||
environment:
|
||||
DRONE_GITEA_SERVER: https://git.unlock-music.dev
|
||||
GITEA_API_KEY:
|
||||
from_secret: GITEA_API_KEY
|
||||
commands:
|
||||
- ./scripts/upload-packages.sh
|
|
@ -1,6 +1,7 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
/build
|
||||
/coverage
|
||||
|
||||
# local env files
|
||||
|
@ -27,3 +28,7 @@ yarn-error.log*
|
|||
/src/QmcWasm/build
|
||||
/src/QmcWasm/*.js
|
||||
/src/QmcWasm/*.wasm
|
||||
|
||||
*.zip
|
||||
*.tar.gz
|
||||
/sha256sum.txt
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019-2021 MengYX
|
||||
Copyright (c) 2019-2023 MengYX
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
145
README.md
145
README.md
|
@ -1,84 +1,77 @@
|
|||
# Unlock Music 音乐解锁
|
||||
|
||||
- 在浏览器中解锁加密的音乐文件。 Unlock encrypted music file in the browser.
|
||||
- Unlock Music 项目是以学习和技术研究的初衷创建的,修改、再分发时请遵循[授权协议]。
|
||||
- Unlock Music 的 CLI 版本可以在 [unlock-music/cli] 找到,大批量转换建议使用 CLI 版本。
|
||||
- 我们新建了 Telegram 群组 [`@unlock_music_chat`] ,欢迎加入!
|
||||
|
||||
[授权协议]: https://git.unlock-music.dev/um/web/src/branch/master/LICENSE
|
||||
[unlock-music/cli]: https://git.unlock-music.dev/um/cli
|
||||
[`@unlock_music_chat`]: https://t.me/unlock_music_chat
|
||||
|
||||
## 特性
|
||||
|
||||
### 支持的格式
|
||||
|
||||
- [x] QQ 音乐 (.qmc0/.qmc2/.qmc3/.qmcflac/.qmcogg/.tkm)
|
||||
- [x] Moo 音乐格式 (.bkcmp3/.bkcflac/...)
|
||||
- [x] QQ 音乐 Tm 格式 (.tm0/.tm2/.tm3/.tm6)
|
||||
- [x] QQ 音乐新格式 (.mflac/.mgg/.mflac0/.mgg1/.mggl)
|
||||
- [x] <ruby>QQ 音乐海外版<rt>JOOX Music</rt></ruby> (.ofl_en)
|
||||
- [x] 网易云音乐格式 (.ncm)
|
||||
- [x] 虾米音乐格式 (.xm)
|
||||
- [x] 酷我音乐格式 (.kwm)
|
||||
- [x] 酷狗音乐格式 (.kgm/.vpr)
|
||||
|
||||
### 其他特性
|
||||
|
||||
- [x] 在浏览器中解锁
|
||||
- [x] 拖放文件
|
||||
- [x] 批量解锁
|
||||
- [x] 渐进式 Web 应用 (PWA)
|
||||
- [x] 多线程
|
||||
- [x] 写入元信息与专辑封面
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 使用预构建版本
|
||||
|
||||
- 从 [Release] 下载预构建的版本
|
||||
- :warning: 本地使用请下载`legacy版本`(`modern版本`只能通过 **http(s)协议** 访问)
|
||||
- 解压缩后即可部署或本地使用(**请勿直接运行源代码**)
|
||||
|
||||
[release]: https://git.unlock-music.dev/um/web/releases/latest
|
||||
|
||||
### 自行构建
|
||||
|
||||
#### JS部分
|
||||
# Unlock Music 音乐解锁
|
||||
|
||||
- 环境要求
|
||||
- nodejs (v16.x)
|
||||
- npm
|
||||
|
||||
1. 获取项目源代码后安装相关依赖:
|
||||
|
||||
```sh
|
||||
npm ci
|
||||
```
|
||||
|
||||
2. 然后进行构建:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
- 构建后的产物可以在 `dist` 目录找到。
|
||||
- 如果是用于开发,可以执行 `npm run serve`。
|
||||
|
||||
3. 如需构建浏览器扩展,构建成功后还需要执行:
|
||||
|
||||
```sh
|
||||
npm run make-extension
|
||||
```
|
||||
[![Build Status](https://ci.unlock-music.dev/api/badges/um/web/status.svg)](https://ci.unlock-music.dev/um/web)
|
||||
|
||||
#### WASM部分
|
||||
- 在浏览器中解锁加密的音乐文件。 Unlock encrypted music file in the browser.
|
||||
- Unlock Music 项目是以学习和技术研究的初衷创建的,修改、再分发时请遵循[授权协议]。
|
||||
- Unlock Music 的 CLI 版本可以在 [unlock-music/cli] 找到,大批量转换建议使用 CLI 版本。
|
||||
- 我们新建了 Telegram 群组 [`@unlock_music_chat`] ,欢迎加入!
|
||||
- CI 自动构建已经部署,可以在 [UM-Packages] 下载
|
||||
|
||||
[授权协议]: https://git.unlock-music.dev/um/web/src/branch/master/LICENSE
|
||||
[unlock-music/cli]: https://git.unlock-music.dev/um/cli
|
||||
[`@unlock_music_chat`]: https://t.me/unlock_music_chat
|
||||
[UM-Packages]: https://git.unlock-music.dev/um/-/packages/generic/web-build/
|
||||
|
||||
## 特性
|
||||
|
||||
### 支持的格式
|
||||
|
||||
- [x] QQ 音乐 (.qmc0/.qmc2/.qmc3/.qmcflac/.qmcogg/.tkm)
|
||||
- [x] Moo 音乐格式 (.bkcmp3/.bkcflac/...)
|
||||
- [x] QQ 音乐 Tm 格式 (.tm0/.tm2/.tm3/.tm6)
|
||||
- [x] QQ 音乐新格式 (.mflac/.mgg/.mflac0/.mgg1/.mggl)
|
||||
- [x] <ruby>QQ 音乐海外版<rt>JOOX Music</rt></ruby> (.ofl_en)
|
||||
- [x] 网易云音乐格式 (.ncm)
|
||||
- [x] 虾米音乐格式 (.xm)
|
||||
- [x] 酷我音乐格式 (.kwm)
|
||||
- [x] 酷狗音乐格式 (.kgm/.vpr)
|
||||
- [x] Android版喜马拉雅文件格式 (.x2m/.x3m)
|
||||
- [x] 咪咕音乐格式 (.mg3d)
|
||||
|
||||
### 其他特性
|
||||
|
||||
- [x] 在浏览器中解锁
|
||||
- [x] 拖放文件
|
||||
- [x] 批量解锁
|
||||
- [x] 渐进式 Web 应用 (PWA)
|
||||
- [x] 多线程
|
||||
- [x] 写入和编辑元信息与专辑封面
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 使用预构建版本
|
||||
|
||||
- 从 [Release] 或 [CI 构建][UM-Packages] 下载预构建的版本
|
||||
- :warning: 本地使用请下载`legacy版本`(`modern版本`只能通过 **http(s)协议** 访问)
|
||||
- 解压缩后即可部署或本地使用(**请勿直接运行源代码**)
|
||||
|
||||
[release]: https://git.unlock-music.dev/um/web/releases/latest
|
||||
|
||||
### 自行构建
|
||||
|
||||
- 环境要求
|
||||
- Linux
|
||||
- python3
|
||||
- nodejs (v16.x)
|
||||
- npm
|
||||
|
||||
- 运行此目录下的build-wasm
|
||||
1. 获取项目源代码后安装相关依赖:
|
||||
|
||||
```sh
|
||||
bash build-wasm
|
||||
npm install
|
||||
npm ci
|
||||
```
|
||||
|
||||
2. 然后进行构建:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
- 构建后的产物可以在 `dist` 目录找到。
|
||||
- 如果是用于开发,可以执行 `npm run serve`。
|
||||
|
||||
3. 如需构建浏览器扩展,构建成功后还需要执行:
|
||||
|
||||
```sh
|
||||
npm run make-extension
|
||||
```
|
||||
|
|
11
build-wasm
11
build-wasm
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
pushd ./src/QmcWasm
|
||||
bash build-wasm
|
||||
popd
|
||||
|
||||
pushd ./src/KgmWasm
|
||||
bash build-wasm
|
||||
popd
|
|
@ -1,8 +1,7 @@
|
|||
module.exports = {
|
||||
setupFilesAfterEnv: [
|
||||
'./src/__test__/setup_jest.js'
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'@/(.*)': '<rootDir>/src/$1'
|
||||
}
|
||||
};
|
||||
module.exports = {
|
||||
testPathIgnorePatterns: ['/build/', '/dist/', '/node_modules/'],
|
||||
setupFilesAfterEnv: ['./src/__test__/setup_jest.js'],
|
||||
moduleNameMapper: {
|
||||
'@/(.*)': '<rootDir>/src/$1',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const src = "./src/extension/"
|
||||
const dst = "./dist"
|
||||
fs.readdirSync(src).forEach(file => {
|
||||
let srcPath = path.join(src, file)
|
||||
let dstPath = path.join(dst, file)
|
||||
fs.copyFileSync(srcPath, dstPath)
|
||||
console.log(`Copy: ${srcPath} => ${dstPath}`)
|
||||
})
|
||||
|
||||
const manifestRaw = fs.readFileSync("./extension-manifest.json", "utf-8")
|
||||
const manifest = JSON.parse(manifestRaw)
|
||||
|
||||
const pkgRaw = fs.readFileSync("./package.json", "utf-8")
|
||||
const pkg = JSON.parse(pkgRaw)
|
||||
|
||||
verExt = pkg["version"]
|
||||
if (verExt.startsWith("v")) verExt = verExt.slice(1)
|
||||
if (verExt.includes("-")) verExt = verExt.split("-")[0]
|
||||
manifest["version"] = `${verExt}.${pkg["ext_build"]}`
|
||||
manifest["version_name"] = pkg["version"]
|
||||
|
||||
fs.writeFileSync("./dist/manifest.json", JSON.stringify(manifest), "utf-8")
|
||||
console.log("Write: manifest.json")
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const src = __dirname + "/src/extension/"
|
||||
const dst = __dirname + "/dist"
|
||||
fs.readdirSync(src).forEach(file => {
|
||||
let srcPath = path.join(src, file)
|
||||
let dstPath = path.join(dst, file)
|
||||
fs.copyFileSync(srcPath, dstPath)
|
||||
console.log(`Copy: ${srcPath} => ${dstPath}`)
|
||||
})
|
||||
|
||||
const manifestRaw = fs.readFileSync(__dirname + "/extension-manifest.json", "utf-8")
|
||||
const manifest = JSON.parse(manifestRaw)
|
||||
|
||||
const pkgRaw = fs.readFileSync(__dirname + "/package.json", "utf-8")
|
||||
const pkg = JSON.parse(pkgRaw)
|
||||
|
||||
verExt = pkg["version"]
|
||||
if (verExt.startsWith("v")) verExt = verExt.slice(1)
|
||||
if (verExt.includes("-")) verExt = verExt.split("-")[0]
|
||||
manifest["version"] = `${verExt}.${pkg["ext_build"]}`
|
||||
manifest["version_name"] = pkg["version"]
|
||||
|
||||
fs.writeFileSync(__dirname + "/dist/manifest.json", JSON.stringify(manifest), "utf-8")
|
||||
console.log("Write: manifest.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",
|
||||
|
@ -5717,7 +5712,7 @@
|
|||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001434",
|
||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
|
||||
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA=="
|
||||
},
|
||||
"node_modules/case-sensitive-paths-webpack-plugin": {
|
||||
|
@ -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",
|
||||
|
@ -25487,7 +25477,7 @@
|
|||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001434",
|
||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
|
||||
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA=="
|
||||
},
|
||||
"case-sensitive-paths-webpack-plugin": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "unlock-music",
|
||||
"version": "v1.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",
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<title>音乐解锁</title>
|
||||
<meta content="音乐,解锁,ncm,qmc,mgg,mflac,qq音乐,网易云音乐,加密" name="keywords"/>
|
||||
<meta content="音乐解锁 - 在任何设备上解锁已购的加密音乐!" name="description"/>
|
||||
<script src="./ixarea-stats.js"></script>
|
||||
<!--@formatter:off-->
|
||||
<style>#loader{position:absolute;left:50%;top:50%;z-index:1010;margin:-75px 0 0 -75px;border:16px solid #f3f3f3;border-radius:50%;border-top:16px solid #1db1ff;width:120px;height:120px;animation:spin 2s linear infinite}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}#loader-mask{text-align:center;position:absolute;width:100%;height:100%;bottom:0;left:0;right:0;top:0;z-index:1009;background-color:rgba(242,246,252,.88)}@media (prefers-color-scheme:dark){#loader-mask{color:#fff;background-color:rgba(0,0,0,.85)}#loader-mask a{color:#ddd}#loader-mask a:hover{color:#1db1ff}}#loader-source{font-size:1.5rem}#loader-tips-timeout{font-size:1.2rem}</style>
|
||||
<!--@formatter:on-->
|
||||
|
@ -19,9 +18,6 @@
|
|||
<div id="loader"></div>
|
||||
<noscript>
|
||||
<h3 id="loader-js">请启用JavaScript</h3>
|
||||
<img alt=""
|
||||
src="https://stats.ixarea.com/ixarea-stats/report?rec=1&action_name=音乐解锁-NoJS&idsite=2"
|
||||
style="border:0"/>
|
||||
</noscript>
|
||||
<h3 id="loader-source"> 请勿直接运行源代码! </h3>
|
||||
<div id="loader-tips-outdated" hidden>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
var _paq = window._paq || [];
|
||||
_paq.push(["setRequestMethod", "POST"], ["trackPageView"], ["enableLinkTracking"],
|
||||
["setSiteId", "2"], ["setTrackerUrl", "https://stats.ixarea.com/ixarea-stats/report"]);
|
||||
|
||||
var tag = document.createElement('script');
|
||||
tag.type = 'text/javascript';
|
||||
tag.async = true;
|
||||
tag.src = 'https://stats.ixarea.com/ixarea-stats.js';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(tag, s);
|
|
@ -0,0 +1,29 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
VERSION="$(jq -r ".version" <package.json)"
|
||||
DIST_NAME="um-web.$1.v${VERSION}"
|
||||
|
||||
case "$1" in
|
||||
"modern") npm run build -- --modern ;;
|
||||
"legacy") npm run build ;;
|
||||
"extension") npm run make-extension ;;
|
||||
|
||||
*)
|
||||
echo "Unknown command: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
mv dist "${DIST_NAME}"
|
||||
zip -rJ9 "${DIST_NAME}.zip" "${DIST_NAME}"
|
||||
|
||||
if [ "$1" = "legacy" ]; then
|
||||
# For upcoming extension build
|
||||
mv "${DIST_NAME}" dist
|
||||
else
|
||||
rm -rf "${DIST_NAME}"
|
||||
fi
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
if [ -z "$GITEA_API_KEY" ]; then
|
||||
echo "GITEA_API_KEY is empty, skip upload."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
URL_BASE="$DRONE_GITEA_SERVER/api/packages/${DRONE_REPO_NAMESPACE}/generic/${DRONE_REPO_NAME}-build"
|
||||
|
||||
for ZIP_NAME in *.zip; do
|
||||
UPLOAD_URL="${URL_BASE}/${DRONE_BUILD_NUMBER}/${ZIP_NAME}"
|
||||
sha256sum "${ZIP_NAME}"
|
||||
curl -sLifu "um-release-bot:$GITEA_API_KEY" -T "${ZIP_NAME}" "${UPLOAD_URL}"
|
||||
echo "Uploaded to: ${UPLOAD_URL}"
|
||||
done
|
|
@ -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(
|
||||
$<TARGET_PROPERTY:INTERFACE_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}
|
||||
)
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// KgmWasm.cpp : Defines the entry point for the application.
|
||||
//
|
||||
|
||||
#include "KgmWasm.h"
|
||||
|
||||
#include "kgm.hpp"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// KgmWasm.h : Include file for standard system include files,
|
||||
// or project specific include files.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <emscripten/bind.h>
|
||||
#include <string>
|
||||
|
||||
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());
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
# KgmWasm
|
||||
|
||||
## 构建
|
||||
|
||||
在 Linux 环境下执行 `bash build-wasm` 即可构建。
|
||||
|
||||
## Build
|
||||
|
||||
Linux environment required. Build wasm binary by execute `bash build-wasm`.
|
|
@ -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
|
||||
|
||||
mkdir -p build/wasm
|
||||
if [ ! -d build/emsdk ]; then
|
||||
git clone https://github.com/emscripten-core/emsdk.git build/emsdk
|
||||
fi
|
||||
|
||||
pushd build/emsdk
|
||||
#git pull
|
||||
./emsdk install 3.0.0
|
||||
./emsdk activate 3.0.0
|
||||
source ./emsdk_env.sh
|
||||
popd # build/emsdk
|
||||
|
||||
pushd build/wasm
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ../..
|
||||
make -j
|
||||
TARGET_FILES="
|
||||
KgmLegacy.js
|
||||
KgmWasm.js
|
||||
KgmWasm.wasm
|
||||
KgmWasmBundle.js
|
||||
"
|
||||
|
||||
#mkdir -p "${CURR_DIR}/npm"
|
||||
#cp $TARGET_FILES "${CURR_DIR}/npm/"
|
||||
cp $TARGET_FILES "${CURR_DIR}/"
|
||||
popd # build/wasm
|
||||
|
||||
popd
|
|
@ -1,112 +0,0 @@
|
|||
#include <vector>
|
||||
|
||||
std::vector<uint8_t> VprHeader = {
|
||||
0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43,
|
||||
0x91, 0xAA, 0xBD, 0xD0, 0x7A, 0xF5, 0x36, 0x31 };
|
||||
std::vector<uint8_t> KgmHeader = {
|
||||
0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B,
|
||||
0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14 };
|
||||
std::vector<uint8_t> VprMaskDiff = {
|
||||
0x25, 0xDF, 0xE8, 0xA6, 0x75, 0x1E, 0x75, 0x0E,
|
||||
0x2F, 0x80, 0xF3, 0x2D, 0xB8, 0xB6, 0xE3, 0x11, 0x00 };
|
||||
|
||||
std::vector<uint8_t> MaskV2;
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
$<TARGET_PROPERTY:INTERFACE_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}
|
||||
)
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// QmcWasm.cpp : Defines the entry point for the application.
|
||||
//
|
||||
|
||||
#include "QmcWasm.h"
|
||||
|
||||
#include "qmc.hpp"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
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<uint8_t> 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;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// QmcWasm.h : Include file for standard system include files,
|
||||
// or project specific include files.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <emscripten/bind.h>
|
||||
#include <string>
|
||||
|
||||
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());
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
# QmcWasm
|
||||
|
||||
## 构建
|
||||
|
||||
在 Linux 环境下执行 `bash build-wasm` 即可构建。
|
||||
|
||||
## Build
|
||||
|
||||
Linux environment required. Build wasm binary by execute `bash build-wasm`.
|
|
@ -1,289 +0,0 @@
|
|||
#ifndef QQMUSIC_CPP_TENCENTTEA_HPP
|
||||
#define QQMUSIC_CPP_TENCENTTEA_HPP
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
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<uint8_t> 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<uint8_t> 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为需加密的明文部分(Body)长度;
|
||||
输出:返回为加密后的长度(是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+Zero的长度*/;
|
||||
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*/
|
||||
{
|
||||
/*模8余0需补0,余1补7,余2补6,...,余7补1*/
|
||||
nPadlen = 8 - nPadlen;
|
||||
}
|
||||
|
||||
return nPadlen;
|
||||
}
|
||||
|
||||
/*pKey为16byte*/
|
||||
/*
|
||||
输入:pInBuf为需加密的明文部分(Body),nInBufLen为pInBuf长度;
|
||||
输出:pOutBuf为密文格式,pOutBufLen为pOutBuf的长度是8byte的倍数;
|
||||
*/
|
||||
/*TEA加密算法,CBC模式*/
|
||||
/*密文格式:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/
|
||||
bool encryptTencentTea(std::vector<uint8_t> inBuf, std::vector<uint8_t> key, std::vector<uint8_t> &outBuf)
|
||||
{
|
||||
srand(time(0));
|
||||
int nPadlen = encryptTencentTeaLen(inBuf.size());
|
||||
size_t ivCrypt;
|
||||
std::vector<uint8_t> srcBuf;
|
||||
srcBuf.resize(8);
|
||||
std::vector<uint8_t> 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++) /*加密前异或前8个byte的密文(iv_crypt指向的)*/\
|
||||
srcBuf[j] ^= outBuf[j + ivCrypt];\
|
||||
/*pOutBuffer、pInBuffer均为8byte, pKey为16byte*/\
|
||||
/*加密*/\
|
||||
TeaEncryptECB(srcBuf.data(), outBuf.data()+outBufPos, key, 16);\
|
||||
for (j = 0; j < 8; j++) /*加密后异或前8个byte的明文(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<uint8_t> inBuf, std::vector<uint8_t> key, std::vector<uint8_t> &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<uint8_t> 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<uint8_t> outBuf;
|
||||
outBuf.resize(outLen);
|
||||
|
||||
std::vector<uint8_t> ivPrev;
|
||||
ivPrev.resize(8);
|
||||
std::vector<uint8_t> 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
|
|
@ -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 <cctype>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
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<char*>(dest);
|
||||
char const* in = static_cast<char const*>(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<char*>(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<std::size_t, std::size_t>
|
||||
decode(void* dest, char const* src, std::size_t len)
|
||||
{
|
||||
char* out = static_cast<char*>(dest);
|
||||
auto in = reinterpret_cast<unsigned char const*>(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<char*>(dest),
|
||||
in - reinterpret_cast<unsigned char const*>(src) };
|
||||
}
|
||||
|
||||
} // base64
|
||||
|
||||
#endif
|
|
@ -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
|
||||
|
||||
mkdir -p build/wasm
|
||||
if [ ! -d build/emsdk ]; then
|
||||
git clone https://github.com/emscripten-core/emsdk.git build/emsdk
|
||||
fi
|
||||
|
||||
pushd build/emsdk
|
||||
#git pull
|
||||
./emsdk install 3.0.0
|
||||
./emsdk activate 3.0.0
|
||||
source ./emsdk_env.sh
|
||||
popd # build/emsdk
|
||||
|
||||
pushd build/wasm
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ../..
|
||||
make -j
|
||||
TARGET_FILES="
|
||||
QmcLegacy.js
|
||||
QmcWasm.js
|
||||
QmcWasm.wasm
|
||||
QmcWasmBundle.js
|
||||
"
|
||||
|
||||
#mkdir -p "${CURR_DIR}/npm"
|
||||
#cp $TARGET_FILES "${CURR_DIR}/npm/"
|
||||
cp $TARGET_FILES "${CURR_DIR}/"
|
||||
popd # build/wasm
|
||||
|
||||
popd
|
|
@ -1,230 +0,0 @@
|
|||
#include <string.h>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <arpa/inet.h>
|
||||
#include "qmc_key.hpp"
|
||||
#include "qmc_cipher.hpp"
|
||||
|
||||
class QmcDecode {
|
||||
private:
|
||||
std::vector<uint8_t> blobData;
|
||||
|
||||
std::vector<uint8_t> 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 == "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<uint8_t> tmp;
|
||||
if (!QmcDecryptKey(rawKeyBuf, tmp)) {
|
||||
error = "cannot decrypt embedded key";
|
||||
return -1;
|
||||
}
|
||||
rawKeyBuf = tmp;
|
||||
}
|
||||
if (cipherType == "invalid") {
|
||||
error = "file is invalid or not supported(Please downgrade your app.)";
|
||||
return -1;
|
||||
}
|
||||
return keySize + tailSize;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t> 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;
|
||||
}
|
|
@ -1,290 +0,0 @@
|
|||
#include <cstdint>
|
||||
#include <vector>
|
||||
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<uint8_t>& buf, size_t offset) {
|
||||
for (size_t i = 0; i < buf.size(); i++) {
|
||||
buf[i] ^= getMask(offset + i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class QmcMapCipher {
|
||||
private:
|
||||
std::vector<uint8_t> 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<uint8_t> &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<uint8_t>& 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<uint8_t>& 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<uint8_t> 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<uint8_t>& 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<uint8_t> S;
|
||||
std::vector<uint8_t> key;
|
||||
uint32_t hash = 1;
|
||||
|
||||
void procFirstSegment(std::vector<uint8_t>& buf, size_t offset) {
|
||||
for (size_t i = 0; i < buf.size(); i++) {
|
||||
buf[i] ^= key[getSegmentKey(offset + i)];
|
||||
}
|
||||
}
|
||||
|
||||
void procASegment(std::vector<uint8_t>& buf, size_t offset) {
|
||||
// Initialise a new seed box
|
||||
std::vector<uint8_t> 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();
|
||||
}
|
||||
};
|
|
@ -1,217 +0,0 @@
|
|||
#include"TencentTea.hpp"
|
||||
#include "base64.hpp"
|
||||
|
||||
void simpleMakeKey(uint8_t salt, int length, std::vector<uint8_t> &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<uint8_t> v2KeyPrefix = { 0x51, 0x51, 0x4D, 0x75, 0x73, 0x69, 0x63, 0x20, 0x45, 0x6E, 0x63, 0x56, 0x32, 0x2C, 0x4B, 0x65, 0x79, 0x3A };
|
||||
|
||||
bool decryptV2Key(std::vector<uint8_t> key, std::vector<uint8_t>& 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<uint8_t> mixKey1 = { 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 };
|
||||
std::vector<uint8_t> mixKey2 = { 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 };
|
||||
|
||||
std::vector<uint8_t> out;
|
||||
std::vector<uint8_t> 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<uint8_t> key, std::vector<uint8_t>& outVec)
|
||||
{
|
||||
if (key.size() < 16)
|
||||
{
|
||||
outVec.resize(0);
|
||||
//EncV2 key size is too small.
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t> mixKey1 = { 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 };
|
||||
std::vector<uint8_t> mixKey2 = { 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 };
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t> raw, std::vector<uint8_t> &outVec) {
|
||||
std::vector<uint8_t> 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<uint8_t> tmpIn = rawDec;
|
||||
if (!decryptV2Key(tmpIn, rawDec))
|
||||
{
|
||||
//decrypt EncV2 failed.
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> simpleKey;
|
||||
simpleKey.resize(8);
|
||||
simpleMakeKey(106, 8, simpleKey);
|
||||
std::vector<uint8_t> 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<uint8_t> out;
|
||||
std::vector<uint8_t> 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<uint8_t> raw, std::vector<uint8_t>& outVec, bool useEncV2 = true) {
|
||||
std::vector<uint8_t> simpleKey;
|
||||
simpleKey.resize(8);
|
||||
simpleMakeKey(106, 8, simpleKey);
|
||||
std::vector<uint8_t> 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<uint8_t> out;
|
||||
out.resize(raw.size() - 8);
|
||||
for (size_t i = 0; i < out.size(); i++)
|
||||
{
|
||||
out[i] = raw[8 + i];
|
||||
}
|
||||
std::vector<uint8_t> 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<uint8_t> tmpIn = raw;
|
||||
if (!encryptV2Key(tmpIn, raw))
|
||||
{
|
||||
//encrypt EncV2 failed.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// import { DecryptBuffer as DecryptQmcCacheBuffer } from '../qmccache';
|
||||
// import fs from 'fs';
|
||||
//
|
||||
// const expectedBuffer = fs.readFileSync(__dirname + '/fixture/qmc_cache_expected.bin');
|
||||
//
|
||||
// const createInputBuffer = () => {
|
||||
// const buffer = Buffer.alloc(256);
|
||||
// for (let i = buffer.byteLength; i >= 0; i--) {
|
||||
// buffer[i] = i;
|
||||
// }
|
||||
// return buffer;
|
||||
// };
|
||||
//
|
||||
// describe('decrypt/qmccache', () => {
|
||||
// it('should decrypt specified buffer correctly', () => {
|
||||
// const input = createInputBuffer();
|
||||
// DecryptQmcCacheBuffer(input);
|
||||
// expect(input).toEqual(expectedBuffer);
|
||||
// });
|
||||
// });
|
|
@ -9,6 +9,7 @@ import { Decrypt as KwmDecrypt } from '@/decrypt/kwm';
|
|||
import { Decrypt as RawDecrypt } from '@/decrypt/raw';
|
||||
import { Decrypt as TmDecrypt } from '@/decrypt/tm';
|
||||
import { Decrypt as JooxDecrypt } from '@/decrypt/joox';
|
||||
import { Decrypt as XimalayaDecrypt } from './ximalaya';
|
||||
import { DecryptResult, FileInfo } from '@/decrypt/entity';
|
||||
import { SplitFilename } from '@/decrypt/utils';
|
||||
import { storage } from '@/utils/storage';
|
||||
|
@ -49,9 +50,12 @@ export async function Decrypt(file: FileInfo, config: Record<string, any>): Prom
|
|||
case 'tm3': // QQ Music IOS Mp3
|
||||
rt_data = await RawDecrypt(file.raw, raw.name, 'mp3');
|
||||
break;
|
||||
case 'qmc0': //QQ Music Android Mp3
|
||||
case 'qmc3': //QQ Music Android Mp3
|
||||
case 'qmc2': //QQ Music Android Ogg
|
||||
case 'qmc0': //QQ Music Android Mp3
|
||||
case 'qmc4': //QQ Music Android Ogg
|
||||
case 'qmc6': //QQ Music Android Ogg
|
||||
case 'qmc8': //QQ Music Android Ogg
|
||||
case 'qmcflac': //QQ Music Android Flac
|
||||
case 'qmcogg': //QQ Music Android Ogg
|
||||
case 'tkm': //QQ Music Accompaniment M4a
|
||||
|
@ -67,9 +71,11 @@ export async function Decrypt(file: FileInfo, config: Record<string, any>): Prom
|
|||
case 'mggl': //QQ Music Mac
|
||||
case 'mflac': //QQ Music New Flac
|
||||
case 'mflac0': //QQ Music New Flac
|
||||
case 'mflach': //QQ Music New Flac
|
||||
case 'mgg': //QQ Music New Ogg
|
||||
case 'mgg1': //QQ Music New Ogg
|
||||
case 'mgg0':
|
||||
case 'mmp4': // QMC MP4 Container w/ E-AC-3 JOC
|
||||
case '666c6163': //QQ Music Weiyun Flac
|
||||
case '6d7033': //QQ Music Weiyun Mp3
|
||||
case '6f6767': //QQ Music Weiyun Ogg
|
||||
|
@ -92,6 +98,12 @@ export async function Decrypt(file: FileInfo, config: Record<string, any>): Prom
|
|||
case 'ofl_en':
|
||||
rt_data = await JooxDecrypt(file.raw, raw.name, raw.ext);
|
||||
break;
|
||||
case 'x2m':
|
||||
case 'x3m':
|
||||
rt_data = await XimalayaDecrypt(file.raw, raw.name, raw.ext);
|
||||
break;
|
||||
case 'mflach': //QQ Music New Flac
|
||||
throw '网页版无法解锁,请使用<a target="_blank" href="https://git.unlock-music.dev/um/cli">CLI版本</a>'
|
||||
default:
|
||||
throw '不支持此文件格式';
|
||||
}
|
||||
|
|
|
@ -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)');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -13,14 +13,18 @@ import { DecryptResult } from '@/decrypt/entity';
|
|||
|
||||
//prettier-ignore
|
||||
const MagicHeader = [
|
||||
0x79, 0x65, 0x65, 0x6C, 0x69, 0x6F, 0x6E, 0x2D,
|
||||
0x6B, 0x75, 0x77, 0x6F, 0x2D, 0x74, 0x6D, 0x65,
|
||||
]
|
||||
0x79, 0x65, 0x65, 0x6C, 0x69, 0x6F, 0x6E, 0x2D,
|
||||
0x6B, 0x75, 0x77, 0x6F, 0x2D, 0x74, 0x6D, 0x65,
|
||||
];
|
||||
const MagicHeader2 = [
|
||||
0x79, 0x65, 0x65, 0x6C, 0x69, 0x6F, 0x6E, 0x2D,
|
||||
0x6B, 0x75, 0x77, 0x6F, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
const PreDefinedKey = 'MoOtOiTvINGwd2E6n0E1i7L5t2IoOoNk';
|
||||
|
||||
export async function Decrypt(file: File, raw_filename: string, _: string): Promise<DecryptResult> {
|
||||
const oriData = new Uint8Array(await GetArrayBuffer(file));
|
||||
if (!BytesHasPrefix(oriData, MagicHeader)) {
|
||||
if (!BytesHasPrefix(oriData, MagicHeader) && !BytesHasPrefix(oriData, MagicHeader2)) {
|
||||
if (SniffAudioExt(oriData) === 'aac') {
|
||||
return await RawDecrypt(file, raw_filename, 'aac', false);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
|
@ -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,6 +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: 'mp4', version: 2 },
|
||||
|
||||
// qmcflac / qmcogg:
|
||||
// 有可能是 v2 加密但混用同一个后缀名。
|
||||
|
@ -27,8 +26,16 @@ export const HandlerMap: { [key: string]: Handler } = {
|
|||
qmc0: { ext: 'mp3', version: 2 },
|
||||
qmc2: { ext: 'ogg', version: 2 },
|
||||
qmc3: { ext: 'mp3', version: 2 },
|
||||
qmc4: { ext: 'ogg', version: 2 },
|
||||
qmc6: { ext: 'ogg', version: 2 },
|
||||
qmc8: { ext: 'ogg', version: 2 },
|
||||
bkcmp3: { ext: 'mp3', version: 1 },
|
||||
bkcm4a: { ext: 'm4a', version: 1 },
|
||||
bkcflac: { ext: 'flac', version: 1 },
|
||||
bkcwav: { ext: 'wav', version: 1 },
|
||||
bkcape: { ext: 'ape', version: 1 },
|
||||
bkcogg: { ext: 'ogg', version: 1 },
|
||||
bkcwma: { ext: 'wma', version: 1 },
|
||||
tkm: { ext: 'm4a', version: 1 },
|
||||
'666c6163': { ext: 'flac', version: 1 },
|
||||
'6d7033': { ext: 'mp3', version: 1 },
|
||||
|
@ -43,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) {
|
||||
|
@ -56,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];
|
||||
|
||||
|
@ -89,86 +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) === '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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<DecryptResult> {
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
import { parseBlob as metaParseBlob } from 'music-metadata-browser';
|
||||
import { AudioMimeType, SniffAudioExt, GetArrayBuffer, GetMetaFromFile } from './utils';
|
||||
import { DecryptResult } from '@/decrypt/entity';
|
||||
|
||||
const HandlerMap: Map<string, (data: Uint8Array) => Uint8Array> = new Map([
|
||||
['x2m', ProcessX2M],
|
||||
['x3m', ProcessX3M],
|
||||
]);
|
||||
|
||||
export async function Decrypt(file: File, raw_filename: string, raw_ext: string): Promise<DecryptResult> {
|
||||
const buffer = new Uint8Array(await GetArrayBuffer(file));
|
||||
const handler = HandlerMap.get(raw_ext);
|
||||
if (!handler) throw 'File type is incorrect!';
|
||||
let musicDecoded: Uint8Array = handler(buffer);
|
||||
|
||||
const ext = SniffAudioExt(musicDecoded, 'm4a');
|
||||
const mime = AudioMimeType[ext];
|
||||
|
||||
let musicBlob = new Blob([musicDecoded], { type: mime });
|
||||
|
||||
const musicMeta = await metaParseBlob(musicBlob);
|
||||
|
||||
const info = GetMetaFromFile(raw_filename, musicMeta.common.title, musicMeta.common.artist);
|
||||
|
||||
return {
|
||||
picture: '',
|
||||
title: info.title,
|
||||
artist: info.artist,
|
||||
ext: ext,
|
||||
album: musicMeta.common.album,
|
||||
blob: musicBlob,
|
||||
file: URL.createObjectURL(musicBlob),
|
||||
mime: mime,
|
||||
};
|
||||
}
|
||||
|
||||
function ProcessX2M(data: Uint8Array) {
|
||||
const x2mHeaderSize = 1024;
|
||||
const x2mKey = [0x78, 0x6d, 0x6c, 0x79];
|
||||
let encryptedHeader = data.slice(0, x2mHeaderSize);
|
||||
for (let idx = 0; idx < x2mHeaderSize; idx++) {
|
||||
let srcIdx = x2mScrambleTable[idx];
|
||||
data[idx] = encryptedHeader[srcIdx] ^ x2mKey[idx % x2mKey.length];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function ProcessX3M(data: Uint8Array) {
|
||||
const x3mHeaderSize = 1024;
|
||||
|
||||
//prettier-ignore: uint8, size 32(8x4)
|
||||
const x3mKey = [
|
||||
0x33, 0x39, 0x38, 0x39, 0x64, 0x31, 0x31, 0x31, 0x61, 0x61, 0x64, 0x35, 0x36, 0x31, 0x33, 0x39, 0x34, 0x30, 0x66,
|
||||
0x34, 0x66, 0x63, 0x34, 0x34, 0x62, 0x36, 0x33, 0x39, 0x62, 0x32, 0x39, 0x32,
|
||||
];
|
||||
|
||||
let encryptedHeader = data.slice(0, x3mHeaderSize);
|
||||
for (let dstIdx = 0; dstIdx < x3mHeaderSize; dstIdx++) {
|
||||
let srcIdx = x3mScrambleTable[dstIdx];
|
||||
data[dstIdx] = encryptedHeader[srcIdx] ^ x3mKey[dstIdx % x3mKey.length];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
//prettier-ignore: uint16, size 1024 (64x16)
|
||||
const x2mScrambleTable = [
|
||||
0x2a9, 0x2ab, 0x154, 0x2aa, 0x2a8, 0x2ac, 0x153, 0x2a7, 0x2ad, 0x152, 0x2a6, 0x3ff, 0x000, 0x155, 0x2ae, 0x151, 0x2a5,
|
||||
0x3fe, 0x001, 0x156, 0x2af, 0x150, 0x2a4, 0x3fd, 0x002, 0x157, 0x2b0, 0x14f, 0x2a3, 0x3fc, 0x003, 0x158, 0x2b1, 0x14e,
|
||||
0x2a2, 0x3fb, 0x004, 0x159, 0x2b2, 0x14d, 0x2a1, 0x3fa, 0x005, 0x15a, 0x2b3, 0x14c, 0x2a0, 0x3f9, 0x006, 0x15b, 0x2b4,
|
||||
0x14b, 0x29f, 0x3f8, 0x007, 0x15c, 0x2b5, 0x14a, 0x29e, 0x3f7, 0x008, 0x15d, 0x2b6, 0x149, 0x29d, 0x3f6, 0x009, 0x15e,
|
||||
0x2b7, 0x148, 0x29c, 0x3f5, 0x00a, 0x15f, 0x2b8, 0x147, 0x29b, 0x3f4, 0x00b, 0x160, 0x2b9, 0x146, 0x29a, 0x3f3, 0x00c,
|
||||
0x161, 0x2ba, 0x145, 0x299, 0x3f2, 0x00d, 0x162, 0x2bb, 0x144, 0x298, 0x3f1, 0x00e, 0x163, 0x2bc, 0x143, 0x297, 0x3f0,
|
||||
0x00f, 0x164, 0x2bd, 0x142, 0x296, 0x3ef, 0x010, 0x165, 0x2be, 0x141, 0x295, 0x3ee, 0x011, 0x166, 0x2bf, 0x140, 0x294,
|
||||
0x3ed, 0x012, 0x167, 0x2c0, 0x13f, 0x293, 0x3ec, 0x013, 0x168, 0x2c1, 0x13e, 0x292, 0x3eb, 0x014, 0x169, 0x2c2, 0x13d,
|
||||
0x291, 0x3ea, 0x015, 0x16a, 0x2c3, 0x13c, 0x290, 0x3e9, 0x016, 0x16b, 0x2c4, 0x13b, 0x28f, 0x3e8, 0x017, 0x16c, 0x2c5,
|
||||
0x13a, 0x28e, 0x3e7, 0x018, 0x16d, 0x2c6, 0x139, 0x28d, 0x3e6, 0x019, 0x16e, 0x2c7, 0x138, 0x28c, 0x3e5, 0x01a, 0x16f,
|
||||
0x2c8, 0x137, 0x28b, 0x3e4, 0x01b, 0x170, 0x2c9, 0x136, 0x28a, 0x3e3, 0x01c, 0x171, 0x2ca, 0x135, 0x289, 0x3e2, 0x01d,
|
||||
0x172, 0x2cb, 0x134, 0x288, 0x3e1, 0x01e, 0x173, 0x2cc, 0x133, 0x287, 0x3e0, 0x01f, 0x174, 0x2cd, 0x0a9, 0x1fe, 0x357,
|
||||
0x020, 0x175, 0x2ce, 0x0aa, 0x1ff, 0x358, 0x021, 0x176, 0x2cf, 0x0ab, 0x200, 0x359, 0x022, 0x177, 0x2d0, 0x0ac, 0x201,
|
||||
0x35a, 0x023, 0x178, 0x2d1, 0x0ad, 0x202, 0x35b, 0x024, 0x179, 0x2d2, 0x0ae, 0x203, 0x35c, 0x025, 0x17a, 0x2d3, 0x0af,
|
||||
0x204, 0x35d, 0x026, 0x17b, 0x2d4, 0x0b0, 0x205, 0x35e, 0x027, 0x17c, 0x2d5, 0x0b1, 0x206, 0x35f, 0x028, 0x17d, 0x2d6,
|
||||
0x0b2, 0x207, 0x360, 0x029, 0x17e, 0x2d7, 0x0b3, 0x208, 0x361, 0x02a, 0x17f, 0x2d8, 0x0b4, 0x209, 0x362, 0x02b, 0x180,
|
||||
0x2d9, 0x0b5, 0x20a, 0x363, 0x02c, 0x181, 0x2da, 0x0b6, 0x20b, 0x364, 0x02d, 0x182, 0x2db, 0x0b7, 0x20c, 0x365, 0x02e,
|
||||
0x183, 0x2dc, 0x0b8, 0x20d, 0x366, 0x02f, 0x184, 0x2dd, 0x0b9, 0x20e, 0x367, 0x030, 0x185, 0x2de, 0x0ba, 0x20f, 0x368,
|
||||
0x031, 0x186, 0x2df, 0x0bb, 0x210, 0x369, 0x032, 0x187, 0x2e0, 0x0bc, 0x211, 0x36a, 0x033, 0x188, 0x2e1, 0x0bd, 0x212,
|
||||
0x36b, 0x034, 0x189, 0x2e2, 0x0be, 0x213, 0x36c, 0x035, 0x18a, 0x2e3, 0x0bf, 0x214, 0x36d, 0x036, 0x18b, 0x2e4, 0x0c0,
|
||||
0x215, 0x36e, 0x037, 0x18c, 0x2e5, 0x0c1, 0x216, 0x36f, 0x038, 0x18d, 0x2e6, 0x0c2, 0x217, 0x370, 0x039, 0x18e, 0x2e7,
|
||||
0x0c3, 0x218, 0x371, 0x03a, 0x18f, 0x2e8, 0x0c4, 0x219, 0x372, 0x03b, 0x190, 0x2e9, 0x0c5, 0x21a, 0x373, 0x03c, 0x191,
|
||||
0x2ea, 0x0c6, 0x21b, 0x374, 0x03d, 0x192, 0x2eb, 0x0c7, 0x21c, 0x375, 0x03e, 0x193, 0x2ec, 0x0c8, 0x21d, 0x376, 0x03f,
|
||||
0x194, 0x2ed, 0x0c9, 0x21e, 0x377, 0x040, 0x195, 0x2ee, 0x0ca, 0x21f, 0x378, 0x041, 0x196, 0x2ef, 0x0cb, 0x220, 0x379,
|
||||
0x042, 0x197, 0x2f0, 0x0cc, 0x221, 0x37a, 0x043, 0x198, 0x2f1, 0x0cd, 0x222, 0x37b, 0x044, 0x199, 0x2f2, 0x0ce, 0x223,
|
||||
0x37c, 0x045, 0x19a, 0x2f3, 0x0cf, 0x224, 0x37d, 0x046, 0x19b, 0x2f4, 0x0d0, 0x225, 0x37e, 0x047, 0x19c, 0x2f5, 0x0d1,
|
||||
0x226, 0x37f, 0x048, 0x19d, 0x2f6, 0x0d2, 0x227, 0x380, 0x049, 0x19e, 0x2f7, 0x0d3, 0x228, 0x381, 0x04a, 0x19f, 0x2f8,
|
||||
0x0d4, 0x229, 0x382, 0x04b, 0x1a0, 0x2f9, 0x0d5, 0x22a, 0x383, 0x04c, 0x1a1, 0x2fa, 0x0d6, 0x22b, 0x384, 0x04d, 0x1a2,
|
||||
0x2fb, 0x0d7, 0x22c, 0x385, 0x04e, 0x1a3, 0x2fc, 0x0d8, 0x22d, 0x386, 0x04f, 0x1a4, 0x2fd, 0x0d9, 0x22e, 0x387, 0x050,
|
||||
0x1a5, 0x2fe, 0x0da, 0x22f, 0x388, 0x051, 0x1a6, 0x2ff, 0x0db, 0x230, 0x389, 0x052, 0x1a7, 0x300, 0x0dc, 0x231, 0x38a,
|
||||
0x053, 0x1a8, 0x301, 0x0dd, 0x232, 0x38b, 0x054, 0x1a9, 0x302, 0x0de, 0x233, 0x38c, 0x055, 0x1aa, 0x303, 0x0df, 0x234,
|
||||
0x38d, 0x056, 0x1ab, 0x304, 0x0e0, 0x235, 0x38e, 0x057, 0x1ac, 0x305, 0x0e1, 0x236, 0x38f, 0x058, 0x1ad, 0x306, 0x0e2,
|
||||
0x237, 0x390, 0x059, 0x1ae, 0x307, 0x0e3, 0x238, 0x391, 0x05a, 0x1af, 0x308, 0x0e4, 0x239, 0x392, 0x05b, 0x1b0, 0x309,
|
||||
0x0e5, 0x23a, 0x393, 0x05c, 0x1b1, 0x30a, 0x0e6, 0x23b, 0x394, 0x05d, 0x1b2, 0x30b, 0x0e7, 0x23c, 0x395, 0x05e, 0x1b3,
|
||||
0x30c, 0x0e8, 0x23d, 0x396, 0x05f, 0x1b4, 0x30d, 0x0e9, 0x23e, 0x397, 0x060, 0x1b5, 0x30e, 0x0ea, 0x23f, 0x398, 0x061,
|
||||
0x1b6, 0x30f, 0x0eb, 0x240, 0x399, 0x062, 0x1b7, 0x310, 0x0ec, 0x241, 0x39a, 0x063, 0x1b8, 0x311, 0x0ed, 0x242, 0x39b,
|
||||
0x064, 0x1b9, 0x312, 0x0ee, 0x243, 0x39c, 0x065, 0x1ba, 0x313, 0x0ef, 0x244, 0x39d, 0x066, 0x1bb, 0x314, 0x0f0, 0x245,
|
||||
0x39e, 0x067, 0x1bc, 0x315, 0x0f1, 0x246, 0x39f, 0x068, 0x1bd, 0x316, 0x0f2, 0x247, 0x3a0, 0x069, 0x1be, 0x317, 0x0f3,
|
||||
0x248, 0x3a1, 0x06a, 0x1bf, 0x318, 0x0f4, 0x249, 0x3a2, 0x06b, 0x1c0, 0x319, 0x0f5, 0x24a, 0x3a3, 0x06c, 0x1c1, 0x31a,
|
||||
0x0f6, 0x24b, 0x3a4, 0x06d, 0x1c2, 0x31b, 0x0f7, 0x24c, 0x3a5, 0x06e, 0x1c3, 0x31c, 0x0f8, 0x24d, 0x3a6, 0x06f, 0x1c4,
|
||||
0x31d, 0x0f9, 0x24e, 0x3a7, 0x070, 0x1c5, 0x31e, 0x0fa, 0x24f, 0x3a8, 0x071, 0x1c6, 0x31f, 0x0fb, 0x250, 0x3a9, 0x072,
|
||||
0x1c7, 0x320, 0x0fc, 0x251, 0x3aa, 0x073, 0x1c8, 0x321, 0x0fd, 0x252, 0x3ab, 0x074, 0x1c9, 0x322, 0x0fe, 0x253, 0x3ac,
|
||||
0x075, 0x1ca, 0x323, 0x0ff, 0x254, 0x3ad, 0x076, 0x1cb, 0x324, 0x100, 0x255, 0x3ae, 0x077, 0x1cc, 0x325, 0x101, 0x256,
|
||||
0x3af, 0x078, 0x1cd, 0x326, 0x102, 0x257, 0x3b0, 0x079, 0x1ce, 0x327, 0x103, 0x258, 0x3b1, 0x07a, 0x1cf, 0x328, 0x104,
|
||||
0x259, 0x3b2, 0x07b, 0x1d0, 0x329, 0x105, 0x25a, 0x3b3, 0x07c, 0x1d1, 0x32a, 0x106, 0x25b, 0x3b4, 0x07d, 0x1d2, 0x32b,
|
||||
0x107, 0x25c, 0x3b5, 0x07e, 0x1d3, 0x32c, 0x108, 0x25d, 0x3b6, 0x07f, 0x1d4, 0x32d, 0x109, 0x25e, 0x3b7, 0x080, 0x1d5,
|
||||
0x32e, 0x10a, 0x25f, 0x3b8, 0x081, 0x1d6, 0x32f, 0x10b, 0x260, 0x3b9, 0x082, 0x1d7, 0x330, 0x10c, 0x261, 0x3ba, 0x083,
|
||||
0x1d8, 0x331, 0x10d, 0x262, 0x3bb, 0x084, 0x1d9, 0x332, 0x10e, 0x263, 0x3bc, 0x085, 0x1da, 0x333, 0x10f, 0x264, 0x3bd,
|
||||
0x086, 0x1db, 0x334, 0x110, 0x265, 0x3be, 0x087, 0x1dc, 0x335, 0x111, 0x266, 0x3bf, 0x088, 0x1dd, 0x336, 0x112, 0x267,
|
||||
0x3c0, 0x089, 0x1de, 0x337, 0x113, 0x268, 0x3c1, 0x08a, 0x1df, 0x338, 0x114, 0x269, 0x3c2, 0x08b, 0x1e0, 0x339, 0x115,
|
||||
0x26a, 0x3c3, 0x08c, 0x1e1, 0x33a, 0x116, 0x26b, 0x3c4, 0x08d, 0x1e2, 0x33b, 0x117, 0x26c, 0x3c5, 0x08e, 0x1e3, 0x33c,
|
||||
0x118, 0x26d, 0x3c6, 0x08f, 0x1e4, 0x33d, 0x119, 0x26e, 0x3c7, 0x090, 0x1e5, 0x33e, 0x11a, 0x26f, 0x3c8, 0x091, 0x1e6,
|
||||
0x33f, 0x11b, 0x270, 0x3c9, 0x092, 0x1e7, 0x340, 0x11c, 0x271, 0x3ca, 0x093, 0x1e8, 0x341, 0x11d, 0x272, 0x3cb, 0x094,
|
||||
0x1e9, 0x342, 0x11e, 0x273, 0x3cc, 0x095, 0x1ea, 0x343, 0x11f, 0x274, 0x3cd, 0x096, 0x1eb, 0x344, 0x120, 0x275, 0x3ce,
|
||||
0x097, 0x1ec, 0x345, 0x121, 0x276, 0x3cf, 0x098, 0x1ed, 0x346, 0x122, 0x277, 0x3d0, 0x099, 0x1ee, 0x347, 0x123, 0x278,
|
||||
0x3d1, 0x09a, 0x1ef, 0x348, 0x124, 0x279, 0x3d2, 0x09b, 0x1f0, 0x349, 0x125, 0x27a, 0x3d3, 0x09c, 0x1f1, 0x34a, 0x126,
|
||||
0x27b, 0x3d4, 0x09d, 0x1f2, 0x34b, 0x127, 0x27c, 0x3d5, 0x09e, 0x1f3, 0x34c, 0x128, 0x27d, 0x3d6, 0x09f, 0x1f4, 0x34d,
|
||||
0x129, 0x27e, 0x3d7, 0x0a0, 0x1f5, 0x34e, 0x12a, 0x27f, 0x3d8, 0x0a1, 0x1f6, 0x34f, 0x12b, 0x280, 0x3d9, 0x0a2, 0x1f7,
|
||||
0x350, 0x12c, 0x281, 0x3da, 0x0a3, 0x1f8, 0x351, 0x12d, 0x282, 0x3db, 0x0a4, 0x1f9, 0x352, 0x12e, 0x283, 0x3dc, 0x0a5,
|
||||
0x1fa, 0x353, 0x12f, 0x284, 0x3dd, 0x0a6, 0x1fb, 0x354, 0x130, 0x285, 0x3de, 0x0a7, 0x1fc, 0x355, 0x131, 0x286, 0x3df,
|
||||
0x0a8, 0x1fd, 0x356, 0x132,
|
||||
];
|
||||
|
||||
//prettier-ignore: uint16, size 1024 (64x16)
|
||||
const x3mScrambleTable = [
|
||||
0x256, 0x28d, 0x213, 0x307, 0x156, 0x39d, 0x062, 0x170, 0x3ca, 0x035, 0x0ed, 0x2a4, 0x1e4, 0x359, 0x0d3, 0x26b, 0x265,
|
||||
0x274, 0x251, 0x297, 0x202, 0x322, 0x126, 0x32b, 0x117, 0x302, 0x15c, 0x3a8, 0x057, 0x148, 0x380, 0x090, 0x1f6, 0x335,
|
||||
0x10c, 0x2ee, 0x175, 0x3d4, 0x02b, 0x0cc, 0x260, 0x27b, 0x23d, 0x2bb, 0x1b6, 0x3a1, 0x05e, 0x157, 0x39e, 0x061, 0x16f,
|
||||
0x3c6, 0x039, 0x0f7, 0x2b9, 0x1b8, 0x39f, 0x060, 0x166, 0x3b9, 0x046, 0x122, 0x31c, 0x12f, 0x33d, 0x0fc, 0x2ca, 0x1a4,
|
||||
0x3cc, 0x033, 0x0e6, 0x293, 0x209, 0x315, 0x13d, 0x358, 0x0d5, 0x26e, 0x25e, 0x27d, 0x23a, 0x2c0, 0x1b1, 0x3af, 0x050,
|
||||
0x136, 0x346, 0x0ef, 0x2aa, 0x1ce, 0x376, 0x0a0, 0x210, 0x30c, 0x14c, 0x389, 0x082, 0x1db, 0x367, 0x0b9, 0x23e, 0x2ba,
|
||||
0x1b7, 0x3a0, 0x05f, 0x164, 0x3b7, 0x048, 0x125, 0x326, 0x11c, 0x30a, 0x14f, 0x38f, 0x070, 0x1a8, 0x3c7, 0x038, 0x0f5,
|
||||
0x2b5, 0x1bd, 0x393, 0x06c, 0x199, 0x3e1, 0x01e, 0x0b3, 0x22f, 0x2d7, 0x193, 0x3ea, 0x015, 0x09d, 0x20a, 0x314, 0x13e,
|
||||
0x35a, 0x0d2, 0x26a, 0x267, 0x272, 0x253, 0x294, 0x208, 0x319, 0x137, 0x34c, 0x0e7, 0x295, 0x205, 0x31d, 0x12e, 0x33c,
|
||||
0x0fe, 0x2cd, 0x1a0, 0x3d5, 0x02a, 0x0c8, 0x258, 0x286, 0x22a, 0x2dc, 0x18e, 0x3f7, 0x008, 0x07c, 0x1d3, 0x370, 0x0a7,
|
||||
0x21d, 0x2f1, 0x171, 0x3cd, 0x032, 0x0e5, 0x292, 0x20b, 0x313, 0x13f, 0x35c, 0x0d0, 0x266, 0x273, 0x252, 0x296, 0x204,
|
||||
0x31f, 0x12a, 0x332, 0x10f, 0x2f4, 0x16c, 0x3c3, 0x03c, 0x101, 0x2d2, 0x19a, 0x3e0, 0x01f, 0x0b5, 0x233, 0x2c9, 0x1a6,
|
||||
0x3c9, 0x036, 0x0f0, 0x2ab, 0x1cb, 0x37c, 0x095, 0x1fd, 0x328, 0x11a, 0x306, 0x158, 0x3a2, 0x05d, 0x155, 0x39c, 0x063,
|
||||
0x174, 0x3d3, 0x02c, 0x0cf, 0x264, 0x275, 0x24f, 0x299, 0x1fa, 0x32c, 0x115, 0x2ff, 0x15f, 0x3ab, 0x054, 0x143, 0x36c,
|
||||
0x0ad, 0x225, 0x2e5, 0x181, 0x3ef, 0x010, 0x08c, 0x1f1, 0x344, 0x0f3, 0x2af, 0x1c4, 0x386, 0x088, 0x1e3, 0x35b, 0x0d1,
|
||||
0x269, 0x268, 0x26d, 0x25f, 0x27c, 0x23b, 0x2bf, 0x1b2, 0x3ae, 0x051, 0x13b, 0x355, 0x0da, 0x278, 0x248, 0x2a6, 0x1dc,
|
||||
0x365, 0x0c0, 0x246, 0x2a8, 0x1d6, 0x36d, 0x0ac, 0x224, 0x2e8, 0x17e, 0x3eb, 0x014, 0x09c, 0x207, 0x31a, 0x133, 0x341,
|
||||
0x0f8, 0x2bc, 0x1b5, 0x3a3, 0x05c, 0x152, 0x395, 0x06a, 0x18c, 0x3f9, 0x006, 0x07a, 0x1d1, 0x373, 0x0a4, 0x217, 0x2fe,
|
||||
0x160, 0x3ad, 0x052, 0x13c, 0x357, 0x0d7, 0x270, 0x25c, 0x281, 0x235, 0x2c6, 0x1aa, 0x3bc, 0x043, 0x11d, 0x30d, 0x14a,
|
||||
0x384, 0x08a, 0x1e7, 0x353, 0x0dd, 0x284, 0x22e, 0x2d8, 0x192, 0x3ec, 0x013, 0x099, 0x201, 0x323, 0x124, 0x321, 0x127,
|
||||
0x32d, 0x114, 0x2fd, 0x161, 0x3b0, 0x04f, 0x135, 0x343, 0x0f4, 0x2b4, 0x1be, 0x392, 0x06d, 0x19d, 0x3db, 0x024, 0x0be,
|
||||
0x244, 0x2b0, 0x1c2, 0x38a, 0x080, 0x1d9, 0x369, 0x0b6, 0x234, 0x2c8, 0x1a7, 0x3c8, 0x037, 0x0f1, 0x2ad, 0x1c6, 0x383,
|
||||
0x08d, 0x1f2, 0x33b, 0x100, 0x2d1, 0x19b, 0x3de, 0x021, 0x0bb, 0x240, 0x2b6, 0x1bb, 0x399, 0x066, 0x17a, 0x3df, 0x020,
|
||||
0x0b8, 0x23c, 0x2bd, 0x1b4, 0x3a5, 0x05a, 0x150, 0x390, 0x06f, 0x1a5, 0x3cb, 0x034, 0x0ea, 0x29d, 0x1ee, 0x348, 0x0ec,
|
||||
0x2a3, 0x1e5, 0x356, 0x0d8, 0x271, 0x257, 0x289, 0x220, 0x2ec, 0x178, 0x3d9, 0x026, 0x0c2, 0x24b, 0x2a1, 0x1ea, 0x34d,
|
||||
0x0e4, 0x291, 0x20c, 0x312, 0x141, 0x360, 0x0ca, 0x25a, 0x283, 0x230, 0x2d0, 0x19c, 0x3dd, 0x022, 0x0bc, 0x241, 0x2b3,
|
||||
0x1bf, 0x391, 0x06e, 0x1a2, 0x3d1, 0x02e, 0x0d6, 0x26f, 0x25d, 0x27f, 0x237, 0x2c4, 0x1ac, 0x3ba, 0x045, 0x121, 0x318,
|
||||
0x138, 0x34e, 0x0e3, 0x28f, 0x211, 0x30b, 0x14d, 0x38c, 0x073, 0x1c3, 0x387, 0x084, 0x1df, 0x362, 0x0c7, 0x255, 0x28e,
|
||||
0x212, 0x309, 0x153, 0x396, 0x069, 0x18b, 0x3fa, 0x005, 0x079, 0x1d0, 0x374, 0x0a2, 0x215, 0x301, 0x15d, 0x3a9, 0x056,
|
||||
0x147, 0x37a, 0x098, 0x200, 0x324, 0x11f, 0x316, 0x13a, 0x352, 0x0df, 0x288, 0x223, 0x2e9, 0x17d, 0x3e9, 0x016, 0x09e,
|
||||
0x20d, 0x310, 0x144, 0x372, 0x0a5, 0x219, 0x2fa, 0x165, 0x3b8, 0x047, 0x123, 0x31e, 0x12d, 0x338, 0x107, 0x2e0, 0x188,
|
||||
0x3fe, 0x001, 0x075, 0x1c9, 0x37e, 0x093, 0x1f9, 0x32f, 0x112, 0x2f9, 0x167, 0x3be, 0x041, 0x10b, 0x2e7, 0x17f, 0x3ed,
|
||||
0x012, 0x097, 0x1ff, 0x325, 0x11e, 0x311, 0x142, 0x366, 0x0ba, 0x23f, 0x2b8, 0x1b9, 0x39b, 0x064, 0x176, 0x3d6, 0x029,
|
||||
0x0c5, 0x250, 0x298, 0x1fc, 0x329, 0x119, 0x304, 0x15a, 0x3a6, 0x059, 0x14e, 0x38e, 0x071, 0x1ad, 0x3b6, 0x049, 0x128,
|
||||
0x32e, 0x113, 0x2fc, 0x162, 0x3b2, 0x04d, 0x131, 0x33f, 0x0fa, 0x2c2, 0x1af, 0x3b3, 0x04c, 0x130, 0x33e, 0x0fb, 0x2c7,
|
||||
0x1a9, 0x3bd, 0x042, 0x116, 0x300, 0x15e, 0x3aa, 0x055, 0x146, 0x378, 0x09b, 0x206, 0x31b, 0x132, 0x340, 0x0f9, 0x2be,
|
||||
0x1b3, 0x3ac, 0x053, 0x140, 0x35d, 0x0ce, 0x262, 0x279, 0x247, 0x2a7, 0x1d7, 0x36b, 0x0ae, 0x226, 0x2e3, 0x185, 0x3f6,
|
||||
0x009, 0x07d, 0x1d4, 0x36f, 0x0a8, 0x21e, 0x2f0, 0x172, 0x3ce, 0x031, 0x0de, 0x287, 0x228, 0x2df, 0x189, 0x3fd, 0x002,
|
||||
0x076, 0x1ca, 0x37d, 0x094, 0x1fb, 0x32a, 0x118, 0x303, 0x15b, 0x3a7, 0x058, 0x14b, 0x388, 0x083, 0x1dd, 0x364, 0x0c1,
|
||||
0x24a, 0x2a2, 0x1e9, 0x350, 0x0e1, 0x28b, 0x21a, 0x2f8, 0x168, 0x3bf, 0x040, 0x10a, 0x2e6, 0x180, 0x3ee, 0x011, 0x091,
|
||||
0x1f7, 0x334, 0x10d, 0x2ef, 0x173, 0x3cf, 0x030, 0x0dc, 0x280, 0x236, 0x2c5, 0x1ab, 0x3bb, 0x044, 0x120, 0x317, 0x139,
|
||||
0x34f, 0x0e2, 0x28c, 0x218, 0x2fb, 0x163, 0x3b4, 0x04b, 0x12c, 0x337, 0x108, 0x2e2, 0x186, 0x3fc, 0x003, 0x077, 0x1cc,
|
||||
0x37b, 0x096, 0x1fe, 0x327, 0x11b, 0x308, 0x154, 0x397, 0x068, 0x183, 0x3f3, 0x00c, 0x085, 0x1e0, 0x361, 0x0c9, 0x259,
|
||||
0x285, 0x22c, 0x2da, 0x190, 0x3f2, 0x00d, 0x086, 0x1e1, 0x35f, 0x0cb, 0x25b, 0x282, 0x232, 0x2cc, 0x1a1, 0x3d2, 0x02d,
|
||||
0x0d4, 0x26c, 0x263, 0x277, 0x249, 0x2a5, 0x1de, 0x363, 0x0c6, 0x254, 0x290, 0x20e, 0x30f, 0x145, 0x377, 0x09f, 0x20f,
|
||||
0x30e, 0x149, 0x382, 0x08e, 0x1f3, 0x33a, 0x102, 0x2d3, 0x198, 0x3e2, 0x01d, 0x0b2, 0x22d, 0x2d9, 0x191, 0x3f1, 0x00e,
|
||||
0x087, 0x1e2, 0x35e, 0x0cd, 0x261, 0x27a, 0x243, 0x2b1, 0x1c1, 0x38b, 0x07f, 0x1d8, 0x36a, 0x0b4, 0x231, 0x2cf, 0x19e,
|
||||
0x3da, 0x025, 0x0bf, 0x245, 0x2ac, 0x1c7, 0x381, 0x08f, 0x1f5, 0x336, 0x109, 0x2e4, 0x184, 0x3f4, 0x00b, 0x081, 0x1da,
|
||||
0x368, 0x0b7, 0x239, 0x2c1, 0x1b0, 0x3b1, 0x04e, 0x134, 0x342, 0x0f6, 0x2b7, 0x1ba, 0x39a, 0x065, 0x179, 0x3dc, 0x023,
|
||||
0x0bd, 0x242, 0x2b2, 0x1c0, 0x38d, 0x072, 0x1bc, 0x398, 0x067, 0x182, 0x3f0, 0x00f, 0x08b, 0x1e8, 0x351, 0x0e0, 0x28a,
|
||||
0x21c, 0x2f3, 0x16d, 0x3c4, 0x03b, 0x0ff, 0x2ce, 0x19f, 0x3d7, 0x028, 0x0c4, 0x24e, 0x29b, 0x1f0, 0x345, 0x0f2, 0x2ae,
|
||||
0x1c5, 0x385, 0x089, 0x1e6, 0x354, 0x0db, 0x27e, 0x238, 0x2c3, 0x1ae, 0x3b5, 0x04a, 0x12b, 0x333, 0x10e, 0x2f2, 0x16e,
|
||||
0x3c5, 0x03a, 0x0fd, 0x2cb, 0x1a3, 0x3d0, 0x02f, 0x0d9, 0x276, 0x24c, 0x29f, 0x1ec, 0x34a, 0x0e9, 0x29c, 0x1ef, 0x347,
|
||||
0x0ee, 0x2a9, 0x1cf, 0x375, 0x0a1, 0x214, 0x305, 0x159, 0x3a4, 0x05b, 0x151, 0x394, 0x06b, 0x196, 0x3e6, 0x019, 0x0ab,
|
||||
0x222, 0x2ea, 0x17c, 0x3e5, 0x01a, 0x0af, 0x227, 0x2e1, 0x187, 0x3ff, 0x000, 0x074, 0x1c8, 0x37f, 0x092, 0x1f8, 0x331,
|
||||
0x110, 0x2f6, 0x16a, 0x3c1, 0x03e, 0x104, 0x2d5, 0x195, 0x3e7, 0x018, 0x0aa, 0x221, 0x2eb, 0x17b, 0x3e4, 0x01b, 0x0b0,
|
||||
0x229, 0x2dd, 0x18d, 0x3f8, 0x007, 0x07b, 0x1d2, 0x371, 0x0a6, 0x21b, 0x2f5, 0x16b, 0x3c2, 0x03d, 0x103, 0x2d4, 0x197,
|
||||
0x3e3, 0x01c, 0x0b1, 0x22b, 0x2db, 0x18f, 0x3f5, 0x00a, 0x07e, 0x1d5, 0x36e, 0x0a9, 0x21f, 0x2ed, 0x177, 0x3d8, 0x027,
|
||||
0x0c3, 0x24d, 0x29e, 0x1ed, 0x349, 0x0eb, 0x2a0, 0x1eb, 0x34b, 0x0e8, 0x29a, 0x1f4, 0x339, 0x106, 0x2de, 0x18a, 0x3fb,
|
||||
0x004, 0x078, 0x1cd, 0x379, 0x09a, 0x203, 0x320, 0x129, 0x330, 0x111, 0x2f7, 0x169, 0x3c0, 0x03f, 0x105, 0x2d6, 0x194,
|
||||
0x3e8, 0x017, 0x0a3, 0x216,
|
||||
];
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue