#ifdef BACKEND_DJGPP


#include "os.h"

#include <dos.h>
#include <dpmi.h>
#include <go32.h>
#include <bios.h>
#include <conio.h>
#include <sys/farptr.h>
#include <sys/nearptr.h>

#include "djgpp.h"


// These are all we support
#define VBE_MM_PACKED  4
#define VBE_MM_DCOLOR  6


// Based on http://www.brackeen.com/vga/source/djgpp20/mouse.c.html
#define MOUSE_INT             0x33

#define MOUSE_RESET           0x00
#define MOUSE_STATUS          0x03
#define MOUSE_GETMOTION       0x0B

#define MOUSE_LEFT_BUTTON     0x01
#define MOUSE_RIGHT_BUTTON    0x02
#define MOUSE_MIDDLE_BUTTON   0x04


#define KEYBOARD_READ_EXTENDED   0x10
#define KEYBOARD_CHECK_EXTENDED  0x11
#define KEYBOARD_META_EXTENDED   0x12


enum MetaBitsE {
	KEY_META_SHIFT_RIGHT = 0,
	KEY_META_SHIFT_LEFT,
	KEY_META_CONTROL,
	KEY_META_ALT,
	KEY_META_SCROLL_LOCKED,
	KEY_META_NUM_LOCKED,
	KEY_META_CAPS_LOCKED,
	KEY_META_INSERT_LOCKED,
	KEY_META_CONTROL_LEFT,
	KEY_META_ALT_LEFT,
	KEY_META_CONTROL_RIGHT,
	KEY_META_ALT_RIGHT,
	KEY_META_SCROLL_LOCK,
	KEY_META_NUM_LOCK,
	KEY_META_CAPS_LOCK,
	KEY_META_SYSREQ
};


typedef struct VBEInfoS {
	char      vbeSignature[4];    // 'VESA' 4 byte signature
	int16_t   vbeVersion;         // VBE version number
	char     *oemStringPtr;       // Pointer to OEM string
	uint32_t  capabilities;       // Capabilities of video card
	uint16_t *videoModePtr;       // Pointer to supported modes
	uint16_t  totalMemory;        // Number of 64kb memory blocks
	// added for VBE 2.0
	uint16_t  oemSoftwareRev;     // OEM Software revision number
	char     *oemVendorNamePtr;   // Pointer to Vendor Name string
	char     *oemProductNamePtr;  // Pointer to Product Name string
	char     *oemProductRevPtr;   // Pointer to Product Revision str
	char      reserved[222];      // Pad to 256 byte block size
	char      oemData[256];       // Scratch pad for OEM data
} __attribute__ ((packed)) VBEInfoT;


typedef struct VBEModeInfoS {
	// Mandatory information for all VBE revisions:
	uint16_t  modeAttributes;       // mode attributes
	uint8_t   winAAttributes;       // window A attributes
	uint8_t   winBAttributes;       // window B attributes
	uint16_t  winGranularity;       // window granularity
	uint16_t  winSize;              // window size
	uint16_t  winASegment;          // window A start segment
	uint16_t  winBSegment;          // window B start segment
	uint32_t  winFuncPtr;           // pointer to window function
	uint16_t  bytesPerScanLine;     // bytes per scan line
	// Mandatory information for VBE 1.2 and above:
	uint16_t  xResolution;          // horizontal resolution in pixels or chars
	uint16_t  yResolution;          // vertical resolution in pixels or chars
	uint8_t   xCharSize;            // character cell width in pixels
	uint8_t   yCharSize;            // character cell height in pixels
	uint8_t   numberOfPlanes;       // number of memory planes
	uint8_t   bitsPerPixel;         // bits per pixel
	uint8_t   numberOfBanks;        // number of banks
	uint8_t   memoryModel;          // memory model type
	uint8_t   bankSize;             // bank size in KB
	uint8_t   numberOfImagePages;   // number of images
	uint8_t   reserved;             // reserved for page function
	// Direct Color fields (required for direct/6 and YUV/7 memory models)
	uint8_t   redMaskSize;          // size of direct color red mask in bits
	uint8_t   redFieldPosition;     // bit position of lsb of red mask
	uint8_t   greenMaskSize;        // size of direct color green mask in bits
	uint8_t   greenFieldPosition;   // bit position of lsb of green mask
	uint8_t   blueMaskSize;         // size of direct color blue mask in bits
	uint8_t   blueFieldPosition;    // bit position of lsb of blue mask
	uint8_t   rsvdMaskSize;         // size of direct color reserved mask in bits
	uint8_t   rsvdFieldPosition;    // bit position of lsb of reserved mask
	uint8_t   directColorModeInfo;  // direct color mode attributes
	// Mandatory information for VBE 2.0 and above:
	uint32_t  physBasePtr;          // physical address for flat frame buffer
	uint32_t  offScreenMemOffset;   // pointer to start of off screen memory
	uint16_t  offScreenMemSize;     // amount of off screen memory in 1k units
	char      reservedBuf[206];
} __attribute__ ((packed)) VBEModeInfoT;


typedef struct PModeInterfaceS {
	int16_t setWindow;
	int16_t setDisplayStart;
	int16_t setPalette;
	int16_t ioInfo;
} __attribute__ ((packed)) PModeInterfaceT;


typedef struct VBESurfaceS {
	uint32_t  lfbLinearAddress;
	uint16_t  lfbSelector;
	void     *lfbNearPtr;
	uint32_t  lfbMemSize;
	uint16_t  xResolution;
	uint16_t  yResolution;
	uint32_t  bitsPerPixel;
	uint16_t  virtualXResolution;
	uint32_t  bytesPerPixel;
	uint32_t  screenBytes;
	uint32_t  screenDWords;
	uint32_t  centerX;
	uint32_t  centerY;
	uint16_t  numberOfOffscreens;
	uint8_t   vbeBoolean;
	uint8_t   vbeInitBoolean;
	int32_t   ioSegment;
	uint32_t  ioLinear;
	uint32_t  rMask;
	uint32_t  gMask;
	uint32_t  bMask;
	uint32_t  aMask;
	uint8_t   rShift;
	uint8_t   gShift;
	uint8_t   bShift;
	uint8_t   aShift;
	uint8_t   rPos;
	uint8_t   gPos;
	uint8_t   bPos;
	uint8_t   aPos;
} VBESurfaceT;


extern ColorT   *__guiBaseColors;


static VBESurfaceT      _vbeSurface;
static VBEInfoT         _vbeInfo;
static VBEModeInfoT     _vbeModeInfo;
static PModeInterfaceT *_pmodeInterfacePtr;
static uint32_t        *_yTable;
static SurfaceT        *_activeSurface     = NULL;


static void          vbeCreatePalette(void);
static VBEInfoT     *vbeGetInfo(void);
static VBEModeInfoT *vbeGetModeInfo(uint16_t vbeModeNumber);
static void         *vbeGetPmodeInterface(void);
static uint8_t       vbeIsDesiredMode(void);
static uint16_t      vbeSelectModeNumber(uint16_t xRes, uint16_t yRes, uint8_t bpp);
static VBESurfaceT  *vbeSetMode(uint16_t vbeModeNumber);
static uint16_t      vbeSetScanlineLength(uint16_t pixelLength);

static void videoSurfacePixelSet8(uint16_t x, uint16_t y, ColorT color);
static void videoSurfacePixelSet16(uint16_t x, uint16_t y, ColorT color);
static void videoSurfacePixelSet32(uint16_t x, uint16_t y, ColorT color);

static void (*pmVBESetDisplayStart)(void);

void (*videoSurfacePixelSet)(uint16_t x, uint16_t y, ColorT color);


void platformEventGet(EventT *event) {
	int32_t    x;
	int32_t    y;
	int16_t    dx;
	int16_t    dy;
	int16_t    h    = videoDisplayHeightGet();
	int16_t    w    = videoDisplayWidthGet();
	int16_t    ext  = bioskey(KEYBOARD_CHECK_EXTENDED);
	int16_t    meta = bioskey(KEYBOARD_META_EXTENDED);
	int16_t    key  = 0;
	union REGS regs;

	// Read mouse motion.
	regs.x.ax = MOUSE_GETMOTION;
	int86(MOUSE_INT, &regs, &regs);
	dx = regs.x.cx;  // Temporary assignment changes values to signed.
	dy = regs.x.dx;  // Don't skip this step.  :-)
	x = event->x + dx;
	y = event->y + dy;
	if (x < 0)     x = 0;
	if (x > w - 1) x = w - 1;
	if (y < 0)     y = 0;
	if (y > h - 1) y = h - 1;
	event->x = x;
	event->y = y;

	// Read mouse buttons.
	regs.x.ax = MOUSE_STATUS;
	int86(MOUSE_INT, &regs, &regs);

	// Was the left button down?
	if (event->buttons & BUTTON_LEFT) {
		// Yes.  Is it still down?
		if ((regs.x.bx & MOUSE_LEFT_BUTTON) > 0) {
			// Yes.  Do nothing.
		} else {
			// No!  Clear it and set UP event.
			event->buttons &= ~BUTTON_LEFT;
			event->flags |= EVENT_FLAG_LEFT_UP;
		}
	} else {
		// No.  Is it down now?
		if ((regs.x.bx & MOUSE_LEFT_BUTTON) > 0) {
			// Yes!  Set bit and DOWN event.
			event->buttons |= BUTTON_LEFT;
			event->flags |= EVENT_FLAG_LEFT_DOWN;
		} else {
			// No.  Do nothing.
		}
	}

	// Was the right button down?
	if (event->buttons & BUTTON_RIGHT) {
		// Yes.  Is it still down?
		if ((regs.x.bx & MOUSE_RIGHT_BUTTON) > 0) {
			// Yes.  Do nothing.
		} else {
			// No!  Clear it and set UP event.
			event->buttons &= ~BUTTON_RIGHT;
			event->flags |= EVENT_FLAG_RIGHT_UP;
		}
	} else {
		// No.  Is it down now?
		if ((regs.x.bx & MOUSE_RIGHT_BUTTON) > 0) {
			// Yes!  Set bit and DOWN event.
			event->buttons |= BUTTON_RIGHT;
			event->flags |= EVENT_FLAG_RIGHT_DOWN;
		} else {
			// No.  Do nothing.
		}
	}

	// Read keyboard.
	event->key    = 0;
	event->kbstat = 0;
	if (ext > 0) {
		key = bioskey(KEYBOARD_READ_EXTENDED);
		// The value returned is a combination of the key's scan code in the high 8 bits
		// and its ASCII code in the low 8 bits. For non-alphanumeric keys, such as the
		// arrow keys, the low 8 bits are zeroed.
		// Extended keys have the E0h prefix in the low 8 bits.
		event->flags |= EVENT_FLAG_KEYPRESS;
		if (LOW_BYTE(key) == 0xE0) {
			//_extended = 1;
			event->key = HIGH_BYTE(key);
		} else {
			//_extended = 0;
			event->key = LOW_BYTE(key);
		}
		if ((meta & (1 << KEY_META_ALT)) + (meta & (1 << KEY_META_ALT_LEFT)) + (meta & (1 << KEY_META_ALT_RIGHT)))             event->kbstat |= META_ALT;
		if ((meta & (1 << KEY_META_CONTROL)) + (meta & (1 << KEY_META_CONTROL_LEFT)) + (meta & (1 << KEY_META_CONTROL_RIGHT))) event->kbstat |= META_CTRL;
		if ((meta & (1 << KEY_META_SHIFT_LEFT)) + (meta & (1 << KEY_META_SHIFT_RIGHT)))                                        event->kbstat |= META_SHIFT;
	}
}


void platformShutdown(void) {
	__dpmi_regs    r;
	__dpmi_meminfo m;

	free(__guiBaseColors);

	if (_vbeSurface.vbeInitBoolean == 0) {
		r.x.ax = 0x03;               // make sure we're in 3h
		__dpmi_int(0x10, &r);        // for buggy vesa implementations
		//return(1);
	}

	if (_yTable) free(_yTable);

	// free mapping etc.
	m.size = (_vbeInfo.totalMemory * 64 * 1024);
	m.address = _vbeSurface.lfbLinearAddress;
	__dpmi_unlock_linear_region(&m);
	__dpmi_free_physical_address_mapping(&m);
	__dpmi_free_ldt_descriptor(_vbeSurface.lfbSelector);

	// get rid of PMI interface
	if (_pmodeInterfacePtr) free(_pmodeInterfacePtr);
	if (_vbeSurface.ioSegment) {
		m.address = _vbeSurface.ioLinear;
		__dpmi_free_physical_address_mapping(&m);
		__dpmi_free_ldt_descriptor(_vbeSurface.ioSegment);
	}

	// return do DOS
	r.x.ax = 0x03;
	__dpmi_int(0x10, &r);

	// deinit VBE surface
	_vbeSurface.vbeInitBoolean = 0;
	_vbeSurface.vbeBoolean = 0;

	// dealloc mode list
	free(_vbeInfo.videoModePtr);
}


uint8_t platformStartup(int16_t width, int16_t height, int16_t depth) {
	uint16_t vbeModeNumber;
	uint8_t  i;
	uint8_t  EGA[16][3] = {
			{ 0,   0,   0   },      /* black */
			{ 0,   0,   170 },      /* blue */
			{ 0,   170, 0   },      /* green */
			{ 0,   170, 170 },      /* cyan */
			{ 170, 0,   0   },      /* red */
			{ 170, 0,   170 },      /* magenta */
			{ 170, 85,  0   },      /* brown */
			{ 170, 170, 170 },      /* light gray */
			{ 85,  85,  85  },      /* dark gray */
			{ 85,  85,  255 },      /* light blue */
			{ 85,  255, 85  },      /* light green */
			{ 85,  255, 255 },      /* light cyan */
			{ 255, 85,  85  },      /* light red */
			{ 255, 85,  255 },      /* light magenta */
			{ 255, 255, 85  },      /* yellow */
			{ 255, 255, 255 }       /* white */
	};

	if (vbeGetInfo() == NULL) {
		logWrite("No VESA BIOS Extensions found.\n");
		platformShutdown();
		return FAIL;
	}

	if (_vbeInfo.vbeVersion < 0x0200) {
		logWrite("VBE Version 2.0 or better required.\n");
		platformShutdown();
		return FAIL;
	}

	vbeGetPmodeInterface();

	if ((vbeModeNumber = vbeSelectModeNumber(width, height, depth)) == 0) {
		logWrite("No appropriate video mode available.\n");
		platformShutdown();
		return FAIL;
	}

	if (vbeSetMode(vbeModeNumber) == NULL) {
		platformShutdown();
		return FAIL;
	}

	if (_vbeSurface.bitsPerPixel ==  8) videoSurfacePixelSet = videoSurfacePixelSet8;
	if (_vbeSurface.bitsPerPixel == 16) videoSurfacePixelSet = videoSurfacePixelSet16;
	if (_vbeSurface.bitsPerPixel == 15) videoSurfacePixelSet = videoSurfacePixelSet16;
	if (_vbeSurface.bitsPerPixel == 32) videoSurfacePixelSet = videoSurfacePixelSet32;

	__guiBaseColors = (ColorT *)malloc(sizeof(ColorT) * 16);
	for (i=0; i<16; i++) {
		__guiBaseColors[i] = videoColorMake(EGA[i][0], EGA[i][1], EGA[i][2]);
	}

	return SUCCESS;
}


static void vbeCreatePalette(void) {
	uint8_t color = 0;
	uint8_t red;
	uint8_t green;
	uint8_t blue;

	// Create palette for 3:3:2 true color emulation
	for (red = 4; red < 68; red += 8) {
		for (green = 4; green < 68; green += 8) {
			for (blue = 4; blue < 68; blue += 16) {
				outportb(0x3C8, color);
				outportb(0x3C9, red);
				outportb(0x3C9, green);
				outportb(0x3C9, blue);
				color++;
			}
		}
	}
}


static VBEInfoT *vbeGetInfo(void) {
	uint16_t     counter     = 0;
	uint16_t     offset      = 0;
	uint16_t     vbeMode     = 0xFFFF;
	uint16_t    *vbeModeList = 0;
	__dpmi_regs  r;

	// we want VBE 2.0+ info
	memcpy(_vbeInfo.vbeSignature, "VBE2", 4);

	// request info
	r.x.ax = 0x4F00;
	r.x.di = __tb & 0x0F;
	r.x.es = (__tb >> 4) & 0xFFFF;
	dosmemput(&_vbeInfo, sizeof(VBEInfoT), __tb);
	__dpmi_int(0x10, &r);
	if (r.x.ax != 0x004F) return NULL;
	dosmemget(__tb, sizeof(VBEInfoT), &_vbeInfo);

	if (strncmp(_vbeInfo.vbeSignature, "VESA", 4) != 0) return NULL;  // VESA ?

	// get the videomode list
	do {
		dosmemget(((((long)_vbeInfo.videoModePtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.videoModePtr & 0xFFFF) + (counter * sizeof(short)), sizeof(short), &vbeMode);
		counter++;
	} while (vbeMode != 0xFFFF);
	vbeModeList = malloc((counter + 1) * sizeof(short));

	counter = 0;
	do {
		dosmemget(((((long)_vbeInfo.videoModePtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.videoModePtr & 0xFFFF) + (counter * sizeof(short)), sizeof(short), &vbeModeList[counter]);
		counter++;
	} while (vbeModeList[counter - 1] != 0xFFFF);
	_vbeInfo.videoModePtr = vbeModeList;

	// get the OEM string
	counter = 0;
	do {
		dosmemget(((((long)_vbeInfo.oemStringPtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.oemStringPtr & 0xFFFF) + counter, sizeof(char), &_vbeInfo.oemData[counter]);
		counter++;
	} while (_vbeInfo.oemData[counter - 1] != 0);
	_vbeInfo.oemStringPtr = &_vbeInfo.oemData[0];
	offset = counter;

	if (_vbeInfo.vbeVersion >= 0x0200) {
		// get the vendor name
		counter = 0;
		do {
			dosmemget(((((long)_vbeInfo.oemVendorNamePtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.oemVendorNamePtr & 0xFFFF) + counter, sizeof(char), &_vbeInfo.oemData[counter + offset]);
			counter++;
		} while (_vbeInfo.oemData[counter + offset - 1] != 0);
		_vbeInfo.oemVendorNamePtr = &_vbeInfo.oemData[offset];
		offset = offset + counter;

		// get the product name
		counter = 0;
		do {
			dosmemget(((((long)_vbeInfo.oemProductNamePtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.oemProductNamePtr & 0xFFFF) + counter, sizeof(char), &_vbeInfo.oemData[counter + offset]);
			counter++;
		} while (_vbeInfo.oemData[counter + offset - 1] != 0);
		_vbeInfo.oemProductNamePtr = &_vbeInfo.oemData[offset];
		offset = offset + counter;

		//get the product revision
		counter = 0;
		do {
			dosmemget(((((long)_vbeInfo.oemProductRevPtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.oemProductRevPtr & 0xFFFF) + counter, sizeof(char), &_vbeInfo.oemData[counter + offset]);
			counter++;
		} while (_vbeInfo.oemData[counter + offset - 1] != 0);
		_vbeInfo.oemProductRevPtr = &_vbeInfo.oemData[offset];
	}

	_vbeSurface.vbeBoolean = 1;

	return(&_vbeInfo);
}


static VBEModeInfoT *vbeGetModeInfo(uint16_t vbeModeNumber) {
	__dpmi_regs r;

	if (_vbeSurface.vbeBoolean == 0) return NULL;

	r.x.ax = 0x4F01;
	r.x.cx = vbeModeNumber;
	r.x.di = __tb & 0x0F;
	r.x.es = (__tb >> 4) & 0xFFFF;
	__dpmi_int(0x10, &r);

	if (r.x.ax != 0x004F) return NULL;

	dosmemget(__tb, sizeof(VBEModeInfoT), &_vbeModeInfo);

	// Fake 3:3:2 true color for packed modes.
	if (_vbeModeInfo.bitsPerPixel == 8) {
		_vbeModeInfo.blueMaskSize  = 2;
		_vbeModeInfo.greenMaskSize = 3;
		_vbeModeInfo.redMaskSize   = 3;
	}

	if (_vbeModeInfo.bitsPerPixel == 16) {
		// for buggy VBE implementations
		_vbeModeInfo.bitsPerPixel = _vbeModeInfo.redMaskSize + _vbeModeInfo.greenMaskSize + _vbeModeInfo.blueMaskSize;
	}

	return(&_vbeModeInfo);
}


static void *vbeGetPmodeInterface(void) {
	__dpmi_regs     r;
	__dpmi_meminfo  m;
	uint16_t       *ptr;

	// only available in VBE 2.0+
	if (_vbeInfo.vbeVersion < 0x200) return NULL;

	r.x.ax = 0x4F0A;
	r.x.bx = 0;
	__dpmi_int(0x10, &r);
	if (r.x.ax != 0x004F) return NULL;

	// copy interface
	_pmodeInterfacePtr = (PModeInterfaceT *)malloc(r.x.cx);
	dosmemget((r.x.es * 16) + r.x.di, r.x.cx, _pmodeInterfacePtr);
	_go32_dpmi_lock_data(_pmodeInterfacePtr, r.x.cx);

	// need memory-mapped IO?
	if (_pmodeInterfacePtr->ioInfo) {
		ptr = (uint16_t *)((char *)_pmodeInterfacePtr + _pmodeInterfacePtr->ioInfo);
		// skip the port table...
		while (*ptr != 0xFFFF)
			ptr++;
		ptr++;
		// ...and get descriptor
		if (*ptr != 0xFFFF) {
			m.address = *((uint32_t *)ptr);
			m.size = *(ptr + 2);
			if (__dpmi_physical_address_mapping(&m) != 0) return NULL;
			_vbeSurface.ioLinear = m.address;
			__dpmi_lock_linear_region(&m);
			_vbeSurface.ioSegment = __dpmi_allocate_ldt_descriptors(1);
			if (_vbeSurface.ioSegment < 0) {
				__dpmi_unlock_linear_region(&m);
				__dpmi_free_physical_address_mapping(&m);
				return NULL;
			}
			__dpmi_set_segment_base_address(_vbeSurface.ioSegment, _vbeSurface.ioLinear);
			__dpmi_set_segment_limit(_vbeSurface.ioSegment, m.size - 1);
		}
	}

	pmVBESetDisplayStart = (void *)((char *)_pmodeInterfacePtr + _pmodeInterfacePtr->setDisplayStart);

	return pmVBESetDisplayStart;
}


static uint8_t vbeIsDesiredMode(void) {
	// This is a custom filter to remove modes not supported by the calling application.

	// Must be linear.
	if (((_vbeModeInfo.modeAttributes) & (1<<7)) >> 7) {
		// Packed or Direct Color mode.
		if (_vbeModeInfo.memoryModel == VBE_MM_PACKED || _vbeModeInfo.memoryModel == VBE_MM_DCOLOR) {
			// We only handle these bit depths.
			if ((_vbeModeInfo.bitsPerPixel == 8) || (_vbeModeInfo.bitsPerPixel == 15) || (_vbeModeInfo.bitsPerPixel == 16) || (_vbeModeInfo.bitsPerPixel == 32)) {
				// Resolution minimum of 640x480.
				if (_vbeModeInfo.xResolution >= 640 && _vbeModeInfo.yResolution >= 480) {
					// Multiple of 8
					if (DIVISIBLE_BY_EIGHT(_vbeModeInfo.xResolution) && DIVISIBLE_BY_EIGHT(_vbeModeInfo.yResolution)) {
						// Valid mode!
						return 1;
					}
				}
			}
		}
	}

	// Rejected!
	return 0;
}


static uint16_t vbeSelectModeNumber(uint16_t xRes, uint16_t yRes, uint8_t bpp) {
	uint16_t counter;

	if (_vbeSurface.vbeBoolean == 0) return(0);

	for (counter = 0; ; counter++) {
		if (_vbeInfo.videoModePtr[counter] == 0xFFFF) return(0);
		vbeGetModeInfo(_vbeInfo.videoModePtr[counter]);
		if (vbeIsDesiredMode()) {
			if (_vbeModeInfo.xResolution  == xRes && _vbeModeInfo.yResolution  == yRes && _vbeModeInfo.bitsPerPixel == bpp)
				break;
		}
	}

	return(_vbeInfo.videoModePtr[counter]);
}


static VBESurfaceT *vbeSetMode(uint16_t vbeModeNumber) {
	__dpmi_regs    r;
	__dpmi_meminfo m;
	uint32_t       counter;

	if (_vbeSurface.vbeBoolean == 0) return NULL;
	if (_vbeSurface.vbeInitBoolean == 1) return NULL;
	if (vbeGetModeInfo(vbeModeNumber) == 0) return NULL;

	if (_yTable) free(_yTable);
	if ((_yTable = malloc(4 * (_vbeModeInfo.yResolution + 1))) == 0) return NULL;
	for (counter = 0; counter <= _vbeModeInfo.yResolution; counter++) {
		_yTable[counter] = _vbeModeInfo.xResolution * counter;// * ((_vbeModeInfo.bitsPerPixel + 1) / 8);
	}

	// request frame buffer
	r.x.ax = 0x4F02;
	r.x.bx = (vbeModeNumber | 0x4000);
	__dpmi_int(0x10, &r);
	if (r.x.ax != 0x004F) return(0);

	m.size = (_vbeInfo.totalMemory * 64 * 1024);
	m.address = _vbeModeInfo.physBasePtr;
	__dpmi_physical_address_mapping(&m);
	__dpmi_lock_linear_region(&m);

	_vbeSurface.lfbLinearAddress = m.address;
	_vbeSurface.lfbSelector = __dpmi_allocate_ldt_descriptors(1);

	__dpmi_set_segment_base_address(_vbeSurface.lfbSelector, _vbeSurface.lfbLinearAddress);
	__dpmi_set_segment_limit(_vbeSurface.lfbSelector, (_vbeInfo.totalMemory * 64 * 1024) - 1);

	_vbeSurface.lfbNearPtr  = (void *)(_vbeSurface.lfbLinearAddress + __djgpp_conventional_base);
	_vbeSurface.xResolution          = _vbeModeInfo.xResolution;
	_vbeSurface.yResolution          = _vbeModeInfo.yResolution;
	_vbeSurface.bitsPerPixel         = _vbeModeInfo.bitsPerPixel;
	_vbeSurface.virtualXResolution   = _vbeModeInfo.xResolution;
	_vbeSurface.bytesPerPixel        = (_vbeModeInfo.bitsPerPixel + 1) / 8;
	_vbeSurface.centerX              = _vbeSurface.virtualXResolution / 2;
	_vbeSurface.centerY              = _vbeSurface.yResolution / 2;
	_vbeSurface.numberOfOffscreens   = vbeSetScanlineLength(_vbeModeInfo.xResolution) / _vbeModeInfo.yResolution;
	_vbeSurface.vbeInitBoolean       = 1;
	_vbeSurface.screenBytes          = (_vbeSurface.xResolution * _vbeSurface.yResolution * _vbeSurface.bytesPerPixel);
	_vbeSurface.screenDWords         = _vbeSurface.screenBytes / 4;
	_vbeSurface.lfbMemSize           = (_vbeSurface.xResolution * _vbeSurface.yResolution * _vbeSurface.bytesPerPixel) / 1024 + _vbeModeInfo.offScreenMemSize;

	for (counter = 0; counter < (_vbeSurface.lfbMemSize * 1024); counter++) {
		_farpokeb(_vbeSurface.lfbSelector, counter, 0x0); // clear Lfb
	}

	if (_vbeModeInfo.memoryModel == VBE_MM_PACKED) {
		vbeCreatePalette();

		_vbeModeInfo.redFieldPosition   = 5;
		_vbeModeInfo.greenFieldPosition = 2;
		_vbeModeInfo.blueFieldPosition  = 0;
		_vbeModeInfo.rsvdFieldPosition  = 7;

		_vbeModeInfo.redMaskSize   = 3;
		_vbeModeInfo.greenMaskSize = 3;
		_vbeModeInfo.blueMaskSize  = 2;
		_vbeModeInfo.rsvdMaskSize  = 0;
	}

	_vbeSurface.rMask = ((1UL << _vbeModeInfo.redMaskSize)   - 1) << _vbeModeInfo.redFieldPosition;
	_vbeSurface.gMask = ((1UL << _vbeModeInfo.greenMaskSize) - 1) << _vbeModeInfo.greenFieldPosition;
	_vbeSurface.bMask = ((1UL << _vbeModeInfo.blueMaskSize)  - 1) << _vbeModeInfo.blueFieldPosition;
	_vbeSurface.aMask = ((1UL << _vbeModeInfo.rsvdMaskSize)  - 1) << _vbeModeInfo.rsvdFieldPosition;

	_vbeSurface.rShift = 8 - _vbeModeInfo.redMaskSize;
	_vbeSurface.gShift = 8 - _vbeModeInfo.greenMaskSize;
	_vbeSurface.bShift = 8 - _vbeModeInfo.blueMaskSize;
	_vbeSurface.aShift = 8 - _vbeModeInfo.rsvdMaskSize;

	_vbeSurface.rPos = _vbeModeInfo.redFieldPosition;
	_vbeSurface.gPos = _vbeModeInfo.greenFieldPosition;
	_vbeSurface.bPos = _vbeModeInfo.blueFieldPosition;
	_vbeSurface.aPos = _vbeModeInfo.rsvdFieldPosition;

	return(&_vbeSurface);
}


static uint16_t vbeSetScanlineLength(uint16_t pixelLength) {
	__dpmi_regs r;

	if (_vbeSurface.vbeBoolean == 0) return(0);

	r.x.ax = 0x4F06;
	r.x.bx = 0x0000;
	r.x.cx = pixelLength;
	__dpmi_int(0x10, &r);
	if (r.h.ah != 0) return(0);
	if (r.x.cx != pixelLength) return(0);

	_vbeSurface.virtualXResolution = pixelLength;

	return(r.x.dx);
}


ColorT videoColorMake(uint8_t red, uint8_t green, uint8_t blue) {
	return
		(((red   >> _vbeSurface.rShift) << _vbeSurface.rPos) & _vbeSurface.rMask) |
		(((green >> _vbeSurface.gShift) << _vbeSurface.gPos) & _vbeSurface.gMask) |
		(((blue  >> _vbeSurface.bShift) << _vbeSurface.bPos) & _vbeSurface.bMask);
}


uint16_t videoDisplayHeightGet(void) {
	return _vbeSurface.yResolution;
}


uint16_t videoDisplayWidthGet(void) {
	return _vbeSurface.xResolution;
}


void videoModesShow(void) {
	int8_t counter;

	//    0        1         2         3         4         5         6         7         8
	//    12345678901234567890123456789012345678901234567890123456789012345678901234567890
	//printf("VBE 2.0 driver v1.0 (c) 2021, Scott Duensing <scott@kangaroopunch.com>\n");
	//printf("Based on:  VBE 2.0 driver v1.0 (c) 1999, Tobias Koch <tobias.koch@gmail.com>\n\n");

	if (vbeGetInfo() == NULL) {
		logWrite("No VESA BIOS Extensions found.\n");
		platformShutdown();
		return;
	}
	logWrite(
		"Video Memory             - %d KB\n"
		"VBE Version              - %d.%d detected\n"
		"OEM Specification        - %s\n",
		_vbeInfo.totalMemory * 64,
		_vbeInfo.vbeVersion >> 8, _vbeInfo.vbeVersion & 0x00FF,
		_vbeInfo.oemStringPtr);

	if (_vbeInfo.vbeVersion >= 0x0200) {
		logWrite(
			"OEM Software Revision    - %d.%d\n"
			"OEM Vendor Name          - %s\n"
			"OEM Product Name         - %s\n"
			"OEM Product Revision     - %s\n"
			"Protected Mode Interface - %s\n\n",
			_vbeInfo.oemSoftwareRev >> 8, _vbeInfo.oemSoftwareRev & 0x00FF,
			_vbeInfo.oemVendorNamePtr,
			_vbeInfo.oemProductNamePtr,
			_vbeInfo.oemProductRevPtr,
			vbeGetPmodeInterface() ? "Found" : "Missing");
	} else {
		logWrite("VESA BIOS Extension 2.0 or better required!\n");
		platformShutdown();
		return;
	}

	for (counter=0; ; counter++) {
		if (_vbeInfo.videoModePtr[counter] == 0xFFFF) break;
		vbeGetModeInfo(_vbeInfo.videoModePtr[counter]);
		if (vbeIsDesiredMode()) {
			logWrite("Mode %Xh - %4d x %4d x %2d - RGB %d:%d:%d - %s\n",
				_vbeInfo.videoModePtr[counter],
				_vbeModeInfo.xResolution,
				_vbeModeInfo.yResolution,
				_vbeModeInfo.bitsPerPixel,
				_vbeModeInfo.redMaskSize,
				_vbeModeInfo.greenMaskSize,
				_vbeModeInfo.blueMaskSize,
				((_vbeModeInfo.memoryModel / 2) - 2) ? "DCOLOR" : "PACKED");
		  }
	}

	platformShutdown();
}


void videoSurfaceBlit(SurfaceT *target, int16_t targetX, int16_t targetY, SurfaceT *source) {
	uint16_t  y1;
	size_t    offsetTarget;
	size_t    offsetSource;

	// HACK!  Are we copying to the screen?  This assumes a full screen update.
	if (!target) {
		_movedatal(_my_ds(), (int32_t)source->buffer.bits32, _vbeSurface.lfbSelector, 0x0, _vbeSurface.screenDWords);
		return;
	}

	if (targetX == 0 && targetY == 0 && target->width == source->width && target->height == source->height) {
		// Direct blit of entire surface.
		memcpy(target->buffer.bits8, source->buffer.bits8, source->bytes);
	} else {
		// Blit into larger surface.
		offsetTarget = targetY * _activeSurface->scanline + targetX * _vbeSurface.bytesPerPixel;
		offsetSource = 0;
		for (y1=targetY; y1<targetY+source->height; y1++) {
			memcpy(&target->buffer.bits8[offsetTarget], &source->buffer.bits8[offsetSource], source->scanline);
			offsetTarget += target->scanline;
			offsetSource += source->scanline;
		}
	}
}


void videoSurfaceBlitWithTransparency(SurfaceT *target, int16_t targetX, int16_t targetY, SurfaceT *source, ColorT transparent) {
	uint16_t  x1;
	uint16_t  y1;
	uint16_t  x2    = source->width;
	uint16_t  y2    = source->height;
	ColorT    pixel;
	SurfaceT *t     = videoSurfaceGet();

	videoSurfaceSet(target);

	// Clip on right and bottom
	if (targetX + x2 > target->width)  x2 -= targetX + x2 - target->width;
	if (targetY + y2 > target->height) y2 -= targetY + y2 - target->height;

	for (y1=0; y1<y2; y1++) {
		for (x1=0; x1<x2; x1++) {
			pixel = source->buffer.bits32[y1 * source->width + x1];
			if (transparent != pixel) {
				videoSurfacePixelSet(targetX + x1, targetY + y1, pixel);
			}
		}
	}

	videoSurfaceSet(t);
}


void videoSurfaceClear(ColorT color) {
	uint16_t x;
	size_t   offsetTarget;

	// Draw the top line.
	for (x=0; x<_activeSurface->width; x++) {
		videoSurfacePixelSet(x, 0, color);
	}

	// Copy it to the other lines.
	offsetTarget = _activeSurface->scanline;
	for (x=1; x<_activeSurface->height; x++) {
		memcpy(&_activeSurface->buffer.bits8[offsetTarget], &_activeSurface->buffer.bits8[0], _activeSurface->scanline);
		offsetTarget += _activeSurface->scanline;
	}
}


SurfaceT *videoSurfaceCreate(int16_t width, int16_t height) {
	SurfaceT *surface = (SurfaceT *)malloc(sizeof(SurfaceT));

	if (!surface) return NULL;

	surface->width    = width;
	surface->height   = height;
	surface->scanline = width * _vbeSurface.bytesPerPixel;
	surface->bytes    = surface->scanline * height;

	surface->buffer.bits8 = malloc(surface->bytes);
	if (!surface->buffer.bits8) {
		free(surface);
		return NULL;
	}

	memset(surface->buffer.bits8, 0, surface->bytes);

	return surface;
}


void videoSurfaceBox(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c) {
	videoSurfaceLineH(x1, x2, y1, c);
	videoSurfaceLineH(x1, x2, y2, c);
	videoSurfaceLineV(x1, y1, y2, c);
	videoSurfaceLineV(x2, y1, y2, c);
}


void videoSurfaceBoxFilled(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c) {
	int16_t  i;
	size_t   offsetTarget;
	size_t   offsetSource;
	uint16_t width;

	if (x1 > x2) {
		i  = x1;
		x1 = x2;
		x2 = i;
	}

	if (y1 > y2) {
		i  = y1;
		y1 = y2;
		y2 = i;
	}

	width = (x2 - x1 + 1) * _vbeSurface.bytesPerPixel;

	// Draw the top line.
	for (i=x1; i<=x2; i++) {
		videoSurfacePixelSet(i, y1, c);
	}

	// Copy it to the other lines.
	offsetTarget = _activeSurface->scanline * (y1 + 1) + (x1 * _vbeSurface.bytesPerPixel);
	offsetSource = _activeSurface->scanline * y1 + (x1 * _vbeSurface.bytesPerPixel);
	for (i=y1 + 1; i<=y2; i++) {
		memcpy(&_activeSurface->buffer.bits8[offsetTarget], &_activeSurface->buffer.bits8[offsetSource], width);
		offsetTarget += _activeSurface->scanline;
	}
}


void videoSurfaceDestroy(SurfaceT *surface) {
	free(surface->buffer.bits8);
	free(surface);
}


SurfaceT *videoSurfaceGet(void) {
	return _activeSurface;
}


int16_t videoSurfaceHeightGet(SurfaceT *surface) {
	return surface->height;
}


void videoSurfaceLineH(int16_t x1, int16_t x2, int16_t y, ColorT c) {
	int16_t i;
	int16_t t;

	if (x1 > x2) {
		t  = x2;
		x2 = x1;
		x1 = t;
	}

	for (i=x1; i<=x2; i++) {
		videoSurfacePixelSet(i, y, c);
	}
}


void videoSurfaceLineV(int16_t x, int16_t y1, int16_t y2, ColorT c) {
	int16_t i;
	int16_t t;

	if (y1 > y2) {
		t  = y2;
		y2 = y1;
		y1 = t;
	}

	for (i=y1; i<=y2; i++) {
		videoSurfacePixelSet(x, i, c);
	}
}


ColorT videoSurfacePixelGet(SurfaceT *surface, int16_t x, int16_t y) {
	return surface->buffer.bits32[y * surface->width + x];
}


static void videoSurfacePixelSet8(uint16_t x, uint16_t y, ColorT color) {
	_activeSurface->buffer.bits8[y * _activeSurface->width + x] = (uint8_t)color;
}


static void videoSurfacePixelSet16(uint16_t x, uint16_t y, ColorT color) {
	_activeSurface->buffer.bits16[y * _activeSurface->width + x] = (uint16_t)color;
}


static void videoSurfacePixelSet32(uint16_t x, uint16_t y, ColorT color) {
	_activeSurface->buffer.bits32[y * _activeSurface->width + x] = color;
}


void videoSurfaceSet(SurfaceT *surface) {
	_activeSurface = surface;
}


SurfaceT *videoSurfaceScreenGet(void) {
	return NULL;
}


int16_t videoSurfaceWidthGet(SurfaceT *surface) {
	return surface->width;
}


#endif  // BACKEND_DJGPP
