Files
90CNServer/server/internal/services/dnfbridge/userinfo_current_equipment_wire.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

268 lines
8.6 KiB
Go

package dnfbridge
import (
"encoding/binary"
"encoding/hex"
"strings"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
)
func writeCurrentMode1EquipmentCreateRow(w *packetWriter, row currentMode1EquipmentObjectRow) {
// MCP/IDA sub_1D77560 creates and attaches current actor equipment objects.
actorSlot, _ := currentMode1EquipmentActorSlot(row)
w.writeByte(actorSlot)
w.writeUint32(row.itemID)
w.writeUint32(row.instance)
w.writeByte(row.extData)
w.writeUint16(row.durability)
w.writeUint32(row.linkedItemID)
if row.linkedItemID != 0 && actorSlot == 9 {
writeCurrentMode1EquipmentRawBlock(w, row.linkedRaw)
}
w.writeUint32(row.auxValue)
w.writeByte(row.auxByte)
w.writeByte(row.bindFlag)
w.writeUint16(row.marker16)
if row.readsRawBlocks {
writeCurrentMode1EquipmentRawBlock(w, row.rawA) // sub_225C960(type=3).
writeCurrentMode1EquipmentRawBlock(w, row.rawB) // sub_225C9B0(type=3).
}
if row.readsDurabilityOverrideU32 {
w.writeUint32(row.durabilityOverride)
}
w.writeByte(byte(len(row.vector1D6E020) / currentMode1EquipmentVectorRecordSize)) // sub_1D6E020 count.
w.writeBytes(row.vector1D6E020)
w.writeUint32(row.state112)
w.writeByte(byte(len(row.vector225CCA0) / 4)) // sub_225CCA0 count.
w.writeBytes(row.vector225CCA0)
// sub_1E636E0 and sub_1C61F40 are mandatory readers in the current EXE.
// Their values come from the same current 0x77 item state used by op25.
w.writeUint16(row.state1E636E0)
writeCurrentMode1EquipmentIndexedState(w, row.indexedState1C61F40)
w.writeByte(row.state120)
w.writeByte(row.state160)
w.writeByte(row.state128)
w.writeUint16(row.state140)
w.writeByte(row.state144)
w.writeByte(row.resourceByte)
w.writeByte(row.state168)
w.writeByte(row.state169)
w.writeByte(row.state170)
writeCurrentMode1EquipmentRawBlock(w, row.lateRawC) // sub_3457C50 raw block.
writeCurrentMode1EquipmentRawBlock(w, row.lateRawD) // sub_3457C50 raw block -> sub_2176DB0 a24.
w.writeByte(row.state183)
writeCurrentMode1EquipmentRawBlock(w, row.lateRawE) // sub_3457C50 raw block -> sub_2176DB0 a25.
}
func currentMode1EquipmentState112(equipped dnfrepo.EquipmentEntry, entry currentItemListEntry) uint32 {
if value, ok := sceneInventoryExtraUint(equipped.Extra, "mode1_state_112", "state_112", "value_after_sub_1d6e020"); ok {
return sceneInventoryClampUint32(value)
}
return binary.LittleEndian.Uint32(entry.data[0x38:0x3c])
}
func currentMode1EquipmentDurabilityWord(equipped dnfrepo.EquipmentEntry, entry currentItemListEntry) (uint16, bool) {
if value, ok := sceneInventoryExtraUint(equipped.Extra, "mode1_durability", "mode1_detail_state_word", "current_exe_detail_state_word", "mode1_state_word"); ok {
if value > uint64(^uint16(0)) {
return 0, false
}
return uint16(value), true
}
return binary.LittleEndian.Uint16(entry.data[0x0b:0x0d]), true
}
func currentMode1EquipmentExtraByteOr(extra map[string]string, fallback byte, keys ...string) byte {
if value, ok := sceneInventoryExtraUint(extra, keys...); ok && value <= 0xff {
return byte(value)
}
return fallback
}
func currentMode1EquipmentExtraUint16Or(extra map[string]string, fallback uint16, keys ...string) uint16 {
if value, ok := sceneInventoryExtraUint(extra, keys...); ok && value <= 0xffff {
return uint16(value)
}
return fallback
}
func currentMode1EquipmentExtraUint32Or(extra map[string]string, fallback uint32, keys ...string) uint32 {
if value, ok := sceneInventoryExtraUint(extra, keys...); ok && value <= uint64(^uint32(0)) {
return uint32(value)
}
return fallback
}
func currentMode1EquipmentDurabilityOverride(equipped dnfrepo.EquipmentEntry) uint32 {
if value, ok := sceneInventoryExtraUint(equipped.Extra, "mode1_durability_override", "mode1_type26_u32", "type26_u32"); ok {
return sceneInventoryClampUint32(value)
}
return 0xffffffff
}
func currentMode1EquipmentCreateRowWireSizeFor(row currentMode1EquipmentObjectRow) int {
size := currentMode1EquipmentCreateRowBaseWireSize +
len(row.vector1D6E020) +
len(row.vector225CCA0) +
currentMode1EquipmentIndexedStateVariableWireSize(row.indexedState1C61F40) +
len(row.lateRawC) +
len(row.lateRawD) +
len(row.lateRawE)
if actorSlot, ok := currentMode1EquipmentActorSlot(row); ok && row.linkedItemID != 0 && actorSlot == 9 {
size += currentMode1EquipmentRawBlockHeaderSize + len(row.linkedRaw)
}
if row.readsRawBlocks {
size += currentMode1EquipmentRawBlockHeaderSize*2 + len(row.rawA) + len(row.rawB)
}
if row.readsDurabilityOverrideU32 {
size += 4
}
return size
}
func currentMode1EquipmentActorSlot(row currentMode1EquipmentObjectRow) (byte, bool) {
// sub_1D77560 stores presence in a 33-byte bitmap and cleans slots 0..32.
// sub_2504C10 forwards the byte to sub_2467A60, which stores the object at
// actorArray[target]. The target is the current runtime equipment slot.
runtimeSlot, ok := currentMode1EquipmentRuntimeType(row)
if !ok || runtimeSlot > 32 {
return 0, false
}
return runtimeSlot, true
}
func currentMode1EquipmentRuntimeType(row currentMode1EquipmentObjectRow) (byte, bool) {
if !row.equipmentTypeKnown || row.equipmentType > uint64(^uint8(0)) {
return 0, false
}
return byte(row.equipmentType), true
}
func currentMode1EquipmentIndexedStateFromCurrentItem(entry currentItemListEntry) currentMode1EquipmentIndexedState {
state := currentMode1EquipmentIndexedState{
activeValue: entry.data[0x51],
selectedIndex: entry.data[0x52],
selectedValue: entry.data[0x53],
selectedRecord: currentMode1EquipmentIndexedStateRecord{
value0: entry.data[0x54],
value1: entry.data[0x55],
value2: entry.data[0x56],
},
}
wantCount := int(entry.data[0x47])
if wantCount > currentMode1EquipmentIndexedStateMaxRecords {
state.currentItemMismatch = true
return state
}
for index := 0; index < currentMode1EquipmentIndexedStateMaxRecords; index++ {
value0 := entry.data[0x48+index]
if value0 == 0 {
continue
}
state.records = append(state.records, currentMode1EquipmentIndexedStateRecord{
value0: value0,
value1: entry.data[0x4b+index],
value2: entry.data[0x4e+index],
})
}
if len(state.records) != wantCount {
state.currentItemMismatch = true
return state
}
if wantCount > 0 && state.selectedIndex != 0xff {
state.hasSelectedRecord = true
}
return state
}
func writeCurrentMode1EquipmentIndexedState(w *packetWriter, state currentMode1EquipmentIndexedState) {
w.writeByte(byte(len(state.records)))
for _, record := range state.records {
w.writeByte(record.value0)
w.writeByte(record.value1)
w.writeByte(record.value2)
}
if len(state.records) == 0 {
return
}
w.writeByte(state.activeValue)
w.writeByte(state.selectedIndex)
if !state.hasSelectedRecord {
return
}
w.writeByte(state.selectedValue)
w.writeByte(state.selectedRecord.value0)
w.writeByte(state.selectedRecord.value1)
w.writeByte(state.selectedRecord.value2)
}
func currentMode1EquipmentIndexedStateVariableWireSize(state currentMode1EquipmentIndexedState) int {
size := len(state.records) * 3
if len(state.records) == 0 {
return size
}
size += 2
if state.hasSelectedRecord {
size += 4
}
return size
}
func writeCurrentMode1EquipmentRawBlock(w *packetWriter, data []byte) {
w.writeUint32(uint32(len(data)))
w.writeBytes(data)
}
func currentMode1EquipmentVectorRecords(extra map[string]string, keys ...string) []byte {
data := currentMode1EquipmentExtraHex(extra, currentMode1EquipmentVectorRecordSize*currentMode1EquipmentVectorRecordMaxCount, keys...)
if len(data) == 0 || len(data)%currentMode1EquipmentVectorRecordSize != 0 {
return nil
}
return data
}
func currentMode1EquipmentDwordVector(extra map[string]string, keys ...string) []byte {
data := currentMode1EquipmentExtraHex(extra, 4*currentMode1EquipmentDwordVectorMaxCount, keys...)
if len(data) == 0 || len(data)%4 != 0 {
return nil
}
return data
}
func currentMode1EquipmentDwordVectorFromCurrentItem(
entry currentItemListEntry,
extra map[string]string,
keys ...string,
) []byte {
if data := currentMode1EquipmentDwordVector(extra, keys...); len(data) != 0 {
return data
}
count := int(entry.data[currentEquipmentVectorOffset])
if count <= 0 || count > currentMode1EquipmentDwordVectorMaxCount {
return nil
}
start := currentEquipmentVectorOffset + 1
end := start + count*4
return append([]byte(nil), entry.data[start:end]...)
}
func currentMode1EquipmentExtraHex(extra map[string]string, maxLen int, keys ...string) []byte {
if maxLen <= 0 {
return nil
}
for _, key := range keys {
raw := strings.TrimSpace(extra[key])
if raw == "" {
continue
}
raw = strings.NewReplacer(" ", "", "\t", "", "\r", "", "\n", "", "0x", "", "0X", "").Replace(raw)
decoded, err := hex.DecodeString(raw)
if err != nil || len(decoded) == 0 || len(decoded) > maxLen {
continue
}
return decoded
}
return nil
}