XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Numerics;
using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.UI.Windowing;
using Windows.Graphics;
using XFEEDToolBox.Controls;
using XFEEDToolBox.Core.Rendering;
using XFEEDToolBox.Rendering;
using XFEEDToolBox.Views;

namespace XFEEDToolBox;

public partial class App
{
    private static async Task RunModelProbeAsync(string reportPath)
    {
        var output = Path.GetDirectoryName(Path.GetFullPath(reportPath))!; Directory.CreateDirectory(output);
        void Stage(string stage) => File.AppendAllText(reportPath + ".progress", DateTimeOffset.UtcNow.ToString("o") + " " + stage + Environment.NewLine);
        Stage("start");
        object? rendering = null, refit = null; var models = new List<object>(); var lifecycle = new List<object>(); var resizing = new List<object>(); string? failure = null;
        try
        {
            var executable = ViewModel.PluginGameExecutable;
            await Task.Run(() =>
            {
                var reader = new EliteLocalModels(executable);
                foreach (var model in EliteLocalModels.Reference.Models)
                {
                    var mesh = reader.Read(model.ShipType)!;
                    var min = new Vector3(float.PositiveInfinity); var max = new Vector3(float.NegativeInfinity);
                    foreach (var v in mesh.Vertices) { min = Vector3.Min(min, v.Position); max = Vector3.Max(max, v.Position); }
                    models.Add(new { model.ShipType, model.ResourceName, model.ResourceHash, mesh.Triangles, mesh.Sha256,
                        finite = mesh.Vertices.All(v => float.IsFinite(v.Position.LengthSquared()) && float.IsFinite(v.Normal.LengthSquared())),
                        dimensions = new[] { max.X - min.X, max.Y - min.Y, max.Z - min.Z }, normalized = (max - min).Length() > 0 && Math.Abs(Math.Max(max.X - min.X, Math.Max(max.Y - min.Y, max.Z - min.Z)) - 2) < .001 });
                }
            });
            GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
            Stage("all-models-verified");
            ((OverlappedPresenter)MainWindow.AppWindow.Presenter).Restore(false);
            MainWindow.AppWindow.Resize(new SizeInt32(1510, 980)); MainWindow.AppWindow.Move(new(60, 60));
            AppShellPage.Current!.Navigate(typeof(MainPage)); MainWindow.Activate();
            await Task.Delay(500);
            var main = (MainPage)AppShellPage.Current.CurrentPage!;
            var preview = (ShipModelView)main.FindName("ShipSchematic");
            preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
            await Task.Delay(300);
            await WaitForModelAsync(preview);
            Stage("home-rendering");
            await Task.Delay(1800);
            var first = await preview.CaptureFrameAsync(); await SaveModelFrameAsync(first, Path.Combine(output, "home-model-first.png"));
            var frames = preview.Frames; var clock = Stopwatch.StartNew(); await Task.Delay(3000);
            var fps = (preview.Frames - frames) / clock.Elapsed.TotalSeconds; var timing = preview.MonitorTiming;
            var second = await preview.CaptureFrameAsync(); await SaveModelFrameAsync(second, Path.Combine(output, "home-model-rotated.png"));
            Stage("rotation-measured");
            var changedPixels = first.Width == second.Width && first.Height == second.Height ? first.Pixels.Zip(second.Pixels).Count(p => p.First != p.Second) : -1;
            var presenter = (OverlappedPresenter)MainWindow.AppWindow.Presenter;
            Stage("minimizing"); presenter.Minimize(); Stage("minimized"); await Task.Delay(400); var before = preview.Frames; await Task.Delay(600); var paused = preview.Frames == before;
            presenter.Restore(false); Stage("restored"); await Task.Delay(400);
            var scroll = (ScrollViewer)main.Content;
            scroll.ChangeView(null, 0, null, true); await Task.Delay(400);
            var offscreenFrames = preview.Frames; await Task.Delay(600);
            var pausedWhenOffscreen = preview.Frames == offscreenFrames;
            preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
            await Task.Delay(400);
            var resumedWhenVisible = preview.Frames > offscreenFrames;
            Stage("visibility-tested");
            rendering = new { preview.ShipType, preview.ModelSource, preview.ModelHash, preview.Triangles, preview.Adapter, preview.GraphicsBackend, preview.FeatureEdges,
                first.Width, first.Height, monitor = timing.Monitor, displayModeHz = timing.ModeHz, desktopCompositionHz = timing.RefreshHz, measuredFps = fps, preview.AverageDrawMilliseconds, preview.AverageWaitMilliseconds,
                synchronized = timing.RefreshHz > 1 && Math.Abs(fps - timing.RefreshHz) / timing.RefreshHz < .12,
                slowRotation = second.AngleDegrees > first.AngleDegrees + 6 && second.AngleDegrees < first.AngleDegrees + 18,
                changedPixels, pixelsPresent = second.Pixels.Where((_, i) => i % 4 == 3).Count(a => a > 0), framing = MeasureHologramFrame(second), pausedWhenMinimized = paused, pausedWhenOffscreen, resumedWhenVisible, preview.RenderError };
            foreach (var size in new[] { new SizeInt32(1900, 1100), new SizeInt32(2560, 1440), new SizeInt32(1510, 980) })
            {
                MainWindow.AppWindow.Resize(size); await Task.Delay(300);
                preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false }); await Task.Delay(250);
                var resized = await preview.CaptureFrameAsync();
                resizing.Add(new { windowWidth = size.Width, windowHeight = size.Height, resized.Width, resized.Height, framing = MeasureHologramFrame(resized), preview.RenderError });
            }
            Stage("native-resizing-tested");
            using (await preview.FreezeForXamlProbeAsync()) await CapturePageAsync(main, Path.Combine(output, "home-layout-with-model.png"));
            Stage("home-captured");
            MainWindow.Activate(); await Task.Delay(200);
            AppShellPage.Current.Navigate(typeof(RefitPage)); await Task.Delay(400);
            Stage("refit-navigated");
            var stoppedWhenUnloaded = !preview.IsRendering;
            var page = (RefitPage)AppShellPage.Current.CurrentPage!; var background = (ShipModelView)page.FindName("RefitShipModel");
            await WaitForModelAsync(background); var initial = background.ShipType;
            Stage("refit-rendering");
            // A stock Sidewinder hash generated independently by the upstream EDSY V8 oracle.
            ViewModel.RefitEdsyLink = "https://edsy.org/#/L=K100000H4C0S00,FBR00FBR00,,9p300A4Y00AKA00AZo00Ans00B1U00BH600BWQ00,,7Og0003w00mpU0nG0-0nF0-";
            await ViewModel.ImportEdsyCommand.ExecuteAsync(null);
            await Task.Delay(500); await WaitForModelAsync(background);
            var image = await background.CaptureFrameAsync(); await SaveModelFrameAsync(image, Path.Combine(output, "refit-model.png"));
            refit = new { initial, selected = ViewModel.RefitShipType, background = background.ShipType, background.ModelSource,
                switched = background.ShipType == ViewModel.RefitShipType && background.ShipType != initial,
                sourceMatchesSelected = background.ModelSource.Contains("#" + EliteLocalModels.Reference.Models.Single(m => EliteLocalModels.Normalize(m.ShipType) == EliteLocalModels.Normalize(background.ShipType)).ResourceName),
                stoppedWhenUnloaded, framing = MeasureHologramFrame(image), background.GraphicsBackend, background.FeatureEdges, unmodifiedGradeEmpty = ViewModel.RefitGrade == 0 && ((XFEEDToolBox.Controls.EngineeringGradePicker)page.FindName("EngineeringGrade")).Value == -1,
                ViewModel.RefitEdsyStatus, documentShip = ViewModel.RefitDocument?.ShipType, background.RenderError };
            using (await background.FreezeForXamlProbeAsync()) await CapturePageAsync(page, Path.Combine(output, "refit-layout-with-model.png"));
            await background.StopAsync();
            Stage("refit-stopped");
            for (var repeat = 0; repeat < 4; repeat++)
            {
                AppShellPage.Current.Navigate(typeof(MainPage)); await Task.Delay(300);
                main = (MainPage)AppShellPage.Current.CurrentPage!; preview = (ShipModelView)main.FindName("ShipSchematic");
                preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
                await WaitForModelAsync(preview); await Task.Delay(200);
                presenter.Minimize(); await Task.Delay(300); var count = preview.Frames; await Task.Delay(300);
                var halted = count == preview.Frames;
                presenter.Restore(false); await Task.Delay(350);
                var resumed = preview.Frames > count && preview.RenderError.Length == 0;
                AppShellPage.Current.Navigate(typeof(RefitPage)); await Task.Delay(400);
                var oldStopped = !preview.IsRendering;
                page = (RefitPage)AppShellPage.Current.CurrentPage!; background = (ShipModelView)page.FindName("RefitShipModel");
                await WaitForModelAsync(background);
                lifecycle.Add(new { repeat, halted, resumed, oldStopped, error = preview.RenderError, refitError = background.RenderError });
                if (!halted || !resumed || !oldStopped || background.RenderError.Length > 0) throw new InvalidOperationException("模型页面切换/最小化恢复检查失败。");
                Stage("lifecycle-" + repeat);
            }
            await background.StopAsync();
        }
        catch (Exception e) { failure = e.ToString(); }
        await File.WriteAllTextAsync(reportPath, JsonSerializer.Serialize(new { models, rendering, refit, lifecycle, resizing, failure, gameActionsInvoked = false, gameFilesChanged = false }, new JsonSerializerOptions { WriteIndented = true }));
        MainWindow.Close();
    }
    private static object MeasureHologramFrame(ShipModelView.RenderedFrame frame)
    {
        var left = frame.Width; var top = frame.Height; var right = -1; var bottom = -1; var transparent = 0; var premultiplied = true;
        for (var y = 0; y < frame.Height; y++) for (var x = 0; x < frame.Width; x++)
        {
            var i = (y * frame.Width + x) * 4; var a = frame.Pixels[i + 3];
            if (a == 0) transparent++; else { left = Math.Min(left, x); top = Math.Min(top, y); right = Math.Max(right, x); bottom = Math.Max(bottom, y); }
            premultiplied &= frame.Pixels[i] <= a + 1 && frame.Pixels[i + 1] <= a + 1 && frame.Pixels[i + 2] <= a + 1;
        }
        return new { left, top, right, bottom, transparentPixels = transparent, transparentFraction = (double)transparent / (frame.Width * frame.Height), premultiplied,
            marginOnEverySide = left > 4 && top > 4 && right < frame.Width - 5 && bottom < frame.Height - 5,
            centred = Math.Abs((left + right + 1) / 2.0 - frame.Width / 2.0) < frame.Width * .12 && Math.Abs((top + bottom + 1) / 2.0 - frame.Height / 2.0) < frame.Height * .12 };
    }
    private static async Task WaitForModelAsync(ShipModelView view)
    {
        var clock = Stopwatch.StartNew();
        while (view.Frames < 3 && view.RenderError.Length == 0 && clock.Elapsed < TimeSpan.FromSeconds(15)) await Task.Delay(100);
        if (view.RenderError.Length > 0 || view.Frames < 3) throw new InvalidOperationException("模型未开始渲染:" + view.ShipType + " · " + view.RenderError);
    }
    private static async Task SaveModelFrameAsync(ShipModelView.RenderedFrame frame, string path)
    {
        using var output = File.Open(path, FileMode.Create);
        var encoder = await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId, output.AsRandomAccessStream());
        encoder.SetPixelData(Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8, Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied,
            (uint)frame.Width, (uint)frame.Height, 96, 96, frame.Pixels);
        await encoder.FlushAsync();
    }
}