diff --git a/README.md b/README.md
index 31be994..acdcbe1 100644
--- a/README.md
+++ b/README.md
@@ -19,16 +19,16 @@ Have fun!
- AdolFITO
- HashIron
- SirRickster
+ - Yoruguaman
- Testers:
- AbenZaX
- Pedro Tomás (Pere)
- Jose Daniel Fernandez Santos (Fenix)
- - Yoruguaman
## Credits
- Icons from [SVG REPO](https://www.svgrepo.com/):
- Blivesta in MIT License
- - Dazzle Ui in CC Attribution License
+ - Dazzle Ui in CC Attribution License
- Kde in GPL License
- Konstantin Filatov in CC Attribution
- Mohamed Raouf in CC Attribution License
@@ -45,5 +45,5 @@ Have fun!
- Siemens in MIT License
- Zest in MIT License
- Ananthanath A X Kalaiism in PD License
-
-
+ - Siemens in MIT License
+ - SVG Repo CC0 License
diff --git a/ZXBSInstaller.Log/ExternalTools.json b/ZXBSInstaller.Log/ExternalTools.json
new file mode 100644
index 0000000..ab6b2cd
--- /dev/null
+++ b/ZXBSInstaller.Log/ExternalTools.json
@@ -0,0 +1,186 @@
+[
+ {
+ "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.",
+ "SupportedOperatingSystems": [
+ 1,
+ 2,
+ 3
+ ],
+ "SiteUrl": "https://github.com/boriel-basic/ZXBasicStudio",
+ "LicenseType": "MIT License",
+ "LicenceUrl": "https://raw.githubusercontent.com/boriel-basic/ZXBasicStudio/refs/heads/master/LICENSE.txt",
+ "VersionsUrl": "https://github.com/boriel-basic/ZXBasicStudio/releases/",
+ "LocalPath": "",
+ "Unzip": true,
+ "CreateVerFile": false,
+ "FullLocalPath": null,
+ "DirectUpdate": true,
+ "Group": "ZX Basic Studio",
+ "Order": 0
+ },
+ {
+ "Id": "zxbasic",
+ "Enabled": true,
+ "Name": "Boriel ZX Basic Compiler",
+ "Author": "Boriel",
+ "Description": "ZXBCompiler is a BORIEL BASIC cross compiler tool. It's a required tool.'",
+ "SupportedOperatingSystems": [
+ 1,
+ 2,
+ 3
+ ],
+ "SiteUrl": "https://boriel-basic.net",
+ "LicenseType": "GNU Affero General Public License v3.0",
+ "LicenceUrl": "https://raw.githubusercontent.com/boriel-basic/zxbasic/refs/heads/main/LICENSE.txt",
+ "VersionsUrl": "https://boriel.com/files/zxb/",
+ "LocalPath": "",
+ "Unzip": true,
+ "CreateVerFile": false,
+ "FullLocalPath": "C:\\ZXNext\\zxbasic\\zxbc.exe",
+ "DirectUpdate": true,
+ "Group": "ZX Basic Studio",
+ "Order": 1
+ },
+ {
+ "Id": "zxbs",
+ "Enabled": true,
+ "Name": "ZX Basic Studio",
+ "Author": "Dr.Gusman, Boriel, Duefectu, AdolFITO, Hash6Iron and SirRickster",
+ "Description": "IDE (Integrated Development Environment) with code editor, Assembler, UDGs, fonts, sprites, .tap editor, debugger, emulator, etc.",
+ "SupportedOperatingSystems": [
+ 1,
+ 2,
+ 3
+ ],
+ "SiteUrl": "https://github.com/boriel-basic/ZXBasicStudio",
+ "LicenseType": "MIT License",
+ "LicenceUrl": "https://raw.githubusercontent.com/boriel-basic/ZXBasicStudio/refs/heads/master/LICENSE.txt",
+ "VersionsUrl": "https://github.com/boriel-basic/ZXBasicStudio/releases/",
+ "LocalPath": "",
+ "Unzip": true,
+ "CreateVerFile": false,
+ "FullLocalPath": null,
+ "DirectUpdate": true,
+ "Group": "ZX Basic Studio",
+ "Order": 2
+ },
+ {
+ "Id": "mame",
+ "Enabled": true,
+ "Name": "M.A.M.E.",
+ "Author": "MAME Development Team",
+ "Description": "MAME (Multi Arcade Machione Emulator) is a multi-purpose emulation framework.",
+ "SupportedOperatingSystems": [
+ 1,
+ 2,
+ 3
+ ],
+ "SiteUrl": "https://github.com/mamedev/mame",
+ "LicenseType": "GPL-2.0 / BSD-3-Clause License",
+ "LicenceUrl": "https://github.com/mamedev/mame?tab=License-1-ov-file",
+ "VersionsUrl": "https://github.com/mamedev/mame/releases",
+ "LocalPath": "",
+ "Unzip": true,
+ "CreateVerFile": false,
+ "FullLocalPath": null,
+ "DirectUpdate": true,
+ "Group": "ZX Spectrum Next",
+ "Order": 3
+ },
+ {
+ "Id": "tbbluemame",
+ "Enabled": true,
+ "Name": "Next boot ROM",
+ "Author": "SpectNext Ltd",
+ "Description": "ZX Basic Studio plugin for MAME. Allows you to debug programmes from ZX Basic Studio.",
+ "SupportedOperatingSystems": [
+ 1,
+ 2,
+ 3
+ ],
+ "SiteUrl": "https://gitlab.com/SpectrumNext/ZX_Spectrum_Next_FPGA",
+ "LicenseType": "GNU GPLv3",
+ "LicenceUrl": "https://gitlab.com/SpectrumNext/ZX_Spectrum_Next_FPGA/-/raw/master/LICENSE?ref_type=heads",
+ "VersionsUrl": "",
+ "LocalPath": "mame\\roms\\tbblue.zip",
+ "Unzip": false,
+ "CreateVerFile": true,
+ "FullLocalPath": null,
+ "DirectUpdate": true,
+ "Group": "ZX Spectrum Next",
+ "Order": 4
+ },
+ {
+ "Id": "zxbsmame",
+ "Enabled": true,
+ "Name": "ZXBS MAME plugin",
+ "Author": "Duefectu Corp",
+ "Description": "ZX Basic Studio plugin for MAME. Allows you to debug programmes from ZX Basic Studio.",
+ "SupportedOperatingSystems": [
+ 1,
+ 2,
+ 3
+ ],
+ "SiteUrl": "https://github.com/boriel-basic/ZXBasicStudio",
+ "LicenseType": "MIT License",
+ "LicenceUrl": "https://raw.githubusercontent.com/boriel-basic/ZXBasicStudio/refs/heads/master/LICENSE.txt",
+ "VersionsUrl": "https://www.duefectucorp.com/descargas/zxbsmame/Versions.txt",
+ "LocalPath": "mame\\plugins\\zxbs",
+ "Unzip": true,
+ "CreateVerFile": false,
+ "FullLocalPath": null,
+ "DirectUpdate": true,
+ "Group": "ZX Spectrum Next",
+ "Order": 5
+ },
+ {
+ "Id": "nextsdimage",
+ "Enabled": true,
+ "Name": "ZX Spectrum Next SD image",
+ "Author": "SpectNext Ltd",
+ "Description": "ZX Basic Studio plugin for MAME. Allows you to debug programmes from ZX Basic Studio.",
+ "SupportedOperatingSystems": [
+ 1,
+ 2,
+ 3
+ ],
+ "SiteUrl": "https://zxnext.uk/hosted/",
+ "LicenseType": "MIT License",
+ "LicenceUrl": "https://zxnext.uk/hosted/",
+ "VersionsUrl": "",
+ "LocalPath": "",
+ "Unzip": true,
+ "CreateVerFile": false,
+ "FullLocalPath": null,
+ "DirectUpdate": true,
+ "Group": "ZX Spectrum Next",
+ "Order": 5
+ },
+ {
+ "Id": "hdfmonkey",
+ "Enabled": true,
+ "Name": "hdfmonkey - jjjs build",
+ "Author": "jjjs, gasman, ChaN",
+ "Description": "A tool for editing the contents of disk images used by ZX Spectrum Next.",
+ "SupportedOperatingSystems": [
+ 1,
+ 2,
+ 3
+ ],
+ "SiteUrl": "https://www.specnext.com/forum/viewtopic.php?t=2604",
+ "LicenseType": "GPL-3.0",
+ "LicenceUrl": "https://codeberg.org/chwe/hdfmonkey/src/branch/dev-v05x",
+ "VersionsUrl": "https://www.duefectucorp.com/descargas/hdfmonkey/Versions.txt",
+ "LocalPath": "",
+ "Unzip": true,
+ "CreateVerFile": false,
+ "FullLocalPath": null,
+ "DirectUpdate": true,
+ "Group": "ZX Spectrum Next",
+ "Order": 6
+ }
+]
\ No newline at end of file
diff --git a/ZXBSInstaller.Log/Neg/ExternalTool.cs b/ZXBSInstaller.Log/Neg/ExternalTool.cs
index 6232d35..37fc327 100644
--- a/ZXBSInstaller.Log/Neg/ExternalTool.cs
+++ b/ZXBSInstaller.Log/Neg/ExternalTool.cs
@@ -53,7 +53,7 @@ public class ExternalTool
///
public string VersionsUrl { get; set; }
///
- /// Local path where the tool will be installed without file name
+ /// If exists Local path where the tool will be installed without file name
///
public string LocalPath { get; set; }
///
@@ -65,6 +65,14 @@ public class ExternalTool
///
public bool DirectUpdate { get; set; }
///
+ /// If true, tool will be downloaded as a zip file and unzipped in the local path, otherwise it will be downloaded as is
+ ///
+ public bool Unzip { get; set; }
+ ///
+ /// True, creates a version.txt file in the installation folder
+ ///
+ public bool CreateVerFile { get; set; }
+ ///
/// Order in the list
///
public int Order { get; set; }
@@ -72,7 +80,10 @@ public class ExternalTool
/// Recommended
///
public bool Recommended { get; set; }
-
+ ///
+ /// Tools group
+ ///
+ public string Group { get; set; }
///
/// Versions of the tool
///
diff --git a/ZXBSInstaller.Log/Neg/ExternalTools_Version.cs b/ZXBSInstaller.Log/Neg/ExternalTools_Version.cs
index 8041254..eb0c2f7 100644
--- a/ZXBSInstaller.Log/Neg/ExternalTools_Version.cs
+++ b/ZXBSInstaller.Log/Neg/ExternalTools_Version.cs
@@ -12,11 +12,11 @@ public class ExternalTools_Version
///
/// Numer of the Beta version, 0 if not a beta
///
- public int BetaNumber { get; set; }
+ public decimal BetaNumber { get; set; }
///
/// Internal version number to order versions
///
- public int VersionNumber { get; set; }
+ public decimal VersionNumber { get; set; }
///
/// Download url for this version
///
diff --git a/ZXBSInstaller.Log/ServiceLayer.cs b/ZXBSInstaller.Log/ServiceLayer.cs
index b3cbc07..5ad103d 100644
--- a/ZXBSInstaller.Log/ServiceLayer.cs
+++ b/ZXBSInstaller.Log/ServiceLayer.cs
@@ -5,6 +5,7 @@
using System.IO;
using System.IO.Compression;
using System.Reflection;
+using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime;
using System.Runtime.InteropServices;
@@ -37,7 +38,7 @@ public static class ServiceLayer
///
/// True if the computer is a Mac
///
- public static bool IsMac=false;
+ public static bool IsMac = false;
///
/// Used to cancel the current operation. It is set to true when the user clicks the cancel button and it is checked in all long operations to stop them if it is true.
///
@@ -268,6 +269,30 @@ private static int ToInteger(object value)
}
+ ///
+ /// Convert an objet to his decimnal value or 0 if it can't do it
+ ///
+ /// Value to convert
+ /// Numeric value or 0 if can't do it
+ private static decimal ToDecimal(object value)
+ {
+ try
+ {
+ if (value == null)
+ {
+ return 0;
+ }
+ decimal v = 0;
+ if (decimal.TryParse(value.ToString(), out v))
+ {
+ return v;
+ }
+ }
+ catch { }
+ return 0;
+ }
+
+
///
/// Open an url in the default browser. It is used to open the site and license urls of the external tools. It is called from the service layer when the user clicks on the site or license buttons of an external tool.
///
@@ -307,20 +332,39 @@ public static void OpenUrlInBrowser(string url)
///
/// Retrieves all external tools configured for use with the application.
///
- /// Json string with the external tools information
/// 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[] SetExternalTools(string json)
+ public static ExternalTool[] GetExternalTools()
{
try
{
UpdateStatus?.Invoke("Retrieving external tools information...", 5);
- var tools = JsonSerializer.Deserialize(json);
- if (tools == null)
+ // Get external tools list from embded resource
+ ExternalTool[] tools = null;
{
- ShowMessage("ERROR, unable to obtain the list of external tools. Download and install a new version of ZXBSInstaller.");
- return null;
+ var assembly = Assembly.GetExecutingAssembly();
+ using (Stream? stream = assembly.GetManifestResourceStream("ZXBSInstaller.Log.ExternalTools.json"))
+ {
+ if (stream == null)
+ {
+ ShowMessage("ERROR, unable to obtain the list of external tools. Download and install a new version of ZXBSInstaller: ERROR #1");
+ return null;
+ }
+ using StreamReader reader = new StreamReader(stream, Encoding.UTF8);
+ var json = reader.ReadToEnd();
+ if (string.IsNullOrEmpty(json))
+ {
+ ShowMessage("ERROR, unable to obtain the list of external tools. Download and install a new version of ZXBSInstaller: ERROR #2");
+ return null;
+ }
+ tools = JsonSerializer.Deserialize(json);
+ if (tools == null)
+ {
+ ShowMessage("ERROR, unable to obtain the list of external tools. Download and install a new version of ZXBSInstaller. ERROR #3");
+ return null;
+ }
+ }
}
int max = tools.Length;
@@ -344,7 +388,7 @@ public static ExternalTool[] SetExternalTools(string json)
tool.Versions = new ExternalTools_Version[0];
}
// Get installed version
- tool.InstalledVersion = GetToolVersion(tool.Id);
+ tool.InstalledVersion = GetToolVersion(tool);
// Set latest version
if (GeneralConfig.OnlyStableVersions)
@@ -360,7 +404,8 @@ public static ExternalTool[] SetExternalTools(string json)
else
{
tool.LatestVersion = tool.Versions.
- Where(d => d.OperatingSystem == CurrentOperatingSystem &&
+ Where(d => (d.OperatingSystem == OperatingSystems.All ||
+ d.OperatingSystem == CurrentOperatingSystem) &&
d.BetaNumber == 0).
OrderByDescending(d => d.VersionNumber).
FirstOrDefault();
@@ -378,7 +423,8 @@ public static ExternalTool[] SetExternalTools(string json)
else
{
tool.LatestVersion = tool.Versions.
- Where(d => d.OperatingSystem == CurrentOperatingSystem).
+ Where(d => (d.OperatingSystem == OperatingSystems.All ||
+ d.OperatingSystem == CurrentOperatingSystem)).
OrderByDescending(d => d.VersionNumber).
FirstOrDefault();
}
@@ -406,7 +452,14 @@ public static ExternalTool[] SetExternalTools(string json)
}
}
// Set tool local path
- tool.LocalPath = Path.Combine(GeneralConfig.BasePath, tool.Id);
+ if (string.IsNullOrEmpty(tool.LocalPath))
+ {
+ tool.LocalPath = Path.Combine(GeneralConfig.BasePath, tool.Id);
+ }
+ else
+ {
+ tool.LocalPath = Path.Combine(GeneralConfig.BasePath, tool.LocalPath);
+ }
}
// order tools by order property
@@ -432,12 +485,12 @@ public static ExternalTool[] SetExternalTools(string json)
///
/// Version string
/// Item1 = version number, Item2 = beta number
- private static (int, int) GetVersionNumber(string versionString)
+ private static (decimal, decimal) GetVersionNumber(string versionString)
{
try
{
- int number = 0;
- int betaNumber = 0;
+ decimal number = 0;
+ decimal betaNumber = 0;
string version = versionString;
// If it is a beta version, replace the -beta with . and add the beta number as the fourth value.
if (version.Contains("-beta"))
@@ -465,7 +518,7 @@ private static (int, int) GetVersionNumber(string versionString)
number *= 1000;
if (n < versionParts.Length)
{
- int v = ToInteger(versionParts[n]);
+ decimal v = ToDecimal(versionParts[n]);
if (n == 3)
{
betaNumber = v;
@@ -523,6 +576,19 @@ private static ExternalTools_Version[] GetAvailableToolVersion(ExternalTool tool
case "zxbsinstaller":
return GetBorielZXBSVersions(tool.VersionsUrl, true);
+ case "mame":
+ return GetMAMEVersions();
+
+ case "tbbluemame":
+ return GetTBBLueMAMEVersions();
+
+ case "zxbsmame":
+ case "hdfmonkey":
+ return GetDuefectuVersions(tool.VersionsUrl);
+
+ case "nextsdimage":
+ return GetNextSDImageVersions();
+
default:
return null;
}
@@ -829,10 +895,15 @@ private static string[] GetAllLinks(string url, string pattern)
///
/// Tool id
/// Installed version
- public static ExternalTools_Version GetToolVersion(string id)
+ public static ExternalTools_Version GetToolVersion(ExternalTool tool)
{
try
{
+ if (tool == null)
+ {
+ return null;
+ }
+ var id = tool.Id;
var dir = Path.Combine(GeneralConfig.BasePath, id);
switch (id)
@@ -843,11 +914,20 @@ public static ExternalTools_Version GetToolVersion(string id)
return GetZXBSVersion(dir);
case "zxbsinstaller":
return GetZXBSInstallerVersion(dir);
+ case "mame":
+ return GetMAMEVersion(dir);
+ case "tbbluemame":
+ return GetTBBLueMAMEVersion(Path.Combine(GeneralConfig.BasePath, tool.LocalPath));
+ case "zxbsmame":
+ case "hdfmonkey":
+ return GetDuefectuVersion(tool);
+ case "nextsdimage":
+ return GetNextSDImageVersion(tool);
}
}
catch (Exception ex)
{
- ShowMessage($"Error retrieving local version for {id}.\r\n{ex.Message}{ex.StackTrace}");
+ ShowMessage($"Error retrieving local version for {tool.Id}.\r\n{ex.Message}{ex.StackTrace}");
}
return null;
}
@@ -978,8 +1058,8 @@ private static ExternalTools_Version GetVersionFromParameter(string fileName)
}
var version = output.Replace("zxbc.py ", "").Replace("\n", "").Replace("\r", "").Replace("v", "");
var v = GetVersionNumber(version);
- int number = v.Item1;
- int beta = v.Item2;
+ decimal number = v.Item1;
+ decimal beta = v.Item2;
return new ExternalTools_Version()
{
@@ -1089,7 +1169,7 @@ public static void DownloadAndInstallTool(ExternalTool tool, ExternalTools_Versi
return;
}
- if(tool.Id == "zxbsinstaller")
+ if (tool.Id == "zxbsinstaller")
{
ShowStatusPanel($"After installing or updating ZXBSInstaller, run this program from {tool.LocalPath}.");
}
@@ -1109,13 +1189,21 @@ public static void DownloadAndInstallTool(ExternalTool tool, ExternalTools_Versi
// Get installation path
step = "Creating installation path";
- var installationPath = Path.Combine(GeneralConfig.BasePath, tool.Id);
+ string installationPath = "";
+ if (string.IsNullOrEmpty(tool.LocalPath))
+ {
+ installationPath = Path.Combine(GeneralConfig.BasePath, tool.Id);
+ }
+ else
+ {
+ installationPath = Path.Combine(GeneralConfig.BasePath, tool.LocalPath);
+ }
// Patch for Boriel Basic
if (tool.Id == "zxbasic")
{
installationPath = Directory.GetParent(installationPath).FullName;
}
- if (!Directory.Exists(installationPath))
+ if (tool.Unzip && !Directory.Exists(installationPath))
{
step = $"Creating application path [{installationPath}]";
Directory.CreateDirectory(installationPath);
@@ -1144,9 +1232,28 @@ public static void DownloadAndInstallTool(ExternalTool tool, ExternalTools_Versi
}
// Extract file
- step = $"Installing {tool.Name}";
- UpdateStatus($"Installing {tool.Name} version {version.Version}...", 50);
- ExtractFile(tempFile, installationPath);
+ if (tool.Unzip)
+ {
+ step = $"Installing {tool.Name}";
+ UpdateStatus($"Installing {tool.Name} version {version.Version}...", 50);
+ ExtractFile(tempFile, installationPath);
+ }
+ else
+ {
+ var dest = Path.Combine(GeneralConfig.BasePath, tool.LocalPath);
+ if (File.Exists(dest))
+ {
+ File.Delete(dest);
+ }
+ File.Move(tempFile, installationPath);
+ }
+
+ // Create Version.txt file
+ if (tool.CreateVerFile)
+ {
+ var fileName = Path.Combine(GeneralConfig.BasePath, $"{tool.LocalPath}.ver");
+ File.WriteAllText(fileName, version.Version);
+ }
// Set ZXBS Options
step = "Set ZX Basic Studio options";
@@ -1158,6 +1265,65 @@ public static void DownloadAndInstallTool(ExternalTool tool, ExternalTools_Versi
UpdateStatus("Deleting temp files...", 90);
File.Delete(tempFile);
+ // hdfmonkey patch
+ if (tool.Id == "hdfmonkey")
+ {
+ string dest = "";
+ string source = "";
+ string basePath = Path.Combine(GeneralConfig.BasePath, tool.Id);
+ switch (CurrentOperatingSystem)
+ {
+ case OperatingSystems.Windows:
+ dest = Path.Combine(basePath, "hdfmonkey.exe");
+ source = Path.Combine(basePath, "windows-64", "hdfmonkey.exe");
+ break;
+ case OperatingSystems.Linux:
+ dest = Path.Combine(basePath, "hdfmonkey");
+ source = Path.Combine(basePath, "linux-musl", "hdfmonkey");
+ break;
+ case OperatingSystems.MacOS_x64:
+ dest = Path.Combine(basePath, "hdfmonkey");
+ source = Path.Combine(basePath, "macos-intel", "hdfmonkey");
+ break;
+ case OperatingSystems.MacOS_arm64:
+ dest = Path.Combine(basePath, "hdfmonkey");
+ source = Path.Combine(basePath, "macos-mn", "hdfmonkey");
+ break;
+
+ }
+ if (File.Exists(dest))
+ {
+ File.Delete(dest);
+ }
+ File.Move(source, dest);
+ }
+
+ // Next SD Image patch
+ if(tool.Id == "nextsdimage")
+ {
+ string dir = Path.Combine(GeneralConfig.BasePath, tool.Id,"2gb");
+ string source = Path.Combine(dir, "cspect-next-2gb.img");
+ string dest=Path.Combine(GeneralConfig.BasePath, tool.Id, "cspect-next-2gb.img");
+ if (File.Exists(dest))
+ {
+ File.Delete(dest);
+ }
+ File.Move(source, dest);
+
+ source= Path.Combine(dir, "version.txt");
+ dest= Path.Combine(GeneralConfig.BasePath, tool.Id, "version.txt");
+ if (File.Exists(dest))
+ {
+ File.Delete(dest);
+ }
+ File.Move(source, dest);
+ try
+ {
+ Directory.Delete(dir);
+ }
+ catch { }
+ }
+
UpdateStatus($"{tool.Name} version {version.Version} installed successfully.", 100);
}
catch (Exception ex)
@@ -1299,10 +1465,37 @@ private static void ExtractFile(string archive, string destination)
// Extract .zip file
System.IO.Compression.ZipFile.ExtractToDirectory(archive, destination, true);
}
+ else if (archive.ToLower().EndsWith(".exe"))
+ {
+ var psi = new ProcessStartInfo
+ {
+ FileName = archive,
+ Arguments = $"-o\"{destination}\" -y",
+ 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;
+ }
+ }
else if (CurrentOperatingSystem != OperatingSystems.Windows)
{
// Extract .tar file on Linux and Mac
- Directory.CreateDirectory(destination);
+ if (!Directory.Exists(destination))
+ {
+ Directory.CreateDirectory(destination);
+ }
var psi = new ProcessStartInfo
{
@@ -1440,5 +1633,320 @@ public static bool RunZXBasicStudio()
return false;
}
}
+
+
+ #region MAME
+
+ private static ExternalTools_Version[] GetMAMEVersions()
+ {
+ var versions = new List();
+
+ // Windows
+ versions.Add(new ExternalTools_Version()
+ {
+ BetaNumber = 0,
+ DownloadUrl = "https://github.com/mamedev/mame/releases/download/mame0285/mame0285b_x64.exe",
+ OperatingSystem = OperatingSystems.Windows,
+ Version = "0.285",
+ VersionNumber = 285
+ });
+ // Mac_x86
+ versions.Add(new ExternalTools_Version()
+ {
+ BetaNumber = 0,
+ DownloadUrl = "https://sdlmame.lngn.net/stable/mame0285-x86.zip",
+ OperatingSystem = OperatingSystems.MacOS_x64,
+ Version = "0.285",
+ VersionNumber = 285
+ });
+ // Mac_arm64
+ versions.Add(new ExternalTools_Version()
+ {
+ BetaNumber = 0,
+ DownloadUrl = "https://sdlmame.lngn.net/stable/mame0285-arm64.zip",
+ OperatingSystem = OperatingSystems.MacOS_arm64,
+ Version = "0.285",
+ VersionNumber = 285
+ });
+ // Mac_arm64
+ versions.Add(new ExternalTools_Version()
+ {
+ BetaNumber = 0,
+ DownloadUrl = "bash -c \"sudo apt install -y mame\"",
+ OperatingSystem = OperatingSystems.Linux,
+ Version = "0.285",
+ VersionNumber = 285
+ });
+ return versions.ToArray();
+ }
+
+
+ private static ExternalTools_Version GetMAMEVersion(string exePath)
+ {
+ try
+ {
+ // Windows fileName
+ var fileName = Path.Combine(exePath, "mame.exe");
+ if (!File.Exists(fileName))
+ {
+ // If not exist, try Linux/Mac fileName
+ fileName = Path.Combine(exePath, "mame");
+ }
+ if (!File.Exists(fileName))
+ {
+ // Not found
+ return null;
+ }
+
+ // Retrieve version executing with -version parameter
+ 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 parts = output.Split(" ");
+ var version = parts[0];
+ int number = ToInteger(version.Replace("0.", ""));
+ int beta = 0;
+
+ return new ExternalTools_Version()
+ {
+ DownloadUrl = "",
+ BetaNumber = beta,
+ OperatingSystem = OperatingSystems.All,
+ Version = version,
+ VersionNumber = number
+ };
+ }
+ catch (Exception ex)
+ {
+ ShowMessage($"Error getting local MAME version.\r\n{ex.Message}{ex.StackTrace}");
+ return null;
+ }
+ }
+
+ #endregion
+
+
+ #region Boot ROM Next - tbbluemame
+
+ private static ExternalTools_Version[] GetTBBLueMAMEVersions()
+ {
+ var versions = new List();
+
+ // All versions
+ var v = new ExternalTools_Version()
+ {
+ DownloadUrl = "https://github.com/Threetwosevensixseven/NexCreator/raw/master/bootroms/tbblue.zip",
+ OperatingSystem = OperatingSystems.All,
+ Version = "3.02.03",
+ };
+ var vv = GetVersionNumber(v.Version);
+ v.VersionNumber = vv.Item1;
+ v.BetaNumber = vv.Item2;
+ versions.Add(v);
+ return versions.ToArray();
+ }
+
+
+ private static ExternalTools_Version GetTBBLueMAMEVersion(string exePath)
+ {
+ try
+ {
+ // Windows fileName
+ var fileName = exePath + ".ver";
+ if (!File.Exists(fileName))
+ {
+ return null;
+ }
+ var version = File.ReadAllText(fileName).Trim();
+ // All versions
+ var v = new ExternalTools_Version()
+ {
+ DownloadUrl = "https://github.com/Threetwosevensixseven/NexCreator/raw/master/bootroms/tbblue.zip",
+ OperatingSystem = OperatingSystems.All,
+ Version = "3.02.03",
+ };
+ var vv = GetVersionNumber(v.Version);
+ v.VersionNumber = vv.Item1;
+ v.BetaNumber = vv.Item2;
+ return v;
+ }
+ catch (Exception ex)
+ {
+ ShowMessage($"Error getting local Next boot ROM version.\r\n{ex.Message}{ex.StackTrace}");
+ return null;
+ }
+ }
+
+ #endregion
+
+
+ #region Version.txt based system
+
+ private static ExternalTools_Version[] GetDuefectuVersions(string url)
+ {
+ try
+ {
+ var versions = new List();
+
+ // Download versions file
+ string data = "";
+ using (HttpClient client = new HttpClient())
+ {
+ client.Timeout = TimeSpan.FromSeconds(20);
+ data = client.GetStringAsync(url).GetAwaiter().GetResult();
+ }
+ if (string.IsNullOrEmpty(data))
+ {
+ return null;
+ }
+
+ var lines = data.Split("\r\n");
+ foreach (var line in lines)
+ {
+ var parts = line.Split(';');
+ if (parts.Length == 2)
+ {
+ var v = new ExternalTools_Version()
+ {
+ DownloadUrl = parts[1],
+ OperatingSystem = OperatingSystems.All,
+ Version = parts[0],
+ };
+ var vv = GetVersionNumber(v.Version);
+ v.VersionNumber = vv.Item1;
+ v.BetaNumber = vv.Item2;
+ versions.Add(v);
+ }
+ }
+
+ return versions.ToArray();
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ private static ExternalTools_Version GetDuefectuVersion(ExternalTool tool)
+ {
+ try
+ {
+ string exePath = "";
+ if (string.IsNullOrEmpty(tool.LocalPath))
+ {
+ exePath = Path.Combine(GeneralConfig.BasePath, tool.Id);
+ }
+ else
+ {
+ exePath = Path.Combine(GeneralConfig.BasePath, tool.LocalPath);
+ }
+ var fileName = Path.Combine(exePath, "Version.txt");
+ if (!File.Exists(fileName))
+ {
+ return null;
+ }
+ var versiontxt = File.ReadAllText(fileName).Trim();
+ if (string.IsNullOrEmpty(versiontxt))
+ {
+ return null;
+ }
+ var v = new ExternalTools_Version()
+ {
+ DownloadUrl = "",
+ OperatingSystem = OperatingSystems.All,
+ Version = versiontxt,
+ };
+ var vv = GetVersionNumber(v.Version);
+ v.VersionNumber = vv.Item1;
+ v.BetaNumber = vv.Item2;
+ return v;
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+ #endregion
+
+
+ #region Next SD Image
+
+ private static ExternalTools_Version[] GetNextSDImageVersions()
+ {
+ try
+ {
+ var versions = new List();
+
+ var v = new ExternalTools_Version()
+ {
+ DownloadUrl = "https://zxnext.uk/hosted/index_files/hdfimages/cspect-next-2gb.zip",
+ OperatingSystem = OperatingSystems.All,
+ Version = "2026-02-27",
+ VersionNumber = 20260227,
+ BetaNumber = 0
+ };
+ versions.Add(v);
+
+ return versions.ToArray();
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+
+ private static ExternalTools_Version GetNextSDImageVersion(ExternalTool tool)
+ {
+ try
+ {
+ string fileName = Path.Combine(GeneralConfig.BasePath,tool.Id,"version.txt");
+ if (!File.Exists(fileName))
+ {
+ return null;
+ }
+ var versiontxt = File.ReadAllText(fileName).Trim();
+ if (string.IsNullOrEmpty(versiontxt))
+ {
+ return null;
+ }
+ var parts = versiontxt.Split(" ");
+
+ var v = new ExternalTools_Version()
+ {
+ DownloadUrl = "",
+ OperatingSystem = OperatingSystems.All,
+ Version = parts[0],
+ BetaNumber = 0,
+ VersionNumber = ToDecimal(parts[0].Replace("-",""))
+ };
+ return v;
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+ #endregion
+
}
}
\ No newline at end of file
diff --git a/ZXBSInstaller.Log/ZXBSInstaller.Log.csproj b/ZXBSInstaller.Log/ZXBSInstaller.Log.csproj
index fa71b7a..e5df59e 100644
--- a/ZXBSInstaller.Log/ZXBSInstaller.Log.csproj
+++ b/ZXBSInstaller.Log/ZXBSInstaller.Log.csproj
@@ -6,4 +6,14 @@
enable
+
+
+
+
+
+
+
+
+
+
diff --git a/ZXBSInstaller/Assets/hdfmonkey.png b/ZXBSInstaller/Assets/hdfmonkey.png
new file mode 100644
index 0000000..e1e115e
Binary files /dev/null and b/ZXBSInstaller/Assets/hdfmonkey.png differ
diff --git a/ZXBSInstaller/Assets/mame.png b/ZXBSInstaller/Assets/mame.png
new file mode 100644
index 0000000..236131c
Binary files /dev/null and b/ZXBSInstaller/Assets/mame.png differ
diff --git a/ZXBSInstaller/Assets/nextsdimage.png b/ZXBSInstaller/Assets/nextsdimage.png
new file mode 100644
index 0000000..a8e3bf0
Binary files /dev/null and b/ZXBSInstaller/Assets/nextsdimage.png differ
diff --git a/ZXBSInstaller/Assets/tbbluemame.png b/ZXBSInstaller/Assets/tbbluemame.png
new file mode 100644
index 0000000..2978686
Binary files /dev/null and b/ZXBSInstaller/Assets/tbbluemame.png differ
diff --git a/ZXBSInstaller/Assets/zxbsmame.png b/ZXBSInstaller/Assets/zxbsmame.png
new file mode 100644
index 0000000..ae4537a
Binary files /dev/null and b/ZXBSInstaller/Assets/zxbsmame.png differ
diff --git a/ZXBSInstaller/Controls/MainControl.axaml b/ZXBSInstaller/Controls/MainControl.axaml
index 765e229..ab2ce78 100644
--- a/ZXBSInstaller/Controls/MainControl.axaml
+++ b/ZXBSInstaller/Controls/MainControl.axaml
@@ -11,8 +11,11 @@
-
+
+
+
diff --git a/ZXBSInstaller/Controls/MainControl.axaml.cs b/ZXBSInstaller/Controls/MainControl.axaml.cs
index 87c1805..e39225c 100644
--- a/ZXBSInstaller/Controls/MainControl.axaml.cs
+++ b/ZXBSInstaller/Controls/MainControl.axaml.cs
@@ -10,6 +10,7 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Threading;
using ZXBSInstaller.Controls;
@@ -32,6 +33,8 @@ public partial class MainControl : UserControl
///
private static Brush Yellow = new SolidColorBrush(Colors.Yellow);
+ private CheckBox chkNextGroup = null;
+
///
/// Main constructor
@@ -109,8 +112,7 @@ private void GetExternalTools()
});
// Get tools
- var json = UITools.GetTextResource("ExternalTools.json");
- var tools = ServiceLayer.SetExternalTools(json);
+ var tools = ServiceLayer.GetExternalTools();
HideStatusPanel();
if (tools == null)
@@ -136,16 +138,87 @@ private void ShowData()
toolItemControls.Clear();
var tools = ServiceLayer.ExternalTools;
- pnlTools.Children.Clear();
+ mainTools.Children.Clear();
+ // ZX Basic Studio
+ {
+ var groupTools = tools.Where(t => t.Group == "ZX Basic Studio");
+ {
+ mainTools.Children.Add(new CheckBox()
+ {
+ Content = "ZX Basic Studio",
+ FontSize = 16,
+ IsChecked = true,
+ Margin = new Thickness(10, 10, 0, 0),
+ Foreground = Yellow
+ });
+ mainTools.Children.Add(new Separator()
+ {
+ Margin = new Thickness(0),
+ HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch
+ });
+ }
+ var pnlTools = new WrapPanel();
+ mainTools.Children.Add(pnlTools);
+ ShowData_AddTools(pnlTools, groupTools);
+ }
+
+ // Next
+ {
+ var groupTools = tools.Where(t => t.Group == "ZX Spectrum Next");
+ {
+ if (chkNextGroup == null)
+ {
+ chkNextGroup = new CheckBox()
+ {
+ Content = "ZX Spectrum Next",
+ FontSize = 16,
+ IsChecked = true,
+ Margin = new Thickness(10, 10, 0, 0),
+ Foreground = Yellow
+ };
+ chkNextGroup.IsCheckedChanged += ZXNextGropu_Changed;
+ }
+ mainTools.Children.Add(chkNextGroup);
+ mainTools.Children.Add(new Separator()
+ {
+ Margin = new Thickness(0),
+ HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch
+ });
+ }
+ var pnlTools = new WrapPanel();
+ mainTools.Children.Add(pnlTools);
+ ShowData_AddTools(pnlTools, groupTools);
+ }
+
+ // Update summary area
+ UpdateSummary();
+ });
+ }
+
+
+ private void ZXNextGropu_Changed(object? sender, RoutedEventArgs e)
+ {
+ foreach(var ctrl in toolItemControls)
+ {
+ if (ctrl.ExternalTool.Group == "ZX Spectrum Next")
+ {
+ ctrl.IsSelected = chkNextGroup.IsChecked == true;
+ }
+ }
+ }
+
+
+ private void ShowData_AddTools(WrapPanel panel, IEnumerable tools)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
foreach (var tool in tools)
{
// Create on ToolItemControl foreach tool
var control = new ToolItemControl(tool, Command_Received);
toolItemControls.Add(control);
- pnlTools.Children.Add(control);
+ panel.Children.Add(control);
}
- // Update summary area
- UpdateSummary();
});
}
diff --git a/ZXBSInstaller/Controls/ToolItemControl.axaml b/ZXBSInstaller/Controls/ToolItemControl.axaml
index cc46644..f8a0bab 100644
--- a/ZXBSInstaller/Controls/ToolItemControl.axaml
+++ b/ZXBSInstaller/Controls/ToolItemControl.axaml
@@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:svg="using:Avalonia.Svg.Skia"
- Width="360" Height="180"
+ Width="360" Height="170"
x:Class="ZXBSInstaller.Controls.ToolItemControl"
FontSize="12">
diff --git a/ZXBSInstaller/Controls/ToolItemControl.axaml.cs b/ZXBSInstaller/Controls/ToolItemControl.axaml.cs
index 3fb33b0..fbc9afc 100644
--- a/ZXBSInstaller/Controls/ToolItemControl.axaml.cs
+++ b/ZXBSInstaller/Controls/ToolItemControl.axaml.cs
@@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
+using Avalonia.Threading;
using System;
using ZXBSInstaller.Log;
using ZXBSInstaller.Log.Neg;
@@ -21,7 +22,20 @@ public partial class ToolItemControl : UserControl
///
/// Tool selected for installation?
///
- public bool IsSelected = false;
+ public bool IsSelected
+ {
+ get
+ {
+ return _IsSelected;
+ }
+ set
+ {
+ _IsSelected = value;
+ SetSelected(value);
+ }
+ }
+
+ private bool _IsSelected = false;
// Colors
public static SolidColorBrush colorRed = new SolidColorBrush(Colors.Red);
@@ -53,6 +67,7 @@ public ToolItemControl(ExternalTool tool, Action callBackCommand
txtPath.Text = "Path: " + tool.LocalPath;
txtLicense.Text = "Licence: " + tool.LicenseType;
txtAuthor.Text = "Author(s): " + tool.Author;
+
// Set chkSelect status
IsSelected = tool.UpdateNeeded;
tool.IsSelected = IsSelected;
@@ -96,9 +111,7 @@ public ToolItemControl(ExternalTool tool, Action callBackCommand
///
private void chkSelect_IsCheckedChanged(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
- IsSelected = chkSelect.IsChecked == true;
- ExternalTool.IsSelected = IsSelected;
- Command(ExternalTool.Id, "CHECKED");
+ SetSelected(chkSelect.IsChecked == true);
}
@@ -116,4 +129,19 @@ private void btnViewSite_Click(object? sender, Avalonia.Interactivity.RoutedEven
{
ServiceLayer.OpenUrlInBrowser(ExternalTool.SiteUrl);
}
+
+
+ private void SetSelected(bool selected)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ _IsSelected = selected;
+ ExternalTool.IsSelected = _IsSelected;
+ if (chkSelect.IsChecked != _IsSelected)
+ {
+ chkSelect.IsChecked = _IsSelected;
+ }
+ Command(ExternalTool.Id, "CHECKED");
+ });
+ }
}
\ No newline at end of file
diff --git a/ZXBSInstaller/ExternalTools.json b/ZXBSInstaller/ExternalTools.json
deleted file mode 100644
index 45ffa91..0000000
--- a/ZXBSInstaller/ExternalTools.json
+++ /dev/null
@@ -1,62 +0,0 @@
-[
- {
- "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.",
- "SupportedOperatingSystems": [
- 1,
- 2,
- 3
- ],
- "SiteUrl": "https://github.com/boriel-basic/ZXBasicStudio",
- "LicenseType": "MIT License",
- "LicenceUrl": "https://raw.githubusercontent.com/boriel-basic/ZXBasicStudio/refs/heads/master/LICENSE.txt",
- "VersionsUrl": "https://github.com/boriel-basic/ZXBasicStudio/releases/",
- "LocalPath": "",
- "FullLocalPath": null,
- "DirectUpdate": true,
- "Order": 0
- },
- {
- "Id": "zxbasic",
- "Enabled": true,
- "Name": "Boriel ZX Basic Compiler",
- "Author": "Boriel",
- "Description": "ZXBCompiler is a BORIEL BASIC cross compiler tool. It's a required tool.'",
- "SupportedOperatingSystems": [
- 1,
- 2,
- 3
- ],
- "SiteUrl": "https://boriel-basic.net",
- "LicenseType": "GNU Affero General Public License v3.0",
- "LicenceUrl": "https://raw.githubusercontent.com/boriel-basic/zxbasic/refs/heads/main/LICENSE.txt",
- "VersionsUrl": "https://boriel.com/files/zxb/",
- "LocalPath": "",
- "FullLocalPath": "C:\\ZXNext\\zxbasic\\zxbc.exe",
- "DirectUpdate": true,
- "Order": 1
- },
- {
- "Id": "zxbs",
- "Enabled": true,
- "Name": "ZX Basic Studio",
- "Author": "Dr.Gusman, Boriel, Duefectu, AdolFITO, Hash6Iron and SirRickster",
- "Description": "IDE (Integrated Development Environment) with code editor, Assembler, UDGs, fonts, sprites, .tap editor, debugger, emulator, etc.",
- "SupportedOperatingSystems": [
- 1,
- 2,
- 3
- ],
- "SiteUrl": "https://github.com/boriel-basic/ZXBasicStudio",
- "LicenseType": "MIT License",
- "LicenceUrl": "https://raw.githubusercontent.com/boriel-basic/ZXBasicStudio/refs/heads/master/LICENSE.txt",
- "VersionsUrl": "https://github.com/boriel-basic/ZXBasicStudio/releases/",
- "LocalPath": "",
- "FullLocalPath": null,
- "DirectUpdate": true,
- "Order": 2
- }
-]
\ No newline at end of file
diff --git a/ZXBSInstaller/MainWindow.axaml b/ZXBSInstaller/MainWindow.axaml
index 2f9fd7e..2d7eaa8 100644
--- a/ZXBSInstaller/MainWindow.axaml
+++ b/ZXBSInstaller/MainWindow.axaml
@@ -2,7 +2,7 @@
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"
- Width="1000" Height="600"
+ Width="1000" Height="650"
x:Class="ZXBSInstaller.MainWindow"
Title="ZX Basic Studio Installer"
WindowStartupLocation="CenterScreen"
diff --git a/ZXBSInstaller/ZXBSInstaller.csproj b/ZXBSInstaller/ZXBSInstaller.csproj
index fa2d5ea..e2aff0a 100644
--- a/ZXBSInstaller/ZXBSInstaller.csproj
+++ b/ZXBSInstaller/ZXBSInstaller.csproj
@@ -6,7 +6,7 @@
app.manifest
true
zxbs.ico
- 1.0.0.0
+ 1.0.1.1
@@ -15,7 +15,6 @@
-
@@ -23,8 +22,23 @@
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
@@ -37,9 +51,6 @@
PreserveNewest
-
-
-
diff --git a/ZXBSInstaller/version.txt b/ZXBSInstaller/version.txt
index bd2666a..86516d0 100644
--- a/ZXBSInstaller/version.txt
+++ b/ZXBSInstaller/version.txt
@@ -1 +1 @@
-1.0.0.0
\ No newline at end of file
+1.0.1.1
\ No newline at end of file
diff --git a/ZXBStudio/Common/Txt2Bas/BasConverter.cs b/ZXBStudio/Common/Txt2Bas/BasConverter.cs
new file mode 100644
index 0000000..c35540a
--- /dev/null
+++ b/ZXBStudio/Common/Txt2Bas/BasConverter.cs
@@ -0,0 +1,254 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace ZXBasicStudio.Common.Txt2Bas
+{
+ ///
+ /// Converts plain text into tokenized Spectrum BASIC binary format.
+ /// Based on Yoruguaman work: https://github.com/Ultrahead/SpeccyNextTools
+ ///
+ internal class BasConverter
+ {
+ private readonly TokenMap _tokenMap;
+ private readonly List _sortedKeys;
+
+ ///
+ /// The Auto-start line number.
+ /// Defaults to 32768 (No Auto-start).
+ /// Set only via the #autostart directive in the source file.
+ ///
+ public int AutoStartLine { get; private set; } = 32768;
+
+ ///
+ /// Initializes member variable fields of the class.
+ ///
+ public BasConverter()
+ {
+ // 1. Initialize the TokenMap dictionary.
+ // 2. Create a list of keys sorted by Length Descending.
+ // (This ensures greedy matching: e.g., "DEFPROC" is matched before "DEF").
+
+ _tokenMap = new TokenMap();
+ _sortedKeys = _tokenMap.Map.Keys
+ .OrderByDescending(k => k.Length)
+ .ToList();
+ }
+
+ ///
+ /// Reads a text file and converts it to a byte array of tokenized BASIC.
+ ///
+ /// Text to convert
+ /// Byte array representing the BASIC program.
+ public byte[] ConvertFile(string textData)
+ {
+ // 1. Read all lines from the source text file.
+ // 2. Initialize state variables (auto-line counter, output buffer).
+ // 3. Iterate through each line:
+ // a. Skip whitespace-only lines (source code formatting).
+ // b. Process directive (#autostart) then skip the line.
+ // c. Skip all other lines starting with # (source code comments).
+ // d. Parse explicit or implicit line numbers and tokenize content.
+ // 4. Return the aggregated binary data.
+
+ string[] lines = textData.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
+ var output = new List();
+
+ int currentLineNum = 10;
+
+ foreach (string line in lines)
+ {
+ string text = line.Trim();
+
+ // 1. Skip Empty Lines completely (do not generate a BASIC line)
+ if (string.IsNullOrWhiteSpace(text)) continue;
+
+ // 2. Handle Lines starting with #
+ if (text.StartsWith("#"))
+ {
+ // Check for directives
+ if (text.StartsWith("#autostart", StringComparison.OrdinalIgnoreCase))
+ {
+ var parts = text.Split([' '], StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length > 1 && int.TryParse(parts[1], out int autoStartVal))
+ {
+ AutoStartLine = autoStartVal;
+ }
+ }
+
+ // Whether it was a directive or a comment, skip it in the output.
+ continue;
+ }
+
+ // 3. Handle Standard Lines
+ int lineNum = currentLineNum;
+ string restOfLine = text;
+
+ Match match = Regex.Match(text, @"^(\d+)\s+(.*)");
+
+ if (match.Success)
+ {
+ lineNum = int.Parse(match.Groups[1].Value);
+ restOfLine = match.Groups[2].Value;
+ currentLineNum = lineNum + 10;
+ }
+ else
+ {
+ currentLineNum += 10;
+ }
+
+ byte[] lineBytes = ParseLine(lineNum, restOfLine);
+ output.AddRange(lineBytes);
+ }
+
+ return output.ToArray();
+ }
+
+ ///
+ /// Parses a single line of text into binary line format.
+ /// Structure: [LineNum(BE)] [Length(LE)] [Data...] [0x0D]
+ ///
+ /// The line number.
+ /// The text content of the line.
+ /// A byte array representing the binary line.
+ private byte[] ParseLine(int lineNum, string text)
+ {
+ // 1. Iterate through the text character by character.
+ // 2. Detect and process String Literals (preserve exactly).
+ // 3. Detect and process Numbers (convert to ASCII + 5-byte hidden Sinclair format).
+ // 4. Detect and process SPECIAL COMMENT (';' after colon or at start).
+ // 5. Detect and process Keywords (Greedy Match against TokenMap):
+ // a. If REM found, consume the rest of the line as a comment.
+ // b. If other keyword found, strip immediately following whitespace.
+ // 6. Fallback: Add character as literal ASCII.
+ // 7. Append End-of-Line marker (0x0D).
+ // 8. Prepend the Line Header (Line Number + Length) and return the byte array.
+
+ List lineData = new List();
+
+ for (int i = 0; i < text.Length; i++)
+ {
+ // String Literals
+ if (text[i] == '"')
+ {
+ int endQuote = text.IndexOf('"', i + 1);
+ if (endQuote == -1) endQuote = text.Length;
+
+ string literal = text.Substring(i, endQuote - i + 1);
+ lineData.AddRange(Encoding.ASCII.GetBytes(literal));
+ i = endQuote;
+ continue;
+ }
+
+ // Numbers
+ if (char.IsDigit(text[i]) || (text[i] == '.' && i + 1 < text.Length && char.IsDigit(text[i + 1])))
+ {
+ string numStr = "";
+ int j = i;
+ while (j < text.Length && (char.IsDigit(text[j]) || text[j] == '.'))
+ {
+ numStr += text[j];
+ j++;
+ }
+
+ if (double.TryParse(numStr, out double val))
+ {
+ lineData.AddRange(Encoding.ASCII.GetBytes(numStr));
+ lineData.Add(0x0E); // Hidden Marker
+ lineData.AddRange(SinclairNumber.Pack(val));
+ i = j - 1;
+ continue;
+ }
+ }
+
+ // COMMENT HANDLING: Strict check for ';comment' idiom
+ // Trigger: Semicolon at start of line OR Semicolon immediately preceded by Colon
+ if (text[i] == ';')
+ {
+ bool isComment = false;
+
+ // Look backwards skipping whitespace to find the context
+ int back = i - 1;
+ while (back >= 0 && text[back] == ' ') back--;
+
+ if (back < 0) isComment = true; // Start of line
+ else if (text[back] == ':') isComment = true; // Preceded by colon
+
+ if (isComment)
+ {
+ // Consume the rest of the line as literal text (do not tokenize)
+ string comment = text.Substring(i);
+ lineData.AddRange(Encoding.ASCII.GetBytes(comment));
+ i = text.Length;
+ continue;
+ }
+ }
+
+ // Keywords
+ bool matched = false;
+ foreach (string k in _sortedKeys)
+ {
+ if (i + k.Length > text.Length) continue;
+
+ if (string.Compare(text.Substring(i, k.Length), k, StringComparison.OrdinalIgnoreCase) != 0)
+ continue;
+
+ bool isAlphaToken = char.IsLetter(k[0]);
+ bool prevCharValid = (i == 0) || !char.IsLetter(text[i - 1]);
+ bool nextCharValid = (i + k.Length >= text.Length) || !char.IsLetterOrDigit(text[i + k.Length]);
+
+ if (isAlphaToken && (!prevCharValid || !nextCharValid)) continue;
+
+ byte token = _tokenMap.Map[k];
+ lineData.Add(token);
+ i += k.Length;
+ matched = true;
+
+ // REM handling
+ if (token == 0xEA)
+ {
+ if (i < text.Length)
+ {
+ string comment = text.Substring(i);
+ lineData.AddRange(Encoding.ASCII.GetBytes(comment));
+ i = text.Length;
+ }
+ }
+ else
+ {
+ // Strip trailing space
+ while (i < text.Length && text[i] == ' ') i++;
+ }
+
+ i--;
+ break;
+ }
+
+ if (matched) continue;
+
+ // Literal
+ lineData.Add((byte)text[i]);
+ }
+
+ lineData.Add(0x0D);
+
+ // Construct Line Header
+ List finalLine =
+ [
+ (byte)((lineNum >> 8) & 0xFF),
+ (byte)(lineNum & 0xFF)
+ ];
+
+ int length = lineData.Count;
+ finalLine.Add((byte)(length & 0xFF));
+ finalLine.Add((byte)((length >> 8) & 0xFF));
+
+ finalLine.AddRange(lineData);
+
+ return finalLine.ToArray();
+ }
+ }
+}
diff --git a/ZXBStudio/Common/Txt2Bas/Plus3Dos.cs b/ZXBStudio/Common/Txt2Bas/Plus3Dos.cs
new file mode 100644
index 0000000..0c7f310
--- /dev/null
+++ b/ZXBStudio/Common/Txt2Bas/Plus3Dos.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ZXBasicStudio.Common.Txt2Bas
+{
+ ///
+ /// Generates the 128-byte +3DOS Header required for Spectrum Next/Plus3 files.
+ /// Based on Yoruguaman work: https://github.com/Ultrahead/SpeccyNextTools
+ ///
+ internal static class Plus3Dos
+ {
+ ///
+ /// Creates a valid +3DOS header.
+ ///
+ /// The length of the BASIC program data (excluding header).
+ /// The line number to auto-run (or 32768 for none).
+ /// A 128-byte byte array containing the header.
+ public static byte[] CreateHeader(int basicLength, int autoStartLine)
+ {
+ // 1. Allocate a zero-filled 128-byte buffer.
+ // 2. Write the "PLUS3DOS" signature and Soft EOF (0x1A) to the start.
+ // 3. Set the Issue (1) and Version (0) bytes.
+ // 4. Calculate and write Total File Size (Header + BASIC Data) at offset 11.
+ // 5. Write BASIC-specific metadata (Type, Length, Vars Offset).
+ // 6. Set the Auto-start line (or 32768 if disabled).
+ // 7. Calculate the Checksum (Sum of bytes 0-126 modulo 256) and write it at byte 127.
+
+ byte[] header = new byte[128];
+ Array.Clear(header, 0, 128);
+
+ // Signature
+ byte[] sig = Encoding.ASCII.GetBytes("PLUS3DOS");
+ Array.Copy(sig, header, sig.Length);
+ header[8] = 0x1A;
+
+ // Version info
+ header[9] = 0x01;
+ header[10] = 0x00;
+
+ // Total File Size (Header + Data)
+ int totalFileSize = basicLength + 128;
+ BitConverter.GetBytes(totalFileSize).CopyTo(header, 11);
+
+ // BASIC Header Info
+ header[15] = 0x00; // Type: Program
+ BitConverter.GetBytes((ushort)basicLength).CopyTo(header, 16); // Length
+
+ // Auto-start
+ if (autoStartLine is >= 0 and < 32768)
+ {
+ BitConverter.GetBytes((ushort)autoStartLine).CopyTo(header, 18);
+ }
+ else
+ {
+ BitConverter.GetBytes((ushort)32768).CopyTo(header, 18);
+ }
+
+ // Vars Offset
+ BitConverter.GetBytes((ushort)basicLength).CopyTo(header, 20);
+
+ // Checksum
+ int sum = 0;
+ for (int i = 0; i < 127; i++) sum += header[i];
+ header[127] = (byte)(sum % 256);
+
+ return header;
+ }
+ }
+}
diff --git a/ZXBStudio/Common/Txt2Bas/SinclairNumber.cs b/ZXBStudio/Common/Txt2Bas/SinclairNumber.cs
new file mode 100644
index 0000000..6fa6dca
--- /dev/null
+++ b/ZXBStudio/Common/Txt2Bas/SinclairNumber.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ZXBasicStudio.Common.Txt2Bas
+{
+ ///
+ /// Handles the 5-byte floating point format used by Sinclair BASIC.
+ /// Based on Yoruguaman work: https://github.com/Ultrahead/SpeccyNextTools
+ ///
+ internal static class SinclairNumber
+ {
+ ///
+ /// Packs a double into the 5-byte internal format.
+ /// Currently, supports integer format optimization (00 Sign LSB MSB 00).
+ ///
+ /// The number to pack.
+ /// A 5-byte array representing the Sinclair number format.
+ public static byte[] Pack(double number)
+ {
+ // 1. Check if the number is a small integer (within +/- 65535).
+ // 2. If yes, create the integer format: 0x00, SignByte, LSB, MSB, 0x00.
+ // 3. If no, return empty zero bytes (full FP implementation omitted for brevity).
+
+ if (number % 1 == 0 && number is >= -65535 and <= 65535)
+ {
+ int val = (int)number;
+ byte sign = 0;
+
+ if (val < 0)
+ {
+ sign = 0xFF;
+ val = -val;
+ }
+
+ return [0x00, sign, (byte)(val & 0xFF), (byte)((val >> 8) & 0xFF), 0x00];
+ }
+
+ return "\0\0\0\0\0"u8.ToArray();
+ }
+ }
+}
diff --git a/ZXBStudio/Common/Txt2Bas/TokenMap.cs b/ZXBStudio/Common/Txt2Bas/TokenMap.cs
new file mode 100644
index 0000000..86fd474
--- /dev/null
+++ b/ZXBStudio/Common/Txt2Bas/TokenMap.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ZXBasicStudio.Common.Txt2Bas
+{
+ ///
+ /// Dictionary mapping String Keywords to Byte Tokens.
+ /// Includes Standard Spectrum and NextBASIC extensions.
+ /// Based on Yoruguaman work: https://github.com/Ultrahead/SpeccyNextTools
+ ///
+ internal class TokenMap
+ {
+ ///
+ /// A dictionary containing the mapping from Keyword string to Byte token.
+ ///
+ public readonly Dictionary Map = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Initializes the TokenMap with standard and extended Spectrum keywords.
+ ///
+ public TokenMap()
+ {
+ // 1. Populate the dictionary with NextBASIC extension tokens (0x80 - 0xA2).
+ // 2. Populate the dictionary with Standard 48K BASIC tokens (0xA3 - 0xFF).
+
+ // === ZX SPECTRUM NEXT EXTENSIONS (0x80 - 0xA2) ===
+ Map["PEEK$"] = 0x87; Map["REG"] = 0x88; Map["DPOKE"] = 0x89; Map["DPEEK"] = 0x8A;
+ Map["MOD"] = 0x8B; Map["<<"] = 0x8C; Map[">>"] = 0x8D; Map["UNTIL"] = 0x8E;
+ Map["ERROR"] = 0x8F; Map["ON"] = 0x90; Map["DEFPROC"] = 0x91; Map["ENDPROC"] = 0x92;
+ Map["PROC"] = 0x93; Map["LOCAL"] = 0x94; Map["DRIVER"] = 0x95; Map["WHILE"] = 0x96;
+ Map["REPEAT"] = 0x97; Map["ELSE"] = 0x98; Map["REMOUNT"] = 0x99; Map["BANK"] = 0x9A;
+ Map["TILE"] = 0x9B; Map["LAYER"] = 0x9C; Map["PALETTE"] = 0x9D; Map["SPRITE"] = 0x9E;
+ Map["PWD"] = 0x9F; Map["CD"] = 0xA0; Map["MKDIR"] = 0xA1; Map["RMDIR"] = 0xA2;
+
+ // === STANDARD ZX SPECTRUM 48K TOKENS (0xA3 - 0xFF) ===
+ Map["SPECTRUM"] = 0xA3; Map["PLAY"] = 0xA4; Map["RND"] = 0xA5; Map["INKEY$"] = 0xA6;
+ Map["PI"] = 0xA7; Map["FN"] = 0xA8; Map["POINT"] = 0xA9; Map["SCREEN$"] = 0xAA;
+ Map["ATTR"] = 0xAB; Map["AT"] = 0xAC; Map["TAB"] = 0xAD; Map["VAL$"] = 0xAE;
+ Map["CODE"] = 0xAF; Map["VAL"] = 0xB0; Map["LEN"] = 0xB1; Map["SIN"] = 0xB2;
+ Map["COS"] = 0xB3; Map["TAN"] = 0xB4; Map["ASN"] = 0xB5; Map["ACS"] = 0xB6;
+ Map["ATN"] = 0xB7; Map["LN"] = 0xB8; Map["EXP"] = 0xB9; Map["INT"] = 0xBA;
+ Map["SQR"] = 0xBB; Map["SGN"] = 0xBC; Map["ABS"] = 0xBD; Map["PEEK"] = 0xBE;
+ Map["IN"] = 0xBF; Map["USR"] = 0xC0; Map["STR$"] = 0xC1; Map["CHR$"] = 0xC2;
+ Map["NOT"] = 0xC3; Map["BIN"] = 0xC4; Map["OR"] = 0xC5; Map["AND"] = 0xC6;
+ Map["<="] = 0xC7; Map[">="] = 0xC8; Map["<>"] = 0xC9; Map["LINE"] = 0xCA;
+ Map["THEN"] = 0xCB; Map["TO"] = 0xCC; Map["STEP"] = 0xCD; Map["DEF FN"] = 0xCE;
+ Map["CAT"] = 0xCF; Map["FORMAT"] = 0xD0; Map["MOVE"] = 0xD1; Map["ERASE"] = 0xD2;
+ Map["OPEN #"] = 0xD3; Map["CLOSE #"] = 0xD4; Map["MERGE"] = 0xD5; Map["VERIFY"] = 0xD6;
+ Map["BEEP"] = 0xD7; Map["CIRCLE"] = 0xD8; Map["INK"] = 0xD9; Map["PAPER"] = 0xDA;
+ Map["FLASH"] = 0xDB; Map["BRIGHT"] = 0xDC; Map["INVERSE"] = 0xDD; Map["OVER"] = 0xDE;
+ Map["OUT"] = 0xDF; Map["LPRINT"] = 0xE0; Map["LLIST"] = 0xE1; Map["STOP"] = 0xE2;
+ Map["READ"] = 0xE3; Map["DATA"] = 0xE4; Map["RESTORE"] = 0xE5; Map["NEW"] = 0xE6;
+ Map["BORDER"] = 0xE7; Map["CONTINUE"] = 0xE8; Map["DIM"] = 0xE9; Map["REM"] = 0xEA;
+ Map["FOR"] = 0xEB; Map["GO TO"] = 0xEC; Map["GOTO"] = 0xEC; Map["GO SUB"] = 0xED;
+ Map["GOSUB"] = 0xED; Map["INPUT"] = 0xEE; Map["LOAD"] = 0xEF; Map["LIST"] = 0xF0;
+ Map["LET"] = 0xF1; Map["PAUSE"] = 0xF2; Map["NEXT"] = 0xF3; Map["POKE"] = 0xF4;
+ Map["PRINT"] = 0xF5; Map["PLOT"] = 0xF6; Map["RUN"] = 0xF7; Map["SAVE"] = 0xF8;
+ Map["RANDOMIZE"] = 0xF9; Map["IF"] = 0xFA; Map["CLS"] = 0xFB; Map["DRAW"] = 0xFC;
+ Map["CLEAR"] = 0xFD; Map["RETURN"] = 0xFE; Map["COPY"] = 0xFF;
+ }
+ }
+}
diff --git a/ZXBStudio/Common/Txt2Bas/Txt2BasConverter.cs b/ZXBStudio/Common/Txt2Bas/Txt2BasConverter.cs
new file mode 100644
index 0000000..519bc5b
--- /dev/null
+++ b/ZXBStudio/Common/Txt2Bas/Txt2BasConverter.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace ZXBasicStudio.Common.Txt2Bas
+{
+ ///
+ /// Converts plain text to ZX Spectrum .bas format
+ /// Based on Yoruguaman work: https://github.com/Ultrahead/SpeccyNextTools
+ ///
+ public static class Txt2BasConverter
+ {
+
+ public static byte[] Text2Bas(string text)
+ {
+ try
+ {
+ BasConverter converter = new BasConverter();
+ byte[] basData = converter.ConvertFile(text);
+
+ // Use the explicitly parsed AutoStartLine (defaults to 32768 if no #autostart found)
+ byte[] header = Plus3Dos.CreateHeader(basData.Length, converter.AutoStartLine);
+
+ var fileData = new List();
+ fileData.AddRange(header);
+ fileData.AddRange(basData);
+
+ return fileData.ToArray();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ return null;
+ }
+ }
+ }
+}
diff --git a/ZXBStudio/Dialogs/ZXAboutDialog.axaml b/ZXBStudio/Dialogs/ZXAboutDialog.axaml
index 7e43d70..3582e78 100644
--- a/ZXBStudio/Dialogs/ZXAboutDialog.axaml
+++ b/ZXBStudio/Dialogs/ZXAboutDialog.axaml
@@ -16,7 +16,7 @@
The ZX Basic Studio development team
- El Dr. Gusman, Boriel, Duefectu, AdolFITO, Hash6Iron, SirRickster
+ El Dr. Gusman, Boriel, Duefectu, AdolFITO, Hash6Iron, SirRickster and Yoruguaman
Many thanks to Boriel for the ZX Basic compiler and his support to this project.
(C) 2023, El Dr. Gusman
(C) 2025, Boriel & DuefectuCorp
diff --git a/ZXBStudio/Dialogs/ZXOptionsDialog.axaml.cs b/ZXBStudio/Dialogs/ZXOptionsDialog.axaml.cs
index df860c5..9ab0e4a 100644
--- a/ZXBStudio/Dialogs/ZXOptionsDialog.axaml.cs
+++ b/ZXBStudio/Dialogs/ZXOptionsDialog.axaml.cs
@@ -94,8 +94,9 @@ private async void BtnSelectNextEmulator_Click(object? sender, Avalonia.Interact
Title = "Select Next emulator path...",
FileTypeFilter = new[]
{
- new FilePickerFileType("CSpect") { Patterns = new[] { "CSpect.exe" } },
- new FilePickerFileType("ZEsarUX") { Patterns = new[] { "zesarux.exe" } },
+ new FilePickerFileType("MAME") { Patterns = new[] { "mame.exe", "mame" } },
+ new FilePickerFileType("CSpect") { Patterns = new[] { "CSpect.exe", "CSpect" } },
+ new FilePickerFileType("ZEsarUX") { Patterns = new[] { "zesarux.exe", "zesarux" } },
new FilePickerFileType("All files") { Patterns = new[] { "*", "*.*" } }
}
});
diff --git a/ZXBStudio/MainWindow.axaml.cs b/ZXBStudio/MainWindow.axaml.cs
index 0859a00..b14706b 100644
--- a/ZXBStudio/MainWindow.axaml.cs
+++ b/ZXBStudio/MainWindow.axaml.cs
@@ -35,6 +35,7 @@
using System.Resources;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.Arm;
+using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ZXBasicStudio.BuildSystem;
@@ -1500,6 +1501,10 @@ private async void BuildAndRun(object? sender, Avalonia.Interactivity.RoutedEven
errorMsg = "Error executing custom emulator launcher.";
}
}
+ else if (emulatorName.ToLower() == "mame")
+ {
+ errorMsg = BuildAndRun_MAME(emulatorPath, nextDrive, settings, project);
+ }
else if (emulatorName.ToLower() == "cspect")
{
if (!File.Exists(emulatorPath))
@@ -1549,7 +1554,7 @@ private async void BuildAndRun(object? sender, Avalonia.Interactivity.RoutedEven
errorMsg = "Error executing emulator: " + ex.Message + ex.StackTrace;
if (ex.InnerException != null)
{
- var ex2= ex.InnerException;
+ var ex2 = ex.InnerException;
errorMsg += "\r\nInner Exception: " + ex.Message + ex.StackTrace;
}
}
@@ -2507,6 +2512,139 @@ private void MnuReportBug_Click(object? sender, RoutedEventArgs e)
#endregion
+
+ #region MAME
+
+ private string BuildAndRun_MAME(string emulatorPath, string nextDrive, ZXBuildSettings settings, ZXProjectManager project)
+ {
+ if (!File.Exists(emulatorPath))
+ {
+ return "Emulator not found on: " + emulatorPath;
+ }
+
+ var basePath =Directory.GetParent(Directory.GetParent(emulatorPath).FullName).FullName;
+ var sdimagePath= Path.Combine(basePath, "nextsdimage", "cspect-next-2gb.img");
+
+ // Delete /nextzxos/autoexec.1st
+ Hdfmonkey($"rm \"{sdimagePath}\" /nextzxos/autoexec.1st", basePath);
+
+ // Create autoexec.bas
+ var nextPath =Path.GetFileNameWithoutExtension(project.GetMainFile());
+ string autoexec = $@"#autostart 10
+10 CD ""{nextPath}""
+20 .nexload {nextPath}.nex
+";
+ var bytes=Common.Txt2Bas.Txt2BasConverter.Text2Bas(autoexec);
+ var autoexecPath = Path.Combine(basePath, "hdfmonkey", "autoexec.bas");
+ File.WriteAllBytes(autoexecPath, bytes);
+
+ // Copy /nextzxos/autoexec.bas
+ if (!Hdfmonkey($"put \"{sdimagePath}\" \"{autoexecPath}\" /nextzxos/autoexec.bas", basePath))
+ {
+ return "Error copying autoexec.bas to the emulator image.";
+ }
+
+ // Empty /zxbsdev folder
+ // TODO: Sync!
+ Hdfmonkey($"rm \"{sdimagePath}\" /{nextPath}", basePath);
+ // Create /zxbsdev
+ Hdfmonkey($"mkdir \"{sdimagePath}\" /{nextPath}", basePath);
+ // Copy nextdrive to /zxbsdev
+ Hdfmonkey($"putdir \"{sdimagePath}\" \"{nextDrive}\" /{nextPath}/", basePath);
+
+ // Launch mame
+ {
+ outLog.Writer.WriteLine("Launching MAME...");
+ var parameters = $"-ui_active -nounevenstretch -aspect 2:1 -video bgfx -bgfx_screen_chains unfiltered -window -skip_gameinfo -mouse_device none -confirm_quit tbblue -hard1 {sdimagePath} -plugin zxbs -debug -debugger none -console";
+ var psi = new ProcessStartInfo()
+ {
+ FileName = emulatorPath,
+ Arguments = parameters,
+ WorkingDirectory = Directory.GetParent(emulatorPath).FullName,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+ var process = new Process()
+ {
+ StartInfo = psi
+ };
+
+ outLog.Writer.WriteLine($">mame {parameters}");
+
+ process.OutputDataReceived += MAME_DataReceived;
+ process.ErrorDataReceived += MAME_ErrorReceived;
+
+ process.Start();
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ process.WaitForExit();
+
+ process.OutputDataReceived -= MAME_DataReceived;
+ process.ErrorDataReceived -= MAME_ErrorReceived;
+ }
+ return "";
+ }
+
+ private bool Hdfmonkey(string parameters, string basePath)
+ {
+ var hdfPath = Path.Combine(basePath, "hdfmonkey");
+
+ outLog.Writer.WriteLine($"> hdfmonkey {parameters}");
+ var psi = new ProcessStartInfo()
+ {
+ FileName = Path.Combine(hdfPath, "hdfmonkey.exe"),
+ Arguments = parameters,
+ WorkingDirectory = hdfPath,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+ var process = new Process()
+ {
+ StartInfo = psi
+ };
+ process.Start();
+ var output = process.StandardOutput.ReadToEnd();
+ var error = process.StandardError.ReadToEnd();
+ process.WaitForExit();
+
+ if (!string.IsNullOrEmpty(error))
+ {
+ outLog.Writer.WriteLine("hdfmonkey ERROR> " + error);
+ }
+ if (!string.IsNullOrEmpty(output))
+ {
+ outLog.Writer.WriteLine("hdfmonkey> " + error);
+ }
+
+ return string.IsNullOrEmpty(error);
+ }
+
+ private void MAME_ErrorReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (e.Data != null)
+ {
+ outLog.Writer.WriteLine("MAME ERROR> " + e.Data.ToStringNoNull());
+ }
+ }
+
+
+ private void MAME_DataReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (e.Data != null)
+ {
+ outLog.Writer.WriteLine("MAME> " + e.Data.ToStringNoNull());
+ }
+ }
+
+
+ #endregion
+
}
public enum PreferredSourceType
diff --git a/ZXBStudio/ZXBasicStudio.csproj b/ZXBStudio/ZXBasicStudio.csproj
index 0edf91e..d2ec7a2 100644
--- a/ZXBStudio/ZXBasicStudio.csproj
+++ b/ZXBStudio/ZXBasicStudio.csproj
@@ -13,7 +13,7 @@
ZX Basic Studio
False
- 1.7.0.0
+ 1.8.0.1
diff --git a/ZXBStudio/version.txt b/ZXBStudio/version.txt
index 1f0cf6b..6482d6f 100644
--- a/ZXBStudio/version.txt
+++ b/ZXBStudio/version.txt
@@ -1 +1 @@
-1.7.0.0
\ No newline at end of file
+1.8.0.1
\ No newline at end of file