Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Vga Text Mode: To Hell and Back

5.00/5 (12 votes)
10 May 2018CPOL2 min read 20.4K  
This article shows how to enter vga text mode and return from it on Windows 7

Introduction

In Windows 7, we deal with display port/miniport + video port/miniport pair:

Image 1

Miniport provides callback routines to corresponding port driver, after that port calls miniport for assistance and miniport calls routines exported by port (so they call each other). Display driver communicates with video driver by making EngDeviceIoControl call (this routine is exported by win32k.sys).

Display miniport and video miniport drivers are supplied by display adapter vendor. However, the system can use default display miniport and video miniport pair if we haven't installed video card drivers or have disabled video card device (in device manager). On my system default pair is framebuf.dll (display miniport) and vga.sys (video miniport); and vendor supplied pair is vboxdisp.dll (display miniport) and vboxvideo.sys (video miniport). Do not get confused by .dll extension, it is kernel mode driver, however it is loaded in session space with win32k.sys and is treated more like .dll file.

Using the Code

Ioctl codes for EngDeviceIoControl are defined in ntddvdeo.h:

Some ioctls are mandatory to support, other ones are optional. Vendor supplied drivers define additional private ioctls, so we need to switch to default driver pair and observe ioctl sequence with kernel mode debugger.

To do this, we need to set breakpoint on EngDeviceIoControl and determine the target device object and ioctl code by looking at stack.

!devstack <address> command will give us the name of device object and the name of corresponding driver object:

Device name: \Device\Video3
Driver name: \Driver\VgaSave

The observed ioctl sequence:

IOCTL_VIDEO_QUERY_NUM_AVAIL_MODES
IOCTL_VIDEO_QUERY_AVAIL_MODES
IOCTL_VIDEO_QUERY_COLOR_CAPABILITIES
IOCTL_VIDEO_QUERY_POINTER_CAPABILITIES
IOCTL_VIDEO_SET_CURRENT_MODE   // vga enters graphics mode, screen turns black
IOCTL_VIDEO_MAP_VIDEO_MEMORY
... write to frame buffer ...
IOCTL_VIDEO_RESET_DEVICE      // vga enters text mode, screen turns black 
                              // and cursor blinks in top left corner
IOCTL_VIDEO_UNMAP_VIDEO_MEMORY

Now, let's see the code. First, disable all display adapters so the system switches to default driver pair (we can do it manually in device manager):

C++
#include <Windows.h>
#include <setupapi.h>

#pragma comment(lib,"setupapi.lib")

void SetGpuState(BOOL Enable)
{
    HDEVINFO hDevInfo;
    SP_DEVINFO_DATA spDevInfoData;
    SP_PROPCHANGE_PARAMS spPropChangeParams;

    GUID ClassGuid = { 0x4d36e968, 0xe325, 0x11ce, 
    { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };        // display adapter setup class

    hDevInfo = SetupDiGetClassDevsW(&ClassGuid, NULL, NULL, DIGCF_PRESENT);

    spDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

    for (ULONG i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &spDevInfoData); i++)
    {
        spPropChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
        spPropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
        spPropChangeParams.Scope = DICS_FLAG_GLOBAL;
        spPropChangeParams.StateChange = Enable ? DICS_ENABLE : DICS_DISABLE;

        SetupDiSetClassInstallParamsW(hDevInfo, &spDevInfoData, 
            (SP_CLASSINSTALL_HEADER*)&spPropChangeParams, sizeof(spPropChangeParams));

        SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, &spDevInfoData);
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);
}

...

SetGpuState(FALSE);

...

Next, call our driver:

C++
#include <Windows.h>
#include <stdio.h>

...

    SC_HANDLE hSCManager;
    SC_HANDLE hService;
    SERVICE_STATUS ServiceStatus;

    hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);

    if (!hSCManager) ...

    hService = CreateServiceW(hSCManager, L"Text Mode Service Name",
        L"Text Mode Display Name",
        SERVICE_START | DELETE | SERVICE_STOP,
        SERVICE_KERNEL_DRIVER,
        SERVICE_DEMAND_START,
        SERVICE_ERROR_IGNORE,
        L"C:\\TextMode.sys",
        NULL, NULL, NULL, NULL, NULL);

    if (!hService)
    {
        hService = OpenServiceW(hSCManager, L"Text Mode Service Name",
            SERVICE_START | DELETE | SERVICE_STOP);

        if (!hService) ...
    }

    if (!StartServiceW(hService, 0, NULL)) ...

    printf("Press Enter to close service\n");
    getchar();

...

The driver:

Header.h:

C++
#ifndef _INBV_
#define _INBV_

extern "C"
{
    typedef enum _INBV_DISPLAY_STATE
    {
        INBV_DISPLAY_STATE_OWNED,     // we own the display
        INBV_DISPLAY_STATE_DISABLED,  // we own but should not use
        INBV_DISPLAY_STATE_LOST       // we lost ownership
    } INBV_DISPLAY_STATE;

    typedef
        BOOLEAN
        (*INBV_RESET_DISPLAY_PARAMETERS)(
        ULONG Cols,
        ULONG Rows
        );

    typedef
        VOID
        (*INBV_DISPLAY_STRING_FILTER)(
        PUCHAR *Str
        );

    NTKERNELAPI
        VOID
        InbvNotifyDisplayOwnershipLost(
        INBV_RESET_DISPLAY_PARAMETERS ResetDisplayParameters
        );

    NTKERNELAPI
        VOID
        InbvInstallDisplayStringFilter(
        INBV_DISPLAY_STRING_FILTER DisplayStringFilter
        );

    NTKERNELAPI
        VOID
        InbvAcquireDisplayOwnership(
        VOID
        );

    BOOLEAN
        InbvDriverInitialize(
        IN PVOID LoaderBlock,
        IN ULONG Count
        );

    NTKERNELAPI
        BOOLEAN
        InbvResetDisplay(
        );

    VOID
        InbvBitBlt(
        PUCHAR Buffer,
        ULONG x,
        ULONG y
        );

    NTKERNELAPI
        VOID
        InbvSolidColorFill(
        ULONG x1,
        ULONG y1,
        ULONG x2,
        ULONG y2,
        ULONG color
        );

    NTKERNELAPI
        BOOLEAN
        InbvDisplayString(
        PUCHAR Str
        );

    VOID
        InbvUpdateProgressBar(
        ULONG Percentage
        );

    VOID
        InbvSetProgressBarSubset(
        ULONG   Floor,
        ULONG   Ceiling
        );

    VOID
        InbvSetBootDriverBehavior(
        PVOID LoaderBlock
        );

    VOID
        InbvIndicateProgress(
        VOID
        );

    VOID
        InbvSetProgressBarCoordinates(
        ULONG x,
        ULONG y
        );

    NTKERNELAPI
        VOID
        InbvEnableBootDriver(
        BOOLEAN bEnable
        );

    NTKERNELAPI
        BOOLEAN
        InbvEnableDisplayString(
        BOOLEAN bEnable
        );

    NTKERNELAPI
        BOOLEAN
        InbvIsBootDriverInstalled(
        VOID
        );

    PUCHAR
        InbvGetResourceAddress(
        IN ULONG ResourceNumber
        );

    VOID
        InbvBufferToScreenBlt(
        PUCHAR Buffer,
        ULONG x,
        ULONG y,
        ULONG width,
        ULONG height,
        ULONG lDelta
        );

    VOID
        InbvScreenToBufferBlt(
        PUCHAR Buffer,
        ULONG x,
        ULONG y,
        ULONG width,
        ULONG height,
        ULONG lDelta
        );

    BOOLEAN
        InbvTestLock(
        VOID
        );

    VOID
        InbvAcquireLock(
        VOID
        );

    VOID
        InbvReleaseLock(
        VOID
        );

    NTKERNELAPI
        BOOLEAN
        InbvCheckDisplayOwnership(
        VOID
        );

    NTKERNELAPI
        VOID
        InbvSetScrollRegion(
        ULONG x1,
        ULONG y1,
        ULONG x2,
        ULONG y2
        );

    NTKERNELAPI
        ULONG
        InbvSetTextColor(
        ULONG Color
        );
}

#endif

Source.cpp:

C++
#include <wdm.h>
#include <ntddvdeo.h>
#include "Header.h"

#define DELAY_SECOND                        -10000000

#define VGA_COLOR_BLACK                      0
#define VGA_COLOR_RED                        1
#define VGA_COLOR_GREEN                      2
#define VGA_COLOR_GR                         3
#define VGA_COLOR_BULE                       4
#define VGA_COLOR_DARK_MEGAENTA              5
#define VGA_COLOR_TURQUOISE                  6
#define VGA_COLOR_GRAY                       7
#define VGA_COLOR_BRIGHT_GRAY                8
#define VGA_COLOR_BRIGHT_RED                 9
#define VGA_COLOR_BRIGHT_GREEN               10
#define VGA_COLOR_BRIGHT_YELLOW              11
#define VGA_COLOR_BRIGHT_BULE                12
#define VGA_COLOR_BRIGHT_PURPLE              13
#define VGA_COLOR_BRIGHT_TURQUOISE           14
#define VGA_COLOR_WHITE                      15

extern "C"
{
    extern POBJECT_TYPE *IoDriverObjectType;

    NTKERNELAPI NTSTATUS ObOpenObjectByName(POBJECT_ATTRIBUTES pObjectAttributes,
        POBJECT_TYPE pObjectType,
        KPROCESSOR_MODE AccessMode,
        PACCESS_STATE pAccessState,
        ACCESS_MASK DesiredAccess,
        void *pOpenPacket,
        PHANDLE pHandle);

    NTKERNELAPI NTSTATUS IoEnumerateDeviceObjectList(PDRIVER_OBJECT pDriverObject,
        PDEVICE_OBJECT *pDeviceObjectList,
        ULONG          DeviceObjectListSize,
        PULONG         pActualNumberDeviceObjects);
}

NTSTATUS OpenDriver(WCHAR *pDriverName, DRIVER_OBJECT **ppDriverObject)
{
    HANDLE Handle;
    NTSTATUS Status;
    UNICODE_STRING ObjectName;
    OBJECT_ATTRIBUTES ObjectAttributes;
    DRIVER_OBJECT *pDriverObject;

    RtlInitUnicodeString(&ObjectName, pDriverName);

    InitializeObjectAttributes(&ObjectAttributes, &ObjectName, OBJ_CASE_INSENSITIVE, NULL, NULL);

    Status = ObOpenObjectByName(&ObjectAttributes, *IoDriverObjectType, 
                                KernelMode, NULL, FILE_READ_ATTRIBUTES, NULL, &Handle);

    if (NT_SUCCESS(Status))
    {
        Status = ObReferenceObjectByHandle(Handle, 0, NULL, KernelMode, (PVOID*)&pDriverObject, NULL);

        if (NT_SUCCESS(Status)) *ppDriverObject = pDriverObject;

        ZwClose(Handle);
    }

    return Status;
}

NTSTATUS VgaGetDevice(DEVICE_OBJECT **ppDeviceObject)
{
    ULONG Count;
    NTSTATUS Status;
    DRIVER_OBJECT *pDriverObject;
    DEVICE_OBJECT *pDeviceObject;

    Status = OpenDriver(L"\\Driver\\VgaSave", &pDriverObject);

    if (NT_SUCCESS(Status))
    {
        Status = IoEnumerateDeviceObjectList(pDriverObject, &pDeviceObject, 
                                             sizeof(DEVICE_OBJECT*), &Count);

        if (NT_SUCCESS(Status)) *ppDeviceObject = pDeviceObject;

        ObDereferenceObject(pDriverObject);
    }

    return Status;
}

NTSTATUS VgaDeviceIoControl(DEVICE_OBJECT *pDeviceObject,
    ULONG IoControlCode,
    void *pInBuffer,
    ULONG InBufferSize,
    void *pOutBuffer,
    ULONG OutBufferSize,
    ULONG *pBytesReturned,
    BOOLEAN Internal)
{
    IRP *pIrp;
    KEVENT Event;
    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;

    KeInitializeEvent(&Event, SynchronizationEvent, FALSE);

    pIrp = IoBuildDeviceIoControlRequest(IoControlCode, pDeviceObject, 
           pInBuffer, InBufferSize, pOutBuffer, OutBufferSize, Internal, &Event, &IoStatusBlock);

    if (pIrp)
    {
        Status = IoCallDriver(pDeviceObject, pIrp);

        if (Status == STATUS_PENDING)
        {
            do
            {
                Status = KeWaitForSingleObject(&Event, UserRequest, KernelMode, TRUE, NULL);
            } while (Status == STATUS_ALERTED);

            Status = IoStatusBlock.Status;
        }

        *pBytesReturned = IoStatusBlock.Information;
    }
    else
    {
        Status = STATUS_INSUFFICIENT_RESOURCES;

        *pBytesReturned = 0;
    }

    return Status;
}

void DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}

extern "C"
{
    NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
    {
        NTSTATUS Status;
        ULONG BytesReturned;
        VIDEO_MODE VideoMode;
        LARGE_INTEGER Interval;
        DEVICE_OBJECT *pDeviceObject;
        VIDEO_MODE_INFORMATION VideoModeInfo;

        Status = VgaGetDevice(&pDeviceObject);

        if (NT_SUCCESS(Status))
        {
            Status = VgaDeviceIoControl(pDeviceObject, IOCTL_VIDEO_QUERY_CURRENT_MODE, 
                     NULL, 0, &VideoModeInfo, sizeof(VideoModeInfo), &BytesReturned, FALSE);

            if (NT_SUCCESS(Status))
            {
                InbvAcquireDisplayOwnership();
                InbvResetDisplay();     // enter text mode
                InbvSetTextColor(VGA_COLOR_BRIGHT_RED);
                InbvInstallDisplayStringFilter(NULL);
                InbvEnableDisplayString(TRUE);
                InbvDisplayString((UCHAR*)"HELLO!\n");
                InbvNotifyDisplayOwnershipLost(NULL);

                Interval.QuadPart = DELAY_SECOND * 10;
                KeDelayExecutionThread(KernelMode, FALSE, &Interval);

                // leave text mode
                VideoMode.RequestedMode = VideoModeInfo.ModeIndex;
                VgaDeviceIoControl(pDeviceObject, IOCTL_VIDEO_SET_CURRENT_MODE, 
                                   &VideoMode, sizeof(VideoMode), NULL, 0, &BytesReturned, FALSE);
            }

            ObDereferenceObject(pDeviceObject);
        }

        pDriverObject->DriverUnload = DriverUnload;

        return STATUS_SUCCESS;
    }
}

This driver will black out the screen and display string in the top left corner. When we return to previous mode, the screen turns black again, however, we will be able to see Press Enter to close service string. When we hit enter, the following code should be executed:

C++
#include <Windows.h>
#include <Shldisp.h>

...

    ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus);

    DeleteService(hService);
    CloseServiceHandle(hService);

    CloseServiceHandle(hSCManager);

    CoInitialize(NULL);
    IShellDispatch4 *pShellDisp = NULL;
    HRESULT sc = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_SERVER, 
                                  IID_IDispatch, (LPVOID *)&pShellDisp);
    sc = pShellDisp->ToggleDesktop();    // refresh desktop (we can do it manually 
          // by pressing Show desktop button in right bottom corner (on task bar near the tray))
    pShellDisp->Release();

...

Finally, we need to enable all gpu devices again:

C++
#include <Windows.h>
#include <setupapi.h>

#pragma comment(lib,"setupapi.lib")

...

SetGpuState(TRUE);

...

If you would be interested, I will investigate Windows 8 and Windows 10 cases on how to do the same thing.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)