Skip to content

Instantly share code, notes, and snippets.

@tabjy
Created January 5, 2025 04:31
Show Gist options
  • Save tabjy/92ea912aa94b82e9e6203b7c1ef2043f to your computer and use it in GitHub Desktop.
Save tabjy/92ea912aa94b82e9e6203b7c1ef2043f to your computer and use it in GitHub Desktop.
Palworld Dedicated Daemond
package main
import (
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/exec"
"runtime"
"time"
)
const BufferSize = 65535 // use max udp packet size for now
var (
executable string
port int
listen string
restPort int
adminPassword string
shutdownDelay int
args []string
)
func init() {
flag.StringVar(&executable, "executable", "", "path to the dedicated server executable")
flag.IntVar(&port, "port", 8211, "upstream port to the dedicated server")
flag.StringVar(&listen, "listen", "0.0.0.0:8211", "host the forwarding daemon binds to")
flag.IntVar(&restPort, "rest-port", 8212, "rest api port for auto shutdown")
flag.StringVar(&adminPassword, "admin-password", "", "admin password for REST API")
flag.IntVar(&shutdownDelay, "shutdown-delay", 10*60,
"shutdown delay after last player is logged off, seconds")
}
func main() {
flag.Parse()
args = flag.Args()
if executable == "" {
switch runtime.GOOS {
case "linux":
executable = "./PalServer"
case "windows":
executable = ".\\PalServer.exe"
default:
log.Fatalln("OS not supported")
}
}
if shutdownDelay < 120 {
log.Fatalln("shutdown delay must be larger than 120 seconds")
}
log.Printf("executable: %s\n", executable)
log.Printf("port: %d\n", port)
log.Printf("listen: %s\n", listen)
log.Printf("restPort: %d\n", restPort)
log.Printf("adminPassword: %s\n", adminPassword)
log.Printf("shutdownDelay: %d\n", shutdownDelay)
log.Printf("args: %s\n", args)
for {
loop()
}
}
func loop() {
addr, err := net.ResolveUDPAddr("udp", listen)
if err != nil {
log.Fatal(err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
log.Println("daemon started on", listen)
for {
buf := make([]byte, 65535)
_, _, err := conn.ReadFromUDP(buf)
if err != nil {
continue
}
log.Println("spawning server...")
cmd := exec.Command(executable, append([]string{fmt.Sprintf("-port=%d", port)}, args...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Fatalln("failed to spawn server:", err)
}
log.Println("server spawned")
if adminPassword != "" {
go func() {
watch()
}()
}
if err := cmd.Wait(); err != nil {
log.Println("server exited:", err)
} else {
log.Println("server exited")
}
break
}
}
type ApiPlayers struct {
Players []struct {
Name string `json:"name"`
AccountName string `json:"accountName"`
PlayerID string `json:"playerId"`
UserID string `json:"userId"`
IP string `json:"ip"`
Ping float64 `json:"ping"`
LocationX float64 `json:"location_x"`
LocationY float64 `json:"location_y"`
Level int `json:"level"`
BuildingCount int `json:"building_count"`
} `json:"players"`
}
func request(method, url string, body io.Reader) (*http.Response, error) {
client := &http.Client{}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:"+adminPassword)))
return client.Do(req)
}
func watch() {
prefix := fmt.Sprintf("http://localhost:%d/v1/api/", restPort)
lastOnline := time.Now()
log.Println("watching players...")
for {
time.Sleep(60 * time.Second)
res, err := request("GET", prefix+"players", nil)
if err != nil {
log.Println(err)
continue
}
var players ApiPlayers
if err := json.NewDecoder(res.Body).Decode(&players); err != nil {
log.Println(err)
continue
}
log.Println("players online:", len(players.Players))
//log.Println("empty server time:", time.Since(lastOnline).Seconds())
if len(players.Players) != 0 {
lastOnline = time.Now()
} else if time.Since(lastOnline).Seconds() > float64(shutdownDelay) {
break
}
}
//log.Println("saving server...")
//_, err := request("POST", prefix+"save", nil)
//if err != nil {
// log.Println(err)
//}
//
//log.Println("shutting down server...")
//_, err = request("POST", prefix+"shutdown", strings.NewReader("{\"waittime\": 0, \"message\": \"No players online. Shutting down.\"}"))
//if err != nil {
// log.Println(err)
//}
log.Println("stopping server...")
_, err := request("POST", prefix+"stop", nil)
if err != nil {
log.Println(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment