// Command chdump 从 PVF 里取出 etc/channel_info.chn.str 并按需过滤, // 用来回答「某个频道在游戏里显示的中文名是什么」。 // // 用法: // // go run ./cmd/scratch/chdump -pvf [-path etc/channel_info.chn.str] [-grep dname_0] package main import ( "encoding/binary" "flag" "fmt" "os" "strings" "unicode/utf16" "longheng.io/server/internal/platform/pvf" ) func main() { pvfPath := flag.String("pvf", "", "Script.pvf 路径") relPath := flag.String("path", "etc/channel_info.chn.str", "PVF 内的相对路径") grep := flag.String("grep", "", "只打印包含该子串的行") limit := flag.Int("limit", 200, "最多打印多少行") flag.Parse() if *pvfPath == "" { fmt.Fprintln(os.Stderr, "需要 -pvf") os.Exit(2) } archive, err := pvf.Open(*pvfPath) if err != nil { fmt.Fprintln(os.Stderr, "打开 PVF 失败:", err) os.Exit(1) } raw, err := archive.ReadRaw(*relPath) if err != nil { fmt.Fprintln(os.Stderr, "读取失败:", err) os.Exit(1) } fmt.Printf("路径=%s 原始大小=%d 字节\n", *relPath, len(raw)) if len(raw) >= 16 { fmt.Printf("前 16 字节: % X\n", raw[:16]) } text := decodeBest(raw) lines := strings.Split(text, "\n") printed := 0 for i, line := range lines { line = strings.TrimRight(line, "\r") if line == "" { continue } if *grep != "" && !strings.Contains(line, *grep) { continue } fmt.Printf("%4d: %s\n", i+1, line) printed++ if printed >= *limit { fmt.Println("... (达到 limit 上限)") break } } if printed == 0 { fmt.Println("(没有匹配的行)") } } // decodeBest 依次尝试 UTF-8、UTF-16LE,选能解出更多可打印字符的那个。 func decodeBest(raw []byte) string { utf8Text := string(raw) utf16Text := decodeUTF16LE(raw) if score(utf16Text) > score(utf8Text) { return utf16Text } return utf8Text } func decodeUTF16LE(raw []byte) string { if len(raw) < 2 { return "" } units := make([]uint16, 0, len(raw)/2) for i := 0; i+1 < len(raw); i += 2 { units = append(units, binary.LittleEndian.Uint16(raw[i:i+2])) } return string(utf16.Decode(units)) } // score 粗略衡量文本「像不像正常文本」:可打印/常见字符越多分越高。 func score(text string) int { total := 0 good := 0 for _, r := range text { total++ switch { case r == '\n' || r == '\r' || r == '\t': good++ case r >= 0x20 && r <= 0x7E: good++ case r >= 0x4E00 && r <= 0x9FFF: // CJK good++ case r >= 0x3000 && r <= 0x303F: // CJK 标点 case r >= 0xFF00 && r <= 0xFFEF: // 全角 good++ } } if total == 0 { return 0 } return good * 1000 / total }