mirror of
https://git.um-react.app/um/cli.git
synced 2025-11-28 11:43:02 +00:00
Compare commits
6 Commits
v0.2.14
...
8c1f40bfe1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c1f40bfe1 | ||
|
|
aea3bd5714 | ||
|
|
e4bfefd0a6 | ||
|
|
b1aa3bacdf | ||
|
|
6ec434f6b1 | ||
|
|
a46428e07c |
24
CHANGELOG.md
24
CHANGELOG.md
@@ -7,12 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [v0.2.14] - 2025-09-08
|
## [v0.2.18] - 2025-09-15
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Fix `musicex\0` tag parsing.
|
||||||
|
|
||||||
|
## [v0.2.17] - 2025-09-09 ⚠️ **(Broken Release)**
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Update RegEx used to extract UDID in plist.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
- Support MMKV dump in QQMusic Mac 10.x (AppStore version).
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
package qmc
|
package qmc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.um-react.app/um/cli/algo/common"
|
"git.um-react.app/um/cli/algo/common"
|
||||||
|
"git.um-react.app/um/cli/algo/qmc/qmmac"
|
||||||
|
"git.um-react.app/um/cli/internal/mmkv"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadMMKVOrDefault(path string, key string, logger *zap.Logger) (result common.QMCKeys, err error) {
|
func LoadMMKVOrDefault(path string, key string, logger *zap.Logger) (result common.QMCKeys, err error) {
|
||||||
key1, err := loadMacKeysV8(logger)
|
key1, err := qmmac.LoadMacKeysV8(logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
key1 = nil
|
key1 = nil
|
||||||
logger.Warn("LoadMMKVOrDefault: could not read QQMusic v8.8.0 keys", zap.Error(err))
|
logger.Warn("LoadMMKVOrDefault: could not read QQMusic v8.8.0 keys", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
key2, err := loadMacKeysV10(logger)
|
key2, err := qmmac.LoadMacKeysV10(logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
key2 = nil
|
key2 = nil
|
||||||
logger.Warn("LoadMMKVOrDefault: could not read QQMusic v10.x keys", zap.Error(err))
|
logger.Warn("LoadMMKVOrDefault: could not read QQMusic v10.x keys", zap.Error(err))
|
||||||
@@ -29,136 +23,16 @@ func LoadMMKVOrDefault(path string, key string, logger *zap.Logger) (result comm
|
|||||||
userKeys := make(common.QMCKeys)
|
userKeys := make(common.QMCKeys)
|
||||||
if path != "" {
|
if path != "" {
|
||||||
logger.Info("Using user mmkv")
|
logger.Info("Using user mmkv")
|
||||||
userKeys, err = LoadMMKV(path, key, logger)
|
userKeys, err = mmkv.LoadFromPath(path, key, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
userKeys = nil
|
userKeys = nil
|
||||||
logger.Warn("LoadMMKVOrDefault: could not read user keys", zap.Error(err))
|
logger.Warn("LoadMMKVOrDefault: could not read user keys", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allKeys := mergeMMKVKeys(key1, key2, userKeys)
|
allKeys := mmkv.Merge(key1, key2, userKeys)
|
||||||
|
|
||||||
logger.Debug("Keys loaded", zap.Any("keys", allKeys), zap.Int("len", len(allKeys)))
|
logger.Debug("Keys loaded", zap.Any("keys", allKeys), zap.Int("len", len(allKeys)))
|
||||||
|
|
||||||
return allKeys, nil
|
return allKeys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadMacKeysV8(logger *zap.Logger) (keys common.QMCKeys, err error) {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("Failed to get home dir")
|
|
||||||
return nil, fmt.Errorf("loadMacKeysV8: failed to get home: %w", err)
|
|
||||||
}
|
|
||||||
p := filepath.Join(
|
|
||||||
homeDir,
|
|
||||||
"Library/Containers/com.tencent.QQMusicMac/Data",
|
|
||||||
"Library/Application Support/QQMusicMac/mmkv",
|
|
||||||
"MMKVStreamEncryptId",
|
|
||||||
)
|
|
||||||
if f, err := os.Stat(p); err == nil && !f.IsDir() {
|
|
||||||
logger.Info("Using QQMusic 8.x mmkv", zap.String("mmkv", p))
|
|
||||||
return LoadMMKV(p, "", logger)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _RE_UDID = regexp.MustCompile(`_\x10\(([0-9a-f]{40})_`)
|
|
||||||
|
|
||||||
func extractUdid(data []byte) (string, error) {
|
|
||||||
match := _RE_UDID.FindSubmatch(data)
|
|
||||||
if len(match) != 2 {
|
|
||||||
return "", fmt.Errorf("extractUdid: could not find udid")
|
|
||||||
}
|
|
||||||
return string(match[1]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func caesar(text string, shift int) string {
|
|
||||||
var result strings.Builder
|
|
||||||
|
|
||||||
for _, char := range []byte(text) {
|
|
||||||
var transformed byte
|
|
||||||
if 'A' <= char && char <= 'Z' {
|
|
||||||
transformed = (char-'A'+byte(shift))%26 + 'A'
|
|
||||||
} else if 'a' <= char && char <= 'z' {
|
|
||||||
transformed = (char-'a'+byte(shift))%26 + 'a'
|
|
||||||
} else if '0' <= char && char <= '9' {
|
|
||||||
transformed = (char-'0'+byte(shift))%10 + '0'
|
|
||||||
} else {
|
|
||||||
transformed = char
|
|
||||||
}
|
|
||||||
result.WriteByte(transformed)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMmkv(udid string, id int) (name string, key string, err error) {
|
|
||||||
str1 := caesar(udid, id+3)
|
|
||||||
int1, err := strconv.ParseInt(udid[5:7], 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("getMmkv: could not parse udid: %w", err)
|
|
||||||
}
|
|
||||||
int2 := 5 + (int(int1)+id)%4
|
|
||||||
name = str1[:int2]
|
|
||||||
|
|
||||||
int3 := id + 0xa546
|
|
||||||
str3 := fmt.Sprintf("%s%04x", udid, int3)
|
|
||||||
hash := md5.Sum([]byte(str3))
|
|
||||||
key = fmt.Sprintf("%x", hash)[:16]
|
|
||||||
|
|
||||||
return name, key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadMacKeysV10(logger *zap.Logger) (common.QMCKeys, error) {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("Failed to get home dir")
|
|
||||||
return nil, fmt.Errorf("loadMacKeysV10: failed to get home: %w", err)
|
|
||||||
}
|
|
||||||
plist_path := filepath.Join(
|
|
||||||
homeDir,
|
|
||||||
"Library/Preferences/com.tencent.QQMusicMac.plist",
|
|
||||||
)
|
|
||||||
if f, err := os.Stat(plist_path); err != nil || f.IsDir() {
|
|
||||||
logger.Debug("loadMacKeysV10: plist not found", zap.String("plist", plist_path))
|
|
||||||
return nil, fmt.Errorf("loadMacKeysV10: plist not found")
|
|
||||||
}
|
|
||||||
plist_data, err := os.ReadFile(plist_path)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("loadMacKeysV10: could not read plist", zap.String("plist", plist_path), zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("loadMacKeysV10: could not read plist: %w", err)
|
|
||||||
}
|
|
||||||
udid, err := extractUdid(plist_data)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("loadMacKeysV10: could not extract udid", zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("loadMacKeysV10: could not extract udid: %w", err)
|
|
||||||
}
|
|
||||||
logger.Debug("loadMacKeysV10: read udid", zap.String("udid", udid))
|
|
||||||
|
|
||||||
mmkv_dir := filepath.Join(
|
|
||||||
homeDir,
|
|
||||||
"Library/Containers/com.tencent.QQMusicMac/Data/",
|
|
||||||
"Library/Application Support/QQMusicMac/iData",
|
|
||||||
)
|
|
||||||
if f, err := os.Stat(mmkv_dir); err != nil || !f.IsDir() {
|
|
||||||
logger.Debug("loadMacKeysV10: mmkv dir not found", zap.String("mmkv_dir", mmkv_dir))
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
mmkv_name, mmkv_key, err := getMmkv(udid, 1)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("loadMacKeysV10: could not get mmkv name/key", zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("loadMacKeysV10: could not get mmkv name/key: %w", err)
|
|
||||||
}
|
|
||||||
mmkv_path := filepath.Join(mmkv_dir, mmkv_name)
|
|
||||||
if f, err := os.Stat(mmkv_path); err != nil || f.IsDir() {
|
|
||||||
logger.Debug("loadMacKeysV10: mmkv file not found", zap.String("mmkv", mmkv_path))
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
logger.Info("Using QQMusic 10.x mmkv", zap.String("mmkv", mmkv_path))
|
|
||||||
keys, err := LoadMMKV(mmkv_path, mmkv_key, logger)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("loadMacKeysV10: could not load mmkv", zap.String("mmkv", mmkv_path), zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("loadMacKeysV10: could not load mmkv: %w", err)
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -137,10 +137,10 @@ 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.logger.Warn("could not derive key, skip", zap.Error(err))
|
||||||
}
|
}
|
||||||
d.decodedKey = nil
|
|
||||||
d.logger.Warn("could not derive key, skip", zap.Error(err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suffixBuf := make([]byte, 4)
|
suffixBuf := make([]byte, 4)
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
163
algo/qmc/qmmac/v10_darwin.go
Normal file
163
algo/qmc/qmmac/v10_darwin.go
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
package qmmac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.um-react.app/um/cli/algo/common"
|
||||||
|
"git.um-react.app/um/cli/internal/mmkv"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _RE_UDID_V10 = regexp.MustCompile(`_\x10\(([0-9a-f]{40})`)
|
||||||
|
|
||||||
|
type QQMusicMacV10 struct {
|
||||||
|
logger *zap.Logger
|
||||||
|
mmkv_dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QQMusicMacV10) extractUdids(data []byte) ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
for _, match := range _RE_UDID_V10.FindAllSubmatch(data, -1) {
|
||||||
|
udid := string(match[1])
|
||||||
|
q.logger.Debug("extractUdids: found udid", zap.String("udid", udid))
|
||||||
|
result = append(result, udid)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QQMusicMacV10) caesar(text string, shift int) string {
|
||||||
|
var result strings.Builder
|
||||||
|
|
||||||
|
for _, char := range []byte(text) {
|
||||||
|
var transformed byte
|
||||||
|
if 'A' <= char && char <= 'Z' {
|
||||||
|
transformed = (char-'A'+byte(shift))%26 + 'A'
|
||||||
|
} else if 'a' <= char && char <= 'z' {
|
||||||
|
transformed = (char-'a'+byte(shift))%26 + 'a'
|
||||||
|
} else if '0' <= char && char <= '9' {
|
||||||
|
transformed = (char-'0'+byte(shift))%10 + '0'
|
||||||
|
} else {
|
||||||
|
transformed = char
|
||||||
|
}
|
||||||
|
result.WriteByte(transformed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QQMusicMacV10) mmkv(udid string, id int) (path string, key string, err error) {
|
||||||
|
str1 := q.caesar(udid, id+3)
|
||||||
|
int1, err := strconv.ParseInt(udid[5:7], 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("getMmkv: could not parse udid: %w", err)
|
||||||
|
}
|
||||||
|
int2 := 5 + (int(int1)+id)%4
|
||||||
|
name := str1[:int2]
|
||||||
|
path = filepath.Join(q.mmkv_dir, name)
|
||||||
|
|
||||||
|
int3 := id + 0xa546
|
||||||
|
str3 := fmt.Sprintf("%s%04x", udid, int3)
|
||||||
|
hash := md5.Sum([]byte(str3))
|
||||||
|
key = fmt.Sprintf("%x", hash)[:16]
|
||||||
|
|
||||||
|
return path, key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QQMusicMacV10) loadByPList(plist_path string) ([]common.QMCKeys, error) {
|
||||||
|
logger := q.logger.With(zap.String("plist", plist_path))
|
||||||
|
logger.Debug("loadMacKeysV10: load key from plist")
|
||||||
|
if f, err := os.Stat(plist_path); err != nil || f.IsDir() {
|
||||||
|
logger.Debug("loadMacKeysV10: plist not found")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
plist_data, err := os.ReadFile(plist_path)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("loadMacKeysV10: could not read plist", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("loadMacKeysV10: could not read plist: %w", err)
|
||||||
|
}
|
||||||
|
udids, err := q.extractUdids(plist_data)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("loadMacKeysV10: could not extract udid", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("loadMacKeysV10: could not extract udid: %w", err)
|
||||||
|
}
|
||||||
|
logger.Debug("loadMacKeysV10: read udid", zap.Strings("udids", udids))
|
||||||
|
|
||||||
|
var keysList []common.QMCKeys
|
||||||
|
for _, udid := range udids {
|
||||||
|
keys, err := q.loadByUDID(udid, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("loadMacKeysV10: could not load by udid", zap.String("udid", udid), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keysList = append(keysList, keys)
|
||||||
|
}
|
||||||
|
return keysList, nil
|
||||||
|
}
|
||||||
|
func (q *QQMusicMacV10) loadByUDID(udid string, logger *zap.Logger) (common.QMCKeys, error) {
|
||||||
|
mmkv_path, mmkv_key, err := q.mmkv(udid, 1)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("loadMacKeysV10: could not get mmkv name/key", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("loadMacKeysV10: could not get mmkv name/key: %w", err)
|
||||||
|
}
|
||||||
|
logger.Info("Using QQMusic 10.x mmkv", zap.String("mmkv", mmkv_path))
|
||||||
|
keys, err := mmkv.LoadFromPath(mmkv_path, mmkv_key, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("loadMacKeysV10: could not load mmkv", zap.String("mmkv", mmkv_path), zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("loadMacKeysV10: could not load mmkv: %w", err)
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadMacKeysV10(logger *zap.Logger) (common.QMCKeys, error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to get home dir")
|
||||||
|
return nil, fmt.Errorf("loadMacKeysV10: failed to get home: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MMKV dir is always inside the sandbox container
|
||||||
|
mmkv_dir := filepath.Join(
|
||||||
|
homeDir,
|
||||||
|
"Library/Containers/com.tencent.QQMusicMac/Data/",
|
||||||
|
"Library/Application Support/QQMusicMac/iData",
|
||||||
|
)
|
||||||
|
if f, err := os.Stat(mmkv_dir); err != nil || !f.IsDir() {
|
||||||
|
logger.Debug("loadMacKeysV10: mmkv dir not found", zap.String("mmkv_dir", mmkv_dir))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// without sandbox
|
||||||
|
plist_without_sandbox := filepath.Join(
|
||||||
|
homeDir,
|
||||||
|
"Library/Preferences/com.tencent.QQMusicMac.plist",
|
||||||
|
)
|
||||||
|
|
||||||
|
// with sandbox (e.g. from App Store)
|
||||||
|
plist_sandboxed := filepath.Join(
|
||||||
|
homeDir,
|
||||||
|
"Library/Containers/com.tencent.QQMusicMac/Data/",
|
||||||
|
"Library/Preferences/com.tencent.QQMusicMac.plist",
|
||||||
|
)
|
||||||
|
|
||||||
|
q := QQMusicMacV10{
|
||||||
|
logger: logger,
|
||||||
|
mmkv_dir: mmkv_dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
keys1, err := q.loadByPList(plist_without_sandbox)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keys2, err := q.loadByPList(plist_sandboxed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mmkv.Merge(append(keys1, keys2...)...), nil
|
||||||
|
}
|
||||||
30
algo/qmc/qmmac/v8_darwin.go
Normal file
30
algo/qmc/qmmac/v8_darwin.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package qmmac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.um-react.app/um/cli/algo/common"
|
||||||
|
"git.um-react.app/um/cli/internal/mmkv"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadMacKeysV8(logger *zap.Logger) (keys common.QMCKeys, err error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to get home dir")
|
||||||
|
return nil, fmt.Errorf("loadMacKeysV8: failed to get home: %w", err)
|
||||||
|
}
|
||||||
|
p := filepath.Join(
|
||||||
|
homeDir,
|
||||||
|
"Library/Containers/com.tencent.QQMusicMac/Data",
|
||||||
|
"Library/Application Support/QQMusicMac/mmkv",
|
||||||
|
"MMKVStreamEncryptId",
|
||||||
|
)
|
||||||
|
if f, err := os.Stat(p); err == nil && !f.IsDir() {
|
||||||
|
logger.Info("Using QQMusic 8.x mmkv", zap.String("mmkv", p))
|
||||||
|
return mmkv.LoadFromPath(p, "", logger)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package qmc
|
package mmkv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mergeMMKVKeys(keys ...common.QMCKeys) common.QMCKeys {
|
func Merge(keys ...common.QMCKeys) common.QMCKeys {
|
||||||
result := make(common.QMCKeys)
|
result := make(common.QMCKeys)
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
for key, value := range k {
|
for key, value := range k {
|
||||||
@@ -20,7 +20,7 @@ func mergeMMKVKeys(keys ...common.QMCKeys) common.QMCKeys {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadMMKV(path string, key string, logger *zap.Logger) (result common.QMCKeys, err error) {
|
func LoadFromPath(path string, key string, logger *zap.Logger) (result common.QMCKeys, err error) {
|
||||||
mmkv_path := path
|
mmkv_path := path
|
||||||
mmkv_crc := path + ".crc"
|
mmkv_crc := path + ".crc"
|
||||||
|
|
||||||
Reference in New Issue
Block a user