add windows support

This commit is contained in:
2025-06-21 09:31:23 -07:00
parent a24eb6903f
commit 6199f0865e
8 changed files with 112 additions and 29 deletions

View File

@ -1,10 +1,12 @@
# epochcli # epochcli
CLI tool for updating and launching [Project Epoch](https://www.project-epoch.net/) on Linux & macOS. CLI tool for updating and launching [Project Epoch](https://www.project-epoch.net/) on Windows, Linux and macOS.
## Setup ## Setup
Installing is different between Linux and macOS ### Windows
Download and extract the latest binary from the [releases](https://git.burkey.co/eburk/epochcli/releases) page or build from source yourself, then copy `epochcli.exe` to a location of your choice. Easiest method is to just use the folder with your wow install.
### Linux ### Linux
A `wine` prefix with `dxvk` installed is sufficient, or you can use something like Lutris or faugus-launcher and just use `epochcli` for updating. A `wine` prefix with `dxvk` installed is sufficient, or you can use something like Lutris or faugus-launcher and just use `epochcli` for updating.

View File

@ -4,7 +4,10 @@ import (
"fmt" "fmt"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"os" "os"
"path"
"path/filepath" "path/filepath"
"runtime"
"strings"
) )
type Config struct { type Config struct {
@ -21,10 +24,11 @@ const (
var cfgPath string var cfgPath string
func setupConfig(rerun bool) (*Config, error) { func setupConfig(rerun bool) (*Config, error) {
home := os.Getenv("HOME") home, err := os.UserHomeDir()
if home == "" { if err != nil {
return nil, fmt.Errorf("$HOME environment variable not set") return nil, fmt.Errorf("unable to determine home directory: %v", err)
} }
cfgPath = filepath.Join(home, ".config", configDirName, configName)
newConfig := Config{ newConfig := Config{
WowDir: defaultWowPath, WowDir: defaultWowPath,
@ -32,37 +36,56 @@ func setupConfig(rerun bool) (*Config, error) {
EnableLauncher: false, EnableLauncher: false,
} }
cfgPath = filepath.Join(home, ".config", configDirName, configName)
_, 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:")
_, err := fmt.Scanln(&newConfig.WowDir) var s string
_, err = fmt.Scanln(&s)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("unable to read input: %v", err)
} }
newConfig.WowDir = strings.TrimSpace(s)
for { for {
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): ") 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): ")
var s string _, err = fmt.Scanf("%s", &s)
_, err := fmt.Scanf("%s", &s)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("unable to read input: %v", err)
} }
s = strings.TrimSpace(s)
if s == "y" || s == "Y" { if s == "y" || s == "Y" {
newConfig.EnableLauncher = true newConfig.EnableLauncher = true
if runtime.GOOS == "windows" {
newConfig.LaunchCmd = path.Join(newConfig.WowDir, "Project-Epoch.exe")
exePath, err := os.Executable()
if err != nil {
fmt.Println("unable to create desktop shortcut: ", err)
break
}
err = makeLink(exePath, path.Join(home, "Desktop", "Project-Epoch.lnk"))
if err != nil {
fmt.Println("unable to create desktop shortcut: ", err)
}
break
} 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("> ")
_, err = fmt.Scanf("%s", &s) _, err = fmt.Scanf("%s", &s)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("unable to read input: %v", err)
} }
s = strings.TrimSpace(s)
if s != "" { if s != "" {
newConfig.LaunchCmd = s newConfig.LaunchCmd = s
} }
break break
} }
}
if s == "n" || s == "N" { if s == "n" || s == "N" {
break break
@ -71,23 +94,26 @@ func setupConfig(rerun bool) (*Config, error) {
fmt.Println("Please enter a valid value of either 'y' or 'n'") fmt.Println("Please enter a valid value of either 'y' or 'n'")
} }
os.MkdirAll(filepath.Join(home, ".config", configDirName), 0755) err = os.MkdirAll(filepath.Join(home, ".config", configDirName), 0755)
if err != nil {
return nil, fmt.Errorf("unable to create config directory: %v", err)
}
file, err := os.Create(cfgPath) file, err := os.Create(cfgPath)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("unable to create config file: %v", err)
} }
defer file.Close() defer file.Close()
encoder := toml.NewEncoder(file) encoder := toml.NewEncoder(file)
if err = encoder.Encode(newConfig); err != nil { if err = encoder.Encode(newConfig); err != nil {
return nil, err return nil, fmt.Errorf("unable to encode config file: %v", err)
} }
fmt.Println("Created new config at ", cfgPath) fmt.Println("Created new config at ", cfgPath)
} }
_, err := toml.DecodeFile(cfgPath, &newConfig) _, err = toml.DecodeFile(cfgPath, &newConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }

8
config_unix.go Normal file
View File

@ -0,0 +1,8 @@
//go:build linux || darwin
package main
func makeLink(src, dst string) error {
// Noop on unix
return nil
}

36
config_windows.go Normal file
View File

@ -0,0 +1,36 @@
package main
import (
"github.com/go-ole/go-ole"
"github.com/go-ole/go-ole/oleutil"
"runtime"
)
func makeLink(src, dst string) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_SPEED_OVER_MEMORY)
if err != nil {
return err
}
oleShellObject, err := oleutil.CreateObject("WScript.Shell")
if err != nil {
return err
}
defer oleShellObject.Release()
wshell, err := oleShellObject.QueryInterface(ole.IID_IDispatch)
if err != nil {
return err
}
defer wshell.Release()
cs, err := oleutil.CallMethod(wshell, "CreateShortcut", dst)
if err != nil {
return err
}
idispatch := cs.ToIDispatch()
oleutil.PutProperty(idispatch, "TargetPath", src)
oleutil.CallMethod(idispatch, "Save")
return nil
}

7
go.mod
View File

@ -2,4 +2,9 @@ module git.burkey.co/eburk/epochcli
go 1.24.3 go 1.24.3
require github.com/BurntSushi/toml v1.5.0 require (
github.com/BurntSushi/toml v1.5.0
github.com/go-ole/go-ole v1.3.0
)
require golang.org/x/sys v0.1.0 // indirect

4
go.sum
View File

@ -1,2 +1,6 @@
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -70,10 +70,9 @@ func main() {
} }
fmt.Println("Starting Epoch...") fmt.Println("Starting Epoch...")
switch runtime.GOOS { if runtime.GOOS == "darwin" {
case "darwin":
exec.Command("open", config.LaunchCmd).Run() exec.Command("open", config.LaunchCmd).Run()
case "linux": } else {
exec.Command(config.LaunchCmd).Run() exec.Command(config.LaunchCmd).Run()
} }
} }

View File

@ -19,4 +19,7 @@ tar czvf epochcli-darwin-arm64.tar.gz bin/epochcli-darwin-arm64
GOOS=darwin GOARCH=amd64 go build -o bin/epochcli-darwin-amd64 GOOS=darwin GOARCH=amd64 go build -o bin/epochcli-darwin-amd64
tar czvf epochcli-darwin-amd64.tar.gz bin/epochcli-darwin-amd64 tar czvf epochcli-darwin-amd64.tar.gz bin/epochcli-darwin-amd64
GOOS=windows GOARCH=amd64 go build -o bin/epochcli-windows-amd64
zip epochcli-windows-amd64.zip bin/epochcli-windows-amd64
rm -rf bin rm -rf bin