diff --git a/MsBox.Avalonia/MsBox.Avalonia.csproj b/MsBox.Avalonia/MsBox.Avalonia.csproj
index 1ed5aa6..3af730f 100644
--- a/MsBox.Avalonia/MsBox.Avalonia.csproj
+++ b/MsBox.Avalonia/MsBox.Avalonia.csproj
@@ -26,9 +26,9 @@
-
+
-
+
diff --git a/ZXBSInstaller.Log/InstallerResources/zxbasic.png b/ZXBSInstaller.Log/InstallerResources/zxbasic.png
new file mode 100644
index 0000000..daec758
Binary files /dev/null and b/ZXBSInstaller.Log/InstallerResources/zxbasic.png differ
diff --git a/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.0.png b/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.0.png
new file mode 100644
index 0000000..daec758
Binary files /dev/null and b/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.0.png differ
diff --git a/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.1.png b/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.1.png
new file mode 100644
index 0000000..13080f9
Binary files /dev/null and b/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.1.png differ
diff --git a/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.2.png b/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.2.png
new file mode 100644
index 0000000..1f9e1e6
Binary files /dev/null and b/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.2.png differ
diff --git a/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.3.png b/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.3.png
new file mode 100644
index 0000000..6f06c09
Binary files /dev/null and b/ZXBSInstaller.Log/InstallerResources/zxbasic.scr.3.png differ
diff --git a/ZXBSInstaller.Log/InstallerResources/zxbs.png b/ZXBSInstaller.Log/InstallerResources/zxbs.png
new file mode 100644
index 0000000..3030aec
Binary files /dev/null and b/ZXBSInstaller.Log/InstallerResources/zxbs.png differ
diff --git a/ZXBSInstaller.Log/InstallerResources/zxbsinstaller.png b/ZXBSInstaller.Log/InstallerResources/zxbsinstaller.png
new file mode 100644
index 0000000..3429899
Binary files /dev/null and b/ZXBSInstaller.Log/InstallerResources/zxbsinstaller.png differ
diff --git a/ZXBSInstaller.Log/Neg/Config.cs b/ZXBSInstaller.Log/Neg/Config.cs
new file mode 100644
index 0000000..76c0087
--- /dev/null
+++ b/ZXBSInstaller.Log/Neg/Config.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ZXBSInstaller.Log.Neg
+{
+ ///
+ /// Config for ZXBSInstaller
+ ///
+ public class Config
+ {
+ ///
+ /// URL where to download the tools list
+ ///
+ public string ToolsListURL { get; set; }
+ ///
+ /// Base path installation for tools
+ ///
+ public string BasePath { get; set; }
+ ///
+ /// Show only stable versions (no beta)
+ ///
+ public bool OnlyStableVersions { get; set; }
+ ///
+ /// Setup ZXBS config when install/update apps
+ ///
+ public bool SetZXBSConfig { get; set; }
+ ///
+ /// Local paths for external tools
+ ///
+ public List ExternalTools_Paths { get; set; }
+ }
+}
diff --git a/ZXBSInstaller.Log/Neg/ExternalTool.cs b/ZXBSInstaller.Log/Neg/ExternalTool.cs
new file mode 100644
index 0000000..6232d35
--- /dev/null
+++ b/ZXBSInstaller.Log/Neg/ExternalTool.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace ZXBSInstaller.Log.Neg
+{
+ ///
+ /// External tool definition for installer and updater
+ ///
+ public class ExternalTool
+ {
+ ///
+ /// Internal unique identifier
+ ///
+ public string Id { get; set; }
+ ///
+ /// The tools is visible/enabled
+ ///
+ public bool Enabled { get; set; }
+ ///
+ /// Display name of the tool
+ ///
+ public string Name { get; set; }
+ ///
+ /// Author of the tool
+ ///
+ public string Author { get; set; }
+ ///
+ /// Description of the tool
+ ///
+ public string Description { get; set; }
+ ///
+ /// Operating systems supported by the tool
+ ///
+ public OperatingSystems[] SupportedOperatingSystems { get; set; }
+ ///
+ /// Site of the tool
+ ///
+ public string SiteUrl { get; set; }
+ ///
+ /// Licence type of the tool
+ ///
+ public string LicenseType { get; set; }
+ ///
+ /// Lucence URL of the tool
+ ///
+ public string LicenceUrl { get; set; }
+ ///
+ /// Url with the versions info
+ ///
+ public string VersionsUrl { get; set; }
+ ///
+ /// Local path where the tool will be installed without file name
+ ///
+ public string LocalPath { get; set; }
+ ///
+ /// Local path where the tool will be installed with file name
+ ///
+ public string FullLocalPath { get; set; }
+ ///
+ /// If this is true, the tool will be updated from ZXBSInstaller
+ ///
+ public bool DirectUpdate { get; set; }
+ ///
+ /// Order in the list
+ ///
+ public int Order { get; set; }
+ ///
+ /// Recommended
+ ///
+ public bool Recommended { get; set; }
+
+ ///
+ /// Versions of the tool
+ ///
+ [JsonIgnore]
+ public ExternalTools_Version[] Versions { get; set; }
+ ///
+ /// Version installed on local computer
+ ///
+ [JsonIgnore]
+ public ExternalTools_Version InstalledVersion { get; set; }
+ ///
+ /// Latest available version
+ ///
+ [JsonIgnore]
+ public ExternalTools_Version LatestVersion { get; set; }
+ ///
+ /// Need to update
+ ///
+ [JsonIgnore]
+ public bool UpdateNeeded { get; set; }
+
+ ///
+ /// Is selected for install
+ ///
+ [JsonIgnore]
+ public bool IsSelected { get; set; }
+
+ }
+}
diff --git a/ZXBSInstaller.Log/Neg/ExternalTools_Path.cs b/ZXBSInstaller.Log/Neg/ExternalTools_Path.cs
new file mode 100644
index 0000000..d168198
--- /dev/null
+++ b/ZXBSInstaller.Log/Neg/ExternalTools_Path.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ZXBSInstaller.Log.Neg
+{
+ ///
+ /// Defines the local path for external tools
+ ///
+ public class ExternalTools_Path
+ {
+ ///
+ /// External tool unique identifier
+ ///
+ public string Id { get; set; }
+ ///
+ /// Local path where the tool will be installed without file name
+ ///
+ public string LocalPath { get; set; }
+ }
+}
diff --git a/ZXBSInstaller.Log/Neg/ExternalTools_Version.cs b/ZXBSInstaller.Log/Neg/ExternalTools_Version.cs
new file mode 100644
index 0000000..8041254
--- /dev/null
+++ b/ZXBSInstaller.Log/Neg/ExternalTools_Version.cs
@@ -0,0 +1,29 @@
+namespace ZXBSInstaller.Log.Neg
+{
+ ///
+ /// Download data and version info for external tool
+ ///
+ public class ExternalTools_Version
+ {
+ ///
+ /// Display version for this version
+ ///
+ public string Version { get; set; }
+ ///
+ /// Numer of the Beta version, 0 if not a beta
+ ///
+ public int BetaNumber { get; set; }
+ ///
+ /// Internal version number to order versions
+ ///
+ public int VersionNumber { get; set; }
+ ///
+ /// Download url for this version
+ ///
+ public string DownloadUrl { get; set; }
+ ///
+ /// Operating system for this version
+ ///
+ public OperatingSystems OperatingSystem { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller.Log/Neg/OperatingSystems.cs b/ZXBSInstaller.Log/Neg/OperatingSystems.cs
new file mode 100644
index 0000000..0bb97e7
--- /dev/null
+++ b/ZXBSInstaller.Log/Neg/OperatingSystems.cs
@@ -0,0 +1,13 @@
+namespace ZXBSInstaller.Log.Neg
+{
+ ///
+ /// Operating System of the external tools
+ ///
+ public enum OperatingSystems
+ {
+ All = 0,
+ Windows = 1,
+ Linux = 2,
+ MacOS = 3
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller.Log/ServiceLayer.cs b/ZXBSInstaller.Log/ServiceLayer.cs
new file mode 100644
index 0000000..c7ad982
--- /dev/null
+++ b/ZXBSInstaller.Log/ServiceLayer.cs
@@ -0,0 +1,1150 @@
+using Microsoft.VisualBasic;
+using System;
+using System.Diagnostics;
+using System.Formats.Tar;
+using System.IO;
+using System.IO.Compression;
+using System.Reflection;
+using System.Reflection.Metadata.Ecma335;
+using System.Runtime;
+using System.Text;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using ZXBSInstaller.Log.Neg;
+using static System.Environment;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+
+namespace ZXBSInstaller.Log
+{
+ ///
+ /// ServiceLayer for installer and updater services
+ ///
+ public static class ServiceLayer
+ {
+ public static Config GeneralConfig = null;
+ public static ExternalTool[] ExternalTools = null;
+ public static OperatingSystems CurrentOperatingSystem = OperatingSystems.All;
+
+ private static Action ShowStatusPanel = null;
+ private static Action UpdateStatus = null;
+ private static Action HideStatusPanel = null;
+ private static Action RefreshTools = null;
+ private static Action ShowMessage = null;
+
+ #region Constructor and tools
+
+ ///
+ /// Initilize the service layer
+ ///
+ /// True if correct or false if error
+ public static bool Initialize(
+ Action callBackShowStatusPanel,
+ Action callBackUpdateStatus,
+ Action callBackHideStatusPanel,
+ Action callBackGetExternalTools,
+ Action callBackShowMessage)
+ {
+ ShowStatusPanel = callBackShowStatusPanel;
+ UpdateStatus = callBackUpdateStatus;
+ HideStatusPanel = callBackHideStatusPanel;
+ RefreshTools = callBackGetExternalTools;
+ ShowMessage = callBackShowMessage;
+
+ GetConfig();
+
+ if (OperatingSystem.IsWindows())
+ {
+ CurrentOperatingSystem = OperatingSystems.Windows;
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ CurrentOperatingSystem = OperatingSystems.Linux;
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ CurrentOperatingSystem = OperatingSystems.MacOS;
+ }
+
+ return true;
+ }
+
+
+ public static Config GetConfig()
+ {
+ try
+ {
+ var filePath = Path.Combine(Environment.GetFolderPath(SpecialFolder.ApplicationData), "ZXBasicStudio", "ZXBSInstallerOptions.json");
+ if (File.Exists(filePath))
+ {
+ var jsonString = File.ReadAllText(filePath);
+ var cfg = JsonSerializer.Deserialize(jsonString);
+
+ cfg.ToolsListURL = "https://zx.duefectucorp.com/zxbsinstaller.json";
+
+ GeneralConfig = cfg;
+ }
+ else
+ {
+ GeneralConfig = CreateConfig();
+ SaveConfig(GeneralConfig);
+ }
+ return GeneralConfig;
+
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ public static Config CreateConfig()
+ {
+ List toolsPaths = null;
+ var cfg = ServiceLayer.GeneralConfig;
+ if (cfg == null)
+ {
+ cfg = new Config()
+ {
+ BasePath = Directory.GetParent(Directory.GetCurrentDirectory()).FullName,
+ OnlyStableVersions = true,
+ SetZXBSConfig = true,
+ ToolsListURL = "https://zx.duefectucorp.com/zxbsinstaller.json"
+ };
+ }
+
+ if (cfg.ExternalTools_Paths == null)
+ {
+ cfg.ExternalTools_Paths = new List();
+ }
+ if (cfg.ExternalTools_Paths.Count == 0)
+ {
+ cfg.ExternalTools_Paths.Add(new ExternalTools_Path()
+ {
+ Id = "zxbsinstaller",
+ LocalPath = Directory.GetCurrentDirectory()
+ });
+ }
+ return cfg;
+ }
+
+
+ public static Config SaveConfig(Config config)
+ {
+ try
+ {
+ var dir = Path.Combine(Environment.GetFolderPath(SpecialFolder.ApplicationData), "ZXBasicStudio");
+ var fileName = Path.Combine(dir, "ZXBSInstallerOptions.json");
+ if (!Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+ var jsonString = JsonSerializer.Serialize(config, new JsonSerializerOptions() { WriteIndented = true });
+ File.WriteAllText(fileName, jsonString);
+ GeneralConfig = config;
+
+ return GetConfig();
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ ///
+ /// Convert an objet to his integer value or 0 if it can't do it
+ ///
+ /// Value to convert
+ /// Numeric value or 0 if can't do it
+ private static int ToInteger(object value)
+ {
+ try
+ {
+ if (value == null)
+ {
+ return 0;
+ }
+ int v = 0;
+ if (int.TryParse(value.ToString(), out v))
+ {
+ return v;
+ }
+ }
+ catch { }
+ return 0;
+ }
+
+
+ public static void OpenUrlInBrowser(string url)
+ {
+ try
+ {
+ switch (CurrentOperatingSystem)
+ {
+ case OperatingSystems.Windows:
+ Process.Start(new ProcessStartInfo(url)
+ {
+ UseShellExecute = true
+ });
+ break;
+ case OperatingSystems.Linux:
+ Process.Start("xdg-open", url);
+ break;
+ case OperatingSystems.MacOS:
+ Process.Start("open", url);
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+
+ #endregion
+
+
+ #region External tools list
+
+ ///
+ /// Retrieves all external tools configured for use with the application.
+ /// The data is stored in https://raw.githubusercontent.com/boriel-basic/ZXBasicStudio/refs/heads/master/externaltools.json
+ ///
+ /// An array of objects representing the available external tools. The array is empty
+ /// if no external tools are configured or can download the config file.
+ public static ExternalTool[] GetExternalTools()
+ {
+ UpdateStatus?.Invoke("Retrieving external tools information...", 5);
+
+ using var httpClient = new HttpClient();
+ string json = httpClient.GetStringAsync(GeneralConfig.ToolsListURL).GetAwaiter().GetResult();
+ var tools = JsonSerializer.Deserialize(json);
+
+ int max = tools.Length;
+ int prg = 10;
+ for (int n = 0; n < max; n++)
+ {
+ var tool = tools[n];
+ prg = (n * 90) / max;
+ UpdateStatus?.Invoke($"Retrieving versions for {tool.Name}...", prg + 10);
+
+ tool.Versions = GetAvailableToolVersion(tool);
+ if (tool.Versions == null)
+ {
+ tool.Versions = new ExternalTools_Version[0];
+ }
+ tool.InstalledVersion = GetToolVersion(tool.Id);
+
+ // Set latest version
+ if (GeneralConfig.OnlyStableVersions)
+ {
+ tool.LatestVersion = tool.Versions.
+ Where(d => d.OperatingSystem == CurrentOperatingSystem &&
+ d.BetaNumber == 0).
+ OrderByDescending(d => d.VersionNumber).
+ FirstOrDefault();
+ }
+ if (tool.LatestVersion == null || !GeneralConfig.OnlyStableVersions)
+ {
+ tool.LatestVersion = tool.Versions.
+ Where(d => d.OperatingSystem == CurrentOperatingSystem).
+ OrderByDescending(d => d.VersionNumber).
+ FirstOrDefault();
+ }
+
+ // Path for first versions of ZXBSInstalller
+ if (tool.Id == "zxbsinstaller" && tool.LatestVersion == null)
+ {
+ tool.LatestVersion = tool.InstalledVersion;
+ }
+
+ // Determine whether you need to update
+ if (tool.InstalledVersion == null)
+ {
+ tool.UpdateNeeded = true;
+ }
+ else
+ {
+ if (tool.LatestVersion != null)
+ {
+ if (tool.LatestVersion.VersionNumber > tool.InstalledVersion.VersionNumber)
+ {
+ tool.UpdateNeeded = true;
+ }
+ }
+ }
+
+ tool.LocalPath = Path.Combine(GeneralConfig.BasePath, tool.Id);
+ }
+
+ //GetPaths(ref tools);
+
+ ExternalTools = tools.OrderBy(d => d.Order).ToArray();
+
+ return ExternalTools;
+
+#if GENERATE_JSON
+ var lst = new List();
+ // Compiler
+ {
+ UpdateStatus?.Invoke(null, 10);
+ var tool = new ExternalTool()
+ {
+ Id = "zxbasic",
+ Enabled = true,
+ Name = "Boriel ZX Basic Compiler",
+ Author = "Boriel",
+ Description = "ZXBCompiler is a ZX Spectrum BASIC cross compiler that translates ZX Spectrum BASIC code into optimized machine code, enabling faster execution and enhanced performance on ZX Spectrum systems. This tool is required to run and debug programs.",
+ DirectUpdate = true,
+ SupportedOperatingSystems = new OperatingSystems[] { OperatingSystems.Windows, OperatingSystems.Linux, OperatingSystems.MacOS },
+ SiteUrl = "https://boriel-basic.net",
+ LicenceUrl = "https://raw.githubusercontent.com/boriel-basic/zxbasic/refs/heads/main/LICENSE.txt",
+ LicenseType = "GNU Affero General Public License v3.0",
+ VersionsUrl = "https://boriel.com/files/zxb/",
+ Order = 1
+ };
+ UpdateStatus?.Invoke(null, 15);
+ tool.Versions = GetBorielBasicVersions(tool.VersionsUrl);
+ lst.Add(tool);
+ UpdateStatus?.Invoke(null, 20);
+ }
+
+ // ZXBasic Studio IDE
+ {
+ UpdateStatus?.Invoke(null, 20);
+ var tool = new ExternalTool()
+ {
+ Id = "zxbs",
+ Enabled = true,
+ Name = "ZX Basic Studio",
+ Author = "Dr.Gusman, Boriel, Duefectu, AdolFITO, Hash6Iron and SirRickster",
+ Description = "IDE (Integrated Development Environment) with Boriel Basic code editor, Assembler, UDGs, fonts, sprites, .tap editor, debugger, emulator, etc. This tool is optional but highly recommended.",
+ DirectUpdate = true,
+ SupportedOperatingSystems = new OperatingSystems[] { OperatingSystems.Windows, OperatingSystems.Linux, OperatingSystems.MacOS },
+ SiteUrl = "https://github.com/boriel-basic/ZXBasicStudio",
+ LicenceUrl = "https://raw.githubusercontent.com/boriel-basic/ZXBasicStudio/refs/heads/master/LICENSE.txt",
+ LicenseType = "MIT License",
+ VersionsUrl = "https://github.com/boriel-basic/ZXBasicStudio/releases/",
+ Order=2
+ };
+ UpdateStatus?.Invoke(null, 25);
+ // Versions
+ var versions = new List();
+ versions.Add(new ExternalTools_Version()
+ {
+ Version = "1.6.0-beta5",
+ BetaNumber = 5,
+ VersionNumber = 1006005,
+ DownloadUrl = "https://github.com/boriel-basic/ZXBasicStudio/releases/download/v1.6/ZXBasicStudio-linux-x64.v1.6.0-beta5.zip",
+ OperatingSystem = OperatingSystems.Linux,
+ });
+ versions.Add(new ExternalTools_Version()
+ {
+ Version = "1.6.0-beta5",
+ BetaNumber = 5,
+ VersionNumber = 1006005,
+ DownloadUrl = "https://github.com/boriel-basic/ZXBasicStudio/releases/download/v1.6/ZXBasicStudio-osx-x64.v1.6.0-beta5.zip",
+ OperatingSystem = OperatingSystems.MacOS,
+ });
+ versions.Add(new ExternalTools_Version()
+ {
+ Version = "1.6.0-beta5",
+ BetaNumber = 5,
+ VersionNumber = 1006005,
+ DownloadUrl = "https://github.com/boriel-basic/ZXBasicStudio/releases/download/v1.6/ZXBasicStudio-win-x64.v1.6.0-beta5.zip",
+ OperatingSystem = OperatingSystems.Windows,
+ });
+ tool.Versions = versions.ToArray();
+ lst.Add(tool);
+ UpdateStatus?.Invoke(null, 30);
+ }
+ // Get tools paths from ZXBS config file
+ GetPaths(ref lst);
+
+
+ // ZXBasic Studio Installer
+ {
+ UpdateStatus?.Invoke(null, 30);
+ var tool = new ExternalTool()
+ {
+ Id = "zxbsinstaller",
+ Enabled = true,
+ Name = "ZX Basic Studio Installer",
+ Author = "Duefectu",
+ Description = "This program, and it is used to download, install and keep all external tools and ZX Basic Studio itself up to date.",
+ DirectUpdate = true,
+ SupportedOperatingSystems = new OperatingSystems[] { OperatingSystems.Windows, OperatingSystems.Linux, OperatingSystems.MacOS },
+ SiteUrl = "https://github.com/boriel-basic/ZXBasicStudio",
+ LicenceUrl = "https://raw.githubusercontent.com/boriel-basic/ZXBasicStudio/refs/heads/master/LICENSE.txt",
+ LicenseType = "MIT License",
+ VersionsUrl = "https://github.com/boriel-basic/ZXBasicStudio/releases/",
+ Order = 0
+ };
+ UpdateStatus?.Invoke(null, 35);
+ // Versions
+ var versions = new List();
+ versions.Add(new ExternalTools_Version()
+ {
+ Version = "0.0.1-beta1",
+ BetaNumber = 1,
+ VersionNumber = 1001,
+ DownloadUrl = "https://github.com/boriel-basic/ZXBasicStudio/releases/download/v1.6/ZXBasicStudio-linux-x64.v1.6.0-beta5.zip",
+ OperatingSystem = OperatingSystems.Linux,
+ });
+ versions.Add(new ExternalTools_Version()
+ {
+ Version = "0.0.1-beta1",
+ BetaNumber = 1,
+ VersionNumber = 1001,
+ DownloadUrl = "https://github.com/boriel-basic/ZXBasicStudio/releases/download/v1.6/ZXBasicStudio-osx-x64.v1.6.0-beta5.zip",
+ OperatingSystem = OperatingSystems.MacOS,
+ });
+ versions.Add(new ExternalTools_Version()
+ {
+ Version = "0.0.1-beta1",
+ BetaNumber = 1,
+ VersionNumber = 1001,
+ DownloadUrl = "https://github.com/boriel-basic/ZXBasicStudio/releases/download/v1.6/ZXBasicStudio-win-x64.v1.6.0-beta5.zip",
+ OperatingSystem = OperatingSystems.Windows,
+ });
+ tool.Versions = versions.ToArray();
+ lst.Add(tool);
+ UpdateStatus?.Invoke(null, 40);
+ }
+ // Get tools paths from ZXBS config file
+ GetPaths(ref lst);
+
+ externalTools = lst.OrderBy(d=>d.Order).ToArray();
+
+ var test=JsonSerializer.Serialize(externalTools);
+ File.WriteAllText(@"c:\temp\zxbsinstaller.json",test);
+
+ return externalTools;
+#endif
+ }
+
+
+
+ public static void GetPaths(ref ExternalTool[] tools)
+ {
+ var filePath = Path.Combine(Environment.GetFolderPath(SpecialFolder.ApplicationData), "ZXBasicStudio", "ZXBasicStudioOptions.json");
+ if (!File.Exists(filePath))
+ {
+ return;
+ }
+ var jsonString = File.ReadAllText(filePath);
+ using JsonDocument doc = JsonDocument.Parse(jsonString);
+ JsonElement root = doc.RootElement;
+
+ UpdatePath("zxbasic", "ZxbcPath", root, ref tools);
+ // ZX Basic Studio
+ {
+ var tool = tools.FirstOrDefault(t => t.Id == "zxbs");
+ if (tool != null)
+ {
+ tool.LocalPath = Directory.GetCurrentDirectory();
+ }
+ }
+ // ZX Basic Studio Installer
+ {
+ var tool = tools.FirstOrDefault(t => t.Id == "zxbsinstaller");
+ if (tool != null)
+ {
+ tool.LocalPath = Directory.GetCurrentDirectory();
+ }
+ }
+ }
+
+
+ private static void UpdatePath(string toolId, string property, JsonElement root, ref ExternalTool[] tools)
+ {
+ var tool = tools.FirstOrDefault(t => t.Id == toolId);
+ if (tool == null)
+ {
+ return;
+ }
+ if (root.TryGetProperty(property, out JsonElement element))
+ {
+ string value = element.GetString();
+ tool.FullLocalPath = value;
+ var fn = Path.GetFileName(value);
+ value = value.Replace(fn, "");
+ tool.LocalPath = value;
+ }
+ }
+
+
+ private static (int, int) GetVersionNumber(string versionString)
+ {
+ int number = 0;
+ int betaNumber = 0;
+ string version = versionString;
+ if (version.Contains("-beta"))
+ {
+ var mv = Regex.Match(version, @"beta(\d+)(?:[-\.]|$)", RegexOptions.IgnoreCase);
+ if (mv.Success)
+ {
+ version = version.Replace("-beta", ".");
+ }
+ }
+ else
+ {
+ version += ".0";
+ }
+
+ var versionParts = version.Split(".");
+ if (versionParts.Length == 5)
+ {
+ versionParts[3] += versionParts[4];
+ }
+ for (int n = 0; n < 4; n++)
+ {
+ number *= 1000;
+ if (n < versionParts.Length)
+ {
+ int v = ToInteger(versionParts[n]);
+ if (n == 3)
+ {
+ betaNumber = v;
+ if (betaNumber == 0)
+ {
+ number += 999;
+ }
+ else
+ {
+ number += betaNumber;
+ }
+ }
+ else
+ {
+ number += v;
+ }
+ }
+ }
+ return (number, betaNumber);
+ }
+
+ #endregion
+
+
+ #region External tools versions retrieval
+
+ private static ExternalTools_Version[] GetAvailableToolVersion(ExternalTool tool)
+ {
+ switch (tool.Id)
+ {
+ case "zxbasic":
+ return GetBorielBasicVersions(tool.VersionsUrl);
+
+ case "zxbs":
+ return GetBorielZXBSVersions(tool.VersionsUrl, false);
+
+ case "zxbsinstaller":
+ return GetBorielZXBSVersions(tool.VersionsUrl, true);
+
+ default:
+ return null;
+ }
+ }
+
+
+ ///
+ /// Get versions data for Boriel Basic Compiler
+ ///
+ ///
+ ///
+ private static ExternalTools_Version[] GetBorielBasicVersions(string versionsUrl)
+ {
+ try
+ {
+ var links = GetAllLinks(versionsUrl, @"]*href\s*=\s*[""']([^""']+)[""']");
+
+ // Parse links extracting versions data
+ var lst = new List();
+ Regex _regex = new Regex(
+ @"zxbasic-(?:v)?(?\d+\.\d+\.\d+(?:-beta\d+)?)",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase
+ );
+ foreach (var link in links)
+ {
+ // Search version, beta and platform
+ var match = _regex.Match(link);
+ if (!match.Success)
+ {
+ continue;
+ }
+ var version = new ExternalTools_Version();
+ // Download url
+ var uri = new Uri(versionsUrl);
+ version.DownloadUrl = $"https://{uri.Host}{link}";
+ // Display version
+ version.Version = match.Groups["version"].Value;
+ // Platform
+ if (link.Contains("win"))
+ {
+ version.OperatingSystem = OperatingSystems.Windows;
+ }
+ else if (link.Contains("linux"))
+ {
+ version.OperatingSystem = OperatingSystems.Linux;
+ }
+ else if (link.Contains("mac"))
+ {
+ version.OperatingSystem = OperatingSystems.MacOS;
+ }
+ else
+ {
+ version.OperatingSystem = OperatingSystems.All;
+ }
+ // Version number
+ var ver = GetVersionNumber(version.Version);
+ version.VersionNumber = ver.Item1;
+ // IsEstable
+ version.BetaNumber = ver.Item2;
+ // Add to list
+ lst.Add(version);
+ }
+ // Return the list ordered by version number
+ return lst.OrderByDescending(d => d.VersionNumber).ToArray();
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ ///
+ /// Get versions data for ZX Basic Studio Compiler
+ ///
+ ///
+ ///
+ private static ExternalTools_Version[] GetBorielZXBSVersions(string versionsUrl, bool installer)
+ {
+ try
+ {
+ // Get all hrefs
+ var links = GetAllLinks(versionsUrl, @"href=""([^""]+)""");
+ // Get only releases
+ links = links.Where(d => d.Contains("/boriel-basic/ZXBasicStudio/releases/tag/")).ToArray();
+
+ var versions = new List();
+ foreach (var link in links)
+ {
+ var url = link.Replace("/boriel-basic/ZXBasicStudio/releases/tag/", "");
+ url = $"https://github.com/boriel-basic/ZXBasicStudio/releases/expanded_assets/{url}";
+ var filesLinks = GetAllLinks(url, @"href=""([^""]+)""");
+ foreach (var fl in filesLinks)
+ {
+ if (fl.Contains("download"))
+ {
+ if (installer)
+ {
+ if (!fl.Contains("zxbsinstaller"))
+ {
+ continue;
+ }
+ }
+ else
+ {
+ if (fl.Contains("zxbsinstaller"))
+ {
+ continue;
+ }
+ }
+ var version = GetGitHubZXBSVersion(fl);
+ if (version != null)
+ {
+ versions.Add(version);
+ }
+ }
+ }
+ }
+ return versions.ToArray();
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ private static ExternalTools_Version GetGitHubZXBSVersion(string fileLink)
+ {
+ try
+ {
+ var version = new ExternalTools_Version();
+ version.DownloadUrl = $"https://github.com{fileLink}";
+
+ // Operating system
+ if (fileLink.Contains("win"))
+ {
+ version.OperatingSystem = OperatingSystems.Windows;
+ }
+ else if (fileLink.Contains("linux"))
+ {
+ version.OperatingSystem = OperatingSystems.Linux;
+ }
+ else if (fileLink.Contains("osx") || fileLink.Contains("mac"))
+ {
+ version.OperatingSystem = OperatingSystems.MacOS;
+ }
+ else
+ {
+ return null;
+ }
+
+ // Version
+ var match = Regex.Match(fileLink, @"v(\d+(?:\.\d+)*(?:-[\w\d.]+)?)", RegexOptions.IgnoreCase);
+ if (match.Success)
+ {
+ version.Version = match.Groups[1].Value;
+ }
+ if (!fileLink.Contains("zxbsinstaller"))
+ {
+ // Path old version numbers
+ switch (version.Version)
+ {
+ case "1.6":
+ version.Version = "1.6.0-beta5";
+ break;
+ case "1.5":
+ version.Version = "1.5.0-beta1";
+ break;
+ }
+ }
+ var v = GetVersionNumber(version.Version);
+ version.VersionNumber = v.Item1;
+ version.BetaNumber = v.Item2;
+ return version;
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ private static string[] GetAllLinks(string url, string pattern)
+ {
+ // Get html file
+ string html;
+ var handler = new HttpClientHandler
+ {
+ AllowAutoRedirect = true
+ };
+ using (HttpClient client = new HttpClient(handler))
+ {
+ html = client.GetStringAsync(url).GetAwaiter().GetResult();
+ }
+ if (string.IsNullOrEmpty(html))
+ {
+ return null;
+ }
+ //File.WriteAllText("c:/temp/html.text", html);
+ // Get links
+ var links = new List();
+ {
+ var regex = new Regex(
+ pattern,
+ RegexOptions.IgnoreCase);
+ var matches = regex.Matches(html);
+ foreach (Match match in matches)
+ {
+ links.Add(match.Groups[1].Value);
+ }
+ }
+
+ return links.ToArray();
+ }
+
+ #endregion
+
+
+ #region Local tools versions
+
+ public static ExternalTools_Version GetToolVersion(string id)
+ {
+ try
+ {
+ var dir = Path.Combine(GeneralConfig.BasePath, id);
+
+ switch (id)
+ {
+ case "zxbasic":
+ return GetBorielBasicVersion(dir);
+ case "zxbs":
+ return GetZXBSVersion(dir);
+ case "zxbsinstaller":
+ return GetZXBSInstallerVersion(dir);
+ }
+ return null;
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ private static ExternalTools_Version GetBorielBasicVersion(string exePath)
+ {
+ try
+ {
+ var fileName = Path.Combine(exePath, "zxbc.exe");
+ if (!File.Exists(fileName))
+ {
+ fileName = Path.Combine(exePath, "zxbc");
+ }
+ if (!File.Exists(fileName))
+ {
+ return null;
+ }
+ // Launch "zxbc.exe --version"
+ ProcessStartInfo psi = new ProcessStartInfo
+ {
+ FileName = fileName,
+ Arguments = "--version",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ using Process process = new Process { StartInfo = psi };
+ process.Start();
+ string output = process.StandardOutput.ReadToEnd();
+ string error = process.StandardError.ReadToEnd();
+ process.WaitForExit();
+
+ if (string.IsNullOrEmpty(output))
+ {
+ return null;
+ }
+ var version = output.Replace("zxbc.py ", "").Replace("\n", "").Replace("\r", "").Replace("v", "");
+ var v = GetVersionNumber(version);
+ int number = v.Item1;
+ int beta = v.Item2;
+
+ return new ExternalTools_Version()
+ {
+ DownloadUrl = "",
+ BetaNumber = beta,
+ OperatingSystem = OperatingSystems.All,
+ Version = version,
+ VersionNumber = number
+ };
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ private static ExternalTools_Version GetZXBSVersion(string exePath)
+ {
+ try
+ {
+ var fileName = Path.Combine(exePath, "ZXBasicStudio.exe");
+ if (!File.Exists(fileName))
+ {
+ fileName = Path.Combine(exePath, "ZXBasicStudio");
+ }
+ if (!File.Exists(fileName))
+ {
+ return null;
+ }
+
+ var fvi = FileVersionInfo.GetVersionInfo(fileName);
+ if (fvi != null)
+ {
+ // Major, minor, Build, private
+ var version = $"{fvi.ProductMajorPart}.{fvi.ProductMinorPart}.{fvi.ProductBuildPart}";
+ if (fvi.ProductPrivatePart > 0)
+ {
+ version += $"-beta{fvi.ProductPrivatePart}";
+ }
+ if (version == "1.0.0")
+ {
+ version = "1.6.0-beta6.3";
+ }
+ var v = GetVersionNumber(version);
+ var versionNumber = v.Item1;
+ var beta = v.Item2;
+ return new ExternalTools_Version()
+ {
+ DownloadUrl = "",
+ BetaNumber = beta,
+ OperatingSystem = OperatingSystems.All,
+ Version = version,
+ VersionNumber = versionNumber
+ };
+ }
+ return null;
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ public static ExternalTools_Version GetZXBSInstallerVersion(string exePath)
+ {
+ try
+ {
+ var assemblyVersion = Assembly.GetEntryAssembly()?.GetName().Version?.ToString();
+ var parts = assemblyVersion.Split('.');
+ if (parts.Length < 4)
+ {
+ return null;
+ }
+ ;
+ var version = $"{parts[0]}.{parts[1]}.{parts[2]}";
+ var beta = ToInteger(parts[3]);
+ if (beta > 0)
+ {
+ version += $"-beta{beta}";
+ }
+ var v = GetVersionNumber(version);
+ return new ExternalTools_Version()
+ {
+ DownloadUrl = "",
+ BetaNumber = v.Item2,
+ OperatingSystem = OperatingSystems.All,
+ Version = version,
+ VersionNumber = v.Item1
+ };
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+ #endregion
+
+
+ #region Install external tool
+
+ public static void DownloadAndInstallTools()
+ {
+ ShowStatusPanel($"Working...");
+ foreach (var tool in ExternalTools)
+ {
+ if (tool.IsSelected)
+ {
+ DownloadAndInstallTool(tool, tool.LatestVersion);
+ }
+ }
+ HideStatusPanel();
+ RefreshTools();
+ }
+
+
+ public static void DownloadAndInstallTool(ExternalTool tool, ExternalTools_Version version)
+ {
+ string step = "";
+ try
+ {
+ ShowStatusPanel($"Downloading {tool.Name} version {version.Version}...");
+
+ // Download path
+ step = "Creating download path";
+ var tempFile = Path.Combine(GeneralConfig.BasePath, "downloads");
+ if (!Directory.Exists(tempFile))
+ {
+ step = $"Creating download path [{tempFile}]";
+ Directory.CreateDirectory(tempFile);
+ }
+ var uri = new Uri(version.DownloadUrl);
+ tempFile = Path.Combine(tempFile, Path.GetFileName(uri.LocalPath));
+
+ // Get installation path
+ step = "Creating installation path";
+ var installationPath = Path.Combine(GeneralConfig.BasePath, tool.Id);
+ // Patch for Boriel Basic
+ if (tool.Id == "zxbasic")
+ {
+ installationPath = Directory.GetParent(installationPath).FullName;
+ }
+ if (!Directory.Exists(installationPath))
+ {
+ step = $"Creating application path [{installationPath}]";
+ Directory.CreateDirectory(installationPath);
+ }
+
+ // Download file
+ step = $"Downloading file {version.DownloadUrl}";
+ UpdateStatus($"Downloading {version.DownloadUrl}", 50);
+ using (var httpClient = new HttpClient())
+ {
+ using (var response = httpClient.GetAsync(version.DownloadUrl).GetAwaiter().GetResult())
+ {
+ response.EnsureSuccessStatusCode();
+ using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ response.Content.CopyToAsync(fs).GetAwaiter().GetResult();
+ }
+ }
+ }
+
+ // Extract file
+ step = $"Installing {tool.Name}";
+ UpdateStatus($"Installing {tool.Name} version {version.Version}...", 50);
+ ExtractFile(tempFile, installationPath);
+
+ // Set ZXBS Options
+ step = "Set ZX Basic Studio options";
+ UpdateStatus(null, 75);
+ SetZXBSConfig();
+
+ // Delete temp file
+ step = "Deleting temp files";
+ UpdateStatus("Deleting temp files...", 90);
+ File.Delete(tempFile);
+
+ UpdateStatus($"{tool.Name} version {version.Version} installed successfully.", 100);
+ }
+ catch (Exception ex)
+ {
+ HideStatusPanel();
+ ShowMessage($"Error installing {tool.Name}\r\n{step}\r\n{ex.Message}\r\n{ex.StackTrace}");
+ }
+ }
+
+
+ private static void ExtractFile(string archive, string destination)
+ {
+ if (archive.ToLower().EndsWith(".zip"))
+ {
+ System.IO.Compression.ZipFile.ExtractToDirectory(archive, destination, true);
+ }
+ else if (CurrentOperatingSystem != OperatingSystems.Windows)
+ {
+ Directory.CreateDirectory(destination);
+
+ var psi = new ProcessStartInfo
+ {
+ FileName = "tar",
+ Arguments = $"-xzf \"{archive}\" -C \"{destination}\"",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ using var process = Process.Start(psi)!;
+
+ string stdout = process.StandardOutput.ReadToEnd();
+ string stderr = process.StandardError.ReadToEnd();
+
+ process.WaitForExit();
+
+ if (process.ExitCode != 0)
+ {
+ ShowMessage($"Error unpacking file {archive}\r\n{stderr}");
+ return;
+ }
+ }
+ //if (archive.ToLower().EndsWith(".tar.gz"))
+ //{
+ // Directory.CreateDirectory(destination);
+
+ // using var fileStream = File.OpenRead(archive);
+ // using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
+ // using var tarReader = new TarReader(gzipStream);
+
+ // TarEntry? entry;
+ // while ((entry = tarReader.GetNextEntry()) != null)
+ // {
+ // string fullPath = Path.Combine(destination, entry.Name);
+
+ // if (entry.EntryType == TarEntryType.Directory)
+ // {
+ // Directory.CreateDirectory(fullPath);
+ // }
+ // else
+ // {
+ // Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
+ // if (entry.Length == 0)
+ // {
+ // File.WriteAllBytes(fullPath, new byte[0]);
+ // }
+ // else
+ // {
+ // entry.DataStream!.CopyTo(File.Create(fullPath));
+ // }
+ // }
+ // }
+ //}
+ }
+
+
+ private static void SetZXBSConfig()
+ {
+ try
+ {
+ var filePath = Path.Combine(Environment.GetFolderPath(SpecialFolder.ApplicationData), "ZXBasicStudio", "ZXBasicStudioOptions.json");
+ if (!File.Exists(filePath))
+ {
+ string data = @"{
+ ""ZxbasmPath"": """",
+ ""ZxbcPath"": """",
+ ""EditorFontSize"": 16.0,
+ ""WordWrap"": true,
+ ""AudioDisabled"": false,
+ ""Cls"": true,
+ ""Borderless"": false,
+ ""AntiAlias"": false,
+ ""LastProjectPath"": """",
+ ""DefaultBuildSettings"": null,
+ ""NextEmulatorPath"": """",
+ ""DisableAuto"": true
+}";
+ File.WriteAllText(filePath, data);
+ }
+
+ var sb = new StringBuilder();
+ var sr = new StreamReader(filePath);
+ while (!sr.EndOfStream)
+ {
+ var line = sr.ReadLine();
+ // Set values
+ if (line.Contains("ZxbasmPath"))
+ {
+ string exe = "zxbasm";
+ if (CurrentOperatingSystem == OperatingSystems.Windows)
+ {
+ exe += ".exe";
+ }
+ var dir = Path.Combine(GeneralConfig.BasePath, "zxbasic", exe).Replace("\\", "\\\\");
+ line = $" \"ZxbasmPath\": \"{dir}\",";
+ }
+ else if (line.Contains("ZxbcPath"))
+ {
+ string exe = "zxbc";
+ if (CurrentOperatingSystem == OperatingSystems.Windows)
+ {
+ exe += ".exe";
+ }
+ var dir = Path.Combine(GeneralConfig.BasePath, "zxbasic", exe).Replace("\\", "\\\\");
+ line = $" \"ZxbcPath\": \"{dir}\",";
+ }
+ sb.AppendLine(line);
+ }
+ sr.Close();
+ sr.Dispose();
+ File.WriteAllText(filePath, sb.ToString());
+ }
+ catch (Exception ex)
+ {
+ //ShowMessage($"Error updating ZX Basic Studio options.\r\n{ex.Message}\r\n{ex.StackTrace}");
+ }
+ }
+
+
+ #endregion
+
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller.Log/ZXBSInstaller.Log.csproj b/ZXBSInstaller.Log/ZXBSInstaller.Log.csproj
new file mode 100644
index 0000000..7cf15fe
--- /dev/null
+++ b/ZXBSInstaller.Log/ZXBSInstaller.Log.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/ZXBSInstaller/App.axaml b/ZXBSInstaller/App.axaml
new file mode 100644
index 0000000..2709927
--- /dev/null
+++ b/ZXBSInstaller/App.axaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZXBSInstaller/App.axaml.cs b/ZXBSInstaller/App.axaml.cs
new file mode 100644
index 0000000..022a205
--- /dev/null
+++ b/ZXBSInstaller/App.axaml.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace ZXBSInstaller
+{
+ 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/ZXBSInstaller/Controls/ConfigControl.axaml b/ZXBSInstaller/Controls/ConfigControl.axaml
new file mode 100644
index 0000000..f400dbf
--- /dev/null
+++ b/ZXBSInstaller/Controls/ConfigControl.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ZXBSInstaller/Controls/ConfigControl.axaml.cs b/ZXBSInstaller/Controls/ConfigControl.axaml.cs
new file mode 100644
index 0000000..ffb31eb
--- /dev/null
+++ b/ZXBSInstaller/Controls/ConfigControl.axaml.cs
@@ -0,0 +1,89 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using ZXBSInstaller.Log;
+using ZXBSInstaller.Log.Neg;
+
+namespace ZXBSInstaller.Controls;
+
+public partial class ConfigControl : UserControl
+{
+ private Action callBack = null;
+
+
+ public ConfigControl()
+ {
+ InitializeComponent();
+ }
+
+
+ public void Show(Action callBack)
+ {
+ this.callBack = callBack;
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ this.IsVisible = true;
+ var cfg = ServiceLayer.GetConfig();
+ if (cfg == null)
+ {
+ cfg = new Log.Neg.Config()
+ {
+ BasePath = "c:\\ZXSpectrum",
+ OnlyStableVersions = true
+ };
+ }
+
+ txtToolsBasePath.Text = cfg.BasePath;
+ chkOnlyStableVersions.IsChecked = cfg.OnlyStableVersions;
+ });
+ }
+
+
+ private void btnCancel_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ Close();
+ callBack?.Invoke(false);
+ }
+
+ private void btnSave_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ var cfg = ServiceLayer.CreateConfig();
+ cfg.BasePath = txtToolsBasePath.Text;
+ cfg.OnlyStableVersions = chkOnlyStableVersions.IsChecked ?? true;
+
+ ServiceLayer.SaveConfig(cfg);
+ Close();
+ callBack?.Invoke(true);
+ }
+
+
+ private void Close()
+ {
+ this.IsVisible = false;
+ }
+
+ private void btnBrowseToolsBasePath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ var dlg = new OpenFolderDialog()
+ {
+ Title = "Select tools base path"
+ };
+ var wnd = this.GetVisualRoot() as Window;
+ dlg.ShowAsync(wnd).ContinueWith((t) =>
+ {
+ if (!string.IsNullOrEmpty(t.Result))
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ txtToolsBasePath.Text = t.Result;
+ });
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/Controls/MainControl.axaml b/ZXBSInstaller/Controls/MainControl.axaml
new file mode 100644
index 0000000..2f2d5a9
--- /dev/null
+++ b/ZXBSInstaller/Controls/MainControl.axaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ZXBSInstaller/Controls/MainControl.axaml.cs b/ZXBSInstaller/Controls/MainControl.axaml.cs
new file mode 100644
index 0000000..3db3eff
--- /dev/null
+++ b/ZXBSInstaller/Controls/MainControl.axaml.cs
@@ -0,0 +1,214 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+using MsBox.Avalonia;
+using MsBox.Avalonia.Dto;
+using MsBox.Avalonia.Enums;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Threading;
+using ZXBSInstaller.Log;
+using ZXBSInstaller.Log.Neg;
+
+namespace ZXBSInstaller.Controls;
+
+public partial class MainControl : UserControl
+{
+ private ExternalTool[] tools = null;
+ private List toolsControls = null;
+
+ public MainControl()
+ {
+ InitializeComponent();
+
+ this.Loaded += MainControl_Loaded;
+ this.lstTools.SelectionChanged += LstTools_SelectionChanged;
+ }
+
+ private void MainControl_Loaded(object? sender, RoutedEventArgs e)
+ {
+ new Thread(Initialize).Start();
+ }
+
+
+ private void Initialize()
+ {
+ ServiceLayer.Initialize(ShowStatusPanel, UpdateStatus, HideStatusPanel, GetExternalTools, ShowMessage);
+ if (ServiceLayer.GeneralConfig == null)
+ {
+ ShowConfig();
+ }
+ else
+ {
+ GetExternalTools();
+ }
+ }
+
+
+ private void ShowMessage(string message)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ pnlStatus.IsVisible = false;
+ var box = MessageBoxManager.GetMessageBoxStandard(new MessageBoxStandardParams
+ {
+ ButtonDefinitions = ButtonEnum.Ok,
+ ContentTitle = "ZX Basic Studio Installer",
+ ContentMessage = message,
+ Icon = MsBox.Avalonia.Enums.Icon.Info,
+ WindowIcon = ((Window)this.VisualRoot).Icon,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner
+ });
+ box.ShowAsPopupAsync(this);
+ });
+ }
+
+
+ private void GetExternalTools()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ txtStatus.Text = "Working...";
+ progressBar.Value = 0;
+ pnlStatus.IsVisible = true;
+ });
+
+ tools = ServiceLayer.GetExternalTools();
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ pnlStatus.IsVisible = false;
+ if (tools == null)
+ {
+ var box = MessageBoxManager.GetMessageBoxStandard(new MessageBoxStandardParams
+ {
+ ButtonDefinitions = ButtonEnum.Ok,
+ ContentTitle = "ERROR",
+ ContentMessage = "Error retrieving the list of tools. Please check your internet connection.",
+ Icon = MsBox.Avalonia.Enums.Icon.Error,
+ WindowIcon = ((Window)this.VisualRoot).Icon,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner
+ });
+ box.ShowAsPopupAsync(this);
+ }
+ else
+ {
+ ShowData(tools);
+ }
+ });
+ }
+
+
+ private void ShowData(ExternalTool[] tools)
+ {
+ toolsControls = new List();
+
+ lstTools.Items.Clear();
+ foreach (var tool in tools)
+ {
+ var control = new ToolsListItemControl();
+ control.ExternalTool = tool;
+ control.Refresh();
+ toolsControls.Add(control);
+ lstTools.Items.Add(control);
+ }
+ lstTools.SelectedIndex = 0;
+ }
+
+
+ private void ShowStatusPanel(string message)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ txtStatus.Text = message;
+ progressBar.Value = 0;
+ pnlStatus.IsVisible = true;
+ });
+ }
+
+
+ private void HideStatusPanel()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ pnlStatus.IsVisible = false;
+ });
+ }
+
+
+ ///
+ /// Update status panel
+ ///
+ /// Message to display or empty if you don't want to change it
+ /// Progress value (0-100)
+ private void UpdateStatus(string message, int progress)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (!string.IsNullOrEmpty(message))
+ {
+ txtStatus.Text = message;
+ }
+ if (progress >= 0 && progress <= 100)
+ {
+ progressBar.Value = progress;
+ }
+ });
+ }
+
+
+ private void LstTools_SelectionChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (lstTools.SelectedItems == null || lstTools.SelectedItems.Count == 0)
+ {
+ return;
+ }
+
+ var ctrl = lstTools.SelectedItems[0] as ToolsListItemControl;
+ if (ctrl == null)
+ {
+ return;
+ }
+
+ var tool = new ToolItemControl();
+ tool.ExternalTool = ctrl.ExternalTool;
+ tool.LocalVersion = ctrl.LocalVersion;
+ tool.Refresh();
+
+ pnlWorking.Children.Clear();
+ pnlWorking.Children.Add(tool);
+ }
+
+ private void btnConfig_Click(object? sender, RoutedEventArgs e)
+ {
+ ShowConfig();
+ }
+
+
+ private void ShowConfig()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ pnlModalContainer.Children.Clear();
+ pnlModalOverlay.IsVisible = true;
+ var dlgConfig = new ConfigControl();
+ dlgConfig.Show(ShowConfig_Closed);
+ pnlModalContainer.Children.Add(dlgConfig);
+ });
+ }
+
+
+ private void ShowConfig_Closed(bool saved)
+ {
+ pnlModalOverlay.IsVisible = false;
+ pnlModalContainer.Children.Clear();
+
+ if (saved)
+ {
+ new Thread(GetExternalTools).Start();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/Controls/ScreenShotControl.axaml b/ZXBSInstaller/Controls/ScreenShotControl.axaml
new file mode 100644
index 0000000..de6cff8
--- /dev/null
+++ b/ZXBSInstaller/Controls/ScreenShotControl.axaml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/ZXBSInstaller/Controls/ScreenShotControl.axaml.cs b/ZXBSInstaller/Controls/ScreenShotControl.axaml.cs
new file mode 100644
index 0000000..4478314
--- /dev/null
+++ b/ZXBSInstaller/Controls/ScreenShotControl.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media.Imaging;
+using System.IO;
+
+namespace ZXBSInstaller.Controls;
+
+public partial class ScreenShotControl : UserControl
+{
+ public ScreenShotControl(string fileName)
+ {
+ InitializeComponent();
+
+ using var stream = File.OpenRead(fileName);
+ imgScreenShot.Source = new Bitmap(stream);
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/Controls/ToolItemControl.axaml b/ZXBSInstaller/Controls/ToolItemControl.axaml
new file mode 100644
index 0000000..e63336a
--- /dev/null
+++ b/ZXBSInstaller/Controls/ToolItemControl.axaml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ZXBSInstaller/Controls/ToolItemControl.axaml.cs b/ZXBSInstaller/Controls/ToolItemControl.axaml.cs
new file mode 100644
index 0000000..80c0452
--- /dev/null
+++ b/ZXBSInstaller/Controls/ToolItemControl.axaml.cs
@@ -0,0 +1,276 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using ZXBSInstaller.Log;
+using ZXBSInstaller.Log.Neg;
+
+namespace ZXBSInstaller.Controls;
+
+public partial class ToolItemControl : UserControl
+{
+ public ExternalTool ExternalTool { get; set; }
+ public ExternalTools_Version LocalVersion = null;
+
+ private ExternalTools_Version AvailableVersion = null;
+ private ExternalTools_Version LatestVersion = null;
+ private ExternalTools_Version LatestStableVersion = null;
+
+ public ToolItemControl()
+ {
+ InitializeComponent();
+ }
+
+
+ public void Refresh()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (ExternalTool == null)
+ {
+ return;
+ }
+
+ ShowImage($"InstallerResources/{ExternalTool.Id}.png", imgIcon);
+
+ txtName.Text = ExternalTool.Name;
+ txtDescription.Text = ExternalTool.Description;
+ txtAuthor.Text = $"Author: {ExternalTool.Author}";
+ txtLicense.Text = $"License: {ExternalTool.LicenseType}";
+
+ string localPath = "";
+ if (ServiceLayer.GeneralConfig.ExternalTools_Paths != null)
+ {
+ var tp = ServiceLayer.GeneralConfig.ExternalTools_Paths.FirstOrDefault(d => d.Id == ExternalTool.Id);
+ if (tp != null)
+ {
+ localPath = tp.LocalPath;
+ }
+ }
+ if (string.IsNullOrEmpty(localPath))
+ {
+ localPath = Path.Combine(ServiceLayer.GeneralConfig.BasePath, ExternalTool.Id);
+ ServiceLayer.GeneralConfig.ExternalTools_Paths.Add(
+ new ExternalTools_Path()
+ {
+ Id = ExternalTool.Id,
+ LocalPath = localPath
+ });
+ ServiceLayer.SaveConfig(ServiceLayer.GeneralConfig);
+ }
+ txtPath.Text = $"Path: {localPath}";
+
+ var versions = ExternalTool.Versions.OrderByDescending(d => d.VersionNumber);
+ LatestVersion = versions.FirstOrDefault(d => d.OperatingSystem == ServiceLayer.CurrentOperatingSystem);
+ LatestStableVersion = versions.FirstOrDefault(d => d.BetaNumber == 0 && d.OperatingSystem == ServiceLayer.CurrentOperatingSystem);
+ if (LatestVersion == null)
+ {
+ txtVersion.Text = "ERROR";
+ txtLatestVersion.Text = "ERROR";
+ txtLatestStableVersion.Text = "ERROR";
+ }
+ else
+ {
+ if (ServiceLayer.GeneralConfig.OnlyStableVersions)
+ {
+ txtVersion.Text = LatestStableVersion == null ?
+ $"Version: {LatestVersion.Version}" :
+ $"Version: {LatestStableVersion.Version}";
+ AvailableVersion = LatestStableVersion ?? LatestVersion;
+ }
+ else
+ {
+ AvailableVersion = LatestVersion ?? LatestStableVersion;
+ }
+
+ txtLatestVersion.Text = $"Latest available version: {LatestVersion.Version}";
+ txtLatestStableVersion.Text = LatestStableVersion == null ?
+ "Latest stable version: No stable versions found" :
+ $"Latest stable version: {LatestStableVersion.Version}";
+ }
+
+ ShowScreenShots();
+ ShowLocalVersion();
+ ShowVersions();
+
+ btnLicense.IsEnabled = !string.IsNullOrEmpty(ExternalTool.LicenceUrl);
+ });
+ }
+
+
+ private void ShowScreenShots()
+ {
+ try
+ {
+ var path = Path.Combine(Directory.GetCurrentDirectory(), "InstallerResources");
+ var sc = $"{ExternalTool.Id}.scr.*";
+ var files = Directory.GetFiles(path, sc).OrderBy(d => d);
+ foreach (var file in files)
+ {
+ var ctrl = new ScreenShotControl(file);
+ pnlScreenShhots.Children.Add(ctrl);
+ }
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+
+
+ private void ShowImage(string fileName, Image imgControl)
+ {
+ try
+ {
+ using var stream = File.OpenRead(fileName);
+ imgControl.Source = new Bitmap(stream);
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+
+
+ private void ShowLocalVersion()
+ {
+ if (LocalVersion == null)
+ {
+ LocalVersion = ServiceLayer.GetToolVersion(ExternalTool.Id);
+ }
+
+ txtChecking.IsVisible = false;
+ txtUpToDate.IsVisible = false;
+ txtRecomended.IsVisible = false;
+ btnUpdate.IsEnabled = false;
+
+ if (LocalVersion == null)
+ {
+ txtVersion.Text = "ERROR";
+ txtVersion.Foreground = new SolidColorBrush(Colors.Red);
+ txtLocalVersion.Text = "Local installed version: Not detected";
+ }
+ else
+ {
+ txtVersion.Text = LocalVersion.Version;
+ txtLocalVersion.Text = $"Local installed version: {LocalVersion.Version}";
+ }
+
+ if (LocalVersion == null)
+ {
+ txtLocalVersion.Foreground = new SolidColorBrush(Colors.Red);
+ txtRecomended.Text = "Download recomended";
+ txtRecomended.IsVisible = true;
+ btnUpdate.IsEnabled = true;
+ btnUpdate.Background = new SolidColorBrush(Colors.LightGreen);
+ btnUpdate.Foreground = new SolidColorBrush(Colors.Black);
+ }
+ else if (AvailableVersion == null)
+ {
+ txtLatestVersion.Foreground = new SolidColorBrush(Colors.Red);
+ txtLatestStableVersion.Foreground = new SolidColorBrush(Colors.Red);
+ txtRecomended.Text = "No download available";
+ txtRecomended.IsVisible = true;
+ }
+ else if (LocalVersion.VersionNumber >= AvailableVersion.VersionNumber)
+ {
+ txtLocalVersion.Foreground = new SolidColorBrush(Colors.LightGreen);
+ txtUpToDate.Text = $"Up to date";
+ txtUpToDate.IsVisible = true;
+ }
+ else
+ {
+ txtLocalVersion.Foreground = new SolidColorBrush(Colors.Red);
+ txtRecomended.Text = "Download recomended";
+ txtRecomended.IsVisible = true;
+ btnUpdate.IsEnabled = true;
+ btnUpdate.Background = new SolidColorBrush(Colors.LightGreen);
+ btnUpdate.Foreground = new SolidColorBrush(Colors.Black);
+ }
+ }
+
+
+ private void ShowVersions()
+ {
+ pnlVersions.Children.Clear();
+ var header = new VersionControl(null,null);
+ pnlVersions.Children.Add(header);
+ var versions = ExternalTool.Versions.OrderByDescending(d => d.VersionNumber).ThenBy(d => d.OperatingSystem);
+ foreach (var version in versions)
+ {
+ var ctrl = new VersionControl(ExternalTool, version);
+ pnlVersions.Children.Add(ctrl);
+ }
+ }
+
+
+ private void btnLicense_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ ServiceLayer.OpenUrlInBrowser(ExternalTool.LicenceUrl);
+ }
+
+
+ private void btnVisit_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ ServiceLayer.OpenUrlInBrowser(ExternalTool.SiteUrl);
+ }
+
+ private void btnUpdate_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ new Thread(() =>
+ {
+ ServiceLayer.DownloadAndInstallTool(ExternalTool, AvailableVersion);
+ }).Start();
+ }
+
+ private void btnViewVersions_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ pnlVersions.IsVisible = !pnlVersions.IsVisible;
+ if (pnlVersions.IsVisible)
+ {
+ btnViewVersions.Content = "Hide all versions";
+ }
+ else
+ {
+ btnViewVersions.Content = "Show all versions";
+ }
+ }
+
+ private void btnSetPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ var dlg = new OpenFolderDialog()
+ {
+ Title = "Select tools base path"
+ };
+ var wnd = this.GetVisualRoot() as Window;
+ dlg.ShowAsync(wnd).ContinueWith((t) =>
+ {
+ if (!string.IsNullOrEmpty(t.Result))
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ txtPath.Text = $"Path: {t.Result}";
+ var tool = ServiceLayer.GeneralConfig.ExternalTools_Paths.FirstOrDefault(d => d.Id == ExternalTool.Id);
+ if (tool != null)
+ {
+ tool.LocalPath = t.Result;
+ }
+ else
+ {
+ ServiceLayer.GeneralConfig.ExternalTools_Paths.Add(new ExternalTools_Path()
+ {
+ Id = ExternalTool.Id,
+ LocalPath = t.Result
+ });
+ ServiceLayer.SaveConfig(ServiceLayer.GeneralConfig);
+ }
+ });
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/Controls/ToolsListItemControl.axaml b/ZXBSInstaller/Controls/ToolsListItemControl.axaml
new file mode 100644
index 0000000..6b7703d
--- /dev/null
+++ b/ZXBSInstaller/Controls/ToolsListItemControl.axaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/ZXBSInstaller/Controls/ToolsListItemControl.axaml.cs b/ZXBSInstaller/Controls/ToolsListItemControl.axaml.cs
new file mode 100644
index 0000000..432b7fb
--- /dev/null
+++ b/ZXBSInstaller/Controls/ToolsListItemControl.axaml.cs
@@ -0,0 +1,107 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Threading;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using ZXBSInstaller.Log;
+using ZXBSInstaller.Log.Neg;
+
+namespace ZXBSInstaller;
+
+public partial class ToolsListItemControl : UserControl
+{
+ public ExternalTool ExternalTool { get; set; }
+ public ExternalTools_Version LocalVersion = null;
+
+ private ExternalTools_Version AvailableVersion = null;
+ private ExternalTools_Version LatestVersion = null;
+ private ExternalTools_Version LatestStableVersion = null;
+
+
+ public ToolsListItemControl()
+ {
+ InitializeComponent();
+ }
+
+
+ public void Refresh()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (ExternalTool == null)
+ {
+ return;
+ }
+
+ try
+ {
+ using var stream = File.OpenRead($"InstallerResources/{ExternalTool.Id}.png");
+ imgIcon.Source = new Bitmap(stream);
+ }
+ catch { }
+ txtName.Text = ExternalTool.Name;
+
+ var versions = ExternalTool.Versions.OrderByDescending(d => d.VersionNumber);
+ LatestVersion = versions.FirstOrDefault(d => d.OperatingSystem == ServiceLayer.CurrentOperatingSystem);
+ LatestStableVersion = versions.FirstOrDefault(d => d.BetaNumber == 0 && d.OperatingSystem == ServiceLayer.CurrentOperatingSystem);
+ if (ServiceLayer.GeneralConfig.OnlyStableVersions)
+ {
+ AvailableVersion = LatestStableVersion == null ?
+ LatestVersion :
+ LatestStableVersion;
+ }
+ else
+ {
+ AvailableVersion = LatestVersion;
+ }
+
+ if (AvailableVersion == null)
+ {
+ txtVersion.Text = "ERROR";
+ txtVersion.Foreground = new SolidColorBrush(Colors.Red);
+ return;
+ }
+ else
+ {
+ txtVersion.Text = AvailableVersion.Version;
+ txtVersion.Foreground = new SolidColorBrush(Colors.Yellow);
+ }
+ new Thread(GetLocalVersion).Start();
+ });
+ }
+
+
+ private void GetLocalVersion()
+ {
+ LocalVersion = ServiceLayer.GetToolVersion(ExternalTool.Id);
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (LocalVersion == null)
+ {
+ txtVersion.Text = $"v{AvailableVersion.Version} - Update";
+ txtVersion.Foreground = new SolidColorBrush(Avalonia.Media.Colors.Red);
+ }
+ else if (AvailableVersion == null)
+ {
+ txtVersion.Text = "ERROR";
+ txtVersion.Foreground = new SolidColorBrush(Avalonia.Media.Colors.Red);
+ }
+ else if (LocalVersion.VersionNumber >= LatestVersion.VersionNumber)
+ {
+ txtVersion.Text = $"v{LocalVersion.Version} - OK";
+ txtVersion.Foreground = new SolidColorBrush(Avalonia.Media.Colors.LightGreen);
+ }
+ else
+ {
+ txtVersion.Text = $"v{LocalVersion.Version} - Update";
+ txtVersion.Foreground = new SolidColorBrush(Avalonia.Media.Colors.Red);
+ }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/Controls/VersionControl.axaml b/ZXBSInstaller/Controls/VersionControl.axaml
new file mode 100644
index 0000000..2eb7283
--- /dev/null
+++ b/ZXBSInstaller/Controls/VersionControl.axaml
@@ -0,0 +1,22 @@
+
+
+
+ Version
+ Status
+ Platform
+ Download
+
+
+
+
+
+
+
+
+
+
diff --git a/ZXBSInstaller/Controls/VersionControl.axaml.cs b/ZXBSInstaller/Controls/VersionControl.axaml.cs
new file mode 100644
index 0000000..b3e068d
--- /dev/null
+++ b/ZXBSInstaller/Controls/VersionControl.axaml.cs
@@ -0,0 +1,85 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using System.Runtime.InteropServices;
+using System.Threading;
+using ZXBSInstaller.Log;
+using ZXBSInstaller.Log.Neg;
+
+namespace ZXBSInstaller.Controls;
+
+public partial class VersionControl : UserControl
+{
+ public ExternalTool Tool { get; set; }
+ public ExternalTools_Version ToolVersion = null;
+
+ private bool IsPair = false;
+
+ public static bool IsPairGlobal = false;
+ public static SolidColorBrush ColorPair= new SolidColorBrush(Color.FromRgb(20, 20, 20));
+ public static SolidColorBrush ColorNormal= new SolidColorBrush(Colors.Black);
+ public static SolidColorBrush ColorHover = new SolidColorBrush(Colors.DarkBlue);
+ public static SolidColorBrush ColorGreen = new SolidColorBrush(Colors.LightGreen);
+
+ public VersionControl(ExternalTool tool, ExternalTools_Version toolVersion)
+ {
+ InitializeComponent();
+
+ Tool= tool;
+ ToolVersion = toolVersion;
+ if (tool == null)
+ {
+ pnlHeader.IsVisible = true;
+ pnlRow.IsVisible = false;
+ IsPair = true;
+ }
+ else
+ {
+ pnlHeader.IsVisible = false;
+ pnlRow.IsVisible = true;
+ txtPlatform.Text = ToolVersion.OperatingSystem.ToString();
+ txtStatus.Text = ToolVersion.BetaNumber == 0 ? "Stable" : "Beta";
+ txtVersion.Text = ToolVersion.Version;
+ IsPair = IsPairGlobal;
+ if (IsPairGlobal)
+ {
+ pnlRow.Background = ColorPair;
+ }
+ IsPairGlobal = !IsPairGlobal;
+
+ if (toolVersion.OperatingSystem == ServiceLayer.CurrentOperatingSystem)
+ {
+ btnDownload.Foreground = ColorGreen;
+ txtPlatform.Foreground = ColorGreen;
+ txtStatus.Foreground = ColorGreen;
+ txtVersion.Foreground = ColorGreen;
+ }
+ }
+ }
+
+ private void btnDownload_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ new Thread(() =>
+ {
+ ServiceLayer.DownloadAndInstallTool(Tool, ToolVersion);
+ }).Start();
+ }
+
+ private void pnlRow_PointerEntered(object? sender, Avalonia.Input.PointerEventArgs e)
+ {
+ pnlRow.Background = ColorHover;
+ }
+
+ private void pnlRow_PointerExited(object? sender, Avalonia.Input.PointerEventArgs e)
+ {
+ if (IsPair)
+ {
+ pnlRow.Background = ColorPair;
+ }
+ else
+ {
+ pnlRow.Background = ColorNormal;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/Controls2/MainControl.axaml b/ZXBSInstaller/Controls2/MainControl.axaml
new file mode 100644
index 0000000..24b7057
--- /dev/null
+++ b/ZXBSInstaller/Controls2/MainControl.axaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ZXBSInstaller/Controls2/MainControl.axaml.cs b/ZXBSInstaller/Controls2/MainControl.axaml.cs
new file mode 100644
index 0000000..a3b64ff
--- /dev/null
+++ b/ZXBSInstaller/Controls2/MainControl.axaml.cs
@@ -0,0 +1,246 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+using MsBox.Avalonia;
+using MsBox.Avalonia.Dto;
+using MsBox.Avalonia.Enums;
+using System.Collections.Generic;
+using System.Data;
+using System.Security.Cryptography;
+using System.Threading;
+using ZXBSInstaller.Controls;
+using ZXBSInstaller.Log;
+using ZXBSInstaller.Log.Neg;
+
+namespace ZXBSInstaller.Controls2;
+
+public partial class MainControl : UserControl
+{
+ private List toolItemControls = new List();
+
+
+ public MainControl()
+ {
+ InitializeComponent();
+
+ this.Loaded += MainControl_Loaded;
+ txtBasePath.TextChanged += TxtBasePath_TextChanged;
+ chkOnlyStableVersions.IsCheckedChanged += ChkOnlyStableVersions_IsCheckedChanged;
+ chkSetZXBSOptions.IsCheckedChanged += ChkSetZXBSOptions_IsCheckedChanged;
+ }
+
+ private void MainControl_Loaded(object? sender, RoutedEventArgs e)
+ {
+ new Thread(Initialize).Start();
+ }
+
+
+ private void Initialize()
+ {
+ ServiceLayer.Initialize(ShowStatusPanel, UpdateStatus, HideStatusPanel, GetExternalTools, ShowMessage);
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ txtBasePath.Text = ServiceLayer.GeneralConfig.BasePath;
+ chkOnlyStableVersions.IsChecked = ServiceLayer.GeneralConfig.OnlyStableVersions;
+ chkSetZXBSOptions.IsChecked = ServiceLayer.GeneralConfig.SetZXBSConfig;
+ });
+
+ GetExternalTools();
+ }
+
+
+ private void ShowMessage(string message)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ pnlStatus.IsVisible = false;
+ var box = MessageBoxManager.GetMessageBoxStandard(new MessageBoxStandardParams
+ {
+ ButtonDefinitions = ButtonEnum.Ok,
+ ContentTitle = "ZX Basic Studio Installer",
+ ContentMessage = message,
+ Icon = MsBox.Avalonia.Enums.Icon.Info,
+ WindowIcon = ((Window)this.VisualRoot).Icon,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner
+ });
+ box.ShowAsPopupAsync(this);
+ });
+ }
+
+
+ private void GetExternalTools()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ txtStatus.Text = "Working...";
+ progressBar.Value = 0;
+ pnlStatus.IsVisible = true;
+ });
+
+ var tools = ServiceLayer.GetExternalTools();
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ pnlStatus.IsVisible = false;
+ if (tools == null)
+ {
+ var box = MessageBoxManager.GetMessageBoxStandard(new MessageBoxStandardParams
+ {
+ ButtonDefinitions = ButtonEnum.Ok,
+ ContentTitle = "ERROR",
+ ContentMessage = "Error retrieving the list of tools. Please check your internet connection.",
+ Icon = MsBox.Avalonia.Enums.Icon.Error,
+ WindowIcon = ((Window)this.VisualRoot).Icon,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner
+ });
+ box.ShowAsPopupAsync(this);
+ }
+ else
+ {
+ ShowData();
+ }
+ });
+ }
+
+
+ private void ShowData()
+ {
+ toolItemControls.Clear();
+ var tools = ServiceLayer.ExternalTools;
+
+ pnlTools.Children.Clear();
+ foreach (var tool in tools)
+ {
+ var control = new ToolItemControl(tool, UpdateSummary);
+ toolItemControls.Add(control);
+ pnlTools.Children.Add(control);
+ }
+ UpdateSummary();
+ }
+
+
+ private void UpdateSummary()
+ {
+ pnlSummary.Children.Clear();
+ bool allUpToDate = true;
+ foreach (var tool in toolItemControls)
+ {
+ if (tool.IsSelected)
+ {
+ var tb = new TextBlock();
+ tb.TextWrapping = Avalonia.Media.TextWrapping.Wrap;
+ if (tool.ExternalTool.InstalledVersion == null)
+ {
+ tb.Text = $"- Install:\r\n\t{tool.ExternalTool.Name}\r\n\tPath: {tool.ExternalTool.LocalPath}\r\n";
+ }
+ else
+ {
+ tb.Text = $"- Update:\r\n\t{tool.ExternalTool.Name}\r\n\tPath: {tool.ExternalTool.LocalPath}\r\n";
+ }
+ pnlSummary.Children.Add(tb);
+ allUpToDate = false;
+ }
+ }
+ if (allUpToDate)
+ {
+ var tb = new TextBlock();
+ tb.TextWrapping = Avalonia.Media.TextWrapping.Wrap;
+ tb.Text = "All tools are up to date.";
+ pnlSummary.Children.Add(tb);
+ }
+ }
+
+
+ private void ShowStatusPanel(string message)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ txtStatus.Text = message;
+ progressBar.Value = 0;
+ pnlStatus.IsVisible = true;
+ });
+ }
+
+
+ private void HideStatusPanel()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ pnlStatus.IsVisible = false;
+ });
+ }
+
+
+ ///
+ /// Update status panel
+ ///
+ /// Message to display or empty if you don't want to change it
+ /// Progress value (0-100)
+ private void UpdateStatus(string message, int progress)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (!string.IsNullOrEmpty(message))
+ {
+ txtStatus.Text = message;
+ }
+ if (progress >= 0 && progress <= 100)
+ {
+ progressBar.Value = progress;
+ }
+ });
+ }
+
+ private void btnSelectPath_Click(object? sender, RoutedEventArgs e)
+ {
+ var dlg = new OpenFolderDialog()
+ {
+ Title = "Select tools base path"
+ };
+ dlg.Directory = txtBasePath.Text;
+ var wnd = this.GetVisualRoot() as Window;
+ dlg.ShowAsync(wnd).ContinueWith((t) =>
+ {
+ if (!string.IsNullOrEmpty(t.Result))
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ txtBasePath.Text = t.Result;
+ });
+ }
+ });
+ }
+
+
+ private void btnInstall_Click(object? sender, RoutedEventArgs e)
+ {
+ new Thread(ServiceLayer.DownloadAndInstallTools).Start();
+ }
+
+
+ private void ChkSetZXBSOptions_IsCheckedChanged(object? sender, RoutedEventArgs e)
+ {
+ ServiceLayer.GeneralConfig.SetZXBSConfig = chkSetZXBSOptions.IsChecked == true;
+ ServiceLayer.SaveConfig(ServiceLayer.GeneralConfig);
+ }
+
+
+ private void ChkOnlyStableVersions_IsCheckedChanged(object? sender, RoutedEventArgs e)
+ {
+ ServiceLayer.GeneralConfig.OnlyStableVersions = chkOnlyStableVersions.IsChecked == true;
+ ServiceLayer.SaveConfig(ServiceLayer.GeneralConfig);
+ }
+
+
+ private void TxtBasePath_TextChanged(object? sender, TextChangedEventArgs e)
+ {
+ ServiceLayer.GeneralConfig.BasePath = txtBasePath.Text;
+ ServiceLayer.SaveConfig(ServiceLayer.GeneralConfig);
+ }
+
+
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/Controls2/ToolItemControl.axaml b/ZXBSInstaller/Controls2/ToolItemControl.axaml
new file mode 100644
index 0000000..890fb67
--- /dev/null
+++ b/ZXBSInstaller/Controls2/ToolItemControl.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+ Name
+ Description
+
+
+
+ Path
+ Installed version:
+ Latest version:
+
+
+
+
+
+
diff --git a/ZXBSInstaller/Controls2/ToolItemControl.axaml.cs b/ZXBSInstaller/Controls2/ToolItemControl.axaml.cs
new file mode 100644
index 0000000..ffa34f3
--- /dev/null
+++ b/ZXBSInstaller/Controls2/ToolItemControl.axaml.cs
@@ -0,0 +1,71 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using System;
+using ZXBSInstaller.Log.Neg;
+
+namespace ZXBSInstaller.Controls2;
+
+public partial class ToolItemControl : UserControl
+{
+ public ExternalTool ExternalTool = null;
+ public bool IsSelected = false;
+
+ public static SolidColorBrush colorRed = new SolidColorBrush(Colors.Red);
+ public static SolidColorBrush colorGreen = new SolidColorBrush(Colors.LightGreen);
+
+ private static Action Checked_Changed = null;
+
+
+ public ToolItemControl(ExternalTool tool, Action callBackChecked_Changed)
+ {
+ InitializeComponent();
+
+ ExternalTool = tool;
+ Checked_Changed = callBackChecked_Changed;
+
+ UITools.ShowImage($"InstallerResources/{ExternalTool.Id}.png", imgIcon);
+ txtName.Text = tool.Name;
+ txtDescription.Text = tool.Description;
+ txtPath.Text = "Path: " + tool.LocalPath;
+
+ IsSelected = tool.UpdateNeeded;
+ tool.IsSelected = IsSelected;
+ chkSelect.IsChecked = IsSelected;
+
+ if (tool.InstalledVersion == null)
+ {
+ txtActual.Text = $"Installed version: None";
+ txtActual.Foreground = colorRed;
+ }
+ else
+ {
+ txtActual.Text = $"Installed version: {tool.InstalledVersion.Version}";
+ if (tool.UpdateNeeded)
+ {
+ txtActual.Foreground = colorRed;
+ }
+ else
+ {
+ txtActual.Foreground = colorGreen;
+ }
+ }
+
+ if (tool.LatestVersion == null)
+ {
+ txtLatest.Text = $"Latest version: Unknow";
+ }
+ else
+ {
+ txtLatest.Text = $"Latest version: {tool.LatestVersion.Version}";
+ }
+ }
+
+ private void chkSelect_IsCheckedChanged(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ IsSelected = chkSelect.IsChecked == true;
+ ExternalTool.IsSelected = IsSelected;
+ Checked_Changed?.Invoke();
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/MainWindow.axaml b/ZXBSInstaller/MainWindow.axaml
new file mode 100644
index 0000000..2f9fd7e
--- /dev/null
+++ b/ZXBSInstaller/MainWindow.axaml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/ZXBSInstaller/MainWindow.axaml.cs b/ZXBSInstaller/MainWindow.axaml.cs
new file mode 100644
index 0000000..dd491a1
--- /dev/null
+++ b/ZXBSInstaller/MainWindow.axaml.cs
@@ -0,0 +1,46 @@
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using MsBox.Avalonia;
+using MsBox.Avalonia.Dto;
+using MsBox.Avalonia.Enums;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+using System.Threading;
+using ZXBSInstaller.Log;
+using ZXBSInstaller.Log.Neg;
+
+namespace ZXBSInstaller
+{
+
+ public partial class MainWindow : Window
+ {
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ var version = ServiceLayer.GetZXBSInstallerVersion("zxbsinstaller");
+ if (version != null)
+ {
+ Title = $"ZX Basic Studio Installer - v{version.Version}";
+ }
+
+ var ctrl = new Controls2.MainControl();
+ pnlMain.Children.Add(ctrl);
+ }
+
+
+ private void btnExit_Click(object? sender, RoutedEventArgs e)
+ {
+ if (App.Current?.ApplicationLifetime
+ is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.Shutdown();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ZXBSInstaller/Program.cs b/ZXBSInstaller/Program.cs
new file mode 100644
index 0000000..16f6385
--- /dev/null
+++ b/ZXBSInstaller/Program.cs
@@ -0,0 +1,22 @@
+using Avalonia;
+using System;
+
+namespace ZXBSInstaller
+{
+ 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.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace();
+ }
+}
diff --git a/ZXBSInstaller/UITools.cs b/ZXBSInstaller/UITools.cs
new file mode 100644
index 0000000..3b629c0
--- /dev/null
+++ b/ZXBSInstaller/UITools.cs
@@ -0,0 +1,26 @@
+using Avalonia.Controls;
+using Avalonia.Media.Imaging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ZXBSInstaller
+{
+ internal static class UITools
+ {
+ public static void ShowImage(string fileName, Image imgControl)
+ {
+ try
+ {
+ using var stream = File.OpenRead(fileName);
+ imgControl.Source = new Bitmap(stream);
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }
+}
diff --git a/ZXBSInstaller/ZXBSInstaller.csproj b/ZXBSInstaller/ZXBSInstaller.csproj
new file mode 100644
index 0000000..dee1fa9
--- /dev/null
+++ b/ZXBSInstaller/ZXBSInstaller.csproj
@@ -0,0 +1,46 @@
+
+
+ WinExe
+ net8.0
+ enable
+ app.manifest
+ true
+ zxbs.ico
+ 0.0.1.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ All
+
+
+
+
+
+
+
+
+
+
+ ScreenShotControl.axaml
+
+
+ ToolsListItemControl.axaml
+
+
+ VersionControl.axaml
+
+
+
diff --git a/ZXBSInstaller/app.manifest b/ZXBSInstaller/app.manifest
new file mode 100644
index 0000000..5083e11
--- /dev/null
+++ b/ZXBSInstaller/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ZXBSInstaller/zxbs.ico b/ZXBSInstaller/zxbs.ico
new file mode 100644
index 0000000..c9dcbb7
Binary files /dev/null and b/ZXBSInstaller/zxbs.ico differ
diff --git a/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs b/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs
index 6aba0e8..b7a2b07 100644
--- a/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs
+++ b/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs
@@ -3,6 +3,8 @@
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using System;
+using System.IO;
+using System.Reflection;
namespace ZXBasicStudio;
@@ -16,20 +18,31 @@ public ZXAboutDialog()
txtDate.Text = Program.VersionDate;
btnClose.Click += BtnClose_Click;
+ }
+
+
+ private DateTime GetBuildDate()
+ {
+ string assemblyPath = Assembly.GetExecutingAssembly().Location;
+
+ const int peHeaderOffset = 60;
+ const int linkerTimestampOffset = 8;
- //var name = System.Reflection.Assembly.GetExecutingAssembly().GetName();
+ byte[] buffer = new byte[2048];
- //if(name == null || name.Version == null)
- //{
- // txtBuild.Text = "Unknown build";
- // txtDate.Text = "Unknown date";
- // return;
- //}
+ using (FileStream fs = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read))
+ {
+ fs.Read(buffer, 0, buffer.Length);
+ }
- //DateTime buildDate = new DateTime(2000, 1, 1).AddDays(name.Version.Revision);
- //txtBuild.Text = $"Build {name.Version.ToString()}";
+ int offset = BitConverter.ToInt32(buffer, peHeaderOffset);
+ int secondsSince1970 = BitConverter.ToInt32(buffer, offset + linkerTimestampOffset);
+ DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ DateTime linkTimeUtc = epoch.AddSeconds(secondsSince1970);
+ return linkTimeUtc.ToLocalTime();
}
+
private void BtnClose_Click(object? sender, RoutedEventArgs e)
{
this.Close();
diff --git a/ZXBStudio/Program.cs b/ZXBStudio/Program.cs
index 2d85834..1242805 100644
--- a/ZXBStudio/Program.cs
+++ b/ZXBStudio/Program.cs
@@ -8,8 +8,8 @@ namespace ZXBasicStudio
{
internal class Program
{
- public static string Version = "1.6.0 - beta 6.3";
- public static string VersionDate = "2025.11.16";
+ public static string Version = "";
+ public static string VersionDate = "";
// Initialization code. Don't use any Avalonia, third-party APIs or any
@@ -24,10 +24,35 @@ public static void Main(string[] args)
TypeNameHandling = TypeNameHandling.Auto,
};
+ SetVerion();
+
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
+
+ public static void SetVerion()
+ {
+ try
+ {
+ var assembly = System.Reflection.Assembly.GetEntryAssembly();
+ var version = assembly.GetName().Version;
+ Version = $"{version.Major}.{version.Minor}.{version.Build}";
+ if (version.Revision != 0)
+ {
+ Version = $"{Version} - beta {version.Revision}";
+ }
+ var buildDate = System.IO.File.GetLastWriteTime(assembly.Location);
+ VersionDate = buildDate.ToString("yyyy.MM.dd");
+ }
+ catch
+ {
+ Version = "Unknown version";
+ VersionDate = "Unknown date";
+ }
+ }
+
+
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
diff --git a/ZXBStudio/ZXBasicStudio.csproj b/ZXBStudio/ZXBasicStudio.csproj
index e58b4f9..1443fa1 100644
--- a/ZXBStudio/ZXBasicStudio.csproj
+++ b/ZXBStudio/ZXBasicStudio.csproj
@@ -11,9 +11,9 @@
AnyCPU;x64
zxbs.ico
ZX Basic Studio
- 1.6.0.*
+
False
-
+ 1.6.0.5
@@ -563,17 +563,17 @@
-
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/ZXBasicStudio.sln b/ZXBasicStudio.sln
index 1a4d6ea..7caeed8 100644
--- a/ZXBasicStudio.sln
+++ b/ZXBasicStudio.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.32014.148
+# Visual Studio Version 18
+VisualStudioVersion = 18.2.11408.102
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreSpectrum", "CoreSpectrum\CoreSpectrum.csproj", "{F65553BC-B86F-4125-B5BA-EBBCC223100B}"
EndProject
@@ -34,6 +34,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{862E
.github\workflows\build.yaml = .github\workflows\build.yaml
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXBSInstaller", "ZXBSInstaller\ZXBSInstaller.csproj", "{67768F7F-169A-420F-9C50-335FB9885EC1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXBSInstaller.Log", "ZXBSInstaller.Log\ZXBSInstaller.Log.csproj", "{A31A06FF-7FAD-4D57-A281-8034BA761669}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -122,6 +126,22 @@ Global
{6A0F3D17-4AC4-43AC-BA18-2133D61D6F23}.Release|Any CPU.Build.0 = Release|Any CPU
{6A0F3D17-4AC4-43AC-BA18-2133D61D6F23}.Release|x64.ActiveCfg = Release|Any CPU
{6A0F3D17-4AC4-43AC-BA18-2133D61D6F23}.Release|x64.Build.0 = Release|Any CPU
+ {67768F7F-169A-420F-9C50-335FB9885EC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {67768F7F-169A-420F-9C50-335FB9885EC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {67768F7F-169A-420F-9C50-335FB9885EC1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {67768F7F-169A-420F-9C50-335FB9885EC1}.Debug|x64.Build.0 = Debug|Any CPU
+ {67768F7F-169A-420F-9C50-335FB9885EC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {67768F7F-169A-420F-9C50-335FB9885EC1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {67768F7F-169A-420F-9C50-335FB9885EC1}.Release|x64.ActiveCfg = Release|Any CPU
+ {67768F7F-169A-420F-9C50-335FB9885EC1}.Release|x64.Build.0 = Release|Any CPU
+ {A31A06FF-7FAD-4D57-A281-8034BA761669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A31A06FF-7FAD-4D57-A281-8034BA761669}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A31A06FF-7FAD-4D57-A281-8034BA761669}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A31A06FF-7FAD-4D57-A281-8034BA761669}.Debug|x64.Build.0 = Debug|Any CPU
+ {A31A06FF-7FAD-4D57-A281-8034BA761669}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A31A06FF-7FAD-4D57-A281-8034BA761669}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A31A06FF-7FAD-4D57-A281-8034BA761669}.Release|x64.ActiveCfg = Release|Any CPU
+ {A31A06FF-7FAD-4D57-A281-8034BA761669}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ZXBasicStudioTest/ZXBasicStudioTest.csproj b/ZXBasicStudioTest/ZXBasicStudioTest.csproj
index 1bff610..e81da67 100644
--- a/ZXBasicStudioTest/ZXBasicStudioTest.csproj
+++ b/ZXBasicStudioTest/ZXBasicStudioTest.csproj
@@ -14,7 +14,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all