// pvfdisableattendance 把客户端 Script.pvf 里「累计在线得好礼」(event 2347) 的 // 活动定义清空,使客户端不再显示该活动。 // // 目标文件:event/chn_event/chn_attendanceevent.evt // // [process time seconds] 1800 1800 3600 3600 ← 四阶段时长 // [reward item] 490006574..490006577 ← 阶段奖励 // [process seconds for max count] 5 10 15 25 ← 25 次满阶段 // [reward item for sum] 490006578..490006581 ← 满阶段奖励 // // 做法:把 [reward item]、[reward item for sum] 两个段的**内容行**清空(保留段标签), // 并把 [process time seconds] 的数值改为一个极大值,使阶段永远不可达。 // // 刻意保持**总字节数不变**(用空格填充),因为归档重打包对文件长度变化更敏感; // 等长替换把风险降到最低。 // // 用法: // // go run ./cmd/scratch/pvfdisableattendance -pvf -out package main import ( "flag" "fmt" "os" "regexp" "strings" platformpvf "longheng.io/server/internal/platform/pvf" ) const targetPath = "event/chn_event/chn_attendanceevent.evt" func main() { pvfPath := flag.String("pvf", "", "源 Script.pvf") outPath := flag.String("out", "", "输出 Script.pvf(不覆盖源文件)") flag.Parse() if *pvfPath == "" || *outPath == "" { fmt.Fprintln(os.Stderr, "usage: pvfdisableattendance -pvf -out ") os.Exit(2) } archive, err := platformpvf.Open(*pvfPath) if err != nil { fmt.Fprintln(os.Stderr, "open:", err) os.Exit(1) } text, err := archive.ReadText(targetPath) if err != nil { fmt.Fprintln(os.Stderr, "read target:", err) os.Exit(1) } fmt.Printf("原文件 %d 字节\n", len(text)) fmt.Printf("---- 原文 ----\n%s\n--------------\n", text) patched := disableActivity(text) if len(patched) != len(text) { fmt.Fprintf(os.Stderr, "!! 长度变化 %d -> %d,中止\n", len(text), len(patched)) os.Exit(1) } fmt.Printf("---- 改后(等长 %d 字节)----\n%s\n--------------\n", len(patched), patched) raw, err := archive.RepackRaw([]platformpvf.RawReplacement{{Path: targetPath, Data: []byte(patched)}}) if err != nil { fmt.Fprintln(os.Stderr, "repack:", err) os.Exit(1) } if err := os.WriteFile(*outPath, raw, 0o644); err != nil { fmt.Fprintln(os.Stderr, "write:", err) os.Exit(1) } fmt.Printf("已写出: %s (%d 字节)\n", *outPath, len(raw)) } // disableActivity 在等长前提下清空奖励内容。 func disableActivity(text string) string { out := text // 逐段处理:段标签之间的内容行替换为等长空白。 out = blankSectionBody(out, "reward item") out = blankSectionBody(out, "reward item for sum") // [process time seconds] 的数值改为极大值(等长:用同样位数的数字)。 out = rewriteSectionNumbers(out, "process time seconds", "999999999") out = rewriteSectionNumbers(out, "process seconds for max count", "999999999") return out } // blankSectionBody 把 [name] ... [/name] 之间的非空内容行清成空格(长度不变)。 // 段落名匹配用宽松规则:"[reward item]" 与 "[reward item for sum]" 都要能命中, // 所以按精确标签匹配。 func blankSectionBody(text, name string) string { open := "[" + name + "]" closeTag := "[/" + name + "]" start := indexOfLine(text, open) if start < 0 { fmt.Printf(" 提示:未找到段 %s\n", open) return text } bodyStart := start + len(open) end := strings.Index(text[bodyStart:], closeTag) if end < 0 { fmt.Printf(" 提示:段 %s 没有闭合标签\n", open) return text } body := text[bodyStart : bodyStart+end] // 逐行:保留换行,把可见字符换成空格。 var b strings.Builder b.Grow(len(body)) for _, line := range strings.SplitAfter(body, "\n") { nl := "" content := line if strings.HasSuffix(line, "\n") { nl = "\n" content = strings.TrimSuffix(line, "\n") } b.WriteString(strings.Repeat(" ", len(content))) b.WriteString(nl) } return text[:bodyStart] + b.String() + text[bodyStart+end:] } // rewriteSectionNumbers 把段内所有数字替换为 given(逐行等长填充)。 func rewriteSectionNumbers(text, name, value string) string { open := "[" + name + "]" closeTag := "[/" + name + "]" start := indexOfLine(text, open) if start < 0 { fmt.Printf(" 提示:未找到段 %s\n", open) return text } bodyStart := start + len(open) end := strings.Index(text[bodyStart:], closeTag) if end < 0 { return text } body := text[bodyStart : bodyStart+end] numRe := regexp.MustCompile(`\d+`) var b strings.Builder b.Grow(len(body)) for _, line := range strings.SplitAfter(body, "\n") { nl := "" content := line if strings.HasSuffix(line, "\n") { nl = "\n" content = strings.TrimSuffix(line, "\n") } replaced := numRe.ReplaceAllStringFunc(content, func(m string) string { // 等长:用 value 的前 len(m) 个字符;若 value 更短则补 '9' if len(m) <= len(value) { return value[:len(m)] } return strings.Repeat("9", len(m)) }) // 保持行长度 if len(replaced) < len(content) { replaced += strings.Repeat(" ", len(content)-len(replaced)) } b.WriteString(replaced) b.WriteString(nl) } return text[:bodyStart] + b.String() + text[bodyStart+end:] } // indexOfLine 找到以给定标签开头的那一行的偏移。 func indexOfLine(text, tag string) int { idx := 0 for { at := strings.Index(text[idx:], tag) if at < 0 { return -1 } at += idx // 必须位于行首(允许前导空白) lineStart := strings.LastIndexByte(text[:at], '\n') + 1 if strings.TrimSpace(text[lineStart:at]) == "" { return at } idx = at + len(tag) } }