From 9adefcf781847f1c48db55af8ddb9294c5fbcc57 Mon Sep 17 00:00:00 2001 From: Maximilien Noal Date: Mon, 1 Sep 2025 18:23:17 +0200 Subject: [PATCH 1/3] refactor: renamed FloppyDiskImage class --- .../Structures/{FloppyDiskDrive.cs => FloppyDiskImage.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/Spice86.Core/Emulator/OperatingSystem/Structures/{FloppyDiskDrive.cs => FloppyDiskImage.cs} (74%) diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/FloppyDiskDrive.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/FloppyDiskImage.cs similarity index 74% rename from src/Spice86.Core/Emulator/OperatingSystem/Structures/FloppyDiskDrive.cs rename to src/Spice86.Core/Emulator/OperatingSystem/Structures/FloppyDiskImage.cs index 21b81ccc4d..4c148642d7 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Structures/FloppyDiskDrive.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/FloppyDiskImage.cs @@ -3,8 +3,8 @@ /// Represents a disk image mounted as a floppy disk drive. /// /// Unimplemented. -public class FloppyDiskDrive : DosDriveBase { - public FloppyDiskDrive() { +public class FloppyDiskImage : DosDriveBase { + public FloppyDiskImage() { IsRemovable = true; } } From b38a35b82162b7e9b51bc2b28a224f02e9e2e8de Mon Sep 17 00:00:00 2001 From: Maximilien Noal Date: Mon, 1 Sep 2025 18:25:23 +0200 Subject: [PATCH 2/3] refactor: renamed VDrive -> HostFolderDrive --- .../InterruptHandlers/Dos/DosInt21Handler.cs | 2 +- .../OperatingSystem/DosDriveManager.cs | 52 +++++++++---------- .../OperatingSystem/DosFileManager.cs | 2 +- .../OperatingSystem/DosPathResolver.cs | 8 +-- .../{VirtualDrive.cs => HostFolderDrive.cs} | 2 +- 5 files changed, 33 insertions(+), 33 deletions(-) rename src/Spice86.Core/Emulator/OperatingSystem/Structures/{VirtualDrive.cs => HostFolderDrive.cs} (91%) diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs index aa10a3e607..18f2039eb3 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs @@ -1027,7 +1027,7 @@ public override void Run() { /// The number of potentially valid drive letters in AL. /// public void SelectDefaultDrive() { - if(_dosDriveManager.TryGetValue(DosDriveManager.DriveLetters.ElementAtOrDefault(State.DL).Key, out VirtualDrive? mountedDrive)) { + if(_dosDriveManager.TryGetValue(DosDriveManager.DriveLetters.ElementAtOrDefault(State.DL).Key, out HostFolderDrive? mountedDrive)) { _dosDriveManager.CurrentDrive = mountedDrive; } if (State.DL > DosDriveManager.MaxDriveCount && LoggerService.IsEnabled(LogEventLevel.Error)) { diff --git a/src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs b/src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs index c0988ed261..2854abe4cc 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs @@ -13,8 +13,8 @@ /// /// The class responsible for centralizing all the mounted DOS drives. /// -public class DosDriveManager : IDictionary { - private readonly SortedDictionary _driveMap = new(); +public class DosDriveManager : IDictionary { + private readonly SortedDictionary _driveMap = new(); private readonly ILoggerService _loggerService; /// @@ -31,7 +31,7 @@ public DosDriveManager(ILoggerService loggerService, string? cDriveFolderPath, s cDriveFolderPath = ConvertUtils.ToSlashFolderPath(cDriveFolderPath); _driveMap.Add('A', null); _driveMap.Add('B', null); - _driveMap.Add('C', new VirtualDrive { DriveLetter = 'C', MountedHostDirectory = cDriveFolderPath, CurrentDosDirectory = "" }); + _driveMap.Add('C', new HostFolderDrive { DriveLetter = 'C', MountedHostDirectory = cDriveFolderPath, CurrentDosDirectory = "" }); CurrentDrive = _driveMap.ElementAt(2).Value!; if(loggerService.IsEnabled(Serilog.Events.LogEventLevel.Verbose)) { loggerService.Verbose("DOS Drives initialized: {@Drives}", _driveMap.Values); @@ -41,7 +41,7 @@ public DosDriveManager(ILoggerService loggerService, string? cDriveFolderPath, s /// /// The currently selected drive. /// - public VirtualDrive CurrentDrive { get; set; } + public HostFolderDrive CurrentDrive { get; set; } internal static readonly ImmutableSortedDictionary DriveLetters = new Dictionary() { { 'A', 0 }, @@ -92,57 +92,57 @@ public byte NumberOfPotentiallyValidDriveLetters { } } - public ICollection Keys => ((IDictionary)_driveMap).Keys; + public ICollection Keys => ((IDictionary)_driveMap).Keys; - public ICollection Values => ((IDictionary)_driveMap).Values; + public ICollection Values => ((IDictionary)_driveMap).Values; - public int Count => ((ICollection>)_driveMap).Count; + public int Count => ((ICollection>)_driveMap).Count; - public bool IsReadOnly => ((ICollection>)_driveMap).IsReadOnly; + public bool IsReadOnly => ((ICollection>)_driveMap).IsReadOnly; - public VirtualDrive this[char key] { get => ((IDictionary)_driveMap)[key]; set => ((IDictionary)_driveMap)[key] = value; } + public HostFolderDrive this[char key] { get => ((IDictionary)_driveMap)[key]; set => ((IDictionary)_driveMap)[key] = value; } public const int MaxDriveCount = 26; - public void Add(char key, VirtualDrive value) { - ((IDictionary)_driveMap).Add(key, value); + public void Add(char key, HostFolderDrive value) { + ((IDictionary)_driveMap).Add(key, value); } public bool ContainsKey(char key) { - return ((IDictionary)_driveMap).ContainsKey(key); + return ((IDictionary)_driveMap).ContainsKey(key); } public bool Remove(char key) { - return ((IDictionary)_driveMap).Remove(key); + return ((IDictionary)_driveMap).Remove(key); } - public bool TryGetValue(char key, [MaybeNullWhen(false)] out VirtualDrive value) { - return ((IDictionary)_driveMap).TryGetValue(key, out value); + public bool TryGetValue(char key, [MaybeNullWhen(false)] out HostFolderDrive value) { + return ((IDictionary)_driveMap).TryGetValue(key, out value); } - public void Add(KeyValuePair item) { - ((ICollection>)_driveMap).Add(item); + public void Add(KeyValuePair item) { + ((ICollection>)_driveMap).Add(item); } public void Clear() { - ((ICollection>)_driveMap).Clear(); + ((ICollection>)_driveMap).Clear(); } - public bool Contains(KeyValuePair item) { - return ((ICollection>)_driveMap).Contains(item); + public bool Contains(KeyValuePair item) { + return ((ICollection>)_driveMap).Contains(item); } - public void CopyTo(KeyValuePair[] array, int arrayIndex) { - ((ICollection>)_driveMap).CopyTo(array, arrayIndex); + public void CopyTo(KeyValuePair[] array, int arrayIndex) { + ((ICollection>)_driveMap).CopyTo(array, arrayIndex); } - public bool Remove(KeyValuePair item) { - return ((ICollection>)_driveMap).Remove(item); + public bool Remove(KeyValuePair item) { + return ((ICollection>)_driveMap).Remove(item); } - public IEnumerator> GetEnumerator() { - return ((IEnumerable>)_driveMap).GetEnumerator(); + public IEnumerator> GetEnumerator() { + return ((IEnumerable>)_driveMap).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { diff --git a/src/Spice86.Core/Emulator/OperatingSystem/DosFileManager.cs b/src/Spice86.Core/Emulator/OperatingSystem/DosFileManager.cs index 685083d63b..a7d82f6925 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/DosFileManager.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/DosFileManager.cs @@ -1117,7 +1117,7 @@ public DosFileOperationResult IoControl(State state) { { // 1) Build the 11-byte volume label (padded with spaces) // 1) pull the raw label (or default), split name/ext - VirtualDrive vDrive = _dosDriveManager.ElementAtOrDefault(drive).Value; + HostFolderDrive vDrive = _dosDriveManager.ElementAtOrDefault(drive).Value; string driveLabel = vDrive.Label.ToUpperInvariant(); diff --git a/src/Spice86.Core/Emulator/OperatingSystem/DosPathResolver.cs b/src/Spice86.Core/Emulator/OperatingSystem/DosPathResolver.cs index ad6a4a09f9..ad8b47b5c2 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/DosPathResolver.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/DosPathResolver.cs @@ -35,13 +35,13 @@ public DosPathResolver(DosDriveManager dosDriveManager) { public DosFileOperationResult GetCurrentDosDirectory(byte driveNumber, out string currentDir) { //0 = default drive if (driveNumber == 0 && _dosDriveManager.Count > 0) { - VirtualDrive virtualDrive = _dosDriveManager.CurrentDrive; + HostFolderDrive virtualDrive = _dosDriveManager.CurrentDrive; currentDir = virtualDrive.CurrentDosDirectory; return DosFileOperationResult.NoValue(); } else { char driveLetter = DosDriveManager.DriveLetters.Keys.ElementAtOrDefault(driveNumber - 1); if (_dosDriveManager.TryGetValue(driveLetter, - out VirtualDrive? virtualDrive)) { + out HostFolderDrive? virtualDrive)) { currentDir = virtualDrive.CurrentDosDirectory; return DosFileOperationResult.NoValue(); } @@ -50,7 +50,7 @@ public DosFileOperationResult GetCurrentDosDirectory(byte driveNumber, out strin return DosFileOperationResult.Error(DosErrorCode.InvalidDrive); } - private static string GetFullCurrentDosPathOnDrive(VirtualDrive virtualDrive) => + private static string GetFullCurrentDosPathOnDrive(HostFolderDrive virtualDrive) => Path.Combine($"{virtualDrive.DosVolume}{DirectorySeparatorChar}", virtualDrive.CurrentDosDirectory); internal static string GetExeParentFolder(string? exe) { @@ -62,7 +62,7 @@ internal static string GetExeParentFolder(string? exe) { return string.IsNullOrWhiteSpace(parent) ? fallbackValue : ConvertUtils.ToSlashFolderPath(parent); } - private static bool IsWithinMountPoint(string hostFullPath, VirtualDrive virtualDrive) => hostFullPath.StartsWith(virtualDrive.MountedHostDirectory); + private static bool IsWithinMountPoint(string hostFullPath, HostFolderDrive virtualDrive) => hostFullPath.StartsWith(virtualDrive.MountedHostDirectory); /// /// Sets the current DOS folder. diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/VirtualDrive.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/HostFolderDrive.cs similarity index 91% rename from src/Spice86.Core/Emulator/OperatingSystem/Structures/VirtualDrive.cs rename to src/Spice86.Core/Emulator/OperatingSystem/Structures/HostFolderDrive.cs index ea4979dba4..323711dbdd 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Structures/VirtualDrive.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/HostFolderDrive.cs @@ -2,7 +2,7 @@ /// /// Represents a host folder used as a drive by DOS. /// -public class VirtualDrive : DosDriveBase { +public class HostFolderDrive : DosDriveBase { /// /// The full host path to the mounted folder. This path serves as the root of the DOS drive. /// From 1db97f5b8634ab887c60f66dbc847c7044c028d6 Mon Sep 17 00:00:00 2001 From: Maximilien Noal Date: Sun, 5 Oct 2025 11:04:36 +0200 Subject: [PATCH 3/3] feat: COMMAND.COM with AUTOEXEC.BAT generation (WIP) --- .../OperatingSystem/Programs/AutoExecFile.cs | 24 +++++++++++++++++++ .../Programs/CommandInterpreter.cs | 16 +++++++++++++ .../Programs/DosInternalProgram.cs | 14 +++++++++++ .../Structures/DosProgramSegmentPrefix.cs | 8 +++++-- .../Structures/IVirtualFile.cs | 4 ++++ 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Programs/AutoExecFile.cs create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Programs/CommandInterpreter.cs create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Programs/DosInternalProgram.cs diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Programs/AutoExecFile.cs b/src/Spice86.Core/Emulator/OperatingSystem/Programs/AutoExecFile.cs new file mode 100644 index 0000000000..d560c4e565 --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Programs/AutoExecFile.cs @@ -0,0 +1,24 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Programs; + +using Spice86.Core.Emulator.OperatingSystem.Structures; + +internal class AutoExecFile : IVirtualFile { + private readonly Configuration _configuration; + + public AutoExecFile(Configuration configuration) { + _configuration = configuration; + } + + public string Name { + get => "AUTOEXEC.BAT"; + set => throw new InvalidOperationException("Cannot rename a built-in operating system file"); + } + + public IEnumerable GetLines() { + string? hostFilePath = _configuration.Exe; + string? arguments = _configuration.ExeArgs; + string? cDrivePath = _configuration.CDrive; + string commandLine = $"{Path.GetRelativePath(cDrivePath!, hostFilePath!)} {arguments}"; + yield return commandLine; + } +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Programs/CommandInterpreter.cs b/src/Spice86.Core/Emulator/OperatingSystem/Programs/CommandInterpreter.cs new file mode 100644 index 0000000000..e2a78c9661 --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Programs/CommandInterpreter.cs @@ -0,0 +1,16 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Programs; + +using Spice86.Core.Emulator.InterruptHandlers.Common.MemoryWriter; +using Spice86.Core.Emulator.Memory.ReaderWriter; + +internal class CommandInterpreter : DosInternalProgram { + public CommandInterpreter(MemoryAsmWriter memoryAsmWriter, IByteReaderWriter byteReaderWriter, uint baseAddress) + : base(memoryAsmWriter, byteReaderWriter, baseAddress) { + + } + + public override string Name { + get => "COMMAND.COM"; + set => throw new InvalidOperationException("Cannot rename a built-in operating system file"); + } +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Programs/DosInternalProgram.cs b/src/Spice86.Core/Emulator/OperatingSystem/Programs/DosInternalProgram.cs new file mode 100644 index 0000000000..d8def81b1c --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Programs/DosInternalProgram.cs @@ -0,0 +1,14 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Programs; + +using Spice86.Core.Emulator.InterruptHandlers.Common.MemoryWriter; +using Spice86.Core.Emulator.Memory.ReaderWriter; +using Spice86.Core.Emulator.OperatingSystem.Structures; + +internal abstract class DosInternalProgram : DosProgramSegmentPrefix, IVirtualFile { + protected DosInternalProgram(MemoryAsmWriter memoryAsmWriter, + IByteReaderWriter byteReaderWriter, uint baseAddress) + : base(byteReaderWriter, baseAddress) { + } + + public abstract string Name { get; set; } +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosProgramSegmentPrefix.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosProgramSegmentPrefix.cs index 7e2aaba50d..a80aca2d86 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosProgramSegmentPrefix.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosProgramSegmentPrefix.cs @@ -1,5 +1,6 @@ namespace Spice86.Core.Emulator.OperatingSystem.Structures; +using Spice86.Core.Emulator.InterruptHandlers.Common.MemoryWriter; using Spice86.Core.Emulator.Memory.ReaderWriter; using Spice86.Core.Emulator.ReverseEngineer.DataStructure; using Spice86.Core.Emulator.ReverseEngineer.DataStructure.Array; @@ -10,10 +11,13 @@ /// Represents the Program Segment Prefix (PSP) /// [DebuggerDisplay("BaseAddress={BaseAddress}, Parent={ParentProgramSegmentPrefix}, EnvSegment={EnvironmentTableSegment}, NextSegment={NextSegment}, StackPointer={StackPointer}, Cmd={DosCommandTail.Command}")] -public sealed class DosProgramSegmentPrefix : MemoryBasedDataStructure { +public class DosProgramSegmentPrefix : MemoryBasedDataStructure { public const ushort MaxLength = 0x80 + 128; - public DosProgramSegmentPrefix(IByteReaderWriter byteReaderWriter, uint baseAddress) : base(byteReaderWriter, baseAddress) { + public DosProgramSegmentPrefix(IByteReaderWriter byteReaderWriter, uint baseAddress) + : base(byteReaderWriter, baseAddress) { + Exit[0] = 0xCD; // INT instruction + Exit[1] = 0x20; // DOS INT 20h } /// diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/IVirtualFile.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/IVirtualFile.cs index 0c06747d52..9edd4bcdd3 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Structures/IVirtualFile.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/IVirtualFile.cs @@ -1,7 +1,11 @@ namespace Spice86.Core.Emulator.OperatingSystem.Structures; + +using System.ComponentModel.DataAnnotations; + public interface IVirtualFile { /// /// The DOS file name of the file or device. /// + [Range(0, 13)] public string Name { get; set; } }