mirror of
https://github.com/knah/VRCMelonAssistant.git
synced 2024-05-22 13:23:24 +12:00
Added One Click Install handlers.
Made exceptions more verbose.
This commit is contained in:
parent
dd28764494
commit
c367dd2e92
|
@ -63,7 +63,17 @@ namespace ModAssistant
|
|||
break;
|
||||
|
||||
case "--register":
|
||||
OneClickInstaller.Register();
|
||||
if (!String.IsNullOrEmpty(Args[1]))
|
||||
OneClickInstaller.Register(Args[1], true);
|
||||
else
|
||||
Utils.SendNotify("Invalid argument! '--register' requires an option.");
|
||||
break;
|
||||
|
||||
case "--unregister":
|
||||
if (!String.IsNullOrEmpty(Args[1]))
|
||||
OneClickInstaller.Unregister(Args[1], true);
|
||||
else
|
||||
Utils.SendNotify("Invalid argument! '--unregister' requires an option.");
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -75,13 +85,9 @@ namespace ModAssistant
|
|||
|
||||
private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
MessageBox.Show("An unhandled exception just occurred: " + e.Exception.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
MessageBox.Show("An unhandled exception just occurred: " + e.Exception, "Exception", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public void RegisterOneClickInstalls ()
|
||||
{
|
||||
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,20 +8,24 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.Win32;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace ModAssistant
|
||||
{
|
||||
class OneClickInstaller
|
||||
{
|
||||
private const string ModelSaberURLPrefix = "https://modelsaber.com/files/";
|
||||
private const string BeatSaverURLPrefix = "https://beatsaver.com/download/";
|
||||
|
||||
private static string BeatSaberPath = App.BeatSaberInstallDirectory;
|
||||
|
||||
private const string CustomAvatarsFolder = "CustomAvatars";
|
||||
private const string CustomSabersFolder = "CustomSabers";
|
||||
private const string CustomPlatformsFolder = "CustomPlatforms";
|
||||
private const string CustomSongsFolder = "CustomSongs";
|
||||
|
||||
private static readonly string[] Protocols = new[] { "modelsaber" };
|
||||
|
||||
private static bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
|
||||
private static readonly string[] Protocols = new[] { "modelsaber", "beatsaver", "modsaber" };
|
||||
|
||||
public static void InstallAsset(string link)
|
||||
{
|
||||
|
@ -33,6 +37,69 @@ namespace ModAssistant
|
|||
case "modelsaber":
|
||||
ModelSaber(uri);
|
||||
break;
|
||||
case "beatsaver":
|
||||
BeatSaver(uri);
|
||||
break;
|
||||
case "modsaber":
|
||||
ModSaber(uri);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void BeatSaver(Uri uri)
|
||||
{
|
||||
string ID = uri.Segments.Last<string>();
|
||||
DownloadAsset(BeatSaverURLPrefix + ID, CustomSongsFolder);
|
||||
File.Move(Path.Combine(BeatSaberPath, CustomSongsFolder, ID), Path.Combine(BeatSaberPath, CustomSongsFolder, ID + ".zip"));
|
||||
string directory = Path.Combine(BeatSaberPath, CustomSongsFolder, ID);
|
||||
|
||||
using (FileStream stream = new FileStream(directory + ".zip", FileMode.Open))
|
||||
{
|
||||
using (ZipArchive archive = new ZipArchive(stream))
|
||||
{
|
||||
foreach (ZipArchiveEntry file in archive.Entries)
|
||||
{
|
||||
string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName));
|
||||
if (!Directory.Exists(fileDirectory))
|
||||
Directory.CreateDirectory(fileDirectory);
|
||||
|
||||
if (!String.IsNullOrEmpty(file.Name))
|
||||
file.ExtractToFile(Path.Combine(directory, file.FullName), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File.Delete(Path.Combine(BeatSaberPath, CustomSongsFolder, ID + ".zip"));
|
||||
}
|
||||
|
||||
private static void ModSaber(Uri uri)
|
||||
{
|
||||
switch (uri.Host)
|
||||
{
|
||||
case "song":
|
||||
string ID = uri.Segments.Last<string>();
|
||||
DownloadAsset(BeatSaverURLPrefix + ID, CustomSongsFolder);
|
||||
File.Move(Path.Combine(BeatSaberPath, CustomSongsFolder, ID), Path.Combine(BeatSaberPath, CustomSongsFolder, ID + ".zip"));
|
||||
string directory = Path.Combine(BeatSaberPath, CustomSongsFolder, ID);
|
||||
|
||||
using (FileStream stream = new FileStream(directory + ".zip", FileMode.Open))
|
||||
{
|
||||
using (ZipArchive archive = new ZipArchive(stream))
|
||||
{
|
||||
foreach (ZipArchiveEntry file in archive.Entries)
|
||||
{
|
||||
string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName));
|
||||
if (!Directory.Exists(fileDirectory))
|
||||
Directory.CreateDirectory(fileDirectory);
|
||||
|
||||
if (!String.IsNullOrEmpty(file.Name))
|
||||
file.ExtractToFile(Path.Combine(directory, file.FullName), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File.Delete(Path.Combine(BeatSaberPath, CustomSongsFolder, ID + ".zip"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +121,6 @@ namespace ModAssistant
|
|||
|
||||
private static void DownloadAsset(string link, string folder)
|
||||
{
|
||||
string BeatSaberPath = App.BeatSaberInstallDirectory;
|
||||
if (string.IsNullOrEmpty(BeatSaberPath))
|
||||
{
|
||||
Utils.SendNotify("Beat Saber installation path not found.");
|
||||
|
@ -72,17 +138,88 @@ namespace ModAssistant
|
|||
}
|
||||
}
|
||||
|
||||
public static void Register()
|
||||
public static void Register(string Protocol, bool Background = false)
|
||||
{
|
||||
foreach (string protocol in Protocols)
|
||||
if (IsRegistered(Protocol) == true)
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (Utils.IsAdmin)
|
||||
{
|
||||
RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol, true);
|
||||
if (ProtocolKey == null)
|
||||
ProtocolKey = Registry.ClassesRoot.CreateSubKey(Protocol, true);
|
||||
RegistryKey CommandKey = ProtocolKey.CreateSubKey(@"shell\open\command", true);
|
||||
if (CommandKey == null)
|
||||
CommandKey = Registry.ClassesRoot.CreateSubKey(@"shell\open\command", true);
|
||||
|
||||
if (ProtocolKey.GetValue("OneClick-Provider", "").ToString() != "ModAssistant")
|
||||
{
|
||||
ProtocolKey.SetValue("URL Protocol", "", RegistryValueKind.String);
|
||||
ProtocolKey.SetValue("OneClick-Provider", "ModAssistant", RegistryValueKind.String);
|
||||
CommandKey.SetValue("", $"\"{Utils.ExePath}\" \"--install\" \"%1\"");
|
||||
}
|
||||
|
||||
Utils.SendNotify($"{Protocol} One Click Install handlers registered!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.StartAsAdmin($"\"--register\" \"{Protocol}\"");
|
||||
}
|
||||
} catch (Exception e)
|
||||
{
|
||||
MessageBox.Show(e.ToString());
|
||||
}
|
||||
|
||||
if (Background)
|
||||
Application.Current.Shutdown();
|
||||
else
|
||||
Pages.Options.Instance.UpdateHandlerStatus();
|
||||
}
|
||||
|
||||
public static bool IsRegistered()
|
||||
public static void Unregister(string Protocol, bool Background = false)
|
||||
{
|
||||
return false;
|
||||
if (IsRegistered(Protocol) == false)
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (Utils.IsAdmin)
|
||||
{
|
||||
using (RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol, true))
|
||||
{
|
||||
if (ProtocolKey != null
|
||||
&& ProtocolKey.GetValue("OneClick-Provider", "").ToString() == "ModAssistant")
|
||||
{
|
||||
Registry.ClassesRoot.DeleteSubKeyTree(Protocol);
|
||||
}
|
||||
}
|
||||
Utils.SendNotify($"{Protocol} One Click Install handlers unregistered!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.StartAsAdmin($"\"--unregister\" \"{Protocol}\"");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MessageBox.Show(e.ToString());
|
||||
}
|
||||
|
||||
if (Background)
|
||||
Application.Current.Shutdown();
|
||||
else
|
||||
Pages.Options.Instance.UpdateHandlerStatus();
|
||||
}
|
||||
|
||||
public static bool IsRegistered(string Protocol)
|
||||
{
|
||||
RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol);
|
||||
if (ProtocolKey != null
|
||||
&& ProtocolKey.GetValue("OneClick-Provider", "").ToString() == "ModAssistant")
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ namespace ModAssistant
|
|||
{
|
||||
private static string APILatestURL = "https://api.github.com/repos/Assistant/ModAssistant/releases/latest";
|
||||
|
||||
private static string ExePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
|
||||
private static Update LatestUpdate;
|
||||
private static Version CurrentVersion;
|
||||
private static Version LatestVersion;
|
||||
|
@ -60,7 +59,7 @@ namespace ModAssistant
|
|||
|
||||
public static void StartUpdate()
|
||||
{
|
||||
string Directory = Path.GetDirectoryName(ExePath);
|
||||
string Directory = Path.GetDirectoryName(Utils.ExePath);
|
||||
string OldExe = Path.Combine(Directory, "ModAssistant.old.exe");
|
||||
|
||||
string DownloadLink = null;
|
||||
|
@ -82,10 +81,10 @@ namespace ModAssistant
|
|||
if (File.Exists(OldExe))
|
||||
File.Delete(OldExe);
|
||||
|
||||
File.Move(ExePath, OldExe);
|
||||
File.Move(Utils.ExePath, OldExe);
|
||||
|
||||
Utils.Download(DownloadLink, ExePath);
|
||||
Process.Start(ExePath);
|
||||
Utils.Download(DownloadLink, Utils.ExePath);
|
||||
Process.Start(Utils.ExePath);
|
||||
App.Current.Shutdown();
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ namespace ModAssistant
|
|||
{
|
||||
public class Utils
|
||||
{
|
||||
public static bool IsAdmin = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
|
||||
public static string ExePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
public class Constants
|
||||
{
|
||||
public const string BeatSaberAPPID = "620980";
|
||||
|
@ -25,7 +28,6 @@ namespace ModAssistant
|
|||
public const string BeatModsURL = "https://beatmods.com";
|
||||
public const string BeatModsModsOptions = "mod?status=approved";
|
||||
public const string MD5Spacer = " ";
|
||||
public static bool IsAdmin = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
|
||||
public static void SendNotify(string message, string title = "Mod Assistant")
|
||||
|
@ -43,22 +45,26 @@ namespace ModAssistant
|
|||
notification.Dispose();
|
||||
}
|
||||
|
||||
private static void RestartAsAdmin(string Arguments)
|
||||
public static void StartAsAdmin(string Arguments, bool Close = false)
|
||||
{
|
||||
Process process = new Process();
|
||||
process.StartInfo.FileName = Process.GetCurrentProcess().MainModule.FileName;
|
||||
process.StartInfo.Arguments = Arguments;
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
process.StartInfo.Verb = "runas";
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
if (!Close)
|
||||
process.WaitForExit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show("Mod Assistant Updater needs to run as Admin. Please try again.");
|
||||
MessageBox.Show("Mod Assistant needs to run this task as Admin. Please try again.");
|
||||
}
|
||||
App.Current.Shutdown();
|
||||
if (Close)
|
||||
App.Current.Shutdown();
|
||||
}
|
||||
|
||||
public static string CalculateMD5(string filename)
|
||||
|
|
|
@ -40,30 +40,41 @@
|
|||
<Border Grid.ColumnSpan="2" Grid.Row="2" Margin="5" Background="LightGray" Height="30" MinWidth="450" >
|
||||
<TextBlock Margin="5" Text="{Binding InstallDirectory}" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="16"/>
|
||||
</Border>
|
||||
<Button Grid.Row="2" Grid.Column="2" Margin="3" Height="30" Width="80" Content="Select Folder" Click="Button_Click"/>
|
||||
<Button Grid.Row="2" Grid.Column="2" Margin="3" Height="30" Width="80" Content="Select Folder" Click="SelectDirButton_Click"/>
|
||||
|
||||
<TextBlock Grid.Row="3" Margin="5" Text="Save Selected Mods" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
<CheckBox Grid.Row="3" Grid.Column="1" Name="SaveSelected" VerticalAlignment="Center" HorizontalAlignment="Left" IsChecked="{Binding SaveSelection, Mode=TwoWay}" Checked="SaveSelected_Checked" Unchecked="SaveSelected_Unchecked"/>
|
||||
|
||||
<TextBlock Grid.Row="4" Margin="5" Text="Check Installed Mods" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
<CheckBox Grid.Row="4" Grid.Column="1" Name="CheckInstalled" VerticalAlignment="Center" HorizontalAlignment="Left" IsChecked="{Binding CheckInstalledMods, Mode=TwoWay}" Checked="CheckInstalled_Checked" Unchecked="CheckInstalled_Unchecked"/>
|
||||
<StackPanel Grid.Row="5" Margin="20,0,0,0" Background="LightYellow" HorizontalAlignment="Left">
|
||||
<StackPanel Grid.Row="4" Grid.Column="1" Margin="20,0,0,0" Background="LightYellow" HorizontalAlignment="Left">
|
||||
<TextBlock Margin="5,5,5,0" Text="Will slow down mod list loading" HorizontalAlignment="Left" />
|
||||
<TextBlock Margin="5,0,5,5" Text="Requires application restart" HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Row="6" Margin="20,5,5,5" Text="Select Installed Mods" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
<CheckBox Grid.Row="6" Grid.Column="1" Name="SelectInstalled" VerticalAlignment="Center" HorizontalAlignment="Left" IsChecked="{Binding SelectInstalledMods, Mode=TwoWay}" Checked="SelectInstalled_Checked" Unchecked="SelectInstalled_Unchecked"/>
|
||||
<TextBlock Grid.Row="5" Margin="20,5,5,5" Text="Select Installed Mods" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
<CheckBox Grid.Row="5" Grid.Column="1" Name="SelectInstalled" VerticalAlignment="Center" HorizontalAlignment="Left" IsChecked="{Binding SelectInstalledMods, Mode=TwoWay}" Checked="SelectInstalled_Checked" Unchecked="SelectInstalled_Unchecked"/>
|
||||
|
||||
|
||||
<TextBlock Grid.Row="7" Margin="5" Text="Enable OneClick Installs" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
<CheckBox Grid.Row="7" Grid.Column="1" Name="ProtocolHandler" VerticalAlignment="Center" HorizontalAlignment="Left" IsChecked="{Binding ProtocolHandlerEnabled, Mode=TwoWay}" Checked="ProtocolHandler_Checked" Unchecked="ProtocolHandler_Unchecked"/>
|
||||
<StackPanel Grid.Row="8" Margin="20,0,0,0" Background="LightYellow" HorizontalAlignment="Left">
|
||||
<TextBlock Grid.Row="6" Margin="5" Text="Enable OneClick Installs" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
|
||||
<StackPanel Grid.Row="6" Grid.Column="1" Margin="20,0,0,0" Background="LightYellow" HorizontalAlignment="Left">
|
||||
<TextBlock Margin="5,5,5,0" Text="Allows use of OneClick Install links" HorizontalAlignment="Left" />
|
||||
<TextBlock Margin="5,0,5,5" Text="Coming soon!" HorizontalAlignment="Left" />
|
||||
<!-- <TextBlock Margin="5,0,5,5" Text="Requires Admin" HorizontalAlignment="Left" /> -->
|
||||
<TextBlock Margin="5,0,5,5" Text="Requires Admin" HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="7" Margin="20,5,5,5" Text="ModelSaber" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
<CheckBox Grid.Row="7" Grid.Column="1" Name="ModelSaberProtocolHandler" VerticalAlignment="Center" HorizontalAlignment="Left" IsChecked="{Binding ModelSaberProtocolHandlerEnabled}" Checked="ModelSaberProtocolHandler_Checked" Unchecked="ModelSaberProtocolHandler_Unchecked"/>
|
||||
|
||||
<TextBlock Grid.Row="8" Margin="20,5,5,5" Text="BeatSaver" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
<CheckBox Grid.Row="8" Grid.Column="1" Name="BeatSaverProtocolHandler" VerticalAlignment="Center" HorizontalAlignment="Left" IsChecked="{Binding BeatSaberProtocolHandlerEnabled}" Checked="BeatSaverProtocolHandler_Checked" Unchecked="BeatSaverProtocolHandler_Unchecked"/>
|
||||
|
||||
<TextBlock Grid.Row="9" Margin="20,5,5,5" Text="ModSaber" FontWeight="Bold" HorizontalAlignment="Left" FontSize="16" />
|
||||
<CheckBox Grid.Row="9" Grid.Column="1" Name="ModSaberProtocolHandler" VerticalAlignment="Center" HorizontalAlignment="Left" IsChecked="{Binding ModSaberProtocolHandlerEnabled}" Checked="ModSaberProtocolHandler_Checked" Unchecked="ModSaberProtocolHandler_Unchecked"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<StackPanel Grid.Row="9" Margin="5" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<StackPanel Grid.Row="12" Margin="5" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Text="Game Type: " FontWeight="Bold" FontSize="16" />
|
||||
<TextBlock Text="{Binding InstallType}" FontSize="16"/>
|
||||
</StackPanel>
|
||||
|
|
|
@ -29,6 +29,10 @@ namespace ModAssistant.Pages
|
|||
public bool SaveSelection { get; set; }
|
||||
public bool CheckInstalledMods { get; set; }
|
||||
public bool SelectInstalledMods { get; set; }
|
||||
public bool ModelSaberProtocolHandlerEnabled { get; set; }
|
||||
public bool BeatSaverProtocolHandlerEnabled { get; set; }
|
||||
public bool ModSaberProtocolHandlerEnabled { get; set; }
|
||||
|
||||
|
||||
|
||||
public Options()
|
||||
|
@ -42,21 +46,19 @@ namespace ModAssistant.Pages
|
|||
if (!CheckInstalledMods)
|
||||
SelectInstalled.IsEnabled = false;
|
||||
|
||||
if (OneClickInstaller.IsRegistered())
|
||||
{
|
||||
ProtocolHandler.IsChecked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProtocolHandler.IsChecked = false;
|
||||
}
|
||||
|
||||
ProtocolHandler.IsEnabled = false;
|
||||
UpdateHandlerStatus();
|
||||
|
||||
this.DataContext = this;
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
public void UpdateHandlerStatus()
|
||||
{
|
||||
ModelSaberProtocolHandlerEnabled = OneClickInstaller.IsRegistered("modelsaber");
|
||||
BeatSaverProtocolHandlerEnabled = OneClickInstaller.IsRegistered("beatsaver");
|
||||
ModSaberProtocolHandlerEnabled = OneClickInstaller.IsRegistered("modsaber");
|
||||
}
|
||||
|
||||
private void SelectDirButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Utils.GetManualDir();
|
||||
}
|
||||
|
@ -98,14 +100,34 @@ namespace ModAssistant.Pages
|
|||
SelectInstalled.IsEnabled = false;
|
||||
}
|
||||
|
||||
private void ProtocolHandler_Checked(object sender, RoutedEventArgs e)
|
||||
public void ModelSaberProtocolHandler_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
OneClickInstaller.Register("modelsaber");
|
||||
}
|
||||
|
||||
private void ProtocolHandler_Unchecked(object sender, RoutedEventArgs e)
|
||||
public void ModelSaberProtocolHandler_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OneClickInstaller.Unregister("modelsaber");
|
||||
}
|
||||
|
||||
public void BeatSaverProtocolHandler_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OneClickInstaller.Register("beatsaver");
|
||||
}
|
||||
|
||||
public void BeatSaverProtocolHandler_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OneClickInstaller.Unregister("beatsaver");
|
||||
}
|
||||
|
||||
public void ModSaberProtocolHandler_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OneClickInstaller.Register("modsaber");
|
||||
}
|
||||
|
||||
public void ModSaberProtocolHandler_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OneClickInstaller.Unregister("modsaber");
|
||||
}
|
||||
|
||||
private void SelectInstalled_Checked(object sender, RoutedEventArgs e)
|
||||
|
|
Loading…
Reference in a new issue