7 Commits

Author SHA1 Message Date
970d8c654f add version check 2025-07-07 11:02:07 -07:00
a5b3719d33 version preupdate 2025-07-07 10:29:23 -07:00
1200a1bfc2 updated to v2 api 2025-07-07 10:01:38 -07:00
af8a2dbd1e warning msg, readme 2025-07-04 10:24:23 -07:00
dd8282623e update README 2025-07-04 06:55:29 -07:00
a9367763f3 Merge pull request 'cleanup prompting' (#1) from cleanup into master
Reviewed-on: #1
2025-07-04 13:29:56 +00:00
7dd915b060 cleanup prompting 2025-07-04 06:10:25 -07:00
6 changed files with 126 additions and 59 deletions

View File

@ -9,6 +9,8 @@ A `wine` prefix with `dxvk` installed is sufficient, or you can use something li
Download and extract the latest binary from the [releases](https://git.burkey.co/eburk/epochcli/releases) page, build from source yourself, or use homebrew from the macOS instructions to install. Download and extract the latest binary from the [releases](https://git.burkey.co/eburk/epochcli/releases) page, build from source yourself, or use homebrew from the macOS instructions to install.
If you are an Arch Linux (or derivative) user, you can use the provided [AUR package](https://aur.archlinux.org/packages/epochcli) to install
### macOS ### macOS
For macOS, I've found the best way to run Wow is in a Parallels Win 11 VM. Kegworks, Codeweavers, etc crash when the game starts up and I have not found a good solution so far. Any suggestions would be welcome, see my contact information below. I currently use a Parallels VM and run the Windows version of epochcli inside the VM as an updater and launcher. For macOS, I've found the best way to run Wow is in a Parallels Win 11 VM. Kegworks, Codeweavers, etc crash when the game starts up and I have not found a good solution so far. Any suggestions would be welcome, see my contact information below. I currently use a Parallels VM and run the Windows version of epochcli inside the VM as an updater and launcher.
@ -29,6 +31,8 @@ Download and extract the latest binary from the [releases](https://git.burkey.co
## Setup ## Setup
![ss](ss.png)
1. Run `epochcli`. You will be taken through a setup process that configures the program and creates a config file at `$HOME/.config/epochcli/config.toml` 1. Run `epochcli`. You will be taken through a setup process that configures the program and creates a config file at `$HOME/.config/epochcli/config.toml`
2. You can now use `epochcli` as a standalone updater, but it can also act as a launcher based on your configuration. You can always run `epochcli -c` to redo the configuration, or edit the config file manually 2. You can now use `epochcli` as a standalone updater, but it can also act as a launcher based on your configuration. You can always run `epochcli -c` to redo the configuration, or edit the config file manually

View File

@ -38,7 +38,7 @@ func setupConfig(rerun bool) (*Config, error) {
_, statErr := os.Stat(cfgPath) _, statErr := os.Stat(cfgPath)
if rerun || os.IsNotExist(statErr) { if rerun || os.IsNotExist(statErr) {
fmt.Println("Enter the path to your Wow directory below:") fmt.Println("Enter the path to your Wow directory below. Use the full path without shortcuts like '~' (ex: /home/user/epoch):")
var s string var s string
_, err = fmt.Scanln(&s) _, err = fmt.Scanln(&s)
if err != nil { if err != nil {
@ -46,29 +46,23 @@ func setupConfig(rerun bool) (*Config, error) {
} }
newConfig.WowDir = strings.TrimSpace(s) newConfig.WowDir = strings.TrimSpace(s)
for { p, err := promptYesNo(fmt.Sprintf("Do you want to use epochcli to launch Wow? Select No if you plan on using a launcher tool like Lutris (y/n): "))
fmt.Printf("Do you want to use epochcli to launch Wow? Select No if you plan on using a launcher tool like Lutris (y/n): ")
_, err = fmt.Scanf("%s", &s)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to read input: %v", err) return nil, err
} }
s = strings.TrimSpace(s) if p {
if s == "y" || s == "Y" {
newConfig.EnableLauncher = true newConfig.EnableLauncher = true
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
newConfig.LaunchCmd = path.Join(newConfig.WowDir, "Project-Epoch.exe") newConfig.LaunchCmd = path.Join(newConfig.WowDir, "Project-Epoch.exe")
exePath, err := os.Executable() exePath, err := os.Executable()
if err != nil { if err != nil {
fmt.Println("unable to create desktop shortcut: ", err) return nil, fmt.Errorf("unable to create desktop shortcut: %v", err)
break
} }
err = makeLink(exePath, path.Join(home, "Desktop", "Project-Epoch.lnk")) err = makeLink(exePath, path.Join(home, "Desktop", "Project-Epoch.lnk"))
if err != nil { if err != nil {
fmt.Println("unable to create desktop shortcut: ", err) return nil, fmt.Errorf("unable to create desktop shortcut: %v", err)
} }
break
} else { } else {
fmt.Println("Enter your launch command to start Wow below. If you would rather configure this later in the configuration file, just press Enter") fmt.Println("Enter your launch command to start Wow below. If you would rather configure this later in the configuration file, just press Enter")
fmt.Printf("> ") fmt.Printf("> ")
@ -82,18 +76,9 @@ func setupConfig(rerun bool) (*Config, error) {
if s != "" { if s != "" {
newConfig.LaunchCmd = s newConfig.LaunchCmd = s
} }
break
} }
} }
if s == "n" || s == "N" {
break
}
fmt.Println("Please enter a valid value of either 'y' or 'n'")
}
err = os.MkdirAll(filepath.Join(home, ".config", configDirName), 0755) err = os.MkdirAll(filepath.Join(home, ".config", configDirName), 0755)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create config directory: %v", err) return nil, fmt.Errorf("unable to create config directory: %v", err)
@ -120,3 +105,24 @@ func setupConfig(rerun bool) (*Config, error) {
return &newConfig, nil return &newConfig, nil
} }
func promptYesNo(prompt string) (bool, error) {
var s string
for {
fmt.Print(prompt)
_, err := fmt.Scanf("%s", &s)
if err != nil {
return false, fmt.Errorf("unable to read input: %v", err)
}
s := strings.TrimSpace(s)
if s == "y" || s == "Y" {
return true, nil
}
if s == "n" || s == "N" {
return false, nil
}
fmt.Println("Please enter a valid value of either 'y' or 'n'")
}
}

20
main.go
View File

@ -16,12 +16,22 @@ import (
) )
const ( const (
manifestUrl = "https://updater.project-epoch.net/api/manifest" manifestUrl = "https://updater.project-epoch.net/api/v2/manifest"
defaultWowPath = "/path/to/wow" defaultWowPath = "/path/to/wow"
defaultLaunchCmd = "not configured" defaultLaunchCmd = "not configured"
) )
func main() { func main() {
outOfDate, err := needUpdate()
if err != nil {
log.Fatal(err)
}
if outOfDate {
fmt.Println("There is a new version of epochcli, please update before running")
os.Exit(1)
}
var ( var (
helpFlag bool helpFlag bool
updateOnlyFlag bool updateOnlyFlag bool
@ -125,13 +135,14 @@ func downloadUpdate(config *Config, force bool) (DownloadStats, error) {
} }
defer outFile.Close() defer outFile.Close()
resp, err := http.Get(file.URL) for _, url := range []string{file.Urls.Cloudflare, file.Urls.Digitalocean, file.Urls.None} {
resp, err := http.Get(url)
if err != nil { if err != nil {
return stats, err return stats, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return stats, fmt.Errorf("failed to download update from %s, status code: %d", file.URL, resp.StatusCode) return stats, fmt.Errorf("failed to download update from %s, status code: %d", url, resp.StatusCode)
} }
_, err = io.Copy(outFile, resp.Body) _, err = io.Copy(outFile, resp.Body)
@ -139,6 +150,9 @@ func downloadUpdate(config *Config, force bool) (DownloadStats, error) {
return stats, err return stats, err
} }
break
}
stats.updated += 1 stats.updated += 1
} }

View File

@ -11,13 +11,18 @@ type File struct {
Hash string `json:"Hash"` Hash string `json:"Hash"`
Size int `json:"Size"` Size int `json:"Size"`
Custom bool `json:"Custom"` Custom bool `json:"Custom"`
URL string `json:"URL"` Urls struct {
Origin string `json:"Origin"` Digitalocean string `json:"digitalocean"`
Cloudflare string `json:"cloudflare"`
None string `json:"none"`
} `json:"Urls"`
} }
type Manifest struct { type Manifest struct {
Version string `json:"Version"` Version string `json:"Version"`
UID string `json:"Uid"`
Files []File `json:"Files"` Files []File `json:"Files"`
CheckedAt string `json:"checked_at"`
} }
func getManifest() (*Manifest, error) { func getManifest() (*Manifest, error) {

BIN
ss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

38
version.go Normal file
View File

@ -0,0 +1,38 @@
package main
import (
"fmt"
"io"
"net/http"
"regexp"
)
const (
version = "1.0.4"
versionUrl = "https://git.burkey.co/eburk/epochcli/raw/branch/master/version.go"
)
func needUpdate() (bool, error) {
resp, err := http.Get(versionUrl)
if err != nil {
return false, fmt.Errorf("unable to get version file: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("failed to download update, status code: %d", resp.StatusCode)
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return false, fmt.Errorf("unable to read response body: %v", err)
}
re := regexp.MustCompile(`const version = "([\d.]+)"`)
ver := re.FindStringSubmatch(string(b))
if len(ver) < 2 || ver[1] == "" {
return false, fmt.Errorf("unable to find version in response")
}
return ver[1] != version, nil
}