using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; 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 const int MaxControllerLaunchArgumentChars = 2048; private const int HookControllerCallTimeoutSeconds = 30; private static readonly TimeSpan HookControllerCallTimeout = TimeSpan.FromSeconds(HookControllerCallTimeoutSeconds); 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; private string? _laneFocusKey; private PerformanceSampler? _perf; 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 _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 _hookControllerCallInFlight; private AnalysisOperationGuard? _activeAnalysisOperation; private LiveAnalysisSessionLease? _liveAnalysisSession; private ProcessSessionTab? _queuedSessionSwitchTab; private bool _sessionSwitchQueued; private LaunchTargetKind _pendingAnalysisSubjectKind = LaunchTargetKind.Executable; private string _pendingAnalysisSubjectPath = string.Empty; private string _pendingAnalysisHostPath = string.Empty; private bool _isMainWindowShuttingDown; private BlackbirdBackendSession? _preparedLaunchBackendSession; private int _preparedLaunchBackendPid; private MainInterfaceViewMode _mainViewMode = MainInterfaceViewMode.Telemetry; private readonly BulkObservableCollection _apiViewRows = new(); private readonly BulkObservableCollection _extendedViewRows = new(); private readonly BulkObservableCollection _extendedComRows = new(); private readonly BulkObservableCollection _extendedEtwRows = new(); private readonly BulkObservableCollection _extendedJobRows = new(); private readonly BulkObservableCollection _extendedYaraRows = 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 _extendedViewRefreshTimer; 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 bool _signatureIntelEnabled = true; private bool _signatureIntelMemoryScanEnabled; private bool _signatureIntelPageScanEnabled; private readonly List _apiViewSnapshotRows = new(); private readonly List _apiGraphSnapshotRows = new(); private ApiViewPresentationMode _apiViewPresentationMode; 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, Extended = 2 } private enum ApiViewPresentationMode { CallGraph = 0, ThreadTimeline = 1 } public MainWindow() { InitializeComponent(); if (Kernel32Native.EnableDebugPrivilege(out int debugPrivilegeError)) { DiagnosticsState.SetValue("Target Handle Access", "SeDebugPrivilege enabled"); } else { DiagnosticsState.SetValue("Target Handle Access", $"SeDebugPrivilege unavailable win32={debugPrivilegeError}"); } if (ApiViewModeBox != null) { ApiViewModeBox.SelectedIndex = 0; } InitializeSignatureIntelSubsystem(); InitializeInterfaceSettings(); SetResourceReference(BackgroundProperty, "WinBgBrush"); RootGrid.SetResourceReference(Panel.BackgroundProperty, "WinBgBrush"); WindowThemeHelper.WireThemeAwareTitleBar(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; _extendedViewRefreshTimer = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromMilliseconds(220) }; _extendedViewRefreshTimer.Tick += (_, __) => FlushExtendedActivitySnapshot(); _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; EventsPaneHost.Grid.ItemsSource = _focusedEvents; EventsPaneHost.SetHasData(false); EventsPaneHost.SetEventLogDetached(false); EventsPaneHost.Timeline.CaptureStartUtc = _captureStartUtc; EventsPaneHost.Timeline.ViewDurationSeconds = DefaultTimelineViewDurationSeconds; EventsPaneHost.Timeline.ViewStartSeconds = 0; EventsPaneHost.Timeline.LaneInteraction += Timeline_LaneInteraction; EventsPaneHost.Timeline.SelectedEventChanged += Timeline_SelectedEventChanged; EventsPaneHost.Timeline.EventDoubleClicked += Timeline_EventDoubleClicked; EventsPaneHost.Grid.SelectionChanged += Grid_SelectionChanged; EventsPaneHost.Scroll.ValueChanged += Scroll_ValueChanged; 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.DisassemblyRequested += PerformancePaneHost_DisassemblyRequested; 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(); RegistryPaneHost.CloseRequested += (_, __) => HideRegistryPane(); RegistryPaneHost.InspectRequested += (_, __) => OpenRegistryInspector(); ProcessRelationsPaneHost.CloseRequested += (_, __) => HideProcessRelationsPane(); ProcessRelationsPaneHost.InspectRequested += (_, __) => OpenProcessRelationsInspector(); ProcessRelationsPaneHost.GraphStateChanged += ProcessRelationsPaneHost_GraphStateChanged; SetupExplorer(); SetupProcessTabs(); InitializeBackendUi(); InitializeRuntimeConfigDefaults(); ApiViewDataGrid.ItemsSource = _apiViewRows; if (ExtendedViewDataGrid != null) { ExtendedViewDataGrid.ItemsSource = _extendedViewRows; } if (ExtendedComDataGrid != null) { ExtendedComDataGrid.ItemsSource = _extendedComRows; } if (ExtendedEtwDataGrid != null) { ExtendedEtwDataGrid.ItemsSource = _extendedEtwRows; } if (ExtendedJobDataGrid != null) { ExtendedJobDataGrid.ItemsSource = _extendedJobRows; } if (ExtendedYaraDataGrid != null) { ExtendedYaraDataGrid.ItemsSource = _extendedYaraRows; } ApiViewDataGrid.SelectedIndex = -1; UpdateApiViewSelection(null); SetMainInterfaceViewMode(MainInterfaceViewMode.Telemetry); ApplyUplinkStatusVisual(healthy: null); UpdateScrollBar(); FocusViewport(); UpdateTopTimeTravelBar(); RebuildToolbarViewMenuOptions(); _perf = new PerformanceSampler(); _perf.SampleArrived += Perf_SampleArrived; 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; return; } _isMainWindowShuttingDown = true; CancelActiveAnalysisOperationForShutdown(); } private void OnClosed(object? sender, EventArgs e) { _isMainWindowShuttingDown = true; CancelActiveAnalysisOperationForShutdown(); DisposeSignatureIntelSubsystem(); _detachedEventLogRefreshTimer.Stop(); _processStateRefreshTimer.Stop(); _apiGraphRefreshTimer.Stop(); _timelineLiveTickTimer.Stop(); 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); StopLiveAnalysisSession(LiveAnalysisStopReason.Shutdown, fastTeardown: true, stopPerformance: true); DisposePreparedLaunchBackendSession(); TerminateLaunchOwnedTargetsOnShutdown(); 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( "Registry", new SolidColorBrush(Color.FromRgb(0x7A, 0x5E, 0xA8))) { IsEnabled = true }); _explorer.Add(new GraphExplorerItem( "Process Relations", new SolidColorBrush(Color.FromRgb(0xD2, 0xB8, 0x55))) { IsEnabled = true }); 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); } private void ApplyDockVisibilityFromExplorer() { bool showEvents = _explorer.FirstOrDefault(x => x.Name == "Events")?.IsEnabled ?? true; bool showPerf = _explorer.FirstOrDefault(x => x.Name == "Performance")?.IsEnabled ?? true; 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 showRegistry = _explorer.FirstOrDefault(x => x.Name == "Registry")?.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 showRegistryContent = showRegistry; 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; RegistryDockBorder.Visibility = showRegistryContent ? Visibility.Visible : Visibility.Collapsed; ProcessRelationsDockBorder.Visibility = showRelationsContent ? 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 = showRegistryContent; bool row8Visible = 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[8].Height = row8Visible ? 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); IntelligenceDock.RowDefinitions[7].Height = (row6Visible && row8Visible) ? new GridLength(2) : new GridLength(0); bool showIntel = row0Visible || row2Visible || row4Visible || row6Visible || row8Visible; if (_mainViewMode != MainInterfaceViewMode.Telemetry) { 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) { _captureProjection?.ObservePerformance(e); _currentSession.PerformanceHistory.Add(ClonePerformanceSample(e)); if (_currentSession.PerformanceHistory.Count > 4000) _currentSession.PerformanceHistory.RemoveRange(0, _currentSession.PerformanceHistory.Count - 4000); } var evCount = _focusedEvents.Count; var eventsItem = _explorer.FirstOrDefault(x => x.Name == "Events"); eventsItem?.PushPreviewValue(evCount); var perfItem = _explorer.FirstOrDefault(x => x.Name == "Performance"); perfItem?.PushPreviewValue(e.CpuPercent); _hasPerformanceData = true; SetExplorerHasData("Performance", true); PerformancePaneHost.PushSample(e); SyncPerformanceViewToTimeline(); } private void StartLiveCaptureForPid(int pid, bool useUsermodeHooks, bool launchStartsSuspended = false, bool fastBackendRestart = 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); PerformancePaneHost.RefreshLiveProcessDetails(); StartTargetExitWatcher(pid); EnsureLiveCaptureStoreForCurrentSession(pid); StartBackendForPid(pid, useUsermodeHooks, fastStopExistingSession: fastBackendRestart); if (_backendSession != null) { TrackLiveAnalysisSession(pid, _currentSession); } bool autoOpenApiGraph = _currentSession?.AutoOpenApiGraphOnNextStart == true; if (_currentSession != null) { _currentSession.AutoOpenApiGraphOnNextStart = false; } SetMainInterfaceViewMode(useUsermodeHooks && autoOpenApiGraph ? MainInterfaceViewMode.Api : MainInterfaceViewMode.Telemetry); 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, BuildProcessExitReason(_targetExitWatchProcess)); } } 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; string exitReason = string.Empty; if (sender is Process p) { try { pid = p.Id; exitReason = BuildProcessExitReason(p); } catch { pid = 0; } } if (pid <= 0) pid = _targetExitWatchPid; Dispatcher.BeginInvoke(new Action(() => HandleTargetProcessExit(pid, exitReason))); } private static string BuildProcessExitReason(Process process) { try { return $"process exited exitCode=0x{unchecked((uint)process.ExitCode):X8}"; } catch { return "process exited; exit code unavailable"; } } private void HandleTargetProcessExit(int pid, string? observedReason = null) { if (pid <= 0) return; ProcessSessionTab? exitedTab = _processTabs.FirstOrDefault(x => x.Pid == pid); if (exitedTab == null || exitedTab.TargetExited) return; exitedTab.TargetExited = true; _targetExitReasonByPid.TryGetValue(unchecked((uint)pid), out string? remembered); string exitReason = !string.IsNullOrWhiteSpace(remembered) ? remembered : (!string.IsNullOrWhiteSpace(observedReason) ? observedReason.Trim() : "process exited; cause not observed before teardown"); exitedTab.TargetExitReason = exitReason; 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 = $"{exitReason}. Data capture stopped for this tab." }); StopLiveAnalysisSession(LiveAnalysisStopReason.TargetExited, preserveApiGraphSnapshot: true, fastTeardown: true, stopPerformance: true); if (exitedTab.ActivePauseStartUtc.HasValue) { exitedTab.PausedTimelineSpans.Add( new PausedTimelineSpan { StartUtc = exitedTab.ActivePauseStartUtc.Value, EndUtc = DateTime.UtcNow }); exitedTab.ActivePauseStartUtc = null; } SyncCurrentSessionStateToMemory(); _ = TryPersistCurrentSessionAfterStop(); StatusBlock.Text = $"Capture stopped; target exited: {exitReason}"; RefreshProcessStateBadge(); ThemedMessageBox.ShowToast(this, $"Target process {pid} exited: {exitReason}", "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) { if (ex.NativeErrorCode != 5) { failure = $"FAILED TO OPEN PID {pid} (WIN32 {ex.NativeErrorCode})"; return false; } processName = $"PID {pid}"; } finally { try { process?.Dispose(); } catch { } } IntPtr handle = Kernel32Native.OpenProcess(ProcessQueryLimitedInformation | 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} (Silo)"; } catch { return $"PID {pid} (Silo)"; } } private void FlashSwitchToast(string label) { SwitchToastText.Text = label; SwitchToastBorder.Visibility = Visibility.Visible; var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(120)); var fadeOut = new DoubleAnimation( 1, 0, TimeSpan.FromMilliseconds(300)) { BeginTime = TimeSpan.FromMilliseconds(900) }; fadeOut.Completed += (_, __) => SwitchToastBorder.Visibility = Visibility.Collapsed; var storyboard = new Storyboard(); Storyboard.SetTarget(fadeIn, SwitchToastBorder); Storyboard.SetTargetProperty(fadeIn, new PropertyPath(OpacityProperty)); Storyboard.SetTarget(fadeOut, SwitchToastBorder); Storyboard.SetTargetProperty(fadeOut, new PropertyPath(OpacityProperty)); storyboard.Children.Add(fadeIn); storyboard.Children.Add(fadeOut); storyboard.Begin(); } 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, KernelHooksEnabled = _kernelHooksArmed, SignatureIntelEnabled = _signatureIntelEnabled, SignatureIntelMemoryScanEnabled = _signatureIntelMemoryScanEnabled, SignatureIntelPageScanEnabled = _signatureIntelPageScanEnabled }; _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 SyncCurrentSessionViewportStateToMemory() { if (_currentSession == null) return; _currentSession.CaptureStartUtc = _captureStartUtc; _currentSession.LaneFocusKey = _laneFocusKey; _currentSession.ViewDurationSeconds = EventsPaneHost.Timeline.ViewDurationSeconds; _currentSession.ViewStartSeconds = EventsPaneHost.Timeline.ViewStartSeconds; SaveIntelSessionState(_currentSession.Pid); } private void SyncCurrentSessionStateToMemory() { if (_currentSession == null) return; SyncCurrentSessionViewportStateToMemory(); if (!CurrentSessionEventMirrorLooksCurrent()) { _currentSession.Events.Clear(); _currentSession.Events.AddRange(_allEvents.Select(CloneTelemetryEvent)); } } private bool CurrentSessionEventMirrorLooksCurrent() { if (_currentSession == null || _currentSession.Events.Count != _allEvents.Count) { return false; } if (_allEvents.Count == 0) { return true; } TelemetryEvent mirrored = _currentSession.Events[^1]; TelemetryEvent live = _allEvents[^1]; return mirrored.TimestampUtc == live.TimestampUtc && mirrored.PID == live.PID && mirrored.TID == live.TID && string.Equals(mirrored.Group, live.Group, StringComparison.Ordinal) && string.Equals(mirrored.SubType, live.SubType, StringComparison.Ordinal); } 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(); IEnumerable restoreEvents = TelemetryEventsAreTimestampSorted(tab.Events) ? tab.Events : tab.Events.OrderBy(x => x.TimestampUtc); foreach (var ev in restoreEvents) { _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.SetAnalysisSubject(tab.AnalysisSubjectPath, tab.AnalysisHostPath); DiagnosticsState.SetValue("Analysis Subject", tab.AnalysisSubjectKind == LaunchTargetKind.Dll && !string.IsNullOrWhiteSpace(tab.AnalysisSubjectPath) ? tab.AnalysisSubjectPath : "Process image"); PerformancePaneHost.LoadHistory(tab.PerformanceHistory); PerformancePaneHost.LoadMemoryRegionAttributionHistory(tab.MemoryRegionAttributionHistory); PerformancePaneHost.LoadThreadLifecycleHistory(tab.ThreadLifecycleHistory); PerformancePaneHost.LoadThreadStackHistory(tab.ThreadStackHistories); 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 (ReferenceEquals(_currentSession, tab)) { return; } if (_currentSession != null && !ReferenceEquals(_currentSession, tab)) { SaveChildProcessGraphStateToSession(_currentSession); SyncCurrentSessionViewportStateToMemory(); FlashSwitchToast($"Switched to {tab.Title}"); } _currentSession = tab; PidBox.Text = tab.Pid.ToString(); RefreshSubsystemSegmentationDiagnostics(); RestoreChildProcessGraphStateFromSession(tab); RestoreSessionState(tab); if (tab.OfflineSnapshot) { StopLiveAnalysisSession(LiveAnalysisStopReason.SessionSwitch, preserveApiGraphSnapshot: true, fastTeardown: true, stopPerformance: true); StatusBlock.Text = $"OFFLINE SESSION: {tab.Title}"; RefreshProcessStateBadge(); return; } if (tab.TargetExited) { StopLiveAnalysisSession(LiveAnalysisStopReason.SessionSwitch, preserveApiGraphSnapshot: true, fastTeardown: true, stopPerformance: true); StatusBlock.Text = "Capture stopped; historical data remains available."; RefreshProcessStateBadge(); return; } bool allowPreparedProtectedAttach = _preparedLaunchBackendSession != null && _preparedLaunchBackendPid == tab.Pid && tab.UseUsermodeHooks; if (!TryOpenTargetProcess(tab.Pid, out _, out var failure, out var accessDenied)) { if (!allowPreparedProtectedAttach) { StopLiveAnalysisSession(LiveAnalysisStopReason.SessionSwitch, preserveApiGraphSnapshot: true, fastTeardown: true, stopPerformance: true); if (!accessDenied) { tab.TargetExited = true; MarkSessionExited(tab); StatusBlock.Text = "Capture stopped; historical data remains available."; } else { StatusBlock.Text = $"Access denied to PID {tab.Pid}. Capture stopped."; } RefreshProcessStateBadge(); return; } OutputCapture.AppendLine( $"Protected launch session accepted without direct process-open access: PID {tab.Pid}"); } bool launchStartsSuspended = tab.LaunchStartsSuspendedPending; tab.LaunchStartsSuspendedPending = false; StartLiveCaptureForPid(tab.Pid, tab.UseUsermodeHooks, launchStartsSuspended, fastBackendRestart: true); } private static bool TelemetryEventsAreTimestampSorted(IReadOnlyList events) { for (int i = 1; i < events.Count; i += 1) { if (events[i].TimestampUtc < events[i - 1].TimestampUtc) { return false; } } return true; } 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(); } 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); _currentSession?.Events.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 {_currentSession.Pid} — STOPPED]"; } else { string captureTitle = NormalizeSessionTitle(_currentSession.Title ?? $"PID {_currentSession.Pid}"); sessionLabel = $"[CAPTURE {_currentSession.Pid} {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) { string reason = string.IsNullOrWhiteSpace(_currentSession.TargetExitReason) ? "capture available" : _currentSession.TargetExitReason; SetProcessStateVisual($"{labelPrefix} • Exited ({reason})", 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); RefreshHooksButtonState(); 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; } 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 = $"Window {viewStart:HH:mm:ss}-{viewEnd:HH:mm:ss} {durationSeconds:0}s"; if (_allEvents.Count == 0) { _focusedEvents.ReplaceAll(Array.Empty()); EventsPaneHost.Timeline.ReplaceItems(_focusedEvents); 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; 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; 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); } 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; } 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); } } 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 HideFilesystemPane() { var fs = FindExplorerItem("Filesystem"); if (fs != null) fs.IsEnabled = false; ApplyDockVisibilityFromExplorer(); } private void HideRegistryPane() { var reg = FindExplorerItem("Registry"); if (reg != null) reg.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 ShowFilesystemPane() { SetExplorerPaneEnabled("Filesystem", true); ApplyDockVisibilityFromExplorer(); } private void ShowRegistryPane() { SetExplorerPaneEnabled("Registry", 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; } if (ExtendedViewBorder != null) { ExtendedViewBorder.Visibility = mode == MainInterfaceViewMode.Extended ? Visibility.Visible : Visibility.Collapsed; } ApplyDockVisibilityFromExplorer(); UpdateMainViewButtons(mode); if (IsLoaded) { RebuildToolbarViewMenuOptions(); } } private void UpdateMainViewButtons(MainInterfaceViewMode mode) { SetMainViewButtonSelected(MainViewButton, mode == MainInterfaceViewMode.Telemetry); SetMainViewButtonSelected(ApiViewButton, mode == MainInterfaceViewMode.Api); SetMainViewButtonSelected(ExtendedViewButton, mode == MainInterfaceViewMode.Extended); } private static void SetMainViewButtonSelected(Button? button, bool selected) { if (button == null) { return; } button.IsEnabled = !selected; button.Opacity = selected ? 0.72 : 1.0; } private void MainView_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; SetMainInterfaceViewMode(MainInterfaceViewMode.Telemetry); } private void ApiView_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; SetMainInterfaceViewMode(MainInterfaceViewMode.Api); } private void ExtendedView_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; SetMainInterfaceViewMode(MainInterfaceViewMode.Extended); } private void OpenDiagnosticsWindow() { var w = new DiagnosticsWindow(TryGetPid()) { Owner = this }; w.Show(); } private void SignatureIntelRules_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; OpenSignatureIntelRulesWindow(); } 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 "registry": ShowRegistryPane(); break; case "process-relations": ShowProcessRelationsPane(); break; case "event-log": SetExplorerPaneEnabled("Events", true); ShowEventsPane(); OpenOrActivateEventLogWindow(); break; case "inspector-etw": ShowEtwPane(); OpenEtwInspector(); break; case "inspector-heuristics": ShowHeuristicsPane(); OpenHeuristicsInspector(); break; case "inspector-filesystem": ShowFilesystemPane(); OpenFilesystemInspector(); break; case "inspector-registry": ShowRegistryPane(); OpenRegistryInspector(); break; case "inspector-relations": ShowProcessRelationsPane(); OpenProcessRelationsInspector(); break; case "diagnostics": OpenDiagnosticsWindow(); break; case "signature-intel-rules": OpenSignatureIntelRulesWindow(); 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("Registry Pane", "registry", IsRegistryPaneOpen()); AddToolbarViewOptionIfClosed("Process Relations Pane", "process-relations", IsRelationsPaneOpen()); AddToolbarViewOptionIfClosed("Event Log Window", "event-log", _eventLogWindow != null && _eventLogWindow.IsVisible); AddToolbarViewOptionIfClosed("ETW Inspector", "inspector-etw", IsWindowOpenByTitle("ETW Inspector")); AddToolbarViewOptionIfClosed("Heuristics Inspector", "inspector-heuristics", IsWindowOpenByTitle("Detection Chain")); AddToolbarViewOptionIfClosed("Filesystem Inspector", "inspector-filesystem", IsWindowOpenByTitle("Filesystem")); AddToolbarViewOptionIfClosed("Registry Inspector", "inspector-registry", IsWindowOpenByTitle("Registry")); 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( "Rules Intel", "signature-intel-rules", Application.Current.Windows.OfType().Any(w => w is SignatureIntelRulesWindow && 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 IsRegistryPaneOpen() => (FindExplorerItem("Registry")?.IsEnabled ?? true); private bool IsRelationsPaneOpen() => (FindExplorerItem("Process Relations")?.IsEnabled ?? true); 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(); } 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 ExtendedActivityCopySummary_Click(object sender, RoutedEventArgs e) { if (TryGetSelectedExtendedActivityRow(sender, out ExtendedActivityRowSnapshot? row)) { Clipboard.SetText( $"{row.LastSeenUtc:O} {row.TypeLabel} {row.ActorLabel}->{row.TargetLabel} {row.SubjectLabel} {row.OperationLabel} hits={row.Hits}"); } } private void ExtendedActivityCopyDetails_Click(object sender, RoutedEventArgs e) { if (TryGetSelectedExtendedActivityRow(sender, out ExtendedActivityRowSnapshot? row)) { Clipboard.SetText(string.Join( Environment.NewLine, new[] { $"Type: {row.TypeLabel}", $"Actor: {row.ActorLabel}", $"Target: {row.TargetLabel}", $"Subject: {row.SubjectLabel}", $"Operation: {row.OperationLabel}", $"Last Seen: {row.LastSeenUtc:O}", $"Hits: {row.Hits}", $"Details: {row.DetailLabel}" })); } } private void ExtendedActivityGrid_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { DataGridRow? row = FindAncestor(e.OriginalSource as DependencyObject); if (row == null) { return; } row.IsSelected = true; row.Focus(); } private static bool TryGetSelectedExtendedActivityRow(object sender, [NotNullWhen(true)] out ExtendedActivityRowSnapshot? row) { row = null; if (sender is MenuItem { Parent : ContextMenu { PlacementTarget : DataGrid grid } } && grid.SelectedItem is ExtendedActivityRowSnapshot selected) { row = selected; return true; } return false; } private static T? FindAncestor(DependencyObject? source) where T : DependencyObject { while (source != null) { if (source is T match) { return match; } source = VisualTreeHelper.GetParent(source); } return null; } 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, threadStateProvider: tid => PerformancePaneHost.SnapshotTopThreads().FirstOrDefault(x => x.Tid == tid)?.State ?? row.State) { Owner = this }; w.Show(); } private void PerformancePaneHost_DisassemblyRequested(object? sender, MemoryDisassemblyRequestedEventArgs e) { if (_backendSession == null || !BkdcNative.IsAvailable) return; var w = new DisassemblyWindow(_backendSession, e.ProcessId, e.BaseAddress, e.RegionSize, e.Label, e.SnapshotBytes, e.SnapshotOffset) { Owner = this }; w.Show(); } private void OpenParallelStacksWindow() { int pid = _currentSession?.Pid ?? TryGetPid(); IReadOnlyList rows = PerformancePaneHost.SnapshotTopThreads(); if (pid <= 0 || rows.Count == 0) { string reason = PerformancePaneHost.IsTargetSuspended ? "The target process is launch-suspended — thread sampling has not run yet. Resume the process first, then open parallel stacks." : "No thread rows are available for stack comparison."; ThemedMessageBox.Show(this, reason, "Parallel Stacks", MessageBoxButton.OK, MessageBoxImage.Information); return; } var window = new ParallelStacksWindow( pid, rows, historyProvider: GetThreadStackHistory, observationTimeUtcProvider: GetCurrentObservedUtc, liveCaptureAvailableProvider: () => _currentSession != null && _currentSession.Pid == pid && !_currentSession.TargetExited && !_currentSession.OfflineSnapshot, threadSnapshotProvider: () => PerformancePaneHost.SnapshotTopThreads(), onSnapshotCaptured: (processId, tid, state, snapshot) => PersistThreadStackSnapshot(processId, tid, state, snapshot)) { 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; _queuedSessionSwitchTab = tab; if (_sessionSwitchQueued) { return; } _sessionSwitchQueued = true; Dispatcher.BeginInvoke(new Action( () => { _sessionSwitchQueued = false; ProcessSessionTab? pending = _queuedSessionSwitchTab; _queuedSessionSwitchTab = null; if (pending == null || !ReferenceEquals(ProcessTabs.SelectedItem, pending) || _isMainWindowShuttingDown) { return; } SwitchToSession(pending); RefreshProcessStateBadge(); }), DispatcherPriority.Background); } 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 static string ResolveDllHostPathFromInterfaceDirectory() { string baseDirectory = AppContext.BaseDirectory; if (string.IsNullOrWhiteSpace(baseDirectory)) { baseDirectory = Environment.CurrentDirectory; } return Path.Combine(baseDirectory, "BlackbirdDllHost.exe"); } private static string QuoteCommandLineArgument(string value) { if (string.IsNullOrEmpty(value)) { return "\"\""; } var builder = new System.Text.StringBuilder(); builder.Append('"'); int backslashes = 0; foreach (char ch in value) { if (ch == '\\') { backslashes += 1; continue; } if (ch == '"') { builder.Append('\\', backslashes * 2 + 1); builder.Append('"'); backslashes = 0; continue; } if (backslashes > 0) { builder.Append('\\', backslashes); backslashes = 0; } builder.Append(ch); } if (backslashes > 0) { builder.Append('\\', backslashes * 2); } builder.Append('"'); return builder.ToString(); } private static string BuildDllHostCommandLineArguments(LaunchProfile launchProfile) { var args = new List { "--dll", QuoteCommandLineArgument(launchProfile.AnalysisSubjectPath), "--mode", launchProfile.DllMode switch { DllLaunchMode.Export => "export", DllLaunchMode.Rundll => "rundll", DllLaunchMode.Register => "register", DllLaunchMode.Unregister => "unregister", DllLaunchMode.Install => "install", _ => "load" }, "--wait-ms", launchProfile.DllWaitMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture) }; if (launchProfile.HasDllExportName) { args.Add("--export"); args.Add(QuoteCommandLineArgument(launchProfile.DllExportName)); } if (launchProfile.HasDllExportOrdinal) { args.Add("--ordinal"); args.Add(launchProfile.DllExportOrdinal.ToString(System.Globalization.CultureInfo.InvariantCulture)); } if (launchProfile.HasDllArgument) { args.Add("--arg"); args.Add(QuoteCommandLineArgument(launchProfile.DllArgument)); } if (launchProfile.HasDllLoadFlags) { args.Add("--load-flags"); args.Add("0x" + launchProfile.DllLoadFlags.ToString("X", System.Globalization.CultureInfo.InvariantCulture)); } if (launchProfile.DllFreeOnExit) { args.Add("--free-on-exit"); } if (launchProfile.DllInstallDisable) { args.Add("--install-disable"); } return string.Join(" ", args); } private bool TrySendUserHookRequest(uint mode, uint processId, uint flags, string? imagePath, out BlackbirdNative.BkSetUserHookTargetResponse response, out string error) { response = default; error = string.Empty; if (!BkctlDeviceSession.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, BlackbirdNative.AnalysisSubjectKindProcess, null, hookPath, null, null, null, 0, 0, 0, false, BlackbirdNative.LaunchIntegrityDefault, out response)) { error = BkctlDeviceSession.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 EnsureNoHookControllerCallInFlight(string operation) { if (!_hookControllerCallInFlight) { return true; } string message = "A hook launch or attach request is still waiting for the controller to return. " + "Blackbird will not start another hook operation until that call unwinds."; StatusBlock.Text = "HOOK CONTROLLER REQUEST STILL IN PROGRESS"; OutputCapture.AppendLine($"Blocked overlapping hook operation ({operation}): previous request in-flight."); ThemedMessageBox.Show(this, message, "Hook Operation In Progress", MessageBoxButton.OK, MessageBoxImage.Warning); return false; } private AnalysisOperationGuard BeginAnalysisOperation(string operation) { var guard = new AnalysisOperationGuard(this, operation); _activeAnalysisOperation = guard; return guard; } private void CancelActiveAnalysisOperationForShutdown() { AnalysisOperationGuard? guard = _activeAnalysisOperation; if (guard != null) { guard.Cancel(); guard.CleanupAfterAbort(); } CompleteHookControllerCall(); _openingProcessPicker = false; _connectInProgress = false; } private void CleanupAbortedAnalysisOperation(AnalysisOperationGuard guard) { DisposePreparedLaunchBackendSession(); ClearPendingLaunchOptions(); if (guard.SessionStartAttempted || _isMainWindowShuttingDown) { StopLiveAnalysisSession(LiveAnalysisStopReason.OperationAbort, preserveApiGraphSnapshot: true, fastTeardown: true, stopPerformance: true); } if (guard.LaunchOwnedPid > 0) { if (TryTerminateTargetProcess(guard.LaunchOwnedPid, out string terminateError)) { AppendOutputFromAnyThread( $"Analysis operation cleanup terminated launch-owned target: PID {guard.LaunchOwnedPid}"); } else { AppendOutputFromAnyThread( $"Analysis operation cleanup could not terminate PID {guard.LaunchOwnedPid}: {terminateError}"); } } } private void CompleteHookControllerCall() { _hookControllerCallInFlight = false; } private void TrackLiveAnalysisSession(int pid, ProcessSessionTab? tab) { _liveAnalysisSession = new LiveAnalysisSessionLease(pid, tab, tab?.LaunchOwnedByInterface == true); } private void StopLiveAnalysisSession(LiveAnalysisStopReason reason, bool preserveApiGraphSnapshot = false, bool fastTeardown = false, bool stopPerformance = false) { LiveAnalysisSessionLease? lease = _liveAnalysisSession; if (lease != null && !lease.TryBeginTeardown()) { lease = null; } _liveAnalysisSession = null; StopTargetExitWatcher(); StopBackendSession(preserveApiGraphSnapshot, fastTeardown); if (stopPerformance) { _perf?.Stop(); _samplerPid = 0; if (PerformancePaneHost != null) { PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.SetTargetSuspended(false); } _targetExecutionSuspended = false; } string reasonText = reason switch { LiveAnalysisStopReason.Shutdown => "shutdown", LiveAnalysisStopReason.TargetExited => "target-exit", LiveAnalysisStopReason.OperationAbort => "operation-abort", LiveAnalysisStopReason.SessionSwitch => "session-switch", _ => "teardown" }; if (lease != null) { AppendOutputFromAnyThread( $"Live analysis session detached pid={lease.Pid} reason={reasonText} launchOwned={lease.LaunchOwnedByInterface}"); } } private enum LiveAnalysisStopReason { SessionSwitch, TargetExited, OperationAbort, Shutdown } private sealed class LiveAnalysisSessionLease { private int _teardownStarted; internal LiveAnalysisSessionLease(int pid, ProcessSessionTab? tab, bool launchOwnedByInterface) { Pid = pid; Tab = tab; LaunchOwnedByInterface = launchOwnedByInterface; } internal int Pid { get; } internal ProcessSessionTab? Tab { get; } internal bool LaunchOwnedByInterface { get; } internal bool TryBeginTeardown() { return Interlocked.Exchange(ref _teardownStarted, 1) == 0; } } private sealed class AnalysisOperationGuard : IDisposable { private readonly MainWindow _owner; private int _disposed; private int _cleanupStarted; private bool _completed; internal AnalysisOperationGuard(MainWindow owner, string operation) { _owner = owner; Operation = operation; Cancellation = new CancellationTokenSource(); } internal string Operation { get; } internal CancellationTokenSource Cancellation { get; } internal CancellationToken Token => Cancellation.Token; internal int LaunchOwnedPid { get; private set; } internal bool SessionStartAttempted { get; private set; } internal void TrackLaunchOwnedPid(int pid) { if (pid > 0) { LaunchOwnedPid = pid; } } internal void MarkSessionStartAttempted() { SessionStartAttempted = true; } internal void Complete() { _completed = true; } internal void Cancel() { try { Cancellation.Cancel(); } catch { } } internal void CleanupAfterAbort() { if (_completed || Interlocked.Exchange(ref _cleanupStarted, 1) != 0) { return; } _owner.CleanupAbortedAnalysisOperation(this); } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) != 0) { return; } if (ReferenceEquals(_owner._activeAnalysisOperation, this)) { _owner._activeAnalysisOperation = null; } CleanupAfterAbort(); Cancellation.Dispose(); } } private void AppendOutputFromAnyThread(string message) { if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) { return; } if (Dispatcher.CheckAccess()) { OutputCapture.AppendLine(message); return; } Dispatcher.BeginInvoke(new Action(() => OutputCapture.AppendLine(message)), DispatcherPriority.Background); } private void CompleteHookControllerCallFromWorker(Task task, Action? lateResultCleanup) { Exception? cleanupError = null; if (task.Status == TaskStatus.RanToCompletion) { try { lateResultCleanup?.Invoke(task.Result); } catch (Exception ex) { cleanupError = ex; } } else if (task.IsFaulted) { _ = task.Exception; } if (cleanupError != null) { AppendOutputFromAnyThread($"Late hook controller cleanup failed: {cleanupError.Message}"); } if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) { return; } Dispatcher.BeginInvoke(new Action(CompleteHookControllerCall), DispatcherPriority.Background); } private async Task<(bool Completed, T Result)> RunHookControllerCallWithTimeoutAsync( Func operation, LoadingWindow loading, string timeoutStatus, string timeoutDetail, Action? lateResultCleanup = null, CancellationToken cancellationToken = default) { _hookControllerCallInFlight = true; Task operationTask = Task.Run(operation); loading.StartTimeout(HookControllerCallTimeout); Task delayTask = Task.Delay(HookControllerCallTimeout, cancellationToken); Task completed = await Task.WhenAny(operationTask, delayTask); if (!ReferenceEquals(completed, operationTask)) { if (!cancellationToken.IsCancellationRequested && !_isMainWindowShuttingDown) { loading.SetTimedOut(timeoutStatus, timeoutDetail, HookControllerCallTimeout); OutputCapture.AppendLine( $"Hook controller request timed out after {HookControllerCallTimeoutSeconds}s; waiting for native IPC unwind."); } else { AppendOutputFromAnyThread("Hook controller request cancelled during interface teardown."); } _ = operationTask.ContinueWith(task => CompleteHookControllerCallFromWorker(task, lateResultCleanup), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); if (!cancellationToken.IsCancellationRequested && !_isMainWindowShuttingDown) { await Task.Delay(700); } return (false, default!); } try { T result = await operationTask; return (true, result); } finally { loading.StopTimeout(); CompleteHookControllerCall(); } } 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; } if (launchProfile.HasCommandLineArguments && launchProfile.CommandLineArguments.Length >= MaxControllerLaunchArgumentChars) { error = $"Launch arguments are too long for the controller IPC limit ({MaxControllerLaunchArgumentChars - 1} characters)."; return false; } uint flags = useEarlyBirdApc ? BlackbirdNative.IpcUserHookFlagLaunchEarlybirdApc : 0; if (launchProfile.LeaveSuspendedAfterReady) { flags |= BlackbirdNative.IpcUserHookFlagDeferredLaunchGateRelease; } try { if (!BkctlDeviceSession.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, launchProfile.HasAnalysisSubject ? BlackbirdNative.AnalysisSubjectKindDll : BlackbirdNative.AnalysisSubjectKindProcess, launchProfile.HasAnalysisSubject ? launchProfile.AnalysisSubjectPath : null, hookPath, launchProfile.HasWorkingDirectory ? launchProfile.WorkingDirectory : null, string.IsNullOrWhiteSpace(environmentOverrides) ? null : environmentOverrides, launchProfile.HasCommandLineArguments ? launchProfile.CommandLineArguments : null, launchProfile.ParentProcessId, MapLaunchPriorityClass(launchProfile.Priority), launchProfile.AffinityMask, launchProfile.InheritHandles, (uint)launchProfile.IntegrityLevel, out BlackbirdNative.BkSetUserHookTargetResponse response)) { error = BkctlDeviceSession.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(); OutputCapture.AppendLine( launchProfile.HasAnalysisSubject ? $"Hook launch via controller (earlybird-apc): host={imagePath} subject={launchProfile.AnalysisSubjectPath} \u2192 PID {pid} (pre-armed session)" : $"Hook launch via controller (earlybird-apc): {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; } } private async void Connect_Click(object sender, RoutedEventArgs e) { using AnalysisOperationGuard operation = BeginAnalysisOperation("attach/analyze"); await ConnectToCurrentPidAsync(operation.Token, operation); if (operation.SessionStartAttempted && !operation.Token.IsCancellationRequested && !_isMainWindowShuttingDown) { operation.Complete(); } } private async Task ConnectToCurrentPidAsync(CancellationToken cancellationToken, AnalysisOperationGuard? operationGuard = null) { if (_connectInProgress || cancellationToken.IsCancellationRequested || _isMainWindowShuttingDown) { return; } _connectInProgress = true; try { int pid = TryGetPid(); if (pid <= 0) { DisposePreparedLaunchBackendSession(); ClearPendingLaunchOptions(); StatusBlock.Text = "ENTER A VALID PID"; RefreshProcessStateBadge(); return; } bool hookPreconfigured = _pendingHookPreconfigured; bool allowPreparedLaunchAttach = hookPreconfigured && _preparedLaunchBackendSession != null && _preparedLaunchBackendPid == pid; if (_pendingLaunchOwnedByInterface) { operationGuard?.TrackLaunchOwnedPid(pid); } if (!TryOpenTargetProcess(pid, out var processName, out var failure, out var accessDenied)) { if (!allowPreparedLaunchAttach) { 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; } processName = $"PID {pid}"; StatusBlock.Text = $"CONNECTED TO PROTECTED TARGET ({pid})"; OutputCapture.AppendLine( $"Protected launch attach accepted via prepared controller session: PID {pid}"); } else { StatusBlock.Text = $"CONNECTED TO {processName} ({pid})"; } var tab = AddOrSelectProcessTab(pid, $"{processName} ({pid})", select: true); if (_pendingLaunchOptions) { tab.LaunchOwnedByInterface = _pendingLaunchOwnedByInterface; } if (_pendingAnalysisSubjectKind == LaunchTargetKind.Dll && !string.IsNullOrWhiteSpace(_pendingAnalysisSubjectPath)) { tab.AnalysisSubjectKind = LaunchTargetKind.Dll; tab.AnalysisSubjectPath = _pendingAnalysisSubjectPath; tab.AnalysisHostPath = _pendingAnalysisHostPath; string subjectName = Path.GetFileName(_pendingAnalysisSubjectPath); tab.Title = NormalizeSessionTitle( $"{(string.IsNullOrWhiteSpace(subjectName) ? "DLL" : subjectName)} via BlackbirdDllHost ({pid})"); DiagnosticsState.SetValue("Analysis Subject", tab.AnalysisSubjectPath); } bool launchStartsSuspended = _pendingLaunchStartsSuspended; bool leaveSuspendedAfterReady = _pendingLeaveSuspendedAfterReady; if (hookPreconfigured) { OutputCapture.AppendLine( $"Protected launch attach state: pid={pid} backendPrepared={allowPreparedLaunchAttach} leaveSuspended={leaveSuspendedAfterReady} uiStartsSuspended={launchStartsSuspended}"); } if (_pendingLaunchOptions) { tab.UseUsermodeHooks = _pendingUseUsermodeHooks; tab.AutoOpenApiGraphOnNextStart = _pendingAutoOpenApiGraph; } tab.LaunchStartsSuspendedPending = launchStartsSuspended; tab.DeferredLaunchGateResumePending = false; ClearPendingLaunchOptions(); if (tab.UseUsermodeHooks && !hookPreconfigured) { LoadingWindow? hookLoading = null; try { if (cancellationToken.IsCancellationRequested || _isMainWindowShuttingDown) { DisposePreparedLaunchBackendSession(); return; } if (!EnsureNoHookControllerCallInFlight("attach usermode hooks")) { DisposePreparedLaunchBackendSession(); return; } hookLoading = new LoadingWindow { Owner = this }; hookLoading.SetProgress(38, "Attaching usermode hooks...", $"Injecting SR71.dll into PID {pid}. UI timeout is {HookControllerCallTimeoutSeconds}s."); hookLoading.Show(); await Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Render); var hookWait = await RunHookControllerCallWithTimeoutAsync( () => { bool ok = TryAttachUsermodeHooks(pid, out string err); return (ok, err); }, hookLoading, "Hook attach timed out", $"The controller did not complete SR71 attach for PID {pid} within {HookControllerCallTimeoutSeconds}s.", cancellationToken: cancellationToken); if (!hookWait.Completed) { DisposePreparedLaunchBackendSession(); if (cancellationToken.IsCancellationRequested || _isMainWindowShuttingDown) { return; } StatusBlock.Text = $"HOOK ATTACH TIMED OUT FOR PID {pid}"; ThemedMessageBox.Show( this, $"Timed out attaching usermode hooks to PID {pid}. The controller IPC call is still being allowed to unwind, and overlapping hook operations are blocked until it returns.", "Hook Attach Timed Out", MessageBoxButton.OK, MessageBoxImage.Error); RefreshProcessStateBadge(); return; } var hookResult = hookWait.Result; 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; } } if (cancellationToken.IsCancellationRequested || _isMainWindowShuttingDown) { DisposePreparedLaunchBackendSession(); return; } tab.TargetExited = false; tab.TargetExitReason = string.Empty; tab.OfflineSnapshot = false; if (!ReferenceEquals(_currentSession, tab)) { operationGuard?.MarkSessionStartAttempted(); SwitchToSession(tab); } else { operationGuard?.MarkSessionStartAttempted(); StartLiveCaptureForPid(pid, tab.UseUsermodeHooks, launchStartsSuspended); } if (hookPreconfigured && !leaveSuspendedAfterReady) { OutputCapture.AppendLine($"Protected launch auto-resume requested: PID {pid}"); if (!TryControlTargetExecution(suspend: false, out string resumeError)) { OutputCapture.AppendLine($"Protected launch auto-resume failed: PID {pid} error={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}")}"; OutputCapture.AppendLine($"Protected launch auto-resume completed: PID {pid}"); } else if (launchStartsSuspended) { if (hookPreconfigured) { OutputCapture.AppendLine($"Protected launch left suspended by operator option: PID {pid}"); } _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(62, "Preparing process list...", "Enumerating launch targets before showing picker."); await Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Render); picker.PrimeForFirstShow(); loading.SetProgress(100, "Opening picker...", "Process picker is ready."); await Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Render); 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; LaunchTargetKind launchTargetKind = picker.LaunchSelectedImage ? picker.SelectedLaunchTargetKind : LaunchTargetKind.Executable; LaunchProfile launchProfile = new(); if (showLaunchOptions && (picker.LaunchSelectedImage || selectedPid > 0)) { var parametersWindow = new LaunchParametersWindow(isLaunchTarget: picker.LaunchSelectedImage, targetKind: launchTargetKind) { Owner = this }; bool? parametersAccepted = parametersWindow.ShowDialog(); if (parametersAccepted != true) { return; } useUsermodeHooks = parametersWindow.UseUsermodeHooks; autoOpenApiGraph = parametersWindow.AutoOpenApiGraphWindow; useEarlyBirdApcLaunch = parametersWindow.UseEarlyBirdApcLaunch; launchProfile = parametersWindow.LaunchProfile; launchProfile.TargetKind = launchTargetKind; } using AnalysisOperationGuard operation = BeginAnalysisOperation(showLaunchOptions && picker.LaunchSelectedImage ? "launch/analyze" : "attach/analyze"); if (showLaunchOptions && picker.LaunchSelectedImage) { string selectedImagePath = picker.LaunchImagePath?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(selectedImagePath)) { ThemedMessageBox.Show(this, "Launch path is empty.", "Launch failed", MessageBoxButton.OK, MessageBoxImage.Error); return; } string launchImagePath = selectedImagePath; if (launchTargetKind == LaunchTargetKind.Dll) { string dllHostPath = ResolveDllHostPathFromInterfaceDirectory(); if (!File.Exists(dllHostPath)) { ThemedMessageBox.Show( this, $"DLL launch failed because the DLL host is missing: '{dllHostPath}'.", "DLL launch failed", MessageBoxButton.OK, MessageBoxImage.Error); return; } launchProfile.TargetKind = LaunchTargetKind.Dll; launchProfile.AnalysisSubjectPath = selectedImagePath; launchProfile.AnalysisHostPath = dllHostPath; launchProfile.CommandLineArguments = BuildDllHostCommandLineArguments(launchProfile); if (!launchProfile.HasWorkingDirectory) { launchProfile.WorkingDirectory = Path.GetDirectoryName(selectedImagePath) ?? string.Empty; } launchImagePath = dllHostPath; useUsermodeHooks = true; useEarlyBirdApcLaunch = true; } if (useUsermodeHooks) { LoadingWindow? launchLoading = null; bool launchOk; int launchedPid = 0; BlackbirdBackendSession? preparedSession = null; string launchError = string.Empty; try { if (!EnsureNoHookControllerCallInFlight("launch target with hooks")) { return; } launchLoading = new LoadingWindow { Owner = this }; launchLoading.SetProgress(42, "Launching target with hooks...", "Submitting launch + SR71 staging request. UI timeout is " + HookControllerCallTimeoutSeconds + "s."); launchLoading.Show(); await Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Render); var launchWait = await RunHookControllerCallWithTimeoutAsync( () => { bool ok = TryLaunchWithUsermodeHooksAndPrepareSession( launchImagePath, useEarlyBirdApcLaunch, launchProfile, out int taskPid, out BlackbirdBackendSession? taskSession, out string taskError); return (ok, taskPid, taskSession, taskError); }, launchLoading, "Hook launch timed out", "The controller did not return from target launch and SR71 readiness " + $"within {HookControllerCallTimeoutSeconds}s.", lateResult => { lateResult.taskSession?.Dispose(); if (lateResult.taskPid > 0) { if (TryTerminateTargetProcess(lateResult.taskPid, out string terminateError)) { AppendOutputFromAnyThread( $"Late hook launch result was terminated fail-closed: PID {lateResult.taskPid}"); } else { AppendOutputFromAnyThread( $"Late hook launch result cleanup could not terminate PID {lateResult.taskPid}: {terminateError}"); } } }, cancellationToken: operation.Token); if (!launchWait.Completed) { launchOk = false; launchError = $"Controller hook launch did not return within {HookControllerCallTimeoutSeconds} seconds. " + "Blackbird has blocked overlapping hook launches until the native IPC call unwinds."; } else { launchOk = launchWait.Result.ok; launchedPid = launchWait.Result.taskPid; preparedSession = launchWait.Result.taskSession; launchError = launchWait.Result.taskError; if (launchedPid > 0) { operation.TrackLaunchOwnedPid(launchedPid); } } } finally { if (launchLoading != null && launchLoading.IsVisible) { launchLoading.Close(); } } if (!launchOk) { if (preparedSession != null) { try { preparedSession.Dispose(); } catch { } } if (operation.Token.IsCancellationRequested || _isMainWindowShuttingDown) { return; } ThemedMessageBox.Show(this, launchError, "Hook launch failed", MessageBoxButton.OK, MessageBoxImage.Error); return; } selectedPid = launchedPid; operation.TrackLaunchOwnedPid(selectedPid); _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; } operation.TrackLaunchOwnedPid(selectedPid); } } if (selectedPid <= 0) { return; } _pendingLaunchOptions = showLaunchOptions; _pendingUseUsermodeHooks = showLaunchOptions && useUsermodeHooks; _pendingAutoOpenApiGraph = showLaunchOptions && autoOpenApiGraph; _pendingHookPreconfigured = hookPreconfigured; _pendingLaunchStartsSuspended = showLaunchOptions && picker.LaunchSelectedImage && launchProfile.LeaveSuspendedAfterReady; _pendingLeaveSuspendedAfterReady = showLaunchOptions && picker.LaunchSelectedImage && launchProfile.LeaveSuspendedAfterReady; _pendingLaunchOwnedByInterface = showLaunchOptions && picker.LaunchSelectedImage; _pendingAnalysisSubjectKind = launchProfile.TargetKind; _pendingAnalysisSubjectPath = launchProfile.HasAnalysisSubject ? launchProfile.AnalysisSubjectPath : string.Empty; _pendingAnalysisHostPath = !string.IsNullOrWhiteSpace(launchProfile.AnalysisHostPath) ? launchProfile.AnalysisHostPath : (launchProfile.HasAnalysisSubject ? ResolveDllHostPathFromInterfaceDirectory() : string.Empty); PidBox.Text = selectedPid.ToString(); await ConnectToCurrentPidAsync(operation.Token, operation); if (operation.SessionStartAttempted && !operation.Token.IsCancellationRequested && !_isMainWindowShuttingDown) { operation.Complete(); } } 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; _pendingAnalysisSubjectKind = LaunchTargetKind.Executable; _pendingAnalysisSubjectPath = string.Empty; _pendingAnalysisHostPath = string.Empty; } 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 async Task TryOpenSessionFromStartupPathAsync(string path) { return await TryOpenSessionArchivePathAsync(path, merge: false); } 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.SetTargetSuspended(true); PerformancePaneHost.SetProcessLiveDataAvailable(false); PerformancePaneHost.RefreshLiveProcessDetails(); 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; } if (_currentSession != null) { _currentSession.DeferredLaunchGateResumePending = false; } _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.ResetBaselines(); _perf.Start(); _perf.RequestImmediateSample(); } PerformancePaneHost.SetProcessLiveDataAvailable(true); PerformancePaneHost.RefreshLiveProcessDetails(); SchedulePostResumeProcessRefresh(pid: _currentSession?.Pid ?? TryGetPid()); StatusBlock.Text = $"TARGET RESUMED: PID {TryGetPid()}"; RefreshProcessStateBadge(); RefreshToolbarCommandState(); } private void SchedulePostResumeProcessRefresh(int pid) { if (pid <= 0) { return; } _ = Task.Run(async () => { await Task.Delay(250).ConfigureAwait(false); await Dispatcher.InvokeAsync( () => { ProcessSessionTab? session = _currentSession; if (_targetExecutionSuspended || session == null || session.Pid != pid || session.TargetExited) { return; } _perf?.RequestImmediateSample(); PerformancePaneHost.SetProcessLiveDataAvailable(true); PerformancePaneHost.RefreshLiveProcessDetails(); RefreshProcessStateBadge(); RefreshToolbarCommandState(); }, DispatcherPriority.Background); }); } 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("Disabled", StringComparison.OrdinalIgnoreCase)) { continue; } if (current == null || !current.Contains("TAMPERED", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue(key, "INACTIVE (target suspended)"); } } if (DiagnosticsState.GetValue("Usermode Hooks")?.Contains("Disabled", StringComparison.OrdinalIgnoreCase) != true) { 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 static string AppendEnvironmentOverride(string existing, string line) { if (string.IsNullOrWhiteSpace(line)) { return existing ?? string.Empty; } if (string.IsNullOrWhiteSpace(existing)) { return line; } string[] entries = existing.Replace("\r\n", "\n", StringComparison.Ordinal) .Replace('\r', '\n') .Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); int separator = line.IndexOf('='); if (separator > 0) { string name = line[..separator]; foreach (string entry in entries) { int existingSeparator = entry.IndexOf('='); if (existingSeparator > 0 && string.Equals(entry[..existingSeparator], name, StringComparison.OrdinalIgnoreCase)) { return existing; } } } return existing + "\n" + line; } 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); } } 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 ApiViewMode_Changed(object sender, SelectionChangedEventArgs e) { _ = sender; _ = e; _apiViewPresentationMode = ApiViewModeBox?.SelectedIndex == 1 ? ApiViewPresentationMode.ThreadTimeline : ApiViewPresentationMode.CallGraph; 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 || ApiViewSelectedField2Label == null || ApiViewSelectedField4Label == null || ApiViewSelectedSizeValue == 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; ApiViewSelectedField2Label.Text = "Context"; ApiViewSelectedField4Label.Text = "Flags"; ApiViewSelectedSizeValue.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} | First Seen {selected.FirstSeen} | 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(); ApiViewSelectedField2Label.Text = selected.Field2Label; ApiViewSelectedField4Label.Text = selected.Field4Label; ApiViewSelectedSizeValue.Text = selected.SizeLabel; 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, TargetSuspended = t.TargetSuspended }) .ToList(), CoreUsage = src.CoreUsage .Select(c => new CoreUsageSample { CoreIndex = c.CoreIndex, BusyPercent = c.BusyPercent, DominantTid = c.DominantTid, DominantThreadKind = c.DominantThreadKind, DominantThreadCpuMs = c.DominantThreadCpuMs, ThreadCount = c.ThreadCount }) .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, AllocationBase = m.AllocationBase, RegionSize = m.RegionSize, State = m.State, Protect = m.Protect, AllocationProtect = m.AllocationProtect, Type = m.Type, StateLabel = m.StateLabel, ProtectLabel = m.ProtectLabel, TypeLabel = m.TypeLabel, Category = m.Category, SpecialUse = m.SpecialUse, BackingPath = m.BackingPath, ModulePath = m.ModulePath, Sr71Owned = m.Sr71Owned, Sr71OwnerTag = m.Sr71OwnerTag, WorkingSetValid = m.WorkingSetValid, WorkingSetShared = m.WorkingSetShared, WorkingSetShareCount = m.WorkingSetShareCount, WorkingSetLocked = m.WorkingSetLocked, WorkingSetLargePage = m.WorkingSetLargePage, SnapshotOffset = m.SnapshotOffset, SnapshotBytes = m.SnapshotBytes?.ToArray() }) .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 static MemoryRegionAttributionSample CloneMemoryRegionAttributionSample(MemoryRegionAttributionSample src) { return new MemoryRegionAttributionSample { TimestampUtc = src.TimestampUtc, ProcessStartKey = src.ProcessStartKey, TargetPid = src.TargetPid, ActorPid = src.ActorPid, ActorTid = src.ActorTid, AllocationBase = src.AllocationBase, BaseAddress = src.BaseAddress, RegionSize = src.RegionSize, ApiName = src.ApiName, EventKind = src.EventKind, RegionKind = src.RegionKind, RegionIdentity = src.RegionIdentity, OriginPath = src.OriginPath, SourceFamily = src.SourceFamily, ExecutionContext = src.ExecutionContext, CallerOrigin = src.CallerOrigin, FirstUserFrame = src.FirstUserFrame, FirstUserFrameModule = src.FirstUserFrameModule, FrameSummary = src.FrameSummary, UnwindClean = src.UnwindClean, FrameChainHadGaps = src.FrameChainHadGaps, ObservedByKernel = src.ObservedByKernel, ObservedByUserHook = src.ObservedByUserHook, BlackbirdOwned = src.BlackbirdOwned, CrossProcess = src.CrossProcess, ImageBacked = src.ImageBacked, InitialProtection = src.InitialProtection, CurrentProtection = src.CurrentProtection, PreviousProtection = src.PreviousProtection, FirstExecutableTransition = src.FirstExecutableTransition, MapCount = src.MapCount, WriteCount = src.WriteCount, ProtectCount = src.ProtectCount, ThreadStartCount = src.ThreadStartCount, ProtectFlipCount = src.ProtectFlipCount, RapidProtectFlipCount = src.RapidProtectFlipCount, ExecutableFlipCount = src.ExecutableFlipCount, GuardNoAccessFlipCount = src.GuardNoAccessFlipCount, WritableExecutableFlipCount = src.WritableExecutableFlipCount, ProtectionTransition = src.ProtectionTransition, EntropyBits = src.EntropyBits, MaxEntropyBits = src.MaxEntropyBits, EntropyFlipCount = src.EntropyFlipCount, RapidEntropyFlipCount = src.RapidEntropyFlipCount, HighEntropyWriteCount = src.HighEntropyWriteCount, SampleBytes = src.SampleBytes, LifecycleSummary = src.LifecycleSummary, ThreadStartObserved = src.ThreadStartObserved, ThreadId = src.ThreadId, ThreadStartAddress = src.ThreadStartAddress, FunctionTableRegistered = src.FunctionTableRegistered, FunctionTablePointer = src.FunctionTablePointer, SignatureLevel = src.SignatureLevel, SignatureType = src.SignatureType }; } private sealed class ApiCallGraphMainRowView { public string GraphKey { get; set; } = string.Empty; public string ThreadGroupKey { get; set; } = string.Empty; public string ViewModeKey { get; set; } = "call"; public string ApiName { get; set; } = string.Empty; public string OriginModule { 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 string FirstSeen { get; set; } = string.Empty; public string AbsoluteFirstSeen { get; set; } = string.Empty; public DateTime FirstSeenUtc { get; set; } 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; } 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 MemoryRegionAttributionHistory { 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 KernelHooksEnabled { get; set; } = true; public bool SignatureIntelEnabled { get; set; } = true; public bool SignatureIntelMemoryScanEnabled { get; set; } public bool SignatureIntelPageScanEnabled { get; set; } public bool AutoOpenApiGraphOnNextStart { get; set; } public bool LaunchStartsSuspendedPending { get; set; } public bool DeferredLaunchGateResumePending { get; set; } public bool LaunchOwnedByInterface { get; set; } public LaunchTargetKind AnalysisSubjectKind { get; set; } = LaunchTargetKind.Executable; public string AnalysisSubjectPath { get; set; } = string.Empty; public string AnalysisHostPath { get; set; } = string.Empty; public bool TargetExited { get; set; } public string TargetExitReason { get; set; } = string.Empty; 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)); } }