298 Commits

Author SHA1 Message Date
MengYX
10e35c5d3e maintenance: update ci 2021-12-16 08:16:04 +08:00
MengYX
12e3f91a1e chore: bump version 2021-12-16 08:03:08 +08:00
MengYX
cb92eed9b1 maintenance: update ci 2021-12-16 07:59:09 +08:00
MengYX
3960ea7d59 maintenance: update ci 2021-12-16 07:25:10 +08:00
MengYX
af20e8a697 maintenance: remove fix-compatibility.js 2021-12-16 07:24:42 +08:00
MengYX
112f2200d7 Merge pull request #208 from unlock-music/feature/refactor-qmc-v1
feat(QMC): use static cipher instead of mask
2021-12-16 07:13:59 +08:00
MengYX
cd6b84ad7e feat: use static cipher instead of mask 2021-12-16 07:07:51 +08:00
MengYX
a14434b956 Merge pull request #207 from jixunmoe/feature/qmcv2-wasm
WIP: 实验 qmc2-crypto 包
2021-12-16 06:35:06 +08:00
Jixun Wu
9470f2ca87 chore: add eol at the end of qmcv2.ts. 2021-12-15 22:20:53 +00:00
Jixun Wu
bdd60bc502 chore: (redone) upgrade qmc2-crypto to 0.0.5-R4
- Remove the use of `new Function` in emscripten generated code.
- This commit is a clean commit that does the same thing as 3b88d168b6
2021-12-15 22:19:24 +00:00
Jixun Wu
0f3cd9b67f Revert "chore: upgrade qmc2-crypto to 0.0.5-R4"
This reverts commit 3b88d168b6.

It generates unexpected large diff in package-lock.json.
2021-12-15 22:16:15 +00:00
Jixun Wu
3b88d168b6 chore: upgrade qmc2-crypto to 0.0.5-R4
- Remove the use of `new Function` in emscripten generated code.
2021-12-15 22:08:25 +00:00
Jixun Wu
41e588e986 fix: treat qmcflac/qmcogg as QMCv2 and fallback to QMCv1 2021-12-15 22:07:05 +00:00
Jixun Wu
5d48b28a94 refactor: remove suppressed qmc mask methods / constants 2021-12-15 20:11:18 +00:00
Jixun Wu
19239f182d refactor: restore support for QMCv1. 2021-12-15 19:59:06 +00:00
Jixun Wu
bdab51bde3 chore: update supported ext list 2021-12-15 19:58:43 +00:00
Jixun Wu
9448b497ed chore: Use QMC2-Crypto with embedded WASM build from 0.0.5-R3 2021-12-15 18:22:05 +00:00
Jixun Wu
4da56bb0fe fix: patch threads to work with production build 2021-12-15 14:02:43 +00:00
Jixun Wu
c8eb1bc481 feat(qmcv2): Experiment with qmc2-crypto 2021-12-15 13:54:35 +00:00
MengYX
1986c1ee41 maintenance: add jest as unit test
(cherry picked from commit ada078df19da90dff701768a3a93f2c8adbc2923)
2021-12-14 15:40:36 +08:00
MengYX
1d510a7afd chore: update deps
(cherry picked from commit 8facd658346c0394216a560696df4bdd9f24fd85)
2021-12-14 15:02:14 +08:00
MengYX
b06b73f43e remove netease: requested by DCMA 2021-10-06 05:07:14 +08:00
MengYX
061392c682 Merge pull request #184 from lvzx123/patch-1
Now it is 2021!
2021-09-25 15:27:57 +08:00
lvzx123
64b51560af Now it is 2021!
大人,时代变了
2021-09-25 08:56:07 +08:00
MengYX
f6d71dc6a2 bump version 2021-08-27 10:01:05 +08:00
MengYX
19b56d1893 fix #169 2021-08-25 01:04:46 +08:00
MengYX
c7181b264f fix #179 2021-08-24 23:55:44 +08:00
MengYX
262c8fa234 fix #179 2021-08-24 23:13:19 +08:00
Emmm Monster
caec454d3b fix(extension): compute version name 2021-08-11 23:04:01 +08:00
Emmm Monster
7cc143d04c chore: bump version & update deps 2021-08-08 08:08:56 +08:00
MengYX
84fd8d2305 change ixarea api endpoint 2021-08-08 07:56:28 +08:00
MengYX
86b543840d optimize: .kgm mask loading 2021-08-08 07:47:28 +08:00
sunhao03
bab5a577da fetch mask file fix on production 2021-08-07 22:13:00 +08:00
Emmm Monster
cffadee581 chore: update deps & fix audit 2021-07-01 01:33:28 +08:00
Emmm Monster
10380d6274 fix: avoid using worker in file protocol 2021-07-01 01:29:04 +08:00
Emmm Monster
af34f64a52 chore(ci): build after *.ts changes 2021-06-03 13:15:04 +08:00
Emmm Monster
ae3c080079 simplify: decrypt/ncm-cache & decrypt/common 2021-06-03 13:09:14 +08:00
Emmm Monster
13e1499e42 fix: decrypt/qmc-cache
adapt: decrypt/qmc for qmc-cache
2021-06-03 13:09:02 +08:00
qq1010903229
6eca3a8405 增加对网易云音乐.uc缓存格式和QQ音乐.cache缓存格式的支持 (#161)
* Update common.ts

* Create ncmcache.ts

* Create qmccache.ts
2021-06-03 13:00:35 +08:00
Emmm Monster
ff5cd344da chore: update deps 2021-05-27 19:53:19 +08:00
Emmm Monster
273e973f32 fix: .vpr/.kgm fail in worker 2021-05-27 19:38:42 +08:00
Emmm Monster
e9b53d880d feature(sniffer): support .dff 2021-05-25 12:27:19 +08:00
Emmm Monster
0a7c99af6f fix: remove test file 2021-05-25 04:36:32 +08:00
Emmm Monster
b3681f162a chore: Bump Version 2021-05-25 03:20:42 +08:00
Emmm Monster
b73848b3a1 feature: directly write to fs 2021-05-25 03:06:28 +08:00
Emmm Monster
3cae9b4cad fixes 2021-05-24 23:48:52 +08:00
Emmm Monster
407d37c770 refactor: component/*.vue 2021-05-24 22:19:37 +08:00
Emmm Monster
e82bbc7aeb refactor(decrypt/*): change interface 2021-05-24 15:05:14 +08:00
Emmm Monster
0be3b29f5d refactor(decrypt/qmc): typescript 2021-05-24 06:50:20 +08:00
Emmm Monster
9e43dbd21a refactor(decrypt/qmc): typescript qmc mask 2021-05-24 05:57:04 +08:00
Emmm Monster
7e0ebf1494 refactor(.ncm): typescript & class 2021-05-24 05:04:16 +08:00
Emmm Monster
9fbe16e155 refactor(typescript): utils.WriteMetaFor{ Mp3, Flac } 2021-05-24 02:55:42 +08:00
Emmm Monster
55fff5d6c7 refactor(typescript): .xm & .kgm 2021-05-24 01:30:38 +08:00
Emmm Monster
f2aa84bfac refactor(typescript): Use ES6 import & use interface 2021-05-23 23:47:01 +08:00
Emmm Monster
901b5cef56 refactor(typescript): utils.GetCoverFromFile & utils.GetMetaFromFile 2021-05-23 23:06:21 +08:00
Emmm Monster
8d9ef9afed feat(decrypt/kwm): support raw .acc 2021-05-23 22:29:34 +08:00
EmmmX
5e3a369609 Merge pull request #157 from unlock-music/add-typescript
Add typescript support
2021-05-23 22:08:23 +08:00
Emmm Monster
dbd0aae3a0 refactor(typescript): utils.GetArrayBuffer 2021-05-23 22:02:36 +08:00
Emmm Monster
fcf0040a16 refactor: move some utils to typescript 2021-05-23 21:40:43 +08:00
Emmm Monster
f4975c0f16 chore: add support for typescript 2021-05-23 21:01:17 +08:00
Emmm Monster
53c1e19cfe chore: update deps & fix audit 2021-05-23 20:41:31 +08:00
MengYX
0373d99fb9 Make Github Dependabot Happy 2021-04-11 13:59:39 +08:00
MengYX
e11e502239 README: Add extension info 2021-02-18 20:08:25 +08:00
MengYX
371b0490dc Remove: [Extension] Stats Code 2021-02-09 15:54:45 +08:00
MengYX
30b8adbb9a Add: [Docs] Docker Usage in README 2021-02-08 20:05:10 +08:00
MengYX
9abb1799f9 Bump Version 2021-02-08 19:28:40 +08:00
MengYX
2639ac3a35 Change: [CI] Action Name 2021-02-08 19:14:26 +08:00
MengYX
ab46eb76d0 Fix: [CI] Build Docker Image 2021-02-08 19:14:12 +08:00
MengYX
6ef8b1fcc6 Fix: [CI] Build Docker Image 2021-02-08 17:09:26 +08:00
MengYX
36f5f9b1e8 Fix: [CI] Build Docker Image 2021-02-08 16:23:24 +08:00
MengYX
ac62fd1122 Update: [CI] Build Docker Image 2021-02-08 16:08:06 +08:00
MengYX
2b16883646 Add: Dockerfile 2021-02-08 14:11:46 +08:00
MengYX
583bb3d8d9 Update: [CI] Remove Cache (because using npm ci) 2021-02-08 14:01:41 +08:00
MengYX
be23dbd3c3 Update: [Docs] README 2021-02-08 12:11:45 +08:00
MengYX
2bb0f25280 Misc: Bump Version 2021-02-08 05:31:16 +08:00
MengYX
1ff5e51afc Fix: [Extension] Use extension API make sure page open successfully 2021-02-08 04:50:00 +08:00
MengYX
9728be6f0d Update: Deps 2021-02-08 04:26:56 +08:00
MengYX
82a211de9a Fix: [CI] Generated zip structure 2021-02-08 04:24:55 +08:00
MengYX
2178a296cb Merge branch 'feature/extension' 2021-02-08 04:14:02 +08:00
MengYX
1429fdf27b Adapt: [Extension] for Firefox 2021-02-08 03:35:26 +08:00
MengYX
4d5c986af9 Fix: [Extension] Remove inline script (for extension's Content Security Policy reason)
Fix: [Extension] Disable Service Worker
2021-02-08 03:05:49 +08:00
MengYX
77f5f06a46 Update CI: Add Extension Build 2021-02-08 01:47:31 +08:00
MengYX
221306f7c5 Add Feature: Browser Extension 2021-02-08 01:28:30 +08:00
MengYX
0c5cae80f0 Change: Web Manifest 2021-02-08 00:26:40 +08:00
MengYX
686f473d55 Update README.md 2021-01-10 16:38:03 +08:00
MengYX
4960322f17 Remove Drone CI 2020-12-23 15:35:46 +08:00
MengYX
1b9bad66ec Remove "By IXarea" 2020-12-23 15:34:37 +08:00
MengYX
f1ba1a3c7e Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/highlight.js-10.4.1' into master 2020-12-19 21:17:27 +08:00
MengYX
1034d54863 Bump Version and Update Deps 2020-12-19 21:16:53 +08:00
MengYX
ad25b3d950 Merge pull request #117 from ix64/fix-qmc-meta
Fix incorrect id3 info for .qmc decryption
2020-12-17 08:36:10 +08:00
MengYX
646bbfb390 Update new-feature.md 2020-12-10 19:01:54 +08:00
MengYX
ac45dc5e47 Update bug-report.md 2020-12-10 19:00:50 +08:00
MengYX
2b55a92f7b Add tips for qmc not writing cover 2020-12-06 02:32:57 +08:00
MengYX
4db4cdbd31 Update CI 2020-12-06 02:22:43 +08:00
MengYX
60fef1c41d Try to fix .qmc ID3 Info 2020-12-06 02:16:45 +08:00
dependabot[bot]
1f41bd8e1f Bump highlight.js from 10.4.0 to 10.4.1
Bumps [highlight.js](https://github.com/highlightjs/highlight.js) from 10.4.0 to 10.4.1.
- [Release notes](https://github.com/highlightjs/highlight.js/releases)
- [Changelog](https://github.com/highlightjs/highlight.js/blob/master/CHANGES.md)
- [Commits](https://github.com/highlightjs/highlight.js/compare/10.4.0...10.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-04 18:43:39 +00:00
MengYX
a2e947cbfb Bump Version and Update Deps 2020-11-26 17:28:14 +08:00
MengYX
87a0a0052d Merge pull request #113 from KyleBing/master
调整暗黑模式样式,新增全局统一样式 by @KyleBing
2020-11-26 16:56:44 +08:00
KyleBing
01cd512178 ^ package-lock.json 2020-11-25 14:47:23 +08:00
KyleBing
880da817a6 暗黑模式颜色调整,载入页颜色适配黑色 2020-11-25 14:38:29 +08:00
KyleBing
b954918820 use scss source file, remove pre-compiled css file. 2020-11-25 13:50:38 +08:00
KyleBing
34a74c761d 调整暗黑模式样式,新增全局统一样式 2020-11-24 22:28:56 +08:00
MengYX
1739215a88 Merge pull request #112 from flosacca/master
Fix #100 by @flosacca
2020-11-23 21:34:58 +08:00
flosacca
76629d955b Fix #100 2020-11-21 07:03:57 +08:00
MengYX
45f1e3575e Update README 2020-11-07 22:46:27 +08:00
MengYX
42629d8075 Bump Version 2020-11-07 01:22:45 +08:00
MengYX
846569ea69 Fix #103 #100 duplicated metadata 2020-11-07 01:12:04 +08:00
MengYX
a0233693fb Update CI 2020-11-06 22:40:23 +08:00
MengYX
0c417edebf Update Deps 2020-11-06 22:40:13 +08:00
MengYX
730fa3465f Merge pull request #106 from lc6464/master
适配浏览器深色模式
2020-11-01 11:40:21 +08:00
NULL-LC
d5dc6866af 适配浏览器深色模式 2020-10-31 19:25:14 +08:00
MengYX
eb7cfd72e5 Merge pull request #101 from renbaoshuo/patch-1
更新 Edge 浏览器下载链接
2020-10-30 10:48:45 +08:00
Baoshuo Ren
7788d98af4 更新 Edge 浏览器下载链接 2020-10-18 19:02:40 +08:00
MengYX
a4b98e12ca Bump Version 2020-09-23 16:51:17 +08:00
MengYX
f64f55a3b7 Merge pull request #97 from qq1010903229/patch-1
Merge pull request #97 增加对QQ音乐微云网盘格式的支持
2020-09-23 16:45:54 +08:00
MengYX
1b7e5702be Fix Kgm Decrypt Bug 2020-09-23 14:15:47 +08:00
MengYX
16c24b4d48 Update Deps 2020-09-23 12:55:14 +08:00
MengYX
7906ca8a72 Fix kgm bug 2020-09-23 12:54:54 +08:00
qq1010903229
98af3a34de Update qmc.js 2020-09-19 12:03:02 +08:00
qq1010903229
2dbfc001b2 Update common.js 2020-09-19 12:00:58 +08:00
MengYX
a8925f4dd7 Add Comment for Issue Template 2020-09-04 19:12:58 +08:00
MengYX
03e06b8fcf Bump Version 2020-09-02 00:04:26 +08:00
MengYX
4b0dc87521 Merge remote-tracking branch into master 2020-09-01 23:31:43 +08:00
MengYX
f4b464b47b Add Tips for .kgm while using "file:" protocol 2020-09-01 23:26:02 +08:00
MengYX
9bd9e272dd Use Small Cover Image for .ncm 2020-09-01 23:17:17 +08:00
MengYX
5cbd26fbce Update README.md 2020-08-15 10:30:13 +08:00
MengYX
5bb0e4f770 fix .xm filename detect 2020-08-13 21:27:50 +08:00
MengYX
b2f437f318 Update CI 2020-08-13 21:27:25 +08:00
MengYX
c7254da74c Merge remote-tracking branch 'origin/master' 2020-08-13 16:01:55 +08:00
MengYX
b5cf435de7 Update GitHub CI 2020-08-13 16:01:35 +08:00
MengYX
9eea1aa15f Bump Version and Update Deps 2020-08-13 15:58:31 +08:00
MengYX
e7e065e014 Fix .xm read info from filename 2020-08-13 15:33:15 +08:00
MengYX
5b7a467dae Fix #84 2020-08-13 15:26:15 +08:00
MengYX
8e00ca3bad Fix .xm file type recognize error 2020-08-13 15:25:41 +08:00
MengYX
0f06b0f306 Update issue templates 2020-08-07 19:33:28 +08:00
MengYX
e6691240dd Fix ncm cover image too big to write into meta 2020-08-03 15:04:54 +08:00
MengYX
0b970ca65d Clean up 2020-08-03 14:03:10 +08:00
MengYX
2c8c88fd9a Fix #79 ncm->flac no metadata (file downloaded from phone) 2020-08-03 14:02:17 +08:00
MengYX
2f8fbc2c14 Bump Version 2020-08-02 18:25:56 +08:00
MengYX
a16cdf8732 Fix wrong zip file in release [Skip CI] 2020-08-01 01:20:56 +08:00
MengYX
1322df34d0 Fix #77 ncm flac meta duplicated
Fix #78 write flac cover sometimes fail
2020-08-01 01:10:27 +08:00
MengYX
3339283883 Update README.md 2020-07-19 00:03:41 +08:00
MengYX
03d0d9e8ef Fix GitHub CI 2020-07-18 23:37:07 +08:00
MengYX
d9f00447ef Bump Version 2020-07-18 23:04:26 +08:00
MengYX
0adc933d2f Change IXarea Api Endpoint 2020-07-18 22:43:25 +08:00
MengYX
43b956d8f6 Change IXarea Api Endpoint 2020-07-18 21:58:07 +08:00
MengYX
6c34a3b0ff Add Support qq music cover 2020-07-18 21:45:53 +08:00
MengYX
1d8c5069e4 Add Support for flac meta/cover 2020-07-18 19:25:41 +08:00
MengYX
9c41991cea Write meta for qq music mp3 2020-07-18 18:48:07 +08:00
MengYX
1c21186306 Resolve QQMusic Cover(By IXarea Server) 2020-07-17 00:33:10 +08:00
MengYX
a8c0c742ca Fix dep security problem 2020-07-16 22:44:42 +08:00
MengYX
7029735889 Update Tips 2020-07-16 22:41:22 +08:00
MengYX
9f7a216144 Add Init Support for Kgm/Vpr 2020-07-16 22:22:32 +08:00
MengYX
5fc74d3fe9 Update Deps 2020-07-16 21:28:49 +08:00
MengYX
2d35cb7be9 Update and rename deploy.yml to release.yml 2020-05-28 02:24:32 +08:00
MengYX
4e257fe8ca Create GitHub Pages 2020-05-28 00:41:17 +08:00
MengYX
eeb09edf3a Fix typo #62 2020-05-21 09:36:59 +08:00
MengYX
539ea0448f Bump Version & Update Deps 2020-05-18 16:41:10 +08:00
MengYX
93f46a2950 Add Tips 2020-05-18 16:40:39 +08:00
MengYX
ea9318554e Simplify 2020-05-14 23:46:21 +08:00
MengYX
0476de3d9b Fix ncm unlock while no album pic #58 2020-05-14 17:37:58 +08:00
MengYX
b667dff401 Fix Xiami return info 2020-05-14 17:36:16 +08:00
MengYX
bb61ef90e6 Fix .qmc Files Unlock Error 2020-04-26 15:20:12 +08:00
MengYX
659cb25476 Bump to 1.5.1 2020-04-26 15:17:40 +08:00
MengYX
be7ac71d5c Fix Qmc Mask Query 2020-04-26 15:04:46 +08:00
MengYX
60bc033d9e Small Bug Fix 2020-04-26 11:20:29 +08:00
MengYX
b09beec673 Update Mgg Detect Algorithm 2020-04-26 01:33:52 +08:00
MengYX
e5505df5f5 Change Tips Info 2020-04-26 01:33:52 +08:00
MengYX
803dab8476 Change CI 2020-04-26 01:33:52 +08:00
MengYX
cd29bde7d0 Bug Fix in Worker Mode 2020-04-23 21:23:01 +08:00
MengYX
049ebd90a0 Remove Console Log 2020-04-23 21:16:41 +08:00
MengYX
52de5b3645 Update README.md 2020-04-23 21:12:11 +08:00
MengYX
62212d3005 Update Description and Bump Version
Small Fixes
2020-04-23 21:10:25 +08:00
MengYX
d3d7ef2eb4 Add Support For Xiami .xm Files! 2020-04-23 20:46:08 +08:00
MengYX
3338524db6 Merge remote-tracking branch 'ixarea/master' 2020-04-23 18:17:58 +08:00
MengYX
36ae570ae5 Small Changes 2020-04-23 18:16:33 +08:00
MengYX
2975508b22 Unlock Kuwo .kwm Files! 2020-04-23 18:15:07 +08:00
MengYX
16f4bdd8a4 Update README Tips 2020-04-22 06:43:40 +00:00
MengYX
f85a1d4ed6 Update README.md 2020-04-21 14:31:01 +00:00
MengYX
b9af9bf227 Update README.md 2020-04-09 08:37:54 +00:00
MengYX
521d9f1677 Change Browser Tips Condition 2020-04-08 22:47:38 +08:00
MengYX
f4780f89d4 Update README.md 2020-04-08 14:12:05 +00:00
MengYX
c4dcfa8a65 Limit Update Tips 2020-04-07 19:02:57 +08:00
MengYX
cd4d270641 Bump Version 2020-04-06 18:20:40 +08:00
MengYX
6d9982a7d4 Fix GBK Detect Bug 2020-04-06 13:03:17 +08:00
MengYX
3570cb5c9a Update Deps 2020-04-05 19:26:05 +08:00
MengYX
a0c9f93788 Hello 2020! 2020-04-05 19:23:30 +08:00
MengYX
607478ce4d Fix GBK Encoding Reading in QQMusic Mp3 2020-04-05 19:20:22 +08:00
MengYX
d19bbf682b Get QQMusic Cover URL (Only Display) 2020-04-05 17:56:10 +08:00
MengYX
55df78396d Move ID3 Writer to Util 2020-04-05 17:31:41 +08:00
MengYX
3ab8fb723e Add progress bar for unlocking #37
Add tips for instant save
2020-04-05 12:35:11 +08:00
MengYX
486a6c2624 Add Support For .qmc2 2020-04-05 01:01:59 +08:00
MengYX
2d35b8b468 Add Header Check For .mgg 2020-03-18 23:03:39 +08:00
MengYX
29801f250a Use Npm Registry 2020-03-18 22:45:07 +08:00
MengYX
a22d78b740 Merge pull request #31
Bump acorn from 6.4.0 to 6.4.1 to fix a security vulnerability
2020-03-15 23:09:18 +08:00
dependabot[bot]
20c8a1d963 Bump acorn from 6.4.0 to 6.4.1
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-15 14:56:29 +00:00
MengYX
fd3cba6c50 Bump Version 2020-03-12 18:56:21 +08:00
MengYX
340c66ec7e Merge CI Settings 2020-03-10 12:22:46 +08:00
MengYX
c968a7578a Fix errors when parsing ncm files without metadata 2020-03-10 09:31:48 +08:00
MengYX
74f8cc8d2a Remove Source Map in production 2020-03-04 10:00:18 +08:00
MengYX
dc8c126cd3 Update CI 2020-03-04 09:38:30 +08:00
MengYX
82915a9dd5 Bump Version 2020-03-04 09:30:48 +08:00
MengYX
8953a57b3e Add fix-compatibility npm command 2020-03-04 09:29:05 +08:00
MengYX
753647fd4d Update Dependencies 2020-03-04 08:37:54 +08:00
MengYX
97f34783ef Update Babel config 2020-03-04 08:37:39 +08:00
MengYX
7ba053cc07 Add temporary solution to fix compatibility for Edge 18 2020-03-04 08:37:16 +08:00
MengYX
a1fb6bc00a Merge branch 'pull/22' 2020-03-03 20:01:42 +08:00
MengYX
de28e844c2 Optimize loader tips for outdated browser 2020-03-03 20:00:44 +08:00
任宝硕
59266f7625 Update index.html 2020-03-03 16:41:45 +08:00
MengYX
1a93da738c Reformat Code 2020-03-01 23:36:16 +08:00
MengYX
ea78532e53 Fix Update Check 2020-03-01 23:34:24 +08:00
任宝硕
3f36619be1 Update index.html 2020-03-01 16:16:34 +08:00
任宝硕
23ada91260 对部分老旧浏览器进行显示调整 + 部分内容修复 2020-03-01 16:14:43 +08:00
MengYX
c15c600cee #20 Add Support For Netease DJ Files 2020-02-29 19:52:41 +08:00
MengYX
d121d38e0d Bump Version 2020-02-23 13:52:03 +08:00
MengYX
166086ae03 #19 Add Download Type: Origin Filename 2020-02-23 13:45:30 +08:00
MengYX
4755674c98 Add Update Check 2020-02-23 13:44:19 +08:00
MengYX
f89202ee62 Immediately Load Latest App 2020-02-23 13:42:50 +08:00
MengYX
b5f8c4a237 Update Dependencies 2020-02-23 13:42:20 +08:00
MengYX
a137af42ec Optimize UI 2020-02-23 13:41:39 +08:00
MengYX
0441f8670a Fix Decrypt Mflac Error 2020-02-12 23:04:48 +08:00
MengYX
07c51bd62d Fix Decrypt Algorithm Error 2020-02-11 17:03:46 +08:00
MengYX
ecbb5b5042 Optimize Loading 2020-02-11 16:35:45 +08:00
MengYX
bc3d7f53aa Fix Mgg Mask Detect Bug 2020-02-11 16:23:50 +08:00
MengYX
354da563b3 Update README and Bump Version 2020-02-11 16:00:34 +08:00
MengYX
9cde491254 Add Detect Media Type by File 2020-02-11 15:52:22 +08:00
MengYX
5171872ec9 Optimize Import 2020-02-11 14:48:27 +08:00
MengYX
08553884ab Use Universal Decoder for Qmc,Mgg,Mflac 2020-02-11 14:35:17 +08:00
MengYX
61622cf7ed Use Universal Mask for Qmc,Mgg,Mflac
Add Local Experimental Support For Mgg
2020-02-11 00:34:26 +08:00
MengYX
4187d433d6 Add Experimental Support For Mgg 2020-02-10 20:10:48 +08:00
MengYX
30853a5617 Bump Version 2020-02-09 15:16:23 +08:00
MengYX
e6cba313c2 Better Way to Detect Mflac Mask 2020-02-09 14:05:40 +08:00
MengYX
34df70ba70 Adjust for Debugging 2020-02-07 20:17:45 +08:00
MengYX
b4be250585 Fix Babel Config 2020-02-07 14:41:21 +08:00
MengYX
b0d8b3c8d2 Remove Useless Information 2020-02-06 16:18:40 +08:00
MengYX
d0b13871f7 Split App.vue 2020-02-06 16:01:35 +08:00
MengYX
e81c3d82e5 Optimize Bundle Size 2020-02-06 14:05:46 +08:00
MengYX
8680be846f Merge branch 'ix-master' 2020-02-05 02:08:08 +08:00
MengYX
285277f303 Bump Version 2020-02-05 01:58:53 +08:00
MengYX
5dd1f3bd9d Add instant download to avoid memory occupation 2020-02-05 01:53:58 +08:00
MengYX
b1fa5612e9 Merge branch 'pull/17'
# Conflicts:
#	src/App.vue
2020-02-05 01:38:34 +08:00
MengYX
f98852a7a0 Add Web Worker 2020-02-05 00:30:44 +08:00
MengYX
f71729d933 Merge pull request #16 from smtop/dev
增加歌曲命名格式选项
2020-02-04 19:41:01 +08:00
1519715742@qq.com
adb44fe8c9 Performance improvement in multiple files 2020-02-04 19:12:44 +08:00
smdev
145e1a6ede 增加歌曲命名格式选项 2020-02-04 18:24:53 +08:00
MengYX
c5b2a8357c Update CI 2020-02-01 12:05:00 +08:00
MengYX
7556c39c71 Edit index.html Upgrade Dependencies 2020-01-31 11:47:16 +08:00
MengYX
304ad63585 #9 Add QQ Music tkm Format 2020-01-27 18:02:39 +08:00
MengYX
77659e0427 Fix QMC filename error 2020-01-27 16:43:40 +08:00
MengYX
7ed3ee8fb0 #11 Add Moo Music Format 2020-01-27 14:06:45 +08:00
MengYX
589697068d Merge branch 'master' of github.com:ix64/unlock-music 2020-01-21 20:00:20 +08:00
MengYX
6918ed257e Update CI 2020-01-21 19:52:45 +08:00
MengYX
6a7af6f5f2 Update Readme [CI SKIP] 2020-01-21 19:31:30 +08:00
MengYX
a0d31d880e Add Support For: tm0/2/3/6 2020-01-21 19:21:17 +08:00
MengYX
8267148e96 Reconstruct 2020-01-21 19:20:46 +08:00
MengYX
e108f7c016 Update Deps 2020-01-21 19:01:52 +08:00
MengYX
7cbf860948 Update README.md 2019-12-23 19:09:24 +08:00
MengYX
8efb1d7d45 Update README.md 2019-12-16 19:01:56 +08:00
MengYX
ba66e38968 Revert: Favicon 2019-12-08 19:12:15 +08:00
MengYX
995b88ff5a Upgrade Vue Cli 2019-12-07 15:09:09 +08:00
MengYX
00de3888ff Remove unused icon 2019-12-07 14:44:32 +08:00
MengYX
4ec1847682 Change build mode 2019-12-07 14:32:28 +08:00
MengYX
1ede0f3193 Fix accept in uploader 2019-12-07 12:23:18 +08:00
MengYX
66a247be3a Use Post 2019-12-01 22:52:29 +08:00
MengYX
55ff80f59b Fix link error in README [SKIP CI] 2019-12-01 21:04:51 +08:00
MengYX
cf078a4fa6 Update README and Dependencies 2019-11-30 21:10:40 +08:00
MengYX
aee06b383f Change Notification 2019-11-24 19:33:11 +08:00
MengYX
1ba6d93fb2 Report Error Type 2019-11-23 19:22:32 +08:00
MengYX
ad3e2d55fc Fix No Status Error 2019-11-23 19:11:40 +08:00
MengYX
fef9841cb4 Fix Download Button 2019-11-23 18:56:45 +08:00
MengYX
ba2f717842 Show Detail Info While Error Occurred 2019-11-23 18:30:59 +08:00
MengYX
e8bee61533 Update Dependencies 2019-11-23 18:14:15 +08:00
MengYX
37c6c5554b Add Partial Support For .mflac 2019-11-23 18:09:33 +08:00
MengYX
093145eb99 Reformat Code [SKIP CI] 2019-11-23 15:10:08 +08:00
MengYX
c1f029705b Add tips for qmcogg 2019-11-23 15:03:45 +08:00
MengYX
d7a2f9361e CI: Auto Deploy and Use Cache 2019-11-10 22:38:43 +08:00
MengYX
62cf8663d8 Add CI 2019-11-10 17:25:01 +08:00
MengYX
831f578daa Use new window to open link 2019-11-10 01:30:11 +08:00
MengYX
03a3e7ef90 Update Dependencies 2019-11-10 01:16:08 +08:00
MengYX
9e9b2ec7f3 Add .qmcogg (without test)
Add Tips
2019-11-10 00:59:13 +08:00
MengYX
3a50460c61 Fix Display Bugs In Edge and Safari 2019-09-21 23:32:21 +08:00
MengYX
83f4015695 Enhanced ease of use 2019-09-18 00:21:18 +08:00
MengYX
3e3a98142b Add Analytics
Fix an error statement
2019-09-14 20:48:57 +08:00
MengYX
91f91ce0c9 Complete ID3 for ncm 2019-09-12 15:51:10 +08:00
MengYX
5c18124ecd Fix an icon error 2019-09-08 15:06:08 +08:00
MengYX
d54a1ebedb Merge pull request #2 from ix64/pull/1
Fix bugs after using music-metadata-browser
2019-09-08 14:50:39 +08:00
MengYX
1fb762630f Merge branch 'master' into pull/1 2019-09-08 14:49:03 +08:00
MengYX
bb9227aa9e Merge pull request #1 from Borewit/music-metadata-browser
Maybe try music-metadata-browser?
2019-09-08 14:45:23 +08:00
Borewit
1bcf198c7b Fix parsing picture in metadata 2019-09-08 08:28:25 +02:00
MengYX
d6e31becc7 Fix bugs after using music-metadata-browser 2019-09-08 13:40:32 +08:00
Borewit
06e04f85b6 use music-metadata-browser 2019-09-07 19:50:04 +02:00
MengYX
b893dd47cf Change icon 2019-08-25 15:55:36 +08:00
MengYX
8011a07342 Downgrade jsmediatag to avoid bug
Rename project
2019-08-25 15:16:20 +08:00
MengYX
087547a7e5 Update page footer 2019-08-25 14:44:27 +08:00
MengYX
d3d8d145ba Update readme 2019-08-25 14:31:38 +08:00
MengYX
ed4d83e19d Update readme 2019-08-25 14:27:11 +08:00
MengYX
e28e6c3654 Merge branch 'master' of https://git.ixarea.com/MusicCrack/music-crack 2019-08-25 14:12:48 +08:00
MengYX
af6d3a9e2e Update dependencies to fix CVE-2019-10744 2019-08-25 14:10:33 +08:00
33 changed files with 27683 additions and 4527 deletions

View File

@@ -3,6 +3,7 @@ on:
push: push:
paths: paths:
- "**/*.js" - "**/*.js"
- "**/*.ts"
- "**/*.vue" - "**/*.vue"
- "public/**/*" - "public/**/*"
- "package-lock.json" - "package-lock.json"
@@ -12,14 +13,21 @@ on:
types: [ opened, synchronize, reopened ] types: [ opened, synchronize, reopened ]
paths: paths:
- "**/*.js" - "**/*.js"
- "**/*.ts"
- "**/*.vue" - "**/*.vue"
- "public/**/*" - "public/**/*"
- "package-lock.json" - "package-lock.json"
- "package.json" - "package.json"
jobs: jobs:
test-coverage:
runs-on: ubuntu-latest
steps:
- name: Test Coverage
uses: ArtiomTr/jest-coverage-report-action@v2.0-rc.6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@@ -35,40 +43,30 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 14.x - name: Use Node.js 16.x
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "16"
- name: Install Dependencies - name: Install Dependencies
run: | run: npm ci
npm ci
npm run fix-compatibility
- name: Build - name: Build
env: run: npm run build ${{ matrix.BUILD_ARGS }}
GZIP: "--best"
run: |
npm run build ${{ matrix.BUILD_ARGS }}
tar -czvf dist.tar.gz -C ./dist .
- name: Build Extension
if: ${{ matrix.BUILD_EXTENSION }}
run: |
npm run make-extension
cd dist
zip -rJ9 ../extension.zip *
cd ..
- name: Publish artifact - name: Publish artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: unlock-music-${{ matrix.build }}.tar.gz name: ${{ matrix.build }}
path: ./dist.tar.gz path: ./dist
- name: Build Extension
if: ${{ matrix.BUILD_EXTENSION }}
run: npm run make-extension
- name: Publish artifact - Extension - name: Publish artifact - Extension
if: ${{ matrix.BUILD_EXTENSION }} if: ${{ matrix.BUILD_EXTENSION }}
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: extension.zip name: extension
path: ./extension.zip path: ./dist

View File

@@ -11,15 +11,13 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 14.x - name: Use Node.js 16.x
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "16"
- name: Install Dependencies - name: Install Dependencies
run: | run: npm ci
npm ci
npm run fix-compatibility
- name: Build Legacy - name: Build Legacy
env: env:

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.DS_Store .DS_Store
node_modules node_modules
/dist /dist
/coverage
# local env files # local env files
.env.local .env.local

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019-2020 MengYX Copyright (c) 2019-2021 MengYX
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

View File

@@ -22,8 +22,6 @@
- [x] QQ音乐新格式 (实验性支持) - [x] QQ音乐新格式 (实验性支持)
- [x] .mflac - [x] .mflac
- [x] [.mgg](https://github.com/ix64/unlock-music/issues/3) - [x] [.mgg](https://github.com/ix64/unlock-music/issues/3)
- [x] 网易云音乐格式 (.ncm)
- [x] 补全ncm的ID3/FlacMeta信息
- [x] 虾米音乐格式 (.xm) (测试阶段) - [x] 虾米音乐格式 (.xm) (测试阶段)
- [x] 酷我音乐格式 (.kwm) (测试阶段) - [x] 酷我音乐格式 (.kwm) (测试阶段)
- [x] 酷狗音乐格式 ( - [x] 酷狗音乐格式 (

View File

@@ -1,6 +1,7 @@
module.exports = { module.exports = {
presets: [ presets: [
'@vue/app' '@vue/app',
'@babel/preset-typescript'
], ],
plugins: [ plugins: [
["component", { ["component", {

5
jest.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1'
}
};

View File

@@ -15,6 +15,9 @@ const manifest = JSON.parse(manifestRaw)
const pkgRaw = fs.readFileSync("./package.json", "utf-8") const pkgRaw = fs.readFileSync("./package.json", "utf-8")
const pkg = JSON.parse(pkgRaw) const pkg = JSON.parse(pkgRaw)
manifest["version"] = pkg["version"] ver_str = pkg["version"]
if (ver_str.startsWith("v")) ver_str = ver_str.slice(1)
manifest["version"] = ver_str
fs.writeFileSync("./dist/manifest.json", JSON.stringify(manifest), "utf-8") fs.writeFileSync("./dist/manifest.json", JSON.stringify(manifest), "utf-8")
console.log("Write: manifest.json") console.log("Write: manifest.json")

31063
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "unlock-music", "name": "unlock-music",
"version": "v1.9.0-beta", "version": "v1.10.0-beta.1",
"updateInfo": "新增写入本地文件系统; 优化.kwm解锁; 支持.acc嗅探; 使用Typescript重构", "updateInfo": "新增写入本地文件系统; 优化.kwm解锁; 支持.acc嗅探; 使用Typescript重构",
"license": "MIT", "license": "MIT",
"description": "Unlock encrypted music file in browser.", "description": "Unlock encrypted music file in browser.",
@@ -10,38 +10,45 @@
}, },
"private": true, "private": true,
"scripts": { "scripts": {
"postinstall": "patch-package",
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"fix-compatibility": "node ./src/fix-compatibility.js", "test": "jest",
"make-extension": "node ./make-extension.js" "make-extension": "node ./make-extension.js"
}, },
"dependencies": { "dependencies": {
"@babel/preset-typescript": "^7.16.5",
"@jixun/qmc2-crypto": "^0.0.5-R4",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"browser-id3-writer": "^4.4.0", "browser-id3-writer": "^4.4.0",
"core-js": "^3.12.1", "core-js": "^3.16.0",
"crypto-js": "^4.0.0", "crypto-js": "^4.1.1",
"element-ui": "^2.15.1", "element-ui": "^2.15.5",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"jimp": "^0.16.1", "jimp": "^0.16.1",
"metaflac-js": "^1.0.5", "metaflac-js": "^1.0.5",
"music-metadata-browser": "^2.2.6", "music-metadata": "7.9.0",
"music-metadata-browser": "2.2.7",
"register-service-worker": "^1.7.2", "register-service-worker": "^1.7.2",
"threads": "^1.6.4", "threads": "^1.6.5",
"vue": "^2.6.12" "vue": "^2.6.14"
}, },
"devDependencies": { "devDependencies": {
"@types/crypto-js": "^4.0.1", "@types/crypto-js": "^4.0.2",
"@types/jest": "^27.0.3",
"@vue/cli-plugin-babel": "^4.5.13", "@vue/cli-plugin-babel": "^4.5.13",
"@vue/cli-plugin-pwa": "^4.5.13", "@vue/cli-plugin-pwa": "^4.5.13",
"@vue/cli-plugin-typescript": "^4.5.13", "@vue/cli-plugin-typescript": "^4.5.13",
"@vue/cli-service": "^4.5.13", "@vue/cli-service": "^4.5.13",
"babel-plugin-component": "^1.1.1", "babel-plugin-component": "^1.1.1",
"node-sass": "^5.0.0", "jest": "^27.4.5",
"patch-package": "^6.4.7",
"sass": "^1.38.1",
"sass-loader": "^10.2.0", "sass-loader": "^10.2.0",
"semver": "^7.3.5", "semver": "^7.3.5",
"threads-plugin": "^1.4.0", "threads-plugin": "^1.4.0",
"typescript": "~4.1.5", "typescript": "^4.5.4",
"vue-cli-plugin-element": "^1.0.1", "vue-cli-plugin-element": "^1.0.1",
"vue-template-compiler": "^2.6.12" "vue-template-compiler": "^2.6.14"
} }
} }

View File

@@ -0,0 +1,11 @@
diff --git a/node_modules/threads/worker.mjs b/node_modules/threads/worker.mjs
index c53ac7d..619007b 100644
--- a/node_modules/threads/worker.mjs
+++ b/node_modules/threads/worker.mjs
@@ -1,4 +1,5 @@
-import WorkerContext from "./dist/worker/index.js"
+// Workaround: use of import seems to break minifier.
+const WorkerContext = require("./dist/worker/index.js")
export const expose = WorkerContext.expose
export const registerSerializer = WorkerContext.registerSerializer

View File

@@ -6,7 +6,7 @@
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1.0" name="viewport"> <meta content="width=device-width,initial-scale=1.0" name="viewport">
<title>音乐解锁</title> <title>音乐解锁</title>
<meta content="音乐,解锁,ncm,qmc,mgg,mflac,qq音乐,网易云音乐,加密" name="keywords"/> <meta content="音乐,解锁,qmc,mgg,mflac,qq音乐,加密" name="keywords"/>
<meta content="音乐解锁 - 在任何设备上解锁已购的加密音乐!" name="description"/> <meta content="音乐解锁 - 在任何设备上解锁已购的加密音乐!" name="description"/>
<script src="./ixarea-stats.js"></script> <script src="./ixarea-stats.js"></script>
<!--@formatter:off--> <!--@formatter:off-->

View File

@@ -10,7 +10,7 @@
<a href="https://github.com/ix64/unlock-music/wiki/使用提示" target="_blank">使用提示</a> <a href="https://github.com/ix64/unlock-music/wiki/使用提示" target="_blank">使用提示</a>
</el-row> </el-row>
<el-row> <el-row>
目前支持网易云音乐(ncm), QQ音乐(qmc, mflac, mgg), 酷狗音乐(kgm), 虾米音乐(xm), 酷我音乐(.kwm) 目前支持 QQ音乐(qmc, mflac, mgg), 酷狗音乐(kgm), 虾米音乐(xm), 酷我音乐(.kwm)
<a href="https://github.com/ix64/unlock-music/blob/master/README.md" target="_blank">更多</a> <a href="https://github.com/ix64/unlock-music/blob/master/README.md" target="_blank">更多</a>
</el-row> </el-row>
<el-row> <el-row>

View File

@@ -63,7 +63,7 @@ export default {
} }
}, },
mounted() { mounted() {
if (window.Worker && process.env.NODE_ENV === 'production') { if (window.Worker && window.location.protocol !== "file:" && process.env.NODE_ENV === 'production') {
console.log("Using Worker Pool") console.log("Using Worker Pool")
this.queue = Pool( this.queue = Pool(
() => spawn(new Worker('@/utils/worker.ts')), () => spawn(new Worker('@/utils/worker.ts')),

View File

@@ -1,37 +1,34 @@
import {Decrypt as NcmDecrypt} from "@/decrypt/ncm";
import {Decrypt as XmDecrypt} from "@/decrypt/xm"; import {Decrypt as XmDecrypt} from "@/decrypt/xm";
import {Decrypt as QmcDecrypt} from "@/decrypt/qmc"; import {Decrypt as QmcDecrypt} from "@/decrypt/qmc";
import {Decrypt as QmcCacheDecrypt} from "@/decrypt/qmccache";
import {Decrypt as KgmDecrypt} from "@/decrypt/kgm"; import {Decrypt as KgmDecrypt} from "@/decrypt/kgm";
import {Decrypt as KwmDecrypt} from "@/decrypt/kwm"; import {Decrypt as KwmDecrypt} from "@/decrypt/kwm";
import {Decrypt as RawDecrypt} from "@/decrypt/raw"; import {Decrypt as RawDecrypt} from "@/decrypt/raw";
import {Decrypt as TmDecrypt} from "@/decrypt/tm"; import {Decrypt as TmDecrypt} from "@/decrypt/tm";
import {DecryptResult, FileInfo} from "@/decrypt/entity"; import {DecryptResult, FileInfo} from "@/decrypt/entity";
import {SplitFilename} from "@/decrypt/utils";
export async function CommonDecrypt(file: FileInfo): Promise<DecryptResult> { export async function CommonDecrypt(file: FileInfo): Promise<DecryptResult> {
let raw_ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase(); const raw = SplitFilename(file.name)
let raw_filename = file.name.substring(0, file.name.lastIndexOf("."));
let rt_data: DecryptResult; let rt_data: DecryptResult;
switch (raw_ext) { switch (raw.ext) {
case "ncm":// Netease Mp3/Flac
rt_data = await NcmDecrypt(file.raw, raw_filename, raw_ext);
break;
case "kwm":// Kuwo Mp3/Flac case "kwm":// Kuwo Mp3/Flac
rt_data = await KwmDecrypt(file.raw, raw_filename, raw_ext); rt_data = await KwmDecrypt(file.raw, raw.name, raw.ext);
break break
case "xm": // Xiami Wav/M4a/Mp3/Flac case "xm": // Xiami Wav/M4a/Mp3/Flac
case "wav":// Xiami/Raw Wav case "wav":// Xiami/Raw Wav
case "mp3":// Xiami/Raw Mp3 case "mp3":// Xiami/Raw Mp3
case "flac":// Xiami/Raw Flac case "flac":// Xiami/Raw Flac
case "m4a":// Xiami/Raw M4a case "m4a":// Xiami/Raw M4a
rt_data = await XmDecrypt(file.raw, raw_filename, raw_ext); rt_data = await XmDecrypt(file.raw, raw.name, raw.ext);
break; break;
case "ogg":// Raw Ogg case "ogg":// Raw Ogg
rt_data = await RawDecrypt(file.raw, raw_filename, raw_ext); rt_data = await RawDecrypt(file.raw, raw.name, raw.ext);
break; break;
case "tm0":// QQ Music IOS Mp3 case "tm0":// QQ Music IOS Mp3
case "tm3":// QQ Music IOS Mp3 case "tm3":// QQ Music IOS Mp3
rt_data = await RawDecrypt(file.raw, raw_filename, "mp3"); rt_data = await RawDecrypt(file.raw, raw.name, "mp3");
break; break;
case "qmc3"://QQ Music Android Mp3 case "qmc3"://QQ Music Android Mp3
case "qmc2"://QQ Music Android Ogg case "qmc2"://QQ Music Android Ogg
@@ -41,30 +38,35 @@ export async function CommonDecrypt(file: FileInfo): Promise<DecryptResult> {
case "tkm"://QQ Music Accompaniment M4a case "tkm"://QQ Music Accompaniment M4a
case "bkcmp3"://Moo Music Mp3 case "bkcmp3"://Moo Music Mp3
case "bkcflac"://Moo Music Flac case "bkcflac"://Moo Music Flac
case "mflac"://QQ Music Desktop Flac case "mflac"://QQ Music New Flac
case "mgg": //QQ Music Desktop Ogg case "mflac0"://QQ Music New Flac
case "mgg": //QQ Music New Ogg
case "mgg1": //QQ Music New Ogg
case "666c6163"://QQ Music Weiyun Flac case "666c6163"://QQ Music Weiyun Flac
case "6d7033"://QQ Music Weiyun Mp3 case "6d7033"://QQ Music Weiyun Mp3
case "6f6767"://QQ Music Weiyun Ogg case "6f6767"://QQ Music Weiyun Ogg
case "6d3461"://QQ Music Weiyun M4a case "6d3461"://QQ Music Weiyun M4a
case "776176"://QQ Music Weiyun Wav case "776176"://QQ Music Weiyun Wav
rt_data = await QmcDecrypt(file.raw, raw_filename, raw_ext); rt_data = await QmcDecrypt(file.raw, raw.name, raw.ext);
break; break;
case "tm2":// QQ Music IOS M4a case "tm2":// QQ Music IOS M4a
case "tm6":// QQ Music IOS M4a case "tm6":// QQ Music IOS M4a
rt_data = await TmDecrypt(file.raw, raw_filename); rt_data = await TmDecrypt(file.raw, raw.name);
break;
case "cache"://QQ Music Cache
rt_data = await QmcCacheDecrypt(file.raw, raw.name, raw.ext);
break; break;
case "vpr": case "vpr":
case "kgm": case "kgm":
case "kgma": case "kgma":
rt_data = await KgmDecrypt(file.raw, raw_filename, raw_ext); rt_data = await KgmDecrypt(file.raw, raw.name, raw.ext);
break break
default: default:
throw "不支持此文件格式" throw "不支持此文件格式"
} }
if (!rt_data.rawExt) rt_data.rawExt = raw_ext; if (!rt_data.rawExt) rt_data.rawExt = raw.ext;
if (!rt_data.rawFilename) rt_data.rawFilename = raw_filename; if (!rt_data.rawFilename) rt_data.rawFilename = raw.name;
console.log(rt_data); console.log(rt_data);
return rt_data; return rt_data;
} }

View File

@@ -5,9 +5,10 @@ import {
GetCoverFromFile, GetCoverFromFile,
GetMetaFromFile, GetMetaFromFile,
SniffAudioExt SniffAudioExt
} from "@/decrypt/utils.ts"; } from "@/decrypt/utils";
import {parseBlob as metaParseBlob} from "music-metadata-browser"; import {parseBlob as metaParseBlob} from "music-metadata-browser";
import {DecryptResult} from "@/decrypt/entity"; import {DecryptResult} from "@/decrypt/entity";
import config from "@/../package.json"
const VprHeader = [ const VprHeader = [
0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43, 0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43,
@@ -22,9 +23,6 @@ const VprMaskDiff = [
export async function Decrypt(file: File, raw_filename: string, raw_ext: string): Promise<DecryptResult> { export async function Decrypt(file: File, raw_filename: string, raw_ext: string): Promise<DecryptResult> {
if (window?.location?.protocol === "file:") {
throw Error("请使用 <a target='_blank' href='https://github.com/unlock-music/cli'>CLI版本</a> 进行解锁")
}
const oriData = new Uint8Array(await GetArrayBuffer(file)); const oriData = new Uint8Array(await GetArrayBuffer(file));
if (raw_ext === "vpr") { if (raw_ext === "vpr") {
@@ -84,8 +82,16 @@ function GetMask(pos: number) {
let MaskV2: Uint8Array = new Uint8Array(0); let MaskV2: Uint8Array = new Uint8Array(0);
async function LoadMaskV2(): Promise<boolean> { async function LoadMaskV2(): Promise<boolean> {
let mask_url = `https://cdn.jsdelivr.net/gh/unlock-music/unlock-music@${config.version}/public/static/kgm.mask`
if (["http:", "https:"].some(v => v == self.location.protocol)) {
if (!!self.document) {// using Web Worker
mask_url = "./static/kgm.mask"
} else {// using Main thread
mask_url = "../static/kgm.mask"
}
}
try { try {
const resp = await fetch("./static/kgm.mask", {method: "GET"}) const resp = await fetch(mask_url, {method: "GET"})
MaskV2 = new Uint8Array(await resp.arrayBuffer()); MaskV2 = new Uint8Array(await resp.arrayBuffer());
return true return true
} catch (e) { } catch (e) {

View File

@@ -5,8 +5,8 @@ import {
GetCoverFromFile, GetCoverFromFile,
GetMetaFromFile, GetMetaFromFile,
SniffAudioExt SniffAudioExt
} from "@/decrypt/utils.ts"; } from "@/decrypt/utils";
import {Decrypt as RawDecrypt} from "@/decrypt/raw.ts"; import {Decrypt as RawDecrypt} from "@/decrypt/raw";
import {parseBlob as metaParseBlob} from "music-metadata-browser"; import {parseBlob as metaParseBlob} from "music-metadata-browser";
import {DecryptResult} from "@/decrypt/entity"; import {DecryptResult} from "@/decrypt/entity";

View File

@@ -1,235 +0,0 @@
import {
AudioMimeType,
BytesHasPrefix,
GetArrayBuffer,
GetImageFromURL,
GetMetaFromFile, IMusicMeta,
SniffAudioExt,
WriteMetaToFlac,
WriteMetaToMp3
} from "@/decrypt/utils.ts";
import {parseBlob as metaParseBlob} from "music-metadata-browser";
import jimp from 'jimp';
import CryptoJS from "crypto-js";
import {DecryptResult} from "@/decrypt/entity";
const CORE_KEY = CryptoJS.enc.Hex.parse("687a4852416d736f356b496e62617857");
const META_KEY = CryptoJS.enc.Hex.parse("2331346C6A6B5F215C5D2630553C2728");
const MagicHeader = [0x43, 0x54, 0x45, 0x4E, 0x46, 0x44, 0x41, 0x4D];
export async function Decrypt(file: File, raw_filename: string, _: string): Promise<DecryptResult> {
return (new NcmDecrypt(await GetArrayBuffer(file), raw_filename)).decrypt()
}
interface NcmMusicMeta {
//musicId: number
musicName?: string
artist?: Array<string | number>[]
format?: string
album?: string
albumPic?: string
}
interface NcmDjMeta {
mainMusic: NcmMusicMeta
}
class NcmDecrypt {
raw: ArrayBuffer
view: DataView
offset: number = 0
filename: string
format: string = ""
mime: string = ""
audio?: Uint8Array
blob?: Blob
oriMeta?: NcmMusicMeta
newMeta?: IMusicMeta
image?: { mime: string, buffer: ArrayBuffer, url: string }
constructor(buf: ArrayBuffer, filename: string) {
const prefix = new Uint8Array(buf, 0, 8)
if (!BytesHasPrefix(prefix, MagicHeader)) throw Error("此ncm文件已损坏")
this.offset = 10
this.raw = buf
this.view = new DataView(buf)
this.filename = filename
}
_getKeyData(): Uint8Array {
const keyLen = this.view.getUint32(this.offset, true);
this.offset += 4;
const cipherText = new Uint8Array(this.raw, this.offset, keyLen)
.map(uint8 => uint8 ^ 0x64);
this.offset += keyLen;
const plainText = CryptoJS.AES.decrypt(
// @ts-ignore
{ciphertext: CryptoJS.lib.WordArray.create(cipherText)},
CORE_KEY,
{mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}
);
const result = new Uint8Array(plainText.sigBytes);
const words = plainText.words;
const sigBytes = plainText.sigBytes;
for (let i = 0; i < sigBytes; i++) {
result[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
}
return result.slice(17)
}
_getKeyBox(): Uint8Array {
const keyData = this._getKeyData()
const box = new Uint8Array(Array(256).keys());
const keyDataLen = keyData.length;
let j = 0;
for (let i = 0; i < 256; i++) {
j = (box[i] + j + keyData[i % keyDataLen]) & 0xff;
[box[i], box[j]] = [box[j], box[i]];
}
return box.map((_, i, arr) => {
i = (i + 1) & 0xff;
const si = arr[i];
const sj = arr[(i + si) & 0xff];
return arr[(si + sj) & 0xff];
});
}
_getMetaData(): NcmMusicMeta {
const metaDataLen = this.view.getUint32(this.offset, true);
this.offset += 4;
if (metaDataLen === 0) return {};
const cipherText = new Uint8Array(this.raw, this.offset, metaDataLen)
.map(data => data ^ 0x63);
this.offset += metaDataLen;
const plainText = CryptoJS.AES.decrypt(
//@ts-ignore
{
ciphertext: CryptoJS.enc.Base64.parse(
//@ts-ignore
CryptoJS.lib.WordArray.create(cipherText.slice(22)).toString(CryptoJS.enc.Utf8)
)
},
META_KEY,
{mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}
).toString(CryptoJS.enc.Utf8);
const labelIndex = plainText.indexOf(":");
let result: NcmMusicMeta;
if (plainText.slice(0, labelIndex) === "dj") {
const tmp: NcmDjMeta = JSON.parse(plainText.slice(labelIndex + 1));
result = tmp.mainMusic;
} else {
result = JSON.parse(plainText.slice(labelIndex + 1));
}
if (!!result.albumPic) {
result.albumPic = result.albumPic.replace("http://", "https://") + "?param=500y500"
}
return result
}
_getAudio(keyBox: Uint8Array): Uint8Array {
this.offset += this.view.getUint32(this.offset + 5, true) + 13
const audioData = new Uint8Array(this.raw, this.offset)
let lenAudioData = audioData.length
for (let cur = 0; cur < lenAudioData; ++cur) audioData[cur] ^= keyBox[cur & 0xff]
return audioData
}
async _buildMeta() {
if (!this.oriMeta) throw Error("invalid sequence")
const info = GetMetaFromFile(this.filename, this.oriMeta.musicName)
// build artists
let artists: string[] = [];
if (!!this.oriMeta.artist) {
this.oriMeta.artist.forEach(arr => artists.push(<string>arr[0]));
}
if (artists.length === 0 && !!info.artist) {
artists = info.artist.split(',')
.map(val => val.trim()).filter(val => val != "");
}
if (this.oriMeta.albumPic) try {
this.image = await GetImageFromURL(this.oriMeta.albumPic)
while (this.image && this.image.buffer.byteLength >= 1 << 24) {
let img = await jimp.read(Buffer.from(this.image.buffer))
await img.resize(Math.round(img.getHeight() / 2), jimp.AUTO)
this.image.buffer = await img.getBufferAsync("image/jpeg")
}
} catch (e) {
console.log("get cover image failed", e)
}
this.newMeta = {title: info.title, artists, album: this.oriMeta.album, picture: this.image?.buffer}
}
async _writeMeta() {
if (!this.audio || !this.newMeta) throw Error("invalid sequence")
if (!this.blob) this.blob = new Blob([this.audio], {type: this.mime})
const ori = await metaParseBlob(this.blob);
let shouldWrite = !ori.common.album && !ori.common.artists && !ori.common.title
if (shouldWrite || this.newMeta.picture) {
if (this.format === "mp3") {
this.audio = WriteMetaToMp3(Buffer.from(this.audio), this.newMeta, ori)
} else if (this.format === "flac") {
this.audio = WriteMetaToFlac(Buffer.from(this.audio), this.newMeta, ori)
} else {
console.info(`writing meta for ${this.format} is not being supported for now`)
return
}
this.blob = new Blob([this.audio], {type: this.mime})
}
}
gatherResult(): DecryptResult {
if (!this.newMeta) throw Error("bad sequence")
return {
title: this.newMeta.title,
artist: this.newMeta.artists?.join("; "),
ext: this.format,
album: this.newMeta.album,
picture: this.image?.url,
file: URL.createObjectURL(this.blob),
blob: this.blob as Blob,
mime: this.mime
}
}
async decrypt() {
const keyBox = this._getKeyBox()
this.oriMeta = this._getMetaData()
this.audio = this._getAudio(keyBox)
this.format = this.oriMeta.format || SniffAudioExt(this.audio)
this.mime = AudioMimeType[this.format]
await this._buildMeta()
try {
await this._writeMeta()
} catch (e) {
console.warn("write meta data failed", e)
}
return this.gatherResult()
}
}

View File

@@ -1,138 +1,139 @@
import {QmcMask, QmcMaskDetectMflac, QmcMaskDetectMgg, QmcMaskGetDefault} from "./qmcMask"; import {QmcStaticCipher} from "./qmc_cipher";
import {toByteArray as Base64Decode} from 'base64-js'
import { import {
AudioMimeType, AudioMimeType,
GetArrayBuffer, GetArrayBuffer,
GetCoverFromFile, GetCoverFromFile,
GetImageFromURL, GetImageFromURL,
GetMetaFromFile, GetMetaFromFile,
SniffAudioExt, WriteMetaToFlac, WriteMetaToMp3 SniffAudioExt,
} from "@/decrypt/utils.ts"; WriteMetaToFlac,
WriteMetaToMp3
} from "@/decrypt/utils";
import {parseBlob as metaParseBlob} from "music-metadata-browser"; import {parseBlob as metaParseBlob} from "music-metadata-browser";
import {DecryptQMCv2} from "./qmcv2";
import iconv from "iconv-lite"; import iconv from "iconv-lite";
import {DecryptResult} from "@/decrypt/entity"; import {DecryptResult} from "@/decrypt/entity";
import {queryAlbumCover, queryKeyInfo, reportKeyUsage} from "@/utils/api"; import {queryAlbumCover} from "@/utils/api";
interface Handler { interface Handler {
ext: string ext: string
detect: boolean version: number
handler(data?: Uint8Array): QmcMask | undefined
} }
const HandlerMap: { [key: string]: Handler } = { export const HandlerMap: { [key: string]: Handler } = {
"mgg": {handler: QmcMaskDetectMgg, ext: "ogg", detect: true}, "mgg": {ext: "ogg", version: 2},
"mflac": {handler: QmcMaskDetectMflac, ext: "flac", detect: true}, "mgg1": {ext: "ogg", version: 2},
"qmc0": {handler: QmcMaskGetDefault, ext: "mp3", detect: false}, "mflac": {ext: "flac", version: 2},
"qmc2": {handler: QmcMaskGetDefault, ext: "ogg", detect: false}, "mflac0": {ext: "flac", version: 2},
"qmc3": {handler: QmcMaskGetDefault, ext: "mp3", detect: false},
"qmcogg": {handler: QmcMaskGetDefault, ext: "ogg", detect: false}, // qmcflac / qmcogg:
"qmcflac": {handler: QmcMaskGetDefault, ext: "flac", detect: false}, // 有可能是 v2 加密但混用同一个后缀名。
"bkcmp3": {handler: QmcMaskGetDefault, ext: "mp3", detect: false}, "qmcflac": {ext: "flac", version: 2},
"bkcflac": {handler: QmcMaskGetDefault, ext: "flac", detect: false}, "qmcogg": {ext: "ogg", version: 2},
"tkm": {handler: QmcMaskGetDefault, ext: "m4a", detect: false},
"666c6163": {handler: QmcMaskGetDefault, ext: "flac", detect: false}, "qmc0": {ext: "mp3", version: 1},
"6d7033": {handler: QmcMaskGetDefault, ext: "mp3", detect: false}, "qmc2": {ext: "ogg", version: 1},
"6f6767": {handler: QmcMaskGetDefault, ext: "ogg", detect: false}, "qmc3": {ext: "mp3", version: 1},
"6d3461": {handler: QmcMaskGetDefault, ext: "m4a", detect: false}, "bkcmp3": {ext: "mp3", version: 1},
"776176": {handler: QmcMaskGetDefault, ext: "wav", detect: false} "bkcflac": {ext: "flac", version: 1},
"tkm": {ext: "m4a", version: 1},
"666c6163": {ext: "flac", version: 1},
"6d7033": {ext: "mp3", version: 1},
"6f6767": {ext: "ogg", version: 1},
"6d3461": {ext: "m4a", version: 1},
"776176": {ext: "wav", version: 1}
}; };
export async function Decrypt(file: File, raw_filename: string, raw_ext: string): Promise<DecryptResult> { export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string): Promise<DecryptResult> {
if (!(raw_ext in HandlerMap)) throw "File type is incorrect!"; if (!(raw_ext in HandlerMap)) throw `Qmc cannot handle type: ${raw_ext}`;
const handler = HandlerMap[raw_ext]; const handler = HandlerMap[raw_ext];
let {version} = handler;
const fileData = new Uint8Array(await GetArrayBuffer(file)); const fileBuffer = await GetArrayBuffer(file);
let audioData, seed, keyData; let musicDecoded: Uint8Array | undefined;
if (handler.detect) {
const keyLen = new DataView(fileData.slice(fileData.length - 4).buffer).getUint32(0, true) if (version === 2) {
const keyPos = fileData.length - 4 - keyLen; const v2Decrypted = await DecryptQMCv2(fileBuffer);
audioData = fileData.slice(0, keyPos); // 如果 v2 检测失败,降级到 v1 再尝试一次
seed = handler.handler(audioData); if (v2Decrypted) {
keyData = fileData.slice(keyPos, keyPos + keyLen); musicDecoded = v2Decrypted;
if (!seed) seed = await queryKey(keyData, raw_filename, raw_ext);
if (!seed) throw raw_ext + "格式仅提供实验性支持";
} else { } else {
audioData = fileData; version = 1;
seed = handler.handler(audioData) as QmcMask;
} }
let musicDecoded = seed.Decrypt(audioData); }
const ext = SniffAudioExt(musicDecoded, handler.ext); if (version === 1) {
const mime = AudioMimeType[ext]; const seed = new QmcStaticCipher();
musicDecoded = new Uint8Array(fileBuffer)
seed.decrypt(musicDecoded, 0);
} else if (!musicDecoded) {
throw new Error(`解密失败: ${raw_ext}`);
}
let musicBlob = new Blob([musicDecoded], {type: mime}); const ext = SniffAudioExt(musicDecoded, handler.ext);
const mime = AudioMimeType[ext];
const musicMeta = await metaParseBlob(musicBlob); let musicBlob = new Blob([musicDecoded], {type: mime});
for (let metaIdx in musicMeta.native) {
if (!musicMeta.native.hasOwnProperty(metaIdx)) continue const musicMeta = await metaParseBlob(musicBlob);
if (musicMeta.native[metaIdx].some(item => item.id === "TCON" && item.value === "(12)")) { for (let metaIdx in musicMeta.native) {
console.warn("try using gbk encoding to decode meta") if (!musicMeta.native.hasOwnProperty(metaIdx)) continue
musicMeta.common.artist = iconv.decode(new Buffer(musicMeta.common.artist ?? ""), "gbk"); if (musicMeta.native[metaIdx].some(item => item.id === "TCON" && item.value === "(12)")) {
musicMeta.common.title = iconv.decode(new Buffer(musicMeta.common.title ?? ""), "gbk"); console.warn("try using gbk encoding to decode meta")
musicMeta.common.album = iconv.decode(new Buffer(musicMeta.common.album ?? ""), "gbk"); musicMeta.common.artist = iconv.decode(new Buffer(musicMeta.common.artist ?? ""), "gbk");
musicMeta.common.title = iconv.decode(new Buffer(musicMeta.common.title ?? ""), "gbk");
musicMeta.common.album = iconv.decode(new Buffer(musicMeta.common.album ?? ""), "gbk");
}
}
const info = GetMetaFromFile(raw_filename, musicMeta.common.title, musicMeta.common.artist)
let imgUrl = GetCoverFromFile(musicMeta);
if (!imgUrl) {
imgUrl = await getCoverImage(info.title, info.artist, musicMeta.common.album);
if (imgUrl) {
const imageInfo = await GetImageFromURL(imgUrl);
if (imageInfo) {
imgUrl = imageInfo.url
try {
const newMeta = {picture: imageInfo.buffer, title: info.title, artists: info.artist?.split(" _ ")}
if (ext === "mp3") {
musicDecoded = WriteMetaToMp3(Buffer.from(musicDecoded), newMeta, musicMeta)
musicBlob = new Blob([musicDecoded], {type: mime});
} else if (ext === 'flac') {
musicDecoded = WriteMetaToFlac(Buffer.from(musicDecoded), newMeta, musicMeta)
musicBlob = new Blob([musicDecoded], {type: mime});
} else {
console.info("writing metadata for " + ext + " is not being supported for now")
}
} catch (e) {
console.warn("Error while appending cover image to file " + e)
} }
}
} }
}
const info = GetMetaFromFile(raw_filename, musicMeta.common.title, musicMeta.common.artist) return {
if (keyData) reportKeyUsage(keyData, seed.getMatrix128(), title: info.title,
raw_filename, raw_ext, info.title, info.artist, musicMeta.common.album).then().catch(); artist: info.artist,
ext: ext,
let imgUrl = GetCoverFromFile(musicMeta); album: musicMeta.common.album,
if (!imgUrl) { picture: imgUrl,
imgUrl = await getCoverImage(info.title, info.artist, musicMeta.common.album); file: URL.createObjectURL(musicBlob),
if (imgUrl) { blob: musicBlob,
const imageInfo = await GetImageFromURL(imgUrl); mime: mime
if (imageInfo) { }
imgUrl = imageInfo.url
try {
const newMeta = {picture: imageInfo.buffer, title: info.title, artists: info.artist?.split(" _ ")}
if (ext === "mp3") {
musicDecoded = WriteMetaToMp3(Buffer.from(musicDecoded), newMeta, musicMeta)
musicBlob = new Blob([musicDecoded], {type: mime});
} else if (ext === 'flac') {
musicDecoded = WriteMetaToFlac(Buffer.from(musicDecoded), newMeta, musicMeta)
musicBlob = new Blob([musicDecoded], {type: mime});
} else {
console.info("writing metadata for " + ext + " is not being supported for now")
}
} catch (e) {
console.warn("Error while appending cover image to file " + e)
}
}
}
}
return {
title: info.title,
artist: info.artist,
ext: ext,
album: musicMeta.common.album,
picture: imgUrl,
file: URL.createObjectURL(musicBlob),
blob: musicBlob,
mime: mime
}
} }
async function queryKey(keyData: Uint8Array, filename: string, format: string): Promise<QmcMask | undefined> {
try {
const data = await queryKeyInfo(keyData, filename, format)
return new QmcMask(Base64Decode(data.Matrix44));
} catch (e) {
console.warn(e);
}
}
async function getCoverImage(title: string, artist?: string, album?: string): Promise<string> { async function getCoverImage(title: string, artist?: string, album?: string): Promise<string> {
const song_query_url = "https://stats.ixarea.com/apis" + "/music/qq-cover" const song_query_url = "https://stats.ixarea.com/apis" + "/music/qq-cover"
try { try {
const data = await queryAlbumCover(title, artist, album) const data = await queryAlbumCover(title, artist, album)
return `${song_query_url}/${data.Type}/${data.Id}` return `${song_query_url}/${data.Type}/${data.Id}`
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
} }
return "" return ""
} }

View File

@@ -1,206 +0,0 @@
import {BytesHasPrefix, FLAC_HEADER, OGG_HEADER} from "@/decrypt/utils.ts";
const QMOggPublicHeader1 = [
0x4f, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x1e, 0x01, 0x76, 0x6f, 0x72,
0x62, 0x69, 0x73, 0x00, 0x00, 0x00, 0x00, 0x02, 0x44, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xee, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x01, 0x4f, 0x67, 0x67, 0x53, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff];
const QMOggPublicHeader2 = [
0x03, 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x2c, 0x00, 0x00, 0x00, 0x58, 0x69, 0x70, 0x68, 0x2e,
0x4f, 0x72, 0x67, 0x20, 0x6c, 0x69, 0x62, 0x56, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x20, 0x49, 0x20,
0x32, 0x30, 0x31, 0x35, 0x30, 0x31, 0x30, 0x35, 0x20, 0x28, 0xe2, 0x9b, 0x84, 0xe2, 0x9b, 0x84,
0xe2, 0x9b, 0x84, 0xe2, 0x9b, 0x84, 0x29, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x54,
0x49, 0x54, 0x4c, 0x45, 0x3d];
const QMOggPublicConf1 = [
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0,
0, 0, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 6, 3, 3, 3, 3, 6, 6, 6, 6,
3, 3, 3, 3, 6, 6, 6, 6, 6, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 9, 9,
0, 0, 0, 0];
const QMOggPublicConf2 = [
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 0, 1, 3, 3, 0, 1, 3, 3, 3,
3, 3, 3, 3, 3];
const QMCDefaultMaskMatrix = [
0xde, 0x51, 0xfa, 0xc3, 0x4a, 0xd6, 0xca, 0x90,
0x7e, 0x67, 0x5e, 0xf7, 0xd5, 0x52, 0x84, 0xd8,
0x47, 0x95, 0xbb, 0xa1, 0xaa, 0xc6, 0x66, 0x23,
0x92, 0x62, 0xf3, 0x74, 0xa1, 0x9f, 0xf4, 0xa0,
0x1d, 0x3f, 0x5b, 0xf0, 0x13, 0x0e, 0x09, 0x3d,
0xf9, 0xbc, 0x00, 0x11];
const AllMapping: number[][] = [];
const Mask128to44: number[] = [];
(function () {
for (let i = 0; i < 128; i++) {
let realIdx = (i * i + 27) % 256
if (realIdx in AllMapping) {
AllMapping[realIdx].push(i)
} else {
AllMapping[realIdx] = [i]
}
}
let idx44 = 0
AllMapping.forEach(all128 => {
all128.forEach(_i128 => {
Mask128to44[_i128] = idx44
})
idx44++
})
})();
export class QmcMask {
private readonly Matrix128: number[];
constructor(matrix: number[] | Uint8Array) {
if (matrix instanceof Uint8Array) matrix = Array.from(matrix)
if (matrix.length === 44) {
this.Matrix128 = this._generate128(matrix)
} else if (matrix.length === 128) {
this.Matrix128 = matrix
} else {
throw Error("invalid mask length")
}
}
getMatrix128() {
return this.Matrix128
}
getMatrix44(): number[] {
const matrix44: number[] = []
let idxI44 = 0
AllMapping.forEach(it256 => {
let it256Len = it256.length
for (let i = 1; i < it256Len; i++) {
if (this.Matrix128[it256[0]] !== this.Matrix128[it256[i]]) {
throw "decode mask-128 to mask-44 failed"
}
}
matrix44[idxI44] = this.Matrix128[it256[0]]
idxI44++
})
return matrix44
}
Decrypt(data: Uint8Array) {
if (!this.Matrix128) throw Error("bad call sequence")
let dst = data.slice(0);
let index = -1;
let maskIdx = -1;
for (let cur = 0; cur < data.length; cur++) {
index++;
maskIdx++;
if (index === 0x8000 || (index > 0x8000 && (index + 1) % 0x8000 === 0)) {
index++;
maskIdx++;
}
if (maskIdx >= 128) maskIdx -= 128;
dst[cur] ^= this.Matrix128[maskIdx];
}
return dst;
}
private _generate128(matrix44: number[]): number[] {
const matrix128: number[] = []
let idx44 = 0
AllMapping.forEach(it256 => {
it256.forEach(m => {
matrix128[m] = matrix44[idx44]
})
idx44++
})
return matrix128
}
}
export function QmcMaskGetDefault() {
return new QmcMask(QMCDefaultMaskMatrix)
}
export function QmcMaskDetectMflac(data: Uint8Array) {
let search_len = Math.min(0x8000, data.length), mask;
for (let block_idx = 0; block_idx < search_len; block_idx += 128) {
try {
mask = new QmcMask(data.slice(block_idx, block_idx + 128));
if (BytesHasPrefix(mask.Decrypt(data.slice(0, FLAC_HEADER.length)), FLAC_HEADER)) {
break;
}
} catch (e) {
}
}
return mask;
}
export function QmcMaskDetectMgg(data: Uint8Array) {
if (data.length < 0x100) return
let matrixConfidence: { [key: number]: { [key: number]: number } } = {};
for (let i = 0; i < 44; i++) matrixConfidence[i] = {};
const page2 = data[0x54] ^ data[0xC] ^ QMOggPublicHeader1[0xC];
const spHeader = QmcGenerateOggHeader(page2)
const spConf = QmcGenerateOggConf(page2)
for (let idx128 = 0; idx128 < spHeader.length; idx128++) {
if (spConf[idx128] === 0) continue;
let idx44 = Mask128to44[idx128 % 128];
let _m = data[idx128] ^ spHeader[idx128]
let confidence = spConf[idx128];
if (_m in matrixConfidence[idx44]) {
matrixConfidence[idx44][_m] += confidence
} else {
matrixConfidence[idx44][_m] = confidence
}
}
let matrix = [];
try {
for (let i = 0; i < 44; i++)
matrix[i] = calcMaskFromConfidence(matrixConfidence[i]);
} catch (e) {
return;
}
const mask = new QmcMask(matrix);
if (!BytesHasPrefix(mask.Decrypt(data.slice(0, OGG_HEADER.length)), OGG_HEADER)) {
return;
}
return mask;
}
function calcMaskFromConfidence(confidence: { [key: number]: number }) {
const count = Object.keys(confidence).length
if (count === 0) throw "can not match at least one key";
if (count > 1) console.warn("There are 2 potential value for the mask!")
let result = ""
let conf = 0
for (let idx in confidence) {
if (confidence[idx] > conf) {
result = idx;
conf = confidence[idx];
}
}
return Number(result)
}
function QmcGenerateOggHeader(page2: number) {
let spec = [page2, 0xFF]
for (let i = 2; i < page2; i++) spec.push(0xFF)
spec.push(0xFF)
return QMOggPublicHeader1.concat(spec, QMOggPublicHeader2)
}
function QmcGenerateOggConf(page2: number) {
let specConf = [6, 0]
for (let i = 2; i < page2; i++) specConf.push(4)
specConf.push(0)
return QMOggPublicConf1.concat(specConf, QMOggPublicConf2)
}

View File

@@ -0,0 +1,27 @@
import {QmcStaticCipher} from "@/decrypt/qmc_cipher";
test("static cipher [0x7ff8,0x8000) ", () => {
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) ", () => {
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)
})

53
src/decrypt/qmc_cipher.ts Normal file
View File

@@ -0,0 +1,53 @@
const staticCipherBox = 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
])
interface streamCipher {
decrypt(buf: Uint8Array, offset: number): void
}
export class QmcStaticCipher implements streamCipher {
public getMask(offset: number) {
if (offset > 0x7FFF) offset %= 0x7FFF
return 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)
}
}
}

51
src/decrypt/qmccache.ts Normal file
View File

@@ -0,0 +1,51 @@
import {
AudioMimeType,
GetArrayBuffer,
GetCoverFromFile,
GetMetaFromFile,
SniffAudioExt,
SplitFilename
} from "@/decrypt/utils";
import {Decrypt as QmcDecrypt, HandlerMap} from "@/decrypt/qmc";
import {DecryptResult} from "@/decrypt/entity";
import {parseBlob as metaParseBlob} from "music-metadata-browser";
export async function Decrypt(file: Blob, raw_filename: string, _: string)
: Promise<DecryptResult> {
const buffer = new Uint8Array(await GetArrayBuffer(file));
let length = buffer.length
for (let i = 0; i < length; i++) {
buffer[i] ^= 0xf4
if (buffer[i] <= 0x3f) buffer[i] = buffer[i] * 4;
else if (buffer[i] <= 0x7f) buffer[i] = (buffer[i] - 0x40) * 4 + 1;
else if (buffer[i] <= 0xbf) buffer[i] = (buffer[i] - 0x80) * 4 + 2;
else buffer[i] = (buffer[i] - 0xc0) * 4 + 3;
}
let ext = SniffAudioExt(buffer, "");
const newName = SplitFilename(raw_filename)
let audioBlob: Blob
if (ext !== "" || newName.ext === "mp3") {
audioBlob = new Blob([buffer], {type: AudioMimeType[ext]})
} else if (newName.ext in HandlerMap) {
audioBlob = new Blob([buffer], {type: "application/octet-stream"})
return QmcDecrypt(audioBlob, newName.name, newName.ext);
} else {
throw "不支持的QQ音乐缓存格式"
}
const tag = await metaParseBlob(audioBlob);
const {title, artist} = GetMetaFromFile(raw_filename, tag.common.title, tag.common.artist)
return {
title,
artist,
ext,
album: tag.common.album,
picture: GetCoverFromFile(tag),
file: URL.createObjectURL(audioBlob),
blob: audioBlob,
mime: AudioMimeType[ext]
}
}

102
src/decrypt/qmcv2.ts Normal file
View File

@@ -0,0 +1,102 @@
import QMCCryptoModule from '@jixun/qmc2-crypto/QMC2-wasm-bundle';
// 检测文件末端使用的缓冲区大小
const DETECTION_SIZE = 40;
// 每次处理 2M 的数据
const DECRYPTION_BUF_SIZE = 2 * 1024 * 1024;
function MergeUint8Array(array: Uint8Array[]): Uint8Array {
let length = 0;
array.forEach(item => {
length += item.length;
});
let mergedArray = new Uint8Array(length);
let offset = 0;
array.forEach(item => {
mergedArray.set(item, offset);
offset += item.length;
});
return mergedArray;
}
/**
* 解密一个 QMC2 加密的文件。
*
* 如果检测并解密成功,返回解密后的 Uint8Array 数据。
* @param {ArrayBuffer} mggBlob 读入的文件 Blob
* @return {Promise<Uint8Array|false>}
*/
export async function DecryptQMCv2(mggBlob: ArrayBuffer) {
// 初始化模组
const QMCCrypto = await QMCCryptoModule();
// 申请内存块,并文件末端数据到 WASM 的内存堆
const detectionBuf = new Uint8Array(mggBlob.slice(-DETECTION_SIZE));
const pDetectionBuf = QMCCrypto._malloc(detectionBuf.length);
QMCCrypto.writeArrayToMemory(detectionBuf, pDetectionBuf);
// 检测结果内存块
const pDetectionResult = QMCCrypto._malloc(QMCCrypto.sizeof_qmc_detection());
// 进行检测
const detectOK = QMCCrypto.detectKeyEndPosition(
pDetectionResult,
pDetectionBuf,
detectionBuf.length
);
// 提取结构体内容:
// (pos: i32; len: i32; error: char[??])
const position = QMCCrypto.getValue(pDetectionResult, "i32");
const len = QMCCrypto.getValue(pDetectionResult + 4, "i32");
// 释放内存
QMCCrypto._free(pDetectionBuf);
QMCCrypto._free(pDetectionResult);
if (!detectOK) {
return false;
}
// 计算解密后文件的大小。
// 之前得到的 position 为相对当前检测数据起点的偏移。
const decryptedSize = mggBlob.byteLength - DETECTION_SIZE + position;
// 提取嵌入到文件的 EKey
const ekey = new Uint8Array(
mggBlob.slice(decryptedSize, decryptedSize + len)
);
// 解码 UTF-8 数据到 string
const decoder = new TextDecoder();
const ekey_b64 = decoder.decode(ekey);
// 初始化加密与缓冲区
const hCrypto = QMCCrypto.createInstWidthEKey(ekey_b64);
const buf = QMCCrypto._malloc(DECRYPTION_BUF_SIZE);
const decryptedParts = [];
let offset = 0;
let bytesToDecrypt = decryptedSize;
while (bytesToDecrypt > 0) {
const blockSize = Math.min(bytesToDecrypt, DECRYPTION_BUF_SIZE);
// 解密一些片段
const blockData = new Uint8Array(
mggBlob.slice(offset, offset + blockSize)
);
QMCCrypto.writeArrayToMemory(blockData, buf);
QMCCrypto.decryptStream(hCrypto, buf, offset, blockSize);
decryptedParts.push(QMCCrypto.HEAPU8.slice(buf, buf + blockSize));
offset += blockSize;
bytesToDecrypt -= blockSize;
}
QMCCrypto._free(buf);
hCrypto.delete();
return MergeUint8Array(decryptedParts);
}

View File

@@ -1,4 +1,4 @@
import {AudioMimeType, GetArrayBuffer, GetCoverFromFile, GetMetaFromFile, SniffAudioExt} from "@/decrypt/utils.ts"; import {AudioMimeType, GetArrayBuffer, GetCoverFromFile, GetMetaFromFile, SniffAudioExt} from "@/decrypt/utils";
import {DecryptResult} from "@/decrypt/entity"; import {DecryptResult} from "@/decrypt/entity";

View File

@@ -1,5 +1,5 @@
import {Decrypt as RawDecrypt} from "./raw"; import {Decrypt as RawDecrypt} from "./raw";
import {GetArrayBuffer} from "@/decrypt/utils.ts"; import {GetArrayBuffer} from "@/decrypt/utils";
import {DecryptResult} from "@/decrypt/entity"; import {DecryptResult} from "@/decrypt/entity";
const TM_HEADER = [0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70]; const TM_HEADER = [0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70];

View File

@@ -12,13 +12,16 @@ export const WMA_HEADER = [
] ]
export const WAV_HEADER = [0x52, 0x49, 0x46, 0x46] export const WAV_HEADER = [0x52, 0x49, 0x46, 0x46]
export const AAC_HEADER = [0xFF, 0xF1] export const AAC_HEADER = [0xFF, 0xF1]
export const DFF_HEADER = [0x46, 0x52, 0x4D, 0x38]
export const AudioMimeType: { [key: string]: string } = { export const AudioMimeType: { [key: string]: string } = {
mp3: "audio/mpeg", mp3: "audio/mpeg",
flac: "audio/flac", flac: "audio/flac",
m4a: "audio/mp4", m4a: "audio/mp4",
ogg: "audio/ogg", ogg: "audio/ogg",
wma: "audio/x-ms-wma", wma: "audio/x-ms-wma",
wav: "audio/x-wav" wav: "audio/x-wav",
dff: "audio/x-dff"
}; };
@@ -39,6 +42,7 @@ export function SniffAudioExt(data: Uint8Array, fallback_ext: string = "mp3"): s
if (BytesHasPrefix(data, WAV_HEADER)) return "wav" if (BytesHasPrefix(data, WAV_HEADER)) return "wav"
if (BytesHasPrefix(data, WMA_HEADER)) return "wma" if (BytesHasPrefix(data, WMA_HEADER)) return "wma"
if (BytesHasPrefix(data, AAC_HEADER)) return "aac" if (BytesHasPrefix(data, AAC_HEADER)) return "aac"
if (BytesHasPrefix(data, DFF_HEADER)) return "dff"
return fallback_ext; return fallback_ext;
} }
@@ -156,3 +160,11 @@ export function WriteMetaToFlac(audioData: Buffer, info: IMusicMeta, original: I
} }
return writer.save() return writer.save()
} }
export function SplitFilename(n: string): { name: string; ext: string } {
const pos = n.lastIndexOf(".")
return {
ext: n.substring(pos + 1).toLowerCase(),
name: n.substring(0, pos)
}
}

View File

@@ -1,6 +1,6 @@
import {Decrypt as RawDecrypt} from "@/decrypt/raw"; import {Decrypt as RawDecrypt} from "@/decrypt/raw";
import {DecryptResult} from "@/decrypt/entity"; import {DecryptResult} from "@/decrypt/entity";
import {AudioMimeType, BytesHasPrefix, GetArrayBuffer, GetCoverFromFile, GetMetaFromFile} from "@/decrypt/utils.ts"; import {AudioMimeType, BytesHasPrefix, GetArrayBuffer, GetCoverFromFile, GetMetaFromFile} from "@/decrypt/utils";
import {parseBlob as metaParseBlob} from "music-metadata-browser"; import {parseBlob as metaParseBlob} from "music-metadata-browser";

View File

@@ -1,25 +0,0 @@
//TODO: Use other method to fix this
// !! Only Temporary Solution
// it seems like that @babel/plugin-proposal-object-rest-spread not working
// to fix up the compatibility for Edge 18 and some older Chromium
// now manually edit the dependency files
const fs = require('fs');
const filePath = "./node_modules/file-type/core.js";
const regReplace = /{\s*([a-zA-Z0-9:,\s]*),\s*\.\.\.([a-zA-Z0-9]*)\s*};/m;
if (fs.existsSync(filePath)) {
console.log("File Found!");
let data = fs.readFileSync(filePath).toString();
const regResult = regReplace.exec(data);
if (regResult != null) {
data = data.replace(regResult[0],
"Object.assign({ " + regResult[1] + " }, " + regResult[2] + ");"
);
fs.writeFileSync(filePath, data);
console.log("Object rest spread in file-type fixed!");
} else {
console.log("No fix needed.");
}
} else {
console.log("File Not Found!");
}

9
src/shims-fs.d.ts vendored
View File

@@ -6,6 +6,10 @@ interface FileSystemCreateWritableOptions {
keepExistingData?: boolean keepExistingData?: boolean
} }
interface FileSystemRemoveOptions {
recursive?: boolean
}
interface FileSystemFileHandle { interface FileSystemFileHandle {
getFile(): Promise<File>; getFile(): Promise<File>;
@@ -37,13 +41,16 @@ interface FileSystemWritableFileStream extends WritableStream {
close(): Promise<undefined> // should be implemented in WritableStream close(): Promise<undefined> // should be implemented in WritableStream
} }
export declare interface FileSystemDirectoryHandle { export declare interface FileSystemDirectoryHandle {
getFileHandle(name: string, options?: FileSystemGetFileOptions): Promise<FileSystemFileHandle> getFileHandle(name: string, options?: FileSystemGetFileOptions): Promise<FileSystemFileHandle>
removeEntry(name: string, options?: FileSystemRemoveOptions): Promise<undefined>
} }
declare global { declare global {
interface Window { interface Window {
FileSystemDirectoryHandle
showDirectoryPicker?(): Promise<FileSystemDirectoryHandle> showDirectoryPicker?(): Promise<FileSystemDirectoryHandle>
} }

View File

@@ -1,6 +1,6 @@
import {fromByteArray as Base64Encode} from "base64-js"; import {fromByteArray as Base64Encode} from "base64-js";
export const IXAREA_API_ENDPOINT = "https://stats.ixarea.com/apis" export const IXAREA_API_ENDPOINT = "https://um-api.ixarea.com"
export interface UpdateInfo { export interface UpdateInfo {
Found: boolean Found: boolean

View File

@@ -144,9 +144,9 @@ export default {
} }
try { try {
this.dir = await window.showDirectoryPicker() this.dir = await window.showDirectoryPicker()
window.dir = this.dir const test_filename = "__unlock_music_write_test.txt"
window.f = await this.dir.getFileHandle("write-test.txt", {create: true}) await this.dir.getFileHandle(test_filename, {create: true})
await this.dir.removeEntry(test_filename)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }

View File

@@ -14,7 +14,8 @@
"sourceMap": true, "sourceMap": true,
"baseUrl": ".", "baseUrl": ".",
"types": [ "types": [
"webpack-env" "webpack-env",
"jest"
], ],
"paths": { "paths": {
"@/*": [ "@/*": [
@@ -26,7 +27,8 @@
"dom", "dom",
"dom.iterable", "dom.iterable",
"scripthost" "scripthost"
] ],
"resolveJsonModule": true
}, },
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",