手机网易云音乐缓存文件生成 mp3 文件

一直使用 Linux 工作、娱乐,因为 Linux 上的网易云音乐缓存在~/.cache/netease-cloud-music/CachedSongs下。直接保存为 mp3 格式的。所以想下载一些会员才能下载的音乐,直接在网易云上听一遍,到缓存的位置找出来就行了。

最近表弟问我有没有网易云音乐的会员,我就在手机上找出网易云音乐的缓存文件,手动改了一个文件的后缀成mp3, 好家伙,手机上的网易云音乐缓存竟然是加密的。emmm,开始 hack 网易云音乐缓存文件。

解密缓存文件

安卓手机上的缓存文件在netease/cloudmusic/Cache/Music1

缓存文件分为两种,一种是.idx!,可以用文本编辑器直接打开,包含歌曲的id信息;另一种是.uc!,根据文件大小判断,很大可能是歌曲文件。

直接修改.uc!文件后缀为.mp3是没办法播放的,很多想从缓存文件中得到可播放文件的人已经不抱希望了。但是作为一个程序员,还是很希望知道怎么实现的。

上网找一圈,终于知道可以通过按位异或0xa3得到的可播放文件。[1]

另外直到现在才知道异或运输在二进制中是可逆运算。 [2]

网上有 java 和 python 的代码,但是没有 nodejs 的,于是自己动手写一个 [3]

1
2
3
4
5
6
7
const fs = require("fs");

fs.readFile("./1498323856-96024-a1ccabb838649c9a8cc96ae5bd249a5e.mp3.uc!",(error, data) =>{
if (error) return ;
const mp3 = data.map(value => value^0xA3);
fs.writeFile("./1498323856-96024-a1ccabb838649c9a8cc96ae5bd249a5e.mp3", mp3,()=>{});
});

bingo,mp3 文件拿到了

获取歌曲信息

网易云缓存文件的命名实在是太奇怪了,一开始我还以为就是随机uuid生成的命名,而.idx!文件的内容改变了我的想法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1498323856-96024-a1ccabb838649c9a8cc96ae5bd249a5e.mp3.idx!
{
"duration": 266301,
"filesize": 3247708,
"musicId": 1498323856,
"filemd5": "a1ccabb838649c9a8cc96ae5bd249a5e",
"version": 2,
"parts": [
"0,3247708"
],
"bitrate": 96024,
"md5": "4fbe5cc8a05fd998b560f342c86f6ec7"
}

文件名被两个横杆-分为三段:

  1. musicId: 音乐的 id
  2. bitrate: 比特率
  3. filemd5: uc!文件的 md5

我用上面的代码生成 mp3 文件后,生成 md5:

1
2
3
4
➜ md5 1498323856-96024-a1ccabb838649c9a8cc96ae5bd249a5e.mp3.uc\! 1498323856-96024-a1ccabb838649c9a8cc96ae5bd249a5e.mp3

A59B5C6B81B2FA2FCBA40CD256566B32 1498323856-96024-a1ccabb838649c9a8cc96ae5bd249a5e.mp3.uc!
A1CCABB838649C9A8CC96AE5BD249A5E 1498323856-96024-a1ccabb838649c9a8cc96ae5bd249a5e.mp3

本来还想从.idx!文件中回去人性化的文件名来着,看来这是没办法了。

我记得以前用 windows 的时候,有一些音乐文件是能看到图片的,而且属性里面还要很多信息。于是找一下怎么获取里面的数据。

原来 mp3 文件有metadata,标签里面包含歌曲的歌手,歌曲图片,歌曲专辑等等信息。[4]

找了一个现成的包,写了个 demo,才发现缓存文件里没有metadata,但是代码对正常下载的网易云音乐文件还是能获取到信息的

1
2
3
4
5
6
// npm i node-id3
const NodeID3 = require('node-id3');

const fs = require('fs');

const tags = NodeID3.read("./3.mp3");

从文件获取id3数据想法破灭了,那只能通过 api 来获取相关信息了:https://api.imjad.cn/cloudmusic/?type=detail&id=1498323856

也可以自己用 Binaryify/NeteaseCloudMusicApi 搭建一个 API。

nodejs 写一个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* @description: 本地网易云音乐缓存文件 uc! 转 mp3
* @author: bubao
* @date: 2021-03-04 05:58:03
* @last author: bubao
* @last edit time: 2021-03-06 19:19:02
*/
const fs = require("fs");
const path = require("path");

let [,,input,output] = process.argv;

if (!output) {
console.log("node insdex.js inputDirectory outputDirectory");
return;
}

try {
const stats = fs.statSync(input);
if(!stats.isDirectory()) throw new Error("");
} catch (error) {
return;
}

const dir = fs.readdirSync(input).filter(value => path.extname(value) === ".uc!");

try {
fs.mkdirSync(output);
} catch (error) { }

Array.isArray(dir) && main(dir);

function main(dir) {
const filename = path.join(input, dir.splice(0, 1)[0]);
fs.readFile(filename, (error, data) => {
if (error) return;
const mp3 = data.map(buffer => buffer ^ 0xA3);
const dlname = path.join(output, path.basename(filename, path.extname(filename)));
fs.writeFile(dlname, mp3, () => {
console.log(dir.length);
dir.length && main(dir);
});
});
}

用 go 写一个脚本工具

用我蹩脚的 go 写一个工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
* @description:
* @author: bubao
* @date: 2021-03-14 17:53:48
* @last author: bubao
* @last edit time: 2021-03-17 18:30:05
*/
package main

import (
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
)

func uc2mp3(ucFilename string, mp3Filename string) {

buf, err := ioutil.ReadFile(ucFilename)
if err != nil {
fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
}
arr := make([]uint8, len(buf))
for i, v := range buf {
arr[i] = v ^ 0xA3
}
err = ioutil.WriteFile(mp3Filename, arr, 0644)
if err != nil {
panic(err.Error())
}
}

func main() {
var dirName string
var outputDir string
var inputFilename string
var rename string
flag.StringVar(&dirName, "d", "", "输入文件夹")
flag.StringVar(&outputDir, "o", "", "输出文件夹")
flag.StringVar(&inputFilename, "f", "", "uc! 文件")
flag.StringVar(&rename, "r", "", "mp3 重命名")
flag.Parse()
if len(flag.Args()) < 1 {
flag.Usage()
return
}
var ext string = ".uc!"

if inputFilename == "" && dirName != "" {
stat, err := os.Stat(dirName)
if err != nil {
fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
panic(err.Error())
}
if stat.IsDir() {
dir, err := ioutil.ReadDir(dirName)
if err != nil {
fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
panic(err.Error())
}

for _, info := range dir {
filename := info.Name()

if !info.IsDir() && path.Ext(filename) == ext {
var outputFile string
outputFile = strings.TrimSuffix(path.Base(filename), ext)
uc2mp3(path.Join(dirName, filename), path.Join(outputDir, outputFile))
}
}
}
} else if inputFilename != "" {
if path.Ext(inputFilename) == ext {
var outputFile string
if rename != "" {
outputFile = rename
} else {
outputFile = strings.TrimSuffix(path.Base(inputFilename), ext)
}
uc2mp3(inputFilename, path.Join(outputFile))
}
}
}

使用也很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uc2mp3 -h
Usage of uc2mp3:
-d string
输入文件夹
-f string
uc! 文件
-o string
输出文件夹
-r string
mp3 重命名
# 文件夹
uc2mp3 -d /home/bubao/Downloads/Jami/music1 -o /home/bubao/Downloads/Jami/music

# 单个文件
uc2mp3 -f /home/bubao/Downloads/Jami/music1/28136576-160000-14cca46fa243ecb590d3fcdf5e530cd1.mp3.uc\! -r "xx.mp3"

参考


手机网易云音乐缓存文件生成 mp3 文件
https://bubao.github.io/posts/bcb27770.html
作者
一念
发布于
2021年3月3日
许可协议