refactor: qmc musicex footer parser

This commit is contained in:
awalol 2024-02-13 02:41:42 +08:00
parent 1835f9852a
commit 2941a9ac76
3 changed files with 78 additions and 35 deletions

View File

@ -1,11 +1,9 @@
package qmc
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
@ -99,21 +97,13 @@ func OpenMMKV(vaultPath string, vaultKey string, logger *zap.Logger) error {
return nil
}
func readKeyFromMMKVCustom(d *Decoder) ([]byte, error) {
func readKeyFromMMKVCustom(mid string) ([]byte, error) {
if streamKeyVault == nil {
return nil, fmt.Errorf("mmkv vault not loaded")
}
// 获取mid即数据库键值
_, err := d.raw.Seek(-128, io.SeekEnd)
if err != nil {
return nil, fmt.Errorf("get mid error: %w", err)
}
mid, err := io.ReadAll(io.LimitReader(d.raw, 64)) // 取64字节确保完全取完
mid = bytes.ReplaceAll(mid, []byte{0x00}, []byte{}) // clean NUL
mid = bytes.Trim(mid, "\0000") // maybe a little stupid
// 从数据库获取eKey
eKey, err := streamKeyVault.GetBytes(string(mid))
// get ekey from mmkv vault
eKey, err := streamKeyVault.GetBytes(mid)
if err != nil {
return nil, fmt.Errorf("get eKey error: %w", err)
}

View File

@ -5,13 +5,12 @@ import (
"encoding/binary"
"errors"
"fmt"
"go.uber.org/zap"
"io"
"runtime"
"strconv"
"strings"
"go.uber.org/zap"
"unlock-music.dev/cli/algo/common"
"unlock-music.dev/cli/internal/sniff"
)
@ -41,6 +40,8 @@ type Decoder struct {
// provider
logger *zap.Logger
footer qqMusicTagMusicEx
}
// Read implements io.Reader, offer the decrypted audio data.
@ -146,27 +147,16 @@ func (d *Decoder) searchKey() (err error) {
case "STag":
return errors.New("qmc: file with 'STag' suffix doesn't contains media key")
case "cex\x00":
d.decodedKey, err = readKeyFromMMKVCustom(d)
if err == nil {
suffix := []byte{0x63, 0x65, 0x78, 0x00} // cex
for i := 0; i <= 3; i++ {
// 末尾的信息数据每192字节出现一次故只要循环判断末尾不为musicex时即为歌曲数据
musicexLen, err := d.raw.Seek(int64(-(192*i)-4), io.SeekEnd)
audioLen, err := d.footer.Read(d.raw)
if err != nil {
return fmt.Errorf("get musicexLen error: %w", err)
}
buf, err := io.ReadAll(io.LimitReader(d.raw, 4))
if err != nil {
return fmt.Errorf("get musicex error: %w", err)
}
if !bytes.Equal(buf, suffix) {
d.audioLen = int(musicexLen) + 4
return nil
}
}
}
return err
}
d.audioLen = int(audioLen)
d.decodedKey, err = readKeyFromMMKVCustom(d.footer.mediafile)
if err != nil {
return err
}
return nil
default:
size := binary.LittleEndian.Uint32(suffixBuf)

63
algo/qmc/qmc_footer.go Normal file
View File

@ -0,0 +1,63 @@
package qmc
import (
"encoding/binary"
"fmt"
"io"
)
type qqMusicTagMusicEx struct {
songid uint32 // Song ID
unknown_1 uint32 // unused & unknown
unknown_2 uint32 // unused & unknown
mid string // Media ID
mediafile string // real file name
unknown_3 uint32 // unused; uninitialized memory?
sizeof_struct uint32 // 19.57: fixed value: 0xC0
version uint32 // 19.57: fixed value: 0x01
tag_magic []byte // fixed value "musicex\0" (8 bytes)
}
func (tag *qqMusicTagMusicEx) Read(raw io.ReadSeeker) (int64, error) {
_, err := raw.Seek(-16, io.SeekEnd)
if err != nil {
return 0, fmt.Errorf("musicex seek error: %w", err)
}
footerBuf := make([]byte, 4)
footerBuf, err = io.ReadAll(io.LimitReader(raw, 4))
if err != nil {
return 0, fmt.Errorf("get musicex error: %w", err)
}
footerLen := int64(binary.LittleEndian.Uint32(footerBuf))
audioLen, err := raw.Seek(-footerLen, io.SeekEnd)
buf, err := io.ReadAll(io.LimitReader(raw, audioLen))
if err != nil {
return 0, err
}
tag.songid = binary.LittleEndian.Uint32(buf[0:4])
tag.unknown_1 = binary.LittleEndian.Uint32(buf[4:8])
tag.unknown_2 = binary.LittleEndian.Uint32(buf[8:12])
for i := 0; i < 30; i++ {
u := binary.LittleEndian.Uint16(buf[12+i*2 : 12+(i+1)*2])
if u != 0 {
tag.mid += string(u)
}
}
for i := 0; i < 50; i++ {
u := binary.LittleEndian.Uint16(buf[72+i*2 : 72+(i+1)*2])
if u != 0 {
tag.mediafile += string(u)
}
}
tag.unknown_3 = binary.LittleEndian.Uint32(buf[173:177])
tag.sizeof_struct = binary.LittleEndian.Uint32(buf[177:181])
tag.version = binary.LittleEndian.Uint32(buf[181:185])
tag.tag_magic = buf[185:193]
return audioLen, nil
}