Files
90CNServer/server/cmd/scratch/pvftokendump/main.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

178 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// pvftokendump 把 protected PVF 里某个脚本的令牌流逐条打印出来:
// 序号、令牌类型、4 字节负载、以及字符串负载解出来的文本。
//
// 用途:看清「某个段的值是空的」在令牌流里到底是【没有 token】还是【一个指向空串/别处的 token】。
// 这正是「工具往空字段里塞看不见的字符串引用」那类问题的判据。
//
// go run ./cmd/scratch/pvftokendump -pvf <Script.pvf> -path stackable/newstk/2026091800.stk
// go run ./cmd/scratch/pvftokendump -pvf <Script.pvf> -path <文件> -around "[booster info]"
package main
import (
"flag"
"fmt"
"os"
"strings"
platformpvf "longheng.io/server/internal/platform/pvf"
)
func main() {
pvfPath := flag.String("pvf", "", "Script.pvf 路径")
path := flag.String("path", "", "要看的文件归档路径")
around := flag.String("around", "", "只打印包含该字符串的 token 前后各若干条")
resolveList := flag.String("resolve", "", "逗号分隔的 magic offset,直接解析成字符串(不需要 -path)")
ctx := flag.Int("ctx", 4, "around 的前后条数")
flag.Parse()
if *pvfPath == "" {
fmt.Fprintln(os.Stderr, "-pvf is required")
os.Exit(2)
}
if *path == "" && *resolveList == "" {
fmt.Fprintln(os.Stderr, "-path(或 -resolve)is required")
os.Exit(2)
}
// -resolve 模式:只把给定的 magic offset 解出来,用来回答「偏移 0 指向哪个字符串」。
if *resolveList != "" {
archive, err := platformpvf.Open(*pvfPath)
if err != nil {
fmt.Fprintln(os.Stderr, "open:", err)
os.Exit(1)
}
fmt.Printf("归档: %s\n\n", *pvfPath)
for _, tok := range strings.Split(*resolveList, ",") {
tok = strings.TrimSpace(tok)
if tok == "" {
continue
}
var v int
if _, e := fmt.Sscanf(tok, "%d", &v); e != nil {
fmt.Fprintf(os.Stderr, " bad offset %q\n", tok)
continue
}
pool := "ANSI池"
if v&1 != 0 {
pool = "UTF16池"
}
fmt.Printf(" magic offset %-12d -> %-8s @%-12d %q\n", v, pool, v>>1, archive.ResolveString(v))
}
return
}
archive, err := platformpvf.Open(*pvfPath)
if err != nil {
fmt.Fprintln(os.Stderr, "open:", err)
os.Exit(1)
}
raw, err := archive.ReadRaw(*path)
if err != nil {
fmt.Fprintf(os.Stderr, "读不到 %s: %v\n", *path, err)
os.Exit(1)
}
n := len(raw) / 5
fmt.Printf("文件: %s\n原始字节: %d 令牌数: %d (%d 字节不是 5 的整数倍则有余数)\n\n",
*path, len(raw), n, len(raw)%5)
if len(raw)%5 != 0 {
fmt.Printf("【注意】余 %d 字节:令牌流长度不是 5 的倍数,这本身可疑。\n\n", len(raw)%5)
}
type tok struct {
i int
kind byte
value int32
text string
}
toks := make([]tok, 0, n)
for i := 0; i < n; i++ {
off := i * 5
kind := raw[off]
v := int32(uint32(raw[off+1]) | uint32(raw[off+2])<<8 | uint32(raw[off+3])<<16 | uint32(raw[off+4])<<24)
t := tok{i: i, kind: kind, value: v}
switch kind {
case 0, 2:
case 3, 5, 6, 7:
t.text = archive.ResolveString(int(v))
}
toks = append(toks, t)
}
kindName := func(k byte) string {
switch k {
case 0:
return "整数"
case 2:
return "浮点"
case 3:
return "行字符串"
case 5:
return "{5=``}"
case 6:
return "反引号串"
case 7:
return "{7=``}"
default:
return "未知"
}
}
poolOf := func(v int32) string {
if v < 0 {
return "-"
}
if v&1 != 0 {
return "UTF16池"
}
return "ANSI池"
}
show := func(i int) {
t := toks[i]
extra := ""
switch t.kind {
case 0:
extra = fmt.Sprintf("%d", t.value)
case 2:
extra = fmt.Sprintf("bits=0x%08X", uint32(t.value))
default:
// 空串要显式标出来 —— 这正是我们关心的
if t.text == "" {
extra = fmt.Sprintf("%s@%d \"\" ←【空串】", poolOf(t.value), t.value>>1)
} else {
extra = fmt.Sprintf("%s@%d %q", poolOf(t.value), t.value>>1, t.text)
}
}
fmt.Printf(" #%-4d type=%-2d %-8s raw=0x%08X %s\n", t.i, t.kind, kindName(t.kind), uint32(t.value), extra)
}
if *around != "" {
hit := false
for i, t := range toks {
if strings.Contains(t.text, *around) {
hit = true
lo, hi := i-*ctx, i+*ctx
if lo < 0 {
lo = 0
}
if hi >= len(toks) {
hi = len(toks) - 1
}
fmt.Printf("=== 命中 %q(token #%d),显示 #%d..#%d ===\n", *around, i, lo, hi)
for j := lo; j <= hi; j++ {
show(j)
}
fmt.Println()
}
}
if !hit {
fmt.Printf("没有 token 的解码文本包含 %q\n", *around)
}
return
}
for i := range toks {
show(i)
}
}