1
0
forked from um/cli
cli/algo/ncm/ncm.go

248 lines
5.6 KiB
Go
Raw Normal View History

2020-12-25 14:41:04 +00:00
package ncm
import (
"bytes"
2022-11-18 23:25:40 +00:00
"context"
2020-12-25 14:41:04 +00:00
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
2022-11-18 23:25:40 +00:00
"fmt"
"io"
2020-12-25 14:41:04 +00:00
"net/http"
"strings"
2022-11-18 23:25:40 +00:00
"github.com/unlock-music/cli/algo/common"
"github.com/unlock-music/cli/internal/utils"
2020-12-25 14:41:04 +00:00
)
var (
magicHeader = []byte{
0x43, 0x54, 0x45, 0x4E, 0x46, 0x44, 0x41, 0x4D}
keyCore = []byte{
0x68, 0x7a, 0x48, 0x52, 0x41, 0x6d, 0x73, 0x6f,
0x35, 0x6b, 0x49, 0x6e, 0x62, 0x61, 0x78, 0x57}
keyMeta = []byte{
0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21,
0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28}
)
2020-12-26 07:47:10 +00:00
func NewDecoder(data []byte) common.Decoder {
2020-12-25 14:41:04 +00:00
return &Decoder{
2020-12-25 16:49:03 +00:00
file: data,
fileLen: uint32(len(data)),
2020-12-25 14:41:04 +00:00
}
}
type Decoder struct {
2020-12-25 16:49:03 +00:00
file []byte
fileLen uint32
2020-12-25 14:41:04 +00:00
key []byte
box []byte
metaRaw []byte
metaType string
2020-12-25 18:53:28 +00:00
meta RawMeta
2020-12-25 14:41:04 +00:00
2020-12-25 18:53:28 +00:00
cover []byte
audio []byte
2020-12-25 14:41:04 +00:00
offsetKey uint32
offsetMeta uint32
offsetCover uint32
offsetAudio uint32
}
2020-12-25 18:53:28 +00:00
func (d *Decoder) Validate() error {
2020-12-25 16:49:03 +00:00
if !bytes.Equal(magicHeader, d.file[:len(magicHeader)]) {
2020-12-25 18:53:28 +00:00
return errors.New("ncm magic header not match")
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
d.offsetKey = 8 + 2
2020-12-25 18:53:28 +00:00
return nil
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
func (d *Decoder) readKeyData() error {
if d.offsetKey == 0 || d.offsetKey+4 > d.fileLen {
return errors.New("invalid cover file offset")
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
bKeyLen := d.file[d.offsetKey : d.offsetKey+4]
2020-12-25 14:41:04 +00:00
iKeyLen := binary.LittleEndian.Uint32(bKeyLen)
2020-12-25 16:49:03 +00:00
d.offsetMeta = d.offsetKey + 4 + iKeyLen
2020-12-25 14:41:04 +00:00
bKeyRaw := make([]byte, iKeyLen)
for i := uint32(0); i < iKeyLen; i++ {
2020-12-25 16:49:03 +00:00
bKeyRaw[i] = d.file[i+4+d.offsetKey] ^ 0x64
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
d.key = utils.PKCS7UnPadding(utils.DecryptAes128Ecb(bKeyRaw, keyCore))[17:]
2020-12-25 14:41:04 +00:00
return nil
}
2020-12-25 16:49:03 +00:00
func (d *Decoder) readMetaData() error {
if d.offsetMeta == 0 || d.offsetMeta+4 > d.fileLen {
return errors.New("invalid meta file offset")
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
bMetaLen := d.file[d.offsetMeta : d.offsetMeta+4]
2020-12-25 14:41:04 +00:00
iMetaLen := binary.LittleEndian.Uint32(bMetaLen)
2020-12-25 16:49:03 +00:00
d.offsetCover = d.offsetMeta + 4 + iMetaLen
2020-12-25 14:41:04 +00:00
if iMetaLen == 0 {
2020-12-25 16:49:03 +00:00
return errors.New("no any meta file found")
2020-12-25 14:41:04 +00:00
}
// Why sub 22: Remove "163 key(Don't modify):"
bKeyRaw := make([]byte, iMetaLen-22)
for i := uint32(0); i < iMetaLen-22; i++ {
2020-12-25 16:49:03 +00:00
bKeyRaw[i] = d.file[d.offsetMeta+4+22+i] ^ 0x63
2020-12-25 14:41:04 +00:00
}
cipherText, err := base64.StdEncoding.DecodeString(string(bKeyRaw))
if err != nil {
return errors.New("decode ncm meta failed: " + err.Error())
}
metaRaw := utils.PKCS7UnPadding(utils.DecryptAes128Ecb(cipherText, keyMeta))
sepIdx := bytes.IndexRune(metaRaw, ':')
if sepIdx == -1 {
2020-12-25 16:49:03 +00:00
return errors.New("invalid ncm meta file")
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
d.metaType = string(metaRaw[:sepIdx])
d.metaRaw = metaRaw[sepIdx+1:]
2020-12-25 14:41:04 +00:00
return nil
}
2020-12-25 16:49:03 +00:00
func (d *Decoder) buildKeyBox() {
2020-12-25 14:41:04 +00:00
box := make([]byte, 256)
for i := 0; i < 256; i++ {
box[i] = byte(i)
}
2020-12-25 16:49:03 +00:00
keyLen := len(d.key)
2020-12-25 14:41:04 +00:00
var j byte
for i := 0; i < 256; i++ {
2020-12-25 16:49:03 +00:00
j = box[i] + j + d.key[i%keyLen]
2020-12-25 14:41:04 +00:00
box[i], box[j] = box[j], box[i]
}
2020-12-25 16:49:03 +00:00
d.box = make([]byte, 256)
2020-12-25 14:41:04 +00:00
var _i byte
for i := 0; i < 256; i++ {
_i = byte(i + 1)
si := box[_i]
sj := box[_i+si]
2020-12-25 16:49:03 +00:00
d.box[i] = box[si+sj]
2020-12-25 14:41:04 +00:00
}
}
2020-12-25 16:49:03 +00:00
func (d *Decoder) parseMeta() error {
switch d.metaType {
2020-12-25 14:41:04 +00:00
case "music":
2020-12-25 18:53:28 +00:00
d.meta = new(RawMetaMusic)
return json.Unmarshal(d.metaRaw, d.meta)
2020-12-25 14:41:04 +00:00
case "dj":
2020-12-25 18:53:28 +00:00
d.meta = new(RawMetaDJ)
return json.Unmarshal(d.metaRaw, d.meta)
2020-12-25 14:41:04 +00:00
default:
2020-12-25 16:49:03 +00:00
return errors.New("unknown ncm meta type: " + d.metaType)
2020-12-25 14:41:04 +00:00
}
}
2020-12-25 16:49:03 +00:00
func (d *Decoder) readCoverData() error {
if d.offsetCover == 0 || d.offsetCover+13 > d.fileLen {
return errors.New("invalid cover file offset")
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
coverLenStart := d.offsetCover + 5 + 4
bCoverLen := d.file[coverLenStart : coverLenStart+4]
2020-12-25 14:41:04 +00:00
iCoverLen := binary.LittleEndian.Uint32(bCoverLen)
2020-12-25 16:49:03 +00:00
d.offsetAudio = coverLenStart + 4 + iCoverLen
2020-12-25 14:41:04 +00:00
if iCoverLen == 0 {
2022-11-18 23:25:40 +00:00
return nil
2020-12-25 14:41:04 +00:00
}
2022-11-18 23:25:40 +00:00
d.cover = d.file[coverLenStart+4 : coverLenStart+4+iCoverLen]
2020-12-25 14:41:04 +00:00
return nil
}
2020-12-25 16:49:03 +00:00
func (d *Decoder) readAudioData() error {
if d.offsetAudio == 0 || d.offsetAudio > d.fileLen {
2020-12-25 14:41:04 +00:00
return errors.New("invalid audio offset")
}
2020-12-25 16:49:03 +00:00
audioRaw := d.file[d.offsetAudio:]
2020-12-25 14:41:04 +00:00
audioLen := len(audioRaw)
2020-12-25 18:53:28 +00:00
d.audio = make([]byte, audioLen)
2020-12-25 14:41:04 +00:00
for i := uint32(0); i < uint32(audioLen); i++ {
2020-12-25 18:53:28 +00:00
d.audio[i] = d.box[i&0xff] ^ audioRaw[i]
2020-12-25 14:41:04 +00:00
}
return nil
}
2020-12-25 16:49:03 +00:00
func (d *Decoder) Decode() error {
if err := d.readKeyData(); err != nil {
2022-11-18 23:25:40 +00:00
return fmt.Errorf("read key data failed: %w", err)
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
d.buildKeyBox()
2020-12-25 14:41:04 +00:00
2022-11-18 23:25:40 +00:00
if err := d.readMetaData(); err != nil {
return fmt.Errorf("read meta date failed: %w", err)
2020-12-25 14:41:04 +00:00
}
2022-11-18 23:25:40 +00:00
if err := d.parseMeta(); err != nil {
return fmt.Errorf("parse meta failed: %w", err)
2020-12-25 14:41:04 +00:00
}
2022-11-18 23:25:40 +00:00
if err := d.readCoverData(); err != nil {
return fmt.Errorf("parse ncm cover file failed: %w", err)
2020-12-25 14:41:04 +00:00
}
2020-12-25 16:49:03 +00:00
return d.readAudioData()
2020-12-25 14:41:04 +00:00
}
2022-11-18 23:25:40 +00:00
func (d *Decoder) GetAudioExt() string {
2020-12-25 18:53:28 +00:00
if d.meta != nil {
if format := d.meta.GetFormat(); format != "" {
return "." + d.meta.GetFormat()
}
2020-12-25 14:41:04 +00:00
}
return ""
}
2022-11-18 23:25:40 +00:00
func (d *Decoder) GetAudioData() []byte {
2020-12-25 18:53:28 +00:00
return d.audio
2020-12-25 14:41:04 +00:00
}
2022-11-18 23:25:40 +00:00
func (d *Decoder) GetCoverImage(ctx context.Context) ([]byte, error) {
2020-12-25 18:53:28 +00:00
if d.cover != nil {
2022-11-18 23:25:40 +00:00
return d.cover, nil
2020-12-25 14:41:04 +00:00
}
2022-11-18 23:25:40 +00:00
imgURL := d.meta.GetAlbumImageURL()
if d.meta != nil && !strings.HasPrefix(imgURL, "http") {
return nil, nil // no cover image
}
// fetch cover image
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imgURL, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("download image failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("download image failed: unexpected http status %s", resp.Status)
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("download image failed: %w", err)
2020-12-25 14:41:04 +00:00
}
2022-11-18 23:25:40 +00:00
return data, nil
2020-12-25 14:41:04 +00:00
}
2022-11-18 23:25:40 +00:00
func (d *Decoder) GetMeta() common.Meta {
2020-12-25 18:53:28 +00:00
return d.meta
2020-12-25 14:41:04 +00:00
}
2020-12-26 07:47:10 +00:00
func init() {
// Netease Mp3/Flac
2021-11-11 15:43:20 +00:00
common.RegisterDecoder("ncm", false, NewDecoder)
2020-12-26 07:47:10 +00:00
}