Browse Source

added new tab to versions downloader window for managing installed versions, updated avalonia libraries

Veloe 2 years ago
parent
commit
e5286a4fcf

+ 2 - 2
VeloeLauncherUpdater/VeloeLauncherUpdater.csproj

@@ -6,8 +6,8 @@
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <DebugType>embedded</DebugType>
-    <AssemblyVersion>1.0.1.1</AssemblyVersion>
-    <FileVersion>1.0.1.1</FileVersion>
+    <AssemblyVersion>1.0.1.3</AssemblyVersion>
+    <FileVersion>1.0.1.3</FileVersion>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
VeloeMinecraftLauncher/Program.cs

@@ -4,7 +4,7 @@ using Serilog;
 using Serilog.Events;
 using System;
 using System.Diagnostics;
-using VeloeMinecraftLauncher.MinecraftLauncher;
+using VeloeMinecraftLauncher.Utils;
 
 namespace VeloeMinecraftLauncher
 {

+ 1 - 1
VeloeMinecraftLauncher/MinecraftLauncher/CaptureFilePathHook .cs → VeloeMinecraftLauncher/Utils/CaptureFilePathHook .cs

@@ -6,7 +6,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace VeloeMinecraftLauncher.MinecraftLauncher
+namespace VeloeMinecraftLauncher.Utils
 {
     internal class CaptureFilePathHook : FileLifecycleHooks
     {

+ 2 - 2
VeloeMinecraftLauncher/MinecraftLauncher/Downloader.cs → VeloeMinecraftLauncher/Utils/Downloader.cs

@@ -13,7 +13,7 @@ using System.Threading.Tasks;
 using VeloeMinecraftLauncher.Entity.Assets;
 using VeloeMinecraftLauncher.Models.Entity;
 
-namespace VeloeMinecraftLauncher.MinecraftLauncher;
+namespace VeloeMinecraftLauncher.Utils;
 
 internal static class Downloader
 {
@@ -566,7 +566,7 @@ internal static class Downloader
 
                 string modpackUrl = string.Empty;
 
-                if (SelectedModpack.Url is not null)
+                if (!string.IsNullOrWhiteSpace(SelectedModpack.Url))
                     modpackUrl = SelectedModpack.Url;
                 else
                     modpackUrl = $"https://files.veloe.link/launcher/modpacks/{SelectedModpack.Name}.zip";

+ 1 - 1
VeloeMinecraftLauncher/MinecraftLauncher/EventSink.cs → VeloeMinecraftLauncher/Utils/EventSink.cs

@@ -1,7 +1,7 @@
 using Serilog.Events;
 using System;
 
-namespace VeloeMinecraftLauncher.MinecraftLauncher;
+namespace VeloeMinecraftLauncher.Utils;
 
 public class EventSink : Serilog.Core.ILogEventSink
 {

+ 1 - 1
VeloeMinecraftLauncher/MinecraftLauncher/JavaProcessException.cs → VeloeMinecraftLauncher/Utils/JavaProcessException.cs

@@ -4,7 +4,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace VeloeMinecraftLauncher.MinecraftLauncher
+namespace VeloeMinecraftLauncher.Utils
 {
     internal class JavaProcessException : Exception
     {

+ 1 - 1
VeloeMinecraftLauncher/MinecraftLauncher/Settings.cs → VeloeMinecraftLauncher/Utils/Settings.cs

@@ -5,7 +5,7 @@ using System.IO;
 using System.Linq;
 using System.Text.Json;
 
-namespace VeloeMinecraftLauncher.MinecraftLauncher;
+namespace VeloeMinecraftLauncher.Utils;
 
 internal static class Settings
 {

+ 30 - 12
VeloeMinecraftLauncher/MinecraftLauncher/StartCommandBuilder.cs → VeloeMinecraftLauncher/Utils/StartCommandBuilder.cs

@@ -1,11 +1,13 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Text;
 using System.Text.Json;
+using VeloeMinecraftLauncher.Entity.Version;
 
-namespace VeloeMinecraftLauncher.MinecraftLauncher;
+namespace VeloeMinecraftLauncher.Utils;
 
 internal static class StartCommandBuilder
 {
@@ -66,18 +68,10 @@ internal static class StartCommandBuilder
 
             //if no path
             if (library.Downloads is null) //for optifine
-            {
-
-                string name = library.Name;
-                var dirs = name.Split(':');
-                dirs[0] = dirs[0].Replace('.','/');
-                var libPath = String.Empty;
-                foreach (var dir in dirs)
-                {
-                    libPath += dir+"/";
-                }
+            {          
+                var libPath = GetLibPathFromName(library.Name);
                 if (rulesVaild)
-                    returnString.Append(Path.GetFullPath(Settings.minecraftForlderPath + "libraries/" + libPath + dirs[dirs.Length-2]+"-"+dirs[dirs.Length-1]+".jar") + separator);
+                    returnString.Append(Path.GetFullPath(Settings.minecraftForlderPath + "libraries/" + libPath +".jar") + separator);
 
                 continue;
             }
@@ -386,4 +380,28 @@ internal static class StartCommandBuilder
         }
         return returnString.ToString();
     }
+
+    public static string GetLibPathFromName(string name, bool extention = false)
+    {
+        Debug.WriteLine($"GameLibPathFromName: {name}");
+        var dirs = name.Split(':');
+        dirs[0] = dirs[0].Replace('.', '/');
+        var libPath = String.Empty;
+        foreach (var dir in dirs)
+        {
+            libPath += dir + "/";
+        }
+
+        libPath += dirs[dirs.Length - 2] + "-" + dirs[dirs.Length - 1];
+
+        if (extention)
+            libPath += ".jar";
+
+        if (File.Exists(Settings.minecraftForlderPath + "libraries/" + libPath))
+            Debug.WriteLine($"Lib exists: {Settings.minecraftForlderPath + "libraries/" + libPath}");
+        else
+            Debug.WriteLine($"Lib not exists: {Settings.minecraftForlderPath + "libraries/" + libPath}");
+
+        return libPath;
+    }
 }

+ 12 - 6
VeloeMinecraftLauncher/VeloeMinecraftLauncher.csproj

@@ -10,8 +10,9 @@
     <DebugType>embedded</DebugType>
     <StartupObject>VeloeMinecraftLauncher.Program</StartupObject>
     <PlatformTarget>x64</PlatformTarget>
-    <AssemblyVersion>1.2.2.197</AssemblyVersion>
-    <FileVersion>1.2.2.197</FileVersion>
+    <AssemblyVersion>1.2.2.351</AssemblyVersion>
+    <FileVersion>1.2.2.351</FileVersion>
+    <Configurations>Debug;Release</Configurations>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
     <NoWarn>NU1605</NoWarn>
@@ -32,12 +33,12 @@
     <TrimmableAssembly Include="Avalonia.Themes.Default" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Avalonia" Version="0.10.18" />
-    <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
-    <PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
+    <PackageReference Include="Avalonia" Version="0.10.21" />
+    <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.21" />
+    <PackageReference Include="Avalonia.Desktop" Version="0.10.21" />
     <!--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.18" />
-    <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
+    <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.21" />
     <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
     <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.0" />
     <PackageReference Include="ReactiveUI.Blazor" Version="18.4.1" />
@@ -48,4 +49,9 @@
     <PackageReference Include="System.Net.Http" Version="4.3.4" />
     <PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
   </ItemGroup>
+  <ItemGroup>
+    <Compile Update="Views\MessageWindow.axaml.cs">
+      <DependentUpon>MessageWindow.axaml</DependentUpon>
+    </Compile>
+  </ItemGroup>
 </Project>

+ 1 - 1
VeloeMinecraftLauncher/ViewModels/MainWindowViewModel.cs

@@ -8,7 +8,7 @@ using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
 using VeloeMinecraftLauncher.Entity.LauncherProfiles;
-using VeloeMinecraftLauncher.MinecraftLauncher;
+using VeloeMinecraftLauncher.Utils;
 using VeloeMinecraftLauncher.Views;
 using Microsoft.AspNetCore.SignalR.Client;
 using VeloeMinecraftLauncher.Entity.McStatus;

+ 1 - 4
VeloeMinecraftLauncher/ViewModels/SettingsWindowViewModel.cs

@@ -3,16 +3,13 @@ using ReactiveUI.Validation.Extensions;
 using System;
 using System.IO;
 using System.Reflection;
-using VeloeMinecraftLauncher.MinecraftLauncher;
+using VeloeMinecraftLauncher.Utils;
 using Avalonia.Controls;
 using System.Threading.Tasks;
 using System.Collections.ObjectModel;
 using Serilog.Events;
 using System.Linq;
 using ReactiveUI.Validation.Components;
-using VeloeMinecraftLauncher.Views;
-using Avalonia.Threading;
-using System.Runtime;
 
 namespace VeloeMinecraftLauncher.ViewModels;
 

+ 611 - 2
VeloeMinecraftLauncher/ViewModels/VersionsDownloaderViewModel.cs

@@ -4,13 +4,17 @@ using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
+using System.IO;
 using System.Linq;
 using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
+using VeloeMinecraftLauncher.Entity.LauncherProfiles;
 using VeloeMinecraftLauncher.Entity.VersionManifest;
-using VeloeMinecraftLauncher.MinecraftLauncher;
+using VeloeMinecraftLauncher.Utils;
 using VeloeMinecraftLauncher.Models.Entity;
+using VeloeMinecraftLauncher.Entity.Version;
+using System.Diagnostics;
 
 namespace VeloeMinecraftLauncher.ViewModels;
 
@@ -33,15 +37,27 @@ public class VersionsDownloaderViewModel : ViewModelBase
     private long _maxprogressvalue = 100;
     private string _downloadingFileName = "No active downloads";
     private string _tasksStatusLine = "No tasks started yet";
+
+    /// <summary>
+    /// Token source for downloading tasks
+    /// </summary>
     private CancellationTokenSource _tokenSource = new();
+
+    /// <summary>
+    /// Token source for checking downloading options on selection from verions available for downloading
+    /// </summary>
     private CancellationTokenSource _filteredVersionTokenSource = new();
 
     Serilog.ILogger _logger;
 
     ObservableCollection<Entity.VersionManifest.Version> _filteredVersions;
+    ObservableCollection<DownloadedVersion> _downloadedVersions;
     ObservableCollection<Modpack> _modpackVersions;
+    ObservableCollection<TreeNode> _downloadedVersionTree;
+    TreeNode _libraries;
     List<Entity.VersionManifest.Version> _modpackVersionsAsVersion;
     Entity.VersionManifest.Version _filteredVersion;
+    DownloadedVersion _downloadedVersion;
     Modpack? _selectedModpack;
     VersionManifest _versionManifest;
 
@@ -61,12 +77,21 @@ public class VersionsDownloaderViewModel : ViewModelBase
                 {
                     _modpackVersions = new();
                 }
+                if (DownloadedVersionTree is null)
+                {
+                    _downloadedVersionTree = new();
+                }
+                if (DownloadedVersionsDictionary is null)
+                {
+                    DownloadedVersionsDictionary = new();
+                }
                 _logger.Debug("Getting versionManifest.json");
                 _versionManifest = await Downloader.DownloadAndDeserializeJsonData<VersionManifest>("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json") ?? new();
                 _modpackVersions.AddRange(await Downloader.DownloadAndDeserializeJsonData<List<Modpack>>("https://files.veloe.link/launcher/modpacks.json") ?? new());
                 _modpackVersionsAsVersion = _modpackVersions.Select(v=> new Entity.VersionManifest.Version() { Id = v.Name, Type = "modpack", ComplianceLevel = v.Revision}).ToList();
                 _logger.Debug("Updating available versions to download.");
                 UpdateList();
+                SearchGameFolderForVersions();
                 IsControlsEnabled = true;
             });
         }
@@ -167,12 +192,366 @@ public class VersionsDownloaderViewModel : ViewModelBase
         }
     }
 
+    public DownloadedVersion DownloadedVersion
+    {
+        get => _downloadedVersion;
+        set 
+        { 
+            this.RaiseAndSetIfChanged(ref _downloadedVersion, value);
+            Task.Run(() =>
+            {
+                try
+                {
+                    IsControlsEnabled = false;
+                    DownloadedVersionTree.Clear();
+                    DownloadedVersionTree = new();
+                    this.RaisePropertyChanged(nameof(DownloadedVersionTree));
+                    DownloadedVersionsDictionary.Clear();
+
+                    if (value is null)
+                    {
+                        IsControlsEnabled = true;
+                        return;
+                    }
+
+                    Entity.Version.Version? version = null;
+
+                    foreach (var dversion in DownloadedVersions)
+                    {
+                        string json;
+                        using (StreamReader reader = new StreamReader(dversion.path))
+                        {
+                            json = reader.ReadToEnd();
+                        }
+
+                        var versionObject = JsonSerializer.Deserialize<Entity.Version.Version>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+                        DownloadedVersionsDictionary.Add(dversion.version, versionObject);
+
+                        if (dversion == _downloadedVersion)
+                        {
+                            version = versionObject;
+                        }
+                    }
+
+                    if (version is null)
+                    {
+                        OpenErrorWindow("Json file is invalid!");
+                        return;
+                    }
+
+                    var versionDir = new FileInfo(value.path)?.Directory?.FullName;
+
+                    if (versionDir is null)
+                    {
+                        OpenErrorWindow("Version folder is invalid!");
+                        return;
+                    }
+
+                    //get version
+
+                    DownloadedVersionTree.Add(new TreeNode() { Title = "Version: " + (version.InheritsFrom ?? version.Id) });
+
+                    //calc Size
+
+                    long allSize = 0;
+                    string[] size_label = { "bytes", "KB", "MB", "GB" };
+                    var dirInfo = new DirectoryInfo(versionDir);
+
+                    foreach (var file in dirInfo.EnumerateFiles("*", SearchOption.AllDirectories))
+                    { allSize += file.Length; }
+
+                    if (version.InheritsFrom is null)
+                    {
+                        //TODO add subnodes with size
+                        dirInfo = new DirectoryInfo(Settings.minecraftForlderPath);
+                        foreach (var dir in dirInfo.GetDirectories())
+                        {
+                            if (dir.Name != ".mixin.out" &&
+                                dir.Name != "assets" &&
+                                dir.Name != "javaruntime" &&
+                                dir.Name != "libraries" &&
+                                dir.Name != "logs" &&
+                                dir.Name != "versions")
+                                foreach (var file in dir.EnumerateFiles("*", SearchOption.AllDirectories))
+                                { allSize += file.Length; }
+                        }
+
+                        foreach (var file in dirInfo.GetFiles("*"))
+                        {
+                            if (!(file.Name.Contains("Veloe") &&
+                                file.Name.Contains("log") &&
+                                file.Name.Contains("exe")) &&
+                                file.Name == "launcher_profiles.json")
+                                allSize += file.Length;
+                        }
+                    }
+
+                    var j = 0;
+                    var result = (double)allSize;
+                    while (j < size_label.Length && result > 1024)
+                    {
+                        result = result / 1024;
+                        j++;
+                    }
+
+                    DownloadedVersionTree.Add(new TreeNode() { Title = "Size: " + string.Format("{0:N2}", result) + " " + size_label[j] });
+
+                    //calc Worlds
+
+                    dirInfo = null;
+
+                    if (version.InheritsFrom is null && Directory.Exists(Settings.minecraftForlderPath + "saves"))
+                        dirInfo = new DirectoryInfo(Settings.minecraftForlderPath + "saves");
+                    else
+                        if (Directory.Exists(versionDir + "/saves"))
+                        dirInfo = new DirectoryInfo(versionDir + "/saves");
+
+                    if (dirInfo is not null)
+                    {
+                        var worldsTreeNode = new TreeNode() { Title = "Worlds: " + dirInfo.GetDirectories().Count() };
+
+                        foreach (var world in dirInfo.GetDirectories())
+                        { worldsTreeNode.SubNode.Add(new TreeNode() { Title = world.Name }); }
+
+                        DownloadedVersionTree.Add(worldsTreeNode);
+                    }
+
+                    //check modloader
+
+                    var modsTreeNode = new TreeNode();
+
+                    if (version.InheritsFrom is null)
+                        modsTreeNode.Title = "Modloader: No";
+                    else if (version.Libraries.Any(l => l.Downloads?.Artifact?.Url?.Contains("forge") ?? false))
+                        modsTreeNode.Title = "Modloader: Forge";
+                    else if (version.Libraries.Any(l => l.Name.Contains("fabric")))
+                        modsTreeNode.Title = "Modloader: Fabric";
+
+                    //get mods list
+
+                    if (modsTreeNode.Title != "No" && Directory.Exists(versionDir + "/mods"))
+                    {
+                        dirInfo = new DirectoryInfo(versionDir + "/mods");
+
+                        modsTreeNode.Title += " (" + dirInfo.EnumerateFiles("*.jar", SearchOption.TopDirectoryOnly).Count() + ")";
+                        foreach (var mod in dirInfo.EnumerateFiles("*.jar", SearchOption.TopDirectoryOnly))
+                        { modsTreeNode.SubNode.Add(new TreeNode() { Title = mod.Name }); }
+
+                    }
+
+                    DownloadedVersionTree.Add(modsTreeNode);
+
+                    //calc resourcepacks
+
+                    dirInfo = null;
+
+                    if (version.InheritsFrom is null && Directory.Exists(Settings.minecraftForlderPath + "resourcepacks"))
+                        dirInfo = new DirectoryInfo(Settings.minecraftForlderPath + "resourcepacks");
+                    else
+                        if (Directory.Exists(versionDir + "/resourcepacks"))
+                        dirInfo = new DirectoryInfo(versionDir + "/resourcepacks");
+
+                    if (dirInfo is not null)
+                    {
+                        var resourcepacksTreeNode = new TreeNode();
+
+                        resourcepacksTreeNode.Title = "Resoursepacks: " + (dirInfo?.GetDirectories()?.Count() + dirInfo?.GetFiles().Count() ?? 0).ToString();
+                        foreach (var resourcepack in dirInfo?.GetFileSystemInfos())
+                        { resourcepacksTreeNode.SubNode.Add(new TreeNode() { Title = resourcepack.Name }); }
+
+                        DownloadedVersionTree.Add(resourcepacksTreeNode);
+                    }
+
+                    //calc assets
+
+                    var assetsFolderName = version.Assets;
+
+                    if (version.Assets is null &&
+                        version.InheritsFrom is not null &&
+                        DownloadedVersionsDictionary.TryGetValue(version.InheritsFrom, out var outVersion) &&
+                        outVersion?.Assets is not null)
+                    {
+                        assetsFolderName = outVersion.Assets;
+                    }
+
+                    if (Directory.Exists(Settings.minecraftForlderPath + "assets/" + assetsFolderName))
+                    {
+                        dirInfo = new DirectoryInfo(Settings.minecraftForlderPath + "assets/" + assetsFolderName);
+                        allSize = 0;
+                        foreach (var file in dirInfo.EnumerateFiles("*", SearchOption.AllDirectories))
+                        { allSize += file.Length; }
+
+                        j = 0;
+                        result = (double)allSize;
+                        while (j < size_label.Length && result > 1024)
+                        {
+                            result = result / 1024;
+                            j++;
+                        }
+
+                        var assetsTreeNode = new TreeNode();
+                        var usedByVersionCount = 0;
+
+                        foreach (var dictionaryVersion in DownloadedVersionsDictionary)
+                        {
+                            if ((dictionaryVersion.Value?.Assets == assetsFolderName &&
+                                 dictionaryVersion.Value?.InheritsFrom is null) ||
+                                (dictionaryVersion.Value?.InheritsFrom is not null &&
+                                 DownloadedVersionsDictionary.TryGetValue(dictionaryVersion.Value.InheritsFrom, out outVersion) &&
+                                 outVersion?.Assets == assetsFolderName))
+                            {
+                                usedByVersionCount++;
+                                assetsTreeNode.SubNode.Add(new TreeNode() { Title = dictionaryVersion.Key });
+                            }
+                        }
+
+                        assetsTreeNode.Title = "Assets: " + version.Assets + " (" + string.Format("{0:N2}", result) + " " + size_label[j] + ") (" + usedByVersionCount + ")";
+                        DownloadedVersionTree.Add(assetsTreeNode);
+                    }
+
+                    // calc libraries
+
+                    var usedLibraries = version.Libraries;
+
+                    var libTreeNode = new TreeNode();
+                    libTreeNode.Title = "Libraries";
+
+                    if (version.InheritsFrom is not null && DownloadedVersionsDictionary.TryGetValue(version.InheritsFrom, out outVersion) && outVersion is not null)
+                    {
+                        usedLibraries.AddRange(outVersion.Libraries);
+                    }
+
+                    usedLibraries = 
+                        usedLibraries
+                            .Where(l =>
+                                File.Exists(Path.Combine(Settings.minecraftForlderPath + "libraries/" + l.Downloads?.Artifact?.Path)) ||
+                                File.Exists(Path.Combine(Settings.minecraftForlderPath + "libraries/" + l.Downloads?.Classifiers?.NativesLinux?.Path)) ||
+                                File.Exists(Path.Combine(Settings.minecraftForlderPath + "libraries/" + l.Downloads?.Classifiers?.NativesWindows?.Path)) ||
+                                File.Exists(Path.Combine(Settings.minecraftForlderPath + "libraries/" + l.Downloads?.Classifiers?.NativesWindows64?.Path)) ||
+                                File.Exists(Path.Combine(Settings.minecraftForlderPath + "libraries/" + l.Downloads?.Classifiers?.NativesWindows32?.Path)) ||
+                                File.Exists(Path.Combine(Settings.minecraftForlderPath + "libraries/" + StartCommandBuilder.GetLibPathFromName(l.Name, true)))
+                            )
+                            .ToList();
+
+                    var libraries = GetLibrariesFromVersions(DownloadedVersionsDictionary)                       
+                        .Where(v => usedLibraries.Any(u => v.Value.Name == u.Name))                      
+                        .GroupBy(a => a.Value.Name)
+                        .Select(g =>
+                        {
+                            return new { 
+                                Name = g.Key, 
+                                Count = g.Select(a => a.Key).Distinct().Count(), 
+                                Versions = g.Select(a => a.Key).Distinct(), 
+                                LibraryUniqueInstances = g.Select(a => a.Value).Distinct() //IEnumerable cause there can be several lib blocks with different rules in one version json
+                            };
+                        });
+
+                    foreach (var lib in libraries)
+                    {
+                        var subLibTreeNode = new TreeNode() 
+                        { 
+                            Title = lib.Name + " (" + lib.Count + ")", 
+                        };
+
+                        //if only one version uses it (selected version), then add path to lib in Tag for deletion
+                        if (lib.Count == 1)
+                        {
+                            subLibTreeNode.Tag = lib.LibraryUniqueInstances.Where(l => l.Downloads?.Artifact?.Path is not null).Select(l => l.Downloads?.Artifact?.Path).FirstOrDefault() ?? string.Empty;
+                            //if fabric library
+                            if (File.Exists(Settings.minecraftForlderPath + "libraries/" + StartCommandBuilder.GetLibPathFromName(lib.Name,true)))
+                                subLibTreeNode.Tag = StartCommandBuilder.GetLibPathFromName(lib.Name,true);
+                        }
+
+                        var subLibUsedVersionsTreeNodeHeader = new TreeNode() { Title = "Using in versions:" };
+
+                        foreach (var ver in lib.Versions)
+                            subLibUsedVersionsTreeNodeHeader.SubNode.Add(new TreeNode() { Title = ver });
+
+                        if (subLibUsedVersionsTreeNodeHeader.SubNode.Count > 0)
+                            subLibTreeNode.SubNode.Add(subLibUsedVersionsTreeNodeHeader);
+
+                        if (lib.LibraryUniqueInstances.Any(l=>l.Natives is not null) || lib.LibraryUniqueInstances.Any(l=>l.Downloads?.Classifiers is not null))
+                        {
+                            var subLibNativesTreeNodeHeader = new TreeNode() { Title = "Natives:" };
+
+                            //check classifiers
+                            var libraryNativeInstance = lib.LibraryUniqueInstances.Where(l => l.Natives is not null || l.Downloads?.Classifiers is not null).First();
+
+                            if (libraryNativeInstance.Downloads?.Classifiers?.NativesWindows is not null)
+                            {
+                                subLibNativesTreeNodeHeader.SubNode.Add(new TreeNode() 
+                                { 
+                                    Title = Path.GetFileName(libraryNativeInstance.Downloads?.Classifiers?.NativesWindows.Path) ?? "No lib name", 
+                                    Tag = lib.Count == 1 ? libraryNativeInstance.Downloads?.Classifiers?.NativesWindows?.Path ?? string.Empty : string.Empty
+                                });
+                            }
+
+                            if (libraryNativeInstance.Downloads?.Classifiers?.NativesWindows32 is not null)
+                            {
+                                subLibNativesTreeNodeHeader.SubNode.Add(new TreeNode() 
+                                { 
+                                    Title = Path.GetFileName(libraryNativeInstance.Downloads?.Classifiers?.NativesWindows32.Path) ?? "No lib name",
+                                    Tag = lib.Count == 1 ? libraryNativeInstance.Downloads?.Classifiers?.NativesWindows32?.Path ?? string.Empty : string.Empty
+                                });
+                            }
+
+                            if (libraryNativeInstance.Downloads?.Classifiers?.NativesWindows64 is not null)
+                            {
+                                subLibNativesTreeNodeHeader.SubNode.Add(new TreeNode()
+                                {
+                                    Title = Path.GetFileName(libraryNativeInstance.Downloads?.Classifiers?.NativesWindows64.Path) ?? "No lib name",
+                                    Tag = lib.Count == 1 ? libraryNativeInstance.Downloads?.Classifiers?.NativesWindows64?.Path ?? string.Empty : string.Empty
+                                });
+                            }
+
+                            if (libraryNativeInstance.Downloads?.Classifiers?.NativesLinux is not null)
+                            {
+                                subLibNativesTreeNodeHeader.SubNode.Add(new TreeNode() 
+                                {
+                                    Title = Path.GetFileName(libraryNativeInstance.Downloads?.Classifiers?.NativesLinux.Path) ?? "No lib name", 
+                                    Tag = lib.Count == 1 ? libraryNativeInstance.Downloads?.Classifiers?.NativesLinux?.Path ?? string.Empty : string.Empty 
+                                });
+                            }
+
+                            if (subLibNativesTreeNodeHeader.SubNode.Count > 0)
+                                subLibTreeNode.SubNode.Add(subLibNativesTreeNodeHeader);
+
+                        }
+
+                        libTreeNode.SubNode.Add(subLibTreeNode);
+                    }
+
+                    _libraries = libTreeNode;
+                    DownloadedVersionTree.Add(libTreeNode);
+                }
+                finally
+                {
+                    this.RaisePropertyChanged(nameof(DownloadedVersionTree));
+                    IsControlsEnabled = true;
+                }
+            });
+        }
+    }
+    public Dictionary<string,Entity.Version.Version?> DownloadedVersionsDictionary { get; set; }
+
     public ObservableCollection<Entity.VersionManifest.Version> FilteredVersions
     {
         get => _filteredVersions; 
         set => this.RaiseAndSetIfChanged(ref _filteredVersions, value);
     }
 
+    public ObservableCollection<DownloadedVersion> DownloadedVersions
+    {
+        get => _downloadedVersions;
+        set => this.RaiseAndSetIfChanged(ref _downloadedVersions, value);
+    }
+
+    public ObservableCollection<TreeNode> DownloadedVersionTree
+    {
+        get => _downloadedVersionTree;
+        set => this.RaiseAndSetIfChanged(ref _downloadedVersionTree, value);
+    }
+
     public string DownloadButtonText
     {
         get => _downloadButtonText; 
@@ -347,7 +726,159 @@ public class VersionsDownloaderViewModel : ViewModelBase
                       InstallForgeOptifine,
                       InstallFabric);
             }
-        return TaskStatus.RanToCompletion; });
+
+            SearchGameFolderForVersions();
+            return TaskStatus.RanToCompletion; 
+        });
+    }
+
+    public async Task OnDeleteButtonClick()
+    {
+        await Task.Run(async () => {
+            try
+            {
+                IsControlsEnabled = false;
+                DownloadedVersionTree.Clear();
+                DownloadedVersionTree = new();
+                this.RaisePropertyChanged(nameof(DownloadedVersionTree));
+                DownloadedVersionsDictionary.Clear();
+                DownloadedVersionsDictionary = new();
+
+                Entity.Version.Version? version = null;
+
+                foreach (var dversion in DownloadedVersions)
+                {
+                    string json;
+                    using (StreamReader reader = new StreamReader(dversion.path))
+                    {
+                        json = reader.ReadToEnd();
+                    }
+
+                    var versionObject = JsonSerializer.Deserialize<Entity.Version.Version>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+                    DownloadedVersionsDictionary.Add(dversion.version, versionObject);
+
+                    if (dversion == _downloadedVersion)
+                    {
+                        version = versionObject;
+                    }
+                }
+
+                if (version is null)
+                {
+                    OpenErrorWindow("Json file is invalid!");
+                    return;
+                }
+
+                var versionDir = new FileInfo(DownloadedVersion.path)?.Directory?.FullName;
+
+                if (versionDir is null)
+                {
+                    OpenErrorWindow("Version folder is invalid!");
+                    return;
+                }
+
+                var dirInfo = new DirectoryInfo(versionDir);
+
+                
+
+                if (version.InheritsFrom is null && DownloadedVersionsDictionary.Where(v=>v.Value.InheritsFrom is null).Count() == 1)
+                {
+                    //if vanilla
+                    Debug.WriteLine("Vanilla root directories deleted!");
+                    
+                    if (Directory.Exists(Settings.minecraftForlderPath + "saves"))
+                        Directory.Delete(Settings.minecraftForlderPath + "saves", true);
+
+                    if (Directory.Exists(Settings.minecraftForlderPath + "resourcepacks"))
+                        Directory.Delete(Settings.minecraftForlderPath + "resourcepacks", true);
+
+                    if (Directory.Exists(Settings.minecraftForlderPath + "shaderpacks"))
+                        Directory.Delete(Settings.minecraftForlderPath + "shaderpacks", true);
+                }
+
+                //calc assets
+
+                var assetsFolderName = version.Assets;
+
+                if (version.Assets is null &&
+                    version.InheritsFrom is not null &&
+                    DownloadedVersionsDictionary.TryGetValue(version.InheritsFrom, out var outVersion) &&
+                    outVersion?.Assets is not null)
+                {
+                    assetsFolderName = outVersion.Assets;
+                }
+
+                if (Directory.Exists(Settings.minecraftForlderPath + "assets/" + assetsFolderName))
+                {
+                    var usedByVersionCount = 0;
+
+                    foreach (var dictionaryVersion in DownloadedVersionsDictionary)
+                    {
+                        if ((dictionaryVersion.Value?.Assets == assetsFolderName &&
+                             dictionaryVersion.Value?.InheritsFrom is null) ||
+                            (dictionaryVersion.Value?.InheritsFrom is not null &&
+                             DownloadedVersionsDictionary.TryGetValue(dictionaryVersion.Value.InheritsFrom, out outVersion) &&
+                             outVersion?.Assets == assetsFolderName))
+                        {
+                            usedByVersionCount++;
+                        }
+                    }
+
+                    //delete assets if only deleting version used it
+                    if (usedByVersionCount == 1)
+                    {
+                        Debug.WriteLine(Settings.minecraftForlderPath + "assets/" + assetsFolderName);
+                        Directory.Delete(Settings.minecraftForlderPath + "assets/" + assetsFolderName,true);
+                    }
+                }
+
+                // calc libraries
+                DeleteLibrariesFromTreeNode(_libraries);
+
+                // delete version dir
+                Debug.WriteLine(versionDir);
+                Directory.Delete(versionDir, true);
+            }
+            catch (Exception ex)
+            {
+                OpenErrorWindow(ex);
+            }
+            finally
+            {
+                SearchGameFolderForVersions();
+                IsControlsEnabled = true;
+            }
+        });
+    }
+
+    private void DeleteLibrariesFromTreeNode(TreeNode library)
+    {
+        foreach (var node in library.SubNode)
+            DeleteLibrariesFromTreeNode(node);
+        
+        if (!string.IsNullOrEmpty(library.Tag))
+        {
+            var libFileInfo = new FileInfo(Settings.minecraftForlderPath + "libraries/" + library.Tag);
+            var libDir = libFileInfo.Directory;
+
+            if(libDir?.Exists == false)
+            {
+                Debug.WriteLine($"Directory {libDir.FullName} does not exists! Subnode: {library.Title}");
+                _logger.Warning($"Directory {libDir.FullName} does not exists!");
+                return;
+            }
+
+            if (libDir?.GetFileSystemInfos().Count() > 1)
+            {
+                Debug.WriteLine(libFileInfo.FullName);
+                libFileInfo.Delete();
+            }
+            else
+            {
+                Debug.WriteLine(libDir?.FullName);
+                libDir?.Delete(true);
+            }
+        }
     }
 
     public void UpdateList()
@@ -368,6 +899,47 @@ public class VersionsDownloaderViewModel : ViewModelBase
         }
     }
 
+    public void SearchGameFolderForVersions()
+    {
+        if (DownloadedVersions is null)
+            DownloadedVersions = new();
+
+        DownloadedVersions.Clear();
+        DownloadedVersions = new();
+
+        DirectoryInfo versions = new(Settings.minecraftForlderPath + "versions");
+        try
+        {
+            var dirs = versions.GetDirectories("*", SearchOption.TopDirectoryOnly);
+            LauncherProfiles profiles = new LauncherProfiles();
+            profiles.SelectedProfile = "NotImplemented";
+
+            foreach (var dir in dirs)
+            {
+                _logger.Debug("Checking folder {0}", dir.Name);
+                string checkedPath;
+                if (File.Exists(checkedPath = dir.FullName + "/" + dir.Name + ".json"))
+                {
+                    _logger.Debug("Found version {0}", dir.Name);
+                    DownloadedVersions.Add(new DownloadedVersion(checkedPath, dir.Name));
+                    profiles.Profiles.Add($"Version {dir.Name}", new Profile() { Name = dir.Name, LastVersionId = dir.Name, LauncherVisibilityOnGameClose = "keep the launcher open" });
+                }
+            }
+        }
+        catch (DirectoryNotFoundException)
+        {
+            Directory.CreateDirectory(Settings.minecraftForlderPath + "versions");
+            return;
+        }
+
+    }
+
+    public void OnOpenForlder()
+    {       
+        if (DownloadedVersion?.version is not null && Path.Exists(Settings.minecraftForlderPath + "versions/" + DownloadedVersion.version))
+            Process.Start(new System.Diagnostics.ProcessStartInfo() { FileName = Settings.minecraftForlderPath + "versions/" + DownloadedVersion.version, UseShellExecute = true });   
+    }
+
     public void OnClosing(object sender, CancelEventArgs args)
     {
         _tokenSource.Cancel();
@@ -375,4 +947,41 @@ public class VersionsDownloaderViewModel : ViewModelBase
         _filteredVersionTokenSource.Cancel();
         _filteredVersionTokenSource.Dispose();
     }
+
+    public IEnumerable<KeyValuePair<string,Library>> GetLibrariesFromVersions(Dictionary<string, Entity.Version.Version?> versions)
+    {
+        foreach (var version in versions)
+        {
+            if (version.Value is null) continue;
+
+            if (version.Value.InheritsFrom is not null && versions.TryGetValue(version.Value.InheritsFrom,out var inheritVersion) && inheritVersion?.Libraries is not null)
+            {
+                foreach (var lib in inheritVersion.Libraries)
+                    yield return new KeyValuePair<string, Library>(version.Key, lib);
+            }
+
+            foreach (var lib in version.Value.Libraries)
+                yield return new KeyValuePair<string, Library>(version.Key,lib);
+        }
+    }
+}
+
+public class TreeNode
+{
+    public TreeNode()
+    {
+        Title = "Default";
+        SubNode = new();
+        Tag = string.Empty;
+    }
+
+    public string Title { get; set; }
+    public string Tag { get; set; }
+
+    public List<TreeNode> SubNode { get; set; }
+
+    public override string ToString()
+    {
+        return Title;
+    }
 }

+ 1 - 1
VeloeMinecraftLauncher/ViewModels/ViewModelBase.cs

@@ -11,7 +11,7 @@ using System.ComponentModel;
 using System.IO;
 using System.Linq;
 using System.Net;
-using VeloeMinecraftLauncher.MinecraftLauncher;
+using VeloeMinecraftLauncher.Utils;
 using VeloeMinecraftLauncher.Views;
 using VeloeMinecraftLauncher.Views.TitleBar;
 

+ 123 - 78
VeloeMinecraftLauncher/Views/VersionsDownloader.axaml

@@ -7,9 +7,9 @@
 		xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
              xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"
 		xmlns:titlebars="using:VeloeMinecraftLauncher.Views.TitleBar"
-        mc:Ignorable="d" d:DesignWidth="350" d:DesignHeight="450"
-		Width="350" Height="450"
-		MaxWidth="350" MaxHeight="450"
+        mc:Ignorable="d" d:DesignWidth="350" d:DesignHeight="500"
+		Width="350" Height="500"
+		MaxWidth="350" MaxHeight="500"
         x:Class="VeloeMinecraftLauncher.Views.VersionsDownloader"
         Icon="/Assets/avalonia-logo.ico"
         Title="Versions Downloader"
@@ -43,81 +43,126 @@
 			  TitleText="Versions"
 			  DockPanel.Dock="Top">
 			</titlebars:TitleBarWindow>
-			<Grid Margin="10" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,*,Auto,Auto,Auto,Auto">
-				<ComboBox 
-					Items="{Binding FilteredVersions}" 
-					PlaceholderText="Select version" 
-					SelectedItem="{Binding FilteredVersion}" 
-					IsEnabled="{Binding IsControlsEnabled}" 
-					HorizontalAlignment="Stretch"
-					Grid.Row="0" Grid.Column="0"/>
-				<CheckBox 
-					IsChecked="{Binding ShowOld}" 
-					IsEnabled="{Binding IsControlsEnabled}"
-					Margin="0,10,0,0"
-					Grid.Row="1" Grid.Column="0"
-					Content="Show old"/>
-				<CheckBox 
-					IsChecked="{Binding ShowSnaps}" 
-					IsEnabled="{Binding IsControlsEnabled}"
-					Margin="0,5,0,0"
-					Grid.Row="2" Grid.Column="0"
-					Content="Show snapshots"/>
-				<CheckBox 
-					IsChecked="{Binding DownloadJava}" 
-					IsEnabled="{Binding IsControlsEnabled}"
-					Margin="0,5,0,0"
-					Grid.Row="3" Grid.Column="0"
-					Content="Download Java"/>
-				<CheckBox 
-					IsChecked="{Binding InstallFabric}" 
-					IsVisible="{Binding InstallFabricVisible}" 
-					IsEnabled="{Binding IsControlsEnabled}"
-					Margin="0,5,0,0"
-					Grid.Row="4" Grid.Column="0"
-					Content="Install Fabric"/>
-				<CheckBox 
-					IsChecked="{Binding InstallForge}" 
-					IsVisible="{Binding InstallForgeVisible}" 
-					IsEnabled="{Binding IsControlsEnabled}"
-					Margin="0,5,0,0"
-					Grid.Row="5" Grid.Column="0"
-					Content="Install Forge"/>
-				<CheckBox 
-					IsChecked="{Binding InstallForgeOptifine}" 
-					IsVisible="{Binding InstallForgeOptifineVisible}" 
-					IsEnabled="{Binding IsControlsEnabled}"
-					Margin="0,5,0,0"
-					Grid.Row="6" Grid.Column="0"
-					Content="Install Optifine (Mod)"/>
-				<CheckBox 
-					IsChecked="{Binding InstallOptifine}" 
-					IsVisible="{Binding InstallOptifineVisible}" 
-					IsEnabled="{Binding IsControlsEnabled}"
-					Margin="0,5,0,0"
-					Grid.Row="7" Grid.Column="0"
-					Content="Install Optifine (Vanilla)"/>
-				<Button 
-					Content="{Binding DownloadButtonText}" 
-					HorizontalAlignment="Center" 
-					Command="{Binding OnStartBunttonClick}" 
-					IsEnabled="{Binding IsControlsEnabled}"
-					Margin="0,10,0,0"
-					Grid.Row="9" Grid.Column="0"/>
-				<ProgressBar 
-					Value="{Binding Progress}" 
-					Maximum="{Binding MaxProgressValue}" 
-					ShowProgressText="true"
-					Margin="0,10,0,0"
-					Grid.Row="10" Grid.Column="0"/>
-				<TextBlock 
-					Text="{Binding TasksStatusLine}"
-					Margin="0,10,0,0"
-					Grid.Row="11" Grid.Column="0"/>
-				<TextBlock 
-					Text="{Binding DownloadingFileName}"
-					Grid.Row="12" Grid.Column="0"/>
-			</Grid>
+			<TabControl>
+				<TabItem Header="Downloader">
+					<Grid Margin="10" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,*,Auto,Auto,Auto,Auto">
+						<ComboBox
+							Items="{Binding FilteredVersions}"
+							PlaceholderText="Select version"
+							SelectedItem="{Binding FilteredVersion}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							HorizontalAlignment="Stretch"
+							Grid.Row="0" Grid.Column="0"/>
+						<CheckBox
+							IsChecked="{Binding ShowOld}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							Margin="0,10,0,0"
+							Grid.Row="1" Grid.Column="0"
+							Content="Show old"/>
+						<CheckBox
+							IsChecked="{Binding ShowSnaps}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							Margin="0,5,0,0"
+							Grid.Row="2" Grid.Column="0"
+							Content="Show snapshots"/>
+						<CheckBox
+							IsChecked="{Binding DownloadJava}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							Margin="0,5,0,0"
+							Grid.Row="3" Grid.Column="0"
+							Content="Download Java"/>
+						<CheckBox
+							IsChecked="{Binding InstallFabric}"
+							IsVisible="{Binding InstallFabricVisible}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							Margin="0,5,0,0"
+							Grid.Row="4" Grid.Column="0"
+							Content="Install Fabric"/>
+						<CheckBox
+							IsChecked="{Binding InstallForge}"
+							IsVisible="{Binding InstallForgeVisible}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							Margin="0,5,0,0"
+							Grid.Row="5" Grid.Column="0"
+							Content="Install Forge"/>
+						<CheckBox
+							IsChecked="{Binding InstallForgeOptifine}"
+							IsVisible="{Binding InstallForgeOptifineVisible}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							Margin="0,5,0,0"
+							Grid.Row="6" Grid.Column="0"
+							Content="Install Optifine (Mod)"/>
+						<CheckBox
+							IsChecked="{Binding InstallOptifine}"
+							IsVisible="{Binding InstallOptifineVisible}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							Margin="0,5,0,0"
+							Grid.Row="7" Grid.Column="0"
+							Content="Install Optifine (Vanilla)"/>
+						<Button
+							Content="{Binding DownloadButtonText}"
+							HorizontalAlignment="Center"
+							Command="{Binding OnStartBunttonClick}"
+							IsEnabled="{Binding IsControlsEnabled}"
+							Margin="0,10,0,0"
+							Grid.Row="9" Grid.Column="0"/>
+						<ProgressBar
+							Value="{Binding Progress}"
+							Maximum="{Binding MaxProgressValue}"
+							ShowProgressText="true"
+							Margin="0,10,0,0"
+							Grid.Row="10" Grid.Column="0"/>
+						<TextBlock
+							Text="{Binding TasksStatusLine}"
+							Margin="0,10,0,0"
+							Grid.Row="11" Grid.Column="0"/>
+						<TextBlock
+							Text="{Binding DownloadingFileName}"
+							Grid.Row="12" Grid.Column="0"/>
+					</Grid>
+				</TabItem>
+				<TabItem Header="Manager">
+					<StackPanel>
+						<ComboBox
+									Items="{Binding DownloadedVersions}"
+									PlaceholderText="Select version"
+									SelectedItem="{Binding DownloadedVersion}"
+									IsEnabled="{Binding IsControlsEnabled}"
+									HorizontalAlignment="Stretch"
+									/>
+						<ScrollViewer Height="340">
+							<TreeView
+								Items="{Binding DownloadedVersionTree}"
+							    IsEnabled="{Binding IsControlsEnabled}"
+								x:Name="treeView">
+								<TreeView.DataTemplates>
+									<TreeDataTemplate DataType="vm:TreeNode" ItemsSource="{Binding SubNode}">
+										<StackPanel>
+											<TextBlock Text="{Binding Title}"/>
+										</StackPanel>
+									</TreeDataTemplate>
+								</TreeView.DataTemplates>
+							</TreeView>
+						</ScrollViewer>
+						<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto" HorizontalAlignment="Center">
+							<Button
+								Content="Delete"
+								HorizontalAlignment="Center"
+								Command="{Binding OnDeleteButtonClick}"
+								IsEnabled="{Binding IsControlsEnabled}"
+								Margin="0,10,0,0"
+								Grid.Row="0" Grid.Column="0"/>
+							<Button
+								Content="Open folder"
+								HorizontalAlignment="Center"
+								Command="{Binding OnOpenForlder}"
+								IsEnabled="{Binding IsControlsEnabled}"
+								Margin="0,10,0,0"
+								Grid.Row="0" Grid.Column="1"/>
+						</Grid>
+					</StackPanel>
+				</TabItem>
+			</TabControl>		
 		</DockPanel>
 	</Panel>
 </Window>

+ 3 - 0
VeloeMinecraftLauncher/Views/VersionsDownloader.axaml.cs

@@ -1,4 +1,7 @@
 using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.LogicalTree;
+using System.Linq;
 using VeloeMinecraftLauncher.ViewModels;
 
 namespace VeloeMinecraftLauncher.Views