Browse Source

v 1.1.0.0

Veloe 3 years ago
parent
commit
7aa2d01dfe

+ 1 - 0
VeloeLauncherUpdater/LatestLauncherVersion.cs

@@ -9,6 +9,7 @@ namespace VeloeLauncherUpdater
     public class LatestLauncherVersion
     {
         public string latest { get; set; }
+        public string updater { get; set; }
         public string url { get; set; }
     }
 }

+ 62 - 45
VeloeLauncherUpdater/Program.cs

@@ -7,8 +7,6 @@ using System.Net;
 using System.Text.Json;
 using VeloeLauncherUpdater;
 
-Console.WriteLine("Hello, World!");
-
 var logger = new LoggerConfiguration()
     .MinimumLevel.Debug()
     .WriteTo.File("update.log", LogEventLevel.Debug)// restricted... is Optional
@@ -16,65 +14,84 @@ var logger = new LoggerConfiguration()
 
 try
 {
-    FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo("VeloeMinecraftLauncher.exe");
-
-    LatestLauncherVersion latestLauncherInfo;
-
-    using (var webClient = new HttpClient())
+    string fileName = "VeloeMinecraftLauncher";
+    if (OperatingSystem.IsWindows())
+        fileName += ".exe";
+    if (File.Exists(fileName))
     {
-        var jsonData = string.Empty;
-        try
-        {
-            jsonData = webClient.GetStringAsync("https://files.veloe.link/launcher/update/versions.json").Result;
-        }
-        catch (Exception) 
-        {
-            logger.Error("Error occured on getting versions.json from the server.");
-            throw;
-        }
+        FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
+        LatestLauncherVersion latestLauncherVersion;
 
-        latestLauncherInfo = string.IsNullOrEmpty(jsonData)
-                    ? new LatestLauncherVersion()
-                    : JsonSerializer.Deserialize<LatestLauncherVersion>(jsonData);
-    }
-
-    if (latestLauncherInfo is not null)
-    {
-        string[] version = latestLauncherInfo.latest.Split('.');
-        logger.Information("Latest version on server: {0}", latestLauncherInfo.latest);
-        string[] versionOnServer = fileVersionInfo.FileVersion.Split('.');
-        logger.Information("Launcher version: {0}", fileVersionInfo.FileVersion);
-
-        if (Int16.Parse(version[0]) > Int16.Parse(versionOnServer[0]) ||
-            Int16.Parse(version[1]) > Int16.Parse(versionOnServer[1]) ||
-            Int16.Parse(version[2]) > Int16.Parse(versionOnServer[2]) ||
-            Int16.Parse(version[3]) > Int16.Parse(versionOnServer[3]))
+        using (var httpClient = new HttpClient())
         {
-            WebClient webClient = new WebClient();
-
+            var jsonData = string.Empty;
             try
             {
-                logger.Information("Downloading latest.zip");
-                webClient.DownloadFile(new System.Uri("https://files.veloe.link/launcher/update/latest.zip"), "latest.zip");
+                jsonData = httpClient.GetStringAsync("https://files.veloe.link/launcher/update/versions.json").Result;
             }
-            catch (Exception ex)
+            catch (Exception)
             {
-                logger.Error("Error occured on getting latest.zip from the server.");
+                logger.Error("Error occured on getting versions.json from the server.");
                 throw;
             }
 
-            logger.Information("Unpacking latest.zip");
-            ZipFile.ExtractToDirectory($"latest.zip", Directory.GetCurrentDirectory(), true);
+            latestLauncherVersion = string.IsNullOrEmpty(jsonData)
+                        ? new LatestLauncherVersion()
+                        : JsonSerializer.Deserialize<LatestLauncherVersion>(jsonData);
+        }
 
-            logger.Information("Deleting latest.zip");
-            File.Delete($"latest.zip");
+        if (OperatingSystem.IsLinux())
+        {
+            logger.Information("Manual updates only.");
+            return;
         }
-        else
+
+        if (latestLauncherVersion != null)
         {
-            logger.Information("No update required.");
+            string[] version = latestLauncherVersion.latest.Split('.');
+            logger.Information("Latest launcher version on server: {0}", latestLauncherVersion.latest);
+            string[] versionOnServer = fileVersionInfo.FileVersion.Split('.');
+            logger.Information("Launcher version: {0}", fileVersionInfo.FileVersion);
+
+            if (!(Int16.Parse(version[0]) > Int16.Parse(versionOnServer[0]) ||
+                Int16.Parse(version[1]) > Int16.Parse(versionOnServer[1]) ||
+                Int16.Parse(version[2]) > Int16.Parse(versionOnServer[2]) ||
+                Int16.Parse(version[3]) > Int16.Parse(versionOnServer[3])))
+            {
+                logger.Information("No update required.");
+                return;
+            }
         }
+    }
+
+    WebClient webClient = new WebClient();
+    try
+    {
+        logger.Information("Downloading latest.zip");
+        string url = String.Empty;
+
+        if (OperatingSystem.IsWindows())
+            url = "https://files.veloe.link/launcher/update/latest.zip";
 
+        if (OperatingSystem.IsLinux())
+            url = "https://files.veloe.link/launcher/update/latest_linux-x64.zip";
+
+        if (url == String.Empty)
+            return;
+
+        webClient.DownloadFile(new System.Uri(url), "latest.zip"); ;
+    }
+    catch (Exception ex)
+    {
+        logger.Error("Error occured on getting latest.zip from the server.");
+        throw;
     }
+
+    logger.Information("Unpacking latest.zip");
+    ZipFile.ExtractToDirectory($"latest.zip", Directory.GetCurrentDirectory(), true);
+
+    logger.Information("Deleting latest.zip");
+    File.Delete($"latest.zip");
 }
 catch (Exception ex)
 {

+ 52 - 4
VeloeMinecraftLauncher/MinecraftLauncher/Downloader.cs

@@ -1,4 +1,6 @@
-using System;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
@@ -7,6 +9,8 @@ using System.Net.Http;
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
+using VeloeMinecraftLauncher.ViewModels;
+using VeloeMinecraftLauncher.Views;
 
 namespace VeloeMinecraftLauncher.MinecraftLauncher
 {
@@ -19,13 +23,57 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
                 var jsonData = string.Empty;
                 try
                 {
+                    Settings.logger.Debug($"Downloading: {url.Split('/').Last()}");
                     jsonData = webClient.GetStringAsync(url).Result;
                 }
-                catch (Exception) { }
+                catch (Exception ex)
+                {
+                    var message = ex.Message;
+                    var stackTrace = ex.StackTrace;
+                    Settings.logger.Error(ex.Message);
+                    Settings.logger.Error(ex.StackTrace);
+                    Exception innerException = ex.InnerException;
+                    while (innerException is not null)
+                    {
+                        message += "\n" + innerException.Message;
+                        stackTrace += "\n" + innerException.StackTrace;
+                        Settings.logger.Error(innerException.Message);
+                        Settings.logger.Error(innerException.StackTrace);
+                        innerException = innerException.InnerException;
+                    }
+
+                    Dispatcher.UIThread.InvokeAsync(() =>
+                    {
+                        if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+                        {
+                            Dispatcher.UIThread.InvokeAsync(() =>
+                            {
+                                var ErrorMessageViewModel = new ErrorWindowViewModel(message, stackTrace);
+                                var ErrorMessage = new ErrorWindow()
+                                {
+                                    DataContext = ErrorMessageViewModel
+                                };
+                                ErrorMessage.ShowDialog(desktop.MainWindow);
+                            });
+                        }
+                    });
+                 
+                }
 
+                if (string.IsNullOrEmpty(jsonData))
+                {
+                    Settings.logger.Warning("Empty string!");
+                    return new T();
+                }
+                else
+                {
+                    //Settings.logger.Debug("Return serialized string.");
+                    return JsonSerializer.Deserialize<T>(jsonData);
+                }
+                /*
                 return string.IsNullOrEmpty(jsonData)
                             ? new T()
-                            : JsonSerializer.Deserialize<T>(jsonData);
+                            : JsonSerializer.Deserialize<T>(jsonData);*/
             }
         }
 
@@ -35,7 +83,7 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
             {
                 if (!Directory.Exists(path))
                     Directory.CreateDirectory(path);
-                webClient.DownloadFileAsync(new System.Uri(url), path + "\\" + filename);
+                webClient.DownloadFileAsync(new System.Uri(url), path + "/" + filename);
             }
         }
     }

+ 1 - 0
VeloeMinecraftLauncher/MinecraftLauncher/Settings.cs

@@ -25,6 +25,7 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
         public static string Username;
         public static string lastChosenVersion;
         public static Serilog.ILogger logger;
+        public static Serilog.ILogger avaloniaLogger;
 
         public static void UpdateSettings(SettingsSerializable settings)
         {

+ 111 - 12
VeloeMinecraftLauncher/MinecraftLauncher/StartCommandBuilder.cs

@@ -13,6 +13,12 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
         public static string Build(Entity.Version.Version version, string username)
         {
             var returnString = new StringBuilder();
+
+            char separator = ':';
+
+            if (OperatingSystem.IsWindows())
+                separator = ';';
+
             if (version is null)
                 return returnString.ToString();
 
@@ -21,19 +27,35 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
 
             //for forge
             if (version.inheritsFrom is null)
-                returnString.Append($"-Djava.library.path={Settings.MinecraftForlderPath + "versions\\" + version.id + "\\natives\\"}");
+                returnString.Append($"-Djava.library.path={Settings.MinecraftForlderPath + "versions/" + version.id + "/natives/"}");
             else
-                returnString.Append($"-Djava.library.path={Settings.MinecraftForlderPath + "versions\\" + version.inheritsFrom + "\\natives\\"}");
+                returnString.Append($"-Djava.library.path={Settings.MinecraftForlderPath + "versions/" + version.inheritsFrom + "//natives/"}");
 
             returnString.Append(" -cp ");
 
             foreach(var library in version.libraries)
             {
-                if (library.natives is null)
-                    returnString.Append(Settings.MinecraftForlderPath + "libraries\\" + library.downloads.artifact.path.Replace("/", "\\") + ";");
+                if (library.natives is not null)
+                    continue;
+
+                if (library.downloads is null) //for optifine
+                {
+                    string name = library.name;
+                    var dirs = name.Split(':');
+                    var libPath = String.Empty;
+                    foreach (var dir in dirs)
+                    {
+                        libPath += dir+"/";
+                    }
+                    returnString.Append(Settings.MinecraftForlderPath + "libraries/" + libPath + dirs[dirs.Length-2]+"-"+dirs[dirs.Length-1]+".jar;");
+
+                    continue;
+                }
+
+                returnString.Append(Settings.MinecraftForlderPath + "libraries/" + library.downloads.artifact.path + separator);
 
             }
-            //for forge
+            //for forge and vanilla optifine
             Entity.Version.Version inheritsFrom = new();
             if (version.inheritsFrom is null)
                 returnString.Append(Settings.MinecraftForlderPath + "versions/" + version.id + "/" + version.id + ".jar");
@@ -45,11 +67,17 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
 
                 foreach (var library in inheritsFrom.libraries)
                 {
-                    if (library.natives is null)
-                        returnString.Append(Settings.MinecraftForlderPath + "libraries\\" + library.downloads.artifact.path.Replace("/", "\\") + ";");
+                    if (library.natives is not null)
+                        continue;
+
+                    returnString.Append(Settings.MinecraftForlderPath + "libraries/" + library.downloads.artifact.path + separator);
     
                 }
-                returnString.Append(Settings.MinecraftForlderPath + "versions/" + version.inheritsFrom + "/" + version.inheritsFrom + ".jar");
+                //check here if there is jar file not inherited
+                if (File.Exists($"{Settings.MinecraftForlderPath}versions/{version.id}/{version.id}.jar"))
+                    returnString.Append(Settings.MinecraftForlderPath + "versions/" + version.id + "/" + version.id + ".jar");//for optifine
+                else
+                    returnString.Append(Settings.MinecraftForlderPath + "versions/" + version.inheritsFrom + "/" + version.inheritsFrom + ".jar");//for forge
             }
 
             returnString.Append(" " + version.mainClass);
@@ -57,7 +85,9 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
             List<string> args = new List<string>();
             List<string> argsValues = new List<string>();
 
+            //TODO check here if all required arguments needed. Check inheritFrom if needed
             if (version.arguments is not null)
+            {
                 foreach (var argument in version.arguments.game)
                 {
                     if (argument is null)
@@ -83,7 +113,7 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
 
                     args.Add(value as string);
                 }
-
+            }
             if (version.minecraftArguments is not null)
             {
                 var minecraftArguments = version.minecraftArguments.Split(' ');
@@ -97,6 +127,75 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
                 }
             }
 
+
+            Dictionary<string, bool> argsProvided = new Dictionary<string, bool>()
+            {
+                { "--username", false },
+                { "--version", false },
+                { "--gameDir", false},
+                { "--assetsDir", false },
+                { "--assetIndex", false },
+                { "--uuid", false },
+                { "--accessToken", false},
+                { "--userType", false},               
+                { "--tweakClass", false }
+            };
+
+            foreach (var arg in args)
+            {
+                argsProvided[arg] = true;
+            }
+
+            if (argsProvided.ContainsValue(false))
+            {
+                if (inheritsFrom.arguments is not null)
+                {
+                    foreach (var argument in inheritsFrom.arguments.game)
+                    {
+                        if (argument is null)
+                            continue;
+
+                        var type = argument.GetType();
+
+                        if (!(argument is JsonElement))
+                            continue;
+
+                        if (!(((JsonElement)argument).ValueKind == JsonValueKind.String))
+                            continue;
+
+                        var value = ((JsonElement)argument).Deserialize(typeof(string));
+
+                        //if ()
+
+                        if (!(value as string).Contains("--"))
+                        {
+                            argsValues.Add(value as string);
+                            continue;
+                        }
+                        if (!argsProvided.GetValueOrDefault(value as string, false))
+                        {
+                            args.Add(value as string);
+                            argsProvided[value as string] = true;
+                        }
+                    }
+                }
+                if (inheritsFrom.minecraftArguments is not null)
+                {
+                    var minecraftArguments = inheritsFrom.minecraftArguments.Split(' ');
+                    //args = version.minecraftArguments.Split(' ').Where(x=> x.Contains("--")).ToList();
+                    for (int i = 0; i < minecraftArguments.Count(); i++)
+                    {
+                        if (minecraftArguments[i].Contains("--"))
+                        {
+                            args.Add(minecraftArguments[i]);
+                            argsProvided[minecraftArguments[i]] = true;
+                        }
+                        else
+                            argsValues.Add(minecraftArguments[i]);
+                    }
+                }
+            }
+
             for (int i = 0; i < args.Count; i++)
             {
                 switch (args[i])
@@ -112,14 +211,14 @@ namespace VeloeMinecraftLauncher.MinecraftLauncher
                         if (!(argsValues.Where(x => x.Contains("forge")).Count() > 0 || argsValues.Where(x => x.Contains("fabric")).Count() > 0))
                             returnString.Append(" --gameDir " + Settings.MinecraftForlderPath);
                         else
-                            returnString.Append(" --gameDir " + Settings.MinecraftForlderPath + "versions\\" + version.id);
+                            returnString.Append(" --gameDir " + Settings.MinecraftForlderPath + "versions/" + version.id);
                         break;
                     case "--assetsDir":
                         //for forge
                         if (version.inheritsFrom is null)
-                            returnString.Append(" --assetsDir " + Settings.MinecraftForlderPath + "assets\\" + version.assets + "\\");
+                            returnString.Append(" --assetsDir " + Settings.MinecraftForlderPath + "assets/" + version.assets + "/");
                         else
-                            returnString.Append(" --assetsDir " + Settings.MinecraftForlderPath + "assets\\" + inheritsFrom.assets + "\\");
+                            returnString.Append(" --assetsDir " + Settings.MinecraftForlderPath + "assets/" + inheritsFrom.assets + "/");
                         break;
                     case "--assetIndex":
                         //for forge

+ 19 - 6
VeloeMinecraftLauncher/Program.cs

@@ -1,12 +1,15 @@
 using Avalonia;
-using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.ReactiveUI;
+using Serilog;
+using Serilog.Events;
 using System;
+using System.Diagnostics;
+using VeloeMinecraftLauncher.MinecraftLauncher;
 
 namespace VeloeMinecraftLauncher
 {
     internal 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.
@@ -16,9 +19,19 @@ namespace VeloeMinecraftLauncher
 
         // Avalonia configuration, don't remove; also used by visual designer.
         public static AppBuilder BuildAvaloniaApp()
-            => AppBuilder.Configure<App>()
-                .UsePlatformDetect()
-                .LogToTrace()
-                .UseReactiveUI();
+        {
+            var logger = new Serilog.LoggerConfiguration()
+               .MinimumLevel.Debug()
+               .WriteTo.File("avalonia.log", LogEventLevel.Debug, fileSizeLimitBytes: 100000000)// restricted... is Optional
+               .CreateLogger();
+            Settings.avaloniaLogger = logger;
+            TraceListener listener = new SerilogTraceListener.SerilogTraceListener(logger);
+            Trace.Listeners.Add(listener);
+            
+            return AppBuilder.Configure<App>()
+                  .UsePlatformDetect()
+                  .LogToTrace()
+                  .UseReactiveUI();
+        }
     }
 }

+ 1 - 0
VeloeMinecraftLauncher/VeloeMinecraftLauncher.csproj

@@ -30,6 +30,7 @@
     <PackageReference Include="ReactiveUI.Validation" Version="3.0.1" />
     <PackageReference Include="Serilog" Version="2.11.0" />
     <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
+    <PackageReference Include="SerilogTraceListener" Version="3.2.0" />
     <PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
   </ItemGroup>
 </Project>

+ 33 - 0
VeloeMinecraftLauncher/ViewModels/ErrorWindowViewModel.cs

@@ -0,0 +1,33 @@
+using ReactiveUI;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace VeloeMinecraftLauncher.ViewModels
+{
+    public class ErrorWindowViewModel : ViewModelBase
+    {
+        public ErrorWindowViewModel(string errorMessage, string errorTrace)
+        {
+            ErrorMessage = errorMessage;
+            ErrorTrace = errorTrace;
+        }
+
+        private string errorMessage;
+        private string errorTrace;
+
+        public string ErrorTrace
+        {
+            get { return errorTrace; }
+            set { this.RaiseAndSetIfChanged(ref errorTrace, value); }
+        }
+
+        public string ErrorMessage
+        {
+            get { return errorMessage; }
+            set { this.RaiseAndSetIfChanged(ref errorMessage, value); }
+        }
+    }
+}

+ 423 - 185
VeloeMinecraftLauncher/ViewModels/MainWindowViewModel.cs

@@ -25,119 +25,161 @@ using Serilog.Events;
 using Serilog.Formatting;
 using Avalonia.Controls;
 using Avalonia.Threading;
+using System.Net;
+using System.IO.Compression;
 
 namespace VeloeMinecraftLauncher.ViewModels
 {
     public class MainWindowViewModel : ViewModelBase
     {
         public MainWindowViewModel()
-        {
-            //creating logger
-            EventSink eventSink = new(null);
-            eventSink.DataReceived += LogHandler;
-
-            logger = new LoggerConfiguration()
-               .MinimumLevel.Debug()
-               .WriteTo.Sink(eventSink)
-               .WriteTo.File("launcher.log", LogEventLevel.Debug, fileSizeLimitBytes: 100000000)// restricted... is Optional
-               .CreateLogger();
-            Settings.logger = logger;
-
-            //loading settings
-            logger.Debug("Loading settings.");
-            Settings.LoadSettings();
-            username = Settings.Username;
-
-            //loading local verions
-            logger.Debug("Loading local versions.");
-            updateAvailable();
-
-            //check launcher update
-            logger.Debug("Checking launcher versions updates.");
-            latestLauncherInfo = Downloader.DownloadAndDeserializeJsonData<LatestLauncherVersion>("https://files.veloe.link/launcher/update/versions.json");
-            if (latestLauncherInfo is not null)
-            {
-                logger.Debug("Launcher version on server: {0}", latestLauncherInfo.latest);
-                logger.Debug("Launcher version: {0}", Assembly.GetExecutingAssembly().GetName().Version);
-                string[] version = latestLauncherInfo.latest.Split('.');
-
-                var vat = Assembly.GetExecutingAssembly().GetName().Version;
-
-                if (Int16.Parse(version[0]) > Assembly.GetExecutingAssembly().GetName().Version.Major ||
-                    Int16.Parse(version[1]) > Assembly.GetExecutingAssembly().GetName().Version.Minor ||
-                    Int16.Parse(version[2]) > Assembly.GetExecutingAssembly().GetName().Version.Build ||
-                    Int16.Parse(version[3]) > Assembly.GetExecutingAssembly().GetName().Version.Revision)
+        {           
+                Task.Run(() =>
                 {
-                    logger.Debug("Update available!");
-                    IsUpdateAvailable = true;
-                }
-            }
+                    try
+                    {
+                        //creating logger
+                        EventSink eventSink = new(null);
+                        eventSink.DataReceived += LogHandler;
+
+                        logger = new LoggerConfiguration()
+                           .MinimumLevel.Debug()
+                           .WriteTo.Sink(eventSink)
+                           .WriteTo.File("launcher.log", LogEventLevel.Debug, fileSizeLimitBytes: 100000000)// restricted... is Optional
+                           .CreateLogger();
+                        Settings.logger = logger;
+
+                        //loading settings
+                        logger.Debug("Loading settings.");
+                        Settings.LoadSettings();
+                        username = Settings.Username;
+
+                        //loading local verions
+                        logger.Debug("Loading local versions.");
+                        updateAvailable();
+                    }
+                    catch (Exception ex)
+                    {
+                        OpenErrorWindow(ex);
+                    }
 
-            logger.Debug("Connecting to WebSoket");
-            //conection to my servers 
-            connection = new HubConnectionBuilder()
-                .WithUrl("https://monitor.veloe.link/hubs/data")
-                .WithAutomaticReconnect()
-                .Build();
+                    UpdateUpdater();
 
-            Func<Exception, Task> reconnecting = ex => Task.Run(() => { 
-                logger.Warning("Reconnecting to WebCoket...");
-            });
-            Func<string, Task> reconnected = str => Task.Run(() => { 
-                logger.Warning("Reconnected to WebCoket.");
-                connection.InvokeAsync("ConnectToGroup", "McTFC");
-                connection.InvokeAsync("ConnectToGroup", "McVanilla");
-                connection.InvokeAsync("ConnectToGroup", "McTech");
-            });
+                    try
+                    {
+                        //check launcher update
+                        logger.Information("Checking launcher versions updates.");
+                        latestLauncherInfo = Downloader.DownloadAndDeserializeJsonData<LatestLauncherVersion>("https://files.veloe.link/launcher/update/versions.json");
+                        if (latestLauncherInfo is not null)
+                        {
+                            logger.Information("Launcher version on server: {0}", latestLauncherInfo.latest);
+                            logger.Information("Launcher version: {0}", Assembly.GetExecutingAssembly().GetName().Version);
+                            string[] version = latestLauncherInfo.latest.Split('.');
 
-            connection.Reconnecting += reconnecting;
-            connection.Reconnected += reconnected;
+                            var vat = Assembly.GetExecutingAssembly().GetName().Version;
 
-            connection.On<string>("UpdateMcTFC", (message) =>
-            {
-                McStatus status = JsonSerializer.Deserialize<McStatus>(message);
-                McTfcBlock = $"McTFC: Online {status.NumPlayers}/{status.MaxPlayers}";
-                mcTfcTimer.Stop();
-                mcTfcTimer.Start();
-                //ConsoleText += message;
-            });
+                            if (Int16.Parse(version[0]) > Assembly.GetExecutingAssembly().GetName().Version.Major ||
+                                Int16.Parse(version[1]) > Assembly.GetExecutingAssembly().GetName().Version.Minor ||
+                                Int16.Parse(version[2]) > Assembly.GetExecutingAssembly().GetName().Version.Build ||
+                                Int16.Parse(version[3]) > Assembly.GetExecutingAssembly().GetName().Version.Revision)
+                            {
+                                logger.Debug("Update available!");
+                                IsUpdateAvailable = true;
+                            }
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        OpenErrorWindow(ex);
+                    }
+                    try
+                    {
+                  
+                    logger.Debug("Connecting to WebSoket");
+                //conection to my servers 
+                connection = new HubConnectionBuilder()
+                        .WithUrl("https://monitor.veloe.link/hubs/data")
+                        .WithAutomaticReconnect()
+                        .Build();
+
+                    Func<Exception, Task> reconnecting = ex => Task.Run(() =>
+                    {
+                        logger.Warning("Reconnecting to WebCoket...");
+                    });
+                    Func<string, Task> reconnected = str => Task.Run(() =>
+                    {
+                        logger.Warning("Reconnected to WebCoket.");
+                        connection.InvokeAsync("ConnectToGroup", "McTFC");
+                        connection.InvokeAsync("ConnectToGroup", "McVanilla");
+                        connection.InvokeAsync("ConnectToGroup", "McTech");
+                    });
 
-            connection.On<string>("UpdateMcVanilla", (message) =>
-            {
-                McStatus status = JsonSerializer.Deserialize<McStatus>(message);
-                McTfcBlock = $"McVanilla: Online {status.NumPlayers}/{status.MaxPlayers}";
-                mcTechTimer.Stop();
-                mcTechTimer.Start();
-                //ConsoleText += message;
-            });
+                    connection.Reconnecting += reconnecting;
+                    connection.Reconnected += reconnected;
 
-            connection.On<string>("UpdateMcTech", (message) =>
-            {
-                McStatus status = JsonSerializer.Deserialize<McStatus>(message);
-                McTfcBlock = $"McTech: Online {status.NumPlayers}/{status.MaxPlayers}";
-                mcVanillaTimer.Stop();
-                mcVanillaTimer.Start();
-                //ConsoleText += message;
-            });
+                    connection.On<string>("UpdateMcTFC", (message) =>
+                    {
+                        McStatus status = JsonSerializer.Deserialize<McStatus>(message);
+                        McTfcBlock = $"McTFC: Online";
+                        McTfcPlayersBlock = $"{status.NumPlayers}/{status.MaxPlayers}";
+                        McTfcTip = String.Empty;
+                        if (UInt16.Parse(status.NumPlayers) > 0)
+                            foreach (var player in status.Players)
+                            {
+                                McTfcTip += player;
+                                McTfcTip += "\n";
+                            }
+                        else
+                            McTfcTip = "No players.";
+                        mcTfcTimer.Stop();
+                        mcTfcTimer.Start();
 
-            mcTfcTimer.Elapsed += OnTimedEventMcTfc;
-            mcTfcTimer.Start();
-            mcTechTimer.Elapsed += OnTimedEventMcTech;
-            mcTechTimer.Start();
-            mcVanillaTimer.Elapsed += OnTimedEventMcVanilla;
-            mcVanillaTimer.Start();
+                    //ConsoleText += message;
+                });
 
-            connection.StartAsync();
+                    connection.On<string>("UpdateMcVanilla", (message) =>
+                    {
+                        McStatus status = JsonSerializer.Deserialize<McStatus>(message);
+                        McVanillaBlock = $"McVanilla: Online";
+                        McVanillaPlayersBlock = $"{status.NumPlayers}/{status.MaxPlayers}";
+                        mcTechTimer.Stop();
+                        mcTechTimer.Start();
+                    //ConsoleText += message;
+                });
 
-            connection.InvokeAsync("ConnectToGroup", "McTFC");
-            connection.InvokeAsync("ConnectToGroup", "McVanilla");
-            connection.InvokeAsync("ConnectToGroup", "McTech");
+                    connection.On<string>("UpdateMcTech", (message) =>
+                    {
+                        McStatus status = JsonSerializer.Deserialize<McStatus>(message);
+                        McTechBlock = $"McTech: Online {status.NumPlayers}/{status.MaxPlayers}";
+                        McTechPlayersBlock = $"{status.NumPlayers}/{status.MaxPlayers}";
+                        mcVanillaTimer.Stop();
+                        mcVanillaTimer.Start();
+                    //ConsoleText += message;
+                });
 
-            consoleOutputTimer.Elapsed += UpdateConsoleOutput;
-            consoleOutputTimer.AutoReset = false;
-        }
+                    mcTfcTimer.Elapsed += OnTimedEventMcTfc;
+                    mcTfcTimer.Start();
+                    mcTechTimer.Elapsed += OnTimedEventMcTech;
+                    mcTechTimer.Start();
+                    mcVanillaTimer.Elapsed += OnTimedEventMcVanilla;
+                    mcVanillaTimer.Start();
+
+                    connection.StartAsync();
 
-        int i = 0;
+                    connection.InvokeAsync("ConnectToGroup", "McTFC");
+                    connection.InvokeAsync("ConnectToGroup", "McVanilla");
+                    connection.InvokeAsync("ConnectToGroup", "McTech");
+
+                    consoleOutputTimer.Elapsed += UpdateConsoleOutput;
+                    consoleOutputTimer.AutoReset = false;
+                    }
+                    catch (Exception ex)
+                    {
+                        OpenErrorWindow(ex);
+                    }
+                });
+            
+        }
 
         System.Timers.Timer mcTfcTimer = new System.Timers.Timer(30000);
         System.Timers.Timer mcTechTimer = new System.Timers.Timer(30000);
@@ -157,6 +199,12 @@ namespace VeloeMinecraftLauncher.ViewModels
         private string mcTfcBlock = "Wait for update...";
         private string mcTechBlock = "Wait for update...";
         private string mcVanillaBlock = "Wait for update...";
+        private string mcTfcPlayersBlock = String.Empty;
+        private string mcTechPlayersBlock = String.Empty;
+        private string mcVanillaPlayersBlock = String.Empty;
+        private string mcTfcTip = "No players.";
+        private string mcTechTip = "No players.";
+        private string mcVanillaTip = "No players.";
 
         ILogger logger;
 
@@ -300,6 +348,60 @@ namespace VeloeMinecraftLauncher.ViewModels
             }
         }
 
+        public string McTfcPlayersBlock
+        {
+            get => mcTfcPlayersBlock;
+            set
+            {
+                this.RaiseAndSetIfChanged(ref mcTfcPlayersBlock, value);
+            }
+        }
+
+        public string McTechPlayersBlock
+        {
+            get => mcTechPlayersBlock;
+            set
+            {
+                this.RaiseAndSetIfChanged(ref mcTechPlayersBlock, value);
+            }
+        }
+
+        public string McVanillaPlayersBlock
+        {
+            get => mcVanillaPlayersBlock;
+            set
+            {
+                this.RaiseAndSetIfChanged(ref mcVanillaPlayersBlock, value);
+            }
+        }
+
+        public string McTfcTip
+        {
+            get => mcTfcTip;
+            set
+            {
+                this.RaiseAndSetIfChanged(ref mcTfcTip, value);
+            }
+        }
+
+        public string McTechTip
+        {
+            get => mcTechTip;
+            set
+            {
+                this.RaiseAndSetIfChanged(ref mcTechTip, value);
+            }
+        }
+
+        public string McVanillaTip
+        {
+            get => mcVanillaTip;
+            set
+            {
+                this.RaiseAndSetIfChanged(ref mcVanillaTip, value);
+            }
+        }
+
         public int ConsoleTextCaretIndex
         {
             get { return int.MaxValue; }
@@ -313,123 +415,144 @@ namespace VeloeMinecraftLauncher.ViewModels
 
         public void OnClickCommand()
         {
-            var versionsDownloader = new VersionsDownloader { DataContext = new VersionsDownloaderViewModel() };
+            var versionsDownloader = new VersionsDownloader 
+            { 
+                DataContext = new VersionsDownloaderViewModel() 
+            };
            
             if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
             {
                 //var versionsDownloaderTask =
                 versionsDownloader.Closed += updateAvailable;
                 versionsDownloader.ShowDialog(desktop.MainWindow);
-                //versionsDownloaderTask.Wait();
-                
+                //versionsDownloaderTask.Wait();              
             }
-
         }
 
         public void StartMinecraft()
         {
-            logger.Debug("Starting minecraft.");
-            if (DownloadedVerion is null)
-                return;
+            try
+            {
+                logger.Debug("Starting minecraft.");
+                if (DownloadedVerion is null)
+                    return;
 
-            int version = 0;
+                int version = 0;
 
-            //var verionJsonFileStream = File.OpenRead(DownloadedVerion.path);
-            using (StreamReader reader = new StreamReader(DownloadedVerion.path))
-            {
-                string json = reader.ReadToEnd();
+                //var verionJsonFileStream = File.OpenRead(DownloadedVerion.path);
+                using (StreamReader reader = new StreamReader(DownloadedVerion.path))
+                {
+                    string json = reader.ReadToEnd();
 
-                var versionJson = JsonSerializer.Deserialize<Entity.Version.Version>(json);
+                    var versionJson = JsonSerializer.Deserialize<Entity.Version.Version>(json);
 
-                string arguments = StartCommandBuilder.Build(versionJson, Username);
+                    string arguments = StartCommandBuilder.Build(versionJson, Username);
 
-                if (!Settings.useCustomJava)
-                {
-                    if (versionJson.javaVersion != null)
+                    if (!Settings.useCustomJava)
                     {
-                        logger.Debug("Java version required: {0}", versionJson.javaVersion.majorVersion);
-                        version = versionJson.javaVersion.majorVersion;
-                    }
-                    else
-                    {
-                        if (versionJson.inheritsFrom != null)
+                        if (versionJson.javaVersion != null)
                         {
-                            using (StreamReader inheritsFromReader = new StreamReader(Settings.MinecraftForlderPath + "versions/" + versionJson.inheritsFrom + "/" + versionJson.inheritsFrom + ".json"))
+                            logger.Debug("Java version required: {0}", versionJson.javaVersion.majorVersion);
+                            version = versionJson.javaVersion.majorVersion;
+                        }
+                        else
+                        {
+                            if (versionJson.inheritsFrom != null)
                             {
-                                string jsonInheritsFrom = inheritsFromReader.ReadToEnd();
-                                var inheritsFromJson = JsonSerializer.Deserialize<Entity.Version.Version>(jsonInheritsFrom);
-                                if (inheritsFromJson.javaVersion != null)
+                                using (StreamReader inheritsFromReader = new StreamReader(Settings.MinecraftForlderPath + "versions/" + versionJson.inheritsFrom + "/" + versionJson.inheritsFrom + ".json"))
                                 {
-                                    logger.Debug("Java version required: {0}", inheritsFromJson.javaVersion.majorVersion);
-                                    version = inheritsFromJson.javaVersion.majorVersion;
+                                    string jsonInheritsFrom = inheritsFromReader.ReadToEnd();
+                                    var inheritsFromJson = JsonSerializer.Deserialize<Entity.Version.Version>(jsonInheritsFrom);
+                                    if (inheritsFromJson.javaVersion != null)
+                                    {
+                                        logger.Debug("Java version required: {0}", inheritsFromJson.javaVersion.majorVersion);
+                                        version = inheritsFromJson.javaVersion.majorVersion;
+                                    }
                                 }
                             }
                         }
                     }
+
+                    ConsoleText += arguments;
+                    ArgumentsBox = arguments;
                 }
-            
-            ConsoleText += arguments;
-            ArgumentsBox = arguments;
-            }
 
-            if (DownloadedVerion is null)
-                return;
+                if (DownloadedVerion is null)
+                    return;
 
-            string javaPath = Settings.JavaPath;
+                string javaPath = Settings.JavaPath;
 
-            if (!Settings.useCustomJava)
-                javaPath = Settings.MinecraftForlderPath + "javaruntime/" + version + "/bin/java.exe";
+                if (!Settings.useCustomJava)
+                {
+                    javaPath = Settings.MinecraftForlderPath + "javaruntime/" + version + "/bin/java";
+                    if (OperatingSystem.IsWindows())
+                        javaPath += ".exe";
+                }
 
-            logger.Debug("Java version path: {0}", javaPath);
-            logger.Debug("Minecraft arguments: {0}", ArgumentsBox);
+                logger.Debug("Java version path: {0}", javaPath);
+                logger.Debug("Minecraft arguments: {0}", ArgumentsBox);
 
-            ProcessStartInfo proc;
-            proc = new ProcessStartInfo
-            {
-                UseShellExecute = false,
-                RedirectStandardOutput = true,
-                RedirectStandardError = true,
-                WindowStyle = ProcessWindowStyle.Hidden,
-                CreateNoWindow = true,
-                FileName = System.IO.Path.Combine(Settings.MinecraftForlderPath, javaPath),
-                StandardErrorEncoding = Encoding.UTF8,
-                WorkingDirectory = Settings.MinecraftForlderPath,
-                Arguments = ArgumentsBox
-            };
+                ProcessStartInfo proc;
+                proc = new ProcessStartInfo
+                {
+                    UseShellExecute = false,
+                    RedirectStandardOutput = true,
+                    RedirectStandardError = true,
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    CreateNoWindow = true,
+                    FileName = System.IO.Path.Combine(Settings.MinecraftForlderPath, javaPath),
+                    StandardErrorEncoding = Encoding.UTF8,
+                    WorkingDirectory = Settings.MinecraftForlderPath,
+                    Arguments = ArgumentsBox
+                };
 
 
-            Process minecraft = new Process();
+                Process minecraft = new Process();
 
-            minecraft.StartInfo = proc;
+                minecraft.StartInfo = proc;
 
-            Task.Run(() =>
-            {
-                logger.Debug("Starting java process.");
-                
-                minecraft.OutputDataReceived += OutputHandler;                                
-                minecraft.ErrorDataReceived += OutputHandler;
+                Task.Run(() =>
+                {
+                    try
+                    {
+                        logger.Debug("Starting java process.");
 
-                //minecraft.Exited += ProcessExited; //dont work properly
-                //* Start process and handlers            
-                //minecraft.WaitForExit();
-                minecraft.Start();
+                        minecraft.OutputDataReceived += OutputHandler;
+                        minecraft.ErrorDataReceived += OutputHandler;
 
-                minecraft.BeginOutputReadLine();                               
-                minecraft.BeginErrorReadLine();
+                        //minecraft.Exited += ProcessExited; //dont work properly
+                        //* Start process and handlers            
+                        //minecraft.WaitForExit();
+                        minecraft.Start();
 
-                if (!Settings.gameLogToLauncher)
-                {
-                    minecraft.OutputDataReceived -= OutputHandler;
-                    minecraft.CancelOutputRead();
-                }
+                        minecraft.BeginOutputReadLine();
+                        minecraft.BeginErrorReadLine();
 
-                return Task.CompletedTask;
-            });
-            logger.Debug("Updating Username in Settings");
-            Settings.Username = username;
-            Settings.lastChosenVersion = DownloadedVerion.version;
-            Settings.SaveSettings();
+                        if (!Settings.gameLogToLauncher)
+                        {
+                            minecraft.OutputDataReceived -= OutputHandler;
+                            minecraft.CancelOutputRead();
+                        }
 
+                        return Task.CompletedTask;
+                    }
+                    catch (Exception ex)
+                    {
+                        OpenErrorWindow(ex);
+                        return Task.CompletedTask;
+                    }
+                });
+                logger.Debug("Updating Settings");
+                Settings.Username = username;
+                Settings.lastChosenVersion = DownloadedVerion.version;
+                Settings.SaveSettings();
+            
+            }
+            catch (Exception ex)
+            {
+                OpenErrorWindow(ex);
+            }
+            
         }
 
         void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
@@ -497,9 +620,11 @@ namespace VeloeMinecraftLauncher.ViewModels
 
                 foreach (var dir in dirs)
                 {
+                    logger.Debug("Checking folder {0}", dir.Name);
                     string checkedPath;
-                    if (File.Exists(checkedPath = dir.FullName + "\\" + dir.Name + ".json"))
+                    if (File.Exists(checkedPath = dir.FullName + "/" + dir.Name + ".json"))
                     {
+                        logger.Debug("Found version {0}",dir.Name);
                         DownloadedVersions.Add(new DownloadedVerion() { path = checkedPath, version = dir.Name });
                         profiles.profiles.Add($"Version {dir.Name}", new Profile() { name = dir.Name, lastVersionId = dir.Name, launcherVisibilityOnGameClose = "keep the launcher open" });
                     }
@@ -538,7 +663,14 @@ namespace VeloeMinecraftLauncher.ViewModels
 
         public void updateAvailable(object sendingObject, EventArgs e)
         {
-            updateAvailable();
+            try
+            {
+                updateAvailable();
+            }
+            catch (Exception ex)
+            {
+                OpenErrorWindow(ex);                
+            }
 
             switch (sendingObject)
             {
@@ -559,11 +691,8 @@ namespace VeloeMinecraftLauncher.ViewModels
 
             if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
             {
-                //var versionsDownloaderTask =
                 settingsWindow.Closed += updateAvailable;
                 settingsWindow.ShowDialog(desktop.MainWindow);
-                //versionsDownloaderTask.Wait();
-                //updateAvailable();
             }
         }
 
@@ -571,19 +700,127 @@ namespace VeloeMinecraftLauncher.ViewModels
         {
             logger.Debug("Started updater.exe");
             Process updater = new Process();
-            updater.StartInfo.FileName = "updater.exe";
+            updater.StartInfo.FileName = "Updater";
+            if (OperatingSystem.IsWindows())
+                updater.StartInfo.FileName += ".exe";
 
-            updater.Start();
+            if (!File.Exists(updater.StartInfo.FileName))
+                UpdateUpdater();
 
-            if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+            if (!File.Exists(updater.StartInfo.FileName))
             {
-                //var versionsDownloaderTask =
-                desktop.MainWindow.Close();
-                //versionsDownloaderTask.Wait();
-                //updateAvailable();
+                updater.Start();
+
+                if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+                {
+                    desktop.MainWindow.Close();
+                }
+            }
+            else
+                OpenErrorWindow(new FileNotFoundException($"File {updater.StartInfo.FileName} not found!"));
+        }
+
+            private void UpdateUpdater()
+        {
+            try
+            {
+                string fileName = "Updater";
+                if (OperatingSystem.IsWindows())
+                    fileName += ".exe";
+                if (File.Exists(fileName))
+                {
+                    FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
+                    LatestLauncherVersion latestLauncherVersion = Downloader.DownloadAndDeserializeJsonData<LatestLauncherVersion>("https://files.veloe.link/launcher/update/versions.json");
+
+                    if (OperatingSystem.IsLinux())
+                    {
+                        logger.Information("Manual updates only.");
+                        return;
+                    }
+
+                        if (latestLauncherVersion != null)
+                    {
+                        string[] version = latestLauncherVersion.updater.Split('.');
+                        logger.Information("Latest updater version on server: {0}", latestLauncherVersion.updater);
+                        string[] versionOnServer = fileVersionInfo.FileVersion.Split('.');
+                        logger.Information("Updater version: {0}", fileVersionInfo.FileVersion);
+
+                        if (!(Int16.Parse(version[0]) > Int16.Parse(versionOnServer[0]) ||
+                            Int16.Parse(version[1]) > Int16.Parse(versionOnServer[1]) ||
+                            Int16.Parse(version[2]) > Int16.Parse(versionOnServer[2]) ||
+                            Int16.Parse(version[3]) > Int16.Parse(versionOnServer[3])))
+                        {
+                            logger.Information("No update required.");
+                            return;
+                        }
+                    }
+                }
+                               
+                WebClient webClient = new WebClient();
+                try
+                {
+                    logger.Information("Downloading updater.zip");
+                    string url = String.Empty;
+
+                    if (OperatingSystem.IsWindows())
+                        url = "https://files.veloe.link/launcher/update/updater.zip";
+
+                    if (OperatingSystem.IsLinux())
+                        url = "https://files.veloe.link/launcher/update/updater_linux-x64.zip";
+
+                    if (url == String.Empty)
+                        return;
+
+                    webClient.DownloadFile(new System.Uri(url), "updater.zip"); ;
+                }
+                catch (Exception ex)
+                {
+                    logger.Error("Error occured on getting updater.zip from the server.");
+                    throw;
+                }
+
+                logger.Information("Unpacking updater.zip");
+                ZipFile.ExtractToDirectory($"updater.zip", Directory.GetCurrentDirectory(), true);
+
+                logger.Information("Deleting updater.zip");
+                File.Delete($"updater.zip");
+                
+            }
+            catch (Exception ex)
+            {
+                OpenErrorWindow(ex);
             }
 
+        }
+
+        private void OpenErrorWindow(Exception ex)
+        {
+            var message = ex.Message;
+            var stackTrace = ex.StackTrace;
+            Settings.logger.Error(ex.Message);
+            Settings.logger.Error(ex.StackTrace);
+            Exception innerException = ex.InnerException;
+            while (innerException is not null)
+            {
+                message += "\n" + innerException.Message;
+                stackTrace += "\n" + innerException.StackTrace;
+                Settings.logger.Error(innerException.Message);
+                Settings.logger.Error(innerException.StackTrace);
+                innerException = innerException.InnerException;
+            }
 
+            Dispatcher.UIThread.InvokeAsync(() =>
+            {
+                if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+                {
+                    var ErrorMessageViewModel = new ErrorWindowViewModel(message, stackTrace);
+                    var ErrorMessage = new ErrorWindow()
+                    {
+                        DataContext = ErrorMessageViewModel
+                    };
+                    ErrorMessage.ShowDialog(desktop.MainWindow);
+                }
+            });
         }
 
         private void OnTimedEventMcTfc(Object source, ElapsedEventArgs e)
@@ -605,6 +842,7 @@ namespace VeloeMinecraftLauncher.ViewModels
     public class LatestLauncherVersion
     {
         public string latest { get; set; }
+        public string updater { get; set; }
         public string url { get; set; }
     }
 

+ 136 - 20
VeloeMinecraftLauncher/ViewModels/VersionsDownloaderViewModel.cs

@@ -1,4 +1,6 @@
-using ReactiveUI;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+using ReactiveUI;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
@@ -12,6 +14,7 @@ using System.Threading.Tasks;
 using VeloeMinecraftLauncher.Entity.Assets;
 using VeloeMinecraftLauncher.Entity.VersionManifest;
 using VeloeMinecraftLauncher.MinecraftLauncher;
+using VeloeMinecraftLauncher.Views;
 
 namespace VeloeMinecraftLauncher.ViewModels
 {
@@ -45,15 +48,50 @@ namespace VeloeMinecraftLauncher.ViewModels
 
         public VersionsDownloaderViewModel()
         {
-            logger = Settings.logger;
-            if (FilteredVerions is null)
+            try
             {
-                FilteredVerions = new ObservableCollection<Entity.VersionManifest.Version>();
+                Task.Run(() =>
+                {
+                    logger = Settings.logger;
+                    if (FilteredVerions is null)
+                    {
+                        FilteredVerions = new ObservableCollection<Entity.VersionManifest.Version>();
+                    }
+                    logger.Debug("Getting versionManifest.json");
+                    versionManifest = Downloader.DownloadAndDeserializeJsonData<VersionManifest>("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json");
+                    logger.Debug("Updating available versions to download.");
+                    updateList();
+                });
+            }
+            catch (Exception ex)
+            {
+                var message = ex.Message;
+                var stackTrace = ex.StackTrace;
+                Settings.logger.Error(ex.Message);
+                Settings.logger.Error(ex.StackTrace);
+                Exception innerException = ex.InnerException;
+                while (innerException is not null)
+                {
+                    message += "\n" + innerException.Message;
+                    stackTrace += "\n" + innerException.StackTrace;
+                    Settings.logger.Error(innerException.Message);
+                    Settings.logger.Error(innerException.StackTrace);
+                    innerException = innerException.InnerException;
+                }
+
+
+                if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+                {
+                    var ErrorMessageViewModel = new ErrorWindowViewModel(message, stackTrace);
+                    var ErrorMessage = new ErrorWindow()
+                    {
+                        DataContext = ErrorMessageViewModel
+                    };
+                    //var versionsDownloaderTask =
+                    ErrorMessage.ShowDialog(desktop.MainWindow);
+                    //versionsDownloaderTask.Wait();              
+                }
             }
-            logger.Debug("Getting versionManifest.json");
-            versionManifest = Downloader.DownloadAndDeserializeJsonData<VersionManifest>("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json");
-            logger.Debug("Updating available versions to download.");
-            updateList();
         }
 
         public Entity.VersionManifest.Version FilteredVersion
@@ -66,11 +104,11 @@ namespace VeloeMinecraftLauncher.ViewModels
                 InstallForge = false;
                 InstallOptifine = false;
                 InstallForgeOptifine = false;
-
-                using (var httpClient = new HttpClient())
+                try
                 {
-                    try
+                    using (var httpClient = new HttpClient())
                     {
+                    
                         var response = httpClient.GetAsync(@$"https://files.veloe.link/launcher/forge/Forge{value.id}/Forge{value.id}.json").Result;
 
                         if (response.IsSuccessStatusCode)
@@ -99,14 +137,38 @@ namespace VeloeMinecraftLauncher.ViewModels
                             InstallOptifineVisible = true;
                         else
                             InstallOptifineVisible = false;
+                    
+
                     }
-                    catch (Exception ex)
+                }
+                catch (Exception ex)
+                {
+                    var message = ex.Message;
+                    var stackTrace = ex.StackTrace;
+                    logger.Error(ex.Message);
+                    logger.Error(ex.StackTrace);
+                    Exception innerException = ex.InnerException;
+                    while (innerException is not null)
                     {
-
+                        message += "\n" + innerException.Message;
+                        stackTrace += "\n" + innerException.StackTrace;
+                        logger.Error(innerException.Message);
+                        logger.Error(innerException.StackTrace);
+                        innerException = innerException.InnerException;
                     }
 
+                    Dispatcher.UIThread.InvokeAsync(async () => { 
+                    if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+                    {
+                        var ErrorMessageViewModel = new ErrorWindowViewModel(message, stackTrace);
+                        var ErrorMessage = new ErrorWindow()
+                        {
+                            DataContext = ErrorMessageViewModel
+                        };
+                        ErrorMessage.ShowDialog(desktop.MainWindow);
+                    }
+                    });
                 }
-
             }
         }
 
@@ -655,8 +717,30 @@ namespace VeloeMinecraftLauncher.ViewModels
             {
                 IsControlsEnabled = true;
                 DownloadingFileName = "Error occured while downloading McTFC.";
+                var message = ex.Message;
+                var stackTrace = ex.StackTrace;
                 logger.Error(ex.Message);
                 logger.Error(ex.StackTrace);
+                Exception innerException = ex.InnerException;
+                while (innerException is not null)
+                {
+                    message += "\n" + innerException.Message;
+                    stackTrace += "\n" + innerException.StackTrace;
+                    logger.Error(innerException.Message);
+                    logger.Error(innerException.StackTrace);
+                    innerException = innerException.InnerException;
+                }
+
+
+                if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+                {
+                    var ErrorMessageViewModel = new ErrorWindowViewModel(message, stackTrace);
+                    var ErrorMessage = new ErrorWindow()
+                    {
+                        DataContext = ErrorMessageViewModel
+                    };
+                    ErrorMessage.ShowDialog(desktop.MainWindow);           
+                }
                 return TaskStatus.Faulted;
             }
             return TaskStatus.RanToCompletion;
@@ -679,15 +763,47 @@ namespace VeloeMinecraftLauncher.ViewModels
 
         public void updateList()
         {
-            FilteredVerions.Clear();
+            try
+            {
+                FilteredVerions.Clear();
 
-            if (versionManifest.versions is null)
-                return;
+                if (versionManifest.versions is null)
+                    return;
 
-            foreach (var version in versionManifest.versions)
+                foreach (var version in versionManifest.versions)
+                {
+                    if (ShowSnaps && version.type == "snapshot" || ShowOld && version.type is ("old_alpha" or "old_beta") || version.type == "release")
+                        FilteredVerions.Add(version);
+                }
+            }
+            catch (Exception ex)
             {
-                if (ShowSnaps && version.type == "snapshot" || ShowOld && version.type is ("old_alpha" or "old_beta") || version.type == "release")    
-                FilteredVerions.Add(version);
+                var message = ex.Message;
+                var stackTrace = ex.StackTrace;
+                logger.Error(ex.Message);
+                logger.Error(ex.StackTrace);
+                Exception innerException = ex.InnerException;
+                while (innerException is not null)
+                {
+                    message += "\n" + innerException.Message;
+                    stackTrace += "\n" + innerException.StackTrace;
+                    logger.Error(innerException.Message);
+                    logger.Error(innerException.StackTrace);
+                    innerException = innerException.InnerException;
+                }
+
+
+                if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+                {
+                    var ErrorMessageViewModel = new ErrorWindowViewModel(message, stackTrace);
+                    var ErrorMessage = new ErrorWindow()
+                    {
+                        DataContext = ErrorMessageViewModel
+                    };
+                    //var versionsDownloaderTask =
+                    ErrorMessage.ShowDialog(desktop.MainWindow);
+                    //versionsDownloaderTask.Wait();              
+                }
             }
         }
 

+ 58 - 0
VeloeMinecraftLauncher/Views/ErrorWindow.axaml

@@ -0,0 +1,58 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+		xmlns:vm="using:VeloeMinecraftLauncher.ViewModels"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="200"
+		Width = "400" Height ="200"
+        x:Class="VeloeMinecraftLauncher.Views.ErrorWindow"
+		xmlns:titlebars="using:VeloeMinecraftLauncher.Views.TitleBar"
+        Icon="/Assets/avalonia-logo.ico"
+        Title="ErrorWindow"
+		TransparencyLevelHint="AcrylicBlur"
+		Background="Transparent"
+		CanResize="True"
+		ExtendClientAreaToDecorationsHint="True"
+		ExtendClientAreaChromeHints="NoChrome"
+		ExtendClientAreaTitleBarHeightHint="-1">
+
+	<Design.DataContext>
+		<vm:ErrorWindowViewModel/>
+	</Design.DataContext>
+
+	<Panel>
+		<ExperimentalAcrylicBorder IsHitTestVisible="False">
+			<ExperimentalAcrylicBorder.Material>
+				<ExperimentalAcrylicMaterial
+					BackgroundSource="Digger"
+					TintColor="Black"
+					TintOpacity="1"
+					MaterialOpacity="0.2" />
+			</ExperimentalAcrylicBorder.Material>
+		</ExperimentalAcrylicBorder>
+		<DockPanel>
+			<titlebars:TitleBarWindow
+			  IsSeamless="False"
+			  IsIconVisible="False"
+			  IsMaximizeVisible="False"
+			  TitleText="Error Message"
+			  DockPanel.Dock="Top">
+			</titlebars:TitleBarWindow>
+			<DockPanel 
+				Margin="10">
+			<TextBlock 
+				Text="{Binding ErrorMessage}" 
+				DockPanel.Dock="Top">
+			</TextBlock>
+			<TextBox 
+				Text="{Binding ErrorTrace}" 
+				IsReadOnly="True" 
+				Margin="0 10 0  0" 
+				DockPanel.Dock="Bottom"
+				HorizontalAlignment="Stretch" 
+				VerticalAlignment="Stretch">
+			</TextBox>
+			</DockPanel>
+		</DockPanel>
+	</Panel>
+</Window>

+ 12 - 0
VeloeMinecraftLauncher/Views/ErrorWindow.axaml.cs

@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+
+namespace VeloeMinecraftLauncher.Views
+{
+    public partial class ErrorWindow : Window
+    {
+        public ErrorWindow()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 294 - 69
VeloeMinecraftLauncher/Views/MainWindow.axaml

@@ -3,90 +3,315 @@
         xmlns:vm="using:VeloeMinecraftLauncher.ViewModels"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+		xmlns:titlebars="using:VeloeMinecraftLauncher.Views.TitleBar"
         mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
 		Width="800" Height="450"
+		MaxWidth="800" MaxHeight="450"
         x:Class="VeloeMinecraftLauncher.Views.MainWindow"
         Icon="/Assets/avalonia-logo.ico"
         Title="VeloeMinecraftLauncher"
-		CanResize="False">
-
+		TransparencyLevelHint="AcrylicBlur"
+		Background="Transparent"		
+		CanResize="False"
+		ExtendClientAreaToDecorationsHint="True"
+		ExtendClientAreaChromeHints="NoChrome"
+		ExtendClientAreaTitleBarHeightHint="-1">
+		
 	
 	
     <Design.DataContext>
         <vm:MainWindowViewModel/>
     </Design.DataContext>
+		
+	<Panel>
+		<ExperimentalAcrylicBorder IsHitTestVisible="False">
+			<ExperimentalAcrylicBorder.Material>
+				<ExperimentalAcrylicMaterial
+					BackgroundSource="Digger"
+					TintColor="Black"
+					TintOpacity="1"
+					MaterialOpacity="0.2" />
+			</ExperimentalAcrylicBorder.Material>
+		</ExperimentalAcrylicBorder>
+		<DockPanel>
+			<titlebars:TitleBarWindow
+			  IsSeamless="False"
+			  IsIconVisible="False"
+			  IsMaximizeVisible="False"
+			  TitleText="Veloe Minecraft Launcher"
+			  DockPanel.Dock="Top">
+			</titlebars:TitleBarWindow>
+			<Grid DockPanel.Dock="Left">
+				<Grid.RowDefinitions>
+					<RowDefinition Height="0"></RowDefinition>
+					<RowDefinition Height="*"></RowDefinition>
+					<RowDefinition Height="40" ></RowDefinition>
+					<RowDefinition Height="0"></RowDefinition>
+				</Grid.RowDefinitions>
+				<Grid.ColumnDefinitions>
+					<ColumnDefinition Width="*"></ColumnDefinition>
+				</Grid.ColumnDefinitions>
+				<Button
+					ZIndex="999"
+					Grid.Row="1"
+					HorizontalAlignment="Right"
+					VerticalAlignment="Top"
+					Command="{Binding DownloadUpdate}"
+					IsVisible="{Binding IsUpdateAvailable}">
+					Update Available
+				</Button>
+				<TabControl
+					Grid.Row="1"
+					HorizontalAlignment="Stretch">
+					<TabItem
+						Header="Servers"
+						VerticalContentAlignment="Center">
+						<StackPanel
+							Orientation="Horizontal"
+							HorizontalAlignment="Stretch">
+							<Panel
+								VerticalAlignment="Top"
+								HorizontalAlignment="Stretch"
+								Height="75" Width="150"
+								Margin="0 0 10 10">
+								<ExperimentalAcrylicBorder
+									IsHitTestVisible="False"
+									CornerRadius="15">
+									<ExperimentalAcrylicBorder.Material>
+										<ExperimentalAcrylicMaterial
+											BackgroundSource="Digger"
+											TintColor="Black"
+											TintOpacity="1"
+											MaterialOpacity="0.5" />
+									</ExperimentalAcrylicBorder.Material>
+								</ExperimentalAcrylicBorder>
+								<ToolTip.Tip>
+									<TextBlock
+										TextWrapping="Wrap"
+										Text="{Binding McTfcTip}">
+									</TextBlock>
+								</ToolTip.Tip>
+								<StackPanel
+									HorizontalAlignment="Center"
+									VerticalAlignment="Center">
+									<TextBlock
+										Text="{Binding McTfcBlock}"
+										VerticalAlignment="Center"
+										HorizontalAlignment="Center">
+										McTFC
+									</TextBlock>
+									<TextBlock
+										Text="{Binding McTfcPlayersBlock}"
+										FontSize="20"
+										VerticalAlignment="Center"
+										HorizontalAlignment="Center">
+									</TextBlock>
+								</StackPanel>
+							</Panel>
+							<Panel
+								VerticalAlignment="Top"
+								HorizontalAlignment="Stretch"
+								Height="75"
+								Width="150"
+								Margin="0 0 10 10">
+								<ExperimentalAcrylicBorder
+									IsHitTestVisible="False"
+									CornerRadius="15">
+									<ExperimentalAcrylicBorder.Material>
+										<ExperimentalAcrylicMaterial
+											BackgroundSource="Digger"
+											TintColor="Black"
+											TintOpacity="1"
+											MaterialOpacity="0.5" />
+									</ExperimentalAcrylicBorder.Material>
+								</ExperimentalAcrylicBorder>
+								<ToolTip.Tip>
+									<TextBlock
+										TextWrapping="Wrap"
+										Text="{Binding McTechTip}">
+									</TextBlock>
+								</ToolTip.Tip>
+								<StackPanel
+									HorizontalAlignment="Center"
+									VerticalAlignment="Center">
+									<TextBlock
+										Text="{Binding McTechBlock}"
+										VerticalAlignment="Center"
+										HorizontalAlignment="Center">
+										McTech
+									</TextBlock>
+									<TextBlock
+										Text="{Binding McTechPlayersBlock}"
+										FontSize="20"
+										VerticalAlignment="Center"
+										HorizontalAlignment="Center">
+									</TextBlock>
+								</StackPanel>
+							</Panel>
+							<Panel
+								VerticalAlignment="Top"
+								HorizontalAlignment="Stretch"
+								Height="75"
+								Width="150"
+								Margin="0 0 10 10">
+								<ExperimentalAcrylicBorder
+									IsHitTestVisible="False"
+									CornerRadius="15">
+									<ExperimentalAcrylicBorder.Material>
+										<ExperimentalAcrylicMaterial
+											BackgroundSource="Digger"
+											TintColor="Black"
+											TintOpacity="1"
+											MaterialOpacity="0.5" />
+									</ExperimentalAcrylicBorder.Material>
+								</ExperimentalAcrylicBorder>
+								<ToolTip.Tip>
+									<TextBlock
+										TextWrapping="Wrap"
+										Text="{Binding McVanillaTip}">
+									</TextBlock>
+								</ToolTip.Tip>
+								<StackPanel
+									HorizontalAlignment="Center"
+									VerticalAlignment="Center">
+									<TextBlock
+										Text="{Binding McVanillaBlock}"
+										VerticalAlignment="Center"
+										HorizontalAlignment="Center">
+										McVanilla
+									</TextBlock>
+									<TextBlock
+										Text="{Binding McVanillaPlayersBlock}"
+										FontSize="20"
+										VerticalAlignment="Center"
+										HorizontalAlignment="Center">
+									</TextBlock>
+								</StackPanel>
+							</Panel>
+						</StackPanel>
+					</TabItem>
+					<TabItem>
+						<TabItem.Header>
+							<TextBlock VerticalAlignment="Center">Console</TextBlock>
+						</TabItem.Header>
+						<ScrollViewer
+							Name="ConsoleScroll"
+							HorizontalScrollBarVisibility="Auto"
+							VerticalAlignment="Stretch"
+							HorizontalAlignment="Stretch">
+							<TextBlock
+								Text="{Binding ConsoleText}"
+								VerticalAlignment="Stretch"
+								Width="770"
+								TextWrapping="Wrap"
+								ScrollViewer.VerticalScrollBarVisibility="Visible"
+								MaxLines="99999">
+							</TextBlock>
+						</ScrollViewer>
+					</TabItem>
+					<TabItem>
+						<TabItem.Header>
+							<TextBlock VerticalAlignment="Center">Changelog</TextBlock>
+						</TabItem.Header>
+						<ScrollViewer
+							HorizontalScrollBarVisibility="Auto"
+							VerticalAlignment="Stretch"
+							HorizontalAlignment="Stretch">
+							<StackPanel>
+								<TextBlock
+									VerticalAlignment="Stretch"
+									TextWrapping="Wrap"
+									ScrollViewer.VerticalScrollBarVisibility="Visible"
+									Text="При проблемах отправьте мне логи.&#10; ">
+								</TextBlock>
+								<TextBlock
+									FontSize="16"
+									Text="v 1.1.0.0 (win x64, linux x64)">
+								</TextBlock>
+								<TextBlock
+									VerticalAlignment="Stretch"
+									TextWrapping="Wrap"
+									ScrollViewer.VerticalScrollBarVisibility="Visible"
+									Text="Обновление интерфейса.&#10;Добавлены сообщения об ошибках при обработке исключений.&#10;Улучшено логгирование.&#10;Исправлена ошибка вылетов при отсутвии интернета.&#10;Установщики Optifine теперь работают кооректно, добавляя рабочий конфиг для запуска.&#10;Updater теперь обновляется при запуске лаунчера. (Windows only)&#10; ">
+								</TextBlock>
+								<TextBlock
+									FontSize="16"
+									Text="v 1.0.0.2 (win x64)">
+								</TextBlock>
+								<TextBlock
+									VerticalAlignment="Stretch"
+									TextWrapping="Wrap"
+									ScrollViewer.VerticalScrollBarVisibility="Visible"
+									Text="Консоль и Changelog теперь выводится в TextBlock вместо TextBox.&#10;Лог игры игры можно выводить в консоль, однако сохраняться в файл лога лаунчера он не будет.&#10;Последняя запущенная версия теперь сохраняется в настройках.&#10;Для выбора папки с игрой и java можно воспользоваться диалоговым окном проводника.&#10;Добавлены отступы к элементам на диалоговых окнах настроек и весий.&#10;Исправлена проблема при загрузке клиентa McTFC.&#10;Исправлена проблема с выбором певрого элемента из скачанных версий.&#10;Добавлен валидатор на поле ввода максимальной оперативной памяти.&#10; ">
+								</TextBlock>
+								<TextBlock
+									FontSize="16"
+									Text="v 1.0.0.1 (win x64)">
+								</TextBlock>
+								<TextBlock
+									VerticalAlignment="Stretch"
+									TextWrapping="Wrap"
+									ScrollViewer.VerticalScrollBarVisibility="Visible"
+									Text="Первый рабочий билд.&#10;">
+								</TextBlock>
+							</StackPanel>
+						</ScrollViewer>
+					</TabItem>
+				</TabControl>
+
+				<StackPanel
+					Grid.Row="2"
+					Orientation="Horizontal"
+					HorizontalAlignment="Center">
 
-  	
-  <Grid>
-	  <Grid.RowDefinitions>
-	  <RowDefinition Height="*"></RowDefinition>
-	  <RowDefinition Height="40" ></RowDefinition>
-	  <RowDefinition Height="0"></RowDefinition>
-	  </Grid.RowDefinitions>
-	  <Grid.ColumnDefinitions>
-		  <ColumnDefinition Width="*"></ColumnDefinition>
-	  </Grid.ColumnDefinitions>
-	  <Button ZIndex="999" Grid.Row="0" HorizontalAlignment="Right" VerticalAlignment="Top" Command="{Binding DownloadUpdate}" IsVisible="{Binding IsUpdateAvailable}">Update Available</Button>
-		<TabControl Grid.Row="0" HorizontalAlignment="Stretch">
-			<TabItem Header="Servers" VerticalContentAlignment="Center">
-				<StackPanel>
-					<TextBlock Text="{Binding McTfcBlock}">
-						  
+					<Button
+						Content="{Binding DownloadButton}"
+						Command="{Binding OnClickCommand}">
+					</Button>
+					<Button
+						Content="{Binding SettingsButton}"
+						Command="{Binding OpenSettings}"
+						IsVisible="true">
+					</Button>
+					<TextBlock
+						Margin="3">
 					</TextBlock>
-					<TextBlock Text="{Binding McTechBlock}">
-						  
+					<ComboBox
+						Items="{Binding DownloadedVersions}"
+						PlaceholderText="Select version"
+						SelectedItem="Binding DownloadedVersion"
+						SelectedIndex="{Binding DownloadedIndex}"
+						Width="220"
+						VerticalAlignment="Center"
+						HorizontalAlignment="Center">
+					</ComboBox>
+					<TextBlock
+						Margin="3">
 					</TextBlock>
-					<TextBlock Text="{Binding McVanillaBlock}">
-						  
+					<TextBox
+						Text="{Binding Username}"
+						Watermark="Username"
+						MinWidth="220"
+						VerticalAlignment="Center"
+						HorizontalAlignment="Center">
+					</TextBox>
+					<TextBlock
+						Margin="3">
 					</TextBlock>
+					<Button
+						Content="{Binding StartButton}"
+						Command="{Binding StartMinecraft}"
+						IsEnabled="{Binding IsNoGameRunning}">
+					</Button>
 				</StackPanel>
-			</TabItem>
-			<TabItem>
-				<TabItem.Header>
-					<TextBlock VerticalAlignment="Center">Console</TextBlock>
-				</TabItem.Header>
-				<ScrollViewer Name="ConsoleScroll" HorizontalScrollBarVisibility="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
-<TextBlock Text="{Binding ConsoleText}"  VerticalAlignment="Stretch" Width="770" TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Visible"  MaxLines="99999">
-
-</TextBlock>
-				</ScrollViewer>
-			</TabItem>
-			<TabItem>
-				<TabItem.Header>
-					<TextBlock VerticalAlignment="Center">Changelog</TextBlock>
-				</TabItem.Header>
-				<ScrollViewer>
-					<StackPanel>
-						<TextBlock VerticalAlignment="Stretch" TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Visible" Text="При проблемах отправьте мне логи.&#10; ">													
-						</TextBlock>
-						<TextBlock FontSize="16" Text="v 1.0.0.2 (win x64)">
-						</TextBlock>
-						<TextBlock VerticalAlignment="Stretch" TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Visible" Text="Консоль и Changelog теперь выводится в TextBlock вместо TextBox.&#10;Лог игры игры можно выводить в консоль, однако сохраняться в файл лога лаунчера он не будет.&#10;Последняя запущенная версия теперь сохраняется в настройках.&#10;Для выбора папки с игрой и java можно воспользоваться диалоговым окном проводника.&#10;Добавлены отступы к элементам на диалоговых окнах настроек и весий.&#10;Исправлена проблема при загрузке клиентa McTFC.&#10;Исправлена проблема с выбором певрого элемента из скачанных версий.&#10;Добавлен валидатор на поле ввода максимальной оперативной памяти.&#10; ">
-						</TextBlock>
-						<TextBlock FontSize="16" Text="v 1.0.0.1 (win x64)">
-						</TextBlock>
-						<TextBlock VerticalAlignment="Stretch" TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Visible" Text="Первый рабочий билд.&#10;">
-						</TextBlock>
-					</StackPanel>
-				</ScrollViewer>
-			</TabItem>
-		</TabControl>
+				<StackPanel Orientation="Horizontal" Grid.Row="2">
+					<TextBox Text="{Binding ArgumentsBox}" TextWrapping="Wrap" Width="680" Height="200" IsVisible="false"></TextBox>
+					<Button Content="{Binding StartButton}" Command="{Binding StartMinecraftCustom}" IsVisible="false"></Button>
+				</StackPanel>
+			</Grid>
+		</DockPanel>
 
-	  <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
-		  
-		  <Button Content="{Binding DownloadButton}" Command="{Binding OnClickCommand}"></Button>
-		  <Button Content="{Binding SettingsButton}" Command="{Binding OpenSettings}" IsVisible="true"></Button>
-		  <TextBlock Margin="3"></TextBlock>
-		  <ComboBox Items="{Binding DownloadedVersions}" SelectedItem="Binding DownloadedVersion" SelectedIndex="{Binding DownloadedIndex}" Width="220" VerticalAlignment="Center" HorizontalAlignment="Center"></ComboBox>
-		  <TextBlock Margin="3"></TextBlock>
-		  <TextBox Text="{Binding Username}" MinWidth="220" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBox>
-		  <TextBlock Margin="3"></TextBlock>
-		  <Button Content="{Binding StartButton}" Command="{Binding StartMinecraft}" IsEnabled="{Binding IsNoGameRunning}"></Button>
-	  </StackPanel>
-	  <StackPanel Orientation="Horizontal" Grid.Row="2">
-		  <TextBox Text="{Binding ArgumentsBox}" TextWrapping="Wrap" Width="680" Height="200" IsVisible="false"></TextBox>
-		  <Button Content="{Binding StartButton}" Command="{Binding StartMinecraftCustom}" IsVisible="false"></Button>
-	  </StackPanel>
-  </Grid>
+		
+	</Panel>
 	
 </Window>

+ 66 - 34
VeloeMinecraftLauncher/Views/SettingsWindow.axaml

@@ -3,45 +3,77 @@
 		xmlns:vm="using:VeloeMinecraftLauncher.ViewModels"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+		xmlns:titlebars="using:VeloeMinecraftLauncher.Views.TitleBar"
         mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="300"
 		Width="600" Height="300"
+		MaxWidth="600" MaxHeight="300"
         x:Class="VeloeMinecraftLauncher.Views.SettingsWindow"
-        Title="SettingsWindow"
-		CanResize="False">
+        Icon="/Assets/avalonia-logo.ico"
+        Title="Settings"
+		TransparencyLevelHint="AcrylicBlur"
+		Background="Transparent"
+		CanResize="False"
+		ExtendClientAreaToDecorationsHint="True"
+		ExtendClientAreaChromeHints="NoChrome"
+		ExtendClientAreaTitleBarHeightHint="-1">
 
 	<Design.DataContext>
 		<vm:SettingsWindowViewModel/>
 	</Design.DataContext>
-	<Grid Margin="10" ShowGridLines="false">
-		<Grid.RowDefinitions>
-			<RowDefinition Height="*" MaxHeight="35"></RowDefinition>
-			<RowDefinition Height="*" MaxHeight="35"></RowDefinition>
-			<RowDefinition Height="Auto" MinHeight="35" MaxHeight="65" ></RowDefinition>
-			<RowDefinition Height="*" MaxHeight="35"></RowDefinition>
-			<RowDefinition Height="*" MaxHeight="35"></RowDefinition>
-			<RowDefinition Height="*"></RowDefinition>
-		</Grid.RowDefinitions>
-		<Grid.ColumnDefinitions>
-			<ColumnDefinition Width="250"></ColumnDefinition>
-			<ColumnDefinition Width="*"></ColumnDefinition>
-			<ColumnDefinition Width="70"></ColumnDefinition>
-		</Grid.ColumnDefinitions>
-		<CheckBox Grid.Row="0" Grid.Column="0" IsChecked="true" IsEnabled="False">Set path to minecraft folder</CheckBox>
-		<TextBox Grid.Row="0" Grid.Column="1" Margin="5" Text="{Binding MinecraftFolderPath}" IsEnabled="{Binding SetMinecraftFolder}"></TextBox>
-		<Button Grid.Row="0" Grid.Column="2" Content="Open" Command="{Binding OpenMinecraftPathDialog}" CommandParameter="{Binding $parent[Window]}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"></Button>
-		
-		<CheckBox Grid.Row="1" Grid.Column="0" IsChecked="{Binding UseCustomJava}">Use custom java</CheckBox>
-		<TextBox Grid.Row="1" Grid.Column="1" Margin="5" Text="{Binding JavaPath}" IsEnabled="{Binding UseCustomJava}"></TextBox>
-		<Button Grid.Row="1" Grid.Column="2" Content="Open" Command="{Binding OpenJavaPathDialog}" CommandParameter="{Binding $parent[Window]}" IsEnabled="{Binding UseCustomJava}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"></Button>
-		
-		<CheckBox Grid.Row="2" Grid.Column="0" IsChecked="{Binding SetMaxRam}">Set max RAM</CheckBox>
-		<TextBox Grid.Row="2" Grid.Column="1" Margin="5" Name="MaxRam" Text="{Binding MaxRam}" IsEnabled="{Binding SetMaxRam}" HorizontalAlignment="Left" VerticalAlignment="Top"></TextBox>
-				
-		<CheckBox Grid.Row="3" Grid.Column="0" IsChecked="{Binding CheckAssets}" IsEnabled="false">Check game assets before start</CheckBox>
-		<CheckBox Grid.Row="4" Grid.ColumnSpan="2" IsChecked="{Binding GameLogToLauncher}">Show game log in launcher (performace issues)</CheckBox>
-		
-		<Button Grid.Row="5" Grid.Column="0" Command="{Binding SaveSettings}" IsEnabled="{Binding IsValid}" Content="Save" VerticalAlignment="Top"></Button>
-		<TextBlock Grid.Row="5" Grid.Column="2" Text="{Binding LauncherVersion}" HorizontalAlignment="Right" VerticalAlignment="Bottom"></TextBlock>
-				
-	</Grid>
+	<Panel>
+		<ExperimentalAcrylicBorder IsHitTestVisible="False">
+			<ExperimentalAcrylicBorder.Material>
+				<ExperimentalAcrylicMaterial
+					BackgroundSource="Digger"
+					TintColor="Black"
+					TintOpacity="1"
+					MaterialOpacity="0.2" />
+			</ExperimentalAcrylicBorder.Material>
+		</ExperimentalAcrylicBorder>
+		<DockPanel>
+			<titlebars:TitleBarWindow
+			  IsSeamless="False"
+			  IsIconVisible="False"
+			  IsMaximizeVisible="False"
+			  TitleText="Versions"
+			  DockPanel.Dock="Top">
+			</titlebars:TitleBarWindow>
+			<Grid Margin="10 0 10 5" ShowGridLines="false">
+				<Grid.RowDefinitions>
+					<RowDefinition Height="0" MaxHeight="0"></RowDefinition>
+					<RowDefinition Height="*" MaxHeight="35"></RowDefinition>
+					<RowDefinition Height="*" MaxHeight="35"></RowDefinition>
+					<RowDefinition Height="Auto" MinHeight="35" MaxHeight="65" ></RowDefinition>
+					<RowDefinition Height="*" MaxHeight="35"></RowDefinition>
+					<RowDefinition Height="*" MaxHeight="35"></RowDefinition>
+					<RowDefinition Height="*"></RowDefinition>
+				</Grid.RowDefinitions>
+				<Grid.ColumnDefinitions>
+					<ColumnDefinition Width="250"></ColumnDefinition>
+					<ColumnDefinition Width="*"></ColumnDefinition>
+					<ColumnDefinition Width="70"></ColumnDefinition>
+				</Grid.ColumnDefinitions>
+
+			
+
+				<CheckBox Grid.Row="1" Grid.Column="0" IsChecked="true" IsEnabled="False">Set path to minecraft folder</CheckBox>
+				<TextBox Grid.Row="1" Grid.Column="1" Margin="5" Text="{Binding MinecraftFolderPath}" IsEnabled="{Binding SetMinecraftFolder}"></TextBox>
+				<Button Grid.Row="1" Grid.Column="2" Content="Open" Command="{Binding OpenMinecraftPathDialog}" CommandParameter="{Binding $parent[Window]}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"></Button>
+
+				<CheckBox Grid.Row="2" Grid.Column="0" IsChecked="{Binding UseCustomJava}">Use custom java</CheckBox>
+				<TextBox Grid.Row="2" Grid.Column="1" Margin="5" Text="{Binding JavaPath}" IsEnabled="{Binding UseCustomJava}"></TextBox>
+				<Button Grid.Row="2" Grid.Column="2" Content="Open" Command="{Binding OpenJavaPathDialog}" CommandParameter="{Binding $parent[Window]}" IsEnabled="{Binding UseCustomJava}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"></Button>
+
+				<CheckBox Grid.Row="3" Grid.Column="0" IsChecked="{Binding SetMaxRam}">Set max RAM</CheckBox>
+				<TextBox Grid.Row="3" Grid.Column="1" Margin="5" Name="MaxRam" Text="{Binding MaxRam}" IsEnabled="{Binding SetMaxRam}" HorizontalAlignment="Left" VerticalAlignment="Top"></TextBox>
+
+				<CheckBox Grid.Row="4" Grid.Column="0" IsChecked="{Binding CheckAssets}" IsEnabled="false">Check game assets before start</CheckBox>
+				<CheckBox Grid.Row="5" Grid.ColumnSpan="2" IsChecked="{Binding GameLogToLauncher}">Show game log in launcher (performace issues)</CheckBox>
+
+				<Button Grid.Row="6" Grid.Column="0" Command="{Binding SaveSettings}" IsEnabled="{Binding IsValid}" Content="Save" VerticalAlignment="Top"></Button>
+				<TextBlock Grid.Row="6" Grid.Column="2" Text="{Binding LauncherVersion}" HorizontalAlignment="Right" VerticalAlignment="Bottom"></TextBlock>
+
+			</Grid>
+		</DockPanel>
+	</Panel>
 </Window>

+ 144 - 0
VeloeMinecraftLauncher/Views/TitleBar/TitleBarWindow.axaml

@@ -0,0 +1,144 @@
+<!-- 
+MIT License
+
+Original work Copyright (c) 2020 Fichtelcoder
+Modified work Copyright 2022 Veloe Aetess
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+-->
+<UserControl xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+        x:Class="VeloeMinecraftLauncher.Views.TitleBar.TitleBarWindow"
+		DockPanel.Dock="Top" Height="30">
+	<StackPanel>
+			<!--The proper way would be not to use white as default, but somehow retrieve the users' window chrome color.-->
+			<DockPanel Background="Transparent"
+					   IsHitTestVisible="False"
+					   Name="TitleBarBackground"></DockPanel>
+			<DockPanel Name="TitleBar">
+				<StackPanel Orientation="Horizontal"
+							DockPanel.Dock="Left"
+							Spacing="0">
+					<Image Source="/Assets/avalonia-logo.ico"
+						   Height="20"
+						   Width="20"
+						   VerticalAlignment="Center"
+						   Margin="5,0,3,0"
+						   Name="WindowIcon"></Image>
+					<NativeMenuBar DockPanel.Dock="Top"
+								   HorizontalAlignment="Left"
+								   Name="SeamlessMenuBar"></NativeMenuBar>
+					<TextBlock Text="Text"
+							   Margin="10 0 0 0"
+							   FontSize="12"
+							   Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}"
+							   VerticalAlignment="Center"
+							   HorizontalAlignment="Left"
+							   IsHitTestVisible="False"
+							   Name="SystemChromeTitle"></TextBlock>
+				</StackPanel>
+				<StackPanel HorizontalAlignment="Right"
+							Orientation="Horizontal"
+							Spacing="0">
+					<Button Width="46"
+							Height="30"
+							HorizontalContentAlignment="Center"
+							BorderThickness="0"
+							Name="MinimizeButton"
+							ToolTip.Tip="Minimize">
+						<Button.Resources>
+							<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
+						</Button.Resources>
+						<Button.Styles>
+							<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+								<Setter Property="Background" Value="#44AAAAAA"/>
+							</Style>
+							<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+								<Setter Property="Background" Value="Transparent"/>
+							</Style>							
+						</Button.Styles>
+						<Path Margin="10,10,10,0"
+							  Stretch="Uniform"
+							  Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
+							  Data="M2048 1229v-205h-2048v205h2048z"></Path>
+					</Button>
+
+					<Button Width="46"
+							VerticalAlignment="Stretch"
+							BorderThickness="0"
+							Name="MaximizeButton">
+						<ToolTip.Tip>
+							<ToolTip Content="Maximize"
+									 Name="MaximizeToolTip"></ToolTip>
+						</ToolTip.Tip>
+
+						<Button.Resources>
+							<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
+						</Button.Resources>
+						<Button.Styles>
+							<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+								<Setter Property="Background" Value="#44AAAAAA"/>
+							</Style>
+							<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+								<Setter Property="Background" Value="Transparent"/>						
+							</Style>
+						</Button.Styles>
+						<Path Margin="10,5,10,0"
+							  Stretch="Uniform"
+							  Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
+							  Name="MaximizeIcon"
+							  Data="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z"></Path>
+					</Button>
+
+					<Button Width="46"
+							VerticalAlignment="Stretch"
+							BorderThickness="0"
+							Name="CloseButton"
+							ToolTip.Tip="Close">
+						<Button.Resources>
+							<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
+						</Button.Resources>
+						<Button.Styles>
+							<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+								<Setter Property="Background" Value="Red"/>
+							</Style>
+							<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+								<Setter Property="Background" Value="Transparent"/>
+							</Style>
+							<Style Selector="Button:pointerover > Path">
+								<Setter Property="Fill" Value="White"/>
+							</Style>
+							<Style Selector="Button:not(:pointerover) > Path">
+								<Setter Property="Fill" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
+							</Style>
+						</Button.Styles>
+						<Path Margin="10,5,10,0"
+							  Stretch="Uniform"
+							  Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z"></Path>
+					</Button>
+
+				</StackPanel>
+			</DockPanel>
+		<NativeMenuBar HorizontalAlignment="Left"
+					   Name="DefaultMenuBar"></NativeMenuBar>
+	</StackPanel>
+</UserControl>

+ 237 - 0
VeloeMinecraftLauncher/Views/TitleBar/TitleBarWindow.axaml.cs

@@ -0,0 +1,237 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+/*
+MIT License
+
+Original work Copyright (c) 2020 Fichtelcoder
+Modified work Copyright 2022 Veloe Aetess
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+namespace VeloeMinecraftLauncher.Views.TitleBar
+{
+    public partial class TitleBarWindow : UserControl
+    {
+        private Button minimizeButton;
+        private Button maximizeButton;
+        private Path maximizeIcon;
+        private ToolTip maximizeToolTip;
+        private Button closeButton;
+        private Image windowIcon;
+
+        private DockPanel titleBar;
+        private DockPanel titleBarBackground;
+        private TextBlock systemChromeTitle;
+        private NativeMenuBar seamlessMenuBar;
+        private NativeMenuBar defaultMenuBar;
+
+        public static readonly StyledProperty<bool> IsSeamlessProperty =
+        AvaloniaProperty.Register<TitleBarWindow, bool>(nameof(IsSeamless));
+
+        public static readonly StyledProperty<bool> IsIconVisibleProperty =
+        AvaloniaProperty.Register<TitleBarWindow, bool>(nameof(IsIconVisible));
+
+        public static readonly StyledProperty<bool> IsMaximizeVisibleProperty =
+        AvaloniaProperty.Register<TitleBarWindow, bool>(nameof(IsMaximizeVisible));
+
+        public static readonly StyledProperty<string> TitleProperty =
+        AvaloniaProperty.Register<TitleBarWindow, string>(nameof(TitleText));
+
+        public bool IsSeamless
+        {
+            get { return GetValue(IsSeamlessProperty); }
+            set
+            {
+                SetValue(IsSeamlessProperty, value);
+                if (titleBarBackground != null &&
+                    systemChromeTitle != null &&
+                    seamlessMenuBar != null &&
+                    defaultMenuBar != null)
+                {
+                    titleBarBackground.IsVisible = IsSeamless ? false : true;
+                    systemChromeTitle.IsVisible = IsSeamless ? false : true;
+                    seamlessMenuBar.IsVisible = IsSeamless ? true : false;
+                    defaultMenuBar.IsVisible = IsSeamless ? false : true;
+
+                    if (IsSeamless == false)
+                    {
+                        //titleBar.Resources["SystemControlForegroundBaseHighBrush"] = new SolidColorBrush { Color = new Color(255, 0, 0, 0) };
+                    }
+                }
+            }
+        }
+
+        public bool IsIconVisible
+        {
+            get { return GetValue(IsIconVisibleProperty); }
+            set
+            {
+                SetValue(IsIconVisibleProperty, value);
+                if (windowIcon != null
+                    )
+                {
+                    windowIcon.IsVisible = IsIconVisible ? true : false;
+                    //systemChromeTitle.IsVisible = IsIconVisible ? true : false;
+                }
+            }
+        }
+
+        public bool IsMaximizeVisible
+        {
+            get { return GetValue(IsIconVisibleProperty); }
+            set
+            {
+                SetValue(IsIconVisibleProperty, value);
+                if (maximizeButton != null &&
+                    maximizeIcon != null &&
+                    maximizeToolTip != null)
+                {
+                    maximizeButton.IsVisible = IsIconVisible ? true : false;
+                    maximizeIcon.IsVisible = IsIconVisible ? true : false;
+                    maximizeToolTip.IsVisible = IsIconVisible ? true : false;
+                }
+            }
+        }
+
+        public string TitleText
+        {
+            get
+            { 
+                if (systemChromeTitle != null)
+                    return systemChromeTitle.Text;
+                return "";
+            }
+            set 
+            {
+                if (systemChromeTitle != null)
+                systemChromeTitle.Text = value;
+            }
+        }
+
+        public TitleBarWindow()
+        {
+            this.InitializeComponent();
+
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) == false)
+            {
+                this.IsVisible = false;
+            }
+            else
+            {
+                minimizeButton = this.FindControl<Button>("MinimizeButton");
+                maximizeButton = this.FindControl<Button>("MaximizeButton");
+                maximizeIcon = this.FindControl<Path>("MaximizeIcon");
+                maximizeToolTip = this.FindControl<ToolTip>("MaximizeToolTip");
+                closeButton = this.FindControl<Button>("CloseButton");
+                windowIcon = this.FindControl<Image>("WindowIcon");
+
+                minimizeButton.Click += MinimizeWindow;
+                maximizeButton.Click += MaximizeWindow;
+                closeButton.Click += CloseWindow;
+                windowIcon.DoubleTapped += CloseWindow;
+
+                titleBar = this.FindControl<DockPanel>("TitleBar");
+                titleBarBackground = this.FindControl<DockPanel>("TitleBarBackground");
+                systemChromeTitle = this.FindControl<TextBlock>("SystemChromeTitle");
+                seamlessMenuBar = this.FindControl<NativeMenuBar>("SeamlessMenuBar");
+                defaultMenuBar = this.FindControl<NativeMenuBar>("DefaultMenuBar");
+
+                SubscribeToWindowState();
+            }
+        }
+
+        private void CloseWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window hostWindow = (Window)this.VisualRoot;
+            hostWindow.Close();
+        }
+
+        private void MaximizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window hostWindow = (Window)this.VisualRoot;
+
+            if (hostWindow.WindowState == WindowState.Normal)
+            {
+                hostWindow.WindowState = WindowState.Maximized;
+            }
+            else
+            {
+                hostWindow.WindowState = WindowState.Normal;
+            }
+        }
+
+        private void MinimizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window hostWindow = (Window)this.VisualRoot;
+            hostWindow.WindowState = WindowState.Minimized;
+        }
+
+        private async void SubscribeToWindowState()
+        {
+            Window hostWindow = (Window)this.VisualRoot;
+
+            while (hostWindow == null)
+            {
+                hostWindow = (Window)this.VisualRoot;
+                await Task.Delay(50);
+            }
+            
+            hostWindow.GetObservable(Window.WindowStateProperty).Subscribe(s =>
+            {
+                if (s != WindowState.Maximized)
+                {
+                    maximizeIcon.Data = Geometry.Parse("M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z");
+                    hostWindow.Padding = new Thickness(0, 0, 0, 0);
+                    maximizeToolTip.Content = "Maximize";
+                }
+                if (s == WindowState.Maximized)
+                {
+                    maximizeIcon.Data = Geometry.Parse("M2048 1638h-410v410h-1638v-1638h410v-410h1638v1638zm-614-1024h-1229v1229h1229v-1229zm409-409h-1229v205h1024v1024h205v-1229z");
+                    hostWindow.Padding = new Thickness(7, 7, 7, 7);
+                    maximizeToolTip.Content = "Restore Down";
+
+                    // This should be a more universal approach in both cases, but I found it to be less reliable, when for example double-clicking the title bar.
+                    /*hostWindow.Padding = new Thickness(
+                            hostWindow.OffScreenMargin.Left,
+                            hostWindow.OffScreenMargin.Top,
+                            hostWindow.OffScreenMargin.Right,
+                            hostWindow.OffScreenMargin.Bottom);*/
+                }
+            });
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+  
+}

+ 47 - 19
VeloeMinecraftLauncher/Views/VersionsDownloader.axaml

@@ -4,30 +4,58 @@
 		xmlns:vm="using:VeloeMinecraftLauncher.ViewModels"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+		xmlns:titlebars="using:VeloeMinecraftLauncher.Views.TitleBar"
         mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="450"
 		Width="250" Height="450"
+		MaxWidth="250" MaxHeight="450"
         x:Class="VeloeMinecraftLauncher.Views.VersionsDownloader"
-        Title="VersionsDownloader"
-		CanResize="False">
+        Icon="/Assets/avalonia-logo.ico"
+        Title="Versions Downloader"
+		TransparencyLevelHint="AcrylicBlur"
+		Background="Transparent"
+		CanResize="False"
+		ExtendClientAreaToDecorationsHint="True"
+		ExtendClientAreaChromeHints="NoChrome"
+		ExtendClientAreaTitleBarHeightHint="-1"
+		>
 	
 	<Design.DataContext>
 		<vm:VersionsDownloaderViewModel/>
 	</Design.DataContext>
-	
-	<StackPanel Margin="10" Spacing="5">
-		
-		<ComboBox Items="{Binding FilteredVerions}" SelectedItem="{Binding FilteredVersion}" IsEnabled="{Binding IsControlsEnabled}" HorizontalAlignment="Stretch"></ComboBox>
-		<CheckBox IsChecked="{Binding ShowOld}" IsEnabled="{Binding IsControlsEnabled}">Show old</CheckBox>
-		<CheckBox IsChecked="{Binding ShowSnaps}" IsEnabled="{Binding IsControlsEnabled}">Show snapshots</CheckBox>
-		<CheckBox IsChecked="{Binding DownloadJava}" IsEnabled="{Binding IsControlsEnabled}">Download Java</CheckBox>
-		<CheckBox IsChecked="{Binding InstallFabric}" IsVisible="{Binding InstallFabricVisible}" IsEnabled="{Binding IsControlsEnabled}">Install Fabric</CheckBox>
-		<CheckBox IsChecked="{Binding InstallForge}" IsVisible="{Binding InstallForgeVisible}" IsEnabled="{Binding IsControlsEnabled}">Install Forge</CheckBox>
-		<CheckBox IsChecked="{Binding InstallForgeOptifine}" IsVisible="{Binding InstallForgeOptifineVisible}" IsEnabled="{Binding IsControlsEnabled}">Install Optifine (Mod)</CheckBox>
-		<CheckBox IsChecked="{Binding InstallOptifine}" IsVisible="{Binding InstallOptifineVisible}" IsEnabled="{Binding IsControlsEnabled}">Install Optifine (Vanilla)</CheckBox>
-		<Button Content="{Binding StartButton}" Command="{Binding OnStartBunttonClick}" IsEnabled="{Binding IsControlsEnabled}"></Button>
-		<Button Content="Install McTFC Client" Command="{Binding OnStartMcTfcBunttonClick}" IsEnabled="{Binding IsControlsEnabled}"></Button>
-		<Button Content="Install McTech Client" Command="{Binding OnClickCommand}" IsEnabled="{Binding IsControlsEnabled}"></Button>
-		<ProgressBar Value="{Binding Progress}" ShowProgressText="true"></ProgressBar>
-		<TextBlock Text="{Binding DownloadingFileName}"></TextBlock>
-	</StackPanel>
+
+	<Panel>
+		<ExperimentalAcrylicBorder IsHitTestVisible="False">
+			<ExperimentalAcrylicBorder.Material>
+				<ExperimentalAcrylicMaterial
+					BackgroundSource="Digger"
+					TintColor="Black"
+					TintOpacity="1"
+					MaterialOpacity="0.2" />
+			</ExperimentalAcrylicBorder.Material>
+		</ExperimentalAcrylicBorder>
+		<DockPanel>
+			<titlebars:TitleBarWindow
+			  IsSeamless="False"
+			  IsIconVisible="False"
+			  IsMaximizeVisible="False"
+			  TitleText="Versions"
+			  DockPanel.Dock="Top">
+			</titlebars:TitleBarWindow>
+			<StackPanel Margin="10" Spacing="5">
+				<ComboBox Items="{Binding FilteredVerions}" PlaceholderText="Select version" SelectedItem="{Binding FilteredVersion}" IsEnabled="{Binding IsControlsEnabled}" HorizontalAlignment="Stretch"></ComboBox>
+				<CheckBox IsChecked="{Binding ShowOld}" IsEnabled="{Binding IsControlsEnabled}">Show old</CheckBox>
+				<CheckBox IsChecked="{Binding ShowSnaps}" IsEnabled="{Binding IsControlsEnabled}">Show snapshots</CheckBox>
+				<CheckBox IsChecked="{Binding DownloadJava}" IsEnabled="{Binding IsControlsEnabled}">Download Java</CheckBox>
+				<CheckBox IsChecked="{Binding InstallFabric}" IsVisible="{Binding InstallFabricVisible}" IsEnabled="{Binding IsControlsEnabled}">Install Fabric</CheckBox>
+				<CheckBox IsChecked="{Binding InstallForge}" IsVisible="{Binding InstallForgeVisible}" IsEnabled="{Binding IsControlsEnabled}">Install Forge</CheckBox>
+				<CheckBox IsChecked="{Binding InstallForgeOptifine}" IsVisible="{Binding InstallForgeOptifineVisible}" IsEnabled="{Binding IsControlsEnabled}">Install Optifine (Mod)</CheckBox>
+				<CheckBox IsChecked="{Binding InstallOptifine}" IsVisible="{Binding InstallOptifineVisible}" IsEnabled="{Binding IsControlsEnabled}">Install Optifine (Vanilla)</CheckBox>
+				<Button Content="{Binding StartButton}" HorizontalAlignment="Center" Command="{Binding OnStartBunttonClick}" IsEnabled="{Binding IsControlsEnabled}"></Button>
+				<Button Content="Install McTFC Client" HorizontalAlignment="Center" Command="{Binding OnStartMcTfcBunttonClick}" IsEnabled="{Binding IsControlsEnabled}"></Button>
+				<Button Content="Install McTech Client" HorizontalAlignment="Center" Command="{Binding OnClickCommand}" IsEnabled="{Binding IsControlsEnabled}"></Button>
+				<ProgressBar Value="{Binding Progress}" ShowProgressText="true"></ProgressBar>
+				<TextBlock Text="{Binding DownloadingFileName}"></TextBlock>
+			</StackPanel>
+		</DockPanel>
+		</Panel>
 </Window>