// pvfcollectboxbase 把 PVF **共享字符串池**里收集箱的 [base expiration date] 改成未来日期。 // // 原因见 internal/modules/dnf/collectbox/owner.go 的 SnapshotBonus: // // expiration := state.Expiration[boxIndex] // Insert(放卡片) 不写它;只有 Extend(延期券) 写 // if expiration.IsZero() { // expiration, _ = parseExpiration(definition.BaseExpirationDate) // ← 回退到 base // } // if !expiration.IsZero() && !now.Before(expiration) { return Bonus{} } // 过期 → 属性不生效 // // 而 localization/collectbox/collectboxinfo.etc 里各箱 base 日期均已是过去: // // Index1 SAO 2014-12-30 Index2 生肖之灵 2015-09-01 // Index3 三国志 2017-01-05 Index4 猎龙者 2017-12-31 // Index5 兽人守护神 2018-12-31 // // 用 RepackStringValues 做**等宽**替换:所有日期都是 19 字符,替换后长度不变, // 池内偏移与所有引用保持有效。 package main import ( "flag" "fmt" "os" "path/filepath" platformpvf "longheng.io/server/internal/platform/pvf" ) var replacements = []platformpvf.StringValueReplacement{ {Old: "2014-12-30 06:00:00", New: "2099-12-30 06:00:00"}, {Old: "2015-09-01 06:00:00", New: "2099-09-01 06:00:00"}, {Old: "2017-01-05 06:00:00", New: "2099-01-05 06:00:00"}, {Old: "2017-12-31 06:00:00", New: "2099-12-31 06:00:00"}, {Old: "2018-12-31 06:00:00", New: "2099-12-31 06:00:00"}, } func main() { input := flag.String("pvf", "", "input PVF (in place)") flag.Parse() if *input == "" { panic("-pvf is required") } archive, err := platformpvf.Open(*input) if err != nil { panic(err) } // 先确认这些日期确实在池里 offsets := archive.FindStringMagicOffsets(func() []string { out := make([]string, 0, len(replacements)) for _, r := range replacements { out = append(out, r.Old) } return out }()) fmt.Println("池中命中情况:") ok := make([]platformpvf.StringValueReplacement, 0, len(replacements)) for _, r := range replacements { if off, found := offsets[r.Old]; found { fmt.Printf(" ✓ %s (池@%d) → %s\n", r.Old, off, r.New) ok = append(ok, r) } else { fmt.Printf(" ✗ %s 不在池中\n", r.Old) } } if len(ok) == 0 { panic("没有任何可替换的日期") } repacked, err := archive.RepackStringValues(ok) if err != nil { panic(err) } fmt.Printf("重打包完成:%d → %d 字节\n", len(archive.Bytes()), len(repacked)) // 校验:新 PVF 里日期已变 verify, err := platformpvf.OpenBytes(repacked) if err != nil { fmt.Println("⚠ 无法重新打开校验:", err) } else { text, err := verify.ReadText("localization/collectbox/collectboxinfo.etc") if err == nil { for _, r := range ok { if contains(text, r.New) { fmt.Printf(" ✓ 已确认含 %s\n", r.New) } if contains(text, r.Old) { fmt.Printf(" ✗ 仍含旧值 %s\n", r.Old) } } } else { fmt.Println(" ⚠ 校验读取失败:", err) } } // 原子写回:先写临时文件再替换 dir := filepath.Dir(*input) tmp := filepath.Join(dir, filepath.Base(*input)+".tmp_collectbox") if err := os.WriteFile(tmp, repacked, 0o644); err != nil { panic(err) } backup := *input + ".bak_before_basefix" if _, statErr := os.Stat(backup); statErr != nil { if err := os.Rename(*input, backup); err != nil { panic(err) } fmt.Println("已备份 →", backup) } else { if err := os.Remove(*input); err != nil { panic(err) } } if err := os.Rename(tmp, *input); err != nil { panic(err) } fmt.Println("已写回", *input) } func contains(haystack, needle string) bool { if len(needle) == 0 || len(haystack) < len(needle) { return false } for i := 0; i+len(needle) <= len(haystack); i++ { if haystack[i:i+len(needle)] == needle { return true } } return false }