DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

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

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

No files matched your search

@@ -0,0 +1,107 @@
package dnfbridge
import (
"errors"
"fmt"
)
var (
errDungeonInfoPairGroupCount = errors.New("dnf dungeon-info pair group count exceeds current EXE range")
errDungeonInfoOpaqueCount = errors.New("dnf dungeon-info opaque entry count exceeds current EXE range")
)
type currentDungeonInfoPair struct {
First byte
Second byte
}
// currentDungeonInfoOpaqueEntry preserves the current EXE's trailing
// u32/u8/u8 records without assigning semantics that PVF/EXE evidence has not
// established yet.
type currentDungeonInfoOpaqueEntry struct {
Value uint32
ParamA byte
ParamB byte
}
type currentDungeonInfo struct {
DungeonID uint32
Difficulty byte
EntryOption uint16
MazeIndex byte
BossX byte
BossY byte
HellPartyRoomX byte
HellPartyRoomY byte
DungeonMode byte
PairGroups [][]currentDungeonInfoPair
HellPartyValue uint16
DungeonValue uint16
Value2 byte
FlagA byte
PacketSeed uint32
ParamA byte
ParamB byte
ParamC byte
TailFlag0 byte
TailFlag1 byte
TailFlag2 byte
OpaqueEntries []currentDungeonInfoOpaqueEntry
TailMode byte
TailValue0 uint16
TailValue1 uint16
}
func (packet currentDungeonInfo) Build() ([]byte, error) {
if len(packet.PairGroups) > 0xff {
return nil, fmt.Errorf("%w: count=%d", errDungeonInfoPairGroupCount, len(packet.PairGroups))
}
for groupIndex, group := range packet.PairGroups {
if len(group) > 0xff {
return nil, fmt.Errorf("%w: group=%d count=%d", errDungeonInfoPairGroupCount, groupIndex, len(group))
}
}
if len(packet.OpaqueEntries) > 0xff {
return nil, fmt.Errorf("%w: count=%d", errDungeonInfoOpaqueCount, len(packet.OpaqueEntries))
}
var writer packetWriter
writer.writeUint32(packet.DungeonID)
writer.writeByte(packet.Difficulty)
writer.writeUint16(packet.EntryOption)
writer.writeByte(packet.MazeIndex)
writer.writeByte(packet.BossX)
writer.writeByte(packet.BossY)
writer.writeByte(packet.HellPartyRoomX)
writer.writeByte(packet.HellPartyRoomY)
writer.writeByte(packet.DungeonMode)
writer.writeByte(byte(len(packet.PairGroups)))
for _, group := range packet.PairGroups {
writer.writeByte(byte(len(group)))
for _, pair := range group {
writer.writeByte(pair.First)
writer.writeByte(pair.Second)
}
}
writer.writeUint16(packet.HellPartyValue)
writer.writeUint16(packet.DungeonValue)
writer.writeByte(packet.Value2)
writer.writeByte(packet.FlagA)
writer.writeUint32(packet.PacketSeed)
writer.writeByte(packet.ParamA)
writer.writeByte(packet.ParamB)
writer.writeByte(packet.ParamC)
writer.writeByte(packet.TailFlag0)
writer.writeByte(packet.TailFlag1)
writer.writeByte(packet.TailFlag2)
writer.writeByte(byte(len(packet.OpaqueEntries)))
for _, entry := range packet.OpaqueEntries {
writer.writeUint32(entry.Value)
writer.writeByte(entry.ParamA)
writer.writeByte(entry.ParamB)
}
writer.writeByte(packet.TailMode)
writer.writeUint16(packet.TailValue0)
writer.writeUint16(packet.TailValue1)
return writer.bytes(), nil
}