feat: add config, auth, fetcher, java modules
- config: OS-specific paths, launcher.json load/save, DefaultSettings with MRIXSCRAFT_SERVER_URL env override - auth: Yggdrasil client (authenticate, refresh, validate), session persistence in session.json, EnsureValid flow - fetcher: HTTP download with SHA-1 verification, WorkerPool for concurrent downloads - java: JRE detection (IsInstalled/Find), platform-specific executable name - utils: SHA1File, SHA1Bytes, Unzip with zip-slip protection - cmd/launcher: wire config + auth into main, session restore on startup Co-Authored-By: OWL <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1,172 @@
|
||||
// package config manages launcher configuration (launcher.json, system paths).
|
||||
// package config manages launcher configuration and system paths.
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// AppName is the canonical name for the launcher and its data directory.
|
||||
const AppName = "MrixsCraft"
|
||||
|
||||
// Settings represents the user-configurable launcher settings stored in launcher.json.
|
||||
type Settings struct {
|
||||
ServerURL string `json:"server_url"` // Base URL of the backend (e.g. "https://minecraft.mrixs.me")
|
||||
SelectedPack string `json:"selected_pack"` // Slug of the selected modpack
|
||||
MemoryMB int `json:"memory_mb"` // Allocated RAM in MB (for -Xms / -Xmx)
|
||||
ExtraArgs string `json:"extra_args"` // Additional JVM flags
|
||||
Width int `json:"window_width"` // Main window width
|
||||
Height int `json:"window_height"` // Main window height
|
||||
}
|
||||
|
||||
// serverEnvURL is the environment variable that overrides the backend base URL.
|
||||
const serverEnvURL = "MRIXSCRAFT_SERVER_URL"
|
||||
|
||||
// defaultServerURL is used when neither the env var nor launcher.json provide a value.
|
||||
const defaultServerURL = "https://minecraft.mrixs.me"
|
||||
|
||||
// DefaultSettings returns sensible defaults.
|
||||
func DefaultSettings() Settings {
|
||||
url := os.Getenv(serverEnvURL)
|
||||
if url == "" {
|
||||
url = defaultServerURL
|
||||
}
|
||||
return Settings{
|
||||
ServerURL: url,
|
||||
SelectedPack: "",
|
||||
MemoryMB: 4096,
|
||||
ExtraArgs: "",
|
||||
Width: 900,
|
||||
Height: 600,
|
||||
}
|
||||
}
|
||||
|
||||
// RootDir returns the OS-specific data directory for the launcher.
|
||||
func RootDir() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData == "" {
|
||||
return "", fmt.Errorf("APPDATA environment variable is empty")
|
||||
}
|
||||
return filepath.Join(appData, AppName), nil
|
||||
|
||||
case "darwin":
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolving home directory: %w", err)
|
||||
}
|
||||
return filepath.Join(home, "Library", "Application Support", AppName), nil
|
||||
|
||||
default: // linux and other unixes
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolving home directory: %w", err)
|
||||
}
|
||||
return filepath.Join(home, "."+AppName), nil
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureRoot creates the root data directory if it does not exist.
|
||||
func EnsureRoot() (string, error) {
|
||||
dir, err := RootDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("creating root directory %s: %w", dir, err)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Subdirectory returns (and optionally creates) a subdirectory under the root.
|
||||
func Subdirectory(name string, create bool) (string, error) {
|
||||
root, err := RootDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := filepath.Join(root, name)
|
||||
if create {
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
return "", fmt.Errorf("creating subdirectory %s: %w", path, err)
|
||||
}
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// JavaDir returns the directory for a specific Java version.
|
||||
func JavaDir(version int) (string, error) {
|
||||
return Subdirectory(filepath.Join("Java", fmt.Sprintf("%d", version)), true)
|
||||
}
|
||||
|
||||
// InstancesDir returns the directory containing modpack instances.
|
||||
func InstancesDir() (string, error) {
|
||||
return Subdirectory("instances", true)
|
||||
}
|
||||
|
||||
// InstanceDir returns the directory for a specific modpack.
|
||||
func InstanceDir(slug string) (string, error) {
|
||||
return Subdirectory(filepath.Join("instances", slug), true)
|
||||
}
|
||||
|
||||
// AssetsDir returns the assets cache directory.
|
||||
func AssetsDir() (string, error) {
|
||||
return Subdirectory("assets", true)
|
||||
}
|
||||
|
||||
// LibrariesDir returns the libraries cache directory.
|
||||
func LibrariesDir() (string, error) {
|
||||
return Subdirectory("libraries", true)
|
||||
}
|
||||
|
||||
// LauncherJSONPath returns the full path to launcher.json.
|
||||
func LauncherJSONPath() (string, error) {
|
||||
root, err := RootDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(root, "launcher.json"), nil
|
||||
}
|
||||
|
||||
// Load reads launcher.json from disk, returning defaults if the file does not exist.
|
||||
func Load() (Settings, error) {
|
||||
path, err := LauncherJSONPath()
|
||||
if err != nil {
|
||||
return Settings{}, err
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return DefaultSettings(), nil
|
||||
}
|
||||
return Settings{}, fmt.Errorf("reading %s: %w", path, err)
|
||||
}
|
||||
|
||||
var s Settings
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return Settings{}, fmt.Errorf("parsing %s: %w", path, err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Save writes the settings to launcher.json.
|
||||
func Save(s Settings) error {
|
||||
path, err := LauncherJSONPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(s, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("serializing settings: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
return fmt.Errorf("writing %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user