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

279 lines
8.0 KiB
Go

package services
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"pvfine/internal/pvf"
)
func TestArchiveServiceImportFilesTextAndDirectoryMapping(t *testing.T) {
sourceDir := t.TempDir()
nestedDir := filepath.Join(sourceDir, "nested")
if err := os.MkdirAll(nestedDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "name.str"), []byte("导入文本"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(nestedDir, "item.equ"), []byte("[name]\n`导入脚本`"), 0o644); err != nil {
t.Fatal(err)
}
c := NewCore()
a := pvf.New()
if err := c.setArchive(a); err != nil {
t.Fatal(err)
}
t.Cleanup(c.closeArchive)
service := NewArchiveService(c)
preview, err := service.PreviewImport(
[]string{sourceDir, nestedDir},
"equipment",
ImportModeText,
)
if err != nil {
t.Fatal(err)
}
if preview.TotalFiles != 2 || preview.ImportedCount != 2 || preview.OverwrittenCount != 0 {
t.Fatalf("import preview = %#v", preview)
}
if c.archive.FileCount() != 0 {
t.Fatalf("preview modified archive: file count = %d", c.archive.FileCount())
}
result, err := service.ImportFiles(
[]string{sourceDir, nestedDir},
"equipment",
ImportModeText,
)
if err != nil {
t.Fatal(err)
}
if result.ImportedCount != 2 || result.OverwrittenCount != 0 {
t.Fatalf("import result = %#v", result)
}
directoryName := filepath.Base(sourceDir)
strPath := filepath.ToSlash(filepath.Join("equipment", directoryName, "name.str"))
equPath := filepath.ToSlash(filepath.Join("equipment", directoryName, "nested", "item.equ"))
strIndex, ok := c.archive.Find(strPath)
if !ok {
t.Fatalf("missing imported string file %q", strPath)
}
equIndex, ok := c.archive.Find(equPath)
if !ok {
t.Fatalf("missing imported script file %q", equPath)
}
if c.archive.File(strIndex).DataType != pvf.TypeUnicode {
t.Fatalf("string data type = %d, want %d", c.archive.File(strIndex).DataType, pvf.TypeUnicode)
}
if c.archive.File(equIndex).DataType != pvf.TypeScript {
t.Fatalf("script data type = %d, want %d", c.archive.File(equIndex).DataType, pvf.TypeScript)
}
children, err := service.ListChildren(filepath.ToSlash(filepath.Join("equipment", directoryName)))
if err != nil {
t.Fatal(err)
}
foundNewKind := false
for _, child := range children {
if child.Path == strPath {
foundNewKind = true
if child.ChangeKind != ChangeKindAdded {
t.Fatalf("new file change kind = %q, want %q", child.ChangeKind, ChangeKindAdded)
}
}
}
if !foundNewKind {
t.Fatalf("new file tree node missing: %q", strPath)
}
if text, err := c.archive.Text(strIndex); err != nil || text != "导入文本" {
t.Fatalf("string text = %q, err = %v", text, err)
}
if text, err := c.archive.Text(equIndex); err != nil || !strings.Contains(text, "导入脚本") {
t.Fatalf("script text = %q, err = %v", text, err)
}
var packed bytes.Buffer
if err := c.archive.SaveTo(&packed); err != nil {
t.Fatal(err)
}
roundTrip, err := pvf.Parse(packed.Bytes())
if err != nil {
t.Fatal(err)
}
if roundTrip.FileCount() != 2 {
t.Fatalf("round-trip file count = %d, want 2", roundTrip.FileCount())
}
roundTripStringIndex, ok := roundTrip.Find(strPath)
if !ok {
t.Fatalf("round-trip string file missing: %q", strPath)
}
if text, err := roundTrip.Text(roundTripStringIndex); err != nil || text != "导入文本" {
t.Fatalf("round-trip string text = %q, err = %v", text, err)
}
}
func TestArchiveServiceImportFilesRawOverwritesAndUpdatesType(t *testing.T) {
sourceDir := t.TempDir()
sourcePath := filepath.Join(sourceDir, "same.str")
raw := []byte{0x4b, 0x00, 0x8b, 0x65}
if err := os.WriteFile(sourcePath, raw, 0o644); err != nil {
t.Fatal(err)
}
c := NewCore()
a := pvf.New()
if _, err := a.AddFileText("same.str", "old", pvf.TypeScript); err != nil {
t.Fatal(err)
}
archivePath := filepath.Join(t.TempDir(), "base.pvf")
if err := a.SaveAs(archivePath); err != nil {
t.Fatal(err)
}
loaded, err := pvf.Open(archivePath)
if err != nil {
t.Fatal(err)
}
if err := c.setArchive(loaded); err != nil {
t.Fatal(err)
}
t.Cleanup(c.closeArchive)
result, err := NewArchiveService(c).ImportFiles([]string{sourcePath}, "", ImportModeRaw)
if err != nil {
t.Fatal(err)
}
if result.ImportedCount != 0 || result.OverwrittenCount != 1 || len(result.ChangedPaths) != 1 {
t.Fatalf("import result = %#v", result)
}
index, ok := c.archive.Find("same.str")
if !ok {
t.Fatal("overwritten file missing")
}
if c.archive.File(index).DataType != pvf.TypeUnicode {
t.Fatalf("overwritten data type = %d, want %d", c.archive.File(index).DataType, pvf.TypeUnicode)
}
got, err := c.archive.RawBytes(index)
if err != nil || !bytes.Equal(got, raw) {
t.Fatalf("overwritten raw = %v, err = %v", got, err)
}
children, err := NewArchiveService(c).ListChildren("")
if err != nil {
t.Fatal(err)
}
foundModifiedKind := false
for _, child := range children {
if child.Path == "same.str" {
foundModifiedKind = true
if child.ChangeKind != ChangeKindModified {
t.Fatalf("overwritten file change kind = %q, want %q", child.ChangeKind, ChangeKindModified)
}
}
}
if !foundModifiedKind {
t.Fatal("overwritten file tree node missing")
}
}
func TestArchiveServiceImportFilesRollsBackOnInvalidText(t *testing.T) {
sourceDir := t.TempDir()
validPath := filepath.Join(sourceDir, "valid.equ")
invalidPath := filepath.Join(sourceDir, "invalid.equ")
if err := os.WriteFile(validPath, []byte("[name]\n`valid`"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(invalidPath, []byte{0xff, 0xfe, 0xfd}, 0o644); err != nil {
t.Fatal(err)
}
c := NewCore()
a := pvf.New()
if _, err := a.AddFileText("existing.equ", "[name]\n`existing`", pvf.TypeScript); err != nil {
t.Fatal(err)
}
if err := c.setArchive(a); err != nil {
t.Fatal(err)
}
t.Cleanup(c.closeArchive)
beforeCount := c.archive.FileCount()
beforeModified := c.archive.ModifiedCount()
_, err := NewArchiveService(c).ImportFiles(
[]string{validPath, invalidPath},
"",
ImportModeText,
)
if err == nil || !strings.Contains(err.Error(), "UTF-8") {
t.Fatalf("invalid text error = %v", err)
}
if c.archive.FileCount() != beforeCount || c.archive.ModifiedCount() != beforeModified {
t.Fatalf("archive changed after failed import: count=%d modified=%d", c.archive.FileCount(), c.archive.ModifiedCount())
}
if _, ok := c.archive.Find("valid.equ"); ok {
t.Fatal("valid file was partially imported")
}
}
func TestCollectImportFilesRejectsTargetCollision(t *testing.T) {
left := filepath.Join(t.TempDir(), "same")
right := filepath.Join(t.TempDir(), "same")
if err := os.MkdirAll(left, 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(right, 0o755); err != nil {
t.Fatal(err)
}
leftFile := filepath.Join(left, "item.equ")
rightFile := filepath.Join(right, "item.equ")
if err := os.WriteFile(leftFile, []byte("left"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(rightFile, []byte("right"), 0o644); err != nil {
t.Fatal(err)
}
_, err := collectImportFiles([]string{left, right}, "")
if err == nil || !strings.Contains(err.Error(), "批次内归档路径冲突") {
t.Fatalf("collision error = %v", err)
}
}
func TestImportNutSourceKeepsSourceText(t *testing.T) {
source := "// 繁體註解\r\nfunction test() { return [1, 2]; }\r\n"
path := filepath.Join(t.TempDir(), "common.NUT")
if err := os.WriteFile(path, []byte(source), 0o644); err != nil {
t.Fatal(err)
}
files := []importFile{{sourcePath: path, targetPath: "sqr/common.NUT"}}
if err := readImportFiles(files, ImportModeText); err != nil {
t.Fatal(err)
}
a := pvf.New()
if _, err := applyPreparedImport(a, files, ImportModeText); err != nil {
t.Fatal(err)
}
var out bytes.Buffer
if err := a.SaveTo(&out); err != nil {
t.Fatal(err)
}
b, err := pvf.Parse(out.Bytes())
if err != nil {
t.Fatal(err)
}
index, ok := b.Find("sqr/common.nut")
if !ok {
t.Fatal("imported NUT missing")
}
if b.File(index).DataType != pvf.TypeUnicode {
t.Fatal("NUT imported as PVF tokens")
}
if text, err := b.Text(index); err != nil || text != source {
t.Fatalf("NUT source changed: %v", err)
}
}