// pvfrawdisableattendance 从 Script.pvf 读出「累计在线得好礼」活动定义文件的 // **原始字节**,把奖励与阶段数据等长清空,导出到 pvfrepack 所需的 raw 目录结构, // 之后由 pvfrepack 重新封包。 // // 为什么必须走 raw: // // pvfrepack 明确说明"decoded 可读文本不能直接重新封包"——直接用 ReadText 得到的 // 文本重新写回会破坏 type-1 成员的令牌流(实测:重打包后该文件只剩 1 行)。 // 所以这里只改 raw 字节,且**保持文件长度完全不变**。 // // 目标文件:event/chn_event/chn_attendanceevent.evt // // go run ./cmd/scratch/pvfrawdisableattendance -pvf -dir package main import ( "flag" "fmt" "os" "path/filepath" "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") dir := flag.String("dir", "", "raw 修改目录(按 PVF 相对路径存放)") flag.Parse() if *pvfPath == "" || *dir == "" { fmt.Fprintln(os.Stderr, "usage: pvfrawdisableattendance -pvf -dir ") os.Exit(2) } archive, err := platformpvf.Open(*pvfPath) if err != nil { fmt.Fprintln(os.Stderr, "open:", err) os.Exit(1) } raw, err := archive.ReadRaw(targetPath) if err != nil { fmt.Fprintln(os.Stderr, "read raw:", err) os.Exit(1) } fmt.Printf("原始字节 %d\n", len(raw)) patched := patchRaw(raw) if len(patched) != len(raw) { fmt.Fprintf(os.Stderr, "!! 长度变化 %d -> %d,中止\n", len(raw), len(patched)) os.Exit(1) } fmt.Printf("改后字节 %d(等长)\n", len(patched)) out := filepath.Join(*dir, filepath.FromSlash(targetPath)) if err := os.MkdirAll(filepath.Dir(out), 0o755); err != nil { fmt.Fprintln(os.Stderr, "mkdir:", err) os.Exit(1) } if err := os.WriteFile(out, patched, 0o644); err != nil { fmt.Fprintln(os.Stderr, "write:", err) os.Exit(1) } fmt.Printf("已写出 raw: %s\n", out) } // patchRaw 在原始字节上做等长替换。 // // type-1 成员里,段标签与数值都是"反引号包裹的 ASCII 字面量"(例如 `1800`), // 因此只需在 ASCII 范围内把数字位换成 9、把奖励物品号整段换成空格,长度不变。 func patchRaw(raw []byte) []byte { out := make([]byte, len(raw)) copy(out, raw) // 1) [reward item] / [reward item for sum] 段内的物品号清空。 out = blankRawSection(out, "reward item") out = blankRawSection(out, "reward item for sum") // 2) [process time seconds] / [process seconds for max count] 的数值改为极大, // 使阶段永远不可达(等长:数字位数不变)。 out = maxRawSectionNumbers(out, "process time seconds") out = maxRawSectionNumbers(out, "process seconds for max count") return out } // findRawTag 在原始字节里找到以 `[tag]` 形式出现的偏移。 func findRawTag(data []byte, tag string) int { needle := []byte("[`" + tag + "`]") if i := strings.Index(string(data), string(needle)); i >= 0 { return i } // 兼容无内层反引号的写法 needle2 := []byte("[" + tag + "]") return strings.Index(string(data), string(needle2)) } // blankRawSection 把 `[tag] ... [/tag]` 之间的可打印字符(非结构性 '`')换成空格。 func blankRawSection(data []byte, tag string) []byte { start := findRawTag(data, tag) if start < 0 { fmt.Printf(" 提示:未找到段 [%s]\n", tag) return data } openEnd := start for openEnd < len(data) && data[openEnd] != ']' { openEnd++ } openEnd++ // 越过 ']' closeTag := "[/`" + tag + "`]" end := strings.Index(string(data[openEnd:]), closeTag) if end < 0 { closeTag = "[/" + tag + "]" end = strings.Index(string(data[openEnd:]), closeTag) } if end < 0 { fmt.Printf(" 提示:[%s] 没有闭合标签\n", tag) return data } bodyEnd := openEnd + end for i := openEnd; i < bodyEnd; i++ { c := data[i] // 只改数字,避免碰结构字符(反引号、方括号、换行) if c >= '0' && c <= '9' { data[i] = ' ' } } return data } // maxRawSectionNumbers 把段内数字改为 9(等长),使门槛极大。 func maxRawSectionNumbers(data []byte, tag string) []byte { start := findRawTag(data, tag) if start < 0 { fmt.Printf(" 提示:未找到段 [%s]\n", tag) return data } openEnd := start for openEnd < len(data) && data[openEnd] != ']' { openEnd++ } openEnd++ closeTag := "[/`" + tag + "`]" end := strings.Index(string(data[openEnd:]), closeTag) if end < 0 { closeTag = "[/" + tag + "]" end = strings.Index(string(data[openEnd:]), closeTag) } if end < 0 { return data } bodyEnd := openEnd + end re := regexp.MustCompile(`[0-9]+`) seg := data[openEnd:bodyEnd] for _, loc := range re.FindAllIndex(seg, -1) { for i := loc[0]; i < loc[1]; i++ { seg[i] = '9' } } return data }