package main import ( "crypto/md5" "encoding/hex" "flag" "fmt" "io" "log" "net/http" "os" "os/exec" "path/filepath" "runtime" "strings" ) const ( manifestUrl = "https://updater.project-epoch.net/api/manifest" defaultWowPath = "/path/to/wow" defaultLaunchCmd = "not configured" ) func main() { var ( helpFlag bool updateOnlyFlag bool forceFlag bool rerunConfig bool ) flag.BoolVar(&helpFlag, "h", false, "Print help") flag.BoolVar(&forceFlag, "f", false, "Forces epochcli to update files even if they match the current version") flag.BoolVar(&updateOnlyFlag, "u", false, "Ignore EnableLauncher setting in config and only runs an update. Does nothing if EnableLauncher is false") flag.BoolVar(&rerunConfig, "c", false, "Runs config configuration step. Overrides the config file") flag.Parse() if helpFlag { flag.CommandLine.SetOutput(os.Stdout) fmt.Println("Epochcli Help:") flag.PrintDefaults() os.Exit(0) } config, err := setupConfig(rerunConfig) if err != nil { log.Fatal(err) } if config.WowDir == defaultWowPath { log.Fatalf("WowDir in %s is still the default setting, exiting", cfgPath) } stats, err := downloadUpdate(config, forceFlag) if err != nil { log.Fatal(err) } fmt.Printf("%d files updated\n", stats.updated) if stats.current > 0 { fmt.Printf("%d files are already up to date\n", stats.current) } if updateOnlyFlag { os.Exit(0) } if config.EnableLauncher { if config.LaunchCmd == defaultLaunchCmd { log.Fatalf("LaunchCmd in %s is still the default setting, exiting\n", cfgPath) } fmt.Println("Starting Epoch...") if runtime.GOOS == "darwin" { exec.Command("open", config.LaunchCmd).Run() } else { exec.Command(config.LaunchCmd).Run() } } } type DownloadStats struct { updated int current int } func downloadUpdate(config *Config, force bool) (DownloadStats, error) { var stats DownloadStats 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 !force { if _, err = os.Stat(localPath); err == nil { data, err := os.ReadFile(localPath) if err != nil { return stats, err } hashBytes := md5.Sum(data) hash := hex.EncodeToString(hashBytes[:]) if hash == file.Hash { fmt.Printf("File %s is up to date\n", localPath) stats.current += 1 continue } } } fmt.Printf(" %s...\n", localPath) outFile, err := os.Create(localPath) if err != nil { return stats, err } defer outFile.Close() resp, err := http.Get(file.URL) if err != nil { return stats, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return stats, 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 stats, err } stats.updated += 1 } return stats, nil }