/*
 *  Copyright (C) 2002-2021  The DOSBox Team
 *
 *  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 2 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, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <assert.h>
#include <string.h>
#include <stddef.h>

#include "dosbox.h"
#include "logging.h"
#include "callback.h"
#include "regs.h"
#include "mem.h"
#include "inout.h"
#include "int10.h"
#include "render.h"
#include "dos_inc.h"
#include "bitop.h"

#define DOSBOX_INCLUDE
#include "iglib.h"

void dosbox_integration_trigger_write_direct32(const uint32_t reg,const uint32_t val);
bool dosbox_int_push_save_state(void);
bool dosbox_int_pop_save_state(void);

int hack_lfb_yadjust = 0;
int hack_lfb_xadjust = 0;

int vesa_set_display_vsync_wait = -1;
bool vesa_bank_switch_window_range_check = true;
bool vesa_bank_switch_window_mirror = false;
bool vesa_zero_on_get_information = true;

extern unsigned int vbe_window_granularity;
extern unsigned int vbe_window_size;

extern int vesa_mode_width_cap;
extern int vesa_mode_height_cap;
extern bool enable_vga_8bit_dac;
extern bool enable_vbe_pmode_if;
extern bool allow_hd_vesa_modes;
extern bool allow_unusual_vesa_modes;
extern bool allow_explicit_vesa_24bpp;
extern bool allow_vesa_lowres_modes;
extern bool allow_vesa_4bpp_packed;
extern bool vesa12_modes_32bpp;
extern bool allow_vesa_32bpp;
extern bool allow_vesa_24bpp;
extern bool allow_vesa_16bpp;
extern bool allow_vesa_15bpp;
extern bool allow_vesa_8bpp;
extern bool allow_vesa_4bpp;
extern bool allow_vesa_tty;
extern bool vga_8bit_dac;

void dosbox_integration_trigger_write_direct32(const uint32_t reg,const uint32_t val);

#define VESA_SUCCESS          0x00
#define VESA_FAIL             0x01
#define VESA_HW_UNSUPPORTED   0x02
#define VESA_MODE_UNSUPPORTED 0x03
// internal definition to pass to the caller
#define VESA_UNIMPLEMENTED    0xFF

static struct {
	Bitu rmWindow;
	Bitu pmStart;
	Bitu pmWindow;
	Bitu pmPalette;
} callback = {0};

void CALLBACK_DeAllocate(Bitu in);

static char string_oem[]="S3 Incorporated. Trio64";
static char string_vendorname[]="DOSBox Development Team";
static char string_productname[]="DOSBox - The DOS Emulator";
static char string_productrev[]="2";

static char dosboxig_string_oem[]="DOSBox Integrated Video";
static char dosboxig_string_vendorname[]="DOSBox Development Team";
static char dosboxig_string_productname[]="DOSBox - The DOS Emulator";
static char dosboxig_string_productrev[]="1";

#ifdef _MSC_VER
#pragma pack (1)
#endif
struct MODE_INFO{
	uint16_t ModeAttributes;
	uint8_t WinAAttributes;
	uint8_t WinBAttributes;
	uint16_t WinGranularity;
	uint16_t WinSize;
	uint16_t WinASegment;
	uint16_t WinBSegment;
	uint32_t WinFuncPtr;
	uint16_t BytesPerScanLine;
	uint16_t XResolution;
	uint16_t YResolution;
	uint8_t XCharSize;
	uint8_t YCharSize;
	uint8_t NumberOfPlanes;
	uint8_t BitsPerPixel;
	uint8_t NumberOfBanks;
	uint8_t MemoryModel;
	uint8_t BankSize;
	uint8_t NumberOfImagePages;
	uint8_t Reserved_page;
	uint8_t RedMaskSize;
	uint8_t RedMaskPos;
	uint8_t GreenMaskSize;
	uint8_t GreenMaskPos;
	uint8_t BlueMaskSize;
	uint8_t BlueMaskPos;
	uint8_t ReservedMaskSize;
	uint8_t ReservedMaskPos;
	uint8_t DirectColorModeInfo;
	uint32_t PhysBasePtr;
	uint32_t OffScreenMemOffset;
	uint16_t OffScreenMemSize;
	uint8_t Reserved[206];
} GCC_ATTRIBUTE(packed);
#ifdef _MSC_VER
#pragma pack()
#endif

void VESA_OnReset_Clear_Callbacks(void) {
    if (callback.rmWindow != 0) {
        CALLBACK_DeAllocate(callback.rmWindow);
        callback.rmWindow = 0;
    }
    if (callback.pmPalette != 0) {
        CALLBACK_DeAllocate(callback.pmPalette);
        callback.pmPalette = 0;
    }
    if (callback.pmStart != 0) {
        CALLBACK_DeAllocate(callback.pmStart);
        callback.pmStart = 0;
    }
    if (callback.pmWindow != 0) {
        CALLBACK_DeAllocate(callback.pmWindow);
        callback.pmWindow = 0;
    }
}

extern bool vesa_bios_modelist_in_info;

uint8_t VESA_GetSVGAInformation(uint16_t seg,uint16_t off) {
	/* Fill 256 byte buffer with VESA information */
	PhysPt buffer=PhysMake(seg,off);
	Bitu i;
	bool vbe2=false;uint16_t vbe2_pos;
	Bitu id=mem_readd(buffer);
	if (((id==0x56424532)||(id==0x32454256)) && (!int10.vesa_oldvbe)) vbe2=true;

	/* The reason this is an option is that there are some old DOS games that assume the BIOS
	 * fills in only the structure members. These games do not provide enough room for the
	 * full 256-byte block. If we zero the entire block, unrelated data next to the buffer
	 * gets wiped and the game crashes. */
	if (vesa_zero_on_get_information) {
		if (vbe2) {
			for (i=0;i<0x200;i++) mem_writeb((PhysPt)(buffer+i),0);		
		} else {
			for (i=0;i<0x100;i++) mem_writeb((PhysPt)(buffer+i),0);
		}
	}

	/* Fill common data */
	MEM_BlockWrite(buffer,(void *)"VESA",4);					//Identification
	if (int10.vesa_vbe3) mem_writew(buffer+0x04,0x300);				//Vesa version 3.0
	else if (!int10.vesa_oldvbe) mem_writew(buffer+0x04,0x200);			//Vesa version 2.0
	else if (!int10.vesa_oldvbe10) mem_writew(buffer+0x04,0x102);			//Vesa version 1.2
	else mem_writew(buffer+0x04,0x100);						//Vesa version 1.0
	if (vbe2) {
		vbe2_pos=256+off;

		if (svgaCard == SVGA_DOSBoxIG) {
			mem_writed(buffer+0x06,RealMake(seg,vbe2_pos));
			for (i=0;i<sizeof(dosboxig_string_oem);i++) real_writeb(seg,vbe2_pos++,(uint8_t)dosboxig_string_oem[i]);
			mem_writew(buffer+0x14,0x200);				//VBE 2 software revision
			mem_writed(buffer+0x16,RealMake(seg,vbe2_pos));
			for (i=0;i<sizeof(dosboxig_string_vendorname);i++) real_writeb(seg,vbe2_pos++,(uint8_t)dosboxig_string_vendorname[i]);
			mem_writed(buffer+0x1a,RealMake(seg,vbe2_pos));
			for (i=0;i<sizeof(dosboxig_string_productname);i++) real_writeb(seg,vbe2_pos++,(uint8_t)dosboxig_string_productname[i]);
			mem_writed(buffer+0x1e,RealMake(seg,vbe2_pos));
			for (i=0;i<sizeof(dosboxig_string_productrev);i++) real_writeb(seg,vbe2_pos++,(uint8_t)dosboxig_string_productrev[i]);
		}
		else {
			mem_writed(buffer+0x06,RealMake(seg,vbe2_pos));
			for (i=0;i<sizeof(string_oem);i++) real_writeb(seg,vbe2_pos++,(uint8_t)string_oem[i]);
			mem_writew(buffer+0x14,0x200);					//VBE 2 software revision
			mem_writed(buffer+0x16,RealMake(seg,vbe2_pos));
			for (i=0;i<sizeof(string_vendorname);i++) real_writeb(seg,vbe2_pos++,(uint8_t)string_vendorname[i]);
			mem_writed(buffer+0x1a,RealMake(seg,vbe2_pos));
			for (i=0;i<sizeof(string_productname);i++) real_writeb(seg,vbe2_pos++,(uint8_t)string_productname[i]);
			mem_writed(buffer+0x1e,RealMake(seg,vbe2_pos));
			for (i=0;i<sizeof(string_productrev);i++) real_writeb(seg,vbe2_pos++,(uint8_t)string_productrev[i]);
		}
	} else {
		vbe2_pos=0x20+off;

		mem_writed(buffer+0x06,int10.rom.oemstring);	//Oemstring
	}

	if (vesa_bios_modelist_in_info) {
		/* put the modelist into the VBE struct itself, as modern BIOSes like to do.
		 * NOTICE: This limits the modelist to what is able to fit! Extended modes may not fit, which is why the option is OFF by default. */
		uint16_t modesg = int10.rom.vesa_modes >> 16;
		uint16_t modoff = int10.rom.vesa_modes & 0xFFFF;

		mem_writed(buffer+0x0e,RealMake(seg,vbe2_pos));	//VESA Mode list

		do {
			if (vbe2) {
				if (vbe2_pos >= (509+off)) break;
			}
			else {
				if (vbe2_pos >= (253+off)) break;
			}
			uint16_t m = real_readw(modesg,modoff);
			if (m == 0xFFFF) break;
			real_writew(seg,vbe2_pos,m);
			vbe2_pos += 2;
			modoff += 2;
		} while (1);
		real_writew(seg,vbe2_pos,0xFFFF);
	}
	else {
		mem_writed(buffer+0x0e,int10.rom.vesa_modes);	//VESA Mode list
	}

	mem_writed(buffer+0x0a,(enable_vga_8bit_dac ? 1 : 0));		//Capabilities and flags
	mem_writew(buffer+0x12,(uint16_t)(vga.mem.vbe_memsize/(64*1024))); // memory size in 64kb blocks
	return VESA_SUCCESS;
}

uint8_t VESA_GetSVGAModeInformation(uint16_t mode,uint16_t seg,uint16_t off) {
	MODE_INFO minfo;
	memset(&minfo,0,sizeof(minfo));
	PhysPt buf=PhysMake(seg,off);
	Bitu pageSize;
	uint8_t modeAttributes;
	unsigned int adj = 0;
	Bitu i=0;

	mode&=0x3fff;	// vbe2 compatible, ignore lfb and keep screen content bits
	if (mode<0x100) return 0x01;
	while (ModeList_VGA[i].mode!=0xffff) {
		/* Hack for VBE 1.2 modes and 24/32bpp ambiguity */
		if (ModeList_VGA[i].mode >= 0x100 && ModeList_VGA[i].mode <= 0x11F &&
			!(ModeList_VGA[i].special & _USER_MODIFIED) &&
			((ModeList_VGA[i].type == M_LIN32 && !vesa12_modes_32bpp) ||
			 (ModeList_VGA[i].type == M_LIN24 && vesa12_modes_32bpp))) {
			/* ignore */
			i++;
		}
		/* ignore deleted modes */
		else if (ModeList_VGA[i].type == M_ERROR) {
			/* ignore */
			i++;
		}
		else if (mode==ModeList_VGA[i].mode)
			goto foundit;
		else
			i++;
	}
	return VESA_FAIL;
foundit:
	if ((int10.vesa_oldvbe) && (ModeList_VGA[i].mode>=0x120)) return 0x01;
	VideoModeBlock * mblock=&ModeList_VGA[i];

	/* Don't allow querying modes the SVGA card does not accept,
	 * unless the user modified the mode. */
	if (svga.accepts_mode && !(mblock->special & _USER_MODIFIED)) {
		if (!svga.accepts_mode(mode)) return 0x01;
	}

	/* do not return information on deleted modes */
	if (mblock->type == M_ERROR) return 0x01;

	bool allow_res = allow_vesa_lowres_modes ||
		(ModeList_VGA[i].swidth >= 640 && ModeList_VGA[i].sheight >= 400);

	unsigned int cwidth = (mblock->pitch != 0) ? mblock->pitch : mblock->swidth;

	switch (mblock->type) {
		case M_PACKED4:
			if (!allow_vesa_4bpp_packed && !(ModeList_VGA[i].mode >= 0x202 && ModeList_VGA[i].mode <= 0x208)) return VESA_FAIL;//TODO: New option to disable
			pageSize = mblock->sheight * cwidth/2;
			adj = hack_lfb_xadjust / 2;
			var_write(&minfo.BytesPerScanLine,(uint16_t)((((cwidth+15U)/8U)&(~1U))*4)); /* NTS: 4bpp requires even value due to VGA registers, round up */
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x1);
				var_write(&minfo.BitsPerPixel,4);
				var_write(&minfo.MemoryModel,4);	//packed pixel
			}
			modeAttributes = 0x1b;	// Color, graphics
			if (!int10.vesa_nolfb && !int10.vesa_oldvbe) modeAttributes |= 0x80;	// linear framebuffer
			if (mblock->special & _REQUIRE_LFB) modeAttributes |= 0x40; // windowed memory mode NOT available
			break;
		case M_CGA2:
			if (svgaCard != SVGA_DOSBoxIG) return VESA_FAIL;
			if (!allow_vesa_4bpp) return VESA_FAIL;//TODO
			if (mblock->special & _REQUIRE_LFB) return VESA_FAIL; /* um, no */
			pageSize = mblock->sheight * (uint16_t)(((cwidth+15U)/8U)&(~1U));
			adj = hack_lfb_xadjust / 8;
			var_write(&minfo.BytesPerScanLine,(uint16_t)(((cwidth+15U)/8U)&(~1U))); /* NTS: 4bpp requires even value due to VGA registers, round up */
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x1);
				var_write(&minfo.BitsPerPixel,1);   // bits per pixel
				var_write(&minfo.MemoryModel,4);	//packed pixel
			}
			modeAttributes = 0x1b;	// Color, graphics
			if (!int10.vesa_nolfb && !int10.vesa_oldvbe) modeAttributes |= 0x80;	// linear framebuffer
			if (mblock->special & _REQUIRE_LFB) modeAttributes |= 0x40; // windowed memory mode NOT available
			break;
		case M_LIN4:
			if (!allow_vesa_4bpp) return VESA_FAIL;
			if (mblock->special & _REQUIRE_LFB) return VESA_FAIL; /* um, no */
			pageSize = mblock->sheight * (uint16_t)(((cwidth+15U)/8U)&(~1U));
			adj = hack_lfb_xadjust / 8;
			var_write(&minfo.BytesPerScanLine,(uint16_t)(((cwidth+15U)/8U)&(~1U))); /* NTS: 4bpp requires even value due to VGA registers, round up */
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x4);
				var_write(&minfo.BitsPerPixel,4);   // bits per pixel is 4 as specified by VESA BIOS 2.0 specification
				var_write(&minfo.MemoryModel,3);	//ega planar mode
			}
			modeAttributes = 0x1b;	// Color, graphics, no linear buffer
			break;
		case M_LIN8:
			if (!allow_vesa_8bpp || !allow_res) return VESA_FAIL;
			pageSize = mblock->sheight * cwidth;
			adj = hack_lfb_xadjust;
			var_write(&minfo.BytesPerScanLine,(uint16_t)cwidth);
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x1);
				var_write(&minfo.BitsPerPixel,8);
				var_write(&minfo.MemoryModel,4);		//packed pixel
			}
			modeAttributes = 0x1b;	// Color, graphics
			if (!int10.vesa_nolfb && !int10.vesa_oldvbe) modeAttributes |= 0x80;	// linear framebuffer
			if (mblock->special & _REQUIRE_LFB) modeAttributes |= 0x40; // windowed memory mode NOT available
			break;
		case M_LIN15:
			if (!allow_vesa_15bpp || !allow_res) return VESA_FAIL;
			pageSize = mblock->sheight * cwidth*2;
			adj = hack_lfb_xadjust * 2;
			var_write(&minfo.BytesPerScanLine,(uint16_t)(cwidth*2));
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x1);
				var_write(&minfo.BitsPerPixel,15);
				var_write(&minfo.MemoryModel,6);	//HiColour
				var_write(&minfo.RedMaskSize,5);
				var_write(&minfo.RedMaskPos,10);
				var_write(&minfo.GreenMaskSize,5);
				var_write(&minfo.GreenMaskPos,5);
				var_write(&minfo.BlueMaskSize,5);
				var_write(&minfo.BlueMaskPos,0);
				var_write(&minfo.ReservedMaskSize,0x01);
				var_write(&minfo.ReservedMaskPos,0x0f);
			}
			modeAttributes = 0x1b;	// Color, graphics
			if (!int10.vesa_nolfb && !int10.vesa_oldvbe) modeAttributes |= 0x80;	// linear framebuffer
			if (mblock->special & _REQUIRE_LFB) modeAttributes |= 0x40; // windowed memory mode NOT available
			break;
		case M_LIN16:
			if (!allow_vesa_16bpp || !allow_res) return VESA_FAIL;
			pageSize = mblock->sheight * cwidth*2;
			adj = hack_lfb_xadjust * 2;
			var_write(&minfo.BytesPerScanLine,(uint16_t)(cwidth*2));
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x1);
				var_write(&minfo.BitsPerPixel,16);
				var_write(&minfo.MemoryModel,6);	//HiColour
				var_write(&minfo.RedMaskSize,5);
				var_write(&minfo.RedMaskPos,11);
				var_write(&minfo.GreenMaskSize,6);
				var_write(&minfo.GreenMaskPos,5);
				var_write(&minfo.BlueMaskSize,5);
				var_write(&minfo.BlueMaskPos,0);
			}
			modeAttributes = 0x1b;	// Color, graphics
			if (!int10.vesa_nolfb && !int10.vesa_oldvbe) modeAttributes |= 0x80;	// linear framebuffer
			if (mblock->special & _REQUIRE_LFB) modeAttributes |= 0x40; // windowed memory mode NOT available
			break;
		case M_LIN24:
			if (!allow_vesa_24bpp || !allow_res) return VESA_FAIL;
			if (mode >= 0x120 && !allow_explicit_vesa_24bpp) return VESA_FAIL;
			pageSize = mblock->sheight * cwidth*3;
			adj = hack_lfb_xadjust * 3;
			var_write(&minfo.BytesPerScanLine,(uint16_t)(cwidth*3));
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x1);
				var_write(&minfo.BitsPerPixel,24);
				var_write(&minfo.MemoryModel,6);	//HiColour
				var_write(&minfo.RedMaskSize,8);
				var_write(&minfo.RedMaskPos,0x10);
				var_write(&minfo.GreenMaskSize,0x8);
				var_write(&minfo.GreenMaskPos,0x8);
				var_write(&minfo.BlueMaskSize,0x8);
				var_write(&minfo.BlueMaskPos,0x0);
			}
			modeAttributes = 0x1b;	// Color, graphics
			if (!int10.vesa_nolfb && !int10.vesa_oldvbe) modeAttributes |= 0x80;	// linear framebuffer
			if (mblock->special & _REQUIRE_LFB) modeAttributes |= 0x40; // windowed memory mode NOT available
			break;
		case M_LIN32:
			if (!allow_vesa_32bpp || !allow_res) return VESA_FAIL;
			pageSize = mblock->sheight * cwidth*4;
			adj = hack_lfb_xadjust * 4;
			var_write(&minfo.BytesPerScanLine,(uint16_t)(cwidth*4));
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x1);
				var_write(&minfo.BitsPerPixel,32);
				var_write(&minfo.MemoryModel,6);	//HiColour
				var_write(&minfo.RedMaskSize,8);
				var_write(&minfo.RedMaskPos,0x10);
				var_write(&minfo.GreenMaskSize,0x8);
				var_write(&minfo.GreenMaskPos,0x8);
				var_write(&minfo.BlueMaskSize,0x8);
				var_write(&minfo.BlueMaskPos,0x0);
				var_write(&minfo.ReservedMaskSize,0x8);
				var_write(&minfo.ReservedMaskPos,0x18);
			}
			modeAttributes = 0x1b;	// Color, graphics
			if (!int10.vesa_nolfb && !int10.vesa_oldvbe) modeAttributes |= 0x80;	// linear framebuffer
			if (mblock->special & _REQUIRE_LFB) modeAttributes |= 0x40; // windowed memory mode NOT available
			break;
		case M_TEXT:
			if (!allow_vesa_tty) return VESA_FAIL;
			if (mblock->special & _REQUIRE_LFB) return VESA_FAIL; /* um, no */
			adj = hack_lfb_xadjust / 8;
			pageSize = mblock->sheight * cwidth/8;
			var_write(&minfo.BytesPerScanLine, (uint16_t)(mblock->twidth * 2));
			if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
				var_write(&minfo.NumberOfPlanes,0x1);
				var_write(&minfo.BitsPerPixel,4);
				var_write(&minfo.MemoryModel,0);	// text
			}
			modeAttributes = 0x0f;	//Color, text, bios output
			break;
		default:
			return VESA_FAIL;
	}
	if (pageSize & 0xFFFFu) {
		// It is documented that many applications assume 64k-aligned page sizes
		// VBETEST is one of them
		pageSize +=  0xFFFFu;
		pageSize &= ~0xFFFFu;
	}
	Bitu pages = 0;
	Bitu calcmemsize = vga.mem.vbe_memsize;
	if (mblock->type == M_LIN4) calcmemsize /= 4u; /* 4bpp planar = 4 bytes per video memory byte */
	if (pageSize > calcmemsize || (mblock->special & (_USER_DISABLED|_BIOS_DISABLED))) {
		// mode not supported by current hardware configuration
		modeAttributes &= ~0x1;
	} else if (pageSize) {
		pages = (calcmemsize / pageSize) - 1;
		if (pages > 254) pages = 254;
	}

	/* VBE 1.0 allows fields "XResolution" and later to be optional.
	 * Video modes had standard numbers at the time, so you were just expected to
	 * "know" what the mode was when you set it. If you set mode 0x101, you were
	 * expected to know that XResolution was 640 and YResolution was 480, and that
	 * there were 8 bits per pixel. */
	if (int10.vesa_oldvbe10)
		modeAttributes &= ~2; /* clear D1, which indicates whether the optional XResolution, etc. fields are present */

	if (!int10.vesa_oldvbe10)
		var_write(&minfo.NumberOfImagePages, (uint8_t)pages); /* did not exist until VBE 1.1 */

	var_write(&minfo.ModeAttributes, modeAttributes);
	if (mblock->special & _REQUIRE_LFB) {
		var_write(&minfo.WinAAttributes, 0x0);	// Not there
	}
	else {
		var_write(&minfo.WinAAttributes, 0x7);	// Exists/readable/writable
	}

	if (mblock->type==M_TEXT) {
		if (mblock->special & _REQUIRE_LFB) {
			var_write(&minfo.WinGranularity,0);
			var_write(&minfo.WinSize,0);
			var_write(&minfo.WinASegment,(uint16_t)0);
		}
		else {
			var_write(&minfo.WinGranularity,32);
			var_write(&minfo.WinSize,32);
			var_write(&minfo.WinASegment,(uint16_t)0xb800);
		}

		if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
			var_write(&minfo.XResolution,(uint16_t)mblock->twidth);
			var_write(&minfo.YResolution,(uint16_t)mblock->theight);
		}
	} else {
		if (mblock->special & _REQUIRE_LFB) {
			var_write(&minfo.WinGranularity,0);
			var_write(&minfo.WinSize,0);
			var_write(&minfo.WinASegment,(uint16_t)0);
		}
		else {
			var_write(&minfo.WinGranularity,vbe_window_granularity>>10u); /* field is in KB */
			var_write(&minfo.WinSize,vbe_window_size>>10u); /* field is in KB */
			var_write(&minfo.WinASegment,(uint16_t)0xa000);
		}

		if (!int10.vesa_oldvbe10) { /* optional in VBE 1.0 */
			var_write(&minfo.XResolution,(uint16_t)mblock->swidth);
			var_write(&minfo.YResolution,(uint16_t)mblock->sheight);
		}
	}
	var_write(&minfo.WinFuncPtr,int10.rom.set_window);

	if (!int10.vesa_oldvbe10) {
		var_write(&minfo.NumberOfBanks,0x1); /* did not exist until VBE 1.1 */
		var_write(&minfo.Reserved_page,0x1); /* did not exist until VBE 1.1 */
		var_write(&minfo.XCharSize,(uint8_t)mblock->cwidth); /* optional in VBE 1.0 */
		var_write(&minfo.YCharSize,(uint8_t)mblock->cheight); /* optional in VBE 1.0 */
	}

	/* this special hack is for the Windows 3.1 S3 driver in "640x480 (1MB) 16 million colors" mode which does no scan line or display offset calls at all */
	if (mblock->special & _S3_POW2_STRIDE) {
		uint16_t w = host_readw((HostPt)(&minfo.BytesPerScanLine));
		if (!bitop::ispowerof2(w)) {
			w = bitop::bit2mask(bitop::log2(w) + 1u);
			var_write(&minfo.BytesPerScanLine,(uint16_t)w);
		}
	}

	if (!int10.vesa_nolfb && !int10.vesa_oldvbe && (modeAttributes&0x80)/*ModeAttributes indicates an LFB*/)
		var_write(&minfo.PhysBasePtr,S3_LFB_BASE + adj + (hack_lfb_yadjust*(long)host_readw((HostPt)(&minfo.BytesPerScanLine))));

	MEM_BlockWrite(buf,&minfo,sizeof(MODE_INFO));
	return VESA_SUCCESS;
}


uint8_t VESA_SetSVGAMode(uint16_t mode) {
	if (INT10_SetVideoMode(mode)) {
		int10.vesa_setmode=mode&0x7fff;
		return VESA_SUCCESS;
	}

	return VESA_FAIL;
}

uint8_t VESA_GetSVGAMode(uint16_t & mode) {
	if (int10.vesa_setmode!=0xffff) mode=int10.vesa_setmode;
	else mode=CurMode->mode;
	return VESA_SUCCESS;
}

uint8_t VESA_SetCPUWindow(uint8_t window,uint16_t address) {
	if (window && !vesa_bank_switch_window_mirror) return VESA_FAIL;

	/* despite the fact DX in INT 10h AX=4F05h is the window address,
	 * VESA BIOSes probably only look at DL anyway, because cards in the
	 * 1990s didn't have enough memory to necessitate the full 16 bit
	 * value and firmware programmers might take shortcuts anyway either
	 * for performance or ROM space optimization. Perhaps someday if
	 * DOSBox-X emulates a more modern SVGA card it would check the
	 * full DX value. DOSBox SVN and forks achieve equivalent behavior
	 * here by defining this function prototype with an 8-bit "address"
	 * parameter. */
	address &= vga.svga.bank_mask;

	Bitu calcmemsize = vga.mem.vbe_memsize;
	if (CurMode->type == M_LIN4) calcmemsize /= 4u; /* 4bpp planar = 4 bytes per video memory byte */

	if ((!vesa_bank_switch_window_range_check) || (uint32_t)(address)*vga.svga.bank_size<calcmemsize) { /* range check, or silently truncate address depending on dosbox-x.conf setting */
		if (svgaCard == SVGA_DOSBoxIG) {
			dosbox_int_push_save_state();
			dosbox_integration_trigger_write_direct32(DOSBOX_ID_REG_VGAIG_BANKWINDOW,(uint32_t)address * (uint32_t)vga.svga.bank_size);
			dosbox_int_pop_save_state();
			VGA_SetupHandlers();
		}
		else {
			IO_Write(0x3d4,0x6a);
			IO_Write(0x3d5,(uint8_t)address); /* NTS: in vga_s3.cpp this is a 7-bit field, wraparound will occur at address >= 128 but only if emulating a full 64KB bank as normal */
		}
		return VESA_SUCCESS;
	} else return VESA_FAIL;
}

uint8_t VESA_GetCPUWindow(uint8_t window,uint16_t & address) {
	if (window && !vesa_bank_switch_window_mirror) return VESA_FAIL;
	if (svgaCard == SVGA_DOSBoxIG) {
		address=vga.svga.bank_read_full / vga.svga.bank_size;
	}
	else {
		IO_Write(0x3d4,0x6a);
		address=IO_Read(0x3d5);
	}
	return VESA_SUCCESS;
}


uint8_t VESA_SetPalette(PhysPt data,Bitu index,Bitu count,bool wait) {
//Structure is (vesa 3.0 doc): blue,green,red,alignment
	if (index>255) return VESA_FAIL;
	if (index+count>256) return VESA_FAIL;
	
	// Wait for retrace if requested
	if (wait) CALLBACK_RunRealFar(RealSeg(int10.rom.wait_retrace),RealOff(int10.rom.wait_retrace));
	
	IO_Write(0x3c8,(uint8_t)index);
	while (count) {
		uint8_t b = mem_readb(data++);
		uint8_t g = mem_readb(data++);
		uint8_t r = mem_readb(data++);
		data++;
		IO_Write(0x3c9,r);
		IO_Write(0x3c9,g);
		IO_Write(0x3c9,b);
		count--;
	}
	return VESA_SUCCESS;
}


uint8_t VESA_GetPalette(PhysPt data,Bitu index,Bitu count) {
	if (index>255) return VESA_FAIL;
	if (index+count>256) return VESA_FAIL;
	IO_Write(0x3c7,(uint8_t)index);
	while (count) {
		uint8_t r = IO_Read(0x3c9);
		uint8_t g = IO_Read(0x3c9);
		uint8_t b = IO_Read(0x3c9);
		mem_writeb(data++,b);
		mem_writeb(data++,g);
		mem_writeb(data++,r);
		data++;
		count--;
	}
	return VESA_SUCCESS;
}

// maximum offset for the S3 Trio64 is 10 bits
#define S3_MAX_OFFSET 0x3ff

uint8_t VESA_ScanLineLength(uint8_t subcall,uint16_t val, uint16_t & bytes,uint16_t & pixels,uint16_t & lines) {
	// offset register: virtual scanline length
	Bitu pixels_per_offset;
	Bitu bytes_per_offset = 8;
	Bitu vmemsize = vga.mem.vbe_memsize;
	Bitu new_offset = vga.dosboxig.svga ? vga.dosboxig.bytes_per_scanline : vga.config.scan_len;
	Bitu screen_height = CurMode->sheight;
	Bitu max_offset;

	/* this special hack is for the Windows 3.1 S3 driver in "640x480 (1MB) 16 million colors" mode which does no scan line or display offset calls at all */
	if (CurMode->special & _S3_POW2_STRIDE)
		return VESA_MODE_UNSUPPORTED;

	if (vga.dosboxig.svga) {
		max_offset = 8192;
		switch (CurMode->type) {
			case M_LIN4:
			case M_EGA:
				bytes_per_offset = 1;
				pixels_per_offset = 8;
				vmemsize /= 4u; /* because planar VGA */
				max_offset /= 4u;
				break;
			case M_CGA2:
				bytes_per_offset = 1;
				pixels_per_offset = 8;
				break;
			case M_PACKED4:
				bytes_per_offset = 1;
				pixels_per_offset = 2;
				break;
			case M_LIN8:
				bytes_per_offset = 1;
				pixels_per_offset = 1;
				break;
			case M_LIN15:
			case M_LIN16:
				bytes_per_offset = 2;
				pixels_per_offset = 1;
				new_offset /= 2;
				break;
			case M_LIN24:
				bytes_per_offset = 3;
				pixels_per_offset = 1;
				new_offset /= 3;
				break;
			case M_LIN32:
				bytes_per_offset = 4;
				pixels_per_offset = 1;
				new_offset /= 4;
				break;
			default:
				return VESA_MODE_UNSUPPORTED;
		}
	}
	else {
		switch (CurMode->type) {
			case M_TEXT:
				vmemsize = 0x8000;      // we have only the 32kB window here
				screen_height = CurMode->theight;
				pixels_per_offset = 16; // two characters each 8 pixels wide
				bytes_per_offset = 4;   // 2 characters + 2 attributes
				break;
			case M_LIN4:
				bytes_per_offset = 2;
				pixels_per_offset = 16;
				vmemsize /= 4u; /* because planar VGA */
				break;
			case M_PACKED4:
				pixels_per_offset = 16;
				break;
			case M_LIN8:
				pixels_per_offset = 8;
				break;
			case M_LIN15:
			case M_LIN16:
				pixels_per_offset = 4;
				break;
			case M_LIN24:
				pixels_per_offset = 8;
				bytes_per_offset = 24;
				break;
			case M_LIN32:
				pixels_per_offset = 2;
				break;
			default:
				return VESA_MODE_UNSUPPORTED;
		}

		max_offset = S3_MAX_OFFSET;
	}

	if ((max_offset * bytes_per_offset * screen_height) > vmemsize)
		max_offset = vmemsize / (bytes_per_offset * screen_height);

	if (max_offset == 0)
		return VESA_HW_UNSUPPORTED; // scanline too long

	switch (subcall) {
		case 0x00: // set scan length in pixels
			new_offset = val / pixels_per_offset;
			if (val % pixels_per_offset) new_offset++;

			/* why does VBETEST do this? */
			if (new_offset == 0)
				return VESA_HW_UNSUPPORTED; // scanline too long

			// NTS: The VESA BIOS standard says a too-large value should return an error.
			//      VBETEST.EXE behavior seems to depend on this call capping the value and returning success, else it misdraws the screen and might get stuck drawing junk.
			// TODO: Add dosbox-x.conf option to control which behavior is emulated.
			if (new_offset > max_offset) new_offset = max_offset;

			if (vga.dosboxig.svga) {
				dosbox_int_push_save_state();
				dosbox_integration_trigger_write_direct32(DOSBOX_ID_REG_VGAIG_FMT_BYTESPERSCANLINE,(new_offset * bytes_per_offset) | DOSBOX_ID_REG_VGAIG_FMT_MASK);
				dosbox_int_pop_save_state();
			}
			else {
				if (CurMode->type == M_LIN24)
					vga.config.scan_len = new_offset * 3u;
				else
					vga.config.scan_len = new_offset;

				VGA_CheckScanLength();
			}
			break;

		case 0x01: // get current scanline length
			// implemented at the end of this function
			break;

		case 0x02: // set scan length in bytes
			new_offset = val / bytes_per_offset;
			if (val % bytes_per_offset) new_offset++;

			/* why does VBETEST do this? */
			if (new_offset == 0)
				return VESA_HW_UNSUPPORTED; // scanline too long

			// NTS: The VESA BIOS standard says a too-large value should return an error.
			//      VBETEST.EXE behavior seems to depend on this call capping the value and returning success, else it misdraws the screen and might get stuck drawing junk.
			// TODO: Add dosbox-x.conf option to control which behavior is emulated.
			if (new_offset > max_offset) new_offset = max_offset;

			if (vga.dosboxig.svga) {
				dosbox_int_push_save_state();
				dosbox_integration_trigger_write_direct32(DOSBOX_ID_REG_VGAIG_FMT_BYTESPERSCANLINE,(new_offset * bytes_per_offset) | DOSBOX_ID_REG_VGAIG_FMT_MASK);
				dosbox_int_pop_save_state();
			}
			else {
				if (CurMode->type == M_LIN24)
					vga.config.scan_len = new_offset * 3u;
				else
					vga.config.scan_len = new_offset;

				VGA_CheckScanLength();
			}
			break;

		case 0x03: // get maximum scan line length
			// the smaller of either the hardware maximum scanline length or
			// the limit to get full y resolution of this mode
			new_offset = max_offset;
			break;

		default:
			return VESA_UNIMPLEMENTED;
	}

	// set up the return values
	bytes = (uint16_t)(new_offset * bytes_per_offset);
	pixels = (uint16_t)(new_offset * pixels_per_offset);
	if (!bytes)
		// return failure on division by zero
		// some real VESA BIOS implementations may crash here
		return VESA_FAIL;

	{
		unsigned int lines32 = (unsigned int)(vmemsize / bytes);
		if (lines32 > 0xFFFF) lines32 = 0xFFFF;
		lines = (uint16_t)lines32;
	}

	if (CurMode->type==M_TEXT)
		lines *= (uint16_t)(CurMode->cheight);

	return VESA_SUCCESS;
}

uint8_t VESA_SetDisplayStart(uint16_t x,uint16_t y,bool wait) {
	Bitu pixels_per_offset;
	Bitu panning_factor = 1;

	if (!wait) {
		if (vesa_set_display_vsync_wait > 0)
			wait = true;
		else if (vesa_set_display_vsync_wait < 0)
			wait = int10.vesa_oldvbe;
	}

	if (vga.dosboxig.svga) {
		uint32_t offset = vga.dosboxig.bytes_per_scanline * y;
		uint8_t hpel = 0;

		switch (CurMode->type) {
			case M_LIN4:
			case M_EGA:
			case M_CGA2:
				offset += x >> 3u;
				hpel = x & 7u;
				break;
			case M_PACKED4:
				offset += x >> 1u;
				hpel = x & 1u;
				break;
			case M_LIN8:
				offset += x;
				break;
			case M_LIN15:
			case M_LIN16:
				offset += x * 2u;
				break;
			case M_LIN24:
				offset += x * 3u;
				break;
			case M_LIN32:
				offset += x * 4u;
				break;
			default:
				return VESA_MODE_UNSUPPORTED;
		}

		dosbox_int_push_save_state();
		dosbox_integration_trigger_write_direct32(DOSBOX_ID_REG_VGAIG_DISPLAYOFFSET,offset);
		dosbox_integration_trigger_write_direct32(DOSBOX_ID_REG_VGAIG_HVPELSCALE,hpel | 0xFFFFFF00u);
		dosbox_int_pop_save_state();

		// Wait for retrace if requested
		if (wait) CALLBACK_RunRealFar(RealSeg(int10.rom.wait_retrace),RealOff(int10.rom.wait_retrace));

		return VESA_SUCCESS;
	}

	/* this special hack is for the Windows 3.1 S3 driver in "640x480 (1MB) 16 million colors" mode which does no scan line or display offset calls at all */
	if (CurMode->special & _S3_POW2_STRIDE)
		return VESA_MODE_UNSUPPORTED;

	switch (CurMode->type) {
	case M_TEXT:
	case M_LIN4:
	case M_CGA2:
	case M_PACKED4:
		pixels_per_offset = 16;
		break;
	case M_LIN8:
		panning_factor = 2; // the panning register ignores bit0 in this mode
		pixels_per_offset = 8;
		break;
	case M_LIN15:
	case M_LIN16:
		panning_factor = 2; // this may be DOSBox specific
		pixels_per_offset = 4;
		break;
	case M_LIN24:
		pixels_per_offset = 8;
		x *= 3;
		break;
	case M_LIN32:
		pixels_per_offset = 2;
		break;
	default:
		return VESA_MODE_UNSUPPORTED;
	}
	// We would have to divide y by the character height for text modes and
	// write the remainder to the CRTC preset row scan register,
	// but VBE2 BIOSes that actually get that far also don't.
	// Only a VBE3 BIOS got it right.
	Bitu virtual_screen_width = vga.config.scan_len * pixels_per_offset;
	Bitu new_start_pixel = virtual_screen_width * y + x;
	Bitu new_crtc_start = new_start_pixel / (pixels_per_offset/2);
	Bitu new_panning = new_start_pixel % (pixels_per_offset/2);
	new_panning *= panning_factor;

	vga.config.display_start = new_crtc_start;
	
	// Setting the panning register is nice as it allows for super smooth
	// scrolling, but if we hit the retrace pulse there may be flicker as
	// panning and display start are latched at different times. 

	IO_Read(0x3da);              // reset attribute flipflop
	IO_Write(0x3c0,0x13 | 0x20); // panning register, screen on
	IO_Write(0x3c0,(uint8_t)new_panning);

	// Wait for retrace if requested
	if (wait) CALLBACK_RunRealFar(RealSeg(int10.rom.wait_retrace),RealOff(int10.rom.wait_retrace));

	return VESA_SUCCESS;
}

uint8_t VESA_GetDisplayStart(uint16_t & x,uint16_t & y) {
	Bitu pixels_per_offset;
	Bitu panning_factor = 1;

	if (vga.dosboxig.svga) {
		if (vga.dosboxig.bytes_per_scanline) {
			x = vga.dosboxig.display_offset % vga.dosboxig.bytes_per_scanline;
			y = vga.dosboxig.display_offset / vga.dosboxig.bytes_per_scanline;
		}
		else {
			x = y = 0;
		}

		switch (CurMode->type) {
			case M_LIN4:
			case M_CGA2:
			case M_EGA:
				x *= 8u;
				x += vga.dosboxig.hpel & 7u;
				break;
			case M_PACKED4:
				x *= 2u;
				x += vga.dosboxig.hpel & 1u;
				break;
			case M_LIN8:
				break;
			case M_LIN15:
			case M_LIN16:
				x /= 2u;
				break;
			case M_LIN24:
				x /= 3u;
				break;
			case M_LIN32:
				x /= 4u;
				break;
			default:
				return VESA_MODE_UNSUPPORTED;
		}

		return VESA_SUCCESS;
	}

	switch (CurMode->type) {
	case M_TEXT:
		pixels_per_offset = 16;
		break;
	case M_LIN4:
	case M_PACKED4:
		pixels_per_offset = 16;
		break;
	case M_LIN8:
		panning_factor = 2;
		pixels_per_offset = 8;
		break;
	case M_LIN15:
	case M_LIN16:
		panning_factor = 2;
		pixels_per_offset = 4;
		break;
	case M_LIN24: // FIXME
	case M_LIN32:
		pixels_per_offset = 2;
		break;
	default:
		return VESA_MODE_UNSUPPORTED;
	}

	IO_Read(0x3da);              // reset attribute flipflop
	IO_Write(0x3c0,0x13 | 0x20); // panning register, screen on
	uint8_t panning = IO_Read(0x3c1);
	if ((CurMode->type == M_TEXT) && (panning > 7)) panning = 0;

	/* FIXME: Why does this happen with VBETEST.EXE and more than 1MB of RAM? */
	if (vga.config.scan_len == 0) {
		y = x = 0;
		return VESA_SUCCESS;
	}

	Bitu virtual_screen_width = vga.config.scan_len * pixels_per_offset;
	Bitu start_pixel = vga.config.display_start * (pixels_per_offset/2) 
		+ panning / panning_factor;
	
	y = (uint16_t)(start_pixel / virtual_screen_width);
	x = (uint16_t)(start_pixel % virtual_screen_width);
	return VESA_SUCCESS;
}

static Bitu VESA_SetWindow(void) {
	if (reg_bh) reg_ah=VESA_GetCPUWindow(reg_bl,reg_dx);
	else reg_ah=VESA_SetCPUWindow(reg_bl,reg_dx);
	reg_al=0x4f;
	return CBRET_NONE;
}

static Bitu VESA_PMSetWindow(void) {
	if (svgaCard == SVGA_DOSBoxIG) {
		dosbox_int_push_save_state();
		dosbox_integration_trigger_write_direct32(DOSBOX_ID_REG_VGAIG_BANKWINDOW,(uint32_t)reg_dl * (uint32_t)vga.svga.bank_size);
		dosbox_int_pop_save_state();
		VGA_SetupHandlers();
	}
	else {
		IO_Write(0x3d4,0x6a);
		IO_Write(0x3d5,reg_dl);
	}
	return CBRET_NONE;
}
static Bitu VESA_PMSetPalette(void) {
	PhysPt data=SegPhys(es)+reg_edi;
	uint32_t count=reg_cx;
	IO_Write(0x3c8,reg_dl);
	do {
		IO_Write(0x3c9,mem_readb(data+2));
		IO_Write(0x3c9,mem_readb(data+1));
		IO_Write(0x3c9,mem_readb(data));
		data+=4;
	} while (--count);
	return CBRET_NONE;
}
static Bitu VESA_PMSetStart(void) {
	uint32_t start = (uint32_t)(((unsigned int)reg_dx << 16u) | (unsigned int)reg_cx);

	if (vga.dosboxig.svga) {
		/* From the VBE 3.0 standard:
		 *
		 * "For the VBE 2.0 32-bit protected mode version, the value passed in DX:CX is the 32 bit offset in
		 *  display memory, aligned to a plane boundary. For planar modes this means the value is the byte
		 *  offset in memory, but in 8+ bits per pixel modes this is the offset from the start of memory divided
		 *  by 4. Hence the value passed in is identical to the value that would be programmed into the
		 *  standard VGA CRTC start address register."
		 *
		 * "For VBE 3.0 the application program may optionally pass the missing two bits of information in the
		 * top two bits of DX, to allow for pixel perfect horizontal panning"
		 *
		 * Ah, so VBE 2.0 defined "byte offset" in a planar manner, then in VBE 3.0 they realized they fucked up
		 * and were like "Ok, ok, here, you can put the missing 2 LSBs in the upper 2 MSBs of DX". Bleh.
		 *
		 * Anyway, to make this compatible with the DOSBox IG the start address needs to be multiplied by 4 and
		 * then add the upper 2 bits of DX to adjust. This is apparently true of any mode except 16-color planar.
		 *
		 * Duke Nukem 3D source code shows that indeed, if it calls the protected mode display start function,
		 * it passes N >> 2, not N, for this reason. If we want the DOSBox IG to display Duke Nukem 3D properly,
		 * we have to compensate for this or else you're going to be fighting monsters while your screen looks
		 * like a TV set with a rapidly rolling picture.
		 *
		 * 16-color planar modes are already 1:1 byte to display memory and do not need this compensation.
		 */

		if (!(CurMode->type == M_LIN4 || CurMode->type == M_EGA)) {
			if (0/*TODO: If VBE 3.0 emulation*/)
				start = (start << 2ul) + (start >> 30ul);
			else
				start <<= 2ul;
		}

		dosbox_int_push_save_state();
		dosbox_integration_trigger_write_direct32(DOSBOX_ID_REG_VGAIG_DISPLAYOFFSET,start);
		dosbox_int_pop_save_state();
	}
	else {
		vga.config.display_start = start;
	}

	return CBRET_NONE;
}



extern int vesa_modelist_cap;

Bitu INT10_WriteVESAModeList(Bitu max_modes) {
    Bitu mode_wptr = int10.rom.vesa_modes;
	Bitu i=0,modecount=0;

    //TODO Maybe add normal vga modes too, but only seems to complicate things
    while (ModeList_VGA[i].mode!=0xffff) {
        bool canuse_mode=false;

        /* Hack for VBE 1.2 modes and 24/32bpp ambiguity */
        if (ModeList_VGA[i].mode >= 0x100 && ModeList_VGA[i].mode <= 0x11F &&
                ((ModeList_VGA[i].type == M_LIN32 && !vesa12_modes_32bpp) ||
                 (ModeList_VGA[i].type == M_LIN24 && vesa12_modes_32bpp))) {
            /* ignore */
        }
        /* ignore deleted modes */
        else if (ModeList_VGA[i].type == M_ERROR) {
            /* ignore */
        }
        /* do not list modes that are marked as do not list */
        else if (ModeList_VGA[i].special & _DO_NOT_LIST) {
            /* ignore */
        }
        /* only DOSBox IG supports 1bpp modes */
        else if (ModeList_VGA[i].type == M_CGA2 && svgaCard != SVGA_DOSBoxIG) {
            /* ignore */
        }
        else {
            /* If there is no "accepts mode" then accept.
             *
             * If the user modified the mode, then accept.
             * If the mode exceeds video memory, then the mode will be reported as not supported by VESA BIOS functions.
             *
             * If the SVGA card would accept the mode (generally it's a memsize check), then accept. */
            if (!svga.accepts_mode)
                canuse_mode=true;
            else if (ModeList_VGA[i].special & _USER_MODIFIED)
                canuse_mode=true;
            else if (svga.accepts_mode(ModeList_VGA[i].mode))
                canuse_mode=true;

            if (canuse_mode) {
                if (ModeList_VGA[i].mode >= 0x100) {
                    bool allow1 = allow_vesa_lowres_modes ||
                        (ModeList_VGA[i].swidth >= 640 && ModeList_VGA[i].sheight >= 400);
                    bool allow2 =
                        allow_explicit_vesa_24bpp || ModeList_VGA[i].type != M_LIN24 ||
                        (ModeList_VGA[i].type == M_LIN24 && ModeList_VGA[i].mode < 0x120);
                    bool allow3 =
                        allow_hd_vesa_modes ||
                        !(ModeList_VGA[i].special & _HIGH_DEFINITION);
                    bool allow4 =
                        allow_unusual_vesa_modes ||
                        !(ModeList_VGA[i].special & _UNUSUAL_MODE);
                    bool allow5 =
                        ModeList_VGA[i].swidth <= vga.max_svga_width && ModeList_VGA[i].sheight <= vga.max_svga_height &&
                        ModeList_VGA[i].vtotal <= vga.max_svga_height;
                    bool allow_res = allow1 && allow2 && allow3 && allow4 && allow5;
                    bool allow_s3_packed4 = (ModeList_VGA[i].mode >= 0x202 && ModeList_VGA[i].mode <= 0x208 && ModeList_VGA[i].type == M_PACKED4);

                    switch (ModeList_VGA[i].type) {
                        case M_LIN32:	canuse_mode=allow_vesa_32bpp && allow_res; break;
                        case M_LIN24:	canuse_mode=allow_vesa_24bpp && allow_res; break;
                        case M_LIN16:	canuse_mode=allow_vesa_16bpp && allow_res; break;
                        case M_LIN15:	canuse_mode=allow_vesa_15bpp && allow_res; break;
                        case M_LIN8:	canuse_mode=allow_vesa_8bpp && allow_res; break;
                        case M_LIN4:	canuse_mode=allow_vesa_4bpp && allow_res; break;
                        case M_CGA2:	canuse_mode=allow_vesa_4bpp && allow_res; break;//TODO
                        case M_PACKED4:	canuse_mode=(allow_vesa_4bpp_packed || allow_s3_packed4) && allow_res; break;
                        case M_TEXT:	canuse_mode=allow_vesa_tty && allow_res; break;
                        default:	break;
                    }
                }
            }
        }

        if (canuse_mode && vesa_modelist_cap > 0 && (unsigned int)modecount >= (unsigned int)vesa_modelist_cap)
            canuse_mode = false;
        if (canuse_mode && (unsigned int)modecount >= (unsigned int)max_modes)
            canuse_mode = false;

        if (ModeList_VGA[i].type != M_TEXT) {
            if (canuse_mode && vesa_mode_width_cap > 0 && (unsigned int)ModeList_VGA[i].swidth > (unsigned int)vesa_mode_width_cap)
                canuse_mode = false;

            if (canuse_mode && vesa_mode_height_cap > 0 && (unsigned int)ModeList_VGA[i].sheight > (unsigned int)vesa_mode_height_cap)
                canuse_mode = false;
        }
        if (ModeList_VGA[i].mode>=0x100 && canuse_mode) {
            if ((!int10.vesa_oldvbe) || (ModeList_VGA[i].mode<0x120)) {
                phys_writew(PhysMake((uint16_t)0xc000,(uint16_t)mode_wptr),(uint16_t)ModeList_VGA[i].mode);
                mode_wptr+=2;
                modecount++;
            }
        }

        i++;
    }

    assert(modecount <= int10.rom.vesa_alloc_modes); /* do not overrun the buffer */

    /* after the buffer, is 0xFFFF */
    phys_writew(PhysMake((uint16_t)0xc000,(uint16_t)mode_wptr),(uint16_t)0xFFFF);
    mode_wptr+=2;

    return modecount;
}

void INT10_SetupVESA(void) {
	Bitu i,modecount=0;

	/* BUGFIX: Generating VESA BIOS data when machine=ega or machine=vgaonly is dumb.
	 * Stop wasting ROM space! --J.C. */
	if (machine != MCH_VGA) return;
	if (svgaCard == SVGA_None) return;

	/* S3: The hardware register for SVGA bank can only count up to 128.
	 *     For 64KB granularity that allows up to (128 * 64KB) = 8MB.
	 *     Modes too large to fit in that limit need to be marked with _REQUIRE_LFB */
	if (svgaCard == SVGA_S3Trio) {
		for (VideoModeBlock* modelist=ModeList_VGA;modelist->mode != 0xFFFFu;modelist++) {
			if (modelist->mode >= 0x100) {
				Bitu sz = VideoModeMemSize(modelist,modelist->mode);
				if (modelist->type == M_LIN4 || modelist->type == M_EGA) sz /= 4u;

				/* convert size to banks */
				Bitu banks = sz / (Bitu)vbe_window_granularity;

				/* S3 hardware can only count up to 128 banks, therefore if the mode
				 * is large enough, it should be restricted only to DOS programs that
				 * support the linear framebuffer.
				 *
				 * This is not foolproof. DOS programs that intend to page flip with
				 * multiple pages of video memory are going to have problems regardless
				 * if they still use bank switching. */
				if (banks > 128) modelist->special |= _REQUIRE_LFB;
			}
		}
	}

	int10.rom.pmode_interface = 0;
	int10.rom.pmode_interface_window = 0;
	int10.rom.pmode_interface_start = 0;
	int10.rom.pmode_interface_palette = 0;
	int10.rom.pmode_interface_size = 0;

	/* default 8-bit mask, because of legacy code and Demoscene bugs */
	vga.svga.bank_mask = 0xFFu;

	/* if there are enough SVGA banks to need more than 256, then allow the full 16 bits */
	{
		unsigned int banks = vga.mem.vbe_memsize;
		banks /= vbe_window_granularity;
		if (banks > 256u) vga.svga.bank_mask = 0xFFFFu;

		LOG(LOG_MISC,LOG_DEBUG)("VESA total banks=%u bank mask=0x%x",banks,vga.svga.bank_mask);
	}

	/* Put the mode list somewhere in memory */
	int10.rom.vesa_alloc_modes = (uint16_t)(~0u);
	int10.rom.vesa_modes = RealMake(0xc000,int10.rom.used);
	modecount = INT10_WriteVESAModeList(0xFFFF/*max mode count*/);
	int10.rom.vesa_alloc_modes = (uint16_t)modecount;
	int10.rom.used += (uint16_t)(modecount * 2u);
	phys_writew(PhysMake(0xc000,int10.rom.used),0xffff);
	int10.rom.used+=2;
	int10.rom.oemstring=RealMake(0xc000,int10.rom.used);
	if (svgaCard == SVGA_DOSBoxIG) {
		const Bitu len=(Bitu)(strlen(dosboxig_string_oem)+1);
		for (i=0;i<len;i++) phys_writeb(0xc0000u+(int10.rom.used++),(uint8_t)dosboxig_string_oem[i]);
	}
	else {
		const Bitu len=(Bitu)(strlen(string_oem)+1);
		for (i=0;i<len;i++) phys_writeb(0xc0000u+(int10.rom.used++),(uint8_t)string_oem[i]);
	}
	/* Prepare the real mode interface */
	int10.rom.wait_retrace=RealMake(0xc000,int10.rom.used);
	int10.rom.used += (uint16_t)CALLBACK_Setup(0, NULL, CB_VESA_WAIT, PhysMake(0xc000,int10.rom.used), "");
	callback.rmWindow=CALLBACK_Allocate();
	int10.rom.set_window=RealMake(0xc000,int10.rom.used);
	int10.rom.used += (uint16_t)CALLBACK_Setup(callback.rmWindow, VESA_SetWindow, CB_RETF, PhysMake(0xc000,int10.rom.used), "VESA Real Set Window");
	if (enable_vbe_pmode_if) {
		LOG(LOG_MISC,LOG_DEBUG)("VBE pmode interface enabled");
		/* Prepare the pmode interface */
		int10.rom.pmode_interface=RealMake(0xc000,int10.rom.used);
		int10.rom.used += 8;		//Skip the byte later used for offsets
		/* PM Set Window call */
		int10.rom.pmode_interface_window = int10.rom.used - RealOff( int10.rom.pmode_interface );
		phys_writew( Real2Phys(int10.rom.pmode_interface) + 0, int10.rom.pmode_interface_window );
		callback.pmWindow=CALLBACK_Allocate();
		int10.rom.used += (uint16_t)CALLBACK_Setup(callback.pmWindow, VESA_PMSetWindow, CB_RETN, PhysMake(0xc000,int10.rom.used), "VESA PM Set Window");
		/* PM Set start call */
		int10.rom.pmode_interface_start = int10.rom.used - RealOff( int10.rom.pmode_interface );
		phys_writew( Real2Phys(int10.rom.pmode_interface) + 2, int10.rom.pmode_interface_start);
		callback.pmStart=CALLBACK_Allocate();
		int10.rom.used += (uint16_t)CALLBACK_Setup(callback.pmStart, VESA_PMSetStart, CB_VESA_PM, PhysMake(0xc000,int10.rom.used), "VESA PM Set Start");
		/* PM Set Palette call */
		int10.rom.pmode_interface_palette = int10.rom.used - RealOff( int10.rom.pmode_interface );
		phys_writew( Real2Phys(int10.rom.pmode_interface) + 4, int10.rom.pmode_interface_palette);
		callback.pmPalette=CALLBACK_Allocate();
		int10.rom.used += (uint16_t)CALLBACK_Setup(0, NULL, CB_VESA_PM, PhysMake(0xc000,int10.rom.used), "");
		int10.rom.used += (uint16_t)CALLBACK_Setup(callback.pmPalette, VESA_PMSetPalette, CB_RETN, PhysMake(0xc000,int10.rom.used), "VESA PM Set Palette");
		/* Finalize the size and clear the required ports pointer */
		phys_writew( Real2Phys(int10.rom.pmode_interface) + 6, 0);
		int10.rom.pmode_interface_size=int10.rom.used - RealOff( int10.rom.pmode_interface );
	}
}

