// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package wine import ( "bytes" "os" "path/filepath" "sort" "strings" "git.stormux.org/storm/cthulhu/internal/config" ) func program_name(commandLine string) string { name := strings.SplitN(commandLine, "\x00", 2)[0] return filepath.Base(strings.ReplaceAll(strings.ToLower(strings.TrimSpace(name)), "\\", "/")) } func wine_loader(exe, configured string) string { if executable(configured) { // Protontricks' proxy requires its own runtime environment. Join the // existing server with the actual Proton loader instead. file, err := os.Open(configured) if err == nil { data := make([]byte, 8192) count, _ := file.Read(data) file.Close() if !bytes.Contains(data[:count], []byte("PROTONTRICKS_STEAM_RUNTIME")) { return configured } } } directory := filepath.Dir(exe) for _, name := range []string{strings.TrimSuffix(filepath.Base(exe), "-preloader"), "wine"} { candidate := filepath.Join(directory, name) if executable(candidate) { return candidate } } for range 5 { parent := filepath.Dir(directory) if parent == directory || parent == string(filepath.Separator) { break } directory = parent for _, name := range []string{"wine", "wine64"} { candidate := filepath.Join(directory, "bin", name) if executable(candidate) { return candidate } } } return "" } // wholesale_controller_only reports whether the whole helper must run // without dialog reading. Only the global controller-only switch and the // environment override select it here; a helper binary remembered as // unconfirmed (see Manager.helper_unconfirmed) is the third trigger. A // listed program by itself never selects wholesale mode: the helper gates // it natively while other programs in the prefix keep dialog reading. func wholesale_controller_only(settings Settings) bool { if settings.ControllerOnly { return true } override := os.Getenv("WINE_ACCESS_NO_DIALOG_READER") return override != "" && override != "0" } // exclusion_names returns the hook-exclusion program names exactly as the // helper will store them: validated at the process boundary, lowercased, // deduplicated case-insensitively, and sorted. The validation mirrors the // helper's own rules so the count sent here matches the count its ready // record reports. func exclusion_names(settings Settings) []string { seen := make(map[string]bool, len(settings.ExcludedPrograms)) var names []string for _, name := range settings.ExcludedPrograms { if !config.ValidWineProgramName(name) { continue } lower := strings.ToLower(name) if seen[lower] { continue } seen[lower] = true names = append(names, lower) } sort.Strings(names) return names } func helper_arguments(controllerOnly bool, settings Settings) []string { var arguments []string for _, name := range exclusion_names(settings) { arguments = append(arguments, "--exclude-hook-program", name) } if controllerOnly { arguments = append(arguments, "--no-dialog-reader") } return arguments }