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,279 @@
|
||||
package pvf
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type fileItem struct {
|
||||
nameOffset int
|
||||
pathOffset int
|
||||
chunkIndex int
|
||||
dataOffset int
|
||||
dataSize int
|
||||
dataType int
|
||||
}
|
||||
|
||||
type groupItem struct {
|
||||
compressedSize int
|
||||
originalSize int
|
||||
}
|
||||
|
||||
type pvfHeader struct {
|
||||
plain [headerSize]byte
|
||||
fileCount int
|
||||
bodySize int
|
||||
groupCount int
|
||||
hashSize int
|
||||
nameSize int
|
||||
}
|
||||
|
||||
func (a *Archive) parse() error {
|
||||
if len(a.data) < headerSize {
|
||||
return fmt.Errorf("%w: header too short", ErrInvalidArchive)
|
||||
}
|
||||
if err := a.parseNKPI(); err == nil {
|
||||
return nil
|
||||
}
|
||||
if err := a.parseProtectedNKPI(); err == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%w: unsupported archive format", ErrInvalidArchive)
|
||||
}
|
||||
|
||||
func (a *Archive) parseNKPI() error {
|
||||
a.resetParseState()
|
||||
header := append([]byte(nil), a.data[:headerSize]...)
|
||||
decryptGuard(header)
|
||||
decrypt("HeaD", header)
|
||||
if binary.LittleEndian.Uint32(header[0:4]) != magicSignature {
|
||||
return fmt.Errorf("%w: bad signature", ErrInvalidArchive)
|
||||
}
|
||||
pvfHeader := parseHeaderSizes(header)
|
||||
if err := validateHeader(pvfHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.parseNKPITables(pvfHeader, false)
|
||||
}
|
||||
|
||||
func (a *Archive) parseProtectedNKPI() error {
|
||||
a.resetParseState()
|
||||
header := append([]byte(nil), a.data[:headerSize]...)
|
||||
decryptProtected("hEAd", header)
|
||||
if binary.LittleEndian.Uint32(header[0:4]) != magicSignature {
|
||||
return fmt.Errorf("%w: bad protected signature", ErrInvalidArchive)
|
||||
}
|
||||
pvfHeader := parseHeaderSizes(header)
|
||||
if err := validateHeader(pvfHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.parseNKPITables(pvfHeader, true)
|
||||
}
|
||||
|
||||
func (a *Archive) parseNKPITables(header pvfHeader, protected bool) error {
|
||||
pos := headerSize
|
||||
tableOffset := pos
|
||||
tableSize, err := checkedMul(header.fileCount, fileItemSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pos += tableSize
|
||||
pos += header.hashSize
|
||||
nameOffset := pos
|
||||
pos += header.nameSize
|
||||
groupOffset := pos
|
||||
groupSize, err := checkedMul(header.groupCount, groupItemSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pos += groupSize
|
||||
if pos < 0 || pos > len(a.data) || header.bodySize > len(a.data)-pos {
|
||||
return fmt.Errorf("%w: section sizes exceed archive", ErrInvalidArchive)
|
||||
}
|
||||
a.header = header
|
||||
a.tableOff = tableOffset
|
||||
a.bodyOff = pos
|
||||
|
||||
nameBytes := append([]byte(nil), a.data[nameOffset:nameOffset+header.nameSize]...)
|
||||
if protected {
|
||||
if err := a.buildPStringBuffers(nameBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
a.format = FormatProtectedNKPI
|
||||
} else {
|
||||
a.buildStringBuffers(nameBytes)
|
||||
a.format = FormatNKPI
|
||||
}
|
||||
|
||||
groupBytes := append([]byte(nil), a.data[groupOffset:groupOffset+groupSize]...)
|
||||
if protected {
|
||||
decryptProtected("grpi", groupBytes)
|
||||
} else {
|
||||
decrypt("GRPI", groupBytes)
|
||||
}
|
||||
if err := a.parseGroups(groupBytes, header.groupCount); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.parseFiles(tableOffset, header.fileCount)
|
||||
}
|
||||
|
||||
func (a *Archive) resetParseState() {
|
||||
a.format = ""
|
||||
a.items = nil
|
||||
a.groups = nil
|
||||
a.header = pvfHeader{}
|
||||
a.pathIdx = make(map[string]int)
|
||||
a.bodyOff = 0
|
||||
a.strA = nil
|
||||
a.strW = nil
|
||||
}
|
||||
|
||||
func parseHeaderSizes(header []byte) pvfHeader {
|
||||
var plain [headerSize]byte
|
||||
copy(plain[:], header)
|
||||
return pvfHeader{
|
||||
plain: plain,
|
||||
fileCount: readInt32(header[24:28]),
|
||||
bodySize: readInt32(header[32:36]),
|
||||
groupCount: readInt32(header[36:40]),
|
||||
hashSize: readInt32(header[40:44]),
|
||||
nameSize: readInt32(header[44:48]),
|
||||
}
|
||||
}
|
||||
|
||||
func validateHeader(header pvfHeader) error {
|
||||
if header.fileCount < 0 || header.bodySize < 0 || header.groupCount < 0 || header.hashSize < 0 || header.nameSize < 0 {
|
||||
return fmt.Errorf("%w: negative header size", ErrInvalidArchive)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Archive) parseGroups(data []byte, count int) error {
|
||||
a.groups = make([]groupItem, 0, count)
|
||||
for idx := 0; idx < count; idx++ {
|
||||
off := idx * groupItemSize
|
||||
compressedSize := readInt32(data[off : off+4])
|
||||
originalSize := readInt32(data[off+4 : off+8])
|
||||
if compressedSize < 0 || originalSize < 0 {
|
||||
return fmt.Errorf("%w: negative group size", ErrInvalidArchive)
|
||||
}
|
||||
a.groups = append(a.groups, groupItem{
|
||||
compressedSize: compressedSize,
|
||||
originalSize: originalSize,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Archive) parseFiles(offset, count int) error {
|
||||
a.items = make([]fileItem, 0, count)
|
||||
for idx := 0; idx < count; idx++ {
|
||||
off := offset + idx*fileItemSize
|
||||
if off+fileItemSize > len(a.data) {
|
||||
return fmt.Errorf("%w: file table exceeds archive", ErrInvalidArchive)
|
||||
}
|
||||
item := fileItem{
|
||||
nameOffset: readInt32(a.data[off : off+4]),
|
||||
pathOffset: readInt32(a.data[off+4 : off+8]),
|
||||
chunkIndex: readInt32(a.data[off+8 : off+12]),
|
||||
dataOffset: readInt32(a.data[off+12 : off+16]),
|
||||
dataSize: readInt32(a.data[off+16 : off+20]),
|
||||
dataType: readInt32(a.data[off+20 : off+24]),
|
||||
}
|
||||
name := a.resolveString(item.nameOffset)
|
||||
dir := a.resolveString(item.pathOffset)
|
||||
archivePath := joinArchivePath(dir, name)
|
||||
a.items = append(a.items, item)
|
||||
if archivePath != "" {
|
||||
key := pathKey(archivePath)
|
||||
if _, exists := a.pathIdx[key]; !exists {
|
||||
a.pathIdx[key] = idx
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Archive) buildStringBuffers(nameBytes []byte) {
|
||||
if len(nameBytes) < 16 {
|
||||
return
|
||||
}
|
||||
idx := 8
|
||||
a.strA = decryptStringBuffer(nameBytes, &idx, "sTrA", 0xAA74472E)
|
||||
a.strW = decryptStringBuffer(nameBytes, &idx, "sTrW", 0x9A82F037)
|
||||
}
|
||||
|
||||
func (a *Archive) buildPStringBuffers(nameBytes []byte) error {
|
||||
if len(nameBytes) < 16 {
|
||||
return nil
|
||||
}
|
||||
idx := 8
|
||||
strA, err := decryptPStringBuffer(nameBytes, &idx, "StRa", 0xAA74472E)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode protected pvf strA: %w", err)
|
||||
}
|
||||
strW, err := decryptPStringBuffer(nameBytes, &idx, "StRw", 0x9A82F037)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode protected pvf strW: %w", err)
|
||||
}
|
||||
a.strA = strA
|
||||
a.strW = strW
|
||||
return nil
|
||||
}
|
||||
|
||||
func decryptStringBuffer(data []byte, idx *int, key string, xorConst uint32) []byte {
|
||||
if *idx+8 > len(data) {
|
||||
return nil
|
||||
}
|
||||
cnt1 := binary.LittleEndian.Uint32(data[*idx : *idx+4])
|
||||
*idx += 4
|
||||
*idx += 4
|
||||
encSize := int(cnt1 ^ xorConst)
|
||||
if encSize <= 0 || *idx+encSize > len(data) {
|
||||
return nil
|
||||
}
|
||||
encrypted := append([]byte(nil), data[*idx:*idx+encSize]...)
|
||||
*idx += encSize
|
||||
decrypt2(key, encrypted)
|
||||
out, err := zlibBytes(encrypted)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func decryptPStringBuffer(data []byte, idx *int, key string, xorConst uint32) ([]byte, error) {
|
||||
if *idx+8 > len(data) {
|
||||
return nil, nil
|
||||
}
|
||||
encodedSize := binary.LittleEndian.Uint32(data[*idx : *idx+4])
|
||||
encodedOriginalSize := binary.LittleEndian.Uint32(data[*idx+4 : *idx+8])
|
||||
*idx += 8
|
||||
encSize := int(encodedSize ^ xorConst)
|
||||
if encSize <= 0 || *idx+encSize > len(data) {
|
||||
return nil, fmt.Errorf("%w: invalid protected string buffer size %d", ErrInvalidArchive, encSize)
|
||||
}
|
||||
encrypted := append([]byte(nil), data[*idx:*idx+encSize]...)
|
||||
*idx += encSize
|
||||
decryptPString(key, encrypted)
|
||||
out, err := zlibBytes(encrypted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if want := int(encodedOriginalSize ^ uint32(encSize)); want >= 0 && want != len(out) {
|
||||
return nil, fmt.Errorf("%w: protected string buffer size mismatch: want %d got %d", ErrInvalidArchive, want, len(out))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func readInt32(data []byte) int {
|
||||
return int(int32(binary.LittleEndian.Uint32(data)))
|
||||
}
|
||||
|
||||
func checkedMul(left, right int) (int, error) {
|
||||
maxInt := int(^uint(0) >> 1)
|
||||
if left < 0 || right < 0 || (right != 0 && left > maxInt/right) {
|
||||
return 0, fmt.Errorf("%w: section size overflow", ErrInvalidArchive)
|
||||
}
|
||||
return left * right, nil
|
||||
}
|
||||
Reference in new issue
Block a user