fteqw/engine/client/cl_ui.c

1915 lines
49 KiB
C

#include "q3common.h"
#ifdef VM_UI
#include "clq3defs.h"
#include "ui_public.h"
#include "cl_master.h"
#include "shader.h"
static int keycatcher;
#include "botlib.h"
void SV_InitBotLib(void);
extern botlib_export_t *botlib;
qboolean CG_GetLimboString(int index, char *outbuf);
#define TT_STRING 1 // string
#define TT_LITERAL 2 // literal
#define TT_NUMBER 3 // number
#define TT_NAME 4 // name
#define TT_PUNCTUATION 5 // punctuation
#define SCRIPT_MAXDEPTH 64
#define SCRIPT_DEFINELENGTH 256
typedef struct {
char *filestack[SCRIPT_MAXDEPTH];
char *originalfilestack[SCRIPT_MAXDEPTH];
char *lastreadptr;
int lastreaddepth;
char filename[MAX_QPATH][SCRIPT_MAXDEPTH];
int stackdepth;
char *defines;
int numdefines;
} script_t;
static script_t *scripts;
static int maxscripts;
static float ui_size[2]; //to track when it needs to be restarted (the api has no video mode changed event)
static menu_t uimenu;
void Q3_SetKeyCatcher(int newcatcher)
{
int delta = newcatcher^keycatcher;
keycatcher = newcatcher;
if (delta & 2)
{
uimenu.isopaque = false; //no surprises.
if (newcatcher&2)
inputfuncs->Menu_Push(&uimenu, false);
else
inputfuncs->Menu_Unlink(&uimenu, false);
}
}
int Q3_GetKeyCatcher(void)
{
return keycatcher;
}
#define Q3SCRIPTPUNCTUATION "(,{})(\':;=!><&|+-\""
void StripCSyntax (char *s)
{
while(*s)
{
if (*s == '\\')
{
memmove(s, s+1, strlen(s+1)+1);
switch (*s)
{
case 'r':
*s = '\r';
break;
case 'n':
*s = '\n';
break;
case '\\':
*s = '\\';
break;
default:
*s = '?';
break;
}
}
s++;
}
}
int Script_Read(int handle, struct pc_token_s *token)
{
char *s;
char readstring[8192];
int i;
script_t *sc = scripts+handle-1;
char thetoken[1024];
com_tokentype_t tokentype;
for(;;)
{
if (!sc->stackdepth)
{
memset(token, 0, sizeof(*token));
return 0;
}
s = sc->filestack[sc->stackdepth-1];
sc->lastreadptr = s;
sc->lastreaddepth = sc->stackdepth;
s = (char *)cmdfuncs->ParsePunctuation(s, Q3SCRIPTPUNCTUATION, thetoken, sizeof(thetoken), &tokentype);
Q_strncpyz(readstring, thetoken, sizeof(readstring));
if (tokentype == TTP_STRING)
{
while(s)
{
while (*s > '\0' && *s <= ' ')
s++;
if (*s == '/' && s[1] == '/')
{
while(*s && *s != '\n')
s++;
continue;
}
while (*s > '\0' && *s <= ' ')
s++;
if (*s == '\"')
{
s = (char*)cmdfuncs->ParsePunctuation(s, Q3SCRIPTPUNCTUATION, thetoken, sizeof(thetoken), &tokentype);
Q_strncatz(readstring, thetoken, sizeof(readstring));
}
else
break;
}
}
sc->filestack[sc->stackdepth-1] = s;
if (tokentype == TTP_LINEENDING)
continue; //apparently we shouldn't stop on linebreaks
if (!strcmp(readstring, "#include"))
{
sc->filestack[sc->stackdepth-1] = (char *)cmdfuncs->ParsePunctuation(sc->filestack[sc->stackdepth-1], Q3SCRIPTPUNCTUATION, thetoken, sizeof(thetoken), &tokentype);
if (sc->stackdepth == SCRIPT_MAXDEPTH) //just don't enter it
continue;
if (sc->originalfilestack[sc->stackdepth])
plugfuncs->Free(sc->originalfilestack[sc->stackdepth]);
sc->filestack[sc->stackdepth] = sc->originalfilestack[sc->stackdepth] = fsfuncs->LoadFile(thetoken, NULL);
Q_strncpyz(sc->filename[sc->stackdepth], thetoken, MAX_QPATH);
sc->stackdepth++;
continue;
}
if (!strcmp(readstring, "#define"))
{
sc->numdefines++;
sc->defines = plugfuncs->Realloc(sc->defines, sc->numdefines*SCRIPT_DEFINELENGTH*2);
sc->filestack[sc->stackdepth-1] = (char *)cmdfuncs->ParsePunctuation(sc->filestack[sc->stackdepth-1], Q3SCRIPTPUNCTUATION, thetoken, sizeof(thetoken), &tokentype);
Q_strncpyz(sc->defines+SCRIPT_DEFINELENGTH*2*(sc->numdefines-1), thetoken, SCRIPT_DEFINELENGTH);
sc->filestack[sc->stackdepth-1] = (char *)cmdfuncs->ParsePunctuation(sc->filestack[sc->stackdepth-1], Q3SCRIPTPUNCTUATION, thetoken, sizeof(thetoken), &tokentype);
Q_strncpyz(sc->defines+SCRIPT_DEFINELENGTH*2*(sc->numdefines-1)+SCRIPT_DEFINELENGTH, thetoken, SCRIPT_DEFINELENGTH);
continue;
}
if (!*readstring && tokentype != TTP_STRING)
{
if (sc->stackdepth==0)
{
memset(token, 0, sizeof(*token));
return 0;
}
sc->stackdepth--;
continue;
}
break;
}
if (tokentype == TTP_STRING)
{
i = sc->numdefines;
}
else
{
for (i = 0; i < sc->numdefines; i++)
{
if (!strcmp(readstring, sc->defines+SCRIPT_DEFINELENGTH*2*i))
{
Q_strncpyz(token->string, sc->defines+SCRIPT_DEFINELENGTH*2*i+SCRIPT_DEFINELENGTH, sizeof(token->string));
break;
}
}
}
if (i == sc->numdefines) //otherwise
Q_strncpyz(token->string, readstring, sizeof(token->string));
StripCSyntax(token->string);
if (token->string[0] == '0' && (token->string[1] == 'x'||token->string[1] == 'X'))
{
token->intvalue = strtoul(token->string, NULL, 16);
token->floatvalue = token->intvalue;
token->type = TT_NUMBER;
token->subtype = 0x100;//TT_HEX;
}
else
{
token->intvalue = atoi(token->string);
token->floatvalue = atof(token->string);
if (token->floatvalue || *token->string == '0' || *token->string == '.')
{
token->type = TT_NUMBER;
token->subtype = 0;
}
else if (tokentype == TTP_STRING)
{
token->type = TT_STRING;
token->subtype = strlen(token->string);
}
else
{
if (token->string[1] == '\0')
{
token->type = TT_PUNCTUATION;
token->subtype = token->string[0];
}
else
{
token->type = TT_NAME;
token->subtype = strlen(token->string);
}
}
}
// Con_Printf("Found %s (%i, %i)\n", token->string, token->type, token->subtype);
return tokentype != TTP_EOF;
}
int Script_LoadFile(char *filename)
{
int i;
script_t *sc;
for (i = 0; i < maxscripts; i++)
if (!scripts[i].stackdepth)
break;
if (i == maxscripts)
{
maxscripts++;
scripts = plugfuncs->Realloc(scripts, sizeof(script_t)*maxscripts);
}
sc = scripts+i;
memset(sc, 0, sizeof(*sc));
sc->filestack[0] = sc->originalfilestack[0] = fsfuncs->LoadFile(filename, NULL);
Q_strncpyz(sc->filename[sc->stackdepth], filename, MAX_QPATH);
sc->stackdepth = 1;
return i+1;
}
void Script_Free(int handle)
{
int i;
script_t *sc = scripts+handle-1;
if (sc->defines)
plugfuncs->Free(sc->defines);
for (i = 0; i < sc->stackdepth; i++)
plugfuncs->Free(sc->originalfilestack[i]);
sc->stackdepth = 0;
}
void Script_Get_File_And_Line(int handle, char *filename, int *line)
{
script_t *sc = scripts+handle-1;
char *src;
char *start;
if (!sc->lastreaddepth)
{
*line = 0;
Q_strncpyz(filename, sc->filename[0], MAX_QPATH);
return;
}
*line = 1;
src = sc->lastreadptr;
start = sc->originalfilestack[sc->lastreaddepth-1];
while(start < src)
{
if (*start == '\n')
(*line)++;
start++;
}
Q_strncpyz(filename, sc->filename[sc->lastreaddepth-1], MAX_QPATH);
}
static vm_t *uivm;
#define MAX_PINGREQUESTS 32
static struct
{
unsigned int startms;
netadr_t adr;
char adrstring[64];
const char *broker;
} ui_pings[MAX_PINGREQUESTS];
#define UITAGNUM 2452
struct q3refEntity_s {
refEntityType_t reType;
int renderfx;
int 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
skinid_t 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;
};
struct q3polyvert_s
{
vec3_t org;
vec2_t tcoord;
qbyte colours[4];
};
#define Q3RF_MINLIGHT 1
#define Q3RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites)
#define Q3RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob)
#define Q3RF_DEPTHHACK 8 // for view weapon Z crunching
#define Q3RF_NOSHADOW 64
#define Q3RF_LIGHTING_ORIGIN 128
#define MAX_VMQ3_CACHED_STRINGS 2048
static char *stringcache[MAX_VMQ3_CACHED_STRINGS];
void VMQ3_FlushStringHandles(void)
{
int i;
for (i = 0; i < MAX_VMQ3_CACHED_STRINGS; i++)
{
if (stringcache[i])
{
Z_Free(stringcache[i]);
stringcache[i] = NULL;
}
}
}
char *VMQ3_StringFromHandle(int handle)
{
if (!handle)
return "";
handle--;
if ((unsigned) handle >= MAX_VMQ3_CACHED_STRINGS)
return "";
return stringcache[handle];
}
int VMQ3_StringToHandle(char *str)
{
int i;
for (i = 0; i < MAX_VMQ3_CACHED_STRINGS; i++)
{
if (!stringcache[i])
break;
if (!strcmp(str, stringcache[i]))
return i+1;
}
if (i == MAX_VMQ3_CACHED_STRINGS)
{
Con_Printf("Q3VM out of string handle space\n");
return 0;
}
stringcache[i] = Z_Malloc(strlen(str)+1);
strcpy(stringcache[i], str);
return i+1;
}
#define VM_TOSTRCACHE(a) VMQ3_StringToHandle(VM_POINTER(a))
#define VM_FROMSTRCACHE(a) VMQ3_StringFromHandle(a)
void VQ3_AddEntity(const q3refEntity_t *q3)
{
entity_t ent;
memset(&ent, 0, sizeof(ent));
ent.model = scenefuncs->ModelFromId(q3->hModel);
ent.framestate.g[FS_REG].frame[0] = q3->frame;
ent.framestate.g[FS_REG].frame[1] = q3->oldframe;
memcpy(ent.axis, q3->axis, sizeof(q3->axis));
ent.framestate.g[FS_REG].lerpweight[1] = q3->backlerp;
ent.framestate.g[FS_REG].lerpweight[0] = 1 - ent.framestate.g[FS_REG].lerpweight[1];
if (q3->reType == RT_SPRITE)
{
ent.scale = q3->radius;
ent.rotation = q3->rotation;
}
else
ent.scale = 1;
ent.rtype = q3->reType;
ent.customskin = q3->customSkin;
ent.skinnum = q3->skinNum;
ent.shaderRGBAf[0] = q3->shaderRGBA[0]/255.0f;
ent.shaderRGBAf[1] = q3->shaderRGBA[1]/255.0f;
ent.shaderRGBAf[2] = q3->shaderRGBA[2]/255.0f;
ent.shaderRGBAf[3] = q3->shaderRGBA[3]/255.0f;
/*don't set force-translucent etc, the shader is meant to already be correct*/
// if (ent.shaderRGBAf[3] <= 0)
// return;
ent.forcedshader = drawfuncs->ShaderFromId(q3->customShader);
ent.shaderTime = q3->shaderTime;
if (q3->renderfx & Q3RF_FIRST_PERSON)
ent.flags |= RF_FIRSTPERSON;
if (q3->renderfx & Q3RF_DEPTHHACK)
ent.flags |= RF_DEPTHHACK;
if (q3->renderfx & Q3RF_THIRD_PERSON)
ent.flags |= RF_EXTERNALMODEL;
if (q3->renderfx & Q3RF_NOSHADOW)
ent.flags |= RF_NOSHADOW;
ent.topcolour = TOP_DEFAULT;
ent.bottomcolour = BOTTOM_DEFAULT;
ent.playerindex = -1;
if ((q3->renderfx & Q3RF_LIGHTING_ORIGIN) && ent.model)
{
VectorCopy(q3->lightingOrigin, ent.origin);
scenefuncs->CalcModelLighting(&ent, ent.model);
}
VectorCopy(q3->origin, ent.origin);
VectorCopy(q3->oldorigin, ent.oldorigin);
scenefuncs->AddEntity(&ent);
}
void VQ3_AddPolys(shader_t *s, int numverts, q3polyvert_t *verts, size_t polycount)
{
unsigned int v;
for (; polycount-->0; verts += numverts)
{
vecV_t *vertcoord;
vec2_t *texcoord;
vec4_t *colour;
index_t *indexes;
index_t indexbias = scenefuncs->AddPolydata(s, BEF_NODLIGHT|BEF_NOSHADOWS, numverts, (numverts-2)*3, &vertcoord, &texcoord, &colour, &indexes);
//and splurge the data out.
for (v = 0; v < numverts; v++)
{
VectorCopy(verts[v].org, vertcoord[v]);
Vector2Copy(verts[v].tcoord, texcoord[v]);
Vector4Scale(verts[v].colours, (1/255.0f), colour[v]);
}
for (v = 2; v < numverts; v++)
{
*indexes++ = indexbias + 0;
*indexes++ = indexbias + (v-1);
*indexes++ = indexbias + v;
}
}
}
int VM_LerpTag(void *out, model_t *model, int f1, int f2, float l2, char *tagname)
{
int tagnum;
float *ang;
float *org;
float tr[12];
qboolean found;
framestate_t fstate;
org = (float*)out;
ang = ((float*)out+3);
memset(&fstate, 0, sizeof(fstate));
fstate.g[FS_REG].frame[0] = f1;
fstate.g[FS_REG].frame[1] = f2;
fstate.g[FS_REG].lerpweight[0] = 1 - l2;
fstate.g[FS_REG].lerpweight[1] = l2;
tagnum = scenefuncs->TagNumForName(model, tagname, 0);
found = scenefuncs->GetTag(model, tagnum, &fstate, tr);
if (found && tagnum)
{
ang[0] = tr[0];
ang[1] = tr[1];
ang[2] = tr[2];
org[0] = tr[3];
ang[3] = tr[4];
ang[4] = tr[5];
ang[5] = tr[6];
org[1] = tr[7];
ang[6] = tr[8];
ang[7] = tr[9];
ang[8] = tr[10];
org[2] = tr[11];
return true;
}
else
{
org[0] = 0;
org[1] = 0;
org[2] = 0;
ang[0] = 1;
ang[1] = 0;
ang[2] = 0;
ang[3] = 0;
ang[4] = 1;
ang[5] = 0;
ang[6] = 0;
ang[7] = 0;
ang[8] = 1;
return false;
}
}
#define MAX_RENDER_STRINGS 8
#define MAX_RENDER_STRING_LENGTH 32
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];
};
void VQ3_RenderView(const q3refdef_t *ref)
{
plugrefdef_t scene;
scene.flags = 0;
scene.rect.x = ref->x;
scene.rect.y = ref->y;
scene.rect.w = ref->width;
scene.rect.h = ref->height;
scene.fov[0] = scene.fov_viewmodel[0] = ref->fov_x;
scene.fov[1] = scene.fov_viewmodel[1] = ref->fov_y;
VectorCopy(ref->viewaxis[0], scene.viewaxisorg[0]);
VectorCopy(ref->viewaxis[1], scene.viewaxisorg[1]);
VectorCopy(ref->viewaxis[2], scene.viewaxisorg[2]);
VectorCopy(ref->vieworg, scene.viewaxisorg[3]);
scene.time = ref->time/1000.0f;
if (ref->rdflags & 1/*Q3RDF_NOWORLDMODEL*/)
scene.flags |= RDF_NOWORLDMODEL;
scenefuncs->RenderScene(&scene, sizeof(ref->areamask), ref->areamask);
}
static void *Little4Block(void *out, qbyte *in, size_t count)
{
while (count-->0)
{
*(unsigned int*)out = (in[0]<<0)|(in[1]<<8)|(in[2]<<16)|(in[3]<<24);
out = (qbyte*)out + 4;
in+=4;
}
return in;
}
void UI_RegisterFont(char *fontName, int pointSize, fontInfo_t *font)
{
union
{
char *c;
int *i;
float *f;
} in;
int i;
char name[MAX_QPATH];
void *filedata;
size_t sz;
snprintf(name, sizeof(name), "fonts/fontImage_%i.dat",pointSize);
in.c = filedata = fsfuncs->LoadFile(name, &sz);
if (sz == sizeof(fontInfo_t))
{
for(i=0; i<GLYPHS_PER_FONT; i++)
{
in.c = Little4Block(&font->glyphs[i].height, in.c, (int*)font->glyphs[i].shaderName-&font->glyphs[i].height);
memcpy(font->glyphs[i].shaderName, in.i, 32);
in.c += 32;
}
in.c = Little4Block(&font->glyphScale, in.c, 1);
memcpy(font->name, in.i, sizeof(font->name));
// Com_Memcpy(font, faceData, sizeof(fontInfo_t));
Q_strncpyz(font->name, name, sizeof(font->name));
for (i = GLYPH_START; i < GLYPH_END; i++)
font->glyphs[i].glyph = drawfuncs->LoadImage(font->glyphs[i].shaderName);
}
plugfuncs->Free(filedata);
}
static struct
{
int shaderhandle;
int x, y, w, h;
qboolean loop;
} uicinematics[16];
int UI_Cin_Play(const char *name, int x, int y, int w, int h, unsigned int flags)
{
int idx;
qhandle_t mediashader;
//cin_t *cin;
for (idx = 0; ; idx++)
{
if (idx == countof(uicinematics))
return -1; //out of handles
if (uicinematics[idx].shaderhandle)
continue; //slot in use
break; //this slot is usable
}
/*mediashader = R_RegisterCustom(name, SUF_NONE, Shader_DefaultCinematic, va("video/%s", name));
if (!mediashader)
return -1; //wtf?
cin = R_ShaderGetCinematic(mediashader);
if (cin)
Media_SetState(cin, CINSTATE_PLAY);
else*/
return -1; //FAIL!
uicinematics[idx].x = x;
uicinematics[idx].y = y;
uicinematics[idx].w = w;
uicinematics[idx].h = h;
uicinematics[idx].loop = !!(flags&1);
uicinematics[idx].shaderhandle = mediashader;
return idx;
}
int UI_Cin_Stop(int idx)
{
if (idx >= 0 && idx < countof(uicinematics))
{
drawfuncs->UnloadImage(uicinematics[idx].shaderhandle);
uicinematics[idx].shaderhandle = 0;
}
return 0;
}
int UI_Cin_Run(int idx)
{
enum {
FMV_IDLE,
FMV_PLAY, // play
FMV_EOF, // all other conditions, i.e. stop/EOF/abort
FMV_ID_BLT,
FMV_ID_IDLE,
FMV_LOOPED,
FMV_ID_WAIT
};
int ret = FMV_IDLE;
if (idx >= 0 && idx < countof(uicinematics))
{
shader_t *shader = drawfuncs->ShaderFromId(uicinematics[idx].shaderhandle);
cin_t *cin = R_ShaderGetCinematic(shader);
if (cin)
{
switch(Media_GetState(cin))
{
case CINSTATE_INVALID: ret = FMV_IDLE; break;
case CINSTATE_PLAY: ret = FMV_PLAY; break;
case CINSTATE_LOOP: ret = FMV_PLAY; break;
case CINSTATE_PAUSE: ret = FMV_PLAY; break;
case CINSTATE_ENDED:
Media_SetState(cin, CINSTATE_FLUSHED);
ret = FMV_EOF;
break;
case CINSTATE_FLUSHED:
//FIXME: roq decoder has no reset method!
Media_Send_Reset(cin);
ret = FMV_LOOPED;
break;
}
}
}
return ret;
}
int UI_Cin_Draw(int idx)
{
if (idx >= 0 && idx < countof(uicinematics))
{
unsigned int size[2];
qhandle_t shader = uicinematics[idx].shaderhandle;
float x = uicinematics[idx].x;
float y = uicinematics[idx].y;
float w = uicinematics[idx].w;
float h = uicinematics[idx].h;
drawfuncs->GetVideoSize(NULL, size);
//gah! q3 compat sucks!
x *= size[0]/640.0;
w *= size[0]/640.0;
y *= size[1]/480.0;
h *= size[1]/480.0;
drawfuncs->Image(x, y, w, h, 0, 0, 1, 1, shader);
}
return 0;
}
int UI_Cin_SetExtents(int idx, int x, int y, int w, int h)
{
if (idx >= 0 && idx < countof(uicinematics))
{
uicinematics[idx].x = x;
uicinematics[idx].y = y;
uicinematics[idx].w = w;
uicinematics[idx].h = h;
}
return 0;
}
static const char *Cvar_FixQ3Name (const char *var_name)
{
struct {
const char *q3;
const char *fte;
} cvarremaps[] =
{
{"s_musicvolume", "bgmvolume"},
{"r_gamma", "gamma"},
{"s_sdlSpeed", "s_khz"},
{"r_fullscreen", "vid_fullscreen"},
{"r_picmip", "gl_picmip"},
{"r_textureMode", "gl_texturemode"},
{"r_lodBias", "d_lodbias"},
{"r_colorbits", "vid_bpp"},
{"r_dynamiclight", "r_dynamic"},
{"r_finish", "gl_finish"},
{"protocol", "com_protocolversion"},
// {"r_glDriver", NULL},
// {"r_depthbits", NULL},
// {"r_stencilbits", NULL},
// {"s_compression", NULL},
// {"r_texturebits", NULL},
// {"r_allowExtensions",NULL},
// {"s_useOpenAL", NULL},
// {"sv_running", NULL},
// {"sv_killserver", NULL},
// {"color1", NULL},
// {"in_joystick", NULL},
// {"joy_threshold", NULL},
// {"cl_freelook", NULL},
// {"color1", NULL},
// {"r_availableModes",NULL},
// {"r_mode", NULL},
};
size_t i;
for (i = 0; i < countof(cvarremaps); i++)
{
if (!strcmp(cvarremaps[i].q3, var_name))
return cvarremaps[i].fte;
}
return var_name;
}
static void UI_SimulateTextEntry(void *cb, const char *utf8)
{
const char *line = utf8;
unsigned int unicode;
int err;
while(*line)
{
unicode = utf8_decode(&err, line, &line);
if (uivm)
vmfuncs->Call(uivm, UI_KEY_EVENT, unicode|1024, true);
}
}
#define VALIDATEPOINTER(o,l) if ((quintptr_t)o + l >= mask || VM_POINTER(o) < offset) plugfuncs->EndGame("Call to ui trap %i passes invalid pointer\n", (int)fn); //out of bounds.
static qintptr_t UI_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, const qintptr_t *arg)
{
static int overstrikemode;
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((uiImport_t)fn)
{
case UI_CVAR_CREATE: Con_Printf("Q3UI: Not implemented system trap: %s\n", "UI_CVAR_CREATE"); return 0;
case UI_CVAR_INFOSTRINGBUFFER: Con_Printf("Q3UI: Not implemented system trap: %s\n", "UI_CVAR_INFOSTRINGBUFFER"); return 0;
case UI_R_ADDPOLYTOSCENE: Con_Printf("Q3UI: Not implemented system trap: %s\n", "UI_R_ADDPOLYTOSCENE"); return 0;
case UI_R_REMAP_SHADER: Con_Printf("Q3UI: Not implemented system trap: %s\n", "UI_R_REMAP_SHADER"); return 0;
case UI_UPDATESCREEN: Con_Printf("Q3UI: Not implemented system trap: %s\n", "UI_UPDATESCREEN"); return 0;
case UI_CM_LOADMODEL: Con_Printf("Q3UI: Not implemented system trap: %s\n", "UI_CM_LOADMODEL"); return 0;
case UI_ERROR:
Con_Printf("%s", (char*)VM_POINTER(arg[0]));
UI_Stop();
plugfuncs->EndGame("UI error");
break;
case UI_PRINT:
Con_Printf("%s", (char*)VM_POINTER(arg[0]));
break;
case UI_MILLISECONDS:
VM_LONG(ret) = plugfuncs->GetMilliseconds();
break;
case UI_ARGC:
VM_LONG(ret) = cmdfuncs->Argc();
break;
case UI_ARGV:
// VALIDATEPOINTER(arg[1], arg[2]);
cmdfuncs->Argv(VM_LONG(arg[0]), VM_POINTER(arg[1]), VM_LONG(arg[2]));
break;
/* case UI_ARGS:
VALIDATEPOINTER(arg[0], arg[1]);
Q_strncpyz(VM_POINTER(arg[0]), Cmd_Args(), VM_LONG(arg[1]));
break;
*/
case UI_CVAR_SET:
cvarfuncs->SetString(Cvar_FixQ3Name(VM_POINTER(arg[0])), VM_POINTER(arg[1]));
break;
case UI_CVAR_VARIABLEVALUE:
VM_FLOAT(ret) = cvarfuncs->GetFloat(Cvar_FixQ3Name(VM_POINTER(arg[0])));
break;
case UI_CVAR_VARIABLESTRINGBUFFER:
if (arg[1] + arg[2] >= mask || VM_POINTER(arg[1]) < offset)
VM_LONG(ret) = -2; //out of bounds.
cvarfuncs->GetString(Cvar_FixQ3Name(VM_POINTER(arg[0])), VM_POINTER(arg[1]), VM_LONG(arg[2]));
break;
case UI_CVAR_SETVALUE:
cvarfuncs->SetFloat(Cvar_FixQ3Name(VM_POINTER(arg[0])), VM_FLOAT(arg[1]));
break;
case UI_CVAR_RESET: //cvar reset
cvarfuncs->SetString(Cvar_FixQ3Name(VM_POINTER(arg[0])), NULL);
break;
case UI_CMD_EXECUTETEXT:
{
char *cmdtext = VM_POINTER(arg[1]);
#ifdef CL_MASTER
if (!strncmp(cmdtext, "ping ", 5))
{
char token[1024];
int i;
for (i = 0; i < MAX_PINGREQUESTS; i++)
if (ui_pings[i].adr.type == NA_INVALID && ui_pings[i].adr.prot == NP_INVALID)
{
const char *p = NULL;
cmdfuncs->ParseToken(cmdtext + 5, token, sizeof(token), NULL);
ui_pings[i].startms = plugfuncs->GetMilliseconds();
Q_strncpyz(ui_pings[i].adrstring, token, sizeof(ui_pings[i].adrstring));
if (masterfuncs->StringToAdr(ui_pings[i].adrstring, 0, &ui_pings[i].adr, 1, &p))
{
#ifdef HAVE_PACKET
serverinfo_t *info = masterfuncs->InfoForServer(&ui_pings[i].adr, p);
if (info)
{
info->special |= SS_KEEPINFO;
info->sends++;
masterfuncs->QueryServer(info);
}
#endif
}
ui_pings[i].broker = p;
break;
}
}
else
#endif
cmdfuncs->AddText(cmdtext, false);
}
break;
case UI_FS_FOPENFILE: //fopen
if ((int)arg[1] + 4 >= mask || VM_POINTER(arg[1]) < offset)
break; //out of bounds.
VM_LONG(ret) = VM_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.
VM_LONG(ret) = VM_FRead(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), 0);
break;
case UI_FS_WRITE: //fwrite
break;
case UI_FS_SEEK:
return VM_FSeek(arg[0], arg[1], arg[2], 0);
case UI_FS_FCLOSEFILE: //fclose
VM_fclose(VM_LONG(arg[0]), 0);
break;
case UI_FS_GETFILELIST: //fs listing
if ((int)arg[2] + arg[3] >= mask || VM_POINTER(arg[2]) < offset)
break; //out of bounds.
return VM_GetFileList(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_LONG(arg[3]));
case UI_R_REGISTERMODEL: //precache model
{
char *name = VM_POINTER(arg[0]);
model_t *mod = scenefuncs->LoadModel(name, MLV_SILENTSYNC);
if (!mod || mod->loadstate != MLS_LOADED || mod->type == mod_dummy)
VM_LONG(ret) = 0;
else
VM_LONG(ret) = scenefuncs->ModelToId(mod);
}
break;
case UI_R_MODELBOUNDS:
{
VALIDATEPOINTER(arg[1], sizeof(vec3_t));
VALIDATEPOINTER(arg[2], sizeof(vec3_t));
{
model_t *mod = scenefuncs->ModelFromId(arg[0]);
if (mod)
{
VectorCopy(mod->mins, ((float*)VM_POINTER(arg[1])));
VectorCopy(mod->maxs, ((float*)VM_POINTER(arg[2])));
}
else
{
VectorClear(((float*)VM_POINTER(arg[1])));
VectorClear(((float*)VM_POINTER(arg[2])));
}
}
}
break;
case UI_R_REGISTERSKIN:
VM_LONG(ret) = scenefuncs->RegisterSkinFile(VM_POINTER(arg[0]));
break;
case UI_R_REGISTERFONT: //register font
UI_RegisterFont(VM_POINTER(arg[0]), arg[1], VM_POINTER(arg[2]));
break;
case UI_R_REGISTERSHADERNOMIP:
if (!*(char*)VM_POINTER(arg[0]))
VM_LONG(ret) = 0;
else
VM_LONG(ret) = drawfuncs->LoadImage(VM_POINTER(arg[0]));
if (!drawfuncs->ImageSize(VM_LONG(ret), NULL, NULL))
VM_LONG(ret) = 0; //not found...
break;
case UI_R_CLEARSCENE: //clear scene
scenefuncs->ClearScene();
break;
case UI_R_ADDREFENTITYTOSCENE: //add ent to scene
VQ3_AddEntity(VM_POINTER(arg[0]));
break;
case UI_R_ADDLIGHTTOSCENE: //add light to scene.
break;
case UI_R_RENDERSCENE: //render scene
VQ3_RenderView(VM_POINTER(arg[0]));
break;
case UI_R_SETCOLOR: //setcolour float*
{
float *fl =VM_POINTER(arg[0]);
if (!fl)
drawfuncs->Colour4f(1, 1, 1, 1);
else
drawfuncs->Colour4f(fl[0], fl[1], fl[2], fl[3]);
}
break;
case UI_R_DRAWSTRETCHPIC:
drawfuncs->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]), VM_LONG(arg[8]));
break;
case UI_CM_LERPTAG: //Lerp tag...
VALIDATEPOINTER(arg[0], sizeof(float)*12);
VM_LONG(ret) = VM_LerpTag(VM_POINTER(arg[0]), scenefuncs->ModelFromId(arg[1]), VM_LONG(arg[2]), VM_LONG(arg[3]), VM_FLOAT(arg[4]), VM_POINTER(arg[5]));
break;
case UI_S_REGISTERSOUND:
{
sfx_t *sfx;
sfx = audiofuncs->PrecacheSound(va("%s", (char*)VM_POINTER(arg[0])));
if (sfx)
VM_LONG(ret) = VM_TOSTRCACHE(arg[0]); //return handle is the parameter they just gave
else
VM_LONG(ret) = -1;
}
break;
case UI_S_STARTLOCALSOUND:
if (VM_LONG(arg[0]) != -1 && arg[0])
audiofuncs->LocalSound(VM_FROMSTRCACHE(arg[0]), CHAN_AUTO, 1.0); //now we can fix up the sound name
break;
case UI_S_STARTBACKGROUNDTRACK:
audiofuncs->ChangeMusicTrack(VM_POINTER(arg[0]), VM_POINTER(arg[1]));
break;
case UI_S_STOPBACKGROUNDTRACK:
audiofuncs->ChangeMusicTrack(NULL, NULL);
break;
//q3 shares insert mode between its ui and console. whereas fte doesn't support it (FIXME: add to Key_EntryInsert).
case UI_KEY_SETOVERSTRIKEMODE:
overstrikemode = arg[0];
return 0;
case UI_KEY_GETOVERSTRIKEMODE:
return overstrikemode;
case UI_GETCLIPBOARDDATA:
if (VM_OOB(arg[0], VM_LONG(arg[1])))
break; //out of bounds.
//our clipboard doesn't allow us to simply query without blocking (would result in stalls on x11/wayland)
//so just return nothing - we can send the text as if it were regular text entry for a similar result.
Q_strncpyz(VM_POINTER(arg[0]), "", VM_LONG(arg[1]));
//but do we really want to let mods read the system clipboard? I suppose it SHOULD be okay if the UI was manually installed by the user.
//side note: q3's text entry logic is kinda flawed.
Sys_Clipboard_PasteText(CBT_CLIPBOARD, UI_SimulateTextEntry, NULL);
break;
case UI_KEY_KEYNUMTOSTRINGBUF:
if (VM_LONG(arg[0]) < 0 || VM_LONG(arg[0]) >= K_MAX || (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]), inputfuncs->GetKeyName(VM_LONG(arg[0]), 0), VM_LONG(arg[2]));
break;
case UI_KEY_GETBINDINGBUF:
if (VM_LONG(arg[0]) < 0 || VM_LONG(arg[0]) >= K_MAX || (int)arg[1] + VM_LONG(arg[2]) >= mask || VM_POINTER(arg[1]) < offset || VM_LONG(arg[2]) < 1)
break; //out of bounds.
{
const char *binding = inputfuncs->GetKeyBind(0, VM_LONG(arg[0]), 0);
if (binding)
Q_strncpyz(VM_POINTER(arg[1]), binding, VM_LONG(arg[2]));
else
*(char *)VM_POINTER(arg[1]) = '\0';
}
break;
case UI_KEY_SETBINDING:
inputfuncs->SetKeyBind(0, VM_LONG(arg[0]), 0, VM_POINTER(arg[1]));
break;
case UI_KEY_ISDOWN:
VM_LONG(ret) = inputfuncs->IsKeyDown(VM_LONG(arg[0]));
break;
case UI_KEY_CLEARSTATES:
inputfuncs->ClearKeyStates();
break;
case UI_KEY_GETCATCHER:
if (Key_Dest_Has(kdm_console))
VM_LONG(ret) = keycatcher | 1;
else
VM_LONG(ret) = keycatcher;
break;
case UI_KEY_SETCATCHER:
Q3_SetKeyCatcher(VM_LONG(arg[0]));
break;
case UI_GETGLCONFIG: //get glconfig
{
q3glconfig_t *cfg;
float vsize[2] = {0,0};
if ((int)arg[0] + sizeof(q3glconfig_t) >= mask || VM_POINTER(arg[0]) < offset)
break; //out of bounds.
drawfuncs->GetVideoSize(vsize, NULL);
cfg = VM_POINTER(arg[0]);
//do any needed work
memset(cfg, 0, sizeof(*cfg));
Q_strncpyz(cfg->renderer_string, "", sizeof(cfg->renderer_string));
Q_strncpyz(cfg->vendor_string, "", sizeof(cfg->vendor_string));
Q_strncpyz(cfg->version_string, "", sizeof(cfg->version_string));
Q_strncpyz(cfg->extensions_string, "", sizeof(cfg->extensions_string));
cfg->colorBits = 32;
cfg->depthBits = 24;
cfg->stencilBits = 8;//sh_config.stencilbits;
cfg->textureCompression = true;//!!sh_config.texfmt[PTI_BC1_RGBA];
cfg->textureEnvAddAvailable = true;//sh_config.env_add;
//these are the only three that really matter.
cfg->vidWidth = vsize[0];
cfg->vidHeight = vsize[1];
cfg->windowAspect = vsize[0]/vsize[1];;
#if 0
char *cfg;
if ((int)arg[0] + 11332/*sizeof(glconfig_t)*/ >= mask || VM_POINTER(arg[0]) < offset)
break; //out of bounds.
cfg = VM_POINTER(arg[0]);
//do any needed work
memset(cfg, 0, 11304);
*(int *)(cfg+11304) = vsize[0];
*(int *)(cfg+11308) = vid.height;
*(float *)(cfg+11312) = (float)vsize[0]/vid.height;
memset(cfg+11316, 0, 11332-11316);
#endif
}
break;
case UI_GETCLIENTSTATE: //get client state
//fixme: we need to fill in a structure.
// Con_Printf("ui_getclientstate\n");
VALIDATEPOINTER(arg[0], sizeof(uiClientState_t));
{
uiClientState_t *state = VM_POINTER(arg[0]);
state->connectPacketCount = 0;//clc.connectPacketCount;
switch(ccs.state)
{
case ca_disconnected:
if (CL_TryingToConnect())
state->connState = Q3CA_CONNECTING;
else
state->connState = Q3CA_DISCONNECTED;
break;
case ca_demostart:
state->connState = Q3CA_CONNECTING;
break;
case ca_connected:
state->connState = Q3CA_CONNECTED;
break;
case ca_onserver:
state->connState = Q3CA_PRIMED;
break;
case ca_active:
state->connState = Q3CA_ACTIVE;
break;
}
cvarfuncs->GetString("cl_servername", state->servername, sizeof(state->servername)); //error message from game server
Q_strncpyz(state->updateInfoString, "FTE!", sizeof(state->updateInfoString)); //warning/motd message from update server
cvarfuncs->GetString("com_errorMessage", state->messageString, sizeof(state->messageString)); //error message from game server
state->clientNum = ccs.playernum;
}
break;
case UI_GETCONFIGSTRING:
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.
}
#ifdef VM_CG
Q_strncpyz(VM_POINTER(arg[1]), CG_GetConfigString(VM_LONG(arg[0])), VM_LONG(arg[2]));
#endif
break;
#ifdef CL_MASTER
case UI_LAN_GETPINGQUEUECOUNT: //these four are master server polling.
{
int i;
for (i = 0; i < MAX_PINGREQUESTS; i++)
if (ui_pings[i].adr.type != NA_INVALID || ui_pings[i].adr.prot != NP_INVALID)
VM_LONG(ret)++;
}
break;
case UI_LAN_CLEARPING: //clear ping
//void (int pingnum)
if (VM_LONG(arg[0])>= 0 && VM_LONG(arg[0]) < MAX_PINGREQUESTS)
ui_pings[VM_LONG(arg[0])].adr.type = NA_INVALID, ui_pings[VM_LONG(arg[0])].adr.prot = NP_INVALID;
break;
case UI_LAN_GETPING:
//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.
masterfuncs->CheckPollSockets();
if (VM_LONG(arg[0])>= 0 && VM_LONG(arg[0]) < MAX_PINGREQUESTS)
{
int i = VM_LONG(arg[0]);
char *buf = VM_POINTER(arg[1]);
size_t bufsize = VM_LONG(arg[2]);
int *ping = VM_POINTER(arg[3]);
serverinfo_t *info;
if (ui_pings[i].adr.type != NA_INVALID || ui_pings[i].adr.prot != NP_INVALID)
info = masterfuncs->InfoForServer(&ui_pings[i].adr, ui_pings[i].broker);
else
info = NULL;
Q_strncpyz(buf, ui_pings[i].adrstring, bufsize);
if (info && /*(info->status & SRVSTATUS_ALIVE) &&*/ info->moreinfo)
{
VM_LONG(ret) = true;
*ping = (info->ping == PING_UNKNOWN)?1:info->ping;
break;
}
i = plugfuncs->GetMilliseconds()-ui_pings[i].startms;
if (i < 999)
i = 0; //don't time out yet.
*ping = i;
}
break;
case UI_LAN_GETPINGINFO:
//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.
masterfuncs->CheckPollSockets();
if (VM_LONG(arg[0])>= 0 && VM_LONG(arg[0]) < MAX_PINGREQUESTS)
{
int i = VM_LONG(arg[0]);
char *buf = VM_POINTER(arg[1]);
size_t bufsize = VM_LONG(arg[2]);
char *adr;
serverinfo_t *info = masterfuncs->InfoForServer(&ui_pings[i].adr, ui_pings[i].broker);
if (info && /*(info->status & SRVSTATUS_ALIVE) &&*/ info->moreinfo)
{
adr = info->moreinfo->info;
if (!adr)
adr = "";
if (strlen(adr) < bufsize)
{
strcpy(buf, adr);
worldfuncs->SetInfoKey(buf, "mapname", info->map, bufsize);
worldfuncs->SetInfoKey(buf, "hostname", info->name, bufsize);
worldfuncs->SetInfoKey(buf, "g_humanplayers", va("%i", info->numhumans), bufsize);
worldfuncs->SetInfoKey(buf, "sv_maxclients", va("%i", info->maxplayers), bufsize);
worldfuncs->SetInfoKey(buf, "clients", va("%i", info->players), bufsize);
if (info->adr.type == NA_IPV6 && info->adr.prot == NP_DGRAM)
worldfuncs->SetInfoKey(buf, "nettype", "2", bufsize);
else if (info->adr.type == NA_IP && info->adr.prot == NP_DGRAM)
worldfuncs->SetInfoKey(buf, "nettype", "1", bufsize);
else
worldfuncs->SetInfoKey(buf, "nettype", "", bufsize);
VM_LONG(ret) = true;
}
}
else
strcpy(buf, "");
}
break;
case UI_LAN_UPDATEVISIBLEPINGS:
return masterfuncs->QueryServers(); //return true while we're still going.
case UI_LAN_RESETPINGS:
return 0;
case UI_LAN_ADDSERVER:
return 0;
case UI_LAN_REMOVESERVER:
return 0;
case UI_LAN_SERVERSTATUS:
//*address, *status, statuslen
return 0;
case UI_LAN_COMPARESERVERS:
{
hostcachekey_t q3tofte[] = {SLKEY_NAME, SLKEY_MAP, SLKEY_NUMHUMANS, SLKEY_GAMEDIR, SLKEY_PING, 0};
qboolean keyisstring[] = {true, true, false, true, false, false};
//int source = VM_LONG(arg[0]);
int key = bound(0, VM_LONG(arg[1]), countof(q3tofte));
int sortdir = VM_LONG(arg[2]);
serverinfo_t *s1 = masterfuncs->InfoForNum(VM_LONG(arg[3]));
serverinfo_t *s2 = masterfuncs->InfoForNum(VM_LONG(arg[4]));
if (keyisstring[key])
ret = strcasecmp(masterfuncs->ReadKeyString(s2, q3tofte[key]), masterfuncs->ReadKeyString(s1, q3tofte[key]));
else
{
ret = masterfuncs->ReadKeyFloat(s2, q3tofte[key]) - masterfuncs->ReadKeyFloat(s1, q3tofte[key]);
if (ret < 0)
ret = -1;
else if (ret > 0)
ret = 1;
}
if (sortdir)
ret = -ret;
}
return ret;
case UI_LAN_GETSERVERCOUNT: //LAN Get server count
//int (int source)
masterfuncs->CheckPollSockets();
VM_LONG(ret) = masterfuncs->TotalCount();
break;
case UI_LAN_GETSERVERADDRESSSTRING: //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;
char adrbuf[MAX_ADR_SIZE];
serverinfo_t *info = masterfuncs->InfoForNum(VM_LONG(arg[1]));
strcpy(buf, "");
if (info)
{
adr = masterfuncs->ServerToString(adrbuf, sizeof(adrbuf), info);
if (strlen(adr) < VM_LONG(arg[3]))
{
strcpy(buf, adr);
VM_LONG(ret) = true;
}
}
}
break;
case UI_LAN_LOADCACHEDSERVERS:
break;
case UI_LAN_SAVECACHEDSERVERS:
break;
case UI_LAN_GETSERVERPING:
return 50;
case UI_LAN_GETSERVERINFO:
if (VM_OOB(arg[2], arg[3]) || !arg[3])
break; //out of bounds.
{
//int source = VM_LONG(arg[0]);
int servernum = VM_LONG(arg[1]);
char *out = VM_POINTER(arg[2]);
int maxsize = VM_LONG(arg[3]);
char adr[MAX_ADR_SIZE];
serverinfo_t *info = masterfuncs->InfoForNum(servernum);
*out = 0;
if (info)
{
worldfuncs->SetInfoKey(out, "hostname", info->name, maxsize);
worldfuncs->SetInfoKey(out, "mapname", info->map, maxsize);
worldfuncs->SetInfoKey(out, "clients", va("%i", info->players), maxsize);
worldfuncs->SetInfoKey(out, "sv_maxclients", va("%i", info->maxplayers), maxsize);
worldfuncs->SetInfoKey(out, "ping", va("%i", info->ping), maxsize);
// worldfuncs->SetInfoKey(out, "minping", info->map, maxsize);
// worldfuncs->SetInfoKey(out, "maxping", info->map, maxsize);
// worldfuncs->SetInfoKey(out, "game", info->map, maxsize);
// worldfuncs->SetInfoKey(out, "gametype", info->map, maxsize);
// worldfuncs->SetInfoKey(out, "nettype", info->map, maxsize);
worldfuncs->SetInfoKey(out, "addr", masterfuncs->AdrToString(adr, sizeof(adr), &info->adr), maxsize);
// worldfuncs->SetInfoKey(out, "punkbuster", info->map, maxsize);
// worldfuncs->SetInfoKey(out, "g_needpass", info->map, maxsize);
// worldfuncs->SetInfoKey(out, "g_humanplayers", info->map, maxsize);
}
}
break;
case UI_LAN_MARKSERVERVISIBLE:
/*not implemented*/
return 0;
case UI_LAN_SERVERISVISIBLE:
return 1;
#endif
case UI_CVAR_REGISTER:
if (VM_OOB(arg[0], sizeof(q3vmcvar_t)))
break; //out of bounds.
return VMQ3_Cvar_Register(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_LONG(arg[3]));
case UI_CVAR_UPDATE:
if (VM_OOB(arg[0], sizeof(q3vmcvar_t)))
break; //out of bounds.
return VMQ3_Cvar_Update(VM_POINTER(arg[0]));
case UI_MEMORY_REMAINING:
VM_LONG(ret) = 1024*1024*8;//Hunk_LowMemAvailable();
break;
case UI_GET_CDKEY: //get cd key
{
cvar_t *cvar = cvarfuncs->GetNVFDG("cl_cdkey", "", CVAR_ARCHIVE, "Quake3 auth", "Q3 Compat");
char *keydest = VM_POINTER(arg[0]);
if (!cvar || (int)arg[0] + VM_LONG(arg[1]) >= mask || VM_POINTER(arg[0]) < offset)
break; //out of bounds.
Q_strncpyz(keydest, cvar->string, VM_LONG(arg[1]));
}
break;
case UI_SET_CDKEY: //set cd key
{
char *keysrc = VM_POINTER(arg[0]);
if ((int)arg[0] + strlen(keysrc) >= mask || VM_POINTER(arg[0]) < offset)
break; //out of bounds.
cvarfuncs->SetString("cl_cdkey", keysrc);
}
break;
case UI_REAL_TIME:
VALIDATEPOINTER(arg[0], sizeof(q3time_t));
return Q3VM_GetRealtime(VM_POINTER(arg[0]));
case UI_VERIFY_CDKEY:
VM_LONG(ret) = true;
break;
case UI_SET_PBCLSTATUS:
break;
// standard Q3
case UI_MEMSET:
{
void *dest = VM_POINTER(arg[0]);
if ((int)arg[0] + arg[2] >= mask || dest < offset)
break; //out of bounds.
memset(dest, arg[1], arg[2]);
}
break;
case UI_MEMCPY:
{
void *dest = VM_POINTER(arg[0]);
void *src = VM_POINTER(arg[1]);
if ((int)arg[0] + arg[2] >= mask || VM_POINTER(arg[0]) < offset)
break; //out of bounds.
memcpy(dest, src, arg[2]);
}
break;
case UI_STRNCPY:
{
void *dest = VM_POINTER(arg[0]);
void *src = VM_POINTER(arg[1]);
if (arg[0] + arg[2] >= mask || VM_POINTER(arg[0]) < offset)
break; //out of bounds.
Q_strncpyS(dest, src, 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_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].rbottomcolor;
vci->frags = cl.players[i].frags;
Q_strncpyz(vci->name, cl.players[i].name, UIMAX_SCOREBOARDNAME);
vci->ping = cl.players[i].ping;
vci->pl = cl.players[i].pl;
vci->starttime = cl.players[i].entertime;
vci->topcolour = cl.players[i].rtopcolor;
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 = vsize[0];
vi->height = vsize[1];
vi->refreshrate = 60;
vi->fullscreen = 1;
Q_strncpyz(vi->renderername, q_renderername, sizeof(vi->renderername));
}
break;
*/
#if 1//def BOTLIB_STATIC
case UI_PC_ADD_GLOBAL_DEFINE:
return botlib->PC_AddGlobalDefine(VM_POINTER(arg[0]));
case UI_PC_LOAD_SOURCE:
return botlib->PC_LoadSourceHandle(VM_POINTER(arg[0]));
case UI_PC_FREE_SOURCE:
return botlib->PC_FreeSourceHandle(VM_LONG(arg[0]));
case UI_PC_READ_TOKEN:
return botlib->PC_ReadTokenHandle(VM_LONG(arg[0]), VM_POINTER(arg[1]));
case UI_PC_SOURCE_FILE_AND_LINE:
return botlib->PC_SourceFileAndLine(VM_LONG(arg[0]), VM_POINTER(arg[1]), VM_POINTER(arg[2]));
#else
case UI_PC_ADD_GLOBAL_DEFINE:
Con_Printf("UI_PC_ADD_GLOBAL_DEFINE not supported\n");
break;
case UI_PC_SOURCE_FILE_AND_LINE:
Script_Get_File_And_Line(arg[0], VM_POINTER(arg[1]), VM_POINTER(arg[2]));
break;
case UI_PC_LOAD_SOURCE:
return Script_LoadFile(VM_POINTER(arg[0]));
case UI_PC_FREE_SOURCE:
Script_Free(arg[0]);
break;
case UI_PC_READ_TOKEN:
//fixme: memory protect.
return Script_Read(arg[0], VM_POINTER(arg[1]));
#endif
case UI_CIN_PLAYCINEMATIC:
return UI_Cin_Play(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), VM_LONG(arg[3]), VM_LONG(arg[4]), VM_LONG(arg[5]));
case UI_CIN_STOPCINEMATIC:
return UI_Cin_Stop(VM_LONG(arg[0]));
case UI_CIN_RUNCINEMATIC:
return UI_Cin_Run(VM_LONG(arg[0]));
case UI_CIN_DRAWCINEMATIC:
return UI_Cin_Draw(VM_LONG(arg[0]));
case UI_CIN_SETEXTENTS:
return UI_Cin_SetExtents(VM_LONG(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), VM_LONG(arg[3]), VM_LONG(arg[4]));
#ifndef _DEBUG
default:
#endif
Con_Printf("Q3UI: Unknown system trap: %i\n", (int)fn);
return 0;
}
return ret;
}
static int UI_SystemCallsVM(void *offset, quintptr_t mask, int fn, const int *arg)
{ //this is so we can use edit and continue properly (vc doesn't like function pointers for edit+continue)
#if __WORDSIZE == 32
return UI_SystemCalls(offset, mask, fn, (qintptr_t*)arg);
#else
qintptr_t args[9];
args[0]=arg[0];
args[1]=arg[1];
args[2]=arg[2];
args[3]=arg[3];
args[4]=arg[4];
args[5]=arg[5];
args[6]=arg[6];
args[7]=arg[7];
args[8]=arg[8];
return UI_SystemCalls(offset, mask, fn, args);
#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 qintptr_t EXPORT_FN UI_SystemCallsNative(qintptr_t arg, ...)
{
qintptr_t args[9];
va_list argptr;
va_start(argptr, arg);
args[0]=va_arg(argptr, qintptr_t);
args[1]=va_arg(argptr, qintptr_t);
args[2]=va_arg(argptr, qintptr_t);
args[3]=va_arg(argptr, qintptr_t);
args[4]=va_arg(argptr, qintptr_t);
args[5]=va_arg(argptr, qintptr_t);
args[6]=va_arg(argptr, qintptr_t);
args[7]=va_arg(argptr, qintptr_t);
args[8]=va_arg(argptr, qintptr_t);
va_end(argptr);
return UI_SystemCalls(NULL, ~(quintptr_t)0, arg, args);
}
static void UI_Release(menu_t *m, qboolean forced)
{
keycatcher &= ~2;
}
static void UI_DrawMenu(menu_t *m)
{
if (uivm)
{
float vsize[2];
if (drawfuncs->GetVideoSize(vsize, NULL) && (ui_size[0] != vsize[0] || ui_size[1] != vsize[1]))
{ //should probably just rescale stuff instead.
qboolean hadfocus = keycatcher&2;
keycatcher &= ~2;
ui_size[0] = vsize[0];
ui_size[1] = vsize[1];
vmfuncs->Call(uivm, UI_INIT);
if (hadfocus)
{
if (ccs.state)
vmfuncs->Call(uivm, UI_SET_ACTIVE_MENU, 2);
else
vmfuncs->Call(uivm, UI_SET_ACTIVE_MENU, 1);
}
}
vmfuncs->Call(uivm, UI_REFRESH, plugfuncs->GetMilliseconds());
uimenu.isopaque = vmfuncs->Call(uivm, UI_IS_FULLSCREEN);
}
}
qboolean UI_KeyPress(struct menu_s *m, qboolean isdown, unsigned int devid, int key, int unicode)
{
// qboolean result;
if (!uivm)
return false;
// if (key_dest == key_menu)
// return false;
if (!(keycatcher&2))
{
if (key == K_ESCAPE && isdown)
{
UI_OpenMenu();
return true;
}
return false;
}
/* if you change this here, it'll have to be changed in cl_cg.c too */
switch (key)
{
/* all these get interpreted as enter in Q3's UI... */
case K_JOY1:
case K_JOY2:
case K_JOY3:
case K_JOY4:
case K_AUX1:
case K_AUX2:
case K_AUX3:
case K_AUX4:
case K_AUX5:
case K_AUX6:
case K_AUX7:
case K_AUX8:
case K_AUX9:
case K_AUX10:
case K_AUX11:
case K_AUX14:
case K_AUX15:
case K_AUX16:
return true;
break;
/* Q3 doesn't know about these keys, remap them */
case K_GP_START:
key = K_ESCAPE;
break;
case K_GP_DPAD_UP:
key = K_UPARROW;
break;
case K_GP_DPAD_DOWN:
key = K_DOWNARROW;
break;
case K_GP_DPAD_LEFT:
key = K_LEFTARROW;
break;
case K_GP_DPAD_RIGHT:
key = K_RIGHTARROW;
break;
case K_GP_A:
key = K_ENTER;
break;
case K_GP_B:
key = K_ESCAPE;
break;
case K_GP_X:
key = K_BACKSPACE;
break;
}
if (key && key < 1024)
/*result = */vmfuncs->Call(uivm, UI_KEY_EVENT, key, isdown);
if (unicode && unicode < 1024)
/*result = */vmfuncs->Call(uivm, UI_KEY_EVENT, unicode|1024, isdown);
return true;
}
static qboolean UI_MousePosition(struct menu_s *m, qboolean abs, unsigned int devid, float x, float y)
{ //q3ui is a peice of poo and only accepts relative mouse movements.
//which it then clamps arbitrarily
//which means we can't use hardware cursors
//which results in clumsyness when switching between q3ui and everything else.
if (uivm && !abs)
{
int px = x, py = y;
vmfuncs->Call(uivm, UI_MOUSE_EVENT, px, py);
return true;
}
return false;
}
void UI_Reset(void)
{
inputfuncs->Menu_Unlink(&uimenu, true);
if (!drawfuncs->GetVideoSize(NULL,NULL)) //no renderer loaded
UI_Stop();
else if (uivm)
vmfuncs->Call(uivm, UI_INIT);
}
void UI_Stop (void)
{
inputfuncs->Menu_Unlink(&uimenu, true);
if (uivm)
{
vmfuncs->Call(uivm, UI_SHUTDOWN);
vmfuncs->Destroy(uivm);
VM_fcloseall(0);
uivm = NULL;
//mimic Q3 and save the config if anything got changed.
//note that q3 checks every frame. we only check when the ui is closed.
cmdfuncs->AddText("cfg_save_ifmodified\n", true);
#if defined(CL_MASTER)
masterfuncs->WriteServers();
#endif
}
}
void UI_Start (void)
{
int i;
int apiversion;
if (!cl_shownet_ptr)
return; //make sure UI_Init was called. if it wasn't then something messed up and we can't do client stuff.
if (!drawfuncs->GetVideoSize(ui_size,NULL))
return;
UI_Stop();
for (i = 0; i < MAX_PINGREQUESTS; i++)
ui_pings[i].adr.type = NA_INVALID;
uimenu.drawmenu = UI_DrawMenu;
uimenu.mousemove = UI_MousePosition;
uimenu.keyevent = UI_KeyPress;
uimenu.release = UI_Release;
uimenu.lowpriority = true;
SV_InitBotLib();
uivm = vmfuncs->Create("ui", cvarfuncs->GetFloat("com_gamedirnativecode")?UI_SystemCallsNative:NULL, "vm/ui", UI_SystemCallsVM);
if (uivm)
{
apiversion = vmfuncs->Call(uivm, UI_GETAPIVERSION, 6);
if (apiversion != 4 && apiversion != 6) //make sure we can run the thing
{
Con_Printf("User-Interface VM uses incompatible API version (%i)\n", apiversion);
vmfuncs->Destroy(uivm);
VM_fcloseall(0);
uivm = NULL;
return;
}
vmfuncs->Call(uivm, UI_INIT);
UI_OpenMenu();
}
}
void UI_Restart_f(void)
{
char arg1[256];
cmdfuncs->Argv(1, arg1, sizeof(arg1));
UI_Stop();
if (strcmp(arg1, "off"))
{
UI_Start();
if (uivm)
{
if (ccs.state)
vmfuncs->Call(uivm, UI_SET_ACTIVE_MENU, 2);
else
vmfuncs->Call(uivm, UI_SET_ACTIVE_MENU, 1);
}
}
}
qboolean UI_OpenMenu(void)
{
if (!uivm)
UI_Start();
if (uivm)
{
if (ccs.state)
vmfuncs->Call(uivm, UI_SET_ACTIVE_MENU, 2);
else
vmfuncs->Call(uivm, UI_SET_ACTIVE_MENU, 1);
return true;
}
return false;
}
qboolean UI_ConsoleCommand(void)
{
if (uivm)
return vmfuncs->Call(uivm, UI_CONSOLE_COMMAND, plugfuncs->GetMilliseconds());
return false;
}
qboolean UI_IsRunning(void)
{
if (uivm)
return true;
return false;
}
vm_t *UI_GetUIVM(void)
{
return uivm;
}
void UI_Init (void)
{
cl_shownet_ptr = cvarfuncs->GetNVFDG("cl_shownet", "", 0, NULL, "Q3 Compat");
cl_c2sdupe_ptr = cvarfuncs->GetNVFDG("cl_c2sdupe", "", 0, NULL, "Q3 Compat");
cl_nodelta_ptr = cvarfuncs->GetNVFDG("cl_nodelta", "", 0, NULL, "Q3 Compat");
cmdfuncs->AddCommand("ui_restart", UI_Restart_f, "Reload the Q3-based User Interface module");
}
#endif