using System.Text.Json; using Microsoft.UI.Dispatching; using XFEExtension.NetCore.WinUIHelper.Utilities; using XFEEDToolBox.Core.Services; using XFEEDToolBox.ViewModels; using XFEEDToolBox.Views; using XFEEDToolBox.Services; namespace XFEEDToolBox; public partial class App : Application { public static MainWindow MainWindow { get; private set; } = null!; public static ToolboxRuntime Runtime { get; private set; } = null!; public static ToolboxViewModel ViewModel { get; private set; } = null!; public static readonly Type[] Pages = [typeof(MainPage),typeof(ProfilePage), typeof(GalaxyMapPage), typeof(NavigationPage),typeof(CurrentSystemPage), typeof(MarketPage), typeof(MaterialsPage), typeof(RefitPage), typeof(RefitShipDetailsPage), typeof(RefitModulePickerPage), typeof(ShipPage), typeof(EngineeringPage), typeof(UpgradeListPage), typeof(PlannerPage), typeof(ExecutionPage), typeof(TranslationPage), typeof(ReadPage), typeof(SettingPage), typeof(LogViewPage)]; public App() { InitializeComponent(); foreach (var page in Pages) PageManager.RegisterPage(page); UnhandledException += (_, e) => { Runtime?.Log("界面异常", e.Exception?.ToString()??e.Message); ViewModel?.ShowError(e.Message); e.Handled = true; }; } protected override async void OnLaunched(LaunchActivatedEventArgs args) { var commandLine = Environment.GetCommandLineArgs(); var offlinePreview=commandLine.Contains("--offline-preview"); var marketProbe=GetArgument(commandLine,"--smoke-market") is not null||GetArgument(commandLine,"--check-market-cache") is not null; if(offlinePreview&&GetArgument(commandLine,"--data-dir") is null)throw new ArgumentException("Offline preview requires an isolated --data-dir."); var directory = GetArgument(commandLine, "--data-dir") ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "XFEstudio", "XFEEDToolBox"); Runtime = new(directory); MainWindow = new(); if(GetArgument(commandLine,"--notify-announcement") is {} announcementPath) {await SendAnnouncementOnceAsync(announcementPath);return;} ViewModel = new(Runtime, MainWindow.DispatcherQueue,offlinePreview||marketProbe); var hiddenProbe=GetArgument(commandLine,"--check-background-data") is not null||GetArgument(commandLine,"--smoke-notifications") is not null||GetArgument(commandLine,"--smoke-current-system") is not null||GetArgument(commandLine,"--smoke-interface") is not null||GetArgument(commandLine,"--smoke-ship-status") is not null||GetArgument(commandLine,"--smoke-refinement") is not null||GetArgument(commandLine,"--smoke-skybox") is not null||GetArgument(commandLine,"--smoke-ui") is not null||GetArgument(commandLine,"--smoke-layout") is not null||GetArgument(commandLine,"--smoke-models") is not null||GetArgument(commandLine,"--smoke-map") is not null||GetArgument(commandLine,"--accept-navigation") is not null||GetArgument(commandLine,"--accept-game-route") is not null||GetArgument(commandLine,"--check-navigation") is not null||GetArgument(commandLine,"--smoke-workspace") is not null; hiddenProbe|=GetArgument(commandLine,"--smoke-game-icons") is not null; hiddenProbe|=GetArgument(commandLine,"--smoke-relog") is not null; hiddenProbe|=GetArgument(commandLine,"--smoke-loading-hologram") is not null; hiddenProbe|=marketProbe; hiddenProbe|=GetArgument(commandLine,"--check-system-read") is not null; hiddenProbe|=GetArgument(commandLine,"--accept-sco") is not null; ViewModel.InitializeHotkeys((!hiddenProbe||GetArgument(commandLine,"--accept-sco") is not null)&&!offlinePreview&&!commandLine.Contains("--no-session-automation")); MainWindow.Content = new AppShellPage(); InitializeArrivalNotifications(!hiddenProbe&&!offlinePreview); if (commandLine.Contains("--maximized")) ((Microsoft.UI.Windowing.OverlappedPresenter)MainWindow.AppWindow.Presenter).Maximize(); var closing=false; MainWindow.Closed += (_, _) => { closing=true; StopArrivalNotifications();ViewModel.Dispose(); // A final-window async-void handler may be abandoned when the host // exits. Complete background writers and their cache seals before // returning; dispose runs without the closing UI context. Task.Run(async()=>await Runtime.DisposeAsync()).GetAwaiter().GetResult(); }; if (!hiddenProbe&&!commandLine.Contains("--background-start")) {if(commandLine.Contains("--show-inactive"))MainWindow.AppWindow.Show(false);else MainWindow.Activate();} else MainWindow.AppWindow.Hide(); try { await Runtime.InitializeAsync(offline:offlinePreview||marketProbe,validateMarketCache:GetArgument(commandLine,"--check-market-cache") is not null); if(closing)return; if(GetArgument(commandLine,"--check-market-cache") is {} cacheReport){await RunMarketCacheCheckAsync(cacheReport);return;} if(GetArgument(commandLine,"--check-background-data") is {} backgroundReport){await RunBackgroundDataProbeAsync(backgroundReport);return;} await ViewModel.InitializeAsync(); if(closing)return; if(GetArgument(commandLine,"--upgrade-native-only") is {} maintenance) {await ViewModel.UpgradeNativeSupportOnlyAsync(maintenance,GetArgument(commandLine,"--maintenance-report")??throw new ArgumentException("Maintenance report path required."));return;} if(GetArgument(commandLine,"--accept-sco") is {} scoReport){await RunScoAcceptanceAsync(commandLine,scoReport);return;} if(GetArgument(commandLine,"--update-and-automatic-trade") is {} updateTrade){AppShellPage.Current!.Navigate(typeof(PlannerPage));await ViewModel.UpdateAndRunAutomaticTradeAsync(updateTrade);return;} if(GetArgument(commandLine,"--automatic-trade") is {} automaticTrade){AppShellPage.Current!.Navigate(typeof(PlannerPage));await ViewModel.RunAutomaticTradeAsync(automaticTrade);return;} if(GetArgument(commandLine,"--station-journey") is {} journey) { var destination=System.Text.Json.JsonSerializer.Deserialize(await File.ReadAllTextAsync(journey),XFEEDToolBox.Core.Models.ToolboxSettings.JsonOptions) ??throw new ArgumentException("Station journey requires a destination station."); AppShellPage.Current!.Navigate(typeof(NavigationPage));await ViewModel.RunStationJourneyAsync(destination);return; } if(GetArgument(commandLine,"--check-system-read") is {} systemReadReport){await RunSystemReadCheckAsync(systemReadReport);return;} if(GetArgument(commandLine,"--smoke-market") is {} marketReport){await RunMarketProbeAsync(marketReport);return;} if(GetArgument(commandLine,"--smoke-relog") is {} relogReport){await RunRelogProbeAsync(relogReport);return;} if(GetArgument(commandLine,"--smoke-loading-hologram") is {} hologramReport){await RunLoadingHologramProbeAsync(hologramReport,GetArgument(commandLine,"--game-executable")??throw new ArgumentException("Original game executable is required."));return;} if(GetArgument(commandLine,"--smoke-game-icons") is {} iconReport){await RunGameIconProbeAsync(iconReport);return;} if(GetArgument(commandLine,"--smoke-notifications") is {} notificationReport){await RunArrivalNotificationProbeAsync(notificationReport);return;} if(GetArgument(commandLine,"--smoke-current-system") is {} systemReport){await RunCurrentSystemProbeAsync(systemReport);return;} if(GetArgument(commandLine,"--smoke-interface") is {} interfaceReport){await RunInterfaceOnlyProbeAsync(interfaceReport);return;} if(GetArgument(commandLine,"--smoke-ship-status") is {} shipStatusReport){await RunShipStatusProbeAsync(shipStatusReport);return;} if(GetArgument(commandLine,"--smoke-refinement") is {} refinementReport){await RunRefinementProbeAsync(refinementReport);return;} if(GetArgument(commandLine,"--smoke-skybox") is {} skyboxReport){await RunSkyboxProbeAsync(commandLine,skyboxReport);return;} if(GetArgument(commandLine,"--smoke-workspace") is {} workspaceReport){await RunWorkspaceProbeAsync(workspaceReport);return;} if(GetArgument(commandLine,"--check-navigation") is {} navigationCheck){await RunNavigationCheckAsync(commandLine,navigationCheck);return;} if(GetArgument(commandLine,"--accept-game-route") is {} routeReport){await RunGameRouteAcceptanceAsync(commandLine,routeReport);return;} if(GetArgument(commandLine,"--accept-navigation") is {} navigationReport){await RunNavigationAcceptanceAsync(commandLine,navigationReport);return;} if(!hiddenProbe&&GetArgument(commandLine,"--page") is {} requestedPage) { var page=Pages.FirstOrDefault(p=>p.Name.Equals(requestedPage,StringComparison.OrdinalIgnoreCase)||p.Name.Equals(requestedPage+"Page",StringComparison.OrdinalIgnoreCase)); if(page is not null)AppShellPage.Current!.Navigate(page); } if(GetArgument(commandLine,"--smoke-layout") is {} layoutReport){await RunLayoutProbeAsync(layoutReport);return;} if(GetArgument(commandLine,"--smoke-models") is {} modelReport){await RunModelProbeAsync(modelReport);return;} if(GetArgument(commandLine,"--smoke-map") is {} mapReport){await RunMapPerformanceProbeAsync(mapReport);return;} if (GetArgument(commandLine, "--smoke-ui") is { } report) await RunUiSmokeAsync(report); if(GetArgument(commandLine,"--market-report") is {} marketRuntimeReport)_=WriteMarketRuntimeReportAsync(marketRuntimeReport); } catch (Exception e) { ViewModel.ShowError(e.Message); Runtime.Log("启动错误", e.ToString()); } } private static string? GetArgument(string[] args, string name) { var index = Array.IndexOf(args, name); return index >= 0 && index + 1 < args.Length ? args[index + 1] : null; } private static async Task RunUiSmokeAsync(string reportPath) { var results = new List(); ViewModel.DemoMode = false; await ViewModel.RefreshCommand.ExecuteAsync(null); await ViewModel.ExportReadReportCommand.ExecuteAsync(null); var actualModules = ViewModel.FittedModules.Count; var actualLocalization=new{untranslatedModules=Runtime.Player.Ships.SelectMany(s=>s.Modules).Where(m=>m.DisplayName.Contains("未收录")||m.SlotName.Contains("未收录")).Select(m=>new{m.Slot,m.Item}).ToArray(),untranslatedModifiers=Runtime.Player.Ships.SelectMany(s=>s.Modules).SelectMany(m=>m.Engineering?.Modifiers??[]).Where(m=>m.Display.Contains("未收录")).Select(m=>m.Label).Distinct().ToArray(),localizedEffectsWithEnglish=Runtime.Player.Ships.SelectMany(s=>s.Modules).Where(m=>m.Engineering?.ExperimentalEffectId.Length>0).All(m=>!Core.Localization.ChineseNames.IsChinese(m.EngineeringOriginal))}; var hotkeyProbe = await WindowsGlobalHotkeys.ProbeAsync(WinRT.Interop.WindowNative.GetWindowHandle(MainWindow)); var existing=ViewModel.EngineeringSources.FirstOrDefault(s=>s.Module?.Engineering is not null); if(existing is not null)ViewModel.EngineeringSource=existing; await ViewModel.CalculateEngineeringCommand.ExecuteAsync(null); var engineering = new { engineers = Runtime.Player.Engineers.Count, verifiedRolls = Runtime.Player.VerifiedEngineeringRolls, mismatchedRolls = Runtime.Player.MismatchedEngineeringRolls, materials = ViewModel.MaterialStocks.Count, materialTotal = Runtime.Player.Materials.Sum(x => x.Count), modules = ViewModel.EngineeringModules.Count, module = ViewModel.EngineeringModule?.Slot, blueprint = ViewModel.EngineeringBlueprint, result = ViewModel.EngineeringResult, rolls = ViewModel.EngineeringRolls.Count, ingredients = ViewModel.EngineeringNeeds.Count }; var sourceModule = ViewModel.RefitSlot?.Module; var sourceSlot = ViewModel.RefitSlot?.Slot; if (sourceModule is not null && ViewModel.RefitEngineer is not null && ViewModel.RefitBlueprint is not null) { ViewModel.RefitEditEnabled = true; ViewModel.RefitGrade = 5; ViewModel.RefitQuality = 100; var other = ViewModel.RefitSlots.FirstOrDefault(s => s.Slot != sourceSlot && s.Module is not null && Core.Engineering.EngineeringCatalog.ModuleType(s.Module.Item) == Core.Engineering.EngineeringCatalog.ModuleType(sourceModule.Item)); if (other is not null) { ViewModel.RefitSlot = ViewModel.RefitVisibleSlots.First(s=>s.Slot==other.Slot); ViewModel.RefitEditEnabled = true; ViewModel.RefitGrade = 5; } await ViewModel.CalculateRefitCommand.ExecuteAsync(null); } var refit = new { source = ViewModel.RefitSourceShip?.Type, slots = ViewModel.RefitSlots.Count, jobs = ViewModel.RefitModuleResults.Count, ingredients = ViewModel.RefitNeeds.Count, targets = ViewModel.RefitDocument?.Slots.Where(s => s.Target is not null).Select(s => new { s.Slot, s.Target }), result = ViewModel.RefitResult, sourceUnchanged = sourceModule is null || Runtime.Player.Ships.FirstOrDefault(s => s.ShipId == ViewModel.RefitSourceShip?.ShipId)?.Modules.FirstOrDefault(m => m.Slot == sourceSlot) == sourceModule, gameEditsInvoked = false }; var plugin = new { status = ViewModel.PluginStatusText, installed = ViewModel.PluginInstalled, tradeReady = ViewModel.PluginTradeReady, installationInvoked = false }; await ViewModel.ExportRefitCommand.ExecuteAsync(null); var refitExport=Directory.Exists(Path.Combine(Runtime.DataDirectory,"refits")) ? Directory.GetFiles(Path.Combine(Runtime.DataDirectory,"refits"),"refit-*.json").OrderByDescending(File.GetLastWriteTimeUtc).FirstOrDefault() : null; var acceptanceReport = ViewModel.ReadReportPath; var refitWorkbench = await RunRefitWorkbenchProbeAsync(reportPath); var incremental = await RunIncrementalProbeAsync(); var navigation=await RunNavigationProbeAsync(); var upgradeList=await RunUpgradeListProbeAsync(); var galaxyProbe=await RunGalaxyProbeAsync(); foreach (var page in Pages) { AppShellPage.Current!.Navigate(page); await Task.Delay(250); var root = AppShellPage.Current.CurrentPage; results.Add(new { page = page.Name, loaded = root?.IsLoaded == true, elements = CountVisuals(root) }); foreach (var pivot in Descendants(root).OfType()) for (var index = 0; index < pivot.Items.Count; index++) { pivot.SelectedIndex = index; await Task.Delay(150); } } ViewModel.DemoMode = true; await ViewModel.RefreshCommand.ExecuteAsync(null); ViewModel.UsePlayerValuesCommand.Execute(null); await ViewModel.CalculateCommand.ExecuteAsync(null); Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(reportPath))!); await File.WriteAllTextAsync(reportPath, JsonSerializer.Serialize(new { pages = results, actualModules, acceptanceReport, engineering, refit, refitExport, refitWorkbench, plugin, incremental, navigation, upgradeList, actualLocalization, galaxyProbe, galaxy=new{systems=ViewModel.GalaxySystems.Count,route=ViewModel.GalaxyRoute.Count,centre=ViewModel.GalaxyCentre,generatedStars=false}, profile = new { avatarPresent=ViewModel.ProfileAvatar is not null, namePresent=ViewModel.ProfileDisplayName.Length>0 }, translation=ViewModel.TranslationStatusText, planLegs = ViewModel.Plan?.Legs.Count, error = ViewModel.ErrorMessage, hotkeyStatus = ViewModel.HotkeyStatus, hotkeyProbe, gameSessionActionsInvoked = false }, new JsonSerializerOptions { WriteIndented = true })); MainWindow.Close(); } private static async Task RunNavigationProbeAsync() { var route=Runtime.Player.Navigation.Route; if(route.Count>0)ViewModel.NavigationDestination=route[^1].Name; AppShellPage.Current!.Navigate(typeof(NavigationPage));await Task.Delay(250); var loaded=AppShellPage.Current.CurrentPage?.IsLoaded==true; var start=Descendants(AppShellPage.Current.CurrentPage).OfType