合并本地领先源码:旧UI、GM疲劳燃烧、白金徽章孔、宠物修复、支援兵普攻开关
This commit is contained in:
1 parent
f2debaf9bf
commit
70b3c5a6dc
57 files changed
+4256
-30
No files matched your search
@@ -0,0 +1,277 @@
|
||||
// Command companionoverlay — 支援兵覆盖工具(单文件版)。
|
||||
//
|
||||
// 把「支援兵 AIC 覆盖」做成一个可独立发布的可执行程序:内置 companionpvf 与
|
||||
// profiles.json,用户只需拖入 Script.pvf(或直接双击用默认路径),即可:
|
||||
//
|
||||
// 1) 生成覆盖版 PVF(默认输出到同目录 Script.companion.pvf);
|
||||
// 2) 可选地安装到客户端/服务端目录(自动备份、打印哈希)。
|
||||
//
|
||||
// 用法:
|
||||
//
|
||||
// 支援兵覆盖工具.exe 交互模式(使用默认客户端路径)
|
||||
// 支援兵覆盖工具.exe <Script.pvf> 拖拽/命令行:只生成覆盖版
|
||||
// 支援兵覆盖工具.exe <Script.pvf> --client <客户端目录> --server <服务端数据目录>
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed companionpvf.exe profiles.json
|
||||
var assets embed.FS
|
||||
|
||||
const (
|
||||
appTitle = "DNF90 支援兵覆盖工具"
|
||||
appVersion = "1.0.0"
|
||||
defaultClient = `D:\90server\Client\Script.pvf`
|
||||
minOutputBytes = 50 << 20
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("%s v%s\n", appTitle, appVersion)
|
||||
fmt.Println(strings.Repeat("=", 60))
|
||||
|
||||
args := os.Args[1:]
|
||||
pvfPath, clientDir, serverDir, install, interactive := parseArgs(args)
|
||||
|
||||
if interactive {
|
||||
pvfPath = prompt("要处理的 Script.pvf 路径", pvfPath)
|
||||
if !ask("是否同时安装到客户端/服务端目录?(y/N): ") {
|
||||
install = false
|
||||
} else {
|
||||
install = true
|
||||
clientDir = prompt("客户端目录(含 Script.pvf)", clientDir)
|
||||
serverDir = prompt("服务端数据目录(含 Script.pvf)", serverDir)
|
||||
}
|
||||
}
|
||||
if !install {
|
||||
clientDir, serverDir = "", ""
|
||||
}
|
||||
|
||||
if strings.TrimSpace(pvfPath) == "" {
|
||||
fail("没有指定 Script.pvf")
|
||||
}
|
||||
source, err := filepath.Abs(pvfPath)
|
||||
if err != nil || !exists(source) {
|
||||
fail("找不到 PVF:" + pvfPath)
|
||||
}
|
||||
|
||||
outPath := strings.TrimSuffix(source, filepath.Ext(source)) + ".companion.pvf"
|
||||
if dir := filepath.Dir(source); dir != "" {
|
||||
outPath = filepath.Join(dir, "Script.companion.pvf")
|
||||
}
|
||||
|
||||
fmt.Printf("输入 PVF:%s\n", source)
|
||||
fmt.Printf("输出 PVF:%s\n", outPath)
|
||||
|
||||
if err := runOverlay(source, outPath); err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
size := fileSize(outPath)
|
||||
fmt.Printf("覆盖版已生成:%d 字节(%.1f MB)\n", size, float64(size)/(1<<20))
|
||||
if size < minOutputBytes {
|
||||
fail("输出文件过小,疑似失败(原 PVF 未被修改)")
|
||||
}
|
||||
hash := fileHash(outPath)
|
||||
fmt.Printf("覆盖版 SHA256:%s\n", hash)
|
||||
|
||||
if install {
|
||||
if err := installTo(source, outPath, hash, clientDir, serverDir); err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
} else {
|
||||
fmt.Println()
|
||||
fmt.Println("未安装。手动替换方法:")
|
||||
fmt.Println(" 客户端:把输出文件改名为 Script.pvf 覆盖到 客户端目录。")
|
||||
fmt.Println(" 服务端:同样覆盖到 服务端 runtime\\data\\dnf\\Script.pvf。")
|
||||
fmt.Println(" 完成后重启服务端,再启动游戏。")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("完成。")
|
||||
if interactive {
|
||||
fmt.Print("按回车退出...")
|
||||
bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
}
|
||||
}
|
||||
|
||||
func parseArgs(args []string) (pvf, clientDir, serverDir string, install, interactive bool) {
|
||||
clientDir = `D:\90server\Client`
|
||||
serverDir = `D:\90server\Server\runtime\data\dnf`
|
||||
pvf = defaultClient
|
||||
interactive = len(args) == 0
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--client":
|
||||
if i+1 < len(args) {
|
||||
clientDir = args[i+1]
|
||||
install = true
|
||||
i++
|
||||
}
|
||||
case "--server":
|
||||
if i+1 < len(args) {
|
||||
serverDir = args[i+1]
|
||||
install = true
|
||||
i++
|
||||
}
|
||||
case "--install":
|
||||
install = true
|
||||
default:
|
||||
if !strings.HasPrefix(args[i], "--") {
|
||||
pvf = args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return pvf, clientDir, serverDir, install, interactive
|
||||
}
|
||||
|
||||
func runOverlay(source, outPath string) error {
|
||||
toolPath := filepath.Join(os.TempDir(), fmt.Sprintf("companionpvf-%d.exe", time.Now().UnixNano()))
|
||||
toolBytes, err := assets.ReadFile("companionpvf.exe")
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取内置工具失败:%w", err)
|
||||
}
|
||||
if err := os.WriteFile(toolPath, toolBytes, 0o700); err != nil {
|
||||
return fmt.Errorf("释放内置工具失败:%w", err)
|
||||
}
|
||||
defer os.Remove(toolPath)
|
||||
|
||||
profilesPath := filepath.Join(os.TempDir(), fmt.Sprintf("companion-profiles-%d.json", time.Now().UnixNano()))
|
||||
profilesBytes, err := assets.ReadFile("profiles.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取内置配置失败:%w", err)
|
||||
}
|
||||
if err := os.WriteFile(profilesPath, profilesBytes, 0o600); err != nil {
|
||||
return fmt.Errorf("释放内置配置失败:%w", err)
|
||||
}
|
||||
defer os.Remove(profilesPath)
|
||||
|
||||
fmt.Println("生成覆盖版(约需 10–60 秒)...")
|
||||
cmd := exec.Command(toolPath, "--profile-overlay", source, outPath, profilesPath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("覆盖失败:%w(原 PVF 未被修改)", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func installTo(source, outPath, hash, clientDir, serverDir string) error {
|
||||
if hasProcess("DNF") {
|
||||
return fmt.Errorf("请先关闭 DNF 客户端:PVF 正被占用")
|
||||
}
|
||||
targets := []string{
|
||||
filepath.Join(clientDir, "Script.pvf"),
|
||||
filepath.Join(serverDir, "Script.pvf"),
|
||||
}
|
||||
stamp := time.Now().Format("20060102-150405")
|
||||
for _, target := range targets {
|
||||
if !exists(target) {
|
||||
fmt.Printf("跳过(不存在):%s\n", target)
|
||||
continue
|
||||
}
|
||||
if fileHash(target) == hash {
|
||||
fmt.Printf("已是最新,跳过:%s\n", target)
|
||||
continue
|
||||
}
|
||||
backup := target + ".bak_before_companion_" + stamp
|
||||
if err := copyFile(target, backup); err != nil {
|
||||
return fmt.Errorf("备份失败:%w", err)
|
||||
}
|
||||
fmt.Printf("已备份:%s\n", backup)
|
||||
if err := copyFile(outPath, target); err != nil {
|
||||
return fmt.Errorf("安装失败:%w", err)
|
||||
}
|
||||
fmt.Printf("已安装:%s\n", target)
|
||||
}
|
||||
fmt.Println("请重启 DNF90 服务端(服务端启动时加载 PVF),再启动游戏。")
|
||||
return nil
|
||||
}
|
||||
|
||||
func prompt(label, fallback string) string {
|
||||
fmt.Printf("%s [%s]: ", label, fallback)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
text, _ := reader.ReadString('\n')
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return fallback
|
||||
}
|
||||
return strings.Trim(text, `"`)
|
||||
}
|
||||
|
||||
func ask(label string) bool {
|
||||
fmt.Print(label)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
text, _ := reader.ReadString('\n')
|
||||
text = strings.ToLower(strings.TrimSpace(text))
|
||||
return text == "y" || text == "yes"
|
||||
}
|
||||
|
||||
func exists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
||||
func fileSize(path string) int64 {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return info.Size()
|
||||
}
|
||||
|
||||
func fileHash(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
out.Close()
|
||||
return err
|
||||
}
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func hasProcess(name string) bool {
|
||||
cmd := exec.Command("tasklist", "/FI", "IMAGENAME eq "+name+".exe", "/NH")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(strings.ToLower(string(output)), strings.ToLower(name+".exe"))
|
||||
}
|
||||
|
||||
func fail(message string) {
|
||||
fmt.Fprintf(os.Stderr, "\n[错误] %s\n", message)
|
||||
fmt.Print("按回车退出...")
|
||||
bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
os.Exit(1)
|
||||
}
|
||||
Reference in new issue
Block a user