From a1d32fd4b41f9564be225bc3e3b8743fa841ff9d Mon Sep 17 00:00:00 2001 From: Maximilien Noal Date: Mon, 30 Sep 2024 20:35:09 +0200 Subject: [PATCH] chore: DOS INT21H 0x4B supporting structures Signed-off-by: Maximilien Noal --- .../InterruptHandlers/Dos/DosInt21Handler.cs | 48 ++++++- .../Emulator/LoadableFile/Dos/Exe/ExeFile.cs | 2 +- .../Emulator/OperatingSystem/Dos.cs | 17 ++- .../OperatingSystem/Enums/DosConstants.cs | 25 ++++ .../OperatingSystem/Enums/DosReturnMode.cs | 13 ++ .../OperatingSystem/Enums/ErrorCode.cs | 15 ++- .../OperatingSystem/Structures/CountryInfo.cs | 88 +++++++++++-- .../Structures/DosCommandTail.cs | 16 +++ .../Structures/DosEnvironmentBlock.cs | 13 ++ .../Structures/DosExecParameterBlock.cs | 45 +++++++ .../Structures/DosProgramSegmentPrefix.cs | 115 ++++++++++++++++ .../Structures/DosSwappableDataArea.cs | 123 ++++++++++++++++++ .../OperatingSystem/Structures/DosTables.cs | 8 +- src/Spice86.Core/Emulator/ProgramExecutor.cs | 4 +- src/Spice86.Core/Spice86.Core.csproj | 1 + src/Spice86.Shared/Spice86.Shared.csproj | 1 + 16 files changed, 506 insertions(+), 28 deletions(-) create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Enums/DosConstants.cs create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Enums/DosReturnMode.cs create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Structures/DosCommandTail.cs create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Structures/DosEnvironmentBlock.cs create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Structures/DosExecParameterBlock.cs create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Structures/DosProgramSegmentPrefix.cs create mode 100644 src/Spice86.Core/Emulator/OperatingSystem/Structures/DosSwappableDataArea.cs diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs index 579cf723a4..3d2b0fe5b3 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs @@ -88,6 +88,7 @@ private void FillDispatchTable() { AddAction(0x2F, GetDiskTransferAddress); AddAction(0x30, GetDosVersion); AddAction(0x33, GetSetControlBreak); + AddAction(0x34, GetInDosFlag); AddAction(0x35, GetInterruptVector); AddAction(0x36, GetFreeDiskSpace); AddAction(0x38, () => SetCountryCode(true)); @@ -675,6 +676,13 @@ public void GetSetControlBreak() { } } + /// + /// Returns the current value of the IN DOS flag in the BX register. + /// + public void GetInDosFlag() { + //State.ES + } + /// /// Returns the current MS-DOS time in CH (hour), CL (minute), DH (second), and DL (millisecond) from the host's DateTime.Now. /// @@ -730,17 +738,53 @@ private enum TypeOfLoad : byte { /// /// CF is cleared on success.
/// CF is set on error. + /// TODO: This needs the DOS Swappable Area, and a lot of other DOS globals (current drive, current folder, ...) ///
/// /// Whether the code was called by the emulator. public void LoadAndOrExecuteProgram(bool calledFromVm) { bool success = false; + byte typeOfLoadByte = State.AL; + if (!Enum.IsDefined(typeof(TypeOfLoad), typeOfLoadByte)) { + SetCarryFlag(false, calledFromVm); + return; + } TypeOfLoad typeOfLoad = (TypeOfLoad)State.AL; string programName = GetZeroTerminatedStringAtDsDx(); + string? fullHostPath = _dosFileManager.TryGetFullHostPathFromDos(programName); + + if(string.IsNullOrWhiteSpace(fullHostPath) || !File.Exists(fullHostPath)) { + SetCarryFlag(false, calledFromVm); + return; + } if (LoggerService.IsEnabled(LogEventLevel.Verbose)) { LoggerService.Verbose("LOAD AND/OR EXECUTE PROGRAM {TypeOfLoad}, {ProgramName}", typeOfLoad, programName); } + + bool isComFile = string.Equals(Path.GetExtension(programName).ToLowerInvariant(), ".com", StringComparison.OrdinalIgnoreCase); + + switch (typeOfLoad) { + case TypeOfLoad.LoadAndExecute: + if (isComFile) { + LoadAndExecComFile(fullHostPath, "", 0x1000); + } else { + LoadAndExecExeFile(fullHostPath, "", 0x1000); + } + success = true; + break; + case TypeOfLoad.LoadOnly: + // Not implemented + success = false; + break; + case TypeOfLoad.LoadOverlay: + // Not implemented + success = false; + break; + default: + SetCarryFlag(false, calledFromVm); + return; + } SetCarryFlag(success, calledFromVm); } @@ -1108,7 +1152,7 @@ private void SetStateFromDosFileOperationResult(bool calledFromVm, DosFileOperat private const ushort ComOffset = 0x100; - internal void LoadEXEFile(string hostFile, string? arguments, ushort startSegment) { + internal void LoadAndExecExeFile(string hostFile, string? arguments, ushort startSegment) { byte[] exe = File.ReadAllBytes(hostFile); if (LoggerService.IsEnabled(LogEventLevel.Debug)) { LoggerService.Debug("Exe size: {ExeSize}", exe.Length); @@ -1186,7 +1230,7 @@ private void SetEntryPoint(ushort cs, ushort ip) { } } - internal void LoadCOMFile(string hostFile, string? arguments, ushort startSegment) { + internal void LoadAndExecComFile(string hostFile, string? arguments, ushort startSegment) { new PspGenerator(Memory, _dos.EnvironmentVariables, _dosMemoryManager, _dosFileManager).GeneratePsp(startSegment, arguments); byte[] com = File.ReadAllBytes(hostFile); uint physicalStartAddress = MemoryUtils.ToPhysicalAddress(startSegment, ComOffset); diff --git a/src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeFile.cs b/src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeFile.cs index 5205325f56..61ff66d94c 100644 --- a/src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeFile.cs +++ b/src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeFile.cs @@ -5,7 +5,7 @@ using Spice86.Shared.Emulator.Memory; /// -/// Representation of an EXE file as a MemoryBasedDataStructure +/// Representation of an EXE file as it is stored on disk, loaded into memory. /// public class ExeFile : MemoryBasedDataStructure { /// diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Dos.cs b/src/Spice86.Core/Emulator/OperatingSystem/Dos.cs index 7fbd7c9a7e..3307215b39 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Dos.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Dos.cs @@ -28,6 +28,11 @@ public class Dos { private readonly KeyboardStreamedInput _keyboardStreamedInput; private readonly ILoggerService _loggerService; + /// + /// Gets or sets the last DOS error code. + /// + public ErrorCode ErrorCode { get; set; } + /// /// Gets the INT 20h DOS services. /// @@ -48,10 +53,12 @@ public class Dos { /// public DosInt28Handler DosInt28Handler { get; } + public DosTables DosTables { get; } + /// /// Gets the country ID from the CountryInfo table /// - public byte CurrentCountryId => CountryInfo.Country; + public CountryId CurrentCountryId => DosTables.CountryInfo.CountryId; /// /// Gets the list of virtual devices. @@ -78,11 +85,6 @@ public class Dos { /// public DosFileManager FileManager { get; } - /// - /// Gets the global DOS memory structures. - /// - public CountryInfo CountryInfo { get; } = new(); - /// /// Gets the current DOS master environment variables. /// @@ -113,7 +115,10 @@ public Dos(IMemory memory, Cpu cpu, KeyboardInt16Handler keyboardInt16Handler, _state = cpu.State; _vgaFunctionality = vgaFunctionality; _keyboardStreamedInput = new KeyboardStreamedInput(keyboardInt16Handler); + AddDefaultDevices(); + DosTables = new(memory); + FileManager = new DosFileManager(_memory, cDriveFolderPath, executablePath, _loggerService, this.Devices); MemoryManager = new DosMemoryManager(_memory, _loggerService); DosInt20Handler = new DosInt20Handler(_memory, cpu, _loggerService); diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Enums/DosConstants.cs b/src/Spice86.Core/Emulator/OperatingSystem/Enums/DosConstants.cs new file mode 100644 index 0000000000..3cac6d156f --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Enums/DosConstants.cs @@ -0,0 +1,25 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +internal static class DosConstants { + public const ushort DosSdaSegment = 0xb2; + public const ushort DosSdaOffset = 0x0; + public const ushort DosCurrentDirectoryStructureSegment = 0x108; + public const ushort DosInfoBlockSegment = 0x80; + public const ushort DosConDrvSegment = 0Xa0; + public const ushort DosConStringSegment = 0xa8; + public const ushort DosFirstShell = 0x118; + public const ushort DosFirstUsableMemorySegment = 0x16f; + public const ushort DosPrivateSegmentStart = 0xc800; + public const ushort DosPrivateSegmentEnd = 0xd000; + public const int SftHeaderSize = 6; + public const int SftEntrySize = 59; + public const uint SftEndPointer = 0xffffffff; + public const ushort SftNextTableOffset = 0x0; + public const ushort SftNumberOfFilesOffset = 0x04; + public const int FakeSftEntries = 16; +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Enums/DosReturnMode.cs b/src/Spice86.Core/Emulator/OperatingSystem/Enums/DosReturnMode.cs new file mode 100644 index 0000000000..ab3cc7d160 --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Enums/DosReturnMode.cs @@ -0,0 +1,13 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +internal enum DosReturnMode : byte { + Exit = 0, + CtrlC = 1, + Abort = 2, + TerminateAndStayResident = 3 +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Enums/ErrorCode.cs b/src/Spice86.Core/Emulator/OperatingSystem/Enums/ErrorCode.cs index fb11ed8bcd..b05cc2c979 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Enums/ErrorCode.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Enums/ErrorCode.cs @@ -3,8 +3,7 @@ namespace Spice86.Core.Emulator.OperatingSystem.Enums; /// /// Defines error codes for MS-DOS operations. /// -public enum ErrorCode : byte -{ +public enum ErrorCode : byte { /// /// No error occurred during the operation. /// @@ -103,5 +102,15 @@ public enum ErrorCode : byte /// /// No more files match the criteria /// - NoMoreMatchingFiles + NoMoreMatchingFiles, + + /// + /// File, or a part of the file, is locked + /// + LockViolation = 0x33, + + /// + /// File already exists in the directory + /// + FileAlreadyExists = 0x80, } diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/CountryInfo.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/CountryInfo.cs index d2d7e6e3f0..a16d72f564 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Structures/CountryInfo.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/CountryInfo.cs @@ -1,42 +1,104 @@ namespace Spice86.Core.Emulator.OperatingSystem.Structures; +using Spice86.Core.Emulator.Memory.ReaderWriter; using Spice86.Core.Emulator.OperatingSystem.Enums; +using Spice86.Core.Emulator.ReverseEngineer.DataStructure; + +using System.Diagnostics; + /// /// Represents information about the formatting of dates, times, and numbers for a specific country. /// -public class CountryInfo { +[DebuggerDisplay("{CountryId}")] +public class CountryInfo : MemoryBasedDataStructure { + + public CountryInfo(IByteReaderWriter byteReaderWriter) : base(byteReaderWriter, 0) { + } + /// /// Gets or sets the country code for the specific country, where 1 represents the United States, 2 represents Canada, and so on. /// - public byte Country { get; set; } = (byte)CountryId.UnitedStates; + public byte Country { get => UInt8[0]; set => UInt8[0] = value; } + + public CountryId CountryId => (CountryId)Country; /// /// Gets or sets the format of the date in the specified country. /// - public byte DateFormat { get; set; } + public byte DateFormat { get => UInt8[1]; set => UInt8[1] = value; } + + /// + /// Gets or sets the currency string for the specified country. + /// + public string CurrencyString { get => GetZeroTerminatedString(2, 5); set => SetZeroTerminatedString(2, value, 5); } + + /// + /// Gets or sets the thousands separator for the specified country. + /// + public byte ThousandsSeparator { get => UInt8[8]; set => UInt8[8] = value; } + + /// + /// Gets or sets the decimal separator for the specified country. + /// + public byte DecimalSeparator { get => UInt8[9]; set => UInt8[9] = value; } + + /// + /// Gets or sets the date separator for the specified country. + /// + public byte DateSeparator { get => UInt8[10]; set => UInt8[10] = value; } + + /// + /// Gets or sets the time separator for the specified country. + /// + public byte TimeSeparator { get => UInt8[11]; set => UInt8[11] = value; } + + /// + /// Gets or sets the currency format for the specified country. + /// + public byte CurrencyFormat { get => UInt8[12]; set => UInt8[12] = value; } + + /// + /// Gets or sets the number of digits after the decimal for the specified country. + /// + public byte DigitsAfterDecimal { get => UInt8[13]; set => UInt8[13] = value; } + + /// + /// Gets or sets the time format for the specified country. + /// + public byte TimeFormat { get => UInt8[14]; set => UInt8[14] = value; } + + /// + /// Gets or sets the casemap for the specified country. + /// + public byte Casemap { get => UInt8[15]; set => UInt8[15] = value; } + + /// + /// Gets or sets the data separator for the specified country. + /// + public byte DataSeparator { get => UInt8[19]; set => UInt8[19] = value; } /// - /// Gets or sets the character used to separate the components of a date in the specified country. + /// Gets or sets the reserved value 1 for the specified country. /// - public byte DateSeparator { get; set; } + public byte Reserved1 { get => UInt8[20]; set => UInt8[20] = value; } /// - /// Gets or sets the format of the time in the specified country. + /// Gets or sets the reserved value 2 for the specified country. /// - public byte TimeFormat { get; set; } + public byte Reserved2 { get => UInt8[21]; set => UInt8[21] = value; } /// - /// Gets or sets the character used to separate the components of a time in the specified country. + /// Gets or sets the reserved value 3 for the specified country. /// - public byte TimeSeparator { get; set; } + public byte Reserved3 { get => UInt8[22]; set => UInt8[22] = value; } /// - /// Gets or sets the character used to separate thousands in a number in the specified country. + /// Gets or sets the reserved value 4 for the specified country. /// - public byte ThousandsSeparator { get; set; } + public byte Reserved4 { get => UInt8[23]; set => UInt8[23] = value; } /// - /// Gets or sets the character used to separate the whole number part from the fractional part in a number in the specified country. + /// Gets or sets the reserved value 5 for the specified country. /// - public byte DecimalSeparator { get; set; } + public byte Reserved5 { get => UInt8[24]; set => UInt8[24] = value; } } \ No newline at end of file diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosCommandTail.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosCommandTail.cs new file mode 100644 index 0000000000..c54d46da1e --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosCommandTail.cs @@ -0,0 +1,16 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Structures; + +using Spice86.Core.Emulator.Memory.ReaderWriter; +using Spice86.Core.Emulator.ReverseEngineer.DataStructure; + +public class DosCommandTail : MemoryBasedDataStructure { + public DosCommandTail(IByteReaderWriter byteReaderWriter, uint baseAddress) : base(byteReaderWriter, baseAddress) { + } + + public string Command { + get => GetZeroTerminatedString(BaseAddress, MaxCharacterLength); + set => SetZeroTerminatedString(BaseAddress, value, MaxCharacterLength); + } + + public const int MaxCharacterLength = 128; +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosEnvironmentBlock.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosEnvironmentBlock.cs new file mode 100644 index 0000000000..014dcddd35 --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosEnvironmentBlock.cs @@ -0,0 +1,13 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Structures; + +using Spice86.Core.Emulator.Memory.ReaderWriter; +using Spice86.Core.Emulator.ReverseEngineer.DataStructure; + +public abstract class DosEnvironmentBlock : MemoryBasedDataStructure { + protected DosEnvironmentBlock(IByteReaderWriter byteReaderWriter, uint baseAddress) : base(byteReaderWriter, baseAddress) { + } + + public abstract string? GetEnvironmentVariable(string variableName); + + public abstract void SetEnvironmentVariable(string variableName, string value); +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosExecParameterBlock.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosExecParameterBlock.cs new file mode 100644 index 0000000000..d1d6e59ec1 --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosExecParameterBlock.cs @@ -0,0 +1,45 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Structures; + +using Spice86.Core.Emulator.Memory.ReaderWriter; + +/// +/// This structure is used by function INT 21H 0x4B (LOAD AND EXEC PROGRAM).
+/// Programs that call this function pass a pointer to this structure as a parameter in ES:BX.
+/// In DOSBox source code, this is the DOS_ParamBlock +///
+internal class DosExecParameterBlock : DosMemoryControlBlock { + public DosExecParameterBlock(IByteReaderWriter byteReaderWriter, uint baseAddress) : base(byteReaderWriter, baseAddress) { + } + + /// + /// Gets or the sets the Segment (address) of the environement for the child process (0x0: duplicate the current PSP's environment) + /// + public ushort EnvironmentSegment { get => UInt16[0x0]; set { UInt16[0x0] = value; } } + + /// + /// Gets or sets the address of the command line used to execute the program. Copied at the end of the child PSP. + /// + public uint CommandTailAddress { get => UInt32[0x2]; set { UInt32[0x2] = value; } } + + public DosCommandTail CommandTail { get => new (ByteReaderWriter, CommandTailAddress); } + + /// + /// Gets or sets the address of the first file control block. Unopened FCB to be copied to the child PSP. + /// + public uint FirstFileControlBlockAddress { get => UInt32[0x6]; set { UInt32[0x6] = value; } } + + /// + /// Gets or sets the address of the second file control block. Unopened FCB to be copied to the child PSP. + /// + public uint SecondFileControlBlockAddress { get => UInt32[0x10]; set { UInt32[0x10] = value; } } + + /// + /// Gets or sets the load segment. This is where the new program will start execution. + /// + public ushort LoadSegment { get => UInt16[0x16]; set { UInt16[0x16] = value; } } + + /// + /// Gets or sets the relocation segment. This influences function INT 21H 0x4B when in OVERLAY mode in the program relocation phase. + /// + public ushort Relocation { get => UInt16[0x18]; set { UInt16[0x18] = value; } } +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosProgramSegmentPrefix.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosProgramSegmentPrefix.cs new file mode 100644 index 0000000000..147a45d1c1 --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosProgramSegmentPrefix.cs @@ -0,0 +1,115 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Structures; + +using Spice86.Core.Emulator.Memory.ReaderWriter; +using Spice86.Shared.Utils; + +/// +/// Represents the Program Segment Prefix (PSP) +/// +public sealed class DosProgramSegmentPrefix : DosEnvironmentBlock { + public DosProgramSegmentPrefix(IByteReaderWriter byteReaderWriter, uint baseAddress) : base(byteReaderWriter, baseAddress) { + } + + public void MakeNew(ushort memSize) { + + } + + public void CloseFiles() { + + } + + /// + /// Gets the of the PSP as a segment. + /// + public ushort Segment => MemoryUtils.ToSegment(BaseAddress); + + public void SaveVectors() { + + } + + public void RestoreVectors() { + + } + + public override string? GetEnvironmentVariable(string variableName) { + throw new NotImplementedException(); + } + + public override void SetEnvironmentVariable(string variableName, string value) { + throw new NotImplementedException(); + } + + /// + /// CP/M like exit point for INT 0x20. (machine code: 0xCD, 0x20). Old way to exit the program. + /// + public byte[] Exit { get => GetData(0, 2); set { LoadData(0, value); } } + + /// + /// Segment of first byte beyond the end of the program image. Reserved. + /// + public ushort NextSegment { get => UInt16[2]; set => UInt16[2] = value; } + + /// + /// (reserved) + /// + public byte Reserved { get => UInt8[4]; set => UInt8[4] = value; } + + /// + /// Far call to DOS INT 0x21 dispatcher. Obsolete. + /// + public byte FarCall { get => UInt8[5]; set => UInt8[5] = value; } + + /// + /// On exit, DOS copies this to the INT 0x22 vector. + /// + public uint TerminateAddress { get => UInt32[6]; set => UInt32[6] = value; } + + /// + /// On exit, DOS copies this to the INT 0x23 vector. + /// + public uint BreakAddress { get => UInt32[10]; set => UInt32[10] = value; } + + /// + /// On exit, DOS copies this to the INT 0x24 vector. + /// + public uint CriticalErrorAddress { get => UInt32[14]; set => UInt32[14] = value; } + + /// + /// Segment of PSP of parent program. + /// + public ushort ParentProgramSegmentPrefix { get => UInt16[18]; set => UInt16[18] = value; } + + public byte[] Files { get => GetData(20, 20); set { LoadData(20, value); } } + + public ushort EnvironmentTableSegment { get => UInt16[40]; set => UInt16[40] = value; } + + public uint StackPointer { get => UInt32[42]; set => UInt32[42] = value; } + + public ushort MaximumOpenFiles { get => UInt16[46]; set => UInt16[46] = value; } + + public uint FileTableAddress { get => UInt32[48]; set => UInt32[48] = value; } + + public uint PreviousPspAddress { get => UInt32[52]; set => UInt32[52] = value; } + + public byte InterimFlag { get => UInt8[56]; set => UInt8[56] = value; } + + public byte TrueNameFlag { get => UInt8[57]; set => UInt8[57] = value; } + + public ushort NNFlags { get => UInt16[58]; set => UInt16[58] = value; } + + public byte DosVersionMajor { get => UInt8[60]; set => UInt8[60] = value; } + + public byte DosVersionMinor { get => UInt8[61]; set => UInt8[61] = value; } + + public byte[] Unused { get => GetData(62, 14); set { LoadData(62, value); } } + + public byte[] Service { get => GetData(76, 3); set { LoadData(76, value); } } + + public byte[] Unused2 { get => GetData(79, 9); set { LoadData(79, value); } } + + public byte[] FirstFileControlBlock { get => GetData(88, 16); set { LoadData(88, value); } } + + public byte[] SecondFileControlBlock { get => GetData(104, 16); set { LoadData(104, value); } } + + public byte[] Unused3 { get => GetData(120, 4); set { LoadData(120, value); } } +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosSwappableDataArea.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosSwappableDataArea.cs new file mode 100644 index 0000000000..d50344417c --- /dev/null +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosSwappableDataArea.cs @@ -0,0 +1,123 @@ +namespace Spice86.Core.Emulator.OperatingSystem.Structures; + +using Spice86.Core.Emulator.Memory.ReaderWriter; +using Spice86.Core.Emulator.ReverseEngineer.DataStructure; + +/// +/// Represents a DOS SDA (Swappable Data Area) in emulated memory. +/// Real implementation of MS-DOS SDA has way more fields than this, but DOSBox doesn't emulate it, so we don't either. +/// +public sealed class DosSwappableDataArea : MemoryBasedDataStructure { + public DosSwappableDataArea(IByteReaderWriter byteReaderWriter, uint baseAddress) : base(byteReaderWriter, baseAddress) { + } + + /// + /// Gets or sets the DOS critical error flag. + /// + public byte CriticalErrorFlag { get => UInt8[0x0]; set => UInt8[0x0] = value; } + + /// + /// Gets or sets the INDOS flag (count of active INT 0x21 calls). + /// + public byte InDosFlag { get => UInt8[0x1]; set => UInt8[0x1] = value; } + + /// + /// Gets or sets the drive on which the current critical error occurred or 0xFFh. + /// + public byte DriveCriticalError { + get => UInt8[0x2]; + set => UInt8[0x2] = value; + } + + /// + /// Gets or sets the locus of the last error. + /// + public byte LocusOfLastError { + get => UInt8[0x3]; + set => UInt8[0x3] = value; + } + + /// + /// Gets or sets the extended error code of the last error. + /// + public ushort ExtendedErrorCode { + get => UInt16[0x4]; + set => UInt16[0x4] = value; + } + + /// + /// Gets or sets the suggested action for the last error. + /// + public byte SuggestedAction { + get => UInt8[0x6]; + set => UInt8[0x6] = value; + } + + /// + /// Gets or sets the class of the last error. + /// + public byte ErrorClass { + get => UInt8[0x7]; + set => UInt8[0x7] = value; + } + + /// + /// Gets or sets the ES:DI pointer for the last error. + /// + public uint LastErrorPointer { + get => UInt32[0x8]; + set => UInt32[0x8] = value; + } + + /// + /// Gets or sets the current DTA (Disk Transfer Area). + /// + public uint CurrentDiskTransferArea { + get => UInt32[0xC]; + set => UInt32[0xC] = value; + } + + /// + /// Gets or sets the current PSP (Program Segment Prefix). + /// + public ushort CurrentProgramSegmentPrefix { + get => UInt16[0x10]; + set => UInt16[0x10] = value; + } + + /// + /// Gets or sets the stored value of the SP register. Used by INT 0x23. + /// + public ushort SpInt23 { + get => UInt16[0x12]; + set => UInt16[0x12] = value; + } + + /// + /// Gets or sets the return code from the last process termination. + /// + public ushort ReturnCode { + get => UInt16[0x14]; + set => UInt16[0x14] = value; + } + + /// + /// Gets or sets the current drive. 0x0: A:, 0x1: B:, etc. + /// + public byte CurrentDrive { get => UInt8[0x16]; set => UInt8[0x16] = value; } + + /// + /// Gets or sets the keyboard extended break flag. + /// + public byte ExtendedBreakFlag { get => UInt8[0x17]; set => UInt8[0x17] = value; } + + /// + /// Gets or sets the flag for code page switching. Unused since we don't support code pages. + /// + public byte CodePageSwitchingFlag { get => UInt8[0x18]; set => UInt8[0x18] = value; } + + /// + /// Gets or sets the copy of the previous byte. MS-DOS uses it for DOS INT 0x28 Abort call. Unused in our implementation. + /// + public byte PreviousByteInt28 { get => UInt8[0x19]; set => UInt8[0x19] = value; } +} diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosTables.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosTables.cs index b91d0daf69..6660a24bbc 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosTables.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosTables.cs @@ -1,5 +1,7 @@ namespace Spice86.Core.Emulator.OperatingSystem.Structures; +using Spice86.Core.Emulator.Memory.ReaderWriter; + /// /// Centralizes global DOS memory structures /// @@ -7,5 +9,9 @@ public class DosTables { /// /// The current country information /// - public CountryInfo CountryInfo { get; set; } = new(); + public CountryInfo CountryInfo { get; init; } + + public DosTables(IByteReaderWriter memory) { + CountryInfo = new(memory); + } } \ No newline at end of file diff --git a/src/Spice86.Core/Emulator/ProgramExecutor.cs b/src/Spice86.Core/Emulator/ProgramExecutor.cs index ef06f29d44..7d1a847ff5 100644 --- a/src/Spice86.Core/Emulator/ProgramExecutor.cs +++ b/src/Spice86.Core/Emulator/ProgramExecutor.cs @@ -94,10 +94,10 @@ public void Run() { string fileExtension = Path.GetExtension(_configuration.Exe).ToLowerInvariant(); switch (fileExtension) { case ".exe": - _dosInt21Handler.LoadEXEFile(_configuration.Exe, _configuration.ExeArgs, _configuration.ProgramEntryPointSegment); + _dosInt21Handler.LoadAndExecExeFile(_configuration.Exe, _configuration.ExeArgs, _configuration.ProgramEntryPointSegment); break; case ".com": - _dosInt21Handler.LoadCOMFile(_configuration.Exe, _configuration.ExeArgs, _configuration.ProgramEntryPointSegment); + _dosInt21Handler.LoadAndExecComFile(_configuration.Exe, _configuration.ExeArgs, _configuration.ProgramEntryPointSegment); break; default: new BiosLoader(_memory, _state, _configuration.Exe, _loggerService).LoadHostFile(); diff --git a/src/Spice86.Core/Spice86.Core.csproj b/src/Spice86.Core/Spice86.Core.csproj index cbfb44a34a..b83c075a02 100644 --- a/src/Spice86.Core/Spice86.Core.csproj +++ b/src/Spice86.Core/Spice86.Core.csproj @@ -6,6 +6,7 @@ enable nullable true + 1591;1572;1573;1570;1587;1574 diff --git a/src/Spice86.Shared/Spice86.Shared.csproj b/src/Spice86.Shared/Spice86.Shared.csproj index 5f2729f154..3734d595b6 100644 --- a/src/Spice86.Shared/Spice86.Shared.csproj +++ b/src/Spice86.Shared/Spice86.Shared.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + 1591;1572;1573;1570;1587;1574 Nullable