diff --git a/.gitignore b/.gitignore
index 1e6cd00..62faaac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@
[Oo]ut/
[Ll]og/
[Ll]ogs/
+[Pp]ublish/
# IDE files
.vs/
diff --git a/dtlauncher.sln b/dtlauncher.sln
index 8e917a0..00f35c3 100644
--- a/dtlauncher.sln
+++ b/dtlauncher.sln
@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution
README.md = README.md
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "launcher-client-avalonia", "launcher-client-avalonia\launcher-client-avalonia.csproj", "{BC1FC2A0-159A-4F17-B076-B39775FB6AAC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +29,10 @@ Global
{A1F770F3-F6B1-4854-9BF0-093F85064B88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1F770F3-F6B1-4854-9BF0-093F85064B88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1F770F3-F6B1-4854-9BF0-093F85064B88}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BC1FC2A0-159A-4F17-B076-B39775FB6AAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BC1FC2A0-159A-4F17-B076-B39775FB6AAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BC1FC2A0-159A-4F17-B076-B39775FB6AAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BC1FC2A0-159A-4F17-B076-B39775FB6AAC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/launcher-client-avalonia/.run/launcher-client-avalonia-win64.run.xml b/launcher-client-avalonia/.run/launcher-client-avalonia-win64.run.xml
new file mode 100644
index 0000000..ed656ef
--- /dev/null
+++ b/launcher-client-avalonia/.run/launcher-client-avalonia-win64.run.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/launcher-client-avalonia/GUI/App.axaml b/launcher-client-avalonia/GUI/App.axaml
new file mode 100644
index 0000000..7aa35af
--- /dev/null
+++ b/launcher-client-avalonia/GUI/App.axaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/launcher-client-avalonia/GUI/App.axaml.cs b/launcher-client-avalonia/GUI/App.axaml.cs
new file mode 100644
index 0000000..0c89f07
--- /dev/null
+++ b/launcher-client-avalonia/GUI/App.axaml.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace launcher_client_avalonia
+{
+ public partial class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new MainWindow();
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+ }
+}
\ No newline at end of file
diff --git a/launcher-client-avalonia/GUI/LauncherWindow.xaml b/launcher-client-avalonia/GUI/LauncherWindow.xaml
new file mode 100644
index 0000000..f75359c
--- /dev/null
+++ b/launcher-client-avalonia/GUI/LauncherWindow.xaml
@@ -0,0 +1,223 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/launcher-client-avalonia/GUI/LauncherWindow.xaml.cs b/launcher-client-avalonia/GUI/LauncherWindow.xaml.cs
new file mode 100644
index 0000000..d48580b
--- /dev/null
+++ b/launcher-client-avalonia/GUI/LauncherWindow.xaml.cs
@@ -0,0 +1,98 @@
+namespace launcher_client_avalonia.GUI;
+
+public partial class LauncherWindow : Window
+{
+ public LauncherWindow()
+ {
+ try
+ {
+ AvaloniaXamlLoader.Load(this);
+ LogBox.Text = Logger.Buffer;
+ Logger.MessageSent += LogHandler;
+ LogfileLabel.Content = Logger.Logfile.Remove(0,Logger.Logfile.LastIndexOf(Путь.Разд)+1);
+ LogfileLabel.MouseLeftButtonDown += (s,e)=>
+ Process.Start("explorer.exe", Logger.Logfile.Remove(Logger.Logfile.LastIndexOf(Путь.Разд)));
+ LogfileLabel.MouseEnter += (s,e)=>LogfileLabel.Foreground=App.MySelectionColor;
+ LogfileLabel.MouseLeave += (s,e)=>LogfileLabel.Foreground=App.MyWhite;
+ LibraryButton.TabGrid = LibraryGrid;
+ DownloadsButton.TabGrid = DownloadsGrid;
+ LogButton.TabGrid = LogGrid;
+ SettingsButton.TabGrid = SettingsGrid;
+ LibraryButton.Click += SelectTab;
+ DownloadsButton.Click += SelectTab;
+ LogButton.Click += SelectTab;
+ SettingsButton.Click += SelectTab;
+ ProgramGrid.Visibility = Visibility.Hidden;
+ SelectTab(LibraryButton, null);
+ FillProgramsPanel();
+ Logger.Log("launcher started");
+ }
+ catch(Exception ex)
+ { LogError("LAUNCHER WINDOW INIT",ex); }
+ }
+
+ void LogHandler(string m) => Dispatcher.Invoke(()=>LogBox.Text += m);
+
+
+ private TabButton CurrentTab;
+ void SelectTab(object sender, RoutedEventArgs _)
+ {
+ if(CurrentTab!=null)
+ {
+ CurrentTab.Background = App.MyDark;
+ CurrentTab.TabGrid.Visibility = Visibility.Collapsed;
+ }
+ var selected = (TabButton)sender;
+ selected.Background = App.MySelectionColor;
+ selected.TabGrid.Visibility = Visibility.Visible;
+ CurrentTab = selected;
+ }
+
+ public Program[] Programs;
+
+ private void FillProgramsPanel()
+ {
+ Logger.Log("reading descriptors...");
+ string[] descriptors = Directory.GetFiles("descriptors");
+ Programs = new Program[descriptors.Length];
+ for (ushort i = 0; i < descriptors.Length; i++)
+ {
+ string descriptor = descriptors[i];
+ if(descriptor.EndsWith(".descriptor"))
+ {
+ Logger.Log('\t'+descriptor);
+ Programs[i] = new Program(descriptors[i]);
+ ProgramsPanel.Children.Add(Programs[i].ProgramLabel);
+ Programs[i].ProgramSelectedEvent += SelectProgram;
+ }
+ }
+ }
+
+ public Program DisplayingProgram;
+ public void SelectProgram(Program selectedProg)
+ {
+ try
+ {
+ if (DisplayingProgram != null)
+ {
+ DisplayingProgram.ProgramLabel.Foreground = App.MyWhite;
+ DisplayingProgram.ProgramLabel.FontWeight = FontWeights.Normal;
+ }
+ else ProgramGrid.Visibility = Visibility.Visible;
+
+ selectedProg.ProgramLabel.Foreground = App.MySelectionColor;
+ selectedProg.ProgramLabel.FontWeight = FontWeights.Bold;
+
+ NameLabel.Content = selectedProg.Name;
+ DescriptionBox.Text = selectedProg.Description;
+ BackgroundImage.Source =
+ new BitmapImage(new Uri(
+ $"{Directory.GetCurrent()}{Путь.Разд}backgrounds{Путь.Разд}{selectedProg.BackgroundFile}",
+ UriKind.Absolute));
+ ProgramSettingsViever.Content = selectedProg.SettingsPanel;
+ DisplayingProgram = selectedProg;
+ }
+ catch(Exception ex)
+ { LogError("SelectProgram()",ex); }
+ }
+}
\ No newline at end of file
diff --git a/launcher-client-avalonia/GUI/MessageBox.axaml b/launcher-client-avalonia/GUI/MessageBox.axaml
new file mode 100644
index 0000000..7308955
--- /dev/null
+++ b/launcher-client-avalonia/GUI/MessageBox.axaml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/launcher-client-avalonia/GUI/MessageBox.axaml.cs b/launcher-client-avalonia/GUI/MessageBox.axaml.cs
new file mode 100644
index 0000000..74510da
--- /dev/null
+++ b/launcher-client-avalonia/GUI/MessageBox.axaml.cs
@@ -0,0 +1,19 @@
+using Avalonia.Markup.Xaml;
+
+namespace launcher_client_avalonia.GUI;
+
+public partial class MessageBox : Window
+{
+ public MessageBox()
+ {
+ AvaloniaXamlLoader.Load(this);
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ public static void Show(string text)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/launcher-client-avalonia/GUI/ProgramLabel.xaml b/launcher-client-avalonia/GUI/ProgramLabel.xaml
new file mode 100644
index 0000000..ba33844
--- /dev/null
+++ b/launcher-client-avalonia/GUI/ProgramLabel.xaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/launcher-client-avalonia/GUI/ProgramLabel.xaml.cs b/launcher-client-avalonia/GUI/ProgramLabel.xaml.cs
new file mode 100644
index 0000000..3624c8c
--- /dev/null
+++ b/launcher-client-avalonia/GUI/ProgramLabel.xaml.cs
@@ -0,0 +1,13 @@
+namespace launcher_client_avalonia.GUI;
+
+public partial class ProgramLabel : UserControl
+{
+ public ProgramLabel(string label, string icon)
+ {
+ AvaloniaXamlLoader.Load(this);
+ NameLabel.Content = label;
+ IconImage.Source = new BitmapImage(new Uri(
+ $"{Directory.GetCurrent()}{Путь.Разд}icons{Путь.Разд}{icon}",
+ UriKind.Absolute));
+ }
+}
\ No newline at end of file
diff --git a/launcher-client-avalonia/GUI/ProgramSettingsPanelItem.xaml b/launcher-client-avalonia/GUI/ProgramSettingsPanelItem.xaml
new file mode 100644
index 0000000..edc6f1d
--- /dev/null
+++ b/launcher-client-avalonia/GUI/ProgramSettingsPanelItem.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/launcher-client-avalonia/GUI/ProgramSettingsPanelItem.xaml.cs b/launcher-client-avalonia/GUI/ProgramSettingsPanelItem.xaml.cs
new file mode 100644
index 0000000..a542d24
--- /dev/null
+++ b/launcher-client-avalonia/GUI/ProgramSettingsPanelItem.xaml.cs
@@ -0,0 +1,41 @@
+namespace launcher_client_avalonia.GUI;
+
+public partial class ProgramSettingsPanelItem : UserControl
+{
+ public static readonly StyledProperty SettingKeyProp =
+ AvaloniaProperty.Register(
+ "SettingKey");
+ public string SettingKey
+ {
+ get => (string)GetValue(SettingKeyProp);
+ set
+ {
+ SetValue(SettingKeyProp, value);
+
+ KeyLabel.ToolTip = new ToolTip
+ {
+ Content = value,
+ Foreground = App.MyWhite,
+ Background = App.MySoftDark
+ };
+ }
+ }
+
+ public static readonly StyledProperty SettingValueProp =
+ AvaloniaProperty.Register("SettingValue");
+ public string SettingValue
+ {
+ get => (string)GetValue(SettingValueProp);
+ set => SetValue(SettingValueProp, value);
+ }
+
+ public event Action UpdatedEvent;
+
+ public ProgramSettingsPanelItem(string key, string value)
+ {
+ AvaloniaXamlLoader.Load(this);
+ SettingKey = key;
+ SettingValue = value;
+ ValueBox.TextChanged += (_,_)=> UpdatedEvent?.Invoke(this);
+ }
+}
\ No newline at end of file
diff --git a/launcher-client-avalonia/GUI/TabButton.cs b/launcher-client-avalonia/GUI/TabButton.cs
new file mode 100644
index 0000000..c9c3f2f
--- /dev/null
+++ b/launcher-client-avalonia/GUI/TabButton.cs
@@ -0,0 +1,11 @@
+namespace launcher_client_avalonia.GUI;
+
+public class TabButton : Button
+{
+ public static readonly StyledProperty TabGridProp = AvaloniaProperty.Register("TabGrid");
+ public Grid TabGrid
+ {
+ get => (Grid)GetValue(TabGridProp);
+ set => SetValue(TabGridProp, value);
+ }
+}
\ No newline at end of file
diff --git a/launcher-client-avalonia/Launcher.cs b/launcher-client-avalonia/Launcher.cs
new file mode 100644
index 0000000..eff62ef
--- /dev/null
+++ b/launcher-client-avalonia/Launcher.cs
@@ -0,0 +1,80 @@
+global using System;
+global using System.Diagnostics;
+global using System.Net;
+global using System.Text;
+global using System.Collections.Generic;
+global using System.Threading;
+global using System.Linq;
+global using Avalonia;
+global using Avalonia.Controls;
+global using Avalonia.Media;
+global using DTLib;
+global using DTLib.Dtsod;
+global using DTLib.Filesystem;
+global using DTLib.Extensions;
+global using static launcher_client_avalonia.Launcher;
+using System.Reflection;
+using launcher_client_avalonia.GUI;
+
+namespace launcher_client_avalonia;
+
+public static class Launcher
+{
+ public static SolidColorBrush MyDark, MySoftDark, MyWhite, MyGreen, MyOrange, MySelectionColor;
+ public static LauncherConfig Config;
+ public static readonly LauncherLogger Logger = new();
+ public static LauncherWindow CurrentLauncherWindow;
+
+ public static void Main(string[] args)
+ {
+ try
+ {
+ var resources = Application.Current.Resources;
+ MyDark = (SolidColorBrush)resources["MyDarkTr"];
+ MySoftDark = (SolidColorBrush)resources["MyGray"];
+ MyWhite = (SolidColorBrush)resources["MyWhite"];
+ MyGreen = (SolidColorBrush)resources["MyGreen"];
+ MyOrange = (SolidColorBrush)resources["MySelectionColor"];
+ MySelectionColor = (SolidColorBrush)resources["MySelectionColor"];
+
+ Logger.Enable();
+
+ AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToTrace()
+ .StartWithClassicDesktopLifetime(args);
+
+ Config = new LauncherConfig();
+ Directory.Create("descriptors");
+ Directory.Create("icons");
+ Directory.Create("backgrounds");
+ Directory.Create("installed");
+ Directory.Create("settings");
+ File.WriteAllText($"descriptors{Путь.Разд}default.descriptor.template",
+ ReadResource("launcher_client_avalonia.Resources.default.descriptor.template"));
+ CurrentLauncherWindow = new LauncherWindow();
+ CurrentLauncherWindow.Show();
+
+ }
+ catch (Exception ex)
+ {
+ LogError("STARTUP", ex);
+ }
+ }
+
+ public static string ReadResource(string resource_path)
+ {
+ using var resourceStreamReader = new System.IO.StreamReader(
+ Assembly.GetExecutingAssembly().GetManifestResourceStream(resource_path)
+ ?? throw new Exception($"embedded resource <{resource_path}> not found"),
+ Encoding.UTF8);
+ return resourceStreamReader.ReadToEnd();
+ }
+
+ public static void LogError(string context, Exception ex)
+ {
+ string errmsg = $"{context} ERROR:\n{ex}";
+ MessageBox.Show(errmsg);
+ Logger.Log(errmsg);
+ }
+}
\ No newline at end of file
diff --git a/launcher-client-avalonia/LauncherConfig.cs b/launcher-client-avalonia/LauncherConfig.cs
new file mode 100644
index 0000000..df0c6fc
--- /dev/null
+++ b/launcher-client-avalonia/LauncherConfig.cs
@@ -0,0 +1,65 @@
+namespace launcher_client_avalonia;
+
+public class LauncherConfig
+{
+
+ public record struct Server(IPEndPoint EndPoint, string Domain)
+ {
+ public Server(string domain, int port) : this
+ (new IPEndPoint(Dns.GetHostAddresses(domain)[0], port), domain)
+ { }
+ public Server(IPAddress address, int port) : this
+ (new IPEndPoint(address, port), "")
+ { }
+ }
+
+ public const int Version=1;
+ public Server[] ServerAddresses;
+
+ const string configFile = "launcher.dtsod";
+ public LauncherConfig()
+ {
+ // читает дефолтный конфиг из ресурсов
+ DtsodV23 updatedConfig;
+ DtsodV23 updatedDefault = new(ReadResource("launcher_client_avalonia.Resources.launcher.dtsod"));
+ // проверка и обновление конфига
+ if (File.Exists(configFile))
+ {
+ DtsodV23 oldConfig = new(File.ReadAllText(configFile));
+ updatedConfig = DtsodConverter.UpdateByDefault(oldConfig, updatedDefault);
+ }
+ else updatedConfig = updatedDefault;
+
+ // парсит парсит полученный дтсод в LauncherConfig
+ List