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

+283
View File
@@ -0,0 +1,283 @@
package npk
import (
"bytes"
"compress/zlib"
"encoding/binary"
"image/color"
"os"
"testing"
)
func putInt32(data *[]byte, values ...int32) {
for _, value := range values {
var raw [4]byte
binary.LittleEndian.PutUint32(raw[:], uint32(value))
*data = append(*data, raw[:]...)
}
}
func makeIMG(version int32, headers, payload []byte, imagesSize int32) []byte {
return makeIMGCount(version, headers, payload, imagesSize, 1)
}
func makeIMGCount(version int32, headers, payload []byte, imagesSize, count int32) []byte {
data := make([]byte, 0, 32+len(headers)+len(payload)+128)
if version == 1 {
data = append(data, []byte(imgMagicOld)...)
data = append(data, 0)
data = append(data, 0, 0)
putInt32(&data, 0, version, count)
} else {
data = append(data, []byte(imgMagic)...)
data = append(data, 0)
putInt32(&data, imagesSize, 0, version, count)
}
data = append(data, headers...)
data = append(data, payload...)
return data
}
func imageHeader(format, extra, width, height, size int32) []byte {
data := make([]byte, 0, 36)
putInt32(&data, format, extra, width, height, size, 0, 0, width, height)
return data
}
func onePixelIMG(format int32, raw []byte) []byte {
header := imageHeader(format, ExtraNone, 1, 1, int32(len(raw)))
return makeIMG(2, header, raw, int32(len(header)))
}
func decodeOnePixel(t *testing.T, data []byte) color.NRGBA {
t.Helper()
parsed, err := ParseIMGAt(bytes.NewReader(data), 0, int64(len(data)))
if err != nil {
t.Fatal(err)
}
decoded, err := DecodeImage(bytes.NewReader(data), 0, int64(len(data)), parsed, 0)
if err != nil {
t.Fatal(err)
}
pixel, ok := decoded.At(0, 0).(color.NRGBA)
if !ok {
return color.NRGBAModel.Convert(decoded.At(0, 0)).(color.NRGBA)
}
return pixel
}
func TestIMGRawFormatsAndZlib(t *testing.T) {
tests := []struct {
name string
data []byte
want color.NRGBA
}{
{name: "1555", data: onePixelIMG(Format1555, []byte{0, 0xfc}), want: color.NRGBA{R: 255, A: 255}},
{name: "4444", data: onePixelIMG(Format4444, []byte{0, 0xff}), want: color.NRGBA{R: 240, A: 240}},
{name: "8888", data: onePixelIMG(Format8888, []byte{3, 2, 1, 255}), want: color.NRGBA{R: 1, G: 2, B: 3, A: 255}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := decodeOnePixel(t, test.data); got != test.want {
t.Fatalf("pixel = %#v, want %#v", got, test.want)
}
})
}
var compressed bytes.Buffer
writer := zlib.NewWriter(&compressed)
_, _ = writer.Write([]byte{3, 2, 1, 255})
if err := writer.Close(); err != nil {
t.Fatal(err)
}
header := imageHeader(Format8888, ExtraZlib, 1, 1, int32(compressed.Len()))
if got := decodeOnePixel(t, makeIMG(2, header, compressed.Bytes(), int32(len(header)))); got != (color.NRGBA{R: 1, G: 2, B: 3, A: 255}) {
t.Fatalf("zlib pixel = %#v", got)
}
if got := decodeOnePixel(t, makeIMG(1, imageHeader(Format8888, ExtraNone, 1, 1, 4), []byte{3, 2, 1, 255}, 0)); got != (color.NRGBA{R: 1, G: 2, B: 3, A: 255}) {
t.Fatalf("v1 pixel = %#v", got)
}
}
func TestIMGLinkPaletteDXTAndSprite(t *testing.T) {
ordinary := imageHeader(Format8888, ExtraNone, 1, 1, 4)
link := make([]byte, 0, 8)
putInt32(&link, FormatLink, 0)
linkIMG := makeIMGCount(2, append(append([]byte{}, ordinary...), link...), []byte{3, 2, 1, 255}, int32(len(ordinary)+len(link)), 2)
parsed, err := ParseIMGAt(bytes.NewReader(linkIMG), 0, int64(len(linkIMG)))
if err != nil {
t.Fatal(err)
}
linked, err := DecodeImage(bytes.NewReader(linkIMG), 0, int64(len(linkIMG)), parsed, 1)
if err != nil || linked.Bounds().Dx() != 1 {
t.Fatalf("link decode = %v, %v", linked, err)
}
palette := make([]byte, 0, 16)
putInt32(&palette, 2)
palette = append(palette, 1, 2, 3, 255)
palette = append(palette, 9, 8, 7, 255)
var paletteCompressed bytes.Buffer
paletteWriter := zlib.NewWriter(&paletteCompressed)
_, _ = paletteWriter.Write([]byte{1})
_ = paletteWriter.Close()
paletteHeader := imageHeader(Format8888, ExtraZlib, 1, 1, int32(paletteCompressed.Len()))
paletteIMG := make([]byte, 0)
paletteIMG = append(paletteIMG, []byte(imgMagic)...)
paletteIMG = append(paletteIMG, 0)
putInt32(&paletteIMG, int32(len(paletteHeader)), 0, 4, 1)
paletteIMG = append(paletteIMG, palette...)
paletteIMG = append(paletteIMG, paletteHeader...)
paletteIMG = append(paletteIMG, paletteCompressed.Bytes()...)
paletteParsed, err := ParseIMGAt(bytes.NewReader(paletteIMG), 0, int64(len(paletteIMG)))
if err != nil {
t.Fatal(err)
}
paletteDecoded, err := DecodeImage(bytes.NewReader(paletteIMG), 0, int64(len(paletteIMG)), paletteParsed, 0)
if err != nil || paletteDecoded.At(0, 0) != (color.NRGBA{R: 9, G: 8, B: 7, A: 255}) {
t.Fatalf("palette decode = %v, %v", paletteDecoded, err)
}
dxtHeader := imageHeader(FormatDXT1, ExtraNone, 4, 4, 8)
dxt := make([]byte, 8)
binary.LittleEndian.PutUint16(dxt[0:], 0xf800)
binary.LittleEndian.PutUint16(dxt[2:], 0x0000)
var dxtCompressed bytes.Buffer
dxtWriter := zlib.NewWriter(&dxtCompressed)
_, _ = dxtWriter.Write(dxt)
_ = dxtWriter.Close()
dxtHeader = imageHeader(FormatDXT1, ExtraZlib, 4, 4, int32(dxtCompressed.Len()))
dxtIMG := makeIMG(2, dxtHeader, dxtCompressed.Bytes(), int32(len(dxtHeader)))
if got := decodeOnePixel(t, dxtIMG); got != (color.NRGBA{R: 255, A: 255}) {
t.Fatalf("DXT decode = %#v", got)
}
for _, format := range []int32{FormatDXT3, FormatDXT5} {
block := make([]byte, 16)
for index := 0; index < 8; index++ {
block[index] = 0xff
}
binary.LittleEndian.PutUint16(block[8:], 0xf800)
binary.LittleEndian.PutUint16(block[10:], 0)
var compressed bytes.Buffer
writer := zlib.NewWriter(&compressed)
_, _ = writer.Write(block)
_ = writer.Close()
header := imageHeader(format, ExtraZlib, 4, 4, int32(compressed.Len()))
if got := decodeOnePixel(t, makeIMG(2, header, compressed.Bytes(), int32(len(header)))); got != (color.NRGBA{R: 255, A: 255}) {
t.Fatalf("DXT%d decode = %#v", format, got)
}
}
spriteRaw := []byte{0, 0, 255, 255, 255, 0, 0, 255}
var spriteCompressed bytes.Buffer
spriteWriter := zlib.NewWriter(&spriteCompressed)
_, _ = spriteWriter.Write(spriteRaw)
_ = spriteWriter.Close()
sprite := make([]byte, 0)
putInt32(&sprite, 0, Format8888, 0, int32(spriteCompressed.Len()), 8, 2, 1)
spriteImage := imageHeader(Format8888, ExtraSprite, 2, 1, 0)
putInt32(&spriteImage, 0, 0, 0, 0, 2, 1, 1)
spriteIMG := make([]byte, 0)
spriteIMG = append(spriteIMG, []byte(imgMagic)...)
spriteIMG = append(spriteIMG, 0)
putInt32(&spriteIMG, int32(len(spriteImage)), 0, 5, 1)
putInt32(&spriteIMG, 1, int32(spriteCompressed.Len()))
putInt32(&spriteIMG, 0)
spriteIMG = append(spriteIMG, sprite...)
spriteIMG = append(spriteIMG, spriteImage...)
spriteIMG = append(spriteIMG, spriteCompressed.Bytes()...)
spriteParsed, err := ParseIMGAt(bytes.NewReader(spriteIMG), 0, int64(len(spriteIMG)))
if err != nil {
t.Fatal(err)
}
spriteDecoded, err := DecodeImage(bytes.NewReader(spriteIMG), 0, int64(len(spriteIMG)), spriteParsed, 0)
if err != nil || spriteDecoded.Bounds().Dx() != 1 || spriteDecoded.Bounds().Dy() != 2 {
t.Fatalf("sprite decode = %v, %v", spriteDecoded, err)
}
cycleHeaders := make([]byte, 0, 16)
putInt32(&cycleHeaders, FormatLink, 1, FormatLink, 0)
cycleIMG := makeIMGCount(2, cycleHeaders, nil, int32(len(cycleHeaders)), 2)
cycleParsed, err := ParseIMGAt(bytes.NewReader(cycleIMG), 0, int64(len(cycleIMG)))
if err != nil {
t.Fatal(err)
}
if _, err := DecodeImage(bytes.NewReader(cycleIMG), 0, int64(len(cycleIMG)), cycleParsed, 0); err == nil {
t.Fatal("link cycle should fail")
}
var v6Compressed bytes.Buffer
v6Writer := zlib.NewWriter(&v6Compressed)
_, _ = v6Writer.Write([]byte{1})
_ = v6Writer.Close()
v6Header := imageHeader(Format8888, ExtraZlib, 1, 1, int32(v6Compressed.Len()))
v6IMG := make([]byte, 0)
v6IMG = append(v6IMG, []byte(imgMagic)...)
v6IMG = append(v6IMG, 0)
putInt32(&v6IMG, int32(len(v6Header)), 0, 6, 1)
// The v6 color board is a count followed by RGBA colors.
putInt32(&v6IMG, 1, 2)
v6IMG = append(v6IMG, 1, 2, 3, 255, 2, 3, 4, 255)
v6IMG = append(v6IMG, v6Header...)
v6IMG = append(v6IMG, v6Compressed.Bytes()...)
v6Parsed, err := ParseIMGAt(bytes.NewReader(v6IMG), 0, int64(len(v6IMG)))
if err != nil {
t.Fatal(err)
}
v6Decoded, err := DecodeImage(bytes.NewReader(v6IMG), 0, int64(len(v6IMG)), v6Parsed, 0)
if err != nil || v6Decoded.At(0, 0) != (color.NRGBA{R: 2, G: 3, B: 4, A: 255}) {
t.Fatalf("v6 decode = %v, %v", v6Decoded, err)
}
}
func TestNPKFilenameXORAndBadFile(t *testing.T) {
name := "sprite/item/test.img"
data := make([]byte, 0, 284)
data = append(data, []byte(npkMagic)...)
data = append(data, 0)
putInt32(&data, 1)
putInt32(&data, 284, 0)
encrypted := make([]byte, 256)
plain := []byte(name)
for index := range encrypted {
value := byte(0)
if index < len(plain) {
value = plain[index]
}
encrypted[index] = value ^ npkFilenameKey[index]
}
data = append(data, encrypted...)
parsed, err := Open(writeTempBytes(t, data))
if err != nil || len(parsed.Entries) != 1 || parsed.Entries[0].Name != name {
t.Fatalf("NPK = %#v, %v", parsed, err)
}
if _, err := Open(writeTempBytes(t, []byte("bad"))); err == nil {
t.Fatal("bad NPK should fail")
}
}
func writeTempBytes(t *testing.T, data []byte) string {
t.Helper()
path := t.TempDir() + "/test.npk"
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatal(err)
}
return path
}
func TestReadIMGFrameCountWithoutFrameTable(t *testing.T) {
// A v2 fixed header claiming a large table must be counted without reading
// or allocating that table. Full validation is deferred to image requests.
data := append([]byte("Neople Img File"), 0)
for _, value := range []int32{0, 0, 2, 1000000} {
var raw [4]byte
binary.LittleEndian.PutUint32(raw[:], uint32(value))
data = append(data, raw[:]...)
}
count, err := ReadIMGFrameCount(bytes.NewReader(data), 0, int64(len(data)))
if err != nil || count != 1000000 {
t.Fatalf("header frame count = %d, %v", count, err)
}
if _, err := ReadIMGFrameCount(bytes.NewReader(data[:20]), 0, 20); err == nil {
t.Fatal("truncated header should fail")
}
}