Skip to content

Commit

Permalink
修复部分 UI,修复启动异常显示
Browse files Browse the repository at this point in the history
  • Loading branch information
natsurainko committed Aug 17, 2024
1 parent b85ef8e commit 846a501
Show file tree
Hide file tree
Showing 20 changed files with 354 additions and 136 deletions.
2 changes: 1 addition & 1 deletion FluentLauncher.Localization
35 changes: 1 addition & 34 deletions Natsurainko.FluentLauncher/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -360,44 +360,11 @@
</TextBlock>
</controls:SettingsCard.Header>

<Button>
<Button Command="{Binding ShowExceptionCommand}">
<StackPanel Orientation="Horizontal" Spacing="5">
<FontIcon FontSize="14" Glyph="&#xf000;" />
<TextBlock x:Uid="Tasks_LaunchPage_B4" Text="Show Details" />
</StackPanel>
<Button.Flyout>
<Flyout Placement="Left">
<RichTextBlock
MaxWidth="400"
DataContext="{Binding Exception}"
FontWeight="SemiBold">
<Paragraph>
<Run Text="Message:" />
<Run Text="{Binding Message}" />
</Paragraph>
<Paragraph>
<Run Text="HelpLink:" />
<Run Text="{Binding HelpLink}" />
</Paragraph>
<Paragraph>
<Run Text="========================================" />

<Run Text="InnerException:" />
<Run Text="{Binding InnerException}" />

<Run Text="========================================" />
</Paragraph>
<Paragraph>
<Run Text="Source:" />
<Run Text="{Binding Source}" />
</Paragraph>
<Paragraph>
<Run Text="StackTrace:" />
<Run Text="{Binding StackTrace}" />
</Paragraph>
</RichTextBlock>
</Flyout>
</Button.Flyout>
</Button>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,17 @@
<StackPanel Padding="20,0" Spacing="10">
<TextBlock Style="{ThemeResource BaseTextBlockStyle}" Text="{Binding Description}" />
<TextBlock
MaxHeight="120"
MaxHeight="200"
Foreground="{ThemeResource ApplicationSecondaryForegroundThemeBrush}"
Style="{ThemeResource CaptionTextBlockStyle}"
TextTrimming="CharacterEllipsis"
TextWrapping="WrapWholeWords">
TextWrapping="NoWrap">
<Run Text="{Binding ExceptionMessage}" />
<Run>...</Run>
</TextBlock>
<Grid>
<Button
x:Uid="Notifications_ExceptionCopyButton"
HorizontalAlignment="Right"
Command="{Binding CopyCommand}"
Content="Copy Full Information"
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<PackageReference Include="NbtToolkit" Version="0.1.2-beta" />
<PackageReference Include="PInvoke.User32" Version="0.7.124" />
<PackageReference Include="ReverseMarkdown" Version="4.6.0" />
<PackageReference Include="System.Management" Version="8.0.0" />
<PackageReference Include="WindowsAPICodePack.Shell.CommonFileDialogs" Version="1.1.5" />
<PackageReference Include="WinUIEx" Version="2.3.4" />

Expand Down
103 changes: 90 additions & 13 deletions Natsurainko.FluentLauncher/Services/Launch/LaunchService.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using Natsurainko.FluentLauncher.Models;
using Natsurainko.FluentLauncher.Models.Launch;
using Natsurainko.FluentLauncher.Models.Launch;
using Natsurainko.FluentLauncher.Services.Accounts;
using Natsurainko.FluentLauncher.Services.Network;
using Natsurainko.FluentLauncher.Services.Settings;
using Natsurainko.FluentLauncher.Services.SystemServices;
using Natsurainko.FluentLauncher.Services.UI;
using Natsurainko.FluentLauncher.Utils;
using Natsurainko.FluentLauncher.Utils.Extensions;
using Natsurainko.FluentLauncher.ViewModels.Tasks;
using Nrk.FluentCore.Authentication;
using Nrk.FluentCore.Environment;
using Nrk.FluentCore.Launch;
Expand All @@ -28,9 +28,9 @@ internal class LaunchService
{
private readonly AuthenticationService _authenticationService;
private readonly DownloadService _downloadService;
private readonly NotificationService _notificationService;
private readonly AccountService _accountService;
private readonly SettingsService _settingsService;
private readonly LaunchSessions _launchSessions;

private SettingsService AppSettingsService => _settingsService;

Expand All @@ -41,42 +41,78 @@ internal class LaunchService

public LaunchService(
SettingsService settingsService,
GameService gameService,
AccountService accountService,
AuthenticationService authenticationService,
DownloadService downloadService,
NotificationService notificationService)
LaunchSessions launchSessions)
{
_settingsService = settingsService;
_authenticationService = authenticationService;
_accountService = accountService;
_downloadService = downloadService;
_notificationService = notificationService;
_launchSessions = launchSessions;

_sessions = [];
Sessions = new(_sessions);
}

public async Task LaunchGame(GameInfo gameInfo)
{
MinecraftSession? minecraftSession = null;
Action<Exception>? onExceptionThrow = null;

try
{
Account? account = _accountService.ActiveAccount ?? throw new Exception(ResourceUtils.GetValue("Exceptions", "_NoAccount"));

var session = CreateMinecraftSessionFromGameInfo(gameInfo, account); // TODO: replace with ctor of MinecraftSession
_sessions.Add(session);
minecraftSession = CreateMinecraftSessionFromGameInfo(gameInfo, account); // TODO: replace with ctor of MinecraftSession
_sessions.Add(minecraftSession);

OnSessionCreated(session);
_launchSessions.CreateLaunchSessionViewModel(minecraftSession, out var handleException);
onExceptionThrow = handleException;

gameInfo.UpdateLastLaunchTimeToNow();
App.GetService<JumpListService>().UpdateJumpList(gameInfo);

await session.StartAsync();
await minecraftSession.StartAsync();
}
catch (Exception ex)
{
_notificationService.NotifyWithoutContent(ex.Message);
(onExceptionThrow ?? OnExceptionThrow).Invoke(ex);
}
finally
{
if (minecraftSession != null)
{
minecraftSession.ProcessExited += (object? sender, MinecraftProcessExitedEventArgs e) =>
{
minecraftSession.McProcess?.Dispose();
};
}
}
}

private void OnExceptionThrow(Exception exception)
{
string errorDescriptionKey = string.Empty;

if (exception is InvalidOperationException)
{

}
else if (exception is YggdrasilAuthenticationException)
{
errorDescriptionKey = "_LaunchGameThrowYggdrasilAuthenticationException";
}
else if (exception is MicrosoftAuthenticationException)
{
errorDescriptionKey = "_LaunchGameThrowMicrosoftAuthenticationException";
}

App.GetService<NotificationService>().NotifyException(
"_LaunchGameThrowException",
exception,
errorDescriptionKey);
}

protected void OnSessionCreated(MinecraftSession minecraftSession)
Expand Down Expand Up @@ -120,9 +156,9 @@ Account Authenticate()
return GetLaunchAccount(config, _accountService);
}
}
catch (Exception ex)
catch (Exception)
{
throw new AuthenticateRefreshAccountException(ex);
throw;
}
}

Expand All @@ -148,6 +184,8 @@ Account Authenticate()
if (AppSettingsService.AutoRefresh)
session.RefreshAccountTask = new Task<Account>(Authenticate);

session.SkipNativesDecompression = CanSkipNativesDecompression(gameInfo);

session.ProcessStarted += (s, e) =>
{
var title = GameWindowTitle(config);
Expand All @@ -170,6 +208,45 @@ Account Authenticate()
return session;
}

private bool CanSkipNativesDecompression(GameInfo gameInfo)
{
var nativesDirectory = new DirectoryInfo(Path.Combine(gameInfo.MinecraftFolderPath, "versions", gameInfo.AbsoluteId, "natives"));

if (!nativesDirectory.Exists) return false;

var files = nativesDirectory.GetFiles();
if (files.Length == 0) return false;

try
{
var processes = FileLockUtils.GetProcessesLockingFile(files.First().FullName);

foreach (var process in processes)
{
var arguments = process?.GetCommandLine();

if ((arguments?.Contains($"-Djava.library.path={nativesDirectory.FullName}")).GetValueOrDefault())
{
processes.ForEach(p => p?.Dispose());
return true;
}

process?.Dispose();
}
}
catch (Win32Exception ex) when ((uint)ex.ErrorCode == 0x80004005)
{
// Intentionally empty - no security access to the process.
}
catch (InvalidOperationException)
{
// Intentionally empty - the process exited before getting details.
}
catch (Exception) { /* */ }

return false;
}

private string? GetSuitableJava(GameInfo gameInfo)
{
var regex = new Regex(@"^([a-zA-Z]:\\)([-\u4e00-\u9fa5\w\s.()~!@#$%^&()\[\]{}+=]+\\?)*$");
Expand Down
10 changes: 4 additions & 6 deletions Natsurainko.FluentLauncher/Services/UI/NotificationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Threading.Tasks;

#nullable disable
namespace Natsurainko.FluentLauncher.Services.UI;

public class NotificationService
Expand All @@ -27,15 +28,12 @@ public void InitContainer(StackPanel itemsContainer, Grid shadowReceiver)

public void NotifyException(string errorTitleKey, Exception exception, string errorDescriptionKey = "")
{
var exceptionViewModel = new NotifyExceptionViewModel
{
Exception = exception
};
var exceptionViewModel = new NotifyExceptionViewModel(exception);

if (!string.IsNullOrEmpty(errorDescriptionKey))
exceptionViewModel.Description = ResourceUtils.GetValue("Notifications", errorDescriptionKey);
exceptionViewModel.Description = ResourceUtils.GetValue("Notifications", errorDescriptionKey) + "\r\n" + exceptionViewModel.Description;

NotifyWithSpecialContent(ResourceUtils.GetValue("Notifications", errorTitleKey), "ExceptionNotifyTemplate", exceptionViewModel, "\uE711", 20 * 1000);
NotifyWithSpecialContent(ResourceUtils.GetValue("Notifications", errorTitleKey), "ExceptionNotifyTemplate", exceptionViewModel, "\uE711", 60 * 1000);
}

public void NotifyMessage(string title, string text, string description = "", string icon = "\uE7E7", int delay = 5000)
Expand Down
37 changes: 37 additions & 0 deletions Natsurainko.FluentLauncher/Utils/Extensions/ProcessExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Diagnostics;
using System.Management;

#nullable disable
namespace Natsurainko.FluentLauncher.Utils.Extensions;

internal static class ProcessExtensions
{
public static string GetCommandLine(this Process process)
{
string cmdLine = null;
using (var searcher = new ManagementObjectSearcher(
$"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}"))
{
// By definition, the query returns at most 1 match, because the process
// is looked up by ID (which is unique by definition).
using (var matchEnum = searcher.Get().GetEnumerator())
{
if (matchEnum.MoveNext()) // Move to the 1st item.
{
cmdLine = matchEnum.Current["CommandLine"]?.ToString();
}
}
}
if (cmdLine == null)
{
// Not having found a command line implies 1 of 2 exceptions, which the
// WMI query masked:
// An "Access denied" exception due to lack of privileges.
// A "Cannot process request because the process (<pid>) has exited."
// exception due to the process having terminated.
// We provoke the same exception again simply by accessing process.MainModule.
var dummy = process.MainModule; // Provoke exception.
}
return cmdLine;
}
}
Loading

0 comments on commit 846a501

Please sign in to comment.