From 2941a9ac769966ba6ef6b05f5033c75f5a6079c3 Mon Sep 17 00:00:00 2001 From: awalol Date: Tue, 13 Feb 2024 02:41:42 +0800 Subject: [PATCH] refactor: qmc musicex footer parser --- algo/qmc/key_mmkv.go | 16 ++--------- algo/qmc/qmc.go | 34 ++++++++--------------- algo/qmc/qmc_footer.go | 63 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 algo/qmc/qmc_footer.go diff --git a/algo/qmc/key_mmkv.go b/algo/qmc/key_mmkv.go index 64cb1b5..88b1ef6 100644 --- a/algo/qmc/key_mmkv.go +++ b/algo/qmc/key_mmkv.go @@ -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) } diff --git a/algo/qmc/qmc.go b/algo/qmc/qmc.go index f991127..3bbf798 100644 --- a/algo/qmc/qmc.go +++ b/algo/qmc/qmc.go @@ -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) - 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 - } - } - + audioLen, err := d.footer.Read(d.raw) + if err != nil { + return err } - 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) diff --git a/algo/qmc/qmc_footer.go b/algo/qmc/qmc_footer.go new file mode 100644 index 0000000..1714175 --- /dev/null +++ b/algo/qmc/qmc_footer.go @@ -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 +}