Add QMC Decoder

This commit is contained in:
Emmm Monster 2020-12-26 00:49:28 +08:00
parent db85f60023
commit fe5403cdb7
No known key found for this signature in database
GPG Key ID: C98279C83FB50DB9
3 changed files with 380 additions and 0 deletions

72
algo/qmc/consts.go Normal file
View 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
View 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
View 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
}