using System.Diagnostics; using System.Security.Cryptography; using System.Text; using System.Text.Json; using Microsoft.UI.Xaml.Media; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Presentation; using XFEEDToolBox.Core.Services; using XFEEDToolBox.Views; namespace XFEEDToolBox; public partial class App { private static async Task RunActivityLogProbeAsync(string directory) { Directory.CreateDirectory(directory); string[] requirements= [ "Use only actual Runtime activity entries; never inject a demonstration exception or replace player data.", "Refresh CurrentSystem read-only with extension preparation disabled in --activity-only mode; never start flight, commerce or a session workflow.", "Select LogTabs index 1 and capture the actual toolbox log page with a frozen array of real immutable entries.", "Record severity, first/last observation, occurrence count, diagnostic text and hashes from those same entries.", "If a real diagnostic exists, expand its actual realized row and capture it; otherwise mark expansion unconfirmed.", "Capture success is not visual acceptance: preserve layout, visible text, screenshot SHA-256 and outstanding pixel-review requirements." ]; await File.WriteAllTextAsync(Path.Combine(directory,"activity-log-requirements.json"),JsonSerializer.Serialize(new{at=DateTimeOffset.UtcNow,requirements},ToolboxSettings.JsonOptions)); var checks=new List();var captures=new List();var refreshes=new List();var states=new List(); var originalSize=MainWindow.AppWindow.Size;var beforePlayer=Runtime.Player; var entries=Array.Empty();var snapshotHash="";string? failure=null;long? expandedEntryId=null; LogViewPage? page=null;Pivot? tabs=null;ListView? list=null;var previousTab=0;var failed=0;var unconfirmed=0; void Check(string id,bool? passed,string evidence) { if(passed==false)failed++;if(passed is null)unconfirmed++; checks.Add(new{id,status=passed is null?"unconfirmed":passed.Value?"passed":"failed",evidence}); } static string Hash(string value)=>Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(value))); static string EntriesHash(ActivityEntry[] value)=>Convert.ToHexStringLower(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(value,ToolboxSettings.JsonOptions))); bool Visible(FrameworkElement element) { DependencyObject? current=element; while(current is not null) { if(current is UIElement ui&&ui.Visibility!=Visibility.Visible)return false; if(ReferenceEquals(current,page))break; current=VisualTreeHelper.GetParent(current); } return element.ActualWidth>0&&element.ActualHeight>0; } object? Bounds(FrameworkElement? element) { if(page is null||element is null||element.ActualWidth<=0||element.ActualHeight<=0)return null; var origin=element.TransformToVisual(page).TransformPoint(new(0,0)); return new{element.Name,x=origin.X,y=origin.Y,width=element.ActualWidth,height=element.ActualHeight, right=origin.X+element.ActualWidth,bottom=origin.Y+element.ActualHeight,visible=Visible(element)}; } async Task Capture(string name) { // Reapply the frozen real snapshot, rather than reevaluating Runtime // enumerables at different points while capture awaits its pixels. ViewModel.Activity.Apply(entries);page!.UpdateLayout();await Task.Delay(100); var rowsBefore=ViewModel.Activity.Select(row=>row.Record).ToArray(); var path=Path.Combine(directory,name+".png");await CapturePageAsync(page,path); var imageHash=Convert.ToHexStringLower(SHA256.HashData(await File.ReadAllBytesAsync(path))); var rowsAfter=ViewModel.Activity.Select(row=>row.Record).ToArray(); var stable=rowsBefore.SequenceEqual(entries)&&rowsAfter.SequenceEqual(entries); var texts=Descendants(page).OfType().Where(Visible).Select(text=>text.Text).Where(text=>!string.IsNullOrWhiteSpace(text)).Distinct().ToArray(); var horizontal=Descendants(list!).OfType().Any(scroll=>Visible(scroll)&&scroll.ScrollableWidth>1); captures.Add(new{name,path,sha256=imageHash,bytes=new FileInfo(path).Length}); states.Add(new{name,pageWidth=page.ActualWidth,pageHeight=page.ActualHeight,windowWidth=MainWindow.AppWindow.Size.Width,windowHeight=MainWindow.AppWindow.Size.Height, selectedTab=tabs!.SelectedIndex,list=Bounds(list),horizontalOverflow=horizontal,snapshotHash,renderedSnapshotHash=EntriesHash(rowsAfter),snapshotStable=stable, realizedRowCount=Descendants(list!).OfType().Count(Visible),expandedEntryId, expandedDetails=Descendants(list!).OfType().Where(expander=>Visible(expander)&&expander.IsExpanded).Select(expander=>new{header=expander.Header?.ToString(),bounds=Bounds(expander)}).ToArray(),texts}); Check("frozen-source-"+name,stable,"Visible activity records before and after pixel capture must equal the frozen actual Runtime entries, including their counts and diagnostics."); Check("layout-"+name,!horizontal&&page.ActualWidth>0&&page.ActualHeight>0,"The selected toolbox log has a positive actual viewport and no realized horizontal scrolling; original pixels still require review."); } try { var args=Environment.GetCommandLineArgs(); if(!args.Contains("--activity-only"))throw new InvalidOperationException("Activity acceptance requires --activity-only so CurrentSystem extension preparation is disabled."); if(ViewModel.DemoMode||ViewModel.OfflinePreview)throw new InvalidOperationException("Activity acceptance requires real Runtime data, without demonstration or offline substitution."); if(ViewModel.RunActive||ViewModel.NavigationActive||ViewModel.SystemFlightActive||ViewModel.SessionBusy) throw new InvalidOperationException("Activity acceptance cannot share this application with an active trade, flight or session operation."); ViewModel.ShowCurrentSystem(); try { for(var attempt=0;attempt<3;attempt++) { var elapsed=Stopwatch.StartNew();await ViewModel.RefreshCurrentSystemCommand.ExecuteAsync(null); var until=DateTimeOffset.UtcNow.AddSeconds(20); while(ViewModel.CurrentSystemReading&&DateTimeOffset.UtcNowentry.Code.StartsWith("current-system.",StringComparison.Ordinal)),"The source is actual read-only refresh output, whether normal loading, successful reading or a real diagnostic. No expected outcome was manufactured."); Check("bounded-activity-snapshot",entries.Length<=200,"The actual Runtime retains the latest bounded activity snapshot; ordinary execution events remain separate."); Check("diagnostic-summary-separation",entries.Where(entry=>entry.HasDiagnostic).All(entry=>!entry.Message.Contains("\n at ",StringComparison.Ordinal)&&!entry.Message.Contains("\n 在 ",StringComparison.Ordinal)),"Full stacks belong to diagnostic details rather than the visible summary."); var repeated=entries.Where(entry=>entry.Occurrences>1).ToArray(); Check("real-aggregation",repeated.Length>0?repeated.All(entry=>entry.LastSeen>=entry.Time&&entry.RepetitionDisplay.Length>0):null, repeated.Length>0?"Real repeated observations retain first/last time and a visible count.":"No actual repeated diagnostic/status was observed; none was inserted for the probe."); AppShellPage.Current!.Navigate(typeof(LogViewPage));await Task.Delay(200);page=(LogViewPage)AppShellPage.Current.CurrentPage!; tabs=(Pivot)page.FindName("LogTabs");previousTab=tabs.SelectedIndex;tabs.SelectedIndex=1; ViewModel.Activity.Apply(entries);page.UpdateLayout();await Task.Delay(160); list=Descendants(page).OfType().FirstOrDefault(candidate=>ReferenceEquals(candidate.ItemsSource,ViewModel.Activity)) ??throw new InvalidOperationException("The actual toolbox activity ListView was not realized."); foreach(var expander in Descendants(list).OfType())expander.IsExpanded=false; Check("toolbox-tab-selected",tabs.SelectedIndex==1,"LogTabs index 1 is the toolbox activity tab."); await Capture("activity-log-summary"); var diagnostic=entries.FirstOrDefault(entry=>entry.HasDiagnostic); if(diagnostic is null) Check("real-diagnostic-expanded",null,"No real Runtime entry contains diagnostic details; expansion remains unconfirmed and no fake error was generated."); else { var row=ViewModel.Activity.Single(value=>value.Record.Id==diagnostic.Id);list.ScrollIntoView(row);page.UpdateLayout();await Task.Delay(160); var container=list.ContainerFromItem(row) as FrameworkElement; var expander=container is null?null:Descendants(container).OfType().FirstOrDefault(value=>value.Header?.ToString()=="诊断详情"); if(expander is null)Check("real-diagnostic-expanded",null,"A real diagnostic exists but its actual Expander was not realized; no substitute row was created."); else { expandedEntryId=diagnostic.Id;expander.IsExpanded=true;page.UpdateLayout();expander.StartBringIntoView(new BringIntoViewOptions{AnimationDesired=false});await Task.Delay(120); Check("real-diagnostic-expanded",expander.IsExpanded&&Visible(expander),"Expanded the existing real diagnostic row identified by its stable ActivityEntry Id."); await Capture("activity-log-real-diagnostic");expander.IsExpanded=false;expandedEntryId=null; } } Check("no-execution-started",!ViewModel.RunActive&&!ViewModel.NavigationActive&&!ViewModel.SystemFlightActive&&!ViewModel.SessionBusy, "This probe invoked only read-only CurrentSystem refresh and log-page presentation; no execution, module preparation, menu operation or OS input was requested."); } catch(Exception error){failure=error.ToString();} finally { ViewModel.HideCurrentSystem();if(tabs is not null)tabs.SelectedIndex=previousTab;MainWindow.AppWindow.Resize(originalSize); var frozenChecks=checks.ToArray();var frozenCaptures=captures.ToArray();var frozenStates=states.ToArray();var frozenRefreshes=refreshes.ToArray(); var frozenEntries=entries.Select(entry=>new{entry.Id,entry.Time,entry.LastSeen,entry.ClosedAt,entry.Kind,entry.Code,entry.Message,entry.FirstMessage, severity=entry.Severity.ToString(),entry.SeverityDisplay,entry.Occurrences,entry.RepetitionDisplay,entry.Resolved,entry.Superseded,entry.HasDiagnostic, entry.FirstDiagnostic,entry.LastDiagnostic,details=entry.DiagnosticDisplay,detailsSha256=Hash(entry.DiagnosticDisplay)}).ToArray(); var current=Runtime.Player; await File.WriteAllTextAsync(Path.Combine(directory,"activity-log-evidence.json"),JsonSerializer.Serialize(new { at=DateTimeOffset.UtcNow,requirements,checks=frozenChecks,captures=frozenCaptures,states=frozenStates,refreshes=frozenRefreshes,entries=frozenEntries,snapshotHash, actualDataDirectory=Runtime.DataDirectory,failure,failedChecks=failed,unconfirmedChecks=unconfirmed,structurePassed=failure is null&&failed==0, playerObservation=new{before=new{beforePlayer.CommanderId,beforePlayer.CurrentShipId,beforePlayer.LoadGameAt,beforePlayer.CargoCount,beforePlayer.Credits}, after=new{current.CommanderId,current.CurrentShipId,current.LoadGameAt,current.CargoCount,current.Credits}}, source="actual-runtime-activity-after-read-only-current-system-refresh",syntheticExceptions=false,syntheticPlayerData=false, gameFlightCommandsInvoked=false,tradeOrdersSubmitted=0,sessionOperationsInvoked=false,modulePreparationInvoked=false,gameRestartInvoked=false,osInputsSent=0, visualReview="Original pixels, local details and OCR still require independent visual inspection. Missing real diagnostics leave expansion unconfirmed." },ToolboxSettings.JsonOptions)); } return new{failure,checks=checks.ToArray(),captures=captures.ToArray(),failedChecks=failed,unconfirmedChecks=unconfirmed,structurePassed=failure is null&&failed==0, entryCount=entries.Length,snapshotHash,realDiagnosticCount=entries.Count(entry=>entry.HasDiagnostic),gameActionsInvoked=false,syntheticExceptions=false}; } }