feat(QMCv2): replace with old decoder
parent
7755d47f7a
commit
deaa58e91f
@ -1,70 +0,0 @@
|
|||||||
package qmc
|
|
||||||
|
|
||||||
var oggPublicHeader1 = []byte{
|
|
||||||
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}
|
|
||||||
|
|
||||||
var oggPublicHeader2 = []byte{
|
|
||||||
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}
|
|
||||||
|
|
||||||
var oggPublicConfidence1 = []uint{
|
|
||||||
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}
|
|
||||||
|
|
||||||
var oggPublicConfidence2 = []uint{
|
|
||||||
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}
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultKey256Mask44 = []byte{
|
|
||||||
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}
|
|
||||||
)
|
|
||||||
var key256MappingAll [][]int //[idx256][idx128]idx44
|
|
||||||
var key256Mapping128to44 map[int]int
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
{ // init all mapping
|
|
||||||
key256MappingAll = make([][]int, 256)
|
|
||||||
for i := 0; i < 128; i++ {
|
|
||||||
realIdx := (i*i + 27) % 256
|
|
||||||
if key256MappingAll[realIdx] == nil {
|
|
||||||
key256MappingAll[realIdx] = []int{i}
|
|
||||||
} else {
|
|
||||||
key256MappingAll[realIdx] = append(key256MappingAll[realIdx], i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ // init
|
|
||||||
key256Mapping128to44 = make(map[int]int, 128)
|
|
||||||
idx44 := 0
|
|
||||||
for _, all128 := range key256MappingAll {
|
|
||||||
if all128 != nil {
|
|
||||||
for _, _i128 := range all128 {
|
|
||||||
key256Mapping128to44[_i128] = idx44
|
|
||||||
}
|
|
||||||
idx44++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
package qmc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"github.com/unlock-music/cli/algo/common"
|
|
||||||
"github.com/unlock-music/cli/internal/logging"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrFailToMatchMask = errors.New("can not match at least one key")
|
|
||||||
ErrTestDataLength = errors.New("invalid length of test file")
|
|
||||||
ErrMaskLength128 = errors.New("incorrect mask length 128")
|
|
||||||
ErrMaskLength44 = errors.New("incorrect mask length 44")
|
|
||||||
ErrMaskDecode = errors.New("decode mask-128 to mask-58 failed")
|
|
||||||
ErrDetectFlacMask = errors.New("can not detect mflac mask")
|
|
||||||
ErrDetectMggMask = errors.New("can not detect mgg mask")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Key256Mask struct {
|
|
||||||
matrix []byte // Mask 128
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKey256FromMask128(mask128 []byte) (*Key256Mask, error) {
|
|
||||||
if len(mask128) != 128 {
|
|
||||||
return nil, ErrMaskLength128
|
|
||||||
}
|
|
||||||
q := &Key256Mask{matrix: mask128}
|
|
||||||
return q, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKey256FromMask44(mask44 []byte) (*Key256Mask, error) {
|
|
||||||
mask128, err := convertKey256Mask44to128(mask44)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
q := &Key256Mask{matrix: mask128}
|
|
||||||
return q, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Key256Mask) GetMatrix44() ([]byte, error) {
|
|
||||||
if len(q.matrix) != 128 {
|
|
||||||
return nil, ErrMaskLength128
|
|
||||||
}
|
|
||||||
matrix44 := make([]byte, 44)
|
|
||||||
idx44 := 0
|
|
||||||
for _, it256 := range key256MappingAll {
|
|
||||||
if it256 != nil {
|
|
||||||
it256Len := len(it256)
|
|
||||||
for i := 1; i < it256Len; i++ {
|
|
||||||
if q.matrix[it256[0]] != q.matrix[it256[i]] {
|
|
||||||
return nil, ErrMaskDecode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matrix44[idx44] = q.matrix[it256[0]]
|
|
||||||
idx44++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matrix44, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Key256Mask) Decrypt(data []byte) []byte {
|
|
||||||
dst := make([]byte, len(data))
|
|
||||||
index := -1
|
|
||||||
maskIdx := -1
|
|
||||||
for cur := 0; cur < len(data); cur++ {
|
|
||||||
index++
|
|
||||||
maskIdx++
|
|
||||||
if index == 0x8000 || (index > 0x8000 && (index+1)%0x8000 == 0) {
|
|
||||||
index++
|
|
||||||
maskIdx++
|
|
||||||
}
|
|
||||||
if maskIdx >= 128 {
|
|
||||||
maskIdx -= 128
|
|
||||||
}
|
|
||||||
dst[cur] = data[cur] ^ q.matrix[maskIdx]
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertKey256Mask44to128(mask44 []byte) ([]byte, error) {
|
|
||||||
if len(mask44) != 44 {
|
|
||||||
return nil, ErrMaskLength44
|
|
||||||
}
|
|
||||||
mask128 := make([]byte, 128)
|
|
||||||
idx44 := 0
|
|
||||||
for _, it256 := range key256MappingAll {
|
|
||||||
if it256 != nil {
|
|
||||||
for _, idx128 := range it256 {
|
|
||||||
mask128[idx128] = mask44[idx44]
|
|
||||||
}
|
|
||||||
idx44++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mask128, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultMask() *Key256Mask {
|
|
||||||
y, _ := NewKey256FromMask44(defaultKey256Mask44)
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectMflac256Mask(input []byte) (*Key256Mask, error) {
|
|
||||||
var q *Key256Mask
|
|
||||||
var rtErr = ErrDetectFlacMask
|
|
||||||
|
|
||||||
lenData := len(input)
|
|
||||||
lenTest := 0x8000
|
|
||||||
if lenData < 0x8000 {
|
|
||||||
lenTest = lenData
|
|
||||||
}
|
|
||||||
|
|
||||||
for blockIdx := 0; blockIdx < lenTest; blockIdx += 128 {
|
|
||||||
var err error
|
|
||||||
q, err = NewKey256FromMask128(input[blockIdx : blockIdx+128])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if common.SnifferFLAC(q.Decrypt(input[:4])) {
|
|
||||||
rtErr = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return q, rtErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectMgg256Mask(input []byte) (*Key256Mask, error) {
|
|
||||||
if len(input) < 0x100 {
|
|
||||||
return nil, ErrTestDataLength
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixConf := make([]map[uint8]uint, 44) //meaning: [idx58][value]confidence
|
|
||||||
for i := uint(0); i < 44; i++ {
|
|
||||||
matrixConf[i] = make(map[uint8]uint)
|
|
||||||
}
|
|
||||||
|
|
||||||
page2size := input[0x54] ^ input[0xC] ^ oggPublicHeader1[0xC]
|
|
||||||
spHeader, spConf := generateOggFullHeader(int(page2size))
|
|
||||||
lenTest := len(spHeader)
|
|
||||||
|
|
||||||
for idx128 := 0; idx128 < lenTest; idx128++ {
|
|
||||||
confidence := spConf[idx128]
|
|
||||||
if confidence > 0 {
|
|
||||||
mask := input[idx128] ^ spHeader[idx128]
|
|
||||||
|
|
||||||
idx44 := key256Mapping128to44[idx128&0x7f] // equals: [idx128 % 128]
|
|
||||||
if _, ok2 := matrixConf[idx44][mask]; ok2 {
|
|
||||||
matrixConf[idx44][mask] += confidence
|
|
||||||
} else {
|
|
||||||
matrixConf[idx44][mask] = confidence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matrix := make([]uint8, 44)
|
|
||||||
var err error
|
|
||||||
for i := uint(0); i < 44; i++ {
|
|
||||||
matrix[i], err = decideMgg256MaskItemConf(matrixConf[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q, err := NewKey256FromMask44(matrix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if common.SnifferOGG(q.Decrypt(input[:4])) {
|
|
||||||
return q, nil
|
|
||||||
}
|
|
||||||
return nil, ErrDetectMggMask
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateOggFullHeader(pageSize int) ([]byte, []uint) {
|
|
||||||
spec := make([]byte, pageSize+1)
|
|
||||||
|
|
||||||
spec[0], spec[1], spec[pageSize] = uint8(pageSize), 0xFF, 0xFF
|
|
||||||
for i := 2; i < pageSize; i++ {
|
|
||||||
spec[i] = 0xFF
|
|
||||||
}
|
|
||||||
specConf := make([]uint, pageSize+1)
|
|
||||||
specConf[0], specConf[1], specConf[pageSize] = 6, 0, 0
|
|
||||||
for i := 2; i < pageSize; i++ {
|
|
||||||
specConf[i] = 4
|
|
||||||
}
|
|
||||||
allConf := append(oggPublicConfidence1, specConf...)
|
|
||||||
allConf = append(allConf, oggPublicConfidence2...)
|
|
||||||
|
|
||||||
allHeader := bytes.Join(
|
|
||||||
[][]byte{oggPublicHeader1, spec, oggPublicHeader2},
|
|
||||||
[]byte{},
|
|
||||||
)
|
|
||||||
return allHeader, allConf
|
|
||||||
}
|
|
||||||
|
|
||||||
func decideMgg256MaskItemConf(confidence map[uint8]uint) (uint8, error) {
|
|
||||||
lenConf := len(confidence)
|
|
||||||
if lenConf == 0 {
|
|
||||||
return 0xff, ErrFailToMatchMask
|
|
||||||
} else if lenConf > 1 {
|
|
||||||
logging.Log().Warn("there are 2 potential value for the mask", zap.Any("confidence", confidence))
|
|
||||||
}
|
|
||||||
result := uint8(0)
|
|
||||||
conf := uint(0)
|
|
||||||
for idx, item := range confidence {
|
|
||||||
if item > conf {
|
|
||||||
result = idx
|
|
||||||
conf = item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
@ -1,129 +0,0 @@
|
|||||||
package qmc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/unlock-music/cli/algo/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrQmcFileLength = errors.New("invalid qmc file length")
|
|
||||||
ErrQmcKeyDecodeFailed = errors.New("base64 decode qmc key failed")
|
|
||||||
ErrQmcKeyLength = errors.New("unexpected decoded qmc key length")
|
|
||||||
)
|
|
||||||
|
|
||||||
type OldDecoder struct {
|
|
||||||
file []byte
|
|
||||||
maskDetector func(encodedData []byte) (*Key256Mask, error)
|
|
||||||
mask *Key256Mask
|
|
||||||
audioExt string
|
|
||||||
key []byte
|
|
||||||
audio []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMflac256Decoder(data []byte) common.Decoder {
|
|
||||||
return &OldDecoder{file: data, maskDetector: detectMflac256Mask, audioExt: "flac"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMgg256Decoder(data []byte) common.Decoder {
|
|
||||||
return &OldDecoder{file: data, maskDetector: detectMgg256Mask, audioExt: "ogg"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *OldDecoder) Validate() error {
|
|
||||||
if nil != d.mask {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if nil != d.maskDetector {
|
|
||||||
if err := d.validateKey(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
d.mask, err = d.maskDetector(d.file)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return errors.New("no mask or mask detector found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *OldDecoder) validateKey() error {
|
|
||||||
lenData := len(d.file)
|
|
||||||
if lenData < 4 {
|
|
||||||
return ErrQmcFileLength
|
|
||||||
}
|
|
||||||
|
|
||||||
keyLen := binary.LittleEndian.Uint32(d.file[lenData-4:])
|
|
||||||
if lenData < int(keyLen+4) {
|
|
||||||
return ErrQmcFileLength
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
d.key, err = base64.StdEncoding.DecodeString(
|
|
||||||
string(d.file[lenData-4-int(keyLen) : lenData-4]))
|
|
||||||
if err != nil {
|
|
||||||
return ErrQmcKeyDecodeFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(d.key) != 272 {
|
|
||||||
return ErrQmcKeyLength
|
|
||||||
}
|
|
||||||
d.file = d.file[:lenData-4-int(keyLen)]
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *OldDecoder) Decode() error {
|
|
||||||
d.audio = d.mask.Decrypt(d.file)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d OldDecoder) GetCoverImage() []byte {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d OldDecoder) GetAudioData() []byte {
|
|
||||||
return d.audio
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d OldDecoder) GetAudioExt() string {
|
|
||||||
if d.audioExt != "" {
|
|
||||||
return "." + d.audioExt
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d OldDecoder) GetMeta() common.Meta {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecoderFuncWithExt(ext string) common.NewDecoderFunc {
|
|
||||||
return func(file []byte) common.Decoder {
|
|
||||||
return &OldDecoder{file: file, audioExt: ext, mask: getDefaultMask()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//goland:noinspection SpellCheckingInspection
|
|
||||||
func init() {
|
|
||||||
common.RegisterDecoder("qmc0", false, DecoderFuncWithExt("mp3")) //QQ Music Mp3
|
|
||||||
common.RegisterDecoder("qmc3", false, DecoderFuncWithExt("mp3")) //QQ Music Mp3
|
|
||||||
|
|
||||||
common.RegisterDecoder("qmc2", false, DecoderFuncWithExt("m4a")) //QQ Music M4A
|
|
||||||
common.RegisterDecoder("qmc4", false, DecoderFuncWithExt("m4a")) //QQ Music M4A
|
|
||||||
common.RegisterDecoder("qmc6", false, DecoderFuncWithExt("m4a")) //QQ Music M4A
|
|
||||||
common.RegisterDecoder("qmc8", false, DecoderFuncWithExt("m4a")) //QQ Music M4A
|
|
||||||
|
|
||||||
common.RegisterDecoder("qmcflac", false, DecoderFuncWithExt("flac")) //QQ Music Flac
|
|
||||||
common.RegisterDecoder("qmcogg", false, DecoderFuncWithExt("ogg")) //QQ Music Ogg
|
|
||||||
common.RegisterDecoder("tkm", false, DecoderFuncWithExt("m4a")) //QQ Music Accompaniment M4a
|
|
||||||
|
|
||||||
common.RegisterDecoder("bkcmp3", false, DecoderFuncWithExt("mp3")) //Moo Music Mp3
|
|
||||||
common.RegisterDecoder("bkcflac", false, DecoderFuncWithExt("flac")) //Moo Music Flac
|
|
||||||
|
|
||||||
common.RegisterDecoder("666c6163", false, DecoderFuncWithExt("flac")) //QQ Music Weiyun Flac
|
|
||||||
common.RegisterDecoder("6d7033", false, DecoderFuncWithExt("mp3")) //QQ Music Weiyun Mp3
|
|
||||||
common.RegisterDecoder("6f6767", false, DecoderFuncWithExt("ogg")) //QQ Music Weiyun Ogg
|
|
||||||
common.RegisterDecoder("6d3461", false, DecoderFuncWithExt("m4a")) //QQ Music Weiyun M4a
|
|
||||||
common.RegisterDecoder("776176", false, DecoderFuncWithExt("wav")) //QQ Music Weiyun Wav
|
|
||||||
|
|
||||||
common.RegisterDecoder("mgg", false, NewMgg256Decoder) //QQ Music New Ogg
|
|
||||||
common.RegisterDecoder("mflac", false, NewMflac256Decoder) //QQ Music New Flac
|
|
||||||
}
|
|
Loading…
Reference in New Issue