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,107 @@
|
||||
// Command dbtables 列出 dnf90.db 里的表,用来核对 GM 工具「是否 90CN 数据库」的判定。
|
||||
//
|
||||
// 用法:
|
||||
//
|
||||
// go run ./cmd/scratch/dbtables -db <dnf90.db> [-like dnf_]
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dbPath := flag.String("db", "", "dnf90.db 路径")
|
||||
like := flag.String("like", "", "只显示名字包含该子串的表")
|
||||
flag.Parse()
|
||||
if *dbPath == "" {
|
||||
fmt.Fprintln(os.Stderr, "需要 -db")
|
||||
os.Exit(2)
|
||||
}
|
||||
if _, err := os.Stat(*dbPath); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "找不到数据库:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// 只读 + 不建 WAL;用 mode=ro 让 SQLite 不尝试创建 -wal/-shm
|
||||
dsn := "file:" + *dbPath + "?mode=ro&_pragma=query_only(1)"
|
||||
db, err := sql.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "打开失败:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT type, name FROM sqlite_master WHERE type IN ('table','view','trigger') ORDER BY type, name")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "查询 sqlite_master 失败:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer rows.Close()
|
||||
var tables, views, triggers []string
|
||||
for rows.Next() {
|
||||
var kind, name string
|
||||
if err := rows.Scan(&kind, &name); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "扫描失败:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if *like != "" && !strings.Contains(name, *like) {
|
||||
continue
|
||||
}
|
||||
if kind == "trigger" {
|
||||
triggers = append(triggers, name)
|
||||
continue
|
||||
}
|
||||
if kind == "view" {
|
||||
views = append(views, name)
|
||||
} else {
|
||||
tables = append(tables, name)
|
||||
}
|
||||
}
|
||||
fmt.Printf("表 %d 个:\n", len(tables))
|
||||
for _, name := range tables {
|
||||
fmt.Println(" " + name)
|
||||
}
|
||||
if len(triggers) > 0 {
|
||||
fmt.Printf("触发器 %d 个:\n", len(triggers))
|
||||
for _, name := range triggers {
|
||||
fmt.Println(" " + name)
|
||||
}
|
||||
}
|
||||
if len(views) > 0 {
|
||||
fmt.Printf("视图 %d 个:\n", len(views))
|
||||
for _, name := range views {
|
||||
fmt.Println(" " + name)
|
||||
}
|
||||
}
|
||||
// 写探测:这些对象是视图时,SQLite 只有在存在 INSTEAD OF 触发器时才能写。
|
||||
// 用 WHERE 0 + 回滚,确保不改任何数据,只验证「可写性」。
|
||||
for _, target := range []string{"dnf_characters", "dnf_inventory_items", "dnf_skills", "dnf_accounts"} {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
fmt.Printf("探测 %-22s 无法开事务: %v\n", target, err)
|
||||
continue
|
||||
}
|
||||
_, execErr := tx.Exec("UPDATE " + target + " SET rowid = rowid WHERE 0")
|
||||
_ = tx.Rollback()
|
||||
if execErr != nil {
|
||||
fmt.Printf("探测 %-22s 可写=false 原因: %v\n", target, execErr)
|
||||
} else {
|
||||
fmt.Printf("探测 %-22s 可写=true\n", target)
|
||||
}
|
||||
}
|
||||
|
||||
for _, want := range []string{"dnf_accounts", "dnf_characters", "accounts", "characters"} {
|
||||
var count int
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", want).Scan(&count)
|
||||
if err != nil {
|
||||
fmt.Printf("检查 %s 失败: %v\n", want, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("关键表 %-16s 存在=%v\n", want, count > 0)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user