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,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")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user