From 70af956e9b57f5ee2a36ebaa918a2af7440daeea Mon Sep 17 00:00:00 2001 From: Nickolas Gupton Date: Wed, 7 Feb 2024 18:43:35 -0600 Subject: [PATCH] Upgrade Avalonia and other NuGet packages (#225) * Upgrade Avalonia to 11.0.6 and make required changes * dotnet format * make codacy happy * Remove duplicate * Fix some styles looking odd with the upgrade * Update Avalonia and Autofac dependencies * Display pop-up when the code scan fails * Add serialization option for enum to make this log easier to read * Fix couple of codacy issues * Fix null exception --- .../UnitystationLauncher.Tests.csproj | 12 +- UnitystationLauncher/App.xaml | 6 +- .../Constants/MessageBoxResults.cs | 1 + .../AssemblyTypeCheckerHelpers.cs | 9 +- .../ContentScanning/Scanners/ILScanner.cs | 15 +- .../PipeHubBuildCommunication.cs | 14 +- .../Infrastructure/MessageBoxBuilder.cs | 17 +- .../Models/ContentScanning/ScanLog.cs | 22 +++ .../Models/Enums/MessageBoxButtons.cs | 3 +- UnitystationLauncher/Program.cs | 33 +++- .../Services/AssemblyTypeCheckerService.cs | 101 ++++++++--- .../Services/CodeScanService.cs | 99 ++++++++--- .../Services/InstallationService.cs | 77 +++++++-- .../Interface/IAssemblyTypeCheckerService.cs | 3 +- .../Services/Interface/ICodeScanService.cs | 3 +- .../UnitystationLauncher.csproj | 29 ++-- UnitystationLauncher/ViewLocator.cs | 11 +- .../ViewModels/BlogPostViewModel.cs | 6 +- .../ViewModels/ChangeViewModel.cs | 13 +- .../ViewModels/InstallationsPanelViewModel.cs | 6 +- .../ViewModels/PreferencesPanelViewModel.cs | 10 +- .../ViewModels/ServersPanelViewModel.cs | 2 +- UnitystationLauncher/Views/BlogPostView.axaml | 2 +- UnitystationLauncher/Views/ChangeView.axaml | 2 +- UnitystationLauncher/Views/ChangelogView.xaml | 2 +- .../Views/ChangelogView.xaml.cs | 21 ++- UnitystationLauncher/Views/HubUpdateView.xaml | 2 +- .../Views/HubUpdateView.xaml.cs | 19 +- .../Views/InstallationView.axaml.cs | 21 ++- .../Views/InstallationsPanelView.xaml | 4 +- .../Views/InstallationsPanelView.xaml.cs | 19 +- UnitystationLauncher/Views/LauncherView.xaml | 6 +- .../Views/LauncherView.xaml.cs | 21 ++- UnitystationLauncher/Views/MainWindow.xaml | 8 +- UnitystationLauncher/Views/MainWindow.xaml.cs | 162 +++++++++--------- .../Views/NewsPanelView.xaml.cs | 19 +- UnitystationLauncher/Views/PopUpDialogue.cs | 45 +++-- .../Views/PreferencesPanelView.xaml | 4 +- .../Views/PreferencesPanelView.xaml.cs | 54 +++--- UnitystationLauncher/Views/ServerView.axaml | 30 +++- .../Views/ServerView.axaml.cs | 21 ++- .../Views/ServersPanelView.xaml | 2 +- .../Views/ServersPanelView.xaml.cs | 19 +- UnitystationLauncher/Views/VersionView.axaml | 2 +- 44 files changed, 608 insertions(+), 369 deletions(-) create mode 100644 UnitystationLauncher/Models/ContentScanning/ScanLog.cs diff --git a/UnitystationLauncher.Tests/UnitystationLauncher.Tests.csproj b/UnitystationLauncher.Tests/UnitystationLauncher.Tests.csproj index d60e67e3..9ae04cb4 100644 --- a/UnitystationLauncher.Tests/UnitystationLauncher.Tests.csproj +++ b/UnitystationLauncher.Tests/UnitystationLauncher.Tests.csproj @@ -9,18 +9,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/UnitystationLauncher/App.xaml b/UnitystationLauncher/App.xaml index ef8103f5..274a8485 100644 --- a/UnitystationLauncher/App.xaml +++ b/UnitystationLauncher/App.xaml @@ -1,14 +1,14 @@ + x:Class="UnitystationLauncher.App" + RequestedThemeVariant="Default"> - - + \ No newline at end of file diff --git a/UnitystationLauncher/Constants/MessageBoxResults.cs b/UnitystationLauncher/Constants/MessageBoxResults.cs index d6bab456..c63caeff 100644 --- a/UnitystationLauncher/Constants/MessageBoxResults.cs +++ b/UnitystationLauncher/Constants/MessageBoxResults.cs @@ -6,4 +6,5 @@ public static class MessageBoxResults public const string Cancel = "Cancel"; public const string Yes = "Yes"; public const string No = "No"; + public const string OpenLogFolder = "Open Log Folder"; } \ No newline at end of file diff --git a/UnitystationLauncher/ContentScanning/AssemblyTypeCheckerHelpers.cs b/UnitystationLauncher/ContentScanning/AssemblyTypeCheckerHelpers.cs index 7db1a8f5..bda6c94a 100644 --- a/UnitystationLauncher/ContentScanning/AssemblyTypeCheckerHelpers.cs +++ b/UnitystationLauncher/ContentScanning/AssemblyTypeCheckerHelpers.cs @@ -4,6 +4,7 @@ using ILVerify; using UnitystationLauncher.Infrastructure; using UnitystationLauncher.Models.ConfigFile; +using UnitystationLauncher.Models.ContentScanning; using UnitystationLauncher.Models.ContentScanning.ScanningTypes; // psst @@ -25,7 +26,7 @@ internal static Resolver CreateResolver(DirectoryInfo managedPath) return new(managedPath); } - internal static bool CheckVerificationResult(SandboxConfig loadedCfg, VerificationResult res, string name, MetadataReader reader, Action logErrors) + internal static bool CheckVerificationResult(SandboxConfig loadedCfg, VerificationResult res, string name, MetadataReader reader, Action scanLog) { if (loadedCfg.AllowedVerifierErrors.Contains(res.Code)) { @@ -49,7 +50,11 @@ internal static bool CheckVerificationResult(SandboxConfig loadedCfg, Verificati msg = $"{msg}, type: {type}"; } - logErrors.Invoke(msg); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = msg + }); return true; } diff --git a/UnitystationLauncher/ContentScanning/Scanners/ILScanner.cs b/UnitystationLauncher/ContentScanning/Scanners/ILScanner.cs index 52cbde71..bff255fd 100644 --- a/UnitystationLauncher/ContentScanning/Scanners/ILScanner.cs +++ b/UnitystationLauncher/ContentScanning/Scanners/ILScanner.cs @@ -7,15 +7,19 @@ using ILVerify; using UnitystationLauncher.Constants; using UnitystationLauncher.Models.ConfigFile; +using UnitystationLauncher.Models.ContentScanning; namespace UnitystationLauncher.ContentScanning.Scanners; internal static class ILScanner { internal static bool IsILValid(string name, IResolver resolver, PEReader peReader, - MetadataReader reader, Action info, Action logErrors, SandboxConfig loadedCfg) + MetadataReader reader, Action scanLog, SandboxConfig loadedCfg) { - info.Invoke($"{name}: Verifying IL..."); + scanLog.Invoke(new() + { + LogMessage = $"{name}: Verifying IL..." + }); Stopwatch sw = Stopwatch.StartNew(); ConcurrentBag bag = new(); @@ -24,14 +28,17 @@ internal static bool IsILValid(string name, IResolver resolver, PEReader peReade bool verifyErrors = false; foreach (VerificationResult res in bag) { - bool error = AssemblyTypeCheckerHelpers.CheckVerificationResult(loadedCfg, res, name, reader, logErrors); + bool error = AssemblyTypeCheckerHelpers.CheckVerificationResult(loadedCfg, res, name, reader, scanLog); if (error) { verifyErrors = true; } } - info.Invoke($"{name}: Verified IL in {sw.Elapsed.TotalMilliseconds}ms"); + scanLog.Invoke(new() + { + LogMessage = $"{name}: Verified IL in {sw.Elapsed.TotalMilliseconds}ms" + }); if (verifyErrors) { diff --git a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs index 07762048..5c63d7f6 100644 --- a/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs +++ b/UnitystationLauncher/GameCommunicationPipe/PipeHubBuildCommunication.cs @@ -3,7 +3,7 @@ using System.IO.Pipes; using System.Reactive.Concurrency; using System.Threading.Tasks; -using MessageBox.Avalonia.BaseWindows.Base; +using MsBox.Avalonia.Base; using ReactiveUI; using Serilog; using UnitystationLauncher.Infrastructure; @@ -60,14 +60,14 @@ public async Task StartServerPipe() { RxApp.MainThreadScheduler.ScheduleAsync(async (_, _) => { - IMsBoxWindow msgBox = MessageBoxBuilder.CreateMessageBox( + IMsBox msgBox = MessageBoxBuilder.CreateMessageBox( MessageBoxButtons.YesNo, string.Empty, $"would you like to add this Domain to The allowed domains to be opened In your browser, {requests[1]} " + @" Justification given by the Fork : " + requests[2]); - string response = await msgBox.Show(); + string response = await msgBox.ShowAsync(); Log.Information($"response {response}"); await _writer.WriteLineAsync(response == "No" ? false.ToString() : true.ToString()); await _writer.FlushAsync(); @@ -78,7 +78,7 @@ public async Task StartServerPipe() { RxApp.MainThreadScheduler.ScheduleAsync(async (_, _) => { - IMsBoxWindow msgBox = MessageBoxBuilder.CreateMessageBox( + IMsBox msgBox = MessageBoxBuilder.CreateMessageBox( MessageBoxButtons.YesNo, string.Empty, $"The build would like to send an API request to, {requests[1]} " + @" @@ -86,7 +86,7 @@ public async Task StartServerPipe() Justification given by the Fork : " + requests[2]); - string response = await msgBox.Show(); + string response = await msgBox.ShowAsync(); Log.Information($"response {response}"); await _writer.WriteLineAsync(response == "No" ? false.ToString() : true.ToString()); await _writer.FlushAsync(); @@ -97,7 +97,7 @@ public async Task StartServerPipe() { RxApp.MainThreadScheduler.ScheduleAsync(async (_, _) => { - IMsBoxWindow msgBox = MessageBoxBuilder.CreateMessageBox( + IMsBox msgBox = MessageBoxBuilder.CreateMessageBox( MessageBoxButtons.YesNo, string.Empty, @" Trusted mode automatically allows every API and open URL action to happen without prompt, this also enables the @@ -106,7 +106,7 @@ The main purpose of this Prompt is to allow the Variable viewer (Variable editin What follows is given by the build, we do not control what is written in the Following text So treat with caution and use your brain Justification : " + requests[1]); //TODO Add text - string response = await msgBox.Show(); + string response = await msgBox.ShowAsync(); Log.Information($"response {response}"); await _writer.WriteLineAsync(response == "No" ? false.ToString() : true.ToString()); await _writer.FlushAsync(); diff --git a/UnitystationLauncher/Infrastructure/MessageBoxBuilder.cs b/UnitystationLauncher/Infrastructure/MessageBoxBuilder.cs index aa55d97e..4534ffc1 100644 --- a/UnitystationLauncher/Infrastructure/MessageBoxBuilder.cs +++ b/UnitystationLauncher/Infrastructure/MessageBoxBuilder.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using MessageBox.Avalonia; -using MessageBox.Avalonia.BaseWindows.Base; -using MessageBox.Avalonia.DTO; -using MessageBox.Avalonia.Models; +using MsBox.Avalonia; +using MsBox.Avalonia.Base; +using MsBox.Avalonia.Dto; +using MsBox.Avalonia.Models; using UnitystationLauncher.Constants; using UnitystationLauncher.Models.Enums; @@ -10,9 +10,9 @@ namespace UnitystationLauncher.Infrastructure; public static class MessageBoxBuilder { - public static IMsBoxWindow CreateMessageBox(MessageBoxButtons buttonLayout, string header, string message) + public static IMsBox CreateMessageBox(MessageBoxButtons buttonLayout, string header, string message) { - IMsBoxWindow msgBox = MessageBoxManager.GetMessageBoxCustomWindow(new MessageBoxCustomParams + IMsBox msgBox = MessageBoxManager.GetMessageBoxCustom(new MessageBoxCustomParams { SystemDecorations = Avalonia.Controls.SystemDecorations.BorderOnly, WindowStartupLocation = Avalonia.Controls.WindowStartupLocation.CenterScreen, @@ -40,6 +40,11 @@ public static IMsBoxWindow CreateMessageBox(MessageBoxButtons buttonLayo new() { Name = MessageBoxResults.No }, new() { Name = MessageBoxResults.Cancel } }, + MessageBoxButtons.OpenLogFolderOk => new() + { + new() { Name = MessageBoxResults.OpenLogFolder }, + new() { Name = MessageBoxResults.Ok } + }, _ => new List() } }); diff --git a/UnitystationLauncher/Models/ContentScanning/ScanLog.cs b/UnitystationLauncher/Models/ContentScanning/ScanLog.cs new file mode 100644 index 00000000..596577ec --- /dev/null +++ b/UnitystationLauncher/Models/ContentScanning/ScanLog.cs @@ -0,0 +1,22 @@ +namespace UnitystationLauncher.Models.ContentScanning; + +public class ScanLog +{ + // Not 100% sure we need this, as this could just be replaced with a bool. + // However, I think it makes the code a bit easier to read to have a named thing so I did it anyways. + public enum LogType + { + Info, + Error + } + + /// + /// Used to know which log we need to write this to + /// + public LogType Type { get; init; } = LogType.Info; + + /// + /// Log message to be written + /// + public string LogMessage { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/UnitystationLauncher/Models/Enums/MessageBoxButtons.cs b/UnitystationLauncher/Models/Enums/MessageBoxButtons.cs index 3d7ebf0e..0f520aba 100644 --- a/UnitystationLauncher/Models/Enums/MessageBoxButtons.cs +++ b/UnitystationLauncher/Models/Enums/MessageBoxButtons.cs @@ -5,5 +5,6 @@ public enum MessageBoxButtons Ok, OkCancel, YesNo, - YesNoCancel + YesNoCancel, + OpenLogFolderOk } \ No newline at end of file diff --git a/UnitystationLauncher/Program.cs b/UnitystationLauncher/Program.cs index a9cf2ea8..1aca73cf 100644 --- a/UnitystationLauncher/Program.cs +++ b/UnitystationLauncher/Program.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Avalonia; using Avalonia.ReactiveUI; @@ -11,13 +12,35 @@ private static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() // Windows Specific - .With(new Win32PlatformOptions { UseDeferredRendering = false }) + .With(new Win32PlatformOptions + { + RenderingMode = new List + { + Win32RenderingMode.Wgl + } + }) // MacOS Specific, AvaloniaNativePlatformOptions is "OSX backend options, // and MacOSPlatformOptions is "OSX front-end options", no idea why they decided to do it that way. - .With(new AvaloniaNativePlatformOptions { UseGpu = true, UseDeferredRendering = false }) - .With(new MacOSPlatformOptions { ShowInDock = true }) + .With(new AvaloniaNativePlatformOptions + { + RenderingMode = new List + { + AvaloniaNativeRenderingMode.OpenGl + } + }) + .With(new MacOSPlatformOptions + { + ShowInDock = true + }) // Linux Specific - .With(new X11PlatformOptions { UseGpu = true, UseDeferredRendering = false }) + .With(new X11PlatformOptions + { + RenderingMode = new List + { + X11RenderingMode.Egl + } + }) .LogToTrace() - .UseReactiveUI(); + .UseReactiveUI() + .WithInterFont(); } diff --git a/UnitystationLauncher/Services/AssemblyTypeCheckerService.cs b/UnitystationLauncher/Services/AssemblyTypeCheckerService.cs index 52b1f08e..3356097f 100644 --- a/UnitystationLauncher/Services/AssemblyTypeCheckerService.cs +++ b/UnitystationLauncher/Services/AssemblyTypeCheckerService.cs @@ -44,11 +44,9 @@ public AssemblyTypeCheckerService(ICodeScanConfigService codeScanConfigService) /// /// /// - /// - /// + /// /// - public async Task CheckAssemblyTypesAsync(FileInfo diskPath, DirectoryInfo managedPath, List otherAssemblies, - Action infoAction, Action errorsAction) + public async Task CheckAssemblyTypesAsync(FileInfo diskPath, DirectoryInfo managedPath, List otherAssemblies, Action scanLog) { await using FileStream assembly = diskPath.OpenRead(); Stopwatch fullStopwatch = Stopwatch.StartNew(); @@ -62,14 +60,24 @@ public async Task CheckAssemblyTypesAsync(FileInfo diskPath, DirectoryInfo // Check for native code if (peReader.PEHeaders.CorHeader?.ManagedNativeHeaderDirectory is { Size: not 0 }) { - errorsAction.Invoke($"Assembly {asmName} contains native code."); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = $"Assembly {asmName} contains native code." + }); + return false; } // Verify the IL - if (ILScanner.IsILValid(asmName, resolver, peReader, reader, infoAction, errorsAction, await _config) == false) + if (ILScanner.IsILValid(asmName, resolver, peReader, reader, scanLog, await _config) == false) { - errorsAction.Invoke($"Assembly {asmName} Has invalid IL code"); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = $"Assembly {asmName} Has invalid IL code" + }); + return false; } @@ -79,8 +87,15 @@ public async Task CheckAssemblyTypesAsync(FileInfo diskPath, DirectoryInfo List types = reader.GetReferencedTypes(errors); List members = reader.GetReferencedMembers(errors); List<(MType type, MType parent, ArraySegment interfaceImpls)> inherited = reader.GetExternalInheritedTypes(errors); - infoAction.Invoke(errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}"); - infoAction.Invoke($"References loaded... {fullStopwatch.ElapsedMilliseconds}ms"); + + scanLog.Invoke(new() + { + LogMessage = errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}" + }); + scanLog.Invoke(new() + { + LogMessage = $"References loaded... {fullStopwatch.ElapsedMilliseconds}ms" + }); SandboxConfig loadedConfig = await _config; @@ -95,36 +110,76 @@ public async Task CheckAssemblyTypesAsync(FileInfo diskPath, DirectoryInfo errors.Add(new($"Access to type not allowed: {type} asmName {asmName}")); } - infoAction.Invoke(errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}"); - infoAction.Invoke($"Types... {fullStopwatch.ElapsedMilliseconds}ms"); + scanLog.Invoke(new() + { + LogMessage = errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}" + }); + scanLog.Invoke(new() + { + LogMessage = $"Types... {fullStopwatch.ElapsedMilliseconds}ms" + }); InheritanceScanner.CheckInheritance(loadedConfig, inherited, errors); - infoAction.Invoke(errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}"); - infoAction.Invoke($"Inheritance... {fullStopwatch.ElapsedMilliseconds}ms"); + scanLog.Invoke(new() + { + LogMessage = errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}" + }); + scanLog.Invoke(new() + { + LogMessage = $"Inheritance... {fullStopwatch.ElapsedMilliseconds}ms" + }); UnmanagedMethodScanner.CheckNoUnmanagedMethodDefs(reader, errors); - infoAction.Invoke(errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}"); - infoAction.Invoke($"Unmanaged methods... {fullStopwatch.ElapsedMilliseconds}ms"); + scanLog.Invoke(new() + { + LogMessage = errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}" + }); + scanLog.Invoke(new() + { + LogMessage = $"Unmanaged methods... {fullStopwatch.ElapsedMilliseconds}ms" + }); TypeAbuseScanner.CheckNoTypeAbuse(reader, errors); - infoAction.Invoke(errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}"); - infoAction.Invoke($"Type abuse... {fullStopwatch.ElapsedMilliseconds}ms"); + scanLog.Invoke(new() + { + LogMessage = errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}" + }); + scanLog.Invoke(new() + { + LogMessage = $"Type abuse... {fullStopwatch.ElapsedMilliseconds}ms" + }); MemberReferenceScanner.CheckMemberReferences(loadedConfig, members, errors); - infoAction.Invoke(errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}"); - infoAction.Invoke($"Member References... {fullStopwatch.ElapsedMilliseconds}ms"); + scanLog.Invoke(new() + { + LogMessage = errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}" + }); + scanLog.Invoke(new() + { + LogMessage = $"Member References... {fullStopwatch.ElapsedMilliseconds}ms" + }); errors = new(errors.OrderBy(x => x.Message)); foreach (SandboxError error in errors) { - errorsAction.Invoke($"Sandbox violation: {error.Message}"); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = $"Sandbox violation: {error.Message}" + }); } - infoAction.Invoke(errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}"); - infoAction.Invoke($"Checked assembly in {fullStopwatch.ElapsedMilliseconds}ms"); + scanLog.Invoke(new() + { + LogMessage = errors.IsEmpty ? "No sandbox violations." : $"Total violations: {errors.Count}" + }); + scanLog.Invoke(new() + { + LogMessage = $"Checked assembly in {fullStopwatch.ElapsedMilliseconds}ms" + }); + resolver.Dispose(); - peReader.Dispose(); return errors.IsEmpty; } } \ No newline at end of file diff --git a/UnitystationLauncher/Services/CodeScanService.cs b/UnitystationLauncher/Services/CodeScanService.cs index f64313be..b41d462a 100644 --- a/UnitystationLauncher/Services/CodeScanService.cs +++ b/UnitystationLauncher/Services/CodeScanService.cs @@ -7,6 +7,7 @@ using Serilog; using UnitystationLauncher.Constants; using UnitystationLauncher.Exceptions; +using UnitystationLauncher.Models.ContentScanning; using UnitystationLauncher.Models.Enums; using UnitystationLauncher.Services.Interface; @@ -30,7 +31,7 @@ public CodeScanService(IAssemblyTypeCheckerService assemblyTypeCheckerService, I _preferencesService = preferencesService; } - public async Task OnScanAsync(ZipArchive archive, string targetDirectory, string goodFileVersion, Action info, Action errors) + public async Task OnScanAsync(ZipArchive archive, string targetDirectory, string goodFileVersion, Action scanLog) { // TODO: Enable extraction cancelling DirectoryInfo root = new(_preferencesService.GetPreferences().InstallationPath); @@ -42,10 +43,18 @@ public async Task OnScanAsync(ZipArchive archive, string targetDirectory, try { DeleteContentsOfDirectory(processingDirectory); - info.Invoke("Copying files"); + + scanLog.Invoke(new() + { + LogMessage = "Copying files" + }); + CopyFilesRecursively(stagingDirectory.ToString(), processingDirectory.ToString()); - info.Invoke("Cleaning out Dlls and Executables"); + scanLog.Invoke(new() + { + LogMessage = "Cleaning out Dlls and Executables" + }); DeleteFilesWithExtension(processingDirectory.ToString(), ".exe"); DeleteFilesWithExtension(processingDirectory.ToString(), ".dll"); DeleteFilesWithExtension(processingDirectory.ToString(), ".so"); @@ -69,7 +78,11 @@ public async Task OnScanAsync(ZipArchive archive, string targetDirectory, { if (dataPath != null) { - errors.Invoke("oh God 2 Datapaths Exiting!!!"); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = "oh God 2 Datapaths Exiting!!!" + }); return false; } @@ -79,7 +92,11 @@ public async Task OnScanAsync(ZipArchive archive, string targetDirectory, if (dataPath == null) { - errors.Invoke("oh God NO Datapath Exiting!!!"); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = "oh God NO Datapath Exiting!!!" + }); return false; } stagingManaged = stagingDirectory.CreateSubdirectory(Path.Combine(dataPath.Name, FolderNames.Managed)); @@ -108,8 +125,11 @@ public async Task OnScanAsync(ZipArchive archive, string targetDirectory, DirectoryInfo goodFileCopy = new(GetManagedOnOS(goodFilePath)); - info.Invoke("Proceeding to scan folder"); - if (await ScanFolderAsync(dllDirectory, goodFileCopy, info, errors) == false) + scanLog.Invoke(new() + { + LogMessage = "Proceeding to scan folder" + }); + if (await ScanFolderAsync(dllDirectory, goodFileCopy, scanLog) == false) { try { @@ -134,11 +154,11 @@ public async Task OnScanAsync(ZipArchive archive, string targetDirectory, switch (_environmentService.GetCurrentEnvironment()) { case CurrentEnvironment.WindowsStandalone: - LocateWindowsExecutable(processingDirectory, stagingDirectory, dataPath, info, errors); + LocateWindowsExecutable(processingDirectory, stagingDirectory, dataPath, scanLog); break; case CurrentEnvironment.LinuxFlatpak: case CurrentEnvironment.LinuxStandalone: - LocateLinuxExecutable(processingDirectory, stagingDirectory, dataPath, info, errors); + LocateLinuxExecutable(processingDirectory, stagingDirectory, dataPath, scanLog); break; } @@ -154,7 +174,11 @@ public async Task OnScanAsync(ZipArchive archive, string targetDirectory, } catch (Exception e) { - errors.Invoke(" an Error happened > " + e); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = " an Error happened > " + e + }); DeleteContentsOfDirectory(processingDirectory); DeleteContentsOfDirectory(stagingDirectory); return false; @@ -163,7 +187,7 @@ public async Task OnScanAsync(ZipArchive archive, string targetDirectory, return true; } - private static void LocateWindowsExecutable(DirectoryInfo processingDirectory, DirectoryInfo stagingDirectory, DirectoryInfo dataPath, Action info, Action errors) + private static void LocateWindowsExecutable(DirectoryInfo processingDirectory, DirectoryInfo stagingDirectory, DirectoryInfo dataPath, Action scanLog) { FileInfo? exeRename = processingDirectory.GetFiles() .FirstOrDefault(x => x.Extension == ".exe" && x.Name != "UnityCrashHandler64.exe"); //TODO OS @@ -171,30 +195,46 @@ private static void LocateWindowsExecutable(DirectoryInfo processingDirectory, D if (exeRename?.Directory == null) { - errors.Invoke("no Executable found "); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = "no Executable found " + }); + DeleteContentsOfDirectory(processingDirectory); DeleteContentsOfDirectory(stagingDirectory); throw new CodeScanningException("No Windows executable found"); } - info.Invoke($"Found exeRename {exeRename}"); + scanLog.Invoke(new() + { + LogMessage = $"Found exeRename {exeRename}" + }); exeRename.MoveTo(Path.Combine(exeRename.Directory.ToString(), dataPath.Name.Replace("_Data", "") + ".exe")); } - private static void LocateLinuxExecutable(DirectoryInfo processingDirectory, DirectoryInfo stagingDirectory, DirectoryInfo dataPath, Action info, Action errors) + private static void LocateLinuxExecutable(DirectoryInfo processingDirectory, DirectoryInfo stagingDirectory, DirectoryInfo dataPath, Action scanLog) { FileInfo? executableRename = processingDirectory.GetFiles() .FirstOrDefault(x => x.Extension == ""); if (executableRename?.Directory == null) { - errors.Invoke("no Executable found "); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = "no Executable found " + }); + DeleteContentsOfDirectory(processingDirectory); DeleteContentsOfDirectory(stagingDirectory); throw new CodeScanningException("No Linux executable found"); } - info.Invoke($"Found ExecutableRename {executableRename}"); + scanLog.Invoke(new() + { + LogMessage = $"Found ExecutableRename {executableRename}" + }); executableRename.MoveTo(Path.Combine(executableRename.Directory.ToString(), dataPath.Name.Replace("_Data", "") + "")); } @@ -217,12 +257,14 @@ private string GetManagedOnOS(string goodFiles) - private async Task ScanFolderAsync(DirectoryInfo @unsafe, DirectoryInfo saveFiles, Action info, Action errors) + private async Task ScanFolderAsync(DirectoryInfo @unsafe, DirectoryInfo saveFiles, Action scanLog) { List goodFiles = saveFiles.GetFiles().Select(x => x.Name).ToList(); - info.Invoke("Provided files " + string.Join(",", goodFiles)); - + scanLog.Invoke(new() + { + LogMessage = "Provided files " + string.Join(",", goodFiles) + }); CopyFilesRecursively(saveFiles.ToString(), @unsafe.ToString()); FileInfo[] files = @unsafe.GetFiles(); @@ -241,21 +283,32 @@ private async Task ScanFolderAsync(DirectoryInfo @unsafe, DirectoryInfo sa { if (goodFiles.Contains(file.Name)) continue; - info.Invoke("Scanning " + file.Name); + scanLog.Invoke(new() + { + LogMessage = "Scanning " + file.Name + }); try { List listy = multiAssemblyReference.ToList(); listy.Remove(Path.GetFileNameWithoutExtension(file.Name)); - if (await _assemblyTypeCheckerService.CheckAssemblyTypesAsync(file, @unsafe, listy, info, errors) == false) + if (await _assemblyTypeCheckerService.CheckAssemblyTypesAsync(file, @unsafe, listy, scanLog) == false) { - errors.Invoke($"{file.Name} Failed scanning Cancelling"); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = $"{file.Name} Failed scanning Cancelling" + }); return false; } } catch (Exception e) { - errors.Invoke(" Failed scan due to error of " + e); + scanLog.Invoke(new() + { + Type = ScanLog.LogType.Error, + LogMessage = " Failed scan due to error of " + e + }); return false; } } diff --git a/UnitystationLauncher/Services/InstallationService.cs b/UnitystationLauncher/Services/InstallationService.cs index 90040184..7e1ff3d5 100644 --- a/UnitystationLauncher/Services/InstallationService.cs +++ b/UnitystationLauncher/Services/InstallationService.cs @@ -6,18 +6,23 @@ using System.Linq; using System.Net.Http; using System.Reactive.Concurrency; +using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using Humanizer; using Humanizer.Bytes; using Mono.Unix; +using MsBox.Avalonia.Base; using ReactiveUI; using Serilog; +using UnitystationLauncher.Constants; using UnitystationLauncher.Exceptions; using UnitystationLauncher.Infrastructure; using UnitystationLauncher.Models; using UnitystationLauncher.Models.Api; using UnitystationLauncher.Models.ConfigFile; +using UnitystationLauncher.Models.ContentScanning; using UnitystationLauncher.Models.Enums; using UnitystationLauncher.Services.Interface; @@ -454,30 +459,31 @@ private async Task StartDownloadAsync(Download download) private async Task ExtractAndScan(Download download, ProgressStream progressStream) { - // TODO: Display infoList and errorList in the UI. - List infoList = new(); - List errorList = new(); + List scanLogs = []; Log.Information("Extracting..."); try { ZipArchive archive = new(progressStream); - //TODO UI - void Info(string log) + void ScanLogs(ScanLog log) { - Log.Information(log); - infoList.Add(log); - } + switch (log.Type) + { + case ScanLog.LogType.Info: + Log.Information(log.LogMessage); + break; + case ScanLog.LogType.Error: + Log.Error(log.LogMessage); + break; + default: // should never happen, Rider complains if we don't cover it though + return; + } - void Errors(string log) - { - Log.Error(log); - errorList.Add(log); + scanLogs.Add(log); } download.DownloadState = DownloadState.Scanning; - bool scanTask = await _codeScanService.OnScanAsync(archive, download.InstallPath, download.GoodFileVersion, - Info, Errors); + bool scanTask = await _codeScanService.OnScanAsync(archive, download.InstallPath, download.GoodFileVersion, ScanLogs); if (scanTask) { @@ -498,12 +504,27 @@ void Errors(string log) } else { - string jsonString = JsonSerializer.Serialize(errorList); - string filePath = Path.Combine(_preferencesService.GetPreferences().InstallationPath, "CodeScanErrors.json"); + string jsonString = JsonSerializer.Serialize(scanLogs, new JsonSerializerOptions + { + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + } + }); + string logFolder = _preferencesService.GetPreferences().InstallationPath; + string filePath = Path.Combine(logFolder, "CodeScanErrors.json"); await File.WriteAllTextAsync(filePath, jsonString); - //TODO UI + StringBuilder sb = new(); + sb.AppendLine($"Security scan failed for: {download.ForkName} {download.BuildVersion}"); + sb.AppendLine(scanLogs.LastOrDefault(l => l.LogMessage.Contains("Total violations"))?.LogMessage ?? string.Empty); + sb.AppendLine($"Scan log written to: {filePath}"); + sb.AppendLine("Please report this issue to the fork developers."); + + // needs to be run in the main thread or it will fail + RxApp.MainThreadScheduler.ScheduleAsync((_, _) => ShowScanFailPopUp(sb.ToString(), logFolder)); + Log.Error($"Scan failed, saved log to file: {filePath}"); download.DownloadState = DownloadState.Failed; } @@ -515,6 +536,28 @@ void Errors(string log) } } + private static async Task ShowScanFailPopUp(string message, string logFolder) + { + IMsBox msgBox = MessageBoxBuilder.CreateMessageBox( + MessageBoxButtons.OpenLogFolderOk, + string.Empty, + message); + + string result = await msgBox.ShowAsync(); // Doesn't need to be awaited + + if (result == MessageBoxResults.OpenLogFolder && Directory.Exists(logFolder)) + { + ProcessStartInfo psi = new() + { + FileName = logFolder, + UseShellExecute = true, + Verb = "open" + }; + + Process.Start(psi); + } + } + private static IDisposable LogProgress(ProgressStream progressStream, Download download) { long lastPosition = 0L; diff --git a/UnitystationLauncher/Services/Interface/IAssemblyTypeCheckerService.cs b/UnitystationLauncher/Services/Interface/IAssemblyTypeCheckerService.cs index e4672d95..7aaed41f 100644 --- a/UnitystationLauncher/Services/Interface/IAssemblyTypeCheckerService.cs +++ b/UnitystationLauncher/Services/Interface/IAssemblyTypeCheckerService.cs @@ -2,10 +2,11 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using UnitystationLauncher.Models.ContentScanning; namespace UnitystationLauncher.Services.Interface; public interface IAssemblyTypeCheckerService { - public Task CheckAssemblyTypesAsync(FileInfo diskPath, DirectoryInfo managedPath, List otherAssemblies, Action infoAction, Action errorsAction); + public Task CheckAssemblyTypesAsync(FileInfo diskPath, DirectoryInfo managedPath, List otherAssemblies, Action scanLog); } \ No newline at end of file diff --git a/UnitystationLauncher/Services/Interface/ICodeScanService.cs b/UnitystationLauncher/Services/Interface/ICodeScanService.cs index 5cf109a5..c1663d1a 100644 --- a/UnitystationLauncher/Services/Interface/ICodeScanService.cs +++ b/UnitystationLauncher/Services/Interface/ICodeScanService.cs @@ -1,10 +1,11 @@ using System; using System.IO.Compression; using System.Threading.Tasks; +using UnitystationLauncher.Models.ContentScanning; namespace UnitystationLauncher.Services.Interface; public interface ICodeScanService { - public Task OnScanAsync(ZipArchive archive, string targetDirectory, string goodFileVersion, Action info, Action errors); + public Task OnScanAsync(ZipArchive archive, string targetDirectory, string goodFileVersion, Action scanLog); } \ No newline at end of file diff --git a/UnitystationLauncher/UnitystationLauncher.csproj b/UnitystationLauncher/UnitystationLauncher.csproj index f993f2c2..ca65b505 100644 --- a/UnitystationLauncher/UnitystationLauncher.csproj +++ b/UnitystationLauncher/UnitystationLauncher.csproj @@ -5,6 +5,7 @@ latest enable enable + false Debug;Release x64 StationHub @@ -62,22 +63,24 @@ - - - - - - - - - + + + + + + + + + + + - - + + - - + + diff --git a/UnitystationLauncher/ViewLocator.cs b/UnitystationLauncher/ViewLocator.cs index 1f0825b1..bd3fc755 100644 --- a/UnitystationLauncher/ViewLocator.cs +++ b/UnitystationLauncher/ViewLocator.cs @@ -10,10 +10,13 @@ namespace UnitystationLauncher { public class ViewLocator : IDataTemplate { - public bool SupportsRecycling => false; - - public IControl Build(object data) + public Control Build(object? data) { + if (data == null) + { + throw new ArgumentNullException(nameof(data), "data is null"); + } + string viewName = data.GetType().FullName!.Replace("ViewModel", "View"); Type? type = Type.GetType(viewName); @@ -25,7 +28,7 @@ public IControl Build(object data) return new TextBlock { Text = "Not Found: " + viewName }; } - public bool Match(object data) + public bool Match(object? data) { return data is ViewModelBase; } diff --git a/UnitystationLauncher/ViewModels/BlogPostViewModel.cs b/UnitystationLauncher/ViewModels/BlogPostViewModel.cs index 63df1056..a2956c42 100644 --- a/UnitystationLauncher/ViewModels/BlogPostViewModel.cs +++ b/UnitystationLauncher/ViewModels/BlogPostViewModel.cs @@ -57,11 +57,11 @@ public BlogPostViewModel(string title, string postLink, string postSummary, Date } } - private void OpenLink() + public bool OpenLink() { if (string.IsNullOrWhiteSpace(PostLink)) { - return; + return false; } ProcessStartInfo psi = new() @@ -70,6 +70,8 @@ private void OpenLink() UseShellExecute = true }; Process.Start(psi); + + return true; } public override void Refresh() diff --git a/UnitystationLauncher/ViewModels/ChangeViewModel.cs b/UnitystationLauncher/ViewModels/ChangeViewModel.cs index 7fd2cb5a..6c30539a 100644 --- a/UnitystationLauncher/ViewModels/ChangeViewModel.cs +++ b/UnitystationLauncher/ViewModels/ChangeViewModel.cs @@ -5,10 +5,10 @@ namespace UnitystationLauncher.ViewModels; public class ChangeViewModel : ViewModelBase { - private string Type { get; } - private string Description { get; } - private string AuthorAndPr { get; } - private string? PrUrl { get; } + public string Type { get; } + public string Description { get; } + public string AuthorAndPr { get; } + public string? PrUrl { get; } public ChangeViewModel(Change change) { @@ -18,7 +18,7 @@ public ChangeViewModel(Change change) PrUrl = change.PrUrl; } - private void OnClick() + public bool OnClick() { if (PrUrl != null) { @@ -29,7 +29,10 @@ private void OnClick() }; Process.Start(psi); + return true; } + + return false; } public override void Refresh() diff --git a/UnitystationLauncher/ViewModels/InstallationsPanelViewModel.cs b/UnitystationLauncher/ViewModels/InstallationsPanelViewModel.cs index f9b6564b..9f8a6428 100644 --- a/UnitystationLauncher/ViewModels/InstallationsPanelViewModel.cs +++ b/UnitystationLauncher/ViewModels/InstallationsPanelViewModel.cs @@ -6,7 +6,7 @@ using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Threading.Tasks; -using MessageBox.Avalonia.BaseWindows.Base; +using MsBox.Avalonia.Base; using Serilog; using UnitystationLauncher.Constants; using UnitystationLauncher.Infrastructure; @@ -68,10 +68,10 @@ private async Task OnAutoRemoveChangedAsync() { if (AutoRemove) { - IMsBoxWindow msgBox = MessageBoxBuilder.CreateMessageBox(MessageBoxButtons.YesNo, + IMsBox msgBox = MessageBoxBuilder.CreateMessageBox(MessageBoxButtons.YesNo, "Are you sure?", "This will remove older installations from disk. Proceed?"); - string response = await msgBox.Show(); + string response = await msgBox.ShowAsync(); if (response.Equals(MessageBoxResults.Yes)) { SaveChoice(); diff --git a/UnitystationLauncher/ViewModels/PreferencesPanelViewModel.cs b/UnitystationLauncher/ViewModels/PreferencesPanelViewModel.cs index 84d9dc3d..0bc7a34d 100644 --- a/UnitystationLauncher/ViewModels/PreferencesPanelViewModel.cs +++ b/UnitystationLauncher/ViewModels/PreferencesPanelViewModel.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using MessageBox.Avalonia.BaseWindows.Base; +using MsBox.Avalonia.Base; using ReactiveUI; using Serilog; using UnitystationLauncher.Constants; @@ -38,13 +38,13 @@ public async Task SetInstallationPathAsync(string path) (bool isValidPath, string invalidReason) = _installationService.IsValidInstallationBasePath(path); if (isValidPath) { - IMsBoxWindow msgBox = MessageBoxBuilder.CreateMessageBox( + IMsBox msgBox = MessageBoxBuilder.CreateMessageBox( MessageBoxButtons.YesNo, string.Empty, $"Would you like to move your old installations to the new location?\n " + $"New installation path: {path}"); - string response = await msgBox.Show(); + string response = await msgBox.ShowAsync(); Log.Information($"Move installations? {response}"); if (response.Equals(MessageBoxResults.Yes)) { @@ -52,7 +52,7 @@ public async Task SetInstallationPathAsync(string path) if (!success) { await MessageBoxBuilder.CreateMessageBox(MessageBoxButtons.Ok, "Error moving installation", - "Could not move existing install.").Show(); + "Could not move existing install.").ShowAsync(); return; } @@ -64,7 +64,7 @@ await MessageBoxBuilder.CreateMessageBox(MessageBoxButtons.Ok, "Error moving ins else { Log.Warning($"Invalid directory as installation path, ignoring change: {path}"); - await MessageBoxBuilder.CreateMessageBox(MessageBoxButtons.Ok, "Invalid installation path", invalidReason).Show(); + await MessageBoxBuilder.CreateMessageBox(MessageBoxButtons.Ok, "Invalid installation path", invalidReason).ShowAsync(); } } diff --git a/UnitystationLauncher/ViewModels/ServersPanelViewModel.cs b/UnitystationLauncher/ViewModels/ServersPanelViewModel.cs index 55df8874..d4c067e2 100644 --- a/UnitystationLauncher/ViewModels/ServersPanelViewModel.cs +++ b/UnitystationLauncher/ViewModels/ServersPanelViewModel.cs @@ -122,7 +122,7 @@ private async Task DownloadServer(Server server) if (download == null) { _ = MessageBoxBuilder.CreateMessageBox(MessageBoxButtons.Ok, "Problem downloading server", - downloadFailReason).Show(); + downloadFailReason).ShowAsync(); return; } diff --git a/UnitystationLauncher/Views/BlogPostView.axaml b/UnitystationLauncher/Views/BlogPostView.axaml index bbf4e7bc..9eb78fcf 100644 --- a/UnitystationLauncher/Views/BlogPostView.axaml +++ b/UnitystationLauncher/Views/BlogPostView.axaml @@ -11,7 +11,7 @@ -