feat: imporve code #39
@ -18,6 +18,7 @@ func RegisterDecoder(ext string, noop bool, dispatchFunc NewDecoderFunc) {
|
|||||||
DecoderRegistry[ext] = append(DecoderRegistry[ext],
|
DecoderRegistry[ext] = append(DecoderRegistry[ext],
|
||||||
decoderItem{noop: noop, decoder: dispatchFunc})
|
decoderItem{noop: noop, decoder: dispatchFunc})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDecoder(filename string, skipNoop bool) (rs []NewDecoderFunc) {
|
func GetDecoder(filename string, skipNoop bool) (rs []NewDecoderFunc) {
|
||||||
ext := strings.ToLower(strings.TrimLeft(filepath.Ext(filename), "."))
|
ext := strings.ToLower(strings.TrimLeft(filepath.Ext(filename), "."))
|
||||||
for _, dec := range DecoderRegistry[ext] {
|
for _, dec := range DecoderRegistry[ext] {
|
||||||
|
@ -35,15 +35,19 @@ func SnifferOGG(header []byte) bool {
|
|||||||
func SnifferFLAC(header []byte) bool {
|
func SnifferFLAC(header []byte) bool {
|
||||||
return bytes.HasPrefix(header, []byte("fLaC"))
|
return bytes.HasPrefix(header, []byte("fLaC"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func SnifferMP3(header []byte) bool {
|
func SnifferMP3(header []byte) bool {
|
||||||
return bytes.HasPrefix(header, []byte("ID3"))
|
return bytes.HasPrefix(header, []byte("ID3"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func SnifferWAV(header []byte) bool {
|
func SnifferWAV(header []byte) bool {
|
||||||
return bytes.HasPrefix(header, []byte("RIFF"))
|
return bytes.HasPrefix(header, []byte("RIFF"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func SnifferWMA(header []byte) bool {
|
func SnifferWMA(header []byte) bool {
|
||||||
return bytes.HasPrefix(header, []byte("\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c"))
|
return bytes.HasPrefix(header, []byte("\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func SnifferAAC(header []byte) bool {
|
func SnifferAAC(header []byte) bool {
|
||||||
return bytes.HasPrefix(header, []byte{0xFF, 0xF1})
|
return bytes.HasPrefix(header, []byte{0xFF, 0xF1})
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/unlock-music/cli/algo/common"
|
"github.com/unlock-music/cli/algo/common"
|
||||||
"github.com/unlock-music/cli/internal/logging"
|
"github.com/unlock-music/cli/internal/logging"
|
||||||
)
|
)
|
||||||
@ -59,6 +60,7 @@ func (d *Decoder) Validate() error {
|
|||||||
d.key = d.file[0x1c:0x2c]
|
d.key = d.file[0x1c:0x2c]
|
||||||
d.key = append(d.key, 0x00)
|
d.key = append(d.key, 0x00)
|
||||||
_ = d.file[0x2c:0x3c] //todo: key2
|
_ = d.file[0x2c:0x3c] //todo: key2
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +69,7 @@ func (d *Decoder) Decode() error {
|
|||||||
dataEncrypted := d.file[headerLen:]
|
dataEncrypted := d.file[headerLen:]
|
||||||
lenData := len(dataEncrypted)
|
lenData := len(dataEncrypted)
|
||||||
initMask()
|
initMask()
|
||||||
|
|
||||||
if fullMaskLen < lenData {
|
if fullMaskLen < lenData {
|
||||||
logging.Log().Warn("The file is too large and the processed audio is incomplete, " +
|
logging.Log().Warn("The file is too large and the processed audio is incomplete, " +
|
||||||
"please report to us about this file at https://github.com/unlock-music/cli/issues")
|
"please report to us about this file at https://github.com/unlock-music/cli/issues")
|
||||||
@ -78,13 +81,16 @@ func (d *Decoder) Decode() error {
|
|||||||
med8 := dataEncrypted[i] ^ d.key[i%17] ^ maskV2PreDef[i%(16*17)] ^ maskV2[i>>4]
|
med8 := dataEncrypted[i] ^ d.key[i%17] ^ maskV2PreDef[i%(16*17)] ^ maskV2[i>>4]
|
||||||
d.audio[i] = med8 ^ (med8&0xf)<<4
|
d.audio[i] = med8 ^ (med8&0xf)<<4
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.isVpr {
|
if d.isVpr {
|
||||||
for i := 0; i < lenData; i++ {
|
for i := 0; i < lenData; i++ {
|
||||||
d.audio[i] ^= maskDiffVpr[i%17]
|
d.audio[i] ^= maskDiffVpr[i%17]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Kugou
|
// Kugou
|
||||||
common.RegisterDecoder("kgm", false, NewDecoder)
|
common.RegisterDecoder("kgm", false, NewDecoder)
|
||||||
|
@ -3,10 +3,11 @@ package kgm
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/ulikunitz/xz"
|
"github.com/ulikunitz/xz"
|
||||||
"github.com/unlock-music/cli/internal/logging"
|
"github.com/unlock-music/cli/internal/logging"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var maskDiffVpr = []byte{
|
var maskDiffVpr = []byte{
|
||||||
@ -41,7 +42,7 @@ var maskV2 []byte
|
|||||||
var fullMaskLen int
|
var fullMaskLen int
|
||||||
var initMaskOK = false
|
var initMaskOK = false
|
||||||
|
|
||||||
//todo: decompress mask on demand
|
// TODO: decompress mask on demand
|
||||||
func initMask() {
|
func initMask() {
|
||||||
if initMaskOK {
|
if initMaskOK {
|
||||||
return
|
return
|
||||||
|
@ -4,10 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/unlock-music/cli/algo/common"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/unlock-music/cli/algo/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -57,6 +58,7 @@ func (d *Decoder) Validate() error {
|
|||||||
if lenData < 1024 {
|
if lenData < 1024 {
|
||||||
return ErrKwFileSize
|
return ErrKwFileSize
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(magicHeader, d.file[:16]) {
|
if !bytes.Equal(magicHeader, d.file[:16]) {
|
||||||
return ErrKwMagicHeader
|
return ErrKwMagicHeader
|
||||||
}
|
}
|
||||||
@ -69,9 +71,11 @@ func generateMask(key []byte) []byte {
|
|||||||
keyStr := strconv.FormatUint(keyInt, 10)
|
keyStr := strconv.FormatUint(keyInt, 10)
|
||||||
keyStrTrim := padOrTruncate(keyStr, 32)
|
keyStrTrim := padOrTruncate(keyStr, 32)
|
||||||
mask := make([]byte, 32)
|
mask := make([]byte, 32)
|
||||||
|
|
||||||
for i := 0; i < 32; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
mask[i] = keyPreDefined[i] ^ keyStrTrim[i]
|
mask[i] = keyPreDefined[i] ^ keyStrTrim[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
return mask
|
return mask
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,13 +87,13 @@ func (d *Decoder) parseBitrateAndType() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
d.bitrate, err = strconv.Atoi(bitType[:charPos])
|
d.bitrate, err = strconv.Atoi(bitType[:charPos])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.bitrate = 0
|
d.bitrate = 0
|
||||||
}
|
}
|
||||||
d.outputExt = strings.ToLower(bitType[charPos:])
|
d.outputExt = strings.ToLower(bitType[charPos:])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Decoder) Decode() error {
|
func (d *Decoder) Decode() error {
|
||||||
@ -102,6 +106,7 @@ func (d *Decoder) Decode() error {
|
|||||||
for i := 0; i < dataLen; i++ {
|
for i := 0; i < dataLen; i++ {
|
||||||
d.audio[i] ^= d.mask[i&0x1F] //equals: [i % 32]
|
d.audio[i] ^= d.mask[i&0x1F] //equals: [i % 32]
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package ncm
|
package ncm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/unlock-music/cli/algo/common"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/unlock-music/cli/algo/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RawMeta interface {
|
type RawMeta interface {
|
||||||
@ -10,6 +11,7 @@ type RawMeta interface {
|
|||||||
GetFormat() string
|
GetFormat() string
|
||||||
GetAlbumImageURL() string
|
GetAlbumImageURL() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawMetaMusic struct {
|
type RawMetaMusic struct {
|
||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
MusicID int `json:"musicId"`
|
MusicID int `json:"musicId"`
|
||||||
@ -30,11 +32,11 @@ type RawMetaMusic struct {
|
|||||||
func (m RawMetaMusic) GetAlbumImageURL() string {
|
func (m RawMetaMusic) GetAlbumImageURL() string {
|
||||||
return m.AlbumPic
|
return m.AlbumPic
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m RawMetaMusic) GetArtists() (artists []string) {
|
func (m RawMetaMusic) GetArtists() (artists []string) {
|
||||||
for _, artist := range m.Artist {
|
for _, artist := range m.Artist {
|
||||||
for _, item := range artist {
|
for _, item := range artist {
|
||||||
name, ok := item.(string)
|
if name, ok := item.(string); ok {
|
||||||
if ok {
|
|
||||||
artists = append(artists, name)
|
artists = append(artists, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,6 +51,7 @@ func (m RawMetaMusic) GetTitle() string {
|
|||||||
func (m RawMetaMusic) GetAlbum() string {
|
func (m RawMetaMusic) GetAlbum() string {
|
||||||
return m.Album
|
return m.Album
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m RawMetaMusic) GetFormat() string {
|
func (m RawMetaMusic) GetFormat() string {
|
||||||
return m.Format
|
return m.Format
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,14 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/unlock-music/cli/algo/common"
|
"github.com/unlock-music/cli/algo/common"
|
||||||
"github.com/unlock-music/cli/internal/logging"
|
"github.com/unlock-music/cli/internal/logging"
|
||||||
"github.com/unlock-music/cli/internal/utils"
|
"github.com/unlock-music/cli/internal/utils"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -65,6 +66,7 @@ func (d *Decoder) readKeyData() error {
|
|||||||
if d.offsetKey == 0 || d.offsetKey+4 > d.fileLen {
|
if d.offsetKey == 0 || d.offsetKey+4 > d.fileLen {
|
||||||
return errors.New("invalid cover file offset")
|
return errors.New("invalid cover file offset")
|
||||||
}
|
}
|
||||||
|
|
||||||
bKeyLen := d.file[d.offsetKey : d.offsetKey+4]
|
bKeyLen := d.file[d.offsetKey : d.offsetKey+4]
|
||||||
iKeyLen := binary.LittleEndian.Uint32(bKeyLen)
|
iKeyLen := binary.LittleEndian.Uint32(bKeyLen)
|
||||||
d.offsetMeta = d.offsetKey + 4 + iKeyLen
|
d.offsetMeta = d.offsetKey + 4 + iKeyLen
|
||||||
@ -82,9 +84,11 @@ func (d *Decoder) readMetaData() error {
|
|||||||
if d.offsetMeta == 0 || d.offsetMeta+4 > d.fileLen {
|
if d.offsetMeta == 0 || d.offsetMeta+4 > d.fileLen {
|
||||||
return errors.New("invalid meta file offset")
|
return errors.New("invalid meta file offset")
|
||||||
}
|
}
|
||||||
|
|
||||||
bMetaLen := d.file[d.offsetMeta : d.offsetMeta+4]
|
bMetaLen := d.file[d.offsetMeta : d.offsetMeta+4]
|
||||||
iMetaLen := binary.LittleEndian.Uint32(bMetaLen)
|
iMetaLen := binary.LittleEndian.Uint32(bMetaLen)
|
||||||
d.offsetCover = d.offsetMeta + 4 + iMetaLen
|
d.offsetCover = d.offsetMeta + 4 + iMetaLen
|
||||||
|
|
||||||
if iMetaLen == 0 {
|
if iMetaLen == 0 {
|
||||||
return errors.New("no any meta file found")
|
return errors.New("no any meta file found")
|
||||||
}
|
}
|
||||||
@ -159,7 +163,9 @@ func (d *Decoder) readCoverData() error {
|
|||||||
if iCoverLen == 0 {
|
if iCoverLen == 0 {
|
||||||
return errors.New("no any cover file found")
|
return errors.New("no any cover file found")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.cover = d.file[coverLenStart+4 : 4+coverLenStart+iCoverLen]
|
d.cover = d.file[coverLenStart+4 : 4+coverLenStart+iCoverLen]
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +173,7 @@ func (d *Decoder) readAudioData() error {
|
|||||||
if d.offsetAudio == 0 || d.offsetAudio > d.fileLen {
|
if d.offsetAudio == 0 || d.offsetAudio > d.fileLen {
|
||||||
return errors.New("invalid audio offset")
|
return errors.New("invalid audio offset")
|
||||||
}
|
}
|
||||||
|
|
||||||
audioRaw := d.file[d.offsetAudio:]
|
audioRaw := d.file[d.offsetAudio:]
|
||||||
audioLen := len(audioRaw)
|
audioLen := len(audioRaw)
|
||||||
d.audio = make([]byte, audioLen)
|
d.audio = make([]byte, audioLen)
|
||||||
@ -186,6 +193,7 @@ func (d *Decoder) Decode() error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
err = d.parseMeta()
|
err = d.parseMeta()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log().Warn("parse ncm meta file failed", zap.Error(err))
|
logging.Log().Warn("parse ncm meta file failed", zap.Error(err))
|
||||||
}
|
}
|
||||||
@ -215,27 +223,32 @@ func (d Decoder) GetCoverImage() []byte {
|
|||||||
if d.cover != nil {
|
if d.cover != nil {
|
||||||
return d.cover
|
return d.cover
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
imgURL := d.meta.GetAlbumImageURL()
|
imgURL := d.meta.GetAlbumImageURL()
|
||||||
if d.meta != nil && !strings.HasPrefix(imgURL, "http") {
|
if d.meta != nil && !strings.HasPrefix(imgURL, "http") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Get(imgURL)
|
resp, err := http.Get(imgURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log().Warn("download image failed", zap.Error(err), zap.String("url", imgURL))
|
logging.Log().Warn("download image failed", zap.Error(err), zap.String("url", imgURL))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
logging.Log().Warn("download image failed", zap.String("http", resp.Status),
|
logging.Log().Warn("download image failed", zap.String("http", resp.Status),
|
||||||
zap.String("url", imgURL))
|
zap.String("url", imgURL))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log().Warn("download image failed", zap.Error(err), zap.String("url", imgURL))
|
logging.Log().Warn("download image failed", zap.Error(err), zap.String("url", imgURL))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ func loadTestDataMapCipher(name string) ([]byte, []byte, []byte, error) {
|
|||||||
}
|
}
|
||||||
return key, raw, target, nil
|
return key, raw, target, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_mapCipher_Decrypt(t *testing.T) {
|
func Test_mapCipher_Decrypt(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A rc4Cipher is an instance of RC4 using a particular key.
|
// rc4Cipher is an instance of RC4 using a particular key.
|
||||||
type rc4Cipher struct {
|
type rc4Cipher struct {
|
||||||
box []byte
|
box []byte
|
||||||
key []byte
|
key []byte
|
||||||
@ -87,6 +87,7 @@ func (c *rc4Cipher) Decrypt(src []byte, offset int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for toProcess > rc4SegmentSize {
|
for toProcess > rc4SegmentSize {
|
||||||
c.encASegment(src[processed:processed+rc4SegmentSize], offset)
|
c.encASegment(src[processed:processed+rc4SegmentSize], offset)
|
||||||
markProcess(rc4SegmentSize)
|
markProcess(rc4SegmentSize)
|
||||||
@ -96,6 +97,7 @@ func (c *rc4Cipher) Decrypt(src []byte, offset int) {
|
|||||||
c.encASegment(src[processed:], offset)
|
c.encASegment(src[processed:], offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rc4Cipher) encFirstSegment(buf []byte, offset int) {
|
func (c *rc4Cipher) encFirstSegment(buf []byte, offset int) {
|
||||||
for i := 0; i < len(buf); i++ {
|
for i := 0; i < len(buf); i++ {
|
||||||
buf[i] ^= c.key[c.getSegmentSkip(offset+i)]
|
buf[i] ^= c.key[c.getSegmentSkip(offset+i)]
|
||||||
@ -117,6 +119,7 @@ func (c *rc4Cipher) encASegment(buf []byte, offset int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rc4Cipher) getSegmentSkip(id int) int {
|
func (c *rc4Cipher) getSegmentSkip(id int) int {
|
||||||
seed := int(c.key[id%c.n])
|
seed := int(c.key[id%c.n])
|
||||||
idx := int64(float64(c.hash) / float64((id+1)*seed) * 100.0)
|
idx := int64(float64(c.hash) / float64((id+1)*seed) * 100.0)
|
||||||
|
@ -13,6 +13,7 @@ func (c *staticCipher) Decrypt(buf []byte, offset int) {
|
|||||||
buf[i] ^= c.getMask(offset + i)
|
buf[i] ^= c.getMask(offset + i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *staticCipher) getMask(offset int) byte {
|
func (c *staticCipher) getMask(offset int) byte {
|
||||||
if offset > 0x7FFF {
|
if offset > 0x7FFF {
|
||||||
offset %= 0x7FFF
|
offset %= 0x7FFF
|
||||||
|
@ -16,17 +16,19 @@ func simpleMakeKey(salt byte, length int) []byte {
|
|||||||
}
|
}
|
||||||
return keyBuf
|
return keyBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecryptKey(rawKey []byte) ([]byte, error) {
|
func DecryptKey(rawKey []byte) ([]byte, error) {
|
||||||
rawKeyDec := make([]byte, base64.StdEncoding.DecodedLen(len(rawKey)))
|
rawKeyDec := make([]byte, base64.StdEncoding.DecodedLen(len(rawKey)))
|
||||||
n, err := base64.StdEncoding.Decode(rawKeyDec, rawKey)
|
n, err := base64.StdEncoding.Decode(rawKeyDec, rawKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if n < 16 {
|
if n < 16 {
|
||||||
return nil, errors.New("key length is too short")
|
return nil, errors.New("key length is too short")
|
||||||
}
|
}
|
||||||
rawKeyDec = rawKeyDec[:n]
|
|
||||||
|
|
||||||
|
rawKeyDec = rawKeyDec[:n]
|
||||||
simpleKey := simpleMakeKey(106, 8)
|
simpleKey := simpleMakeKey(106, 8)
|
||||||
teaKey := make([]byte, 16)
|
teaKey := make([]byte, 16)
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
@ -38,14 +40,17 @@ func DecryptKey(rawKey []byte) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(rawKeyDec[:8], rs...), nil
|
return append(rawKeyDec[:8], rs...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
|
func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
|
||||||
const saltLen = 2
|
const saltLen = 2
|
||||||
const zeroLen = 7
|
const zeroLen = 7
|
||||||
if len(inBuf)%8 != 0 {
|
if len(inBuf)%8 != 0 {
|
||||||
return nil, errors.New("inBuf size not a multiple of the block size")
|
return nil, errors.New("inBuf size not a multiple of the block size")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(inBuf) < 16 {
|
if len(inBuf) < 16 {
|
||||||
return nil, errors.New("inBuf size too small")
|
return nil, errors.New("inBuf size too small")
|
||||||
}
|
}
|
||||||
@ -62,8 +67,8 @@ func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
|
|||||||
if padLen+saltLen != 8 {
|
if padLen+saltLen != 8 {
|
||||||
return nil, errors.New("invalid pad len")
|
return nil, errors.New("invalid pad len")
|
||||||
}
|
}
|
||||||
out := make([]byte, outLen)
|
|
||||||
|
|
||||||
|
out := make([]byte, outLen)
|
||||||
ivPrev := make([]byte, 8)
|
ivPrev := make([]byte, 8)
|
||||||
ivCur := inBuf[:8]
|
ivCur := inBuf[:8]
|
||||||
|
|
||||||
@ -80,6 +85,7 @@ func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
|
|||||||
inBufPos += 8
|
inBufPos += 8
|
||||||
destIdx = 0
|
destIdx = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 1; i <= saltLen; {
|
for i := 1; i <= saltLen; {
|
||||||
if destIdx < 8 {
|
if destIdx < 8 {
|
||||||
destIdx++
|
destIdx++
|
||||||
@ -108,6 +114,7 @@ func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
|
|||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func xor8Bytes(dst, a, b []byte) {
|
func xor8Bytes(dst, a, b []byte) {
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
dst[i] = a[i] ^ b[i]
|
dst[i] = a[i] ^ b[i]
|
||||||
|
@ -15,6 +15,7 @@ func TestSimpleMakeKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDecryptKeyData(name string) ([]byte, []byte, error) {
|
func loadDecryptKeyData(name string) ([]byte, []byte, error) {
|
||||||
keyRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s_key_raw.bin", name))
|
keyRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s_key_raw.bin", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -26,6 +27,7 @@ func loadDecryptKeyData(name string) ([]byte, []byte, error) {
|
|||||||
}
|
}
|
||||||
return keyRaw, keyDec, nil
|
return keyRaw, keyDec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecryptKey(t *testing.T) {
|
func TestDecryptKey(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -37,6 +39,7 @@ func TestDecryptKey(t *testing.T) {
|
|||||||
{"mflac_rc4(256)", "mflac_rc4", false},
|
{"mflac_rc4(256)", "mflac_rc4", false},
|
||||||
{"mgg_map(256)", "mgg_map", false},
|
{"mgg_map(256)", "mgg_map", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
raw, want, err := loadDecryptKeyData(tt.filename)
|
raw, want, err := loadDecryptKeyData(tt.filename)
|
||||||
|
@ -100,10 +100,12 @@ func (d *Decoder) searchKey() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := io.ReadAll(io.LimitReader(d.r, 4))
|
buf, err := io.ReadAll(io.LimitReader(d.r, 4))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(buf) == "QTag" {
|
if string(buf) == "QTag" {
|
||||||
if err := d.readRawMetaQTag(); err != nil {
|
if err := d.readRawMetaQTag(); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -118,6 +120,7 @@ func (d *Decoder) searchKey() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,10 +149,12 @@ func (d *Decoder) readRawMetaQTag() error {
|
|||||||
if _, err := d.r.Seek(-8, io.SeekEnd); err != nil {
|
if _, err := d.r.Seek(-8, io.SeekEnd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := io.ReadAll(io.LimitReader(d.r, 4))
|
buf, err := io.ReadAll(io.LimitReader(d.r, 4))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rawMetaLen := int64(binary.BigEndian.Uint32(buf))
|
rawMetaLen := int64(binary.BigEndian.Uint32(buf))
|
||||||
|
|
||||||
// read raw meta data
|
// read raw meta data
|
||||||
@ -157,6 +162,7 @@ func (d *Decoder) readRawMetaQTag() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.audioLen = int(audioLen)
|
d.audioLen = int(audioLen)
|
||||||
rawMetaData, err := io.ReadAll(io.LimitReader(d.r, rawMetaLen))
|
rawMetaData, err := io.ReadAll(io.LimitReader(d.r, rawMetaLen))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -177,6 +183,7 @@ func (d *Decoder) readRawMetaQTag() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.rawMetaExtra2, err = strconv.Atoi(items[2])
|
d.rawMetaExtra2, err = strconv.Atoi(items[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -24,8 +24,8 @@ func loadTestDataQmcDecoder(filename string) ([]byte, []byte, error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return bytes.Join([][]byte{encBody, encSuffix}, nil), target, nil
|
return bytes.Join([][]byte{encBody, encSuffix}, nil), target, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMflac0Decoder_Read(t *testing.T) {
|
func TestMflac0Decoder_Read(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -60,7 +60,6 @@ func TestMflac0Decoder_Read(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMflac0Decoder_Validate(t *testing.T) {
|
func TestMflac0Decoder_Validate(t *testing.T) {
|
||||||
|
@ -3,6 +3,7 @@ package tm
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/unlock-music/cli/algo/common"
|
"github.com/unlock-music/cli/algo/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,5 +76,4 @@ func init() {
|
|||||||
// QQ Music IOS Mp3
|
// QQ Music IOS Mp3
|
||||||
common.RegisterDecoder("tm0", false, common.NewRawDecoder)
|
common.RegisterDecoder("tm0", false, common.NewRawDecoder)
|
||||||
common.RegisterDecoder("tm3", false, common.NewRawDecoder)
|
common.RegisterDecoder("tm3", false, common.NewRawDecoder)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package xm
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/unlock-music/cli/algo/common"
|
"github.com/unlock-music/cli/algo/common"
|
||||||
"github.com/unlock-music/cli/internal/logging"
|
"github.com/unlock-music/cli/internal/logging"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -58,6 +59,7 @@ func (d *Decoder) Validate() error {
|
|||||||
if lenData < 16 {
|
if lenData < 16 {
|
||||||
return ErrFileSize
|
return ErrFileSize
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(magicHeader, d.file[:4]) ||
|
if !bytes.Equal(magicHeader, d.file[:4]) ||
|
||||||
!bytes.Equal(magicHeader2, d.file[8:12]) {
|
!bytes.Equal(magicHeader2, d.file[8:12]) {
|
||||||
return ErrMagicHeader
|
return ErrMagicHeader
|
||||||
@ -76,6 +78,7 @@ func (d *Decoder) Validate() error {
|
|||||||
if d.headerLen+16 > uint32(lenData) {
|
if d.headerLen+16 > uint32(lenData) {
|
||||||
return ErrFileSize
|
return ErrFileSize
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,17 +9,17 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/unlock-music/cli/algo/common"
|
||||||
|
"github.com/unlock-music/cli/internal/logging"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/unlock-music/cli/algo/common"
|
|
||||||
_ "github.com/unlock-music/cli/algo/kgm"
|
_ "github.com/unlock-music/cli/algo/kgm"
|
||||||
_ "github.com/unlock-music/cli/algo/kwm"
|
_ "github.com/unlock-music/cli/algo/kwm"
|
||||||
_ "github.com/unlock-music/cli/algo/ncm"
|
_ "github.com/unlock-music/cli/algo/ncm"
|
||||||
_ "github.com/unlock-music/cli/algo/qmc"
|
_ "github.com/unlock-music/cli/algo/qmc"
|
||||||
_ "github.com/unlock-music/cli/algo/tm"
|
_ "github.com/unlock-music/cli/algo/tm"
|
||||||
_ "github.com/unlock-music/cli/algo/xm"
|
_ "github.com/unlock-music/cli/algo/xm"
|
||||||
"github.com/unlock-music/cli/internal/logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppVersion = "v0.0.6"
|
var AppVersion = "v0.0.6"
|
||||||
@ -43,26 +43,30 @@ func main() {
|
|||||||
HideHelpCommand: true,
|
HideHelpCommand: true,
|
||||||
UsageText: "um [-o /path/to/output/dir] [--extra-flags] [-i] /path/to/input",
|
UsageText: "um [-o /path/to/output/dir] [--extra-flags] [-i] /path/to/input",
|
||||||
}
|
}
|
||||||
err := app.Run(os.Args)
|
|
||||||
if err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
logging.Log().Fatal("run app failed", zap.Error(err))
|
logging.Log().Fatal("run app failed", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printSupportedExtensions() {
|
func printSupportedExtensions() {
|
||||||
exts := []string{}
|
exts := []string{}
|
||||||
for ext := range common.DecoderRegistry {
|
for ext := range common.DecoderRegistry {
|
||||||
exts = append(exts, ext)
|
exts = append(exts, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(exts)
|
sort.Strings(exts)
|
||||||
for _, ext := range exts {
|
for _, ext := range exts {
|
||||||
fmt.Printf("%s: %d\n", ext, len(common.DecoderRegistry[ext]))
|
fmt.Printf("%s: %d\n", ext, len(common.DecoderRegistry[ext]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func appMain(c *cli.Context) (err error) {
|
func appMain(c *cli.Context) (err error) {
|
||||||
if c.Bool("supported-ext") {
|
if c.Bool("supported-ext") {
|
||||||
printSupportedExtensions()
|
printSupportedExtensions()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
input := c.String("input")
|
input := c.String("input")
|
||||||
if input == "" {
|
if input == "" {
|
||||||
switch c.Args().Len() {
|
switch c.Args().Len() {
|
||||||
@ -90,9 +94,6 @@ func appMain(c *cli.Context) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
skipNoop := c.Bool("skip-noop")
|
|
||||||
removeSource := c.Bool("remove-source")
|
|
||||||
|
|
||||||
inputStat, err := os.Stat(input)
|
inputStat, err := os.Stat(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -110,26 +111,32 @@ func appMain(c *cli.Context) (err error) {
|
|||||||
return errors.New("output should be a writable directory")
|
return errors.New("output should be a writable directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipNoop := c.Bool("skip-noop")
|
||||||
|
removeSource := c.Bool("remove-source")
|
||||||
|
|
||||||
if inputStat.IsDir() {
|
if inputStat.IsDir() {
|
||||||
return dealDirectory(input, output, skipNoop, removeSource)
|
return dealDirectory(input, output, skipNoop, removeSource)
|
||||||
} else {
|
|
||||||
allDec := common.GetDecoder(inputStat.Name(), skipNoop)
|
|
||||||
if len(allDec) == 0 {
|
|
||||||
logging.Log().Fatal("skipping while no suitable decoder")
|
|
||||||
}
|
|
||||||
return tryDecFile(input, output, allDec, removeSource)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allDec := common.GetDecoder(inputStat.Name(), skipNoop)
|
||||||
|
if len(allDec) == 0 {
|
||||||
|
logging.Log().Fatal("skipping while no suitable decoder")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryDecFile(input, output, allDec, removeSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dealDirectory(inputDir string, outputDir string, skipNoop bool, removeSource bool) error {
|
func dealDirectory(inputDir string, outputDir string, skipNoop bool, removeSource bool) error {
|
||||||
items, err := os.ReadDir(inputDir)
|
items, err := os.ReadDir(inputDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item.IsDir() {
|
if item.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
allDec := common.GetDecoder(item.Name(), skipNoop)
|
allDec := common.GetDecoder(item.Name(), skipNoop)
|
||||||
if len(allDec) == 0 {
|
if len(allDec) == 0 {
|
||||||
logging.Log().Info("skipping while no suitable decoder", zap.String("file", item.Name()))
|
logging.Log().Info("skipping while no suitable decoder", zap.String("file", item.Name()))
|
||||||
@ -141,6 +148,7 @@ func dealDirectory(inputDir string, outputDir string, skipNoop bool, removeSourc
|
|||||||
logging.Log().Error("conversion failed", zap.String("source", item.Name()))
|
logging.Log().Error("conversion failed", zap.String("source", item.Name()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,9 +167,11 @@ func tryDecFile(inputFile string, outputDir string, allDec []common.NewDecoderFu
|
|||||||
logging.Log().Warn("try decode failed", zap.Error(err))
|
logging.Log().Warn("try decode failed", zap.Error(err))
|
||||||
dec = nil
|
dec = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if dec == nil {
|
if dec == nil {
|
||||||
return errors.New("no any decoder can resolve the file")
|
return errors.New("no any decoder can resolve the file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dec.Decode(); err != nil {
|
if err := dec.Decode(); err != nil {
|
||||||
return errors.New("failed while decoding: " + err.Error())
|
return errors.New("failed while decoding: " + err.Error())
|
||||||
}
|
}
|
||||||
@ -178,21 +188,20 @@ func tryDecFile(inputFile string, outputDir string, allDec []common.NewDecoderFu
|
|||||||
filenameOnly := strings.TrimSuffix(filepath.Base(inputFile), filepath.Ext(inputFile))
|
filenameOnly := strings.TrimSuffix(filepath.Base(inputFile), filepath.Ext(inputFile))
|
||||||
|
|
||||||
outPath := filepath.Join(outputDir, filenameOnly+outExt)
|
outPath := filepath.Join(outputDir, filenameOnly+outExt)
|
||||||
err = os.WriteFile(outPath, outData, 0644)
|
if err = os.WriteFile(outPath, outData, 0644); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if source file need to be removed
|
// if source file need to be removed
|
||||||
if removeSource {
|
if !removeSource {
|
||||||
err := os.RemoveAll(inputFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logging.Log().Info("successfully converted, and source file is removed", zap.String("source", inputFile), zap.String("destination", outPath))
|
|
||||||
} else {
|
|
||||||
logging.Log().Info("successfully converted", zap.String("source", inputFile), zap.String("destination", outPath))
|
logging.Log().Info("successfully converted", zap.String("source", inputFile), zap.String("destination", outPath))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(inputFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logging.Log().Info("successfully converted, and source file is removed", zap.String("source", inputFile), zap.String("destination", outPath))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user