Compare commits

...

6 Commits

Author SHA1 Message Date
Jixun
43f96e5d89 Merge remote-tracking branch 'awalol/master' 2024-07-06 19:45:10 +01:00
3ae2ec34f4 一些修改 2024-02-14 16:59:05 +08:00
655b091744 refactor: qmc musicex footer parser 2024-02-13 02:41:42 +08:00
18eb369de8 修改 shuffixBuf 匹配方法 2024-02-13 01:17:52 +08:00
dc6f268d74 refactor: load mmkv on startup 2024-02-13 00:58:13 +08:00
b7b1240603 feat: mmkv 加密数据库解析 2024-02-05 08:41:40 +08:00
4 changed files with 122 additions and 7 deletions

View File

@ -80,6 +80,35 @@ func readKeyFromMMKV(file string, logger *zap.Logger) ([]byte, error) {
return deriveKey(buf)
}
func OpenMMKV(vaultPath string, vaultKey string, logger *zap.Logger) error {
filePath, fileName := filepath.Split(vaultPath)
mgr, err := mmkv.NewManager(filepath.Dir(filePath))
if err != nil {
return fmt.Errorf("init mmkv manager: %w", err)
}
streamKeyVault, err = mgr.OpenVaultCrypto(fileName, vaultKey)
if err != nil {
return fmt.Errorf("open mmkv vault: %w", err)
}
logger.Debug("mmkv vault opened", zap.Strings("keys", streamKeyVault.Keys()))
return nil
}
func readKeyFromMMKVCustom(mid string) ([]byte, error) {
if streamKeyVault == nil {
return nil, fmt.Errorf("mmkv vault not loaded")
}
// get ekey from mmkv vault
eKey, err := streamKeyVault.GetBytes(mid)
if err != nil {
return nil, fmt.Errorf("get eKey error: %w", err)
}
return deriveKey(eKey)
}
func getRelativeMMKVDir(file string) (string, error) {
mmkvDir := filepath.Join(filepath.Dir(file), "../mmkv")
if _, err := os.Stat(mmkvDir); err != nil {

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"
)
@ -145,6 +144,18 @@ func (d *Decoder) searchKey() (err error) {
return d.readRawMetaQTag()
case "STag":
return errors.New("qmc: file with 'STag' suffix doesn't contains media key")
case "cex\x00":
footer := qqMusicTagMusicEx{}
audioLen, err := footer.Read(d.raw)
if err != nil {
return err
}
d.audioLen = int(audioLen)
d.decodedKey, err = readKeyFromMMKVCustom(footer.mediafile)
if err != nil {
return err
}
return nil
default:
size := binary.LittleEndian.Uint32(suffixBuf)

View File

@ -0,0 +1,65 @@
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 {
break
}
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 {
break
}
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
}

View File

@ -5,6 +5,9 @@ import (
"context"
"errors"
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"io"
"os"
"os/signal"
@ -15,15 +18,11 @@ import (
"strings"
"time"
"github.com/fsnotify/fsnotify"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"unlock-music.dev/cli/algo/common"
_ "unlock-music.dev/cli/algo/kgm"
_ "unlock-music.dev/cli/algo/kwm"
_ "unlock-music.dev/cli/algo/ncm"
_ "unlock-music.dev/cli/algo/qmc"
"unlock-music.dev/cli/algo/qmc"
_ "unlock-music.dev/cli/algo/tm"
_ "unlock-music.dev/cli/algo/xiami"
_ "unlock-music.dev/cli/algo/ximalaya"
@ -50,6 +49,8 @@ func main() {
Flags: []cli.Flag{
&cli.StringFlag{Name: "input", Aliases: []string{"i"}, Usage: "path to input file or dir", Required: false},
&cli.StringFlag{Name: "output", Aliases: []string{"o"}, Usage: "path to output dir", Required: false},
&cli.StringFlag{Name: "vault-file", Aliases: []string{"db"}, Usage: "数据库文件位置 (请确保crc文件在同目录下)", Required: false},
&cli.StringFlag{Name: "vault-key", Aliases: []string{"key"}, Usage: "数据库密钥 (length 32)", Required: false},
&cli.BoolFlag{Name: "remove-source", Aliases: []string{"rs"}, Usage: "remove source file", Required: false, Value: false},
&cli.BoolFlag{Name: "skip-noop", Aliases: []string{"n"}, Usage: "skip noop decoder", Required: false, Value: true},
&cli.BoolFlag{Name: "update-metadata", Usage: "update metadata & album art from network", Required: false, Value: false},
@ -130,6 +131,15 @@ func appMain(c *cli.Context) (err error) {
return errors.New("output should be a writable directory")
}
vaultPath := c.String("vault-file")
vaultKey := c.String("vault-key")
if vaultPath != "" && vaultKey != "" {
err := qmc.OpenMMKV(vaultPath, vaultKey, logger)
if err != nil {
return err
}
}
proc := &processor{
outputDir: output,
skipNoopDecoder: c.Bool("skip-noop"),