Implement hunting AP and progress, event tasks, exchanges, games, stories and actions; connect battle and reward handling. Add CashShop plugin and installation, server purchases and entitlements, recharge rules and plugin version metadata. Distribute binary calendars independently; cache GameData in memory and fix single-connection query deadlocks. Improve automatic login and recovery diagnostics; document configuration and release flows.
86 lines
3.3 KiB
Go
86 lines
3.3 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const ReleaseFileName = "versions.json"
|
|
|
|
var (
|
|
gameVersionPattern = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+$`)
|
|
releaseVersionPattern = regexp.MustCompile(`^([0-9]+\.[0-9]+\.[0-9]+)\+(client|server)\.([0-9]+\.[0-9]+\.[0-9]+)$`)
|
|
resourceVersionPattern = regexp.MustCompile(`^[0-9]{14}$`)
|
|
)
|
|
|
|
// ReleaseVersions separates the official game compatibility version from the
|
|
// independently evolving client and server software release versions. The
|
|
// release package carries the authoritative versions.json beside bd2client.
|
|
type ReleaseVersions struct {
|
|
GameVersion string `json:"game_version"`
|
|
ClientVersion string `json:"client_version"`
|
|
ServerVersion string `json:"server_version"`
|
|
GameDataVersion string `json:"game_data_version"`
|
|
BundleVersion string `json:"bundle_version"`
|
|
SeedDirectory string `json:"seed_directory"`
|
|
Plugins struct {
|
|
LocalIdentity string `json:"local_identity"`
|
|
CaptureEnvironment string `json:"capture_environment"`
|
|
LoginUI string `json:"login_ui"`
|
|
CashShop string `json:"cash_shop"`
|
|
} `json:"plugins"`
|
|
}
|
|
|
|
func LoadReleaseVersions(path string) (ReleaseVersions, error) {
|
|
data, err := os.ReadFile(filepath.Clean(path))
|
|
if err != nil {
|
|
return ReleaseVersions{}, fmt.Errorf("read client release versions: %w", err)
|
|
}
|
|
decoder := json.NewDecoder(strings.NewReader(string(data)))
|
|
decoder.DisallowUnknownFields()
|
|
var versions ReleaseVersions
|
|
if err := decoder.Decode(&versions); err != nil {
|
|
return ReleaseVersions{}, fmt.Errorf("decode client release versions: %w", err)
|
|
}
|
|
var trailing any
|
|
if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
|
|
return ReleaseVersions{}, errors.New("client release versions must contain exactly one JSON object")
|
|
}
|
|
if !gameVersionPattern.MatchString(versions.GameVersion) {
|
|
return ReleaseVersions{}, fmt.Errorf("invalid game_version %q", versions.GameVersion)
|
|
}
|
|
if !validReleaseVersion(versions.ClientVersion, versions.GameVersion, "client") {
|
|
return ReleaseVersions{}, fmt.Errorf("invalid client_version %q", versions.ClientVersion)
|
|
}
|
|
if !validReleaseVersion(versions.ServerVersion, versions.GameVersion, "server") {
|
|
return ReleaseVersions{}, fmt.Errorf("invalid server_version %q", versions.ServerVersion)
|
|
}
|
|
if !resourceVersionPattern.MatchString(versions.BundleVersion) || !resourceVersionPattern.MatchString(versions.GameDataVersion) {
|
|
return ReleaseVersions{}, errors.New("bundle_version and game_data_version must be 14-digit timestamps")
|
|
}
|
|
if versions.SeedDirectory == "" ||
|
|
versions.Plugins.LocalIdentity == "" || versions.Plugins.LoginUI == "" || versions.Plugins.CaptureEnvironment == "" {
|
|
return ReleaseVersions{}, errors.New("client release versions are incomplete")
|
|
}
|
|
return versions, nil
|
|
}
|
|
|
|
func validReleaseVersion(value, game, component string) bool {
|
|
match := releaseVersionPattern.FindStringSubmatch(value)
|
|
return len(match) == 4 && match[1] == game && match[2] == component
|
|
}
|
|
|
|
func ReleaseVersionsBesideExecutable() (ReleaseVersions, error) {
|
|
executable, err := os.Executable()
|
|
if err != nil {
|
|
return ReleaseVersions{}, fmt.Errorf("locate bd2client executable: %w", err)
|
|
}
|
|
return LoadReleaseVersions(filepath.Join(filepath.Dir(executable), ReleaseFileName))
|
|
}
|