Add QMC Decoder
This commit is contained in:
parent
db85f60023
commit
fe5403cdb7
72
algo/qmc/consts.go
Normal file
72
algo/qmc/consts.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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}
|
||||||
|
headerFlac = []byte{'f', 'L', 'a', 'C'}
|
||||||
|
headerOgg = []byte{'O', 'g', 'g', 'S'}
|
||||||
|
)
|
||||||
|
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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
211
algo/qmc/mask_key256.go
Normal file
211
algo/qmc/mask_key256.go
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package qmc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/umlock-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() (mask44 []byte, err 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q.matrix[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 bytes.Equal(headerFlac, q.Decrypt(input[:len(headerFlac)])) {
|
||||||
|
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%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 bytes.Equal(headerOgg, q.Decrypt(input[:len(headerOgg)])) {
|
||||||
|
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
|
||||||
|
}
|
97
algo/qmc/qmc.go
Normal file
97
algo/qmc/qmc.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package qmc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"github.com/umlock-music/cli/algo/common"
|
||||||
|
"github.com/umlock-music/cli/internal/logging"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 Decoder struct {
|
||||||
|
file []byte
|
||||||
|
maskDetector func(encodedData []byte) (*Key256Mask, error)
|
||||||
|
mask *Key256Mask
|
||||||
|
audioExt string
|
||||||
|
key []byte
|
||||||
|
audio []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultDecoder(data []byte) *Decoder {
|
||||||
|
return &Decoder{file: data, mask: getDefaultMask()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMflac256Decoder(data []byte) *Decoder {
|
||||||
|
return &Decoder{file: data, maskDetector: detectMflac256Mask, audioExt: "flac"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMgg256Decoder(data []byte) *Decoder {
|
||||||
|
return &Decoder{file: data, maskDetector: detectMgg256Mask, audioExt: "ogg"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) Validate() bool {
|
||||||
|
if nil != d.mask {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if nil != d.maskDetector {
|
||||||
|
if err := d.validateKey(); err != nil {
|
||||||
|
logging.Log().Error("detect file failed", zap.Error(err))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
d.mask, _ = d.maskDetector(d.file)
|
||||||
|
}
|
||||||
|
return d.mask != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) 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 *Decoder) Decode() error {
|
||||||
|
d.audio = d.mask.Decrypt(d.file)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) GetCoverImage() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) GetAudioData() []byte {
|
||||||
|
return d.audio
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) GetAudioExt() string {
|
||||||
|
return d.audioExt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Decoder) GetMeta() common.Meta {
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user