package qmc import ( "crypto/md5" "fmt" "os" "path/filepath" "regexp" "strconv" "strings" "git.um-react.app/um/cli/algo/common" "go.uber.org/zap" ) func LoadMMKVOrDefault(path string, key string, logger *zap.Logger) (result common.QMCKeys, err error) { key1, err := loadMacKeysV8(logger) if err != nil { key1 = nil logger.Warn("LoadMMKVOrDefault: could not read QQMusic v8.8.0 keys", zap.Error(err)) } key2, err := loadMacKeysV10(logger) if err != nil { key2 = nil logger.Warn("LoadMMKVOrDefault: could not read QQMusic v10.x keys", zap.Error(err)) } userKeys := make(common.QMCKeys) if path != "" { logger.Info("Using user mmkv") userKeys, err = LoadMMKV(path, key, logger) if err != nil { userKeys = nil logger.Warn("LoadMMKVOrDefault: could not read user keys", zap.Error(err)) } } allKeys := mergeMMKVKeys(key1, key2, userKeys) logger.Debug("Keys loaded", zap.Any("keys", allKeys), zap.Int("len", len(allKeys))) 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 }