// pvftext 在 Script.pvf 的全部文本文件里搜索子串,用于定位客户端提示文字来自哪个文件。 // // 城市 NPC 的对话、公告、任务文本都以反引号字符串形式存在 PVF 文本里, // 所以"这句话是谁弹的"可以直接从数据回答。 // // go run ./cmd/scratch/pvftext -pvf -find 无法创建公会 package main import ( "flag" "fmt" "os" "strings" platformpvf "longheng.io/server/internal/platform/pvf" ) func main() { pvfPath := flag.String("pvf", `../runtime/data/dnf/Script.pvf`, "Script.pvf path") find := flag.String("find", "", "要搜索的子串(逗号分隔多个)") limit := flag.Int("limit", 20, "每个子串最多报告多少条") contextLines := flag.Int("context", 0, "额外打印命中行的上下文行数") flag.Parse() if strings.TrimSpace(*find) == "" { fmt.Fprintln(os.Stderr, "usage: pvftext -pvf -find ") os.Exit(2) } var needles []string for _, n := range strings.Split(*find, ",") { if n = strings.TrimSpace(n); n != "" { needles = append(needles, n) } } archive, err := platformpvf.Open(*pvfPath) if err != nil { fmt.Fprintln(os.Stderr, "open:", err) os.Exit(1) } fmt.Printf("PVF=%s 文件数=%d 搜索=%q\n", *pvfPath, archive.FileCount(), needles) counts := map[string]int{} for _, f := range archive.Files() { // 只扫文本类文件,跳过巨大的二进制资源 lower := strings.ToLower(f.ArchivePath) if !strings.HasSuffix(lower, ".npc") && !strings.HasSuffix(lower, ".qst") && !strings.HasSuffix(lower, ".etc") && !strings.HasSuffix(lower, ".lst") && !strings.HasSuffix(lower, ".twn") && !strings.HasSuffix(lower, ".map") && !strings.HasSuffix(lower, ".stk") && !strings.HasSuffix(lower, ".equ") && !strings.HasSuffix(lower, ".evt") && !strings.HasSuffix(lower, ".shp") && !strings.HasSuffix(lower, ".str") { continue } text, err := archive.FileText(f.Index) if err != nil { continue } for _, n := range needles { if !strings.Contains(text, n) { continue } counts[n]++ if counts[n] <= *limit { fmt.Printf("\n[%s] %s\n", n, f.ArchivePath) lines := strings.Split(strings.ReplaceAll(text, "\r\n", "\n"), "\n") for i, line := range lines { if !strings.Contains(line, n) { continue } lo := i - *contextLines if lo < 0 { lo = 0 } hi := i + *contextLines if hi >= len(lines) { hi = len(lines) - 1 } for j := lo; j <= hi; j++ { mark := " " if j == i { mark = ">>" } fmt.Printf(" %s %5d| %s\n", mark, j+1, strings.TrimSpace(lines[j])) } } } } } for _, n := range needles { fmt.Printf("\n命中统计 [%s] = %d 个文件\n", n, counts[n]) } }