commit d0f7fdc76efa51a34ecf0c12edbfdd2c36e18041 Author: Evan Burkey Date: Mon Jun 2 12:43:33 2025 -0700 init diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..10ca9bc --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,12 @@ +name: Build Epoch Launcher + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + - run: go build -v ./... \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2101a41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +epoch-linux \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3d6ebcd --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.burkey.co/eburk/epoch-linux + +go 1.24.3 + +require github.com/BurntSushi/toml v1.5.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ff7fd09 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= diff --git a/main.go b/main.go new file mode 100644 index 0000000..3244657 --- /dev/null +++ b/main.go @@ -0,0 +1,148 @@ +package main + +import ( + "crypto/md5" + "encoding/hex" + "flag" + "fmt" + "github.com/BurntSushi/toml" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" +) + +const ( + manifestUrl = "https://updater.project-epoch.net/api/manifest" + configDirName = "epoch-linux" + configName = "config.toml" +) + +type Config struct { + WowDir string +} + +var ( + config Config +) + +func setupConfig() { + home := os.Getenv("HOME") + if home == "" { + log.Fatal("$HOME environment variable not set") + } + + cfgPath := filepath.Join(home, ".config", configDirName, configName) + + if _, statErr := os.Stat(cfgPath); os.IsNotExist(statErr) { + os.MkdirAll(filepath.Join(home, ".config", configDirName), 0755) + + newConfig := &Config{ + WowDir: "/path/to/wow", + } + + file, err := os.Create(cfgPath) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + encoder := toml.NewEncoder(file) + if err = encoder.Encode(newConfig); err != nil { + log.Fatal(err) + } + + fmt.Printf("Created new config at %s, edit it before running the launcher again\n", cfgPath) + os.Exit(0) + } + + _, err := toml.DecodeFile(cfgPath, &config) + if err != nil { + log.Fatal(err) + } +} + +func printHelp() { + fmt.Println("Usage:") + fmt.Println(" " + os.Args[0] + "[-hu]") + fmt.Println(" " + os.Args[0] + "-h -- prints this help message") +} + +func main() { + helpFlag := flag.Bool("help", false, "Show this printHelp") + flag.Parse() + + if *helpFlag { + printHelp() + os.Exit(0) + } + + setupConfig() + + count, err := downloadUpdate() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Updated %d files\n", count) +} + +func downloadUpdate() (int, error) { + var c int + + manifest, err := getManifest() + if err != nil { + log.Fatalf("Failed to get manifest: %v\n", err) + } + + for _, file := range manifest.Files { + path := strings.ReplaceAll(file.Path, `\`, `/`) + path = strings.TrimLeft(path, `\`) + + localPath := filepath.Join(config.WowDir, path) + localDir := filepath.Dir(localPath) + if _, err = os.Stat(localDir); os.IsNotExist(err) { + os.MkdirAll(localDir, 0755) + } + + if _, err = os.Stat(localPath); err == nil { + data, err := os.ReadFile(localPath) + if err != nil { + return c, err + } + hashBytes := md5.Sum(data) + hash := hex.EncodeToString(hashBytes[:]) + if hash == file.Hash { + continue + } + } + + fmt.Printf("Updating %s...\n", file.Path) + + outFile, err := os.Create(localPath) + if err != nil { + return c, err + } + defer outFile.Close() + + resp, err := http.Get(file.URL) + if err != nil { + return c, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return c, fmt.Errorf("failed to download update from %s, status code: %d", file.URL, resp.StatusCode) + } + + _, err = io.Copy(outFile, resp.Body) + if err != nil { + return c, err + } + + c += 1 + } + + return c, nil +} diff --git a/manifest.go b/manifest.go new file mode 100644 index 0000000..755872f --- /dev/null +++ b/manifest.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/json" + "io" + "net/http" +) + +type File struct { + Path string `json:"Path"` + Hash string `json:"Hash"` + Size int `json:"Size"` + Custom bool `json:"Custom"` + URL string `json:"URL"` + Origin string `json:"Origin"` +} + +type Manifest struct { + Version string `json:"Version"` + Files []File `json:"Files"` +} + +func getManifest() (*Manifest, error) { + resp, err := http.Get(manifestUrl) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var manifest Manifest + err = json.Unmarshal(data, &manifest) + if err != nil { + return nil, err + } + + return &manifest, nil +}