DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,167 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"longheng.io/server/internal/platform/logcrypt"
|
||||
"longheng.io/server/internal/platform/logkey"
|
||||
)
|
||||
|
||||
const (
|
||||
// packetLogHexLimit 是单行记录的 hex 截断长度(未变)。
|
||||
packetLogHexLimit = 2048
|
||||
// 下面三个沿用既有名字,值来自 logcrypt 的默认值,并在 openPacketLogger 里
|
||||
// 显式传进配置——测试与诊断代码一直以它们为"多久会落盘"的依据。
|
||||
packetLogQueueSize = logcrypt.DefaultQueueSize
|
||||
packetLogBufferSize = logcrypt.DefaultBufferSize
|
||||
packetLogFlushInterval = logcrypt.DefaultFlushInterval
|
||||
|
||||
// packetLogArchiveDirName 是锚点同级下的归档目录名。
|
||||
packetLogArchiveDirName = "packet"
|
||||
// packetLogArchiveExt 是加密归档的扩展名;.enc 明确表示"这是密文,别用文本工具开"。
|
||||
packetLogArchiveExt = ".log.enc"
|
||||
// packetLogDropMarkerKind 与旧实现保持同名,解密后的事件流与历史一致。
|
||||
packetLogDropMarkerKind = "packet-log-backpressure-dropped"
|
||||
|
||||
// 默认密钥路径,**相对于服务进程工作目录**(也就是 runtime/),刻意放在 logs/ 之外:
|
||||
// 只归档 logs/ 不会连带把密钥一起带走。公钥随分发包分发;私钥**绝不**放进分发包。
|
||||
defaultLogPublicKeyFile = "keys/log-public.key"
|
||||
defaultLogPrivateKeyFile = "keys/log-private.key"
|
||||
)
|
||||
|
||||
// packetLogger 把既有的日志行格式交给 logcrypt 做逐行加密与轮转。
|
||||
//
|
||||
// 落盘布局(锚点 path 默认是 logs/packet_log.txt):
|
||||
//
|
||||
// logs/packet/<YYYYMMDD>/packet_log-<YYYYMMDD>-NNN.log.enc 密文分片,单文件上限 100 MB
|
||||
// logs/packet/current.txt 当前分片指针(解密工具默认读它)
|
||||
//
|
||||
// 收件人公钥固化在二进制里(internal/platform/logkey),**服务端不需要任何密钥文件**。
|
||||
//
|
||||
// 为什么不把锚点本身当输出文件:records 目录是从锚点路径的**父目录**推导出来的
|
||||
// (defaultRecordDirFor),保持父目录不变,logs/records 才不会被搬走或孤立。
|
||||
//
|
||||
// 解密后的文本与加密前的历史格式逐字节一致,因此仓库里那些"从 packet_log 抄下来的"
|
||||
// 现场注释与既有分析脚本在解密之后仍然适用。
|
||||
type packetLogger struct {
|
||||
writer *logcrypt.Writer
|
||||
}
|
||||
|
||||
func openPacketLogger(path string) (*packetLogger, error) {
|
||||
if path == "" {
|
||||
return nil, nil
|
||||
}
|
||||
anchorDir := filepath.Dir(path)
|
||||
archiveDir := filepath.Join(anchorDir, packetLogArchiveDirName)
|
||||
prefix := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
|
||||
if prefix == "" || prefix == "." {
|
||||
prefix = "packet"
|
||||
}
|
||||
|
||||
// 收件人公钥**固化在二进制里**(internal/platform/logkey):用服务端的人不需要
|
||||
// 理解、放置或配置任何密钥文件,拿到就能跑。公钥只能加密、不能解密,写死是安全的。
|
||||
// 只有显式设了 DNFBRIDGE_LOG_PUBLIC_KEY_FILE 才用它覆盖(轮换/测试用)。
|
||||
recipient, err := logkey.ResolveRecipient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writer, err := logcrypt.Open(logcrypt.Config{
|
||||
Dir: archiveDir,
|
||||
Prefix: prefix,
|
||||
Ext: packetLogArchiveExt,
|
||||
Recipient: recipient,
|
||||
MaxBytes: int64(envInt("DNFBRIDGE_LOG_MAX_BYTES", logcrypt.DefaultMaxBytes)),
|
||||
RetentionDays: envInt("DNFBRIDGE_LOG_RETENTION_DAYS", logcrypt.DefaultRetentionDays),
|
||||
QueueSize: packetLogQueueSize,
|
||||
BufferSize: packetLogBufferSize,
|
||||
FlushInterval: packetLogFlushInterval,
|
||||
DropMarkerKind: packetLogDropMarkerKind,
|
||||
Logger: slog.Default(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := &packetLogger{writer: writer}
|
||||
logger.writeLine("=== LongHeng Go dnfbridge packet log started " + time.Now().UTC().Format(time.RFC3339) + " ===")
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
func (l *packetLogger) Close() error {
|
||||
if l == nil || l.writer == nil {
|
||||
return nil
|
||||
}
|
||||
return l.writer.Close()
|
||||
}
|
||||
|
||||
// Dropped 返回因写队列溢出被丢弃的行数。诊断用它判断"某段日志里的计数是不是下限"。
|
||||
func (l *packetLogger) Dropped() uint64 {
|
||||
if l == nil || l.writer == nil {
|
||||
return 0
|
||||
}
|
||||
return l.writer.Dropped()
|
||||
}
|
||||
|
||||
// Path 当前分片路径(测试与诊断用)。
|
||||
func (l *packetLogger) Path() string {
|
||||
if l == nil || l.writer == nil {
|
||||
return ""
|
||||
}
|
||||
return l.writer.Path()
|
||||
}
|
||||
|
||||
func (l *packetLogger) Log(direction string, kind string, data []byte, fields ...any) {
|
||||
if l == nil || l.writer == nil {
|
||||
return
|
||||
}
|
||||
hexData := data
|
||||
truncated := ""
|
||||
if len(hexData) > packetLogHexLimit {
|
||||
hexData = hexData[:packetLogHexLimit]
|
||||
truncated = " truncated=true"
|
||||
}
|
||||
line := fmt.Sprintf("%s kind=%s", direction, kind)
|
||||
for i := 0; i+1 < len(fields); i += 2 {
|
||||
line += fmt.Sprintf(" %v=%v", fields[i], fields[i+1])
|
||||
}
|
||||
line += fmt.Sprintf(" len=%d%s hex=%s", len(data), truncated, hex.EncodeToString(hexData))
|
||||
l.writeLine(line)
|
||||
}
|
||||
|
||||
func (l *packetLogger) Event(kind string, fields ...any) {
|
||||
if l == nil || l.writer == nil {
|
||||
return
|
||||
}
|
||||
line := "EVENT kind=" + kind
|
||||
for i := 0; i+1 < len(fields); i += 2 {
|
||||
line += fmt.Sprintf(" %v=%v", fields[i], fields[i+1])
|
||||
}
|
||||
l.writeLine(line)
|
||||
}
|
||||
|
||||
func (l *packetLogger) writeLine(line string) {
|
||||
if l == nil || l.writer == nil {
|
||||
return
|
||||
}
|
||||
l.writer.Write(time.Now().UTC().Format(time.RFC3339Nano) + " " + line + "\n")
|
||||
}
|
||||
|
||||
// logPrivateKeyPath 解析私钥路径。
|
||||
//
|
||||
// **服务端运行不需要私钥**(写日志只要公钥)。这里只为两种情况保留:
|
||||
// 测试解密断言,以及运营在自己服务器上做本机回读。分发包里绝不放私钥。
|
||||
func logPrivateKeyPath(anchorPath string) string {
|
||||
if explicit := strings.TrimSpace(os.Getenv("DNFBRIDGE_LOG_PRIVATE_KEY_FILE")); explicit != "" {
|
||||
return explicit
|
||||
}
|
||||
anchorDir := filepath.Dir(anchorPath)
|
||||
if anchorDir == "" || anchorDir == "." {
|
||||
return filepath.Join("keys", logcrypt.PrivateKeyFileName)
|
||||
}
|
||||
return filepath.Join(filepath.Dir(anchorDir), "keys", logcrypt.PrivateKeyFileName)
|
||||
}
|
||||
Reference in new issue
Block a user