diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/IVesaBiosExtension.cs b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/IVesaBiosExtension.cs
new file mode 100644
index 0000000000..5c07c7f575
--- /dev/null
+++ b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/IVesaBiosExtension.cs
@@ -0,0 +1,49 @@
+namespace Spice86.Core.Emulator.InterruptHandlers.VGA;
+
+///
+/// "The standard provides a set of functions which an application program can use
+/// to A) obtain information about the capabilities and characteristics of a
+/// specific Super VGA implementation and B) to control the operation of such
+/// hardware in terms of video mode initialization and video memory access.
+/// The functions are provided as an extension to the VGA BIOS video services, accessed
+/// through interrupt 10h."
+/// VESA Super VGA BIOS Extension Standard #VS911022, October 22, 1991, VBE Version 1.2
+///
+public interface IVesaBiosExtension {
+ ///
+ /// "Function 00h - Return Super VGA Information.
+ /// The purpose of this function is to provide information to the calling program
+ /// about the general capabilities of the Super VGA environment. The function fills
+ /// an information block structure at the address specified by the caller.
+ /// The information block size is 256 bytes."
+ /// Input: AH = 4Fh Super VGA support, AL = 00h Return Super VGA information, ES:DI = Pointer to buffer.
+ /// Output: AX = Status (All other registers are preserved).
+ ///
+ void VbeGetControllerInfo();
+
+ ///
+ /// "Function 01h - Return Super VGA mode information.
+ /// This function returns information about a specific Super VGA video mode that was
+ /// returned by Function 0. The function fills a mode information block structure
+ /// at the address specified by the caller. The mode information block size is
+ /// maximum 256 bytes."
+ /// Input: AH = 4Fh Super VGA support, AL = 01h Return Super VGA mode information,
+ /// CX = Super VGA video mode (mode number must be one of those returned by Function 0),
+ /// ES:DI = Pointer to 256 byte buffer.
+ /// Output: AX = Status (All other registers are preserved).
+ ///
+ void VbeGetModeInfo();
+
+ ///
+ /// "Function 02h - Set Super VGA video mode.
+ /// This function initializes a video mode. The BX register contains the mode to
+ /// set. The format of VESA mode numbers is described in chapter 2. If the mode
+ /// cannot be set, the BIOS should leave the video environment unchanged and return
+ /// a failure error code."
+ /// Input: AH = 4Fh Super VGA support, AL = 02h Set Super VGA video mode,
+ /// BX = Video mode (D0-D14 = Video mode, D15 = Clear memory flag:
+ /// 0 = Clear video memory, 1 = Don't clear video memory).
+ /// Output: AX = Status (All other registers are preserved).
+ ///
+ void VbeSetMode();
+}
diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaBios.cs b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaBios.cs
index c3467635a7..dcc9374909 100644
--- a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaBios.cs
+++ b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaBios.cs
@@ -10,6 +10,8 @@ namespace Spice86.Core.Emulator.InterruptHandlers.VGA;
using Spice86.Core.Emulator.InterruptHandlers.VGA.Enums;
using Spice86.Core.Emulator.InterruptHandlers.VGA.Records;
using Spice86.Core.Emulator.Memory;
+using Spice86.Core.Emulator.Memory.ReaderWriter;
+using Spice86.Core.Emulator.ReverseEngineer.DataStructure;
using Spice86.Shared.Emulator.Memory;
using Spice86.Shared.Interfaces;
using Spice86.Shared.Utils;
@@ -17,11 +19,259 @@ namespace Spice86.Core.Emulator.InterruptHandlers.VGA;
///
/// A VGA BIOS implementation.
///
-public class VgaBios : InterruptHandler, IVideoInt10Handler {
+public class VgaBios : InterruptHandler, IVideoInt10Handler, IVesaBiosExtension {
private readonly BiosDataArea _biosDataArea;
private readonly ILoggerService _logger;
private readonly IVgaFunctionality _vgaFunctions;
+ ///
+ /// "Several new BIOS calls have been defined to support Super VGA modes. For
+ /// maximum compatibility with the standard VGA BIOS, these calls are grouped under
+ /// one function number."
+ /// VBE (VESA BIOS Extension) function codes (AL register values when AH=4Fh).
+ /// "The designated Super VGA extended function number is 4Fh."
+ ///
+ private enum VbeFunction : byte {
+ ///
+ /// "Function 00h - Return Super VGA Information"
+ ///
+ GetControllerInfo = 0x00,
+ ///
+ /// "Function 01h - Return Super VGA mode information"
+ ///
+ GetModeInfo = 0x01,
+ ///
+ /// "Function 02h - Set Super VGA video mode"
+ ///
+ SetMode = 0x02
+ }
+
+ ///
+ /// "Every function returns status information in the AX register. The format of the
+ /// status word is as follows:
+ /// AL == 4Fh: Function is supported
+ /// AL != 4Fh: Function is not supported
+ /// AH == 00h: Function call successful
+ /// AH == 01h: Function call failed"
+ /// VBE status codes returned in AX register.
+ ///
+ private enum VbeStatus : ushort {
+ ///
+ /// "AL == 4Fh: Function is supported, AH == 00h: Function call successful"
+ ///
+ Success = 0x004F,
+ ///
+ /// "AL == 4Fh: Function is supported, AH == 01h: Function call failed"
+ ///
+ Failed = 0x014F
+ }
+
+ ///
+ /// INT 10h AH=12h (Video Subsystem Configuration) subfunctions (BL register values).
+ ///
+ private enum VideoSubsystemFunction : byte {
+ EgaVgaInformation = 0x10,
+ SelectScanLines = 0x30,
+ DefaultPaletteLoading = 0x31,
+ VideoEnableDisable = 0x32,
+ SummingToGrayScales = 0x33,
+ CursorEmulation = 0x34,
+ DisplaySwitch = 0x35,
+ VideoScreenOnOff = 0x36
+ }
+
+ ///
+ /// "The format of VESA mode numbers is as follows:
+ /// D0-D8 = Mode number (If D8 == 0, not VESA defined; If D8 == 1, VESA defined)
+ /// D9-D14 = Reserved by VESA for future expansion (= 0)
+ /// D15 = Reserved (= 0)"
+ ///
+ /// VBE mode flags (used in BX register for VBE Set Mode function).
+ /// "Input: BX = Video mode
+ /// D0-D14 = Video mode
+ /// D15 = Clear memory flag (0 = Clear video memory, 1 = Don't clear video memory)"
+ ///
+ /// Note: Bit 14 (UseLinearFrameBuffer) is VBE 2.0+ only, ignored in VBE 1.0/1.2.
+ ///
+ [Flags]
+ private enum VbeModeFlags : ushort {
+ None = 0x0000,
+ ///
+ /// Use linear frame buffer (VBE 2.0+ only, ignored in VBE 1.0/1.2).
+ ///
+ UseLinearFrameBuffer = 0x4000,
+ ///
+ /// "D15 = Clear memory flag (1 = Don't clear video memory)"
+ ///
+ DontClearMemory = 0x8000,
+ ///
+ /// "D0-D8 = Mode number" (bits 0-8, mask for extracting mode number)
+ ///
+ ModeNumberMask = 0x01FF
+ }
+
+ ///
+ /// VBE-related constants from VESA Super VGA BIOS Extension Standard #VS911022, VBE Version 1.2.
+ ///
+ private static class VbeConstants {
+ ///
+ /// "The current VESA version number is 1.2."
+ /// VBE 1.0 version number in BCD format (major.minor = 0x0100 = 1.0).
+ ///
+ public const ushort Version10 = 0x0100;
+
+ ///
+ /// "D0 = DAC is switchable (0 = DAC is fixed width, with 6-bits per primary color,
+ /// 1 = DAC width is switchable)"
+ /// Capability bit indicating DAC width is switchable.
+ ///
+ public const uint DacSwitchableCapability = 0x00000001;
+
+ ///
+ /// "The TotalMemory field indicates the amount of memory installed on the VGA
+ /// board. Its value represents the number of 64kb blocks of memory currently
+ /// installed."
+ /// Total memory: 1MB = 16 blocks of 64KB each.
+ ///
+ public const ushort TotalMemory1MB = 16;
+
+ ///
+ /// OEM identification string for Spice86 VBE implementation.
+ ///
+ public const string OemString = "Spice86 VBE";
+
+ ///
+ /// Offset from VbeInfoBlock base where OEM string is written (beyond 256-byte structure).
+ ///
+ public const uint OemStringOffset = 256;
+
+ ///
+ /// Offset from VbeInfoBlock base where mode list is written (after OEM string).
+ ///
+ public const uint ModeListOffset = 280;
+
+ ///
+ /// "The list of mode numbers is terminated by a -1 (0FFFFh)."
+ /// Mode list terminator value.
+ ///
+ public const ushort ModeListTerminator = 0xFFFF;
+
+ ///
+ /// "To date, VESA has defined a 7-bit video mode number, 6Ah, for the 800x600,
+ /// 16-color, 4-plane graphics mode. The corresponding 15-bit mode number for this
+ /// mode is 102h."
+ /// VESA mode 102h: 800x600, 16 colors (4-plane planar).
+ ///
+ public const ushort VesaMode800x600x16 = 0x102;
+
+ ///
+ /// Internal VGA mode 6Ah corresponding to VESA mode 102h (800x600x16).
+ ///
+ public const int InternalMode800x600x16 = 0x6A;
+ }
+
+ ///
+ /// VBE Mode Info Block constants.
+ ///
+ private static class VbeModeInfoConstants {
+ ///
+ /// Mode attributes: D0=1 (supported), D1=1 (extended info), D2=0, D3=1 (color), D4=1 (graphics) = 0x001B
+ ///
+ public const ushort ModeAttributesSupported = 0x001B;
+ ///
+ /// "D0 = Window supported, D1 = Window readable, D2 = Window writeable" = 0x07
+ ///
+ public const byte WindowAttributesReadWriteSupported = 0x07;
+ public const byte WindowAttributesNotSupported = 0x00;
+ ///
+ /// "WinGranularity specifies the smallest boundary, in KB" = 64KB
+ ///
+ public const ushort WindowGranularity64KB = 64;
+ ///
+ /// "WinSize specifies the size of the window in KB" = 64KB
+ ///
+ public const ushort WindowSize64KB = 64;
+ ///
+ /// "WinASegment address...in CPU address space" = 0xA000
+ ///
+ public const ushort WindowASegmentAddress = 0xA000;
+ public const ushort WindowBSegmentAddress = 0x0000;
+ ///
+ /// "The XCharCellSize...size of the character cell in pixels" = 8
+ ///
+ public const byte CharWidth = 8;
+ ///
+ /// "The YCharSellSize...size of the character cell in pixels" = 16
+ ///
+ public const byte CharHeight = 16;
+ ///
+ /// "For modes that don't have scanline banks...this field should be set to 1"
+ ///
+ public const byte SingleBank = 1;
+ public const byte BankSize64KB = 64;
+ ///
+ /// "The NumberOfImagePages field specifies the number of additional...images"
+ ///
+ public const byte NoImagePages = 0;
+ ///
+ /// "The Reserved field...will always be set to one in this version"
+ ///
+ public const byte Reserved = 1;
+ ///
+ /// "03h = 4-plane planar"
+ ///
+ public const byte MemoryModelPlanar = 3;
+ ///
+ /// "04h = Packed pixel"
+ ///
+ public const byte MemoryModelPackedPixel = 4;
+ ///
+ /// "06h = Direct Color"
+ ///
+ public const byte MemoryModelDirectColor = 6;
+ ///
+ /// "the MaskSize values for a Direct Color 5:6:5 mode would be 5, 6, 5"
+ ///
+ public const byte RedGreenBlueMaskSize = 5;
+ public const byte RedGreenBlueMaskSize8 = 8;
+ ///
+ /// "the MaskSize values for a Direct Color 5:6:5 mode would be 5, 6, 5" - green=6
+ ///
+ public const byte GreenMaskSize6Bit = 6;
+ }
+
+ ///
+ /// Common VGA/BIOS constants.
+ ///
+ private static class BiosConstants {
+ public const byte MaxVideoPage = 7;
+ public const byte MaxPaletteRegister = 0x0F;
+ public const byte FunctionSupported = 0x1A;
+ public const byte SubfunctionSuccess = 0x12;
+ public const byte NoSecondaryDisplay = 0x00;
+ public const byte DefaultDisplayCombinationCode = 0x08;
+ public const byte Memory256KB = 0x03;
+ public const ushort VideoControl80x25Color = 0x20;
+ public const byte DefaultModeSetControl = 0x51;
+ public const byte ModeAbove7Return = 0x20;
+ public const byte Mode6Return = 0x3F;
+ public const byte ModeBelow7Return = 0x30;
+ public const byte VideoModeMask = 0x7F;
+ public const byte DontClearMemoryFlag = 0x80;
+ public const byte IncludeAttributesFlag = 0x02;
+ public const byte UpdateCursorPositionFlag = 0x01;
+ public const byte ColorModeMemory = 0x01;
+ public const byte VideoControlBitMask = 0x80;
+ public const ushort EquipmentListFlagsMask = 0x30;
+ public const int ScanLines200 = 200;
+ public const int ScanLines350 = 350;
+ public const int ScanLines400 = 400;
+ public const byte CursorTypeMask = 0x3F;
+ public const byte CursorEndMask = 0x1F;
+ public const uint StaticFunctionalityAllModes = 0x000FFFFF;
+ public const byte StaticFunctionalityAllScanLines = 0x07;
+ }
+
///
/// VGA BIOS constructor.
///
@@ -46,6 +296,526 @@ public VgaBios(IMemory memory, IFunctionHandlerProvider functionHandlerProvider,
InitializeStaticFunctionalityTable();
}
+ ///
+ /// Represents the VBE Info Block structure (VBE 1.0/1.2).
+ /// "The purpose of this function is to provide information to the calling program
+ /// about the general capabilities of the Super VGA environment. The function fills
+ /// an information block structure at the address specified by the caller.
+ /// The information block size is 256 bytes."
+ /// Returned by VBE Function 00h - Return VBE Controller Information.
+ /// Minimum size is 256 bytes, but callers should provide ~512 bytes for OEM string and mode list.
+ ///
+ public class VbeInfoBlock : MemoryBasedDataStructure {
+ private const int SignatureLength = 4;
+ private const int OemStringMaxLength = 256;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory bus.
+ /// The base address of the structure in memory.
+ public VbeInfoBlock(IByteReaderWriter byteReaderWriter, uint baseAddress)
+ : base(byteReaderWriter, baseAddress) {
+ }
+
+ ///
+ /// "The VESASignature field contains the characters 'VESA' if this is a valid block."
+ /// Gets or sets the VBE signature (4 bytes: "VESA" for VBE 1.x, "VBE2" for VBE 2.0+).
+ /// Offset: 0x00, Size: 4 bytes.
+ ///
+ public string Signature {
+ get => GetZeroTerminatedString(0x00, SignatureLength);
+ set {
+ string truncated = value.Length >= SignatureLength
+ ? value[..SignatureLength]
+ : value;
+ for (int i = 0; i < SignatureLength; i++) {
+ UInt8[0x00 + i] = i < truncated.Length ? (byte)truncated[i] : (byte)0;
+ }
+ }
+ }
+
+ ///
+ /// "The VESAVersion is a binary field which specifies what level of the VESA
+ /// standard the Super VGA BIOS conforms to. The higher byte specifies the major
+ /// version number. The lower byte specifies the minor version number. The current
+ /// VESA version number is 1.2."
+ /// Gets or sets the VBE version (BCD format: 0x0100 = 1.0, 0x0102 = 1.2, 0x0200 = 2.0).
+ /// Offset: 0x04, Size: 2 bytes (word).
+ ///
+ public ushort Version {
+ get => UInt16[0x04];
+ set => UInt16[0x04] = value;
+ }
+
+ ///
+ /// "The OEMStringPtr is a far pointer to a null terminated OEM-defined string."
+ /// Gets or sets the pointer to OEM string (far pointer stored as offset:segment).
+ /// Offset part at 0x06, Size: 2 bytes (word).
+ ///
+ public ushort OemStringOffset {
+ get => UInt16[0x06];
+ set => UInt16[0x06] = value;
+ }
+
+ ///
+ /// "The OEMStringPtr is a far pointer to a null terminated OEM-defined string."
+ /// Gets or sets the pointer to OEM string (far pointer stored as offset:segment).
+ /// Segment part at 0x08, Size: 2 bytes (word).
+ ///
+ public ushort OemStringSegment {
+ get => UInt16[0x08];
+ set => UInt16[0x08] = value;
+ }
+
+ ///
+ /// "The Capabilities field describes what general features are supported in the
+ /// video environment. The bits are defined as follows:
+ /// D0 = DAC is switchable (0 = DAC is fixed width, with 6-bits per primary color,
+ /// 1 = DAC width is switchable)
+ /// D1-31 = Reserved"
+ /// Gets or sets the capabilities flags (4 bytes).
+ /// Offset: 0x0A, Size: 4 bytes (dword).
+ ///
+ public uint Capabilities {
+ get => UInt32[0x0A];
+ set => UInt32[0x0A] = value;
+ }
+
+ ///
+ /// "The VideoModePtr points to a list of supported Super VGA (VESA-defined as well
+ /// as OEM-specific) mode numbers. Each mode number occupies one word (16 bits).
+ /// The list of mode numbers is terminated by a -1 (0FFFFh)."
+ /// Gets or sets the pointer to video mode list (far pointer stored as offset:segment).
+ /// Offset part at 0x0E, Size: 2 bytes (word).
+ /// Points to array of ushort mode numbers, terminated by 0xFFFF.
+ ///
+ public ushort VideoModeListOffset {
+ get => UInt16[0x0E];
+ set => UInt16[0x0E] = value;
+ }
+
+ ///
+ /// "The VideoModePtr points to a list of supported Super VGA (VESA-defined as well
+ /// as OEM-specific) mode numbers."
+ /// Gets or sets the pointer to video mode list (far pointer stored as offset:segment).
+ /// Segment part at 0x10, Size: 2 bytes (word).
+ ///
+ public ushort VideoModeListSegment {
+ get => UInt16[0x10];
+ set => UInt16[0x10] = value;
+ }
+
+ ///
+ /// "The TotalMemory field indicates the amount of memory installed on the VGA
+ /// board. Its value represents the number of 64kb blocks of memory currently
+ /// installed."
+ /// Gets or sets the total memory in 64KB blocks.
+ /// Offset: 0x12, Size: 2 bytes (word).
+ ///
+ public ushort TotalMemory {
+ get => UInt16[0x12];
+ set => UInt16[0x12] = value;
+ }
+
+ ///
+ /// Writes the OEM string at the specified address (typically beyond the main structure).
+ ///
+ /// The OEM string to write.
+ /// Offset from base address where to write the string.
+ public void WriteOemString(string oemString, uint offsetFromBase) {
+ string truncated = oemString.Length >= OemStringMaxLength
+ ? oemString[..(OemStringMaxLength - 1)]
+ : oemString;
+ SetZeroTerminatedString(offsetFromBase, truncated, OemStringMaxLength);
+ }
+
+ ///
+ /// "The list of mode numbers is terminated by a -1 (0FFFFh)."
+ /// Writes the video mode list at the specified address.
+ ///
+ /// Array of mode numbers (will be terminated with 0xFFFF).
+ /// Offset from base address where to write the mode list.
+ public void WriteModeList(ushort[] modes, uint offsetFromBase) {
+ for (int i = 0; i < modes.Length; i++) {
+ UInt16[offsetFromBase + (uint)(i * 2)] = modes[i];
+ }
+ UInt16[offsetFromBase + (uint)(modes.Length * 2)] = 0xFFFF;
+ }
+ }
+
+ ///
+ /// Represents the VBE Mode Info Block structure (VBE 1.0/1.2).
+ /// "This function returns information about a specific Super VGA video mode that was
+ /// returned by Function 0. The function fills a mode information block structure
+ /// at the address specified by the caller. The mode information block size is
+ /// maximum 256 bytes."
+ /// Returned by VBE Function 01h - Return VBE Mode Information.
+ /// Size is 256 bytes.
+ ///
+ public class VbeModeInfoBlock : MemoryBasedDataStructure {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory bus.
+ /// The base address of the structure in memory.
+ public VbeModeInfoBlock(IByteReaderWriter byteReaderWriter, uint baseAddress)
+ : base(byteReaderWriter, baseAddress) {
+ }
+
+ ///
+ /// "The ModeAttributes field describes certain important characteristics of the
+ /// video mode. Bit D0 specifies whether this mode can be initialized in the
+ /// present video configuration."
+ /// Offset: 0x00, Size: 2 bytes (word).
+ /// Bit definitions:
+ /// "D0 = Mode supported in hardware (0 = Mode not supported, 1 = Mode supported)
+ /// D1 = 1 (Reserved)
+ /// D2 = Output functions supported by BIOS (0 = not supported, 1 = supported)
+ /// D3 = Monochrome/color mode (0 = Monochrome, 1 = Color)
+ /// D4 = Mode type (0 = Text mode, 1 = Graphics mode)
+ /// D5-D15 = Reserved"
+ ///
+ public ushort ModeAttributes {
+ get => UInt16[0x00];
+ set => UInt16[0x00] = value;
+ }
+
+ ///
+ /// "The WinAAttributes and WinBAttributes describe the characteristics of the CPU
+ /// windowing scheme such as whether the windows exist and are read/writeable."
+ /// Window A attributes. Offset: 0x02, Size: 1 byte.
+ /// "D0 = Window supported (0 = not supported, 1 = supported)
+ /// D1 = Window readable (0 = not readable, 1 = readable)
+ /// D2 = Window writeable (0 = not writeable, 1 = writeable)
+ /// D3-D7 = Reserved"
+ ///
+ public byte WindowAAttributes {
+ get => UInt8[0x02];
+ set => UInt8[0x02] = value;
+ }
+
+ ///
+ /// "The WinAAttributes and WinBAttributes describe the characteristics of the CPU
+ /// windowing scheme such as whether the windows exist and are read/writeable."
+ /// Window B attributes (same bits as WindowA). Offset: 0x03, Size: 1 byte.
+ ///
+ public byte WindowBAttributes {
+ get => UInt8[0x03];
+ set => UInt8[0x03] = value;
+ }
+
+ ///
+ /// "WinGranularity specifies the smallest boundary, in KB, on which the window can
+ /// be placed in the video memory. The value of this field is undefined if Bit D0
+ /// of the appropriate WinAttributes field is not set."
+ /// Offset: 0x04, Size: 2 bytes (word).
+ ///
+ public ushort WindowGranularity {
+ get => UInt16[0x04];
+ set => UInt16[0x04] = value;
+ }
+
+ ///
+ /// "WinSize specifies the size of the window in KB."
+ /// Offset: 0x06, Size: 2 bytes (word).
+ ///
+ public ushort WindowSize {
+ get => UInt16[0x06];
+ set => UInt16[0x06] = value;
+ }
+
+ ///
+ /// "WinASegment and WinBSegment address specify the segment addresses where the
+ /// windows are located in CPU address space."
+ /// Window A start segment. Offset: 0x08, Size: 2 bytes (word).
+ ///
+ public ushort WindowASegment {
+ get => UInt16[0x08];
+ set => UInt16[0x08] = value;
+ }
+
+ ///
+ /// "WinASegment and WinBSegment address specify the segment addresses where the
+ /// windows are located in CPU address space."
+ /// Window B start segment. Offset: 0x0A, Size: 2 bytes (word).
+ ///
+ public ushort WindowBSegment {
+ get => UInt16[0x0A];
+ set => UInt16[0x0A] = value;
+ }
+
+ ///
+ /// "WinFuncAddr specifies the address of the CPU video memory windowing function.
+ /// The windowing function can be invoked either through VESA BIOS function 05h, or
+ /// by calling the function directly."
+ /// Pointer to window positioning function (far pointer stored as offset:segment).
+ /// Offset part at 0x0C, Size: 2 bytes (word).
+ ///
+ public ushort WindowFunctionOffset {
+ get => UInt16[0x0C];
+ set => UInt16[0x0C] = value;
+ }
+
+ ///
+ /// "WinFuncAddr specifies the address of the CPU video memory windowing function."
+ /// Pointer to window positioning function (far pointer stored as offset:segment).
+ /// Segment part at 0x0E, Size: 2 bytes (word).
+ ///
+ public ushort WindowFunctionSegment {
+ get => UInt16[0x0E];
+ set => UInt16[0x0E] = value;
+ }
+
+ ///
+ /// "The BytesPerScanline field specifies how many bytes each logical scanline
+ /// consists of. The logical scanline could be equal to or larger then the
+ /// displayed scanline."
+ /// Offset: 0x10, Size: 2 bytes (word).
+ /// For planar modes (4-bit), this is bytes per plane.
+ /// For packed pixel modes, this is total bytes per line.
+ ///
+ public ushort BytesPerScanLine {
+ get => UInt16[0x10];
+ set => UInt16[0x10] = value;
+ }
+
+ ///
+ /// "The XResolution and YResolution specify the width and height of the video mode.
+ /// In graphics modes, this resolution is in units of pixels."
+ /// Horizontal resolution in pixels. Offset: 0x12, Size: 2 bytes (word).
+ ///
+ public ushort XResolution {
+ get => UInt16[0x12];
+ set => UInt16[0x12] = value;
+ }
+
+ ///
+ /// "The XResolution and YResolution specify the width and height of the video mode.
+ /// In graphics modes, this resolution is in units of pixels."
+ /// Vertical resolution in pixels. Offset: 0x14, Size: 2 bytes (word).
+ ///
+ public ushort YResolution {
+ get => UInt16[0x14];
+ set => UInt16[0x14] = value;
+ }
+
+ ///
+ /// "The XCharCellSize and YCharSellSize specify the size of the character cell in
+ /// pixels."
+ /// Character cell width in pixels. Offset: 0x16, Size: 1 byte.
+ ///
+ public byte XCharSize {
+ get => UInt8[0x16];
+ set => UInt8[0x16] = value;
+ }
+
+ ///
+ /// "The XCharCellSize and YCharSellSize specify the size of the character cell in
+ /// pixels."
+ /// Character cell height in pixels. Offset: 0x17, Size: 1 byte.
+ ///
+ public byte YCharSize {
+ get => UInt8[0x17];
+ set => UInt8[0x17] = value;
+ }
+
+ ///
+ /// "The NumberOfPlanes field specifies the number of memory planes available to
+ /// software in that mode. For standard 16-color VGA graphics, this would be set to
+ /// 4. For standard packed pixel modes, the field would be set to 1."
+ /// Offset: 0x18, Size: 1 byte.
+ ///
+ public byte NumberOfPlanes {
+ get => UInt8[0x18];
+ set => UInt8[0x18] = value;
+ }
+
+ ///
+ /// "The BitsPerPixel field specifies the total number of bits that define the color
+ /// of one pixel. For example, a standard VGA 4 Plane 16-color graphics mode would
+ /// have a 4 in this field and a packed pixel 256-color graphics mode would specify
+ /// 8 in this field."
+ /// Offset: 0x19, Size: 1 byte.
+ ///
+ public byte BitsPerPixel {
+ get => UInt8[0x19];
+ set => UInt8[0x19] = value;
+ }
+
+ ///
+ /// "The NumberOfBanks field specifies the number of banks in which the scan lines
+ /// are grouped. The remainder from dividing the scan line number by the number of
+ /// banks is the bank that contains the scan line and the quotient is the scan line
+ /// number within the bank."
+ /// Offset: 0x1A, Size: 1 byte.
+ ///
+ public byte NumberOfBanks {
+ get => UInt8[0x1A];
+ set => UInt8[0x1A] = value;
+ }
+
+ ///
+ /// "The MemoryModel field specifies the general type of memory organization used in
+ /// this mode."
+ /// Offset: 0x1B, Size: 1 byte.
+ /// "00h = Text mode
+ /// 01h = CGA graphics
+ /// 02h = Hercules graphics
+ /// 03h = 4-plane planar
+ /// 04h = Packed pixel
+ /// 05h = Non-chain 4, 256 color
+ /// 06h = Direct Color
+ /// 07h = YUV.
+ /// 08h-0Fh = Reserved, to be defined by VESA
+ /// 10h-FFh = To be defined by OEM"
+ ///
+ public byte MemoryModel {
+ get => UInt8[0x1B];
+ set => UInt8[0x1B] = value;
+ }
+
+ ///
+ /// "The BankSize field specifies the size of a bank (group of scan lines) in units
+ /// of 1KB. For CGA and Hercules graphics modes this is 8, as each bank is 8192
+ /// bytes in length."
+ /// Offset: 0x1C, Size: 1 byte.
+ ///
+ public byte BankSize {
+ get => UInt8[0x1C];
+ set => UInt8[0x1C] = value;
+ }
+
+ ///
+ /// "The NumberOfImagePages field specifies the number of additional complete display
+ /// images that will fit into the VGA's memory, at one time, in this mode."
+ /// Offset: 0x1D, Size: 1 byte.
+ ///
+ public byte NumberOfImagePages {
+ get => UInt8[0x1D];
+ set => UInt8[0x1D] = value;
+ }
+
+ ///
+ /// "The Reserved field has been defined to support a future VESA BIOS extension
+ /// feature and will always be set to one in this version."
+ /// Offset: 0x1E, Size: 1 byte.
+ ///
+ public byte Reserved1 {
+ get => UInt8[0x1E];
+ set => UInt8[0x1E] = value;
+ }
+
+ ///
+ /// "The RedMaskSize, GreenMaskSize, BlueMaskSize, and RsvdMaskSize fields define the
+ /// size, in bits, of the red, green, and blue components of a direct color pixel."
+ /// Red mask size (number of bits). Offset: 0x1F, Size: 1 byte.
+ ///
+ public byte RedMaskSize {
+ get => UInt8[0x1F];
+ set => UInt8[0x1F] = value;
+ }
+
+ ///
+ /// "The RedFieldPosition, GreenFieldPosition, BlueFieldPosition, and
+ /// RsvdFieldPosition fields define the bit position within the direct color pixel
+ /// or YUV pixel of the least significant bit of the respective color component."
+ /// Red field position (bit offset). Offset: 0x20, Size: 1 byte.
+ ///
+ public byte RedFieldPosition {
+ get => UInt8[0x20];
+ set => UInt8[0x20] = value;
+ }
+
+ ///
+ /// "The RedMaskSize, GreenMaskSize, BlueMaskSize, and RsvdMaskSize fields define the
+ /// size, in bits, of the red, green, and blue components of a direct color pixel."
+ /// Green mask size (number of bits). Offset: 0x21, Size: 1 byte.
+ ///
+ public byte GreenMaskSize {
+ get => UInt8[0x21];
+ set => UInt8[0x21] = value;
+ }
+
+ ///
+ /// "The RedFieldPosition, GreenFieldPosition, BlueFieldPosition, and
+ /// RsvdFieldPosition fields define the bit position within the direct color pixel
+ /// or YUV pixel of the least significant bit of the respective color component."
+ /// Green field position (bit offset). Offset: 0x22, Size: 1 byte.
+ ///
+ public byte GreenFieldPosition {
+ get => UInt8[0x22];
+ set => UInt8[0x22] = value;
+ }
+
+ ///
+ /// "The RedMaskSize, GreenMaskSize, BlueMaskSize, and RsvdMaskSize fields define the
+ /// size, in bits, of the red, green, and blue components of a direct color pixel."
+ /// Blue mask size (number of bits). Offset: 0x23, Size: 1 byte.
+ ///
+ public byte BlueMaskSize {
+ get => UInt8[0x23];
+ set => UInt8[0x23] = value;
+ }
+
+ ///
+ /// "The RedFieldPosition, GreenFieldPosition, BlueFieldPosition, and
+ /// RsvdFieldPosition fields define the bit position within the direct color pixel
+ /// or YUV pixel of the least significant bit of the respective color component."
+ /// Blue field position (bit offset). Offset: 0x24, Size: 1 byte.
+ ///
+ public byte BlueFieldPosition {
+ get => UInt8[0x24];
+ set => UInt8[0x24] = value;
+ }
+
+ ///
+ /// "The RedMaskSize, GreenMaskSize, BlueMaskSize, and RsvdMaskSize fields define the
+ /// size, in bits, of the red, green, and blue components of a direct color pixel."
+ /// Reserved mask size (number of bits). Offset: 0x25, Size: 1 byte.
+ ///
+ public byte ReservedMaskSize {
+ get => UInt8[0x25];
+ set => UInt8[0x25] = value;
+ }
+
+ ///
+ /// "The RedFieldPosition, GreenFieldPosition, BlueFieldPosition, and
+ /// RsvdFieldPosition fields define the bit position within the direct color pixel
+ /// or YUV pixel of the least significant bit of the respective color component."
+ /// Reserved field position (bit offset). Offset: 0x26, Size: 1 byte.
+ ///
+ public byte ReservedFieldPosition {
+ get => UInt8[0x26];
+ set => UInt8[0x26] = value;
+ }
+
+ ///
+ /// "The DirectColorModeInfo field describes important characteristics of direct
+ /// color modes. Bit D0 specifies whether the color ramp of the DAC is fixed or
+ /// programmable."
+ /// Offset: 0x27, Size: 1 byte.
+ /// "D0 = Color ramp is fixed/programmable (0 = fixed, 1 = programmable)
+ /// D1 = Bits in Rsvd field are usable/reserved (0 = reserved, 1 = usable)"
+ ///
+ public byte DirectColorModeInfo {
+ get => UInt8[0x27];
+ set => UInt8[0x27] = value;
+ }
+
+ ///
+ /// "Version 1.1 and later VESA BIOS extensions will zero out all unused fields in
+ /// the Mode Information Block, always returning exactly 256 bytes."
+ /// Clears the entire 256-byte mode info block.
+ ///
+ public void Clear() {
+ for (uint i = 0; i < 256; i++) {
+ UInt8[i] = 0;
+ }
+ }
+ }
+
+
///
/// The interrupt vector this class handles.
///
@@ -58,8 +828,8 @@ public void WriteString() {
ushort segment = State.ES;
ushort offset = State.BP;
byte attribute = State.BL;
- bool includeAttributes = (State.AL & 0x02) != 0;
- bool updateCursorPosition = (State.AL & 0x01) != 0;
+ bool includeAttributes = (State.AL & BiosConstants.IncludeAttributesFlag) != 0;
+ bool updateCursorPosition = (State.AL & BiosConstants.UpdateCursorPositionFlag) != 0;
if (_logger.IsEnabled(LogEventLevel.Debug)) {
uint address = MemoryUtils.ToPhysicalAddress(segment, offset);
string str = Memory.GetZeroTerminatedString(address, State.CX);
@@ -73,9 +843,9 @@ public void WriteString() {
public void GetSetDisplayCombinationCode() {
switch (State.AL) {
case 0x00: {
- State.AL = 0x1A; // Function supported
- State.BL = _biosDataArea.DisplayCombinationCode; // Primary display
- State.BH = 0x00; // No secondary display
+ State.AL = BiosConstants.FunctionSupported;
+ State.BL = _biosDataArea.DisplayCombinationCode;
+ State.BH = BiosConstants.NoSecondaryDisplay;
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 1A {MethodName} - Get: DCC 0x{Dcc:X2}",
nameof(VgaBios), nameof(GetSetDisplayCombinationCode), State.BL);
@@ -83,7 +853,7 @@ public void GetSetDisplayCombinationCode() {
break;
}
case 0x01: {
- State.AL = 0x1A; // Function supported
+ State.AL = BiosConstants.FunctionSupported;
_biosDataArea.DisplayCombinationCode = State.BL;
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 1A {MethodName} - Set: DCC 0x{Dcc:X2}",
@@ -103,29 +873,29 @@ public void VideoSubsystemConfiguration() {
_logger.Verbose("{ClassName} INT 10 12 {MethodName} - Sub function 0x{SubFunction:X2}",
nameof(VgaBios), nameof(LoadFontInfo), State.BL);
}
- switch (State.BL) {
- case 0x10:
+ switch ((VideoSubsystemFunction)State.BL) {
+ case VideoSubsystemFunction.EgaVgaInformation:
EgaVgaInformation();
break;
- case 0x30:
+ case VideoSubsystemFunction.SelectScanLines:
SelectScanLines();
break;
- case 0x31:
+ case VideoSubsystemFunction.DefaultPaletteLoading:
DefaultPaletteLoading();
break;
- case 0x32:
+ case VideoSubsystemFunction.VideoEnableDisable:
VideoEnableDisable();
break;
- case 0x33:
+ case VideoSubsystemFunction.SummingToGrayScales:
SummingToGrayScales();
break;
- case 0x34:
+ case VideoSubsystemFunction.CursorEmulation:
CursorEmulation();
break;
- case 0x35:
+ case VideoSubsystemFunction.DisplaySwitch:
DisplaySwitch();
break;
- case 0x36:
+ case VideoSubsystemFunction.VideoScreenOnOff:
VideoScreenOnOff();
break;
default:
@@ -220,10 +990,10 @@ public void SetPaletteRegisters() {
_vgaFunctions.SetAllPaletteRegisters(State.ES, State.DX);
break;
case 0x03:
- _vgaFunctions.ToggleIntensity((State.BL & 1) != 0);
+ _vgaFunctions.ToggleIntensity((State.BL & BiosConstants.UpdateCursorPositionFlag) != 0);
break;
case 0x07:
- if (State.BL > 0xF) {
+ if (State.BL > BiosConstants.MaxPaletteRegister) {
return;
}
State.BH = _vgaFunctions.ReadPaletteRegister(State.BL);
@@ -250,7 +1020,7 @@ public void SetPaletteRegisters() {
nameof(VgaBios), nameof(SetPaletteRegisters), State.BL == 0 ? "set Mode Control register bit 7" : "set color select register", State.BH);
}
if (State.BL == 0) {
- _vgaFunctions.SetP5P4Select((State.BH & 1) != 0);
+ _vgaFunctions.SetP5P4Select((State.BH & BiosConstants.UpdateCursorPositionFlag) != 0);
} else {
_vgaFunctions.SetColorSelectRegister(State.BH);
}
@@ -284,7 +1054,7 @@ public void SetPaletteRegisters() {
///
public void GetVideoState() {
State.BH = _biosDataArea.CurrentVideoPage;
- State.AL = (byte)(_biosDataArea.VideoMode | _biosDataArea.VideoCtl & 0x80);
+ State.AL = (byte)(_biosDataArea.VideoMode | _biosDataArea.VideoCtl & BiosConstants.VideoControlBitMask);
State.AH = (byte)_biosDataArea.ScreenColumns;
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 0F {MethodName} - Page {Page}, mode {Mode}, columns {Columns}",
@@ -445,19 +1215,19 @@ public void WriteDot() {
///
public void SetVideoMode() {
- int modeId = State.AL & 0x7F;
+ int modeId = State.AL & BiosConstants.VideoModeMask;
ModeFlags flags = ModeFlags.Legacy | (ModeFlags)_biosDataArea.ModesetCtl & (ModeFlags.NoPalette | ModeFlags.GraySum);
- if ((State.AL & 0x80) != 0) {
+ if ((State.AL & BiosConstants.DontClearMemoryFlag) != 0) {
flags |= ModeFlags.NoClearMem;
}
// Set AL
if (modeId > 7) {
- State.AL = 0x20;
+ State.AL = BiosConstants.ModeAbove7Return;
} else if (modeId == 6) {
- State.AL = 0x3F;
+ State.AL = BiosConstants.Mode6Return;
} else {
- State.AL = 0x30;
+ State.AL = BiosConstants.ModeBelow7Return;
}
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 00 {MethodName} - mode {ModeId:X2}, {Flags}",
@@ -520,11 +1290,11 @@ public void ReadLightPenPosition() {
private void InitializeBiosArea() {
// init detected hardware BIOS Area
// set 80x25 color (not clear from RBIL but usual)
- _biosDataArea.EquipmentListFlags = (ushort)(_biosDataArea.EquipmentListFlags & ~0x30 | 0x20);
+ _biosDataArea.EquipmentListFlags = (ushort)(_biosDataArea.EquipmentListFlags & ~BiosConstants.EquipmentListFlagsMask | BiosConstants.VideoControl80x25Color);
// Set the basic modeset options
- _biosDataArea.ModesetCtl = 0x51;
- _biosDataArea.DisplayCombinationCode = 0x08;
+ _biosDataArea.ModesetCtl = BiosConstants.DefaultModeSetControl;
+ _biosDataArea.DisplayCombinationCode = BiosConstants.DefaultDisplayCombinationCode;
}
private void VideoScreenOnOff() {
@@ -532,7 +1302,7 @@ private void VideoScreenOnOff() {
_logger.Debug("{ClassName} INT 10 12 36 {MethodName} - Ignored",
nameof(VgaBios), nameof(VideoScreenOnOff));
}
- State.AL = 0x12;
+ State.AL = BiosConstants.SubfunctionSuccess;
}
private void DisplaySwitch() {
@@ -540,52 +1310,52 @@ private void DisplaySwitch() {
_logger.Debug("{ClassName} INT 10 12 35 {MethodName} - Ignored",
nameof(VgaBios), nameof(DisplaySwitch));
}
- State.AL = 0x12;
+ State.AL = BiosConstants.SubfunctionSuccess;
}
private void CursorEmulation() {
- bool enabled = (State.AL & 0x01) == 0;
+ bool enabled = (State.AL & BiosConstants.UpdateCursorPositionFlag) == 0;
_vgaFunctions.CursorEmulation(enabled);
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 12 34 {MethodName} - {Result}",
nameof(VgaBios), nameof(CursorEmulation), enabled ? "Enabled" : "Disabled");
}
- State.AL = 0x12;
+ State.AL = BiosConstants.SubfunctionSuccess;
}
private void SummingToGrayScales() {
- bool enabled = (State.AL & 0x01) == 0;
+ bool enabled = (State.AL & BiosConstants.UpdateCursorPositionFlag) == 0;
_vgaFunctions.SummingToGrayScales(enabled);
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 12 33 {MethodName} - {Result}",
nameof(VgaBios), nameof(SummingToGrayScales), enabled ? "Enabled" : "Disabled");
}
- State.AL = 0x12;
+ State.AL = BiosConstants.SubfunctionSuccess;
}
private void VideoEnableDisable() {
- _vgaFunctions.EnableVideoAddressing((State.AL & 1) == 0);
+ _vgaFunctions.EnableVideoAddressing((State.AL & BiosConstants.UpdateCursorPositionFlag) == 0);
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 12 32 {MethodName} - {Result}",
- nameof(VgaBios), nameof(VideoEnableDisable), (State.AL & 0x01) == 0 ? "Enabled" : "Disabled");
+ nameof(VgaBios), nameof(VideoEnableDisable), (State.AL & BiosConstants.UpdateCursorPositionFlag) == 0 ? "Enabled" : "Disabled");
}
- State.AL = 0x12;
+ State.AL = BiosConstants.SubfunctionSuccess;
}
private void DefaultPaletteLoading() {
- _vgaFunctions.DefaultPaletteLoading((State.AL & 1) != 0);
+ _vgaFunctions.DefaultPaletteLoading((State.AL & BiosConstants.UpdateCursorPositionFlag) != 0);
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 12 31 {MethodName} - 0x{Al:X2}",
nameof(VgaBios), nameof(DefaultPaletteLoading), State.AL);
}
- State.AL = 0x12;
+ State.AL = BiosConstants.SubfunctionSuccess;
}
private void SelectScanLines() {
int lines = State.AL switch {
- 0x00 => 200,
- 0x01 => 350,
- 0x02 => 400,
+ 0x00 => BiosConstants.ScanLines200,
+ 0x01 => BiosConstants.ScanLines350,
+ 0x02 => BiosConstants.ScanLines400,
_ => throw new NotSupportedException($"AL=0x{State.AL:X2} is not a valid subFunction for INT 10 12 30")
};
_vgaFunctions.SelectScanLines(lines);
@@ -593,12 +1363,12 @@ private void SelectScanLines() {
_logger.Debug("{ClassName} INT 10 12 30 {MethodName} - {Lines} lines",
nameof(VgaBios), nameof(SelectScanLines), lines);
}
- State.AL = 0x12;
+ State.AL = BiosConstants.SubfunctionSuccess;
}
private void EgaVgaInformation() {
- State.BH = (byte)(_vgaFunctions.GetColorMode() ? 0x01 : 0x00);
- State.BL = 0x03;
+ State.BH = (byte)(_vgaFunctions.GetColorMode() ? BiosConstants.ColorModeMemory : 0x00);
+ State.BL = BiosConstants.Memory256KB;
State.CX = _vgaFunctions.GetFeatureSwitches();
if (_logger.IsEnabled(LogEventLevel.Debug)) {
_logger.Debug("{ClassName} INT 10 12 10 {MethodName} - ColorMode 0x{ColorMode:X2}, Memory: 0x{Memory:X2}, FeatureSwitches: 0x{FeatureSwitches:X2}",
@@ -636,13 +1406,269 @@ private void FillDispatchTable() {
AddAction(0x4F, VesaFunctions);
}
+ ///
+ /// VESA VBE 1.0 function dispatcher (INT 10h AH=4Fh).
+ /// Dispatches to specific VBE functions based on AL subfunction.
+ ///
public void VesaFunctions() {
- if (_logger.IsEnabled(LogEventLevel.Warning)) {
- // This can be valid, video cards came to the scene before VESA was a standard.
- // It seems some games can expect that (eg. Rules of Engagement 2)
- //TODO: Implement at least VESA 1.2
- _logger.Warning("Emulated program tried to call VESA functions. Not implemented, moving on!");
+ byte subfunction = State.AL;
+ switch ((VbeFunction)subfunction) {
+ case VbeFunction.GetControllerInfo:
+ VbeGetControllerInfo();
+ break;
+ case VbeFunction.GetModeInfo:
+ VbeGetModeInfo();
+ break;
+ case VbeFunction.SetMode:
+ VbeSetMode();
+ break;
+ default:
+ if (_logger.IsEnabled(LogEventLevel.Warning)) {
+ _logger.Warning("{ClassName} INT 10 4F{Subfunction:X2} - Unsupported VBE function",
+ nameof(VgaBios), subfunction);
+ }
+ State.AX = (ushort)VbeStatus.Failed;
+ break;
+ }
+ }
+
+ ///
+ public void VbeGetControllerInfo() {
+ ushort segment = State.ES;
+ ushort offset = State.DI;
+ uint address = MemoryUtils.ToPhysicalAddress(segment, offset);
+
+ VbeInfoBlock vbeInfo = new VbeInfoBlock(Memory, address);
+ vbeInfo.Clear();
+
+ // Fill VBE Info Block (VBE 1.0)
+ vbeInfo.Signature = "VESA";
+ vbeInfo.Version = VbeConstants.Version10;
+
+ // OEM String pointer - point to a location beyond the main structure
+ vbeInfo.OemStringOffset = (ushort)(offset + VbeConstants.OemStringOffset);
+ vbeInfo.OemStringSegment = segment;
+
+ // Capabilities: DAC is switchable, controller is VGA compatible
+ vbeInfo.Capabilities = VbeConstants.DacSwitchableCapability;
+
+ // Video Mode List pointer - point after OEM string
+ vbeInfo.VideoModeListOffset = (ushort)(offset + VbeConstants.ModeListOffset);
+ vbeInfo.VideoModeListSegment = segment;
+
+ // Total Memory in 64KB blocks (1MB = 16 blocks)
+ vbeInfo.TotalMemory = VbeConstants.TotalMemory1MB;
+
+ // Write OEM String at offset+256
+ vbeInfo.WriteOemString(VbeConstants.OemString, VbeConstants.OemStringOffset);
+
+ // Write Video Mode List at offset+280
+ ushort[] vesaModes = { VbeConstants.VesaMode800x600x16 };
+ vbeInfo.WriteModeList(vesaModes, VbeConstants.ModeListOffset);
+
+ if (_logger.IsEnabled(LogEventLevel.Debug)) {
+ _logger.Debug("{ClassName} INT 10 4F00 VbeGetControllerInfo - Returning VBE 1.0 info at {Segment:X4}:{Offset:X4}",
+ nameof(VgaBios), segment, offset);
}
+
+ State.AX = (ushort)VbeStatus.Success;
+ }
+
+
+ ///
+ public void VbeGetModeInfo() {
+ ushort requestedModeNumber = State.CX;
+ const ushort SupportedVbeMode = 0x0102;
+ ushort modeNumber = (ushort)(requestedModeNumber & 0x3FFF);
+ ushort segment = State.ES;
+ ushort offset = State.DI;
+ uint address = MemoryUtils.ToPhysicalAddress(segment, offset);
+
+ // Get mode parameters based on VESA base mode number (without high flag bits)
+ (ushort width, ushort height, byte bpp, bool supported) = GetVesaModeParams(modeNumber);
+
+ if (!supported || modeNumber != SupportedVbeMode) {
+ if (_logger.IsEnabled(LogEventLevel.Warning)) {
+ _logger.Warning("{ClassName} INT 10 4F01 VbeGetModeInfo - Unsupported mode 0x{Mode:X4}",
+ nameof(VgaBios), requestedModeNumber);
+ }
+ State.AX = (ushort)VbeStatus.Failed;
+ return;
+ }
+
+ VbeModeInfoBlock modeInfo = new VbeModeInfoBlock(Memory, address);
+ modeInfo.Clear();
+
+ // Mode Attributes
+ modeInfo.ModeAttributes = VbeModeInfoConstants.ModeAttributesSupported;
+
+ // Window attributes
+ modeInfo.WindowAAttributes = VbeModeInfoConstants.WindowAttributesReadWriteSupported;
+ modeInfo.WindowBAttributes = VbeModeInfoConstants.WindowAttributesNotSupported;
+ modeInfo.WindowGranularity = VbeModeInfoConstants.WindowGranularity64KB;
+ modeInfo.WindowSize = VbeModeInfoConstants.WindowSize64KB;
+ modeInfo.WindowASegment = VbeModeInfoConstants.WindowASegmentAddress;
+ modeInfo.WindowBSegment = VbeModeInfoConstants.WindowBSegmentAddress;
+ modeInfo.WindowFunctionOffset = 0;
+ modeInfo.WindowFunctionSegment = 0;
+
+ // Calculate bytes per scan line
+ ushort bytesPerLine;
+ if (bpp == 4) {
+ bytesPerLine = (ushort)(width / 8); // 4-bit planar
+ } else if (bpp == 1) {
+ bytesPerLine = (ushort)(width / 8);
+ } else if (bpp == 15 || bpp == 16) {
+ bytesPerLine = (ushort)(width * 2);
+ } else if (bpp == 24) {
+ bytesPerLine = (ushort)(width * 3);
+ } else if (bpp == 32) {
+ bytesPerLine = (ushort)(width * 4);
+ } else {
+ bytesPerLine = width; // 8-bit packed pixel
+ }
+ modeInfo.BytesPerScanLine = bytesPerLine;
+
+ // Resolution and character info
+ modeInfo.XResolution = width;
+ modeInfo.YResolution = height;
+ modeInfo.XCharSize = VbeModeInfoConstants.CharWidth;
+ modeInfo.YCharSize = VbeModeInfoConstants.CharHeight;
+ modeInfo.NumberOfPlanes = (byte)(bpp == 4 ? 4 : 1);
+ modeInfo.BitsPerPixel = bpp;
+ modeInfo.NumberOfBanks = VbeModeInfoConstants.SingleBank;
+
+ // Memory model
+ byte memoryModel = bpp switch {
+ 4 => VbeModeInfoConstants.MemoryModelPlanar,
+ 8 => VbeModeInfoConstants.MemoryModelPackedPixel,
+ 15 => VbeModeInfoConstants.MemoryModelDirectColor,
+ 16 => VbeModeInfoConstants.MemoryModelDirectColor,
+ 24 => VbeModeInfoConstants.MemoryModelDirectColor,
+ 32 => VbeModeInfoConstants.MemoryModelDirectColor,
+ _ => VbeModeInfoConstants.MemoryModelPackedPixel
+ };
+ modeInfo.MemoryModel = memoryModel;
+ modeInfo.BankSize = VbeModeInfoConstants.BankSize64KB;
+ modeInfo.NumberOfImagePages = VbeModeInfoConstants.NoImagePages;
+ modeInfo.Reserved1 = VbeModeInfoConstants.Reserved;
+
+ // Direct color fields for high-color/true-color modes
+ if (bpp >= 15) {
+ if (bpp == 15 || bpp == 16) {
+ modeInfo.RedMaskSize = VbeModeInfoConstants.RedGreenBlueMaskSize;
+ modeInfo.RedFieldPosition = (byte)(bpp == 16 ? 11 : 10);
+ modeInfo.GreenMaskSize = (byte)(bpp == 16 ? VbeModeInfoConstants.GreenMaskSize6Bit : VbeModeInfoConstants.RedGreenBlueMaskSize);
+ modeInfo.GreenFieldPosition = 5;
+ modeInfo.BlueMaskSize = VbeModeInfoConstants.RedGreenBlueMaskSize;
+ modeInfo.BlueFieldPosition = 0;
+ } else if (bpp == 24 || bpp == 32) {
+ modeInfo.RedMaskSize = VbeModeInfoConstants.RedGreenBlueMaskSize8;
+ modeInfo.RedFieldPosition = 16;
+ modeInfo.GreenMaskSize = VbeModeInfoConstants.RedGreenBlueMaskSize8;
+ modeInfo.GreenFieldPosition = 8;
+ modeInfo.BlueMaskSize = VbeModeInfoConstants.RedGreenBlueMaskSize8;
+ modeInfo.BlueFieldPosition = 0;
+ }
+ }
+
+ if (_logger.IsEnabled(LogEventLevel.Debug)) {
+ _logger.Debug("{ClassName} INT 10 4F01 VbeGetModeInfo - Mode 0x{Mode:X4}: {Width}x{Height}x{Bpp}",
+ nameof(VgaBios), modeNumber, width, height, bpp);
+ }
+
+ // Return success
+ State.AX = 0x004F;
+ }
+
+ ///
+ public void VbeSetMode() {
+ ushort modeNumber = State.BX;
+ // VBE 1.0/1.2 does not support LFB; bit 14 is ignored (banked mode is always used)
+ bool dontClearDisplay = (modeNumber & (ushort)VbeModeFlags.DontClearMemory) != 0;
+ ushort mode = (ushort)(modeNumber & (ushort)VbeModeFlags.ModeNumberMask);
+
+ // Map VESA mode to internal mode
+ int? internalMode = MapVesaModeToInternal(mode);
+
+ if (!internalMode.HasValue) {
+ if (_logger.IsEnabled(LogEventLevel.Warning)) {
+ _logger.Warning("{ClassName} INT 10 4F02 VbeSetMode - Unsupported mode 0x{Mode:X4}",
+ nameof(VgaBios), mode);
+ }
+ State.AX = (ushort)VbeStatus.Failed;
+ return;
+ }
+
+ ModeFlags flags = ModeFlags.Legacy | (ModeFlags)_biosDataArea.ModesetCtl & (ModeFlags.NoPalette | ModeFlags.GraySum);
+ if (dontClearDisplay) {
+ flags |= ModeFlags.NoClearMem;
+ }
+
+ if (_logger.IsEnabled(LogEventLevel.Debug)) {
+ _logger.Debug("{ClassName} INT 10 4F02 VbeSetMode - Setting VESA mode 0x{VesaMode:X4} (internal mode 0x{InternalMode:X2})",
+ nameof(VgaBios), mode, internalMode.Value);
+ }
+
+ _vgaFunctions.VgaSetMode(internalMode.Value, flags);
+
+ State.AX = (ushort)VbeStatus.Success;
+ }
+
+ ///
+ /// Gets the parameters for a VESA mode number.
+ /// Returns mode information for all standard VESA modes defined in VBE 1.2 spec.
+ /// This is used by VbeGetModeInfo (VBE 01h) to return mode characteristics.
+ /// Note: supported=true means mode info is available for queries (per VBE spec);
+ /// actual ability to SET the mode depends on MapVesaModeToInternal returning a valid internal mode.
+ /// Programs query mode info before setting modes to check if they're suitable.
+ ///
+ private static (ushort width, ushort height, byte bpp, bool supported) GetVesaModeParams(ushort mode) {
+ return mode switch {
+ // VBE 1.2 standard modes - return info for queries
+ // Note: Only mode 0x102 can actually be SET (has internal VGA mode support)
+ 0x100 => (640, 400, 8, true), // 640x400x256
+ 0x101 => (640, 480, 8, true), // 640x480x256
+ 0x102 => (800, 600, 4, true), // 800x600x16 (planar) - CAN BE SET via mode 0x6A
+ 0x103 => (800, 600, 8, true), // 800x600x256
+ 0x104 => (1024, 768, 4, true), // 1024x768x16 (planar)
+ 0x105 => (1024, 768, 8, true), // 1024x768x256
+ 0x106 => (1280, 1024, 4, true), // 1280x1024x16 (planar)
+ 0x107 => (1280, 1024, 8, true), // 1280x1024x256
+ 0x10D => (320, 200, 15, true), // 320x200x15-bit
+ 0x10E => (320, 200, 16, true), // 320x200x16-bit
+ 0x10F => (320, 200, 24, true), // 320x200x24-bit
+ 0x110 => (640, 480, 15, true), // 640x480x15-bit (S3 mode 0x70)
+ 0x111 => (640, 480, 16, true), // 640x480x16-bit
+ 0x112 => (640, 480, 24, true), // 640x480x24-bit
+ 0x113 => (800, 600, 15, true), // 800x600x15-bit
+ 0x114 => (800, 600, 16, true), // 800x600x16-bit
+ 0x115 => (800, 600, 24, true), // 800x600x24-bit
+ 0x116 => (1024, 768, 15, true), // 1024x768x15-bit
+ 0x117 => (1024, 768, 16, true), // 1024x768x16-bit
+ 0x118 => (1024, 768, 24, true), // 1024x768x24-bit
+ 0x119 => (1280, 1024, 15, true), // 1280x1024x15-bit
+ 0x11A => (1280, 1024, 16, true), // 1280x1024x16-bit
+ 0x11B => (1280, 1024, 24, true), // 1280x1024x24-bit
+ _ => (0, 0, 0, false)
+ };
+ }
+
+ ///
+ /// Maps a VESA mode number to an internal VGA mode number.
+ /// Returns null if the mode is not supported by the emulator's VGA hardware.
+ /// Per VBE 1.2 spec, only modes with actual hardware support should be settable.
+ ///
+ private static int? MapVesaModeToInternal(ushort vesaMode) {
+ // Only map modes that have actual internal VGA mode support
+ // The emulator currently only supports standard VGA modes + mode 0x6A (800x600x16)
+ // High-color/true-color modes and higher resolutions require SVGA hardware
+ // that is not emulated, so they return null (unsupported)
+ return vesaMode switch {
+ VbeConstants.VesaMode800x600x16 => VbeConstants.InternalMode800x600x16,
+ // All other VESA modes are not supported by the current VGA emulation
+ _ => null
+ };
}
///
@@ -652,7 +1678,7 @@ public VideoFunctionalityInfo GetFunctionalityInfo() {
switch (State.BX) {
case 0x0:
- State.AL = 0x1B;
+ State.AL = BiosConstants.FunctionSupported;
break;
default:
if (_logger.IsEnabled(LogEventLevel.Warning)) {
@@ -684,14 +1710,14 @@ public VideoFunctionalityInfo GetFunctionalityInfo() {
ScreenRows = _biosDataArea.ScreenRows,
CharacterMatrixHeight = _biosDataArea.CharacterHeight,
ActiveDisplayCombinationCode = _biosDataArea.DisplayCombinationCode,
- AlternateDisplayCombinationCode = 0x00,
+ AlternateDisplayCombinationCode = BiosConstants.NoSecondaryDisplay,
NumberOfColorsSupported = CalculateColorCount(currentMode),
NumberOfPages = CalculatePageCount(currentMode),
NumberOfActiveScanLines = CalculateScanLineCode(currentMode),
TextCharacterTableUsed = primaryCharacterTable,
TextCharacterTableUsed2 = secondaryCharacterTable,
OtherStateInformation = GetOtherStateInformation(currentMode),
- VideoRamAvailable = 3,
+ VideoRamAvailable = BiosConstants.Memory256KB,
SaveAreaStatus = 0
};
@@ -711,16 +1737,16 @@ public VideoFunctionalityInfo GetFunctionalityInfo() {
/// Writes values to the static functionality table in emulated memory.
///
private void InitializeStaticFunctionalityTable() {
- Memory.UInt32[MemoryMap.StaticFunctionalityTableSegment, 0] = 0x000FFFFF; // supports all video modes
- Memory.UInt8[MemoryMap.StaticFunctionalityTableSegment, 0x07] = 0x07; // supports all scanLines
+ Memory.UInt32[MemoryMap.StaticFunctionalityTableSegment, 0] = BiosConstants.StaticFunctionalityAllModes;
+ Memory.UInt8[MemoryMap.StaticFunctionalityTableSegment, 0x07] = BiosConstants.StaticFunctionalityAllScanLines;
}
private static byte ExtractCursorStartLine(ushort cursorType) {
- return (byte)((cursorType >> 8) & 0x3F);
+ return (byte)((cursorType >> 8) & BiosConstants.CursorTypeMask);
}
private static byte ExtractCursorEndLine(ushort cursorType) {
- return (byte)(cursorType & 0x1F);
+ return (byte)(cursorType & BiosConstants.CursorEndMask);
}
private static (byte Primary, byte Secondary) DecodeCharacterMapSelections(byte registerValue) {