96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
//go:build ship
|
|
|
|
package runtimeassets
|
|
|
|
import (
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Keep the ship build self-contained. Probes and generated Lua files are
|
|
// deliberately excluded; only the maintained client scripts are embedded.
|
|
//
|
|
//go:embed resources res.json teleports.json challenge_data.json mission_unlocks.json server.json freesr-data.json persistent hotfix.json versions.json scripts/damage_analyst_live.lua scripts/freecam.lua scripts/hide_ui.lua scripts/navigation_profiles.lua scripts/ping_rainbow.lua
|
|
var embedded embed.FS
|
|
|
|
var seedFiles = []string{"server.json", "freesr-data.json", "persistent", "hotfix.json", "versions.json"}
|
|
|
|
func ReadFile(name string) ([]byte, error) {
|
|
return fs.ReadFile(embedded, filepath.ToSlash(name))
|
|
}
|
|
|
|
func ReadDir(name string) ([]fs.DirEntry, error) {
|
|
return fs.ReadDir(embedded, filepath.ToSlash(name))
|
|
}
|
|
|
|
// Bootstrap places mutable defaults beside the executable, so each pair of
|
|
// shipped binaries can run without the source checkout or a resource folder.
|
|
// Existing operator settings and saves are never replaced.
|
|
func Bootstrap() error {
|
|
executable, err := os.Executable()
|
|
if err != nil {
|
|
return fmt.Errorf("locate shipping executable: %w", err)
|
|
}
|
|
if configPath := os.Getenv("GAC_CONFIG"); configPath != "" && !filepath.IsAbs(configPath) {
|
|
absolute, err := filepath.Abs(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve GAC_CONFIG before starting: %w", err)
|
|
}
|
|
if err := os.Setenv("GAC_CONFIG", absolute); err != nil {
|
|
return fmt.Errorf("preserve GAC_CONFIG: %w", err)
|
|
}
|
|
}
|
|
workingDirectory := filepath.Dir(executable)
|
|
if err := os.Chdir(workingDirectory); err != nil {
|
|
return fmt.Errorf("use executable directory %s: %w", workingDirectory, err)
|
|
}
|
|
return seedMissingFiles(workingDirectory)
|
|
}
|
|
|
|
func seedMissingFiles(directory string) error {
|
|
for _, name := range seedFiles {
|
|
target := filepath.Join(directory, filepath.FromSlash(name))
|
|
if _, err := os.Stat(target); err == nil {
|
|
continue
|
|
} else if !errors.Is(err, os.ErrNotExist) {
|
|
return fmt.Errorf("check %s: %w", name, err)
|
|
}
|
|
data, err := fs.ReadFile(embedded, name)
|
|
if err != nil {
|
|
return fmt.Errorf("read embedded startup file %s: %w", name, err)
|
|
}
|
|
if err := writeIfMissing(target, data); err != nil {
|
|
return fmt.Errorf("create startup file %s: %w", name, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeIfMissing(target string, data []byte) error {
|
|
directory := filepath.Dir(target)
|
|
temp, err := os.CreateTemp(directory, ".gac-bootstrap-*.tmp")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tempName := temp.Name()
|
|
defer os.Remove(tempName)
|
|
if _, err := temp.Write(data); err != nil {
|
|
_ = temp.Close()
|
|
return err
|
|
}
|
|
if err := temp.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Link(tempName, target); err != nil {
|
|
if _, statErr := os.Stat(target); statErr == nil {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|