initial commit
This commit is contained in:
commit
75170c76c9
17 changed files with 974 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
/packages/
|
||||||
|
riderModule.iml
|
||||||
|
/_ReSharper.Caches/
|
16
PS2-Manager.sln
Normal file
16
PS2-Manager.sln
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PS2_Manager", "PS2_Manager\PS2_Manager.csproj", "{05475C7F-17C8-4110-A381-3D21D6B3C85E}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{05475C7F-17C8-4110-A381-3D21D6B3C85E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{05475C7F-17C8-4110-A381-3D21D6B3C85E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{05475C7F-17C8-4110-A381-3D21D6B3C85E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{05475C7F-17C8-4110-A381-3D21D6B3C85E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
64
PS2_Manager/AddGameWindow.axaml
Normal file
64
PS2_Manager/AddGameWindow.axaml
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<Window 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"
|
||||||
|
Height="349"
|
||||||
|
Width="598"
|
||||||
|
CanResize="False"
|
||||||
|
x:Class="PS2_Manager.AddGameWindow"
|
||||||
|
Title="Install Game"
|
||||||
|
Loaded="Control_OnLoaded"
|
||||||
|
SizeChanged="Control_OnSizeChanged"
|
||||||
|
Background="#201c29"
|
||||||
|
SystemDecorations="None">
|
||||||
|
<Grid RowDefinitions="20, *">
|
||||||
|
<Grid Grid.Row="0">
|
||||||
|
<Border Background="#35313d"
|
||||||
|
PointerPressed="WindowDrag">
|
||||||
|
<Grid ColumnDefinitions="*, 20">
|
||||||
|
<TextBlock Name="WindowTitle" FontSize="12" Text="Install Game" Padding="8,0,0,0" VerticalAlignment="Center"/>
|
||||||
|
<Border Grid.Column="1" Background="#4b4753" PointerPressed="WindowClose">
|
||||||
|
<TextBlock FontSize="12" Text="×" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,*" Margin="20">
|
||||||
|
<!-- PS2 Cover -->
|
||||||
|
<Border Width="205" Height="292" Background="Black" CornerRadius="5" HorizontalAlignment="Left">
|
||||||
|
<Image Name="CoverImage" Source="Images/missing.png" Stretch="UniformToFill"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Spacer -->
|
||||||
|
<Rectangle Width="30" Grid.Column="1"/>
|
||||||
|
|
||||||
|
<!-- Form, centered vertically -->
|
||||||
|
<Grid Grid.Column="2">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Width="300" Spacing="10" HorizontalAlignment="Left">
|
||||||
|
<TextBlock Text="Game Name"/>
|
||||||
|
<TextBox Name="GameNameBox" MaxLength="20" TextChanged="GameNameBox_OnTextChanged"/>
|
||||||
|
|
||||||
|
<TextBlock Text="Serial Number"/>
|
||||||
|
<TextBox Name="SerialBox" IsEnabled="False"/>
|
||||||
|
|
||||||
|
<TextBlock Text="ISO Path"/>
|
||||||
|
<TextBox Name="IsoPathBox" IsEnabled="False"/>
|
||||||
|
|
||||||
|
<Button Name="InstallButton" Content="Install Game" HorizontalAlignment="Center"
|
||||||
|
Background="#aa4de8"
|
||||||
|
Foreground="Black"
|
||||||
|
Click="InstallButton_OnClick"/>
|
||||||
|
<ProgressBar Name="InstallationProgressBar"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Window>
|
95
PS2_Manager/AddGameWindow.axaml.cs
Normal file
95
PS2_Manager/AddGameWindow.axaml.cs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using PS2_Manager.Core;
|
||||||
|
|
||||||
|
namespace PS2_Manager;
|
||||||
|
|
||||||
|
public partial class AddGameWindow : Window
|
||||||
|
{
|
||||||
|
public string Gamepath { get; set; }
|
||||||
|
public Game newGame { get; set; }
|
||||||
|
public AddGameWindow(string filePath)
|
||||||
|
{
|
||||||
|
this.newGame = new Game(filePath);
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Control_OnLoaded(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
IsoPathBox.Text = this.newGame.GamePath;
|
||||||
|
SerialBox.Text = this.newGame.GameID;
|
||||||
|
CoverImage.Source = this.newGame.Cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Control_OnSizeChanged(object? sender, SizeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var newSize = e.NewSize;
|
||||||
|
Console.WriteLine($"[SizeChanged] New size: {newSize.Width} x {newSize.Height}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadPs2Cover(string gameId, Image imageControl)
|
||||||
|
{
|
||||||
|
//gameId = "SCPS-15110";
|
||||||
|
string url = $"https://github.com/xlenore/ps2-covers/blob/main/covers/default/{gameId}.jpg?raw=true";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using HttpClient client = new();
|
||||||
|
byte[] data = client.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
using MemoryStream stream = new(data);
|
||||||
|
var bitmap = new Bitmap(stream);
|
||||||
|
|
||||||
|
imageControl.Source = bitmap;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Error] Could not load cover for {gameId}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void WindowDrag(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
|
{
|
||||||
|
BeginMoveDrag(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WindowClose(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InstallButton_OnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
InstallButton.IsEnabled = false;
|
||||||
|
GameNameBox.IsEnabled = false;
|
||||||
|
InstallButton.Content = "Installing...";
|
||||||
|
newGame.Install();
|
||||||
|
newGame.InstallProgress.ValueChanged += () =>
|
||||||
|
{
|
||||||
|
InstallationProgressBar.Value = newGame.InstallProgress.Value;
|
||||||
|
};
|
||||||
|
newGame.InstallationFinished += (o, args) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Installation finished");
|
||||||
|
this.Close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GameNameBox_OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
this.newGame.Name = GameNameBox.Text ?? "";
|
||||||
|
}
|
||||||
|
}
|
11
PS2_Manager/App.axaml
Normal file
11
PS2_Manager/App.axaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="PS2_Manager.App"
|
||||||
|
RequestedThemeVariant="Default"
|
||||||
|
>
|
||||||
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
</Application.Styles>
|
||||||
|
</Application>
|
25
PS2_Manager/App.axaml.cs
Normal file
25
PS2_Manager/App.axaml.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using PS2_Manager.Core;
|
||||||
|
|
||||||
|
namespace PS2_Manager;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
Globals.LoadSettings();
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
desktop.MainWindow = new MainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
357
PS2_Manager/Core/ConfigUtils.cs
Normal file
357
PS2_Manager/Core/ConfigUtils.cs
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Nocksoft.IO.ConfigFiles;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static class ConfigUtils
|
||||||
|
{
|
||||||
|
private static INIFile config;
|
||||||
|
|
||||||
|
public static void InitConfig(string path = "config.ini")
|
||||||
|
{
|
||||||
|
config = new INIFile(path, true);
|
||||||
|
}
|
||||||
|
public static void SetValue<T>(this Enum _type, T _value)
|
||||||
|
{
|
||||||
|
if (!checkIfInitialized())
|
||||||
|
return;
|
||||||
|
config.SetValue(getStrType(_type), getStrName(_type), _value.ToString());
|
||||||
|
}
|
||||||
|
public static T GetValue<T>(this Enum _type)
|
||||||
|
{
|
||||||
|
if (!checkIfInitialized())
|
||||||
|
return default(T);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var value = config.GetValue(getStrType(_type), getStrName(_type));
|
||||||
|
return (T)Convert.ChangeType(value, typeof(T));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("ConfigManager Exception on " + getStrType(_type) + "." + getStrName(_type) + ": " + ex.Message);
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static string getStrType(Enum _enum)
|
||||||
|
{
|
||||||
|
return _enum.GetType().Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string getStrName(Enum _enum)
|
||||||
|
{
|
||||||
|
return Enum.GetName(_enum.GetType(), _enum);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool checkIfInitialized()
|
||||||
|
{
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Config not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright by Nocksoft
|
||||||
|
* https://www.nocksoft.de
|
||||||
|
* https://nocksoft.de/tutorials/visual-c-sharp-arbeiten-mit-ini-dateien/
|
||||||
|
* https://github.com/Nocksoft/INIFile.cs
|
||||||
|
* -----------------------------------
|
||||||
|
* Author: Rafael Nockmann @ Nocksoft
|
||||||
|
* Updated: 2022-01-09
|
||||||
|
* Version: 1.0.3
|
||||||
|
*
|
||||||
|
* Language: Visual C#
|
||||||
|
*
|
||||||
|
* License: MIT license
|
||||||
|
* License URL: https://github.com/Nocksoft/INIFile.cs/blob/master/LICENSE
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Provides basic functions for working with INI files.
|
||||||
|
*
|
||||||
|
* ##############################################################################################
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace Nocksoft.IO.ConfigFiles
|
||||||
|
{
|
||||||
|
public class INIFile
|
||||||
|
{
|
||||||
|
private string _File;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call the constructor creates a new object of the INIFile class to work with INI files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">Name of INI file, which you want to access.</param>
|
||||||
|
/// <param name="createFile">Specifies whether the INI file should be created if it does not exist.</param>
|
||||||
|
public INIFile(string file, bool createFile = false)
|
||||||
|
{
|
||||||
|
if (createFile == true && File.Exists(file) == false)
|
||||||
|
{
|
||||||
|
FileInfo fileInfo = new FileInfo(file);
|
||||||
|
FileStream fileStream = fileInfo.Create();
|
||||||
|
fileStream.Close();
|
||||||
|
}
|
||||||
|
_File = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all comments and empty lines from a complete section and returns the sections.
|
||||||
|
/// This method is not case-sensitive.
|
||||||
|
/// The return value does not contain any spaces at the beginning or at the end of a line.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="section">Name of the requested section.</param>
|
||||||
|
/// <param name="includeComments">Specifies whether comments should also be returned.</param>
|
||||||
|
/// <returns>Returns the whole section.</returns>
|
||||||
|
public List<string> GetSection(string section, bool includeComments = false)
|
||||||
|
{
|
||||||
|
section = CheckSection(section);
|
||||||
|
|
||||||
|
List<string> completeSection = new List<string>();
|
||||||
|
bool sectionStart = false;
|
||||||
|
|
||||||
|
string[] fileArray = File.ReadAllLines(_File);
|
||||||
|
|
||||||
|
foreach (var item in fileArray)
|
||||||
|
{
|
||||||
|
if (item.Length <= 0) continue;
|
||||||
|
|
||||||
|
// Beginning of section.
|
||||||
|
if (item.Replace(" ", "").ToLower() == section)
|
||||||
|
{
|
||||||
|
sectionStart = true;
|
||||||
|
}
|
||||||
|
// Beginning of next section.
|
||||||
|
if (sectionStart == true && item.Replace(" ", "").ToLower() != section && item.Replace(" ", "").Substring(0, 1) == "[" && item.Replace(" ", "").Substring(item.Length - 1, 1) == "]")
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sectionStart == true)
|
||||||
|
{
|
||||||
|
// Add the entry to the List<string> completeSection, if it is not a comment or an empty entry.
|
||||||
|
if (includeComments == false
|
||||||
|
&& item.Replace(" ", "").Substring(0, 1) != ";" && !string.IsNullOrWhiteSpace(item))
|
||||||
|
{
|
||||||
|
completeSection.Add(ReplaceSpacesAtStartAndEnd(item));
|
||||||
|
}
|
||||||
|
if (includeComments == true && !string.IsNullOrWhiteSpace(item))
|
||||||
|
{
|
||||||
|
completeSection.Add(ReplaceSpacesAtStartAndEnd(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completeSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The method returns a value for the associated key.
|
||||||
|
/// This method is not case-sensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="section">Name of the requested section.</param>
|
||||||
|
/// <param name="key">Name of the requested key.</param>
|
||||||
|
/// <param name="convertValueToLower">If "true" is passed, the value will be returned in lowercase letters.</param>
|
||||||
|
/// <returns>Returns the value for the specified key in the specified section, if available, otherwise null.</returns>
|
||||||
|
public string GetValue(string section, string key, bool convertValueToLower = false)
|
||||||
|
{
|
||||||
|
section = CheckSection(section);
|
||||||
|
key = key.ToLower();
|
||||||
|
|
||||||
|
List<string> completeSection = GetSection(section);
|
||||||
|
|
||||||
|
foreach (var item in completeSection)
|
||||||
|
{
|
||||||
|
// Continue if entry is no key.
|
||||||
|
if (!item.Contains("=") && item.Contains("[") && item.Contains("]")) continue;
|
||||||
|
|
||||||
|
string[] keyAndValue = item.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (keyAndValue[0].ToLower() == key && keyAndValue.Count() > 1)
|
||||||
|
{
|
||||||
|
if (convertValueToLower == true)
|
||||||
|
{
|
||||||
|
keyAndValue[1] = keyAndValue[1].ToLower();
|
||||||
|
}
|
||||||
|
return keyAndValue[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set or add a value of the associated key in the specified section.
|
||||||
|
/// This method is not case-sensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="section">Name of the section.</param>
|
||||||
|
/// <param name="key">Name of the key.</param>
|
||||||
|
/// <param name="value">Value to save.</param>
|
||||||
|
/// <param name="convertValueToLower">If "true" is passed, the value will be saved in lowercase letters.</param>
|
||||||
|
public void SetValue(string section, string key, string value, bool convertValueToLower = false)
|
||||||
|
{
|
||||||
|
section = CheckSection(section, false);
|
||||||
|
string sectionToLower = section.ToLower();
|
||||||
|
|
||||||
|
bool sectionFound = false;
|
||||||
|
|
||||||
|
List<string> iniFileContent = new List<string>();
|
||||||
|
|
||||||
|
string[] fileLines = File.ReadAllLines(_File);
|
||||||
|
|
||||||
|
// Creates a new INI file if none exists.
|
||||||
|
if (fileLines.Length <= 0)
|
||||||
|
{
|
||||||
|
iniFileContent = AddSection(iniFileContent, section, key, value, convertValueToLower);
|
||||||
|
WriteFile(iniFileContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < fileLines.Length; i++)
|
||||||
|
{
|
||||||
|
// Possibility 1: The desired section has not (yet) been found.
|
||||||
|
if (fileLines[i].Replace(" ", "").ToLower() != sectionToLower)
|
||||||
|
{
|
||||||
|
iniFileContent.Add(fileLines[i]);
|
||||||
|
// If a section does not exist, the section will be created.
|
||||||
|
if (i == fileLines.Length - 1 && fileLines[i].Replace(" ", "").ToLower() != sectionToLower && sectionFound == false)
|
||||||
|
{
|
||||||
|
iniFileContent.Add(null);
|
||||||
|
iniFileContent = AddSection(iniFileContent, section, key, value, convertValueToLower);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Possibility 2 -> Desired section was found.
|
||||||
|
sectionFound = true;
|
||||||
|
|
||||||
|
// Get the complete section in which the target key may be.
|
||||||
|
List<string> targetSection = GetSection(sectionToLower, true);
|
||||||
|
|
||||||
|
for (int x = 0; x < targetSection.Count; x++)
|
||||||
|
{
|
||||||
|
string[] targetKey = targetSection[x].Split(new string[] { "=" }, StringSplitOptions.None);
|
||||||
|
// When the target key is found.
|
||||||
|
if (targetKey[0].ToLower() == key.ToLower())
|
||||||
|
{
|
||||||
|
if (convertValueToLower == true)
|
||||||
|
{
|
||||||
|
iniFileContent.Add(key + "=" + value.ToLower());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iniFileContent.Add(key + "=" + value);
|
||||||
|
}
|
||||||
|
i = i + x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iniFileContent.Add(targetSection[x]);
|
||||||
|
// If the target key is not found, it will be created.
|
||||||
|
if (x == targetSection.Count - 1 && targetKey[0].ToLower() != key.ToLower())
|
||||||
|
{
|
||||||
|
if (convertValueToLower == true)
|
||||||
|
{
|
||||||
|
iniFileContent.Add(key + "=" + value.ToLower());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iniFileContent.Add(key + "=" + value);
|
||||||
|
}
|
||||||
|
i = i + x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteFile(iniFileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that a section is always in the following format: [section].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="section">Section to be checked for correct format.</param>
|
||||||
|
/// <param name="convertToLower">Specifies whether the section should be vonverted in lower case letters.</param>
|
||||||
|
/// <returns>Returns section in this form: [section].</returns>
|
||||||
|
private string CheckSection(string section, bool convertToLower = true)
|
||||||
|
{
|
||||||
|
if (convertToLower == true)
|
||||||
|
{
|
||||||
|
section = section.ToLower();
|
||||||
|
}
|
||||||
|
if (!section.StartsWith("[") && !section.EndsWith("]"))
|
||||||
|
{
|
||||||
|
section = "[" + section + "]";
|
||||||
|
}
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes leading and trailing spaces from sections, keys and values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">String to be trimmed.</param>
|
||||||
|
/// <returns>Returns the trimmed string.</returns>
|
||||||
|
private string ReplaceSpacesAtStartAndEnd(string item)
|
||||||
|
{
|
||||||
|
// If the string has a key and a value.
|
||||||
|
if (item.Contains("=") && !item.Contains("[") && !item.Contains("]"))
|
||||||
|
{
|
||||||
|
string[] keyAndValue = item.Split(new string[] { "=" }, StringSplitOptions.None);
|
||||||
|
return keyAndValue[0].Trim() + "=" + keyAndValue[1].Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new section with key value pair.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iniFileContent">List iniFileContent from SetValue.</param>
|
||||||
|
/// <param name="section">Section to be created.</param>
|
||||||
|
/// <param name="key">Key to be added.</param>
|
||||||
|
/// <param name="value">Value to be added.</param>
|
||||||
|
/// <param name="convertValueToLower">Specifies whether the key and value should be saved in lower case letters.</param>
|
||||||
|
/// <returns>Returns the new created section with key value pair.</returns>
|
||||||
|
private List<string> AddSection(List<string> iniFileContent, string section, string key, string value, bool convertValueToLower)
|
||||||
|
{
|
||||||
|
if (convertValueToLower == true)
|
||||||
|
{
|
||||||
|
value = value.ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
iniFileContent.Add(section);
|
||||||
|
iniFileContent.Add($"{key}={value}");
|
||||||
|
return iniFileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteFile(List<string> content)
|
||||||
|
{
|
||||||
|
StreamWriter writer = new StreamWriter(_File);
|
||||||
|
foreach (var item in content)
|
||||||
|
{
|
||||||
|
writer.WriteLine(item);
|
||||||
|
}
|
||||||
|
writer.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
103
PS2_Manager/Core/Game.cs
Normal file
103
PS2_Manager/Core/Game.cs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
namespace PS2_Manager.Core;
|
||||||
|
|
||||||
|
public class Game
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string GameID { get; set; }
|
||||||
|
public string GamePath { get; set; }
|
||||||
|
public Bitmap Cover { get; set; }
|
||||||
|
public EventHandler? InstallationFinished { get; set; }
|
||||||
|
public UpdateVar<double> InstallProgress { get; private set; }
|
||||||
|
|
||||||
|
public Game(string isoPath)
|
||||||
|
{
|
||||||
|
this.GamePath = isoPath;
|
||||||
|
this.GameID = ISO.GetSerial(isoPath);
|
||||||
|
this.Cover = this.GetCover();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Install()
|
||||||
|
{
|
||||||
|
this.InstallProgress = new UpdateVar<double>();
|
||||||
|
await this.CopyIsoWithProgressAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap GetCover()
|
||||||
|
{
|
||||||
|
|
||||||
|
Bitmap cover = null;
|
||||||
|
string url = $"https://github.com/xlenore/ps2-covers/blob/main/covers/default/{this.GameID.Replace(".", "").Replace("_", "-")}.jpg?raw=true";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using HttpClient client = new();
|
||||||
|
byte[] data = client.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
using MemoryStream stream = new(data);
|
||||||
|
var bitmap = new Bitmap(stream);
|
||||||
|
|
||||||
|
cover = bitmap;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Error] Could not load cover for {this.GameID}: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CopyIsoWithProgressAsync()
|
||||||
|
{
|
||||||
|
string targetDirectory = settings.library_path.GetValue<string>();
|
||||||
|
if(!Directory.Exists(Path.Combine(targetDirectory, "DVD")))
|
||||||
|
Directory.CreateDirectory(Path.Combine(targetDirectory, "DVD"));
|
||||||
|
|
||||||
|
string newFileName = $"{this.GameID}.{this.Name}.iso";
|
||||||
|
string destPath = Path.Combine(Path.Combine(targetDirectory, "DVD"), newFileName);
|
||||||
|
|
||||||
|
const int bufferSize = 1024 * 1024;
|
||||||
|
byte[] buffer = new byte[bufferSize];
|
||||||
|
|
||||||
|
long totalBytes = new FileInfo(this.GamePath).Length;
|
||||||
|
long bytesCopied = 0;
|
||||||
|
|
||||||
|
using (FileStream source = new FileStream(this.GamePath, FileMode.Open, FileAccess.Read))
|
||||||
|
using (FileStream destination = new FileStream(destPath, FileMode.Create, FileAccess.Write))
|
||||||
|
{
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await source.ReadAsync(buffer.AsMemory(0, bufferSize))) > 0)
|
||||||
|
{
|
||||||
|
await destination.WriteAsync(buffer.AsMemory(0, bytesRead));
|
||||||
|
bytesCopied += bytesRead;
|
||||||
|
|
||||||
|
this.InstallProgress.Value = (double)bytesCopied / totalBytes * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.InstallCover();
|
||||||
|
this.InstallationFinished?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
Console.WriteLine($"Copied ISO to: {destPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void InstallCover()
|
||||||
|
{
|
||||||
|
string targetDirectory = settings.library_path.GetValue<string>();
|
||||||
|
if(!Directory.Exists(Path.Combine(targetDirectory, "ART")))
|
||||||
|
Directory.CreateDirectory(Path.Combine(targetDirectory, "ART"));
|
||||||
|
this.Cover.Save(Path.Combine(Path.Combine(targetDirectory, "ART"), this.GameID + "_COV.png"));
|
||||||
|
}
|
||||||
|
}
|
54
PS2_Manager/Core/Globals.cs
Normal file
54
PS2_Manager/Core/Globals.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
|
namespace PS2_Manager.Core;
|
||||||
|
|
||||||
|
public static class Globals
|
||||||
|
{
|
||||||
|
|
||||||
|
public static List<Game> Games { get; set; }
|
||||||
|
|
||||||
|
public static void LoadSettings()
|
||||||
|
{
|
||||||
|
Games = new List<Game>();
|
||||||
|
ConfigUtils.InitConfig("settings.ini");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class FileDialogTypes
|
||||||
|
{
|
||||||
|
public static FilePickerFileType PS2Game { get; } = new("PS2 Game")
|
||||||
|
{
|
||||||
|
Patterns = new[] { "*.iso" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum settings
|
||||||
|
{
|
||||||
|
library_path
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateVar<T>
|
||||||
|
{
|
||||||
|
private T _value;
|
||||||
|
|
||||||
|
public Action ValueChanged;
|
||||||
|
|
||||||
|
public T Value
|
||||||
|
{
|
||||||
|
get => _value;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
OnValueChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnValueChanged() => ValueChanged?.Invoke();
|
||||||
|
}
|
44
PS2_Manager/Core/ISO.cs
Normal file
44
PS2_Manager/Core/ISO.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using DiscUtils.Iso9660;
|
||||||
|
|
||||||
|
public static class ISO
|
||||||
|
{
|
||||||
|
public static string GetSerial(string isoPath)
|
||||||
|
{
|
||||||
|
using (FileStream isoStream = File.OpenRead(isoPath))
|
||||||
|
{
|
||||||
|
CDReader cd = new CDReader(isoStream, true);
|
||||||
|
|
||||||
|
if (cd.FileExists("SYSTEM.CNF"))
|
||||||
|
{
|
||||||
|
using (Stream fileStream = cd.OpenFile("SYSTEM.CNF", FileMode.Open))
|
||||||
|
using (StreamReader reader = new StreamReader(fileStream))
|
||||||
|
{
|
||||||
|
string line;
|
||||||
|
while ((line = reader.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
line = line.Trim();
|
||||||
|
|
||||||
|
if (line.StartsWith("BOOT2") || line.StartsWith("BOOT"))
|
||||||
|
{
|
||||||
|
int pathStart = line.IndexOf(@"\");
|
||||||
|
int pathEnd = line.IndexOf(";");
|
||||||
|
|
||||||
|
if (pathStart != -1 && pathEnd != -1 && pathEnd > pathStart)
|
||||||
|
{
|
||||||
|
string bootPath = line.Substring(pathStart + 1, pathEnd - pathStart - 1);
|
||||||
|
return bootPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
25
PS2_Manager/Core/Util.cs
Normal file
25
PS2_Manager/Core/Util.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace PS2_Manager.Core;
|
||||||
|
|
||||||
|
public static class Util
|
||||||
|
{
|
||||||
|
public static string? OpenFileDialogSync(Window parent)
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "Select PS2 ISO",
|
||||||
|
AllowMultiple = false,
|
||||||
|
Filters = new List<FileDialogFilter>
|
||||||
|
{
|
||||||
|
new FileDialogFilter { Name = "PS2 ISO", Extensions = { "iso", "bin" } },
|
||||||
|
new FileDialogFilter { Name = "All Files", Extensions = { "*" } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = dialog.ShowAsync(parent).GetAwaiter().GetResult();
|
||||||
|
return result?.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
BIN
PS2_Manager/Images/missing.png
Normal file
BIN
PS2_Manager/Images/missing.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
39
PS2_Manager/MainWindow.axaml
Normal file
39
PS2_Manager/MainWindow.axaml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<Window 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="PS2_Manager.MainWindow"
|
||||||
|
Title="PS2_Manager"
|
||||||
|
Background="#201c29"
|
||||||
|
SystemDecorations="None"
|
||||||
|
Width="880"
|
||||||
|
Height="520"
|
||||||
|
Loaded="Control_OnLoaded">
|
||||||
|
<Grid RowDefinitions="20, *">
|
||||||
|
<Grid Grid.Row="0">
|
||||||
|
<Border Background="#35313d"
|
||||||
|
PointerPressed="WindowDrag">
|
||||||
|
<Grid ColumnDefinitions="*, 20">
|
||||||
|
<TextBlock Name="WindowTitle" FontSize="12" Padding="8,0,0,0" VerticalAlignment="Center"/>
|
||||||
|
<Border Grid.Column="1" Background="#625f69" PointerPressed="WindowClose">
|
||||||
|
<TextBlock FontSize="12" Text="×" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Border Background="Red" CornerRadius="0" Margin="8">
|
||||||
|
<Grid ColumnDefinitions="300, *">
|
||||||
|
<Border Background="Aqua">
|
||||||
|
<ListBox Name="GamesList"></ListBox>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Column="1" Background="Aqua">
|
||||||
|
<Button Click="OpenFileButton_Clicked" Content="Install Game"></Button>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
69
PS2_Manager/MainWindow.axaml.cs
Normal file
69
PS2_Manager/MainWindow.axaml.cs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
using PS2_Manager.Core;
|
||||||
|
|
||||||
|
namespace PS2_Manager;
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WindowDrag(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
|
{
|
||||||
|
BeginMoveDrag(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WindowClose(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OpenFileButton_Clicked(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
var topLevel = TopLevel.GetTopLevel(this);
|
||||||
|
|
||||||
|
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Open Text File",
|
||||||
|
AllowMultiple = false,
|
||||||
|
FileTypeFilter = new []
|
||||||
|
{
|
||||||
|
Globals.FileDialogTypes.PS2Game
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files.Count >= 1)
|
||||||
|
{
|
||||||
|
//Console.WriteLine(ISO.GetSerial());
|
||||||
|
new AddGameWindow(files[0].Path.LocalPath).Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Control_OnLoaded(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
WindowTitle.Text = "PS2 Games Manager";
|
||||||
|
if (String.IsNullOrEmpty(settings.library_path.GetValue<string>()))
|
||||||
|
{
|
||||||
|
var topLevel = TopLevel.GetTopLevel(this);
|
||||||
|
var path = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Open Game Library",
|
||||||
|
AllowMultiple = false
|
||||||
|
});
|
||||||
|
Console.WriteLine(path[0].Path.LocalPath);
|
||||||
|
if(!String.IsNullOrEmpty(path[0].Path.LocalPath))
|
||||||
|
settings.library_path.SetValue(path[0].Path.LocalPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
PS2_Manager/PS2_Manager.csproj
Normal file
28
PS2_Manager/PS2_Manager.csproj
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.2.7" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.2.7" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.7" />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.7" />
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.7">
|
||||||
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Images\missing.png" />
|
||||||
|
<AvaloniaResource Include="Images\missing.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
21
PS2_Manager/Program.cs
Normal file
21
PS2_Manager/Program.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using Avalonia;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PS2_Manager;
|
||||||
|
|
||||||
|
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) => BuildAvaloniaApp()
|
||||||
|
.StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.WithInterFont()
|
||||||
|
.LogToTrace();
|
||||||
|
}
|
18
PS2_Manager/app.manifest
Normal file
18
PS2_Manager/app.manifest
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<!-- This manifest is used on Windows only.
|
||||||
|
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||||
|
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="PS2_Manager.Desktop"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- A list of the Windows versions that this application has been tested on
|
||||||
|
and is designed to work with. Uncomment the appropriate elements
|
||||||
|
and Windows will automatically select the most compatible environment. -->
|
||||||
|
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
Loading…
Add table
Reference in a new issue