using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Input; using System.Windows.Threading; namespace BlackbirdInterface { public partial class MainWindow : Window, IIntelDetailsProvider { private readonly TelemetryEventStore _allEvents = new(); private readonly BulkObservableCollection _focusedEvents = new(); private readonly Dictionary> _focusedClusterMembers = new(); private readonly BoundedStringPool _telemetryTextPool = new(8192); private readonly HashSet _knownLaneKeys = new(StringComparer.Ordinal); private const double DefaultTimelineViewDurationSeconds = 120; private bool _viewportRefreshPending; private DateTime _latestEventTimestampUtc; private DateTime _captureStartUtc; private EventsFloatWindow? _eventsFloatWindow; private EventLogWindow? _eventLogWindow; private PerformanceFloatWindow? _performanceFloatWindow; private EtwFloatWindow? _etwFloatWindow; private HeuristicsFloatWindow? _heuristicsFloatWindow; private ChildProcessGraphWindow? _childProcessGraphWindow; private bool _eventsPaneVisible = true; private bool _performancePaneVisible = true; private bool _eventsPaneFloating; private bool _performancePaneFloating; private bool _etwPaneFloating; private bool _heuristicsPaneFloating; private bool _heuristicsOnTop; // Lane focus key (optional) private string? _laneFocusKey; // Performance sampler (system-wide for now, PID-aware if provided) private PerformanceSampler? _perf; // Explorer items private readonly ObservableCollection _explorer = new(); private readonly ObservableCollection _processTabs = new(); private readonly int _defaultPid = Process.GetCurrentProcess().Id; private bool _suppressTabSelectionChange; private ProcessSessionTab? _currentSession; private int _samplerPid; private bool _performanceOnTop; private bool _hasPerformanceData; private bool _hasIpcUplinkData; private bool _connectivityHealthy = true; private bool _draggingEventsPaneHeader; private bool _draggingPerformancePaneHeader; private bool _openingProcessPicker; private bool _connectInProgress; private bool _pendingLaunchOptions; private bool _pendingUseUsermodeHooks; private bool _pendingAutoOpenApiGraph; private bool _pendingHookPreconfigured; private bool _pendingLaunchStartsSuspended; private bool _pendingLeaveSuspendedAfterReady; private bool _pendingLaunchOwnedByInterface; private bool _isMainWindowShuttingDown; private BlackbirdBackendSession? _preparedLaunchBackendSession; private int _preparedLaunchBackendPid; private MainInterfaceViewMode _mainViewMode = MainInterfaceViewMode.Telemetry; private readonly BulkObservableCollection _apiViewRows = new(); private Process? _targetExitWatchProcess; private int _targetExitWatchPid; private Vector _floatingPaneDragOffset; private readonly DispatcherTimer _detachedEventLogRefreshTimer; private readonly DispatcherTimer _processStateRefreshTimer; private readonly DispatcherTimer _apiGraphRefreshTimer; private readonly DispatcherTimer _childProcessGraphRefreshTimer; private readonly DispatcherTimer _timelineLiveTickTimer; private bool _scrollSyncPending; private bool _topTimeTravelSyncing; private bool _toolbarViewMenuSyncing; private bool _followLiveTimeline = true; private bool _eventSelectionSyncing; private EventSelectionKey? _selectedEventAnchor; private double _pendingScrollStartSeconds; private bool _targetExecutionSuspended; private readonly List _apiViewSnapshotRows = new(); private readonly List _apiGraphSnapshotRows = new(); private DateTime _scopeStatusCacheUtc; private int _scopeStatusCachePid; private IntelScopeStatus _scopeStatusCache = IntelScopeStatus.Unknown; private static readonly Brush ProcessStateRunningBackground = new SolidColorBrush(Color.FromRgb(0x14, 0x3A, 0x1E)); private static readonly Brush ProcessStateRunningBorder = new SolidColorBrush(Color.FromRgb(0x49, 0xC1, 0x66)); private static readonly Brush ProcessStateRunningForeground = new SolidColorBrush(Color.FromRgb(0xA9, 0xF5, 0xB8)); private static readonly Brush ProcessStateWaitingBackground = new SolidColorBrush(Color.FromRgb(0x3D, 0x34, 0x16)); private static readonly Brush ProcessStateWaitingBorder = new SolidColorBrush(Color.FromRgb(0xDE, 0xC2, 0x62)); private static readonly Brush ProcessStateWaitingForeground = new SolidColorBrush(Color.FromRgb(0xF8, 0xE9, 0xAF)); private static readonly Brush ProcessStateExitedBackground = new SolidColorBrush(Color.FromRgb(0x28, 0x1A, 0x1A)); private static readonly Brush ProcessStateExitedBorder = new SolidColorBrush(Color.FromRgb(0x73, 0x4A, 0x4A)); private static readonly Brush ProcessStateExitedForeground = new SolidColorBrush(Color.FromRgb(0xE1, 0xB4, 0xB4)); private static readonly Brush ProcessStateUnknownBackground = new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x1E)); private static readonly Brush ProcessStateUnknownBorder = new SolidColorBrush(Color.FromRgb(0x4A, 0x4A, 0x4A)); private static readonly Brush ProcessStateUnknownForeground = new SolidColorBrush(Color.FromRgb(0xB8, 0xB8, 0xB8)); private static readonly Brush ToolbarInactiveBackground = new SolidColorBrush(Color.FromRgb(0x18, 0x18, 0x18)); private static readonly Brush ToolbarInactiveBorder = new SolidColorBrush(Color.FromRgb(0x46, 0x46, 0x46)); private static readonly Brush ToolbarInactiveForeground = new SolidColorBrush(Color.FromRgb(0x7A, 0x7A, 0x7A)); private static readonly Brush ToolbarTargetBackground = new SolidColorBrush(Color.FromRgb(0x4C, 0x14, 0x18)); private static readonly Brush ToolbarTargetBorder = new SolidColorBrush(Color.FromRgb(0xE0, 0x52, 0x5A)); private static readonly Brush ToolbarTargetForeground = new SolidColorBrush(Color.FromRgb(0xFF, 0xC6, 0xCB)); private static readonly Brush ToolbarPauseBackground = new SolidColorBrush(Color.FromRgb(0x3B, 0x24, 0x11)); private static readonly Brush ToolbarPauseBorder = new SolidColorBrush(Color.FromRgb(0xE1, 0x92, 0x2C)); private static readonly Brush ToolbarPauseForeground = new SolidColorBrush(Color.FromRgb(0xF0, 0xAB, 0x4B)); private static readonly Brush ToolbarResumeBackground = new SolidColorBrush(Color.FromRgb(0x14, 0x35, 0x1C)); private static readonly Brush ToolbarResumeBorder = new SolidColorBrush(Color.FromRgb(0x4C, 0xC5, 0x68)); private static readonly Brush ToolbarResumeForeground = new SolidColorBrush(Color.FromRgb(0x98, 0xEF, 0xAD)); private static readonly Brush ToolbarStopBackground = new SolidColorBrush(Color.FromRgb(0x3D, 0x15, 0x1A)); private static readonly Brush ToolbarStopBorder = new SolidColorBrush(Color.FromRgb(0xD6, 0x43, 0x43)); private static readonly Brush ToolbarStopForeground = new SolidColorBrush(Color.FromRgb(0xF0, 0x74, 0x74)); private const string WindowTitleBase = "Blackbird"; private const uint ProcessTerminate = 0x0001; private const uint ProcessSynchronize = 0x00100000; private const uint ProcessSuspendResume = 0x0800; private const uint ProcessVmRead = 0x0010; private const uint ProcessQueryLimitedInformation = 0x1000; private enum MainInterfaceViewMode { Telemetry = 0, Api = 1 } public MainWindow() { InitializeComponent(); SetResourceReference(BackgroundProperty, "WinBgBrush"); RootGrid.SetResourceReference(Panel.BackgroundProperty, "WinBgBrush"); WindowThemeHelper.ApplyDarkTitleBar(this); _detachedEventLogRefreshTimer = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromMilliseconds(110) }; _detachedEventLogRefreshTimer.Tick += DetachedEventLogRefreshTimer_Tick; _processStateRefreshTimer = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromMilliseconds(900) }; _processStateRefreshTimer.Tick += (_, __) => { ValidateCurrentSessionState(); RefreshProcessStateBadge(); }; _apiGraphRefreshTimer = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromMilliseconds(180) }; _apiGraphRefreshTimer.Tick += ApiGraphRefreshTimer_Tick; _childProcessGraphRefreshTimer = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromMilliseconds(140) }; _childProcessGraphRefreshTimer.Tick += ChildProcessGraphRefreshTimer_Tick; _timelineLiveTickTimer = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromMilliseconds(250) }; _timelineLiveTickTimer.Tick += (_, __) => { if (_followLiveTimeline && _allEvents.Count > 0 && !_viewportRefreshPending) { UpdateScrollBar(); double viewport = Math.Max(1, EventsPaneHost?.Scroll?.ViewportSize ?? 1); ApplyTimelineViewport(ComputeTimelineMaxStart(viewport), syncScroll: true, updateFollowState: false); } }; Loaded += OnLoaded; Closing += OnClosing; Closed += OnClosed; RefreshToolbarCommandState(); } private void OnLoaded(object sender, RoutedEventArgs e) { PidBox.Text = string.Empty; _samplerPid = 0; _captureStartUtc = AnchorCaptureStartUtc(DefaultTimelineViewDurationSeconds); _latestEventTimestampUtc = _captureStartUtc; // Bind grid to focused items EventsPaneHost.Grid.ItemsSource = _focusedEvents; EventsPaneHost.SetHasData(false); EventsPaneHost.SetEventLogDetached(false); // Timeline init EventsPaneHost.Timeline.CaptureStartUtc = _captureStartUtc; EventsPaneHost.Timeline.ViewDurationSeconds = DefaultTimelineViewDurationSeconds; EventsPaneHost.Timeline.ViewStartSeconds = 0; // Timeline interactions EventsPaneHost.Timeline.LaneInteraction += Timeline_LaneInteraction; EventsPaneHost.Timeline.SelectedEventChanged += Timeline_SelectedEventChanged; EventsPaneHost.Timeline.EventDoubleClicked += Timeline_EventDoubleClicked; // Grid selection sync EventsPaneHost.Grid.SelectionChanged += Grid_SelectionChanged; // Scrollbar sync EventsPaneHost.Scroll.ValueChanged += Scroll_ValueChanged; // Pane header buttons EventsPaneHost.CloseRequested += (_, __) => HideEventsPane(); EventsPaneHost.FloatRequested += (_, __) => ToggleFloatDock(); EventsPaneHost.LogPopoutRequested += (_, __) => ToggleEventLogDock(); EventsPaneHost.SettingsRequested += (_, __) => OpenLaneSettings(); EventsPaneHost.LaneFilterSelectionChanged += (_, key) => SetLaneFocus(key); EventsPaneHost.EventLogEntryOpenRequested += EventsPaneHost_EventLogEntryOpenRequested; EventsPaneHost.HeaderDragStarted += (_, a) => BeginPaneHeaderDrag(isEventsPane: true, a.ScreenPosition); EventsPaneHost.HeaderDragDelta += (_, a) => ContinuePaneHeaderDrag(isEventsPane: true, a.ScreenPosition); EventsPaneHost.HeaderDragCompleted += (_, a) => EndPaneHeaderDrag(isEventsPane: true, a.ScreenPosition); PerformancePaneHost.CloseRequested += (_, __) => HidePerformancePane(); PerformancePaneHost.ReorderRequested += (_, __) => TogglePaneOrder(); PerformancePaneHost.FloatRequested += (_, __) => TogglePerformanceFloatDock(); PerformancePaneHost.ThreadDoubleClicked += PerformancePaneHost_ThreadDoubleClicked; PerformancePaneHost.ParallelStacksRequested += (_, __) => OpenParallelStacksWindow(); PerformancePaneHost.HeaderDragStarted += (_, a) => BeginPaneHeaderDrag(isEventsPane: false, a.ScreenPosition); PerformancePaneHost.HeaderDragDelta += (_, a) => ContinuePaneHeaderDrag(isEventsPane: false, a.ScreenPosition); PerformancePaneHost.HeaderDragCompleted += (_, a) => EndPaneHeaderDrag(isEventsPane: false, a.ScreenPosition); EtwPaneHost.ReorderRequested += (_, __) => ToggleIntelPaneOrder(); EtwPaneHost.FloatRequested += (_, __) => ToggleEtwFloatDock(); EtwPaneHost.CloseRequested += (_, __) => HideEtwPane(); EtwPaneHost.InspectRequested += (_, __) => OpenEtwInspector(); HeuristicsPaneHost.ReorderRequested += (_, __) => ToggleIntelPaneOrder(); HeuristicsPaneHost.FloatRequested += (_, __) => ToggleHeuristicsFloatDock(); HeuristicsPaneHost.CloseRequested += (_, __) => HideHeuristicsPane(); HeuristicsPaneHost.InspectRequested += (_, __) => OpenHeuristicsInspector(); FilesystemPaneHost.CloseRequested += (_, __) => HideFilesystemPane(); FilesystemPaneHost.InspectRequested += (_, __) => OpenFilesystemInspector(); ProcessRelationsPaneHost.CloseRequested += (_, __) => HideProcessRelationsPane(); ProcessRelationsPaneHost.InspectRequested += (_, __) => OpenProcessRelationsInspector(); ProcessRelationsPaneHost.GraphStateChanged += ProcessRelationsPaneHost_GraphStateChanged; IpcUplinkPaneHost.CloseRequested += (_, __) => HideIpcUplinkPane(); // Explorer setup SetupExplorer(); SetupProcessTabs(); InitializeBackendUi(); InitializeRuntimeConfigDefaults(); ApiViewDataGrid.ItemsSource = _apiViewRows; ApiViewDataGrid.SelectedIndex = -1; UpdateApiViewSelection(null); SetMainInterfaceViewMode(MainInterfaceViewMode.Telemetry); ApplyUplinkStatusVisual(healthy: null); UpdateScrollBar(); FocusViewport(); UpdateTopTimeTravelBar(); RebuildToolbarViewMenuOptions(); // Performance sampler + UI sync _perf = new PerformanceSampler(); _perf.SampleArrived += Perf_SampleArrived; // Make performance pane aware of capture start immediately PerformancePaneHost.SetCaptureStart(_captureStartUtc); PerformancePaneHost.SetPid(TryGetPid()); PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.SetTargetSuspended(false); SyncPerformanceViewToTimeline(); StatusBlock.Text = "NO TARGET SELECTED"; ApplyPaneOrder(); ApplyIntelPaneOrder(); _processStateRefreshTimer.Start(); _apiGraphRefreshTimer.Start(); _timelineLiveTickTimer.Start(); RefreshProcessStateBadge(); } private void SetupProcessTabs() { ProcessTabs.ItemsSource = _processTabs; _currentSession = null; } private void OnClosing(object? sender, CancelEventArgs e) { _ = sender; if (!PrepareSessionShutdown()) { e.Cancel = true; } } private void OnClosed(object? sender, EventArgs e) { _isMainWindowShuttingDown = true; _detachedEventLogRefreshTimer.Stop(); _processStateRefreshTimer.Stop(); _apiGraphRefreshTimer.Stop(); _timelineLiveTickTimer.Stop(); TerminateLaunchOwnedTargetsOnShutdown(); if (_perf != null) { _perf.SampleArrived -= Perf_SampleArrived; _perf.Stop(); _perf = null; } if (_eventsFloatWindow != null) { _eventsFloatWindow.Closing -= EventsFloatWindow_Closing; _eventsFloatWindow.Closed -= EventsFloatWindow_Closed; if (_eventsFloatWindow.Content == EventsPaneHost) { _eventsFloatWindow.Content = null; } _eventsFloatWindow.Close(); _eventsFloatWindow = null; } if (_eventLogWindow != null) { _eventLogWindow.Closed -= EventLogWindow_Closed; _eventLogWindow.Close(); _eventLogWindow = null; } if (_performanceFloatWindow != null) { _performanceFloatWindow.Closing -= PerformanceFloatWindow_Closing; _performanceFloatWindow.Closed -= PerformanceFloatWindow_Closed; if (_performanceFloatWindow.Content == PerformancePaneHost) { _performanceFloatWindow.Content = null; } _performanceFloatWindow.Close(); _performanceFloatWindow = null; } if (_etwFloatWindow != null) { _etwFloatWindow.Closing -= EtwFloatWindow_Closing; _etwFloatWindow.Closed -= EtwFloatWindow_Closed; if (_etwFloatWindow.Content == EtwPaneHost) { _etwFloatWindow.Content = null; } _etwFloatWindow.Close(); _etwFloatWindow = null; } if (_heuristicsFloatWindow != null) { _heuristicsFloatWindow.Closing -= HeuristicsFloatWindow_Closing; _heuristicsFloatWindow.Closed -= HeuristicsFloatWindow_Closed; if (_heuristicsFloatWindow.Content == HeuristicsPaneHost) { _heuristicsFloatWindow.Content = null; } _heuristicsFloatWindow.Close(); _heuristicsFloatWindow = null; } if (_childProcessGraphWindow != null) { SaveChildProcessGraphStateToSession(); _childProcessGraphWindow.Closed -= ChildProcessGraphWindow_Closed; _childProcessGraphWindow.Close(); _childProcessGraphWindow = null; } ProcessRelationsPaneHost.GraphStateChanged -= ProcessRelationsPaneHost_GraphStateChanged; _childProcessGraphRefreshTimer.Stop(); SaveIntelSessionState(_currentSession?.Pid ?? 0); StopTargetExitWatcher(); DisposePreparedLaunchBackendSession(); StopBackendSession(); HideDockPreview(); CleanupTemporarySessionBackingStores(); } private void SetupExplorer() { _explorer.Clear(); _explorer.Add(new GraphExplorerItem("Events", new SolidColorBrush(Color.FromRgb(0xAA, 0x7D, 0x4A))) { IsEnabled = true }); _explorer.Add(new GraphExplorerItem("Performance", new SolidColorBrush(Color.FromRgb(0x58, 0xB6, 0x58))) { IsEnabled = true }); _explorer.Add(new GraphExplorerItem("ETW", new SolidColorBrush(Color.FromRgb(0xD2, 0x89, 0x34))) { IsEnabled = true }); _explorer.Add(new GraphExplorerItem("Heuristics", new SolidColorBrush(Color.FromRgb(0xD2, 0x55, 0x55))) { IsEnabled = true }); _explorer.Add(new GraphExplorerItem("Filesystem", new SolidColorBrush(Color.FromRgb(0x45, 0x8E, 0x7A))) { IsEnabled = true }); _explorer.Add(new GraphExplorerItem("Process Relations", new SolidColorBrush(Color.FromRgb(0xD2, 0xB8, 0x55))) { IsEnabled = true }); _explorer.Add(new GraphExplorerItem("IPC Uplink", new SolidColorBrush(Color.FromRgb(0x52, 0xA9, 0xB3))) { IsEnabled = false, ShowDetails = false, DetailPrimary = "", DetailSecondary = "" }); GraphExplorer.ItemsSource = _explorer; foreach (var item in _explorer) { item.PropertyChanged += (_, args) => { if (string.Equals(args.PropertyName, nameof(GraphExplorerItem.IsEnabled), StringComparison.Ordinal)) { ApplyDockVisibilityFromExplorer(); } }; } RefreshExplorerDataBadges(); ApplyDockVisibilityFromExplorer(); } private GraphExplorerItem? FindExplorerItem(string name) => _explorer.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); private void SetExplorerHasData(string name, bool hasData) { var item = FindExplorerItem(name); if (item != null) { item.HasData = hasData; if (!hasData) { item.ClearPreviewValues(); } } } private void RefreshExplorerDataBadges() { SetExplorerHasData("Events", _allEvents.Count > 0); SetExplorerHasData("Performance", _hasPerformanceData || (_currentSession?.PerformanceHistory.Count ?? 0) > 0); SetExplorerHasData("ETW", EtwPaneHost.ItemCount > 0); SetExplorerHasData("Heuristics", HeuristicsPaneHost.ItemCount > 0); SetExplorerHasData("Filesystem", FilesystemPaneHost.ItemCount > 0); SetExplorerHasData("Process Relations", ProcessRelationsPaneHost.ItemCount > 0); SetExplorerHasData("IPC Uplink", _hasIpcUplinkData); } private void ApplyDockVisibilityFromExplorer() { bool showEvents = _explorer.FirstOrDefault(x => x.Name == "Events")?.IsEnabled ?? true; bool showPerf = _explorer.FirstOrDefault(x => x.Name == "Performance")?.IsEnabled ?? true; bool showIpcUplink = _explorer.FirstOrDefault(x => x.Name == "IPC Uplink")?.IsEnabled ?? false; bool showEtw = _explorer.FirstOrDefault(x => x.Name == "ETW")?.IsEnabled ?? true; bool showHeuristics = _explorer.FirstOrDefault(x => x.Name == "Heuristics")?.IsEnabled ?? true; bool showFilesystem = _explorer.FirstOrDefault(x => x.Name == "Filesystem")?.IsEnabled ?? true; bool showRelations = _explorer.FirstOrDefault(x => x.Name == "Process Relations")?.IsEnabled ?? true; if (!showEvents && _eventsFloatWindow != null) _eventsFloatWindow.Close(); if (!showEvents && _eventLogWindow != null) _eventLogWindow.Close(); if (!showPerf && _performanceFloatWindow != null) _performanceFloatWindow.Close(); if (!showEtw && _etwFloatWindow != null) _etwFloatWindow.Close(); if (!showHeuristics && _heuristicsFloatWindow != null) _heuristicsFloatWindow.Close(); bool showEventsContent = showEvents && _eventsPaneVisible && !_eventsPaneFloating; bool showPerformanceContent = showPerf && _performancePaneVisible && !_performancePaneFloating; bool showEtwContent = showEtw && !_etwPaneFloating; bool showHeuristicsContent = showHeuristics && !_heuristicsPaneFloating; bool showFilesystemContent = showFilesystem; bool showRelationsContent = showRelations; EventsDockBorder.Visibility = showEventsContent ? Visibility.Visible : Visibility.Collapsed; PerformanceDockBorder.Visibility = showPerformanceContent ? Visibility.Visible : Visibility.Collapsed; CollapsedEventsBar.Visibility = (showEvents && !_eventsPaneVisible && !_eventsPaneFloating) ? Visibility.Visible : Visibility.Collapsed; CollapsedPerformanceBar.Visibility = (showPerf && !_performancePaneVisible && !_performancePaneFloating) ? Visibility.Visible : Visibility.Collapsed; DockGrid.RowDefinitions[0].Height = showEvents ? (_eventsPaneFloating ? new GridLength(0) : (_eventsPaneVisible ? new GridLength(4, GridUnitType.Star) : new GridLength(28))) : new GridLength(0); DockGrid.RowDefinitions[2].Height = showPerf ? (_performancePaneFloating ? new GridLength(0) : (_performancePaneVisible ? new GridLength(5, GridUnitType.Star) : new GridLength(28))) : new GridLength(0); DockGrid.RowDefinitions[1].Height = (showEventsContent && showPerformanceContent) ? new GridLength(2) : new GridLength(0); EtwDockBorder.Visibility = showEtwContent ? Visibility.Visible : Visibility.Collapsed; HeuristicsDockBorder.Visibility = showHeuristicsContent ? Visibility.Visible : Visibility.Collapsed; FilesystemDockBorder.Visibility = showFilesystemContent ? Visibility.Visible : Visibility.Collapsed; ProcessRelationsDockBorder.Visibility = showRelationsContent ? Visibility.Visible : Visibility.Collapsed; IpcUplinkDockBorder.Visibility = showIpcUplink ? Visibility.Visible : Visibility.Collapsed; IpcUplinkColumn.Width = showIpcUplink ? new GridLength(380) : new GridLength(0); IpcUplinkSplitterColumn.Width = showIpcUplink ? new GridLength(2) : new GridLength(0); IpcUplinkSplitter.Visibility = showIpcUplink ? Visibility.Visible : Visibility.Collapsed; bool row0Visible = (Grid.GetRow(EtwDockBorder) == 0 && showEtwContent) || (Grid.GetRow(HeuristicsDockBorder) == 0 && showHeuristicsContent); bool row2Visible = (Grid.GetRow(EtwDockBorder) == 2 && showEtwContent) || (Grid.GetRow(HeuristicsDockBorder) == 2 && showHeuristicsContent); bool row4Visible = showFilesystemContent; bool row6Visible = showRelationsContent; IntelligenceDock.RowDefinitions[0].Height = row0Visible ? new GridLength(1, GridUnitType.Star) : new GridLength(0); IntelligenceDock.RowDefinitions[2].Height = row2Visible ? new GridLength(1, GridUnitType.Star) : new GridLength(0); IntelligenceDock.RowDefinitions[4].Height = row4Visible ? new GridLength(1, GridUnitType.Star) : new GridLength(0); IntelligenceDock.RowDefinitions[6].Height = row6Visible ? new GridLength(1, GridUnitType.Star) : new GridLength(0); IntelligenceDock.RowDefinitions[1].Height = (row0Visible && row2Visible) ? new GridLength(2) : new GridLength(0); IntelligenceDock.RowDefinitions[3].Height = (row2Visible && row4Visible) ? new GridLength(2) : new GridLength(0); IntelligenceDock.RowDefinitions[5].Height = (row4Visible && row6Visible) ? new GridLength(2) : new GridLength(0); bool showIntel = row0Visible || row2Visible || row4Visible || row6Visible; if (_mainViewMode == MainInterfaceViewMode.Api) { showIntel = false; } IntelligenceColumn.Width = showIntel ? new GridLength(560) : new GridLength(0); IntelligenceSplitterColumn.Width = showIntel ? new GridLength(2) : new GridLength(0); IntelligenceSplitter.Visibility = showIntel ? Visibility.Visible : Visibility.Collapsed; IntelligenceDock.Visibility = showIntel ? Visibility.Visible : Visibility.Collapsed; } private void GraphExplorer_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { var element = e.OriginalSource as DependencyObject; if (element == null) return; var container = ItemsControl.ContainerFromElement(GraphExplorer, element) as ListBoxItem; if (container?.DataContext is not GraphExplorerItem item) return; item.IsEnabled = !item.IsEnabled; e.Handled = true; } private void Perf_SampleArrived(object? sender, PerformanceSample e) { if (!Dispatcher.CheckAccess()) { Dispatcher.BeginInvoke(new Action(() => Perf_SampleArrived(sender, e))); return; } if (_currentSession != null) { _currentSession.PerformanceHistory.Add(ClonePerformanceSample(e)); if (_currentSession.PerformanceHistory.Count > 4000) _currentSession.PerformanceHistory.RemoveRange(0, _currentSession.PerformanceHistory.Count - 4000); } // Update explorer mini previews // Events preview: events count in view window (cheap approximation) var evCount = _focusedEvents.Count; var eventsItem = _explorer.FirstOrDefault(x => x.Name == "Events"); eventsItem?.PushPreviewValue(evCount); // Performance preview: CPU % var perfItem = _explorer.FirstOrDefault(x => x.Name == "Performance"); perfItem?.PushPreviewValue(e.CpuPercent); _hasPerformanceData = true; SetExplorerHasData("Performance", true); // Feed performance pane PerformancePaneHost.PushSample(e); // Keep performance view aligned to timeline window SyncPerformanceViewToTimeline(); } private void StartLiveCaptureForPid(int pid, bool useUsermodeHooks, bool launchStartsSuspended = false) { if (pid <= 0 || _perf == null) return; _targetExecutionSuspended = launchStartsSuspended; if (_currentSession != null && _currentSession.Pid == pid && _currentSession.Events.Count == 0 && _currentSession.PerformanceHistory.Count == 0 && _currentSession.ThreadLifecycleHistory.Count == 0) { double viewDuration = Math.Max(1, EventsPaneHost.Timeline.ViewDurationSeconds); _captureStartUtc = AnchorCaptureStartUtc(viewDuration); _latestEventTimestampUtc = _captureStartUtc; _currentSession.CaptureStartUtc = _captureStartUtc; _currentSession.ViewDurationSeconds = viewDuration; _currentSession.ViewStartSeconds = 0; EventsPaneHost.Timeline.CaptureStartUtc = _captureStartUtc; EventsPaneHost.Timeline.ViewDurationSeconds = viewDuration; EventsPaneHost.Timeline.ViewStartSeconds = 0; EventsPaneHost.Scroll.Value = 0; UpdateScrollBar(); FocusViewport(); PerformancePaneHost.SetCaptureStart(_captureStartUtc); SyncPerformanceViewToTimeline(); } _perf.SetTargetPid(pid); _perf.Start(); _samplerPid = pid; PerformancePaneHost.SetProcessLiveDataAvailable(true); PerformancePaneHost.SetTargetSuspended(launchStartsSuspended); StartTargetExitWatcher(pid); EnsureLiveCaptureStoreForCurrentSession(pid); StartBackendForPid(pid, useUsermodeHooks); bool autoOpenApiGraph = _currentSession?.AutoOpenApiGraphOnNextStart == true; if (_currentSession != null) { _currentSession.AutoOpenApiGraphOnNextStart = false; } if (useUsermodeHooks && autoOpenApiGraph) { SetMainInterfaceViewMode(MainInterfaceViewMode.Api); } StatusBlock.Text = $"LIVE CAPTURE: {NormalizeSessionTitle(_currentSession?.Title ?? $"PID {pid}")}"; RefreshProcessStateBadge(); } private void StartTargetExitWatcher(int pid) { StopTargetExitWatcher(); if (pid <= 0) return; Process? process = null; try { process = Process.GetProcessById(pid); process.EnableRaisingEvents = true; process.Exited += TargetExitWatchProcess_Exited; _targetExitWatchProcess = process; _targetExitWatchPid = pid; process = null; // ownership transferred to _targetExitWatchProcess if (_targetExitWatchProcess.HasExited) { HandleTargetProcessExit(pid); } } catch { process?.Dispose(); _targetExitWatchProcess?.Dispose(); _targetExitWatchProcess = null; _targetExitWatchPid = 0; if (!Dispatcher.HasShutdownStarted) Dispatcher.BeginInvoke(new Action(ValidateCurrentSessionState)); } } private void StopTargetExitWatcher() { if (_targetExitWatchProcess != null) { try { _targetExitWatchProcess.Exited -= TargetExitWatchProcess_Exited; } catch { } try { _targetExitWatchProcess.Dispose(); } catch { } } _targetExitWatchProcess = null; _targetExitWatchPid = 0; } private void TargetExitWatchProcess_Exited(object? sender, EventArgs e) { int pid = 0; if (sender is Process p) { try { pid = p.Id; } catch { pid = 0; } } if (pid <= 0) pid = _targetExitWatchPid; Dispatcher.BeginInvoke(new Action(() => HandleTargetProcessExit(pid))); } private void HandleTargetProcessExit(int pid) { if (pid <= 0) return; ProcessSessionTab? exitedTab = _processTabs.FirstOrDefault(x => x.Pid == pid); if (exitedTab == null || exitedTab.TargetExited) return; exitedTab.TargetExited = true; MarkSessionExited(exitedTab); if (!ReferenceEquals(_currentSession, exitedTab)) { if (exitedTab.ActivePauseStartUtc.HasValue) { exitedTab.PausedTimelineSpans.Add(new PausedTimelineSpan { StartUtc = exitedTab.ActivePauseStartUtc.Value, EndUtc = DateTime.UtcNow }); exitedTab.ActivePauseStartUtc = null; } SaveTabToBackingStore(exitedTab); RefreshProcessStateBadge(); return; } AppendEvent(new TelemetryEvent { TimestampUtc = DateTime.UtcNow, PID = pid, TID = 0, Group = "Session", SubType = "ProcessExit", Summary = "TARGET PROCESS EXITED", Details = "Data capture stopped for this tab." }); StopTargetExitWatcher(); StopBackendSession(preserveApiGraphSnapshot: true); _perf?.Stop(); _samplerPid = 0; PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.SetTargetSuspended(false); _targetExecutionSuspended = false; if (exitedTab.ActivePauseStartUtc.HasValue) { exitedTab.PausedTimelineSpans.Add(new PausedTimelineSpan { StartUtc = exitedTab.ActivePauseStartUtc.Value, EndUtc = DateTime.UtcNow }); exitedTab.ActivePauseStartUtc = null; } SyncCurrentSessionStateToMemory(); _ = TryPersistCurrentSessionAfterStop(); StatusBlock.Text = $"Target {pid} exited. Capture stopped."; RefreshProcessStateBadge(); ThemedMessageBox.ShowToast( this, $"Target process {pid} exited. Data capture has been stopped for this tab.", "Target Exited", MessageBoxImage.Warning, durationMs: 5000); } private void ValidateCurrentSessionState() { if (_currentSession == null || _currentSession.Pid <= 0 || _currentSession.OfflineSnapshot || _currentSession.TargetExited) { return; } if (TryOpenTargetProcess(_currentSession.Pid, out _, out _, out bool accessDenied)) { return; } if (!accessDenied) { HandleTargetProcessExit(_currentSession.Pid); } } private static void MarkSessionExited(ProcessSessionTab tab) { tab.Title = NormalizeSessionTitle(tab.Title); } private bool TryOpenTargetProcess(int pid, out string processName, out string failure, out bool accessDenied) { processName = string.Empty; failure = string.Empty; accessDenied = false; Process? process = null; try { process = Process.GetProcessById(pid); processName = process.ProcessName; if (process.HasExited) { failure = $"TARGET PID {pid} HAS EXITED"; return false; } } catch (ArgumentException) { failure = $"PID {pid} NOT FOUND"; return false; } catch (InvalidOperationException) { failure = $"PID {pid} IS NOT AVAILABLE"; return false; } catch (Win32Exception ex) { accessDenied = ex.NativeErrorCode == 5; failure = accessDenied ? $"ACCESS DENIED TO PID {pid}" : $"FAILED TO OPEN PID {pid} (WIN32 {ex.NativeErrorCode})"; return false; } finally { try { process?.Dispose(); } catch { } } IntPtr handle = Kernel32Native.OpenProcess( ProcessQueryLimitedInformation | ProcessVmRead | ProcessSynchronize, false, unchecked((uint)pid)); if (handle == IntPtr.Zero) { int err = Marshal.GetLastWin32Error(); accessDenied = err == 5; failure = accessDenied ? $"ACCESS DENIED TO PID {pid}" : $"FAILED TO OPEN PID {pid} (WIN32 {err})"; return false; } _ = Kernel32Native.CloseHandle(handle); return true; } private int TryGetPid() { if (PidBox == null) return 0; var s = PidBox.Text?.Trim(); if (string.IsNullOrWhiteSpace(s)) return _currentSession?.Pid ?? 0; if (int.TryParse(s, out int pid) && pid > 0) return pid; return 0; } private string GetProcessTabTitle(int pid) { try { using var p = Process.GetProcessById(pid); return $"{p.ProcessName} ({pid})"; } catch { return $"PID {pid}"; } } private ProcessSessionTab AddOrSelectProcessTab(int pid, string title, bool select) { var existing = _processTabs.FirstOrDefault(t => t.Pid == pid); if (existing == null) { double initialDuration = EventsPaneHost?.Timeline != null ? Math.Clamp(EventsPaneHost.Timeline.ViewDurationSeconds, 1, 120) : DefaultTimelineViewDurationSeconds; existing = new ProcessSessionTab { Pid = pid, Title = NormalizeSessionTitle(title), CaptureStartUtc = AnchorCaptureStartUtc(initialDuration), ViewDurationSeconds = initialDuration, ViewStartSeconds = 0 }; _processTabs.Add(existing); } else { existing.Title = NormalizeSessionTitle(title); } if (select) { _suppressTabSelectionChange = true; ProcessTabs.SelectedItem = existing; _suppressTabSelectionChange = false; } return existing; } private static string NormalizeSessionTitle(string? title) { string value = string.IsNullOrWhiteSpace(title) ? "PID" : title.Trim(); while (value.EndsWith("[EXITED]", StringComparison.OrdinalIgnoreCase)) { value = value[..^"[EXITED]".Length].TrimEnd(); } return value; } private void SyncCurrentSessionStateToMemory() { if (_currentSession == null) return; _currentSession.CaptureStartUtc = _captureStartUtc; _currentSession.LaneFocusKey = _laneFocusKey; _currentSession.ViewDurationSeconds = EventsPaneHost.Timeline.ViewDurationSeconds; _currentSession.ViewStartSeconds = EventsPaneHost.Timeline.ViewStartSeconds; _currentSession.Events.Clear(); _currentSession.Events.AddRange(_allEvents.Select(CloneTelemetryEvent)); SaveIntelSessionState(_currentSession.Pid); } private void SaveCurrentSessionState() { if (_currentSession == null) return; SyncCurrentSessionStateToMemory(); if (_currentSession.OfflineSnapshot || _liveCaptureStore == null) { SaveTabToBackingStore(_currentSession); } } private bool TryPersistCurrentSessionAfterStop() { if (_currentSession == null) { return false; } try { SyncCurrentSessionStateToMemory(); SaveTabToBackingStore(_currentSession); return true; } catch { return false; } } private void RestoreSessionState(ProcessSessionTab tab) { EnsureSessionMaterialized(tab); double restoredDuration = Math.Clamp(tab.ViewDurationSeconds <= 0 ? DefaultTimelineViewDurationSeconds : tab.ViewDurationSeconds, 1, 120); bool freshSession = !tab.OfflineSnapshot && tab.Events.Count == 0 && tab.PerformanceHistory.Count == 0 && tab.ThreadLifecycleHistory.Count == 0; if (freshSession) { tab.CaptureStartUtc = AnchorCaptureStartUtc(restoredDuration); tab.ViewStartSeconds = 0; } _captureStartUtc = tab.CaptureStartUtc == default ? AnchorCaptureStartUtc(restoredDuration) : tab.CaptureStartUtc; _laneFocusKey = tab.LaneFocusKey; _allEvents.Clear(); _knownLaneKeys.Clear(); _telemetryTextPool.Clear(); _focusedEvents.Clear(); EventsPaneHost.Timeline.Items.Clear(); EventsPaneHost.Timeline.ClearAllLaneFilters(); ClearSelectedEvent(); foreach (var ev in tab.Events.OrderBy(x => x.TimestampUtc)) { _allEvents.Add(NormalizeTelemetryEventForStore(ev)); } _latestEventTimestampUtc = _allEvents.Count > 0 ? _allEvents[^1].TimestampUtc : _captureStartUtc; EventsPaneHost.Timeline.CaptureStartUtc = _captureStartUtc; EventsPaneHost.Timeline.ViewDurationSeconds = restoredDuration; EventsPaneHost.Timeline.ViewStartSeconds = Math.Max(0, tab.ViewStartSeconds); ApplyPausedTimelineRanges(); UpdateScrollBar(); EventsPaneHost.Scroll.Value = Math.Min(EventsPaneHost.Scroll.Maximum, EventsPaneHost.Timeline.ViewStartSeconds); FocusViewport(); PerformancePaneHost.SetCaptureStart(_captureStartUtc); PerformancePaneHost.SetPid(tab.Pid); PerformancePaneHost.LoadHistory(tab.PerformanceHistory); PerformancePaneHost.LoadThreadLifecycleHistory(tab.ThreadLifecycleHistory); RestoreIntelSessionState(tab.Pid); SyncPerformanceViewToTimeline(); _hasPerformanceData = tab.PerformanceHistory.Count > 0; SetExplorerHasData("Performance", tab.PerformanceHistory.Count > 0); EventsPaneHost.SetHasData(_allEvents.Count > 0); RefreshExplorerDataBadges(); RefreshChildProcessGraphWindowIfOpen(); } private void SwitchToSession(ProcessSessionTab tab) { if (_currentSession != null && !ReferenceEquals(_currentSession, tab)) { SaveChildProcessGraphStateToSession(_currentSession); SaveCurrentSessionState(); } _currentSession = tab; PidBox.Text = tab.Pid.ToString(); RestoreChildProcessGraphStateFromSession(tab); RestoreSessionState(tab); if (tab.OfflineSnapshot) { StopTargetExitWatcher(); StopBackendSession(preserveApiGraphSnapshot: true); _perf?.Stop(); _samplerPid = 0; PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.SetTargetSuspended(false); StatusBlock.Text = $"OFFLINE SESSION: {tab.Title}"; RefreshProcessStateBadge(); return; } if (tab.TargetExited) { StopTargetExitWatcher(); StopBackendSession(preserveApiGraphSnapshot: true); _perf?.Stop(); _samplerPid = 0; PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.SetTargetSuspended(false); StatusBlock.Text = $"Target {tab.Pid} exited. Capture stopped."; RefreshProcessStateBadge(); return; } if (!TryOpenTargetProcess(tab.Pid, out _, out var failure, out var accessDenied)) { StopTargetExitWatcher(); StopBackendSession(preserveApiGraphSnapshot: true); _perf?.Stop(); _samplerPid = 0; PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.SetTargetSuspended(false); if (!accessDenied) { tab.TargetExited = true; MarkSessionExited(tab); StatusBlock.Text = $"Target {tab.Pid} exited. Capture stopped."; } else { StatusBlock.Text = $"Access denied to PID {tab.Pid}. Capture stopped."; } RefreshProcessStateBadge(); return; } bool launchStartsSuspended = tab.LaunchStartsSuspendedPending; tab.LaunchStartsSuspendedPending = false; StartLiveCaptureForPid(tab.Pid, tab.UseUsermodeHooks, launchStartsSuspended); } private void SyncPerformanceViewToTimeline() { var viewStart = _captureStartUtc + TimeSpan.FromSeconds(EventsPaneHost.Timeline.ViewStartSeconds); var viewEnd = viewStart + TimeSpan.FromSeconds(EventsPaneHost.Timeline.ViewDurationSeconds); PerformancePaneHost.SetViewWindow(viewStart, viewEnd); } private void ApplyTimelineViewport(double targetStart, bool syncScroll, bool updateFollowState) { if (EventsPaneHost?.Scroll == null || EventsPaneHost?.Timeline == null) { return; } double viewport = Math.Max(1, EventsPaneHost.Scroll.ViewportSize); double maxStart = ComputeTimelineMaxStart(viewport); double clamped = Math.Max(0, Math.Min(maxStart, targetStart)); if (updateFollowState) { _followLiveTimeline = maxStart <= 0.001 || Math.Abs(clamped - maxStart) < 0.25; } _pendingScrollStartSeconds = clamped; EventsPaneHost.Timeline.ViewStartSeconds = clamped; if (syncScroll && Math.Abs(EventsPaneHost.Scroll.Value - clamped) > 0.001) { EventsPaneHost.Scroll.Value = clamped; } FocusViewport(); UpdateTopTimeTravelBar(); SyncPerformanceViewToTimeline(); } // ------------------------------- // Core: Append events from your DLL pump // ------------------------------- private void AppendEvent(TelemetryEvent ev) { AppendEvents(new[] { ev }); } private void AppendEvents(IReadOnlyList events) { if (events.Count == 0) { return; } bool laneKeysChanged = false; for (int i = 0; i < events.Count; i += 1) { TelemetryEvent ev = NormalizeTelemetryEventForStore(events[i]); _allEvents.Add(ev); if (ev.TimestampUtc > _latestEventTimestampUtc) { _latestEventTimestampUtc = ev.TimestampUtc; } if (!string.IsNullOrWhiteSpace(ev.Group) && _knownLaneKeys.Add(ev.Group)) { laneKeysChanged = true; } } SetExplorerHasData("Events", _allEvents.Count > 0); EventsPaneHost.SetHasData(true); if (laneKeysChanged) { EventsPaneHost.SetLaneFilterOptions(_knownLaneKeys.OrderBy(k => k)); } ScheduleViewportRefresh(); } internal void SetBackendConnectivity(bool healthy) { _connectivityHealthy = healthy; ApplyUplinkStatusVisual(healthy); EventsPaneHost.SetConnectivityHealthy(healthy); EventsPaneHost.SetHasData(_allEvents.Count > 0); RefreshExplorerDataBadges(); } private void ApplyUplinkStatusVisual(bool? healthy) { if (StatusBlock == null) return; if (healthy == true) { StatusBlock.SetResourceReference(TextBlock.ForegroundProperty, "WinMutedTextBrush"); return; } if (healthy == false) { StatusBlock.SetResourceReference(TextBlock.ForegroundProperty, "StatusFailedBrush"); return; } StatusBlock.SetResourceReference(TextBlock.ForegroundProperty, "WinMutedTextBrush"); } private void UpdateWindowTitle() { string sessionLabel; if (_currentSession == null || _currentSession.Pid <= 0) { sessionLabel = "IDLE"; } else if (_currentSession.OfflineSnapshot) { sessionLabel = $"OFFLINE SESSION: {NormalizeSessionTitle(_currentSession.Title)}"; } else if (_currentSession.TargetExited) { sessionLabel = $"CAPTURE STOPPED: PID {_currentSession.Pid}"; } else { string captureTitle = NormalizeSessionTitle(_currentSession.Title ?? $"PID {_currentSession.Pid}"); sessionLabel = $"LIVE CAPTURE: {captureTitle}"; } string newTitle = $"{WindowTitleBase} | {sessionLabel}"; if (!string.Equals(Title, newTitle, StringComparison.Ordinal)) { Title = newTitle; } } private void RefreshProcessStateBadge() { UpdateWindowTitle(); RefreshToolbarCommandState(); if (ProcessStateBadge == null || ProcessStateBlock == null) { return; } if (_currentSession == null || _currentSession.Pid <= 0) { SetProcessStateVisual("Disconnected", ProcessStateUnknownBackground, ProcessStateUnknownBorder, ProcessStateUnknownForeground); return; } string labelPrefix = $"PID {_currentSession.Pid}"; if (_currentSession.OfflineSnapshot) { SetProcessStateVisual($"{labelPrefix} • Exited (offline capture)", ProcessStateExitedBackground, ProcessStateExitedBorder, ProcessStateExitedForeground); return; } if (_currentSession.TargetExited) { SetProcessStateVisual($"{labelPrefix} • Exited (capture available)", ProcessStateExitedBackground, ProcessStateExitedBorder, ProcessStateExitedForeground); return; } IntelScopeStatus scope = ((IIntelDetailsProvider)this).GetIntelScopeStatus(); switch (scope) { case IntelScopeStatus.Running: SetProcessStateVisual($"{labelPrefix} • Connected / Running", ProcessStateRunningBackground, ProcessStateRunningBorder, ProcessStateRunningForeground); break; case IntelScopeStatus.Waiting: SetProcessStateVisual($"{labelPrefix} • Suspended / Waiting", ProcessStateWaitingBackground, ProcessStateWaitingBorder, ProcessStateWaitingForeground); break; case IntelScopeStatus.Exited: SetProcessStateVisual($"{labelPrefix} • Exited (capture available)", ProcessStateExitedBackground, ProcessStateExitedBorder, ProcessStateExitedForeground); break; default: SetProcessStateVisual($"{labelPrefix} • Connected / Unknown", ProcessStateUnknownBackground, ProcessStateUnknownBorder, ProcessStateUnknownForeground); break; } } private void SetProcessStateVisual(string text, Brush background, Brush border, Brush foreground) { ProcessStateBlock.Text = text; ProcessStateBadge.Background = background; ProcessStateBadge.BorderBrush = border; ProcessStateBlock.Foreground = foreground; } private void RefreshToolbarCommandState() { bool hasAttachedTarget = _currentSession != null && _currentSession.Pid > 0 && !_currentSession.OfflineSnapshot && !_currentSession.TargetExited; bool canPause = hasAttachedTarget; bool canResume = hasAttachedTarget && _targetExecutionSuspended; if (canResume) { canPause = false; } SetToolbarCommandButtonState(TargetCommandButton, TargetCommandGlyph, true, ToolbarTargetBackground, ToolbarTargetBorder, ToolbarTargetForeground); SetToolbarCommandButtonState(PauseCommandButton, PauseCommandGlyph, canPause, ToolbarPauseBackground, ToolbarPauseBorder, ToolbarPauseForeground); SetToolbarCommandButtonState(ResumeCommandButton, ResumeCommandGlyph, canResume, ToolbarResumeBackground, ToolbarResumeBorder, ToolbarResumeForeground); SetToolbarCommandButtonState(TerminateCommandButton, TerminateCommandGlyph, hasAttachedTarget, ToolbarStopBackground, ToolbarStopBorder, ToolbarStopForeground); if (TimeTravelLiveNotice != null) { bool targetStopped = _currentSession != null && _currentSession.TargetExited; TimeTravelLiveNotice.Visibility = targetStopped ? Visibility.Collapsed : Visibility.Visible; } } private static void SetToolbarCommandButtonState(Button? button, Border? glyph, bool enabled, Brush activeBackground, Brush activeBorder, Brush activeForeground) { if (button == null || glyph == null) { return; } button.IsEnabled = enabled; button.Background = enabled ? activeBackground : ToolbarInactiveBackground; button.BorderBrush = enabled ? activeBorder : ToolbarInactiveBorder; button.Foreground = enabled ? activeForeground : ToolbarInactiveForeground; glyph.Background = enabled ? activeForeground : ToolbarInactiveForeground; } // ------------------------------- // Scroll / viewport // ------------------------------- private void Scroll_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { double viewport = Math.Max(1, EventsPaneHost.Scroll.ViewportSize); double maxStart = ComputeTimelineMaxStart(viewport); double clamped = Math.Max(0, Math.Min(maxStart, EventsPaneHost.Scroll.Value)); _pendingScrollStartSeconds = clamped; EventsPaneHost.Timeline.ViewStartSeconds = clamped; _followLiveTimeline = maxStart <= 0.001 || Math.Abs(clamped - maxStart) < 0.25; if (_scrollSyncPending) { return; } _scrollSyncPending = true; Dispatcher.BeginInvoke(new Action(() => { _scrollSyncPending = false; ApplyTimelineViewport(_pendingScrollStartSeconds, syncScroll: false, updateFollowState: false); }), DispatcherPriority.Render); } private void UpdateScrollBar() { if (_allEvents.Count == 0) { EventsPaneHost.Scroll.Maximum = 0; EventsPaneHost.Scroll.ViewportSize = 1; EventsPaneHost.Scroll.SmallChange = 1; EventsPaneHost.Scroll.LargeChange = 1; EventsPaneHost.Scroll.Value = 0; EventsPaneHost.Timeline.ViewStartSeconds = 0; _latestEventTimestampUtc = _captureStartUtc; UpdateTopTimeTravelBar(); return; } DateTime horizonUtc = GetTimelineHorizonUtc(); var totalSeconds = (horizonUtc - _captureStartUtc).TotalSeconds; if (totalSeconds < 0) { totalSeconds = 0; } double duration = Math.Max(1, EventsPaneHost.Timeline.ViewDurationSeconds); double maxStart = Math.Max(0, totalSeconds - duration); EventsPaneHost.Scroll.ViewportSize = duration; EventsPaneHost.Scroll.Maximum = maxStart; EventsPaneHost.Scroll.SmallChange = Math.Max(1, duration / 20.0); EventsPaneHost.Scroll.LargeChange = Math.Max(1, duration * 0.8); if (EventsPaneHost.Timeline.ViewStartSeconds > maxStart) { EventsPaneHost.Timeline.ViewStartSeconds = maxStart; } if (EventsPaneHost.Scroll.Value > maxStart) { EventsPaneHost.Scroll.Value = maxStart; } UpdateTopTimeTravelBar(); } private void FocusViewport() { var viewStart = _captureStartUtc + TimeSpan.FromSeconds(EventsPaneHost.Timeline.ViewStartSeconds); var viewEnd = viewStart + TimeSpan.FromSeconds(EventsPaneHost.Timeline.ViewDurationSeconds); var selectedAnchor = CaptureSelectedEventAnchor(); double durationSeconds = Math.Max(1, (viewEnd - viewStart).TotalSeconds); RangeBlock.Text = $"Range {viewStart:HH:mm:ss} | {viewEnd:HH:mm:ss} ({durationSeconds:0}s)"; EventsPaneHost.Timeline.ReplaceItems(Array.Empty()); if (_allEvents.Count == 0) { _focusedEvents.ReplaceAll(Array.Empty()); SetExplorerHasData("Events", false); EventsPaneHost.SetHeaderStats("View 0 | Total 0 | 0.0/s"); ClearSelectedEvent(); UpdateDetachedEventLogWindow(); return; } int start = LowerBoundEventIndex(viewStart); int endExclusive = UpperBoundEventIndex(viewEnd); var rawVisibleEvents = new List(Math.Max(16, endExclusive - start)); for (int i = start; i < endExclusive; i += 1) { var ev = _allEvents[i]; if (!PassLaneFocus(ev)) { continue; } rawVisibleEvents.Add(ev); } var visibleEvents = ClusterViewportEvents(rawVisibleEvents, Math.Max(1.0, EventsPaneHost.Timeline.ViewDurationSeconds)); _focusedEvents.ReplaceAll(visibleEvents); EventsPaneHost.Timeline.ReplaceItems(_focusedEvents); RestoreSelectedEventInFocusedView(selectedAnchor); FindExplorerItem("Events")?.PushPreviewValue(_focusedEvents.Count); SetExplorerHasData("Events", _allEvents.Count > 0); double viewSeconds = Math.Max(1.0, EventsPaneHost.Timeline.ViewDurationSeconds); double rate = rawVisibleEvents.Count / viewSeconds; EventsPaneHost.SetHeaderStats($"View {_focusedEvents.Count} clustered | Raw {rawVisibleEvents.Count} | Total {_allEvents.Count} | {rate:0.0}/s"); UpdateDetachedEventLogWindow(); UpdateTopTimeTravelBar(); } private IReadOnlyList ClusterViewportEvents(IReadOnlyList rawVisibleEvents, double viewDurationSeconds) { _focusedClusterMembers.Clear(); if (rawVisibleEvents.Count == 0) { return Array.Empty(); } if (rawVisibleEvents.Count <= 180) { for (int i = 0; i < rawVisibleEvents.Count; i += 1) { TelemetryEvent raw = rawVisibleEvents[i]; _focusedClusterMembers[new EventSelectionKey(raw)] = new[] { raw }; } return rawVisibleEvents; } int targetVisibleCount = Math.Clamp((int)Math.Round((EventsPaneHost?.Timeline?.ActualWidth ?? 1280.0) / 9.0), 80, 160); bool includeThreadId = rawVisibleEvents.Count <= 800; double bucketSeconds = Math.Max(0.12, viewDurationSeconds / Math.Max(60, targetVisibleCount)); List clustered = new(); for (int iteration = 0; iteration < 5; iteration += 1) { clustered = BuildClusteredViewportEvents(rawVisibleEvents, bucketSeconds, includeThreadId); if (clustered.Count <= targetVisibleCount) { break; } if (includeThreadId && rawVisibleEvents.Count > targetVisibleCount * 2) { includeThreadId = false; continue; } bucketSeconds *= 1.7; } return clustered; } private List BuildClusteredViewportEvents( IReadOnlyList rawVisibleEvents, double bucketSeconds, bool includeThreadId) { var buckets = new Dictionary>(StringComparer.Ordinal); for (int i = 0; i < rawVisibleEvents.Count; i += 1) { TelemetryEvent ev = rawVisibleEvents[i]; long bucketIndex = (long)Math.Floor(Math.Max(0, (ev.TimestampUtc - _captureStartUtc).TotalSeconds) / Math.Max(0.01, bucketSeconds)); string normalizedSummary = NormalizeClusterSummary(ev.Summary); int clusterTid = includeThreadId ? ev.TID : 0; string key = $"{bucketIndex}|{ev.PID}|{clusterTid}|{ev.Group}|{ev.SubType}|{normalizedSummary}|{ev.ProcessName}"; if (!buckets.TryGetValue(key, out List? members)) { members = new List(4); buckets[key] = members; } members.Add(ev); } var displayEvents = new List(buckets.Count); foreach (List members in buckets.Values.OrderBy(x => x[^1].TimestampUtc)) { if (members.Count == 1) { TelemetryEvent raw = members[0]; _focusedClusterMembers[new EventSelectionKey(raw)] = members; displayEvents.Add(raw); continue; } TelemetryEvent first = members[0]; TelemetryEvent last = members[^1]; bool mixedThreads = members.Any(x => x.TID != first.TID); bool mixedProcesses = members.Any(x => x.PID != first.PID); string baseSummary = string.IsNullOrWhiteSpace(last.Summary) ? last.SubType : last.Summary; string summary = $"[{members.Count}x] {baseSummary}"; string details = $"cluster-count={members.Count}; first={first.TimestampUtc:O}; last={last.TimestampUtc:O}; " + $"group={first.Group}; subtype={first.SubType}; mixedThreads={(mixedThreads ? "yes" : "no")}; mixedProcesses={(mixedProcesses ? "yes" : "no")}"; var clustered = new TelemetryEvent { TimestampUtc = last.TimestampUtc, PID = mixedProcesses ? 0 : first.PID, TID = mixedThreads ? 0 : first.TID, Group = first.Group, SubType = first.SubType, ProcessName = first.ProcessName, Summary = summary, Details = details }; _focusedClusterMembers[new EventSelectionKey(clustered)] = members.ToList(); displayEvents.Add(clustered); } return displayEvents; } private static string NormalizeClusterSummary(string? summary) { if (string.IsNullOrWhiteSpace(summary)) { return string.Empty; } string normalized = summary.Trim(); if (normalized.Length > 160) { normalized = normalized[..160]; } return normalized; } private IReadOnlyList GetFocusedEventMembers(TelemetryEvent? displayEvent) { if (displayEvent == null) { return Array.Empty(); } if (_focusedClusterMembers.TryGetValue(new EventSelectionKey(displayEvent), out IReadOnlyList? members)) { return members; } return new[] { displayEvent }; } private IReadOnlyList ExpandFocusedEventMembers(IEnumerable displayEvents) { var expanded = new List(); foreach (TelemetryEvent ev in displayEvents) { expanded.AddRange(GetFocusedEventMembers(ev)); } return expanded; } private void UpdateTopTimeTravelBar() { if (TopTimeTravelSlider == null || TopTimeTravelRangeBlock == null || EventsPaneHost?.Scroll == null || EventsPaneHost?.Timeline == null) { return; } double viewport = Math.Max(1, EventsPaneHost.Scroll.ViewportSize); double maxStart = ComputeTimelineMaxStart(viewport); double start = Math.Max(0, Math.Min(maxStart, EventsPaneHost.Timeline.ViewStartSeconds)); _topTimeTravelSyncing = true; TopTimeTravelSlider.Minimum = 0; TopTimeTravelSlider.Maximum = Math.Max(0, maxStart); TopTimeTravelSlider.Value = start; TopTimeTravelSlider.SmallChange = Math.Max(1, viewport / 30.0); TopTimeTravelSlider.LargeChange = Math.Max(1, viewport * 0.5); TopTimeTravelSlider.IsEnabled = maxStart > 0.001; _topTimeTravelSyncing = false; DateTime viewStart = _captureStartUtc + TimeSpan.FromSeconds(start); DateTime viewEnd = viewStart + TimeSpan.FromSeconds(viewport); TopTimeTravelRangeBlock.Text = $"{viewStart:HH:mm:ss} | {viewEnd:HH:mm:ss}"; } private void TopTimeTravelSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { if (_topTimeTravelSyncing || EventsPaneHost?.Scroll == null || EventsPaneHost?.Timeline == null) { return; } ApplyTimelineViewport(e.NewValue, syncScroll: true, updateFollowState: true); } private void NudgeTopTimeTravel(double secondsDelta) { if (EventsPaneHost?.Scroll == null || EventsPaneHost?.Timeline == null) { return; } ApplyTimelineViewport(EventsPaneHost.Scroll.Value + secondsDelta, syncScroll: true, updateFollowState: true); } private void TopTimeTravelBack10_Click(object sender, RoutedEventArgs e) => NudgeTopTimeTravel(-10); private void TopTimeTravelBack1_Click(object sender, RoutedEventArgs e) => NudgeTopTimeTravel(-1); private void TopTimeTravelForward1_Click(object sender, RoutedEventArgs e) => NudgeTopTimeTravel(1); private void TopTimeTravelForward10_Click(object sender, RoutedEventArgs e) => NudgeTopTimeTravel(10); private void ScheduleViewportRefresh() { if (_viewportRefreshPending) { return; } _viewportRefreshPending = true; Dispatcher.BeginInvoke(new Action(() => { _viewportRefreshPending = false; UpdateScrollBar(); if (_followLiveTimeline && EventsPaneHost?.Scroll != null) { ApplyTimelineViewport(ComputeTimelineMaxStart(Math.Max(1, EventsPaneHost.Scroll.ViewportSize)), syncScroll: true, updateFollowState: false); } else { FocusViewport(); UpdateTopTimeTravelBar(); SyncPerformanceViewToTimeline(); } double viewStartSeconds = EventsPaneHost?.Timeline?.ViewStartSeconds ?? 0; DiagnosticsState.SetValue( "UI Viewport", $"view={_focusedEvents.Count} total={_allEvents.Count} start={viewStartSeconds:0.0}s"); }), System.Windows.Threading.DispatcherPriority.Background); } private double ComputeTimelineMaxStart(double viewportSeconds) { double viewport = Math.Max(1, viewportSeconds); double totalSeconds = Math.Max(0, (GetTimelineHorizonUtc() - _captureStartUtc).TotalSeconds); return Math.Max(0, totalSeconds - viewport); } private DateTime GetTimelineHorizonUtc() { DateTime eventHorizon = _latestEventTimestampUtc > _captureStartUtc ? _latestEventTimestampUtc : _captureStartUtc; return eventHorizon; } private static DateTime AnchorCaptureStartUtc(double viewDurationSeconds) { double seconds = Math.Max(1, viewDurationSeconds); return DateTime.UtcNow - TimeSpan.FromSeconds(seconds); } private TelemetryEvent NormalizeTelemetryEventForStore(TelemetryEvent source) { string group = _telemetryTextPool.Intern(source.Group, 48); string subType = _telemetryTextPool.Intern(source.SubType, 96); string processName = _telemetryTextPool.Intern(source.ProcessName, 96); string summary = _telemetryTextPool.Intern(source.Summary, 160); string details = _telemetryTextPool.Intern(source.Details, 384); if (ReferenceEquals(group, source.Group) && ReferenceEquals(subType, source.SubType) && ReferenceEquals(processName, source.ProcessName) && ReferenceEquals(summary, source.Summary) && ReferenceEquals(details, source.Details)) { return source; } return new TelemetryEvent { TimestampUtc = source.TimestampUtc, PID = source.PID, TID = source.TID, Group = group, SubType = subType, ProcessName = processName, Summary = summary, Details = details }; } private int LowerBoundEventIndex(DateTime timestampUtc) { int lo = 0; int hi = _allEvents.Count; while (lo < hi) { int mid = lo + ((hi - lo) / 2); if (_allEvents[mid].TimestampUtc < timestampUtc) { lo = mid + 1; } else { hi = mid; } } return lo; } private int UpperBoundEventIndex(DateTime timestampUtc) { int lo = 0; int hi = _allEvents.Count; while (lo < hi) { int mid = lo + ((hi - lo) / 2); if (_allEvents[mid].TimestampUtc <= timestampUtc) { lo = mid + 1; } else { hi = mid; } } return lo; } private bool PassLaneFocus(TelemetryEvent ev) { if (string.IsNullOrWhiteSpace(_laneFocusKey)) return true; // lane key is either "Group" or "Group/SubType" string key = string.IsNullOrWhiteSpace(ev.SubType) ? ev.Group : $"{ev.Group}/{ev.SubType}"; return string.Equals(key, _laneFocusKey, StringComparison.OrdinalIgnoreCase) || string.Equals(ev.Group, _laneFocusKey, StringComparison.OrdinalIgnoreCase); } // ------------------------------- // Timeline lane interactions // ------------------------------- private void Timeline_LaneInteraction(object? sender, LaneInteractionEventArgs e) { if (e.Button == System.Windows.Input.MouseButton.Left && !e.IsArrow) { _laneFocusKey = e.LaneKey; FocusViewport(); return; } if (e.Button != System.Windows.Input.MouseButton.Right) return; var menu = new ContextMenu(); var miEnableDisable = new MenuItem { Header = EventsPaneHost.Timeline.IsLaneVisible(e.LaneKey) ? "Disable" : "Enable" }; miEnableDisable.Click += (_, __) => { bool nowVisible = !EventsPaneHost.Timeline.IsLaneVisible(e.LaneKey); EventsPaneHost.Timeline.SetLaneVisible(e.LaneKey, nowVisible); FocusViewport(); }; menu.Items.Add(miEnableDisable); var miFilter = new MenuItem { Header = "Filter to selection" }; miFilter.Click += (_, __) => { _laneFocusKey = e.LaneKey; FocusViewport(); }; menu.Items.Add(miFilter); var miClear = new MenuItem { Header = "Clear filter" }; miClear.Click += (_, __) => { _laneFocusKey = null; EventsPaneHost.Timeline.ClearAllLaneFilters(); FocusViewport(); }; menu.Items.Add(miClear); menu.Items.Add(new Separator()); var miColor = new MenuItem { Header = "Set colour (cycle)" }; miColor.Click += (_, __) => { Color[] options = { Color.FromRgb(0x4C,0x8F,0xD2), Color.FromRgb(0x6C,0xA4,0xDE), Color.FromRgb(0x58,0xB6,0x58), Color.FromRgb(0x7B,0xC7,0x7B), Color.FromRgb(0x8D,0x97,0xA3), }; int idx = (Math.Abs(e.LaneKey.GetHashCode()) % options.Length); var next = options[(idx + DateTime.UtcNow.Second) % options.Length]; EventsPaneHost.Timeline.SetLaneColor(e.LaneKey, next); }; menu.Items.Add(miColor); menu.IsOpen = true; } // ------------------------------- // Selection sync // ------------------------------- private void Timeline_EventDoubleClicked(object? sender, TelemetryEventSelectedEventArgs e) { _ = sender; _ = e; } private EventSelectionKey? CaptureSelectedEventAnchor() { if (_eventSelectionSyncing) { return _selectedEventAnchor; } if (EventsPaneHost.Grid.SelectedItem is TelemetryEvent selectedInGrid) { return new EventSelectionKey(selectedInGrid); } if (EventsPaneHost.Timeline.SelectedEvent is TelemetryEvent selectedInTimeline) { return new EventSelectionKey(selectedInTimeline); } return _selectedEventAnchor; } private void RestoreSelectedEventInFocusedView(EventSelectionKey? preferred) { if (preferred is not EventSelectionKey key || _focusedEvents.Count == 0) { if (preferred == null) { ClearSelectedEvent(); } return; } TelemetryEvent? matched = _focusedEvents.FirstOrDefault(ev => key.Matches(ev)); if (matched != null) { ApplySelectedEvent(matched, scrollIntoView: false); return; } TelemetryEvent? clusteredMatch = _focusedEvents.FirstOrDefault(ev => GetFocusedEventMembers(ev).Any(raw => key.Matches(raw))); if (clusteredMatch != null) { ApplySelectedEvent(clusteredMatch, scrollIntoView: false); return; } if (_selectedEventAnchor.HasValue && !_selectedEventAnchor.Value.Equals(key)) { TelemetryEvent? fallback = _focusedEvents.FirstOrDefault(ev => _selectedEventAnchor.Value.Matches(ev)); if (fallback != null) { ApplySelectedEvent(fallback, scrollIntoView: false); return; } TelemetryEvent? clusteredFallback = _focusedEvents.FirstOrDefault(ev => GetFocusedEventMembers(ev).Any(raw => _selectedEventAnchor.Value.Matches(raw))); if (clusteredFallback != null) { ApplySelectedEvent(clusteredFallback, scrollIntoView: false); } } } private void ApplySelectedEvent(TelemetryEvent? selected, bool scrollIntoView) { if (EventsPaneHost?.Grid == null || EventsPaneHost?.Timeline == null) { return; } _eventSelectionSyncing = true; try { EventsPaneHost.Timeline.SelectedEvent = selected; EventsPaneHost.Grid.SelectedItem = selected; if (selected != null) { UpdateSelectedEventAnchor(selected); if (scrollIntoView) { EventsPaneHost.Grid.ScrollIntoView(selected); } } } finally { _eventSelectionSyncing = false; } } private void UpdateSelectedEventAnchor(TelemetryEvent selected) { _selectedEventAnchor = new EventSelectionKey(selected); } private void ClearSelectedEvent() { if (EventsPaneHost?.Grid == null || EventsPaneHost?.Timeline == null) { _selectedEventAnchor = null; return; } _eventSelectionSyncing = true; try { EventsPaneHost.Grid.SelectedItem = null; EventsPaneHost.Timeline.SelectedEvent = null; _selectedEventAnchor = null; } finally { _eventSelectionSyncing = false; } } private void Timeline_SelectedEventChanged(object? sender, TelemetryEventSelectedEventArgs e) { if (_eventSelectionSyncing) return; if (e.Selected == null) return; ApplySelectedEvent(e.Selected, scrollIntoView: true); } private void Grid_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (_eventSelectionSyncing) return; if (EventsPaneHost.Grid.SelectedItem is TelemetryEvent te) { ApplySelectedEvent(te, scrollIntoView: false); } } // ------------------------------- // Pane: hide/collapse/float/dock/reset // ------------------------------- private void HideEventsPane() { _eventsPaneVisible = false; ApplyDockVisibilityFromExplorer(); } private void ShowEventsPane() { _eventsPaneVisible = true; ApplyDockVisibilityFromExplorer(); } private void HidePerformancePane() { _performancePaneVisible = false; ApplyDockVisibilityFromExplorer(); } private void HideEtwPane() { var sw = FindExplorerItem("ETW"); if (sw != null) sw.IsEnabled = false; ApplyDockVisibilityFromExplorer(); } private void HideHeuristicsPane() { var heur = FindExplorerItem("Heuristics"); if (heur != null) heur.IsEnabled = false; ApplyDockVisibilityFromExplorer(); } private void HideIpcUplinkPane() { var ipc = FindExplorerItem("IPC Uplink"); if (ipc != null) ipc.IsEnabled = false; ApplyDockVisibilityFromExplorer(); } private void HideFilesystemPane() { var fs = FindExplorerItem("Filesystem"); if (fs != null) fs.IsEnabled = false; ApplyDockVisibilityFromExplorer(); } private void HideProcessRelationsPane() { var rel = FindExplorerItem("Process Relations"); if (rel != null) rel.IsEnabled = false; ApplyDockVisibilityFromExplorer(); } private void SetExplorerPaneEnabled(string name, bool enabled) { var item = FindExplorerItem(name); if (item != null) { item.IsEnabled = enabled; } } private void ShowEtwPane() { SetExplorerPaneEnabled("ETW", true); ApplyDockVisibilityFromExplorer(); } private void ShowHeuristicsPane() { SetExplorerPaneEnabled("Heuristics", true); ApplyDockVisibilityFromExplorer(); } private void ShowIpcUplinkPane() { SetExplorerPaneEnabled("IPC Uplink", true); ApplyDockVisibilityFromExplorer(); } private void ShowFilesystemPane() { SetExplorerPaneEnabled("Filesystem", true); ApplyDockVisibilityFromExplorer(); } private void ShowProcessRelationsPane() { SetExplorerPaneEnabled("Process Relations", true); ApplyDockVisibilityFromExplorer(); } private void ShowPerformancePane() { _performancePaneVisible = true; ApplyDockVisibilityFromExplorer(); } private void CollapsedEventsBar_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { ShowEventsPane(); e.Handled = true; } private void CollapsedPerformanceBar_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { ShowPerformancePane(); e.Handled = true; } private void CollapseOrExpandPane() { if (_eventsPaneVisible) HideEventsPane(); else ShowEventsPane(); } private void CollapseOrExpandPerformancePane() { if (_performancePaneVisible) HidePerformancePane(); else ShowPerformancePane(); } private void OpenOrActivateEventLogWindow() { if (_eventLogWindow == null) { UndockEventLog(); return; } if (_eventLogWindow.WindowState == WindowState.Minimized) { _eventLogWindow.WindowState = WindowState.Normal; } _eventLogWindow.Activate(); } private void SetMainInterfaceViewMode(MainInterfaceViewMode mode) { _mainViewMode = mode; if (DockGrid != null) { DockGrid.Visibility = mode == MainInterfaceViewMode.Telemetry ? Visibility.Visible : Visibility.Collapsed; } if (ApiViewBorder != null) { ApiViewBorder.Visibility = mode == MainInterfaceViewMode.Api ? Visibility.Visible : Visibility.Collapsed; } ApplyDockVisibilityFromExplorer(); if (SwitchViewMenuItem != null) { SwitchViewMenuItem.Header = mode == MainInterfaceViewMode.Api ? "Switch View (Telemetry)" : "Switch View (API)"; } if (SwitchViewButtonLabel != null) { SwitchViewButtonLabel.Text = mode == MainInterfaceViewMode.Api ? "Switch to Main View" : "Switch to API View"; } if (IsLoaded) { RebuildToolbarViewMenuOptions(); } } private void ToggleMainInterfaceViewMode() { SetMainInterfaceViewMode(_mainViewMode == MainInterfaceViewMode.Telemetry ? MainInterfaceViewMode.Api : MainInterfaceViewMode.Telemetry); } private void SwitchView_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; ToggleMainInterfaceViewMode(); } private void OpenDiagnosticsWindow() { var w = new DiagnosticsWindow(TryGetPid()) { Owner = this }; w.Show(); } private void ProcessRelationsPaneHost_GraphStateChanged(object? sender, EventArgs e) { _ = sender; _ = e; ScheduleChildProcessGraphWindowRefresh(); } private void OpenOrActivateChildProcessGraphWindow() { if (_childProcessGraphWindow == null) { _childProcessGraphWindow = new ChildProcessGraphWindow(ProcessRelationsPaneHost.CurrentRootPid) { Owner = this }; _childProcessGraphWindow.Closed += ChildProcessGraphWindow_Closed; RestoreChildProcessGraphStateFromSession(); RefreshChildProcessGraphWindow(); _childProcessGraphWindow.Show(); RebuildToolbarViewMenuOptions(); return; } RefreshChildProcessGraphWindow(); if (_childProcessGraphWindow.WindowState == WindowState.Minimized) { _childProcessGraphWindow.WindowState = WindowState.Normal; } _childProcessGraphWindow.Activate(); } private void RefreshChildProcessGraphWindowIfOpen() { if (_childProcessGraphWindow == null) { return; } RefreshChildProcessGraphWindow(); } private void ScheduleChildProcessGraphWindowRefresh() { if (_childProcessGraphWindow == null) { return; } _childProcessGraphRefreshTimer.Stop(); _childProcessGraphRefreshTimer.Start(); } private void ChildProcessGraphRefreshTimer_Tick(object? sender, EventArgs e) { _ = sender; _ = e; _childProcessGraphRefreshTimer.Stop(); RefreshChildProcessGraphWindowIfOpen(); } private void RefreshChildProcessGraphWindow() { _childProcessGraphWindow?.UpdateGraph(ProcessRelationsPaneHost.SnapshotItems(), ProcessRelationsPaneHost.CurrentRootPid); } private void ChildProcessGraphWindow_Closed(object? sender, EventArgs e) { _ = sender; _ = e; SaveChildProcessGraphStateToSession(); if (_childProcessGraphWindow != null) { _childProcessGraphWindow.Closed -= ChildProcessGraphWindow_Closed; } _childProcessGraphWindow = null; RebuildToolbarViewMenuOptions(); } private void SaveChildProcessGraphStateToSession(ProcessSessionTab? tab = null) { ProcessSessionTab? session = tab ?? _currentSession; if (session == null) { return; } session.ChildProcessExpandedKeys.Clear(); if (_childProcessGraphWindow == null) { return; } foreach (string key in _childProcessGraphWindow.GetExpandedKeysSnapshot()) { session.ChildProcessExpandedKeys.Add(key); } } private void RestoreChildProcessGraphStateFromSession(ProcessSessionTab? tab = null) { if (_childProcessGraphWindow == null) { return; } _childProcessGraphWindow.SetExpandedKeys((tab ?? _currentSession)?.ChildProcessExpandedKeys); } private void LayoutsComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { _ = e; if (!IsLoaded || sender is not ComboBox combo) return; string? tag = (combo.SelectedItem as System.Windows.Controls.ContentControl)?.Tag as string; if (string.IsNullOrEmpty(tag)) return; combo.SelectedIndex = 0; if (tag == "layout-simple") { ApplySimpleLayout(); } else if (tag == "layout-advanced") { ApplyAdvancedLayout(); } } private void ApplySimpleLayout() { if (EtwPaneHost != null) EtwPaneHost.Visibility = Visibility.Collapsed; if (HeuristicsPaneHost != null) HeuristicsPaneHost.Visibility = Visibility.Collapsed; if (FilesystemPaneHost != null) FilesystemPaneHost.Visibility = Visibility.Collapsed; if (ProcessRelationsPaneHost != null) ProcessRelationsPaneHost.Visibility = Visibility.Collapsed; if (IntelligenceColumn != null) IntelligenceColumn.Width = new GridLength(0); if (IntelligenceSplitterColumn != null) IntelligenceSplitterColumn.Width = new GridLength(0); } private void ApplyAdvancedLayout() { if (EtwPaneHost != null) EtwPaneHost.Visibility = Visibility.Visible; if (HeuristicsPaneHost != null) HeuristicsPaneHost.Visibility = Visibility.Visible; if (FilesystemPaneHost != null) FilesystemPaneHost.Visibility = Visibility.Visible; if (ProcessRelationsPaneHost != null) ProcessRelationsPaneHost.Visibility = Visibility.Visible; if (IntelligenceColumn != null) IntelligenceColumn.Width = new GridLength(560); if (IntelligenceSplitterColumn != null) IntelligenceSplitterColumn.Width = new GridLength(2); } private void ToolbarViewMenu_SelectionChanged(object sender, SelectionChangedEventArgs e) { _ = e; if (_toolbarViewMenuSyncing || !IsLoaded || sender is not ComboBox combo) { return; } string tag = combo.SelectedItem switch { ComboBoxItem item when item.Tag is string itemTag => itemTag, _ => string.Empty }; if (string.IsNullOrWhiteSpace(tag)) { return; } switch (tag) { case "events": SetExplorerPaneEnabled("Events", true); ShowEventsPane(); break; case "performance": SetExplorerPaneEnabled("Performance", true); ShowPerformancePane(); break; case "etw": ShowEtwPane(); break; case "heuristics": ShowHeuristicsPane(); break; case "filesystem": ShowFilesystemPane(); break; case "process-relations": ShowProcessRelationsPane(); break; case "ipc-uplink": ShowIpcUplinkPane(); break; case "event-log": SetExplorerPaneEnabled("Events", true); ShowEventsPane(); OpenOrActivateEventLogWindow(); break; case "switch-view": ToggleMainInterfaceViewMode(); break; case "inspector-etw": ShowEtwPane(); OpenEtwInspector(); break; case "inspector-heuristics": ShowHeuristicsPane(); OpenHeuristicsInspector(); break; case "inspector-filesystem": ShowFilesystemPane(); OpenFilesystemInspector(); break; case "inspector-relations": ShowProcessRelationsPane(); OpenProcessRelationsInspector(); break; case "diagnostics": OpenDiagnosticsWindow(); break; case "child-process-graph": OpenOrActivateChildProcessGraphWindow(); break; } combo.SelectedIndex = 0; RebuildToolbarViewMenuOptions(); } private void ToolbarViewMenu_DropDownOpened(object sender, EventArgs e) { _ = sender; _ = e; RebuildToolbarViewMenuOptions(); } private void RebuildToolbarViewMenuOptions() { if (ToolbarViewMenu == null) { return; } _toolbarViewMenuSyncing = true; ToolbarViewMenu.Items.Clear(); ToolbarViewMenu.Items.Add(new ComboBoxItem { Content = "Open Window..." }); AddToolbarViewOptionIfClosed("Events Pane", "events", IsEventsPaneOpen()); AddToolbarViewOptionIfClosed("Performance Pane", "performance", IsPerformancePaneOpen()); AddToolbarViewOptionIfClosed("ETW Pane", "etw", IsEtwPaneOpen()); AddToolbarViewOptionIfClosed("Heuristics Pane", "heuristics", IsHeuristicsPaneOpen()); AddToolbarViewOptionIfClosed("Filesystem Pane", "filesystem", IsFilesystemPaneOpen()); AddToolbarViewOptionIfClosed("Process Relations Pane", "process-relations", IsRelationsPaneOpen()); AddToolbarViewOptionIfClosed("IPC Uplink Pane", "ipc-uplink", IsIpcUplinkPaneOpen()); AddToolbarViewOptionIfClosed("Event Log Window", "event-log", _eventLogWindow != null && _eventLogWindow.IsVisible); AddToolbarViewOptionIfClosed( _mainViewMode == MainInterfaceViewMode.Api ? "Switch View: Telemetry" : "Switch View: API", "switch-view", isOpen: false); AddToolbarViewOptionIfClosed("ETW Inspector", "inspector-etw", IsWindowOpenByTitle("ETW Inspector")); AddToolbarViewOptionIfClosed("Heuristics Inspector", "inspector-heuristics", IsWindowOpenByTitle("Detection Chain")); AddToolbarViewOptionIfClosed("Filesystem Inspector", "inspector-filesystem", IsWindowOpenByTitle("Filesystem")); AddToolbarViewOptionIfClosed("Process Relations Inspector", "inspector-relations", IsWindowOpenByTitle("Process Relations")); AddToolbarViewOptionIfClosed("Diagnostics Window", "diagnostics", Application.Current.Windows.OfType().Any(w => w is DiagnosticsWindow && w.IsVisible)); AddToolbarViewOptionIfClosed("Child Process Graph", "child-process-graph", _childProcessGraphWindow != null && _childProcessGraphWindow.IsVisible); ToolbarViewMenu.SelectedIndex = 0; _toolbarViewMenuSyncing = false; } private void AddToolbarViewOptionIfClosed(string label, string tag, bool isOpen) { if (ToolbarViewMenu == null || isOpen) { return; } ToolbarViewMenu.Items.Add(new ComboBoxItem { Content = label, Tag = tag }); } private bool IsEventsPaneOpen() => (FindExplorerItem("Events")?.IsEnabled ?? true) && (_eventsPaneVisible || _eventsPaneFloating || _eventsFloatWindow != null); private bool IsPerformancePaneOpen() => (FindExplorerItem("Performance")?.IsEnabled ?? true) && (_performancePaneVisible || _performancePaneFloating || _performanceFloatWindow != null); private bool IsEtwPaneOpen() => (FindExplorerItem("ETW")?.IsEnabled ?? true); private bool IsHeuristicsPaneOpen() => (FindExplorerItem("Heuristics")?.IsEnabled ?? true); private bool IsFilesystemPaneOpen() => (FindExplorerItem("Filesystem")?.IsEnabled ?? true); private bool IsRelationsPaneOpen() => (FindExplorerItem("Process Relations")?.IsEnabled ?? true); private bool IsIpcUplinkPaneOpen() => (FindExplorerItem("IPC Uplink")?.IsEnabled ?? false); private static bool IsWindowOpenByTitle(string title) => Application.Current?.Windows .OfType() .Any(w => w.IsVisible && string.Equals(w.Title, title, StringComparison.OrdinalIgnoreCase)) ?? false; private void ToggleFloatDock() { if (_eventsFloatWindow == null) { UndockEventsPane(); } else { RedockEventsPane(); } } private void ToggleEventLogDock() { if (_eventLogWindow == null) { UndockEventLog(); } else { RedockEventLog(); } } private void UndockEventLog() { if (_eventLogWindow != null) { return; } _eventLogWindow = new EventLogWindow { Owner = this, ShowInTaskbar = false }; _eventLogWindow.EtwFeedRequested += EventLogWindow_EtwFeedRequested; _eventLogWindow.Closed += EventLogWindow_Closed; EventsPaneHost.SetEventLogDetached(true); UpdateDetachedEventLogWindow(immediate: true); _eventLogWindow.Show(); } private void RedockEventLog() { if (_eventLogWindow != null) { _eventLogWindow.EtwFeedRequested -= EventLogWindow_EtwFeedRequested; _eventLogWindow.Closed -= EventLogWindow_Closed; _eventLogWindow.Close(); _eventLogWindow = null; } EventsPaneHost.SetEventLogDetached(false); } private void EventLogWindow_Closed(object? sender, EventArgs e) { if (_eventLogWindow != null) { _eventLogWindow.EtwFeedRequested -= EventLogWindow_EtwFeedRequested; } _eventLogWindow = null; EventsPaneHost.SetEventLogDetached(false); } private void DetachedEventLogRefreshTimer_Tick(object? sender, EventArgs e) { _detachedEventLogRefreshTimer.Stop(); _eventLogWindow?.RefreshEvents(_focusedEvents); } private void UpdateDetachedEventLogWindow(bool immediate = false) { if (_eventLogWindow == null) { return; } if (immediate) { _detachedEventLogRefreshTimer.Stop(); _eventLogWindow.RefreshEvents(_focusedEvents); return; } _detachedEventLogRefreshTimer.Stop(); _detachedEventLogRefreshTimer.Start(); } private void EventLogWindow_EtwFeedRequested(object? sender, EventLogCardOpenRequestedEventArgs e) { _ = sender; if (e == null) { return; } var displayMatches = _focusedEvents .Where(ev => EventMatchesLogCard(ev, e.Group, e.SubType, e.Summary, e.Details, e.Pid, e.Tid)) .ToList(); OpenEventLogDetailWindow("Event Log Detail", ExpandFocusedEventMembers(displayMatches)); } private void EventsPaneHost_EventLogEntryOpenRequested(object? sender, EventLogEntryOpenRequestedEventArgs e) { _ = sender; if (e?.Event == null) { return; } TelemetryEvent selected = e.Event; OpenEventLogDetailWindow("Event Detail", GetFocusedEventMembers(selected)); } private void OpenEventLogDetailWindow(string title, IReadOnlyList matches) { if (matches == null || matches.Count == 0) { return; } GroupedEventRow row = BuildEventLogDetailRow(matches); var detail = new SimpleEventDetailWindow(title, row) { Owner = this }; detail.Show(); detail.Activate(); } private static bool EventMatchesLogCard( TelemetryEvent ev, string group, string subType, string summary, string details, int pid, int tid) { if (ev == null) { return false; } return string.Equals(ev.Group ?? string.Empty, group ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(ev.SubType ?? string.Empty, subType ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(ev.Summary ?? string.Empty, summary ?? string.Empty, StringComparison.Ordinal) && string.Equals(ev.Details ?? string.Empty, details ?? string.Empty, StringComparison.Ordinal) && ev.PID == pid && ev.TID == tid; } private static GroupedEventRow BuildEventLogDetailRow(IReadOnlyList matches) { TelemetryEvent first = matches[0]; string group = string.IsNullOrWhiteSpace(first.Group) ? "Other" : first.Group; string subType = first.SubType ?? string.Empty; string eventName = string.IsNullOrWhiteSpace(subType) ? group : $"{group}/{subType}"; string summary = first.Summary ?? string.Empty; string key = $"{group}\u001F{subType}\u001F{first.PID}\u001F{first.TID}\u001F{summary}\u001F{first.Details}"; var row = new GroupedEventRow { GroupKey = key, LastSeenUtc = matches.Max(x => x.TimestampUtc), Event = eventName, Severity = "Event", Detection = summary, Hits = matches.Count }; foreach (TelemetryEvent ev in matches.OrderByDescending(x => x.TimestampUtc)) { string actor = string.IsNullOrWhiteSpace(ev.ProcessName) ? $"pid:{ev.PID}" : ev.ProcessName; string target = ev.TID > 0 ? $"tid={ev.TID}" : string.Empty; row.Details.Add(new GroupedEventDetailRow { TimestampUtc = ev.TimestampUtc, Event = eventName, Severity = "Event", Detection = ev.Summary ?? string.Empty, Source = group, Actor = actor, Target = target, ActorPid = ev.PID > 0 ? unchecked((uint)ev.PID) : 0u, TargetPid = 0u, ActorToolTip = ev.PID > 0 ? $"PID {ev.PID}" : string.Empty, Details = ev.Details ?? string.Empty }); } return row; } private void TogglePerformanceFloatDock() { if (_performanceFloatWindow == null) { UndockPerformancePane(); } else { RedockPerformancePane(); } } private void ToggleEtwFloatDock() { if (_etwFloatWindow == null) UndockEtwPane(); else RedockEtwPane(); } private void ToggleHeuristicsFloatDock() { if (_heuristicsFloatWindow == null) UndockHeuristicsPane(); else RedockHeuristicsPane(); } private void ResetDock_Click(object sender, RoutedEventArgs e) { _eventsFloatWindow?.Close(); _eventLogWindow?.Close(); _performanceFloatWindow?.Close(); _etwFloatWindow?.Close(); _heuristicsFloatWindow?.Close(); _laneFocusKey = null; EventsPaneHost.Timeline.ClearAllLaneFilters(); EventsPaneHost.Timeline.ViewDurationSeconds = 120; EventsPaneHost.Timeline.ViewStartSeconds = 0; EventsPaneHost.Scroll.Value = 0; _followLiveTimeline = true; ShowEventsPane(); ShowPerformancePane(); var sw = FindExplorerItem("ETW"); if (sw != null) sw.IsEnabled = true; var heur = FindExplorerItem("Heuristics"); if (heur != null) heur.IsEnabled = true; var fs = FindExplorerItem("Filesystem"); if (fs != null) fs.IsEnabled = true; var rel = FindExplorerItem("Process Relations"); if (rel != null) rel.IsEnabled = true; var ipc = FindExplorerItem("IPC Uplink"); if (ipc != null) ipc.IsEnabled = false; _performanceOnTop = false; _heuristicsOnTop = false; ApplyPaneOrder(); ApplyIntelPaneOrder(); HideDockPreview(); FocusViewport(); SyncPerformanceViewToTimeline(); } private void DockEventsToggle_Click(object sender, RoutedEventArgs e) { ToggleFloatDock(); } private void DockPerfToggle_Click(object sender, RoutedEventArgs e) { TogglePerformanceFloatDock(); } private void DockEtwToggle_Click(object sender, RoutedEventArgs e) { ToggleEtwFloatDock(); } private void DockHeurToggle_Click(object sender, RoutedEventArgs e) { ToggleHeuristicsFloatDock(); } private void DockSwapMain_Click(object sender, RoutedEventArgs e) { TogglePaneOrder(); } private void DockSwapIntel_Click(object sender, RoutedEventArgs e) { ToggleIntelPaneOrder(); } private void ChildProcessView_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; OpenOrActivateChildProcessGraphWindow(); } // ------------------------------- // Settings / diagnostics // ------------------------------- private void OpenLaneSettings() { var w = new LaneSettingsWindow(this); w.Owner = this; w.Show(); } private void Diagnostics_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; OpenDiagnosticsWindow(); } private void PerformancePaneHost_ThreadDoubleClicked(object? sender, ThreadUsageRow row) { int pid = _currentSession?.Pid ?? TryGetPid(); var w = new ThreadStackWindow( pid, row.Tid, row.State, initialHistory: GetThreadStackHistory(pid, row.Tid, row.State), onSnapshotCaptured: snapshot => PersistThreadStackSnapshot(pid, row.Tid, row.State, snapshot), observationTimeUtcProvider: GetCurrentObservedUtc, liveCaptureAvailableProvider: () => _currentSession != null && _currentSession.Pid == pid && !_currentSession.TargetExited && !_currentSession.OfflineSnapshot) { Owner = this }; w.Show(); } private void OpenParallelStacksWindow() { int pid = _currentSession?.Pid ?? TryGetPid(); IReadOnlyList rows = PerformancePaneHost.SnapshotTopThreads(); if (pid <= 0 || rows.Count == 0) { ThemedMessageBox.Show(this, "No thread rows are available for stack comparison.", "Parallel Stacks", MessageBoxButton.OK, MessageBoxImage.Information); return; } var window = new ParallelStacksWindow(pid, rows) { Owner = this }; window.Show(); } private async void FindProcess_Click(object sender, RoutedEventArgs e) => await OpenProcessPickerAndConnectAsync(showLaunchOptions: true); private async void NewProcessTab_Click(object sender, RoutedEventArgs e) => await OpenProcessPickerAndConnectAsync(showLaunchOptions: true); private void CloseProcessTab_Click(object sender, RoutedEventArgs e) { if ((sender as FrameworkElement)?.Tag is not ProcessSessionTab tab) return; if (_processTabs.Count <= 1) return; bool wasSelected = ReferenceEquals(ProcessTabs.SelectedItem, tab); if (ReferenceEquals(_currentSession, tab)) SaveCurrentSessionState(); _processTabs.Remove(tab); if (_processTabs.Count == 0) { _currentSession = null; PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.SetTargetSuspended(false); StatusBlock.Text = "NO TARGET SELECTED"; RefreshProcessStateBadge(); return; } if (wasSelected) { _currentSession = null; ProcessTabs.SelectedItem = _processTabs[0]; } } private void ProcessTabs_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (_suppressTabSelectionChange) return; if (ProcessTabs.SelectedItem is not ProcessSessionTab tab) return; SwitchToSession(tab); RefreshProcessStateBadge(); } private DateTime GetCurrentObservedUtc() { if (EventsPaneHost?.Timeline == null) { return DateTime.UtcNow; } return _captureStartUtc + TimeSpan.FromSeconds(EventsPaneHost.Timeline.ViewStartSeconds + EventsPaneHost.Timeline.ViewDurationSeconds); } private static string ResolveHookDllPathFromInterfaceDirectory() { string baseDirectory = AppContext.BaseDirectory; if (string.IsNullOrWhiteSpace(baseDirectory)) { baseDirectory = Environment.CurrentDirectory; } return Path.Combine(baseDirectory, "SR71.dll"); } private bool TrySendUserHookRequest( uint mode, uint processId, uint flags, string? imagePath, out BlackbirdNative.BkSetUserHookTargetResponse response, out string error) { response = default; error = string.Empty; if (!BlackbirdControlDeviceSession.TryOpen(out var control, out error)) { return false; } using (control) { string hookPath = ResolveHookDllPathFromInterfaceDirectory(); if (!File.Exists(hookPath)) { error = $"SetUserHookTarget failed because the hook DLL is missing: '{hookPath}'."; return false; } if (!BlackbirdNative.SetUserHookTarget( control.Handle, mode, processId, flags, imagePath, hookPath, null, null, 0, 0, 0, false, out response)) { error = BlackbirdControlDeviceSession.FormatUserHookOperationError( "SetUserHookTarget", BlackbirdNative.LastError("SetUserHookTarget failed"), hookPath); return false; } return true; } } private bool TryAttachUsermodeHooks(int pid, out string error) { error = string.Empty; if (pid <= 0) { error = "Invalid PID for hook attach."; return false; } if (!TrySendUserHookRequest( BlackbirdNative.IpcUserHookTargetAttach, unchecked((uint)pid), 0, null, out _, out error)) { return false; } OutputCapture.AppendLine($"Hook attached via controller: PID {pid}"); return true; } private bool TryLaunchWithUsermodeHooksAndPrepareSession( string imagePath, bool useEarlyBirdApc, LaunchProfile launchProfile, out int pid, out BlackbirdBackendSession? preparedSession, out string error) { pid = 0; preparedSession = null; error = string.Empty; if (string.IsNullOrWhiteSpace(imagePath)) { error = "Launch path is empty."; return false; } uint flags = useEarlyBirdApc ? BlackbirdNative.IpcUserHookFlagLaunchEarlybirdApc : 0u; try { if (!BlackbirdControlDeviceSession.TryOpen(out var control, out error)) { return false; } using (control) { string hookPath = ResolveHookDllPathFromInterfaceDirectory(); if (!File.Exists(hookPath)) { error = $"Hook launch failed because the hook DLL is missing: '{hookPath}'."; return false; } string environmentOverrides = launchProfile.ToIpcEnvironmentOverrideBlock(); if (!BlackbirdNative.SetUserHookTarget( control.Handle, BlackbirdNative.IpcUserHookTargetLaunch, 0, flags, imagePath, hookPath, launchProfile.HasWorkingDirectory ? launchProfile.WorkingDirectory : null, string.IsNullOrWhiteSpace(environmentOverrides) ? null : environmentOverrides, launchProfile.ParentProcessId, MapLaunchPriorityClass(launchProfile.Priority), launchProfile.AffinityMask, launchProfile.InheritHandles, out BlackbirdNative.BkSetUserHookTargetResponse response)) { error = BlackbirdControlDeviceSession.FormatUserHookOperationError( "SetUserHookTarget(launch)", BlackbirdNative.LastError("SetUserHookTarget(launch) failed"), hookPath); return false; } if (response.ProcessId == 0) { error = "Controller launch returned no PID."; return false; } pid = unchecked((int)response.ProcessId); preparedSession = BlackbirdBackendSession.StartFromHandle( pid, BlackbirdNative.StreamAll, useUsermodeHooks: true, control.Handle); _ = control.DetachHandle(); string modeLabel = useEarlyBirdApc ? "earlybird-apc" : "remote-thread"; OutputCapture.AppendLine($"Hook launch via controller ({modeLabel}): {imagePath} \u2192 PID {pid} (pre-armed session)"); return true; } } catch (Exception ex) { if (preparedSession != null) { preparedSession.Dispose(); preparedSession = null; } if (string.IsNullOrWhiteSpace(error)) { error = ex.Message; } return false; } } // ------------------------------- // Stub buttons // ------------------------------- private async void Connect_Click(object sender, RoutedEventArgs e) { if (_connectInProgress) { return; } _connectInProgress = true; try { int pid = TryGetPid(); if (pid <= 0) { DisposePreparedLaunchBackendSession(); ClearPendingLaunchOptions(); StatusBlock.Text = "ENTER A VALID PID"; RefreshProcessStateBadge(); return; } if (!TryOpenTargetProcess(pid, out var processName, out var failure, out var accessDenied)) { DisposePreparedLaunchBackendSession(); ClearPendingLaunchOptions(); StatusBlock.Text = failure; if (accessDenied) { ThemedMessageBox.Show( this, $"Access denied while opening PID {pid}. The process handle could not be opened with required access rights.", "Target Attach Failed", MessageBoxButton.OK, MessageBoxImage.Warning); } RefreshProcessStateBadge(); return; } StatusBlock.Text = $"CONNECTED TO {processName} ({pid})"; var tab = AddOrSelectProcessTab(pid, $"{processName} ({pid})", select: true); if (_pendingLaunchOptions) { tab.LaunchOwnedByInterface = _pendingLaunchOwnedByInterface; } bool hookPreconfigured = _pendingHookPreconfigured; bool launchStartsSuspended = _pendingLaunchStartsSuspended; bool leaveSuspendedAfterReady = _pendingLeaveSuspendedAfterReady; if (_pendingLaunchOptions) { tab.UseUsermodeHooks = _pendingUseUsermodeHooks; tab.AutoOpenApiGraphOnNextStart = _pendingAutoOpenApiGraph; } tab.LaunchStartsSuspendedPending = launchStartsSuspended; ClearPendingLaunchOptions(); if (tab.UseUsermodeHooks && !hookPreconfigured) { LoadingWindow? hookLoading = null; try { hookLoading = new LoadingWindow { Owner = this }; hookLoading.SetProgress(38, "Attaching usermode hooks...", $"Injecting SR71.dll into PID {pid}."); hookLoading.Show(); await Dispatcher.InvokeAsync(() => { }, DispatcherPriority.Render); var hookResult = await Task.Run(() => { bool ok = TryAttachUsermodeHooks(pid, out string err); return (ok, err); }); if (!hookResult.ok) { DisposePreparedLaunchBackendSession(); StatusBlock.Text = $"HOOK ATTACH FAILED FOR PID {pid}"; ThemedMessageBox.Show( this, $"Failed to attach usermode hooks for PID {pid}.\n\n{hookResult.err}", "Hook Attach Failed", MessageBoxButton.OK, MessageBoxImage.Error); RefreshProcessStateBadge(); return; } } finally { if (hookLoading != null && hookLoading.IsVisible) { hookLoading.Close(); } } if (!IsLoaded) { DisposePreparedLaunchBackendSession(); return; } } tab.TargetExited = false; tab.OfflineSnapshot = false; if (!ReferenceEquals(_currentSession, tab)) { SwitchToSession(tab); } else { StartLiveCaptureForPid(pid, tab.UseUsermodeHooks, launchStartsSuspended); } if (hookPreconfigured && !leaveSuspendedAfterReady) { if (!TryControlTargetExecution(suspend: false, out string resumeError)) { _targetExecutionSuspended = true; PerformancePaneHost.SetTargetSuspended(true); StatusBlock.Text = $"SESSION READY, TARGET STILL SUSPENDED: PID {pid}"; ThemedMessageBox.Show( this, $"Blackbird attached successfully, but the target could not be resumed automatically.\n\n{resumeError}", "Launch Resume Failed", MessageBoxButton.OK, MessageBoxImage.Warning); RefreshProcessStateBadge(); RefreshToolbarCommandState(); return; } _targetExecutionSuspended = false; PerformancePaneHost.SetTargetSuspended(false); StatusBlock.Text = $"LIVE CAPTURE READY: {NormalizeSessionTitle(_currentSession?.Title ?? $"PID {pid}")}"; } else if (launchStartsSuspended) { _targetExecutionSuspended = true; PerformancePaneHost.SetTargetSuspended(true); StatusBlock.Text = $"LIVE CAPTURE READY, TARGET SUSPENDED: {NormalizeSessionTitle(_currentSession?.Title ?? $"PID {pid}")}"; } _ = RunPreflightAsync(pid); RefreshProcessStateBadge(); } finally { _connectInProgress = false; } } private async void Launch_Click(object sender, RoutedEventArgs e) => await OpenProcessPickerAndConnectAsync(showLaunchOptions: true); private async Task OpenProcessPickerAndConnectAsync(bool showLaunchOptions) { if (_openingProcessPicker) return; DisposePreparedLaunchBackendSession(); ClearPendingLaunchOptions(); _openingProcessPicker = true; LoadingWindow? loading = null; try { loading = new LoadingWindow { Owner = this }; loading.SetProgress(14, "Preparing process picker...", "Initializing process view shell."); loading.Show(); await Dispatcher.InvokeAsync(() => { }, System.Windows.Threading.DispatcherPriority.Render); var picker = new ProcessPickerWindow { Owner = this, ShowLaunchOptions = showLaunchOptions }; loading.SetProgress(100, "Opening picker...", "Process picker will stream process metadata after opening."); await Task.Delay(90); loading.Close(); loading = null; bool? result = picker.ShowDialog(); if (result != true) return; int selectedPid = picker.SelectedPid; bool hookPreconfigured = false; bool useUsermodeHooks = false; bool autoOpenApiGraph = false; bool useEarlyBirdApcLaunch = false; LaunchProfile launchProfile = new(); if (showLaunchOptions && (picker.LaunchSelectedImage || selectedPid > 0)) { var parametersWindow = new LaunchParametersWindow(isLaunchTarget: picker.LaunchSelectedImage) { Owner = this }; bool? parametersAccepted = parametersWindow.ShowDialog(); if (parametersAccepted != true) { return; } useUsermodeHooks = parametersWindow.UseUsermodeHooks; autoOpenApiGraph = parametersWindow.AutoOpenApiGraphWindow; useEarlyBirdApcLaunch = parametersWindow.UseEarlyBirdApcLaunch; launchProfile = parametersWindow.LaunchProfile; } if (showLaunchOptions && picker.LaunchSelectedImage) { string launchImagePath = picker.LaunchImagePath?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(launchImagePath)) { ThemedMessageBox.Show(this, "Launch path is empty.", "Launch failed", MessageBoxButton.OK, MessageBoxImage.Error); return; } if (useUsermodeHooks) { LoadingWindow? launchLoading = null; bool launchOk; int launchedPid = 0; BlackbirdBackendSession? preparedSession = null; string launchError = string.Empty; try { launchLoading = new LoadingWindow { Owner = this }; launchLoading.SetProgress(42, "Launching target with hooks...", "Submitting launch + SR71 staging request."); launchLoading.Show(); await Dispatcher.InvokeAsync(() => { }, DispatcherPriority.Render); launchOk = await Task.Run(() => TryLaunchWithUsermodeHooksAndPrepareSession( launchImagePath, useEarlyBirdApcLaunch, launchProfile, out launchedPid, out preparedSession, out launchError)); } finally { if (launchLoading != null && launchLoading.IsVisible) { launchLoading.Close(); } } if (!launchOk) { if (preparedSession != null) { try { preparedSession.Dispose(); } catch { } } ThemedMessageBox.Show(this, launchError, "Hook launch failed", MessageBoxButton.OK, MessageBoxImage.Error); return; } selectedPid = launchedPid; _preparedLaunchBackendSession = preparedSession; _preparedLaunchBackendPid = selectedPid; hookPreconfigured = true; } else { if (!BlackbirdNative.TryLaunchProcess(launchImagePath, launchProfile, out selectedPid, out string launchError)) { ThemedMessageBox.Show(this, launchError, "Launch failed", MessageBoxButton.OK, MessageBoxImage.Error); return; } } } if (selectedPid <= 0) { return; } _pendingLaunchOptions = showLaunchOptions; _pendingUseUsermodeHooks = showLaunchOptions && useUsermodeHooks; _pendingAutoOpenApiGraph = showLaunchOptions && autoOpenApiGraph; _pendingHookPreconfigured = hookPreconfigured; _pendingLaunchStartsSuspended = showLaunchOptions && picker.LaunchSelectedImage && (hookPreconfigured || launchProfile.LeaveSuspendedAfterReady); _pendingLeaveSuspendedAfterReady = showLaunchOptions && picker.LaunchSelectedImage && launchProfile.LeaveSuspendedAfterReady; _pendingLaunchOwnedByInterface = showLaunchOptions && picker.LaunchSelectedImage; PidBox.Text = selectedPid.ToString(); Connect_Click(this, new RoutedEventArgs()); } finally { if (loading != null && loading.IsVisible) { loading.Close(); } _openingProcessPicker = false; } } internal void ConnectToStartupPid(int pid) { if (pid <= 0) { return; } PidBox.Text = pid.ToString(); Connect_Click(this, new RoutedEventArgs()); } internal async Task BeginStartupLaunchFlowAsync() { await OpenProcessPickerAndConnectAsync(showLaunchOptions: true); } private void ApplyPausedTimelineRanges() { if (EventsPaneHost?.Timeline == null) { return; } if (_currentSession == null) { EventsPaneHost.Timeline.SetPauseRanges(null); return; } var ranges = _currentSession.PausedTimelineSpans .Select(x => new TimelinePauseRange { StartUtc = x.StartUtc, EndUtc = x.EndUtc }) .ToList(); if (_currentSession.ActivePauseStartUtc.HasValue) { ranges.Add(new TimelinePauseRange { StartUtc = _currentSession.ActivePauseStartUtc.Value, EndUtc = DateTime.UtcNow }); } EventsPaneHost.Timeline.SetPauseRanges(ranges); } private void DisposePreparedLaunchBackendSession() { if (_preparedLaunchBackendSession != null) { try { _preparedLaunchBackendSession.Dispose(); } catch { } } _preparedLaunchBackendSession = null; _preparedLaunchBackendPid = 0; } private void ClearPendingLaunchOptions() { _pendingLaunchOptions = false; _pendingUseUsermodeHooks = false; _pendingAutoOpenApiGraph = false; _pendingHookPreconfigured = false; _pendingLaunchStartsSuspended = false; _pendingLeaveSuspendedAfterReady = false; _pendingLaunchOwnedByInterface = false; } private static uint MapLaunchPriorityClass(LaunchPriorityPreset priority) => priority switch { LaunchPriorityPreset.Idle => 0x00000040, LaunchPriorityPreset.BelowNormal => 0x00004000, LaunchPriorityPreset.Normal => 0x00000020, LaunchPriorityPreset.AboveNormal => 0x00008000, LaunchPriorityPreset.High => 0x00000080, LaunchPriorityPreset.Realtime => 0x00000100, _ => 0u }; internal bool TryOpenSessionFromStartupPath(string path, out string error) { return TryOpenSessionArchivePath(path, merge: false, out error); } private void Suspend_Click(object sender, RoutedEventArgs e) { if (!TryControlTargetExecution(suspend: true, out string error)) { ThemedMessageBox.Show(this, error, "Suspend Target", MessageBoxButton.OK, MessageBoxImage.Error); return; } _targetExecutionSuspended = true; if (_currentSession != null && !_currentSession.OfflineSnapshot && !_currentSession.TargetExited && !_currentSession.ActivePauseStartUtc.HasValue) { _currentSession.ActivePauseStartUtc = DateTime.UtcNow; } _perf?.Stop(); PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.SetTargetSuspended(true); ApplyPausedTimelineRanges(); SetIntegrityDiagnosticsForSuspension(); StatusBlock.Text = $"TARGET PAUSED: PID {TryGetPid()}"; RefreshProcessStateBadge(); RefreshToolbarCommandState(); } private void Resume_Click(object sender, RoutedEventArgs e) { if (!TryControlTargetExecution(suspend: false, out string error)) { ThemedMessageBox.Show(this, error, "Resume Target", MessageBoxButton.OK, MessageBoxImage.Error); return; } _targetExecutionSuspended = false; ClearIntegrityDiagnosticsForSuspension(); PerformancePaneHost.SetTargetSuspended(false); if (_currentSession != null && _currentSession.ActivePauseStartUtc.HasValue) { DateTime resumeUtc = DateTime.UtcNow; _currentSession.PausedTimelineSpans.Add(new PausedTimelineSpan { StartUtc = _currentSession.ActivePauseStartUtc.Value, EndUtc = resumeUtc }); _currentSession.ActivePauseStartUtc = null; _latestEventTimestampUtc = resumeUtc; ApplyPausedTimelineRanges(); UpdateScrollBar(); _followLiveTimeline = true; double viewport = Math.Max(1, EventsPaneHost.Scroll.ViewportSize); double maxStart = ComputeTimelineMaxStart(viewport); EventsPaneHost.Timeline.ViewStartSeconds = maxStart; EventsPaneHost.Scroll.Value = maxStart; SyncPerformanceViewToTimeline(); } if (_currentSession != null && !_currentSession.OfflineSnapshot && !_currentSession.TargetExited && _perf != null) { _perf.SetTargetPid(_currentSession.Pid); _perf.Start(); } PerformancePaneHost.SetProcessLiveDataAvailable(true); StatusBlock.Text = $"TARGET RESUMED: PID {TryGetPid()}"; RefreshProcessStateBadge(); RefreshToolbarCommandState(); } private static readonly string[] _integrityDiagnosticKeys = ["Hook Integrity", "AMSI Integrity", "ETW Integrity"]; private static void SetIntegrityDiagnosticsForSuspension() { foreach (string key in _integrityDiagnosticKeys) { string? current = DiagnosticsState.GetValue(key); if (current == null || !current.Contains("TAMPERED", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue(key, "INACTIVE (target suspended)"); } } DiagnosticsState.SetValue("Usermode Hooks", "INACTIVE (target suspended)"); } private static void ClearIntegrityDiagnosticsForSuspension() { foreach (string key in _integrityDiagnosticKeys) { string? current = DiagnosticsState.GetValue(key); if (current != null && current.Contains("INACTIVE", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue(key, "INACTIVE (resuming)"); } } if (DiagnosticsState.GetValue("Usermode Hooks")?.Contains("INACTIVE", StringComparison.OrdinalIgnoreCase) == true) { DiagnosticsState.SetValue("Usermode Hooks", "INACTIVE (resuming)"); } } private void TerminateLaunchOwnedTargetsOnShutdown() { foreach (ProcessSessionTab tab in _processTabs) { if (!tab.LaunchOwnedByInterface || tab.OfflineSnapshot || tab.TargetExited || tab.Pid <= 0) { continue; } if (TryTerminateTargetProcess(tab.Pid, out _)) { tab.TargetExited = true; } } } private void TerminateTarget_Click(object sender, RoutedEventArgs e) { int pid = _currentSession?.Pid ?? TryGetPid(); if (pid <= 0) { ThemedMessageBox.Show(this, "No target process is selected.", "Terminate Target", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (_currentSession?.OfflineSnapshot == true) { ThemedMessageBox.Show(this, "Cannot terminate an offline session.", "Terminate Target", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (_currentSession?.TargetExited == true) { ThemedMessageBox.Show(this, $"PID {pid} has already exited.", "Terminate Target", MessageBoxButton.OK, MessageBoxImage.Information); return; } var confirm = ThemedMessageBox.Show( this, $"Terminate PID {pid} and stop live capture for this tab?", "Terminate Target", MessageBoxButton.YesNo, MessageBoxImage.Warning); if (confirm != MessageBoxResult.Yes) { return; } if (!TryTerminateTargetProcess(pid, out string error)) { ThemedMessageBox.Show(this, error, "Terminate Target", MessageBoxButton.OK, MessageBoxImage.Error); return; } HandleTargetProcessExit(pid); } private void Restart_Click(object sender, RoutedEventArgs e) { } private bool TryControlTargetExecution(bool suspend, out string error) { error = string.Empty; int pid = _currentSession?.Pid ?? TryGetPid(); if (pid <= 0) { error = "No target process is selected."; return false; } if (_currentSession?.OfflineSnapshot == true) { error = "Cannot control execution for an offline session."; return false; } if (_currentSession?.TargetExited == true) { error = $"PID {pid} has already exited."; return false; } if (_backendSession == null) { error = "No active backend session."; return false; } bool ok = _backendSession.ControlProcessExecution(unchecked((uint)pid), suspend); if (!ok) { int ioErr = Marshal.GetLastWin32Error(); error = $"{(suspend ? "Suspend" : "Resume")} failed via controller (win32={ioErr})."; return false; } return true; } private bool TryTerminateTargetProcess(int pid, out string error) { error = string.Empty; IntPtr handle = Kernel32Native.OpenProcess( ProcessTerminate | ProcessQueryLimitedInformation | ProcessSynchronize, false, unchecked((uint)pid)); if (handle == IntPtr.Zero) { int openErr = Marshal.GetLastWin32Error(); error = $"Failed to open PID {pid} for termination (win32={openErr})."; return false; } try { if (!Kernel32Native.TerminateProcess(handle, 1)) { int terminateErr = Marshal.GetLastWin32Error(); error = $"TerminateProcess failed for PID {pid} (win32={terminateErr})."; return false; } return true; } finally { _ = Kernel32Native.CloseHandle(handle); } } // ------------------------------- // Demo feed // ------------------------------- private void SeedFake() { var t0 = _captureStartUtc; AppendEvent(new TelemetryEvent { TimestampUtc = t0.AddSeconds(1), PID = 1234, TID = 10, Group = "Execution", SubType = "CreateProcess", Summary = "proc created" }); AppendEvent(new TelemetryEvent { TimestampUtc = t0.AddSeconds(2), PID = 1234, TID = 11, Group = "Thread", SubType = "RemoteThread", Summary = "remote thread start" }); AppendEvent(new TelemetryEvent { TimestampUtc = t0.AddSeconds(6), PID = 1234, TID = 12, Group = "Registry", SubType = "SetValue", Summary = "reg set value" }); AppendEvent(new TelemetryEvent { TimestampUtc = t0.AddSeconds(12), PID = 1234, TID = 13, Group = "Handles", SubType = "Duplicate", Summary = "dup handle" }); AppendEvent(new TelemetryEvent { TimestampUtc = t0.AddSeconds(35), PID = 1234, TID = 14, Group = "Injection", SubType = "MapView", Summary = "write+map" }); } private void UndockEventsPane() { if (_eventsFloatWindow != null) return; if (EventsDockBorder.Child == EventsPaneHost) EventsDockBorder.Child = null; _eventsPaneFloating = true; _eventsPaneVisible = true; ApplyDockVisibilityFromExplorer(); Dispatcher.BeginInvoke(new Action(() => { if (_eventsFloatWindow != null) return; _eventsFloatWindow = new EventsFloatWindow(EventsPaneHost) { Owner = this, ShowInTaskbar = false }; _eventsFloatWindow.Closing += EventsFloatWindow_Closing; _eventsFloatWindow.Closed += EventsFloatWindow_Closed; _eventsFloatWindow.Show(); }), System.Windows.Threading.DispatcherPriority.Background); } private void RedockEventsPane() { if (_eventsFloatWindow != null) { _eventsFloatWindow.Closing -= EventsFloatWindow_Closing; _eventsFloatWindow.Closed -= EventsFloatWindow_Closed; if (_eventsFloatWindow.Content == EventsPaneHost) _eventsFloatWindow.Content = null; _eventsFloatWindow.Close(); _eventsFloatWindow = null; } _eventsPaneFloating = false; if (EventsDockBorder.Child == null) EventsDockBorder.Child = EventsPaneHost; _eventsPaneVisible = true; _draggingEventsPaneHeader = false; HideDockPreview(); ApplyDockVisibilityFromExplorer(); } private void UndockPerformancePane() { if (_performanceFloatWindow != null) return; if (PerformanceDockBorder.Child == PerformancePaneHost) PerformanceDockBorder.Child = null; _performancePaneFloating = true; _performancePaneVisible = true; ApplyDockVisibilityFromExplorer(); Dispatcher.BeginInvoke(new Action(() => { if (_performanceFloatWindow != null) return; _performanceFloatWindow = new PerformanceFloatWindow(PerformancePaneHost) { Owner = this, ShowInTaskbar = false }; _performanceFloatWindow.Closing += PerformanceFloatWindow_Closing; _performanceFloatWindow.Closed += PerformanceFloatWindow_Closed; _performanceFloatWindow.Show(); }), System.Windows.Threading.DispatcherPriority.Background); } private void RedockPerformancePane() { if (_performanceFloatWindow != null) { _performanceFloatWindow.Closing -= PerformanceFloatWindow_Closing; _performanceFloatWindow.Closed -= PerformanceFloatWindow_Closed; if (_performanceFloatWindow.Content == PerformancePaneHost) _performanceFloatWindow.Content = null; _performanceFloatWindow.Close(); _performanceFloatWindow = null; } _performancePaneFloating = false; if (PerformanceDockBorder.Child == null) PerformanceDockBorder.Child = PerformancePaneHost; _performancePaneVisible = true; _draggingPerformancePaneHeader = false; HideDockPreview(); ApplyDockVisibilityFromExplorer(); } private void UndockEtwPane() { if (_etwFloatWindow != null) return; if (EtwDockBorder.Child == EtwPaneHost) EtwDockBorder.Child = null; _etwPaneFloating = true; ApplyDockVisibilityFromExplorer(); Dispatcher.BeginInvoke(new Action(() => { if (_etwFloatWindow != null) return; _etwFloatWindow = new EtwFloatWindow(EtwPaneHost) { Owner = this, ShowInTaskbar = false }; _etwFloatWindow.Closing += EtwFloatWindow_Closing; _etwFloatWindow.Closed += EtwFloatWindow_Closed; _etwFloatWindow.Show(); }), System.Windows.Threading.DispatcherPriority.Background); } private void RedockEtwPane() { if (_etwFloatWindow != null) { _etwFloatWindow.Closing -= EtwFloatWindow_Closing; _etwFloatWindow.Closed -= EtwFloatWindow_Closed; if (_etwFloatWindow.Content == EtwPaneHost) _etwFloatWindow.Content = null; _etwFloatWindow.Close(); _etwFloatWindow = null; } _etwPaneFloating = false; if (EtwDockBorder.Child == null) EtwDockBorder.Child = EtwPaneHost; ApplyDockVisibilityFromExplorer(); } private void UndockHeuristicsPane() { if (_heuristicsFloatWindow != null) return; if (HeuristicsDockBorder.Child == HeuristicsPaneHost) HeuristicsDockBorder.Child = null; _heuristicsPaneFloating = true; ApplyDockVisibilityFromExplorer(); Dispatcher.BeginInvoke(new Action(() => { if (_heuristicsFloatWindow != null) return; _heuristicsFloatWindow = new HeuristicsFloatWindow(HeuristicsPaneHost) { Owner = this, ShowInTaskbar = false }; _heuristicsFloatWindow.Closing += HeuristicsFloatWindow_Closing; _heuristicsFloatWindow.Closed += HeuristicsFloatWindow_Closed; _heuristicsFloatWindow.Show(); }), System.Windows.Threading.DispatcherPriority.Background); } private void RedockHeuristicsPane() { if (_heuristicsFloatWindow != null) { _heuristicsFloatWindow.Closing -= HeuristicsFloatWindow_Closing; _heuristicsFloatWindow.Closed -= HeuristicsFloatWindow_Closed; if (_heuristicsFloatWindow.Content == HeuristicsPaneHost) _heuristicsFloatWindow.Content = null; _heuristicsFloatWindow.Close(); _heuristicsFloatWindow = null; } _heuristicsPaneFloating = false; if (HeuristicsDockBorder.Child == null) HeuristicsDockBorder.Child = HeuristicsPaneHost; ApplyDockVisibilityFromExplorer(); } private void EventsFloatWindow_Closed(object? sender, EventArgs e) { _eventsFloatWindow = null; if (EventsDockBorder.Child == null) RedockEventsPane(); } private void EventsFloatWindow_Closing(object? sender, CancelEventArgs e) { bool explorerEnabled = FindExplorerItem("Events")?.IsEnabled ?? true; if (_isMainWindowShuttingDown || !explorerEnabled || !_eventsPaneVisible || sender is not EventsFloatWindow window || !ReferenceEquals(window.Content, EventsPaneHost)) { return; } e.Cancel = true; RedockEventsPane(); } private void PerformanceFloatWindow_Closed(object? sender, EventArgs e) { _performanceFloatWindow = null; if (PerformanceDockBorder.Child == null) RedockPerformancePane(); } private void PerformanceFloatWindow_Closing(object? sender, CancelEventArgs e) { bool explorerEnabled = FindExplorerItem("Performance")?.IsEnabled ?? true; if (_isMainWindowShuttingDown || !explorerEnabled || !_performancePaneVisible || sender is not PerformanceFloatWindow window || !ReferenceEquals(window.Content, PerformancePaneHost)) { return; } e.Cancel = true; RedockPerformancePane(); } private void EtwFloatWindow_Closed(object? sender, EventArgs e) { _etwFloatWindow = null; if (EtwDockBorder.Child == null) RedockEtwPane(); } private void EtwFloatWindow_Closing(object? sender, CancelEventArgs e) { bool explorerEnabled = FindExplorerItem("ETW")?.IsEnabled ?? true; if (_isMainWindowShuttingDown || !explorerEnabled || sender is not EtwFloatWindow window || !ReferenceEquals(window.Content, EtwPaneHost)) { return; } e.Cancel = true; RedockEtwPane(); } private void HeuristicsFloatWindow_Closed(object? sender, EventArgs e) { _heuristicsFloatWindow = null; if (HeuristicsDockBorder.Child == null) RedockHeuristicsPane(); } private void HeuristicsFloatWindow_Closing(object? sender, CancelEventArgs e) { bool explorerEnabled = FindExplorerItem("Heuristics")?.IsEnabled ?? true; if (_isMainWindowShuttingDown || !explorerEnabled || sender is not HeuristicsFloatWindow window || !ReferenceEquals(window.Content, HeuristicsPaneHost)) { return; } e.Cancel = true; RedockHeuristicsPane(); } private void ApiViewDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) { _ = sender; _ = e; UpdateApiViewSelection(ApiViewDataGrid?.SelectedItem as ApiCallGraphMainRowView); RefreshApiGraphSelectionVisual(); } private void ApiViewTextFilter_Changed(object sender, TextChangedEventArgs e) { _ = sender; _ = e; RefreshApiViewPresentation(); } private void ApiViewSelectionFilter_Changed(object sender, SelectionChangedEventArgs e) { _ = sender; _ = e; RefreshApiViewPresentation(); } private void ApiViewClearFilters_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; if (ApiFilterCallBox != null) ApiFilterCallBox.Text = string.Empty; if (ApiFilterActionBox != null) ApiFilterActionBox.Text = string.Empty; if (ApiFilterCallerBox != null) ApiFilterCallerBox.Text = string.Empty; if (ApiFilterTargetBox != null) ApiFilterTargetBox.Text = string.Empty; if (ApiFilterThreadBox != null) ApiFilterThreadBox.Text = string.Empty; if (ApiFilterRegionBox != null) ApiFilterRegionBox.Text = string.Empty; if (ApiFilterProtectBox != null) ApiFilterProtectBox.Text = string.Empty; if (ApiFilterMinHitsBox != null) ApiFilterMinHitsBox.Text = string.Empty; if (ApiFilterSensorBox != null) ApiFilterSensorBox.SelectedIndex = 0; if (ApiFilterOriginBox != null) ApiFilterOriginBox.SelectedIndex = 0; RefreshApiViewPresentation(); } private void UpdateApiViewSelection(ApiCallGraphMainRowView? selected) { if (ApiViewSelectedTitleBlock == null || ApiViewSelectedMetaBlock == null || ApiViewSelectedActionValue == null || ApiViewSelectedSensorValue == null || ApiViewSelectedOriginValue == null || ApiViewSelectedFramesValue == null || ApiViewSelectedSourceValue == null || ApiViewSelectedTargetValue == null || ApiViewSelectedThreadValue == null || ApiViewSelectedHitsValue == null || ApiViewSelectedField1Label == null || ApiViewSelectedField2Label == null || ApiViewSelectedField3Label == null || ApiViewSelectedField4Label == null || ApiViewSelectedBaseValue == null || ApiViewSelectedSizeValue == null || ApiViewSelectedAllocTypeValue == null || ApiViewSelectedProtectValue == null || ApiViewSelectedDetailValue == null) { return; } if (selected == null) { ApiViewSelectedTitleBlock.Text = "No selection"; ApiViewSelectedMetaBlock.Text = "Select a row to inspect decoded details"; ApiViewSelectedActionValue.Text = string.Empty; ApiViewSelectedSensorValue.Text = string.Empty; ApiViewSelectedOriginValue.Text = string.Empty; ApiViewSelectedFramesValue.Text = string.Empty; ApiViewSelectedSourceValue.Text = string.Empty; ApiViewSelectedTargetValue.Text = string.Empty; ApiViewSelectedThreadValue.Text = string.Empty; ApiViewSelectedHitsValue.Text = string.Empty; ApiViewSelectedField1Label.Text = "Base"; ApiViewSelectedField2Label.Text = "Size"; ApiViewSelectedField3Label.Text = "Alloc Type"; ApiViewSelectedField4Label.Text = "Protect"; ApiViewSelectedBaseValue.Text = string.Empty; ApiViewSelectedSizeValue.Text = string.Empty; ApiViewSelectedAllocTypeValue.Text = string.Empty; ApiViewSelectedProtectValue.Text = string.Empty; ApiViewSelectedDetailValue.Text = string.Empty; return; } ApiViewSelectedTitleBlock.Text = selected.ApiName; ApiViewSelectedMetaBlock.Text = $"Caller {selected.SourceLabel} | Target {selected.TargetLabel} | Thread {selected.ThreadLabel} | Hits {selected.Hits} | Last Seen {selected.LastSeen}" + (string.IsNullOrWhiteSpace(selected.AbsoluteLastSeen) ? string.Empty : $" ({selected.AbsoluteLastSeen})"); ApiViewSelectedActionValue.Text = selected.ActionLabel; ApiViewSelectedSensorValue.Text = selected.SensorLabel; ApiViewSelectedOriginValue.Text = selected.CallerOriginLabel; ApiViewSelectedFramesValue.Text = selected.CallChainLabel; ApiViewSelectedSourceValue.Text = selected.SourceLabel; ApiViewSelectedTargetValue.Text = selected.TargetLabel; ApiViewSelectedThreadValue.Text = selected.ThreadLabel; ApiViewSelectedHitsValue.Text = selected.Hits.ToString(); ApiViewSelectedField1Label.Text = selected.Field1Label; ApiViewSelectedField2Label.Text = selected.Field2Label; ApiViewSelectedField3Label.Text = selected.Field3Label; ApiViewSelectedField4Label.Text = selected.Field4Label; ApiViewSelectedBaseValue.Text = selected.BaseLabel; ApiViewSelectedSizeValue.Text = selected.SizeLabel; ApiViewSelectedAllocTypeValue.Text = selected.AllocTypeLabel; ApiViewSelectedProtectValue.Text = selected.ProtectLabel; ApiViewSelectedDetailValue.Text = selected.DetailFull; } private void TogglePaneOrder() { _performanceOnTop = !_performanceOnTop; ApplyPaneOrder(); } private void ToggleIntelPaneOrder() { _heuristicsOnTop = !_heuristicsOnTop; ApplyIntelPaneOrder(); } private void ApplyPaneOrder() { if (_performanceOnTop) { Grid.SetRow(PerformancePaneRow, 0); Grid.SetRow(EventsPaneRow, 2); PerformancePaneRow.Margin = new Thickness(0, 0, 0, 3); EventsPaneRow.Margin = new Thickness(0, 3, 0, 0); } else { Grid.SetRow(EventsPaneRow, 0); Grid.SetRow(PerformancePaneRow, 2); EventsPaneRow.Margin = new Thickness(0, 0, 0, 3); PerformancePaneRow.Margin = new Thickness(0, 3, 0, 0); } } private void ApplyIntelPaneOrder() { if (_heuristicsOnTop) { Grid.SetRow(HeuristicsDockBorder, 0); Grid.SetRow(EtwDockBorder, 2); HeuristicsDockBorder.Margin = new Thickness(0, 0, 0, 3); EtwDockBorder.Margin = new Thickness(0, 3, 0, 0); } else { Grid.SetRow(EtwDockBorder, 0); Grid.SetRow(HeuristicsDockBorder, 2); EtwDockBorder.Margin = new Thickness(0, 0, 0, 3); HeuristicsDockBorder.Margin = new Thickness(0, 3, 0, 0); } ApplyDockVisibilityFromExplorer(); } private void BeginPaneHeaderDrag(bool isEventsPane, Point screenPosition) { if (isEventsPane) { if (_eventsFloatWindow == null) ToggleFloatDock(); if (_eventsFloatWindow == null) return; _draggingEventsPaneHeader = true; _draggingPerformancePaneHeader = false; _floatingPaneDragOffset = new Vector( screenPosition.X - _eventsFloatWindow.Left, screenPosition.Y - _eventsFloatWindow.Top); } else { if (_performanceFloatWindow == null) TogglePerformanceFloatDock(); if (_performanceFloatWindow == null) return; _draggingPerformancePaneHeader = true; _draggingEventsPaneHeader = false; _floatingPaneDragOffset = new Vector( screenPosition.X - _performanceFloatWindow.Left, screenPosition.Y - _performanceFloatWindow.Top); } ContinuePaneHeaderDrag(isEventsPane, screenPosition); } private void ContinuePaneHeaderDrag(bool isEventsPane, Point screenPosition) { if (isEventsPane && !_draggingEventsPaneHeader) return; if (!isEventsPane && !_draggingPerformancePaneHeader) return; var floatingWindow = isEventsPane ? (Window?)_eventsFloatWindow : _performanceFloatWindow; if (floatingWindow == null) return; floatingWindow.Left = screenPosition.X - _floatingPaneDragOffset.X; floatingWindow.Top = screenPosition.Y - _floatingPaneDragOffset.Y; UpdateDockPreview(screenPosition); } private void EndPaneHeaderDrag(bool isEventsPane, Point screenPosition) { if (isEventsPane) _draggingEventsPaneHeader = false; else _draggingPerformancePaneHeader = false; var slot = GetDockDropSlot(screenPosition); HideDockPreview(); if (slot == DockDropSlot.None) return; if (isEventsPane) { _eventsFloatWindow?.Close(); _performanceOnTop = slot == DockDropSlot.Bottom; } else { _performanceFloatWindow?.Close(); _performanceOnTop = slot == DockDropSlot.Top; } ApplyPaneOrder(); } private DockDropSlot GetDockDropSlot(Point screenPosition) { if (DockGrid.ActualWidth < 8 || DockGrid.ActualHeight < 8) return DockDropSlot.None; var dockTopLeft = DockGrid.PointToScreen(new Point(0, 0)); var dockRect = new Rect(dockTopLeft, new Size(DockGrid.ActualWidth, DockGrid.ActualHeight)); if (!dockRect.Contains(screenPosition)) return DockDropSlot.None; double relativeY = screenPosition.Y - dockRect.Top; return relativeY <= dockRect.Height * 0.5 ? DockDropSlot.Top : DockDropSlot.Bottom; } private void UpdateDockPreview(Point screenPosition) { var slot = GetDockDropSlot(screenPosition); DockPreviewTop.Visibility = slot == DockDropSlot.Top ? Visibility.Visible : Visibility.Collapsed; DockPreviewBottom.Visibility = slot == DockDropSlot.Bottom ? Visibility.Visible : Visibility.Collapsed; } private void HideDockPreview() { DockPreviewTop.Visibility = Visibility.Collapsed; DockPreviewBottom.Visibility = Visibility.Collapsed; } private enum DockDropSlot { None, Top, Bottom } private readonly struct EventSelectionKey : IEquatable { public EventSelectionKey(TelemetryEvent ev) { TimestampUtc = ev.TimestampUtc; Pid = ev.PID; Tid = ev.TID; Group = ev.Group ?? string.Empty; SubType = ev.SubType ?? string.Empty; Summary = ev.Summary ?? string.Empty; Details = ev.Details ?? string.Empty; } private DateTime TimestampUtc { get; } private int Pid { get; } private int Tid { get; } private string Group { get; } private string SubType { get; } private string Summary { get; } private string Details { get; } public bool Matches(TelemetryEvent ev) { return ev.TimestampUtc == TimestampUtc && ev.PID == Pid && ev.TID == Tid && string.Equals(ev.Group ?? string.Empty, Group, StringComparison.OrdinalIgnoreCase) && string.Equals(ev.SubType ?? string.Empty, SubType, StringComparison.OrdinalIgnoreCase) && string.Equals(ev.Summary ?? string.Empty, Summary, StringComparison.Ordinal) && string.Equals(ev.Details ?? string.Empty, Details, StringComparison.Ordinal); } public bool Equals(EventSelectionKey other) { return TimestampUtc == other.TimestampUtc && Pid == other.Pid && Tid == other.Tid && string.Equals(Group, other.Group, StringComparison.OrdinalIgnoreCase) && string.Equals(SubType, other.SubType, StringComparison.OrdinalIgnoreCase) && string.Equals(Summary, other.Summary, StringComparison.Ordinal) && string.Equals(Details, other.Details, StringComparison.Ordinal); } public override bool Equals(object? obj) => obj is EventSelectionKey other && Equals(other); public override int GetHashCode() { HashCode hash = new(); hash.Add(TimestampUtc); hash.Add(Pid); hash.Add(Tid); hash.Add(Group, StringComparer.OrdinalIgnoreCase); hash.Add(SubType, StringComparer.OrdinalIgnoreCase); hash.Add(Summary, StringComparer.Ordinal); hash.Add(Details, StringComparer.Ordinal); return hash.ToHashCode(); } } private static TelemetryEvent CloneTelemetryEvent(TelemetryEvent src) { return new TelemetryEvent { TimestampUtc = src.TimestampUtc, PID = src.PID, TID = src.TID, Group = src.Group, SubType = src.SubType, ProcessName = src.ProcessName, Summary = src.Summary, Details = src.Details }; } private static PerformanceSample ClonePerformanceSample(PerformanceSample src) { return new PerformanceSample { TimestampUtc = src.TimestampUtc, CoreCount = src.CoreCount, CpuPercent = src.CpuPercent, CoresUsedPercent = src.CoresUsedPercent, DiskReadBytesPerSec = src.DiskReadBytesPerSec, DiskWriteBytesPerSec = src.DiskWriteBytesPerSec, PrivateBytes = src.PrivateBytes, ReservedBytes = src.ReservedBytes, NetInBytesPerSec = src.NetInBytesPerSec, NetOutBytesPerSec = src.NetOutBytesPerSec, NetPacketsPerSec = src.NetPacketsPerSec, TopThreads = src.TopThreads.Select(t => new ThreadUsageSample { Tid = t.Tid, CpuMsDelta = t.CpuMsDelta, State = t.State, WaitReason = t.WaitReason, Kind = t.Kind, StartTimeUtc = t.StartTimeUtc }).ToList(), MemoryMetrics = src.MemoryMetrics.Select(m => new MemoryMetricSample { Metric = m.Metric, Value = m.Value, BytesValue = m.BytesValue }).ToList(), MemoryPages = src.MemoryPages.Select(m => new MemoryPageSample { BaseAddress = m.BaseAddress, RegionSize = m.RegionSize, State = m.State, Protect = m.Protect, Type = m.Type, StateLabel = m.StateLabel, ProtectLabel = m.ProtectLabel, TypeLabel = m.TypeLabel, Category = m.Category }).ToList() }; } private static ThreadLifecycleEventSample CloneThreadLifecycleEvent(ThreadLifecycleEventSample src) { return new ThreadLifecycleEventSample { TimestampUtc = src.TimestampUtc, ProcessPid = src.ProcessPid, ThreadId = src.ThreadId, CreatorPid = src.CreatorPid, Flags = src.Flags, StartAddress = src.StartAddress, ImageBase = src.ImageBase, ImageSize = src.ImageSize, EventKind = src.EventKind, Notes = src.Notes }; } private sealed class ApiCallGraphMainRowView { public string GraphKey { get; set; } = string.Empty; public string ApiName { get; set; } = string.Empty; public string ActionLabel { get; set; } = string.Empty; public string SensorLabel { get; set; } = string.Empty; public string CallerOriginKey { get; set; } = string.Empty; public string CallerOriginLabel { get; set; } = string.Empty; public string CallChainLabel { get; set; } = string.Empty; public Brush? CallerOriginBackground { get; set; } public Brush? CallerOriginForeground { get; set; } public Brush? SensorBackground { get; set; } public Brush? SensorForeground { get; set; } public Brush? HeatTrackBackground { get; set; } public Brush? HeatFillBackground { get; set; } public Brush? RowBackground { get; set; } public Brush? RowBorderBrush { get; set; } public string SourceLabel { get; set; } = string.Empty; public string TargetLabel { get; set; } = string.Empty; public string ThreadLabel { get; set; } = string.Empty; public string Field1Label { get; set; } = "Base"; public string Field2Label { get; set; } = "Size"; public string Field3Label { get; set; } = "Alloc Type"; public string Field4Label { get; set; } = "Protect"; public string BaseLabel { get; set; } = string.Empty; public string SizeLabel { get; set; } = string.Empty; public string AllocTypeLabel { get; set; } = string.Empty; public string ProtectLabel { get; set; } = string.Empty; public string LastSeen { get; set; } = string.Empty; public string AbsoluteLastSeen { get; set; } = string.Empty; public DateTime LastSeenUtc { get; set; } public int Hits { get; set; } public double HeatPercent { get; set; } public double ActivityFillWidth { get; set; } public string DetailFull { get; set; } = string.Empty; } // Lane focus API used by LaneSettingsWindow public void SetLaneFocus(string? laneKey) { _laneFocusKey = string.IsNullOrWhiteSpace(laneKey) ? null : laneKey; FocusViewport(); } public string? GetLaneFocus() => _laneFocusKey; } public sealed class PausedTimelineSpan { public DateTime StartUtc { get; set; } public DateTime EndUtc { get; set; } } public sealed class ProcessSessionTab : INotifyPropertyChanged { private int _pid; private string _title = ""; public List Events { get; } = new(); public List PerformanceHistory { get; } = new(); public List ThreadLifecycleHistory { get; } = new(); public List ThreadStackHistories { get; } = new(); internal HashSet ChildProcessExpandedKeys { get; } = new(StringComparer.Ordinal); public DateTime CaptureStartUtc { get; set; } = DateTime.UtcNow; public double ViewDurationSeconds { get; set; } = 120; public double ViewStartSeconds { get; set; } public string? LaneFocusKey { get; set; } public bool UseUsermodeHooks { get; set; } public bool AutoOpenApiGraphOnNextStart { get; set; } public bool LaunchStartsSuspendedPending { get; set; } public bool LaunchOwnedByInterface { get; set; } public bool TargetExited { get; set; } public bool OfflineSnapshot { get; set; } public string? BackingStorePath { get; set; } public List PausedTimelineSpans { get; } = new(); public DateTime? ActivePauseStartUtc { get; set; } public int Pid { get => _pid; set { if (_pid == value) return; _pid = value; OnPropertyChanged(); } } public string Title { get => _title; set { if (_title == value) return; _title = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged([CallerMemberName] string? prop = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); } }