Files

273 lines
7.5 KiB
Go

package fsService
import (
"fmt"
"g-launcher/pkg/logger"
"g-launcher/pkg/sevenzip"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/wailsapp/wails/v3/pkg/application"
"golang.org/x/sys/windows"
)
type FSService struct{}
func (f *FSService) PickFolder() (string, error) {
dialog := application.Get().Dialog.OpenFile().
CanChooseDirectories(true).
CanChooseFiles(false).
ResolvesAliases(true)
if runtime.GOOS == "darwin" {
dialog.SetMessage("Select a folder")
} else {
dialog.SetTitle("Select a folder")
}
res, err := dialog.PromptForSingleSelection()
if err != nil {
logger.Error("PickFolder error: %v", err)
} else {
logger.Info("PickFolder selected: %s", res)
}
return res, err
}
func (f *FSService) PickFile(filter string) (string, error) {
dialog := application.Get().Dialog.OpenFile().
CanChooseFiles(true).
ResolvesAliases(true)
if runtime.GOOS == "darwin" {
dialog.SetMessage("Select a file")
} else {
dialog.SetTitle("Select a file")
}
if filter == "exe" {
dialog.AddFilter("Executable Files (*.exe)", "*.exe")
} else if filter == "zip" {
dialog.AddFilter("ZIP archives (*.zip)", "*.zip")
} else if filter == "patch" {
dialog.AddFilter("Patch files (*.zip;*.7z;*.ziff)", "*.zip;*.7z;*.ziff")
}
if path, err := dialog.PromptForSingleSelection(); err == nil {
logger.Info("PickFile (%s) selected: %s", filter, path)
return path, nil
}
return "", nil
}
func (f *FSService) DirExists(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
return info.IsDir()
}
func (f *FSService) FileExists(path string) bool {
if info, err := os.Stat(path); err == nil {
return info.Mode().IsRegular()
}
return false
}
func (f *FSService) GetDir(path string) string {
return filepath.Dir(path)
}
func (f *FSService) Join(paths ...string) string {
return filepath.Join(paths...)
}
func (f *FSService) RemoveFile(path string) error {
logger.Info("RemoveFile: %s", path)
return os.Remove(path)
}
func (f *FSService) StartApp(path string) (bool, string) {
absPath, err := filepath.Abs(path)
if err != nil {
logger.Error("StartApp: failed to resolve absolute path for %s: %v", path, err)
return false, err.Error()
}
if _, err := os.Stat(absPath); os.IsNotExist(err) {
logger.Error("StartApp: executable file not found: %s", absPath)
return false, "file not found: " + absPath
}
dir := filepath.Dir(absPath)
cmd := exec.Command(absPath)
cmd.Dir = dir
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.CREATE_NEW_CONSOLE | windows.CREATE_BREAKAWAY_FROM_JOB,
NoInheritHandles: true,
}
logger.Info("StartApp: launching process %s in directory %s", absPath, dir)
err = cmd.Start()
if err != nil {
logger.Error("StartApp: failed to start process %s: %v", absPath, err)
return false, err.Error()
}
pid := cmd.Process.Pid
logger.Info("StartApp: process started successfully, PID: %d", pid)
go func() {
waitErr := cmd.Wait()
base := strings.ToLower(filepath.Base(path))
if waitErr != nil {
logger.Warn("StartApp: process PID %d (%s) exited with error: %v", pid, base, waitErr)
} else {
logger.Info("StartApp: process PID %d (%s) exited cleanly", pid, base)
}
if strings.HasSuffix(base, "starrail.exe") || strings.HasSuffix(base, "zenlesszonezerobeta.exe") || strings.HasSuffix(base, "launcher.exe") {
application.Get().Event.Emit("game:exit")
}
}()
return true, ""
}
func (f *FSService) StartWithConsole(path string) (bool, string) {
absPath, err := filepath.Abs(path)
if err != nil {
logger.Error("StartWithConsole: failed to resolve absolute path for %s: %v", path, err)
return false, err.Error()
}
if _, err := os.Stat(absPath); os.IsNotExist(err) {
logger.Error("StartWithConsole: file not found: %s", absPath)
return false, "file not found: " + absPath
}
dir := filepath.Dir(absPath)
cmd := exec.Command(absPath)
cmd.Dir = dir
cmd.Stdin = nil
cmd.Stdout = nil
cmd.Stderr = nil
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.CREATE_NEW_CONSOLE |
windows.CREATE_BREAKAWAY_FROM_JOB,
NoInheritHandles: true,
}
logger.Info("StartWithConsole: starting process %s (WorkingDir: %s)", absPath, dir)
err = cmd.Start()
if err != nil {
logger.Error("StartWithConsole: failed to start %s: %v", absPath, err)
return false, err.Error()
}
pid := cmd.Process.Pid
logger.Info("StartWithConsole: process started successfully, PID: %d", pid)
go func() {
waitErr := cmd.Wait()
base := strings.ToLower(filepath.Base(path))
if waitErr != nil {
logger.Warn("StartWithConsole: process PID %d (%s) exited with error: %v", pid, base, waitErr)
} else {
logger.Info("StartWithConsole: process PID %d (%s) exited cleanly", pid, base)
}
if base == "launcher.exe" || base == "starrail.exe" {
application.Get().Event.Emit("game:exit")
} else if base == "firefly-go_win.exe" || base == "gameserver.exe" || base == "gameserver-go.exe" || base == "server.exe" || base == "sdkserver-go.exe" || base == "sdkserver.exe" {
application.Get().Event.Emit("server:exit")
} else if base == "firefly-go-proxy.exe" || base == "proxy.exe" {
application.Get().Event.Emit("proxy:exit")
}
}()
return true, ""
}
func (f *FSService) OpenFolder(path string) (bool, string) {
absPath, err := filepath.Abs(path)
if err != nil {
logger.Error("OpenFolder: resolve path failed for %s: %v", path, err)
return false, "failed to resolve absolute path: " + err.Error()
}
if !f.DirExists(absPath) {
logger.Warn("OpenFolder: directory not found: %s", absPath)
return false, "directory not found: " + absPath
}
url := "file:///" + filepath.ToSlash(absPath)
logger.Info("OpenFolder: opening %s", url)
application.Get().Browser.OpenURL(url)
return true, ""
}
func (f *FSService) FileExistsInZip(archivePath, fileInside string) (bool, string) {
exists, err := sevenzip.IsFileIn7z(archivePath, fileInside)
if err != nil {
logger.Error("FileExistsInZip error: %v", err)
return false, err.Error()
}
return exists, ""
}
func (f *FSService) ReadConfigFile(gameDir, fileName string) (string, error) {
if strings.TrimSpace(gameDir) == "" {
return "", fmt.Errorf("select the game folder first")
}
target := filepath.Join(gameDir, fileName)
content, err := os.ReadFile(target)
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", fmt.Errorf("read file %s: %w", target, err)
}
return string(content), nil
}
func (f *FSService) WriteConfigFile(gameDir, fileName, content string) error {
if strings.TrimSpace(gameDir) == "" {
return fmt.Errorf("select the game folder first")
}
target := filepath.Join(gameDir, fileName)
logger.Info("WriteConfigFile: saving %s", target)
return os.WriteFile(target, []byte(content), 0644)
}
// LogMessage receives log entries from the frontend and appends them to launcher.log
func (f *FSService) LogMessage(level string, message string) {
logger.GetInstance().Log(level, "[UI] "+message)
}
// GetLogFilePath returns the absolute path of launcher.log
func (f *FSService) GetLogFilePath() (string, error) {
p := logger.GetLogPath()
if p == "" {
return "", fmt.Errorf("log path not initialized")
}
return p, nil
}
// OpenLogsFolder opens the folder containing launcher.log in Windows File Explorer
func (f *FSService) OpenLogsFolder() (bool, string) {
dir := logger.GetLogDir()
if dir == "" {
return false, "log directory not found"
}
return f.OpenFolder(dir)
}
// ReadLogs returns the last maxLines of the log file
func (f *FSService) ReadLogs(maxLines int) (string, error) {
if maxLines <= 0 {
maxLines = 200
}
return logger.ReadRecentLogs(maxLines)
}