From b7b1240603db36f40c6447e37537b743e11384c5 Mon Sep 17 00:00:00 2001 From: awalol Date: Mon, 5 Feb 2024 08:41:40 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20mmkv=20=E5=8A=A0=E5=AF=86=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- algo/qmc/key_mmkv.go | 44 +++++++++++++++++++++++++++++++++++++++++++- algo/qmc/qmc.go | 26 +++++++++++++++++++++++++- cmd/um/main.go | 12 ++++++++---- go.mod | 9 +++++---- go.sum | 8 ++++++++ 5 files changed, 89 insertions(+), 10 deletions(-) diff --git a/algo/qmc/key_mmkv.go b/algo/qmc/key_mmkv.go index 3dff9eb..72374fa 100644 --- a/algo/qmc/key_mmkv.go +++ b/algo/qmc/key_mmkv.go @@ -1,17 +1,20 @@ package qmc import ( + "bytes" + "encoding/base64" "errors" "fmt" + "io" "os" "path/filepath" "runtime" + "git.unlock-music.dev/awalol/go-mmkv" "github.com/samber/lo" "go.uber.org/zap" "golang.org/x/exp/slices" "golang.org/x/text/unicode/norm" - "unlock-music.dev/mmkv" ) var streamKeyVault mmkv.Vault @@ -80,6 +83,45 @@ func readKeyFromMMKV(file string, logger *zap.Logger) ([]byte, error) { return deriveKey(buf) } +func readKeyFromMMKVCustom(d *Decoder) ([]byte, error) { + logger := d.logger + filePath, fileName := filepath.Split(VaultPath) + + if streamKeyVault == nil { + mgr, err := mmkv.NewManager(filepath.Dir(filePath)) + if err != nil { + return nil, fmt.Errorf("init mmkv manager: %w", err) + } + + streamKeyVault, err = mgr.OpenVaultCrypto(fileName, VaultKey) + if err != nil { + return nil, fmt.Errorf("open mmkv vault: %w", err) + } + + logger.Debug("mmkv vault opened", zap.Strings("keys", streamKeyVault.Keys())) + } + + // 获取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)) + if err != nil { + return nil, fmt.Errorf("get eKey error: %w", err) + } + n, err := base64.StdEncoding.Decode(eKey, eKey) + if err != nil { + return nil, fmt.Errorf("base64 error: %w", err) + } + return deriveKeyV1(eKey[:n]) +} + func getRelativeMMKVDir(file string) (string, error) { mmkvDir := filepath.Join(filepath.Dir(file), "../mmkv") if _, err := os.Stat(mmkvDir); err != nil { diff --git a/algo/qmc/qmc.go b/algo/qmc/qmc.go index d6c084d..ace73fd 100644 --- a/algo/qmc/qmc.go +++ b/algo/qmc/qmc.go @@ -16,6 +16,9 @@ import ( "unlock-music.dev/cli/internal/sniff" ) +var VaultPath = "" +var VaultKey = "" + type Decoder struct { raw io.ReadSeeker // raw is the original file reader params *common.DecoderParams @@ -140,11 +143,32 @@ func (d *Decoder) searchKey() (err error) { return err } - switch string(suffixBuf) { + switch string(bytes.ReplaceAll(suffixBuf, []byte{0x00}, []byte{})) { case "QTag": return d.readRawMetaQTag() case "STag": return errors.New("qmc: file with 'STag' suffix doesn't contains media key") + case "cex": + 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 + } + } + } + return err default: size := binary.LittleEndian.Uint32(suffixBuf) diff --git a/cmd/um/main.go b/cmd/um/main.go index 8402142..459b6a4 100644 --- a/cmd/um/main.go +++ b/cmd/um/main.go @@ -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" @@ -14,10 +17,7 @@ import ( "sort" "strings" "time" - - "github.com/fsnotify/fsnotify" - "github.com/urfave/cli/v2" - "go.uber.org/zap" + "unlock-music.dev/cli/algo/qmc" "unlock-music.dev/cli/algo/common" _ "unlock-music.dev/cli/algo/kgm" @@ -50,6 +50,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-path", Aliases: []string{"db"}, Usage: "数据库文件位置 (请确保crc文件在同目录下)", Required: false}, + &cli.StringFlag{Name: "vault-key", Aliases: []string{"key"}, Usage: "数据库密钥", 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}, @@ -86,6 +88,8 @@ func appMain(c *cli.Context) (err error) { printSupportedExtensions() return nil } + qmc.VaultPath = c.String("vault-path") // TODO: 更改参数传递方式 + qmc.VaultKey = c.String("vault-key") input := c.String("input") if input == "" { switch c.Args().Len() { diff --git a/go.mod b/go.mod index 7362c01..fcab7bd 100644 --- a/go.mod +++ b/go.mod @@ -11,18 +11,19 @@ require ( github.com/urfave/cli/v2 v2.23.6 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.3.0 - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db + golang.org/x/exp v0.0.0-20240119083558-1b970713d09a golang.org/x/text v0.5.0 - unlock-music.dev/mmkv v0.0.0-20221204231432-41a75bd29939 + //unlock-music.dev/mmkv v0.0.0-20221204231432-41a75bd29939 ) require ( + git.unlock-music.dev/awalol/go-mmkv v0.1.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect golang.org/x/sys v0.2.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect ) diff --git a/go.sum b/go.sum index 2a3652f..f7bfc20 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +git.unlock-music.dev/awalol/go-mmkv v0.1.0 h1:Ev4buS12RNOUYd99wtE0P7CUIForghsBt0zRpaCnaSU= +git.unlock-music.dev/awalol/go-mmkv v0.1.0/go.mod h1:aM3nwVQyWkCH424GWxwU5w9JeRLUWNz7HgPxw0lWWOY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -17,6 +19,8 @@ github.com/go-flac/go-flac v0.3.1/go.mod h1:jG9IumOfAXr+7J40x0AiQIbJzXf9Y7+Zs/2C github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= @@ -48,6 +52,8 @@ golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -58,6 +64,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -- 2.40.1 From dc6f268d749866acd6a9624dd1d47002d4521c3e Mon Sep 17 00:00:00 2001 From: awalol Date: Tue, 13 Feb 2024 00:58:13 +0800 Subject: [PATCH 2/5] refactor: load mmkv on startup --- algo/qmc/key_mmkv.go | 34 ++++++++++++++++++---------------- algo/qmc/qmc.go | 3 --- cmd/um/main.go | 16 +++++++++++----- go.mod | 7 ++++--- go.sum | 15 ++++----------- 5 files changed, 37 insertions(+), 38 deletions(-) diff --git a/algo/qmc/key_mmkv.go b/algo/qmc/key_mmkv.go index 72374fa..64cb1b5 100644 --- a/algo/qmc/key_mmkv.go +++ b/algo/qmc/key_mmkv.go @@ -83,24 +83,26 @@ func readKeyFromMMKV(file string, logger *zap.Logger) ([]byte, error) { return deriveKey(buf) } -func readKeyFromMMKVCustom(d *Decoder) ([]byte, error) { - logger := d.logger - filePath, fileName := filepath.Split(VaultPath) - - if streamKeyVault == nil { - mgr, err := mmkv.NewManager(filepath.Dir(filePath)) - if err != nil { - return nil, fmt.Errorf("init mmkv manager: %w", err) - } - - streamKeyVault, err = mgr.OpenVaultCrypto(fileName, VaultKey) - if err != nil { - return nil, fmt.Errorf("open mmkv vault: %w", err) - } - - logger.Debug("mmkv vault opened", zap.Strings("keys", streamKeyVault.Keys())) +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(d *Decoder) ([]byte, error) { + if streamKeyVault == nil { + return nil, fmt.Errorf("mmkv vault not loaded") + } // 获取mid即数据库键值 _, err := d.raw.Seek(-128, io.SeekEnd) if err != nil { diff --git a/algo/qmc/qmc.go b/algo/qmc/qmc.go index ace73fd..1f6843a 100644 --- a/algo/qmc/qmc.go +++ b/algo/qmc/qmc.go @@ -16,9 +16,6 @@ import ( "unlock-music.dev/cli/internal/sniff" ) -var VaultPath = "" -var VaultKey = "" - type Decoder struct { raw io.ReadSeeker // raw is the original file reader params *common.DecoderParams diff --git a/cmd/um/main.go b/cmd/um/main.go index 459b6a4..9c5c329 100644 --- a/cmd/um/main.go +++ b/cmd/um/main.go @@ -17,13 +17,12 @@ import ( "sort" "strings" "time" - "unlock-music.dev/cli/algo/qmc" "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,7 +49,7 @@ 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-path", Aliases: []string{"db"}, Usage: "数据库文件位置 (请确保crc文件在同目录下)", Required: false}, + &cli.StringFlag{Name: "vault-file", Aliases: []string{"db"}, Usage: "数据库文件位置 (请确保crc文件在同目录下)", Required: false}, &cli.StringFlag{Name: "vault-key", Aliases: []string{"key"}, Usage: "数据库密钥", 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}, @@ -84,12 +83,19 @@ func printSupportedExtensions() { } func appMain(c *cli.Context) (err error) { + vaultPath := c.String("vault-file") + vaultKey := c.String("vault-key") + if vaultPath != "" && vaultKey != "" { + err := qmc.OpenMMKV(vaultPath, vaultKey, logger) + if err != nil { + return err + } + } + if c.Bool("supported-ext") { printSupportedExtensions() return nil } - qmc.VaultPath = c.String("vault-path") // TODO: 更改参数传递方式 - qmc.VaultKey = c.String("vault-key") input := c.String("input") if input == "" { switch c.Args().Len() { diff --git a/go.mod b/go.mod index fcab7bd..778d846 100644 --- a/go.mod +++ b/go.mod @@ -3,24 +3,25 @@ module unlock-music.dev/cli go 1.19 require ( + git.unlock-music.dev/awalol/go-mmkv v0.1.0 github.com/fsnotify/fsnotify v1.6.0 github.com/go-flac/flacpicture v0.2.0 github.com/go-flac/flacvorbis v0.1.0 github.com/go-flac/go-flac v0.3.1 github.com/samber/lo v1.36.0 - github.com/urfave/cli/v2 v2.23.6 + github.com/urfave/cli/v2 v2.27.1 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.3.0 golang.org/x/exp v0.0.0-20240119083558-1b970713d09a golang.org/x/text v0.5.0 - //unlock-music.dev/mmkv v0.0.0-20221204231432-41a75bd29939 +//unlock-music.dev/mmkv v0.0.0-20221204231432-41a75bd29939 ) require ( - git.unlock-music.dev/awalol/go-mmkv v0.1.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect diff --git a/go.sum b/go.sum index f7bfc20..a6d8443 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,6 @@ github.com/go-flac/flacvorbis v0.1.0/go.mod h1:70N9vVkQ4Jew0oBWkwqDMIE21h7pMUtQJ github.com/go-flac/go-flac v0.3.1 h1:BWA7HdO67S4ZLWSVHCxsDHuedFFu5RiV/wmuhvO6Hxo= github.com/go-flac/go-flac v0.3.1/go.mod h1:jG9IumOfAXr+7J40x0AiQIbJzXf9Y7+Zs/2CNWe4LMk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -34,10 +32,11 @@ github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlge github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= -github.com/urfave/cli/v2 v2.23.6 h1:iWmtKD+prGo1nKUtLO0Wg4z9esfBM4rAV4QRLQiEmJ4= -github.com/urfave/cli/v2 v2.23.6/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= +github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -50,8 +49,6 @@ go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -62,13 +59,9 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -unlock-music.dev/mmkv v0.0.0-20221204231432-41a75bd29939 h1:qWv734RbYjIHtHhZSRbdSyAEJ5K1rWcPSuUOen86tvI= -unlock-music.dev/mmkv v0.0.0-20221204231432-41a75bd29939/go.mod h1:1+Hdsrk8gl1i4/oxOnAhx6y51DAcUfi2CDni6Qhk8Kw= -- 2.40.1 From 18eb369de8947d26ff3c0666bf6a6ba9786780a8 Mon Sep 17 00:00:00 2001 From: awalol Date: Tue, 13 Feb 2024 01:17:52 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20shuffixBuf=20=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- algo/qmc/qmc.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/algo/qmc/qmc.go b/algo/qmc/qmc.go index 1f6843a..f991127 100644 --- a/algo/qmc/qmc.go +++ b/algo/qmc/qmc.go @@ -140,12 +140,12 @@ func (d *Decoder) searchKey() (err error) { return err } - switch string(bytes.ReplaceAll(suffixBuf, []byte{0x00}, []byte{})) { + switch string(suffixBuf) { case "QTag": return d.readRawMetaQTag() case "STag": return errors.New("qmc: file with 'STag' suffix doesn't contains media key") - case "cex": + case "cex\x00": d.decodedKey, err = readKeyFromMMKVCustom(d) if err == nil { suffix := []byte{0x63, 0x65, 0x78, 0x00} // cex @@ -164,6 +164,7 @@ func (d *Decoder) searchKey() (err error) { return nil } } + } return err default: -- 2.40.1 From 655b091744e4c2cca62d63c7959dbe0f7eefc235 Mon Sep 17 00:00:00 2001 From: awalol Date: Tue, 13 Feb 2024 02:41:42 +0800 Subject: [PATCH 4/5] 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 +} -- 2.40.1 From 3ae2ec34f41246de6d9b1025f0d8cce3223c641a Mon Sep 17 00:00:00 2001 From: awalol Date: Wed, 14 Feb 2024 16:59:05 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- algo/qmc/key_mmkv.go | 9 ++------- algo/qmc/qmc.go | 7 +++---- .../{qmc_footer.go => qmc_footer_musicex.go} | 10 ++++++---- cmd/um/main.go | 20 +++++++++---------- go.mod | 3 +-- go.sum | 4 ++-- 6 files changed, 24 insertions(+), 29 deletions(-) rename algo/qmc/{qmc_footer.go => qmc_footer_musicex.go} (94%) diff --git a/algo/qmc/key_mmkv.go b/algo/qmc/key_mmkv.go index 88b1ef6..24b51cb 100644 --- a/algo/qmc/key_mmkv.go +++ b/algo/qmc/key_mmkv.go @@ -1,18 +1,17 @@ package qmc import ( - "encoding/base64" "errors" "fmt" "os" "path/filepath" "runtime" - "git.unlock-music.dev/awalol/go-mmkv" "github.com/samber/lo" "go.uber.org/zap" "golang.org/x/exp/slices" "golang.org/x/text/unicode/norm" + "unlock-music.dev/mmkv" ) var streamKeyVault mmkv.Vault @@ -107,11 +106,7 @@ func readKeyFromMMKVCustom(mid string) ([]byte, error) { if err != nil { return nil, fmt.Errorf("get eKey error: %w", err) } - n, err := base64.StdEncoding.Decode(eKey, eKey) - if err != nil { - return nil, fmt.Errorf("base64 error: %w", err) - } - return deriveKeyV1(eKey[:n]) + return deriveKey(eKey) } func getRelativeMMKVDir(file string) (string, error) { diff --git a/algo/qmc/qmc.go b/algo/qmc/qmc.go index 3bbf798..310e642 100644 --- a/algo/qmc/qmc.go +++ b/algo/qmc/qmc.go @@ -40,8 +40,6 @@ type Decoder struct { // provider logger *zap.Logger - - footer qqMusicTagMusicEx } // Read implements io.Reader, offer the decrypted audio data. @@ -147,12 +145,13 @@ func (d *Decoder) searchKey() (err error) { case "STag": return errors.New("qmc: file with 'STag' suffix doesn't contains media key") case "cex\x00": - audioLen, err := d.footer.Read(d.raw) + footer := qqMusicTagMusicEx{} + audioLen, err := footer.Read(d.raw) if err != nil { return err } d.audioLen = int(audioLen) - d.decodedKey, err = readKeyFromMMKVCustom(d.footer.mediafile) + d.decodedKey, err = readKeyFromMMKVCustom(footer.mediafile) if err != nil { return err } diff --git a/algo/qmc/qmc_footer.go b/algo/qmc/qmc_footer_musicex.go similarity index 94% rename from algo/qmc/qmc_footer.go rename to algo/qmc/qmc_footer_musicex.go index 1714175..444c2aa 100644 --- a/algo/qmc/qmc_footer.go +++ b/algo/qmc/qmc_footer_musicex.go @@ -43,15 +43,17 @@ func (tag *qqMusicTagMusicEx) Read(raw io.ReadSeeker) (int64, error) { 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) + 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 { - tag.mediafile += string(u) + if u == 0 { + break } + tag.mediafile += string(u) } tag.unknown_3 = binary.LittleEndian.Uint32(buf[173:177]) diff --git a/cmd/um/main.go b/cmd/um/main.go index 9c5c329..4b74ce7 100644 --- a/cmd/um/main.go +++ b/cmd/um/main.go @@ -50,7 +50,7 @@ func main() { &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: "数据库密钥", 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}, @@ -83,15 +83,6 @@ func printSupportedExtensions() { } func appMain(c *cli.Context) (err error) { - vaultPath := c.String("vault-file") - vaultKey := c.String("vault-key") - if vaultPath != "" && vaultKey != "" { - err := qmc.OpenMMKV(vaultPath, vaultKey, logger) - if err != nil { - return err - } - } - if c.Bool("supported-ext") { printSupportedExtensions() return nil @@ -140,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"), diff --git a/go.mod b/go.mod index 778d846..9854614 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module unlock-music.dev/cli go 1.19 require ( - git.unlock-music.dev/awalol/go-mmkv v0.1.0 github.com/fsnotify/fsnotify v1.6.0 github.com/go-flac/flacpicture v0.2.0 github.com/go-flac/flacvorbis v0.1.0 @@ -14,7 +13,7 @@ require ( golang.org/x/crypto v0.3.0 golang.org/x/exp v0.0.0-20240119083558-1b970713d09a golang.org/x/text v0.5.0 -//unlock-music.dev/mmkv v0.0.0-20221204231432-41a75bd29939 + unlock-music.dev/mmkv v0.0.0-20240213204546-08c723fb2ed3 ) require ( diff --git a/go.sum b/go.sum index a6d8443..52c7d2c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -git.unlock-music.dev/awalol/go-mmkv v0.1.0 h1:Ev4buS12RNOUYd99wtE0P7CUIForghsBt0zRpaCnaSU= -git.unlock-music.dev/awalol/go-mmkv v0.1.0/go.mod h1:aM3nwVQyWkCH424GWxwU5w9JeRLUWNz7HgPxw0lWWOY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -65,3 +63,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +unlock-music.dev/mmkv v0.0.0-20240213204546-08c723fb2ed3 h1:soJr2wq4gDr+oCz81OCLJX/YjMsx9cwQGAEzvmw2yEs= +unlock-music.dev/mmkv v0.0.0-20240213204546-08c723fb2ed3/go.mod h1:1+Hdsrk8gl1i4/oxOnAhx6y51DAcUfi2CDni6Qhk8Kw= -- 2.40.1