ncm.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package ncm
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/base64"
  6. "encoding/binary"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "go.uber.org/zap"
  11. "io"
  12. "net/http"
  13. "strings"
  14. "unlock-music.dev/cli/algo/common"
  15. "unlock-music.dev/cli/internal/utils"
  16. )
  17. const magicHeader = "CTENFDAM"
  18. var (
  19. keyCore = []byte{
  20. 0x68, 0x7a, 0x48, 0x52, 0x41, 0x6d, 0x73, 0x6f,
  21. 0x35, 0x6b, 0x49, 0x6e, 0x62, 0x61, 0x78, 0x57,
  22. }
  23. keyMeta = []byte{
  24. 0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21,
  25. 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28,
  26. }
  27. )
  28. func NewDecoder(p *common.DecoderParams) common.Decoder {
  29. return &Decoder{rd: p.Reader, logger: p.Logger.With(zap.String("module", "ncm"))}
  30. }
  31. type Decoder struct {
  32. logger *zap.Logger
  33. rd io.ReadSeeker // rd is the original file reader
  34. offset int
  35. cipher common.StreamDecoder
  36. metaRaw []byte
  37. metaType string
  38. meta ncmMeta
  39. cover []byte
  40. }
  41. // Validate checks if the file is a valid Netease .ncm file.
  42. // rd will be seeked to the beginning of the encrypted audio.
  43. func (d *Decoder) Validate() error {
  44. if err := d.validateMagicHeader(); err != nil {
  45. return err
  46. }
  47. if _, err := d.rd.Seek(2, io.SeekCurrent); err != nil { // 2 bytes gap
  48. return fmt.Errorf("ncm seek file: %w", err)
  49. }
  50. keyData, err := d.readKeyData()
  51. if err != nil {
  52. return err
  53. }
  54. if err := d.readMetaData(); err != nil {
  55. return fmt.Errorf("read meta date failed: %w", err)
  56. }
  57. if _, err := d.rd.Seek(5, io.SeekCurrent); err != nil { // 5 bytes gap
  58. return fmt.Errorf("ncm seek gap: %w", err)
  59. }
  60. if err := d.readCoverData(); err != nil {
  61. return fmt.Errorf("parse ncm cover file failed: %w", err)
  62. }
  63. if err := d.parseMeta(); err != nil {
  64. return fmt.Errorf("parse meta failed: %w (raw json=%s)", err, string(d.metaRaw))
  65. }
  66. d.cipher = newNcmCipher(keyData)
  67. return nil
  68. }
  69. func (d *Decoder) validateMagicHeader() error {
  70. header := make([]byte, len(magicHeader)) // 0x00 - 0x07
  71. if _, err := d.rd.Read(header); err != nil {
  72. return fmt.Errorf("ncm read magic header: %w", err)
  73. }
  74. if !bytes.Equal([]byte(magicHeader), header) {
  75. return errors.New("ncm magic header not match")
  76. }
  77. return nil
  78. }
  79. func (d *Decoder) readKeyData() ([]byte, error) {
  80. bKeyLen := make([]byte, 4) //
  81. if _, err := io.ReadFull(d.rd, bKeyLen); err != nil {
  82. return nil, fmt.Errorf("ncm read key length: %w", err)
  83. }
  84. iKeyLen := binary.LittleEndian.Uint32(bKeyLen)
  85. bKeyRaw := make([]byte, iKeyLen)
  86. if _, err := io.ReadFull(d.rd, bKeyRaw); err != nil {
  87. return nil, fmt.Errorf("ncm read key data: %w", err)
  88. }
  89. for i := uint32(0); i < iKeyLen; i++ {
  90. bKeyRaw[i] ^= 0x64
  91. }
  92. return utils.PKCS7UnPadding(utils.DecryptAES128ECB(bKeyRaw, keyCore))[17:], nil
  93. }
  94. func (d *Decoder) readMetaData() error {
  95. bMetaLen := make([]byte, 4) //
  96. if _, err := io.ReadFull(d.rd, bMetaLen); err != nil {
  97. return fmt.Errorf("ncm read key length: %w", err)
  98. }
  99. iMetaLen := binary.LittleEndian.Uint32(bMetaLen)
  100. if iMetaLen == 0 {
  101. return nil // no meta data
  102. }
  103. bMetaRaw := make([]byte, iMetaLen)
  104. if _, err := io.ReadFull(d.rd, bMetaRaw); err != nil {
  105. return fmt.Errorf("ncm read meta data: %w", err)
  106. }
  107. bMetaRaw = bMetaRaw[22:] // skip "163 key(Don't modify):"
  108. for i := 0; i < len(bMetaRaw); i++ {
  109. bMetaRaw[i] ^= 0x63
  110. }
  111. cipherText, err := base64.StdEncoding.DecodeString(string(bMetaRaw))
  112. if err != nil {
  113. return errors.New("decode ncm meta failed: " + err.Error())
  114. }
  115. metaRaw := utils.PKCS7UnPadding(utils.DecryptAES128ECB(cipherText, keyMeta))
  116. sep := bytes.IndexByte(metaRaw, ':')
  117. if sep == -1 {
  118. return errors.New("invalid ncm meta file")
  119. }
  120. d.metaType = string(metaRaw[:sep])
  121. d.metaRaw = metaRaw[sep+1:]
  122. return nil
  123. }
  124. func (d *Decoder) readCoverData() error {
  125. bCoverFrameLen := make([]byte, 4)
  126. if _, err := io.ReadFull(d.rd, bCoverFrameLen); err != nil {
  127. return fmt.Errorf("ncm read cover length: %w", err)
  128. }
  129. coverFrameStartOffset, err := d.rd.Seek(0, io.SeekCurrent)
  130. if err != nil {
  131. return fmt.Errorf("ncm fetch cover frame start offset: %w", err)
  132. }
  133. coverFrameLen := binary.LittleEndian.Uint32(bCoverFrameLen)
  134. bCoverLen := make([]byte, 4)
  135. if _, err := io.ReadFull(d.rd, bCoverLen); err != nil {
  136. return fmt.Errorf("ncm read cover length: %w", err)
  137. }
  138. iCoverLen := binary.LittleEndian.Uint32(bCoverLen)
  139. coverBuf := make([]byte, iCoverLen)
  140. if _, err := io.ReadFull(d.rd, coverBuf); err != nil {
  141. return fmt.Errorf("ncm read cover data: %w", err)
  142. }
  143. d.cover = coverBuf
  144. offsetAudioData := coverFrameStartOffset + int64(coverFrameLen) + 4
  145. _, err = d.rd.Seek(offsetAudioData, io.SeekStart)
  146. return err
  147. }
  148. func (d *Decoder) parseMeta() error {
  149. switch d.metaType {
  150. case "music":
  151. d.meta = newNcmMetaMusic(d.logger)
  152. return json.Unmarshal(d.metaRaw, d.meta)
  153. case "dj":
  154. d.meta = new(ncmMetaDJ)
  155. return json.Unmarshal(d.metaRaw, d.meta)
  156. default:
  157. return errors.New("unknown ncm meta type: " + d.metaType)
  158. }
  159. }
  160. func (d *Decoder) Read(buf []byte) (int, error) {
  161. n, err := d.rd.Read(buf)
  162. if n > 0 {
  163. d.cipher.Decrypt(buf[:n], d.offset)
  164. d.offset += n
  165. }
  166. return n, err
  167. }
  168. func (d *Decoder) GetAudioExt() string {
  169. if d.meta != nil {
  170. if format := d.meta.GetFormat(); format != "" {
  171. return "." + d.meta.GetFormat()
  172. }
  173. }
  174. return ""
  175. }
  176. func (d *Decoder) GetCoverImage(ctx context.Context) ([]byte, error) {
  177. if d.cover != nil {
  178. return d.cover, nil
  179. }
  180. if d.meta == nil {
  181. return nil, errors.New("ncm meta not found")
  182. }
  183. imgURL := d.meta.GetAlbumImageURL()
  184. if !strings.HasPrefix(imgURL, "http") {
  185. return nil, nil // no cover image
  186. }
  187. // fetch cover image
  188. req, err := http.NewRequestWithContext(ctx, http.MethodGet, imgURL, nil)
  189. resp, err := http.DefaultClient.Do(req)
  190. if err != nil {
  191. return nil, fmt.Errorf("ncm download image failed: %w", err)
  192. }
  193. defer resp.Body.Close()
  194. if resp.StatusCode != http.StatusOK {
  195. return nil, fmt.Errorf("ncm download image failed: unexpected http status %s", resp.Status)
  196. }
  197. d.cover, err = io.ReadAll(resp.Body)
  198. if err != nil {
  199. return nil, fmt.Errorf("ncm download image failed: %w", err)
  200. }
  201. return d.cover, nil
  202. }
  203. func (d *Decoder) GetAudioMeta(_ context.Context) (common.AudioMeta, error) {
  204. return d.meta, nil
  205. }
  206. func init() {
  207. // Netease Mp3/Flac
  208. common.RegisterDecoder("ncm", false, NewDecoder)
  209. }