/*
 * Kangaroo Punch MultiPlayer Game Server Mark II
 * Copyright (C) 2020-2021  Scott Duensing
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */


#include "os.h"

#include "vesa.h"
#include "surface.h"
#include "log.h"


// http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html


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


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;


static VBESurfaceT      _vbeSurface;
static VBEInfoT         _vbeInfo;
static VBEModeInfoT     _vbeModeInfo;
static PModeInterfaceT *_pmodeInterfacePtr;
static uint32_t        *_yTable;


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 (*pmVBESetDisplayStart)(void);


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++;
			}
		}
	}
}


uint8_t vbeDisplayDepthBitsGet(void) {
	return _vbeSurface.bitsPerPixel;
}


uint8_t vbeDisplayDepthBytesGet(void) {
	return _vbeSurface.bytesPerPixel;
}


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


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


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) {
			// Resolution minimum of 640x480.
			if (_vbeModeInfo.xResolution >= 800 && _vbeModeInfo.yResolution >= 600) {
				// Multiple of 8
				if (DIVISIBLE_BY_EIGHT(_vbeModeInfo.xResolution) && DIVISIBLE_BY_EIGHT(_vbeModeInfo.yResolution)) {
					// Valid mode!
					return 1;
				}
			}
		}
	}

	// Rejected!
	return 0;
}


ColorT vbeColorMake(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);
}


void vbePresent(void) {
	SurfaceT *o = surfaceOffScreenGet();
	 _movedatal(_my_ds(), (int32_t)o->buffer.bits32, _vbeSurface.lfbSelector, 0x0, _vbeSurface.screenDWords);
}


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);
}


int16_t vbeInfoShow(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");
		vbeShutdown();
		return(1);
	}
	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");
		vbeShutdown();
		return(1);
	}

	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");
		  }
	}

	vbeShutdown();
	return(0);
}


int16_t vbeShutdown(void) {
	__dpmi_regs    r;
	__dpmi_meminfo m;

	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);
	return(0);
}


uint8_t vbeStartup(uint16_t xRes, uint16_t yRes, uint8_t bpp) {
	uint16_t vbeModeNumber;

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

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

	vbeGetPmodeInterface();

	if ((vbeModeNumber = vbeSelectModeNumber(xRes, yRes, bpp)) == 0) {
		logWrite("No appropriate video mode available.\n");
		vbeShutdown();
		return 3;
	}

	if (vbeSetMode(vbeModeNumber) == NULL) {
		vbeShutdown();
		return 4;
	}

	return 0;
}


void vbeVBlankWait(void) {
	 while(inportb(0x3DA) & 8);
	 while(!(inportb(0x3DA) & 8));
}
