key_mmkv.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package qmc
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "runtime"
  8. "github.com/samber/lo"
  9. "go.uber.org/zap"
  10. "golang.org/x/exp/slices"
  11. "golang.org/x/text/unicode/norm"
  12. "unlock-music.dev/mmkv"
  13. )
  14. var streamKeyVault mmkv.Vault
  15. // TODO: move to factory
  16. func readKeyFromMMKV(file string, logger *zap.Logger) ([]byte, error) {
  17. if file == "" {
  18. return nil, errors.New("file path is required while reading key from mmkv")
  19. }
  20. //goland:noinspection GoBoolExpressions
  21. if runtime.GOOS != "darwin" {
  22. return nil, errors.New("mmkv vault not supported on this platform")
  23. }
  24. if streamKeyVault == nil {
  25. mmkvDir, err := getRelativeMMKVDir(file)
  26. if err != nil {
  27. mmkvDir, err = getDefaultMMKVDir()
  28. if err != nil {
  29. return nil, fmt.Errorf("mmkv key valut not found: %w", err)
  30. }
  31. }
  32. mgr, err := mmkv.NewManager(mmkvDir)
  33. if err != nil {
  34. return nil, fmt.Errorf("init mmkv manager: %w", err)
  35. }
  36. streamKeyVault, err = mgr.OpenVault("MMKVStreamEncryptId")
  37. if err != nil {
  38. return nil, fmt.Errorf("open mmkv vault: %w", err)
  39. }
  40. logger.Debug("mmkv vault opened", zap.Strings("keys", streamKeyVault.Keys()))
  41. }
  42. _, partName := filepath.Split(file)
  43. partName = normalizeUnicode(partName)
  44. buf, err := streamKeyVault.GetBytes(file)
  45. if buf == nil {
  46. filePaths := streamKeyVault.Keys()
  47. fileNames := lo.Map(filePaths, func(filePath string, _ int) string {
  48. _, name := filepath.Split(filePath)
  49. return normalizeUnicode(name)
  50. })
  51. for _, key := range fileNames { // fallback: match filename only
  52. if key != partName {
  53. continue
  54. }
  55. idx := slices.Index(fileNames, key)
  56. buf, err = streamKeyVault.GetBytes(filePaths[idx])
  57. if err != nil {
  58. logger.Warn("read key from mmkv", zap.String("key", filePaths[idx]), zap.Error(err))
  59. }
  60. }
  61. }
  62. if len(buf) == 0 {
  63. return nil, errors.New("key not found in mmkv vault")
  64. }
  65. return deriveKey(buf)
  66. }
  67. func OpenMMKV(mmkvPath string, key string, logger *zap.Logger) error {
  68. filePath, fileName := filepath.Split(mmkvPath)
  69. mgr, err := mmkv.NewManager(filepath.Dir(filePath))
  70. if err != nil {
  71. return fmt.Errorf("init mmkv manager: %w", err)
  72. }
  73. // If `vaultKey` is empty, the key is ignored.
  74. streamKeyVault, err = mgr.OpenVaultCrypto(fileName, key)
  75. if err != nil {
  76. return fmt.Errorf("open mmkv vault: %w", err)
  77. }
  78. logger.Debug("mmkv vault opened", zap.Strings("keys", streamKeyVault.Keys()))
  79. return nil
  80. }
  81. // /
  82. func readKeyFromMMKVCustom(mid string) ([]byte, error) {
  83. if streamKeyVault == nil {
  84. return nil, fmt.Errorf("mmkv vault not loaded")
  85. }
  86. // get ekey from mmkv vault
  87. eKey, err := streamKeyVault.GetBytes(mid)
  88. if err != nil {
  89. return nil, fmt.Errorf("get eKey error: %w", err)
  90. }
  91. return deriveKey(eKey)
  92. }
  93. // / getRelativeMMKVDir get mmkv dir relative to file (legacy QQMusic for macOS behaviour)
  94. func getRelativeMMKVDir(file string) (string, error) {
  95. mmkvDir := filepath.Join(filepath.Dir(file), "../mmkv")
  96. if _, err := os.Stat(mmkvDir); err != nil {
  97. return "", fmt.Errorf("stat default mmkv dir: %w", err)
  98. }
  99. keyFile := filepath.Join(mmkvDir, "MMKVStreamEncryptId")
  100. if _, err := os.Stat(keyFile); err != nil {
  101. return "", fmt.Errorf("stat default mmkv file: %w", err)
  102. }
  103. return mmkvDir, nil
  104. }
  105. func getDefaultMMKVDir() (string, error) {
  106. homeDir, err := os.UserHomeDir()
  107. if err != nil {
  108. return "", fmt.Errorf("get user home dir: %w", err)
  109. }
  110. mmkvDir := filepath.Join(
  111. homeDir,
  112. "Library/Containers/com.tencent.QQMusicMac/Data",
  113. "Library/Application Support/QQMusicMac/mmkv",
  114. )
  115. if _, err := os.Stat(mmkvDir); err != nil {
  116. return "", fmt.Errorf("stat default mmkv dir: %w", err)
  117. }
  118. keyFile := filepath.Join(mmkvDir, "MMKVStreamEncryptId")
  119. if _, err := os.Stat(keyFile); err != nil {
  120. return "", fmt.Errorf("stat default mmkv file: %w", err)
  121. }
  122. return mmkvDir, nil
  123. }
  124. // normalizeUnicode normalizes unicode string to NFC.
  125. // since macOS may change some characters in the file name.
  126. // e.g. "ぜ"(e3 81 9c) -> "ぜ"(e3 81 9b e3 82 99)
  127. func normalizeUnicode(str string) string {
  128. return norm.NFC.String(str)
  129. }