89 lines
3.2 KiB
Go
89 lines
3.2 KiB
Go
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// RuntimeConfig is shared by both server processes. Deployment values belong
|
|
// in configuration rather than in handlers or executable entry points.
|
|
type RuntimeConfig struct {
|
|
BattleDiagnostics BattleDiagnosticsConfig `json:"battle_diagnostics"`
|
|
ClientVersion string `json:"client_version"`
|
|
GameHost string `json:"game_host"`
|
|
GamePort uint16 `json:"game_port"`
|
|
SDKHost string `json:"sdk_host"`
|
|
SDKPort uint16 `json:"sdk_port"`
|
|
PublicGameHost string `json:"public_game_host"`
|
|
PublicSDKURL string `json:"public_sdk_url"`
|
|
}
|
|
|
|
// The obfuscated protobuf class name is version-specific and comes from the
|
|
// client's stack trace. Empty disables the read-only client wire probe.
|
|
type BattleDiagnosticsConfig struct {
|
|
ClientBattleType string `json:"client_battle_type"`
|
|
}
|
|
|
|
func LoadRuntimeConfig() (RuntimeConfig, error) {
|
|
path := os.Getenv("GAC_CONFIG")
|
|
if path == "" {
|
|
path = "server.json"
|
|
}
|
|
var cfg RuntimeConfig
|
|
for i, filename := range []string{path, filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))+".local.json")} {
|
|
raw, err := os.ReadFile(filename)
|
|
if i == 1 && os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("read runtime config %s: %w", filename, err)
|
|
}
|
|
if err := json.Unmarshal(bytes.TrimPrefix(raw, []byte{0xef, 0xbb, 0xbf}), &cfg); err != nil {
|
|
return cfg, fmt.Errorf("parse runtime config %s: %w", filename, err)
|
|
}
|
|
}
|
|
for name, dest := range map[string]*string{
|
|
"GAC_CLIENT_VERSION": &cfg.ClientVersion, "GAC_GAME_HOST": &cfg.GameHost,
|
|
"GAC_SDK_HOST": &cfg.SDKHost, "GAC_PUBLIC_GAME_HOST": &cfg.PublicGameHost,
|
|
"GAC_PUBLIC_SDK_URL": &cfg.PublicSDKURL,
|
|
} {
|
|
if value, ok := os.LookupEnv(name); ok {
|
|
*dest = value
|
|
}
|
|
}
|
|
for name, dest := range map[string]*uint16{"GAC_GAME_PORT": &cfg.GamePort, "GAC_SDK_PORT": &cfg.SDKPort} {
|
|
if value, ok := os.LookupEnv(name); ok {
|
|
n, err := strconv.ParseUint(value, 10, 16)
|
|
if err != nil || n == 0 {
|
|
return cfg, fmt.Errorf("%s must be a port between 1 and 65535", name)
|
|
}
|
|
*dest = uint16(n)
|
|
}
|
|
}
|
|
if cfg.GamePort == 0 || cfg.SDKPort == 0 || strings.TrimSpace(cfg.ClientVersion) == "" {
|
|
return cfg, fmt.Errorf("runtime config requires client_version, game_port and sdk_port")
|
|
}
|
|
for name, host := range map[string]string{"game_host": cfg.GameHost, "sdk_host": cfg.SDKHost, "public_game_host": cfg.PublicGameHost} {
|
|
if strings.TrimSpace(host) == "" || strings.ContainsAny(host, "/\\ \t\r\n") {
|
|
return cfg, fmt.Errorf("invalid %s", name)
|
|
}
|
|
}
|
|
u, err := url.Parse(cfg.PublicSDKURL)
|
|
if err != nil || u.Hostname() == "" || (u.Scheme != "http" && u.Scheme != "https") || u.RawQuery != "" || u.Fragment != "" || u.User != nil {
|
|
return cfg, fmt.Errorf("public_sdk_url must be an HTTP(S) base URL")
|
|
}
|
|
cfg.PublicSDKURL = strings.TrimRight(cfg.PublicSDKURL, "/")
|
|
return cfg, nil
|
|
}
|
|
|
|
func (c RuntimeConfig) SDKAddress() string {
|
|
return net.JoinHostPort(c.SDKHost, strconv.Itoa(int(c.SDKPort)))
|
|
}
|