diff --git a/Demo/dark.png b/Demo/dark.png
new file mode 100644
index 0000000..44cedfd
Binary files /dev/null and b/Demo/dark.png differ
diff --git a/Demo/light.png b/Demo/light.png
new file mode 100644
index 0000000..ab9a0cf
Binary files /dev/null and b/Demo/light.png differ
diff --git a/NCMDumpCLI/NCMDump.ico b/NCMDumpCLI/NCMDump.ico
index 66db9bf..806974e 100644
Binary files a/NCMDumpCLI/NCMDump.ico and b/NCMDumpCLI/NCMDump.ico differ
diff --git a/NCMDumpCLI/NCMDumpCLI.csproj b/NCMDumpCLI/NCMDumpCLI.csproj
index fae01c9..24ded9a 100644
--- a/NCMDumpCLI/NCMDumpCLI.csproj
+++ b/NCMDumpCLI/NCMDumpCLI.csproj
@@ -2,17 +2,19 @@
Exe
- net7.0-windows10.0.19041.0
+ net8.0-windows10.0.19041.0
enable
enable
NCMDumpCLI
NCMDump.ico
x64
app.manifest
- 1.6.2
+ 2.0.0
x64
+
+
@@ -21,4 +23,8 @@
+
+
+
+
diff --git a/NCMDumpCLI/Program.cs b/NCMDumpCLI/Program.cs
index caf68b5..f195fef 100644
--- a/NCMDumpCLI/Program.cs
+++ b/NCMDumpCLI/Program.cs
@@ -39,8 +39,9 @@ public static void Main(string[] args)
Console.Write("Press Any Key to Exit...");
Console.ReadLine();
+ return;
- async void WalkThrough(DirectoryInfo dir)
+ void WalkThrough(DirectoryInfo dir)
{
Console.WriteLine("DIR: " + dir.FullName);
foreach (DirectoryInfo d in dir.GetDirectories())
@@ -50,7 +51,7 @@ async void WalkThrough(DirectoryInfo dir)
foreach (FileInfo f in dir.EnumerateFiles())
{
Console.WriteLine("Converting : " + f.FullName);
- if (await Core.ConvertAsync(f.FullName)) Console.WriteLine("...OK");
+ if (Core.Convert(f.FullName)) Console.WriteLine("...OK");
else Console.WriteLine("...Fail");
Console.WriteLine();
}
diff --git a/NCMDumpCLI/ncmdump.png b/NCMDumpCLI/ncmdump.png
index 1dcd1e4..42af96c 100644
Binary files a/NCMDumpCLI/ncmdump.png and b/NCMDumpCLI/ncmdump.png differ
diff --git a/NCMDumpCore/MetaInfo.cs b/NCMDumpCore/MetaInfo.cs
index cab4c55..f60ecf4 100644
--- a/NCMDumpCore/MetaInfo.cs
+++ b/NCMDumpCore/MetaInfo.cs
@@ -4,18 +4,21 @@ namespace NCMDumpCore
{
public class MetaInfo
{
- public int musicId { get; set; }
+ public string musicId { get; set; }
public string musicName { get; set; }
public List> artist { get; set; }
- public int albumId { get; set; }
+ public string albumId { get; set; }
public string album { get; set; }
public JsonElement albumPicDocId { get; set; }
public string albumPic { get; set; }
public int bitrate { get; set; }
public string mp3DocId { get; set; }
public int duration { get; set; }
- public int mvId { get; set; }
+ public string mvId { get; set; }
public List alias { get; set; }
+ public List transNames { get; set; }
public string format { get; set; }
+ public JsonElement fee { get; set; }
+ public Dictionary privilege { get; set; }
}
}
\ No newline at end of file
diff --git a/NCMDumpCore/NCMDumpCore.csproj b/NCMDumpCore/NCMDumpCore.csproj
index 29541db..ed1cb0e 100644
--- a/NCMDumpCore/NCMDumpCore.csproj
+++ b/NCMDumpCore/NCMDumpCore.csproj
@@ -1,13 +1,14 @@
- net7.0;
+ net8.0;
enable
enable
- 1.6.2
+ 1.7.0
x64
+ Library
diff --git a/NCMDumpCore/RC4_NCM.cs b/NCMDumpCore/RC4_NCM.cs
index 7f975a4..38ac780 100644
--- a/NCMDumpCore/RC4_NCM.cs
+++ b/NCMDumpCore/RC4_NCM.cs
@@ -1,9 +1,5 @@
namespace NCMDumpCore
{
- ///
- /// In Cloud Music. There is a modified RC4 encryptor.
- /// Not standard RC4 algorithm.
- ///
public class RC4_NCM
{
private byte[] Keybox;
diff --git a/NCMDumpGUI/App.xaml b/NCMDumpGUI/App.xaml
index 0c0072f..3a323f0 100644
--- a/NCMDumpGUI/App.xaml
+++ b/NCMDumpGUI/App.xaml
@@ -1,12 +1,12 @@
+ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
-
+
+
diff --git a/NCMDumpGUI/App.xaml.cs b/NCMDumpGUI/App.xaml.cs
index 75469f3..faaa9f5 100644
--- a/NCMDumpGUI/App.xaml.cs
+++ b/NCMDumpGUI/App.xaml.cs
@@ -1,11 +1,39 @@
-using System.Windows;
+using Microsoft.Extensions.DependencyInjection;
+using NCMDumpCore;
+using System;
+using System.Windows;
namespace NCMDumpGUI
{
- ///
- /// Interaction logic for App.xaml
- ///
public partial class App : Application
{
+ private IServiceProvider _serviceProvider;
+
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+ var services = new ServiceCollection();
+ ConfigureServices(services);
+ _serviceProvider = services.BuildServiceProvider();
+ var mainWindow = _serviceProvider.GetRequiredService();
+ mainWindow.Show();
+ }
+
+ private void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ }
+
+ protected override void OnExit(ExitEventArgs e)
+ {
+ if (_serviceProvider is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+
+ base.OnExit(e);
+ }
}
}
\ No newline at end of file
diff --git a/NCMDumpGUI/MainWindow.xaml b/NCMDumpGUI/MainWindow.xaml
index 01d9092..5191f01 100644
--- a/NCMDumpGUI/MainWindow.xaml
+++ b/NCMDumpGUI/MainWindow.xaml
@@ -1,108 +1,130 @@
-
+ Closed="Window_Closed"
+ ExtendsContentIntoTitleBar="True"
+ WindowBackdropType="Acrylic">
-
-
-
-
-
+
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/NCMDumpGUI/MainWindow.xaml.cs b/NCMDumpGUI/MainWindow.xaml.cs
index f686703..08f198c 100644
--- a/NCMDumpGUI/MainWindow.xaml.cs
+++ b/NCMDumpGUI/MainWindow.xaml.cs
@@ -1,202 +1,39 @@
using System;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Forms;
-using HandyControl.Controls;
-using NCMDumpCore;
+using Wpf.Ui.Appearance;
+using Wpf.Ui.Controls;
namespace NCMDumpGUI
{
- public enum ACCENTSTATE
+ public partial class MainWindow : FluentWindow
{
- ACCENT_DISABLED = 0,
- ACCENT_ENABLE_GRADIENT = 1,
- ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
- ACCENT_ENABLE_BLURBEHIND = 3,
- ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,
- ACCENT_INVALID_STATE = 5
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct ACCENTPOLICY
- {
- public ACCENTSTATE AccentState;
- public int AccentFlags;
- public uint GradientColor;
- public int AnimationId;
- }
-
- public enum WINDOWCOMPOSITIONATTRIB
- {
- WCA_ACCENT_POLICY = 19
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct WINCOMPATTRDATA
- {
- public WINDOWCOMPOSITIONATTRIB Attribute;
- public IntPtr Data;
- public int DataSize;
- }
-
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow : BlurWindow
- {
- private NCMDump Core = new NCMDump();
-
- [DllImport("user32.dll")]
- public static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WINCOMPATTRDATA data);
-
- public ObservableCollection NCMCollection { get; set; }
- = new ObservableCollection();
-
private MainWindowViewModel VM;
- public MainWindow()
+ public MainWindow(MainWindowViewModel _vm)
{
- VM = new MainWindowViewModel();
+ VM = _vm;
this.DataContext = VM;
InitializeComponent();
- WorkingList.ItemsSource = NCMCollection;
- App.Current.Resources["BlurGradientValue"] = 0xaaffffff;
+ SystemThemeWatcher.Watch(this);
}
- private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
+ private void DataGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
- System.Windows.Controls.ListView listView = sender as System.Windows.Controls.ListView;
- GridView gView = listView.View as GridView;
-
- var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
- var col1 = 0.60;
- var col2 = 0.20;
- var col3 = 0.20;
- gView.Columns[0].Width = workingWidth * col1;
- gView.Columns[1].Width = workingWidth * col2;
- gView.Columns[2].Width = workingWidth * col3;
+ DataGrid grid = sender as DataGrid;
+ var workingWidth = grid.ActualWidth - SystemParameters.VerticalScrollBarWidth;
+ grid.Columns[0].Width = workingWidth - 130;
+ grid.Columns[1].Width = 120;
}
private void WorkingList_Drop(object sender, System.Windows.DragEventArgs e)
{
- string[] args = (string[])e.Data.GetData(System.Windows.DataFormats.FileDrop);
+ string[] args = (string[])e.Data.GetData(DataFormats.FileDrop);
if (args is not null && args.Length != 0)
{
- foreach (string _path in args)
- {
- if (new DirectoryInfo(_path).Exists)
- {
- WalkThrough(new DirectoryInfo(_path));
- }
- else if (new FileInfo(_path).Exists)
- {
- if (_path.EndsWith(@".ncm") && !NCMCollection.Any(x => x.FilePath == _path))
- NCMCollection.Add(new NCMProcessStatus(_path, "Await"));
- }
- }
- }
- }
-
- private void WalkThrough(DirectoryInfo dir)
- {
- foreach (DirectoryInfo d in dir.GetDirectories())
- {
- WalkThrough(d);
- }
- foreach (FileInfo f in dir.EnumerateFiles())
- {
- if (f.FullName.EndsWith(@".ncm") && !NCMCollection.Any(x => x.FilePath == f.FullName))
- NCMCollection.Add(new NCMProcessStatus(f.FullName, "Await"));
+ VM.OnDrop(args);
}
}
- private async void StartButton_Click(object sender, RoutedEventArgs e)
- {
- ParallelLoopResult result = Parallel.For(0, NCMCollection.Count, async (i, state) =>
- {
- if (NCMCollection[i].FileStatus != "Success")
- {
- Stopwatch sw = Stopwatch.StartNew();
- sw.Start();
- if (await Core.ConvertAsync(NCMCollection[i].FilePath))
- {
- sw.Stop();
- NCMCollection[i].Elapsedms = $"{sw.ElapsedMilliseconds:f0}ms";
- NCMCollection[i].FileStatus = "Success";
- Dispatcher.Invoke(() => this.UpdateLayout());
- try
- {
- if (VM.WillDeleteNCM)
- {
- File.Delete(NCMCollection[i].FilePath);
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine(ex.ToString());
- }
- }
- else
- {
- sw.Stop();
- NCMCollection[i].Elapsedms = $"{sw.ElapsedMilliseconds:f0}ms";
- NCMCollection[i].FileStatus = "Failed";
- Dispatcher.Invoke(() => this.UpdateLayout());
- }
- }
- });
-
- if (result.IsCompleted)
- {
- GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
- GC.WaitForPendingFinalizers();
- }
- else
- {
- Debug.WriteLine("Paralle Loop Not Complete.");
- }
- }
-
- private void SelectFileButton_Click(object sender, RoutedEventArgs e)
- {
- Microsoft.Win32.OpenFileDialog ofp = new Microsoft.Win32.OpenFileDialog();
- ofp.Multiselect = true;
- ofp.Filter = "NCM File(*.ncm)|*.ncm";
-
- if (ofp.ShowDialog() == true)
- {
- foreach (string file in ofp.FileNames)
- {
- if (file.EndsWith(@".ncm") && !NCMCollection.Any(x => x.FilePath == file))
- NCMCollection.Add(new NCMProcessStatus(file, "Await"));
- }
- }
- }
-
- private void SelectFolderButton_Click(object sender, RoutedEventArgs e)
- {
- using (var dialog = new FolderBrowserDialog())
- {
- DialogResult result = dialog.ShowDialog();
- if (result == System.Windows.Forms.DialogResult.OK)
- {
- string folderPath = dialog.SelectedPath;
- WalkThrough(new DirectoryInfo(folderPath));
- }
- }
- }
-
- private void ClearButton_Click(object sender, RoutedEventArgs e)
- {
- NCMCollection.Clear();
- }
-
private void Window_Closed(object sender, System.EventArgs e)
{
Environment.Exit(0);
diff --git a/NCMDumpGUI/MainWindowViewModel.cs b/NCMDumpGUI/MainWindowViewModel.cs
index 475b24c..1b4a29d 100644
--- a/NCMDumpGUI/MainWindowViewModel.cs
+++ b/NCMDumpGUI/MainWindowViewModel.cs
@@ -1,29 +1,171 @@
-using System.Diagnostics;
-using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.WindowsAPICodePack.Dialogs;
+using NCMDumpCore;
+using System;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Wpf.Ui.Appearance;
+using Wpf.Ui.Controls;
namespace NCMDumpGUI
{
[ObservableObject]
public partial class MainWindowViewModel
{
+ private readonly NCMDump Core;
+
private bool _willDeleteNCM;
public bool WillDeleteNCM
{
- get
+ get => _willDeleteNCM;
+ set => SetProperty(ref _willDeleteNCM, value);
+ }
+
+ private string _ApplicationTitle;
+
+ public string ApplicationTitle
+ {
+ get => _ApplicationTitle;
+ set => _ApplicationTitle = value;
+ }
+
+ public ObservableCollection NCMCollection { get; set; }
+
+ public MainWindowViewModel(NCMDump _core)
+ {
+ Core = _core;
+ WillDeleteNCM = true;
+ ApplicationTitle = "NCMDump.NET";
+ NCMCollection = new ObservableCollection();
+ AddFolderCommand = new RelayCommand(FolderDialog);
+ AddFileCommand = new RelayCommand(FileDialog);
+ ClearCommand = new RelayCommand(ClearList);
+ ConvertCommand = new RelayCommand(StartConvert);
+ ThemeCommand = new RelayCommand(SwitchTheme);
+ }
+
+ public void OnDrop(string[] args)
+ {
+ foreach (string _path in args)
+ {
+ if (new DirectoryInfo(_path).Exists)
+ {
+ WalkThrough(new DirectoryInfo(_path));
+ }
+ else if (new FileInfo(_path).Exists)
+ {
+ if (_path.EndsWith(@".ncm") && !NCMCollection.Any(x => x.FilePath == _path))
+ NCMCollection.Add(new NCMProcessStatus(_path, "Await"));
+ }
+ }
+ }
+
+ private void WalkThrough(DirectoryInfo dir)
+ {
+ foreach (DirectoryInfo d in dir.GetDirectories())
{
- return _willDeleteNCM;
+ WalkThrough(d);
}
- set
+ foreach (FileInfo f in dir.EnumerateFiles())
{
- SetProperty(ref _willDeleteNCM, value);
- Debug.Assert(WillDeleteNCM == value);
+ if (f.FullName.EndsWith(@".ncm") && !NCMCollection.Any(x => x.FilePath == f.FullName))
+ NCMCollection.Add(new NCMProcessStatus(f.FullName, "Await"));
}
}
- public MainWindowViewModel()
+ public ICommand AddFolderCommand { get; }
+ public ICommand AddFileCommand { get; }
+ public ICommand ClearCommand { get; }
+ public ICommand ConvertCommand { get; }
+ public ICommand ThemeCommand { get; }
+
+ private void StartConvert()
{
- WillDeleteNCM = true;
+ ParallelLoopResult result = Parallel.For(0, NCMCollection.Count, async (i, state) =>
+ {
+ if (NCMCollection[i].FileStatus != "Success")
+ {
+ try
+ {
+ if (await Core.ConvertAsync(NCMCollection[i].FilePath))
+ {
+ NCMCollection[i].FileStatus = "Success";
+ try
+ {
+ if (WillDeleteNCM)
+ {
+ File.Delete(NCMCollection[i].FilePath);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex.ToString());
+ }
+ }
+ else
+ {
+ NCMCollection[i].FileStatus = "Failed";
+ }
+ }
+ catch
+ {
+ NCMCollection[i].FileStatus = "Failed";
+ }
+ }
+ });
+
+ if (result.IsCompleted)
+ {
+ GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
+ GC.WaitForPendingFinalizers();
+ }
+ else
+ {
+ Debug.WriteLine("Paralle Loop Not Complete.");
+ }
+ }
+
+ private void FolderDialog()
+ {
+ var dialog = new CommonOpenFileDialog
+ {
+ IsFolderPicker = true,
+ Title = "选择文件夹"
+ };
+
+ if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
+ {
+ string folderPath = dialog.FileName;
+ OnDrop([folderPath]);
+ }
+ }
+
+ private void FileDialog()
+ {
+ Microsoft.Win32.OpenFileDialog ofp = new Microsoft.Win32.OpenFileDialog();
+ ofp.Multiselect = true;
+ ofp.Filter = "NCM File(*.ncm)|*.ncm";
+ if (ofp.ShowDialog() == true)
+ {
+ foreach (string file in ofp.FileNames)
+ {
+ if (file.EndsWith(@".ncm") && !NCMCollection.Any(x => x.FilePath == file))
+ NCMCollection.Add(new NCMProcessStatus(file, "Await"));
+ }
+ }
}
+
+ private void SwitchTheme() =>
+ ApplicationThemeManager.Apply(
+ ApplicationThemeManager.GetAppTheme() == ApplicationTheme.Dark ? ApplicationTheme.Light : ApplicationTheme.Dark,
+ ApplicationThemeManager.GetAppTheme() == ApplicationTheme.Dark ? WindowBackdropType.Acrylic : WindowBackdropType.Mica);
+
+ private void ClearList() => NCMCollection.Clear();
}
}
\ No newline at end of file
diff --git a/NCMDumpGUI/NCMDump.ico b/NCMDumpGUI/NCMDump.ico
index 66db9bf..806974e 100644
Binary files a/NCMDumpGUI/NCMDump.ico and b/NCMDumpGUI/NCMDump.ico differ
diff --git a/NCMDumpGUI/NCMDumpGUI - Backup.csproj b/NCMDumpGUI/NCMDumpGUI - Backup.csproj
new file mode 100644
index 0000000..ffb87fa
--- /dev/null
+++ b/NCMDumpGUI/NCMDumpGUI - Backup.csproj
@@ -0,0 +1,31 @@
+
+
+
+ WinExe
+ net7.0-windows10.0.19041.0
+ enable
+ True
+ NCMDump.ico
+ True
+ 1.6.2
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/NCMDumpGUI/NCMDumpGUI.csproj b/NCMDumpGUI/NCMDumpGUI.csproj
index 7595f89..6d95e96 100644
--- a/NCMDumpGUI/NCMDumpGUI.csproj
+++ b/NCMDumpGUI/NCMDumpGUI.csproj
@@ -2,17 +2,18 @@
WinExe
- net7.0-windows10.0.19041.0
+ net8.0-windows;
enable
- true
+ True
NCMDump.ico
- True
- 1.6.2
+ 2.0.0
x64
+
+
@@ -20,12 +21,11 @@
-
-
-
-
-
-
+
+
+
+
+
diff --git a/NCMDumpGUI/NCMProcessStatus.cs b/NCMDumpGUI/NCMProcessStatus.cs
index 88db619..412acd9 100644
--- a/NCMDumpGUI/NCMProcessStatus.cs
+++ b/NCMDumpGUI/NCMProcessStatus.cs
@@ -1,40 +1,29 @@
-using System.ComponentModel;
+using CommunityToolkit.Mvvm.ComponentModel;
namespace NCMDumpGUI
{
- public class NCMProcessStatus : INotifyPropertyChanged
+ public partial class NCMProcessStatus : ObservableObject
{
- public string FilePath { get; set; }
+ private string filePath;
- public string _filestatus;
-
- private string _elapsedms;
-
- public string Elapsedms
+ public string FilePath
{
- get { return _elapsedms; }
- set { _elapsedms = value; OnPropertyChanged("Elapsedms"); }
+ get => filePath;
+ set => SetProperty(ref filePath, value);
}
+ public string _filestatus;
+
public string FileStatus
{
- get { return _filestatus; }
- set { _filestatus = value; OnPropertyChanged("FileStatus"); }
+ get => _filestatus;
+ set => SetProperty(ref _filestatus, value);
}
public NCMProcessStatus(string _path, string _status)
{
FilePath = _path;
FileStatus = _status;
- Elapsedms = "";
}
-
- protected internal virtual void OnPropertyChanged(string propertyName)
- {
- if (PropertyChanged is not null)
- PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
- }
-
- public event PropertyChangedEventHandler PropertyChanged;
}
}
\ No newline at end of file
diff --git a/NCMDumpGUI/ncmdump.png b/NCMDumpGUI/ncmdump.png
index 1dcd1e4..42af96c 100644
Binary files a/NCMDumpGUI/ncmdump.png and b/NCMDumpGUI/ncmdump.png differ
diff --git a/README.md b/README.md
index 8c6a930..56481ec 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,22 @@ Keep ID3 tags and cover image.
## Changelog
+### v2.0.0 2023.11.17
+
+Move to .NET 8.
+
+[Runtime Download Link](https://dotnet.microsoft.com/zh-cn/download/dotnet/thank-you/runtime-desktop-8.0.0-windows-x64-installer ".NET 8.0 Desktop Runtime (v8.0.0) - Windows x64 Installer")
+
+Add Support for cloud music 3.0.0(beta)
+
+UI Remake.
+
+Dark mode available now.
+
+
+
+
+
### v1.6.2 2023.08.05
Critical Bug Fix
@@ -30,7 +46,7 @@ code cleaning
### v1.5
Upgrade to .Net 7 Runtime with so Fxxxxxx high performance.
-[Download Link](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-7.0.4-windows-x64-installer ".NET 7.0 Desktop Runtime (v7.0.4) - Windows x64 Installer")
+[Runtime Download Link](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-7.0.4-windows-x64-installer ".NET 7.0 Desktop Runtime (v7.0.4) - Windows x64 Installer")
Add check box: Delete .ncm file when done
@@ -111,7 +127,7 @@ File Path to a NCM file.
## Refrence
-
+