commit 846ac96c72cf5f3835bd72991f4e148682ec55e9 Author: Omar Date: Thu Oct 21 22:22:45 2021 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8afdcb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,454 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a1033e Binary files /dev/null and b/README.md differ diff --git a/assets/AllowExecute.png b/assets/AllowExecute.png new file mode 100644 index 0000000..6e23f55 Binary files /dev/null and b/assets/AllowExecute.png differ diff --git a/assets/Fedora-34.png b/assets/Fedora-34.png new file mode 100644 index 0000000..72ba610 Binary files /dev/null and b/assets/Fedora-34.png differ diff --git a/assets/OpenWithRunExecutable.png b/assets/OpenWithRunExecutable.png new file mode 100644 index 0000000..9558f9b Binary files /dev/null and b/assets/OpenWithRunExecutable.png differ diff --git a/assets/Ubuntu-18.04.png b/assets/Ubuntu-18.04.png new file mode 100644 index 0000000..c896f67 Binary files /dev/null and b/assets/Ubuntu-18.04.png differ diff --git a/assets/Windows.png b/assets/Windows.png new file mode 100644 index 0000000..112910d Binary files /dev/null and b/assets/Windows.png differ diff --git a/assets/nxDumpFuseLogo.png b/assets/nxDumpFuseLogo.png new file mode 100644 index 0000000..5243339 Binary files /dev/null and b/assets/nxDumpFuseLogo.png differ diff --git a/src/nxDumpFuse.sln b/src/nxDumpFuse.sln new file mode 100644 index 0000000..bd267bd --- /dev/null +++ b/src/nxDumpFuse.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31815.197 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nxDumpFuse", "nxDumpFuse\nxDumpFuse.csproj", "{0EA7C0CB-5E34-4F90-96EE-BBF7831323F5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0EA7C0CB-5E34-4F90-96EE-BBF7831323F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EA7C0CB-5E34-4F90-96EE-BBF7831323F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EA7C0CB-5E34-4F90-96EE-BBF7831323F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EA7C0CB-5E34-4F90-96EE-BBF7831323F5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {116E3613-4814-428B-8203-E2EE87688F14} + EndGlobalSection +EndGlobal diff --git a/src/nxDumpFuse/App.axaml b/src/nxDumpFuse/App.axaml new file mode 100644 index 0000000..bbfa9b7 --- /dev/null +++ b/src/nxDumpFuse/App.axaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/nxDumpFuse/App.axaml.cs b/src/nxDumpFuse/App.axaml.cs new file mode 100644 index 0000000..5e6ce1d --- /dev/null +++ b/src/nxDumpFuse/App.axaml.cs @@ -0,0 +1,38 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using nxDumpFuse.Extensions; +using nxDumpFuse.Interfaces; +using nxDumpFuse.ViewModels; +using nxDumpFuse.Views; +using Splat; + +namespace nxDumpFuse +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + DataContext = GetRequiredService(); + desktop.MainWindow = new MainWindow + { + DataContext = DataContext + }; + } + + var theme = new Avalonia.Themes.Default.DefaultTheme(); + theme.TryGetResource("Button", out _); + + base.OnFrameworkInitializationCompleted(); + } + + private static T GetRequiredService() => Locator.Current.GetRequiredService(); + } +} diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Black.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Black.ttf new file mode 100644 index 0000000..fe62ead Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Black.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-BlackItalic.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-BlackItalic.ttf new file mode 100644 index 0000000..a412dd9 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-BlackItalic.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Bold.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Bold.ttf new file mode 100644 index 0000000..c790e04 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Bold.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-BoldItalic.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-BoldItalic.ttf new file mode 100644 index 0000000..0878199 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-BoldItalic.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-ExtraLight.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-ExtraLight.ttf new file mode 100644 index 0000000..ca7bce8 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-ExtraLight.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-ExtraLightItalic.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-ExtraLightItalic.ttf new file mode 100644 index 0000000..c33f284 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-ExtraLightItalic.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Italic.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Italic.ttf new file mode 100644 index 0000000..91a2a44 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Italic.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Light.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Light.ttf new file mode 100644 index 0000000..7af27ce Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Light.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-LightItalic.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-LightItalic.ttf new file mode 100644 index 0000000..330c523 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-LightItalic.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Medium.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Medium.ttf new file mode 100644 index 0000000..6450399 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Medium.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-MediumItalic.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-MediumItalic.ttf new file mode 100644 index 0000000..50cd774 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-MediumItalic.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Regular.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Regular.ttf new file mode 100644 index 0000000..3563e73 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-Regular.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-SemiBold.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-SemiBold.ttf new file mode 100644 index 0000000..526be56 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-SemiBold.ttf differ diff --git a/src/nxDumpFuse/Assets/Fonts/SourceCodePro-SemiBoldItalic.ttf b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-SemiBoldItalic.ttf new file mode 100644 index 0000000..689c138 Binary files /dev/null and b/src/nxDumpFuse/Assets/Fonts/SourceCodePro-SemiBoldItalic.ttf differ diff --git a/src/nxDumpFuse/Assets/github_icon.png b/src/nxDumpFuse/Assets/github_icon.png new file mode 100644 index 0000000..c9904ee Binary files /dev/null and b/src/nxDumpFuse/Assets/github_icon.png differ diff --git a/src/nxDumpFuse/Assets/nxDumpFuse.ico b/src/nxDumpFuse/Assets/nxDumpFuse.ico new file mode 100644 index 0000000..3093cfc Binary files /dev/null and b/src/nxDumpFuse/Assets/nxDumpFuse.ico differ diff --git a/src/nxDumpFuse/DependencyInjection/Bootstrapper.cs b/src/nxDumpFuse/DependencyInjection/Bootstrapper.cs new file mode 100644 index 0000000..7b030fb --- /dev/null +++ b/src/nxDumpFuse/DependencyInjection/Bootstrapper.cs @@ -0,0 +1,13 @@ +using Splat; + +namespace nxDumpFuse.DependencyInjection +{ + public static class Bootstrapper + { + public static void Register(IMutableDependencyResolver services, IReadonlyDependencyResolver resolver) + { + DialogServiceBootstrapper.RegisterDialogService(services, resolver); + ViewModelBootstrapper.RegisterViewModels(services, resolver); + } + } +} diff --git a/src/nxDumpFuse/DependencyInjection/DialogServiceBootstrapper.cs b/src/nxDumpFuse/DependencyInjection/DialogServiceBootstrapper.cs new file mode 100644 index 0000000..3bce121 --- /dev/null +++ b/src/nxDumpFuse/DependencyInjection/DialogServiceBootstrapper.cs @@ -0,0 +1,18 @@ +using nxDumpFuse.Extensions; +using nxDumpFuse.Interfaces; +using nxDumpFuse.Model; +using nxDumpFuse.Services; +using Splat; + +namespace nxDumpFuse.DependencyInjection +{ + public static class DialogServiceBootstrapper + { + public static void RegisterDialogService(IMutableDependencyResolver services, IReadonlyDependencyResolver resolver) + { + services.RegisterLazySingleton((() => new MainWindowProvider())); + services.RegisterLazySingleton(() => new DialogService( + resolver.GetRequiredService())); + } + } +} diff --git a/src/nxDumpFuse/DependencyInjection/ViewModelBootstrapper.cs b/src/nxDumpFuse/DependencyInjection/ViewModelBootstrapper.cs new file mode 100644 index 0000000..3745fac --- /dev/null +++ b/src/nxDumpFuse/DependencyInjection/ViewModelBootstrapper.cs @@ -0,0 +1,22 @@ +using nxDumpFuse.Extensions; +using nxDumpFuse.Interfaces; +using nxDumpFuse.ViewModels; +using Splat; + +namespace nxDumpFuse.DependencyInjection +{ + public static class ViewModelBootstrapper + { + public static void RegisterViewModels(IMutableDependencyResolver services, IReadonlyDependencyResolver resolver) + { + services.RegisterLazySingleton(() => new MainWindowViewModel( + resolver.GetRequiredService(), + resolver.GetRequiredService() + )); + + services.RegisterLazySingleton(() => new FuseViewModel( + resolver.GetRequiredService())); + services.RegisterLazySingleton(() => new AboutViewModel()); + } + } +} diff --git a/src/nxDumpFuse/Events/EventHandlers.cs b/src/nxDumpFuse/Events/EventHandlers.cs new file mode 100644 index 0000000..75a6060 --- /dev/null +++ b/src/nxDumpFuse/Events/EventHandlers.cs @@ -0,0 +1,11 @@ +using nxDumpFuse.Model; + +namespace nxDumpFuse.Events +{ + public class EventHandlers + { + public delegate void FuseUpdateEventHandler(FuseUpdateInfo fuseUpdateInfo); + + public delegate void FuseSimpleLogEventHandler(FuseSimpleLog log); + } +} diff --git a/src/nxDumpFuse/Extensions/ReadonlyDependencyResolverExtensions.cs b/src/nxDumpFuse/Extensions/ReadonlyDependencyResolverExtensions.cs new file mode 100644 index 0000000..e26eac0 --- /dev/null +++ b/src/nxDumpFuse/Extensions/ReadonlyDependencyResolverExtensions.cs @@ -0,0 +1,18 @@ +using System; +using Splat; + +namespace nxDumpFuse.Extensions +{ + public static class ReadonlyDependencyResolverExtensions + { + public static TService GetRequiredService(this IReadonlyDependencyResolver resolver) + { + var service = resolver.GetService(); + if (service == null) + { + throw new InvalidOperationException($"Failed to resolve object of type {typeof(TService)}"); + } + return service; + } + } +} diff --git a/src/nxDumpFuse/FluentWindow.cs b/src/nxDumpFuse/FluentWindow.cs new file mode 100644 index 0000000..cf6c832 --- /dev/null +++ b/src/nxDumpFuse/FluentWindow.cs @@ -0,0 +1,45 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Platform; +using Avalonia.Styling; + +namespace nxDumpFuse +{ + public class FluentWindow : Window, IStyleable + { + Type IStyleable.StyleKey => typeof(Window); + + public FluentWindow() + { + ExtendClientAreaToDecorationsHint = true; + ExtendClientAreaTitleBarHeightHint = -1; + + TransparencyLevelHint = WindowTransparencyLevel.AcrylicBlur; + + this.GetObservable(WindowStateProperty) + .Subscribe(x => + { + PseudoClasses.Set(":maximized", x == WindowState.Maximized); + PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); + }); + + this.GetObservable(IsExtendedIntoWindowDecorationsProperty) + .Subscribe(x => + { + if (x) return; + SystemDecorations = SystemDecorations.Full; + TransparencyLevelHint = WindowTransparencyLevel.Blur; + }); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + ExtendClientAreaChromeHints = + ExtendClientAreaChromeHints.PreferSystemChrome | + ExtendClientAreaChromeHints.OSXThickTitleBar; + } + } +} \ No newline at end of file diff --git a/src/nxDumpFuse/Interfaces/IAboutViewModel.cs b/src/nxDumpFuse/Interfaces/IAboutViewModel.cs new file mode 100644 index 0000000..0685b87 --- /dev/null +++ b/src/nxDumpFuse/Interfaces/IAboutViewModel.cs @@ -0,0 +1,6 @@ +namespace nxDumpFuse.Interfaces +{ + public interface IAboutViewModel + { + } +} diff --git a/src/nxDumpFuse/Interfaces/IDialogService.cs b/src/nxDumpFuse/Interfaces/IDialogService.cs new file mode 100644 index 0000000..e4f5d51 --- /dev/null +++ b/src/nxDumpFuse/Interfaces/IDialogService.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using Avalonia.Controls; +using nxDumpFuse.ViewModels; + +namespace nxDumpFuse.Interfaces +{ + public interface IDialogService + { + Task ShowOpenFileDialogAsync(string title, FileDialogFilter filter); + + Task ShowOpenFolderDialogAsync(string title); + } +} diff --git a/src/nxDumpFuse/Interfaces/IFuseViewModel.cs b/src/nxDumpFuse/Interfaces/IFuseViewModel.cs new file mode 100644 index 0000000..addcd84 --- /dev/null +++ b/src/nxDumpFuse/Interfaces/IFuseViewModel.cs @@ -0,0 +1,6 @@ +namespace nxDumpFuse.Interfaces +{ + public interface IFuseViewModel + { + } +} diff --git a/src/nxDumpFuse/Interfaces/IMainWindowProvider.cs b/src/nxDumpFuse/Interfaces/IMainWindowProvider.cs new file mode 100644 index 0000000..98a1de5 --- /dev/null +++ b/src/nxDumpFuse/Interfaces/IMainWindowProvider.cs @@ -0,0 +1,9 @@ +using Avalonia.Controls; + +namespace nxDumpFuse.Interfaces +{ + public interface IMainWindowProvider + { + Window GetMainWindow(); + } +} diff --git a/src/nxDumpFuse/Interfaces/IMainWindowViewModel.cs b/src/nxDumpFuse/Interfaces/IMainWindowViewModel.cs new file mode 100644 index 0000000..b10a1a0 --- /dev/null +++ b/src/nxDumpFuse/Interfaces/IMainWindowViewModel.cs @@ -0,0 +1,6 @@ +namespace nxDumpFuse.Interfaces +{ + public interface IMainWindowViewModel + { + } +} diff --git a/src/nxDumpFuse/Launchers/Linux/Ubuntu/Run Executable.desktop b/src/nxDumpFuse/Launchers/Linux/Ubuntu/Run Executable.desktop new file mode 100644 index 0000000..effcc50 --- /dev/null +++ b/src/nxDumpFuse/Launchers/Linux/Ubuntu/Run Executable.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Terminal=false +Name=Run Executable +Comment=Run Executable +Icon=application.png +Exec="/bin/sh" -c %f +Categories=Application; \ No newline at end of file diff --git a/src/nxDumpFuse/Launchers/Linux/nxDumpFuse.sh b/src/nxDumpFuse/Launchers/Linux/nxDumpFuse.sh new file mode 100644 index 0000000..1ab6c68 --- /dev/null +++ b/src/nxDumpFuse/Launchers/Linux/nxDumpFuse.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./nxDumpFuse \ No newline at end of file diff --git a/src/nxDumpFuse/MainWindowProvider.cs b/src/nxDumpFuse/MainWindowProvider.cs new file mode 100644 index 0000000..2e5f6ed --- /dev/null +++ b/src/nxDumpFuse/MainWindowProvider.cs @@ -0,0 +1,17 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using nxDumpFuse.Interfaces; + +namespace nxDumpFuse +{ + public class MainWindowProvider : IMainWindowProvider + { + public Window GetMainWindow() + { + var lifetime = (IClassicDesktopStyleApplicationLifetime) Application.Current.ApplicationLifetime; + + return lifetime.MainWindow; + } + } +} diff --git a/src/nxDumpFuse/Model/Fuse.cs b/src/nxDumpFuse/Model/Fuse.cs new file mode 100644 index 0000000..0b687d4 --- /dev/null +++ b/src/nxDumpFuse/Model/Fuse.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using nxDumpFuse.Events; + +namespace nxDumpFuse.Model +{ + public class Fuse + { + private const string XciExt = "xci"; + private const string NspExt = "nsp"; + private readonly CancellationTokenSource _cts; + private readonly string _inputFilePath; + private readonly string _outputDir; + private string _outputFilePath = string.Empty; + + public Fuse(string inputFilePath, string outputDir) + { + _inputFilePath = inputFilePath; + _outputDir = outputDir; + _cts = new CancellationTokenSource(); + } + + public event EventHandlers.FuseUpdateEventHandler? FuseUpdateEvent; + + public event EventHandlers.FuseSimpleLogEventHandler? FuseSimpleLogEvent; + + protected virtual void OnFuseUpdate(FuseUpdateInfo fuseUpdateInfo) + { + FuseUpdateEvent?.Invoke(fuseUpdateInfo); + } + + protected virtual void OnFuseSimpleLogEvent(FuseSimpleLog log) + { + FuseSimpleLogEvent?.Invoke(log); + } + + private void Log(FuseSimpleLogType type, string message) + { + OnFuseSimpleLogEvent(new FuseSimpleLog(type, DateTime.Now, message)); + } + + public void FuseDump() + { + if (string.IsNullOrEmpty(_inputFilePath)) + { + Log(FuseSimpleLogType.Error, "Input File cannot be empty"); + return; + } + + if (string.IsNullOrEmpty(_outputDir)) + { + Log(FuseSimpleLogType.Error, "Output Directory cannot be empty"); + return; + } + Log(FuseSimpleLogType.Information, "Fuse Started"); + GetOutputFilePath(_inputFilePath, _outputDir); + if (string.IsNullOrEmpty(_outputFilePath)) + { + Log(FuseSimpleLogType.Error, "Output path was null"); + return; + } + var inputFiles = GetInputFiles(); + if (inputFiles.Length == 0) + { + Log(FuseSimpleLogType.Error, "No input files found"); + return; + } + FuseFiles(inputFiles, _outputFilePath); + } + + private void GetOutputFilePath(string inputFilePath, string outputDir) + { + + string? fileName = Path.GetFileName(inputFilePath); + if (Path.HasExtension(fileName)) + { + string ext = Path.GetExtension(fileName).Replace(".", string.Empty); + List split = fileName.Split(".").ToList(); + + if (int.TryParse(ext, out _) && split.Count >= 3 && split[^2] == XciExt) // .xci.00 + { + _outputFilePath = Path.Join(outputDir, $"{string.Join("", split.Take(split.Count - 2))}.{XciExt}"); + } + else if (int.TryParse(ext, out _) && split.Count >= 3 && split[^2] == NspExt) // .nsp.00 + _outputFilePath = Path.Join(outputDir, $"{string.Join("", split.Take(split.Count - 2))}.{NspExt}"); + else switch (ext.Substring(0, 2)) + { + // .xc0 + case "xc" when int.TryParse(ext.Substring(ext.Length - 1, 1), out _): + _outputFilePath = Path.Join(outputDir, $"{Path.GetFileNameWithoutExtension(fileName)}.{XciExt}"); + break; + // .ns0 + case "ns" when int.TryParse(ext.Substring(ext.Length - 1, 1), out _): + _outputFilePath = Path.Join(outputDir, $"{Path.GetFileNameWithoutExtension(fileName)}.{NspExt}"); + break; + } + } + else // dir/00 + { + var inputDir = new FileInfo(inputFilePath).Directory?.Name; + if (string.IsNullOrEmpty(inputDir)) + { + inputDir = Path.GetPathRoot(_inputFilePath); + _outputFilePath = $"{inputDir}.{NspExt}"; + return; + } + var inputDirSplit = inputDir.Split("."); + _outputFilePath = Path.Join(outputDir, inputDirSplit.Length == 1 + ? $"{inputDir}.{NspExt}" + : $"{string.Join("", (inputDirSplit).Take(inputDirSplit.Length - 1))}.{NspExt}"); + } + } + + private async void FuseFiles(string[] inputFiles, string outputFilePath) + { + var buffer = new byte[1024 * 1024]; + var count = 0; + long totalBytes = 0; + var totalFileLength = GetTotalFileSize(inputFiles); + Log(FuseSimpleLogType.Information, $"Fusing {inputFiles.Length} parts to {outputFilePath}:{totalFileLength / 1000}kB"); + await using var outputStream = File.Create(outputFilePath); + foreach (var inputFilePath in inputFiles) + { + if (_cts.Token.IsCancellationRequested) return; + ++count; + await using var inputStream = File.OpenRead(inputFilePath); + Log(FuseSimpleLogType.Information, $"Fusing file part {count} --> {inputFilePath}/{inputStream.Length / 1000}kB"); + var fileLength = inputStream.Length; + long currentBytes = 0; + int currentBlockSize; + + while ((currentBlockSize = inputStream.Read(buffer, 0, buffer.Length)) > 0) + { + if (_cts.Token.IsCancellationRequested) return; + currentBytes += currentBlockSize; + totalBytes += currentBlockSize; + + try + { + await outputStream.WriteAsync(buffer, 0, currentBlockSize, _cts.Token); + } + catch (TaskCanceledException e) + { + Log(FuseSimpleLogType.Error, e.Message); + } + + OnFuseUpdate(new FuseUpdateInfo + { + Part = count, + Parts = inputFiles.Length, + ProgressPart = currentBytes * 100.0 / fileLength, + Progress = totalBytes * 100.0 / totalFileLength + }); + } + } + + Log(FuseSimpleLogType.Information, "Fuse Complete"); + } + + private static long GetTotalFileSize(string[] inputFiles) + { + long totalFileSize = 0; + inputFiles.Select(f => f).ToList().ForEach(f => totalFileSize += new FileInfo(f).Length); + return totalFileSize; + } + + private string[] GetInputFiles() + { + var inputDir = Path.GetDirectoryName(_inputFilePath); + if (string.IsNullOrEmpty(inputDir)) inputDir = Path.GetPathRoot(_inputFilePath); + return inputDir != null ? Directory.GetFiles(inputDir) : new string[] { }; + } + + public void StopFuse() + { + _cts.Cancel(); + Log(FuseSimpleLogType.Information, "Fuse Stopped"); + OnFuseUpdate(new FuseUpdateInfo + { + Part = 0, + Parts = 0, + ProgressPart = 0, + Progress = 0 + }); + if (File.Exists(_outputFilePath)) + { + Task.Run((() => + { + const int retries = 5; + for (var i = 0; i <= retries; i++) + { + try + { + File.Delete(_outputFilePath); + Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => Log(FuseSimpleLogType.Information, $"Deleted {_outputFilePath}")); + break; + } + catch (IOException) + { + Thread.Sleep(1000); + } + } + })); + } + } + } +} \ No newline at end of file diff --git a/src/nxDumpFuse/Model/FuseSimpleLog.cs b/src/nxDumpFuse/Model/FuseSimpleLog.cs new file mode 100644 index 0000000..4b39644 --- /dev/null +++ b/src/nxDumpFuse/Model/FuseSimpleLog.cs @@ -0,0 +1,32 @@ +using System; +using System.Diagnostics; +using Avalonia.Media; + +namespace nxDumpFuse.Model +{ + public enum FuseSimpleLogType + { + Error, + Information + } + + public class FuseSimpleLog + { + public FuseSimpleLog(FuseSimpleLogType type, DateTime time, string message) + { + Type = type; + Time = time; + Message = message; + Color = Type == FuseSimpleLogType.Information ? Brushes.White : Brushes.Red; + Debug.WriteLine($"Color is {Color}"); + } + + public FuseSimpleLogType Type { get; } + + public DateTime Time { get; } + + public string Message { get; } + + public IBrush Color { get; } + } +} diff --git a/src/nxDumpFuse/Model/FuseUpdateInfo.cs b/src/nxDumpFuse/Model/FuseUpdateInfo.cs new file mode 100644 index 0000000..e5b63d0 --- /dev/null +++ b/src/nxDumpFuse/Model/FuseUpdateInfo.cs @@ -0,0 +1,13 @@ +namespace nxDumpFuse.Model +{ + public class FuseUpdateInfo + { + public double Progress { get; set; } + + public double ProgressPart { get; set; } + + public int Part { get; set; } + + public int Parts { get; set; } + } +} diff --git a/src/nxDumpFuse/Program.cs b/src/nxDumpFuse/Program.cs new file mode 100644 index 0000000..1c84f0e --- /dev/null +++ b/src/nxDumpFuse/Program.cs @@ -0,0 +1,34 @@ +using Avalonia; +using Avalonia.ReactiveUI; +using System; +using nxDumpFuse.DependencyInjection; +using Splat; + +namespace nxDumpFuse +{ + class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) + { + RegisterDependencies(); + + BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } + + private static void RegisterDependencies() => Bootstrapper.Register(Locator.CurrentMutable, Locator.Current); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .With(new SkiaOptions { MaxGpuResourceSizeBytes = 8096000 }) + .With(new Win32PlatformOptions { AllowEglInitialization = true }) + .LogToTrace() + .UseReactiveUI(); + } +} diff --git a/src/nxDumpFuse/Properties/Resources.Designer.cs b/src/nxDumpFuse/Properties/Resources.Designer.cs new file mode 100644 index 0000000..c782990 --- /dev/null +++ b/src/nxDumpFuse/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace nxDumpFuse.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("nxDumpFuse.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/nxDumpFuse/Properties/launchSettings.json b/src/nxDumpFuse/Properties/launchSettings.json new file mode 100644 index 0000000..4f4dfef --- /dev/null +++ b/src/nxDumpFuse/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "nxDumpFuse": { + "commandName": "Project", + "commandLineArgs": "/l Trace" + } + } +} \ No newline at end of file diff --git a/src/nxDumpFuse/Services/DialogService.cs b/src/nxDumpFuse/Services/DialogService.cs new file mode 100644 index 0000000..d9a41c6 --- /dev/null +++ b/src/nxDumpFuse/Services/DialogService.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using nxDumpFuse.Interfaces; +using nxDumpFuse.ViewModels; + +namespace nxDumpFuse.Services +{ + public class DialogService : IDialogService + { + private readonly IMainWindowProvider _mainWindowProvider; + public DialogService(IMainWindowProvider mainWindowProvider) + { + _mainWindowProvider = mainWindowProvider; + } + public async Task ShowOpenFileDialogAsync(string title, FileDialogFilter filter) + { + var openFileDialog = new OpenFileDialog() + { + Title = title, + AllowMultiple = false + }; + openFileDialog.Filters.Add(filter); + + var result = await openFileDialog.ShowAsync(_mainWindowProvider.GetMainWindow()); + return result.Length == 0 ? string.Empty : result[0]; + } + + public async Task ShowOpenFolderDialogAsync(string title) + { + var openFolderDialog = new OpenFolderDialog() + { + Title = title + }; + return await openFolderDialog.ShowAsync(_mainWindowProvider.GetMainWindow()); + } + } +} diff --git a/src/nxDumpFuse/Styles/SideBar.axaml b/src/nxDumpFuse/Styles/SideBar.axaml new file mode 100644 index 0000000..e7e7666 --- /dev/null +++ b/src/nxDumpFuse/Styles/SideBar.axaml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nxDumpFuse/ViewLocator.cs b/src/nxDumpFuse/ViewLocator.cs new file mode 100644 index 0000000..1bbeaf7 --- /dev/null +++ b/src/nxDumpFuse/ViewLocator.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using System; +using nxDumpFuse.ViewModels; + +namespace nxDumpFuse +{ + public class ViewLocator : IDataTemplate + { + //private static ConsoleLogger _logger = new ConsoleLogger(); + public IControl Build(object data) + { + var name = data.GetType().FullName!.Replace("ViewModel", ""); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type)!; + } + else + { + return new TextBlock { Text = "Not Found: " + name }; + } + } + + public bool Match(object data) + { + return data is ViewModelBase; + } + } +} diff --git a/src/nxDumpFuse/ViewModels/AboutViewModel.cs b/src/nxDumpFuse/ViewModels/AboutViewModel.cs new file mode 100644 index 0000000..8e6645a --- /dev/null +++ b/src/nxDumpFuse/ViewModels/AboutViewModel.cs @@ -0,0 +1,55 @@ +using System.Diagnostics; +using System.Reactive; +using System.Runtime.InteropServices; +using System.Text; +using nxDumpFuse.Interfaces; +using ReactiveUI; + +namespace nxDumpFuse.ViewModels +{ + public class AboutViewModel : ViewModelBase, IAboutViewModel + { + private string _gitHubUrl = "https://github.com/oMaN-Rod/nxDumpFuse"; + private string _explorer = "explorer.exe"; + + public AboutViewModel() + { + OpenGithubCommand = ReactiveCommand.Create(OpenGithub); + } + + public ReactiveCommand OpenGithubCommand { get; } + + public string UsageText => BuildUsageText(); + public string AuthorInfo => "Made for fun by oMaN-Rod, check me out on -->"; + + private void OpenGithub() + { + try + { + Process.Start(_explorer, _gitHubUrl); + } + catch + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", _gitHubUrl); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", _gitHubUrl); + } + } + } + + private string BuildUsageText() + { + var sb = new StringBuilder(); + sb.AppendLine("To fuse files make sure all file parts are alone in a directory with no other files, i.e"); + sb.AppendLine("├── _dir"); + sb.AppendLine("│ ├── nxDumpPart0"); + sb.AppendLine("│ ├── nxDumpPart1"); + sb.AppendLine("│ ├── ..........."); + return sb.ToString(); + } + } +} diff --git a/src/nxDumpFuse/ViewModels/FuseViewModel.cs b/src/nxDumpFuse/ViewModels/FuseViewModel.cs new file mode 100644 index 0000000..3b1769c --- /dev/null +++ b/src/nxDumpFuse/ViewModels/FuseViewModel.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Reactive; +using Avalonia.Controls; +using nxDumpFuse.Interfaces; +using nxDumpFuse.Model; +using nxDumpFuse.Services; +using ReactiveUI; + +namespace nxDumpFuse.ViewModels +{ + public class FuseViewModel : ViewModelBase, IFuseViewModel + { + private readonly IDialogService _dialogService; + private readonly List _extensions = new() { "*" }; + private Fuse? _fuse; + + public FuseViewModel(IDialogService dialogService) + { + _dialogService = dialogService; + SelectInputFileCommand = ReactiveCommand.Create(SelectInputFile); + SelectOutputFolderCommand = ReactiveCommand.Create(SelectOutputFolder); + FuseCommand = ReactiveCommand.Create(FuseNxDump); + StopCommand = ReactiveCommand.Create(StopDump); + ProgressPartText = "Part 0/0"; + } + + public ReactiveCommand SelectInputFileCommand { get; } + + public ReactiveCommand SelectOutputFolderCommand { get; } + + public ReactiveCommand FuseCommand { get; } + + public ReactiveCommand StopCommand { get; } + + private string _inputFilePath = string.Empty; + public string InputFilePath + { + get => _inputFilePath; + set => this.RaiseAndSetIfChanged(ref _inputFilePath, value); + } + + private string _outputDir = string.Empty; + public string OutputDir + { + get => _outputDir; + set => this.RaiseAndSetIfChanged(ref _outputDir, value); + } + + private string _progressPartText = string.Empty; + public string ProgressPartText + { + get => _progressPartText; + set => this.RaiseAndSetIfChanged(ref _progressPartText, value); + } + + private double _progressPart; + public double ProgressPart + { + get => _progressPart; + set => this.RaiseAndSetIfChanged(ref _progressPart, value); + } + + private double _progress; + public double Progress + { + get => _progress; + set => this.RaiseAndSetIfChanged(ref _progress, value); + } + + private ObservableCollection _logItems = new(); + public ObservableCollection LogItems + { + get => _logItems; + set => this.RaiseAndSetIfChanged( ref _logItems, value); + } + + private async void SelectInputFile() + { + InputFilePath = await _dialogService.ShowOpenFileDialogAsync("Choose Input File", new FileDialogFilter { Name = string.Empty, Extensions = _extensions }); + } + + private async void SelectOutputFolder() + { + OutputDir = await _dialogService.ShowOpenFolderDialogAsync("Choose Output Folder"); + } + + private void FuseNxDump() + { + _fuse = new Fuse(InputFilePath, OutputDir); + _fuse.FuseUpdateEvent += OnFuseUpdate; + _fuse.FuseSimpleLogEvent += OnFuseSimpleLogEvent; + try + { + _fuse.FuseDump(); + } + catch (Exception e) { + OnFuseSimpleLogEvent(new FuseSimpleLog(FuseSimpleLogType.Error, DateTime.Now, e.Message)); + } + } + + private void StopDump() + { + _fuse?.StopFuse(); + } + + private void OnFuseUpdate(FuseUpdateInfo fuseUpdateInfo) + { + ProgressPart = fuseUpdateInfo.ProgressPart; + Progress = fuseUpdateInfo.Progress; + ProgressPartText = $"Part {fuseUpdateInfo.Part}/{fuseUpdateInfo.Parts}"; + } + + private void OnFuseSimpleLogEvent(FuseSimpleLog log) + { + LogItems.Add(log); + } + } +} diff --git a/src/nxDumpFuse/ViewModels/MainWindowViewModel.cs b/src/nxDumpFuse/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..94fce6c --- /dev/null +++ b/src/nxDumpFuse/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,19 @@ + +using nxDumpFuse.Interfaces; + +namespace nxDumpFuse.ViewModels +{ + public class MainWindowViewModel : ViewModelBase, IMainWindowViewModel + { + public IFuseViewModel FuseViewModel { get; } + public IAboutViewModel AboutViewModel { get; } + public IMainWindowViewModel MainViewModel { get; } + + public MainWindowViewModel(IFuseViewModel fuseViewModel, IAboutViewModel aboutViewModel) + { + FuseViewModel = fuseViewModel; + AboutViewModel = aboutViewModel; + MainViewModel = this; + } + } +} diff --git a/src/nxDumpFuse/ViewModels/ViewModelBase.cs b/src/nxDumpFuse/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..00fad44 --- /dev/null +++ b/src/nxDumpFuse/ViewModels/ViewModelBase.cs @@ -0,0 +1,8 @@ +using ReactiveUI; + +namespace nxDumpFuse.ViewModels +{ + public class ViewModelBase : ReactiveObject + { + } +} diff --git a/src/nxDumpFuse/Views/AboutView.axaml b/src/nxDumpFuse/Views/AboutView.axaml new file mode 100644 index 0000000..072f93b --- /dev/null +++ b/src/nxDumpFuse/Views/AboutView.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + diff --git a/src/nxDumpFuse/Views/AboutView.axaml.cs b/src/nxDumpFuse/Views/AboutView.axaml.cs new file mode 100644 index 0000000..380c129 --- /dev/null +++ b/src/nxDumpFuse/Views/AboutView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using nxDumpFuse.ViewModels; + +namespace nxDumpFuse.Views +{ + public class AboutView : UserControl + { + public AboutView() + { + InitializeComponent(); + DataContext = new AboutViewModel(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/nxDumpFuse/Views/FuseView.axaml b/src/nxDumpFuse/Views/FuseView.axaml new file mode 100644 index 0000000..4b1623f --- /dev/null +++ b/src/nxDumpFuse/Views/FuseView.axaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + +