#include "quakedef.h"

#include "ui_public.h"
#include "cl_master.h"

#ifdef VM_UI

#ifdef RGLQUAKE
#include "glquake.h"//hack
#else
typedef float m3by3_t[3][3];
#endif

static vm_t *uivm;

static char *scr_centerstring;

static int ox, oy;

static int keycatcher;

void GLDraw_Image(float x, float y, float w, float h, float s1, float t1, float s2, float t2, qpic_t *pic);
void SWDraw_Image (float xp, float yp, float wp, float hp, float s1, float t1, float s2, float t2, qpic_t *pic);
char *Get_Q2ConfigString(int i);
void SWDraw_ImageColours (float r, float g, float b, float a);


#define MAX_PINGREQUESTS 16

netadr_t ui_pings[MAX_PINGREQUESTS];



#define MAX_VMUI_FILES 8

typedef struct {
	char name[256];
	char *data;
	int bufferlen;
	int len;
	int ofs;
	int accessmode;
	int owner;
} vmui_fopen_files_t;
vmui_fopen_files_t vmui_fopen_files[MAX_VMUI_FILES];

int VMUI_fopen (char *name, int *handle, int fmode, int owner)
{
	int i;

	*handle = 0;

	for (i = 0; i < MAX_VMUI_FILES; i++)
		if (!vmui_fopen_files[i].data)
			break;

	if (i == MAX_VMUI_FILES)	//too many already open
	{
		return -1;
	}

	if (name[1] == ':' ||	//dos filename absolute path specified - reject.
		*name == '\\' || *name == '/' ||	//absolute path was given - reject
		strstr(name, ".."))	//someone tried to be cleaver.
	{
		return -1;
	}

	Q_strncpyz(vmui_fopen_files[i].name, name, sizeof(vmui_fopen_files[i].name));

	vmui_fopen_files[i].accessmode = fmode;
	vmui_fopen_files[i].owner = owner;
	switch (fmode)
	{
	case 0:	//read
		vmui_fopen_files[i].data = COM_LoadMallocFile(name);
		vmui_fopen_files[i].bufferlen = vmui_fopen_files[i].len = com_filesize;
		vmui_fopen_files[i].ofs = 0;
		if (vmui_fopen_files[i].data)
			break;
		else
			return -1;
		break;
		/*
	case 2:	//append
	case 3:	//append
		vmui_fopen_files[i].data = COM_LoadMallocFile(name);
		vmui_fopen_files[i].ofs = vmui_fopen_files[i].bufferlen = vmui_fopen_files[i].len = com_filesize;
		if (vmui_fopen_files[i].data)
			break;
		//fall through
	case 1:	//write
		vmui_fopen_files[i].bufferlen = 8192;
		vmui_fopen_files[i].data = BZ_Malloc(vmui_fopen_files[i].bufferlen);
		vmui_fopen_files[i].len = 0;
		vmui_fopen_files[i].ofs = 0;
		break;
		*/
	default: //bad
		return -1;
	}

	*handle = i+1;
	return vmui_fopen_files[i].len;
}

void VMUI_fclose (int fnum, int owner)
{
	fnum--;

	if (fnum < 0 || fnum >= MAX_VMUI_FILES)
		return;	//out of range

	if (vmui_fopen_files[fnum].owner != owner)
		return;	//cgs?

	if (!vmui_fopen_files[fnum].data)
		return;	//not open

	switch(vmui_fopen_files[fnum].accessmode)
	{
	case 0:
		BZ_Free(vmui_fopen_files[fnum].data);
		break;
	case 1:
	case 2:
	case 3:
		COM_WriteFile(vmui_fopen_files[fnum].name, vmui_fopen_files[fnum].data, vmui_fopen_files[fnum].len);
		BZ_Free(vmui_fopen_files[fnum].data);
		break;
	}
	vmui_fopen_files[fnum].data = NULL;
}

void VMUI_FRead (char *dest, int quantity, int fnum, int owner)
{
	fnum--;
	if (fnum < 0 || fnum >= MAX_VMUI_FILES)
		return;	//out of range

	if (vmui_fopen_files[fnum].owner != owner)
		return;	//cgs?

	if (!vmui_fopen_files[fnum].data)
		return;	//not open

	if (quantity > vmui_fopen_files[fnum].len - vmui_fopen_files[fnum].ofs)
		quantity = vmui_fopen_files[fnum].len - vmui_fopen_files[fnum].ofs;
	memcpy(dest, vmui_fopen_files[fnum].data + vmui_fopen_files[fnum].ofs, quantity);
	vmui_fopen_files[fnum].ofs += quantity;

	
}
/*
void VMUI_fputs (progfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int fnum = G_FLOAT(OFS_PARM0);
	char *msg = PF_VarString(prinst, 1, pr_globals);
	int len = strlen(msg);
	if (fnum < 0 || fnum >= MAX_QC_FILES)
		return;	//out of range

	if (!pf_fopen_files[fnum].data)
		return;	//not open

	if (pf_fopen_files[fnum].prinst != prinst)
		return;	//this just isn't ours.

	if (pf_fopen_files[fnum].bufferlen < pf_fopen_files[fnum].ofs + len)
	{
		char *newbuf;
		pf_fopen_files[fnum].bufferlen = pf_fopen_files[fnum].bufferlen*2 + len;
		newbuf = BZF_Malloc(pf_fopen_files[fnum].bufferlen);
		memcpy(newbuf, pf_fopen_files[fnum].data, pf_fopen_files[fnum].len);
		BZ_Free(pf_fopen_files[fnum].data);
		pf_fopen_files[fnum].data = newbuf;
	}

	memcpy(pf_fopen_files[fnum].data + pf_fopen_files[fnum].ofs, msg, len);
	if (pf_fopen_files[fnum].len < pf_fopen_files[fnum].ofs + len)
		pf_fopen_files[fnum].len = pf_fopen_files[fnum].ofs + len;
	pf_fopen_files[fnum].ofs+=len;
}
*/
void VMUI_fcloseall (int owner)
{
	int i;
	for (i = 1; i <= MAX_VMUI_FILES; i++)
	{
		VMUI_fclose(i, owner);
	}
}



typedef struct {
	char *initialbuffer;
	char *buffer;
	int found;
	int bufferleft;
	int skip;
} vmsearch_t;
int VMEnum(char *match, int size, void *args)
{
	char *check;
	int newlen;
	match += ((vmsearch_t *)args)->skip;
	newlen = strlen(match)+1;
	if (newlen > ((vmsearch_t *)args)->bufferleft)
		return false;	//too many files for the buffer

	check = ((vmsearch_t *)args)->initialbuffer;
	while(check < ((vmsearch_t *)args)->buffer)
	{
		if (!stricmp(check, match))
			return true;	//we found this one already
		check += strlen(check)+1;
	}

	memcpy(((vmsearch_t *)args)->buffer, match, newlen);
	((vmsearch_t *)args)->buffer+=newlen;
	((vmsearch_t *)args)->bufferleft-=newlen;
	((vmsearch_t *)args)->found++;
	return true;
}

int VMEnumMods(char *match, int size, void *args)
{
	char *check;
	char desc[1024];
	int newlen;
	int desclen;

	newlen = strlen(match)+1;

	if (*match && match[newlen-2] != '/')
		return true;
	match[newlen-2] = '\0';

	Q_strncpyz(desc, match, sizeof(desc));

	desclen = strlen(desc)+1;

	if (newlen+desclen > ((vmsearch_t *)args)->bufferleft)
		return false;	//too many files for the buffer

	check = ((vmsearch_t *)args)->initialbuffer;
	while(check < ((vmsearch_t *)args)->buffer)
	{
		if (!stricmp(check, match))
			return true;	//we found this one already
		check += strlen(check)+1;
		check += strlen(check)+1;
	}

	memcpy(((vmsearch_t *)args)->buffer, match, newlen);
	((vmsearch_t *)args)->buffer+=newlen;
	((vmsearch_t *)args)->bufferleft-=newlen;

	memcpy(((vmsearch_t *)args)->buffer, desc, desclen);
	((vmsearch_t *)args)->buffer+=desclen;
	((vmsearch_t *)args)->bufferleft-=desclen;

	((vmsearch_t *)args)->found++;
	return true;
}





typedef enum {
	RT_MODEL,
	RT_POLY,
	RT_SPRITE,
	RT_BEAM,
	RT_RAIL_CORE,
	RT_RAIL_RINGS,
	RT_LIGHTNING,
	RT_PORTALSURFACE,		// doesn't draw anything, just info for portals

	RT_MAX_REF_ENTITY_TYPE
} q3refEntityType_t;

typedef struct q3refEntity_s {
	q3refEntityType_t	reType;
	int			renderfx;

	struct model_s	*hModel;				// opaque type outside refresh

	// most recent data
	vec3_t		lightingOrigin;		// so multi-part models can be lit identically (RF_LIGHTING_ORIGIN)
	float		shadowPlane;		// projection shadows go here, stencils go slightly lower

	vec3_t		axis[3];			// rotation vectors
	qboolean	nonNormalizedAxes;	// axis are not normalized, i.e. they have scale
	float		origin[3];			// also used as MODEL_BEAM's "from"
	int			frame;				// also used as MODEL_BEAM's diameter

	// previous data for frame interpolation
	float		oldorigin[3];		// also used as MODEL_BEAM's "to"
	int			oldframe;
	float		backlerp;			// 0.0 = current, 1.0 = old

	// texturing
	int			skinNum;			// inline skin index
	int		customSkin;			// NULL for default skin
	int		customShader;		// use one image for the entire thing

	// misc
	qbyte		shaderRGBA[4];		// colors used by rgbgen entity shaders
	float		shaderTexCoord[2];	// texture coordinates used by tcMod entity modifiers
	float		shaderTime;			// subtracted from refdef time to control effect start times

	// extra sprite information
	float		radius;
	float		rotation;
} q3refEntity_t;

void VQ3_AddEntity(const q3refEntity_t *q3)
{
	entity_t ent;
	if (!cl_visedicts)
		cl_visedicts = cl_visedicts_list[0];
	memset(&ent, 0, sizeof(ent));
	ent.model = q3->hModel;
	ent.frame = q3->frame;
	ent.oldframe = q3->oldframe;
	ent.angles[0] = (atan2(q3->axis[0][2], sqrt(q3->axis[0][1]*q3->axis[0][1]+q3->axis[0][0]*q3->axis[0][0])) * 180 / M_PI);
	ent.angles[1] = (atan2(q3->axis[0][1], q3->axis[0][0]) * 180 / M_PI);
	ent.angles[2] = 0;//(atan2(q3->axis[2][1], q3->axis[2][0]) * 180 / M_PI);;
	ent.lerpfrac = ent.lerptime = q3->backlerp;
	ent.alpha = 1;
	ent.scale = 1;
	VectorCopy(q3->origin, ent.origin);
	V_AddEntity(&ent);
}


#define	MAX_RENDER_STRINGS			8
#define	MAX_RENDER_STRING_LENGTH	32
#define MAX_MAP_AREA_BYTES 16

typedef struct q3refdef_s {
	int			x, y, width, height;
	float		fov_x, fov_y;
	vec3_t		vieworg;
	vec3_t		viewaxis[3];		// transformation matrix

	// time in milliseconds for shader effects and other time dependent rendering issues
	int			time;

	int			rdflags;			// RDF_NOWORLDMODEL, etc

	// 1 bits will prevent the associated area from rendering at all
	qbyte		areamask[MAX_MAP_AREA_BYTES];

	// text messages for deform text shaders
	char		text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH];
} q3refdef_t;
void VQ3_RenderView(const q3refdef_t *ref)
{
	VectorCopy(ref->vieworg, r_refdef.vieworg);
	r_refdef.viewangles[0] = (atan2(ref->viewaxis[0][2], sqrt(ref->viewaxis[0][1]*ref->viewaxis[0][1]+ref->viewaxis[0][0]*ref->viewaxis[0][0])) * 180 / M_PI);
	r_refdef.viewangles[1] = (atan2(ref->viewaxis[0][1], ref->viewaxis[0][0]) * 180 / M_PI);
	r_refdef.viewangles[2] = 0;
	r_refdef.flags = ref->rdflags;
	r_refdef.fov_x = ref->fov_x;
	r_refdef.fov_y = ref->fov_y;
	r_refdef.vrect.x = ref->x;
	r_refdef.vrect.y = ref->y;
	r_refdef.vrect.width = ref->width;
	r_refdef.vrect.height = ref->height;
#ifdef RGLQUAKE
	if (qrenderer == QR_OPENGL)
	{
		glDisable(GL_ALPHA_TEST);
		glDisable(GL_BLEND);
	}
#endif
	R_RenderView();
#ifdef RGLQUAKE
	if (qrenderer == QR_OPENGL)
	{
		GL_Set2D ();
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	}
#endif

	vid.recalc_refdef = 1;
}




typedef struct {
	int			handle;
	int			modificationCount;
	float		value;
	int			integer;
	char		string[256];
} vmcvar_t;

#ifndef _DEBUG
static
#endif
long UI_SystemCallsEx(void *offset, unsigned int mask, int fn, const long *arg)
{
	int ret=0;

	//Remember to range check pointers.
	//The QVM must not be allowed to write to anything outside it's memory.
	//This includes getting the exe to copy it for it.

	//don't bother with reading, as this isn't a virus risk.
	//could be a cheat risk, but hey.

	//make sure that any called functions are also range checked.
	//like reading from files copies names into alternate buffers, allowing stack screwups.

	switch((ui_builtinnum_t)fn)
	{
	case UI_SYSERROR:
		Sys_Error("%s", VM_POINTER(arg[0]));
		break;
	case UI_PRINT:
		Con_Printf("%s", VM_POINTER(arg[0]));
		break;

	case UI_CVAR_SET:
		{
			cvar_t *var;
			var = Cvar_FindVar(VM_POINTER(arg[0]));
			if (var)
				Cvar_Set(var, VM_POINTER(arg[1]));	//set it
			else
				Cvar_Get(VM_POINTER(arg[0]), VM_POINTER(arg[1]), 0, "UI created");	//create one
		}
		break;
	case UI_CVAR_GET_VALUE:
		{
			cvar_t *var;
			var = Cvar_FindVar(VM_POINTER(arg[0]));
			if (var)
				VM_FLOAT(ret) = var->value;
			else
				VM_FLOAT(ret) = 0;
		}
		break;
	case UI_CVAR_GET_STRING:
		{
			cvar_t *var;
			var = Cvar_FindVar(VM_POINTER(arg[0]));
			if (!VM_LONG(arg[2]))
				VM_LONG(ret) = 0;
			else if (!var)
			{
				*(char *)VM_POINTER(arg[1]) = '\0';
				VM_LONG(ret) = -1;
			}
			else
			{
				if (arg[1] + arg[2] >= mask || VM_POINTER(arg[1]) < offset)
					VM_LONG(ret) = -2;	//out of bounds.
				else
					Q_strncpyz(VM_POINTER(arg[1]), var->string, VM_LONG(arg[2]));
			}
		}
		break;

	case UI_CVAR_SET_VALUE:
		Cvar_SetValue(VM_POINTER(arg[0]), VM_FLOAT(arg[1]));
		break;

	case UI_CVAR_RESET:	//cvar reset
		break;

	case UI_CBUF_ADD_COMMAND:
		if (!strncmp(VM_POINTER(arg[1]), "ping ", 5))
		{
			int i;
			for (i = 0; i < MAX_PINGREQUESTS; i++)
				if (ui_pings[i].type == NA_INVALID)
				{
					serverinfo_t *info;
					NET_StringToAdr((char *)VM_POINTER(arg[1]) + 5, &ui_pings[i]);
					info = Master_InfoForServer(ui_pings[i]);
					if (info)
					{
						info->special |= SS_KEEPINFO;
						Master_QueryServer(info);
					}
					break;
				}
		}
		else if (!strncmp(VM_POINTER(arg[1]), "localservers", 12))
		{
			MasterInfo_Begin();
		}
/*		else if (!strncmp(VM_POINTER(arg[1]), "r_vidmode", 12))
		{
			MasterInfo_Begin();
		}
*/		else
			Cbuf_AddText(VM_POINTER(arg[1]), RESTRICT_SERVER);
		break;

	case UI_FS_OPEN: //fopen
		if ((int)arg[1] + 4 >= mask || VM_POINTER(arg[1]) < offset)
			break;	//out of bounds.
		VM_LONG(ret) = VMUI_fopen(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_LONG(arg[2]), 0);
		break;

	case UI_FS_READ:	//fread
		if ((int)arg[0] + VM_LONG(arg[1]) >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.

		VMUI_FRead(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), 0);
		break;
	case UI_FS_WRITE:	//fwrite
		break;
	case UI_FS_CLOSE:	//fclose
		VMUI_fclose(VM_LONG(arg[0]), 0);
		break;

	case UI_FS_LISTFILES:	//fs listing
		if ((int)arg[2] + arg[3] >= mask || VM_POINTER(arg[2]) < offset)
			break;	//out of bounds.
		{
			vmsearch_t vms;
			vms.initialbuffer = vms.buffer = VM_POINTER(arg[2]);
			vms.skip = strlen(VM_POINTER(arg[0]))+1;
			vms.bufferleft = arg[3];
			vms.found=0;
			if (*(char *)VM_POINTER(arg[0]) == '$')
			{
				extern char	com_basedir[];
				vms.skip=0;
				Sys_EnumerateFiles(com_basedir, "*", VMEnumMods, &vms);
			}
			else if (*(char *)VM_POINTER(arg[1]) == '.' || *(char *)VM_POINTER(arg[1]) == '/')
				COM_EnumerateFiles(va("%s/*%s", VM_POINTER(arg[0]), VM_POINTER(arg[1])), VMEnum, &vms);
			else
				COM_EnumerateFiles(va("%s/*.%s", VM_POINTER(arg[0]), VM_POINTER(arg[1])), VMEnum, &vms);
			VM_LONG(ret) = vms.found;
		}
		break;

	case UI_PRECACHE_MODEL:	//precache model
		VM_LONG(ret) = (int)Mod_ForName(VM_POINTER(arg[0]), false);
		break;

	case UI_PRECACHE_SKIN:
		VM_LONG(ret) = 1;	//precache skin - engine ignores these anyway... (for now)
		break;

	case UI_DRAW_CACHEPIC:
		if (!Draw_SafeCachePic)
			VM_LONG(ret) = 0;
		else
			VM_LONG(ret) = (long)Draw_SafeCachePic(VM_POINTER(arg[0]));
		break;

	case UI_SCENE_CLEAR:	//clear scene
		cl_numvisedicts=0;
		break;
	case UI_SCENE_ADD_ENTITY:	//add ent to scene
		VQ3_AddEntity(VM_POINTER(arg[0]));
		break;
	case UI_SCENE_ADD_LIGHT:	//add light to scene.
		break;
	case UI_SCENE_RENDER:	//render scene
		VQ3_RenderView(VM_POINTER(arg[0]));
		break;

	case UI_DRAW_COLOUR:	//setcolour float*
		switch (qrenderer)
		{
#ifdef RGLQUAKE
		case QR_OPENGL:
			if (VM_POINTER(arg[0]))
			{
				float *fl =VM_POINTER(arg[0]);
				glColor4fv(fl);
			}
			else
				glColor4f(1, 1, 1, 1);
			break;
#endif
#ifdef SWQUAKE
		case QR_SOFTWARE:
			if (VM_POINTER(arg[0]))
			{
				float *c = VM_POINTER(arg[0]);
				SWDraw_ImageColours(c[0], c[1], c[2], c[3]);
			}
			else
				SWDraw_ImageColours(1, 1, 1, 1);
			break;
#endif
		default:
			break;
		}
		break;

	case UI_DRAW_IMAGE:
		switch (qrenderer)
		{
#ifdef RGLQUAKE
		case QR_OPENGL:
			glEnable(GL_BLEND);
			glDisable(GL_DEPTH_TEST);
			glEnable(GL_ALPHA_TEST);
			GLDraw_Image(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), VM_FLOAT(arg[4]), VM_FLOAT(arg[5]), VM_FLOAT(arg[6]), VM_FLOAT(arg[7]), (qpic_t *)VM_LONG(arg[8]));
			break;
#endif
#ifdef SWQUAKE
		case QR_SOFTWARE:
			SWDraw_Image(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), VM_FLOAT(arg[4]), VM_FLOAT(arg[5]), VM_FLOAT(arg[6]), VM_FLOAT(arg[7]), (qpic_t *)VM_LONG(arg[8]));
			break;	//FIXME
#endif
		default:
			break;
		}
		break;

	case UI_LERP_TAG:	//Lerp tag...
		if ((int)arg[0] + sizeof(float)*12 >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.
/*
		{
#ifdef RGLQUAKE
			float *org1;
			float *org2;
			float l1;
			float l2;

//			m3by3_t *ang1;
//			m3by3_t *ang2;
#endif
			m3by3_t *ang;
			float *org;

			org = (float*)VM_POINTER(arg[0]);
			ang = (m3by3_t *)((float*)VM_POINTER(arg[0])+3);

			switch(qrenderer)
			{
#ifdef RGLQUAKE
			case QR_OPENGL:

				GetTag((model_t *)VM_LONG(arg[1]), VM_POINTER(arg[5]), VM_LONG(arg[2]), &org1, &ang1);
				GetTag((model_t *)VM_LONG(arg[1]), VM_POINTER(arg[5]), VM_LONG(arg[3]), &org2, &ang2);

				l1 = 1-VM_FLOAT(arg[4]);
				l2 = VM_FLOAT(arg[4]);

				org[0] = org1[0]*l1 + org2[0]*l2;
				org[1] = org1[1]*l1 + org2[1]*l2;
				org[2] = org1[2]*l1 + org2[2]*l2;

				(*ang)[0][0] = (*ang1)[0][0]*l1 + (*ang2)[0][0]*l2;
				(*ang)[0][1] = (*ang1)[0][1]*l1 + (*ang2)[0][1]*l2;
				(*ang)[0][2] = (*ang1)[0][2]*l1 + (*ang2)[0][2]*l2;

				(*ang)[1][0] = (*ang1)[1][0]*l1 + (*ang2)[1][0]*l2;
				(*ang)[1][1] = (*ang1)[1][1]*l1 + (*ang2)[1][1]*l2;
				(*ang)[1][2] = (*ang1)[1][2]*l1 + (*ang2)[1][2]*l2;

				(*ang)[2][0] = (*ang1)[2][0]*l1 + (*ang2)[2][0]*l2;
				(*ang)[2][1] = (*ang1)[2][1]*l1 + (*ang2)[2][1]*l2;
				(*ang)[2][2] = (*ang1)[2][2]*l1 + (*ang2)[2][2]*l2;
				break;
#endif
			default:
				memset(org, 0, sizeof(vec3_t));
				memset(ang, 0, sizeof(m3by3_t));
				break;
			}
		}
		*/
		break;

	case UI_SOUND_PRECACHE:
		{
			sfx_t *sfx;
			sfx = S_PrecacheSound(va("../%s", VM_POINTER(arg[0])));
			if (sfx)
				VM_LONG(ret) = sfx->name - (char *)offset;
			else
				VM_LONG(ret) = -1;
		}
		break;
	case UI_SOUND_PLAY:
		if (VM_LONG(arg[0]) != -1)
			S_LocalSound(VM_LONG(arg[0])+(char *)offset);
		break;

	case UI_KEY_NAME:
		if (VM_LONG(arg[0]) < 0 || VM_LONG(arg[0]) > 255 || (int)arg[1] + VM_LONG(arg[2]) >= mask || VM_POINTER(arg[1]) < offset || VM_LONG(arg[2]) < 1)
			break;	//out of bounds.

		Q_strncpyz(VM_POINTER(arg[1]), Key_KeynumToString(VM_LONG(arg[0])), VM_LONG(arg[2]));
		break;

	case UI_KEY_GETBINDING:
		if (VM_LONG(arg[0]) < 0 || VM_LONG(arg[0]) > 255 || (int)arg[1] + VM_LONG(arg[2]) >= mask || VM_POINTER(arg[1]) < offset || VM_LONG(arg[2]) < 1)
			break;	//out of bounds.

		if (keybindings[VM_LONG(arg[0])][0])
			Q_strncpyz(VM_POINTER(arg[1]), keybindings[VM_LONG(arg[0])][0], VM_LONG(arg[2]));
		else
			*(char *)VM_POINTER(arg[1]) = '\0';
		break;

	case UI_KEY_SETBINDING:
		Key_SetBinding(VM_LONG(arg[0]), ~0, VM_POINTER(arg[1]), RESTRICT_LOCAL);
		break;

	case UI_KEY_ISDOWN:
		{
			extern qboolean	keydown[256];
			if (keydown[VM_LONG(arg[0])])
				VM_LONG(ret) = 1;
			else
				VM_LONG(ret) = 0;
		}
		break;

	case 37:
		break;
	case 38:
		break;

	case UI_KEY_CLEARALL:
		Key_ClearStates();
		break;
	case UI_KEY_GETDEST:
		VM_LONG(ret) = keycatcher;
		break;
	case UI_KEY_SETDEST:
		keycatcher = VM_LONG(arg[0]);
		break;

	case UI_GET_VID_CONFIG:	//get glconfig
		if ((int)arg[0] + 11332/*sizeof(glconfig_t)*/ >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.

		//do any needed work
		memset(VM_POINTER(arg[0]), 0, 11304);
		*(int *)VM_POINTER(arg[0]+11304) = vid.width;
		*(int *)VM_POINTER(arg[0]+11308) = vid.height;
		*(float *)VM_POINTER(arg[0]+11312) = (float)vid.width/vid.height;
		memset(VM_POINTER(arg[0]+11316), 0, 11332-11316);
		break;

	case UI_GET_CLIENT_STATE:	//get client state
		break;

	case UI_GET_SERVERINFO:
		if (arg[1] + VM_LONG(arg[2]) >= mask || VM_POINTER(arg[1]) < offset || VM_LONG(arg[2]) < 1)
		{
			VM_LONG(ret) = 0;
			break;	//out of bounds.
		}
		switch(VM_LONG(arg[0]))
		{
		case 0:
			Q_strncpyz(VM_POINTER(arg[1]), cl.serverinfo, VM_LONG(arg[2]));
			break;
		default:
			*(char *)VM_POINTER(arg[1]) = 0;
		}
		break;

	case UI_MS_GETSERVERCOUNT:	//these four are master server polling.
		{
			int i;
			for (i = 0; i < MAX_PINGREQUESTS; i++)
				if (ui_pings[i].type != NA_INVALID)
					VM_LONG(ret)++;
		}
		break;
	case UI_CLEAR_PINGS:	//clear ping
		//void (int pingnum)
		if (VM_LONG(arg[0])>= 0 && VM_LONG(arg[0]) <= MAX_PINGREQUESTS)
			ui_pings[VM_LONG(arg[0])].type = NA_INVALID;
		break;
	case UI_GET_PINGADDRESS:
		//void (int pingnum, char *buffer, int buflen, int *ping)
		if ((int)arg[1] + VM_LONG(arg[2]) >= mask || VM_POINTER(arg[1]) < offset)
			break;	//out of bounds.
		if ((int)arg[3] + sizeof(int) >= mask || VM_POINTER(arg[3]) < offset)
			break;	//out of bounds.

		NET_CheckPollSockets();
		if (VM_LONG(arg[0])>= 0 && VM_LONG(arg[0]) <= MAX_PINGREQUESTS)
		{
			char *buf = VM_POINTER(arg[1]);
			char *adr;
			serverinfo_t *info = Master_InfoForServer(ui_pings[VM_LONG(arg[0])]);
			if (info)
			{
				adr = NET_AdrToString(info->adr);
				if (strlen(adr) < VM_LONG(arg[2]))
				{
					strcpy(buf, adr);
					VM_LONG(ret) = true;
					*(int *)VM_POINTER(arg[3]) = info->ping;
				}	
			}
			else
				strcpy(buf, "");
		}
		break;
	case UI_GET_PINGINFO:
		//void (int pingnum, char *buffer, int buflen, )
		if ((int)arg[1] + VM_LONG(arg[2]) >= mask || VM_POINTER(arg[1]) < offset)
			break;	//out of bounds.
		if ((int)arg[3] + sizeof(int) >= mask || VM_POINTER(arg[3]) < offset)
			break;	//out of bounds.

		NET_CheckPollSockets();
		if (VM_LONG(arg[0])>= 0 && VM_LONG(arg[0]) <= MAX_PINGREQUESTS)
		{
			char *buf = VM_POINTER(arg[1]);
			char *adr;
			serverinfo_t *info = Master_InfoForServer(ui_pings[VM_LONG(arg[0])]);
			if (info)
			{
				adr = info->moreinfo->info;
				if (!adr)
					adr = "";
				if (strlen(adr) < VM_LONG(arg[2]))
				{
					strcpy(buf, adr);
					if (!*Info_ValueForKey(buf, "mapname"))
					{
						Info_SetValueForKey(buf, "mapname", Info_ValueForKey(buf, "map"), VM_LONG(arg[2]));
						Info_RemoveKey(buf, "map");
					}
					Info_SetValueForKey(buf, "sv_maxclients", va("%i", info->maxplayers), VM_LONG(arg[2]));
					Info_SetValueForKey(buf, "clients", va("%i", info->players), VM_LONG(arg[2]));
					VM_LONG(ret) = true;
				}	
			}
			else
				strcpy(buf, "");
		}
		break;

	case UI_CVAR_REGISTER:
		if ((int)arg[0] + sizeof(vmcvar_t) >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.
		{
			vmcvar_t *vmc;
			cvar_t *var;
			vmc = VM_POINTER(arg[0]);
			var = Cvar_Get(VM_POINTER(arg[1]), VM_POINTER(arg[2]), 0/*VM_LONG(arg[3])*/, "UI cvar");
			if (!vmc)
				break;
			vmc->handle = (char *)var - (char *)offset;

			vmc->integer = var->value;
			vmc->value = var->value;
			vmc->modificationCount = var->modified;
			Q_strncpyz(vmc->string, var->string, sizeof(vmc->string));
		}
	case UI_CVAR_UPDATE:
		if ((int)arg[0] + sizeof(vmcvar_t) >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.
		{
			cvar_t *var;
			vmcvar_t *vmc;
			vmc = VM_POINTER(arg[0]);
			var = (cvar_t *)((int)vmc->handle + (char *)offset);

			vmc->integer = var->value;
			vmc->value = var->value;
			vmc->modificationCount = var->modified;
			Q_strncpyz(vmc->string, var->string, sizeof(vmc->string));
		}
		break;

	case UI_MEM_AVAILABLE:
		VM_LONG(ret) = Hunk_LowMemAvailable();
		break;

	case UI_CDKEY_GET:	//get cd key
		break;
	case UI_CDKEY_SET:	//set cd key
		if ((int)arg[0] + strlen(VM_POINTER(arg[0])) >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.
		break;

	case UI_REGISTERFRONT:	//register font
		if (!Draw_SafeCachePic)
			VM_LONG(ret) = 0;
		else
			VM_LONG(ret) = (long)Draw_SafeCachePic(VM_POINTER(arg[0]));
		break;

	case UI_GET_REALTIME:
		VM_FLOAT(ret) = realtime;
		break;

	case UI_LAN_GET_COUNT:	//LAN Get server count
		//int (int source)
		VM_LONG(ret) = Master_TotalCount();
		break;
	case UI_LAN_GET_ADDRESS:	//LAN get server address
		//void (int source, int svnum, char *buffer, int buflen)
		if ((int)arg[2] + VM_LONG(arg[3]) >= mask || VM_POINTER(arg[2]) < offset)
			break;	//out of bounds.
		{
			char *buf = VM_POINTER(arg[2]);
			char *adr;
			serverinfo_t *info = Master_InfoForNum(VM_LONG(arg[1]));
			if (info)
			{
				adr = NET_AdrToString(info->adr);
				if (strlen(adr) < VM_LONG(arg[3]))
				{
					strcpy(buf, adr);
					VM_LONG(ret) = true;
				}	
			}
			else
				strcpy(buf, "");
		}
		break;
	case 67:
		break;

	case UI_SOMETHINGTHATRETURNSTRUE:
		VM_LONG(ret) = true;
		break;

	case UI_SOMETHINGTODOWITHPUNKBUSTER:	//something undocumented about punkbuster...
		break;

// standard Q3
	case UI_MEMSET:
		if ((int)arg[0] + arg[2] >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.
		memset(VM_POINTER(arg[0]), arg[1], arg[2]);
		break;
	case UI_MEMMOVE:
		if ((int)arg[0] + arg[2] >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.
		memmove(VM_POINTER(arg[0]), VM_POINTER(arg[1]), arg[2]);
		break;
	case UI_STRNCPY:
		if (arg[0] + arg[2] >= mask || VM_POINTER(arg[0]) < offset)
			break;	//out of bounds.
		Q_strncpyS(VM_POINTER(arg[0]), VM_POINTER(arg[1]), arg[2]);
		break;
	case UI_SIN:
		VM_FLOAT(ret)=(float)sin(VM_FLOAT(arg[0]));
		break;
	case UI_COS:
		VM_FLOAT(ret)=(float)cos(VM_FLOAT(arg[0]));
		break;
	case UI_ATAN2:
		VM_FLOAT(ret)=(float)atan2(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]));
		break;
	case UI_SQRT:
		VM_FLOAT(ret)=(float)sqrt(VM_FLOAT(arg[0]));
		break;
	case UI_FLOOR:
		VM_FLOAT(ret)=(float)floor(VM_FLOAT(arg[0]));
		break;
	case UI_CEIL:
		VM_FLOAT(ret)=(float)ceil(VM_FLOAT(arg[0]));
		break;

	case UI_CACHE_PIC:
		if (!Draw_SafeCachePic)
			VM_LONG(ret) = 0;
		else
			VM_LONG(ret) = (long)Draw_SafeCachePic(VM_POINTER(arg[0]));
		break;
	case UI_PICFROMWAD:
		if (!Draw_SafePicFromWad)
			VM_LONG(ret) = 0;
		else
			VM_LONG(ret) = (long)Draw_SafePicFromWad(VM_POINTER(arg[0]));
		break;
	case UI_GETPLAYERINFO:
		if (arg[1] + sizeof(vmuiclientinfo_t) >= mask || VM_POINTER(arg[1]) < offset)
			break;	//out of bounds.
		if (VM_LONG(arg[0]) < -1 || VM_LONG(arg[0] ) >= MAX_CLIENTS)
			break;
		{
			int i = VM_LONG(arg[0]);
			vmuiclientinfo_t *vci = VM_POINTER(arg[1]);
			if (i == -1)
			{
				i = cl.playernum[0];
				if (i < 0)
				{
					memset(vci, 0, sizeof(*vci));
					return 0;
				}	
			}
			vci->bottomcolour = cl.players[i].bottomcolor;
			vci->frags = cl.players[i].frags;
			strcpy(vci->name, cl.players[i].name);
			vci->ping = cl.players[i].ping;
			vci->pl = cl.players[i].pl;
			vci->starttime = cl.players[i].entertime;
			vci->topcolour = cl.players[i].topcolor;
			vci->userid = cl.players[i].userid;
			Q_strncpyz(vci->userinfo, cl.players[i].userinfo, sizeof(vci->userinfo));
		}
		break;
	case UI_GETSTAT:
		if (VM_LONG(arg[0]) < 0 || VM_LONG(arg[0]) >= MAX_CL_STATS)
			VM_LONG(ret) = 0;	//invalid stat num.
		else
			VM_LONG(ret) = cl.stats[0][VM_LONG(arg[0])];
		break;
	case UI_GETVIDINFO:
		{
			vidinfo_t *vi;
			if (arg[0] + VM_LONG(arg[1]) >= mask || VM_POINTER(arg[1]) < offset)
			{
				VM_LONG(ret) = 0;
				break;	//out of bounds.
			}
			vi = VM_POINTER(arg[0]);
			if (VM_LONG(arg[1]) < sizeof(vidinfo_t))
			{
				VM_LONG(ret) = 0;
				break;
			}
			VM_LONG(ret) = sizeof(vidinfo_t);
			vi->width = vid.width;
			vi->height = vid.height;
#ifdef SWQUAKE
			vi->bpp = r_pixbytes;
#endif
			vi->refreshrate = 60;
			vi->fullscreen = 1;
			Q_strncpyz(vi->renderername, q_renderername, sizeof(vi->renderername));
		}
		break;
	case UI_GET_STRING:
		{
			char *str = NULL;
			switch (arg[0])
			{
			case SID_Q2STATUSBAR:
				str = cl.q2statusbar;
				break;
			case SID_Q2LAYOUT:
				str = cl.q2layout;
				break;
			case SID_CENTERPRINTTEXT:
				str = scr_centerstring;
				break;
			case SID_SERVERNAME:
				str = cls.servername;
				break;

			default:
				str = Get_Q2ConfigString(arg[0]);
				break;
			}
			if (!str)
				return -1;

			if (arg[1] + arg[2] >= mask || VM_POINTER(arg[1]) < offset)
				return -1;	//out of bounds.

			if (strlen(str)>= arg[2])
				return -1;

			strcpy(VM_POINTER(arg[1]), str);	//already made sure buffer is big enough

			return strlen(str);
		}

	default:
		Sys_Error("Bad system trap: %d\n", fn);
	}

	return ret;
}

#ifdef _DEBUG
static long UI_SystemCallsExWrapper(void *offset, unsigned int mask, int fn, const long *arg)
{	//this is so we can use edit and continue properly (vc doesn't like function pointers for edit+continue)
	return UI_SystemCallsEx(offset, mask, fn, arg);
}
#define UI_SystemCallsEx UI_SystemCallsExWrapper
#endif

//I'm not keen on this.
//but dlls call it without saying what sort of vm it comes from, so I've got to have them as specifics
static int EXPORT_FN UI_SystemCalls(int arg, ...)
{
	long args[9];
	va_list argptr;

	va_start(argptr, arg);
	args[0]=va_arg(argptr, int);
	args[1]=va_arg(argptr, int);
	args[2]=va_arg(argptr, int);
	args[3]=va_arg(argptr, int);
	args[4]=va_arg(argptr, int);
	args[5]=va_arg(argptr, int);
	args[6]=va_arg(argptr, int);
	args[7]=va_arg(argptr, int);
	args[8]=va_arg(argptr, int);
	va_end(argptr);

	return UI_SystemCallsEx(NULL, ~0, arg, args);
}

qboolean UI_DrawStatusBar(int scores)
{
	if (!uivm)
		return false;

	return VM_Call(uivm, UI_DRAWSTATUSBAR, scores);
}

qboolean UI_DrawFinale(void)
{
	if (!uivm)
		return false;

	return VM_Call(uivm, UI_FINALE);
}

qboolean UI_DrawIntermission(void)
{
	if (!uivm)
		return false;

	return VM_Call(uivm, UI_INTERMISSION);
}

void UI_DrawMenu(void)
{
	if (uivm)
	{
		VM_Call(uivm, UI_DRAWMENU, (int)(realtime * 1000));
		if (keycatcher&2 && key_dest != key_console)
			key_dest = key_game;
	}
}

qboolean UI_CenterPrint(char *text, qboolean finale)
{
	scr_centerstring = text;

	if (!uivm)
		return false;

	return VM_Call(uivm, UI_STRINGCHANGED, SID_CENTERPRINTTEXT);
}

qboolean UI_Q2LayoutChanged(void)
{
	if (!uivm)
		return false;

	return VM_Call(uivm, UI_STRINGCHANGED, SID_CENTERPRINTTEXT);
}

void UI_StringChanged(int num)
{
	if (uivm)
		VM_Call(uivm, UI_STRINGCHANGED, num);
}

void UI_Reset(void)
{
	keycatcher &= ~2;
	if (uivm)
		VM_Call(uivm, UI_INIT);
}

int UI_MenuState(void)
{
	if (key_dest == key_menu)
	{
		return false;
	}
	if (!uivm)
		return false;
	if (VM_Call(uivm, UI_FULLSCREEN))
		return 2;
	else if (keycatcher&2)
		return 3;
	else
		return 0;
}

qboolean UI_KeyPress(int key, qboolean down)
{
	extern qboolean	keydown[256];
	extern int		keyshift[256];		// key to map to if shift held down in console
	qboolean result;
	if (!uivm)
		return false;
	if (key_dest == key_menu)
		return false;
	if (!(keycatcher&2))
	{
		if (key == K_ESCAPE && down)
		{
			if (cls.state)
				return VM_Call(uivm, 7, 2)>0;
			else
				return VM_Call(uivm, 7, 1)>0;
		}
		return false;
	}

	if (keydown[K_SHIFT])
		key = keyshift[key];
	if (key < K_BACKSPACE && key >= ' ')
		key |= 1024;

	result = VM_Call(uivm, UI_KEY_EVENT, key, down);

	if (!keycatcher && !cls.state)
	{
		M_Menu_Main_f();
		return true;
	}

	return result;
}

void UI_MousePosition(int xpos, int ypos)
{
	if (uivm && (ox != xpos || oy != ypos))
	{
		if (xpos < 0)
			xpos = 0;
		if (ypos < 0)
			ypos = 0;
		if (xpos > vid.width)
			xpos = vid.width;
		if (ypos > vid.height)
			ypos = vid.height;
		ox=0;oy=0;
		//force a cap
		VM_Call(uivm, 4, -32767, -32767);
		VM_Call(uivm, 4, (xpos-ox)*640/vid.width, (ypos-oy)*480/vid.height);
		ox = xpos;
		oy = ypos;

	}
}

void UI_Stop (void)
{
	keycatcher &= ~2;
	if (uivm)
	{
		VM_Call(uivm, UI_SHUTDOWN);
		VM_Destroy(uivm);
		VMUI_fcloseall(0);
		uivm = NULL;
	}
}

void UI_Start (void)
{
	int apiversion;
	if (!Draw_SafeCachePic)	//no renderer loaded
		return;

	uivm = VM_Create(NULL, "vm/qwui", UI_SystemCalls, UI_SystemCallsEx);
	if (!uivm)
		uivm = VM_Create(NULL, "vm/ui", UI_SystemCalls, UI_SystemCallsEx);
	if (uivm)
	{
		apiversion = VM_Call(uivm, UI_GETAPIVERSION, UI_API_VERSION);
		if (apiversion == UI_API_VERSION)
			keycatcher = 0;
		else if (apiversion != 4)	//make sure we can run the thing
		{
			Con_Print("VM uses incompatable API version\n");
			VM_Destroy(uivm);
			VMUI_fcloseall(0);
			uivm = NULL;
			return;
		}
		VM_Call(uivm, UI_INIT);

		VM_Call(uivm, 4, -32767, -32767);
		ox = 0;
		oy = 0;
	}
}

void UI_Restart_f(void)
{
	UI_Stop();
	UI_Start();

	if (uivm)
	{
		if (cls.state)
			VM_Call(uivm, 7, 2);
		else
			VM_Call(uivm, 7, 1);
	}
}

void UI_Init (void)
{
	Cmd_AddRemCommand("ui_restart", UI_Restart_f);
	UI_Start();
}
#else



qboolean UI_DrawStatusBar(int scores) {return false;}
qboolean UI_DrawFinale(void) {return false;}
qboolean UI_DrawIntermission(void) {return false;}
void UI_DrawMenu(void) {}
qboolean UI_CenterPrint(char *text, qboolean finale) {return false;}
qboolean UI_Q2LayoutChanged(void) {return false;}
void UI_StringChanged(int num) {}
void UI_Reset(void) {}
int UI_MenuState(void)
{
	return false;
}
qboolean UI_KeyPress(int key, qboolean down) {return false;}
void UI_MousePosition(int xpos, int ypos) {}
void UI_Start (void){}
void UI_Stop (void) {}
void UI_Init (void) {}
#endif
