Skip to content

Commit

Permalink
feat: DOS INT21H 0x4B (WIP)
Browse files Browse the repository at this point in the history
Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>
  • Loading branch information
maximilien-noal committed Oct 21, 2024
1 parent cb583fb commit 5fee758
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 28 deletions.
48 changes: 46 additions & 2 deletions src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -675,6 +676,13 @@ public void GetSetControlBreak() {
}
}

/// <summary>
/// Returns the current value of the IN DOS flag in the BX register.
/// </summary>
public void GetInDosFlag() {
//State.ES
}

/// <summary>
/// Returns the current MS-DOS time in CH (hour), CL (minute), DH (second), and DL (millisecond) from the host's DateTime.Now.
/// </summary>
Expand Down Expand Up @@ -730,17 +738,53 @@ private enum TypeOfLoad : byte {
/// <returns>
/// CF is cleared on success. <br/>
/// CF is set on error.
/// TODO: This needs the DOS Swappable Area, and a lot of other DOS globals (current drive, current folder, ...)
/// </returns>
/// </summary>
/// <param name="calledFromVm">Whether the code was called by the emulator.</param>
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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Spice86.Shared.Emulator.Memory;

/// <summary>
/// Representation of an EXE file as a MemoryBasedDataStructure
/// Representation of an EXE file as it is stored on disk, loaded into memory.
/// </summary>
public class ExeFile : MemoryBasedDataStructure {
/// <summary>
Expand Down
17 changes: 11 additions & 6 deletions src/Spice86.Core/Emulator/OperatingSystem/Dos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public class Dos {
private readonly KeyboardStreamedInput _keyboardStreamedInput;
private readonly ILoggerService _loggerService;

/// <summary>
/// Gets or sets the last DOS error code.
/// </summary>
public ErrorCode ErrorCode { get; set; }

/// <summary>
/// Gets the INT 20h DOS services.
/// </summary>
Expand All @@ -48,10 +53,12 @@ public class Dos {
/// </summary>
public DosInt28Handler DosInt28Handler { get; }

public DosTables DosTables { get; }

/// <summary>
/// Gets the country ID from the CountryInfo table
/// </summary>
public byte CurrentCountryId => CountryInfo.Country;
public CountryId CurrentCountryId => DosTables.CountryInfo.CountryId;

/// <summary>
/// Gets the list of virtual devices.
Expand All @@ -78,11 +85,6 @@ public class Dos {
/// </summary>
public DosFileManager FileManager { get; }

/// <summary>
/// Gets the global DOS memory structures.
/// </summary>
public CountryInfo CountryInfo { get; } = new();

/// <summary>
/// Gets the current DOS master environment variables.
/// </summary>
Expand Down Expand Up @@ -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);
Expand Down
25 changes: 25 additions & 0 deletions src/Spice86.Core/Emulator/OperatingSystem/Enums/DosConstants.cs
Original file line number Diff line number Diff line change
@@ -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;
}
13 changes: 13 additions & 0 deletions src/Spice86.Core/Emulator/OperatingSystem/Enums/DosReturnMode.cs
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 12 additions & 3 deletions src/Spice86.Core/Emulator/OperatingSystem/Enums/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ namespace Spice86.Core.Emulator.OperatingSystem.Enums;
/// <summary>
/// Defines error codes for MS-DOS operations.
/// </summary>
public enum ErrorCode : byte
{
public enum ErrorCode : byte {
/// <summary>
/// No error occurred during the operation.
/// </summary>
Expand Down Expand Up @@ -103,5 +102,15 @@ public enum ErrorCode : byte
/// <summary>
/// No more files match the criteria
/// </summary>
NoMoreMatchingFiles
NoMoreMatchingFiles,

/// <summary>
/// File, or a part of the file, is locked
/// </summary>
LockViolation = 0x33,

/// <summary>
/// File already exists in the directory
/// </summary>
FileAlreadyExists = 0x80,
}
88 changes: 75 additions & 13 deletions src/Spice86.Core/Emulator/OperatingSystem/Structures/CountryInfo.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Represents information about the formatting of dates, times, and numbers for a specific country.
/// </summary>
public class CountryInfo {
[DebuggerDisplay("{CountryId}")]
public class CountryInfo : MemoryBasedDataStructure {

public CountryInfo(IByteReaderWriter byteReaderWriter) : base(byteReaderWriter, 0) {
}

/// <summary>
/// Gets or sets the country code for the specific country, where 1 represents the United States, 2 represents Canada, and so on.
/// </summary>
public byte Country { get; set; } = (byte)CountryId.UnitedStates;
public byte Country { get => UInt8[0]; set => UInt8[0] = value; }

public CountryId CountryId => (CountryId)Country;

/// <summary>
/// Gets or sets the format of the date in the specified country.
/// </summary>
public byte DateFormat { get; set; }
public byte DateFormat { get => UInt8[1]; set => UInt8[1] = value; }

/// <summary>
/// Gets or sets the currency string for the specified country.
/// </summary>
public string CurrencyString { get => GetZeroTerminatedString(2, 5); set => SetZeroTerminatedString(2, value, 5); }

/// <summary>
/// Gets or sets the thousands separator for the specified country.
/// </summary>
public byte ThousandsSeparator { get => UInt8[8]; set => UInt8[8] = value; }

/// <summary>
/// Gets or sets the decimal separator for the specified country.
/// </summary>
public byte DecimalSeparator { get => UInt8[9]; set => UInt8[9] = value; }

/// <summary>
/// Gets or sets the date separator for the specified country.
/// </summary>
public byte DateSeparator { get => UInt8[10]; set => UInt8[10] = value; }

/// <summary>
/// Gets or sets the time separator for the specified country.
/// </summary>
public byte TimeSeparator { get => UInt8[11]; set => UInt8[11] = value; }

/// <summary>
/// Gets or sets the currency format for the specified country.
/// </summary>
public byte CurrencyFormat { get => UInt8[12]; set => UInt8[12] = value; }

/// <summary>
/// Gets or sets the number of digits after the decimal for the specified country.
/// </summary>
public byte DigitsAfterDecimal { get => UInt8[13]; set => UInt8[13] = value; }

/// <summary>
/// Gets or sets the time format for the specified country.
/// </summary>
public byte TimeFormat { get => UInt8[14]; set => UInt8[14] = value; }

/// <summary>
/// Gets or sets the casemap for the specified country.
/// </summary>
public byte Casemap { get => UInt8[15]; set => UInt8[15] = value; }

/// <summary>
/// Gets or sets the data separator for the specified country.
/// </summary>
public byte DataSeparator { get => UInt8[19]; set => UInt8[19] = value; }

/// <summary>
/// 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.
/// </summary>
public byte DateSeparator { get; set; }
public byte Reserved1 { get => UInt8[20]; set => UInt8[20] = value; }

/// <summary>
/// Gets or sets the format of the time in the specified country.
/// Gets or sets the reserved value 2 for the specified country.
/// </summary>
public byte TimeFormat { get; set; }
public byte Reserved2 { get => UInt8[21]; set => UInt8[21] = value; }

/// <summary>
/// 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.
/// </summary>
public byte TimeSeparator { get; set; }
public byte Reserved3 { get => UInt8[22]; set => UInt8[22] = value; }

/// <summary>
/// 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.
/// </summary>
public byte ThousandsSeparator { get; set; }
public byte Reserved4 { get => UInt8[23]; set => UInt8[23] = value; }

/// <summary>
/// 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.
/// </summary>
public byte DecimalSeparator { get; set; }
public byte Reserved5 { get => UInt8[24]; set => UInt8[24] = value; }
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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);
}
Loading

0 comments on commit 5fee758

Please sign in to comment.