212 lines
6.0 KiB
Go
212 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
webview "github.com/jchv/go-webview2"
|
|
)
|
|
|
|
var AppVersion = "dev"
|
|
|
|
var currentHwnd atomic.Uintptr
|
|
|
|
func setupFileLogging() {
|
|
logPath := filepath.Join(userDataDir(), "debug.log")
|
|
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.SetOutput(f)
|
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
|
}
|
|
|
|
func main() {
|
|
setupFileLogging()
|
|
log.Printf("HPL Toolbox starting (pid=%d)", os.Getpid())
|
|
|
|
if focusExistingInstance() {
|
|
log.Printf("Another instance is running; focused it and exiting.")
|
|
return
|
|
}
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
port := listener.Addr().(*net.TCPAddr).Port
|
|
log.Printf("Server listening on 127.0.0.1:%d", port)
|
|
|
|
mux := buildMux()
|
|
srv := &http.Server{Handler: mux}
|
|
go func() {
|
|
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
log.Printf("Server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
if err := writeLockFile(port); err != nil {
|
|
log.Printf("Failed to write lock file: %v", err)
|
|
}
|
|
defer removeLockFile()
|
|
|
|
webviewTmp, err := os.MkdirTemp("", "hpltoolbox-wv2-*")
|
|
if err != nil {
|
|
log.Fatalf("Failed to create webview temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(webviewTmp)
|
|
|
|
w := webview.NewWithOptions(webview.WebViewOptions{
|
|
Debug: false,
|
|
AutoFocus: true,
|
|
DataPath: webviewTmp,
|
|
WindowOptions: webview.WindowOptions{
|
|
Title: "HPL Toolbox " + AppVersion,
|
|
Width: 1100,
|
|
Height: 720,
|
|
IconId: 1,
|
|
Center: true,
|
|
},
|
|
})
|
|
if w == nil {
|
|
log.Fatalln("Failed to create webview.")
|
|
}
|
|
defer w.Destroy()
|
|
|
|
// Stash HWND for the /api/focus handler.
|
|
currentHwnd.Store(uintptr(w.Window()))
|
|
|
|
w.SetSize(1100, 720, webview.HintNone)
|
|
w.Navigate(fmt.Sprintf("http://127.0.0.1:%d/", port))
|
|
w.Run()
|
|
|
|
// Window closed — shut the server down cleanly.
|
|
log.Printf("Window closed; shutting down server.")
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
_ = srv.Shutdown(ctx)
|
|
}
|
|
|
|
func buildMux() *http.ServeMux {
|
|
mux := http.NewServeMux()
|
|
|
|
// Pages
|
|
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" && r.URL.Path != "/home" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
HomePage(w, r)
|
|
})
|
|
mux.HandleFunc("GET /plec", PlecPage)
|
|
mux.HandleFunc("GET /applovin", ApplovinPage)
|
|
mux.HandleFunc("GET /base64", Base64Page)
|
|
mux.HandleFunc("GET /mraid", MraidPage)
|
|
mux.HandleFunc("GET /mintegral", MintegralPage)
|
|
mux.HandleFunc("GET /mobile", MobilePage)
|
|
mux.HandleFunc("GET /playworks", PlayworksPage)
|
|
mux.HandleFunc("GET /device-simulator", DeviceSimulatorPage)
|
|
mux.HandleFunc("GET /changelog", ChangelogPage)
|
|
mux.HandleFunc("GET /assets/qrcode.min.js", MobileQrScript)
|
|
|
|
// Shared API
|
|
mux.HandleFunc("POST /api/clipboard", ClipboardEndpoint)
|
|
mux.HandleFunc("POST /api/open", OpenEndpoint)
|
|
mux.HandleFunc("POST /api/focus", FocusEndpoint)
|
|
mux.HandleFunc("POST /api/betaTools", BetaToolsEndpoint)
|
|
mux.HandleFunc("GET /api/update/check", UpdateCheckEndpoint)
|
|
mux.HandleFunc("POST /api/drop/resolve", DropResolveEndpoint)
|
|
|
|
// PLEC
|
|
mux.HandleFunc("POST /api/plec/pick", PlecPick)
|
|
mux.HandleFunc("POST /api/plec/pickFolder", PlecPickFolder)
|
|
mux.HandleFunc("POST /api/plec/upload", PlecUpload)
|
|
|
|
// AppLovin
|
|
mux.HandleFunc("POST /api/applovin/pick", ApplovinPick)
|
|
mux.HandleFunc("POST /api/applovin/pickFolder", ApplovinPickFolder)
|
|
mux.HandleFunc("POST /api/applovin/upload", ApplovinUpload)
|
|
mux.HandleFunc("POST /api/applovin/saveQr", ApplovinSaveQR)
|
|
mux.HandleFunc("POST /api/applovin/saveAllQrs", ApplovinSaveAllQRs)
|
|
|
|
// Base64
|
|
mux.HandleFunc("POST /api/base64/pickFolder", Base64PickFolder)
|
|
mux.HandleFunc("POST /api/base64/pickFiles", Base64PickFiles)
|
|
mux.HandleFunc("POST /api/base64/scan", Base64Scan)
|
|
|
|
// MRAID Checker
|
|
mux.HandleFunc("POST /api/mraid/pickFolder", MraidPickFolder)
|
|
mux.HandleFunc("POST /api/mraid/pickFiles", MraidPickFiles)
|
|
mux.HandleFunc("POST /api/mraid/scan", MraidScan)
|
|
|
|
// Mintegral Checker
|
|
mux.HandleFunc("POST /api/mintegral/pick", MintegralPick)
|
|
mux.HandleFunc("POST /api/mintegral/load", MintegralLoad)
|
|
mux.HandleFunc("GET /mintegral/runtime", MintegralRuntimePage)
|
|
mux.HandleFunc("GET /mintegral/ad", MintegralAd)
|
|
mux.HandleFunc("GET /mintegral/monitor.js", MintegralMonitor)
|
|
mux.HandleFunc("GET /mintegral/preview-util.js", MintegralPreviewUtil)
|
|
|
|
// Send To Mobile
|
|
mux.HandleFunc("POST /api/mobile/pick", MobilePick)
|
|
mux.HandleFunc("POST /api/mobile/pickFolder", MobilePickFolder)
|
|
mux.HandleFunc("POST /api/mobile/start", MobileStart)
|
|
mux.HandleFunc("POST /api/mobile/stop", MobileStop)
|
|
|
|
// Playworks Converter
|
|
mux.HandleFunc("POST /api/playworks/pickSource", PlayworksPickSource)
|
|
mux.HandleFunc("POST /api/playworks/pickOutput", PlayworksPickOutput)
|
|
mux.HandleFunc("POST /api/playworks/convert", PlayworksConvert)
|
|
|
|
// Device Simulator
|
|
mux.HandleFunc("POST /api/device-simulator/pick", DeviceSimulatorPick)
|
|
mux.HandleFunc("POST /api/device-simulator/load", DeviceSimulatorLoad)
|
|
|
|
return mux
|
|
}
|
|
|
|
// FocusEndpoint brings the webview window to the foreground. Used by a second
|
|
// instance that wants to focus the running one before exiting.
|
|
func FocusEndpoint(w http.ResponseWriter, r *http.Request) {
|
|
hwnd := currentHwnd.Load()
|
|
if hwnd != 0 {
|
|
restoreAndFocus(hwnd)
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
var (
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
|
procShowWindow = user32.NewProc("ShowWindow")
|
|
procSetForeground = user32.NewProc("SetForegroundWindow")
|
|
procIsIconic = user32.NewProc("IsIconic")
|
|
)
|
|
|
|
const (
|
|
swRestore = 9
|
|
swShow = 5
|
|
)
|
|
|
|
func restoreAndFocus(hwnd uintptr) {
|
|
// If minimized, restore. Otherwise just ensure it's shown.
|
|
iconic, _, _ := procIsIconic.Call(hwnd)
|
|
if iconic != 0 {
|
|
_, _, _ = procShowWindow.Call(hwnd, swRestore)
|
|
} else {
|
|
_, _, _ = procShowWindow.Call(hwnd, swShow)
|
|
}
|
|
_, _, _ = procSetForeground.Call(hwnd)
|
|
}
|
|
|
|
// Silence "imported and not used" complaints if any.
|
|
var _ = unsafe.Sizeof(int(0))
|