From 52e986e644bfb6661aaf6e0a7d729dad95e18b8e Mon Sep 17 00:00:00 2001 From: Unlock Music Dev Date: Mon, 5 Dec 2022 00:06:38 +0800 Subject: [PATCH] feat(qmc): support .mflach on darwin --- algo/qmc/key_mmkv.go | 92 ++++++++++++++++++++++++++++++++++++++++++++ algo/qmc/qmc.go | 12 ++++-- 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 algo/qmc/key_mmkv.go diff --git a/algo/qmc/key_mmkv.go b/algo/qmc/key_mmkv.go new file mode 100644 index 0000000..b7f89d8 --- /dev/null +++ b/algo/qmc/key_mmkv.go @@ -0,0 +1,92 @@ +package qmc + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "unlock-music.dev/mmkv" +) + +var streamKeyVault mmkv.MMKV + +func readKeyFromMMKV(file string) ([]byte, error) { + if file == "" { + return nil, errors.New("file path is required while reading key from mmkv") + } + + //goland:noinspection GoBoolExpressions + if runtime.GOOS != "darwin" { + return nil, errors.New("mmkv vault not supported on this platform") + } + + if streamKeyVault == nil { + mmkvDir, err := getRelativeMMKVDir(file) + if err != nil { + mmkvDir, err = getDefaultMMKVDir() + if err != nil { + return nil, fmt.Errorf("mmkv key valut not found: %w", err) + } + } + mmkv.InitializeMMKV(mmkvDir) + streamKeyVault = mmkv.MMKVWithID("MMKVStreamEncryptId") + } + + buf := streamKeyVault.GetBytes(file) + if len(buf) == 0 { + _, partName := filepath.Split(file) + keys := streamKeyVault.AllKeys() + for _, key := range keys { + if strings.HasSuffix(key, partName) { + buf = streamKeyVault.GetBytes(key) + break + } + } + } + + if len(buf) == 0 { + return nil, errors.New("key not found in mmkv vault") + } + + return deriveKey(buf) +} + +func getRelativeMMKVDir(file string) (string, error) { + mmkvDir := filepath.Join(filepath.Dir(file), "../mmkv") + if _, err := os.Stat(mmkvDir); err != nil { + return "", fmt.Errorf("stat default mmkv dir: %w", err) + } + + keyFile := filepath.Join(mmkvDir, "MMKVStreamEncryptId") + if _, err := os.Stat(keyFile); err != nil { + return "", fmt.Errorf("stat default mmkv file: %w", err) + } + + return mmkvDir, nil +} + +func getDefaultMMKVDir() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("get user home dir: %w", err) + } + + mmkvDir := filepath.Join( + homeDir, + "Library/Containers/com.tencent.QQMusicMac/Data", // todo: make configurable + "Library/Application Support/QQMusicMac/mmkv", + ) + if _, err := os.Stat(mmkvDir); err != nil { + return "", fmt.Errorf("stat default mmkv dir: %w", err) + } + + keyFile := filepath.Join(mmkvDir, "MMKVStreamEncryptId") + if _, err := os.Stat(keyFile); err != nil { + return "", fmt.Errorf("stat default mmkv file: %w", err) + } + + return mmkvDir, nil +} diff --git a/algo/qmc/qmc.go b/algo/qmc/qmc.go index 96e56c7..987d01a 100644 --- a/algo/qmc/qmc.go +++ b/algo/qmc/qmc.go @@ -14,7 +14,8 @@ import ( ) type Decoder struct { - raw io.ReadSeeker // raw is the original file reader + raw io.ReadSeeker // raw is the original file reader + params *common.DecoderParams audio io.Reader // audio is the encrypted audio data audioLen int // audioLen is the audio data length @@ -39,7 +40,7 @@ func (d *Decoder) Read(p []byte) (int, error) { } func NewDecoder(p *common.DecoderParams) common.Decoder { - return &Decoder{raw: p.Reader} + return &Decoder{raw: p.Reader, params: p} } func (d *Decoder) Validate() error { @@ -97,7 +98,12 @@ func (d *Decoder) validateDecode() error { return nil } -func (d *Decoder) searchKey() error { +func (d *Decoder) searchKey() (err error) { + if d.params.Extension == ".mflach" { + d.decodedKey, err = readKeyFromMMKV(d.params.FilePath) + return err + } + fileSizeM4, err := d.raw.Seek(-4, io.SeekEnd) if err != nil { return err