// pvfref 在 PVF 全库按**子串**搜引用,只打印命中文件与命中片段。 // // 与 cmd/scratch/pvfgrep 的区别:pvfgrep 走 dnfpvf.Index(受前缀/扩展名限制), // pvfref 直接用 platform/pvf 的 RangePaths 遍历**全部 100 万+ 成员**, // 因此能找到 ui/ 下的 .ani / .xui 等界面资源引用。 // // 跑法: // // go run ./cmd/scratch/pvfref -pvf Script.pvf -find interface2_reward_ver2_cn \ // -ext .ani,.xui,.als -limit 40 package main import ( "flag" "fmt" "strings" platformpvf "longheng.io/server/internal/platform/pvf" ) func main() { pvfPath := flag.String("pvf", "", "Script.pvf (required)") find := flag.String("find", "", "要搜索的子串 (required)") extFlag := flag.String("ext", "", "只搜这些扩展名,逗号分隔;留空=全部") limit := flag.Int("limit", 40, "最多命中文件数") width := flag.Int("width", 90, "命中片段前后各取多少字符") flag.Parse() if *pvfPath == "" || *find == "" { panic("-pvf and -find are required") } needle := strings.ToLower(*find) var exts []string for _, e := range strings.Split(*extFlag, ",") { if e = strings.ToLower(strings.TrimSpace(e)); e != "" { exts = append(exts, e) } } archive, err := platformpvf.Open(*pvfPath) if err != nil { panic(err) } fmt.Printf("PVF %s 成员 %d 搜索 %q\n", *pvfPath, archive.FileCount(), *find) files, scanned := 0, 0 archive.RangePaths(func(p string) bool { lower := strings.ToLower(p) if len(exts) > 0 { ok := false for _, e := range exts { if strings.HasSuffix(lower, e) { ok = true break } } if !ok { return true } } scanned++ text, readErr := archive.ReadText(p) if readErr != nil || !strings.Contains(strings.ToLower(text), needle) { return true } files++ fmt.Printf("\n=== %s ===\n", p) for _, line := range strings.Split(text, "\n") { at := strings.Index(strings.ToLower(line), needle) if at < 0 { continue } from := at - *width if from < 0 { from = 0 } to := at + len(*find) + *width if to > len(line) { to = len(line) } fmt.Printf(" >> %s\n", strings.TrimSpace(line[from:to])) } return files < *limit }) fmt.Printf("\n扫描 %d 个文件,命中 %d 个\n", scanned, files) }