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) {