3 Commits

Author SHA1 Message Date
鲁树人
8c1f40bfe1 docs: update changelog 2025-09-15 21:48:20 +09:00
鲁树人
aea3bd5714 fix: Handle musicex tag correctly 2025-09-15 21:35:12 +09:00
鲁树人
e4bfefd0a6 fix: use proper regex 2025-09-09 22:32:43 +09:00
4 changed files with 35 additions and 15 deletions

View File

@@ -7,22 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [v0.2.16] - 2025-09-09 ## [v0.2.18] - 2025-09-15
### Changed
- Fix `musicex\0` tag parsing.
## [v0.2.17] - 2025-09-09 ⚠️ **(Broken Release)**
### Changed ### Changed
- Update RegEx used to extract UDID in plist. - Update RegEx used to extract UDID in plist.
## [v0.2.15] - 2025-09-09 ## [v0.2.16] - 2025-09-09 ⚠️ **(Broken Release)**
### Changed
- Update RegEx used to extract UDID in plist.
## [v0.2.15] - 2025-09-09 ⚠️ **(Broken Release)**
### Added ### Added
- Support MMKV dump in QQMusic Mac 10.x (AppStore version). - Support MMKV dump in QQMusic Mac 10.x (AppStore version).
## [v0.2.14] - 2025-09-08 ## [v0.2.14] - 2025-09-08 ⚠️ **(Broken Release)**
### Added ### Added
- Support MMKV dump in QQMusic Mac 10.x. - Support MMKV dump in QQMusic Mac 10.x.
## [v0.2.13] - 2025-09-06 ## [v0.2.13] - 2025-09-06 ⚠️ **(Broken Release)**
### Changed ### Changed
- Updated project namespace and repository URLs to new url - Updated project namespace and repository URLs to new url

View File

@@ -137,11 +137,11 @@ func (d *Decoder) searchKey() (err error) {
d.decodedKey, err = deriveKey([]byte(key)) d.decodedKey, err = deriveKey([]byte(key))
if err == nil { if err == nil {
d.audioLen = fileSize d.audioLen = fileSize
return nil } else {
}
d.decodedKey = nil d.decodedKey = nil
d.logger.Warn("could not derive key, skip", zap.Error(err)) d.logger.Warn("could not derive key, skip", zap.Error(err))
} }
}
suffixBuf := make([]byte, 4) suffixBuf := make([]byte, 4)
if _, err := io.ReadFull(d.raw, suffixBuf); err != nil { if _, err := io.ReadFull(d.raw, suffixBuf); err != nil {
@@ -153,7 +153,7 @@ func (d *Decoder) searchKey() (err error) {
return d.readRawMetaQTag() return d.readRawMetaQTag()
case "STag": case "STag":
return errors.New("qmc: file with 'STag' suffix doesn't contains media key") return errors.New("qmc: file with 'STag' suffix doesn't contains media key")
// MusicEx\0 // speculative guess for "musicex\0"
case "cex\x00": case "cex\x00":
footer, err := NewMusicExTag(d.raw) footer, err := NewMusicExTag(d.raw)
if err != nil { if err != nil {
@@ -161,17 +161,26 @@ func (d *Decoder) searchKey() (err error) {
} }
d.audioLen = fileSize - int(footer.TagSize) d.audioLen = fileSize - int(footer.TagSize)
if key, ok := d.params.CryptoParams.QmcKeys.Get(footer.MediaFileName); ok { if key, ok := d.params.CryptoParams.QmcKeys.Get(footer.MediaFileName); ok {
d.logger.Debug("searchKey: using key from MediaFileName", zap.String("MediaFileName", footer.MediaFileName), zap.String("key", key))
d.decodedKey, err = deriveKey([]byte(key)) d.decodedKey, err = deriveKey([]byte(key))
} else if d.decodedKey == nil {
return errors.New("searchKey: no key found for musicex tag")
} }
return err return err
default: default:
size := binary.LittleEndian.Uint32(suffixBuf) // if we already have a key from legacy mmkv, use it
if d.decodedKey != nil {
return nil
}
// try to use suffix as key length
size := binary.LittleEndian.Uint32(suffixBuf)
if size <= 0xFFFF && size != 0 { // assume size is key len if size <= 0xFFFF && size != 0 { // assume size is key len
return d.readRawKey(int64(size)) return d.readRawKey(int64(size))
} }
// try to use default static cipher // try to use default static cipher,
// or the key read from the legacy mmkv
d.audioLen = fileSize d.audioLen = fileSize
return nil return nil
} }

View File

@@ -42,24 +42,25 @@ func NewMusicExTag(f io.ReadSeeker) (*MusicExTagV1, error) {
tag := &MusicExTagV1{ tag := &MusicExTagV1{
TagSize: binary.LittleEndian.Uint32(buffer[0x00:0x04]), TagSize: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
TagVersion: binary.LittleEndian.Uint32(buffer[0x04:0x08]), TagVersion: binary.LittleEndian.Uint32(buffer[0x04:0x08]),
TagMagic: buffer[0x04:0x0C], TagMagic: buffer[0x08:],
} }
if !bytes.Equal(tag.TagMagic, []byte("musicex\x00")) { if !bytes.Equal(tag.TagMagic, []byte("musicex\x00")) {
return nil, errors.New("MusicEx magic mismatch") return nil, errors.New("MusicEx magic mismatch")
} }
if tag.TagVersion != 1 { if tag.TagVersion != 1 {
return nil, errors.New(fmt.Sprintf("unsupported musicex tag version. expecting 1, got %d", tag.TagVersion)) return nil, fmt.Errorf("unsupported musicex tag version. expecting 1, got %d", tag.TagVersion)
} }
if tag.TagSize < 0xC0 { if tag.TagSize < 0xC0 {
return nil, errors.New(fmt.Sprintf("unsupported musicex tag size. expecting at least 0xC0, got 0x%02x", tag.TagSize)) return nil, fmt.Errorf("unsupported musicex tag size. expecting at least 0xC0, got 0x%02x", tag.TagSize)
} }
buffer = make([]byte, tag.TagSize) buffer = make([]byte, tag.TagSize)
f.Seek(-int64(tag.TagSize), io.SeekEnd)
bytesRead, err = f.Read(buffer) bytesRead, err = f.Read(buffer)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("MusicExV1: Read error %w", err)
} }
if uint32(bytesRead) != tag.TagSize { if uint32(bytesRead) != tag.TagSize {
return nil, fmt.Errorf("MusicExV1: read %d bytes (expected %d)", bytesRead, tag.TagSize) return nil, fmt.Errorf("MusicExV1: read %d bytes (expected %d)", bytesRead, tag.TagSize)

View File

@@ -14,7 +14,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
var _RE_UDID_V10 = regexp.MustCompile(`_\x10\(([0-9a-f]{40}`) var _RE_UDID_V10 = regexp.MustCompile(`_\x10\(([0-9a-f]{40})`)
type QQMusicMacV10 struct { type QQMusicMacV10 struct {
logger *zap.Logger logger *zap.Logger