package main import ( "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" "sync" "syscall" ) type PlecConfig struct { UploadUrl string `json:"uploadUrl"` OriginUrl string `json:"originUrl"` PreviewBase string `json:"previewBase"` SkipExistsCheck bool `json:"skipExistsCheck"` } type ApplovinConfig struct { Cookie string `json:"cookie"` DelayMs int `json:"delayMs"` RandomizeUserAgent *bool `json:"randomizeUserAgent,omitempty"` } type SendToMobileConfig struct { Port int `json:"port"` } type AppConfig struct { Plec PlecConfig `json:"plec"` Applovin ApplovinConfig `json:"applovin"` SendToMobile SendToMobileConfig `json:"sendToMobile"` BetaToolsEnabled bool `json:"betaToolsEnabled"` LastPickDir string `json:"lastPickDir,omitempty"` } var configMu sync.Mutex // userDataDir returns %LOCALAPPDATA%\HPLToolbox, creating it if needed. // All mutable app files (config, lock, logs, WebView2 cache) live here so // they are never placed next to the executable, which may be on a synced or // antivirus-watched drive and would cause message-loop stalls. func userDataDir() string { local := os.Getenv("LOCALAPPDATA") if local == "" { local = os.Getenv("APPDATA") } if local == "" { return appDir() } dir := filepath.Join(local, "HPLToolbox") _ = os.MkdirAll(dir, 0755) return dir } func defaultConfig() AppConfig { return AppConfig{ Plec: PlecConfig{ UploadUrl: "http://167.99.227.249/src/upload_HTML_Experimental.php", OriginUrl: "http://167.99.227.249", PreviewBase: "https://playable.applovindemo.com/phaser/", SkipExistsCheck: false, }, Applovin: ApplovinConfig{Cookie: "", DelayMs: 5000}, SendToMobile: SendToMobileConfig{Port: 0}, } } func appDir() string { exe, err := os.Executable() if err != nil { wd, _ := os.Getwd() return wd } // `go run` puts the exe in a temp dir; fall back to cwd in that case. resolved, err := filepath.EvalSymlinks(exe) if err == nil { exe = resolved } dir := filepath.Dir(exe) if strings.Contains(strings.ToLower(dir), "go-build") { wd, _ := os.Getwd() return wd } return dir } func configPath() string { return filepath.Join(userDataDir(), "config.json") } func LoadConfig() AppConfig { configMu.Lock() defer configMu.Unlock() cfg := defaultConfig() data, err := os.ReadFile(configPath()) if err != nil { return cfg } _ = json.Unmarshal(data, &cfg) return cfg } func SaveConfig(cfg AppConfig) error { configMu.Lock() defer configMu.Unlock() data, err := json.MarshalIndent(cfg, "", " ") if err != nil { return err } return os.WriteFile(configPath(), data, 0644) } func OpenInBrowser(url string) { // `start` is a cmd builtin. Quoted empty arg is the window title. cmd := hiddenCommand("cmd", "/c", "start", "", url) _ = cmd.Start() } func CopyToClipboard(text string) error { cmd := hiddenCommand("clip") cmd.Stdin = strings.NewReader(text) return cmd.Run() } type FileFilter struct { Name string Extensions []string } func psEscape(s string) string { return strings.ReplaceAll(s, "'", "''") } func hiddenCommand(name string, args ...string) *exec.Cmd { cmd := exec.Command(name, args...) cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: true, CreationFlags: 0x08000000, } return cmd } func PickFiles(title string, filters []FileFilter, multi bool, initialDir string) []string { filterStr := "All files|*.*" if len(filters) > 0 { parts := make([]string, 0, len(filters)) for _, f := range filters { exts := make([]string, 0, len(f.Extensions)) for _, e := range f.Extensions { exts = append(exts, "*."+e) } parts = append(parts, fmt.Sprintf("%s|%s", f.Name, strings.Join(exts, ";"))) } filterStr = strings.Join(parts, "|") } multiStr := "false" if multi { multiStr = "true" } script := fmt.Sprintf(` Add-Type -AssemblyName System.Windows.Forms $dlg = New-Object System.Windows.Forms.OpenFileDialog $dlg.Title = '%s' $dlg.Filter = '%s' $dlg.Multiselect = $%s if ('%s'.Length -gt 0) { $dlg.InitialDirectory = '%s' } if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $dlg.FileNames | ForEach-Object { Write-Output $_ } } `, psEscape(title), psEscape(filterStr), multiStr, psEscape(initialDir), psEscape(initialDir)) out, err := hiddenCommand("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output() if err != nil { return nil } lines := strings.Split(string(out), "\n") result := make([]string, 0, len(lines)) for _, l := range lines { t := strings.TrimSpace(l) if t != "" { result = append(result, t) } } return result } func PickSaveFile(title, defaultName string, filters []FileFilter, initialDir string) string { filterStr := "All files|*.*" if len(filters) > 0 { parts := make([]string, 0, len(filters)) for _, f := range filters { exts := make([]string, 0, len(f.Extensions)) for _, e := range f.Extensions { exts = append(exts, "*."+e) } parts = append(parts, fmt.Sprintf("%s|%s", f.Name, strings.Join(exts, ";"))) } filterStr = strings.Join(parts, "|") } script := fmt.Sprintf(` Add-Type -AssemblyName System.Windows.Forms $dlg = New-Object System.Windows.Forms.SaveFileDialog $dlg.Title = '%s' $dlg.Filter = '%s' $dlg.FileName = '%s' if ('%s'.Length -gt 0) { $dlg.InitialDirectory = '%s' } if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $dlg.FileName } `, psEscape(title), psEscape(filterStr), psEscape(defaultName), psEscape(initialDir), psEscape(initialDir)) out, err := hiddenCommand("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output() if err != nil { return "" } return strings.TrimSpace(string(out)) } func PickFolder(title, initialDir string) string { script := fmt.Sprintf(` Add-Type -AssemblyName System.Windows.Forms $dlg = New-Object System.Windows.Forms.FolderBrowserDialog $dlg.Description = '%s' if ('%s'.Length -gt 0) { $dlg.SelectedPath = '%s' } if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $dlg.SelectedPath } `, psEscape(title), psEscape(initialDir), psEscape(initialDir)) out, err := hiddenCommand("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output() if err != nil { return "" } return strings.TrimSpace(string(out)) }