Initial commit

This commit is contained in:
Omar 2021-10-21 22:22:45 -04:00
commit 846ac96c72
64 changed files with 1716 additions and 0 deletions

454
.gitignore vendored Normal file
View File

@ -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

BIN
README.md Normal file

Binary file not shown.

BIN
assets/AllowExecute.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
assets/Fedora-34.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
assets/Ubuntu-18.04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
assets/Windows.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

BIN
assets/nxDumpFuseLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

25
src/nxDumpFuse.sln Normal file
View File

@ -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

14
src/nxDumpFuse/App.axaml Normal file
View File

@ -0,0 +1,14 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:nxDumpFuse"
x:Class="nxDumpFuse.App">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Dark" />
<StyleInclude Source="/Styles/SideBar.axaml" />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>

View File

@ -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<IMainWindowViewModel>();
desktop.MainWindow = new MainWindow
{
DataContext = DataContext
};
}
var theme = new Avalonia.Themes.Default.DefaultTheme();
theme.TryGetResource("Button", out _);
base.OnFrameworkInitializationCompleted();
}
private static T GetRequiredService<T>() => Locator.Current.GetRequiredService<T>();
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -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);
}
}
}

View File

@ -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<IMainWindowProvider>((() => new MainWindowProvider()));
services.RegisterLazySingleton<IDialogService>(() => new DialogService(
resolver.GetRequiredService<IMainWindowProvider>()));
}
}
}

View File

@ -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<IMainWindowViewModel>(() => new MainWindowViewModel(
resolver.GetRequiredService<IFuseViewModel>(),
resolver.GetRequiredService<IAboutViewModel>()
));
services.RegisterLazySingleton<IFuseViewModel>(() => new FuseViewModel(
resolver.GetRequiredService<IDialogService>()));
services.RegisterLazySingleton<IAboutViewModel>(() => new AboutViewModel());
}
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,18 @@
using System;
using Splat;
namespace nxDumpFuse.Extensions
{
public static class ReadonlyDependencyResolverExtensions
{
public static TService GetRequiredService<TService>(this IReadonlyDependencyResolver resolver)
{
var service = resolver.GetService<TService>();
if (service == null)
{
throw new InvalidOperationException($"Failed to resolve object of type {typeof(TService)}");
}
return service;
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,6 @@
namespace nxDumpFuse.Interfaces
{
public interface IAboutViewModel
{
}
}

View File

@ -0,0 +1,13 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using nxDumpFuse.ViewModels;
namespace nxDumpFuse.Interfaces
{
public interface IDialogService
{
Task<string> ShowOpenFileDialogAsync(string title, FileDialogFilter filter);
Task<string> ShowOpenFolderDialogAsync(string title);
}
}

View File

@ -0,0 +1,6 @@
namespace nxDumpFuse.Interfaces
{
public interface IFuseViewModel
{
}
}

View File

@ -0,0 +1,9 @@
using Avalonia.Controls;
namespace nxDumpFuse.Interfaces
{
public interface IMainWindowProvider
{
Window GetMainWindow();
}
}

View File

@ -0,0 +1,6 @@
namespace nxDumpFuse.Interfaces
{
public interface IMainWindowViewModel
{
}
}

View File

@ -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;

View File

@ -0,0 +1,2 @@
#!/bin/bash
./nxDumpFuse

View File

@ -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;
}
}
}

View File

@ -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<string> 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);
}
}
}));
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

34
src/nxDumpFuse/Program.cs Normal file
View File

@ -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<App>()
.UsePlatformDetect()
.With(new SkiaOptions { MaxGpuResourceSizeBytes = 8096000 })
.With(new Win32PlatformOptions { AllowEglInitialization = true })
.LogToTrace()
.UseReactiveUI();
}
}

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace nxDumpFuse.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"profiles": {
"nxDumpFuse": {
"commandName": "Project",
"commandLineArgs": "/l Trace"
}
}
}

View File

@ -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<string> 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<string> ShowOpenFolderDialogAsync(string title)
{
var openFolderDialog = new OpenFolderDialog()
{
Title = title
};
return await openFolderDialog.ShowAsync(_mainWindowProvider.GetMainWindow());
}
}
}

View File

@ -0,0 +1,74 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="10">
<TabControl Classes="sidebar">
<TabItem Header="Item1"/>
<TabItem Header="Item2"/>
</TabControl>
</Border>
</Design.PreviewWith>
<Style Selector="TabControl.sidebar">
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="Padding" Value="0 0 0 0"/>
<Setter Property="Background" Value="{x:Null}"/>
<Setter Property="Template">
<ControlTemplate>
<Border
Margin="{TemplateBinding Margin}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<ScrollViewer Width="150" Margin="0 0 0 0"
Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
Background="{TemplateBinding Background}"
DockPanel.Dock="Left">
<ItemsPresenter
Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}">
</ItemsPresenter>
</ScrollViewer>
<ContentControl Content="{TemplateBinding Tag}" HorizontalContentAlignment="Right" DockPanel.Dock="Bottom"/>
<ScrollViewer Background="{x:Null}"
HorizontalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.VerticalScrollBarVisibility)}">
<ContentPresenter
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabControl.sidebar > TabItem">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Width" Value="150" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Auto"/>
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Auto"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Background" Value="#1fffffff"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="#3fffffff"/>
</Style>
</Styles>

View File

@ -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;
}
}
}

View File

@ -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<Unit, Unit> 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();
}
}
}

View File

@ -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<string> _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<Unit, Unit> SelectInputFileCommand { get; }
public ReactiveCommand<Unit, Unit> SelectOutputFolderCommand { get; }
public ReactiveCommand<Unit, Unit> FuseCommand { get; }
public ReactiveCommand<Unit, Unit> 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<FuseSimpleLog> _logItems = new();
public ObservableCollection<FuseSimpleLog> 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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
using ReactiveUI;
namespace nxDumpFuse.ViewModels
{
public class ViewModelBase : ReactiveObject
{
}
}

View File

@ -0,0 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:imaging="clr-namespace:Avalonia.Media.Imaging;assembly=Avalonia.Visuals"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="nxDumpFuse.Views.AboutView"
xmlns:vm="clr-namespace:nxDumpFuse.ViewModels"
x:DataType="vm:AboutViewModel">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Text="{Binding UsageText}"/>
<TextBlock Grid.Row="1" Margin="5" VerticalAlignment="Center" Text="{Binding AuthorInfo}" HorizontalAlignment="Right"/>
<Button Grid.Row="1" Grid.Column="1" Padding="-10" Command="{Binding OpenGithubCommand}">
<Panel>
<Image Width="75" Height="75" Source="resm:nxDumpFuse.Assets.github_icon.png" />
</Panel>
</Button>
</Grid>
</UserControl>

View File

@ -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);
}
}
}

View File

@ -0,0 +1,62 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="nxDumpFuse.Views.FuseView"
xmlns:vm="clr-namespace:nxDumpFuse.ViewModels"
x:DataType="vm:FuseViewModel"
Name="FuseViewControl">
<DockPanel LastChildFill="True">
<Grid Margin="20" DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Command="{Binding SelectInputFileCommand}" Content="Input" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalAlignment="Center" Text="{Binding InputFilePath}" Name="InputFileTextBox"/>
<Button Grid.Row="1" Grid.Column="0" Command="{Binding SelectOutputFolderCommand}" Content="Output" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalAlignment="Center" Text="{Binding OutputDir}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding ProgressPartText}" HorizontalAlignment="Right" Margin="2"/>
<ProgressBar Grid.Row="2" Grid.Column="1" Margin="2" Height="10" Value="{Binding ProgressPart}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Total" HorizontalAlignment="Right" Margin="2"/>
<ProgressBar Grid.Row="3" Grid.Column="1" Margin="2" Height="10" Value="{Binding Progress}"/>
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Command="{Binding FuseCommand}" Content="Fuse" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Margin="2"/>
<Button Command="{Binding StopCommand}" Content="Stop" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Margin="2"/>
</StackPanel>
<DataGrid Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
x:Name="FuseSimpleLog"
Items="{Binding LogItems}"
CanUserSortColumns="False"
CanUserResizeColumns="True"
CanUserReorderColumns="True"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Width="{Binding #InputFileTextBox.Width}"
VerticalAlignment="Top"
Margin="4"
Height="350">
<DataGrid.Columns>
<DataGridTextColumn Header="Type" Binding="{Binding Type}" Width="Auto" Foreground="{Binding Color}"/>
<DataGridTextColumn Header="Time" Binding="{Binding Time}" Width="Auto"/>
<DataGridTextColumn Header="Message" Binding="{Binding Message}" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</DockPanel>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using nxDumpFuse.ViewModels;
namespace nxDumpFuse.Views
{
public class FuseView : UserControl
{
public FuseView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,35 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:nxDumpFuse.ViewModels;assembly=nxDumpFuse"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="1350" d:DesignHeight="700"
x:Class="nxDumpFuse.Views.MainView"
FontSize="14"
Foreground="{StaticResource SystemBaseHighColor}"
xmlns:views="clr-namespace:nxDumpFuse.Views;assembly=nxDumpFuse">
<Panel>
<DockPanel HorizontalAlignment="Stretch">
<ExperimentalAcrylicBorder DockPanel.Dock="Left" Width="150">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial TintColor="Black" MaterialOpacity="0.85" TintOpacity="1" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder IsHitTestVisible="False">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial TintColor="#222222" MaterialOpacity="0.85" TintOpacity="1" />
</ExperimentalAcrylicBorder.Material>
<TextBlock Text="{Binding #TabControl.SelectedItem.Header}" Margin="40 20" FontSize="32" FontWeight="Light" />
</ExperimentalAcrylicBorder>
</DockPanel>
<TabControl x:Name="TabControl" Classes="sidebar" Margin="0 40 0 20">
<TabItem Header="Fuse">
<views:FuseView DataContext="{Binding FuseViewModel}"/>
</TabItem>
<TabItem Header="About">
<views:AboutView DataContext="{Binding AboutViewModel}"/>
</TabItem>
</TabControl>
</Panel>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace nxDumpFuse.Views
{
public class MainView : UserControl
{
public MainView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,29 @@
<nxDumpFuse:FluentWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:nxDumpFuse.ViewModels;assembly=nxDumpFuse"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="nxDumpFuse.Views.MainWindow"
x:Name="MainWindow"
xmlns:local="clr-namespace:nxDumpFuse.Views"
xmlns:nxDumpFuse="clr-namespace:nxDumpFuse"
Icon="{x:Null}"
Title="nxDumpFuse"
WindowStartupLocation="CenterScreen"
Background="{x:Null}"
Width="1280" Height="720">
<nxDumpFuse:FluentWindow.Styles>
<Style Selector="TitleBar:fullscreen">
<Setter Property="Background" Value="#7f000000" />
</Style>
</nxDumpFuse:FluentWindow.Styles>
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Panel Margin="{Binding #MainWindow.OffScreenMargin}">
<local:MainView DataContext="{Binding MainViewModel}"/>
</Panel>
</nxDumpFuse:FluentWindow>

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace nxDumpFuse.Views
{
public class MainWindow : FluentWindow
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<SelfContained>true</SelfContained>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<!--<RuntimeIdentifier>win10-x64</RuntimeIdentifier>-->
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>
<ApplicationIcon>nxDumpFuse.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\Fonts\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\github_icon.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\github_icon.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.8" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.8" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.8" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.8" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.8" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\AboutView.axaml.cs">
<DependentUpon>AboutView.axaml</DependentUpon>
</Compile>
<Compile Update="Views\FuseView.axaml.cs">
<DependentUpon>FuseView.axaml</DependentUpon>
</Compile>
<Compile Update="Views\FuseView.axaml.cs">
<DependentUpon>FuseView.axaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Launchers\Linux\nxDumpFuse.sh">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Launchers\Linux\Ubuntu\Run Executable.desktop">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB