package diffService import ( "bufio" "fmt" "io" "os" "os/exec" "path/filepath" "regexp" "strconv" "strings" "github.com/wailsapp/wails/v3/pkg/application" ) const ziftExecutablePath = "bin/zift.exe" var ziftPercentPattern = regexp.MustCompile(`(?i)(\d+(?:\.\d+)?)\s*%`) var ziftANSIPattern = regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~]`) func (h *DiffService) ApplyDiffZiff(gamePath, patchPath string) (bool, string) { launcherPath, err := os.Executable() if err != nil { return false, fmt.Sprintf("locate launcher directory: %v", err) } launcherDir := filepath.Dir(launcherPath) ziftPath := filepath.Join(launcherDir, filepath.FromSlash(ziftExecutablePath)) if _, err := os.Stat(ziftPath); err != nil { if cwdPath, cwdErr := filepath.Abs(filepath.FromSlash(ziftExecutablePath)); cwdErr == nil { if _, statErr := os.Stat(cwdPath); statErr == nil { ziftPath = cwdPath } } } if _, err := os.Stat(ziftPath); err != nil { return false, fmt.Sprintf("Zift executable not found at %s: %v", ziftPath, err) } if info, err := os.Stat(patchPath); err != nil || !info.Mode().IsRegular() { if err != nil { return false, fmt.Sprintf("patch file is unavailable: %v", err) } return false, "selected patch is not a file" } cmd := exec.Command(ziftPath, "-a", "-y", patchPath, gamePath) cmd.Dir = launcherDir outputReader, outputWriter := io.Pipe() cmd.Stdout = outputWriter cmd.Stderr = outputWriter application.Get().Event.Emit("diff:ziff-progress", map[string]interface{}{ "message": "Starting Zift…", "percent": -1.0, }) outputDone := make(chan string, 1) go func() { outputDone <- collectZiftOutput(outputReader) }() if err := cmd.Start(); err != nil { _ = outputWriter.Close() message := <-outputDone if message != "" { return false, fmt.Sprintf("run Zift: %v\n%s", err, message) } return false, fmt.Sprintf("run Zift: %v", err) } err = cmd.Wait() _ = outputWriter.Close() message := strings.TrimSpace(<-outputDone) if err != nil { if message == "" { return false, fmt.Sprintf("run Zift: %v", err) } return false, fmt.Sprintf("Zift failed: %v\n%s", err, message) } if message == "" { message = "Ziff patch applied successfully" } return true, message } func collectZiftOutput(reader io.Reader) string { buffered := bufio.NewReader(reader) var output strings.Builder var line strings.Builder emitLine := func() { text := strings.TrimSpace(ziftANSIPattern.ReplaceAllString(line.String(), "")) line.Reset() if text == "" { return } output.WriteString(text) output.WriteByte('\n') progress := -1.0 if match := ziftPercentPattern.FindStringSubmatch(text); len(match) > 1 { if value, err := strconv.ParseFloat(match[1], 64); err == nil && value >= 0 && value <= 100 { progress = value } } application.Get().Event.Emit("diff:ziff-progress", map[string]interface{}{ "message": text, "percent": progress, }) } for { value, err := buffered.ReadByte() if err != nil { if line.Len() > 0 { emitLine() } break } if value == '\r' || value == '\n' { emitLine() continue } line.WriteByte(value) } return output.String() }