Files
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

371 lines
12 KiB
Go

package dnfbridge
import (
"context"
dnfcharstat "longheng.io/server/internal/modules/dnf/charstat"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
"strconv"
)
const csharpLegacyUserInfoKind = "legacy_userinfo"
func csharpLegacyUserInfoInitPackets() []csharpSelectInitPacket {
// 0x015d 是角色栏解锁相关通知,不能放进选择角色主动初始化;基础 8 栏由 roster 字段默认开放。
// C# registry 中部分上下文刷新包仅保留 builder,不放入 active init,避免进入角色后重复推栏位/上下文状态。
ids := []uint16{
0x0023, 0x0047,
0x0057, 0x0058, 0x0059, 0x005b, 0x005c, 0x005f, 0x005a,
0x0060, 0x0064, 0x0067, 0x006a, 0x006b, 0x0073, 0x007a,
0x0080, 0x0081, 0x0082, 0x0083, 0x0085, 0x0086, 0x0087,
0x0088, 0x0089, 0x008f, 0x0090, 0x0091, 0x0092, 0x0098,
0x009b, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00aa, 0x00b0,
0x00b6, 0x00bc, 0x00c8, 0x00c9, 0x00cf, 0x00d0, 0x00d1,
0x00d2, 0x00d3, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00dc,
0x00dd, 0x00df, 0x00e0, 0x00e3, 0x00e6, 0x00eb, 0x00fe,
0x00ff, 0x0103, 0x0109, 0x010c, 0x0114, 0x0115, 0x0117,
0x0118, 0x011d, 0x0126, 0x012a, 0x012d, 0x0154, 0x0159,
0x017c, 0x0182, 0x0183, 0x0184, 0x0186, 0x0192,
0x022d, 0x022e, 0x0237, 0x0238, 0x0253, 0x0254, 0x0255,
0x025b, 0x026e, 0x0274, 0x0275, 0x0276, 0x0287, 0x028a,
0x028b, 0x029f, 0x02a9, 0x02aa, 0x02b0, 0x02bc, 0x02c1,
0x02d2, 0x02d3, 0x02d8, 0x02ef, 0x0311, 0x0312, 0x031d,
0x0324, 0x0336, 0x034b,
0x034c, 0x034d, 0x034e, 0x0352, 0x0354, 0x0355, 0x0359,
0x036b, 0x037b, 0x0393, 0x03c7, 0x03cd, 0x03d0,
0x03e6, 0x03e7, 0x03e8, 0x03f3, 0x03fd, 0x0400, 0x0406,
0x0407, 0x0408, 0x0409, 0x040a, 0x040b, 0x040c, 0x040d,
0x040e, 0x040f, 0x0410, 0x0411, 0x0412, 0x0413, 0x0415,
0x0416, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d,
0x041e, 0x0424, 0x0425, 0x0428, 0x0429, 0x042a, 0x042c,
0x042d, 0x042e, 0x0430, 0x0435, 0x043e, 0x043f, 0x0440,
0x044e, 0x044f, 0x0457, 0x0458, 0x0459, 0x045a, 0x045f,
0x0462, 0x0469, 0x046b, 0x046c, 0x046e, 0x046f, 0x0470,
0x048c, 0x048e, 0x048f, 0x04a6, 0x04a7, 0x04a9, 0x04aa,
0x04b0, 0x04b1, 0x04b2, 0x04c6, 0x04c7, 0x04c8, 0x04ca,
0x04d4, 0x04d5, 0x04da, 0x04db, 0x04df, 0x04e2, 0x04ec,
0x04f0, 0x04f1, 0x04f8, 0x04fe, 0x04ff, 0x0508, 0x050a,
0x050b, 0x050c, 0x050d, 0x0514, 0x0515, 0x0516, 0x0517,
0x0518, 0x052b, 0x052c, 0x052f, 0x0532, 0x0533, 0x0534,
0x0547, 0x0549,
}
packets := make([]csharpSelectInitPacket, 0, len(ids))
for _, id := range ids {
packets = append(packets, csharpSelectInitPacket{class: 0, msgID: id, kind: csharpLegacyUserInfoKind})
}
return packets
}
type csharpLegacyUserInfoReader struct {
ctx context.Context
repo dnfrepo.LegacyUserInfoRepository
characterID string
service *Service
session *gameSession
pvfStats dnfcharstat.Vector
hasPVFStats bool
// suppressActorSkillResources 用于「为已存在的 actor 重新绑定上下文」的场景:
// 这种重绑不能重播已学技能资源,否则客户端会看到技能页被再次点亮。
// (支援兵的 mode1 就是这种场景,见 dungeon_companion_balance.go。)
suppressActorSkillResources bool
// equipmentSnapshot 非空时用这份冻结的装备记录替代仓储读取 —— 支援兵的
// 临时平衡装备只存在于运行态,没有落库。
equipmentSnapshot *dnfrepo.EquipmentRecord
// peerSkills 是决斗场房间演员专用的技能页投影(见 pvp_skills.go)。
// 非空时覆盖常规技能页;为空时沿用本树原有的 currentUserInfoSkillPages。
peerSkills []byte
loaded bool
}
func (s *Service) buildCSharpLegacyUserInfoBody(ctx context.Context, session *gameSession, repo dnfrepo.LegacyUserInfoRepository, charID uint16, msgID uint16) ([]byte, bool) {
if repo == nil {
return nil, false
}
reader := csharpLegacyUserInfoReader{
ctx: ctx,
repo: repo,
characterID: strconv.Itoa(int(charID)),
service: s,
session: session,
}
var body []byte
switch msgID {
case 0x0023:
body = reader.build23()
case 0x0047:
body = reader.build47()
case 0x0057:
body = reader.build57()
case 0x0058:
body = reader.build58()
case 0x0059:
body = reader.build59()
case 0x005b:
body = reader.build5B()
case 0x005c:
body = reader.build5C()
case 0x005f:
body = reader.build5F()
case 0x005a, 0x0082, 0x00e3, 0x0103, 0x0114, 0x0115, 0x012d, 0x0186, 0x0192, 0x01d5, 0x01d6, 0x0376:
return nil, true
case 0x0060:
body = reader.build60()
case 0x0064:
body = reader.buildOneU16("legacy_character_userinfo64_state", "object_key")
case 0x0067:
body = reader.buildTwoU32("legacy_character_userinfo67_state", "value_a", "value_b")
case 0x006a:
body = reader.build6A()
case 0x006b:
body = reader.buildOneU16("legacy_character_userinfo6b_state", "object_key")
case 0x0073:
body = reader.build73()
case 0x007a:
body = reader.build7A()
case 0x0080:
body = reader.build80()
case 0x0081:
body = reader.buildOneU16("legacy_character_userinfo81_state", "value")
case 0x0083:
body = reader.buildOneU16("legacy_character_userinfo83_state", "value")
case 0x0085:
body = reader.buildOneU16("legacy_character_userinfo85_state", "object_key")
case 0x0086:
body = reader.build86()
case 0x0087:
body = reader.buildOneU16("legacy_character_userinfo87_state", "value")
case 0x0088:
body = reader.build88()
case 0x0089:
body = reader.buildOneU8("legacy_character_userinfo89_state", "state")
case 0x008f:
body = reader.build8F()
case 0x0090:
body = reader.build90()
case 0x0091:
body = reader.build91()
case 0x0092:
body = reader.buildOneU8("legacy_character_userinfo92_state", "mode_flag")
case 0x0098:
body = reader.build98()
case 0x009b:
body = reader.buildTwoU32("legacy_character_userinfo9b_state", "value_a", "value_b")
case 0x00a0:
body = reader.buildA0()
case 0x00a1:
body = reader.buildA1()
case 0x00a2:
body = reader.buildA2()
case 0x00a3:
body = reader.buildOneU32("legacy_character_userinfoa3_state", "value")
case 0x00aa:
body = reader.buildAA()
case 0x00b0:
body = reader.buildOneU8("legacy_character_userinfob0_state", "enabled")
case 0x00b6:
body = reader.buildB6()
case 0x00bc:
body = reader.buildOneU8("legacy_character_userinfobc_state", "state")
case 0x00c8:
body = reader.buildOneU8("legacy_character_userinfoc8_state", "delta")
case 0x00c9:
body = reader.buildC9()
case 0x00cf:
body = reader.buildOneU32("legacy_character_userinfocf_state", "value")
case 0x00d0:
body = reader.buildD0()
case 0x00d1:
body = reader.buildD1()
case 0x00d2:
body = reader.buildD2()
case 0x00d3:
body = reader.buildD3()
case 0x00d5:
body = reader.buildD5Like("legacy_character_userinfod5_state")
case 0x00d6:
body = reader.buildD6()
case 0x00d7:
body = reader.buildD7()
case 0x00d8:
body = reader.buildD8()
case 0x00dc:
body = reader.buildOneU32("legacy_character_userinfodc_state", "value")
case 0x00dd:
body = reader.buildTwoU32("legacy_character_userinfodd_state", "value_a", "value_b")
case 0x00df:
body = reader.buildDF()
case 0x00e0:
body = reader.buildE0()
case 0x00e6:
body = reader.buildOneU8("legacy_character_userinfoe6_state", "value")
case 0x00eb:
body = reader.buildOneU16("legacy_character_userinfoeb_state", "value")
case 0x00fe:
body = reader.buildFE()
case 0x00ff:
body = reader.buildD5Like("legacy_character_userinfoff_state")
case 0x0109:
body = reader.build109()
case 0x010c:
body = reader.buildOneU16("legacy_character_userinfo10c_state", "value")
case 0x0117:
body = reader.buildOneU32("legacy_character_userinfo117_state", "value")
case 0x0118:
body = reader.build118()
case 0x011d:
body = reader.build11D()
case 0x0126:
body = reader.build126()
case 0x012a:
body = reader.buildD5Like("legacy_character_userinfo12a_state")
case 0x0154:
body = reader.build154()
case 0x0159:
body = reader.build159()
case 0x017c:
body = reader.build17C()
case 0x0182:
body = reader.build182()
case 0x0183:
body = reader.build183()
case 0x0184:
body = reader.build184()
case 0x01bf:
body = reader.build1BF()
case 0x022d:
body = reader.build22D()
case 0x022e:
body = reader.build22E()
case 0x0237:
body = reader.buildConditionalOneU32("legacy_character_userinfo237_state", "value")
case 0x0238:
body = reader.buildConditionalOneU32("legacy_character_userinfo238_state", "value")
case 0x0253:
body = reader.build253()
case 0x0254:
body = reader.build254()
case 0x0255:
body = reader.buildConditionalOneU16("legacy_character_userinfo255_state", "value")
case 0x025b:
body = reader.build25B()
case 0x026e:
body = reader.build26E()
case 0x0274:
body = reader.buildConditionalOneU32("legacy_character_userinfo274_state", "value")
case 0x0275:
body = reader.build275()
case 0x0276:
body = reader.build276()
case 0x0287:
body = reader.build287()
case 0x028a:
body = reader.build28A()
case 0x028b:
body = reader.build28B()
case 0x029f:
body = reader.build29F()
case 0x02a9:
body = reader.build2A9()
case 0x02aa:
body = reader.build2AA()
case 0x02b0:
body = reader.build2B0()
case 0x02bc:
body = reader.build2BC()
case 0x02c1:
body = reader.build2C1()
case 0x02d2:
body = reader.build2D2()
case 0x02d3:
body = reader.buildOneU32IfPresent("legacy_character_userinfo2d3_state", "value")
case 0x02d8:
body = reader.buildOneU32IfPresent("legacy_character_userinfo2d8_state", "value")
case 0x02ef:
body = reader.buildOneU32IfPresent("legacy_character_userinfo2ef_state", "value")
case 0x031d:
body = reader.buildOneU8IfPresent("legacy_character_userinfo31d_state", "value")
case 0x0324:
body = reader.build324()
case 0x0327:
body = reader.build327()
case 0x0329:
body = reader.build329()
case 0x0336:
body = reader.build336()
case 0x034b:
body = reader.build34B()
case 0x034c:
body = reader.build34CText()
case 0x034d:
body = reader.build34DValue()
case 0x034e:
body = reader.build34EByte()
case 0x0352:
body = reader.build352()
case 0x0354:
body = reader.build354()
case 0x0355:
body = reader.buildOneU32IfPresent("legacy_character_userinfo355_state", "value")
case 0x0359:
body = reader.build359()
case 0x036b:
body = reader.build36B()
case 0x0161:
body = reader.buildRawFixed(0x0161, 1)
case 0x01d4:
body = reader.buildRawByteCountList(0x01d4, 8)
case 0x01d7:
body = reader.buildRawByteCountList(0x01d7, 7)
case 0x01d8:
body = reader.buildRawByteCountList(0x01d8, 7)
case 0x01d9:
body = reader.buildRawFixed(0x01d9, 1)
case 0x0343:
body = reader.buildRawFixed(0x0343, 1)
case 0x0344:
body = reader.buildRawFixed(0x0344, 24)
case 0x0373:
body = reader.buildRawFixed(0x0373, 8)
case 0x0374:
body = reader.build374()
case 0x0375:
body = reader.buildRawFixed(0x0375, 38)
case 0x0377:
body = reader.buildRawFixed(0x0377, 8)
case 0x0378:
body = reader.buildRawFixed(0x0378, 5)
case 0x0379:
body = reader.build379()
case 0x037a:
body = reader.build37A()
case 0x037b:
body = reader.build37B()
case 0x0393:
body = reader.build393()
case 0x03cd:
body = reader.build3CD()
case 0x03e6:
body = reader.buildOneU8IfPresent("legacy_character_userinfo3e6_state", "value")
case currentRaidDailyAvailabilityMsgID:
// Fixture/template path: the live send in sendCurrentRaidDailyAvailability
// reads the account's real counters, this one only needs the fixed shape.
structured, err := buildCurrentRaidDailyAvailabilityBody(defaultCurrentRaidDailyAvailabilityRows())
if err != nil {
return nil, false
}
body = structured
reader.loaded = true
default:
if expectedLength, ok := csharpRawFixedUserInfoLength(msgID); ok {
body = reader.buildRawFixed(msgID, expectedLength)
} else if csharpRawUserInfoBody(msgID) {
body = reader.buildRawBody(msgID)
} else {
return nil, false
}
}
if !reader.loaded {
return nil, false
}
return body, true
}