如何为魔兽世界正式服插件计算CurseForge API指纹?
CurseForge魔兽世界插件指纹计算修正方案
核心计算规则(非官方但验证有效)
- 使用Murmur2 32位哈希算法,哈希种子固定为
0 - 需哈希插件目录下所有文件的内容(并非仅lua/toc/xml类型)
- 文件路径采用相对于插件根目录的相对路径(例如
AutoRepair.lua,而非完整的Interface/Addons/AutoRepair/AutoRepair.lua) - 按相对路径的字典序对文件排序,依次计算每个文件内容的Murmur2哈希
- 将所有哈希值按升序排序后,直接以
uint32的字节形式拼接(不转为字符串),最后对该字节流再做一次Murmur2哈希(种子0),得到最终指纹
你的代码问题分析
- 哈希种子错误:使用了
1,正确应为0 - 文件路径使用完整WoW路径,而非相对插件根目录的路径
- 最终拼接哈希值时转为字符串再哈希,而非直接用uint32字节序列拼接
- 仅过滤lua/toc/xml文件,未覆盖插件目录下所有文件
修正后的Go代码
package main import ( "bytes" "encoding/binary" "fmt" "io/ioutil" "log" "os" "path/filepath" "sort" "strings" "github.com/joho/godotenv" mm "github.com/mistweaverco/go-murmur/murmur2" ) func ReadFileAsBytes(path string) []byte { b, err := os.ReadFile(path) if err != nil { panic(err) } return b } // 获取插件目录下的所有相对路径文件 func GetRelativeFileList(addonRootDir string) ([]string, error) { var relativePaths []string err := filepath.Walk(addonRootDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } // 计算相对路径 relPath, err := filepath.Rel(addonRootDir, path) if err != nil { return err } // 统一路径分隔符为/(符合WoW插件路径格式) relPath = strings.ReplaceAll(relPath, "\\", "/") relativePaths = append(relativePaths, relPath) return nil }) return relativePaths, err } type Fingerprint struct { AddonDir string Fingerprint uint32 } func GetFingerprint(addonDir string) Fingerprint { fingerprint := Fingerprint{AddonDir: addonDir} fullAddonDir := wowDirPrefix + "/Interface/Addons/" + addonDir + "/" // 获取所有相对路径文件 relativeFiles, err := GetRelativeFileList(fullAddonDir) if err != nil { log.Fatal(err) } // 按字典序排序文件路径 sort.Strings(relativeFiles) fmt.Println("Sorted relative files:", relativeFiles) var fileHashes []uint32 for _, relPath := range relativeFiles { fullPath := filepath.Join(fullAddonDir, relPath) fc := ReadFileAsBytes(fullPath) // 使用种子0计算文件内容的Murmur2哈希 hash := mm.MurmurHash2(fc, 0) fileHashes = append(fileHashes, hash) } fmt.Println("File hashes:", fileHashes) // 对哈希值升序排序 sort.Slice(fileHashes, func(i, j int) bool { return fileHashes[i] < fileHashes[j] }) fmt.Println("Sorted hashes:", fileHashes) // 将排序后的哈希值转为字节流(小端序) var buf bytes.Buffer for _, hash := range fileHashes { err := binary.Write(&buf, binary.LittleEndian, hash) if err != nil { log.Fatal(err) } } // 对字节流计算最终Murmur2哈希,种子0 fingerprint.Fingerprint = mm.MurmurHash2(buf.Bytes(), 0) return fingerprint } var wowDirPrefix string func env(k string, failOnMissing bool) string { value := os.Getenv(k) if value != "" { return value } if failOnMissing { log.Fatalf("%v environment variable is not set.", k) } return "" } func main() { godotenv.Load() wowDirPrefix = env("DEBUG_WOW_RETAIL_DIR", false) fp := GetFingerprint("AutoRepair") fmt.Printf("{AddonDir: %s, Fingerprint: %d}\n", fp.AddonDir, fp.Fingerprint) }
关键修正点说明
- 改用相对路径:确保与CurseForge服务器端计算的路径基准一致
- 哈希种子改为0:匹配CurseForge使用的标准种子值
- 直接拼接uint32字节:避免字符串转换导致的哈希值偏差
- 遍历所有文件:覆盖CurseForge计算时纳入的全部文件类型
内容的提问来源于stack exchange,提问作者GorillaMoe
相关产品推荐
相关产品推荐

