DOSBox Glide patch.

From: gulikoza <gulikoza@si-gamer.net>

Add Glide support.

Author: gulikoza (http://vogons.zetafleet.com/viewtopic.php?t=16462)

Changes:
 - configurable splash screen
 - support multiple buffer locks (eg: back + depth) (Extreme Assault)
 - increase available LFB memory
 - fix Tie Break Tennis init
 - fix Unreal GPF

Previous versions:
 - 2012/06/08: Fix grTexClampMode (thanks robertmo!)
 - 2011/08/03: Fix polygon rendering
 - 2010/10/27: Add Pył support, fix grLFBRead/WriteRegion
 - 2009/11/24: Fix some minor issues, add MacOSX support (!)
 - 2009/10/09: Various fixes and code cleanups, fix originlocation + splash screen
 - 2009/08/30: Fix LFB operations when paging is active
 - 2009/06/04: Updated for 0.73, add MSVC6 projects for glide2x win9x build
 - 2008/09/14: Updated for current CVS, allow disabling LFB reads/writes
 - 2008/02/17: Updated for current CVS
 - 2007/11/06: Improve TR shadow support (gidierre)
 - 2007/10/18: Add TombRaider shadow hack (thanks gidierre!)
 - 2007/09/03: Fix a crash when closing dosbox while glide is active
 - 2007/08/28: x86_64 support
 - 2007/08/23: Fix buffer overflow
 - 2007/08/22: Fix compilation problems

TODO:
 - threading (?)
---
 configure.ac             |    2 
 include/glide.h          |  331 ++++++++
 include/glidedef.h       |  179 ++++
 include/mem.h            |    2 
 src/dosbox.cpp           |   12 
 src/gui/sdlmain.cpp      |   13 
 src/hardware/Makefile.am |    2 
 src/hardware/glide.cpp   | 1962 ++++++++++++++++++++++++++++++++++++++++++++++
 src/hardware/memory.cpp  |   21 
 9 files changed, 2520 insertions(+), 4 deletions(-)
 create mode 100644 include/glide.h
 create mode 100644 include/glidedef.h
 create mode 100644 src/hardware/glide.cpp

diff -Naur dosbox.orig/configure.ac dosbox/configure.ac
--- dosbox.orig/configure.ac	2021-02-14 12:26:31.000653746 +0000
+++ dosbox/configure.ac	2021-02-14 12:28:58.493761398 +0000
@@ -616,7 +616,7 @@
 dnl Some target detection and actions for them
 case "$host" in
     *-*-cygwin* | *-*-mingw32*)
-       LIBS="$LIBS -lwinmm"
+       LIBS="$LIBS -lwinmm -lshlwapi"
        AC_DEFINE(C_DIRECTSERIAL, 1, [ Define to 1 if you want serial passthrough support (Win32, Posix and OS/2 only).])
        if test x$have_sdl_net_lib = xyes -a x$have_sdl_net_h = xyes ; then
          LIBS="$LIBS -lws2_32"
diff -Naur dosbox.orig/include/glidedef.h dosbox/include/glidedef.h
--- dosbox.orig/include/glidedef.h	1970-01-01 01:00:00.000000000 +0100
+++ dosbox/include/glidedef.h	2021-02-14 12:28:58.493761398 +0000
@@ -0,0 +1,179 @@
+/*
+ *  Copyright (C) 2002-2007  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GLIDEDEF_H
+#define GLIDEDEF_H
+
+#ifdef DOSBOX_DOSBOX_H
+struct GLIDE_Block
+{
+    bool splash;
+    bool enabled;
+    bool * fullscreen;
+    Bit16u width, height;
+    class GLIDE_PageHandler * lfb_pagehandler;
+    GLIDE_Block():enabled(false),fullscreen(0),width(0),height(0),lfb_pagehandler((GLIDE_PageHandler*)0) { }
+};
+extern GLIDE_Block glide;
+extern void GLIDE_ResetScreen(bool update=false);
+extern void GLIDE_DisableScreen(void);
+#endif
+
+#define GLIDE_LFB		0x60000000
+#define GLIDE_BUFFERS		3			/* Front, Back, AUX */
+#define GLIDE_PAGE_BITS		11			/* =2048 pages per buffer, should be enough for 1600x1200x4 */
+#define GLIDE_PAGES		(GLIDE_BUFFERS*(1<<GLIDE_PAGE_BITS))
+
+#ifdef __3DFX_H__
+/* If you change these defines, don't forget to change the table in glide.h and compile a matching GLIDE2X.OVL */
+
+#define	_grAADrawLine8			0	// void grAADrawLine(GrVertex *va, GrVertex *vb)
+#define	_grAADrawPoint4			1	// void grAADrawPoint(GrVertex *p)
+#define	_grAADrawPolygon12		2	// void grAADrawPolygon(int nVerts, const int ilist[], const GrVertex vlist[])
+#define	_grAADrawPolygonVertexList8	3	// void grAADrawPolygonVertexList(int nVerts, const GrVertex vlist[])
+#define	_grAADrawTriangle24		4	// void grAADrawTriangle(GrVertex *a, GrVertex *b, GrVertex *c, FxBool antialiasAB, FxBool antialiasBC, FxBool antialiasCA)
+#define	_grAlphaBlendFunction16		5	// void grAlphaBlendFunction(GrAlphaBlendFnc_t rgb_sf, GrAlphaBlendFnc_t rgb_df, GrAlphaBlendFnc_t alpha_sf, GrAlphaBlendFnc_t alpha_df)
+#define	_grAlphaCombine20		6	// void grAlphaCombine(GrCombineFunction_t func, GrCombineFactor_t factor, GrCombineLocal_t local, GrCombineOther_t other, FxBool invert)
+#define	_grAlphaControlsITRGBLighting4	7	// void grAlphaControlsITRGBLighting(FxBool enable)
+#define	_grAlphaTestFunction4		8	// void grAlphaTestFunction(GrCmpFnc_t function)
+#define	_grAlphaTestReferenceValue4	9	// void grAlphaTestReferenceValue(GrAlpha_t value)
+#define	_grBufferClear12		10	// void grBufferClear(GrColor_t color, GrAlpha_t alpha, FxU16 depth)
+#define	_grBufferNumPending0		11	// int grBufferNumPending(void)
+#define	_grBufferSwap4			12	// void grBufferSwap(int swap_interval)
+#define	_grCheckForRoom4		13	// void grCheckForRoom(FxI32 n)
+#define	_grChromakeyMode4		14	// void grChromakeyMode(GrChromakeyMode_t mode)
+#define	_grChromakeyValue4		15	// void grChromakeyValue(GrColor_t value)
+#define	_grClipWindow16			16	// void grClipWindow(FxU32 minx, FxU32 miny, FxU32 maxx, FxU32 maxy)
+#define	_grColorCombine20		17	// void grColorCombine(GrCombineFunction_t func, GrCombineFactor_t factor, GrCombineLocal_t local, GrCombineOther_t other, FxBool invert)
+#define	_grColorMask8			18	// void grColorMask(FxBool rgb, FxBool alpha)
+#define	_grConstantColorValue416	19	// void grConstantColorValue4(float a, float r, float g, float b)
+#define	_grConstantColorValue4		20	// void grConstantColorValue(GrColor_t color)
+#define	_grCullMode4			21	// void grCullMode(GrCullMode_t mode)
+#define	_grDepthBiasLevel4		22	// void grDepthBiasLevel(FxI16 level)
+#define	_grDepthBufferFunction4		23	// void grDepthBufferFunction(GrCmpFnc_t func)
+#define	_grDepthBufferMode4		24	// void grDepthBufferMode(GrDepthBufferMode_t mode)
+#define	_grDepthMask4			25	// void grDepthMask(FxBool enable)
+#define	_grDisableAllEffects0		26	// void grDisableAllEffects(void)
+#define	_grDitherMode4			27	// void grDitherMode(GrDitherMode_t mode)
+#define	_grDrawLine8			28	// void grDrawLine(const GrVertex *a, const GrVertex *b)
+#define	_grDrawPlanarPolygon12		29	// void grDrawPlanarPolygon(int nVerts, int ilist[], const GrVertex vlist[])
+#define	_grDrawPlanarPolygonVertexList8 30	// void grDrawPlanarPolygonVertexList(int nVerts, const GrVertex vlist[])
+#define	_grDrawPoint4			31	// void grDrawPoint(const GrVertex *a)
+#define	_grDrawPolygon12		32	// void grDrawPolygon(int nVerts, int ilist[], const GrVertex vlist[])
+#define	_grDrawPolygonVertexList8	33	// void grDrawPolygonVertexList(int nVerts, const GrVertex vlist[])
+#define	_grDrawTriangle12		34	// void grDrawTriangle(const GrVertex *a, const GrVertex *b, const GrVertex *c)
+#define	_grErrorSetCallback4		35	// void grErrorSetCallback(void (*function)(const char *string, FxBool fatal))
+#define	_grFogColorValue4		36	// void grFogColorValue(GrColor_t value)
+#define	_grFogMode4			37	// void grFogMode(GrFogMode_t mode)
+#define	_grFogTable4			38	// void grFogTable(const GrFog_t table[GR_FOG_TABLE_SIZE])
+#define	_grGammaCorrectionValue4	39	// void grGammaCorrectionValue(float value)
+#define	_grGlideGetState4		40	// void grGlideGetState(GrState *state)
+#define	_grGlideGetVersion4		41	// void grGlideGetVersion(char version[80])
+#define	_grGlideInit0			42	// void grGlideInit(void)
+#define	_grGlideSetState4		43	// void grGlideSetState(const GrState *state)
+#define	_grGlideShamelessPlug4		44	// void grGlideShamelessPlug(const FxBool on)
+#define	_grGlideShutdown0		45	// void grGlideShutdown(void)
+#define	_grHints8			46	// void grHints(GrHint_t type, FxU32 hintMask)
+#define	_grLfbConstantAlpha4		47	// void grLfbConstantAlpha(GrAlpha_t alpha)
+#define	_grLfbConstantDepth4		48	// void grLfbConstantDepth(FxU16 depth)
+#define	_grLfbLock24			49	// FxBool grLfbLock(GrLock_t type, GrBuffer_t buffer, GrLfbWriteMode_t writeMode, GrOriginLocation_t origin, FxBool pixelPipeline, GrLfbInfo_t *info)
+#define	_grLfbReadRegion28		50	// FxBool grLfbReadRegion(GrBuffer_t src_buffer, FxU32 src_x, FxU32 src_y, FxU32 src_width, FxU32 src_height, FxU32 dst_stride, void *dst_data)
+#define	_grLfbUnlock8			51	// FxBool grLfbUnlock(GrLock_t type, GrBuffer_t buffer)
+#define	_grLfbWriteColorFormat4		52	// void grLfbWriteColorFormat(GrColorFormat_t colorFormat)
+#define	_grLfbWriteColorSwizzle8	53	// void grLfbWriteColorSwizzle(FxBool swizzleBytes, FxBool swapWords)
+#define	_grLfbWriteRegion32		54	// FxBool grLfbWriteRegion(GrBuffer_t dst_buffer, FxU32 dst_x, FxU32 dst_y, GrLfbSrcFmt_t src_format, FxU32 src_width, FxU32 src_height, FxU32 src_stride, void *src_data)
+#define	_grRenderBuffer4		55	// void grRenderBuffer(GrBuffer_t buffer)
+#define	_grResetTriStats0		56	// void grResetTriStats()
+#define	_grSplash20			57	// void grSplash(float x, float y, float width, float height, FxU32 frame)
+#define	_grSstConfigPipeline12		58	//
+#define	_grSstControl4			59	// FxBool grSstControl(FxU32 code)
+#define	_grSstIdle0			60	// void grSstIdle(void)
+#define	_grSstIsBusy0			61	// FxBool grSstIsBusy(void)
+#define	_grSstOrigin4			62	// void grSstOrigin(GrOriginLocation_t origin)
+#define	_grSstPerfStats4		63	// void grSstPerfStats(GrSstPerfStats_t *pStats)
+#define	_grSstQueryBoards4		64	// FxBool grSstQueryBoards(GrHwConfiguration *hwConfig)
+#define	_grSstQueryHardware4		65	// FxBool grSstQueryHardware(GrHwConfiguration *hwConfig)
+#define	_grSstResetPerfStats0		66	// void grSstResetPerfStats(void)
+#define	_grSstScreenHeight0		67	// FxU32 grSstScreenHeight(void)
+#define	_grSstScreenWidth0		68	// FxU32 grSstScreenWidth(void)
+#define	_grSstSelect4			69	// void grSstSelect(int which_sst)
+#define	_grSstStatus0			70	// FxU32 grSstStatus(void)
+#define	_grSstVRetraceOn0		71	// FxBool grSstVRetraceOn(void)
+#define	_grSstVidMode8			72	//
+#define	_grSstVideoLine0		73	// FxU32 grSstVideoLine(void)
+#define	_grSstWinClose0			74	// void grSstWinClose(void)
+#define	_grSstWinOpen28			75	// FxBool grSstWinOpen(FxU32 hwnd, GrScreenResolution_t res, GrScreenRefresh_t ref, GrColorFormat_t cformat, GrOriginLocation_t org_loc, int num_buffers, int num_aux_buffers)
+#define	_grTexCalcMemRequired16		76	// FxU32 grTexCalcMemRequired(GrLOD_t smallLod, GrLOD_t largeLod, GrAspectRatio_t aspect, GrTextureFormat_t format)
+#define	_grTexClampMode12		77	// void grTexClampMode(GrChipID_t tmu, GrTextureClampMode_t sClampMode, GrTextureClampMode_t tClampMode)
+#define	_grTexCombine28			78	// void grTexCombine(GrChipID_t tmu, GrCombineFunction_t rgb_function, GrCombineFactor_t rgb_factor, GrCombineFunction_t alpha_function, GrCombineFactor_t alpha_factor, FxBool rgb_invert, FxBool alpha_invert)
+#define	_grTexCombineFunction8		79	// void grTexCombineFunction(GrChipID_t tmu, GrTextureCombineFnc_t fnc)
+#define	_grTexDetailControl16		80	// void grTexDetailControl(GrChipID_t tmu, int lodBias, FxU8 detailScale, float detailMax)
+#define	_grTexDownloadMipMap16		81	// void grTexDownloadMipMap(GrChipID_t tmu, FxU32 startAddress, FxU32 evenOdd, GrTexInfo *info)
+#define	_grTexDownloadMipMapLevel32	82	// void grTexDownloadMipMapLevel(GrChipID_t tmu, FxU32 startAddress, GrLOD_t thisLod, GrLOD_t largeLod, GrAspectRatio_t aspectRatio, GrTextureFormat_t format, FxU32 evenOdd, void *data)
+#define	_grTexDownloadMipMapLevelPartial40 83	// void grTexDownloadMipMapLevelPartial(GrChipID_t tmu, FxU32 startAddress, GrLOD_t thisLod, GrLOD_t largeLod, GrAspectRatio_t aspectRatio, GrTextureFormat_t format, FxU32 evenOdd, void *data, int start, int end)
+#define	_grTexDownloadTable12		84	// void grTexDownloadTable(GrChipID_t tmu, GrTexTable_t type, void *data)
+#define	_grTexDownloadTablePartial20	85	// void grTexDownloadTablePartial(GrChipID_t tmu, GrTexTable_t type, void *data, int start, int end)
+#define	_grTexFilterMode12		86	// void grTexFilterMode(GrChipID_t tmu, GrTextureFilterMode_t minFilterMode, GrTextureFilterMode_t magFilterMode)
+#define	_grTexLodBiasValue8		87	// void grTexLodBiasValue(GrChipID_t tmu, float bias)
+#define	_grTexMaxAddress4		88	// FxU32 grTexMaxAddress(GrChipID_t tmu)
+#define	_grTexMinAddress4		89	// FxU32 grTexMinAddress(GrChipID_t tmu)
+#define	_grTexMipMapMode12		90	// void grTexMipMapMode(GrChipID_t tmu, GrMipMapMode_t mode, FxBool lodBlend)
+#define	_grTexMultibase8		91	// void grTexMultibase(GrChipID_t tmu, FxBool enable)
+#define	_grTexMultibaseAddress20	92	// void grTexMultibaseAddress(GrChipID_t tmu, GrTexBaseRange_t range, FxU32 startAddress, FxU32 evenOdd, GrTexInfo *info)
+#define	_grTexNCCTable8			93	// void grTexNCCTable(GrChipID_t tmu, GrNCCTable_t table)
+#define	_grTexSource16			94	// void grTexSource(GrChipID_t tmu, FxU32 startAddress, FxU32 evenOdd, GrTexInfo *info)
+#define	_grTexTextureMemRequired8	95	// FxU32 grTexTextureMemRequired(FxU32 evenOdd, GrTexInfo *info)
+#define	_grTriStats8			96	// void grTriStats(FxU32 *trisProcessed, FxU32 *trisDrawn)
+#define	_gu3dfGetInfo8			97	// FxBool gu3dfGetInfo(const char *filename, Gu3dfInfo *info)
+#define	_gu3dfLoad8			98	// FxBool gu3dfLoad(const char *filename, Gu3dfInfo *info)
+#define	_guAADrawTriangleWithClip12	99	// void guAADrawTriangleWithClip(const GrVertex *va, const GrVertex *vb, const GrVertex *vc)
+#define	_guAlphaSource4			100	// void guAlphaSource(GrAlphaSource_t mode)
+#define	_guColorCombineFunction4	101	// void guColorCombineFunction(GrColorCombineFnc_t func)
+#define	_guDrawPolygonVertexListWithClip8 102	// void guDrawPolygonVertexListWithClip(int nverts, const GrVertex vlist[])
+#define	_guDrawTriangleWithClip12	103	// void guDrawTriangleWithClip(const GrVertex *va, const GrVertex *vb, const GrVertex *vc)
+#define	_guEncodeRLE1616		104	//
+#define	_guEndianSwapBytes4		105	//
+#define	_guEndianSwapWords4		106	//
+#define	_guFogGenerateExp28		107	// void guFogGenerateExp2(GrFog_t fogTable[GR_FOG_TABLE_SIZE], float density)
+#define	_guFogGenerateExp8		108	// void guFogGenerateExp(GrFog_t fogTable[GR_FOG_TABLE_SIZE], float density)
+#define	_guFogGenerateLinear12		109	// void guFogGenerateLinear(GrFog_t fogTable[GR_FOG_TABLE_SIZE], float nearW, float farW)
+#define	_guFogTableIndexToW4		110	// float guFogTableIndexToW(int i)
+#define	_guMPDrawTriangle12		111	//
+#define	_guMPInit0			112	//
+#define	_guMPTexCombineFunction4	113	//
+#define	_guMPTexSource8			114	//
+#define	_guMovieSetName4		115	//
+#define	_guMovieStart0			116	//
+#define	_guMovieStop0			117	//
+#define	_guTexAllocateMemory60		118	// GrMipMapId_t guTexAllocateMemory(GrChipID_t tmu, FxU8 evenOddMask, int width, int height, GrTextureFormat_t format, GrMipMapMode_t mmMode, GrLOD_t smallLod, GrLOD_t largeLod, GrAspectRatio_t aspectRatio, GrTextureClampMode_t sClampMode, GrTextureClampMode_t tClampMode, GrTextureFilterMode_t minFilterMode, GrTextureFilterMode_t magFilterMode, float lodBias, FxBool lodBlend)
+#define	_guTexChangeAttributes48	119	// FxBool guTexChangeAttributes(GrMipMapID_t mmid, int width, int height, GrTextureFormat_t format, GrMipMapMode_t mmMode, GrLOD_t smallLod, GrLOD_t largeLod, GrAspectRatio_t aspectRatio, GrTextureClampMode_t sClampMode, GrTextureClampMode_t tClampMode, GrTextureFilterMode_t minFilterMode, GrTextureFilterMode_t magFilterMode)
+#define	_guTexCombineFunction8		120	// void guTexCombineFunction(GrChipID_t tmu, GrTextureCombineFnc_t func)
+#define	_guTexCreateColorMipMap0	121	//
+#define	_guTexDownloadMipMap12		122	// void guTexDownloadMipMap(GrMipMapId_t mmid, const void *src, const GuNccTable *nccTable)
+#define	_guTexDownloadMipMapLevel12	123	// void guTexDownloadMipMapLevel(GrMipMapId_t mmid, GrLOD_t lod, const void **src)
+#define	_guTexGetCurrentMipMap4		124	// GrMipMapId_t guTexGetCurrentMipMap (GrChipID_t tmu)
+#define	_guTexGetMipMapInfo4		125	// GrMipMapInfo *guTexGetMipMapInfo(GrMipMapId_t mmid)
+#define	_guTexMemQueryAvail4		126	// FxU32 guTexMemQueryAvail(GrChipID_t tmu)
+#define	_guTexMemReset0			127	// void guTexMemReset(void)
+#define	_guTexSource4			128	// void guTexSource(GrMipMapId_t mmid)
+#define	_ConvertAndDownloadRle64	129	// void ConvertAndDownloadRle(GrChipID_t tmu, FxU32 startAddress, GrLOD_t thisLod, GrLOD_t largeLod, GrAspectRatio_t aspectRatio, GrTextureFormat_t format, FxU32 evenOdd, FxU8 *bm_data, long  bm_h, FxU32 u0, FxU32 v0, FxU32 width, FxU32 height, FxU32 dest_width, FxU32 dest_height, FxU16 *tlut)
+#define GLIDE_MAX			129
+
+#endif // __3DFX_H__
+
+#endif // GLIDEDEF_H
diff -Naur dosbox.orig/include/glide.h dosbox/include/glide.h
--- dosbox.orig/include/glide.h	1970-01-01 01:00:00.000000000 +0100
+++ dosbox/include/glide.h	2021-02-14 12:28:58.493761398 +0000
@@ -0,0 +1,331 @@
+/*
+ *  Copyright (C) 2002-2007  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DOSBOX_GLIDE_H
+#define DOSBOX_GLIDE_H
+
+#define __3DFX_H__
+
+/*
+** basic data types
+*/
+typedef Bit8u   FxU8;
+typedef Bit8s   FxI8;
+typedef Bit16u  FxU16;
+typedef Bit16s  FxI16;
+typedef Bit32u  FxU32;
+typedef Bit32s  FxI32;
+typedef Bit32s  FxBool;
+typedef float   FxFloat;
+typedef double  FxDouble;
+
+/*
+** color types
+*/
+typedef Bit32u                       FxColor_t;
+typedef struct { float r, g, b, a; } FxColor4;
+
+/*
+** fundamental types
+*/
+#define FXTRUE    1
+#define FXFALSE   0
+
+/*
+** helper macros
+*/
+#define FXUNUSED( a ) ((void)(a))
+#define FXBIT( i )    ( 1L << (i) )
+
+#define FX_ENTRY
+#define FX_GLIDE_NO_FUNC_PROTO
+
+#if defined (WIN32)
+#define FX_CALL __stdcall
+#else
+#define FX_CALL
+#endif
+
+#include <sdk2_glide.h>
+#include "glidedef.h"
+
+// Careful with structures containing pointers
+//
+// GrTexInfo; GrLfbInfo_t; Gu3dfInfo; GrMipMapInfo;
+//
+
+// Some glide structs might have different size in guest 32-bit DOS (pointers)
+typedef struct {
+    Bit32s		smallLod;
+    Bit32s		largeLod;
+    Bit32s		aspectRatio;
+    Bit32s		format;
+    PhysPt		data;
+} DBGrTexInfo;
+
+typedef struct {
+    Bit32s		size;
+    PhysPt		lfbPtr;
+    Bit32u		strideInBytes;
+    Bit32s		writeMode;
+    Bit32s		origin;
+} DBGrLfbInfo_t;
+
+typedef struct {
+    Gu3dfHeader 	header;
+    GuTexTable		table;
+    PhysPt		data;
+    Bit32u		mem_required;
+} DBGu3dfInfo;
+
+typedef struct {
+    const char * name;
+    const Bit8u parms;
+} GLIDE_TABLE;
+
+typedef void (FX_CALL *pfunc0)		(void);
+typedef void (FX_CALL *pfunc1i)		(FxU32);
+typedef void (FX_CALL *pfunc1p)		(void*);
+typedef void (FX_CALL *pfunc1f)		(float);
+typedef void (FX_CALL *pfunc2i)		(FxU32, FxU32);
+typedef void (FX_CALL *pfunc1i1p)	(FxU32, void*);
+typedef void (FX_CALL *pfunc2p)		(void*, void*);
+typedef void (FX_CALL *pfunc1i1f)	(FxU32, float);
+typedef void (FX_CALL *pfunc1p1f)	(void*, float);
+typedef void (FX_CALL *pfunc3i)		(FxU32, FxU32, FxU32);
+typedef void (FX_CALL *pfunc2i1p)	(FxU32, FxU32, void*);
+typedef void (FX_CALL *pfunc1i2p)	(FxU32, void*, void*);
+typedef void (FX_CALL *pfunc3p)		(void*, void*, void*);
+typedef void (FX_CALL *pfunc1p2f)	(void*, float, float);
+typedef void (FX_CALL *pfunc4i)		(FxU32, FxU32, FxU32, FxU32);
+typedef void (FX_CALL *pfunc3i1p)	(FxU32, FxU32, FxU32, void*);
+typedef void (FX_CALL *pfunc3i1f)	(FxU32, FxU32, FxU32, float);
+typedef void (FX_CALL *pfunc4f)		(float, float, float, float);
+typedef void (FX_CALL *pfunc5i)		(FxU32, FxU32, FxU32, FxU32, FxU32);
+typedef void (FX_CALL *pfunc2i1p2i)	(FxU32, FxU32, void*, FxU32, FxU32);
+typedef void (FX_CALL *pfunc4f1i)	(float, float, float, float, FxU32);
+typedef void (FX_CALL *pfunc3p3i)	(void*, void*, void*, FxU32, FxU32, FxU32);
+typedef void (FX_CALL *pfunc7i)		(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32);
+typedef void (FX_CALL *pfunc7i1p)	(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, void*);
+typedef void (FX_CALL *pfunc7i1p2i)	(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, void*,
+					    FxU32, FxU32);
+typedef void (FX_CALL *pfunc7i1p7i1p)	(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, void*,
+					    FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, void*);
+
+typedef FxU32 (FX_CALL *prfunc0)		(void);
+typedef FxU32 (FX_CALL *prfunc1i)	(FxU32);
+typedef FxU32 (FX_CALL *prfunc1p)	(void*);
+typedef FxU32 (FX_CALL *prfunc2i)	(FxU32, FxU32);
+typedef FxU32 (FX_CALL *prfunc1i1p)	(FxU32, void*);
+typedef FxU32 (FX_CALL *prfunc2p)	(void*, void*);
+typedef FxU32 (FX_CALL *prfunc4i)	(FxU32, FxU32, FxU32, FxU32);
+typedef FxU32 (FX_CALL *prfunc5i1p)	(FxU32, FxU32, FxU32, FxU32, FxU32, void*);
+typedef FxU32 (FX_CALL *prfunc7i)	(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32);
+typedef FxU32 (FX_CALL *prfunc1p6i)	(void*, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32);
+typedef FxU32 (FX_CALL *prfunc6i1p)	(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, void*);
+typedef FxU32 (FX_CALL *prfunc7i1p)	(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, void*);
+typedef FxU32 (FX_CALL *prfunc12i)	(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32,
+					    FxU32, FxU32, FxU32, FxU32, FxU32);
+typedef FxU32 (FX_CALL *prfunc13i1f1i)	(FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32, FxU32,
+					    FxU32, FxU32, FxU32, FxU32, FxU32, float, FxU32);
+
+typedef void* (FX_CALL *prptfunc1i)	(FxU32);
+typedef float (FX_CALL *pffunc1i)	(FxU32);
+
+typedef union {
+    pfunc0	grFunction0;
+    pfunc1i	grFunction1i;
+    pfunc1p	grFunction1p;
+    pfunc1f	grFunction1f;
+    pfunc2i	grFunction2i;
+    pfunc1i1p	grFunction1i1p;
+    pfunc2p	grFunction2p;
+    pfunc1i1f	grFunction1i1f;
+    pfunc1p1f	grFunction1p1f;
+    pfunc3i	grFunction3i;
+    pfunc2i1p	grFunction2i1p;
+    pfunc1i2p	grFunction1i2p;
+    pfunc3p	grFunction3p;
+    pfunc1p2f	grFunction1p2f;
+    pfunc4i	grFunction4i;
+    pfunc3i1p	grFunction3i1p;
+    pfunc3i1f	grFunction3i1f;
+    pfunc4f	grFunction4f;
+    pfunc5i	grFunction5i;
+    pfunc2i1p2i	grFunction2i1p2i;
+    pfunc4f1i	grFunction4f1i;
+    pfunc3p3i	grFunction3p3i;
+    pfunc7i	grFunction7i;
+    pfunc7i1p	grFunction7i1p;
+    pfunc7i1p2i	grFunction7i1p2i;
+    pfunc7i1p7i1p grFunction7i1p7i1p;
+
+    prfunc0	grRFunction0;
+    prfunc1i	grRFunction1i;
+    prfunc1p	grRFunction1p;
+    prfunc2i	grRFunction2i;
+    prfunc1i1p	grRFunction1i1p;
+    prfunc2p	grRFunction2p;
+    prfunc4i	grRFunction4i;
+    prfunc5i1p	grRFunction5i1p;
+    prfunc7i	grRFunction7i;
+    prfunc1p6i	grRFunction1p6i;
+    prfunc6i1p	grRFunction6i1p;
+    prfunc7i1p	grRFunction7i1p;
+    prfunc12i	grRFunction12i;
+    prfunc13i1f1i grRFunction13i1f1i;
+
+    prptfunc1i	grRPTFunction1i;
+
+    pffunc1i	grFFunction1i;
+} FncPointers;
+
+static const GLIDE_TABLE grTable[] = {
+    { "grAADrawLine", 8 },
+    { "grAADrawPoint", 4 },
+    { "grAADrawPolygon", 12 },
+    { "grAADrawPolygonVertexList", 8 },
+    { "grAADrawTriangle", 24 },
+    { "grAlphaBlendFunction", 16 },
+    { "grAlphaCombine", 20 },
+    { "grAlphaControlsITRGBLighting", 4 },
+    { "grAlphaTestFunction", 4 },
+    { "grAlphaTestReferenceValue", 4 },
+    { "grBufferClear", 12 },
+    { "grBufferNumPending", 0 },
+    { "grBufferSwap", 4 },
+    { "grCheckForRoom", 4 },
+    { "grChromakeyMode", 4 },
+    { "grChromakeyValue", 4 },
+    { "grClipWindow", 16 },
+    { "grColorCombine", 20 },
+    { "grColorMask", 8 },
+    { "grConstantColorValue4", 16 },
+    { "grConstantColorValue", 4 },
+    { "grCullMode", 4 },
+    { "grDepthBiasLevel", 4 },
+    { "grDepthBufferFunction", 4 },
+    { "grDepthBufferMode", 4 },
+    { "grDepthMask", 4 },
+    { "grDisableAllEffects", 0 },
+    { "grDitherMode", 4 },
+    { "grDrawLine", 8 },
+    { "grDrawPlanarPolygon", 12 },
+    { "grDrawPlanarPolygonVertexList", 8 },
+    { "grDrawPoint", 4 },
+    { "grDrawPolygon", 12 },
+    { "grDrawPolygonVertexList", 8 },
+    { "grDrawTriangle", 12 },
+    { "grErrorSetCallback", 4 },
+    { "grFogColorValue", 4 },
+    { "grFogMode", 4 },
+    { "grFogTable", 4 },
+    { "grGammaCorrectionValue", 4 },
+    { "grGlideGetState", 4 },
+    { "grGlideGetVersion", 4 },
+    { "grGlideInit", 0 },
+    { "grGlideSetState", 4 },
+    { "grGlideShamelessPlug", 4 },
+    { "grGlideShutdown", 0 },
+    { "grHints", 8 },
+    { "grLfbConstantAlpha", 4 },
+    { "grLfbConstantDepth", 4 },
+    { "grLfbLock", 24 },
+    { "grLfbReadRegion", 28 },
+    { "grLfbUnlock", 8 },
+    { "grLfbWriteColorFormat", 4 },
+    { "grLfbWriteColorSwizzle", 8 },
+    { "grLfbWriteRegion", 32 },
+    { "grRenderBuffer", 4 },
+    { "grResetTriStats", 0 },
+    { "grSplash", 20 },
+    { "grSstConfigPipeline", 12 },
+    { "grSstControl", 4 },
+    { "grSstIdle", 0 },
+    { "grSstIsBusy", 0 },
+    { "grSstOrigin", 4 },
+    { "grSstPerfStats", 4 },
+    { "grSstQueryBoards", 4 },
+    { "grSstQueryHardware", 4 },
+    { "grSstResetPerfStats", 0 },
+    { "grSstScreenHeight", 0 },
+    { "grSstScreenWidth", 0 },
+    { "grSstSelect", 4 },
+    { "grSstStatus", 0 },
+    { "grSstVRetraceOn", 0 },
+    { "grSstVidMode", 8 },
+    { "grSstVideoLine", 0 },
+    { "grSstWinClose", 0 },
+    { "grSstWinOpen", 28 },
+    { "grTexCalcMemRequired", 16 },
+    { "grTexClampMode", 12 },
+    { "grTexCombine", 28 },
+    { "grTexCombineFunction", 8 },
+    { "grTexDetailControl", 16 },
+    { "grTexDownloadMipMap", 16 },
+    { "grTexDownloadMipMapLevel", 32 },
+    { "grTexDownloadMipMapLevelPartial", 40 },
+    { "grTexDownloadTable", 12 },
+    { "grTexDownloadTablePartial", 20 },
+    { "grTexFilterMode", 12 },
+    { "grTexLodBiasValue", 8 },
+    { "grTexMaxAddress", 4 },
+    { "grTexMinAddress", 4 },
+    { "grTexMipMapMode", 12 },
+    { "grTexMultibase", 8 },
+    { "grTexMultibaseAddress", 20 },
+    { "grTexNCCTable", 8 },
+    { "grTexSource", 16 },
+    { "grTexTextureMemRequired", 8 },
+    { "grTriStats", 8 },
+    { "gu3dfGetInfo", 8 },
+    { "gu3dfLoad", 8 },
+    { "guAADrawTriangleWithClip", 12 },
+    { "guAlphaSource", 4 },
+    { "guColorCombineFunction", 4 },
+    { "guDrawPolygonVertexListWithClip", 8 },
+    { "guDrawTriangleWithClip", 12 },
+    { "guEncodeRLE16", 16 },
+    { "guEndianSwapBytes", 4 },
+    { "guEndianSwapWords", 4 },
+    { "guFogGenerateExp2", 8 },
+    { "guFogGenerateExp", 8 },
+    { "guFogGenerateLinear", 12 },
+    { "guFogTableIndexToW", 4 },
+    { "guMPDrawTriangle", 12 },
+    { "guMPInit", 0 },
+    { "guMPTexCombineFunction", 4 },
+    { "guMPTexSource", 8 },
+    { "guMovieSetName", 4 },
+    { "guMovieStart", 0 },
+    { "guMovieStop", 0 },
+    { "guTexAllocateMemory", 60 },
+    { "guTexChangeAttributes", 48 },
+    { "guTexCombineFunction", 8 },
+    { "guTexCreateColorMipMap", 0 },
+    { "guTexDownloadMipMap", 12 },
+    { "guTexDownloadMipMapLevel", 12 },
+    { "guTexGetCurrentMipMap", 4 },
+    { "guTexGetMipMapInfo", 4 },
+    { "guTexMemQueryAvail", 4 },
+    { "guTexMemReset", 0 },
+    { "guTexSource", 4 },
+    { "ConvertAndDownloadRle", 64 }
+};
+
+#endif // DOSBOX_GLIDE_H
diff -Naur dosbox.orig/include/mem.h dosbox/include/mem.h
--- dosbox.orig/include/mem.h	2021-02-14 12:26:31.001653774 +0000
+++ dosbox/include/mem.h	2021-02-14 12:28:58.493761398 +0000
@@ -173,6 +173,8 @@
 
 void MEM_BlockWrite(PhysPt pt,void const * const data,Bitu size);
 void MEM_BlockRead(PhysPt pt,void * data,Bitu size);
+void MEM_BlockWrite32(PhysPt pt,void * data,Bitu size);
+void MEM_BlockRead32(PhysPt pt,void * data,Bitu size);
 void MEM_BlockCopy(PhysPt dest,PhysPt src,Bitu size);
 void MEM_StrCopy(PhysPt pt,char * data,Bitu size);
 
diff -Naur dosbox.orig/src/dosbox.cpp dosbox/src/dosbox.cpp
--- dosbox.orig/src/dosbox.cpp	2021-02-14 12:26:31.003653829 +0000
+++ dosbox/src/dosbox.cpp	2021-02-14 12:28:58.494761426 +0000
@@ -81,6 +81,7 @@
 
 void KEYBOARD_Init(Section*);	//TODO This should setup INT 16 too but ok ;)
 void JOYSTICK_Init(Section*);
+void GLIDE_Init(Section*);
 void MOUSE_Init(Section*);
 void SBLASTER_Init(Section*);
 void GUS_Init(Section*);
@@ -749,6 +750,17 @@
 	Pmulti_remain->Set_help("see serial1");
 
 
+	secprop=control->AddSection_prop("glide",&GLIDE_Init,true);
+	Pbool = secprop->Add_bool("glide",Property::Changeable::WhenIdle,false);
+	Pbool->Set_help("Enable glide emulation: true,false.");
+	//Phex = secprop->Add_hex("grport",Property::Changeable::WhenIdle,0x600);
+	//Phex->Set_help("I/O port to use for host communication.");
+	Pstring = secprop->Add_string("lfb",Property::Changeable::WhenIdle,"full_noaux");
+	Pstring->Set_help("LFB access: full,full_noaux,read,read_noaux,write,write_noaux,none.\n"
+		"OpenGlide does not support locking aux buffer, please use _noaux modes.");
+	Pbool = secprop->Add_bool("splash",Property::Changeable::WhenIdle,true);
+	Pbool->Set_help("Show 3dfx splash screen (requires 3dfxSpl2.dll).");
+
 	/* All the DOS Related stuff, which will eventually start up in the shell */
 	secprop=control->AddSection_prop("dos",&DOS_Init,false);//done
 	secprop->AddInitFunction(&XMS_Init,true);//done
diff -Naur dosbox.orig/src/gui/sdlmain.cpp dosbox/src/gui/sdlmain.cpp
--- dosbox.orig/src/gui/sdlmain.cpp	2021-02-14 12:26:31.015654162 +0000
+++ dosbox/src/gui/sdlmain.cpp	2021-02-14 12:29:29.613640385 +0000
@@ -50,6 +50,7 @@
 #include "cross.h"
 #include "control.h"
 #include "render.h"
+#include "glidedef.h"
 
 #define MAPPERFILE "mapper-" VERSION ".map"
 //#define DISABLE_JOYSTICK
@@ -620,6 +621,10 @@
 
 
 void GFX_ResetScreen(void) {
+	if(glide.enabled) {
+		GLIDE_ResetScreen(true);
+		return;
+	}
 	GFX_Stop();
 	if (sdl.draw.callback)
 		(sdl.draw.callback)( GFX_CallBackReset );
@@ -1249,7 +1254,10 @@
 		sticky_keys(true); //restore sticky keys to default state in windowed mode.
 #endif
 	}
-	GFX_ResetScreen();
+	if (glide.enabled)
+		GLIDE_ResetScreen();
+	else
+		GFX_ResetScreen();
 }
 
 static void SwitchFullScreen(bool pressed) {
@@ -2184,7 +2192,7 @@
 			throw(0);
 			break;
 		case SDL_VIDEOEXPOSE:
-			if (sdl.draw.callback) sdl.draw.callback( GFX_CallBackRedraw );
+			if ((sdl.draw.callback) && (!glide.enabled)) sdl.draw.callback( GFX_CallBackRedraw );
 			break;
 #ifdef WIN32
 		case SDL_KEYDOWN:
@@ -2669,6 +2677,7 @@
 			if (strcmp(sdl_drv_name,"windib")==0) LOG_MSG("SDL_Init: Starting up with SDL windib video driver.\n          Try to update your video card and directx drivers!");
 		}
 #endif
+	glide.fullscreen = &sdl.desktop.fullscreen;
 	sdl.num_joysticks=SDL_NumJoysticks();
 
 	/* Parse configuration files */
diff -Naur dosbox.orig/src/gui/sdlmain.cpp.rej dosbox/src/gui/sdlmain.cpp.rej
--- dosbox.orig/src/gui/sdlmain.cpp.rej	1970-01-01 01:00:00.000000000 +0100
+++ dosbox/src/gui/sdlmain.cpp.rej	2021-02-14 12:28:58.494761426 +0000
@@ -0,0 +1,10 @@
+--- src/gui/sdlmain.cpp	2017-10-10 16:40:44.924658343 +0200
++++ src/gui/sdlmain.cpp	2018-01-11 15:11:29.584499628 +0100
+@@ -49,6 +49,7 @@
+ #include "cpu.h"
+ #include "cross.h"
+ #include "control.h"
++#include "glidedef.h"
+ 
+ #define MAPPERFILE "mapper-" VERSION ".map"
+ //#define DISABLE_JOYSTICK
diff -Naur dosbox.orig/src/hardware/glide.cpp dosbox/src/hardware/glide.cpp
--- dosbox.orig/src/hardware/glide.cpp	1970-01-01 01:00:00.000000000 +0100
+++ dosbox/src/hardware/glide.cpp	2021-02-14 12:28:58.495761454 +0000
@@ -0,0 +1,1962 @@
+/*
+ *  Copyright (C) 2002-2013  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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "dosbox.h"
+#include "inout.h"
+#include "mem.h"
+#include "paging.h"
+#include "glide.h"
+#include "setup.h"
+#include "vga.h"
+#include "dos_inc.h"		/* for Drives[] */
+#include "control.h"
+#include "../dos/drives.h"
+
+#include <iomanip>
+#include <sstream>
+using namespace std;
+
+#include "SDL.h"
+
+#if defined (WIN32)
+#include "SDL_syswm.h"
+#include <windows.h>
+#include <Shlwapi.h>
+
+#ifdef _MSC_VER
+#pragma comment( lib, "Shlwapi" )
+#endif
+
+#else // *nix
+#include <dlfcn.h>
+
+#include <dirent.h>
+#include <errno.h>
+
+#endif
+
+extern void GFX_Stop(void);
+extern void GFX_ResetScreen(void);
+extern const char* RunningProgram;
+
+static float int_to_float(const Bit32u i)
+{
+    float f;
+    Bit32u i_native = SDL_SwapLE32(i);
+    SDL_memcpy(&f, &i_native, 4);
+    return f;
+}
+
+#define SAFE_DELETE(p)	{ if(p) { delete p; p = NULL; } }
+
+#define G_OK	1
+#define G_FAIL	0
+
+// Print debug messages
+#define LOG_GLIDE 0
+
+void VFILE_Remove(const char *name);
+static void process_msg(Bitu);
+
+/** Global Variables **/
+GLIDE_Block glide;
+
+//  Pointers to loaded routines
+static FncPointers FP;
+static void ** fn_pt=NULL;
+
+// Shared memory
+static Bit32u param[20];
+
+// Pointer to return value
+static PhysPt ret;
+static Bit16u ret_value;
+
+// Temporary texture buffer
+static Bit32u texsize=0;
+static void* texmem=NULL;
+
+static HostPt hwnd=NULL;
+static char lfbacc=0;
+
+// Tomb Rider shadow hack
+static Bit8u tomb = 0;
+static FxI32 GrOriginLocation = 0;
+
+#if defined (WIN32)
+static HINSTANCE hdll=NULL;	//  Handle to glide2x lib file
+#else
+static void * hdll=NULL;
+#endif
+
+#if LOG_GLIDE
+static int GLIDE_count[GLIDE_MAX+2];
+#endif
+
+static Bitu read_gl(Bitu port,Bitu iolen)
+{
+    Bitu r=ret_value;
+#if LOG_GLIDE
+    if(ret_value == G_OK)
+	LOG_MSG("Glide:Port read. Return address: 0x%x, value: %d", ret, mem_readd(ret));
+    else if(ret_value == G_FAIL)
+	LOG_MSG("Glide:Port read. Return address: 0x%x, value: %d. Writing G_FAIL to port", ret, mem_readd(ret));
+    else
+	LOG_MSG("Glide:Port read. Returning %hu", ret_value);
+#endif
+
+    ret_value = ret_value >> 8;
+
+    return r;
+}
+
+static void write_gl(Bitu port,Bitu val,Bitu iolen)
+{
+    static Bit16u glsegment = 0;
+
+    ret = 0;
+    ret_value = G_FAIL;
+    FP.grFunction0 = NULL;
+
+    // Allocate shared memory (80 bytes)
+    if(val > GLIDE_MAX) {
+	if(glsegment==0) {
+	    glsegment=DOS_GetMemory(5);
+#if LOG_GLIDE
+	    LOG_MSG("Glide:Memory allocated at 0x%x (segment: %hu)", glsegment<<4, glsegment);
+#endif
+	}
+	ret_value=glsegment;
+	LOG_MSG("Glide:Activated");
+	return;
+    }
+
+    // Process function parameters (80 bytes)
+    MEM_BlockRead32(PhysMake(glsegment,0), param, 80);
+    process_msg(val);
+
+//  LOG_MSG("Glide:Function %s executed OK", grTable[val].name);
+}
+
+class GLIDE_PageHandler : public PageHandler {
+private:
+    PhysPt base_addr[GLIDE_BUFFERS];	// GLIDE_LFB physical address
+    PhysPt lin_addr[GLIDE_BUFFERS];	// GLIDE_LFB linear address
+    HostPt lfb_addr[GLIDE_BUFFERS];	// Address offset from base_addr for the readable/writable buffers
+
+    /* Calculate physical address for access */
+    #define LFB_getAddr(addr) (lfb_addr[((addr - base_addr[0])>>12)>>GLIDE_PAGE_BITS]+addr)
+
+public:
+
+    FxU8 locked[GLIDE_BUFFERS];
+
+    GLIDE_PageHandler(HostPt addr, PhysPt phyaddr = GLIDE_LFB) {
+
+	if(addr == NULL) {
+	    LOG_MSG("Glide:NULL address passed to pagehandler!");
+	}
+
+	/* Set base addresses */
+	for(int i = 0; i < GLIDE_BUFFERS; i++) {
+	    locked[i] = 0;
+	    base_addr[i] = phyaddr;
+	    phyaddr += ((1<<GLIDE_PAGE_BITS)<<12);
+
+	    /* store offset from base address */
+	    lfb_addr[i] = (addr == NULL) ? NULL : (addr - base_addr[i]);
+	}
+
+	flags=PFLAG_READABLE|PFLAG_WRITEABLE|PFLAG_NOCODE;
+	PAGING_UnlinkPages(base_addr[0]>>12, GLIDE_PAGES);
+#if LOG_GLIDE
+	LOG_MSG("Glide:GLIDE_PageHandler installed at 0x%x", base_addr[0]);
+#endif
+    }
+
+    ~GLIDE_PageHandler() {
+#if LOG_GLIDE
+	LOG_MSG("Glide:Resetting page handler at 0x%x", base_addr[0]);
+#endif
+	PAGING_UnlinkPages(base_addr[0]>>12, GLIDE_PAGES);
+    }
+
+    void SetLFBAddr(HostPt addr, Bitu buffer) {
+	if(buffer >= GLIDE_BUFFERS) return;
+
+	addr = addr - base_addr[buffer];	/* Calculate offset for current buffer */
+
+	if(addr != lfb_addr[buffer]) {
+	    lfb_addr[buffer] = addr;
+#if LOG_GLIDE
+	    LOG_MSG("Glide:LFB for buffer %d offset set to 0x%p (addr: 0x%p), clear TLB", buffer, lfb_addr[buffer], addr+base_addr[buffer]);
+#endif
+	    PAGING_UnlinkPages(base_addr[buffer]>>12, 1<<GLIDE_PAGE_BITS);
+	}
+    }
+
+    PhysPt GetPhysPt(Bitu buffer = 0) {
+	if(buffer >= GLIDE_BUFFERS) return base_addr[0];
+	return base_addr[buffer];
+    }
+
+    void SetLinPt(PhysPt linaddr) {
+	for(int i = 0; i < GLIDE_BUFFERS; i++) {
+	    lin_addr[i] = linaddr;
+	    linaddr += ((1<<GLIDE_PAGE_BITS)<<12);
+	}
+    }
+
+    PhysPt GetLinPt(Bitu buffer = 0) {
+	if(buffer >= GLIDE_BUFFERS) return lin_addr[0];
+	return lin_addr[buffer];
+    }
+
+    Bitu readb(PhysPt addr) {
+//	LOG_MSG("Glide:Read from 0x%p", LFB_getAddr(addr));
+	return *(Bit8u *)(LFB_getAddr(addr));
+    }
+
+    Bitu readw(PhysPt addr) {
+//	LOG_MSG("Glide:Read from 0x%p", LFB_getAddr(addr));
+	return *(Bit16u *)(LFB_getAddr(addr));
+    }
+
+    Bitu readd(PhysPt addr) {
+//	LOG_MSG("Glide:Read from 0x%p", LFB_getAddr(addr));
+	return *(Bit32u *)(LFB_getAddr(addr));
+    }
+
+    void writeb(PhysPt addr,Bitu val) {
+//	LOG_MSG("Glide:Write to 0x%p", LFB_getAddr(addr));
+	*(Bit8u *)(LFB_getAddr(addr))=(Bit8u)val;
+    }
+
+    void writew(PhysPt addr,Bitu val) {
+//	LOG_MSG("Glide:Write to 0x%p", LFB_getAddr(addr));
+	*(Bit16u *)(LFB_getAddr(addr))=(Bit16u)val;
+    }
+
+    void writed(PhysPt addr,Bitu val) {
+//	LOG_MSG("Glide:Write to 0x%p", LFB_getAddr(addr));
+	*(Bit32u *)(LFB_getAddr(addr))=(Bit32u)val;
+    }
+
+    HostPt GetHostReadPt(Bitu phys_page) {
+	Bitu buffer = (((phys_page<<12) - base_addr[0])>>12)>>GLIDE_PAGE_BITS;
+#if LOG_GLIDE
+	// This only makes sense if full lfb access is used...
+	if (!locked[buffer]) LOG_MSG("Glide:Read from unlocked LFB at: 0x%x", phys_page<<12);
+#endif
+//	LOG_MSG("Glide:GetHostReadPt called with %d, returning 0x%p", phys_page, LFB_getAddr((phys_page*MEM_PAGESIZE)));
+	return lfb_addr[buffer]+(phys_page<<12);
+    }
+
+    HostPt GetHostWritePt(Bitu phys_page) {
+	Bitu buffer = (((phys_page<<12) - base_addr[0])>>12)>>GLIDE_PAGE_BITS;
+#if LOG_GLIDE
+	// This only makes sense if full lfb access is used...
+	if (!locked[buffer]) LOG_MSG("Glide:Write to unlocked LFB at: 0x%x", phys_page<<12);
+#endif
+//	LOG_MSG("Glide:GetHostWritePt called with %d, returning 0x%p", phys_page, LFB_getAddr((phys_page*MEM_PAGESIZE)));
+	return lfb_addr[buffer]+(phys_page<<12);
+    }
+
+    #undef LFB_getAddr
+};
+
+class GLIDE: public Module_base {
+private:
+    AutoexecObject autoexecline;
+    // Glide port
+    Bitu glide_base;
+    Bit8u *ovl_data;
+public:
+    GLIDE(Section* configuration):Module_base(configuration),glide_base(0),ovl_data(NULL) {
+	Section_prop * section=static_cast<Section_prop *>(configuration);
+
+	if(!section->Get_bool("glide")) return;
+	std::string str = section->Get_string("lfb");
+	lowcase(str);
+	if(str == "none") {
+	    LOG_MSG("Glide:Disabled LFB access");
+	    lfbacc=0;
+	} else if(str == "read_noaux") {
+	    LOG_MSG("Glide:LFB access: read-only (no aux)");
+	    lfbacc=1;
+	} else if(str == "write_noaux") {
+	    LOG_MSG("Glide:LFB access: write-only (no aux)");
+	    lfbacc=2;
+	} else if(str == "full_noaux") {
+	    LOG_MSG("Glide:LFB access: read-write (no aux)");
+	    lfbacc=3;
+	} else if(str == "read") {
+	    LOG_MSG("Glide:LFB access: read-only");
+	    lfbacc=5;
+	} else if(str == "write") {
+	    LOG_MSG("Glide:LFB access: write-only");
+	    lfbacc=6;
+	} else {
+	    LOG_MSG("Glide:LFB access: read-write");
+	    lfbacc=7;
+	}
+
+	// Load glide2x.dll
+#if defined(WIN32)
+	hdll = LoadLibrary("glide2x.dll");
+#elif defined(MACOSX)
+	hdll = dlopen("libglide2x.dylib", RTLD_NOW);
+#else
+	hdll = dlopen("libglide2x.so", RTLD_NOW);
+#endif
+
+	if(!hdll) {
+	    LOG_MSG("Glide:Unable to load glide2x library, glide emulation disabled");
+	    return;
+	}
+
+	// Allocate some temporary space
+	texmem = (void*)malloc(1600*1200*4);
+	if(texmem == NULL) {
+	    LOG_MSG("Glide:Unable to allocate texture memory, glide disabled");
+	    return;
+	}
+
+	glide.lfb_pagehandler = new GLIDE_PageHandler((HostPt)texmem);
+	if(!glide.lfb_pagehandler) {
+	    LOG_MSG("Glide:Failed to install page handler, glide disabled!");
+	    free(texmem); texmem = NULL;
+	    return;
+	}
+
+	// Load glide2x.ovl if possible, so it is available on the Z: drive
+	const char ovl_name[] = "glide2x.ovl";
+
+#if defined (WIN32)
+	// Windows is simple, the search is case insensitive
+	FILE * ovl = fopen(ovl_name, "rb");
+
+	// if not successful try to specifically search dosbox directory
+	if (ovl == NULL) {
+	    char * path = (char*)texmem;
+	    GetModuleFileName(NULL, path, 32768);
+	    // strip "\dosbox.exe" from path
+	    PathRemoveFileSpec(path);
+	    strcat(path, "\\"); strcat(path, ovl_name);
+	    ovl = fopen(path, "rb");
+	}
+
+#else
+	// Try a bit harder in *nix, perform case insensitive search and check /usr/share/dosbox as well
+	DIR * pdir; struct dirent * pfile;
+	char * path = (char*)texmem;
+	FILE * ovl = NULL;
+
+	const char * const dirname[] = { "./", "/usr/share/dosbox/", NULL };
+
+	for (int i = 0; dirname[i]; i++) {
+	    if((pdir = opendir(dirname[i]))) {
+		while(pfile = readdir(pdir)) {
+		    if(!strcasecmp(pfile->d_name, ovl_name)) {
+			strcpy(path, dirname[i]); strcat(path, pfile->d_name);
+			ovl = fopen(path, "rb");
+			if(ovl) break;
+		    }
+		}
+		closedir(pdir);
+		if(ovl) break;
+	    }
+	}
+#endif
+
+	long ovl_size = 0;
+
+	if(ovl == NULL) {
+	    LOG_MSG("Glide:GLIDE2X.OVL could not be found, make sure correct (compatible) version is in the game directory!");
+	} else {
+	    fseek(ovl, 0, SEEK_END);
+	    ovl_size=ftell(ovl);
+	    ovl_data=(Bit8u*)malloc(ovl_size);
+	    fseek(ovl, 0, SEEK_SET);
+	    fread(ovl_data, sizeof(char), ovl_size, ovl);
+	    fclose(ovl);
+	}
+
+#if LOG_GLIDE
+	SDL_memset(GLIDE_count, 0, sizeof(GLIDE_count));
+#endif
+
+	// Allocate memory for dll pointers
+	fn_pt = (void**)malloc(sizeof(void*)*(GLIDE_MAX+1));
+	if(fn_pt == NULL) {
+	    LOG_MSG("Glide:Unable to allocate memory, glide disabled");
+	    free(texmem); texmem = NULL;
+	    if(ovl_data) free(ovl_data); ovl_data = NULL;
+	    return;
+	}
+
+	for(int i=0; i<(GLIDE_MAX+1); i++) {
+#if defined(WIN32)
+	    ostringstream temp;
+	    // Add function decoration
+	    temp << "_" << grTable[i].name << "@" << (Bitu)grTable[i].parms;
+	    fn_pt[i] = (void*)(GetProcAddress(hdll, temp.str().c_str()));
+#else
+	    fn_pt[i] = (void*)(dlsym(hdll, grTable[i].name));
+#endif
+#if LOG_GLIDE
+	    if(fn_pt[i] == NULL) {
+		LOG_MSG("Glide:Warning, unable to load %s from glide2x", grTable[i].name);
+	    }
+#endif
+	}
+
+	//glide_base=section->Get_hex("grport");
+	glide_base=0x600;	// Anybody else got a better idea?
+
+	IO_RegisterReadHandler(glide_base,read_gl,IO_MB);
+	IO_RegisterWriteHandler(glide_base,write_gl,IO_MB);
+
+	ostringstream temp;
+	temp << "SET GLIDE=" << hex << glide_base << ends;
+
+	autoexecline.Install(temp.str());
+	glide.splash = section->Get_bool("splash");
+
+#if defined (WIN32)
+	// Get hwnd information
+	SDL_SysWMinfo wmi;
+	SDL_VERSION(&wmi.version);
+	if(SDL_GetWMInfo(&wmi)) {
+	    hwnd = (HostPt)wmi.window;
+	} else {
+	    LOG_MSG("SDL:Error retrieving window information");
+	}
+#endif
+
+	if(ovl_data) {
+	    VFILE_Register("GLIDE2X.OVL", ovl_data, ovl_size);
+	}
+    }
+
+    ~GLIDE() {
+	if(glide.enabled) {
+	    // void grGlideShutdown(void)
+	    FP.grFunction0 = (pfunc0)fn_pt[_grGlideShutdown0];
+	    if(FP.grFunction0) FP.grFunction0();
+	    glide.enabled = false;
+	}
+
+	SAFE_DELETE(glide.lfb_pagehandler);
+	if(fn_pt)
+	    free(fn_pt); fn_pt = NULL;
+	if(texmem)
+	    free(texmem); texmem = NULL;
+
+	if(glide_base) {
+	    IO_FreeReadHandler(glide_base,IO_MB);
+	    IO_FreeWriteHandler(glide_base,IO_MB);
+	}
+
+	if(hdll) {
+#if defined (WIN32)
+	    FreeLibrary(hdll);
+#else
+	    dlclose(hdll);
+#endif
+	    hdll = NULL;
+	}
+
+	VFILE_Remove("GLIDE2X.OVL");
+	if(ovl_data) free(ovl_data);
+    }
+};
+
+static GLIDE* test;
+void GLIDE_ShutDown(Section* sec) {
+    delete test;
+}
+
+void GLIDE_Init(Section* sec) {
+    test = new GLIDE(sec);
+    sec->AddDestroyFunction(&GLIDE_ShutDown,true);
+}
+
+void GLIDE_ResetScreen(bool update)
+{
+#if LOG_GLIDE
+	LOG_MSG("Glide:ResetScreen");
+#endif
+	VGA_SetOverride(true);
+	GFX_Stop();
+
+	// OpenGlide will resize the window on it's own (using SDL)
+	if(glide.width && (
+#ifdef WIN32
+	// dgVoodoo needs a little help :)
+	// Most other wrappers render fullscreen by default
+	  (GetProcAddress(hdll, "DispatchDosNT") != NULL) ||
+#endif
+	// and resize when mapper and/or GUI finish
+	  update)) {
+	    SDL_SetVideoMode(glide.width,glide.height,0,
+		(glide.fullscreen[0]?SDL_FULLSCREEN:0)|SDL_ANYFORMAT|SDL_SWSURFACE);
+	}
+}
+
+void GLIDE_DisableScreen(void)
+{
+	glide.enabled = false;	/* if not disabled, GFX_ResetScreen() will call GLIDE_ResetScreen() */
+	VGA_SetOverride(false);
+	GFX_ResetScreen();
+}
+
+static bool GetFileName(char * filename)
+{
+    localDrive	*ldp;
+    Bit8u	drive;
+    char	fullname[DOS_PATHLENGTH];
+
+    // Get full path
+    if(!DOS_MakeName(filename,fullname,&drive)) return false;
+
+#if LOG_GLIDE
+    LOG_MSG("Glide:Fullname: %s", fullname);
+#endif
+
+    // Get real system path
+    ldp = dynamic_cast<localDrive*>(Drives[drive]);
+    if(ldp == NULL) return false;
+
+    ldp->GetSystemFilename(filename,fullname);
+#if LOG_GLIDE
+    LOG_MSG("Glide:System path: %s", filename);
+#endif
+    return true;
+}
+
+typedef FxBool (FX_CALL *pfxSplashInit)(FxU32 hWnd, FxU32 screenWidth, FxU32 screenHeight,
+					FxU32 numColBuf, FxU32 numAuxBuf, GrColorFormat_t colorFormat);
+typedef void (FX_CALL *pfxSplash)(float x, float y, float w, float h, FxU32 frameNumber);
+
+static void grSplash(void)
+{
+#ifdef WIN32
+    FxU32 screenWidth = glide.width, screenHeight = glide.height;
+    HINSTANCE dll = LoadLibrary("3dfxSpl2.dll");
+    if(dll == NULL) {
+	return;
+    }
+
+    pfxSplashInit fxSplashInit = (pfxSplashInit)GetProcAddress(dll, "_fxSplashInit@24");
+    pfxSplash fxSplash = (pfxSplash)GetProcAddress(dll, "_fxSplash@20");
+
+    if((fxSplashInit == NULL) || (fxSplash == NULL)) {
+	FreeLibrary(dll);
+	return;
+    }
+
+    if (screenWidth == 0) {
+	screenWidth = 640;
+	screenHeight = 480;
+    }
+
+    fxSplashInit(0, screenWidth, screenHeight, 2, 1, GR_COLORFORMAT_ABGR);
+    fxSplash(0, 0, screenWidth, screenHeight, 0);
+
+    // OpenGlide does not restore this state
+    FP.grFunction1i = (pfunc1i)fn_pt[_grSstOrigin4];
+    if(FP.grFunction1i) {
+	FP.grFunction1i(GrOriginLocation);
+    }
+
+    FreeLibrary(dll);
+#endif
+}
+
+static void process_msg(Bitu value)
+{
+    GrLfbInfo_t   lfbinfo;
+    DBGrLfbInfo_t dblfbinfo;
+
+    GrTexInfo     texinfo;
+    DBGrTexInfo   dbtexinfo;
+
+    Gu3dfInfo     guinfo;
+    DBGu3dfInfo   dbguinfo;
+
+    GrVertex      vertex[3];
+
+    GrMipMapInfo * mipmap;
+
+    // Temporary memory used in functions
+    FxI32	* ilist = (FxI32*)texmem;
+    FxU16	* ptr16 = (FxU16*)texmem;
+
+    // Filename translation
+    char	  filename[512];
+
+    // Used so that the pagehandler returns the same address after buffer swap
+    static char	  b_swap = 0;
+
+    // Return value address
+    ret = param[0];
+    Bitu i = value;
+    Bitu buffer;
+    FxU32 j, k;
+
+    if((i > GLIDE_MAX) || (fn_pt[i] == NULL)) {
+	LOG_MSG("Glide:Invalid function pointer for call %s", (i > GLIDE_MAX) ? "(invalid)" : grTable[i].name);
+	return;
+    }
+
+#if LOG_GLIDE
+    LOG_MSG("Glide:Processing call %s (%d), return address: 0x%x", grTable[i].name, value, ret);
+    GLIDE_count[i]++;
+#endif
+
+    switch (value) {
+
+    case _grAADrawLine8:
+	// void grAADrawLine(GrVertex *va, GrVertex *vb)
+	FP.grFunction2p = (pfunc2p)fn_pt[i];
+	MEM_BlockRead32(param[1], &vertex[0], sizeof(GrVertex));
+	MEM_BlockRead32(param[2], &vertex[1], sizeof(GrVertex));
+	FP.grFunction2p(&vertex[0], &vertex[1]);
+	break;
+    case _grAADrawPoint4:
+	// void grAADrawPoint(GrVertex *p)
+	FP.grFunction1p = (pfunc1p)fn_pt[i];
+	MEM_BlockRead32(param[1], &vertex[0], sizeof(GrVertex));
+	FP.grFunction1p(&vertex[0]);
+	break;
+    case _grAADrawPolygon12:
+	// void grAADrawPolygon(int nVerts, const int ilist[], const GrVertex vlist[])
+	FP.grFunction1i2p = (pfunc1i2p)fn_pt[i];
+	i = sizeof(FxI32)*param[1];
+	MEM_BlockRead32(param[2], ilist, i);
+
+	// Find the number of vertices (?)
+	k = 0;
+	for(j = 0; j < param[1]; j++) {
+	    if(ilist[j] > k)
+		k = ilist[j];
+	}
+	k++;
+
+	MEM_BlockRead32(param[3], ilist+i, sizeof(GrVertex)*k);
+	FP.grFunction1i2p(param[1], ilist, ilist+i);
+	break;
+    case _grAADrawPolygonVertexList8:
+	// void grAADrawPolygonVertexList(int nVerts, const GrVertex vlist[])
+	FP.grFunction1i1p = (pfunc1i1p)fn_pt[i];
+	MEM_BlockRead32(param[2], texmem, sizeof(GrVertex)*param[1]);
+	FP.grFunction1i1p(param[1], texmem);
+	break;
+    case _grAADrawTriangle24:
+	// void grAADrawTriangle(GrVertex *a, GrVertex *b, GrVertex *c,
+	//	FxBool antialiasAB, FxBool antialiasBC, FxBool antialiasCA)
+	FP.grFunction3p3i = (pfunc3p3i)fn_pt[i];
+	MEM_BlockRead32(param[1], &vertex[0], sizeof(GrVertex));
+	MEM_BlockRead32(param[2], &vertex[1], sizeof(GrVertex));
+	MEM_BlockRead32(param[3], &vertex[2], sizeof(GrVertex));
+	FP.grFunction3p3i(&vertex[0], &vertex[1], &vertex[2], param[4], param[5], param[6]);
+	break;
+    case _grAlphaBlendFunction16:
+	// void grAlphaBlendFunction(GrAlphaBlendFnc_t rgb_sf, GrAlphaBlendFnc_t rgb_df,
+	//		GrAlphaBlendFnc_t alpha_sf, GrAlphaBlendFnc_t alpha_df)
+	if(tomb == 1) {
+	    if((FP.grFunction1i = (pfunc1i)fn_pt[_grConstantColorValue4]))
+		FP.grFunction1i(0x7f000000);
+	} else if(tomb == 2) {
+	    float color1 = 127.0;
+	    float color2 = 0.0;
+	    if((FP.grFunction4f = (pfunc4f)fn_pt[_grConstantColorValue416]))
+		FP.grFunction4f(color1, color2, color2, color2);
+	}
+	FP.grFunction4i = (pfunc4i)fn_pt[i];
+	FP.grFunction4i(param[1], param[2], param[3], param[4]);
+	break;
+    case _grAlphaCombine20:
+	// void grAlphaCombine(GrCombineFunction_t func, GrCombineFactor_t factor,
+	//		GrCombineLocal_t local, GrCombineOther_t other, FxBool invert)
+	FP.grFunction5i = (pfunc5i)fn_pt[i];
+	FP.grFunction5i(param[1], param[2], param[3], param[4], param[5]);
+	break;
+    case _grAlphaControlsITRGBLighting4:
+	// void grAlphaControlsITRGBLighting(FxBool enable)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grAlphaTestFunction4:
+	// void grAlphaTestFunction(GrCmpFnc_t function)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grAlphaTestReferenceValue4:
+	// void grAlphaTestReferenceValue(GrAlpha_t value)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grBufferClear12:
+	// void grBufferClear(GrColor_t color, GrAlpha_t alpha, FxU16 depth)
+	FP.grFunction3i = (pfunc3i)fn_pt[i];
+	FP.grFunction3i(param[1], param[2], param[3]);
+	break;
+    case _grBufferNumPending0:
+	// int grBufferNumPending(void)
+	FP.grRFunction0 = (prfunc0)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction0());
+	ret_value = G_OK;
+	break;
+    case _grBufferSwap4:
+	// void grBufferSwap(int swap_interval)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	/* Breaks Extreme Assault (which always writes to same address ?) */
+	//if(!(glide.lfb_pagehandler->locked[0] || glide.lfb_pagehandler->locked[1])) b_swap = !b_swap;
+#if LOG_GLIDE
+	LOG_MSG("Glide:BufferSwap (0x%x)", b_swap);
+#endif
+	break;
+    case _grCheckForRoom4:
+	// void grCheckForRoom(FxI32 n)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grChromakeyMode4:
+	// void grChromakeyMode(GrChromakeyMode_t mode)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grChromakeyValue4:
+	// void grChromakeyValue(GrColor_t value)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grClipWindow16:
+	// void grClipWindow(FxU32 minx, FxU32 miny, FxU32 maxx, FxU32 maxy)
+	FP.grFunction4i = (pfunc4i)fn_pt[i];
+	FP.grFunction4i(param[1], param[2], param[3], param[4]);
+	break;
+    case _grColorCombine20:
+	// void grColorCombine(GrCombineFunction_t func, GrCombineFactor_t factor,
+	//		GrCombineLocal_t local, GrCombineOther_t other, FxBool invert)
+	FP.grFunction5i = (pfunc5i)fn_pt[i];
+	FP.grFunction5i(param[1], param[2], param[3], param[4], param[5]);
+	break;
+    case _grColorMask8:
+	// void grColorMask(FxBool rgb, FxBool alpha)
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i(param[1], param[2]);
+	break;
+    case _grConstantColorValue416:
+	// void grConstantColorValue4(float a, float r, float g, float b)
+	FP.grFunction4f = (pfunc4f)fn_pt[i];
+	FP.grFunction4f(int_to_float(param[1]), int_to_float(param[2]), int_to_float(param[3]), int_to_float(param[4]));
+	break;
+    case _grConstantColorValue4:
+	// void grConstantColorValue(GrColor_t color)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grCullMode4:
+	// void grCullMode(GrCullMode_t mode)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grDepthBiasLevel4:
+	// void grDepthBiasLevel(FxI16 level)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grDepthBufferFunction4:
+	// void grDepthBufferFunction(GrCmpFnc_t func)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grDepthBufferMode4:
+	// void grDepthBufferMode(GrDepthBufferMode_t mode)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grDepthMask4:
+	// void grDepthMask(FxBool enable)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grDisableAllEffects0:
+	// void grDisableAllEffects(void)
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+    case _grDitherMode4:
+	// void grDitherMode(GrDitherMode_t mode)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grDrawLine8:
+	// void grDrawLine(const GrVertex *a, const GrVertex *b)
+	FP.grFunction2p = (pfunc2p)fn_pt[i];
+	MEM_BlockRead32(param[1], &vertex[0], sizeof(GrVertex));
+	MEM_BlockRead32(param[2], &vertex[1], sizeof(GrVertex));
+	FP.grFunction2p(&vertex[0], &vertex[1]);
+	break;
+    case _grDrawPlanarPolygon12:
+	// void grDrawPlanarPolygon(int nVerts, int ilist[], const GrVertex vlist[])
+	FP.grFunction1i2p = (pfunc1i2p)fn_pt[i];
+	i = sizeof(FxI32)*param[1];
+	MEM_BlockRead32(param[2], ilist, i);
+
+	// Find the number of vertices (?)
+	k = 0;
+	for(j = 0; j < param[1]; j++) {
+	    if(ilist[j] > k)
+		k = ilist[j];
+	}
+	k++;
+
+	MEM_BlockRead32(param[3], ilist+i, sizeof(GrVertex)*k);
+	FP.grFunction1i2p(param[1], ilist, ilist+i);
+	break;
+    case _grDrawPlanarPolygonVertexList8:
+	// void grDrawPlanarPolygonVertexList(int nVertices, const GrVertex vlist[])
+	FP.grFunction1i1p = (pfunc1i1p)fn_pt[i];
+	MEM_BlockRead32(param[2], texmem, sizeof(GrVertex)*param[1]);
+	FP.grFunction1i1p(param[1], texmem);
+	break;
+    case _grDrawPoint4:
+	// void grDrawPoint(const GrVertex *a)
+	FP.grFunction1p = (pfunc1p)fn_pt[i];
+	MEM_BlockRead32(param[1], &vertex[0], sizeof(GrVertex));
+	FP.grFunction1p(&vertex[0]);
+	break;
+    case _grDrawPolygon12:
+	// void grDrawPolygon(int nVerts, int ilist[], const GrVertex vlist[])
+	FP.grFunction1i2p = (pfunc1i2p)fn_pt[i];
+	i = sizeof(FxI32)*param[1];
+	MEM_BlockRead32(param[2], ilist, i);
+
+	// Find the number of vertices (?)
+	k = 0;
+	for(j = 0; j < param[1]; j++) {
+	    if(ilist[j] > k)
+		k = ilist[j];
+	}
+	k++;
+
+	MEM_BlockRead32(param[3], ilist+i, sizeof(GrVertex)*k);
+	FP.grFunction1i2p(param[1], ilist, ilist+i);
+	break;
+    case _grDrawPolygonVertexList8:
+	// void grDrawPolygonVertexList(int nVerts, const GrVertex vlist[])
+	FP.grFunction1i1p = (pfunc1i1p)fn_pt[i];
+	MEM_BlockRead32(param[2], texmem, sizeof(GrVertex)*param[1]);
+	FP.grFunction1i1p(param[1], texmem);
+	break;
+    case _grDrawTriangle12:
+	// void grDrawTriangle(const GrVertex *a, const GrVertex *b, const GrVertex *c)
+	FP.grFunction3p = (pfunc3p)fn_pt[i];
+	MEM_BlockRead32(param[1], &vertex[0], sizeof(GrVertex));
+	MEM_BlockRead32(param[2], &vertex[1], sizeof(GrVertex));
+	MEM_BlockRead32(param[3], &vertex[2], sizeof(GrVertex));
+	FP.grFunction3p(&vertex[0], &vertex[1], &vertex[2]);
+	break;
+/*
+    case _grErrorSetCallback4:
+	// void grErrorSetCallback(void (*function)(const char *string, FxBool fatal))
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(*param[1]);
+	break;
+*/
+    case _grFogColorValue4:
+	// void grFogColorValue(GrColor_t value)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grFogMode4:
+	// void grFogMode(GrFogMode_t mode)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grFogTable4:
+	// void grFogTable(const GrFog_t grTable[GR_FOG_TABLE_SIZE])
+	FP.grFunction1p = (pfunc1p)fn_pt[i];
+	MEM_BlockRead32(param[1], texmem, sizeof(GrFog_t)*GR_FOG_TABLE_SIZE);
+	FP.grFunction1p(texmem);
+	break;
+    case _grGammaCorrectionValue4:
+	// void grGammaCorrectionValue(float value)
+	FP.grFunction1f = (pfunc1f)fn_pt[i];
+	FP.grFunction1f(int_to_float(param[1]));
+	break;
+    case _grGlideGetState4:
+	// void grGlideGetState(GrState *state)
+	FP.grFunction1p = (pfunc1p)fn_pt[i];
+	MEM_BlockRead32(param[1], texmem, sizeof(GrState));
+	FP.grFunction1p(texmem);
+	MEM_BlockWrite32(param[1], texmem, sizeof(GrState));
+	break;
+    case _grGlideGetVersion4:
+	// void grGlideGetVersion(char version[80])
+	FP.grFunction1p = (pfunc1p)fn_pt[i];
+	FP.grFunction1p(filename);
+	k = 0;
+	do {
+	    mem_writeb(param[1]++, filename[k++]);
+	} while(filename[k] != '\0');
+	mem_writeb(param[1], '\0');
+	break;
+    case _grGlideInit0:
+	// void grGlideInit(void)
+	if(glide.enabled) break;	/* Tie Break Tennis */
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+
+	// Enable Tomb Rider displaying shadow
+	if(!strncasecmp(RunningProgram, "Tombub", 6)) tomb = 2;
+	else if(!strncasecmp(RunningProgram, "Tomb", 4)) tomb = 1;
+	else tomb = 0;
+
+	// Send LFB to the OVL (so it can map it in linear address)
+	if(mem_readd(param[1]) == 0xFFFFF1FB) {		// Find LFB magic
+	    mem_writed(param[1], glide.lfb_pagehandler->GetPhysPt());
+	    mem_writed(param[2], GLIDE_PAGES);
+	} else {
+	    LOG_MSG("Glide:Detected incompatible guest ovl/dll!");
+	}
+	break;
+    case _grGlideSetState4:
+	// void grGlideSetState(const GrState *state)
+	FP.grFunction1p = (pfunc1p)fn_pt[i];
+	MEM_BlockRead32(param[1], texmem, sizeof(GrState));
+	FP.grFunction1p(texmem);
+	MEM_BlockWrite32(param[1], texmem, sizeof(GrState));
+	break;
+    case _grGlideShamelessPlug4:
+	// void grGlideShamelessPlug(const FxBool on)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grGlideShutdown0: {
+	// void grGlideShutdown(void)
+	if(glide.enabled) {
+	    FP.grFunction0 = (pfunc0)fn_pt[_grSstWinClose0];
+	    FP.grFunction0();
+	    GLIDE_DisableScreen();
+	}
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+
+	Section_prop *section=static_cast<Section_prop *>(control->GetSection("glide"));
+	if (section) glide.splash = section->Get_bool("splash");
+
+#if LOG_GLIDE
+	for(j=0;j<(GLIDE_MAX+1);j++) {
+	    if(GLIDE_count[j]) {
+		LOG_MSG("Glide:%6d calls function %s (%d)", GLIDE_count[j], grTable[j].name, j);
+	    }
+	}
+	LOG_MSG("Glide: %d framebuffer locks (%d read, %d write)", GLIDE_count[_grLfbLock24],
+	    GLIDE_count[GLIDE_MAX+1], GLIDE_count[_grLfbLock24] - GLIDE_count[GLIDE_MAX+1]);
+	SDL_memset(GLIDE_count, 0, sizeof(GLIDE_count));
+#endif
+	break;
+	}
+    case _grHints8:
+	// void grHints(GrHints_t type, FxU32 hintMask)
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i(param[1], param[2]);
+	break;
+    case _grLfbConstantAlpha4:
+	// void grLfbConstantAlpha(GrAlpha_t alpha)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grLfbConstantDepth4:
+	// void grLfbConstantDepth(FxU16 depth)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grLfbLock24:
+	// FxBool grLfbLock(GrLock_t type, GrBuffer_t buffer, GrLfbWriteMode_t writeMode,
+	//		GrOriginLocation_t origin, FxBool pixelPipeline, GrLfbInfo_t *info)
+	FP.grRFunction5i1p = (prfunc5i1p)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+
+	buffer = (Bitu)param[2];
+	if (buffer >= GLIDE_BUFFERS) {
+	    LOG_MSG("Glide:Invalid buffer passed in grLfbLock (%d)", buffer);
+	    return;
+	}
+
+	// Buffer address should stay the same after BufferSwap, so check which buffer to lock
+	// (currently disabled, see _grBufferSwap4)
+	//if (buffer < 2) buffer = buffer ^ b_swap;
+
+	// Read parameters
+	MEM_BlockRead32(param[6], &dblfbinfo, sizeof(DBGrLfbInfo_t));
+	lfbinfo.size = sizeof(GrLfbInfo_t);
+	lfbinfo.origin = dblfbinfo.origin;
+	lfbinfo.lfbPtr = texmem;
+
+	k = FXTRUE;
+	j = param[1]&1; j++;
+	j = j&lfbacc;				// Check LFB access (j = 0 : fake lock)
+	if((buffer > 1) && (lfbacc < 4)) j = 0;	// Disable access to AUX buffers
+
+	if(j) {
+		// Lock the buffer
+		k = FP.grRFunction5i1p(param[1], param[2], param[3], param[4], param[5], &lfbinfo);
+		if(k == FXTRUE) {
+		    glide.lfb_pagehandler->locked[buffer]++;
+		    dblfbinfo.writeMode = lfbinfo.writeMode;
+		    dblfbinfo.strideInBytes = lfbinfo.strideInBytes;
+		    dblfbinfo.lfbPtr = glide.lfb_pagehandler->GetLinPt(buffer);
+		    MEM_BlockWrite32(param[6], &dblfbinfo, sizeof(DBGrLfbInfo_t));
+		} else {
+		    LOG_MSG("Glide:LFB Lock failed!");
+		    //k = FXFALSE;	// Lock failed ?
+		}
+	} else {
+		// else lock is faked (texmem used for read/write)
+		if(param[3] == GR_LFBWRITEMODE_ANY) dblfbinfo.writeMode = GR_LFBWRITEMODE_565;
+		else dblfbinfo.writeMode = param[3];
+
+		if(param[4] == GR_ORIGIN_ANY) dblfbinfo.origin = GR_ORIGIN_UPPER_LEFT;
+		else dblfbinfo.origin = param[4];
+
+		dblfbinfo.strideInBytes = ((glide.width>0) ? glide.width : 1600) * 4;
+		dblfbinfo.lfbPtr = glide.lfb_pagehandler->GetLinPt(buffer);
+
+		//if(!(param[1]&GR_LFB_WRITE_ONLY)) SDL_memset(texmem, 0, 1600*1200*4);	// Clear memory on read-lock
+
+		MEM_BlockWrite32(param[6], &dblfbinfo, sizeof(DBGrLfbInfo_t));
+	}
+
+	// Set LFB address for page handler to read from/write to
+	glide.lfb_pagehandler->SetLFBAddr((HostPt)lfbinfo.lfbPtr, buffer);
+	if(dblfbinfo.strideInBytes > 1600*4) LOG_MSG("Glide:WARNING LFB stride (%d) is larger than max supported: %d!", dblfbinfo.strideInBytes, 1600*4);
+
+	if(param[1]&GR_LFB_WRITE_ONLY) {	// Is a write-only lock
+#if LOG_GLIDE
+		LOG_MSG("Glide:W/O lock (%d). %s LFB: 0x%p. Returning 0x%x (stride: %d)", param[2], (j ? "Real" : "Fake"),
+			lfbinfo.lfbPtr, dblfbinfo.lfbPtr, dblfbinfo.strideInBytes);
+#endif
+	} else {	// Is a read-only lock
+#if LOG_GLIDE
+		LOG_MSG("Glide:R/O lock (%d). %s LFB: 0x%p. Returning 0x%x (stride: %d)", param[2], (j ? "Real" : "Fake"),
+			lfbinfo.lfbPtr, dblfbinfo.lfbPtr, dblfbinfo.strideInBytes);
+		GLIDE_count[GLIDE_MAX+1]++;
+#endif
+	}
+
+	mem_writed(ret, k);
+	ret_value = G_OK;
+	break;
+    case _grLfbReadRegion28:
+	// FxBool grLfbReadRegion(GrBuffer_t src_buffer, FxU32 src_x, FxU32 src_y, FxU32 src_width,
+	//		FxU32 src_height, FxU32 dst_stride, void *dst_data)
+	FP.grRFunction6i1p = (prfunc6i1p)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction6i1p(param[1], param[2], param[3], param[4], param[5], param[6], ptr16));
+	MEM_BlockWrite(param[7], ptr16, param[5]*param[6]);
+	ret_value = G_OK;
+	break;
+    case _grLfbUnlock8:
+	// FxBool grLfbUnlock(GrLock_t type, GrBuffer_t buffer)
+	FP.grRFunction2i = (prfunc2i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle (%d)!", _grLfbUnlock8);
+	    return;
+	}
+	k = FXTRUE;
+	buffer = param[2];
+	if (buffer < 2) buffer = buffer ^ b_swap;
+#if LOG_GLIDE
+	LOG_MSG("Glide:LFB unlock buffer (%d), %d locks active", param[2], glide.lfb_pagehandler->locked[buffer]);
+#endif
+	if(glide.lfb_pagehandler->locked[buffer]) {
+	    k = FP.grRFunction2i(param[1], param[2]);
+	    glide.lfb_pagehandler->locked[buffer]--;
+	    glide.lfb_pagehandler->SetLFBAddr((HostPt)texmem, buffer);	// Reset page handler and clear TLB
+	} else {
+	    //k = FXFALSE;
+	}
+	mem_writed(ret, k);
+	ret_value = G_OK;
+	break;
+    case _grLfbWriteColorFormat4:
+	// void grLfbWriteColorFormat(GrColorFormat_t colorFormat)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grLfbWriteColorSwizzle8:
+	// void grLfbWriteColorSwizzle(FxBool swizzleBytes, FxBool swapWords)
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i(param[1], param[2]);
+	break;
+    case _grLfbWriteRegion32:
+	// FxBool grLfbWriteRegion(GrBuffer_t dst_buffer, FxU32 dst_x, FxU32 dst_y,
+	//	GrLfbSrcFmt_t src_format, FxU32 src_width, FxU32 src_height, FxU32 src_stride, void *src_data)
+	FP.grRFunction7i1p = (prfunc7i1p)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+
+	MEM_BlockRead(param[8], ptr16, param[6]*param[7]);
+	mem_writed(ret, FP.grRFunction7i1p(param[1], param[2], param[3], param[4], param[5], param[6], param[7], ptr16));
+	ret_value = G_OK;
+	break;
+    case _grRenderBuffer4:
+	// void grRenderBuffer(GrBuffer_t buffer)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grResetTriStats0:
+	// void grResetTriStats()
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+    case _grSplash20:
+	// void grSplash(float x, float y, float width, float height, FxU32 frame)
+	FP.grFunction4f1i = (pfunc4f1i)fn_pt[i];
+	FP.grFunction4f1i(int_to_float(param[1]), int_to_float(param[2]), int_to_float(param[3]), int_to_float(param[4]), param[5]);
+	break;
+/*
+    case _grSstConfigPipeline12:
+	//
+	FP.grFunction3i = (pfunc3i)fn_pt[i];
+	FP.grFunction3i();
+	break;
+*/
+    case _grSstControl4:
+	// FxBool grSstControl(FxU32 code)
+	FP.grRFunction1i = (prfunc1i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction1i(param[1]));
+	ret_value = G_OK;
+	break;
+    case _grSstIdle0:
+	// void grSstIdle(void)
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+    case _grSstIsBusy0:
+	// FxBool grSstIsBusy(void)
+	FP.grRFunction0 = (prfunc0)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction0());
+	ret_value = G_OK;
+	break;
+    case _grSstOrigin4:
+	// void grSstOrigin(GrOriginLocation_t origin)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	GrOriginLocation = param[1];
+	break;
+    case _grSstPerfStats4:
+	// void grSstPerfStats(GrSstPerfStats_t *pStats)
+	FP.grFunction1p = (pfunc1p)fn_pt[i];
+	MEM_BlockRead32(param[1], texmem, sizeof(GrSstPerfStats_t));
+	FP.grFunction1p(texmem);
+	MEM_BlockWrite32(param[1], texmem, sizeof(GrSstPerfStats_t));
+	break;
+    case _grSstQueryBoards4:
+	// FxBool grSstQueryBoards(GrHwConfiguration *hwConfig)
+	FP.grRFunction1p = (prfunc1p)fn_pt[i];
+	MEM_BlockRead32(param[1], texmem, sizeof(GrHwConfiguration));
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction1p(texmem));
+	MEM_BlockWrite32(param[1], texmem, sizeof(GrHwConfiguration));
+	ret_value = G_OK;
+	break;
+    case _grSstQueryHardware4:
+	// FxBool grSstQueryHardware(GrHwConfiguration *hwConfig)
+	FP.grRFunction1p = (prfunc1p)fn_pt[i];
+	MEM_BlockRead32(param[1], texmem, sizeof(GrHwConfiguration));
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction1p(texmem));
+	MEM_BlockWrite32(param[1], texmem, sizeof(GrHwConfiguration));
+	ret_value = G_OK;
+	break;
+    case _grSstResetPerfStats0:
+	// void grSstResetPerfStats(void)
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+    case _grSstScreenHeight0:
+	// FxU32 grSstScreenHeight(void)
+	FP.grRFunction0 = (prfunc0)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction0());
+	ret_value = G_OK;
+	break;
+    case _grSstScreenWidth0:
+	// FxU32 grSstScreenWidth(void)
+	FP.grRFunction0 = (prfunc0)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction0());
+	ret_value = G_OK;
+	break;
+    case _grSstSelect4:
+	// void grSstSelect(int which_sst)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _grSstStatus0:
+	// FxU32 grSstStatus(void)
+	FP.grRFunction0 = (prfunc0)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction0());
+	ret_value = G_OK;
+	break;
+    case _grSstVRetraceOn0:
+	// FxBool grSstVRetraceOn(void)
+	FP.grRFunction0 = (prfunc0)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction0());
+	ret_value = G_OK;
+	break;
+/*
+    case _grSstVidMode8:
+	//
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i();
+	break;
+*/
+    case _grSstVideoLine0:
+	// FxU32 grSstVideoLine(void)
+	FP.grRFunction0 = (prfunc0)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction0());
+	ret_value = G_OK;
+	break;
+    case _grSstWinClose0:
+	// void grSstWinClose(void)
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	if(glide.enabled) {
+	    GLIDE_DisableScreen();
+	}
+	break;
+    case _grSstWinOpen28:
+	// FxBool grSstWinOpen(FxU32 hwnd, GrScreenResolution_t res, GrScreenRefresh_t ref,
+	//	GrColorFormat_t cformat, GrOriginLocation_t org_loc, int num_buffers, int num_aux_buffers)
+	FP.grRFunction1p6i = (prfunc1p6i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+
+	/* Tie Break Tennis */
+	if(glide.enabled) {
+	    LOG_MSG("Glide:grSstWinOpen called when glide is active!");
+	    mem_writed(ret, FXTRUE);
+	    ret_value = G_OK;
+	    break;
+	}
+
+	// Check for successful memory map
+	if(mem_readd(param[10]) == 0) {
+	    LOG_MSG("Glide:LFB memory map failed, using default LFB address!");
+	    // Write physical address instead, it can crash but it just might work
+	    mem_writed(param[10], glide.lfb_pagehandler->GetPhysPt());
+	}
+
+	glide.enabled = true;
+	glide.width = param[8];
+	glide.height = param[9];
+	GrOriginLocation = param[5];
+
+	// Resize window and disable updates
+	GLIDE_ResetScreen();
+
+	k = FP.grRFunction1p6i(hwnd, param[2], param[3], param[4], param[5], param[6], param[7]);
+	if(k == FXFALSE) {
+	    LOG_MSG("Glide:grSstWinOpen failed!");
+	    GLIDE_DisableScreen();
+	    mem_writed(ret, FXFALSE);
+	    ret_value = G_OK;
+	    break;
+	}
+
+	mem_writed(ret, k);
+	if(glide.splash) {
+	    grSplash();
+	    glide.splash = false;
+	}
+
+	glide.lfb_pagehandler->SetLinPt(mem_readd(param[10]));
+	LOG_MSG("Glide:Resolution:%dx%d, LFB at 0x%x (physical) / 0x%x (linear)",
+		glide.width, glide.height, glide.lfb_pagehandler->GetPhysPt(), glide.lfb_pagehandler->GetLinPt());
+	ret_value = G_OK;
+	break;
+    case _grTexCalcMemRequired16:
+	// FxU32 grTexCalcMemRequired(GrLOD_t smallLod, GrLOD_t largeLod, GrAspectRatio_t aspect,
+	//	    GrTextureFormat_t format)
+	FP.grRFunction4i = (prfunc4i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction4i(param[1], param[2], param[3], param[4]));
+	ret_value = G_OK;
+	break;
+    case _grTexClampMode12:
+	// void grTexClampMode(GrChipID_t tmu, GrTextureClampMode_t sClampMode, GrTextureClampMode_t tClampMode)
+	FP.grFunction3i = (pfunc3i)fn_pt[i];
+	FP.grFunction3i(param[1], param[2], param[3]);
+	break;
+    case _grTexCombine28:
+	// void grTexCombine(GrChipID_t tmu, GrCombineFunction_t rgb_function, GrCombineFactor_t rgb_factor,
+	//	GrCombineFunction_t alpha_function, GrCombineFactor_t alpha_factor, FxBool rgb_invert,
+	//	FxBool alpha_invert)
+	FP.grFunction7i = (pfunc7i)fn_pt[i];
+	FP.grFunction7i(param[1], param[2], param[3], param[4], param[5], param[6], param[7]);
+	break;
+    case _grTexCombineFunction8:
+	// void grTexCombineFunction(GrChipID_t tmu, GrTextureCombineFnc_t fnc)
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i(param[1], param[2]);
+	break;
+    case _grTexDetailControl16:
+	// void grTexDetailControl(GrChipID_t tmu, int lodBias, FxU8 detailScale, float detailMax)
+	FP.grFunction3i1f = (pfunc3i1f)fn_pt[i];
+	FP.grFunction3i1f(param[1], param[2], param[3], int_to_float(param[4]));
+	break;
+    case _grTexDownloadMipMap16:
+	// void grTexDownloadMipMap(GrChipID_t tmu, FxU32 startAddress, FxU32 evenOdd, GrTexInfo *info)
+	FP.grRFunction1i1p = (prfunc1i1p)fn_pt[_grTexTextureMemRequired8];
+	if(FP.grRFunction1i1p == NULL) {
+	    LOG_MSG("Glide:Unable to get pointer to grTexTextureMemRequired");
+	    return;
+	}
+
+	MEM_BlockRead32(param[4], &dbtexinfo, sizeof(DBGrTexInfo));
+
+	texinfo.smallLod = dbtexinfo.smallLod;
+	texinfo.largeLod = dbtexinfo.largeLod;
+	texinfo.aspectRatio = dbtexinfo.aspectRatio;
+	texinfo.format = dbtexinfo.format;
+	texinfo.data = NULL;
+
+	// grTexTextureMemRequired
+	texsize = FP.grRFunction1i1p(param[3], &texinfo);
+	MEM_BlockRead(dbtexinfo.data, texmem, texsize);
+	texinfo.data = texmem;
+
+	FP.grFunction3i1p = (pfunc3i1p)fn_pt[i];
+	FP.grFunction3i1p(param[1], param[2], param[3], &texinfo);
+	break;
+    case _grTexDownloadMipMapLevel32:
+	// void grTexDownloadMipMapLevel(GrChipID_t tmu, FxU32 startAddress, GrLOD_t thisLod,
+	//	GrLOD_t largeLod, GrAspectRatio_t aspectRatio, GrTextureFormat_t format,
+	//	FxU32 evenOdd, void *data)
+	FP.grRFunction1i1p = (prfunc1i1p)fn_pt[_grTexTextureMemRequired8];
+	if(FP.grRFunction1i1p == NULL) {
+	    LOG_MSG("Glide:Unable to get pointer to grTexTextureMemRequired");
+	    return;
+	}
+
+	texinfo.smallLod = param[3];
+	texinfo.largeLod = param[3];		// Calculate only thisLod
+	texinfo.aspectRatio = param[5];
+	texinfo.format = param[6];
+	texinfo.data = NULL;
+
+	// grTexTextureMemRequired
+	texsize = FP.grRFunction1i1p(param[7], &texinfo);
+	MEM_BlockRead(param[8], texmem, texsize);
+
+#if LOG_GLIDE
+	LOG_MSG("Read %d (%d %d %d %d %d) bytes of texture data", texsize, param[3], param[4], param[5], param[6], param[7]);
+#endif
+
+	FP.grFunction7i1p = (pfunc7i1p)fn_pt[i];
+	FP.grFunction7i1p(param[1], param[2], param[3], param[4], param[5], param[6], param[7], texmem);
+	break;
+    case _grTexDownloadMipMapLevelPartial40:
+	// FX_ENTRY void FX_CALL grTexDownloadMipMapLevelPartial(GrChipID_t tmu, FxU32 startAddress,
+	//    GrLOD_t thisLod, GrLOD_t largeLod, GrAspectRatio_t aspectRatio, GrTextureFormat_t format,
+	//    FxU32 evenOdd, void *data, int start, int end);
+	FP.grRFunction1i1p = (prfunc1i1p)fn_pt[_grTexTextureMemRequired8];
+	if(FP.grRFunction1i1p == NULL) {
+	    LOG_MSG("Glide:Unable to get pointer to grTexTextureMemRequired");
+	    return;
+	}
+
+	texinfo.smallLod = param[3];
+	texinfo.largeLod = param[3];		// Calculate only thisLod
+	texinfo.aspectRatio = param[5];
+	texinfo.format = param[6];
+	texinfo.data = NULL;
+
+	// grTexTextureMemRequired
+	texsize = FP.grRFunction1i1p(param[7], &texinfo);
+	MEM_BlockRead(param[8], texmem, texsize);
+
+	FP.grFunction7i1p2i = (pfunc7i1p2i)fn_pt[i];
+	FP.grFunction7i1p2i(param[1], param[2], param[3], param[4], param[5], param[6], param[7], texmem,
+			    param[9], param[10]);
+	break;
+    case _grTexDownloadTable12:
+	// void grTexDownloadTable(GrChipID_t tmu, GrTexTable_t type, void *data)
+	FP.grFunction2i1p = (pfunc2i1p)fn_pt[i];
+
+	if(param[2] == GR_TEXTABLE_PALETTE) {
+	    MEM_BlockRead32(param[3], texmem, sizeof(GuTexPalette));
+	} else { // GR_TEXTABLE_NCC0 or GR_TEXTABLE_NCC1
+	    MEM_BlockRead32(param[3], texmem, sizeof(GuNccTable));
+	}
+
+	FP.grFunction2i1p(param[1], param[2], texmem);
+	break;
+    case _grTexDownloadTablePartial20:
+	// void grTexDownloadTablePartial(GrChipID_t tmu, GrTexTable_t type, void *data, int start, int end)
+	FP.grFunction2i1p2i = (pfunc2i1p2i)fn_pt[i];
+
+	if(param[2] == GR_TEXTABLE_PALETTE) {
+	    MEM_BlockRead32(param[3], texmem, sizeof(GuTexPalette));
+	    FP.grFunction2i1p2i(param[1], param[2], texmem, param[4], param[5]);
+	} else { // GR_TEXTABLE_NCC0 or GR_TEXTABLE_NCC1
+	    LOG_MSG("Glide:Downloading partial NCC tables is not supported!");
+	}
+
+	break;
+    case _grTexFilterMode12:
+	// void grTexFilterMode(GrChipID_t tmu, GrTextureFilterMode_t minFilterMode,
+	//	GrTextureFilterMode_t magFilterMode)
+	FP.grFunction3i = (pfunc3i)fn_pt[i];
+	FP.grFunction3i(param[1], param[2], param[3]);
+	break;
+    case _grTexLodBiasValue8:
+	// void grTexLodBiasValue(GrChipID_t tmu, float bias)
+	FP.grFunction1i1f = (pfunc1i1f)fn_pt[i];
+	FP.grFunction1i1f(param[1], int_to_float(param[2]));
+	break;
+    case _grTexMaxAddress4:
+	// FxU32 grTexMaxAddress(GrChipID_t tmu)
+	FP.grRFunction1i = (prfunc1i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction1i(param[1]));
+	ret_value = G_OK;
+	break;
+    case _grTexMinAddress4:
+	// FxU32 grTexMinAddress(GrChipID_t tmu)
+	FP.grRFunction1i = (prfunc1i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction1i(param[1]));
+	ret_value = G_OK;
+	break;
+    case _grTexMipMapMode12:
+	// void grTexMipMapMode(GrChipID_t tmu, GrMipMapMode_t mode, FxBool lodBlend)
+	FP.grFunction3i = (pfunc3i)fn_pt[i];
+	FP.grFunction3i(param[1], param[2], param[3]);
+	break;
+    case _grTexMultibase8:
+	// void grTexMultibase(GrChipID_t tmu, FxBool enable)
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i(param[1], param[2]);
+	break;
+/*
+    case _grTexMultibaseAddress20:
+	// void grTexMultibaseAddress(GrChipID_t tmu, GrTexBaseRange_t range, FxU32 startAddress,
+	//	FxU32 evenOdd, GrTexInfo *info)
+	FP.grFunction4i1p = (pfunc4i1p)fn_pt[i];
+
+	// This is a bit more complicated since *info contains a pointer to data
+	texinfo = (GrTexInfo*)param[5];
+	data = (PhysPt)texinfo->data;			// Store for later reference
+	texinfo->data = VIRTOREAL(data);
+#if LOG_GLIDE
+	if(log_func[value-_grAADrawLine8] == 1) {
+	    LOG_MSG("Glide:Replacing pointer 0x%x with 0x%x in function %d", data, texinfo->data, _grTexMultibaseAddress20);
+	    log_func[value-_grAADrawLine8] = 2;
+	}
+#endif
+	FP.grFunction20(param[1], param[2], param[3], param[4], (int)texinfo);
+	texinfo->data = (void*)data;			// Change the pointer back
+	break;
+*/
+    case _grTexNCCTable8:
+	// void grTexNCCTable(GrChipID_t tmu, GrNCCTable_t table)
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i(param[1], param[2]);
+	break;
+    case _grTexSource16:
+	// void grTexSource(GrChipID_t tmu, FxU32 startAddress, FxU32 evenOdd, GrTexInfo *info)
+	FP.grFunction3i1p = (pfunc3i1p)fn_pt[i];
+
+	// Copy the data from DB struct
+	MEM_BlockRead32(param[4], &dbtexinfo, sizeof(DBGrTexInfo));
+
+	texinfo.smallLod = dbtexinfo.smallLod;
+	texinfo.largeLod = dbtexinfo.largeLod;
+	texinfo.aspectRatio = dbtexinfo.aspectRatio;
+	texinfo.format = dbtexinfo.format;
+	// Should the data pointer be filled ?
+
+	FP.grFunction3i1p(param[1], param[2], param[3], &texinfo);
+	break;
+    case _grTexTextureMemRequired8:
+	// FxU32 grTexTextureMemRequired(FxU32 evenOdd, GrTexInfo *info)
+	FP.grRFunction1i1p = (prfunc1i1p)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+
+	// Copy the data from DB struct
+	MEM_BlockRead32(param[2], &dbtexinfo, sizeof(DBGrTexInfo));
+
+	texinfo.smallLod = dbtexinfo.smallLod;
+	texinfo.largeLod = dbtexinfo.largeLod;
+	texinfo.aspectRatio = dbtexinfo.aspectRatio;
+	texinfo.format = dbtexinfo.format;
+
+	mem_writed(ret, FP.grRFunction1i1p(param[1], &texinfo));
+
+	ret_value = G_OK;
+	break;
+    case _grTriStats8:
+	// void grTriStats(FxU32 *trisProcessed, FxU32 *trisDrawn)
+	FP.grFunction2p = (pfunc2p)fn_pt[i];
+	MEM_BlockRead32(param[1], ilist, sizeof(FxU32));
+	MEM_BlockRead32(param[2], ilist + 1, sizeof(FxU32));
+	FP.grFunction2p(ilist, ilist + 1);
+	MEM_BlockWrite32(param[1], ilist, sizeof(FxU32));
+	MEM_BlockWrite32(param[2], ilist + 1, sizeof(FxU32));
+	break;
+    case _gu3dfGetInfo8:
+	// FxBool gu3dfGetInfo(const char *filename, Gu3dfInfo *info)
+	FP.grRFunction2p = (prfunc2p)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	MEM_StrCopy(param[1], filename, 512);
+
+#if LOG_GLIDE
+	LOG_MSG("Glide:Filename info found in gu3dfGetInfo: %s", filename);
+#endif
+	if(!GetFileName(filename)) break;
+	mem_writed(ret, FP.grRFunction2p(filename, &guinfo));
+
+	// Copy the data back to DB struct if successful
+	if(mem_readd(ret)) {
+	    MEM_BlockRead32(param[2], &dbguinfo, sizeof(DBGu3dfInfo));
+	    dbguinfo.header.width = (Bit32u)guinfo.header.width;
+	    dbguinfo.header.height = (Bit32u)guinfo.header.height;
+	    dbguinfo.header.small_lod = (Bit32s)guinfo.header.small_lod;
+	    dbguinfo.header.large_lod = (Bit32s)guinfo.header.large_lod;
+	    dbguinfo.header.aspect_ratio = (Bit32s)guinfo.header.aspect_ratio;
+	    dbguinfo.header.format = (Bit32s)guinfo.header.format;
+	    dbguinfo.mem_required = (Bit32u)guinfo.mem_required;
+	    MEM_BlockWrite32(param[2], &dbguinfo, sizeof(DBGu3dfInfo));
+	}
+
+	ret_value = G_OK;
+	break;
+    case _gu3dfLoad8:
+	// FxBool gu3dfLoad(const char *filename, Gu3dfInfo *info)
+	FP.grRFunction2p = (prfunc2p)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+
+	// Although glide ref specifies *info should be filled by gu3dfGetInfo before calling gu3dfLoad,
+	// OpenGlide will re-read the header in gu3dfLoad as well
+	MEM_BlockRead32(param[2], &dbguinfo, sizeof(DBGu3dfInfo));
+	MEM_StrCopy(param[1], filename, 512);
+
+#if LOG_GLIDE
+	LOG_MSG("Glide:Filename info found in gu3dfLoad: %s", filename);
+#endif
+	if(!GetFileName(filename)) break;
+
+	guinfo.data = texmem;
+	mem_writed(ret, FP.grRFunction2p(filename, &guinfo));
+
+	// Copy the data back to DB struct if successful
+	if(mem_readd(ret)) {
+	    for(j=0;j<256;j++)
+		dbguinfo.table.palette.data[j] = (Bit32u)guinfo.table.palette.data[j];
+	    MEM_BlockWrite(dbguinfo.data, guinfo.data, guinfo.mem_required);
+	}
+
+	MEM_BlockWrite32(param[2], &dbguinfo, sizeof(DBGu3dfInfo));
+	ret_value = G_OK;
+	break;
+    case _guAADrawTriangleWithClip12:
+	// void guAADrawTriangleWithClip(const GrVertex *va, const GrVertex *vb, const GrVertex *vc)
+	FP.grFunction3p = (pfunc3p)fn_pt[i];
+	MEM_BlockRead32(param[1], &vertex[0], sizeof(GrVertex));
+	MEM_BlockRead32(param[2], &vertex[1], sizeof(GrVertex));
+	MEM_BlockRead32(param[3], &vertex[2], sizeof(GrVertex));
+	FP.grFunction3p(&vertex[0], &vertex[1], &vertex[2]);
+	break;
+    case _guAlphaSource4:
+	// void guAlphaSource(GrAlphaSourceMode_t mode)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _guColorCombineFunction4:
+	// void guColorCombineFunction(GrColorCombineFunction_t func)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _guDrawPolygonVertexListWithClip8:
+	// void guDrawPolygonVertexListWithClip(int nverts, const GrVertex vlist[])
+	FP.grFunction1i1p = (pfunc1i1p)fn_pt[i];
+	MEM_BlockRead32(param[2], texmem, sizeof(GrVertex)*param[1]);
+	FP.grFunction1i1p(param[1], texmem);
+	break;
+    case _guDrawTriangleWithClip12:
+	// void guDrawTriangleWithClip(const GrVertex *va, const GrVertex *vb, const GrVertex *vc)
+	FP.grFunction3p = (pfunc3p)fn_pt[i];
+	MEM_BlockRead32(param[1], &vertex[0], sizeof(GrVertex));
+	MEM_BlockRead32(param[2], &vertex[1], sizeof(GrVertex));
+	MEM_BlockRead32(param[3], &vertex[2], sizeof(GrVertex));
+	FP.grFunction3p(&vertex[0], &vertex[1], &vertex[2]);
+	break;
+/*
+    case _guEncodeRLE1616:
+	//
+	FP.grFunction16 = (pfunc16)fn_pt[i];
+	FP.grFunction16();
+	break;
+    case _guEndianSwapBytes4:
+	//
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i();
+	break;
+    case _guEndianSwapWords4:
+	//
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i();
+	break;
+*/
+    case _guFogGenerateExp28:
+	// void guFogGenerateExp2(GrFog_t fogTable[GR_FOG_TABLE_SIZE], float density)
+	FP.grFunction1p1f = (pfunc1p1f)fn_pt[i];
+	FP.grFunction1p1f(texmem, int_to_float(param[2]));
+	MEM_BlockWrite32(param[1], texmem, GR_FOG_TABLE_SIZE*sizeof(GrFog_t));
+	break;
+    case _guFogGenerateExp8:
+	// void guFogGenerateExp(GrFog_t fogTable[GR_FOG_TABLE_SIZE], float density)
+	FP.grFunction1p1f = (pfunc1p1f)fn_pt[i];
+	FP.grFunction1p1f(texmem, int_to_float(param[2]));
+	MEM_BlockWrite32(param[1], texmem, GR_FOG_TABLE_SIZE*sizeof(GrFog_t));
+	break;
+    case _guFogGenerateLinear12:
+	// void guFogGenerateLinear(GrFog_t fogTable[GR_FOG_TABLE_SIZE], float nearW, float farW)
+	FP.grFunction1p2f = (pfunc1p2f)fn_pt[i];
+	FP.grFunction1p2f(texmem, int_to_float(param[2]), int_to_float(param[3]));
+	MEM_BlockWrite32(param[1], texmem, GR_FOG_TABLE_SIZE*sizeof(GrFog_t));
+	break;
+    case _guFogTableIndexToW4: {
+	// float guFogTableIndexToW(int i)
+	FP.grFFunction1i = (pffunc1i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	float tmp = FP.grFFunction1i(param[1]);
+	SDL_memcpy(&j, &tmp, 4);
+	mem_writed(ret, SDL_SwapLE32(j));
+	ret_value = G_OK;
+	break;
+    }
+/*
+    case _guMPDrawTriangle12:
+	//
+	FP.grFunction3i = (pfunc3i)fn_pt[i];
+	FP.grFunction3i();
+	break;
+    case _guMPInit0:
+	//
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+    case _guMPTexCombineFunction4:
+	//
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i();
+	break;
+    case _guMPTexSource8:
+	//
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i();
+	break;
+    case _guMovieSetName4:
+	//
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i();
+	break;
+    case _guMovieStart0:
+	//
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+    case _guMovieStop0:
+	//
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+*/
+    case _guTexAllocateMemory60:
+	// GrMipMapId_t guTexAllocateMemory(GrChipID_t tmu, FxU8 evenOddMask, int width, int height,
+	//	    GrTextureFormat_t format, GrMipMapMode_t mmMode, GrLOD_t smallLod, GrLOD_t largeLod,
+	//	    GrAspectRatio_t aspectRatio, GrTextureClampMode_t sClampMode,
+	//	    GrTextureClampMode_t tClampMode, GrTextureFilterMode_t minFilterMode,
+	//	    GrTextureFilterMode_t magFilterMode, float lodBias, FxBool lodBlend)
+	FP.grRFunction13i1f1i = (prfunc13i1f1i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction13i1f1i(param[1], param[2], param[3], param[4], param[5], param[6], param[7],
+			    param[8], param[9], param[10], param[11], param[12], param[13], int_to_float(param[14]), param[15]));
+	ret_value = G_OK;
+	break;
+    case _guTexChangeAttributes48:
+	// FxBool guTexChangeAttributes(GrMipMapID_t mmid, int width, int height, GrTextureFormat_t format,
+	//	    GrMipMapMode_t mmMode, GrLOD_t smallLod, GrLOD_t largeLod, GrAspectRatio_t aspectRatio,
+	//	    GrTextureClampMode_t sClampMode, GrTextureClampMode_t tClampMode,
+	//	    GrTextureFilterMode_t minFilterMode, GrTextureFilterMode_t magFilterMode)
+	FP.grRFunction12i = (prfunc12i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction12i(param[1], param[2], param[3], param[4], param[5], param[6], param[7],
+			param[8], param[9], param[10], param[11], param[12]));
+	ret_value = G_OK;
+	break;
+    case _guTexCombineFunction8:
+	// void guTexCombineFunction(GrChipID_t tmu, GrTextureCombineFnc_t func)
+	FP.grFunction2i = (pfunc2i)fn_pt[i];
+	FP.grFunction2i(param[1], param[2]);
+	break;
+/*
+    case _guTexCreateColorMipMap0:
+	//
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+*/
+    case _guTexDownloadMipMap12:
+	// void guTexDownloadMipMap(GrMipMapId_t mmid, const void *src, const GuNccTable *nccTable)
+	FP.grRPTFunction1i = (prptfunc1i)fn_pt[_guTexGetMipMapInfo4];
+	mipmap = (GrMipMapInfo*)FP.grRPTFunction1i(param[1]);
+
+	if(mipmap) {
+	    texinfo.aspectRatio = mipmap->aspect_ratio;
+	    texinfo.format      = mipmap->format;
+	    texinfo.largeLod    = mipmap->lod_max;
+	    texinfo.smallLod    = mipmap->lod_min;
+
+	    FP.grRFunction1i1p = (prfunc1i1p)fn_pt[_grTexTextureMemRequired8];
+	    if(FP.grRFunction1i1p == NULL) {
+		LOG_MSG("Glide:Unable to get pointer to grTexTextureMemRequired");
+		return;
+	    }
+
+	    texsize = FP.grRFunction1i1p(mipmap->odd_even_mask, &texinfo);
+
+	    MEM_BlockRead(param[2], texmem, texsize);
+	    MEM_BlockRead32(param[3], (Bit8u*)texmem+texsize, sizeof(GuNccTable));
+
+	    FP.grFunction1i2p = (pfunc1i2p)fn_pt[i];
+	    FP.grFunction1i2p(param[1], texmem, (Bit8u*)texmem+texsize);
+	} else {
+	    LOG_MSG("Glide:Unable to get GrMipMapInfo pointer");
+	}
+	break;
+/*
+    case _guTexDownloadMipMapLevel12:
+	// void guTexDownloadMipMapLevel(GrMipMapId_t mmid, GrLOD_t lod, const void **src)
+	FP.grFunction2i1p = (pfunc2i1p)fn_pt[i];
+	FP.grFunction2i1p(param[1], param[2], *param[3]);
+	break;
+*/
+    case _guTexGetCurrentMipMap4:
+	// GrMipMapId_t guTexGetCurrentMipMap(GrChipID_t tmu)
+	FP.grRFunction1i = (prfunc1i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction1i(param[1]));
+	ret_value = G_OK;
+	break;
+/*
+    case _guTexGetMipMapInfo4:
+	// GrMipMapInfo *guTexGetMipMapInfo(GrMipMapId_t mmid)
+	FP.grRFunction4 = (prfunc4)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction4(param[1]));
+	ret_value = G_OK;
+	break;
+*/
+    case _guTexMemQueryAvail4:
+	// FxU32 guTexMemQueryAvail(GrChipID_t tmu)
+	FP.grRFunction1i = (prfunc1i)fn_pt[i];
+	if(ret == 0) {
+	    LOG_MSG("Glide:Invalid return value handle for %s!", grTable[i].name);
+	    return;
+	}
+	mem_writed(ret, FP.grRFunction1i(param[1]));
+	ret_value = G_OK;
+	break;
+    case _guTexMemReset0:
+	// void guTexMemReset(void)
+	FP.grFunction0 = (pfunc0)fn_pt[i];
+	FP.grFunction0();
+	break;
+    case _guTexSource4:
+	// void guTexSource(GrMipMapId_t mmid)
+	FP.grFunction1i = (pfunc1i)fn_pt[i];
+	FP.grFunction1i(param[1]);
+	break;
+    case _ConvertAndDownloadRle64: {
+	// void ConvertAndDownloadRle(GrChipID_t tmu, FxU32 startAddress, GrLOD_t thisLod, GrLOD_t largeLod,
+	//				GrAspectRatio_t aspectRatio, GrTextureFormat_t format, FxU32 evenOdd,
+	//				FxU8 *bm_data, long  bm_h, FxU32 u0, FxU32 v0, FxU32 width, FxU32 height,
+	//				FxU32 dest_width, FxU32 dest_height, FxU16 *tlut)
+
+#if LOG_GLIDE
+	LOG_MSG("Glide: RLE width: %d, height: %d, bm_h: %d, u0: %d, v0: %d, dest %dx%d",
+	    param[12], param[13], param[9], param[10], param[11], param[14], param[15]);
+#endif
+
+	FxU8 c;
+	FxU32 scount = 0;
+	FxU32 dcount = 0;
+
+	FxU16 * src = ptr16 + param[14]*param[15];
+	FxU32 offset = 4 + param[9];
+
+	// Line offset (v0)
+	for(j = 0; j < param[11]; j++ ) {
+	    offset += mem_readb(param[8]+4+j);
+	}
+
+	// Write height lines
+	for(k = 0; k < param[13]; k++) {
+
+	    // Decode one RLE line
+	    scount = offset;
+	    while((c = mem_readb(param[8]+scount)) != 0xE0) {
+
+		if(c > 0xE0) {
+		    for(int count = 0; count < (c&0x1f); count++) {
+
+			// tlut is FxU16*
+			src[dcount] = mem_readw(param[16]+(mem_readb(param[8]+scount+1)<<1));
+			dcount++;
+		    }
+		    scount += 2;
+
+		} else {
+		    src[dcount] = mem_readw(param[16]+(c<<1));
+		    dcount++; scount++;
+		}
+	    }
+
+	    // Copy Line into destination texture, offset u0
+	    SDL_memcpy(ptr16 + (k*param[14]), src + param[10], sizeof(FxU16)*param[14]);
+	    offset += mem_readb(param[8] + 4 + j++);
+	    dcount = 0;
+	}
+
+	// One additional line
+	if(param[13] < param[15])
+	    SDL_memcpy(ptr16 + (k*param[14]), src + param[10], sizeof(FxU16)*param[14]);
+
+	// Download decoded texture
+	texinfo.smallLod = param[3];
+	texinfo.largeLod = param[4];
+	texinfo.aspectRatio = param[5];
+	texinfo.format = param[6];
+	texinfo.data = ptr16;
+
+	// void grTexDownloadMipMap(GrChipID_t tmu, FxU32 startAddress, FxU32 evenOdd, GrTexInfo *info)
+	FP.grFunction3i1p = (pfunc3i1p)fn_pt[_grTexDownloadMipMap16];
+	if(FP.grFunction3i1p == NULL) {
+	    LOG_MSG("Glide:Unable to get pointer to grTexDownloadMipMap");
+	    break;
+	}
+	FP.grFunction3i1p(param[1], param[2], param[7], &texinfo);
+	break;
+    }
+    default:
+	LOG_MSG("Glide:Unsupported glide call %s", grTable[i].name);
+	break;
+
+    }	/* switch */
+}	/* process_msg() */
diff -Naur dosbox.orig/src/hardware/Makefile.am dosbox/src/hardware/Makefile.am
--- dosbox.orig/src/hardware/Makefile.am	2021-02-14 12:26:31.015654162 +0000
+++ dosbox/src/hardware/Makefile.am	2021-02-14 12:28:58.494761426 +0000
@@ -10,6 +10,6 @@
                         memory.cpp mixer.cpp pcspeaker.cpp pci_bus.cpp pic.cpp sblaster.cpp tandy_sound.cpp timer.cpp \
 			vga.cpp vga_attr.cpp vga_crtc.cpp vga_dac.cpp vga_draw.cpp vga_gfx.cpp vga_other.cpp \
 			vga_memory.cpp vga_misc.cpp vga_seq.cpp vga_xga.cpp vga_s3.cpp vga_tseng.cpp vga_paradise.cpp \
-			cmos.cpp disney.cpp gus.cpp mpu401.cpp ipx.cpp ipxserver.cpp dbopl.cpp
+			cmos.cpp disney.cpp gus.cpp mpu401.cpp ipx.cpp ipxserver.cpp dbopl.cpp glide.cpp
 
 
diff -Naur dosbox.orig/src/hardware/memory.cpp dosbox/src/hardware/memory.cpp
--- dosbox.orig/src/hardware/memory.cpp	2021-02-14 12:26:31.016654190 +0000
+++ dosbox/src/hardware/memory.cpp	2021-02-14 12:28:58.495761454 +0000
@@ -23,6 +23,7 @@
 #include "setup.h"
 #include "paging.h"
 #include "regs.h"
+#include "glidedef.h"
 
 #include <string.h>
 
@@ -140,6 +141,8 @@
 	} else if ((phys_page>=memory.lfb.start_page+0x01000000/4096) &&
 				(phys_page<memory.lfb.start_page+0x01000000/4096+16)) {
 		return memory.lfb.mmiohandler;
+	} else if (glide.enabled && (phys_page>=(GLIDE_LFB>>12)) && (phys_page<(GLIDE_LFB>>12)+GLIDE_PAGES)) {
+		return (PageHandler*)glide.lfb_pagehandler;
 	}
 	return &illegal_page_handler;
 }
@@ -191,6 +194,24 @@
 	}
 }
 
+void MEM_BlockRead32(PhysPt pt,void * data,Bitu size) {
+	Bit32u * write=(Bit32u *) data;
+	size>>=2;
+	while (size--) {
+		*write++=mem_readd_inline(pt);
+		pt+=4;
+	}
+}
+
+void MEM_BlockWrite32(PhysPt pt,void * data,Bitu size) {
+	Bit32u * read=(Bit32u *) data;
+	size>>=2;
+	while (size--) {
+		mem_writed_inline(pt,*read++);
+		pt+=4;
+	}
+}
+
 void MEM_BlockCopy(PhysPt dest,PhysPt src,Bitu size) {
 	mem_memcpy(dest,src,size);
 }
