diff --git a/engine/Makefile b/engine/Makefile index 3ac81db4..45e46f41 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -226,13 +226,13 @@ endif ifeq ($(FTE_TARGET),) #user didn't specify prefered target - ifneq ($(shell $(CC) -v 2>&1 | grep mingw),) - FTE_TARGET=win32 - endif - ifneq ($(shell $(CC) -v 2>&1 | grep cygwin),) + ifneq ($(shell uname 2>&1 | grep CYGWIN),) FTE_TARGET=cygwin ANDROID_SCRIPT=android.bat endif + ifneq ($(shell $(CC) -v 2>&1 | grep mingw),) + FTE_TARGET=win32 + endif ifeq ($(FTE_TARGET),) #still not set ifeq ($(shell uname),Linux) @@ -256,7 +256,7 @@ ifeq ($(FTE_TARGET),) #user didn't specify prefered target #else I've no idea what it is you're running endif - FTE_TARGET ?= sdl #so go for sdl. + FTE_TARGET ?= unk #so go for sdl. endif ifneq ($(shell ls|grep config.h),) @@ -323,22 +323,22 @@ ifeq ($(FTE_TARGET),macosx) endif BASELDFLAGS ?= -lm -ldl -lpthread -ifeq ($(shell echo $(FTE_TARGET)|grep -v win),) - BASELDFLAGS=-lm - MINGW_LIBS_DIR=$(LIBS_DIR)/mingw-libs - - ifeq ($(shell echo $(FTE_TARGET)|grep -v win64),) - MINGW_LIBS_DIR=$(LIBS_DIR)/mingw64-libs - endif - - IMAGELDFLAGS=$(MINGW_LIBS_DIR)/libpng.a $(MINGW_LIBS_DIR)/libz.a $(MINGW_LIBS_DIR)/libjpeg.a - OGGVORBISLDFLAGS=$(MINGW_LIBS_DIR)/libvorbisfile.a $(MINGW_LIBS_DIR)/libvorbis.a $(MINGW_LIBS_DIR)/libogg.a - - ifeq ($(shell echo $(FTE_TARGET)|grep -v -i _SDL),) - RELEASE_CFLAGS+= -D_SDL - SDL_LDFLAGS=$(MINGW_LIBS_DIR)/libSDL.a $(MINGW_LIBS_DIR)/libSDLmain.a -L./libs/mingw64-libs - endif -endif +#ifeq ($(shell echo $(FTE_TARGET)|grep -v win),) +# BASELDFLAGS=-lm +# MINGW_LIBS_DIR=$(LIBS_DIR)/mingw-libs +# +# ifeq ($(shell echo $(FTE_TARGET)|grep -v win64),) +# MINGW_LIBS_DIR=$(LIBS_DIR)/mingw64-libs +# endif +# +# IMAGELDFLAGS=$(MINGW_LIBS_DIR)/libpng.a $(MINGW_LIBS_DIR)/libz.a $(MINGW_LIBS_DIR)/libjpeg.a +# OGGVORBISLDFLAGS=$(MINGW_LIBS_DIR)/libvorbisfile.a $(MINGW_LIBS_DIR)/libvorbis.a $(MINGW_LIBS_DIR)/libogg.a +# +# ifeq ($(shell echo $(FTE_TARGET)|grep -v -i _SDL),) +# RELEASE_CFLAGS+= -D_SDL +# SDL_LDFLAGS=$(MINGW_LIBS_DIR)/libSDL.a $(MINGW_LIBS_DIR)/libSDLmain.a -L./libs/mingw64-libs +# endif +#endif IMAGELDFLAGS ?= -lpng -ljpeg OGGVORBISLDFLAGS ?= -lvorbisfile -lvorbis -logg @@ -617,6 +617,7 @@ COMMON_OBJS = \ log.o \ net_chan.o \ net_wins.o \ + net_ice.o \ zone.o \ qvm.o \ r_d3.o \ @@ -1101,20 +1102,20 @@ ifeq ($(FTE_TARGET),cygwin) GLCL_OBJS=$(GL_OBJS) $(D3DGL_OBJS) $(GLQUAKE_OBJS) $(BOTLIB_OBJS) $(SPEEX_OBJS) gl_vidlinuxglx.o snd_linux.o cd_null.o sys_linux.o sys_linux_threads.o GL_EXE_NAME=../fteqwglcyg$(EXEPOSTFIX) GLCL_EXE_NAME=../fteqwclglcyg$(EXEPOSTFIX) - GL_LDFLAGS=$(GLLDFLAGS) $(XLDFLAGS) $(OGGVORBISLDFLAGS) - GL_CFLAGS=$(GLCFLAGS) -I/usr/X11R6/include $(SPEEXCFLAGS) -DLIBVORBISFILE_STATIC + GL_LDFLAGS=$(GLLDFLAGS) $(XLDFLAGS) $(OGGVORBISLDFLAGS) -lz -lltdl + GL_CFLAGS=$(GLCFLAGS) -I/usr/X11R6/include $(SPEEXCFLAGS) -DLIBVORBISFILE_STATIC -DUSE_LIBTOOL GLB_DIR=gl_cygwin GLCL_DIR=glcl_cygwin MCL_OBJS=$(D3DGL_OBJS) $(GLQUAKE_OBJS) $(SOFTWARE_OBJS) $(BOTLIB_OBJS) $(SPEEX_OBJS) gl_vidlinuxglx.o snd_linux.o cd_null.o sys_linux.o sys_linux_threads.o M_EXE_NAME=../fteqwcyg$(EXEPOSTFIX) MCL_EXE_NAME=../fteqwclcyg$(EXEPOSTFIX) - M_LDFLAGS=$(GLLDFLAGS) $(XLDFLAGS) $(OGGVORBISLDFLAGS) - M_CFLAGS=$(GLCFLAGS) $(SPEEXCFLAGS) -DLIBVORBISFILE_STATIC + M_LDFLAGS=$(GLLDFLAGS) $(XLDFLAGS) $(OGGVORBISLDFLAGS) -lz -lltdl + M_CFLAGS=$(GLCFLAGS) $(SPEEXCFLAGS) -DLIBVORBISFILE_STATIC -DUSE_LIBTOOL MB_DIR=m_cygwin MCL_DIR=mcl_cygwin - + LIBS_DIR = $(BASE_DIR)/libs MINGL_EXE_NAME=../fteqwminglcyg$(EXEPOSTFIX) MINGL_DIR=mingl_cygwin diff --git a/engine/client/cd_win.c b/engine/client/cd_win.c index b7d7da2f..c8261489 100644 --- a/engine/client/cd_win.c +++ b/engine/client/cd_win.c @@ -81,7 +81,7 @@ int CDAudio_GetAudioDiskInfo(void) qboolean CDAudio_Startup(void) { DWORD dwReturn; - static MCI_OPEN_PARMS mciOpenParms; + static MCI_OPEN_PARMSA mciOpenParms; static MCI_SET_PARMS mciSetParms; if (initializefailed) @@ -269,6 +269,9 @@ void CDAudio_Pause(void) DWORD dwReturn; static MCI_GENERIC_PARMS mciGenericParms; + if (!pollneeded) + return; + mciGenericParms.dwCallback = (DWORD_PTR)mainwindow; dwReturn = mciSendCommand(wDeviceID, MCI_PAUSE, 0, (DWORD_PTR)(LPVOID) &mciGenericParms); if (dwReturn) diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index 0a48f038..22797bc6 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -1797,7 +1797,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) if (cls.demorecording) CL_WriteDemoCmd(cmd); - Con_DPrintf("generated sequence %i\n", cl.movesequence); +// Con_DPrintf("generated sequence %i\n", cl.movesequence); cl.movesequence++; #ifdef IRCCONNECT diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 59283a87..9405b4e2 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -1804,7 +1804,7 @@ void CL_FullInfo_f (void) if (!*s) { - Con_TPrintf (TL_KEYHASNOVALUE); + Con_Printf ("key %s has no value\n", key); return; } @@ -1899,7 +1899,7 @@ void CL_SetInfo_f (void) return; } - Con_TPrintf (TL_STARKEYPROTECTED); + Con_Printf ("Can't set * keys\n"); return; } @@ -3458,7 +3458,7 @@ void VARGS Host_EndGame (char *message, ...) vsnprintf (string,sizeof(string)-1, message,argptr); va_end (argptr); Con_TPrintf (TLC_CLIENTCON_ERROR_ENDGAME, string); - Con_TPrintf (TL_NL); + Con_Printf ("\n"); SCR_EndLoadingPlaque(); @@ -4349,9 +4349,7 @@ void Host_FinishLoading(void) CL_ArgumentOverrides(); -Con_TPrintf (TL_NL); - Con_Printf ("%s", version_string()); -Con_TPrintf (TL_NL); + Con_Printf ("\n%s\n", version_string()); Con_DPrintf("This program is free software; you can redistribute it and/or " "modify it under the terms of the GNU General Public License " diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 45d3307a..df9bbc12 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -3308,6 +3308,7 @@ void CL_ParseModellist (qboolean lots) Host_EndGame ("Server sent too many model_precache"); strcpy (cl.model_name[nummodels], str); + //qw has a special network protocol for spikes. if (!strcmp(cl.model_name[nummodels],"progs/spike.mdl")) cl_spikeindex = nummodels; if (!strcmp(cl.model_name[nummodels],"progs/player.mdl")) @@ -3319,12 +3320,13 @@ void CL_ParseModellist (qboolean lots) if (!strcmp(cl.model_name[nummodels],"progs/flag.mdl")) cl_flagindex = nummodels; + //rocket to grenade if (!strcmp(cl.model_name[nummodels],"progs/missile.mdl")) cl_rocketindex = nummodels; if (!strcmp(cl.model_name[nummodels],"progs/grenade.mdl")) cl_grenadeindex = nummodels; - + //cl_gibfilter if (!strcmp(cl.model_name[nummodels],"progs/gib1.mdl")) cl_gib1index = nummodels; if (!strcmp(cl.model_name[nummodels],"progs/gib2.mdl")) @@ -5271,7 +5273,7 @@ void CL_ParsePrecache(void) if (!model) Con_Printf("svc_precache: Mod_ForName(\"%s\") failed\n", s); cl.model_precache[i] = model; - strcpy (cl.model_name[i], s); + Q_strncpyz (cl.model_name[i], s, sizeof(cl.model_name[i])); cl.model_precaches_added = true; } @@ -5290,7 +5292,7 @@ void CL_ParsePrecache(void) if (!sfx) Con_Printf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); cl.sound_precache[i] = sfx; - strcpy (cl.sound_name[i], s); + Q_strncpyz (cl.sound_name[i], s, sizeof(cl.sound_name[i])); } else Con_Printf("svc_precache: sound index %i outside range %i...%i\n", i, 1, MAX_SOUNDS); @@ -5817,10 +5819,7 @@ void CLQW_ParseServerMessage (void) case svc_setpause: cl.paused = MSG_ReadByte (); - if (cl.paused) - CDAudio_Pause (); - else - CDAudio_Resume (); +// Media_SetPauseTrack(!!cl.paused); break; // case svc_ftesetclientpersist: diff --git a/engine/client/cl_plugin.inc b/engine/client/cl_plugin.inc index 90061610..0abf984d 100644 --- a/engine/client/cl_plugin.inc +++ b/engine/client/cl_plugin.inc @@ -70,16 +70,17 @@ typedef struct { //Make SURE that the engine has resolved all cvar pointers into globals before this happens. plugin_t *plugin; char name[64]; - qboolean picfromwad; + int type; + char *script; mpic_t *pic; } pluginimagearray_t; int pluginimagearraylen; pluginimagearray_t *pluginimagearray; -qintptr_t VARGS Plug_Draw_LoadImage(void *offset, quintptr_t mask, const qintptr_t *arg) +#include "shader.h" + +qintptr_t VARGS Plug_Draw_LoadImage(char *name, int type, char *script) { - char *name = VM_POINTER(arg[0]); - qboolean fromwad = arg[1]; int i; mpic_t *pic; @@ -109,7 +110,11 @@ qintptr_t VARGS Plug_Draw_LoadImage(void *offset, quintptr_t mask, const qintptr if (qrenderer != QR_NONE) { - if (fromwad) + if (type == 3) + pic = NULL; + else if (type == 2) + pic = R_RegisterShader(name, SUF_NONE, script); + else if (type) pic = R2D_SafePicFromWad(name); else pic = R2D_SafeCachePic(name); @@ -118,12 +123,59 @@ qintptr_t VARGS Plug_Draw_LoadImage(void *offset, quintptr_t mask, const qintptr pic = NULL; Q_strncpyz(pluginimagearray[i].name, name, sizeof(pluginimagearray[i].name)); - pluginimagearray[i].picfromwad = fromwad; + pluginimagearray[i].type = type; pluginimagearray[i].pic = pic; pluginimagearray[i].plugin = currentplug; + pluginimagearray[i].script = script?Z_StrDup(script):NULL; return i + 1; } +qbyte *Read32BitImageFile(qbyte *buf, int len, int *width, int *height, qboolean *hasalpha, char *fname); +qintptr_t VARGS Plug_Draw_LoadImageData(void *offset, quintptr_t mask, const qintptr_t *arg) +{ + qintptr_t ret = 0; + char *name = VM_POINTER(arg[0]); + char *mimetype = VM_POINTER(arg[1]); + void *codeddata = VM_POINTER(arg[2]); + unsigned int datalength = VM_LONG(arg[3]); + texid_t t; + qbyte *rgbdata; + unsigned int width, height; + + if (VM_OOB(arg[2], arg[3])) + return 0; + + if ((rgbdata = Read32BitImageFile(codeddata, datalength, &width, &height, NULL, name))) + { + name = va("%s/", name); + t = R_FindTexture(name, IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); + if (!TEXVALID(t)) + t = R_AllocNewTexture(name, width, height, IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); + if (TEXVALID(t)) + { + R_Upload(t, name, TF_RGBA32, rgbdata, NULL, width, height, IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); + ret = Plug_Draw_LoadImage(name, 3, NULL); + } + + BZ_Free(rgbdata); + } + return ret; +} +qintptr_t VARGS Plug_Draw_LoadImageShader(void *offset, quintptr_t mask, const qintptr_t *arg) +{ + char *name = VM_POINTER(arg[0]); + char *script = VM_POINTER(arg[1]); + return Plug_Draw_LoadImage(name, 2, script); +} +qintptr_t VARGS Plug_Draw_LoadImagePic(void *offset, quintptr_t mask, const qintptr_t *arg) +{ + char *name = VM_POINTER(arg[0]); + int type = arg[1]; + if (type != 0 && type != 1) + return 0; + return Plug_Draw_LoadImage(name, type, NULL); +} + void Plug_DrawReloadImages(void) { int i; @@ -172,7 +224,7 @@ qintptr_t VARGS Plug_Draw_Image(void *offset, quintptr_t mask, const qintptr_t * if (pluginimagearray[i].pic) pic = pluginimagearray[i].pic; - else if (pluginimagearray[i].picfromwad) + else if (pluginimagearray[i].type == 1) return 0; //wasn't loaded. else { @@ -214,6 +266,29 @@ qintptr_t VARGS Plug_Draw_Character(void *offset, quintptr_t mask, const qintptr Font_EndString(font_conchar); return 0; } +qintptr_t VARGS Plug_Draw_String(void *offset, quintptr_t mask, const qintptr_t *arg) +{ + int ipx, px, py; + conchar_t buffer[2048], *str; + if (qrenderer == QR_NONE) + return 0; + COM_ParseFunString(CON_WHITEMASK, VM_POINTER(arg[2]), buffer, sizeof(buffer), false); + str = buffer; + Font_BeginString(font_conchar, VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), &px, &py); + ipx = px; + while(*str) + { + if ((*str & CON_CHARMASK) == '\n') + py += Font_CharHeight(); + else if ((*str & CON_CHARMASK) == '\r') + px = ipx; + else + px = Font_DrawChar(px, py, *str); + str++; + } + Font_EndString(font_conchar); + return 0; +} qintptr_t VARGS Plug_Draw_Fill(void *offset, quintptr_t mask, const qintptr_t *arg) { @@ -525,9 +600,12 @@ void Plug_Client_Init(void) Plug_RegisterBuiltin("Menu_Control", Plug_Menu_Control, PLUG_BIF_NEEDSRENDERER); Plug_RegisterBuiltin("Key_GetKeyCode", Plug_Key_GetKeyCode, PLUG_BIF_NEEDSRENDERER); - Plug_RegisterBuiltin("Draw_LoadImage", Plug_Draw_LoadImage, PLUG_BIF_NEEDSRENDERER); + Plug_RegisterBuiltin("Draw_LoadImageData", Plug_Draw_LoadImageData, PLUG_BIF_NEEDSRENDERER); + Plug_RegisterBuiltin("Draw_LoadImageShader", Plug_Draw_LoadImageShader, PLUG_BIF_NEEDSRENDERER); + Plug_RegisterBuiltin("Draw_LoadImage", Plug_Draw_LoadImagePic, PLUG_BIF_NEEDSRENDERER); Plug_RegisterBuiltin("Draw_Image", Plug_Draw_Image, PLUG_BIF_NEEDSRENDERER); Plug_RegisterBuiltin("Draw_Character", Plug_Draw_Character, PLUG_BIF_NEEDSRENDERER); + Plug_RegisterBuiltin("Draw_String", Plug_Draw_String, PLUG_BIF_NEEDSRENDERER); Plug_RegisterBuiltin("Draw_Fill", Plug_Draw_Fill, PLUG_BIF_NEEDSRENDERER); Plug_RegisterBuiltin("Draw_Line", Plug_Draw_Line, PLUG_BIF_NEEDSRENDERER); Plug_RegisterBuiltin("Draw_Colourp", Plug_Draw_ColourP, PLUG_BIF_NEEDSRENDERER); diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index bc18e2a5..dc99f97a 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -747,7 +747,7 @@ static void CL_EntStateToPlayerCommand(usercmd_t *cmd, entity_state_t *state, fl // extra += realtime - cl.inframes[cl.validsequence&UPDATE_MASK].receivedtime; // extra += (cl.inframes[cl.validsequence&UPDATE_MASK].receivedtime - cl.inframes[cl.oldvalidsequence&UPDATE_MASK].receivedtime)*4; msec = 1000*extra; - Con_DPrintf("%i: age = %i, stale=%i\n", state->number, msec, state->u.q1.msec); +// Con_DPrintf("%i: age = %i, stale=%i\n", state->number, msec, state->u.q1.msec); msec += state->u.q1.msec; //this is the age on the server cmd->msec = bound(0, msec, 250); diff --git a/engine/client/console.c b/engine/client/console.c index 88ed8ec7..89598a9a 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -83,6 +83,9 @@ void Con_Finit (console_t *con) con->selstartline = NULL; con->selendline = NULL; + + con->defaultcharbits = CON_WHITEMASK; + con->parseflags = 0; } /*returns a bitmask: @@ -636,7 +639,7 @@ void Con_PrintCon (console_t *con, char *txt) conline_t *oc; conline_t *reuse; - COM_ParseFunString(CON_WHITEMASK, txt, expanded, sizeof(expanded), false); + COM_ParseFunString(con->defaultcharbits, txt, expanded, sizeof(expanded), con->parseflags); c = expanded; if (*c) @@ -645,7 +648,7 @@ void Con_PrintCon (console_t *con, char *txt) { conchar_t *o; - switch (*c & (CON_CHARMASK)) + switch (*c & (CON_CHARMASK|CON_HIDDEN)) { case '\r': con->cr = true; @@ -1239,13 +1242,32 @@ void Con_DrawNotify (void) conchar_t *starts[8]; conchar_t *ends[8]; conchar_t markup[MAXCMDLINE+64]; - conchar_t *c; - int lines, i; + conchar_t *c, *end; + char *foo = va(chat_team?"say_team: %s":"say: %s", chat_buffer?chat_buffer:""); + int lines, i, pos; Font_BeginString(font_conchar, 0, 0, &x, &y); y = con_main.notif_l * Font_CharHeight(); - c = COM_ParseFunString(CON_WHITEMASK, va(chat_team?"say_team: %s":"say: %s", chat_buffer), markup, sizeof(markup), true); - *c++ = (0xe00a+((int)(realtime*con_cursorspeed)&1))|CON_WHITEMASK; - lines = Font_LineBreaks(markup, c, Font_ScreenWidth(), 8, starts, ends); + + i = chat_team?10:5; + pos = strlen(foo)+i; + pos = min(pos, chat_bufferpos + i); + + //figure out where the cursor is, if its safe + i = foo[pos]; + foo[pos] = 0; + c = COM_ParseFunString(CON_WHITEMASK, foo, markup, sizeof(markup), PFS_KEEPMARKUP|PFS_FORCEUTF8); + foo[pos] = i; + + //k, build the string properly. + end = COM_ParseFunString(CON_WHITEMASK, foo, markup, sizeof(markup) - sizeof(markup[0]), PFS_KEEPMARKUP | PFS_FORCEUTF8); + + //and overwrite the cursor so that it blinks. + if (((int)(realtime*con_cursorspeed)&1)) + *c = 0xe00b|CON_WHITEMASK; + if (c == end) + end++; + + lines = Font_LineBreaks(markup, end, Font_ScreenWidth(), 8, starts, ends); for (i = 0; i < lines; i++) { x = 0; @@ -1444,7 +1466,7 @@ int Con_DrawAlternateConsoles(int lines) consshown++; } - if (lines == scr_conlines && consshown > 1) + if (lines == (int)scr_conlines && consshown > 1) { int mx, my, h; Font_BeginString(font_conchar, mousecursor_x, mousecursor_y, &mx, &my); @@ -1507,6 +1529,9 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in x = (Font_DrawChar (x, y, '^'|CON_WHITEMASK)-x)*4+x; } + if (!selactive) + selactive = 2; + //deactivate the selection if the start and end is outside if ( (selsx < sx && selex < sx) || @@ -1671,9 +1696,9 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in if (y >= seley) { send = sstart; - for (i = 0; i < linelength; i++) + for (i = 0; i < linelength; ) { - send += Font_CharWidth(s[i]); + send += Font_CharWidth(s[i++]); if (send > selex) break; @@ -1681,7 +1706,7 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in con->selendline = l; if (s) - con->selendoffset = (s+i+1) - (conchar_t*)(l+1); + con->selendoffset = (s+i) - (conchar_t*)(l+1); else con->selendoffset = 0; } @@ -1701,8 +1726,11 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in else con->selstartoffset = 0; } - R2D_ImagePaletteColour(0, 1.0); - R2D_FillBlock((sstart*vid.width)/vid.rotpixelwidth, (y*vid.height)/vid.rotpixelheight, ((send - sstart)*vid.width)/vid.rotpixelwidth, (Font_CharHeight()*vid.height)/vid.rotpixelheight); + if (selactive == 1) + { + R2D_ImagePaletteColour(0, 1.0); + R2D_FillBlock((sstart*vid.width)/(float)vid.rotpixelwidth, (y*vid.height)/(float)vid.rotpixelheight, ((send - sstart)*vid.width)/(float)vid.rotpixelwidth, (Font_CharHeight()*vid.height)/(float)vid.rotpixelheight); + } } } } @@ -1721,6 +1749,8 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in return y; } +void Draw_ExpandedString(float x, float y, conchar_t *str); + /* ================ Con_DrawConsole @@ -1795,6 +1825,57 @@ void Con_DrawConsole (int lines, qboolean noback) } Font_EndString(font_conchar); + + if (con_current->selstartline) + { + char *mouseover = Con_CopyConsole(false, true); + if (mouseover) + { + char *end = strstr(mouseover, "^]"); + char *info = strchr(mouseover, '\\'); + char *key; + if (!info) + info = ""; + *end = 0; + if (!Plug_ConsoleLinkMouseOver(mousecursor_x, mousecursor_y, mouseover+2, info)) + { + float x = mousecursor_x+8; + float y = mousecursor_y+8; + float ih = 0; + key = Info_ValueForKey(info, "tipimg"); + if (*key) + { + shader_t *s = R2D_SafeCachePic(key); + if (s) + { + R2D_Image(x, y, s->width, s->height, 0, 0, 1, 1, s); + ih = s->height; + x += s->width + 8; + } + } + key = Info_ValueForKey(info, "tip"); + if (*key) + { + //FIXME: draw a proper background. + //FIXME: support line breaks. + conchar_t buffer[2048], *starts[8], *ends[8]; + int lines, i, px, py; + Font_BeginString(font_conchar, x, y, &px, &py); + lines = Font_LineBreaks(buffer, COM_ParseFunString(CON_WHITEMASK|CON_NONCLEARBG, key, buffer, sizeof(buffer), false), 256, 8, starts, ends); + ih = max(Font_CharHeight()*lines, ih)/2; + y += ih - (Font_CharHeight()*lines)/2; + Font_BeginString(font_conchar, x, y, &px, &py); + for (i = 0; i < lines; i++) + { + Font_LineDraw(px, py, starts[i], ends[i]); + py += Font_CharHeight(); + } + Font_EndString(font_conchar); + } + } + } + Z_Free(mouseover); + } } void Con_DrawOneConsole(console_t *con, struct font_s *font, float fx, float fy, float fsx, float fsy) @@ -1833,7 +1914,7 @@ void Con_DrawOneConsole(console_t *con, struct font_s *font, float fx, float fy, } -char *Con_CopyConsole(qboolean nomarkup) +char *Con_CopyConsole(qboolean nomarkup, qboolean onlyiflink) { console_t *con = con_current; conchar_t *cur; @@ -1846,9 +1927,6 @@ char *Con_CopyConsole(qboolean nomarkup) if (!con->selstartline || !con->selendline) return NULL; - maxlen = 1024*1024; - result = Z_Malloc(maxlen+1); - // for (cur = (conchar_t*)(selstartline+1), finalendoffset = 0; cur < (conchar_t*)(selstartline+1) + selstartline->length; cur++, finalendoffset++) // result[finalendoffset] = *cur & 0xffff; @@ -1908,6 +1986,15 @@ char *Con_CopyConsole(qboolean nomarkup) } } + if (onlyiflink) + { + if (*cur != CON_LINKSTART) + return NULL; + } + + maxlen = 1024*1024; + result = Z_Malloc(maxlen+1); + outlen = 0; for (;;) { diff --git a/engine/client/image.c b/engine/client/image.c index 9a94be19..99dbcb92 100644 --- a/engine/client/image.c +++ b/engine/client/image.c @@ -163,7 +163,7 @@ qbyte *ReadTargaFile(qbyte *buf, int length, int *width, int *height, qboolean * tgaheader_t tgaheader; - if (buf[16] != 8 && buf[16] != 16 && buf[16] != 24 && buf[16] != 32) + if (length < 18 || buf[16] != 8 && buf[16] != 16 && buf[16] != 24 && buf[16] != 32) return NULL; //BUMMER! tgaheader.id_len = buf[0]; @@ -1631,6 +1631,9 @@ qbyte *ReadPCXFile(qbyte *buf, int length, int *width, int *height) // parse the PCX file // + if (length < sizeof(*pcx)) + return NULL; + pcx = (pcx_t *)buf; xmin = LittleShort(pcx->xmin); @@ -2462,7 +2465,7 @@ qbyte *Read32BitImageFile(qbyte *buf, int len, int *width, int *height, qboolean } #ifdef AVAIL_PNGLIB - if ((buf[0] == 137 && buf[1] == 'P' && buf[2] == 'N' && buf[3] == 'G') && (data = ReadPNGFile(buf, com_filesize, width, height, fname))) + if (len > 4 && (buf[0] == 137 && buf[1] == 'P' && buf[2] == 'N' && buf[3] == 'G') && (data = ReadPNGFile(buf, len, width, height, fname))) { TRACE(("dbg: Read32BitImageFile: png\n")); return data; @@ -2470,19 +2473,19 @@ qbyte *Read32BitImageFile(qbyte *buf, int len, int *width, int *height, qboolean #endif #ifdef AVAIL_JPEGLIB //jpeg jfif only. - if ((buf[0] == 0xff && buf[1] == 0xd8 && buf[2] == 0xff && buf[3] == 0xe0) && (data = ReadJPEGFile(buf, com_filesize, width, height))) + if (len > 4 && (buf[0] == 0xff && buf[1] == 0xd8 && buf[2] == 0xff && buf[3] == 0xe0) && (data = ReadJPEGFile(buf, len, width, height))) { TRACE(("dbg: Read32BitImageFile: jpeg\n")); return data; } #endif - if ((data = ReadPCXFile(buf, com_filesize, width, height))) + if ((data = ReadPCXFile(buf, len, width, height))) { TRACE(("dbg: Read32BitImageFile: pcx\n")); return data; } - if ((buf[0] == 'B' && buf[1] == 'M') && (data = ReadBMPFile(buf, com_filesize, width, height))) + if (len > 2 && (buf[0] == 'B' && buf[1] == 'M') && (data = ReadBMPFile(buf, len, width, height))) { TRACE(("dbg: Read32BitImageFile: bitmap\n")); return data; @@ -2610,22 +2613,28 @@ texid_t R_LoadHiResTexture(char *name, char *subpath, unsigned int flags) image_width = 0; image_height = 0; - COM_StripExtension(name, nicename, sizeof(nicename)); + if (flags & IF_EXACTEXTENSION) + Q_strncpyz(nicename, name, sizeof(nicename)); + else + COM_StripExtension(name, nicename, sizeof(nicename)); while((data = strchr(nicename, '*'))) { *data = '#'; } - snprintf(fname, sizeof(fname)-1, "%s/%s", subpath, name); /*should be safe if its null*/ - if (subpath && *subpath && !(flags & IF_REPLACE)) + if (subpath) { - tex = R_FindTexture(fname, flags); - if (TEXVALID(tex)) //don't bother if it already exists. + snprintf(fname, sizeof(fname)-1, "%s/%s", subpath, name); /*should be safe if its null*/ + if (*subpath && !(flags & IF_REPLACE)) { - image_width = tex.ref->width; - image_height = tex.ref->height; - return tex; + tex = R_FindTexture(fname, flags); + if (TEXVALID(tex)) //don't bother if it already exists. + { + image_width = tex.ref->width; + image_height = tex.ref->height; + return tex; + } } } if (!(flags & IF_SUBDIRONLY) && !(flags & IF_REPLACE)) @@ -2675,7 +2684,7 @@ texid_t R_LoadHiResTexture(char *name, char *subpath, unsigned int flags) for (i = 0; i < 6; i++) { tex = r_nulltex; - for (e = sizeof(tex_extensions)/sizeof(tex_extensions[0])-1; e >=0 ; e--) + for (e = (flags & IF_EXACTEXTENSION)?0:sizeof(tex_extensions)/sizeof(tex_extensions[0])-1; e >=0 ; e--) { if (!tex_extensions[e].enabled) continue; @@ -2751,7 +2760,7 @@ texid_t R_LoadHiResTexture(char *name, char *subpath, unsigned int flags) { if (!tex_path[i].enabled) continue; - for (e = sizeof(tex_extensions)/sizeof(tex_extensions[0])-1; e >=0 ; e--) + for (e = (flags & IF_EXACTEXTENSION)?0:sizeof(tex_extensions)/sizeof(tex_extensions[0])-1; e >=0 ; e--) { if (!tex_extensions[e].enabled) continue; @@ -2856,7 +2865,10 @@ texid_t R_LoadHiResTexture(char *name, char *subpath, unsigned int flags) if (!(flags & IF_SUBDIRONLY)) { /*still failed? attempt to load quake lmp files, which have no real format id*/ - snprintf(fname, sizeof(fname)-1, "%s%s", nicename, ".lmp"); + if (flags & IF_EXACTEXTENSION) + Q_strncpyz(fname, nicename, sizeof(fname)); + else + snprintf(fname, sizeof(fname)-1, "%s%s", nicename, ".lmp"); if ((buf = COM_LoadFile (fname, 5))) { extern cvar_t vid_hardwaregamma; @@ -2865,7 +2877,7 @@ texid_t R_LoadHiResTexture(char *name, char *subpath, unsigned int flags) { image_width = LittleLong(((int*)buf)[0]); image_height = LittleLong(((int*)buf)[1]); - if (image_width*image_height+8 == com_filesize) + if (image_width >= 4 && image_height >= 4 && image_width*image_height+8 == com_filesize) { tex = R_LoadTexture8(name, image_width, image_height, buf+8, flags, 1); } diff --git a/engine/client/in_win.c b/engine/client/in_win.c index 0af1ede0..a8d872ef 100644 --- a/engine/client/in_win.c +++ b/engine/client/in_win.c @@ -1142,7 +1142,7 @@ void INS_Init (void) Cmd_AddCommand ("force_centerview", Force_CenterView_f); Cmd_AddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f); - uiWheelMessage = RegisterWindowMessage ( "MSWHEEL_ROLLMSG" ); + uiWheelMessage = RegisterWindowMessageA ( "MSWHEEL_ROLLMSG" ); #ifdef USINGRAWINPUT Cvar_Register (&in_rawinput, "Input Controls"); diff --git a/engine/client/keys.c b/engine/client/keys.c index f96fb78b..abf09304 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -457,6 +457,8 @@ qboolean Key_GetConsoleSelectionBox(console_t *con, int *sx, int *sy, int *ex, i if (con->mousedown[2] == 1) { + //left-mouse. + //scroll the console with the mouse. trigger links on release. while (con->mousecursor[1] - con->mousedown[1] > 8 && con->display->older) { con->mousedown[1] += 8; @@ -476,13 +478,22 @@ qboolean Key_GetConsoleSelectionBox(console_t *con, int *sx, int *sy, int *ex, i } else if (con->mousedown[2] == 2) { + //right-mouse + //select. copy-to-clipboard on release. *sx = con->mousedown[0]; *sy = con->mousedown[1]; *ex = con->mousecursor[0]; *ey = con->mousecursor[1]; return true; } - return false; + else + { + *sx = con->mousecursor[0]; + *sy = con->mousecursor[1]; + *ex = con->mousecursor[0]; + *ey = con->mousecursor[1]; + return false; + } } /*insert the given text at the console input line at the current cursor pos*/ @@ -745,7 +756,7 @@ void Key_ConsoleRelease(console_t *con, int key, int unicode) con->mousedown[2] = 0; if (abs(con->mousedown[0] - con->mousecursor[0]) < 5 && abs(con->mousedown[1] - con->mousecursor[1]) < 5) { - buffer = Con_CopyConsole(false); + buffer = Con_CopyConsole(false, false); Con_Footerf(false, ""); if (!buffer) return; @@ -821,7 +832,7 @@ void Key_ConsoleRelease(console_t *con, int key, int unicode) if (key == K_MOUSE2 && con->mousedown[2] == 2) { con->mousedown[2] = 0; - buffer = Con_CopyConsole(true); //don't keep markup if we're copying to the clipboard + buffer = Con_CopyConsole(true, false); //don't keep markup if we're copying to the clipboard if (!buffer) return; Sys_SaveClipboard(buffer); @@ -925,6 +936,210 @@ static unsigned char *utf_right(unsigned char *start, unsigned char *cursor) return cursor; } +void Key_EntryInsert(unsigned char **line, int *linepos, char *instext) +{ + int i; + int len, olen; + char *old; + + if (!*instext) + return; + + old = (*line); + len = strlen(instext); + olen = strlen(old); + *line = BZ_Malloc(olen + len + 1); + memcpy(*line, old, *linepos); + memcpy(*line+*linepos, instext, len); + memcpy(*line+*linepos+len, old+*linepos, olen - *linepos+1); + Z_Free(old); + for (i = *linepos; i < *linepos+len; i++) + { + if ((*line)[i] == '\r') + (*line)[i] = ' '; + else if ((*line)[i] == '\n') + (*line)[i] = ';'; + } + *linepos += len; +} + +qboolean Key_EntryLine(unsigned char **line, int lineoffset, int *linepos, int key, unsigned int unicode) +{ + qboolean ctrl = keydown[K_LCTRL] || keydown[K_RCTRL]; + qboolean shift = keydown[K_LSHIFT] || keydown[K_RSHIFT]; + char utf8[8]; + + if (key == K_LEFTARROW || key == K_KP_LEFTARROW) + { + if (ctrl) + { + //ignore whitespace if we're at the end of the word + while (*linepos > 0 && (*line)[*linepos-1] == ' ') + *linepos = utf_left((*line)+lineoffset, (*line) + *linepos) - (*line); + //keep skipping until we find the start of that word + while (ctrl && *linepos > lineoffset && (*line)[*linepos-1] != ' ') + *linepos = utf_left((*line)+lineoffset, (*line) + *linepos) - (*line); + } + else + *linepos = utf_left((*line)+lineoffset, (*line) + *linepos) - (*line); + return true; + } + if (key == K_RIGHTARROW || key == K_KP_RIGHTARROW) + { + if ((*line)[*linepos]) + { + *linepos = utf_right((*line)+lineoffset, (*line) + *linepos) - (*line); + if (ctrl) + { + //skip over the word + while ((*line)[*linepos] && (*line)[*linepos] != ' ') + *linepos = utf_right((*line)+lineoffset, (*line) + *linepos) - (*line); + //as well as any trailing whitespace + while ((*line)[*linepos] == ' ') + *linepos = utf_right((*line)+lineoffset, (*line) + *linepos) - (*line); + } + return true; + } + else + unicode = ' '; + } + + if (key == K_DEL || key == K_KP_DEL) + { + if ((*line)[*linepos]) + { + int charlen = utf_right((*line)+lineoffset, (*line) + *linepos) - ((*line) + *linepos); + memmove((*line)+*linepos, (*line)+*linepos+charlen, strlen((*line)+*linepos+charlen)+1); + return true; + } + else + key = K_BACKSPACE; + } + + if (key == K_BACKSPACE) + { + if (*linepos > lineoffset) + { + int charlen = ((*line)+*linepos) - utf_left((*line)+lineoffset, (*line) + *linepos); + memmove((*line)+*linepos-charlen, (*line)+*linepos, strlen((*line)+*linepos)+1); + *linepos -= charlen; + } + if (!(*line)[lineoffset]) //oops? + con_commandmatch = 0; + return true; + } + + + + if (key == K_HOME || key == K_KP_HOME) + { + *linepos = lineoffset; + return true; + } + + if (key == K_END || key == K_KP_END) + { + *linepos = strlen(*line); + return true; + } + + //beware that windows translates ctrl+c and ctrl+v to a control char + if (((unicode=='C' || unicode=='c' || unicode==3) && ctrl) || (ctrl && key == K_INS)) + { + Sys_SaveClipboard(*line); + return true; + } + + if (((unicode=='V' || unicode=='v' || unicode==22) && ctrl) || (shift && key == K_INS)) + { + char *clipText = Sys_GetClipboard(); + if (clipText) + { + Key_EntryInsert(line, linepos, clipText); + Sys_CloseClipboard(clipText); + } + return true; + } + + if (unicode < ' ') + { + //if the user is entering control codes, then the ctrl+foo mechanism is probably unsupported by the unicode input stuff, so give best-effort replacements. + switch(unicode) + { + case 27/*'['*/: unicode = 0xe010; break; + case 29/*']'*/: unicode = 0xe011; break; + case 7/*'g'*/: unicode = 0xe086; break; + case 18/*'r'*/: unicode = 0xe087; break; + case 25/*'y'*/: unicode = 0xe088; break; + case 2/*'b'*/: unicode = 0xe089; break; + case 19/*'s'*/: unicode = 0xe080; break; + case 4/*'d'*/: unicode = 0xe081; break; + case 6/*'f'*/: unicode = 0xe082; break; + case 1/*'a'*/: unicode = 0xe083; break; + case 21/*'u'*/: unicode = 0xe01d; break; + case 9/*'i'*/: unicode = 0xe01e; break; + case 15/*'o'*/: unicode = 0xe01f; break; + case 10/*'j'*/: unicode = 0xe01c; break; + case 16/*'p'*/: unicode = 0xe09c; break; + case 13/*'m'*/: unicode = 0xe08b; break; + case 11/*'k'*/: unicode = 0xe08d; break; + case 14/*'n'*/: unicode = '\r'; break; + default: +// if (unicode) +// Con_Printf("escape code %i\n", unicode); + + //even if we don't print these, we still need to cancel them in the caller. + if (key == K_LALT || key == K_RALT || + key == K_LCTRL || key == K_RCTRL || + key == K_LSHIFT || key == K_RSHIFT) + return true; + return false; + } + } + else if (com_parseutf8.ival >= 0) //don't do this for iso8859-1. the major user of that is hexen2 which doesn't have these chars. + { + if (ctrl && !keydown[K_RALT]) + { + if (unicode >= '0' && unicode <= '9') + unicode = unicode - '0' + 0xe012; // yellow number + else switch (unicode) + { + case '[': unicode = 0xe010; break; + case ']': unicode = 0xe011; break; + case 'g': unicode = 0xe086; break; + case 'r': unicode = 0xe087; break; + case 'y': unicode = 0xe088; break; + case 'b': unicode = 0xe089; break; + case '(': unicode = 0xe080; break; + case '=': unicode = 0xe081; break; + case ')': unicode = 0xe082; break; + case 'a': unicode = 0xe083; break; + case '<': unicode = 0xe01d; break; + case '-': unicode = 0xe01e; break; + case '>': unicode = 0xe01f; break; + case ',': unicode = 0xe01c; break; + case '.': unicode = 0xe09c; break; + case 'B': unicode = 0xe08b; break; + case 'C': unicode = 0xe08d; break; + case 'n': unicode = '\r'; break; + } + } + + if (keydown[K_LALT] && unicode > 32 && unicode < 128) + unicode |= 0xe080; // red char + } + + unicode = utf8_encode(utf8, unicode, sizeof(utf8)-1); + if (unicode) + { + utf8[unicode] = 0; + Key_EntryInsert(line, linepos, utf8); + return true; + } + + return false; +} + /* ==================== Key_Console @@ -936,8 +1151,6 @@ qboolean Key_Console (console_t *con, unsigned int unicode, int key) { qboolean ctrl = keydown[K_LCTRL] || keydown[K_RCTRL]; qboolean shift = keydown[K_LSHIFT] || keydown[K_RSHIFT]; - char *clipText; - char utf8[8]; //weirdness for the keypad. if ((unicode >= '0' && unicode <= '9') || unicode == '.') @@ -959,9 +1172,8 @@ qboolean Key_Console (console_t *con, unsigned int unicode, int key) if ((key == K_MOUSE1 || key == K_MOUSE2)) { - int xpos, ypos; - xpos = (int)((con->mousecursor[0]*vid.width)/(vid.pixelwidth*8)); - ypos = (int)((con->mousecursor[1]*vid.height)/(vid.pixelheight*8)); + // int xpos = (int)((con->mousecursor[0]*vid.width)/(vid.pixelwidth*8)); + int ypos = (int)((con->mousecursor[1]*vid.height)/(vid.pixelheight*8)); con->mousedown[0] = con->mousecursor[0]; con->mousedown[1] = con->mousecursor[1]; if (ypos == 0 && con_mouseover) @@ -1024,66 +1236,6 @@ qboolean Key_Console (console_t *con, unsigned int unicode, int key) if (key != K_CTRL && key != K_SHIFT && con_commandmatch) con_commandmatch=1; - if (key == K_LEFTARROW || key == K_KP_LEFTARROW) - { - if (ctrl) - { - //ignore whitespace if we're at the end of the word - while (key_linepos > 0 && key_lines[edit_line][key_linepos-1] == ' ') - key_linepos = utf_left(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos) - key_lines[edit_line]; - //keep skipping until we find the start of that word - while (ctrl && key_linepos > 1 && key_lines[edit_line][key_linepos-1] != ' ') - key_linepos = utf_left(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos) - key_lines[edit_line]; - } - else - key_linepos = utf_left(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos) - key_lines[edit_line]; - return true; - } - if (key == K_RIGHTARROW || key == K_KP_RIGHTARROW) - { - if (key_lines[edit_line][key_linepos]) - { - key_linepos = utf_right(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos) - key_lines[edit_line]; - if (ctrl) - { - //skip over the word - while (key_lines[edit_line][key_linepos] && key_lines[edit_line][key_linepos] != ' ') - key_linepos = utf_right(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos) - key_lines[edit_line]; - //as well as any trailing whitespace - while (key_lines[edit_line][key_linepos] == ' ') - key_linepos = utf_right(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos) - key_lines[edit_line]; - } - return true; - } - else - unicode = ' '; - } - - if (key == K_DEL || key == K_KP_DEL) - { - if (key_lines[edit_line][key_linepos]) - { - int charlen = utf_right(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos) - (key_lines[edit_line] + key_linepos); - memmove(key_lines[edit_line]+key_linepos, key_lines[edit_line]+key_linepos+charlen, strlen(key_lines[edit_line]+key_linepos+charlen)+1); - return true; - } - else - key = K_BACKSPACE; - } - - if (key == K_BACKSPACE) - { - if (key_linepos > 1) - { - int charlen = (key_lines[edit_line]+key_linepos) - utf_left(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos); - memmove(key_lines[edit_line]+key_linepos-charlen, key_lines[edit_line]+key_linepos, strlen(key_lines[edit_line]+key_linepos)+1); - key_linepos -= charlen; - } - if (!key_lines[edit_line][1]) - con_commandmatch = 0; - return true; - } - if (key == K_UPARROW || key == K_KP_UPARROW) { do @@ -1169,140 +1321,65 @@ qboolean Key_Console (console_t *con, unsigned int unicode, int key) return true; } - if (key == K_HOME || key == K_KP_HOME) + if ((key == K_HOME || key == K_KP_HOME) && ctrl) { - if (ctrl) - con->display = con->oldest; - else - key_linepos = 1; + con->display = con->oldest; return true; } - if (key == K_END || key == K_KP_END) + if ((key == K_END || key == K_KP_END) && ctrl) { - if (ctrl) - con->display = con->current; - else - key_linepos = strlen(key_lines[edit_line]); + con->display = con->current; return true; } - //beware that windows translates ctrl+c and ctrl+v to a control char - if (((unicode=='C' || unicode=='c' || unicode==3) && ctrl) || (ctrl && key == K_INS)) - { - Sys_SaveClipboard(key_lines[edit_line]+1); - return true; - } - - if (((unicode=='V' || unicode=='v' || unicode==22) && ctrl) || (shift && key == K_INS)) - { - clipText = Sys_GetClipboard(); - if (clipText) - { - Key_ConsoleInsert(clipText); - Sys_CloseClipboard(clipText); - } - return true; - } - - if (unicode < ' ') - { - //if the user is entering control codes, then the ctrl+foo mechanism is probably unsupported by the unicode input stuff, so give best-effort replacements. - switch(unicode) - { - case 27/*'['*/: unicode = 0xe010; break; - case 29/*']'*/: unicode = 0xe011; break; - case 7/*'g'*/: unicode = 0xe086; break; - case 18/*'r'*/: unicode = 0xe087; break; - case 25/*'y'*/: unicode = 0xe088; break; - case 2/*'b'*/: unicode = 0xe089; break; - case 19/*'s'*/: unicode = 0xe080; break; - case 4/*'d'*/: unicode = 0xe081; break; - case 6/*'f'*/: unicode = 0xe082; break; - case 1/*'a'*/: unicode = 0xe083; break; - case 21/*'u'*/: unicode = 0xe01d; break; - case 9/*'i'*/: unicode = 0xe01e; break; - case 15/*'o'*/: unicode = 0xe01f; break; - case 10/*'j'*/: unicode = 0xe01c; break; - case 16/*'p'*/: unicode = 0xe09c; break; - case 13/*'m'*/: unicode = 0xe08b; break; - case 11/*'k'*/: unicode = 0xe08d; break; - case 14/*'n'*/: unicode = '\r'; break; - default: -// if (unicode) -// Con_Printf("escape code %i\n", unicode); - - //even if we don't print these, we still need to cancel them in the caller. - if (key == K_LALT || key == K_RALT || - key == K_LCTRL || key == K_RCTRL || - key == K_LSHIFT || key == K_RSHIFT) - return true; - return false; - } - } - else if (com_parseutf8.ival >= 0) //don't do this for iso8859-1. the major user of that is hexen2 which doesn't have these chars. - { - if (ctrl && !keydown[K_RALT]) - { - if (unicode >= '0' && unicode <= '9') - unicode = unicode - '0' + 0xe012; // yellow number - else switch (unicode) - { - case '[': unicode = 0xe010; break; - case ']': unicode = 0xe011; break; - case 'g': unicode = 0xe086; break; - case 'r': unicode = 0xe087; break; - case 'y': unicode = 0xe088; break; - case 'b': unicode = 0xe089; break; - case '(': unicode = 0xe080; break; - case '=': unicode = 0xe081; break; - case ')': unicode = 0xe082; break; - case 'a': unicode = 0xe083; break; - case '<': unicode = 0xe01d; break; - case '-': unicode = 0xe01e; break; - case '>': unicode = 0xe01f; break; - case ',': unicode = 0xe01c; break; - case '.': unicode = 0xe09c; break; - case 'B': unicode = 0xe08b; break; - case 'C': unicode = 0xe08d; break; - case 'n': unicode = '\r'; break; - } - } - - if (keydown[K_LALT] && unicode > 32 && unicode < 128) - unicode |= 0xe080; // red char - } - - unicode = utf8_encode(utf8, unicode, sizeof(utf8)-1); - if (unicode) - { - utf8[unicode] = 0; - Key_ConsoleInsert(utf8); - return true; - } - - return false; + return Key_EntryLine(&key_lines[edit_line], 1, &key_linepos, key, unicode); } //============================================================================ qboolean chat_team; -char chat_buffer[MAXCMDLINE]; -int chat_bufferlen = 0; +unsigned char *chat_buffer; +int chat_bufferpos; void Key_Message (int key, int unicode) { + if (!chat_buffer) + { + chat_buffer = BZ_Malloc(1); + chat_buffer[0] = 0; + chat_bufferpos = 0; + } if (key == K_ENTER || key == K_KP_ENTER) { - if (chat_buffer[0]) + if (chat_buffer && chat_buffer[0]) { //send it straight into the command. - Cmd_TokenizeString(va("%s %s", chat_team?"say_team":"say", chat_buffer), true, false); + char *line = chat_buffer; + char deutf8[8192]; + if (com_parseutf8.ival <= 0) + { + unsigned int unicode; + int err; + int len = 0; + while(*line) + { + unicode = utf8_decode(&err, line, &line); + if (com_parseutf8.ival < 0) + len += iso88591_encode(deutf8+len, unicode, sizeof(deutf8)-1 - len); + else + len += qchar_encode(deutf8+len, unicode, sizeof(deutf8)-1 - len); + } + deutf8[len] = 0; + line = deutf8; + } + + Cmd_TokenizeString(va("%s %s", chat_team?"say_team":"say", line), true, false); CL_Say(chat_team, ""); } Key_Dest_Remove(kdm_message); - chat_bufferlen = 0; + chat_bufferpos = 0; chat_buffer[0] = 0; return; } @@ -1310,29 +1387,12 @@ void Key_Message (int key, int unicode) if (key == K_ESCAPE) { Key_Dest_Remove(kdm_message); - chat_bufferlen = 0; + chat_bufferpos = 0; chat_buffer[0] = 0; return; } - if (key < 32 || key > 127) - return; // non printable - - if (key == K_BACKSPACE) - { - if (chat_bufferlen) - { - chat_bufferlen--; - chat_buffer[chat_bufferlen] = 0; - } - return; - } - - if (chat_bufferlen == sizeof(chat_buffer)-1) - return; // all full - - chat_buffer[chat_bufferlen++] = unicode; - chat_buffer[chat_bufferlen] = 0; + Key_EntryLine(&chat_buffer, 0, &chat_bufferpos, key, unicode); } //============================================================================ diff --git a/engine/client/keys.h b/engine/client/keys.h index cdbbc06f..a55f0f23 100644 --- a/engine/client/keys.h +++ b/engine/client/keys.h @@ -192,8 +192,8 @@ extern int key_repeats[K_MAX]; extern int key_count; // incremented every key event extern int key_lastpress; -extern char chat_buffer[]; -extern int chat_bufferlen; +extern unsigned char *chat_buffer; +extern int chat_bufferpos; extern qboolean chat_team; void Key_Event (int devid, int key, unsigned int unicode, qboolean down); diff --git a/engine/client/m_mp3.c b/engine/client/m_mp3.c index f49d7810..68de6cd1 100644 --- a/engine/client/m_mp3.c +++ b/engine/client/m_mp3.c @@ -402,12 +402,16 @@ void Media_SetPauseTrack(qboolean paused) { if (paused) { + if (!cdplayingtrack) + return; cdpausedtrack = cdplayingtrack; cdplayingtrack = 0; CDAudio_Pause(); } else { + if (!cdpausedtrack) + return; cdplayingtrack = cdpausedtrack; cdpausedtrack = 0; CDAudio_Resume(); diff --git a/engine/client/m_options.c b/engine/client/m_options.c index f720cd7b..7b2da800 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -553,6 +553,8 @@ const char *presetexec[] = "r_waterwarp 0;" "r_lightstylesmooth 0;" "r_part_density 0.25;" + "cl_nolerp 1;" + "r_lerpmuzzlehack 0;" , // fast options "gl_texturemode ln;" @@ -583,6 +585,8 @@ const char *presetexec[] = "r_waterwarp 1;" "r_drawflame 1;" "r_coronas 1;" + "cl_nolerp 0;" + "r_lerpmuzzlehack 1;" , // nice options "r_stains 0.75;" diff --git a/engine/client/m_script.c b/engine/client/m_script.c index 1133346e..301a1078 100644 --- a/engine/client/m_script.c +++ b/engine/client/m_script.c @@ -408,7 +408,7 @@ void M_MenuS_Combos_f (void) /* menuclear -menualias menucallback +conmenu menucallback menubox 0 0 320 8 menutext 0 0 "GO GO GO!!!" "radio21" diff --git a/engine/client/p_null.c b/engine/client/p_null.c index 55e381f4..ab975bd6 100644 --- a/engine/client/p_null.c +++ b/engine/client/p_null.c @@ -7,7 +7,7 @@ //returns a valid effect if its existance is known. static int PNULL_FindParticleType(char *name) { - Con_DPrintf("P_FindParticleType %s\n", name); +// Con_DPrintf("P_FindParticleType %s\n", name); return P_INVALID; } diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index 19bff10e..1e5e17c4 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -1342,31 +1342,31 @@ static void QCBUILTIN PF_R_GetViewFlag(pubprogfuncs_t *prinst, struct globalvars break; case VF_VIEWPORT: - r[0] = r_refdef.vrect.width; - r[1] = r_refdef.vrect.height; + r[0] = r_refdef.grect.width; + r[1] = r_refdef.grect.height; break; case VF_SIZE_X: - *r = r_refdef.vrect.width; + *r = r_refdef.grect.width; break; case VF_SIZE_Y: - *r = r_refdef.vrect.height; + *r = r_refdef.grect.height; break; case VF_SIZE: - r[0] = r_refdef.vrect.width; - r[1] = r_refdef.vrect.height; + r[0] = r_refdef.grect.width; + r[1] = r_refdef.grect.height; r[2] = 0; break; case VF_MIN_X: - *r = r_refdef.vrect.x; + *r = r_refdef.grect.x; break; case VF_MIN_Y: - *r = r_refdef.vrect.y; + *r = r_refdef.grect.y; break; case VF_MIN: - r[0] = r_refdef.vrect.x; - r[1] = r_refdef.vrect.y; + r[0] = r_refdef.grect.x; + r[1] = r_refdef.grect.y; break; case VF_DRAWWORLD: @@ -3415,7 +3415,7 @@ static void QCBUILTIN PF_cs_addprogs (pubprogfuncs_t *prinst, struct globalvars_ { newp = PR_LoadProgs(prinst, s, 0, NULL, 0); if (newp >= 0) - PR_AutoCvarSetup(csqcprogs); + PR_ProgsAdded(csqcprogs, newp, s); } G_FLOAT(OFS_RETURN) = newp; } @@ -5298,6 +5298,8 @@ qboolean CSQC_Init (qboolean anycsqc, qboolean csdatenabled, unsigned int checks csqctime = Sys_DoubleTime(); if (!csqcprogs) { + int csprogsnum = -1; + int csaddonnum = -1; in_sensitivityscale = 1; csqcmapentitydataloaded = true; csqcprogs = InitProgs(&csqcprogparms); @@ -5324,45 +5326,39 @@ qboolean CSQC_Init (qboolean anycsqc, qboolean csdatenabled, unsigned int checks csqc_isdarkplaces = false; if (csdatenabled || csqc_singlecheats || anycsqc) { - if (PR_LoadProgs(csqcprogs, "csprogs.dat", 22390, NULL, 0) >= 0) - loaded = true; - else + csprogsnum = PR_LoadProgs(csqcprogs, "csprogs.dat", 22390, NULL, 0); + if (csprogsnum == -1) { - if (PR_LoadProgs(csqcprogs, "csprogs.dat", 52195, NULL, 0) >= 0) - { + csprogsnum = PR_LoadProgs(csqcprogs, "csprogs.dat", 52195, NULL, 0); + if (csprogsnum >= 0) csqc_isdarkplaces = true; - loaded = true; - } - else if (PR_LoadProgs(csqcprogs, "csprogs.dat", 0, NULL, 0) >= 0) - loaded = true; else - loaded = false; + csprogsnum = PR_LoadProgs(csqcprogs, "csprogs.dat", 0, NULL, 0); - if (loaded) + if (csprogsnum != -1) Con_Printf(CON_WARNING "Running outdated or unknown csprogs.dat version\n"); } } if (csqc_singlecheats || anycsqc) { - if (PR_LoadProgs(csqcprogs, "csaddon.dat", 0, NULL, 0) >= 0) - { + csaddonnum = PR_LoadProgs(csqcprogs, "csaddon.dat", 0, NULL, 0); + if (csaddonnum >= 0) Con_DPrintf("loaded csaddon.dat...\n"); - loaded = true; - } else Con_DPrintf("unable to find csaddon.dat.\n"); } else Con_DPrintf("skipping csaddon.dat due to cheat restrictions\n"); - if (!loaded) + if (csprogsnum == -1 && csaddonnum == -1) { CSQC_Shutdown(); return false; } - PR_AutoCvarSetup(csqcprogs); + PR_ProgsAdded(csqcprogs, csprogsnum, "csprogs.dat"); + PR_ProgsAdded(csqcprogs, csaddonnum, "csaddon.dat"); PF_InitTempStrings(csqcprogs); diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index 09738bd0..89bd41f8 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -626,6 +626,36 @@ void QCBUILTIN PF_SubConGetSet (pubprogfuncs_t *prinst, struct globalvars_s *pr_ if (value) con->unseentext = atoi(value); } + else if (!strcmp(field, "markup")) + { + int cur; + if (con->parseflags & PFS_NOMARKUP) + cur = 0; + else if (con->parseflags & PFS_KEEPMARKUP) + cur = 2; + else + cur = 1; + RETURN_TSTRING(va("%i", cur)); + if (value) + { + cur = atoi(value); + con->parseflags &= ~(PFS_NOMARKUP|PFS_KEEPMARKUP); + if (cur == 0) + con->parseflags |= PFS_NOMARKUP; + else if (cur == 2) + con->parseflags |= PFS_KEEPMARKUP; + } + } + else if (!strcmp(field, "forceutf8")) + { + RETURN_TSTRING((con->parseflags&PFS_FORCEUTF8)?"1":"0"); + if (value) + { + con->parseflags &= ~PFS_FORCEUTF8; + if (atoi(value)) + con->parseflags |= PFS_FORCEUTF8; + } + } else if (!strcmp(field, "hidden")) { RETURN_TSTRING((con->flags & CON_HIDDEN)?"1":"0"); @@ -1799,10 +1829,12 @@ qboolean MP_Init (void) menutime = Sys_DoubleTime(); if (!menu_world.progs) { + int mprogs; Con_DPrintf("Initializing menu.dat\n"); menu_world.progs = InitProgs(&menuprogparms); PR_Configure(menu_world.progs, 64*1024*1024, 1); - if (PR_LoadProgs(menu_world.progs, "menu.dat", 10020, NULL, 0) < 0) //no per-progs builtins. + mprogs = PR_LoadProgs(menu_world.progs, "menu.dat", 10020, NULL, 0); + if (mprogs < 0) //no per-progs builtins. { //failed to load or something // CloseProgs(menu_world.progs); @@ -1826,7 +1858,7 @@ qboolean MP_Init (void) menu_world.g.drawfont = (float*)PR_FindGlobal(menu_world.progs, "drawfont", 0, NULL); menu_world.g.drawfontscale = (float*)PR_FindGlobal(menu_world.progs, "drawfontscale", 0, NULL); - PR_AutoCvarSetup(menu_world.progs); + PR_ProgsAdded(menu_world.progs, mprogs, "menu.dat"); menuentsize = PR_InitEnts(menu_world.progs, 8192); diff --git a/engine/client/pr_skelobj.c b/engine/client/pr_skelobj.c index 05185f55..940b9d9c 100644 --- a/engine/client/pr_skelobj.c +++ b/engine/client/pr_skelobj.c @@ -1038,7 +1038,7 @@ static skelobject_t *skel_create(pubprogfuncs_t *prinst, int bonecount) if (!skelobjects[skelidx].numbones) { skelobjects[skelidx].numbones = bonecount; - skelobjects[skelidx].bonematrix = (float*)PR_AddString(prinst, "", sizeof(float)*12*bonecount); + skelobjects[skelidx].bonematrix = (float*)PR_AddString(prinst, "", sizeof(float)*12*bonecount, false); } skelobjects[skelidx].world = prinst->parms->user; if (numskelobjectsused <= skelidx) diff --git a/engine/client/r_2d.c b/engine/client/r_2d.c index 886f0364..47051313 100644 --- a/engine/client/r_2d.c +++ b/engine/client/r_2d.c @@ -3,7 +3,7 @@ #include "shader.h" #include "gl_draw.h" -qboolean r2d_noshadergamma; //says the video code has successfully activated hardware gamma +qboolean r2d_canhwgamma; //says the video code has successfully activated hardware gamma texid_t missing_texture; texid_t missing_texture_gloss; texid_t missing_texture_normal; @@ -629,7 +629,7 @@ void R2D_Font_Callback(struct cvar_s *var, char *oldvalue) }; qboolean MyRegGetStringValue(HKEY base, char *keyname, char *valuename, void *data, int datalen); LOGFONT lf = {0}; - CHOOSEFONT cf = {sizeof(cf)}; + CHOOSEFONTA cf = {sizeof(cf)}; extern HWND mainwindow; extern qboolean WinNT; font_conchar = Font_LoadFont(8, ""); @@ -831,7 +831,7 @@ void R2D_BrightenScreen (void) if (fabs(v_contrast.value - 1.0) < 0.05 && fabs(v_brightness.value - 0) < 0.05 && fabs(v_gamma.value - 1) < 0.05) return; - if (r2d_noshadergamma) + if (r2d_canhwgamma) return; if (v_gamma.value != 1 && shader_gammacb->prog) diff --git a/engine/client/render.h b/engine/client/render.h index bd4bbb1c..7b40a150 100644 --- a/engine/client/render.h +++ b/engine/client/render.h @@ -152,8 +152,8 @@ typedef struct mplane_s #define RDFD_FOV 1 typedef struct { - vrect_t grect; // game rectangle. fullscreen except for csqc/splitscreen. - vrect_t vrect; // subwindow in grect for 3d view + vrect_t grect; // game rectangle. fullscreen except for csqc/splitscreen/hud. + vrect_t vrect; // subwindow in grect for 3d view. equal to grect if no hud. vec3_t pvsorigin; /*render the view using this point for pvs (useful for mirror views)*/ vec3_t vieworg; /*logical view center*/ @@ -180,7 +180,7 @@ typedef struct vec4_t gfog_rgbd; - vrect_t pxrect; /*vrect, but in pixels rather than virtual coords*/ + pxrect_t pxrect; /*vrect, but in pixels rather than virtual coords*/ qboolean externalview; /*draw external models and not viewmodels*/ qboolean recurse; /*in a mirror/portal/half way through drawing something else*/ qboolean forcevis; /*if true, vis comes from the forcedvis field instead of recalculated*/ @@ -308,6 +308,7 @@ enum imageflags IF_TEXTYPE = (1<<6) | (1<<7) | (1<<8), /*0=2d, 1=3d, 2-7=cubeface*/ IF_TEXTYPESHIFT = 6, /*0=2d, 1=3d, 2-7=cubeface*/ IF_MIPCAP = 1<<9, + IF_EXACTEXTENSION = 1<<29, IF_REPLACE = 1<<30, IF_SUBDIRONLY = 1<<31 }; diff --git a/engine/client/renderer.c b/engine/client/renderer.c index d5c6c4d8..6bf3ff50 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -119,6 +119,7 @@ cvar_t r_loadlits = CVARF ("r_loadlit", "1", CVAR_ARCHIVE); cvar_t r_menutint = SCVARF ("r_menutint", "0.68 0.4 0.13", CVAR_RENDERERCALLBACK); cvar_t r_netgraph = SCVAR ("r_netgraph", "0"); +cvar_t r_lerpmuzzlehack = CVARF ("r_lerpmuzzlehack", "1", CVAR_ARCHIVE); cvar_t r_nolerp = CVARF ("r_nolerp", "0", CVAR_ARCHIVE); cvar_t r_noframegrouplerp = CVARF ("r_noframegrouplerp", "0", CVAR_ARCHIVE); cvar_t r_nolightdir = CVARF ("r_nolightdir", "0", CVAR_ARCHIVE); @@ -402,6 +403,7 @@ void GLRenderer_Init(void) Cvar_Register (&r_postprocshader, GLRENDEREROPTIONS); Cvar_Register (&dpcompat_psa_ungroup, GLRENDEREROPTIONS); + Cvar_Register (&r_lerpmuzzlehack, GLRENDEREROPTIONS); Cvar_Register (&r_noframegrouplerp, GLRENDEREROPTIONS); Cvar_Register (&r_noportals, GLRENDEREROPTIONS); Cvar_Register (&r_noaliasshadows, GLRENDEREROPTIONS); @@ -426,8 +428,6 @@ void GLRenderer_Init(void) #ifdef R_XFLIP Cvar_Register (&r_xflip, GLRENDEREROPTIONS); #endif - Cvar_Register (&gl_specular, GRAPHICALNICETIES); - Cvar_Register (&gl_specular_fallback, GRAPHICALNICETIES); // Cvar_Register (&gl_lightmapmode, GLRENDEREROPTIONS); @@ -611,6 +611,8 @@ void Renderer_Init(void) Cvar_Register (&r_coronas, GRAPHICALNICETIES); Cvar_Register (&r_flashblend, GRAPHICALNICETIES); Cvar_Register (&r_flashblendscale, GRAPHICALNICETIES); + Cvar_Register (&gl_specular, GRAPHICALNICETIES); + Cvar_Register (&gl_specular_fallback, GRAPHICALNICETIES); Sh_RegisterCvars(); @@ -1335,44 +1337,31 @@ void R_ReloadRenderer_f (void) #define DEFAULT_HEIGHT 480 #define DEFAULT_BPP 32 -void R_RestartRenderer_f (void) +//use Cvar_ApplyLatches(CVAR_RENDERERLATCH) beforehand. +qboolean R_BuildRenderstate(rendererstate_t *newr, char *rendererstring) { int i, j; - rendererstate_t oldr; - rendererstate_t newr; - if (r_blockvidrestart) - { - Con_Printf("Ignoring vid_restart from config\n"); - return; - } - M_Shutdown(); - memset(&newr, 0, sizeof(newr)); + memset(newr, 0, sizeof(*newr)); -TRACE(("dbg: R_RestartRenderer_f\n")); + newr->width = vid_width.value; + newr->height = vid_height.value; - Media_CaptureDemoEnd(); - - Cvar_ApplyLatches(CVAR_RENDERERLATCH); - - newr.width = vid_width.value; - newr.height = vid_height.value; - - newr.triplebuffer = vid_triplebuffer.value; - newr.multisample = vid_multisample.value; - newr.bpp = vid_bpp.value; - newr.fullscreen = vid_fullscreen.value; - newr.rate = vid_refreshrate.value; - newr.stereo = (r_stereo_method.ival == 1); + newr->triplebuffer = vid_triplebuffer.value; + newr->multisample = vid_multisample.value; + newr->bpp = vid_bpp.value; + newr->fullscreen = vid_fullscreen.value; + newr->rate = vid_refreshrate.value; + newr->stereo = (r_stereo_method.ival == 1); if (!*_vid_wait_override.string || _vid_wait_override.value < 0) - newr.wait = -1; + newr->wait = -1; else - newr.wait = _vid_wait_override.value; + newr->wait = _vid_wait_override.value; - Q_strncpyz(newr.glrenderer, gl_driver.string, sizeof(newr.glrenderer)); + newr->renderer = NULL; - newr.renderer = NULL; + rendererstring = COM_Parse(rendererstring); for (i = 0; i < sizeof(rendererinfo)/sizeof(rendererinfo[0]); i++) { if (!rendererinfo[i]->description) @@ -1381,14 +1370,82 @@ TRACE(("dbg: R_RestartRenderer_f\n")); { if (!rendererinfo[i]->name[j]) continue; - if (!stricmp(rendererinfo[i]->name[j], vid_renderer.string)) + if (!stricmp(rendererinfo[i]->name[j], com_token)) { - newr.renderer = rendererinfo[i]; + newr->renderer = rendererinfo[i]; break; } } } - if (!newr.renderer) + + rendererstring = COM_Parse(rendererstring); + if (*com_token) + Q_strncpyz(newr->subrenderer, com_token, sizeof(newr->subrenderer)); + else if (newr->renderer && newr->renderer->rtype == QR_OPENGL) + Q_strncpyz(newr->subrenderer, gl_driver.string, sizeof(newr->subrenderer)); + + // use desktop settings if set to 0 and not dedicated + if (newr->renderer && newr->renderer->rtype != QR_NONE) + { + int dbpp, dheight, dwidth, drate; + extern qboolean isPlugin; + + if ((!newr->fullscreen && !vid_desktopsettings.value && !isPlugin) || !Sys_GetDesktopParameters(&dwidth, &dheight, &dbpp, &drate)) + { + // force default values for systems not supporting desktop parameters + dwidth = DEFAULT_WIDTH; + dheight = DEFAULT_HEIGHT; + dbpp = DEFAULT_BPP; + drate = 0; + } + + if (vid_desktopsettings.value) + { + newr->width = dwidth; + newr->height = dheight; + newr->bpp = dbpp; + newr->rate = drate; + } + else + { + if (newr->width <= 0 || newr->height <= 0) + { + newr->width = dwidth; + newr->height = dheight; + } + + if (newr->bpp <= 0) + newr->bpp = dbpp; + } + } + +#ifdef CLIENTONLY + if (newr->renderer && newr->renderer->rtype == QR_NONE) + { + Con_Printf("Client-only builds cannot use dedicated modes.\n"); + return NULL; + } +#endif + + return newr->renderer != NULL; +} + +void R_RestartRenderer_f (void) +{ + rendererstate_t oldr; + rendererstate_t newr; + if (r_blockvidrestart) + { + Con_Printf("Ignoring vid_restart from config\n"); + return; + } + + memset(&newr, 0, sizeof(newr)); + +TRACE(("dbg: R_RestartRenderer_f\n")); + + Cvar_ApplyLatches(CVAR_RENDERERLATCH); + if (!R_BuildRenderstate(&newr, vid_renderer.string)) { int i; if (*vid_renderer.string) @@ -1408,40 +1465,8 @@ TRACE(("dbg: R_RestartRenderer_f\n")); return; } - // use desktop settings if set to 0 and not dedicated - if (newr.renderer->rtype != QR_NONE) - { - int dbpp, dheight, dwidth, drate; - extern qboolean isPlugin; - - if ((!newr.fullscreen && !vid_desktopsettings.value && !isPlugin) || !Sys_GetDesktopParameters(&dwidth, &dheight, &dbpp, &drate)) - { - // force default values for systems not supporting desktop parameters - dwidth = DEFAULT_WIDTH; - dheight = DEFAULT_HEIGHT; - dbpp = DEFAULT_BPP; - drate = 0; - } - - if (vid_desktopsettings.value) - { - newr.width = dwidth; - newr.height = dheight; - newr.bpp = dbpp; - newr.rate = drate; - } - else - { - if (newr.width <= 0 || newr.height <= 0) - { - newr.width = dwidth; - newr.height = dheight; - } - - if (newr.bpp <= 0) - newr.bpp = dbpp; - } - } + M_Shutdown(); + Media_CaptureDemoEnd(); TRACE(("dbg: R_RestartRenderer_f renderer %i\n", newr.renderer)); @@ -1502,9 +1527,10 @@ TRACE(("dbg: R_RestartRenderer_f\n")); void R_SetRenderer_f (void) { - int i, j; - int best; + int i; char *param = Cmd_Argv(1); + rendererstate_t newr; + if (Cmd_Argc() == 1 || !stricmp(param, "help")) { Con_Printf ("\nValid setrenderer parameters are:\n"); @@ -1516,32 +1542,7 @@ void R_SetRenderer_f (void) return; } - best = -1; - for (i = 0; i < sizeof(rendererinfo)/sizeof(rendererinfo[0]); i++) - { - if (!rendererinfo[i]->description) - continue; //not valid in this build. :( - for (j = 4-1; j >= 0; j--) - { - if (!rendererinfo[i]->name[j]) - continue; - if (!stricmp(rendererinfo[i]->name[j], param)) - { - best = i; - break; - } - } - } - -#ifdef CLIENTONLY - if (best == 0) - { - Con_Printf("Client-only builds cannot use dedicated modes.\n"); - return; - } -#endif - - if (best == -1) + if (!R_BuildRenderstate(&newr, param)) { Con_Printf("setrenderer: parameter not supported (%s)\n", param); return; diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index 4ff50660..f0db9e87 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -1266,17 +1266,20 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } #ifdef SUPPORT_ICE - switch(s_voip.enccodec) + if (rtpstream) { - case VOIP_SPEEX_NARROW: - case VOIP_SPEEX_WIDE: - case VOIP_SPEEX_ULTRAWIDE: - case VOIP_SPEEX_OLD: - NET_RTP_Transmit(initseq, inittimestamp, va("speex@%i", s_voip.encsamplerate), outbuf, outpos); - break; - case VOIP_OPUS: - NET_RTP_Transmit(initseq, inittimestamp, "opus", outbuf, outpos); - break; + switch(s_voip.enccodec) + { + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_ULTRAWIDE: + case VOIP_SPEEX_OLD: + NET_RTP_Transmit(initseq, inittimestamp, va("speex@%i", s_voip.encsamplerate), outbuf, outpos); + break; + case VOIP_OPUS: + NET_RTP_Transmit(initseq, inittimestamp, "opus", outbuf, outpos); + break; + } } #endif diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index ce6b4b67..bcf3b392 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -51,6 +51,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #endif +#define USE_LIBTOOL +#ifdef USE_LIBTOOL +#include +#endif + #include "quakedef.h" #undef malloc @@ -67,7 +72,7 @@ long sys_parentwindow; qboolean X11_GetDesktopParameters(int *width, int *height, int *bpp, int *refreshrate); -char *basedir = "."; +char *basedir = "./"; qboolean Sys_InitTerminal (void) //we either have one or we don't. { @@ -244,6 +249,10 @@ void Sys_Quit (void) { Host_Shutdown(); fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); + +#ifdef USE_LIBTOOL + lt_dlexit(); +#endif exit(0); } @@ -268,6 +277,9 @@ void Sys_Error (const char *error, ...) fprintf(stderr, "Error: %s\n", string); Host_Shutdown (); +#ifdef USE_LIBTOOL + lt_dlexit(); +#endif exit (1); } @@ -454,6 +466,7 @@ int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const if (!func(file, st.st_size, parm, spath)) { + Con_DPrintf("giving up on search after finding %s\n", file); closedir(dir); return false; } @@ -492,6 +505,52 @@ unsigned int Sys_Milliseconds (void) return Sys_DoubleTime() * 1000; } +#ifdef USE_LIBTOOL +void Sys_CloseLibrary(dllhandle_t *lib) +{ + lt_dlclose((void*)lib); +} + +dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) +{ + int i; + dllhandle_t lib; + + lib = NULL; + if (!lib) + lib = lt_dlopenext (name); + if (!lib) + { + Con_DPrintf("%s: %s\n", name, lt_dlerror()); + return NULL; + } + + if (funcs) + { + for (i = 0; funcs[i].name; i++) + { + *funcs[i].funcptr = lt_dlsym(lib, funcs[i].name); + if (!*funcs[i].funcptr) + break; + } + if (funcs[i].name) + { + Con_DPrintf("Unable to find symbol \"%s\" in \"%s\"\n", funcs[i].name, name); + Sys_CloseLibrary((dllhandle_t*)lib); + lib = NULL; + } + } + + return (dllhandle_t*)lib; +} + +void *Sys_GetAddressForName(dllhandle_t *module, const char *exportname) +{ + if (!module) + return NULL; + return lt_dlsym(module, exportname); +} +#else void Sys_CloseLibrary(dllhandle_t *lib) { dlclose((void*)lib); @@ -538,6 +597,7 @@ void *Sys_GetAddressForName(dllhandle_t *module, const char *exportname) return NULL; return dlsym(module, exportname); } +#endif // ======================================================================= //friendly way to crash, including stack traces. should help reduce gdb use. @@ -671,14 +731,18 @@ int main (int c, const char **v) quakeparms_t parms; int j; -// static char cwd[1024]; - static char bindir[1024]; +// char cwd[1024]; + char bindir[1024]; signal(SIGFPE, SIG_IGN); signal(SIGPIPE, SIG_IGN); memset(&parms, 0, sizeof(parms)); +#ifdef USE_LIBTOOL + lt_dlinit(); +#endif + parms.argc = c; parms.argv = v; COM_InitArgv(parms.argc, parms.argv); diff --git a/engine/client/sys_sdl.c b/engine/client/sys_sdl.c index f3245f67..dc06843d 100644 --- a/engine/client/sys_sdl.c +++ b/engine/client/sys_sdl.c @@ -456,7 +456,7 @@ int QDECL main(int argc, char **argv) memset(&parms, 0, sizeof(parms)); - parms.basedir = "."; + parms.basedir = "./"; parms.argc = argc; parms.argv = (const char**)argv; diff --git a/engine/client/sys_win.c b/engine/client/sys_win.c index 5356fa2b..35c423fa 100644 --- a/engine/client/sys_win.c +++ b/engine/client/sys_win.c @@ -198,7 +198,7 @@ char *Sys_GetNameForAddress(dllhandle_t *module, void *address) int starttime; qboolean ActiveApp, Minimized; -qboolean WinNT; +qboolean WinNT; //NT has a) proper unicode support that does not unconditionally result in errors. b) a few different registry paths. static HANDLE hinput, houtput; @@ -445,7 +445,7 @@ DWORD CrashExceptionHandler (qboolean iswatchdog, DWORD exceptionCode, LPEXCEPTI } Sys_SaveClipboard(stacklog+logstart); #ifdef _MSC_VER - if (MessageBox(0, stacklog, "KABOOM!", MB_ICONSTOP|MB_YESNO) != IDYES) + if (MessageBoxA(0, stacklog, "KABOOM!", MB_ICONSTOP|MB_YESNO) != IDYES) return EXCEPTION_EXECUTE_HANDLER; #else MessageBox(0, stacklog, "KABOOM!", MB_ICONSTOP); @@ -454,7 +454,7 @@ DWORD CrashExceptionHandler (qboolean iswatchdog, DWORD exceptionCode, LPEXCEPTI } else { - if (MessageBox(NULL, "KABOOM! We crashed!\nBlame the monkey in the corner.\nI hope you saved your work.\nWould you like to take a dump now?", DISTRIBUTION " Sucks", MB_ICONSTOP|MB_YESNO) != IDYES) + if (MessageBoxA(NULL, "KABOOM! We crashed!\nBlame the monkey in the corner.\nI hope you saved your work.\nWould you like to take a dump now?", DISTRIBUTION " Sucks", MB_ICONSTOP|MB_YESNO) != IDYES) return EXCEPTION_EXECUTE_HANDLER; } } @@ -471,7 +471,7 @@ DWORD CrashExceptionHandler (qboolean iswatchdog, DWORD exceptionCode, LPEXCEPTI { if (iswatchdog) { - switch (MessageBox(NULL, "Fizzle... We hit an infinite loop! Or something is just really slow.\nBlame the monkey in the corner.\nI hope you saved your work.\nWould you like to take a dump now?", DISTRIBUTION " Sucks", MB_ICONSTOP|MB_YESNOCANCEL)) + switch (MessageBoxA(NULL, "Fizzle... We hit an infinite loop! Or something is just really slow.\nBlame the monkey in the corner.\nI hope you saved your work.\nWould you like to take a dump now?", DISTRIBUTION " Sucks", MB_ICONSTOP|MB_YESNOCANCEL)) { case IDYES: break; //take a dump. @@ -675,133 +675,343 @@ FILE IO =============================================================================== */ + +wchar_t *widen(wchar_t *out, size_t outlen, const char *utf8) +{ + wchar_t *ret = out; + //utf-8 to utf-16, not ucs-2. + unsigned int codepoint; + int error; + if (!outlen) + return L""; + outlen /= sizeof(wchar_t); + outlen--; + while (*utf8) + { + codepoint = utf8_decode(&error, utf8, (void*)&utf8); + if (error) + codepoint = 0xFFFDu; + if (codepoint > 0xffff) + { + if (outlen < 2) + break; + outlen -= 2; + codepoint -= 0x10000u; + *out++ = 0xD800 | (codepoint>>10); + *out++ = 0xDC00 | (codepoint&0x3ff); + } + else + { + if (outlen < 1) + break; + outlen -= 1; + *out++ = codepoint; + } + } + *out = 0; + return ret; +} + +char *narrowen(char *out, size_t outlen, wchar_t *wide) +{ + char *ret = out; + int bytes; + unsigned int codepoint; + if (!outlen) + return ""; + outlen--; + //utf-8 to utf-16, not ucs-2. + while (*wide) + { + codepoint = *wide++; + if (codepoint >= 0xD800u && codepoint <= 0xDBFFu) + { //handle utf-16 surrogates + if (*wide >= 0xDC00u && *wide <= 0xDFFFu) + { + codepoint = (codepoint&0x3ff)<<10; + codepoint |= *wide++ & 0x3ff; + } + else + codepoint = 0xFFFDu; + } + bytes = utf8_encode(out, codepoint, outlen); + if (bytes <= 0) + break; + out += bytes; + outlen -= bytes; + } + *out = 0; + return ret; +} + void Sys_mkdir (char *path) { - _mkdir (path); + if (WinNT) + { + wchar_t wide[MAX_OSPATH]; + widen(wide, sizeof(wide), path); + CreateDirectoryW(wide, NULL); + } + else + _mkdir (path); } qboolean Sys_remove (char *path) { - if (remove (path) != 0) + if (WinNT) { - int e = errno; - if (e == ENOENT) - return true; //return success if it doesn't already exist. - return false; + wchar_t wide[MAX_OSPATH]; + widen(wide, sizeof(wide), path); + if (DeleteFileW(wide)) + return true; //success + if (GetLastError() == ERROR_FILE_NOT_FOUND) + return true; //succeed when the file already didn't exist + return false; //other errors? panic + } + else + { + if (remove (path) != 0) + { + int e = errno; + if (e == ENOENT) + return true; //return success if it doesn't already exist. + return false; + } + return true; } - - return true; } qboolean Sys_Rename (char *oldfname, char *newfname) { - return !rename(oldfname, newfname); + if (WinNT) + { + wchar_t oldwide[MAX_OSPATH]; + wchar_t newwide[MAX_OSPATH]; + widen(oldwide, sizeof(oldwide), oldfname); + widen(newwide, sizeof(newwide), newfname); + return MoveFileW(oldwide, newwide); + } + else + return !rename(oldfname, newfname); } static int Sys_EnumerateFiles2 (const char *match, int matchstart, int neststart, int (QDECL *func)(const char *fname, int fsize, void *parm, searchpathfuncs_t *spath), void *parm, searchpathfuncs_t *spath) { - HANDLE r; - WIN32_FIND_DATA fd; - int nest = neststart; //neststart refers to just after a / qboolean go; - qboolean wild = false; - - while(match[nest] && match[nest] != '/') + if (!WinNT) { - if (match[nest] == '?' || match[nest] == '*') - wild = true; - nest++; - } - if (match[nest] == '/') - { - char submatch[MAX_OSPATH]; - char tmproot[MAX_OSPATH]; - char file[MAX_OSPATH]; + HANDLE r; + WIN32_FIND_DATAA fd; + int nest = neststart; //neststart refers to just after a / + qboolean wild = false; - if (!wild) - return Sys_EnumerateFiles2(match, matchstart, nest+1, func, parm, spath); - - if (nest-neststart+1> MAX_OSPATH) - return 1; - memcpy(submatch, match+neststart, nest - neststart); - submatch[nest - neststart] = 0; - nest++; - - if (neststart+4 > MAX_OSPATH) - return 1; - memcpy(tmproot, match, neststart); - strcpy(tmproot+neststart, "*.*"); - - r = FindFirstFile(tmproot, &fd); - strcpy(tmproot+neststart, ""); - if (r==(HANDLE)-1) - return 1; - go = true; - do + while(match[nest] && match[nest] != '/') { - if (*fd.cFileName == '.'); //don't ever find files with a name starting with '.' - else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory + if (match[nest] == '?' || match[nest] == '*') + wild = true; + nest++; + } + if (match[nest] == '/') + { + char submatch[MAX_OSPATH]; + char tmproot[MAX_OSPATH]; + char file[MAX_OSPATH]; + + if (!wild) + return Sys_EnumerateFiles2(match, matchstart, nest+1, func, parm, spath); + + if (nest-neststart+1> MAX_OSPATH) + return 1; + memcpy(submatch, match+neststart, nest - neststart); + submatch[nest - neststart] = 0; + nest++; + + if (neststart+4 > MAX_OSPATH) + return 1; + memcpy(tmproot, match, neststart); + strcpy(tmproot+neststart, "*.*"); + + r = FindFirstFile(tmproot, &fd); + strcpy(tmproot+neststart, ""); + if (r==(HANDLE)-1) + return 1; + go = true; + do { - if (wildcmp(submatch, fd.cFileName)) + if (*fd.cFileName == '.'); //don't ever find files with a name starting with '.' + else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory { - int newnest; - if (strlen(tmproot) + strlen(fd.cFileName) + strlen(match+nest) + 2 < MAX_OSPATH) + if (wildcmp(submatch, fd.cFileName)) { - Q_snprintfz(file, sizeof(file), "%s%s/", tmproot, fd.cFileName); - newnest = strlen(file); - strcpy(file+newnest, match+nest); - go = Sys_EnumerateFiles2(file, matchstart, newnest, func, parm, spath); + int newnest; + if (strlen(tmproot) + strlen(fd.cFileName) + strlen(match+nest) + 2 < MAX_OSPATH) + { + Q_snprintfz(file, sizeof(file), "%s%s/", tmproot, fd.cFileName); + newnest = strlen(file); + strcpy(file+newnest, match+nest); + go = Sys_EnumerateFiles2(file, matchstart, newnest, func, parm, spath); + } } } - } - } while(FindNextFile(r, &fd) && go); - FindClose(r); + } while(FindNextFile(r, &fd) && go); + FindClose(r); + } + else + { + const char *submatch = match + neststart; + char tmproot[MAX_OSPATH]; + char file[MAX_OSPATH]; + + if (neststart+4 > MAX_OSPATH) + return 1; + memcpy(tmproot, match, neststart); + strcpy(tmproot+neststart, "*.*"); + + r = FindFirstFile(tmproot, &fd); + strcpy(tmproot+neststart, ""); + if (r==(HANDLE)-1) + return 1; + go = true; + do + { + if (*fd.cFileName == '.') + ; //don't ever find files with a name starting with '.' (includes .. and . directories, and unix hidden files) + else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory + { + if (wildcmp(submatch, fd.cFileName)) + { + if (strlen(tmproot+matchstart) + strlen(fd.cFileName) + 2 < MAX_OSPATH) + { + Q_snprintfz(file, sizeof(file), "%s%s/", tmproot+matchstart, fd.cFileName); + go = func(file, fd.nFileSizeLow, parm, spath); + } + } + } + else + { + if (wildcmp(submatch, fd.cFileName)) + { + if (strlen(tmproot+matchstart) + strlen(fd.cFileName) + 1 < MAX_OSPATH) + { + Q_snprintfz(file, sizeof(file), "%s%s", tmproot+matchstart, fd.cFileName); + go = func(file, fd.nFileSizeLow, parm, spath); + } + } + } + } while(FindNextFile(r, &fd) && go); + FindClose(r); + } } else { - const char *submatch = match + neststart; - char tmproot[MAX_OSPATH]; - char file[MAX_OSPATH]; + HANDLE r; + WIN32_FIND_DATAW fd; + int nest = neststart; //neststart refers to just after a / + qboolean wild = false; - if (neststart+4 > MAX_OSPATH) - return 1; - memcpy(tmproot, match, neststart); - strcpy(tmproot+neststart, "*.*"); - - r = FindFirstFile(tmproot, &fd); - strcpy(tmproot+neststart, ""); - if (r==(HANDLE)-1) - return 1; - go = true; - do + while(match[nest] && match[nest] != '/') { - if (*fd.cFileName == '.'); //don't ever find files with a name starting with '.' - else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory - { - if (wildcmp(submatch, fd.cFileName)) - { - if (strlen(tmproot+matchstart) + strlen(fd.cFileName) + 2 < MAX_OSPATH) - { - Q_snprintfz(file, sizeof(file), "%s%s/", tmproot+matchstart, fd.cFileName); - go = func(file, fd.nFileSizeLow, parm, spath); - } - } - } - else - { - if (wildcmp(submatch, fd.cFileName)) - { - if (strlen(tmproot+matchstart) + strlen(fd.cFileName) + 1 < MAX_OSPATH) - { - Q_snprintfz(file, sizeof(file), "%s%s", tmproot+matchstart, fd.cFileName); - go = func(file, fd.nFileSizeLow, parm, spath); - } - } - } - } while(FindNextFile(r, &fd) && go); - FindClose(r); - } + if (match[nest] == '?' || match[nest] == '*') + wild = true; + nest++; + } + if (match[nest] == '/') + { + char utf8[MAX_OSPATH]; + wchar_t wroot[MAX_OSPATH]; + char submatch[MAX_OSPATH]; + char tmproot[MAX_OSPATH]; + char file[MAX_OSPATH]; + if (!wild) + return Sys_EnumerateFiles2(match, matchstart, nest+1, func, parm, spath); + + if (nest-neststart+1> MAX_OSPATH) + return 1; + memcpy(submatch, match+neststart, nest - neststart); + submatch[nest - neststart] = 0; + nest++; + + if (neststart+4 > MAX_OSPATH) + return 1; + memcpy(tmproot, match, neststart); + strcpy(tmproot+neststart, "*.*"); + + r = FindFirstFileW(widen(wroot, sizeof(wroot), tmproot), &fd); + strcpy(tmproot+neststart, ""); + if (r==(HANDLE)-1) + return 1; + go = true; + do + { + narrowen(utf8, sizeof(utf8), fd.cFileName); + if (*utf8 == '.'); //don't ever find files with a name starting with '.' + else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory + { + if (wildcmp(submatch, utf8)) + { + int newnest; + if (strlen(tmproot) + strlen(utf8) + strlen(match+nest) + 2 < MAX_OSPATH) + { + Q_snprintfz(file, sizeof(file), "%s%s/", tmproot, utf8); + newnest = strlen(file); + strcpy(file+newnest, match+nest); + go = Sys_EnumerateFiles2(file, matchstart, newnest, func, parm, spath); + } + } + } + } while(FindNextFileW(r, &fd) && go); + FindClose(r); + } + else + { + const char *submatch = match + neststart; + char tmproot[MAX_OSPATH]; + wchar_t wroot[MAX_OSPATH]; + char utf8[MAX_OSPATH]; + char file[MAX_OSPATH]; + + if (neststart+4 > MAX_OSPATH) + return 1; + memcpy(tmproot, match, neststart); + strcpy(tmproot+neststart, "*.*"); + + r = FindFirstFileW(widen(wroot, sizeof(wroot), tmproot), &fd); + strcpy(tmproot+neststart, ""); + if (r==(HANDLE)-1) + return 1; + go = true; + do + { + narrowen(utf8, sizeof(utf8), fd.cFileName); + if (*utf8 == '.') + ; //don't ever find files with a name starting with '.' (includes .. and . directories, and unix hidden files) + else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory + { + if (wildcmp(submatch, utf8)) + { + if (strlen(tmproot+matchstart) + strlen(utf8) + 2 < MAX_OSPATH) + { + Q_snprintfz(file, sizeof(file), "%s%s/", tmproot+matchstart, utf8); + go = func(file, fd.nFileSizeLow, parm, spath); + } + } + } + else + { + if (wildcmp(submatch, utf8)) + { + if (strlen(tmproot+matchstart) + strlen(utf8) + 1 < MAX_OSPATH) + { + Q_snprintfz(file, sizeof(file), "%s%s", tmproot+matchstart, utf8); + go = func(file, fd.nFileSizeLow, parm, spath); + } + } + } + } while(FindNextFileW(r, &fd) && go); + FindClose(r); + } + } return go; } int Sys_EnumerateFiles (const char *gpath, const char *match, int (QDECL *func)(const char *fname, int fsize, void *parm, searchpathfuncs_t *spath), void *parm, searchpathfuncs_t *spath) @@ -937,7 +1147,7 @@ void Sys_Init (void) if ((vinfo.dwMajorVersion < 4) || (vinfo.dwPlatformId == VER_PLATFORM_WIN32s)) { - Sys_Error ("QuakeWorld requires at least Win95 or NT 4.0"); + Sys_Error (FULLENGINENAME " requires at least Win95 or NT 4.0"); } if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) @@ -2401,6 +2611,8 @@ void VARGS Signal_Error_Handler(int i) #endif */ +extern char sys_language[64]; + int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // MSG msg; @@ -2409,6 +2621,8 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin char cwd[1024], bindir[1024]; const char *qtvfile = NULL; int delay = 0; + char lang[32]; + char ctry[32]; /* previous instances do not exist in Win32 */ if (hPrevInstance) @@ -2618,6 +2832,15 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin } } + //98+/nt4+ + if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lang, sizeof(lang)) > 0) + { + if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, ctry, sizeof(ctry)) > 0) + Q_snprintfz(sys_language, sizeof(sys_language), "%s_%s", lang, ctry); + else + Q_snprintfz(sys_language, sizeof(sys_language), "%s", lang); + } + TL_InitLanguages(); //tprints are now allowed diff --git a/engine/client/vid.h b/engine/client/vid.h index 65e41474..7a635c50 100644 --- a/engine/client/vid.h +++ b/engine/client/vid.h @@ -38,7 +38,7 @@ typedef struct { int wait; //-1 = default, 0 = off, 1 = on, 2 = every other int multisample; //for opengl antialiasing (which requires context stuff) int triplebuffer; - char glrenderer[MAX_QPATH]; + char subrenderer[MAX_QPATH]; struct rendererinfo_s *renderer; } rendererstate_t; extern rendererstate_t currentrendererstate; @@ -47,6 +47,14 @@ typedef struct vrect_s { float x,y,width,height; } vrect_t; +typedef struct +{ + int x; + int y; + int width; + int height; + int maxheight; //vid.pixelheight or so +} pxrect_t; typedef struct { diff --git a/engine/client/view.c b/engine/client/view.c index fcecdb12..9a8b3a0a 100644 --- a/engine/client/view.c +++ b/engine/client/view.c @@ -298,9 +298,9 @@ cshift_t cshift_lava = { {255,80,0}, 150 }; cshift_t cshift_server = { {130,80,50}, 0 }; -cvar_t v_gamma = SCVARF("gamma", "0.8", CVAR_ARCHIVE|CVAR_RENDERERCALLBACK); -cvar_t v_contrast = SCVARF("contrast", "1.3", CVAR_ARCHIVE); -cvar_t v_brightness = SCVARF("brightness", "0.0", CVAR_ARCHIVE); +cvar_t v_gamma = CVARFD("gamma", "1.0", CVAR_ARCHIVE|CVAR_RENDERERCALLBACK, "Controls how bright the screen is. Setting this to anything but 1 without hardware gamma requires glsl support and can noticably harm your framerate."); +cvar_t v_contrast = CVARFD("contrast", "1.0", CVAR_ARCHIVE, "Scales colour values linearly to make your screen easier to see. Setting this to anything but 1 without hardware gamma will reduce your framerates a little."); +cvar_t v_brightness = CVARFD("brightness", "0.0", CVAR_ARCHIVE, "Brightness is how much 'white' to add to each and every pixel on the screen."); qbyte gammatable[256]; // palette is sent through this @@ -394,6 +394,7 @@ void V_ParseDamage (playerview_t *pv) count = 10; #ifdef ANDROID + //later versions of android might support strength values, but the simple standard interface is duration only. Sys_Vibrate(count); #endif @@ -638,7 +639,7 @@ V_CalcBlend */ void V_CalcBlend (float *hw_blend) { - extern qboolean r2d_noshadergamma; + extern qboolean r2d_canhwgamma; float a2; int j; float *blend; @@ -671,7 +672,7 @@ void V_CalcBlend (float *hw_blend) } else { - if (j == CSHIFT_BONUS || j == CSHIFT_DAMAGE || gl_nohwblend.ival || r2d_noshadergamma) + if (j == CSHIFT_BONUS || j == CSHIFT_DAMAGE || gl_nohwblend.ival || !r2d_canhwgamma) blend = sw_blend; else //powerup or contents? blend = hw_blend; @@ -701,7 +702,7 @@ V_UpdatePalette */ void V_UpdatePalette (qboolean force) { - extern qboolean r2d_noshadergamma; + extern qboolean r2d_canhwgamma; int i; float newhw_blend[4]; int ir, ig, ib; @@ -758,9 +759,9 @@ void V_UpdatePalette (qboolean force) } applied = rf->VID_ApplyGammaRamps ((unsigned short*)ramps); - if (!applied && r2d_noshadergamma) + if (!applied && r2d_canhwgamma) rf->VID_ApplyGammaRamps (NULL); - r2d_noshadergamma = applied; + r2d_canhwgamma = applied; } RSpeedEnd(RSPEED_PALETTEFLASHES); diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 2d7f2d2a..dcfe7aec 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -27,10 +27,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //#define VERSION 2.56 #ifndef DISTRIBUTION - #define DISTRIBUTION "FTE" - #define DISTRIBUTIONLONG "Forethought Entertainment" - #define FULLENGINENAME "FTE QuakeWorld" - #define ENGINEWEBSITE "http://www.fteqw.com" + #define DISTRIBUTION "FTE" //short name used to identify this engine. must be a single word + #define DISTRIBUTIONLONG "Forethought Entertainment" //effectively the 'company' name + #define FULLENGINENAME "FTE QuakeWorld" //the posh name for the engine + #define ENGINEWEBSITE "http://www.fteqw.com" //url for program #endif @@ -87,25 +87,18 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif #define AVAIL_OGGVORBIS - #if !defined(__CYGWIN__) && !defined(MINGW) && !defined(MACOSX) + #if defined(__CYGWIN__) + #define AVAIL_ZLIB + #else #define AVAIL_PNGLIB #define AVAIL_JPEGLIB #define AVAIL_ZLIB - #define AVAIL_OGGVORBIS - - /* Jogi's OpenAL support */ - #define AVAIL_OPENAL #endif -#if defined(MINGW) || defined(MACOSX) - #define AVAIL_PNGLIB - #define AVAIL_ZLIB - #define AVAIL_JPEGLIB - #define AVAIL_OGGVORBIS -#endif + #define AVAIL_OPENAL -#if !defined(NO_DIRECTX) && !defined(NODIRECTX) +#if !defined(NO_DIRECTX) && !defined(NODIRECTX) && defined(_WIN32) #define AVAIL_DINPUT #define AVAIL_DDRAW #define AVAIL_DSOUND @@ -114,8 +107,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #if defined(_WIN32) && !defined(_SDL) #define HAVE_WINSSPI //built in component, checks against windows' root ca database and revocations etc. -#elif defined(__linux__) -// #define HAVE_GNUTLS //currently disabled as it does not validate the server's certificate, beware the mitm attack. +#elif defined(__linux__) || defined(__CYGWIN__) + #define HAVE_GNUTLS //currently disabled as it does not validate the server's certificate, beware the mitm attack. #endif #if defined(HAVE_WINSSPI) || defined(HAVE_GNUTLS) #define HAVE_SSL @@ -358,6 +351,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #undef WEBSERVER #endif +#ifndef AVAIL_ZLIB + #undef SUPPORT_ICE +#endif + #ifdef SERVERONLY //remove options that don't make sense on only a server #undef Q2CLIENT #undef Q3CLIENT @@ -447,6 +444,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define PLATFORM "Win32" #endif #define ARCH_DL_POSTFIX ".dll" + #elif defined(__CYGWIN__) + #define PLATFORM "Cygwin" /*technically also windows*/ + #define ARCH_DL_POSTFIX ".dll" #elif defined(ANDROID) #define PLATFORM "Android" /*technically also linux*/ #elif defined(__linux__) diff --git a/engine/common/com_mesh.c b/engine/common/com_mesh.c index 9d925223..52c5328a 100644 --- a/engine/common/com_mesh.c +++ b/engine/common/com_mesh.c @@ -7,6 +7,7 @@ extern char loadname[]; qboolean r_loadbumpmapping; extern cvar_t dpcompat_psa_ungroup; extern cvar_t r_noframegrouplerp; +extern cvar_t r_lerpmuzzlehack; //Common loader function. void Mod_DoCRC(model_t *mod, char *buffer, int buffersize) @@ -1259,7 +1260,7 @@ void R_LightArrays(const entity_t *entity, vecV_t *coords, avec4_t *colours, int } } -static void R_LerpFrames(mesh_t *mesh, galiaspose_t *p1, galiaspose_t *p2, float lerp, float expand) +static void R_LerpFrames(mesh_t *mesh, galiaspose_t *p1, galiaspose_t *p2, float lerp, float expand, float lerpcutoff) { extern cvar_t r_nolerp; // r_nolightdir is unused float blerp = 1-lerp; @@ -1293,11 +1294,12 @@ static void R_LerpFrames(mesh_t *mesh, galiaspose_t *p1, galiaspose_t *p2, float if (expand) { + vecV_t *oxyz = mesh->xyz_array; for (i = 0; i < mesh->numvertexes; i++) { - mesh->xyz_array[i][0] = p1v[i][0] + p1n[i][0]*expand; - mesh->xyz_array[i][1] = p1v[i][1] + p1n[i][1]*expand; - mesh->xyz_array[i][2] = p1v[i][2] + p1n[i][2]*expand; + oxyz[i][0] = p1v[i][0] + p1n[i][0]*expand; + oxyz[i][1] = p1v[i][1] + p1n[i][1]*expand; + oxyz[i][2] = p1v[i][2] + p1n[i][2]*expand; } return; } @@ -1306,24 +1308,60 @@ static void R_LerpFrames(mesh_t *mesh, galiaspose_t *p1, galiaspose_t *p2, float } else { - for (i = 0; i < mesh->numvertexes; i++) + vecV_t *oxyz = mesh->xyz_array; + vec3_t *onorm = mesh->normals_array; + if (lerpcutoff) { - mesh->normals_array[i][0] = p1n[i][0]*lerp + p2n[i][0]*blerp; - mesh->normals_array[i][1] = p1n[i][1]*lerp + p2n[i][1]*blerp; - mesh->normals_array[i][2] = p1n[i][2]*lerp + p2n[i][2]*blerp; + vec3_t d; + lerpcutoff *= lerpcutoff; + for (i = 0; i < mesh->numvertexes; i++) + { + VectorSubtract(p2v[i], p1v[i], d); + if (DotProduct(d, d) > lerpcutoff) + { + //just use the current frame if we're over the lerp threshold. + //these verts are considered to have teleported. + onorm[i][0] = p2n[i][0]; + onorm[i][1] = p2n[i][1]; + onorm[i][2] = p2n[i][2]; - mesh->xyz_array[i][0] = p1v[i][0]*lerp + p2v[i][0]*blerp; - mesh->xyz_array[i][1] = p1v[i][1]*lerp + p2v[i][1]*blerp; - mesh->xyz_array[i][2] = p1v[i][2]*lerp + p2v[i][2]*blerp; + oxyz[i][0] = p2v[i][0]; + oxyz[i][1] = p2v[i][1]; + oxyz[i][2] = p2v[i][2]; + } + else + { + onorm[i][0] = p1n[i][0]*lerp + p2n[i][0]*blerp; + onorm[i][1] = p1n[i][1]*lerp + p2n[i][1]*blerp; + onorm[i][2] = p1n[i][2]*lerp + p2n[i][2]*blerp; + + oxyz[i][0] = p1v[i][0]*lerp + p2v[i][0]*blerp; + oxyz[i][1] = p1v[i][1]*lerp + p2v[i][1]*blerp; + oxyz[i][2] = p1v[i][2]*lerp + p2v[i][2]*blerp; + } + } + } + else + { + for (i = 0; i < mesh->numvertexes; i++) + { + onorm[i][0] = p1n[i][0]*lerp + p2n[i][0]*blerp; + onorm[i][1] = p1n[i][1]*lerp + p2n[i][1]*blerp; + onorm[i][2] = p1n[i][2]*lerp + p2n[i][2]*blerp; + + oxyz[i][0] = p1v[i][0]*lerp + p2v[i][0]*blerp; + oxyz[i][1] = p1v[i][1]*lerp + p2v[i][1]*blerp; + oxyz[i][2] = p1v[i][2]*lerp + p2v[i][2]*blerp; + } } if (expand) { for (i = 0; i < mesh->numvertexes; i++) { - mesh->xyz_array[i][0] += mesh->normals_array[i][0]*expand; - mesh->xyz_array[i][1] += mesh->normals_array[i][1]*expand; - mesh->xyz_array[i][2] += mesh->normals_array[i][2]*expand; + oxyz[i][0] += onorm[i][0]*expand; + oxyz[i][1] += onorm[i][1]*expand; + oxyz[i][2] += onorm[i][2]*expand; } } } @@ -1450,6 +1488,7 @@ qboolean Alias_GAliasBuildMesh(mesh_t *mesh, vbo_t **vbop, galiasinfo_t *inf, in { extern cvar_t r_nolerp; galiasgroup_t *g1, *g2; + float lerpcutoff; int frame1; int frame2; @@ -1674,13 +1713,14 @@ qboolean Alias_GAliasBuildMesh(mesh_t *mesh, vbo_t **vbop, galiasinfo_t *inf, in frame2=0; } + lerpcutoff = inf->lerpcutoff * r_lerpmuzzlehack.value; - if (Sh_StencilShadowsActive() || qrenderer != QR_OPENGL) + if (Sh_StencilShadowsActive() || qrenderer != QR_OPENGL || e->fatness || lerpcutoff) { mesh->xyz2_array = NULL; mesh->xyz_blendw[0] = 1; mesh->xyz_blendw[1] = 0; - R_LerpFrames(mesh, &g1->poseofs[frame1], &g2->poseofs[frame2], 1-lerp, e->fatness); + R_LerpFrames(mesh, &g1->poseofs[frame1], &g2->poseofs[frame2], 1-lerp, e->fatness, lerpcutoff); } else { @@ -2610,7 +2650,7 @@ static void *Alias_LoadFrameGroup (daliasframetype_t *pframetype, int *seamremap //greatly reduced version of Q1_LoadSkins //just skips over the data -static void *Q1_LoadSkins_SV (daliasskintype_t *pskintype, qboolean alpha) +static void *Q1_LoadSkins_SV (daliasskintype_t *pskintype, unsigned int skintranstype) { int i; int s; @@ -3176,6 +3216,21 @@ qboolean QDECL Mod_LoadQ1Model (model_t *mod, void *buffer) mod->funcs.NativeTrace = Mod_Trace; + if (!strcmp(mod->name, "progs/v_shot.mdl")) + galias->lerpcutoff = 20; + else if (!strcmp(mod->name, "progs/v_shot2.mdl")) + galias->lerpcutoff = 20; + else if (!strcmp(mod->name, "progs/v_nail.mdl")) + galias->lerpcutoff = 7; + else if (!strcmp(mod->name, "progs/v_nail2.mdl")) + galias->lerpcutoff = 6; + else if (!strcmp(mod->name, "progs/v_rock.mdl")) + galias->lerpcutoff = 30; + else if (!strcmp(mod->name, "progs/v_rock2.mdl")) + galias->lerpcutoff = 30; + else if (!strcmp(mod->name, "progs/v_light.mdl")) + galias->lerpcutoff = 30; + return true; } #endif @@ -6185,7 +6240,7 @@ qboolean Mod_ParseIQMAnim(char *buffer, galiasinfo_t *prototype, void**poseofs, -qboolean Mod_LoadInterQuakeModel(model_t *mod, void *buffer) +qboolean QDECL Mod_LoadInterQuakeModel(model_t *mod, void *buffer) { int i; galiasinfo_t *root; diff --git a/engine/common/com_mesh.h b/engine/common/com_mesh.h index d0e66213..d116edd6 100644 --- a/engine/common/com_mesh.h +++ b/engine/common/com_mesh.h @@ -104,6 +104,7 @@ typedef struct galiasinfo_s int numindexes; int *ofs_trineighbours; + float lerpcutoff; //hack. should probably be part of the entity structure, but I really don't want new models (and thus code) to have access to this ugly inefficient hack. make your models properly in the first place. int numskins; #ifndef SERVERONLY diff --git a/engine/common/common.c b/engine/common/common.c index cceaa79c..ce600e48 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -419,12 +419,14 @@ int wildcmp(const char *wild, const char *string) { if (*wild == '*') { - if (wild[1] == *string || *string == '/' || *string == '\\') + if (*string == '/' || *string == '\\') { //* terminates if we get a match on the char following it, or if its a \ or / char wild++; continue; } + if (wildcmp(wild+1, string)) + return true; string++; } else if ((*wild == *string) || (*wild == '?')) @@ -2916,6 +2918,8 @@ conchar_t *COM_ParseFunString(conchar_t defaultflags, const char *str, conchar_t } else if (str[0] == '=' && str[1] == '`' && str[2] == 'k' && str[3] == '8' && str[4] == ':' && !keepmarkup) { + //ezquake compat: koi8 compat for crazy russian people. + //we parse for compat but don't generate (they'll see utf-8 from us). //this code can just recurse. saves affecting the rest of the code with weird encodings. int l; char temp[1024]; @@ -3315,7 +3319,7 @@ skipwhite: if (c == '\\' && data[1] == '\"') { - return COM_ParseCString(data+1, token, tokenlen); + return COM_ParseCString(data+1, token, tokenlen, NULL); } // handle quoted strings specially @@ -3600,6 +3604,10 @@ const char *COM_QuotedString(const char *string, char *buf, int buflen) *buf++ = '\\'; *buf++ = 'r'; break; + case '\t': + *buf++ = '\\'; + *buf++ = 't'; + break; case '\'': *buf++ = '\\'; *buf++ = '\''; @@ -3640,14 +3648,17 @@ const char *COM_QuotedString(const char *string, char *buf, int buflen) } } -char *COM_ParseCString (const char *data, char *token, int tokenlen) +char *COM_ParseCString (const char *data, char *token, size_t sizeoftoken, size_t *lengthwritten) { int c; - int len; + size_t len; len = 0; token[0] = 0; + if (lengthwritten) + *lengthwritten = 0; + if (!data) return NULL; @@ -3678,9 +3689,11 @@ skipwhite: data++; while (1) { - if (len >= tokenlen-2) + if (len >= sizeoftoken-2) { token[len] = '\0'; + if (lengthwritten) + *lengthwritten = len; return (char*)data; } @@ -3688,6 +3701,8 @@ skipwhite: if (!c) { token[len] = 0; + if (lengthwritten) + *lengthwritten = len; return (char*)data-1; } if (c == '\\') @@ -3703,6 +3718,9 @@ skipwhite: case 'n': c = '\n'; break; + case 't': + c = '\t'; + break; case 'r': c = '\r'; break; @@ -3723,6 +3741,8 @@ skipwhite: if (c=='\"' || !c) { token[len] = 0; + if (lengthwritten) + *lengthwritten = len; return (char*)data; } token[len] = c; @@ -3733,7 +3753,7 @@ skipwhite: // parse a regular word do { - if (len >= tokenlen-1) + if (len >= sizeoftoken-1) break; token[len] = c; data++; @@ -3742,6 +3762,8 @@ skipwhite: } while (c>32); token[len] = 0; + if (lengthwritten) + *lengthwritten = len; return (char*)data; } @@ -4633,7 +4655,7 @@ void Info_RemoveKey (char *s, const char *key) if (strstr (key, "\\")) { - Con_TPrintf (TL_KEYHASSLASH); + Con_Printf ("Can't use a key with a \\\n"); return; } @@ -4775,19 +4797,19 @@ void Info_SetValueForStarKey (char *s, const char *key, const char *value, int m if (strstr (key, "\\") || strstr (value, "\\") ) { - Con_TPrintf (TL_KEYHASSLASH); + Con_Printf ("Can't use a key with a \\\n"); return; } if (strstr (key, "\"") || strstr (value, "\"") ) { - Con_TPrintf (TL_KEYHASQUOTE); + Con_Printf ("Can't use a key with a \"\n"); return; } if (strlen(key) >= MAX_INFO_KEY)// || strlen(value) >= MAX_INFO_KEY) { - Con_TPrintf (TL_KEYTOOLONG); + Con_Printf ("Keys and values must be < %i characters.\n", MAX_INFO_KEY); return; } @@ -4804,7 +4826,7 @@ void Info_SetValueForStarKey (char *s, const char *key, const char *value, int m Info_SetValueForStarKey (s, key, value, maxsize); return; } - Con_TPrintf (TL_INFOSTRINGTOOLONG); + Con_Printf ("Info string length exceeded on addition of %s\n", key); return; } } @@ -4816,7 +4838,7 @@ void Info_SetValueForStarKey (char *s, const char *key, const char *value, int m if ((int)(strlen(newv) + strlen(s) + 1) > maxsize) { - Con_TPrintf (TL_INFOSTRINGTOOLONG); + Con_Printf ("Info string length exceeded on addition of %s\n", key); return; } @@ -4854,7 +4876,7 @@ void Info_SetValueForKey (char *s, const char *key, const char *value, int maxsi { if (key[0] == '*') { - Con_TPrintf (TL_STARKEYPROTECTED); + Con_Printf ("Can't set * keys\n"); return; } @@ -4888,7 +4910,8 @@ void Info_Print (char *s, char *lineprefix) if (!*s) { - Con_TPrintf (TL_KEYHASNOVALUE); + //should never happen. + Con_Printf ("\n"); return; } diff --git a/engine/common/common.h b/engine/common/common.h index cd107257..02a1d4da 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -273,7 +273,7 @@ extern qboolean com_eof; #define COM_Parse(d) COM_ParseOut(d,com_token, sizeof(com_token)) char *COM_ParseOut (const char *data, char *out, int outlen); char *COM_ParseStringSet (const char *data); -char *COM_ParseCString (const char *data, char *out, int outlen); +char *COM_ParseCString (const char *data, char *out, size_t maxoutlen, size_t *writtenlen); char *COM_StringParse (const char *data, char *token, unsigned int tokenlen, qboolean expandmacros, qboolean qctokenize); char *COM_ParseToken (const char *data, const char *punctuation); char *COM_TrimString(char *str); @@ -527,6 +527,11 @@ char *T_GetString(int num); void T_FreeInfoStrings(void); char *T_GetInfoString(int num); +struct po_s; +const char *PO_GetText(struct po_s *po, const char *msg); +struct po_s *PO_Load(vfsfile_t *file); +void PO_Close(struct po_s *po); + // // log.c // diff --git a/engine/common/console.h b/engine/common/console.h index defbeb39..ff0e96ce 100644 --- a/engine/common/console.h +++ b/engine/common/console.h @@ -38,7 +38,7 @@ extern conchar_t q3codemasks[MAXQ3COLOURS]; #define CON_HIDDEN 0x00080000 #define CON_BLINKTEXT 0x00040000 #define CON_2NDCHARSETTEXT 0x00020000 -#define CON_RICHFORECOLOUR 0x00010000 // +#define CON_RICHFORECOLOUR 0x00010000 //if set, the upper 3 nibbles are r4g4b4. background is clear, halfalpha is ignored. //#define CON_HIGHCHARSMASK 0x00000080 // Quake's alternative mask #define CON_FLAGSMASK 0xFFFF0000 @@ -134,6 +134,8 @@ typedef struct console_s int vislines; // pixel lines int linesprinted; // for notify times qboolean unseentext; + unsigned parseflags; + conchar_t defaultcharbits; int commandcompletion; //allows tab completion of quake console commands void (*linebuffered) (struct console_s *con, char *line); //if present, called on enter, causes the standard console input to appear. void (*redirect) (struct console_s *con, int key); //if present, called every character. @@ -178,7 +180,7 @@ void Con_History_Load(void); struct font_s; void Con_DrawOneConsole(console_t *con, struct font_s *font, float fx, float fy, float fsx, float fsy); void Con_DrawConsole (int lines, qboolean noback); -char *Con_CopyConsole(qboolean nomarkup); +char *Con_CopyConsole(qboolean nomarkup, qboolean onlyiflink); void Con_Print (char *txt); void VARGS Con_Printf (const char *fmt, ...) LIKEPRINTF(1); void VARGS Con_TPrintf (translation_t text, ...); @@ -211,3 +213,4 @@ void Con_NotifyBox (char *text); // during startup for sound / cd warnings #else #define TRACE(x) #endif + diff --git a/engine/common/cvar.c b/engine/common/cvar.c index e1b32708..e874e65c 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -1296,6 +1296,10 @@ void Cvar_WriteVariables (vfsfile_t *f, qboolean all) for (var = grp->cvars ; var ; var = var->next) if (var->flags & CVAR_ARCHIVE || (all && var != &cl_warncmd)) { + //yeah, don't force-save readonly cvars. + if (var->flags & CVAR_NOSET) + continue; + if (!writtengroupheader) { writtengroupheader = true; diff --git a/engine/common/fs_win32.c b/engine/common/fs_win32.c index 8c5ad5e1..1e571ca9 100644 --- a/engine/common/fs_win32.c +++ b/engine/common/fs_win32.c @@ -1,6 +1,6 @@ #include "quakedef.h" #include "fs.h" -#include +#include "winquake.h" #ifndef INVALID_SET_FILE_POINTER #define INVALID_SET_FILE_POINTER ~0 @@ -8,6 +8,7 @@ //read-only memory mapped files. //for write access, we use the stdio module as a fallback. +//do you think anyone will ever notice that utf8 filenames work even in windows? probably not. oh well, worth a try. #define VFSW32_Open VFSOS_Open #define VFSW32_OpenPath VFSOS_OpenPath @@ -26,6 +27,11 @@ typedef struct { unsigned int length; unsigned int offset; } vfsw32file_t; + +wchar_t *widen(wchar_t *out, size_t outlen, const char *utf8); +char *narrowen(char *out, size_t outlen, const wchar_t *wide); + + static int QDECL VFSW32_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) { DWORD read; @@ -127,14 +133,30 @@ vfsfile_t *QDECL VFSW32_Open(const char *osname, const char *mode) if (strchr(mode, '+')) read = write = true; - if ((write && read) || append) - h = CreateFileA(osname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - else if (write) - h = CreateFileA(osname, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - else if (read) - h = CreateFileA(osname, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (!WinNT) + { + if ((write && read) || append) + h = CreateFileA(osname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + else if (write) + h = CreateFileA(osname, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + else if (read) + h = CreateFileA(osname, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + else + h = INVALID_HANDLE_VALUE; + } else - h = INVALID_HANDLE_VALUE; + { + wchar_t wide[MAX_OSPATH]; + widen(wide, sizeof(wide), osname); + if ((write && read) || append) + h = CreateFileW(wide, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + else if (write) + h = CreateFileW(wide, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + else if (read) + h = CreateFileW(wide, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + else + h = INVALID_HANDLE_VALUE; + } if (h == INVALID_HANDLE_VALUE) return NULL; @@ -251,6 +273,7 @@ static qboolean QDECL VFSW32_FLocate(searchpathfuncs_t *handle, flocation_t *loc FILE *f; int len; char netpath[MAX_OSPATH]; + wchar_t wide[MAX_OSPATH]; if (hashedresult && (void *)hashedresult != wp) @@ -267,7 +290,10 @@ static qboolean QDECL VFSW32_FLocate(searchpathfuncs_t *handle, flocation_t *loc // check a file in the directory tree snprintf (netpath, sizeof(netpath)-1, "%s/%s", wp->rootpath, filename); - f = fopen(netpath, "rb"); + if (!WinNT) + f = fopen(netpath, "rb"); + else + f = _wfopen(widen(wide, sizeof(wide), netpath), L"rb"); if (!f) return false; @@ -289,7 +315,11 @@ static void QDECL VFSW32_ReadFile(searchpathfuncs_t *handle, flocation_t *loc, c // vfsw32path_t *wp = handle; FILE *f; - f = fopen(loc->rawname, "rb"); + wchar_t wide[MAX_OSPATH]; + if (!WinNT) + f = fopen(loc->rawname, "rb"); + else + f = _wfopen(widen(wide, sizeof(wide), loc->rawname), L"rb"); if (!f) //err... return; fseek(f, loc->offset, SEEK_SET); @@ -302,6 +332,30 @@ static int QDECL VFSW32_EnumerateFiles (searchpathfuncs_t *handle, const char *m return Sys_EnumerateFiles(wp->rootpath, match, func, parm, handle); } +qboolean QDECL VFSW32_RenameFile(searchpathfuncs_t *handle, const char *oldfname, const char *newfname) +{ + vfsw32path_t *wp = (vfsw32path_t*)handle; + char oldsyspath[MAX_OSPATH]; + char newsyspath[MAX_OSPATH]; + snprintf (oldsyspath, sizeof(oldsyspath)-1, "%s/%s", wp->rootpath, oldfname); + snprintf (newsyspath, sizeof(newsyspath)-1, "%s/%s", wp->rootpath, newfname); + return Sys_Rename(oldsyspath, newsyspath); +} +qboolean QDECL VFSW32_RemoveFile(searchpathfuncs_t *handle, const char *filename) +{ + vfsw32path_t *wp = (vfsw32path_t*)handle; + char syspath[MAX_OSPATH]; + snprintf (syspath, sizeof(syspath)-1, "%s/%s", wp->rootpath, filename); + return Sys_remove(syspath); +} +qboolean QDECL VFSW32_MkDir(searchpathfuncs_t *handle, const char *filename) +{ + vfsw32path_t *wp = (vfsw32path_t*)handle; + char syspath[MAX_OSPATH]; + snprintf (syspath, sizeof(syspath)-1, "%s/%s", wp->rootpath, filename); + Sys_mkdir(syspath); + return true; +} searchpathfuncs_t *QDECL VFSW32_OpenPath(vfsfile_t *mustbenull, const char *desc) { @@ -312,9 +366,12 @@ searchpathfuncs_t *QDECL VFSW32_OpenPath(vfsfile_t *mustbenull, const char *desc np = Z_Malloc(sizeof(*np) + dlen); if (np) { + wchar_t wide[MAX_OSPATH]; memcpy(np->rootpath, desc, dlen+1); - - np->changenotification = FindFirstChangeNotification(np->rootpath, true, FILE_NOTIFY_CHANGE_FILE_NAME); + if (!WinNT) + np->changenotification = FindFirstChangeNotificationA(np->rootpath, true, FILE_NOTIFY_CHANGE_FILE_NAME); + else + np->changenotification = FindFirstChangeNotificationW(widen(wide, sizeof(wide), np->rootpath), true, FILE_NOTIFY_CHANGE_FILE_NAME); } np->pub.fsver = FSVER; @@ -325,5 +382,10 @@ searchpathfuncs_t *QDECL VFSW32_OpenPath(vfsfile_t *mustbenull, const char *desc np->pub.EnumerateFiles = VFSW32_EnumerateFiles; np->pub.OpenVFS = VFSW32_OpenVFS; np->pub.PollChanges = VFSW32_PollChanges; + + np->pub.RenameFile = VFSW32_RenameFile; + np->pub.RemoveFile = VFSW32_RemoveFile; + np->pub.MkDir = VFSW32_MkDir; + return &np->pub; } diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c new file mode 100644 index 00000000..84e448c0 --- /dev/null +++ b/engine/common/net_ice.c @@ -0,0 +1,1317 @@ +#include "quakedef.h" +#include "netinc.h" + +typedef struct +{ + unsigned short msgtype; + unsigned short msglen; + unsigned int magiccookie; + unsigned int transactid[3]; +} stunhdr_t; +typedef struct +{ + unsigned short attrtype; + unsigned short attrlen; +} stunattr_t; +#ifdef SUPPORT_ICE +/* +Interactive Connectivity Establishment (rfc 5245) +find out your peer's potential ports. +spam your peer with stun packets. +see what sticks. +the 'controller' assigns some final candidate pair to ensure that both peers send+receive from a single connection. +if no candidates are available, try using stun to find public nat addresses. + +in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. + +stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. +peers don't like it when those are missing. + +host candidates - addresses that are directly known +server reflexive candidates - addresses that we found from some public stun server +peer reflexive candidates - addresses that our peer finds out about as we spam them +relayed candidates - some sort of socks5 or something proxy. + + +Note: Even after the ICE connection becomes active, you should continue to collect local candidates and transmit them to the peer out of band. +this allows the connection to pick a new route if some router dies (like a relay kicking us). + +tcp rtp framing should generally be done with a 16-bit network-endian length prefix followed by the data. +*/ + +struct icecandidate_s +{ + struct icecandinfo_s info; + + struct icecandidate_s *next; + + netadr_t peer; + //peer needs telling or something. + qboolean dirty; + + //these are bitmasks. one bit for each local socket. + unsigned int reachable; + unsigned int tried; +}; +struct icestate_s +{ + struct icestate_s *next; + void *module; + + netadr_t chosenpeer; + + netadr_t pubstunserver; + unsigned int stunretry; //once a second, extended to once a minite on reply + char *stunserver;//where to get our public ip from. + int stunport; + unsigned int stunrnd[3]; + + unsigned int timeout; //time when we consider the connection dead + unsigned int keepalive; //sent periodically... + unsigned int retries; //bumped after each round of connectivity checks. affects future intervals. + enum iceproto_e proto; + enum icemode_e mode; + qboolean controlled; //controller chooses final ports. + enum icestate_e state; + char *conname; //internal id. + char *friendlyname; //who you're talking to. + + struct icecandidate_s *lc; + char *lpwd; + char *lufrag; + + struct icecandidate_s *rc; + char *rpwd; + char *rufrag; + + unsigned int tiehigh; + unsigned int tielow; + int foundation; + + ftenet_connections_t *connections; + + //FIXME: we should probably include decode state in here somehow so multiple connections don't clobber each other. + char *codec[32]; //96-127. don't really need to care about other ones. +}; +static struct icestate_s *icelist; + + +#if !defined(SERVERONLY) && defined(VOICECHAT) +extern cvar_t snd_voip_send; +struct rtpheader_s +{ + unsigned char v2_p1_x1_cc4; + unsigned char m1_pt7; + unsigned short seq; + unsigned int timestamp; + unsigned int ssrc; + unsigned int csrc[1]; //sized according to cc +}; +void S_Voip_RTP_Parse(unsigned short sequence, const char *codec, const unsigned char *data, unsigned int datalen); +qboolean S_Voip_RTP_CodecOkay(char *codec); +qboolean NET_RTP_Parse(void) +{ + struct rtpheader_s *rtpheader = (void*)net_message.data; + if (net_message.cursize >= sizeof(*rtpheader) && (rtpheader->v2_p1_x1_cc4 & 0xc0) == 0x80) + { + int hlen; + int padding = 0; + struct icestate_s *con; + int proto; + //make sure this really came from an accepted rtp stream + //note that an rtp connection equal to the game connection will likely mess up when sequences start to get big + //(especially problematic in sane clients that start with a random sequence) + for (con = icelist; con; con = con->next) + { + if (con->state != ICE_INACTIVE && (con->proto == ICEP_VIDEO || con->proto == ICEP_VOICE) && NET_CompareAdr(&net_from, &con->chosenpeer)) + { + proto = rtpheader->m1_pt7 & 0x7f; + if (proto >= 96 && proto <= 127) //rtp dynamic assignments + { + char *codecname = con->codec[proto-96]; + if (!codecname) + continue; + + if (rtpheader->v2_p1_x1_cc4 & 0x20) + padding = net_message.data[net_message.cursize-1]; + hlen = sizeof(*rtpheader); + hlen += ((rtpheader->v2_p1_x1_cc4 & 0xf)-1) * sizeof(int); + if (con->proto == ICEP_VOICE) + S_Voip_RTP_Parse((unsigned short)BigShort(rtpheader->seq), codecname, hlen+(char*)(rtpheader), net_message.cursize - padding - hlen); +// if (con->proto == ICEP_VIDEO) +// S_Video_RTP_Parse((unsigned short)BigShort(rtpheader->seq), codecname, hlen+(char*)(rtpheader), net_message.cursize - padding - hlen); + return true; + } + } + } + } + return false; +} +qboolean NET_RTP_Active(void) +{ + struct icestate_s *con; + for (con = icelist; con; con = con->next) + { + if (con->state == ICE_CONNECTED && con->proto == ICEP_VOICE) + return true; + } + return false; +} +qboolean NET_RTP_Transmit(unsigned int sequence, unsigned int timestamp, const char *codec, char *cdata, int clength) +{ + sizebuf_t buf; + char pdata[512]; + int i; + struct icestate_s *con; + qboolean built = false; + + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(pdata); + buf.cursize = 0; + buf.allowoverflow = true; + buf.data = pdata; + + for (con = icelist; con; con = con->next) + { + if (con->state == ICE_CONNECTED && con->proto == ICEP_VOICE) + { + for (i = 0; i < sizeof(con->codec)/sizeof(con->codec[0]); i++) + { + if (con->codec[i] && !strcmp(con->codec[i], codec)) + { + if (!built) + { + built = true; + MSG_WriteByte(&buf, (2u<<6) | (0u<<5) | (0u<<4) | (0<<0)); //v2_p1_x1_cc4 + MSG_WriteByte(&buf, (0u<<7) | ((i+96)<<0)); //m1_pt7 + MSG_WriteShort(&buf, BigShort(sequence)); //seq + MSG_WriteLong(&buf, BigLong(timestamp)); //timestamp + MSG_WriteLong(&buf, BigLong(0)); //ssrc + SZ_Write(&buf, cdata, clength); + if (buf.overflowed) + return built; + } + NET_SendPacket(NS_CLIENT, buf.cursize, buf.data, &con->chosenpeer); + break; + } + } + } + } + return built; +} +#endif + + + +struct icestate_s *QDECL ICE_Find(void *module, char *conname) +{ + struct icestate_s *con; + + for (con = icelist; con; con = con->next) + { + if (con->module == module && !strcmp(con->conname, conname)) + return con; + } + return NULL; +} +ftenet_connections_t *ICE_PickConnection(struct icestate_s *con) +{ + if (con->connections) + return con->connections; + switch(con->proto) + { + default: + break; +#ifndef SERVERONLY + case ICEP_VOICE: + case ICEP_QWCLIENT: + return cls.sockets; +#endif +#ifndef CLIENTONLY + case ICEP_QWSERVER: + return svs.sockets; +#endif + } + return NULL; +} +struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, enum icemode_e mode, enum iceproto_e proto) +{ + ftenet_connections_t *collection; + struct icestate_s *con; + + //only allow modes that we actually support. + if (mode != ICEM_RAW && mode != ICEM_ICE) + return NULL; + + //only allow protocols that we actually support. + switch(proto) + { + default: + return NULL; +// case ICEP_VIDEO: +// collection = NULL; +// break; +#if !defined(SERVERONLY) && defined(VOICECHAT) + case ICEP_VOICE: + case ICEP_VIDEO: + collection = cls.sockets; + break; +#endif +#ifndef SERVERONLY + case ICEP_QWCLIENT: + collection = cls.sockets; + break; +#endif +#ifndef CLIENTONLY + case ICEP_QWSERVER: + collection = svs.sockets; + break; +#endif + } + + if (!conname) + { + int rnd[2]; + Sys_RandomBytes((void*)rnd, sizeof(rnd)); + conname = va("fte%08x%08x", rnd[0], rnd[1]); + } + + con = Z_Malloc(sizeof(*con)); + con->conname = Z_StrDup(conname); + con->friendlyname = Z_StrDup(peername); + con->proto = proto; + con->rpwd = Z_StrDup(""); + con->rufrag = Z_StrDup(""); + + con->mode = mode; + + if (!collection) + { + con->connections = collection = FTENET_CreateCollection(true); + FTENET_AddToCollection(collection, "UDP", "0", NA_IP, true); + FTENET_AddToCollection(collection, "natpmp", "natpmp://5351", NA_IP, true); + } + + con->next = icelist; + icelist = con; + + { + int rnd[1]; //'must have at least 24 bits randomness' + Sys_RandomBytes((void*)rnd, sizeof(rnd)); + con->lufrag = Z_StrDup(va("%08x", rnd[0])); + } + { + int rnd[4]; //'must have at least 128 bits randomness' + Sys_RandomBytes((void*)rnd, sizeof(rnd)); + con->lpwd = Z_StrDup(va("%08x%08x%08x%08x", rnd[0], rnd[1], rnd[2], rnd[3])); + } + + Sys_RandomBytes((void*)&con->tiehigh, sizeof(con->tiehigh)); + Sys_RandomBytes((void*)&con->tielow, sizeof(con->tielow)); + + if (collection) + { + int i; + int adrno, adrcount=1; + netadr_t adr; + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (!collection->conn[i]) + continue; + adrno = 0; + if (collection->conn[i]->GetLocalAddress) + { + for (adrcount=1; (adrcount = collection->conn[i]->GetLocalAddress(collection->conn[i], &adr, adrno)) && adrno < adrcount; adrno++) + { + if (adr.type == NA_IP || adr.type == NA_IPV6) + { + adr.connum = i; + ICE_AddLCandidateInfo(con, &adr, adrno, ICE_HOST); + + /* + cand = Z_Malloc(sizeof(*cand)); + cand->info.network = i; + cand->info.port = ntohs(adr.port); + adr.port = 0; //to make sure its not part of the string... + Q_strncpyz(cand->info.addr, NET_AdrToString(adrbuf, sizeof(adrbuf), &adr), sizeof(cand->info.addr)); + cand->info.generation = 0; + cand->info.component = 1; + cand->info.foundation = 1; + cand->info.priority = + (1<<24)*(126) + + (1<<8)*((adr.type == NA_IP?32768:0)+net*256+(255-adrno)) + + (1<<0)*(256 - cand->info.component); + + Sys_RandomBytes((void*)rnd, sizeof(rnd)); + Q_strncpyz(cand->info.candidateid, va("x%08x%08x", rnd[0], rnd[1]), sizeof(cand->info.candidateid)); + cand->dirty = true; + + cand->next = con->lc; + con->lc = cand; + */ + } + } + } + } + } + + return con; +} +#include "zlib.h" +//if either remotecand is null, new packets will be sent to all. +static qboolean ICE_SendSpam(struct icestate_s *con) +{ + struct icecandidate_s *rc; + int i; + int bestlocal = -1; + struct icecandidate_s *bestpeer = NULL; + ftenet_connections_t *collection = ICE_PickConnection(con); + if (!collection) + return false; + + //only send one ping to each. + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (collection->conn[i]) + { + for(rc = con->rc; rc; rc = rc->next) + { + if (!(rc->tried & (1u<tried & (1u<info.priority < rc->info.priority) + { + bestpeer = rc; + bestlocal = i; + } + } + } + } + } + + + if (bestpeer && bestlocal >= 0) + { + netadr_t to; + sizebuf_t buf; + char data[512]; + char integ[20]; + int crc; + qboolean usecandidate = false; + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + bestpeer->tried |= (1u<info.addr, bestpeer->info.port, &to)) + return true; + Con_DPrintf("Spam %i -> %s:%i\n", bestlocal, bestpeer->info.addr, bestpeer->info.port); + + if (!con->controlled && NET_CompareAdr(&to, &con->chosenpeer)) + usecandidate = true; + + MSG_WriteShort(&buf, BigShort(0x0001)); + MSG_WriteShort(&buf, 0); //fill in later + MSG_WriteLong(&buf, BigLong(0x2112a442)); //magic + MSG_WriteLong(&buf, BigLong(0)); //randomid + MSG_WriteLong(&buf, BigLong(0)); //randomid + MSG_WriteLong(&buf, BigLong(0x80000000|bestlocal)); //randomid + + if (usecandidate) + { + MSG_WriteShort(&buf, BigShort(0x25));//ICE-USE-CANDIDATE + MSG_WriteShort(&buf, BigShort(0)); //just a flag, so no payload to this attribute + } + + //username + MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME + MSG_WriteShort(&buf, BigShort(strlen(con->rufrag) + 1 + strlen(con->lufrag))); + SZ_Write(&buf, con->rufrag, strlen(con->rufrag)); + MSG_WriteChar(&buf, ':'); + SZ_Write(&buf, con->lufrag, strlen(con->lufrag)); + while(buf.cursize&3) + MSG_WriteChar(&buf, 0); //pad + + //priority + MSG_WriteShort(&buf, BigShort(0x24));//ICE-PRIORITY + MSG_WriteShort(&buf, BigShort(4)); + MSG_WriteLong(&buf, 0); //FIXME. should be set to: + // priority = (2^24)*(type preference) + + // (2^8)*(local preference) + + // (2^0)*(256 - component ID) + //type preference should be 126 and is a function of the candidate type (direct sending should be highest priority at 126) + //local preference should reflect multi-homed preferences. ipv4+ipv6 count as multihomed. + //component ID should be 1 (rtcp would be 2 if we supported it) + + //these two attributes carry a random 64bit tie-breaker. + //the controller is the one with the highest number. + if (con->controlled) + { + MSG_WriteShort(&buf, BigShort(0x8029));//ICE-CONTROLLED + MSG_WriteShort(&buf, BigShort(8)); + MSG_WriteLong(&buf, BigLong(con->tiehigh)); + MSG_WriteLong(&buf, BigLong(con->tielow)); + } + else + { + MSG_WriteShort(&buf, BigShort(0x802A));//ICE-CONTROLLING + MSG_WriteShort(&buf, BigShort(8)); + MSG_WriteLong(&buf, BigLong(con->tiehigh)); + MSG_WriteLong(&buf, BigLong(con->tielow)); + } + + //message integrity is a bit annoying + data[2] = ((buf.cursize+4+sizeof(integ)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + data[3] = ((buf.cursize+4+sizeof(integ)-20)>>0)&0xff; + //but the hash is to the start of the attribute's header + SHA1_HMAC(integ, sizeof(integ), data, buf.cursize, con->rpwd, strlen(con->rpwd)); + MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY + MSG_WriteShort(&buf, BigShort(20)); //sha1 key length + SZ_Write(&buf, integ, sizeof(integ)); //integrity data + + data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length + data[3] = ((buf.cursize+8-20)>>0)&0xff; + crc = crc32(0, data, buf.cursize)^0x5354554e; + MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT + MSG_WriteShort(&buf, BigShort(sizeof(crc))); + MSG_WriteLong(&buf, BigLong(crc)); + + //fill in the length (for the fourth time, after filling in the integrity and fingerprint) + data[2] = ((buf.cursize-20)>>8)&0xff; + data[3] = ((buf.cursize-20)>>0)&0xff; + + collection->conn[bestlocal]->SendPacket(collection->conn[bestlocal], buf.cursize, data, &to); + return true; + } + return false; +} + +void ICE_ToStunServer(struct icestate_s *con) +{ + sizebuf_t buf; + char data[512]; + int crc; + ftenet_connections_t *collection = ICE_PickConnection(con); + if (!collection) + return; + if (!con->stunrnd[0]) + Sys_RandomBytes((char*)con->stunrnd, sizeof(con->stunrnd)); + + Con_DPrintf("Spam stun %s\n", NET_AdrToString(data, sizeof(data), &con->pubstunserver)); + + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + MSG_WriteShort(&buf, BigShort(0x0001)); + MSG_WriteShort(&buf, 0); //fill in later + MSG_WriteLong(&buf, BigLong(0x2112a442)); + MSG_WriteLong(&buf, BigLong(con->stunrnd[0])); //randomid + MSG_WriteLong(&buf, BigLong(con->stunrnd[1])); //randomid + MSG_WriteLong(&buf, BigLong(con->stunrnd[2])); //randomid + + data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length + data[3] = ((buf.cursize+8-20)>>0)&0xff; + crc = crc32(0, data, buf.cursize)^0x5354554e; + MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT + MSG_WriteShort(&buf, BigShort(sizeof(crc))); + MSG_WriteLong(&buf, BigLong(crc)); + + //fill in the length (for the fourth time, after filling in the integrity and fingerprint) + data[2] = ((buf.cursize-20)>>8)&0xff; + data[3] = ((buf.cursize-20)>>0)&0xff; + + NET_SendPacket((con->proto==ICEP_QWSERVER)?NS_SERVER:NS_CLIENT, buf.cursize, data, &con->pubstunserver); +} + +qboolean QDECL ICE_Set(struct icestate_s *con, char *prop, char *value) +{ + if (!strcmp(prop, "state")) + { + int oldstate = con->state; + if (!strcmp(value, STRINGIFY(ICE_CONNECTING))) + con->state = ICE_CONNECTING; + else if (!strcmp(value, STRINGIFY(ICE_INACTIVE))) + con->state = ICE_INACTIVE; + else if (!strcmp(value, STRINGIFY(ICE_FAILED))) + con->state = ICE_FAILED; + else if (!strcmp(value, STRINGIFY(ICE_CONNECTED))) + con->state = ICE_CONNECTED; + else + { + Con_Printf("ICE_Set invalid state %s\n", value); + con->state = ICE_INACTIVE; + } + con->timeout = Sys_Milliseconds(); + + con->retries = 0; + + if (oldstate != con->state && con->state == ICE_CONNECTED) + { + if (con->chosenpeer.type == NA_INVALID) + { + con->state = ICE_FAILED; + Con_Printf("ICE failed. peer not valid.\n"); + } +#ifndef SERVERONLY + else if (con->proto == ICEP_QWCLIENT) + { + char msg[256]; + //FIXME: should make a proper connection type for this so we can switch to other candidates if one route goes down +// Con_Printf("Try typing connect %s\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); + Cbuf_AddText(va("\nconnect \"%s\"\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)), RESTRICT_LOCAL); + } +#endif +#ifndef CLIENTONLY + else if (con->proto == ICEP_QWSERVER) + { + extern void SVC_GetChallenge(); + net_from = con->chosenpeer; + SVC_GetChallenge(); + } +#endif + if (con->state == ICE_CONNECTED) + Con_Printf("%s connection established.\n", con->proto == ICEP_VOICE?"voice":"Quake"); + } + +#if !defined(SERVERONLY) && defined(VOICECHAT) + snd_voip_send.ival = (snd_voip_send.ival & ~4) | (NET_RTP_Active()?4:0); +#endif + } + else if (!strcmp(prop, "controlled")) + con->controlled = !!atoi(value); + else if (!strcmp(prop, "controller")) + con->controlled = !atoi(value); + else if (!strncmp(prop, "codec", 5)) + { + int codec = atoi(prop+5); + if (codec < 96 || codec > 127) + return false; + codec -= 96; +#if !defined(SERVERONLY) && defined(VOICECHAT) + if (!S_Voip_RTP_CodecOkay(value)) +#endif + { + Z_Free(con->codec[codec]); + con->codec[codec] = NULL; + return false; + } + Z_Free(con->codec[codec]); + con->codec[codec] = Z_StrDup(value); + } + else if (!strcmp(prop, "rufrag")) + { + Z_Free(con->rufrag); + con->rufrag = Z_StrDup(value); + } + else if (!strcmp(prop, "rpwd")) + { + Z_Free(con->rpwd); + con->rpwd = Z_StrDup(value); + } + else if (!strcmp(prop, "stunip")) + { + Z_Free(con->stunserver); + con->stunserver = Z_StrDup(value); + NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver); + } + else if (!strcmp(prop, "stunport")) + { + con->stunport = atoi(value); + if (con->stunserver) + NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver); + } + else + return false; + return true; +} +qboolean QDECL ICE_Get(struct icestate_s *con, char *prop, char *value, int valuelen) +{ + if (!strcmp(prop, "sid")) + Q_strncpyz(value, con->conname, valuelen); + else if (!strcmp(prop, "state")) + Q_snprintfz(value, valuelen, "%i", con->state); + else if (!strcmp(prop, "lufrag")) + Q_strncpyz(value, con->lufrag, valuelen); + else if (!strcmp(prop, "lpwd")) + Q_strncpyz(value, con->lpwd, valuelen); + else if (!strncmp(prop, "codec", 5)) + { + int codec = atoi(prop+5); + if (codec < 96 || codec > 127) + return false; + codec -= 96; + if (con->codec[codec]) + Q_strncpyz(value, con->codec[codec], valuelen); + else + Q_strncpyz(value, "", valuelen); + } + else if (!strcmp(prop, "newlc")) + { + struct icecandidate_s *can; + Q_strncpyz(value, "0", valuelen); + for (can = con->lc; can; can = can->next) + { + if (can->dirty) + { + Q_strncpyz(value, "1", valuelen); + break; + } + } + } + else + return false; + return true; +} +struct icecandinfo_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) +{ + struct icecandidate_s *can; + for (can = con->lc; can; can = can->next) + { + if (can->dirty) + { + can->dirty = false; + return &can->info; + } + } + return NULL; +} +//adrno is 0 if the type is anything but host. +void QDECL ICE_AddLCandidateInfo(struct icestate_s *con, netadr_t *adr, int adrno, int type) +{ + int rnd[2]; + struct icecandidate_s *cand; + if (!con) + return; + + for (cand = con->lc; cand; cand = cand->next) + { + if (NET_CompareAdr(adr, &cand->peer)) + break; + } + if (cand) + return; //DUPE + + cand = Z_Malloc(sizeof(*cand)); + cand->next = con->lc; + con->lc = cand; + NET_BaseAdrToString(cand->info.addr, sizeof(cand->info.addr), adr); + cand->info.port = ntohs(adr->port); + cand->info.type = type; + cand->info.generation = 0; + cand->info.foundation = 1; + cand->info.component = 1; + cand->info.network = adr->connum; + cand->dirty = true; + + Sys_RandomBytes((void*)rnd, sizeof(rnd)); + Q_strncpyz(cand->info.candidateid, va("x%08x%08x", rnd[0], rnd[1]), sizeof(cand->info.candidateid)); + + cand->info.priority = + (1<<24)*(126) + + (1<<8)*((adr->type == NA_IP?32768:0)+cand->info.network*256+(255-adrno)) + + (1<<0)*(256 - cand->info.component); +} +void QDECL ICE_AddLCandidateConn(ftenet_connections_t *col, netadr_t *addr, int type) +{ + struct icestate_s *ice; + for (ice = icelist; ice; ice = ice->next) + { + if (ICE_PickConnection(ice) == col) + ICE_AddLCandidateInfo(ice, addr, 0, type); + } +} + +void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n) +{ + struct icecandidate_s *o; + qboolean isnew; + netadr_t peer; + //I don't give a damn about rtpc. + if (n->component != 1) + return; + + if (!NET_StringToAdr(n->addr, n->port, &peer)) + return; + + if (peer.type == NA_IP) + { + //ignore invalid addresses + if (!peer.address.ip[0] && !peer.address.ip[1] && !peer.address.ip[2] && !peer.address.ip[3]) + return; + } + + for (o = con->rc; o; o = o->next) + { + //not sure that updating candidates is particuarly useful tbh, but hey. + if (!strcmp(o->info.candidateid, n->candidateid)) + break; + } + if (!o) + { + o = Z_Malloc(sizeof(*o)); + o->next = con->rc; + con->rc = o; + Q_strncpyz(o->info.candidateid, n->candidateid, sizeof(o->info.candidateid)); + + isnew = true; + } + else + { + isnew = false; + } + Q_strncpyz(o->info.addr, n->addr, sizeof(o->info.addr)); + o->info.port = n->port; + o->info.type = n->type; + o->info.priority = n->priority; + o->info.network = n->network; + o->info.generation = n->generation; + o->info.foundation = n->foundation; + o->info.component = n->component; + o->info.transport = n->transport; + o->dirty = true; + o->peer = peer; + o->tried = 0; + o->reachable = 0; + + Con_DPrintf("%s remote candidate %s: [%s]:%i\n", isnew?"Added":"Updated", o->info.candidateid, o->info.addr, o->info.port); +} +static void ICE_Destroy(struct icestate_s *con) +{ + if (con->connections) + FTENET_CloseCollection(con->connections); + //has already been unlinked + Z_Free(con); +} + +//send pings to establish/keep the connection alive +void ICE_Tick(void) +{ + struct icestate_s *con; + unsigned int curtime = Sys_Milliseconds(); + + for (con = icelist; con; con = con->next) + { + switch(con->mode) + { + case ICEM_RAW: + //raw doesn't do handshakes or keepalives. it should just directly connect. + //raw just uses the first (assumed only) option + if (con->state == ICE_CONNECTING) + { + struct icecandidate_s *rc; + rc = con->rc; + if (rc) + NET_StringToAdr(rc->info.addr, rc->info.port, &con->chosenpeer); + else + con->chosenpeer.type = NA_INVALID; + ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); + } + break; + case ICEM_ICE: + if (con->state == ICE_CONNECTING || con->state == ICE_FAILED) + { + if (con->stunretry < curtime && con->pubstunserver.type != NA_INVALID) + { + ICE_ToStunServer(con); + con->stunretry = curtime + 2*1000; + } + if (con->keepalive < curtime) + { + if (!ICE_SendSpam(con)) + { + struct icecandidate_s *rc; + struct icecandidate_s *best = NULL; + for (rc = con->rc; rc; rc = rc->next) + { + if (rc->reachable && (!best || rc->info.priority > best->info.priority)) + best = rc; + } + if (best) + { + best->tried = ~best->reachable; + con->chosenpeer = best->peer; + ICE_SendSpam(con); + } + else + { + for (rc = con->rc; rc; rc = rc->next) + rc->tried = 0; + } + con->retries++; + if (con->retries > 32) + con->retries = 32; + con->keepalive = curtime + 200*(con->retries); //RTO + } + else + con->keepalive = curtime + 50*(con->retries+1); //Ta + } + } + else if (con->state == ICE_CONNECTED) + { + //keepalive + //if (timeout) + // con->state = ICE_CONNECTING; + } + break; + } + } +} +void QDECL ICE_Close(struct icestate_s *con) +{ + struct icestate_s **link; + + ICE_Set(con, "state", STRINGIFY(ICE_INACTIVE)); + + for (link = &icelist; *link; ) + { + if (con == *link) + { + *link = con->next; + ICE_Destroy(con); + return; + } + else + link = &(*link)->next; + } +} +void QDECL ICE_CloseModule(void *module) +{ + struct icestate_s **link, *con; + + for (link = &icelist; *link; ) + { + con = *link; + if (con->module == module) + { + *link = con->next; + ICE_Destroy(con); + } + else + link = &(*link)->next; + } +} + +icefuncs_t iceapi = +{ + ICE_Create, + ICE_Set, + ICE_Get, + ICE_GetLCandidateInfo, + ICE_AddRCandidateInfo, + ICE_Close, + ICE_CloseModule +}; + +qboolean ICE_WasStun(netsrc_t netsrc) +{ +#if !defined(SERVERONLY) && defined(VOICECHAT) + if (netsrc == NS_CLIENT) + { + if (NET_RTP_Parse()) + return true; + } +#endif + + if ((net_from.type == NA_IP || net_from.type == NA_IPV6) && net_message.cursize >= 20) + { + stunhdr_t *stun = (stunhdr_t*)net_message.data; + int stunlen = BigShort(stun->msglen); + if ((stun->msgtype == BigShort(0x0101) || stun->msgtype == BigShort(0x0111)) && net_message.cursize == stunlen + sizeof(*stun)) + { + //binding reply (or error) + netadr_t adr = net_from; + char xor[16]; + short portxor; + stunattr_t *attr = (stunattr_t*)(stun+1); + int alen; + while(stunlen) + { + stunlen -= sizeof(*attr); + alen = (unsigned short)BigShort(attr->attrlen); + if (alen > stunlen) + return false; + stunlen -= alen; + switch(BigShort(attr->attrtype)) + { + default: + break; + case 1: + case 0x20: + if (BigShort(attr->attrtype) == 0x20) + { + portxor = *(short*)&stun->magiccookie; + memcpy(xor, &stun->magiccookie, sizeof(xor)); + } + else + { + portxor = 0; + memset(xor, 0, sizeof(xor)); + } + if (alen == 8 && ((qbyte*)attr)[5] == 1) //ipv4 MAPPED-ADDRESS + { + char str[256]; + adr.type = NA_IP; + adr.port = (((short*)attr)[3]) ^ portxor; + *(int*)adr.address.ip = *(int*)(&((qbyte*)attr)[8]) ^ *(int*)xor; + NET_AdrToString(str, sizeof(str), &adr); + } + else if (alen == 20 && ((qbyte*)attr)[5] == 2) //ipv6 MAPPED-ADDRESS + { + netadr_t adr; + char str[256]; + adr.type = NA_IPV6; + adr.port = (((short*)attr)[3]) ^ portxor; + ((int*)adr.address.ip6)[0] = ((int*)&((qbyte*)attr)[8])[0] ^ ((int*)xor)[0]; + ((int*)adr.address.ip6)[1] = ((int*)&((qbyte*)attr)[8])[1] ^ ((int*)xor)[1]; + ((int*)adr.address.ip6)[2] = ((int*)&((qbyte*)attr)[8])[2] ^ ((int*)xor)[2]; + ((int*)adr.address.ip6)[3] = ((int*)&((qbyte*)attr)[8])[3] ^ ((int*)xor)[3]; + NET_AdrToString(str, sizeof(str), &adr); + } + + { + struct icestate_s *con; + for (con = icelist; con; con = con->next) + { + char str[256]; + struct icecandidate_s *rc; + if (con->mode != ICEM_ICE) + continue; + + //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. + if (NET_CompareAdr(&net_from, &con->pubstunserver)) + { + for (rc = con->lc; rc; rc = rc->next) + { + if (NET_CompareAdr(&adr, &rc->peer)) + break; + } + if (!rc) + { + struct icecandidate_s *rc; + rc = Z_Malloc(sizeof(*rc)); + rc->next = con->lc; + con->lc = rc; + rc->peer = adr; + NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &adr); + rc->info.port = ntohs(adr.port); + rc->info.type = ICE_SRFLX; + rc->info.component = 1; + rc->dirty = true; + rc->info.priority = 1; //FIXME + + Con_DPrintf("ICE: Public address: %s\n", rc->info.addr); + } + con->stunretry = Sys_Milliseconds() + 60*1000; + } + else + { + for (rc = con->rc; rc; rc = rc->next) + { + if (NET_CompareAdr(&net_from, &rc->peer)) + { + if (!(rc->reachable & (1u<<(net_from.connum-1)))) + Con_DPrintf("ICE: We can reach %s\n", NET_AdrToString(str, sizeof(str), &net_from)); + rc->reachable |= 1u<<(net_from.connum-1); + + if (NET_CompareAdr(&net_from, &con->chosenpeer) && (stun->transactid[2] & BigLong(0x80000000))) + { + if (con->state == ICE_CONNECTING) + ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); + } + } + } + } + } + } + break; + case 9: + { + char msg[64]; + char sender[256]; + unsigned short len = BigShort(attr->attrlen)-4; + if (len > sizeof(msg)-1) + len = sizeof(msg)-1; + memcpy(msg, &((qbyte*)attr)[8], len); + msg[len] = 0; + Con_DPrintf("%s: Stun error code %u : %s\n", NET_AdrToString(sender, sizeof(sender), &net_from), ((qbyte*)attr)[7], msg); + if (((qbyte*)attr)[7] == 1) + { + //not authorised. + } + if (((qbyte*)attr)[7] == 87) + { + //role conflict. + } + } + break; + } + alen = (alen+3)&~3; + attr = (stunattr_t*)((char*)(attr+1) + alen); + } + return true; + } + else if (stun->msgtype == BigShort(0x0011) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) + { + //binding indication. used as an rtp keepalive. + return true; + } + else if (stun->msgtype == BigShort(0x0001) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) + { + char username[256]; + char integrity[20]; + char *integritypos = NULL; + int role = 0; + struct icestate_s *con; + unsigned int tiehigh = 0; + unsigned int tielow = 0; + qboolean usecandidate = false; + int error = 0; + unsigned int priority = 0; + + //binding request + stunattr_t *attr = (stunattr_t*)(stun+1); + int alen; + *username = 0; + while(stunlen) + { + alen = (unsigned short)BigShort(attr->attrlen); + if (alen+sizeof(*attr) > stunlen) + return false; + switch((unsigned short)BigShort(attr->attrtype)) + { + default: + //unknown attributes < 0x8000 are 'mandatory to parse', and such packets must be dropped in their entirety. + //other ones are okay. + if (!((unsigned short)BigShort(attr->attrtype) & 0x8000)) + return false; + break; + case 0x6: + //username + if (alen < sizeof(username)) + { + memcpy(username, attr+1, alen); + username[alen] = 0; +// Con_Printf("Stun username = \"%s\"\n", username); + } + break; + case 0x8: + //message integrity + memcpy(integrity, attr+1, sizeof(integrity)); + integritypos = (char*)(attr+1); + break; + case 0x24: + //priority +// Con_Printf("priority = \"%i\"\n", priority); + priority = BigLong(*(int*)(attr+1)); + break; + case 0x25: + //USE-CANDIDATE + usecandidate = true; + break; + case 0x8028: + //fingerprint +// Con_Printf("fingerprint = \"%08x\"\n", BigLong(*(int*)(attr+1))); + break; + case 0x8029://ice controlled + case 0x802A://ice controlling + role = (unsigned short)BigShort(attr->attrtype); + //ice controlled + tiehigh = BigLong(((int*)(attr+1))[0]); + tielow = BigLong(((int*)(attr+1))[1]); + break; + } + alen = (alen+3)&~3; + attr = (stunattr_t*)((char*)(attr+1) + alen); + stunlen -= alen+sizeof(*attr); + } + + //we need to know which connection its from in order to validate the integrity + for (con = icelist; con; con = con->next) + { + if (!strcmp(va("%s:%s", con->lufrag, con->rufrag), username)) + break; + } + if (!con) + { + Con_DPrintf("Received STUN request from unknown user \"%s\"\n", username); + } + else + { + if (integritypos) + { + char key[20]; + //the hmac is a bit weird. the header length includes the integrity attribute's length, but the checksum doesn't even consider the attribute header. + stun->msglen = BigShort(integritypos+sizeof(integrity) - (char*)stun - sizeof(*stun)); + SHA1_HMAC(key, sizeof(key), (qbyte*)stun, integritypos-4 - (char*)stun, con->lpwd, strlen(con->lpwd)); + if (memcmp(key, integrity, sizeof(integrity))) + { + Con_DPrintf("Integrity is bad! needed %x got %x\n", *(int*)key, *(int*)integrity); + return true; + } + } + + if (con->state != ICE_INACTIVE) + { + sizebuf_t buf; + char data[512]; + int alen = 0, atype = 0, aofs = 0; + int crc; + struct icecandidate_s *rc; + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. + for (rc = con->rc; rc; rc = rc->next) + { + if (NET_CompareAdr(&net_from, &rc->peer)) + break; + } + if (!rc) + { + struct icecandidate_s *rc; + rc = Z_Malloc(sizeof(*rc)); + rc->next = con->rc; + con->rc = rc; + rc->peer = net_from; + NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &net_from); + rc->info.port = ntohs(net_from.port); + rc->info.type = ICE_PRFLX; + rc->dirty = true; + rc->info.priority = priority; + } + + //flip ice control role, if we're wrong. + if (role && role != (con->controlled?0x802A:0x8029)) + { + if (tiehigh == con->tiehigh && tielow == con->tielow) + { + Con_Printf("ICE: Evil loopback hack enabled\n"); + if (usecandidate) + { + con->chosenpeer = net_from; + if (con->state == ICE_CONNECTING) + ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); + } + } + else + { + con->controlled = (tiehigh > con->tiehigh) || (tiehigh == con->tiehigh && tielow > con->tielow); + Con_DPrintf("ICE: role conflict detected. We should be %s\n", con->controlled?"controlled":"controlling"); + error = 87; + } + } + else if (usecandidate && con->controlled) + { + //in the controlled role, we're connected once we're told the pair to use (by the usecandidate flag). + //note that this 'nominates' candidate pairs, from which the highest priority is chosen. + //so we just pick select the highest. + //this is problematic, however, as we don't actually know the real priority that the peer thinks we'll nominate it with. + + if (con->chosenpeer.type != NA_INVALID && !NET_CompareAdr(&net_from, &con->chosenpeer)) + Con_DPrintf("ICE: Duplicate use-candidate\n"); + con->chosenpeer = net_from; + Con_DPrintf("ICE: use-candidate: %s\n", NET_AdrToString(data, sizeof(data), &net_from)); + + if (con->state == ICE_CONNECTING) + ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); + } + + if (net_from.type == NA_IP) + { + alen = 4; + atype = 1; + aofs = 0; + } + else if (net_from.type == NA_IPV6 && + !*(int*)&net_from.address.ip6[0] && + !*(int*)&net_from.address.ip6[4] && + !*(short*)&net_from.address.ip6[8] && + *(short*)&net_from.address.ip6[10] == (short)0xffff) + { //just because we use an ipv6 address for ipv4 internally doesn't mean we should tell the peer that they're on ipv6... + alen = 4; + atype = 1; + aofs = sizeof(net_from.address.ip6) - sizeof(net_from.address.ip); + } + else if (net_from.type == NA_IPV6) + { + alen = 16; + atype = 2; + aofs = 0; + } + + MSG_WriteShort(&buf, BigShort(error?0x0111:0x0101)); + MSG_WriteShort(&buf, BigShort(0)); //fill in later + MSG_WriteLong(&buf, stun->magiccookie); + MSG_WriteLong(&buf, stun->transactid[0]); + MSG_WriteLong(&buf, stun->transactid[1]); + MSG_WriteLong(&buf, stun->transactid[2]); + + if (error == 87) + { + char *txt = "Role Conflict"; + MSG_WriteShort(&buf, BigShort(0x0009)); + MSG_WriteShort(&buf, BigShort(4 + strlen(txt))); + MSG_WriteShort(&buf, 0); //reserved + MSG_WriteByte(&buf, 0); //class + MSG_WriteByte(&buf, error); //code + SZ_Write(&buf, txt, strlen(txt)); //readable + while(buf.cursize&3) //padding + MSG_WriteChar(&buf, 0); + } + else if (1) + { //xor mapped + MSG_WriteShort(&buf, BigShort(0x0020)); + MSG_WriteShort(&buf, BigShort(4+alen)); + MSG_WriteShort(&buf, BigShort(atype)); + MSG_WriteShort(&buf, net_from.port); + SZ_Write(&buf, (char*)&net_from.address + aofs, alen); + } + else + { //non-xor mapped + MSG_WriteShort(&buf, BigShort(0x0001)); + MSG_WriteShort(&buf, BigShort(4+alen)); + MSG_WriteShort(&buf, BigShort(atype)); + MSG_WriteShort(&buf, net_from.port); + SZ_Write(&buf, (char*)&net_from.address + aofs, alen); + } + + MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME + MSG_WriteShort(&buf, BigShort(strlen(username))); + SZ_Write(&buf, username, strlen(username)); + while(buf.cursize&3) + MSG_WriteChar(&buf, 0); + + //message integrity is a bit annoying + data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; + //but the hash is to the start of the attribute's header + SHA1_HMAC(integrity, sizeof(integrity), data, buf.cursize, con->lpwd, strlen(con->lpwd)); + MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY + MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length + SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data + + data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length + data[3] = ((buf.cursize+8-20)>>0)&0xff; + crc = crc32(0, data, buf.cursize)^0x5354554e; + MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT + MSG_WriteShort(&buf, BigShort(sizeof(crc))); + MSG_WriteLong(&buf, BigLong(crc)); + + data[2] = ((buf.cursize-20)>>8)&0xff; + data[3] = ((buf.cursize-20)>>0)&0xff; + NET_SendPacket(netsrc, buf.cursize, data, &net_from); + } + } + + return true; + } + } + return false; +} +#endif \ No newline at end of file diff --git a/engine/common/net_ssl_gnutls.c b/engine/common/net_ssl_gnutls.c index 7abf1523..254cae13 100644 --- a/engine/common/net_ssl_gnutls.c +++ b/engine/common/net_ssl_gnutls.c @@ -3,13 +3,19 @@ //named functions, this makes it *really* easy to port plugins from one engine to annother. #include "quakedef.h" - + #ifdef HAVE_GNUTLS -cvar_t *tls_ignorecertificateerrors; - #if 1//defined(_WIN32) && !defined(MINGW) +#ifdef _MSC_VER +#if SIZE_MAX == ULONG_MAX +#define ssize_t long +#else +#define ssize_t int +#endif +#endif + //lets rip stuff out of the header and supply a seperate dll. //gnutls is huge. //also this helps get around the whole msvc/mingw thing. @@ -21,13 +27,44 @@ typedef struct DSTRUCT* gnutls_anon_client_credentials; struct gnutls_session_int; typedef struct gnutls_session_int* gnutls_session_t; typedef void * gnutls_transport_ptr_t; +struct gnutls_x509_crt_int; +typedef struct gnutls_x509_crt_int *gnutls_x509_crt_t; +typedef struct +{ + unsigned char *data; + unsigned int size; +} gnutls_datum_t; typedef enum gnutls_kx_algorithm { GNUTLS_KX_RSA=1, GNUTLS_KX_DHE_DSS, GNUTLS_KX_DHE_RSA, GNUTLS_KX_ANON_DH, GNUTLS_KX_SRP, GNUTLS_KX_RSA_EXPORT, GNUTLS_KX_SRP_RSA, GNUTLS_KX_SRP_DSS } gnutls_kx_algorithm; -typedef enum gnutls_certificate_type { GNUTLS_CRT_X509=1, GNUTLS_CRT_OPENPGP -} gnutls_certificate_type; +typedef enum { + GNUTLS_CRT_UNKNOWN = 0, + GNUTLS_CRT_X509 = 1, + GNUTLS_CRT_OPENPGP = 2, + GNUTLS_CRT_RAW = 3 +} gnutls_certificate_type_t; +typedef enum { + GNUTLS_X509_FMT_DER = 0, + GNUTLS_X509_FMT_PEM = 1 +} gnutls_x509_crt_fmt_t; +typedef enum +{ + GNUTLS_CERT_INVALID = 1<<1, + GNUTLS_CERT_REVOKED = 1<<5, + GNUTLS_CERT_SIGNER_NOT_FOUND = 1<<6, + GNUTLS_CERT_SIGNER_NOT_CA = 1<<7, + GNUTLS_CERT_INSECURE_ALGORITHM = 1<<8, + GNUTLS_CERT_NOT_ACTIVATED = 1<<9, + GNUTLS_CERT_EXPIRED = 1<<10, + GNUTLS_CERT_SIGNATURE_FAILURE = 1<<11, + GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED = 1<<12, + GNUTLS_CERT_UNEXPECTED_OWNER = 1<<14, + GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE = 1<<15, + GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE = 1<<16, + GNUTLS_CERT_MISMATCH = 1<<17, +} gnutls_certificate_status_t; typedef enum gnutls_connection_end { GNUTLS_SERVER=1, GNUTLS_CLIENT } gnutls_connection_end; typedef enum gnutls_credentials_type { GNUTLS_CRD_CERTIFICATE=1, GNUTLS_CRD_ANON, GNUTLS_CRD_SRP } gnutls_credentials_type; typedef enum gnutls_close_request { GNUTLS_SHUT_RDWR=0, GNUTLS_SHUT_WR=1 } gnutls_close_request; @@ -35,6 +72,7 @@ typedef ssize_t (*gnutls_pull_func) (gnutls_transport_ptr_t, void *, size_t); typedef ssize_t (*gnutls_push_func) (gnutls_transport_ptr_t, const void *, size_t); #define GNUTLS_E_AGAIN -28 +#define GNUTLS_E_CERTIFICATE_ERROR -43 #define GNUTLS_E_INTERRUPTED -52 static int (VARGS *gnutls_bye)(gnutls_session_t session, gnutls_close_request how); @@ -45,17 +83,38 @@ static void (VARGS *gnutls_transport_set_push_function)(gnutls_session_t session static void (VARGS *gnutls_transport_set_pull_function)(gnutls_session_t session, gnutls_pull_func pull_func); static void (VARGS *gnutls_transport_set_errno)(gnutls_session_t session, int err); static int (VARGS *gnutls_error_is_fatal)(int error); -static int (VARGS *gnutls_certificate_type_set_priority)(gnutls_session_t session, const int*); static int (VARGS *gnutls_credentials_set)(gnutls_session_t, gnutls_credentials_type type, void* cred); static int (VARGS *gnutls_kx_set_priority)(gnutls_session_t session, const int*); static int (VARGS *gnutls_init)(gnutls_session_t * session, gnutls_connection_end con_end); static int (VARGS *gnutls_set_default_priority)(gnutls_session_t session); static int (VARGS *gnutls_certificate_allocate_credentials)(gnutls_certificate_credentials *sc); +static int (VARGS *gnutls_certificate_type_set_priority)(gnutls_session_t session, const int*); static int (VARGS *gnutls_anon_allocate_client_credentials)(gnutls_anon_client_credentials *sc); static int (VARGS *gnutls_global_init)(void); static int (VARGS *gnutls_record_send)(gnutls_session_t session, const void *data, size_t sizeofdata); static int (VARGS *gnutls_record_recv)(gnutls_session_t session, void *data, size_t sizeofdata); +static int (VARGS *gnutls_certificate_set_verify_function)(); +static void *(VARGS *gnutls_session_get_ptr)(gnutls_session_t session); +static void (VARGS *gnutls_session_set_ptr)(gnutls_session_t session, void *ptr); +#if GNUTLS_VERSION_3_0_0_PLUS +static int (VARGS *gnutls_certificate_set_x509_system_trust)(gnutls_certificate_credentials cred); +#else +static int (VARGS *gnutls_certificate_set_x509_trust_file)(gnutls_certificate_credentials cred, const char * cafile, gnutls_x509_crt_fmt_t type); +#endif +#if GNUTLS_VERSION_3_1_4_PLUS +static int (VARGS *gnutls_certificate_verify_peers3)(gnutls_session_t session, const char* hostname, unsigned int * status); +static int (VARGS *gnutls_certificate_verification_status_print)(unsigned int status, gnutls_certificate_type_t type, gnutls_datum_t * out, unsigned int flags); +#else +static int (VARGS *gnutls_certificate_verify_peers2)(gnutls_session_t session, unsigned int * status); +static int (VARGS *gnutls_x509_crt_check_hostname)(gnutls_x509_crt_t cert, const char * hostname); +static int (VARGS *gnutls_x509_crt_init)(gnutls_x509_crt_t * cert); +static int (VARGS *gnutls_x509_crt_import)(gnutls_x509_crt_t cert, const gnutls_datum_t *data, gnutls_x509_crt_fmt_t format); +static const gnutls_datum_t *(VARGS *gnutls_certificate_get_peers)(gnutls_session_t session, unsigned int * list_size); +#endif +static gnutls_certificate_type_t (VARGS *gnutls_certificate_type_get)(gnutls_session_t session); +static void (VARGS *gnutls_free)(void * ptr); + static qboolean Init_GNUTLS(void) { dllhandle_t hmod; @@ -80,10 +139,35 @@ static qboolean Init_GNUTLS(void) {(void**)&gnutls_global_init, "gnutls_global_init"}, {(void**)&gnutls_record_send, "gnutls_record_send"}, {(void**)&gnutls_record_recv, "gnutls_record_recv"}, + + {(void**)&gnutls_certificate_set_verify_function, "gnutls_certificate_set_verify_function"}, + {(void**)&gnutls_session_get_ptr, "gnutls_session_get_ptr"}, + {(void**)&gnutls_session_set_ptr, "gnutls_session_set_ptr"}, +#if GNUTLS_VERSION_3_0_0_PLUS + {(void**)&gnutls_certificate_set_x509_system_trust, "gnutls_certificate_set_x509_system_trust"}, +#else + {(void**)&gnutls_certificate_set_x509_trust_file, "gnutls_certificate_set_x509_trust_file"}, +#endif +#if GNUTLS_VERSION_3_1_4_PLUS + {(void**)&gnutls_certificate_verify_peers3, "gnutls_certificate_verify_peers3"}, + {(void**)&gnutls_certificate_verification_status_print, "gnutls_certificate_verification_status_print"}, +#else + {(void**)&gnutls_certificate_verify_peers2, "gnutls_certificate_verify_peers2"}, + {(void**)&gnutls_x509_crt_init, "gnutls_x509_crt_init"}, + {(void**)&gnutls_x509_crt_import, "gnutls_x509_crt_import"}, + {(void**)&gnutls_certificate_get_peers, "gnutls_certificate_get_peers"}, + {(void**)&gnutls_x509_crt_check_hostname, "gnutls_x509_crt_check_hostname"}, +#endif + {(void**)&gnutls_certificate_type_get, "gnutls_certificate_type_get"}, + {(void**)&gnutls_free, "gnutls_free"}, {NULL, NULL} }; +#ifdef __CYGWIN__ + hmod = Sys_LoadLibrary("cyggnutls-26.dll", functable); +#else hmod = Sys_LoadLibrary("libgnutls.so.26", functable); +#endif if (!hmod) return false; return true; @@ -104,6 +188,7 @@ typedef struct vfsfile_t funcs; vfsfile_t *stream; + char certname[512]; gnutls_session_t session; qboolean handshaking; @@ -114,26 +199,121 @@ typedef struct struct sslbuf incrypt; } gnutlsfile_t; -static void SSL_Close(vfsfile_t *vfs) +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" + +static void QDECL SSL_Close(vfsfile_t *vfs) { gnutlsfile_t *file = (void*)vfs; - gnutls_bye (file->session, GNUTLS_SHUT_RDWR); - VFS_CLOSE(file->stream); + file->handshaking = true; + + if (file->session) + gnutls_bye (file->session, GNUTLS_SHUT_RDWR); + file->session = NULL; + if (file->stream) + VFS_CLOSE(file->stream); + file->stream = NULL; } -static int SSL_Read(struct vfsfile_s *f, void *buffer, int bytestoread) +static int QDECL SSL_CheckCert(gnutls_session_t session) +{ + gnutlsfile_t *file = gnutls_session_get_ptr (session); + unsigned int certstatus; + cvar_t *tls_ignorecertificateerrors; + +#if GNUTLS_VERSION_3_1_4_PLUS + if (gnutls_certificate_verify_peers3(session, file->certname, &certstatus) >= 0) + { + { + gnutls_datum_t out; + gnutls_certificate_type_t type; + if (certstatus == 0) + return 0; + + type = gnutls_certificate_type_get (session); + if (gnutls_certificate_verification_status_print(certstatus, type, &out, 0) >= 0) + { + Con_Printf("%s: %s\n", file->certname, out.data); + gnutls_free(out.data); + +#else + if (gnutls_certificate_verify_peers2(session, &certstatus) >= 0) + { + int certslen; + //grab the certificate + const gnutls_datum_t *const certlist = gnutls_certificate_get_peers(session, &certslen); + if (certlist && certslen) + { + //and make sure the hostname on it actually makes sense. + gnutls_x509_crt_t cert; + gnutls_x509_crt_init(&cert); + gnutls_x509_crt_import(cert, certlist, GNUTLS_X509_FMT_DER); + if (gnutls_x509_crt_check_hostname(cert, file->certname)) + { + if (certstatus == 0) + return 0; + + if (certstatus & GNUTLS_CERT_SIGNER_NOT_FOUND) + Con_Printf("%s: Certificate authority is not recognised\n", file->certname); + else if (certstatus & GNUTLS_CERT_INSECURE_ALGORITHM) + Con_Printf("%s: Certificate uses insecure algorithm\n", file->certname); + else if (certstatus & (GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE|GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED|GNUTLS_CERT_EXPIRED|GNUTLS_CERT_REVOKED|GNUTLS_CERT_NOT_ACTIVATED)) + Con_Printf("%s: Certificate has expired or was revoked or not yet valid\n", file->certname); + else if (certstatus & GNUTLS_CERT_SIGNATURE_FAILURE) + Con_Printf("%s: Certificate signature failure\n", file->certname); + else + Con_Printf("%s: Certificate error\n", file->certname); +#endif + tls_ignorecertificateerrors = Cvar_Get("tls_ignorecertificateerrors", "0", CVAR_NOTFROMSERVER, "TLS"); + if (tls_ignorecertificateerrors && tls_ignorecertificateerrors->ival) + { + Con_Printf("%s: Ignoring certificate errors (tls_ignorecertificateerrors is %i)\n", file->certname, tls_ignorecertificateerrors->ival); + return 0; + } + } + } + } + + Con_DPrintf("%s: rejecting certificate\n", file->certname); + return GNUTLS_E_CERTIFICATE_ERROR; +} + +//return 1 to read data. +//-1 or 0 for error or not ready +int SSL_DoHandshake(gnutlsfile_t *file) +{ + int err; + //session was previously closed = error + if (!file->session) + return -1; + + err = gnutls_handshake (file->session); + if (err < 0) + { //non-fatal errors can just handshake again the next time the caller checks to see if there's any data yet + //(e_again or e_intr) + if (!gnutls_error_is_fatal(err)) + return 0; + + //certificate errors etc +// gnutls_perror (err); + + SSL_Close(&file->funcs); +// Con_Printf("%s: abort\n", file->certname); + return -1; + } + file->handshaking = false; + return 1; +} + +static int QDECL SSL_Read(struct vfsfile_s *f, void *buffer, int bytestoread) { gnutlsfile_t *file = (void*)f; int read; if (file->handshaking) { - read = gnutls_handshake (file->session); - if (read == GNUTLS_E_AGAIN || read == GNUTLS_E_INTERRUPTED) - return 0; - if (read < 0) - gnutls_perror (read); - file->handshaking = false; + read = SSL_DoHandshake(file); + if (read <= 0) + return read; } read = gnutls_record_recv(file->session, buffer, bytestoread); @@ -151,19 +331,16 @@ static int SSL_Read(struct vfsfile_s *f, void *buffer, int bytestoread) return -1; //closed by remote connection. return read; } -static int SSL_Write(struct vfsfile_s *f, const void *buffer, int bytestowrite) +static int QDECL SSL_Write(struct vfsfile_s *f, const void *buffer, int bytestowrite) { gnutlsfile_t *file = (void*)f; int written; if (file->handshaking) { - written = gnutls_handshake (file->session); - if (written == GNUTLS_E_AGAIN || written == GNUTLS_E_INTERRUPTED) - return 0; - if (written < 0) - gnutls_perror (written); - file->handshaking = false; + written = SSL_DoHandshake(file); + if (written <= 0) + return written; } written = gnutls_record_send(file->session, buffer, bytestowrite); @@ -265,7 +442,7 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server) /* Need to enable anonymous KX specifically. */ const int kx_prio[] = {GNUTLS_KX_ANON_DH, 0}; - const int cert_type_priority[3] = {GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0}; + const int cert_type_priority[3] = {GNUTLS_CRT_X509, 0}; { @@ -282,7 +459,13 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server) gnutls_anon_allocate_client_credentials (&anoncred); gnutls_certificate_allocate_credentials (&xcred); - // gnutls_certificate_set_x509_trust_file (xcred, "ca.pem", GNUTLS_X509_FMT_PEM); + +#ifdef GNUTLS_VERSION_3_0_0_PLUS + gnutls_certificate_set_x509_system_trust (xcred); +#else + gnutls_certificate_set_x509_trust_file (xcred, CAFILE, GNUTLS_X509_FMT_PEM); +#endif + gnutls_certificate_set_verify_function (xcred, SSL_CheckCert); needinit = false; } @@ -304,9 +487,13 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server) newf->funcs.Tell = SSL_Tell; newf->funcs.seekingisabadplan = true; + Q_strncpyz(newf->certname, hostname, sizeof(newf->certname)); + // Initialize TLS session gnutls_init (&newf->session, GNUTLS_CLIENT); + gnutls_session_set_ptr(newf->session, newf); + // Use default priorities gnutls_set_default_priority (newf->session); if (anon) diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index f1a52154..4f3af55e 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -83,28 +83,6 @@ extern cvar_t sv_public, sv_listen_qw, sv_listen_nq, sv_listen_dp, sv_listen_q3; static qboolean allowconnects = false; -#define FTENET_ADDRTYPES 2 -typedef struct ftenet_generic_connection_s { - char name[MAX_QPATH]; - - int (*GetLocalAddress)(struct ftenet_generic_connection_s *con, netadr_t *local, int adridx); - qboolean (*ChangeLocalAddress)(struct ftenet_generic_connection_s *con, const char *newaddress); - qboolean (*GetPacket)(struct ftenet_generic_connection_s *con); - qboolean (*SendPacket)(struct ftenet_generic_connection_s *con, int length, void *data, netadr_t *to); - void (*Close)(struct ftenet_generic_connection_s *con); -#ifdef HAVE_PACKET - int (*SetReceiveFDSet) (struct ftenet_generic_connection_s *con, fd_set *fdset); /*set for connections which have multiple sockets (ie: listening tcp connections)*/ -#endif - - netadrtype_t addrtype[FTENET_ADDRTYPES]; - qboolean islisten; -#ifdef HAVE_PACKET - SOCKET thesocket; -#else - int thesocket; -#endif -} ftenet_generic_connection_t; - @@ -1643,13 +1621,6 @@ static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean iss #endif //============================================================================= -#define MAX_CONNECTIONS 8 -typedef struct ftenet_connections_s -{ - qboolean islisten; - ftenet_generic_connection_t *conn[MAX_CONNECTIONS]; -} ftenet_connections_t; - ftenet_connections_t *FTENET_CreateCollection(qboolean listen) { ftenet_connections_t *col; @@ -1722,7 +1693,11 @@ static qboolean NET_Was_NATPMP(ftenet_connections_t *collection) pmp->natadr.port = 0; memcpy(pmp->natadr.address.ip, pmpreqrep->ipv4, sizeof(pmp->natadr.address.ip)); NET_AdrToString(adrbuf, sizeof(adrbuf), &pmp->natadr); -// Con_Printf("Public ip is %s\n", adrbuf); + pmp->natadr.connum = i; + Con_DPrintf("NAT-PMP: Public ip is %s\n", adrbuf); + + if (pmp->natadr.type && pmp->natadr.port) + ICE_AddLCandidateConn(collection, &pmp->natadr, ICE_SRFLX); //let ICE connections know about it return true; } if (net_message.cursize == 16 && pmpreqrep->op == 129) @@ -1750,8 +1725,11 @@ static qboolean NET_Was_NATPMP(ftenet_connections_t *collection) return false; } -// Con_Printf("Local port %u publically available on port %u\n", (unsigned short)BigShort(pmpreqrep->privport), (unsigned short)BigShort(pmpreqrep->pubport)); + Con_DPrintf("NAT-PMP: Local port %u publically available on port %u\n", (unsigned short)BigShort(pmpreqrep->privport), (unsigned short)BigShort(pmpreqrep->pubport)); pmp->natadr.port = pmpreqrep->pubport; + + if (pmp->natadr.type && pmp->natadr.port) + ICE_AddLCandidateConn(collection, &pmp->natadr, ICE_SRFLX); return true; } return false; @@ -1876,6 +1854,7 @@ void FTENET_NATPMP_Close(struct ftenet_generic_connection_s *con) //FIXME: we should send a packet to close the port Z_Free(con); } +//qboolean Net_OpenUDPPort(char *privateip, int privateport, char *publicip, size_t publiciplen, int *publicport); ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(qboolean isserver, const char *address) { pmpcon_t *pmp; @@ -1900,20 +1879,72 @@ ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(qboolean isserver pmp->refreshtime = Sys_Milliseconds() + PMP_POLL_TIME*64; +// Net_OpenUDPPort(); + return &pmp->pub; } #endif -qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, const char *address, netadrtype_t addrtype, qboolean islisten) +#ifdef UPNP +ftenet_generic_connection_t *FTENET_NATUPNP_EstablishConnection(qboolean isserver, const char *address); +#endif + +qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char *name, const char *address, ftenet_generic_connection_t *(*establish)(qboolean isserver, const char *address), qboolean islisten) { int count = 0; int i; - netadr_t adr; - ftenet_generic_connection_t *(*establish)(qboolean isserver, const char *address) = NULL; if (!col) return false; + if (name) + { + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (col->conn[i]) + if (col->conn[i]->name && !strcmp(col->conn[i]->name, name)) + { + if (address && *address) + if (col->conn[i]->ChangeLocalAddress) + { + if (col->conn[i]->ChangeLocalAddress(col->conn[i], address)) + return true; + } + + col->conn[i]->Close(col->conn[i]); + col->conn[i] = NULL; + } + } + } + + if (address && *address && establish) + { + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (!col->conn[i]) + { + address = COM_Parse(address); + col->conn[i] = establish(islisten, com_token); + if (!col->conn[i]) + break; + if (name) + Q_strncpyz(col->conn[i]->name, name, sizeof(col->conn[i]->name)); + count++; + + if (address && *address) + continue; + break; + } + } + } + return count > 0; +} +qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, const char *address, netadrtype_t addrtype, qboolean islisten) +{ + ftenet_generic_connection_t *(*establish)(qboolean isserver, const char *address) = NULL; + netadr_t adr; + + //resolve the address to something sane so we can determine the address type and thus the connection type to use if (!address || !*address) adr.type = NA_INVALID; else if (islisten) @@ -1959,47 +1990,7 @@ qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, con #endif } - if (name) - { - for (i = 0; i < MAX_CONNECTIONS; i++) - { - if (col->conn[i]) - if (col->conn[i]->name && !strcmp(col->conn[i]->name, name)) - { - if (address && *address) - if (col->conn[i]->ChangeLocalAddress) - { - if (col->conn[i]->ChangeLocalAddress(col->conn[i], address)) - return true; - } - - col->conn[i]->Close(col->conn[i]); - col->conn[i] = NULL; - } - } - } - - if (address && *address && establish) - { - for (i = 0; i < MAX_CONNECTIONS; i++) - { - if (!col->conn[i]) - { - address = COM_Parse(address); - col->conn[i] = establish(islisten, com_token); - if (!col->conn[i]) - break; - if (name) - Q_strncpyz(col->conn[i]->name, name, sizeof(col->conn[i]->name)); - count++; - - if (address && *address) - continue; - break; - } - } - } - return count > 0; + return FTENET_AddToCollection_Ptr(col, name, address, establish, islisten); } void FTENET_CloseCollection(ftenet_connections_t *col) @@ -4956,1216 +4947,6 @@ void NET_GetLocalAddress (int socket, netadr_t *out) #endif } - -typedef struct -{ - unsigned short msgtype; - unsigned short msglen; - unsigned int magiccookie; - unsigned int transactid[3]; -} stunhdr_t; -typedef struct -{ - unsigned short attrtype; - unsigned short attrlen; -} stunattr_t; -#ifdef SUPPORT_ICE -/* -Interactive Connectivity Establishment (rfc 5245) -find out your peer's potential ports. -spam your peer with stun packets. -see what sticks. -the 'controller' assigns some final candidate pair to ensure that both peers send+receive from a single connection. -if no candidates are available, try using stun to find public nat addresses. - -in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. - -stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. -peers don't like it when those are missing. - -host candidates - addresses that are directly known -server reflexive candidates - addresses that we found from some public stun server -peer reflexive candidates - addresses that our peer finds out about as we spam them -relayed candidates - some sort of socks5 or something proxy. - -*/ - -struct icecandidate_s -{ - struct icecandinfo_s info; - - struct icecandidate_s *next; - - netadr_t peer; - //peer needs telling or something. - qboolean dirty; - - //these are bitmasks. one bit for each local socket. - unsigned int reachable; - unsigned int tried; -}; -struct icestate_s -{ - struct icestate_s *next; - void *module; - - netadr_t chosenpeer; - - netadr_t pubstunserver; - unsigned int stunretry; //once a second, extended to once a minite on reply - char *stunserver;//where to get our public ip from. - int stunport; - unsigned int stunrnd[3]; - - unsigned int timeout; //time when we consider the connection dead - unsigned int keepalive; //sent periodically... - unsigned int retries; //bumped after each round of connectivity checks. affects future intervals. - enum iceproto_e proto; - enum icemode_e mode; - qboolean controlled; //controller chooses final ports. - enum icestate_e state; - char *conname; //internal id. - char *friendlyname; //who you're talking to. - - struct icecandidate_s *lc; - char *lpwd; - char *lufrag; - - struct icecandidate_s *rc; - char *rpwd; - char *rufrag; - - unsigned int tiehigh; - unsigned int tielow; - - char *codec[32]; //96-127. don't really need to care about other ones. -}; -static struct icestate_s *icelist; - - -#if !defined(SERVERONLY) && defined(VOICECHAT) -extern cvar_t snd_voip_send; -struct rtpheader_s -{ - unsigned char v2_p1_x1_cc4; - unsigned char m1_pt7; - unsigned short seq; - unsigned int timestamp; - unsigned int ssrc; - unsigned int csrc[1]; //sized according to cc -}; -void S_Voip_RTP_Parse(unsigned short sequence, const char *codec, const unsigned char *data, unsigned int datalen); -qboolean S_Voip_RTP_CodecOkay(char *codec); -qboolean NET_RTP_Parse(void) -{ - struct rtpheader_s *rtpheader = (void*)net_message.data; - if (net_message.cursize >= sizeof(*rtpheader) && (rtpheader->v2_p1_x1_cc4 & 0xc0) == 0x80) - { - int hlen; - int padding = 0; - struct icestate_s *con; - int proto; - //make sure this really came from an accepted rtp stream - //note that an rtp connection equal to the game connection will likely mess up when sequences start to get big - //(especially problematic in sane clients that start with a random sequence) - for (con = icelist; con; con = con->next) - { - if (con->state != ICE_INACTIVE && con->proto == ICEP_VOICE && NET_CompareAdr(&net_from, &con->chosenpeer)) - break; - } - //and continue with parsing it if its okay. - if (con) - { - proto = rtpheader->m1_pt7 & 0x7f; - if (proto < 96 || proto > 127) - return false; - proto -= 96; - if (rtpheader->v2_p1_x1_cc4 & 0x20) - padding = net_message.data[net_message.cursize-1]; - hlen = sizeof(*rtpheader); - hlen += ((rtpheader->v2_p1_x1_cc4 & 0xf)-1) * sizeof(int); - S_Voip_RTP_Parse((unsigned short)BigShort(rtpheader->seq), con->codec[proto], hlen+(char*)(rtpheader), net_message.cursize - padding - hlen); - return true; - } - } - return false; -} -qboolean NET_RTP_Active(void) -{ - struct icestate_s *con; - for (con = icelist; con; con = con->next) - { - if (con->state == ICE_CONNECTED && con->proto == ICEP_VOICE) - return true; - } - return false; -} -qboolean NET_RTP_Transmit(unsigned int sequence, unsigned int timestamp, const char *codec, char *cdata, int clength) -{ - sizebuf_t buf; - char pdata[512]; - int i; - struct icestate_s *con; - qboolean built = false; - - memset(&buf, 0, sizeof(buf)); - buf.maxsize = sizeof(pdata); - buf.cursize = 0; - buf.allowoverflow = true; - buf.data = pdata; - - for (con = icelist; con; con = con->next) - { - if (con->state == ICE_CONNECTED && con->proto == ICEP_VOICE) - { - for (i = 0; i < sizeof(con->codec)/sizeof(con->codec[0]); i++) - { - if (con->codec[i] && !strcmp(con->codec[i], codec)) - { - if (!built) - { - built = true; - MSG_WriteByte(&buf, (2u<<6) | (0u<<5) | (0u<<4) | (0<<0)); //v2_p1_x1_cc4 - MSG_WriteByte(&buf, (0u<<7) | ((i+96)<<0)); //m1_pt7 - MSG_WriteShort(&buf, BigShort(sequence)); //seq - MSG_WriteLong(&buf, BigLong(timestamp)); //timestamp - MSG_WriteLong(&buf, BigLong(0)); //ssrc - SZ_Write(&buf, cdata, clength); - if (buf.overflowed) - return built; - } - NET_SendPacket(NS_CLIENT, buf.cursize, buf.data, &con->chosenpeer); - break; - } - } - } - } - return built; -} -#endif - - - -struct icestate_s *QDECL ICE_Find(void *module, char *conname) -{ - struct icestate_s *con; - - for (con = icelist; con; con = con->next) - { - if (con->module == module && !strcmp(con->conname, conname)) - return con; - } - return NULL; -} -struct icestate_s *QDECL ICE_Create(void *module, char *conname, char *peername, enum icemode_e mode, enum iceproto_e proto) -{ - ftenet_connections_t *collection; - struct icestate_s *con; - - //only allow modes that we actually support. - if (mode != ICEM_RAW && mode != ICEM_ICE) - return NULL; - - //only allow protocols that we actually support. - switch(proto) - { - default: - return NULL; -#if !defined(SERVERONLY) && defined(VOICECHAT) - case ICEP_VOICE: - collection = cls.sockets; - break; -#endif -#ifndef SERVERONLY - case ICEP_QWCLIENT: - collection = cls.sockets; - break; -#endif -#ifndef CLIENTONLY - case ICEP_QWSERVER: - collection = svs.sockets; - break; -#endif - } - - if (!conname) - { - int rnd[2]; - Sys_RandomBytes((void*)rnd, sizeof(rnd)); - conname = va("fte%08x%08x", rnd[0], rnd[1]); - } - - con = Z_Malloc(sizeof(*con)); - con->conname = Z_StrDup(conname); - con->friendlyname = Z_StrDup(peername); - con->proto = proto; - con->rpwd = Z_StrDup(""); - con->rufrag = Z_StrDup(""); - - con->mode = mode; - - con->next = icelist; - icelist = con; - - { - int rnd[1]; //'must have at least 24 bits randomness' - Sys_RandomBytes((void*)rnd, sizeof(rnd)); - con->lufrag = Z_StrDup(va("%08x", rnd[0])); - } - { - int rnd[4]; //'must have at least 128 bits randomness' - Sys_RandomBytes((void*)rnd, sizeof(rnd)); - con->lpwd = Z_StrDup(va("%08x%08x%08x%08x", rnd[0], rnd[1], rnd[2], rnd[3])); - } - - Sys_RandomBytes((void*)&con->tiehigh, sizeof(con->tiehigh)); - Sys_RandomBytes((void*)&con->tielow, sizeof(con->tielow)); - - if (collection) - { - int i; - int adrno, adrcount=1; - netadr_t adr; - char adrbuf[MAX_ADR_SIZE]; - int net = 0; - - for (i = 0; i < MAX_CONNECTIONS; i++) - { - if (!collection->conn[i]) - continue; - adrno = 0; - if (collection->conn[i]->GetLocalAddress) - { - for (adrcount=1; (adrcount = collection->conn[i]->GetLocalAddress(collection->conn[i], &adr, adrno)) && adrno < adrcount; adrno++) - { - struct icecandidate_s *cand; - int rnd[2]; - if (adr.type == NA_IP || adr.type == NA_IPV6) - { - cand = Z_Malloc(sizeof(*cand)); - cand->info.network = net; - cand->info.port = ntohs(adr.port); - adr.port = 0; //to make sure its not part of the string... - Q_strncpyz(cand->info.addr, NET_AdrToString(adrbuf, sizeof(adrbuf), &adr), sizeof(cand->info.addr)); - cand->info.generation = 0; - cand->info.component = 1; - cand->info.foundation = 1; - cand->info.priority = - (1<<24)*(126) + - (1<<8)*((adr.type == NA_IP?32768:0)+net*256+(255-adrno)) + - (1<<0)*(256 - cand->info.component); - - Sys_RandomBytes((void*)rnd, sizeof(rnd)); - Q_strncpyz(cand->info.candidateid, va("x%08x%08x", rnd[0], rnd[1]), sizeof(cand->info.candidateid)); - cand->dirty = true; - - cand->next = con->lc; - con->lc = cand; - } - } - } - net++; - } - } - - return con; -} -#include "zlib.h" -ftenet_connections_t *ICE_PickConnection(struct icestate_s *con) -{ - switch(con->proto) - { - default: - break; -#ifndef SERVERONLY - case ICEP_VOICE: - case ICEP_QWCLIENT: - return cls.sockets; -#endif -#ifndef CLIENTONLY - case ICEP_QWSERVER: - return svs.sockets; -#endif - } - return NULL; -} -//if either remotecand is null, new packets will be sent to all. -static qboolean ICE_SendSpam(struct icestate_s *con) -{ - struct icecandidate_s *rc; - int i; - int bestlocal = -1; - struct icecandidate_s *bestpeer = NULL; - ftenet_connections_t *collection = ICE_PickConnection(con); - if (!collection) - return false; - - //only send one ping to each. - for (i = 0; i < MAX_CONNECTIONS; i++) - { - if (collection->conn[i]) - { - for(rc = con->rc; rc; rc = rc->next) - { - if (!(rc->tried & (1u<tried & (1u<info.priority < rc->info.priority) - { - bestpeer = rc; - bestlocal = i; - } - } - } - } - } - - - if (bestpeer && bestlocal >= 0) - { - netadr_t to; - sizebuf_t buf; - char data[512]; - char integ[20]; - int crc; - qboolean usecandidate = false; - memset(&buf, 0, sizeof(buf)); - buf.maxsize = sizeof(data); - buf.cursize = 0; - buf.data = data; - - bestpeer->tried |= (1u<info.addr, bestpeer->info.port, &to)) - return true; - Con_DPrintf("Spam %i -> %s:%i\n", bestlocal, bestpeer->info.addr, bestpeer->info.port); - - if (!con->controlled && NET_CompareAdr(&to, &con->chosenpeer)) - usecandidate = true; - - MSG_WriteShort(&buf, BigShort(0x0001)); - MSG_WriteShort(&buf, 0); //fill in later - MSG_WriteLong(&buf, BigLong(0x2112a442)); - MSG_WriteLong(&buf, BigLong(0)); //randomid - MSG_WriteLong(&buf, BigLong(0)); //randomid - MSG_WriteLong(&buf, BigLong(0x80000000|bestlocal)); //randomid - - if (usecandidate) - { - MSG_WriteShort(&buf, BigShort(0x25));//ICE-USE-CANDIDATE - MSG_WriteShort(&buf, BigShort(0)); - } - - //username - MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME - MSG_WriteShort(&buf, BigShort(strlen(con->rufrag) + 1 + strlen(con->lufrag))); - SZ_Write(&buf, con->rufrag, strlen(con->rufrag)); - MSG_WriteChar(&buf, ':'); - SZ_Write(&buf, con->lufrag, strlen(con->lufrag)); - while(buf.cursize&3) - MSG_WriteChar(&buf, 0); - - //priority - MSG_WriteShort(&buf, BigShort(0x24));//ICE-PRIORITY - MSG_WriteShort(&buf, BigShort(4)); - MSG_WriteLong(&buf, 0); //FIXME - - //these two attributes carry a random 64bit tie-breaker. - //the controller is the one with the highest number. - if (con->controlled) - { - MSG_WriteShort(&buf, BigShort(0x8029));//ICE-CONTROLLED - MSG_WriteShort(&buf, BigShort(8)); - MSG_WriteLong(&buf, BigLong(con->tiehigh)); - MSG_WriteLong(&buf, BigLong(con->tielow)); - } - else - { - MSG_WriteShort(&buf, BigShort(0x802A));//ICE-CONTROLLING - MSG_WriteShort(&buf, BigShort(8)); - MSG_WriteLong(&buf, BigLong(con->tiehigh)); - MSG_WriteLong(&buf, BigLong(con->tielow)); - } - - //message integrity is a bit annoying - data[2] = ((buf.cursize+4+sizeof(integ)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute - data[3] = ((buf.cursize+4+sizeof(integ)-20)>>0)&0xff; - //but the hash is to the start of the attribute's header - SHA1_HMAC(integ, sizeof(integ), data, buf.cursize, con->rpwd, strlen(con->rpwd)); - MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY - MSG_WriteShort(&buf, BigShort(20)); //sha1 key length - SZ_Write(&buf, integ, sizeof(integ)); //integrity data - - data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length - data[3] = ((buf.cursize+8-20)>>0)&0xff; - crc = crc32(0, data, buf.cursize)^0x5354554e; - MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT - MSG_WriteShort(&buf, BigShort(sizeof(crc))); - MSG_WriteLong(&buf, BigLong(crc)); - - //fill in the length (for the fourth time, after filling in the integrity and fingerprint) - data[2] = ((buf.cursize-20)>>8)&0xff; - data[3] = ((buf.cursize-20)>>0)&0xff; - - collection->conn[bestlocal]->SendPacket(collection->conn[bestlocal], buf.cursize, data, &to); - return true; - } - return false; -} - -void ICE_ToStunServer(struct icestate_s *con) -{ - sizebuf_t buf; - char data[512]; - int crc; - ftenet_connections_t *collection = ICE_PickConnection(con); - if (!collection) - return; - if (!con->stunrnd[0]) - Sys_RandomBytes((char*)con->stunrnd, sizeof(con->stunrnd)); - - Con_DPrintf("Spam stun %s\n", NET_AdrToString(data, sizeof(data), &con->pubstunserver)); - - memset(&buf, 0, sizeof(buf)); - buf.maxsize = sizeof(data); - buf.cursize = 0; - buf.data = data; - - MSG_WriteShort(&buf, BigShort(0x0001)); - MSG_WriteShort(&buf, 0); //fill in later - MSG_WriteLong(&buf, BigLong(0x2112a442)); - MSG_WriteLong(&buf, BigLong(con->stunrnd[0])); //randomid - MSG_WriteLong(&buf, BigLong(con->stunrnd[1])); //randomid - MSG_WriteLong(&buf, BigLong(con->stunrnd[2])); //randomid - - data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length - data[3] = ((buf.cursize+8-20)>>0)&0xff; - crc = crc32(0, data, buf.cursize)^0x5354554e; - MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT - MSG_WriteShort(&buf, BigShort(sizeof(crc))); - MSG_WriteLong(&buf, BigLong(crc)); - - //fill in the length (for the fourth time, after filling in the integrity and fingerprint) - data[2] = ((buf.cursize-20)>>8)&0xff; - data[3] = ((buf.cursize-20)>>0)&0xff; - - NET_SendPacket((con->proto==ICEP_QWSERVER)?NS_SERVER:NS_CLIENT, buf.cursize, data, &con->pubstunserver); -} - -qboolean QDECL ICE_Set(struct icestate_s *con, char *prop, char *value) -{ - if (!strcmp(prop, "state")) - { - int oldstate = con->state; - if (!strcmp(value, STRINGIFY(ICE_CONNECTING))) - con->state = ICE_CONNECTING; - else if (!strcmp(value, STRINGIFY(ICE_INACTIVE))) - con->state = ICE_INACTIVE; - else if (!strcmp(value, STRINGIFY(ICE_FAILED))) - con->state = ICE_FAILED; - else if (!strcmp(value, STRINGIFY(ICE_CONNECTED))) - con->state = ICE_CONNECTED; - else - { - Con_Printf("ICE_Set invalid state %s\n", value); - con->state = ICE_INACTIVE; - } - con->timeout = Sys_Milliseconds(); - - con->retries = 0; - - if (oldstate != con->state && con->state == ICE_CONNECTED) - { - if (con->chosenpeer.type == NA_INVALID) - { - con->state = ICE_FAILED; - Con_Printf("ICE failed. peer not valid.\n"); - } -#ifndef SERVERONLY - else if (con->proto == ICEP_QWCLIENT) - { - char msg[256]; -// Con_Printf("Try typing connect %s\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); - Cbuf_AddText(va("\nconnect \"%s\"\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)), RESTRICT_LOCAL); - } -#endif -#ifndef CLIENTONLY - else if (con->proto == ICEP_QWSERVER) - { - extern void SVC_GetChallenge(); - net_from = con->chosenpeer; - SVC_GetChallenge(); - } -#endif - if (con->state == ICE_CONNECTED) - Con_Printf("%s connection established.\n", con->proto == ICEP_VOICE?"voice":"Quake"); - } - -#if !defined(SERVERONLY) && defined(VOICECHAT) - snd_voip_send.ival = (snd_voip_send.ival & ~4) | (NET_RTP_Active()?4:0); -#endif - } - else if (!strcmp(prop, "controlled")) - con->controlled = !!atoi(value); - else if (!strcmp(prop, "controller")) - con->controlled = !atoi(value); - else if (!strncmp(prop, "codec", 5)) - { - int codec = atoi(prop+5); - if (codec < 96 || codec > 127) - return false; - codec -= 96; -#if !defined(SERVERONLY) && defined(VOICECHAT) - if (!S_Voip_RTP_CodecOkay(value)) -#endif - { - Z_Free(con->codec[codec]); - con->codec[codec] = NULL; - return false; - } - Z_Free(con->codec[codec]); - con->codec[codec] = Z_StrDup(value); - } - else if (!strcmp(prop, "rufrag")) - { - Z_Free(con->rufrag); - con->rufrag = Z_StrDup(value); - } - else if (!strcmp(prop, "rpwd")) - { - Z_Free(con->rpwd); - con->rpwd = Z_StrDup(value); - } - else if (!strcmp(prop, "stunip")) - { - Z_Free(con->stunserver); - con->stunserver = Z_StrDup(value); - NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver); - } - else if (!strcmp(prop, "stunport")) - { - con->stunport = atoi(value); - if (con->stunserver) - NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver); - } - else - return false; - return true; -} -qboolean QDECL ICE_Get(struct icestate_s *con, char *prop, char *value, int valuelen) -{ - if (!strcmp(prop, "sid")) - Q_strncpyz(value, con->conname, valuelen); - else if (!strcmp(prop, "state")) - Q_snprintfz(value, valuelen, "%i", con->state); - else if (!strcmp(prop, "lufrag")) - Q_strncpyz(value, con->lufrag, valuelen); - else if (!strcmp(prop, "lpwd")) - Q_strncpyz(value, con->lpwd, valuelen); - else if (!strncmp(prop, "codec", 5)) - { - int codec = atoi(prop+5); - if (codec < 96 || codec > 127) - return false; - codec -= 96; - if (con->codec[codec]) - Q_strncpyz(value, con->codec[codec], valuelen); - else - Q_strncpyz(value, "", valuelen); - } - else if (!strcmp(prop, "newlc")) - { - struct icecandidate_s *can; - Q_strncpyz(value, "0", valuelen); - for (can = con->lc; can; can = can->next) - { - if (can->dirty) - { - Q_strncpyz(value, "1", valuelen); - break; - } - } - } - else - return false; - return true; -} -struct icecandinfo_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) -{ - struct icecandidate_s *can; - for (can = con->lc; can; can = can->next) - { - if (can->dirty) - { - can->dirty = false; - return &can->info; - } - } - return NULL; -} -void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n) -{ - struct icecandidate_s *o; - qboolean isnew; - netadr_t peer; - //I don't give a damn about rtpc. - if (n->component != 1) - return; - - if (!NET_StringToAdr(n->addr, n->port, &peer)) - return; - - if (peer.type == NA_IP) - { - //ignore invalid addresses - if (!peer.address.ip[0] && !peer.address.ip[1] && !peer.address.ip[2] && !peer.address.ip[3]) - return; - } - - for (o = con->rc; o; o = o->next) - { - //not sure that updating candidates is particuarly useful tbh, but hey. - if (!strcmp(o->info.candidateid, n->candidateid)) - break; - } - if (!o) - { - o = Z_Malloc(sizeof(*o)); - o->next = con->rc; - con->rc = o; - Q_strncpyz(o->info.candidateid, n->candidateid, sizeof(o->info.candidateid)); - - isnew = true; - } - else - { - isnew = false; - } - Q_strncpyz(o->info.addr, n->addr, sizeof(o->info.addr)); - o->info.port = n->port; - o->info.type = n->type; - o->info.priority = n->priority; - o->info.network = n->network; - o->info.generation = n->generation; - o->info.foundation = n->foundation; - o->info.component = n->component; - o->info.transport = n->transport; - o->dirty = true; - o->peer = peer; - o->tried = 0; - o->reachable = 0; - - Con_DPrintf("%s remote candidate %s: [%s]:%i\n", isnew?"Added":"Updated", o->info.candidateid, o->info.addr, o->info.port); -} -static void ICE_Destroy(struct icestate_s *con) -{ - //has already been unlinked - Z_Free(con); -} -static void ICE_Tick(void) -{ - struct icestate_s *con; - unsigned int curtime = Sys_Milliseconds(); - - for (con = icelist; con; con = con->next) - { - switch(con->mode) - { - case ICEM_RAW: - //raw doesn't do handshakes or keepalives. it should just directly connect. - //raw just uses the first (assumed only) option - if (con->state == ICE_CONNECTING) - { - struct icecandidate_s *rc; - rc = con->rc; - if (rc) - NET_StringToAdr(rc->info.addr, rc->info.port, &con->chosenpeer); - else - con->chosenpeer.type = NA_INVALID; - ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); - } - break; - case ICEM_ICE: - if (con->state == ICE_CONNECTING) - { - if (con->stunretry < curtime && con->pubstunserver.type != NA_INVALID) - { - ICE_ToStunServer(con); - con->stunretry = curtime + 2*1000; - } - if (con->keepalive < curtime) - { - if (!ICE_SendSpam(con)) - { - struct icecandidate_s *rc; - struct icecandidate_s *best = NULL; - for (rc = con->rc; rc; rc = rc->next) - { - if (rc->reachable && (!best || rc->info.priority > best->info.priority)) - best = rc; - } - if (best) - { - best->tried = ~best->reachable; - con->chosenpeer = best->peer; - ICE_SendSpam(con); - } - else - { - for (rc = con->rc; rc; rc = rc->next) - rc->tried = 0; - } - con->retries++; - if (con->retries > 32) - con->retries = 32; - con->keepalive = curtime + 200*(con->retries); //RTO - } - else - con->keepalive = curtime + 50*(con->retries+1); //Ta - } - } - break; - } - } -} -void QDECL ICE_Close(struct icestate_s *con) -{ - struct icestate_s **link; - - ICE_Set(con, "state", STRINGIFY(ICE_INACTIVE)); - - for (link = &icelist; *link; ) - { - if (con == *link) - { - *link = con->next; - ICE_Destroy(con); - return; - } - else - link = &(*link)->next; - } -} -void QDECL ICE_CloseModule(void *module) -{ - struct icestate_s **link, *con; - - for (link = &icelist; *link; ) - { - con = *link; - if (con->module == module) - { - *link = con->next; - ICE_Destroy(con); - } - else - link = &(*link)->next; - } -} -icefuncs_t iceapi = -{ - ICE_Create, - ICE_Set, - ICE_Get, - ICE_GetLCandidateInfo, - ICE_AddRCandidateInfo, - ICE_Close, - ICE_CloseModule -}; - -static qboolean NET_WasStun(netsrc_t netsrc) -{ -#if !defined(SERVERONLY) && defined(VOICECHAT) - if (netsrc == NS_CLIENT) - { - if (NET_RTP_Parse()) - return true; - } -#endif - - if ((net_from.type == NA_IP || net_from.type == NA_IPV6) && net_message.cursize >= 20) - { - stunhdr_t *stun = (stunhdr_t*)net_message.data; - int stunlen = BigShort(stun->msglen); - if ((stun->msgtype == BigShort(0x0101) || stun->msgtype == BigShort(0x0111)) && net_message.cursize == stunlen + sizeof(*stun)) - { - //binding reply (or error) - netadr_t adr = net_from; - char xor[16]; - short portxor; - stunattr_t *attr = (stunattr_t*)(stun+1); - int alen; - while(stunlen) - { - stunlen -= sizeof(*attr); - alen = (unsigned short)BigShort(attr->attrlen); - if (alen > stunlen) - return false; - stunlen -= alen; - switch(BigShort(attr->attrtype)) - { - default: - break; - case 1: - case 0x20: - if (BigShort(attr->attrtype) == 0x20) - { - portxor = *(short*)&stun->magiccookie; - memcpy(xor, &stun->magiccookie, sizeof(xor)); - } - else - { - portxor = 0; - memset(xor, 0, sizeof(xor)); - } - if (alen == 8 && ((qbyte*)attr)[5] == 1) //ipv4 MAPPED-ADDRESS - { - char str[256]; - adr.type = NA_IP; - adr.port = (((short*)attr)[3]) ^ portxor; - *(int*)adr.address.ip = *(int*)(&((qbyte*)attr)[8]) ^ *(int*)xor; - NET_AdrToString(str, sizeof(str), &adr); - } - else if (alen == 20 && ((qbyte*)attr)[5] == 2) //ipv6 MAPPED-ADDRESS - { - netadr_t adr; - char str[256]; - adr.type = NA_IPV6; - adr.port = (((short*)attr)[3]) ^ portxor; - ((int*)adr.address.ip6)[0] = ((int*)&((qbyte*)attr)[8])[0] ^ ((int*)xor)[0]; - ((int*)adr.address.ip6)[1] = ((int*)&((qbyte*)attr)[8])[1] ^ ((int*)xor)[1]; - ((int*)adr.address.ip6)[2] = ((int*)&((qbyte*)attr)[8])[2] ^ ((int*)xor)[2]; - ((int*)adr.address.ip6)[3] = ((int*)&((qbyte*)attr)[8])[3] ^ ((int*)xor)[3]; - NET_AdrToString(str, sizeof(str), &adr); - } - - { - struct icestate_s *con; - for (con = icelist; con; con = con->next) - { - char str[256]; - struct icecandidate_s *rc; - if (con->mode != ICEM_ICE) - continue; - - //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. - if (NET_CompareAdr(&net_from, &con->pubstunserver)) - { - for (rc = con->lc; rc; rc = rc->next) - { - if (NET_CompareAdr(&adr, &rc->peer)) - break; - } - if (!rc) - { - struct icecandidate_s *rc; - rc = Z_Malloc(sizeof(*rc)); - rc->next = con->lc; - con->lc = rc; - rc->peer = adr; - NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &adr); - rc->info.port = ntohs(adr.port); - rc->info.type = ICE_SRFLX; - rc->info.component = 1; - rc->dirty = true; - rc->info.priority = 1; //FIXME - - Con_DPrintf("Public address: %s\n", str); - } - con->stunretry = Sys_Milliseconds() + 60*1000; - } - else - { - for (rc = con->rc; rc; rc = rc->next) - { - if (NET_CompareAdr(&net_from, &rc->peer)) - { - if (!(rc->reachable & (1u<<(net_from.connum-1)))) - Con_DPrintf("We can reach %s\n", NET_AdrToString(str, sizeof(str), &net_from)); - rc->reachable |= 1u<<(net_from.connum-1); - - if (NET_CompareAdr(&net_from, &con->chosenpeer) && (stun->transactid[2] & BigLong(0x80000000))) - ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); - } - } - } - } - } - break; - case 9: - { - char msg[64]; - char sender[256]; - unsigned short len = BigShort(attr->attrlen)-4; - if (len > sizeof(msg)-1) - len = sizeof(msg)-1; - memcpy(msg, &((qbyte*)attr)[8], len); - msg[len] = 0; - Con_DPrintf("%s: Stun error code %u : %s\n", NET_AdrToString(sender, sizeof(sender), &net_from), ((qbyte*)attr)[7], msg); - if (((qbyte*)attr)[7] == 1) - { - //not authorised. - } - if (((qbyte*)attr)[7] == 87) - { - //role conflict. - } - } - break; - } - alen = (alen+3)&~3; - attr = (stunattr_t*)((char*)(attr+1) + alen); - } - return true; - } - else if (stun->msgtype == BigShort(0x0011) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) - { - //binding indication. used as an rtp keepalive. - return true; - } - else if (stun->msgtype == BigShort(0x0001) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) - { - char username[256]; - char integrity[20]; - char *integritypos = NULL; - int role = 0; - struct icestate_s *con; - unsigned int tiehigh = 0; - unsigned int tielow = 0; - qboolean usecandidate = false; - int error = 0; - unsigned int priority = 0; - - //binding request - stunattr_t *attr = (stunattr_t*)(stun+1); - int alen; - *username = 0; - while(stunlen) - { - alen = (unsigned short)BigShort(attr->attrlen); - if (alen+sizeof(*attr) > stunlen) - return false; - switch((unsigned short)BigShort(attr->attrtype)) - { - default: - //unknown attributes < 0x8000 are 'mandatory to parse', and such packets must be dropped in their entirety. - //other ones are okay. - if (!((unsigned short)BigShort(attr->attrtype) & 0x8000)) - return false; - break; - case 0x6: - //username - if (alen < sizeof(username)) - { - memcpy(username, attr+1, alen); - username[alen] = 0; -// Con_Printf("Stun username = \"%s\"\n", username); - } - break; - case 0x8: - //message integrity - memcpy(integrity, attr+1, sizeof(integrity)); - integritypos = (char*)(attr+1); - break; - case 0x24: - //priority -// Con_Printf("priority = \"%i\"\n", priority); - priority = BigLong(*(int*)(attr+1)); - break; - case 0x25: - //USE-CANDIDATE - usecandidate = true; - break; - case 0x8028: - //fingerprint -// Con_Printf("fingerprint = \"%08x\"\n", BigLong(*(int*)(attr+1))); - break; - case 0x8029://ice controlled - case 0x802A://ice controlling - role = (unsigned short)BigShort(attr->attrtype); - //ice controlled - tiehigh = BigLong(((int*)(attr+1))[0]); - tielow = BigLong(((int*)(attr+1))[1]); - break; - } - alen = (alen+3)&~3; - attr = (stunattr_t*)((char*)(attr+1) + alen); - stunlen -= alen+sizeof(*attr); - } - - //we need to know which connection its from in order to validate the integrity - for (con = icelist; con; con = con->next) - { - if (!strcmp(va("%s:%s", con->lufrag, con->rufrag), username)) - break; - } - if (!con) - { - Con_DPrintf("Received STUN request from unknown user \"%s\"\n", username); - } - else - { - if (integritypos) - { - char key[20]; - //the hmac is a bit weird. the header length includes the integrity attribute's length, but the checksum doesn't even consider the attribute header. - stun->msglen = BigShort(integritypos+sizeof(integrity) - (char*)stun - sizeof(*stun)); - SHA1_HMAC(key, sizeof(key), (qbyte*)stun, integritypos-4 - (char*)stun, con->lpwd, strlen(con->lpwd)); - if (memcmp(key, integrity, sizeof(integrity))) - { - Con_DPrintf("Integrity is bad! needed %x got %x\n", *(int*)key, *(int*)integrity); - return true; - } - } - - if (con->state != ICE_INACTIVE) - { - sizebuf_t buf; - char data[512]; - int alen = 0, atype = 0, aofs = 0; - int crc; - struct icecandidate_s *rc; - memset(&buf, 0, sizeof(buf)); - buf.maxsize = sizeof(data); - buf.cursize = 0; - buf.data = data; - - //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. - for (rc = con->rc; rc; rc = rc->next) - { - if (NET_CompareAdr(&net_from, &rc->peer)) - break; - } - if (!rc) - { - struct icecandidate_s *rc; - rc = Z_Malloc(sizeof(*rc)); - rc->next = con->rc; - con->rc = rc; - rc->peer = net_from; - NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &net_from); - rc->info.port = ntohs(net_from.port); - rc->info.type = ICE_PRFLX; - rc->dirty = true; - rc->info.priority = priority; - } - - //flip ice control role, if we're wrong. - if (role && role != (con->controlled?0x802A:0x8029)) - { - con->controlled = (tiehigh > con->tiehigh) || (tiehigh == con->tiehigh && tielow > con->tielow); - Con_DPrintf("role conflict detected. We should be %s\n", con->controlled?"controlled":"controlling"); - error = 87; - } - else if (usecandidate && con->controlled) - { - //in the controlled role, we're connected once we're told the pair to use (by the usecandidate flag). - //note that this 'nominates' candidate pairs, from which the highest priority is chosen. - //so we just pick select the highest. - //this is problematic, however, as we don't actually know the real priority that the peer thinks we'll nominate it with. - - if (con->chosenpeer.type != NA_INVALID && !NET_CompareAdr(&net_from, &con->chosenpeer)) - Con_DPrintf("Duplicate use-candidate\n"); - con->chosenpeer = net_from; - Con_DPrintf("use-candidate: %s\n", NET_AdrToString(data, sizeof(data), &net_from)); - - ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); - } - - if (net_from.type == NA_IP) - { - alen = 4; - atype = 1; - aofs = 0; - } - else if (net_from.type == NA_IPV6 && - !*(int*)&net_from.address.ip6[0] && - !*(int*)&net_from.address.ip6[4] && - !*(short*)&net_from.address.ip6[8] && - *(short*)&net_from.address.ip6[10] == (short)0xffff) - { //just because we use an ipv6 address for ipv4 internally doesn't mean we should tell the peer that they're on ipv6... - alen = 4; - atype = 1; - aofs = sizeof(net_from.address.ip6) - sizeof(net_from.address.ip); - } - else if (net_from.type == NA_IPV6) - { - alen = 16; - atype = 2; - aofs = 0; - } - - MSG_WriteShort(&buf, BigShort(error?0x0111:0x0101)); - MSG_WriteShort(&buf, BigShort(0)); //fill in later - MSG_WriteLong(&buf, stun->magiccookie); - MSG_WriteLong(&buf, stun->transactid[0]); - MSG_WriteLong(&buf, stun->transactid[1]); - MSG_WriteLong(&buf, stun->transactid[2]); - - if (error) - { - char *txt = "Role Conflict"; - MSG_WriteShort(&buf, BigShort(0x0009)); - MSG_WriteShort(&buf, BigShort(4 + strlen(txt))); - MSG_WriteShort(&buf, 0); //reserved - MSG_WriteByte(&buf, 0); //class - MSG_WriteByte(&buf, error); //code - SZ_Write(&buf, txt, strlen(txt)); //readable - while(buf.cursize&3) //padding - MSG_WriteChar(&buf, 0); - } - else if (1) - { //xor mapped - MSG_WriteShort(&buf, BigShort(0x0020)); - MSG_WriteShort(&buf, BigShort(4+alen)); - MSG_WriteShort(&buf, BigShort(atype)); - MSG_WriteShort(&buf, net_from.port); - SZ_Write(&buf, (char*)&net_from.address + aofs, alen); - } - else - { //non-xor mapped - MSG_WriteShort(&buf, BigShort(0x0001)); - MSG_WriteShort(&buf, BigShort(4+alen)); - MSG_WriteShort(&buf, BigShort(atype)); - MSG_WriteShort(&buf, net_from.port); - SZ_Write(&buf, (char*)&net_from.address + aofs, alen); - } - - MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME - MSG_WriteShort(&buf, BigShort(strlen(username))); - SZ_Write(&buf, username, strlen(username)); - while(buf.cursize&3) - MSG_WriteChar(&buf, 0); - - //message integrity is a bit annoying - data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute - data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; - //but the hash is to the start of the attribute's header - SHA1_HMAC(integrity, sizeof(integrity), con->lpwd, strlen(con->lpwd), data, buf.cursize); - MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY - MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length - SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data - - data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length - data[3] = ((buf.cursize+8-20)>>0)&0xff; - crc = crc32(0, data, buf.cursize)^0x5354554e; - MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT - MSG_WriteShort(&buf, BigShort(sizeof(crc))); - MSG_WriteLong(&buf, BigLong(crc)); - - data[2] = ((buf.cursize-20)>>8)&0xff; - data[3] = ((buf.cursize-20)>>0)&0xff; - NET_SendPacket(netsrc, buf.cursize, data, &net_from); - } - } - - return true; - } - } - return false; -} -#endif - #ifndef CLIENTONLY void SVNET_AddPort_f(void) { @@ -6221,7 +5002,7 @@ qboolean NET_WasSpecialPacket(netsrc_t netsrc) } #ifdef SUPPORT_ICE - if (NET_WasStun(netsrc)) + if (ICE_WasStun(netsrc)) return true; #endif #ifdef HAVE_NATPMP @@ -6574,7 +5355,7 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea Con_DPrintf("connection to \"%s\" reset\n", tf->peer); break; default: - Con_Printf("socket error %i\n", e); + Con_Printf("tcp socket error %i (%s)\n", e, tf->peer); } VFSTCP_Error(tf); } @@ -6608,7 +5389,6 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea { if (tf->sock == INVALID_SOCKET) { -perror("moo"); return -1; //signal an error } return 0; //signal nothing available @@ -6641,14 +5421,19 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt int e = qerrno; switch(e) { + case EWOULDBLOCK: + return 0; //nothing available yet. + case ENOTCONN: + Con_Printf("connection to \"%s\" failed\n", tf->peer); + break; default: - Sys_Printf("socket error %i\n", e); + Sys_Printf("tcp socket error %i (%s)\n", e, tf->peer); break; } // don't destroy it on write errors, because that prevents us from reading anything that was sent to us afterwards. // instead let the read handling kill it if there's nothing new to be read VFSTCP_ReadBytes(file, NULL, 0); - return 0; + return -1; } return len; } diff --git a/engine/common/netinc.h b/engine/common/netinc.h index f91df94b..770cf3d0 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -65,7 +65,7 @@ }; }; - #if !(_MSC_VER >= 1500) + #if !defined(in_addr6) struct in6_addr { u_char s6_addr[16]; /* IPv6 address */ @@ -206,8 +206,8 @@ struct icecandinfo_s enum { ICE_HOST=0, - ICE_SRFLX=1, - ICE_PRFLX=2, + ICE_SRFLX=1, //Server Reflexive (from stun, etc) + ICE_PRFLX=2, //Peer Reflexive ICE_RELAY=3, } type; //says what sort of proxy is used. char reladdr[64]; //when proxied, this is our local info @@ -220,7 +220,8 @@ enum iceproto_e ICEP_INVALID, //not allowed.. ICEP_QWSERVER, //we're server side ICEP_QWCLIENT, //we're client side - ICEP_VOICE //speex. requires client. + ICEP_VOICE, //speex. requires client. + ICEP_VIDEO //err... REALLY?!?!? }; enum icemode_e { @@ -248,3 +249,43 @@ typedef struct } icefuncs_t; extern icefuncs_t iceapi; #endif + + + +#define FTENET_ADDRTYPES 2 +typedef struct ftenet_generic_connection_s { + char name[MAX_QPATH]; + + int (*GetLocalAddress)(struct ftenet_generic_connection_s *con, netadr_t *local, int adridx); + qboolean (*ChangeLocalAddress)(struct ftenet_generic_connection_s *con, const char *newaddress); + qboolean (*GetPacket)(struct ftenet_generic_connection_s *con); + qboolean (*SendPacket)(struct ftenet_generic_connection_s *con, int length, void *data, netadr_t *to); + void (*Close)(struct ftenet_generic_connection_s *con); +#ifdef HAVE_PACKET + int (*SetReceiveFDSet) (struct ftenet_generic_connection_s *con, fd_set *fdset); /*set for connections which have multiple sockets (ie: listening tcp connections)*/ +#endif + + netadrtype_t addrtype[FTENET_ADDRTYPES]; + qboolean islisten; +#ifdef HAVE_PACKET + SOCKET thesocket; +#else + int thesocket; +#endif +} ftenet_generic_connection_t; + +#define MAX_CONNECTIONS 8 +typedef struct ftenet_connections_s +{ + qboolean islisten; + ftenet_generic_connection_t *conn[MAX_CONNECTIONS]; +} ftenet_connections_t; + +void ICE_Tick(void); +qboolean ICE_WasStun(netsrc_t netsrc); +void QDECL ICE_AddLCandidateConn(ftenet_connections_t *col, netadr_t *addr, int type); +void QDECL ICE_AddLCandidateInfo(struct icestate_s *con, netadr_t *adr, int adrno, int type); + +ftenet_connections_t *FTENET_CreateCollection(qboolean listen); +void FTENET_CloseCollection(ftenet_connections_t *col); +qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *name, const char *address, netadrtype_t addrtype, qboolean islisten); diff --git a/engine/common/plugin.c b/engine/common/plugin.c index 3538ce08..1c589e87 100644 --- a/engine/common/plugin.c +++ b/engine/common/plugin.c @@ -41,6 +41,7 @@ typedef struct plugin_s { int executestring; #ifndef SERVERONLY int consolelink; + int consolelinkmouseover; int conexecutecommand; int menufunction; int sbarlevel[3]; //0 - main sbar, 1 - supplementry sbar sections (make sure these can be switched off), 2 - overlays (scoreboard). menus kill all. @@ -341,6 +342,8 @@ static qintptr_t VARGS Plug_ExportToEngine(void *offset, quintptr_t mask, const #ifndef SERVERONLY else if (!strcmp(name, "ConsoleLink")) currentplug->consolelink = functionid; + else if (!strcmp(name, "ConsoleLinkMouseOver")) + currentplug->consolelinkmouseover = functionid; else if (!strcmp(name, "ConExecuteCommand")) currentplug->conexecutecommand = functionid; else if (!strcmp(name, "MenuEvent")) @@ -950,6 +953,8 @@ qintptr_t VARGS Plug_FS_Open(void *offset, quintptr_t mask, const qintptr_t *arg } if (!strcmp(fname, "**plugconfig")) f = FS_OpenVFS(va("%s.cfg", currentplug->name), mode, FS_ROOT); + else if (arg[2] == 2) + f = FS_OpenVFS(fname, mode, FS_GAMEONLY); else f = FS_OpenVFS(fname, mode, FS_GAME); if (!f) @@ -1210,8 +1215,8 @@ void Plug_Load_f(void) { Con_Printf("Loads a plugin\n"); Con_Printf("plug_load [pluginpath]\n"); - Con_Printf("example pluginpath: plugins/blah\n"); - Con_Printf("will load blahx86.dll or blah.so\n"); + Con_Printf("example pluginpath: blah\n"); + Con_Printf("will load fteplug_blah"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX" or $gamedir/plugins/blah.qvm\n"); return; } if (!Plug_Load(plugin, PLUG_EITHER)) @@ -1321,7 +1326,8 @@ void Plug_Initialise(qboolean fromgamedir) if (!fromgamedir) { FS_NativePath("", FS_BINARYPATH, nat, sizeof(nat)); - Sys_EnumerateFiles(nat, "fteplug_*"ARCH_DL_POSTFIX, Plug_EnumeratedRoot, NULL, NULL); + Con_Printf("Loading plugins from %s\n", nat); + Sys_EnumerateFiles(nat, "fteplug_*" ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, Plug_EnumeratedRoot, NULL, NULL); } if (fromgamedir) { @@ -1380,8 +1386,28 @@ qboolean Plugin_ExecuteString(void) } #ifndef SERVERONLY +qboolean Plug_ConsoleLinkMouseOver(float x, float y, char *text, char *info) +{ + qboolean result = false; + plugin_t *oldplug = currentplug; + for (currentplug = plugs; currentplug; currentplug = currentplug->next) + { + if (currentplug->consolelinkmouseover) + { + char buffer[2048]; + Q_strncpyz(buffer, va("\"%s\" \"%s\"", text, info), sizeof(buffer)); + Cmd_TokenizeString(buffer, false, false); + result = VM_Call(currentplug->vm, currentplug->consolelinkmouseover, *(int*)&(x), *(int*)&(y)); + if (result) + break; + } + } + currentplug = oldplug; + return result; +} qboolean Plug_ConsoleLink(char *text, char *info) { + qboolean result = false; plugin_t *oldplug = currentplug; for (currentplug = plugs; currentplug; currentplug = currentplug->next) { @@ -1390,11 +1416,13 @@ qboolean Plug_ConsoleLink(char *text, char *info) char buffer[2048]; Q_strncpyz(buffer, va("\"%s\" \"%s\"", text, info), sizeof(buffer)); Cmd_TokenizeString(buffer, false, false); - VM_Call(currentplug->vm, currentplug->consolelink); + result = VM_Call(currentplug->vm, currentplug->consolelink); + if (result) + break; } } currentplug = oldplug; - return false; + return result; } void Plug_SubConsoleCommand(console_t *con, char *line) diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index 8c6d8b9d..dc85b52b 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -146,7 +146,7 @@ void PF_InitTempStrings(pubprogfuncs_t *prinst) pr_tempstringsize.flags |= CVAR_NOSET; if (pr_tempstringcount.value >= 2) - prinst->tempstringbase = prinst->AddString(prinst, "", MAXTEMPBUFFERLEN*MAX_TEMPSTRS); + prinst->tempstringbase = prinst->AddString(prinst, "", MAXTEMPBUFFERLEN*MAX_TEMPSTRS, false); else prinst->tempstringbase = 0; prinst->tempstringnum = 0; @@ -4566,7 +4566,7 @@ void PR_AutoCvar(pubprogfuncs_t *prinst, cvar_t *var) } } -void PDECL PR_FoundAutoCvarGlobal(pubprogfuncs_t *progfuncs, char *name, eval_t *val, etype_t type) +void PDECL PR_FoundAutoCvarGlobal(pubprogfuncs_t *progfuncs, char *name, eval_t *val, etype_t type, void *ctx) { cvar_t *var; char *vals; @@ -4605,9 +4605,54 @@ void PDECL PR_FoundAutoCvarGlobal(pubprogfuncs_t *progfuncs, char *name, eval_t PR_AutoCvarApply(progfuncs, val, type, var); } -void PR_AutoCvarSetup(pubprogfuncs_t *prinst) +void PDECL PR_FoundDoTranslateGlobal(pubprogfuncs_t *progfuncs, char *name, eval_t *val, etype_t type, void *ctx) { - prinst->FindPrefixGlobals (prinst, "autocvar_", PR_FoundAutoCvarGlobal); + const char *olds; + const char *news; + if ((type & ~DEF_SAVEGLOBAL) != ev_string) + return; + + olds = PR_GetString(progfuncs, val->string); + news = PO_GetText(ctx, olds); + if (news != olds) + val->string = PR_NewString(progfuncs, news, 0); +} + +//called after each progs is loaded +void PR_ProgsAdded(pubprogfuncs_t *prinst, int newprogs, const char *modulename) +{ + vfsfile_t *f = NULL; + char lang[64], *h; + extern cvar_t language; + if (newprogs == -1) + return; + if (*language.string) + { + Q_strncpyz(lang, language.string, sizeof(lang)); + while ((h = strchr(lang, '-'))) + *h = '_'; + for(;;) + { + if (!*lang) + break; + f = FS_OpenVFS(va("%s.%s.po", modulename, lang), "rb", FS_GAME); + if (f) + break; + h = strchr(lang, '_'); + if (h) + *h = 0; + else + break; + } + } + + if (f) + { + void *pofile = PO_Load(f); + prinst->FindPrefixGlobals (prinst, newprogs, "dotranslate_", PR_FoundDoTranslateGlobal, pofile); + PO_Close(pofile); + } + prinst->FindPrefixGlobals (prinst, newprogs, "autocvar_", PR_FoundAutoCvarGlobal, NULL); } lh_extension_t QSG_Extensions[] = { diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index 69a818f5..b4e33d63 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -174,7 +174,7 @@ void QCBUILTIN PF_stoh (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) void QCBUILTIN PF_htos (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void PR_fclose_progs (pubprogfuncs_t *prinst); char *PF_VarString (pubprogfuncs_t *prinst, int first, struct globalvars_s *pr_globals); -void PR_AutoCvarSetup(pubprogfuncs_t *prinst); +void PR_ProgsAdded(pubprogfuncs_t *prinst, int newprogs, const char *modulename); void PR_AutoCvar(pubprogfuncs_t *prinst, cvar_t *var); void QCBUILTIN PF_numentityfields (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_entityfieldname (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); diff --git a/engine/common/translate.c b/engine/common/translate.c index 898e781f..46705621 100644 --- a/engine/common/translate.c +++ b/engine/common/translate.c @@ -2,7 +2,6 @@ static char *defaultlanguagetext = "STL_LANGUAGENAME \"English\"\n" -"TL_NL \"\\n\"\n" "TL_STNL \"%s\\n\"\n" "STL_CLIENTCONNECTED \"client %s connected\\n\"\n" "STL_SPECTATORCONNECTED \"spectator %s connected\\n\"\n" @@ -262,7 +261,8 @@ static char *defaultlanguagetext = -cvar_t language = SCVAR("language", "en-gb"); +char sys_language[64] = ""; +cvar_t language = SCVAR("language", sys_language); char lastlang[9]; typedef struct trans_s { @@ -551,7 +551,7 @@ void TL_ParseLanguage (char *name, char *data, int num) //this is one of the fir break; } - s = COM_ParseCString(s, com_token, sizeof(com_token)); + s = COM_ParseCString(s, com_token, sizeof(com_token), NULL); if (i == STL_MAXSTL) //silently ignore - allow other servers or clients to add stuff continue; @@ -850,6 +850,7 @@ char *T_GetString(int num) //#endif #ifndef SERVERONLY +//for hexen2's objectives and stuff. static char *info_strings_list; static char **info_strings_table; static int info_strings_count; @@ -915,3 +916,128 @@ char *T_GetInfoString(int num) } #endif +struct poline_s +{ + bucket_t buck; + struct poline_s *next; + char *orig; + char *translated; +}; + +struct po_s +{ + hashtable_t hash; + + struct poline_s *lines; +}; + +const char *PO_GetText(struct po_s *po, const char *msg) +{ + struct poline_s *line; + line = Hash_Get(&po->hash, msg); + if (line) + return line->translated; + return msg; +} +static void PO_AddText(struct po_s *po, const char *orig, const char *trans) +{ + size_t olen = strlen(orig)+1; + size_t tlen = strlen(trans)+1; + struct poline_s *line = Z_Malloc(sizeof(*line)+olen+tlen); + memcpy(line+1, orig, olen); + orig = (const char*)(line+1); + line->translated = (char*)(line+1)+olen; + memcpy(line->translated, trans, tlen); + trans = (const char*)(line->translated); + Hash_Add(&po->hash, orig, line, &line->buck); +} +struct po_s *PO_Load(vfsfile_t *file) +{ + struct po_s *po; + unsigned int buckets = 1024; + char *in, *end; + int inlen; + char msgid[32768]; + char msgstr[32768]; + + po = Z_Malloc(sizeof(*po) + Hash_BytesForBuckets(buckets)); + Hash_InitTable(&po->hash, buckets, po+1); + + inlen = file?VFS_GETLEN(file):0; + in = BZ_Malloc(inlen+1); + if (file) + VFS_READ(file, in, inlen); + in[inlen] = 0; + if (file) + VFS_CLOSE(file); + + end = in + inlen; + while(in < end) + { + while(*in == ' ' || *in == '\n' || *in == '\r' || *in == '\t') + in++; + if (*in == '#') + { + while (*in && *in != '\n') + in++; + } + else if (!strncmp(in, "msgid", 5) && (in[5] == ' ' || in[5] == '\t' || in[5] == '\r' || in[5] == '\n')) + { + size_t start = 0; + size_t ofs = 0; + in += 5; + while(1) + { + while(*in == ' ' || *in == '\n' || *in == '\r' || *in == '\t') + in++; + if (*in == '\"') + { + in = COM_ParseCString(in, msgid+start, sizeof(msgid) - start, &ofs); + start += ofs; + } + else + break; + } + } + else if (!strncmp(in, "msgstr", 6) && (in[6] == ' ' || in[6] == '\t' || in[6] == '\r' || in[6] == '\n')) + { + size_t start = 0; + size_t ofs = 0; + in += 6; + while(1) + { + while(*in == ' ' || *in == '\n' || *in == '\r' || *in == '\t') + in++; + if (*in == '\"') + { + in = COM_ParseCString(in, msgstr+start, sizeof(msgstr) - start, &ofs); + start += ofs; + } + else + break; + } + +// if (*msgid && start) + PO_AddText(po, msgid, msgstr); + } + else + { + //some sort of junk? + in++; + while (*in && *in != '\n') + in++; + } + } + + return po; +} +void PO_Close(struct po_s *po) +{ + while(po->lines) + { + struct poline_s *r = po->lines; + po->lines = r->next; + Z_Free(r); + } + Z_Free(po); +} diff --git a/engine/common/translate.h b/engine/common/translate.h index d897628d..7b28c6d6 100644 --- a/engine/common/translate.h +++ b/engine/common/translate.h @@ -1,16 +1,12 @@ #ifdef TRANSLATE_H NAME(STL_LANGUAGENAME) - NAME(TL_NL) - NAME(TL_STNL) - NAME(STL_CLIENTCONNECTED) NAME(STL_SPECTATORCONNECTED) NAME(STL_RECORDEDCLIENTCONNECTED) NAME(STL_RECORDEDSPECTATORCONNECTED) NAME(STL_CLIENTWASBANNED) NAME(STL_YOUWEREBANNED) - NAME(STL_YOUAREBANNED) NAME(STL_CLIENTTIMEDOUT) NAME(STL_LOADZOMIBETIMEDOUT) NAME(STL_CLIENTWASKICKED) @@ -25,13 +21,11 @@ NAME(STL_CLIENTISCUFFEDPERMANENTLY) NAME(STL_CLIENTISCUFFED) NAME(STL_CLIENTISSTILLCUFFED) - NAME(STL_YOUWERECUFFED) NAME(STL_YOUARNTCUFFED) NAME(STL_CLIENTISCRIPPLEDPERMANENTLY) NAME(STL_CLIENTISCRIPPLED) NAME(STL_CLIENTISSTILLCRIPPLED) - NAME(STL_YOUWERECLIPPLED) NAME(STL_YOUARNTCRIPPLED) NAME(STL_CLIENTISMUTEDPERMANENTLY) @@ -42,7 +36,6 @@ NAME(STL_NONAMEASMUTE) NAME(STL_MUTEDVOTE) NAME(STL_MUTEDCHAT) - NAME(STL_FLOODPROTACTIVE) NAME(STL_FLOODPROTTIME) NAME(STL_BUFFERPROTECTION) @@ -54,7 +47,6 @@ NAME(STL_POSSIBLEMODELCHEAT) NAME(STL_MAPCHEAT) NAME(STL_INVALIDTRACKCLIENT) - NAME(STL_BADNAME) NAME(STL_CLIENTNAMECHANGE) NAME(STL_SERVERPAUSED) NAME(STL_UPLOADDENIED) @@ -65,11 +57,9 @@ NAME(STL_CLIENTPAUSED) NAME(STL_CLIENTUNPAUSED) NAME(STL_CLIENTLESSUNPAUSE) - NAME(STL_CURRENTRATE) NAME(STL_RATESETTO) NAME(STL_CURRENTMSGLEVEL) NAME(STL_MSGLEVELSET) - NAME(STL_GAMESAVED) NAME(STL_CLIENTDROPPED) NAME(STL_SNAPREFUSED) @@ -80,7 +70,6 @@ NAME(STL_SPEEDCHEATPOSSIBLE) NAME(STL_INITED) - NAME(STL_BACKBUFSET) NAME(STL_MESSAGEOVERFLOW) @@ -88,14 +77,12 @@ NAME(STL_BUILDINGPHS) NAME(STL_PHSINFO) - NAME(STL_BREAKSTATEMENT) NAME(STL_BADSPRINT) NAME(STL_NOPRECACHE) NAME(STL_CANTFREEWORLD) NAME(STL_CANTFREEPLAYERS) NAME(STL_COMPILEROVER) NAME(STL_EDICTWASFREE) - NAME(STL_NOFREEEDICTS) NAME(STL_NEEDCHEATPARM) NAME(STL_USERDOESNTEXIST) @@ -143,25 +130,13 @@ - NAME(TL_SHAREWAREVERSION) NAME(TL_REGISTEREDVERSION) NAME(TL_CURRENTSEARCHPATH) - NAME(TL_SERACHPATHISPACK) - NAME(TL_SERACHPATHISZIP) - - NAME(TL_COMPRESSEDFILEOPENFAILED) NAME(TL_ADDEDPACKFILE) NAME(TL_COULDNTOPENZIP) NAME(TL_ADDEDZIPFILE) - NAME(TL_GAMEDIRAINTPATH) - NAME(TL_KEYHASSLASH) - NAME(TL_KEYHASQUOTE) - NAME(TL_KEYTOOLONG) - NAME(TL_INFOSTRINGTOOLONG) - NAME(TL_STARKEYPROTECTED) - NAME(TL_KEYHASNOVALUE) NAME(TL_OVERSIZEPACKETFROM) @@ -180,28 +155,16 @@ NAME(STL_SERVERSPAWNED) NAME(TL_EXEDATETIME) - NAME(TL_HEAPSIZE) - - NAME(TL_VERSION) //savegame.c - NAME(STL_SAVESYNTAX) - NAME(STL_NORELATIVEPATHS) NAME(STL_SAVEGAMETO) NAME(STL_ERRORCOULDNTOPEN) - NAME(STL_SAVEDONE) - NAME(STL_LOADSYNTAX) NAME(STL_LOADGAMEFROM) NAME(STL_BADSAVEVERSION) NAME(STL_LOADFAILED) //sv_ccmds.c - NAME(STL_NOMASTERMODE) - NAME(STL_MASTERAT) - NAME(STL_SENDINGPING) NAME(STL_SHUTTINGDOWN) - NAME(STL_LOGGINGOFF) - NAME(STL_LOGGINGTO) NAME(STL_FLOGGINGOFF) NAME(STL_FLOGGINGFAILED) NAME(STL_FLOGGINGTO) @@ -212,7 +175,6 @@ NAME(STL_LOCALINFOSETTINGS) NAME(STL_LOCALINFOSYNTAX) NAME(STL_USERINFOSYNTAX) - NAME(STL_NONEGATIVEVALUES) NAME(STL_CURRENTGAMEDIR) NAME(STL_SVGAMEDIRUSAGE) NAME(STL_GAMEDIRCANTBEPATH) @@ -221,9 +183,6 @@ NAME(STL_SNAPREQUEST) NAME(STL_SNAPUSAGE) - - NAME(TLC_VERSIONST) - NAME(TLC_BADSERVERADDRESS) NAME(TLC_ILLEGALSERVERADDRESS) NAME(TLC_CONNECTINGTO) @@ -243,7 +202,6 @@ NAME(TLC_SYNTAX_FULLINFO) NAME(TLC_SYNTAX_SETINFO) NAME(TLC_PACKET_SYNTAX) - NAME(TLC_BADADDRESS) NAME(TLC_CHANGINGMAP) NAME(TLC_RECONNECTING) NAME(TLC_RECONNECT_NOSERVER) @@ -256,18 +214,15 @@ NAME(TLC_LOCALID_NOTSET) NAME(TLC_LOCALID_BAD) NAME(TLC_A2C_PRINT) - NAME(TLC_A2A_PING) NAME(TLC_S2C_CHALLENGE) NAME(TLC_CONLESSPACKET_UNKNOWN) NAME(TL_RUNTPACKET) NAME(TLC_SERVERTIMEOUT) NAME(TLC_CONNECTFIRST) NAME(TLC_SYNTAX_DOWNLOAD) - NAME(TLC_REQUIRESSERVERMOD) NAME(TLC_CLIENTCON_ERROR_ENDGAME) NAME(TLC_HOSTFATALERROR) NAME(TLC_CONFIGCFG_WRITEFAILED) - NAME(TLC_HOSTSPEEDSOUTPUT) NAME(TLC_QUAKEWORLD_INITED) NAME(TLC_DEDICATEDCANNOTCONNECT) NAME(TLC_Q2CONLESSPACKET_UNKNOWN) @@ -275,23 +230,17 @@ NAME(TL_NORELATIVEPATHS) NAME(TL_NODOWNLOADINDEMO) NAME(TL_DOWNLOADINGFILE) - NAME(TLC_CHECKINGMODELS) - NAME(TLC_CHECKINGSOUNDS) NAME(TL_FILENOTFOUND) NAME(TL_CLS_DOWNLOAD_ISSET) NAME(TL_FAILEDTOOPEN) - NAME(TL_RENAMEFAILED) NAME(TL_UPLOADCOMPLEATE) NAME(TL_FTEEXTENSIONS) NAME(TLC_LINEBREAK_NEWLEVEL) NAME(TLC_PC_PS_NL) NAME(TLC_GOTSVDATAPACKET) - NAME(TLC_BAD_MAXCLIENTS) NAME(TLC_TOOMANYMODELPRECACHES) NAME(TLC_TOOMANYSOUNDPRECACHES) NAME(TLC_PARSESTATICWITHNOMAP) - NAME(TL_FILE_X_MISSING) - NAME(TL_GETACLIENTPACK) NAME(TLC_LINEBREAK_MINUS) NAME(TL_INT_SPACE) diff --git a/engine/common/vm.h b/engine/common/vm.h index b0e6d793..191b3a8e 100644 --- a/engine/common/vm.h +++ b/engine/common/vm.h @@ -70,6 +70,7 @@ qboolean Plug_ChatMessage(char *buffer, int talkernum, int tpflags); void Plug_Command_f(void); int Plug_ConnectionlessClientPacket(char *buffer, int size); qboolean Plug_ConsoleLink(char *text, char *info); +qboolean Plug_ConsoleLinkMouseOver(float x, float y, char *text, char *info); void Plug_DrawReloadImages(void); void Plug_Initialise(qboolean fromgamedir); void Plug_Shutdown(qboolean preliminary); diff --git a/engine/d3d/d3d11_backend.c b/engine/d3d/d3d11_backend.c index 23852622..8413e234 100644 --- a/engine/d3d/d3d11_backend.c +++ b/engine/d3d/d3d11_backend.c @@ -10,14 +10,20 @@ extern ID3D11Device *pD3DDev11; extern ID3D11DeviceContext *d3ddevctx; +extern cvar_t r_shadow_realtime_world_lightmaps; +extern cvar_t gl_overbright; + +void D3D11_TerminateShadowMap(void); +void D3D11BE_BeginShadowmapFace(void); + //#define d3dcheck(foo) foo #define d3dcheck(foo) do{HRESULT err = foo; if (FAILED(err)) Sys_Error("D3D reported error on backend line %i - error 0x%x\n", __LINE__, (unsigned int)err);} while(0) #define MAX_TMUS 16 -extern float d3d_trueprojection[16]; - static void BE_RotateForEntity (const entity_t *e, const model_t *mod); +void D3D11BE_SetupLightCBuffer(dlight_t *l, vec3_t colour); +texid_t D3D11_GetShadowMap(int id); /*========================================== tables for deforms =====================================*/ #if 0 @@ -120,10 +126,22 @@ typedef struct { float m_view[16]; float m_projection[16]; - vec3_t v_eyepos; - float v_time; + vec3_t v_eyepos; float v_time; + vec3_t e_light_ambient; float pad1; + vec3_t e_light_dir; float pad2; + vec3_t e_light_mul; float pad3; } cbuf_view_t; +typedef struct +{ + float l_cubematrix[16]; + vec3_t l_lightposition; float padl1; + vec3_t l_colour; float pad2; + vec3_t l_lightcolourscale; float l_lightradius; + vec4_t l_shadowmapproj; + vec2_t l_shadowmapscale; vec2_t pad3; +} cbuf_light_t; + //entity-specific constant-buffer typedef struct { @@ -133,6 +151,7 @@ typedef struct vec3_t e_light_ambient; float pad1; vec3_t e_light_dir; float pad2; vec3_t e_light_mul; float pad3; + vec4_t e_lmscale[4]; } cbuf_entity_t; //vertex attributes @@ -147,16 +166,27 @@ typedef struct byte_vec4_t colorsb; } vbovdata_t; +typedef struct blendstates_s +{ + struct blendstates_s *next; + ID3D11BlendState *val; + unsigned int bits; +} blendstates_t; + typedef struct { + unsigned int inited; + backendmode_t mode; unsigned int flags; + float identitylighting; float curtime; const entity_t *curentity; const dlight_t *curdlight; vec3_t curdlight_colours; shader_t *curshader; + shader_t *depthonly; texnums_t *curtexnums; int curvertdecl; unsigned int shaderbits; @@ -168,17 +198,23 @@ typedef struct vbo_t *batchvbo; batch_t *curbatch; batch_t dummybatch; + vec4_t lightshadowmapproj; + vec2_t lightshadowmapscale; - shader_t *shader_rtlight; + unsigned int curlmode; + shader_t *shader_rtlight[LSHADER_MODES]; texid_t curtex[MAX_TMUS]; unsigned int tmuflags[MAX_TMUS]; ID3D11SamplerState *cursamplerstate[MAX_TMUS]; - ID3D11SamplerState *sampstate[(SHADER_PASS_NEAREST|SHADER_PASS_CLAMP)+1]; + ID3D11SamplerState *sampstate[(SHADER_PASS_NEAREST|SHADER_PASS_CLAMP|SHADER_PASS_DEPTHCMP)+1]; + ID3D11DepthStencilState *depthstates[1u<<4]; //index, its fairly short. + blendstates_t *blendstates; //list. this could get big. mesh_t **meshlist; unsigned int nummeshes; #define NUMECBUFFERS 8 + ID3D11Buffer *lcbuffer; ID3D11Buffer *vcbuffer; ID3D11Buffer *ecbuffers[NUMECBUFFERS]; int ecbufferidx; @@ -198,6 +234,10 @@ typedef struct qboolean purgeindexstream; ID3D11Buffer *indexstream; int indexstreamoffset; + + + int numlivevbos; + int numliveshadowbuffers; } d3d11backend_t; #define VERTEXSTREAMSIZE (1024*1024*2) //2mb = 1 PAE jumbo page @@ -213,12 +253,24 @@ static void BE_CreateSamplerStates(void) { D3D11_SAMPLER_DESC sampdesc; int flags; - for (flags = 0; flags <= (SHADER_PASS_CLAMP|SHADER_PASS_NEAREST); flags++) + for (flags = 0; flags <= (SHADER_PASS_CLAMP|SHADER_PASS_NEAREST|SHADER_PASS_DEPTHCMP); flags++) { - if (flags & SHADER_PASS_NEAREST) - sampdesc.Filter = D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR; + if (flags & SHADER_PASS_DEPTHCMP) + { + if (flags & SHADER_PASS_NEAREST) + sampdesc.Filter = D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_MIP_POINT; + else + sampdesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT; + sampdesc.ComparisonFunc = D3D11_COMPARISON_LESS_EQUAL; + } else - sampdesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + { + if (flags & SHADER_PASS_NEAREST) + sampdesc.Filter = D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR; + else + sampdesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sampdesc.ComparisonFunc = D3D11_COMPARISON_NEVER; + } if (flags & SHADER_PASS_CLAMP) { sampdesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; @@ -233,7 +285,6 @@ static void BE_CreateSamplerStates(void) } sampdesc.MipLODBias = 0.0f; sampdesc.MaxAnisotropy = 1; - sampdesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; sampdesc.BorderColor[0] = 0; sampdesc.BorderColor[1] = 0; sampdesc.BorderColor[2] = 0; @@ -244,22 +295,85 @@ static void BE_CreateSamplerStates(void) ID3D11Device_CreateSamplerState(pD3DDev11, &sampdesc, &shaderstate.sampstate[flags]); } } -static void BE_DestroySamplerStates(void) +static void BE_DestroyVariousStates(void) { + blendstates_t *bs; int flags; - for (flags = 0; flags <= (SHADER_PASS_CLAMP|SHADER_PASS_NEAREST); flags++) + int i; + + for (i = 0; i < MAX_TMUS/*shaderstate.lastpasscount*/; i++) + { + shaderstate.cursamplerstate[i] = NULL; + } + if (d3ddevctx && i) + ID3D11DeviceContext_PSSetSamplers(d3ddevctx, 0, i, shaderstate.cursamplerstate); + + for (flags = 0; flags <= (SHADER_PASS_CLAMP|SHADER_PASS_NEAREST|SHADER_PASS_DEPTHCMP); flags++) { if (shaderstate.sampstate[flags]) ID3D11SamplerState_Release(shaderstate.sampstate[flags]); shaderstate.sampstate[flags] = NULL; } + + if (d3ddevctx) + ID3D11DeviceContext_OMSetDepthStencilState(d3ddevctx, NULL, 0); + for (i = 0; i < (1u<<4); i++) + { + if (shaderstate.depthstates[i]) + ID3D11DepthStencilState_Release(shaderstate.depthstates[i]); + shaderstate.depthstates[i] = NULL; + } + + if (d3ddevctx) + ID3D11DeviceContext_OMSetBlendState(d3ddevctx, NULL, NULL, 0xffffffff); + //hopefully the caches inside shaders should get flushed too... + while(shaderstate.blendstates) + { + bs = shaderstate.blendstates; + shaderstate.blendstates = bs->next; + + if (bs->val) + ID3D11BlendState_Release(bs->val); + BZ_Free(bs); + } + + if (shaderstate.indexstream) + ID3D11Buffer_Release(shaderstate.indexstream); + shaderstate.indexstream = NULL; + + if (shaderstate.vertexstream) + ID3D11Buffer_Release(shaderstate.vertexstream); + shaderstate.vertexstream = NULL; + + if (shaderstate.lcbuffer) + ID3D11Buffer_Release(shaderstate.lcbuffer); + shaderstate.lcbuffer = NULL; + + if (shaderstate.vcbuffer) + ID3D11Buffer_Release(shaderstate.vcbuffer); + shaderstate.vcbuffer = NULL; + + for (i = 0; i < NUMECBUFFERS; i++) + { + if (shaderstate.ecbuffers[i]) + ID3D11Buffer_Release(shaderstate.ecbuffers[i]); + shaderstate.ecbuffers[i] = NULL; + } + + //make sure the device doesn't have any textures still referenced. + for (i = 0; i < MAX_TMUS/*shaderstate.lastpasscount*/; i++) + { + shaderstate.pendingtextures[i] = NULL; + } + if (d3ddevctx && i) + ID3D11DeviceContext_PSSetShaderResources(d3ddevctx, 0, i, shaderstate.pendingtextures); } static void BE_ApplyTMUState(unsigned int tu, unsigned int flags) { ID3D11SamplerState *nstate; - flags = flags & (SHADER_PASS_CLAMP|SHADER_PASS_NEAREST); + flags = flags & (SHADER_PASS_CLAMP|SHADER_PASS_NEAREST|SHADER_PASS_DEPTHCMP); nstate = shaderstate.sampstate[flags]; if (nstate != shaderstate.cursamplerstate[tu]) { @@ -311,12 +425,81 @@ static void BE_ApplyTMUState(unsigned int tu, unsigned int flags) */ } -static void D3D11BE_ApplyShaderBits(unsigned int bits) +static void *D3D11BE_GenerateBlendState(unsigned int bits) +{ + D3D11_BLEND_DESC blend = {0}; + ID3D11BlendState *newblendstate; + blend.IndependentBlendEnable = FALSE; + blend.AlphaToCoverageEnable = FALSE; //FIXME + + if (bits & SBITS_BLEND_BITS) + { + switch(bits & SBITS_SRCBLEND_BITS) + { + case SBITS_SRCBLEND_ZERO: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO; break; + case SBITS_SRCBLEND_ONE: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; break; + case SBITS_SRCBLEND_DST_COLOR: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_DEST_COLOR; break; + case SBITS_SRCBLEND_ONE_MINUS_DST_COLOR: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_INV_DEST_COLOR; break; + case SBITS_SRCBLEND_SRC_ALPHA: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; break; + case SBITS_SRCBLEND_ONE_MINUS_SRC_ALPHA: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_INV_SRC_ALPHA; break; + case SBITS_SRCBLEND_DST_ALPHA: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_DEST_ALPHA; break; + case SBITS_SRCBLEND_ONE_MINUS_DST_ALPHA: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_INV_DEST_ALPHA; break; + case SBITS_SRCBLEND_ALPHA_SATURATE: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA_SAT; break; + default: Sys_Error("Bad shader blend src\n"); return NULL; + } + switch(bits & SBITS_DSTBLEND_BITS) + { + case SBITS_DSTBLEND_ZERO: blend.RenderTarget[0].DestBlend = D3D11_BLEND_ZERO; break; + case SBITS_DSTBLEND_ONE: blend.RenderTarget[0].DestBlend = D3D11_BLEND_ONE; break; + case SBITS_DSTBLEND_SRC_ALPHA: blend.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_ALPHA; break; + case SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA: blend.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; break; + case SBITS_DSTBLEND_DST_ALPHA: blend.RenderTarget[0].DestBlend = D3D11_BLEND_DEST_ALPHA; break; + case SBITS_DSTBLEND_ONE_MINUS_DST_ALPHA: blend.RenderTarget[0].DestBlend = D3D11_BLEND_INV_DEST_ALPHA; break; + case SBITS_DSTBLEND_SRC_COLOR: blend.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_COLOR; break; + case SBITS_DSTBLEND_ONE_MINUS_SRC_COLOR: blend.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_COLOR; break; + default: Sys_Error("Bad shader blend dst\n"); return NULL; + } + blend.RenderTarget[0].BlendEnable = TRUE; + } + else + { + blend.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO; + blend.RenderTarget[0].DestBlend = D3D11_BLEND_ZERO; + blend.RenderTarget[0].BlendEnable = FALSE; + } + blend.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blend.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA;//blend.RenderTarget[0].SrcBlend; + blend.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;//blend.RenderTarget[0].DestBlend; + blend.RenderTarget[0].BlendOpAlpha = blend.RenderTarget[0].BlendOp; + + if (bits&SBITS_MASK_BITS) + { + blend.RenderTarget[0].RenderTargetWriteMask = 0; + if (!(bits&SBITS_MASK_RED)) + blend.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_RED; + if (!(bits&SBITS_MASK_GREEN)) + blend.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_GREEN; + if (!(bits&SBITS_MASK_BLUE)) + blend.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_BLUE; + if (!(bits&SBITS_MASK_ALPHA)) + blend.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_ALPHA; + } + else + blend.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + if (!FAILED(ID3D11Device_CreateBlendState(pD3DDev11, &blend, &newblendstate))) + return newblendstate; + return NULL; +} + +static void D3D11BE_ApplyShaderBits(unsigned int bits, void **blendstatecache) { unsigned int delta; if (shaderstate.flags & (BEF_FORCEADDITIVE|BEF_FORCETRANSPARENT|BEF_FORCENODEPTH|BEF_FORCEDEPTHTEST|BEF_FORCEDEPTHWRITE)) { + blendstatecache = NULL; + if (shaderstate.flags & BEF_FORCEADDITIVE) bits = (bits & ~(SBITS_MISC_DEPTHWRITE|SBITS_BLEND_BITS|SBITS_ATEST_BITS)) | (SBITS_SRCBLEND_SRC_ALPHA | SBITS_DSTBLEND_ONE); @@ -345,71 +528,28 @@ static void D3D11BE_ApplyShaderBits(unsigned int bits) if (delta & (SBITS_BLEND_BITS|SBITS_MASK_BITS)) { - D3D11_BLEND_DESC blend = {0}; - ID3D11BlendState *newblendstate; - blend.IndependentBlendEnable = FALSE; - blend.AlphaToCoverageEnable = FALSE; //FIXME - - if (bits & SBITS_BLEND_BITS) - { - switch(bits & SBITS_SRCBLEND_BITS) - { - case SBITS_SRCBLEND_ZERO: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO; break; - case SBITS_SRCBLEND_ONE: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; break; - case SBITS_SRCBLEND_DST_COLOR: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_DEST_COLOR; break; - case SBITS_SRCBLEND_ONE_MINUS_DST_COLOR: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_INV_DEST_COLOR; break; - case SBITS_SRCBLEND_SRC_ALPHA: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; break; - case SBITS_SRCBLEND_ONE_MINUS_SRC_ALPHA: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_INV_SRC_ALPHA; break; - case SBITS_SRCBLEND_DST_ALPHA: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_DEST_ALPHA; break; - case SBITS_SRCBLEND_ONE_MINUS_DST_ALPHA: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_INV_DEST_ALPHA; break; - case SBITS_SRCBLEND_ALPHA_SATURATE: blend.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA_SAT; break; - default: Sys_Error("Bad shader blend src\n"); return; - } - switch(bits & SBITS_DSTBLEND_BITS) - { - case SBITS_DSTBLEND_ZERO: blend.RenderTarget[0].DestBlend = D3D11_BLEND_ZERO; break; - case SBITS_DSTBLEND_ONE: blend.RenderTarget[0].DestBlend = D3D11_BLEND_ONE; break; - case SBITS_DSTBLEND_SRC_ALPHA: blend.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_ALPHA; break; - case SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA: blend.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; break; - case SBITS_DSTBLEND_DST_ALPHA: blend.RenderTarget[0].DestBlend = D3D11_BLEND_DEST_ALPHA; break; - case SBITS_DSTBLEND_ONE_MINUS_DST_ALPHA: blend.RenderTarget[0].DestBlend = D3D11_BLEND_INV_DEST_ALPHA; break; - case SBITS_DSTBLEND_SRC_COLOR: blend.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_COLOR; break; - case SBITS_DSTBLEND_ONE_MINUS_SRC_COLOR: blend.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_COLOR; break; - default: Sys_Error("Bad shader blend dst\n"); return; - } - blend.RenderTarget[0].BlendEnable = TRUE; - } + int sbits = bits & (SBITS_BLEND_BITS|SBITS_MASK_BITS); + if (blendstatecache && *blendstatecache) + ID3D11DeviceContext_OMSetBlendState(d3ddevctx, *blendstatecache, NULL, 0xffffffff); else { - blend.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO; - blend.RenderTarget[0].DestBlend = D3D11_BLEND_ZERO; - blend.RenderTarget[0].BlendEnable = FALSE; - } - blend.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - blend.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA;//blend.RenderTarget[0].SrcBlend; - blend.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;//blend.RenderTarget[0].DestBlend; - blend.RenderTarget[0].BlendOpAlpha = blend.RenderTarget[0].BlendOp; - - if (bits&SBITS_MASK_BITS) - { - blend.RenderTarget[0].RenderTargetWriteMask = 0; - if (!(bits&SBITS_MASK_RED)) - blend.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_RED; - if (!(bits&SBITS_MASK_GREEN)) - blend.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_GREEN; - if (!(bits&SBITS_MASK_BLUE)) - blend.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_BLUE; - if (!(bits&SBITS_MASK_ALPHA)) - blend.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_ALPHA; - } - else - blend.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - - - if (!FAILED(ID3D11Device_CreateBlendState(pD3DDev11, &blend, &newblendstate))) - { - ID3D11DeviceContext_OMSetBlendState(d3ddevctx, newblendstate, NULL, 0xffffffff); - ID3D11BlendState_Release(newblendstate); + blendstates_t *bs; + for (bs = shaderstate.blendstates; bs; bs = bs->next) + { + if (bs->bits == sbits) + break; + } + if (!bs) + { + bs = BZ_Malloc(sizeof(*bs)); + bs->next = shaderstate.blendstates; + shaderstate.blendstates = bs; + bs->bits = sbits; + bs->val = D3D11BE_GenerateBlendState(sbits); + } + ID3D11DeviceContext_OMSetBlendState(d3ddevctx, bs->val, NULL, 0xffffffff); + if (blendstatecache) + *blendstatecache = bs->val; } } @@ -444,50 +584,60 @@ static void D3D11BE_ApplyShaderBits(unsigned int bits) if (delta & (SBITS_MISC_DEPTHEQUALONLY|SBITS_MISC_DEPTHCLOSERONLY|SBITS_MISC_NODEPTHTEST|SBITS_MISC_DEPTHWRITE)) { - D3D11_DEPTH_STENCIL_DESC depthdesc; - ID3D11DepthStencilState *newdepthstate; - + unsigned int key = 0; + if (bits & SBITS_MISC_DEPTHEQUALONLY) + key |= 1u<<0; + if (bits & SBITS_MISC_DEPTHCLOSERONLY) + key |= 1u<<1; if (bits & SBITS_MISC_NODEPTHTEST) - depthdesc.DepthEnable = false; - else - depthdesc.DepthEnable = true; + key |= 1u<<2; if (bits & SBITS_MISC_DEPTHWRITE) - depthdesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + key |= 1u<<3; + + if (shaderstate.depthstates[key]) + ID3D11DeviceContext_OMSetDepthStencilState(d3ddevctx, shaderstate.depthstates[key], 0); else - depthdesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; - - switch(bits & (SBITS_MISC_DEPTHEQUALONLY|SBITS_MISC_DEPTHCLOSERONLY)) { - default: - case 0: - depthdesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; - break; - case SBITS_MISC_DEPTHEQUALONLY: - depthdesc.DepthFunc = D3D11_COMPARISON_EQUAL; - break; - case SBITS_MISC_DEPTHCLOSERONLY: - depthdesc.DepthFunc = D3D11_COMPARISON_LESS; - break; - } + D3D11_DEPTH_STENCIL_DESC depthdesc; + if (bits & SBITS_MISC_NODEPTHTEST) + depthdesc.DepthEnable = false; + else + depthdesc.DepthEnable = true; + if (bits & SBITS_MISC_DEPTHWRITE) + depthdesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + else + depthdesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; - //make sure the stencil part is actually valid, even if we're not using it. - depthdesc.StencilEnable = false; - depthdesc.StencilReadMask = 0xFF; - depthdesc.StencilWriteMask = 0xFF; - depthdesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; - depthdesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; - depthdesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; - depthdesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; - depthdesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; - depthdesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; - depthdesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; - depthdesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + switch(bits & (SBITS_MISC_DEPTHEQUALONLY|SBITS_MISC_DEPTHCLOSERONLY)) + { + default: + case 0: + depthdesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; + break; + case SBITS_MISC_DEPTHEQUALONLY: + depthdesc.DepthFunc = D3D11_COMPARISON_EQUAL; + break; + case SBITS_MISC_DEPTHCLOSERONLY: + depthdesc.DepthFunc = D3D11_COMPARISON_LESS; + break; + } - //and change it - if (!FAILED(ID3D11Device_CreateDepthStencilState(pD3DDev11, &depthdesc, &newdepthstate))) - { - ID3D11DeviceContext_OMSetDepthStencilState(d3ddevctx, newdepthstate, 0); - ID3D11DepthStencilState_Release(newdepthstate); + //make sure the stencil part is actually valid, even if we're not using it. + depthdesc.StencilEnable = false; + depthdesc.StencilReadMask = 0xFF; + depthdesc.StencilWriteMask = 0xFF; + depthdesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + depthdesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; + depthdesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + depthdesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + depthdesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + depthdesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; + depthdesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + depthdesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + + //and change it + if (!FAILED(ID3D11Device_CreateDepthStencilState(pD3DDev11, &depthdesc, &shaderstate.depthstates[key]))) + ID3D11DeviceContext_OMSetDepthStencilState(d3ddevctx, shaderstate.depthstates[key], 0); } } } @@ -495,6 +645,9 @@ static void D3D11BE_ApplyShaderBits(unsigned int bits) void D3D11BE_Reset(qboolean before) { int i; + if (!shaderstate.inited) + return; + if (before) { /*backbuffer is going away, release stuff so it can be destroyed cleanly*/ @@ -510,7 +663,7 @@ void D3D11BE_Reset(qboolean before) /*force all state to change, thus setting a known state*/ shaderstate.shaderbits = ~0; - D3D11BE_ApplyShaderBits(0); + D3D11BE_ApplyShaderBits(0, NULL); } } @@ -527,6 +680,18 @@ static const char LIGHTPASS_SHADER[] = "\ {\n\ map $specular\n\ }\n\ + {\n\ + map $lightcubemap\n\ + }\n\ + {\n\ + map $shadowmap\n\ + }\n\ + {\n\ + map $loweroverlay\n\ + }\n\ + {\n\ + map $upperoverlay\n\ + }\n\ }"; void D3D11BE_Init(void) @@ -536,6 +701,7 @@ void D3D11BE_Init(void) be_maxpasses = MAX_TMUS; memset(&shaderstate, 0, sizeof(shaderstate)); + shaderstate.inited = true; shaderstate.curvertdecl = -1; for (i = 0; i < MAXRLIGHTMAPS; i++) shaderstate.dummybatch.lightmap[i] = -1; @@ -572,6 +738,15 @@ void D3D11BE_Init(void) if (FAILED(ID3D11Device_CreateBuffer(pD3DDev11, &bd, NULL, &shaderstate.vcbuffer))) return; + bd.Usage = D3D11_USAGE_DYNAMIC; + bd.ByteWidth = sizeof(cbuf_light_t); + bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + bd.MiscFlags = 0; + bd.StructureByteStride = 0; + if (FAILED(ID3D11Device_CreateBuffer(pD3DDev11, &bd, NULL, &shaderstate.lcbuffer))) + return; + //generate the streaming buffers for stuff that doesn't provide info in nice static vbos bd.BindFlags = D3D11_BIND_INDEX_BUFFER; bd.ByteWidth = VERTEXSTREAMSIZE; @@ -590,14 +765,34 @@ void D3D11BE_Init(void) if (FAILED(ID3D11Device_CreateBuffer(pD3DDev11, &bd, NULL, &shaderstate.vertexstream))) return; - shaderstate.shader_rtlight = R_RegisterShader("rtlight", SUF_NONE, LIGHTPASS_SHADER); + for (i = 0; i < LSHADER_MODES; i++) + { + if ((i & LSHADER_CUBE) && (i & LSHADER_SPOT)) + continue; + shaderstate.shader_rtlight[i] = R_RegisterShader(va("rtlight%s%s%s", + (i & LSHADER_SMAP)?"#PCF":"", + (i & LSHADER_SPOT)?"#SPOT":"", + (i & LSHADER_CUBE)?"#CUBE":"") + , SUF_NONE, LIGHTPASS_SHADER); + } +// shaderstate.shader_rtlight = R_RegisterShader("rtlight", SUF_NONE, LIGHTPASS_SHADER); + shaderstate.depthonly = R_RegisterShader("depthonly", SUF_NONE, + "{\n" + "program depthonly\n" + "{\n" + "depthwrite\n" + "maskcolor\n" + "}\n" + "}\n"); R_InitFlashblends(); } void D3D11BE_Shutdown(void) { - BE_DestroySamplerStates(); + shaderstate.inited = false; + D3D11_TerminateShadowMap(); + BE_DestroyVariousStates(); Z_Free(shaderstate.wbatches); shaderstate.wbatches = NULL; } @@ -653,19 +848,26 @@ static void BindTexture(unsigned int tu, const texid_t *id) static void SelectPassTexture(unsigned int tu, shaderpass_t *pass) { - extern texid_t r_whiteimage; + extern texid_t r_whiteimage, missing_texture_gloss, missing_texture_normal; texid_t foo; switch(pass->texgen) { default: + case T_GEN_DIFFUSE: BindTexture(tu, &shaderstate.curtexnums->base); break; case T_GEN_NORMALMAP: - BindTexture(tu, &shaderstate.curtexnums->bump); + if (TEXVALID(shaderstate.curtexnums->bump)) + BindTexture(tu, &shaderstate.curtexnums->bump); + else + BindTexture(tu, &missing_texture_normal); break; case T_GEN_SPECULAR: - BindTexture(tu, &shaderstate.curtexnums->specular); + if (TEXVALID(shaderstate.curtexnums->specular)) + BindTexture(tu, &shaderstate.curtexnums->specular); + else + BindTexture(tu, &missing_texture_gloss); break; case T_GEN_UPPEROVERLAY: BindTexture(tu, &shaderstate.curtexnums->upperoverlay); @@ -679,6 +881,8 @@ static void SelectPassTexture(unsigned int tu, shaderpass_t *pass) case T_GEN_ANIMMAP: BindTexture(tu, &pass->anim_frames[(int)(pass->anim_fps * shaderstate.curtime) % pass->anim_numframes]); break; + case T_GEN_3DMAP: + case T_GEN_CUBEMAP: case T_GEN_SINGLEMAP: BindTexture(tu, &pass->anim_frames[0]); break; @@ -713,6 +917,30 @@ static void SelectPassTexture(unsigned int tu, shaderpass_t *pass) BindTexture(tu, &foo); } break; + + case T_GEN_LIGHTCUBEMAP: //light's projected cubemap + BindTexture(tu, &shaderstate.curdlight->cubetexture); + break; + + case T_GEN_SHADOWMAP: //light's depth values. + if (!shaderstate.curdlight) + break; + foo = D3D11_GetShadowMap(shaderstate.curdlight->fov>0); + BindTexture(tu, &foo); + break; + + case T_GEN_CURRENTRENDER://copy the current screen to a texture, and draw that + + case T_GEN_SOURCECOLOUR: //used for render-to-texture targets + case T_GEN_SOURCEDEPTH: //used for render-to-texture targets + + case T_GEN_REFLECTION: //reflection image (mirror-as-fbo) + case T_GEN_REFRACTION: //refraction image (portal-as-fbo) + case T_GEN_REFRACTIONDEPTH: //refraction image (portal-as-fbo) + case T_GEN_RIPPLEMAP: //ripplemap image (water surface distortions-as-fbo) + + case T_GEN_SOURCECUBE: //used for render-to-texture targets + break; } BE_ApplyTMUState(tu, pass->flags); @@ -1587,10 +1815,11 @@ static void BE_SubmitMeshChain(int idxfirst) static void BE_ApplyUniforms(program_t *prog, int permu) { - ID3D11Buffer *cbuf[2] = + ID3D11Buffer *cbuf[3] = { - shaderstate.ecbuffers[shaderstate.ecbufferidx], - shaderstate.vcbuffer + shaderstate.ecbuffers[shaderstate.ecbufferidx], //entity buffer + shaderstate.vcbuffer, //view buffer that changes rarely + shaderstate.lcbuffer //light buffer that changes rarelyish }; //FIXME: how many of these calls can we avoid? ID3D11DeviceContext_IASetInputLayout(d3ddevctx, prog->permu[permu].handle.hlsl.layout); @@ -1598,8 +1827,8 @@ static void BE_ApplyUniforms(program_t *prog, int permu) ID3D11DeviceContext_PSSetShader(d3ddevctx, prog->permu[permu].handle.hlsl.frag, NULL, 0); ID3D11DeviceContext_IASetPrimitiveTopology(d3ddevctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - ID3D11DeviceContext_VSSetConstantBuffers(d3ddevctx, 0, 2, cbuf); - ID3D11DeviceContext_PSSetConstantBuffers(d3ddevctx, 0, 2, cbuf); + ID3D11DeviceContext_VSSetConstantBuffers(d3ddevctx, 0, 3, cbuf); + ID3D11DeviceContext_PSSetConstantBuffers(d3ddevctx, 0, 3, cbuf); } static void BE_RenderMeshProgram(shader_t *s, unsigned int vertcount, unsigned int idxfirst, unsigned int idxcount) @@ -1623,7 +1852,7 @@ static void BE_RenderMeshProgram(shader_t *s, unsigned int vertcount, unsigned i BE_ApplyUniforms(p, perm); - D3D11BE_ApplyShaderBits(s->passes->shaderbits); + D3D11BE_ApplyShaderBits(s->passes->shaderbits, &s->passes->becache); /*activate tmus*/ for (passno = 0; passno < s->numpasses; passno++) @@ -1637,7 +1866,7 @@ static void BE_RenderMeshProgram(shader_t *s, unsigned int vertcount, unsigned i shaderstate.textureschanged = true; } if (shaderstate.textureschanged) - ID3D11DeviceContext_PSSetShaderResources(d3ddevctx, 0, passno, shaderstate.pendingtextures); + ID3D11DeviceContext_PSSetShaderResources(d3ddevctx, 0, max(passno, s->numpasses), shaderstate.pendingtextures); shaderstate.lastpasscount = s->numpasses; BE_SubmitMeshChain(idxfirst); @@ -1645,6 +1874,7 @@ static void BE_RenderMeshProgram(shader_t *s, unsigned int vertcount, unsigned i static void D3D11BE_Cull(unsigned int cullflags) { + HRESULT hr; D3D11_RASTERIZER_DESC rasterdesc; ID3D11RasterizerState *newrasterizerstate; @@ -1683,10 +1913,39 @@ static void D3D11BE_Cull(unsigned int cullflags) rasterdesc.FillMode = D3D11_FILL_SOLID; rasterdesc.FrontCounterClockwise = false; rasterdesc.MultisampleEnable = false; - rasterdesc.ScissorEnable = true; + rasterdesc.ScissorEnable = false;//true; rasterdesc.SlopeScaledDepthBias = 0.0f; - ID3D11Device_CreateRasterizerState(pD3DDev11, &rasterdesc, &newrasterizerstate); + if (FAILED(hr=ID3D11Device_CreateRasterizerState(pD3DDev11, &rasterdesc, &newrasterizerstate))) + { + if (hr == DXGI_ERROR_DEVICE_REMOVED) + { + hr = ID3D11Device_GetDeviceRemovedReason(pD3DDev11); + switch(hr) + { + case DXGI_ERROR_DEVICE_HUNG: + Sys_Error("Your graphics driver found something too interesting\n"); + break; + case DXGI_ERROR_DEVICE_REMOVED: + Sys_Error("You unplugged your graphics card. A bit rude, don't you think?\n"); + break; + case DXGI_ERROR_DEVICE_RESET: + Sys_Error("Your drivers are fucked and got reset\n"); + break; + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: + Sys_Error("Your graphics driver is beyond redemption\n"); + break; + case DXGI_ERROR_INVALID_CALL: + Sys_Error("invalid call! oh noes!\n"); + break; + default: + break; + } + } + else + Con_Printf("ID3D11Device_CreateRasterizerState failed\n"); + return; + } ID3D11DeviceContext_RSSetState(d3ddevctx, newrasterizerstate); ID3D11RasterizerState_Release(newrasterizerstate); } @@ -1697,7 +1956,7 @@ static void BE_DrawMeshChain_Internal(void) unsigned int vertcount, idxcount, idxfirst; mesh_t *m; // void *map; - int i; +// int i; unsigned int mno; unsigned int passno = 0; shaderpass_t *pass = shaderstate.curshader->passes; @@ -1790,9 +2049,12 @@ static void BE_DrawMeshChain_Internal(void) switch (shaderstate.mode) { case BEM_LIGHT: - BE_RenderMeshProgram(shaderstate.shader_rtlight, vertcount, idxfirst, idxcount); + if (shaderstate.shader_rtlight[shaderstate.curlmode]->prog) + BE_RenderMeshProgram(shaderstate.shader_rtlight[shaderstate.curlmode], vertcount, idxfirst, idxcount); break; case BEM_DEPTHONLY: + BE_RenderMeshProgram(shaderstate.depthonly, vertcount, idxfirst, idxcount); +#if 0 shaderstate.lastpasscount = 0; i = 0; if (i != shaderstate.curvertdecl) @@ -1800,18 +2062,15 @@ static void BE_DrawMeshChain_Internal(void) shaderstate.curvertdecl = i; // d3dcheck(IDirect3DDevice9_SetVertexDeclaration(pD3DDev9, vertexdecls[shaderstate.curvertdecl])); } -// IDirect3DDevice9_SetRenderState(pD3DDev9, D3DRS_COLORWRITEENABLE, 0); /*deactivate any extras*/ for (passno = 0; passno < shaderstate.lastpasscount; ) { -// d3dcheck(IDirect3DDevice9_SetStreamSource(pD3DDev9, STRM_TC0+passno, NULL, 0, 0)); BindTexture(passno, NULL); -// d3dcheck(IDirect3DDevice9_SetTextureStageState(pD3DDev9, passno, D3DTSS_COLOROP, D3DTOP_DISABLE)); passno++; } shaderstate.lastpasscount = 0; BE_SubmitMeshChain(idxfirst); -// IDirect3DDevice9_SetRenderState(pD3DDev9, D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_ALPHA); +#endif break; default: case BEM_STANDARD: @@ -1831,19 +2090,40 @@ void D3D11BE_SelectMode(backendmode_t mode) shaderstate.mode = mode; if (mode == BEM_STENCIL) - D3D11BE_ApplyShaderBits(SBITS_MASK_BITS); + D3D11BE_ApplyShaderBits(SBITS_MASK_BITS, NULL); } qboolean D3D11BE_SelectDLight(dlight_t *dl, vec3_t colour, unsigned int lmode) { shaderstate.curdlight = dl; + shaderstate.curlmode = lmode; VectorCopy(colour, shaderstate.curdlight_colours); - if (lmode != LSHADER_STANDARD) - return false; + D3D11BE_SetupLightCBuffer(dl, colour); + return true; } +void D3D11BE_SetupForShadowMap(dlight_t *dl, qboolean isspot, int texwidth, int texheight, float shadowscale) +{ +#define SHADOWMAP_SIZE 512 + extern cvar_t r_shadow_shadowmapping_nearclip, r_shadow_shadowmapping_bias; + float nc = r_shadow_shadowmapping_nearclip.value; + float bias = r_shadow_shadowmapping_bias.value; + + //much of the projection matrix cancels out due to symmetry and stuff + //we need to scale between -0.5,0.5 within the sub-image. the fragment shader will center on the subimage based upon the major axis. + //in d3d, the depth value is scaled between 0 and 1 (gl is -1 to 1). + //d3d's framebuffer is upside down or something annoying like that. + shaderstate.lightshadowmapproj[0] = shadowscale * (1.0-(1.0/texwidth)) * 0.5/3.0; //pinch x inwards + shaderstate.lightshadowmapproj[1] = -shadowscale * (1.0-(1.0/texheight)) * 0.5/2.0; //pinch y inwards + shaderstate.lightshadowmapproj[2] = 0.5*(dl->radius+nc)/(nc-dl->radius); //proj matrix 10 + shaderstate.lightshadowmapproj[3] = (dl->radius*nc)/(nc-dl->radius) - bias*nc*(1024/texheight); //proj matrix 14 + + shaderstate.lightshadowmapscale[0] = 1.0/(SHADOWMAP_SIZE*3); + shaderstate.lightshadowmapscale[1] = -1.0/(SHADOWMAP_SIZE*2); +} + void D3D11BE_SelectEntity(entity_t *ent) { BE_RotateForEntity(ent, ent->model); @@ -2119,6 +2399,7 @@ void D3D11BE_GenBatchVBOs(vbo_t **vbochain, batch_t *firstbatch, batch_t *stopba srd.SysMemPitch = 0; srd.SysMemSlicePitch = 0; ID3D11Device_CreateBuffer(pD3DDev11, &ebodesc, &srd, &ebuff); + shaderstate.numlivevbos++; BZ_Free(vboedatastart); //generate the vbo, and submit the data to the driver @@ -2132,6 +2413,7 @@ void D3D11BE_GenBatchVBOs(vbo_t **vbochain, batch_t *firstbatch, batch_t *stopba srd.SysMemPitch = 0; srd.SysMemSlicePitch = 0; ID3D11Device_CreateBuffer(pD3DDev11, &vbodesc, &srd, &vbuff); + shaderstate.numlivevbos++; BZ_Free(vbovdatastart); vbovdata = NULL; @@ -2199,9 +2481,15 @@ void D3D11BE_ClearVBO(vbo_t *vbo) ID3D11Buffer *vbuff = vbo->coord.d3d.buff; ID3D11Buffer *ebuff = vbo->indicies.d3d.buff; if (vbuff) + { ID3D11Buffer_Release(vbuff); + shaderstate.numlivevbos--; + } if (ebuff) + { ID3D11Buffer_Release(ebuff); + shaderstate.numlivevbos--; + } vbo->coord.d3d.buff = NULL; vbo->indicies.d3d.buff = NULL; @@ -2261,6 +2549,20 @@ batch_t *D3D11BE_GetTempBatch(void) return &shaderstate.wbatches[shaderstate.wbatch++]; } +float projd3dtogl[16] = +{ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 2.0, 0.0, + 0.0, 0.0, -1.0, 1.0 +}; +float projgltod3d[16] = +{ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.0, 0.0, 0.5, 1.0 +}; void D3D11BE_SetupViewCBuffer(void) { cbuf_view_t *cbv; @@ -2272,19 +2574,58 @@ void D3D11BE_SetupViewCBuffer(void) } cbv = (cbuf_view_t*)msr.pData; - memcpy(cbv->m_projection, d3d_trueprojection/*r_refdef.m_projection*/, sizeof(cbv->m_projection)); + //we internally use gl-style projection matricies. + //gl's viewport is based upon -1 to 1 depth. + //d3d uses 0 to 1 depth. + //so we scale the projection matrix by a bias +#if 1 + Matrix4_Multiply(projgltod3d, r_refdef.m_projection, cbv->m_projection); +#else + memcpy(cbv->m_projection, r_refdef.m_projection, sizeof(cbv->m_projection)); + cbv->m_projection[10] = r_refdef.m_projection[10] * 0.5; +#endif memcpy(cbv->m_view, r_refdef.m_view, sizeof(cbv->m_view)); VectorCopy(r_origin, cbv->v_eyepos); cbv->v_time = r_refdef.time; ID3D11DeviceContext_Unmap(d3ddevctx, (ID3D11Resource*)shaderstate.vcbuffer, 0); } +void D3D11BE_SetupLightCBuffer(dlight_t *l, vec3_t colour) +{ + extern cvar_t gl_specular; + cbuf_light_t *cbl; + D3D11_MAPPED_SUBRESOURCE msr; + if (FAILED(ID3D11DeviceContext_Map(d3ddevctx, (ID3D11Resource*)shaderstate.lcbuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &msr))) + { + Con_Printf("BE_RotateForEntity: failed to map constant buffer\n"); + return; + } + cbl = (cbuf_light_t*)msr.pData; + + cbl->l_lightradius = l->radius; + + Matrix4x4_CM_LightMatrixFromAxis(cbl->l_cubematrix, l->axis[0], l->axis[1], l->axis[2], l->origin); + VectorCopy(l->origin, cbl->l_lightposition); + cbl->padl1 = 0; + VectorCopy(colour, cbl->l_colour); + VectorCopy(l->lightcolourscales, cbl->l_lightcolourscale); + cbl->l_lightcolourscale[0] = l->lightcolourscales[0]; + cbl->l_lightcolourscale[1] = l->lightcolourscales[1]; + cbl->l_lightcolourscale[2] = l->lightcolourscales[2] * gl_specular.value; + cbl->l_lightradius = l->radius; + Vector4Copy(shaderstate.lightshadowmapproj, cbl->l_shadowmapproj); + Vector2Copy(shaderstate.lightshadowmapscale, cbl->l_shadowmapscale); + + ID3D11DeviceContext_Unmap(d3ddevctx, (ID3D11Resource*)shaderstate.lcbuffer, 0); +} + //also updates the entity constant buffer static void BE_RotateForEntity (const entity_t *e, const model_t *mod) { + int i; float ndr; - float mv[16]; + float mv[16], modelinv[16]; float *m = shaderstate.m_model; cbuf_entity_t *cbe; D3D11_MAPPED_SUBRESOURCE msr; @@ -2389,12 +2730,42 @@ static void BE_RotateForEntity (const entity_t *e, const model_t *mod) { memcpy(cbe->m_model, m, sizeof(cbe->m_model)); } + + Matrix4_Invert(shaderstate.m_model, modelinv); + cbe->e_time = r_refdef.time - shaderstate.curentity->shaderTime; VectorCopy(e->light_avg, cbe->e_light_ambient); VectorCopy(e->light_dir, cbe->e_light_dir); VectorCopy(e->light_range, cbe->e_light_mul); + //various stuff in modelspace + Matrix4x4_CM_Transform3(modelinv, r_origin, cbe->e_eyepos); + + for (i = 0; i < MAXRLIGHTMAPS ; i++) + { + extern cvar_t gl_overbright; + unsigned char s = shaderstate.curbatch?shaderstate.curbatch->lmlightstyle[i]:0; + float sc; + if (s == 255) + { + for (; i < MAXRLIGHTMAPS ; i++) + { + cbe->e_lmscale[i][0] = 0; + cbe->e_lmscale[i][1] = 0; + cbe->e_lmscale[i][2] = 0; + cbe->e_lmscale[i][3] = 1; + } + break; + } + if (shaderstate.curentity->model && shaderstate.curentity->model->engineflags & MDLF_NEEDOVERBRIGHT) + sc = (1<e_lmscale[i], sc, sc, sc, 1); + } + ID3D11DeviceContext_Unmap(d3ddevctx, (ID3D11Resource*)shaderstate.ecbuffers[shaderstate.ecbufferidx], 0); ndr = (e->flags & Q2RF_DEPTHHACK)?0.333:1; @@ -2404,13 +2775,13 @@ static void BE_RotateForEntity (const entity_t *e, const model_t *mod) shaderstate.depthrange = ndr; - vport.TopLeftX = (r_refdef.vrect.x * vid.pixelwidth) / vid.width; - vport.TopLeftY = (r_refdef.vrect.y * vid.pixelheight) / vid.height; - vport.Width = (r_refdef.vrect.width * vid.pixelwidth) / vid.width; - vport.Height = (r_refdef.vrect.height * vid.pixelheight) / vid.height; + vport.TopLeftX = r_refdef.pxrect.x; + vport.TopLeftY = r_refdef.pxrect.y; + vport.Width = r_refdef.pxrect.width; + vport.Height = r_refdef.pxrect.height; vport.MinDepth = 0; vport.MaxDepth = shaderstate.depthrange; - d3ddevctx->lpVtbl->RSSetViewports(d3ddevctx, 1, &vport); + ID3D11DeviceContext_RSSetViewports(d3ddevctx, 1, &vport); } } @@ -2837,20 +3208,89 @@ void D3D11BE_BaseEntTextures(void) BE_SelectEntity(&r_worldentity); } -void D3D11BE_RenderShadowBuffer(unsigned int numverts, ID3D11Buffer *vbuf, unsigned int numindicies, ID3D11Buffer *ibuf) +void D3D11BE_GenerateShadowBuffer(void **vbuf_out, vecV_t *verts, int numverts, void **ibuf_out, index_t *indicies, int numindicies) { -/* - IDirect3DDevice9_SetStreamSource(pD3DDev9, STRM_VERT, vbuf, 0, sizeof(vecV_t)); - IDirect3DDevice9_SetIndices(pD3DDev9, ibuf); + D3D11_BUFFER_DESC desc; + D3D11_SUBRESOURCE_DATA srd; + ID3D11Buffer *vbuf; + ID3D11Buffer *ibuf; - if (0 != shaderstate.curvertdecl) + + //generate the ebo, and submit the data to the driver + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + desc.ByteWidth = sizeof(*verts) * numverts; + desc.CPUAccessFlags = 0; + desc.MiscFlags = 0; + desc.StructureByteStride = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + srd.pSysMem = verts; + srd.SysMemPitch = 0; + srd.SysMemSlicePitch = 0; + ID3D11Device_CreateBuffer(pD3DDev11, &desc, &srd, &vbuf); + + //generate the vbo, and submit the data to the driver + desc.BindFlags = D3D11_BIND_INDEX_BUFFER; + desc.ByteWidth = sizeof(*indicies) * numindicies; + desc.CPUAccessFlags = 0; + desc.MiscFlags = 0; + desc.StructureByteStride = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + srd.pSysMem = indicies; + srd.SysMemPitch = 0; + srd.SysMemSlicePitch = 0; + ID3D11Device_CreateBuffer(pD3DDev11, &desc, &srd, &ibuf); + + shaderstate.numliveshadowbuffers++; + + *vbuf_out = vbuf; + *ibuf_out = ibuf; +} +void D3D11_DestroyShadowBuffer(void *vbuf_in, void *ibuf_in) +{ + ID3D11Buffer *vbuf = vbuf_in; + ID3D11Buffer *ibuf = ibuf_in; + + if (vbuf && ibuf) { - shaderstate.curvertdecl = 0; - d3dcheck(IDirect3DDevice9_SetVertexDeclaration(pD3DDev9, vertexdecls[shaderstate.curvertdecl])); + ID3D11Buffer_Release(vbuf); + ID3D11Buffer_Release(ibuf); + shaderstate.numliveshadowbuffers--; } +} +//draws all depth-only surfaces from the perspective of the light. +void D3D11BE_RenderShadowBuffer(unsigned int numverts, void *vbuf, unsigned int numindicies, void *ibuf) +{ + ID3D11Buffer *vbufs[] = {vbuf}; + int vstrides[] = {sizeof(vecV_t)}; + int voffsets[] = {0}; + int i; - IDirect3DDevice9_DrawIndexedPrimitive(pD3DDev9, D3DPT_TRIANGLELIST, 0, 0, numverts, 0, numindicies/3); -*/ + D3D11BE_SetupViewCBuffer(); + + D3D11BE_Cull(SHADER_CULL_FRONT); + + for (i = 0; i < shaderstate.lastpasscount; i++) + { + shaderstate.pendingtextures[i] = NULL; + shaderstate.textureschanged = true; + } + if (shaderstate.textureschanged) + ID3D11DeviceContext_PSSetShaderResources(d3ddevctx, 0, shaderstate.lastpasscount, shaderstate.pendingtextures); + shaderstate.lastpasscount = 0; + + ID3D11DeviceContext_IASetVertexBuffers(d3ddevctx, 0, 1, vbufs, vstrides, voffsets); + ID3D11DeviceContext_IASetIndexBuffer(d3ddevctx, ibuf, DXGI_FORMAT_R16_UINT, 0); + + BE_ApplyUniforms(shaderstate.depthonly->prog, 0); + + ID3D11DeviceContext_DrawIndexed(d3ddevctx, numindicies, 0, 0); +} +void D3D11BE_DoneShadows(void) +{ + D3D11BE_SetupViewCBuffer(); + BE_SelectEntity(&r_worldentity); + + D3D11BE_BeginShadowmapFace(); } #endif @@ -2894,6 +3334,14 @@ void D3D11BE_DrawWorld (qboolean drawworld, qbyte *vis) r_worldentity.axis[1][1] = 1; r_worldentity.axis[2][2] = 1; +#ifdef RTLIGHTS + if (vis && r_shadow_realtime_world.ival) + shaderstate.identitylighting = r_shadow_realtime_world_lightmaps.value; + else +#endif + shaderstate.identitylighting = 1; +// shaderstate.identitylightmap = shaderstate.identitylighting / (1<lightmap_texture = ToTexID(tex); } +static void genNormalMap(unsigned int *nmap, qbyte *pixels, int w, int h, float scale) +{ + int i, j, wr, hr; + unsigned char r, g, b; + float sqlen, reciplen, nx, ny, nz; + + const float oneOver255 = 1.0f/255.0f; + + float c, cx, cy, dcx, dcy; + + wr = w; + hr = h; + + for (i=0; i Added support for big endian. + } + } +} + + texid_t D3D11_LoadTexture (char *identifier, int width, int height, enum uploadfmt fmt, void *data, unsigned int flags) { d3d11texture_t *tex; @@ -579,6 +628,24 @@ texid_t D3D11_LoadTexture (char *identifier, int width, int height, enum uploadf switch (fmt) { + case TF_HEIGHT8PAL: + { + extern cvar_t r_shadow_bumpscale_basetexture; + unsigned int *norm = malloc(width*height*4); + genNormalMap(norm, data, width, height, r_shadow_bumpscale_basetexture.value); + D3D11_LoadTexture_32(tex, norm, width, height, flags); + free(norm); + return ToTexID(tex); + } + case TF_HEIGHT8: + { + extern cvar_t r_shadow_bumpscale_basetexture; + unsigned int *norm = malloc(width*height*4); + genNormalMap(norm, data, width, height, r_shadow_bumpscale_basetexture.value); + D3D11_LoadTexture_32(tex, norm, width, height, flags); + free(norm); + return ToTexID(tex); + } case TF_SOLID8: case TF_TRANS8: case TF_H2_T7G1: @@ -592,9 +659,6 @@ texid_t D3D11_LoadTexture (char *identifier, int width, int height, enum uploadf case TF_RGBA32: D3D11_LoadTexture_32(tex, data, width, height, flags); return ToTexID(tex); - case TF_HEIGHT8PAL: - OutputDebugString(va("D3D11_LoadTexture doesn't support fmt TF_HEIGHT8PAL (%s)\n", identifier)); - return r_nulltex; default: OutputDebugString(va("D3D11_LoadTexture doesn't support fmt %i (%s)\n", fmt, identifier)); return r_nulltex; @@ -634,4 +698,115 @@ texid_t D3D11_LoadTexture8Pal24 (char *identifier, int width, int height, qbyte return D3D11_LoadTexture8Pal32(identifier, width, height, data, (qbyte*)pal32, flags); } +#ifdef RTLIGHTS +static const int shadowfmt = 1; +static const int shadowfmts[][3] = +{ + //sampler, creation, render + {DXGI_FORMAT_R24_UNORM_X8_TYPELESS, DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_D24_UNORM_S8_UINT}, + {DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_D16_UNORM}, + {DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM} +}; +d3d11texture_t shadowmap_texture[2]; +ID3D11DepthStencilView *shadowmap_dsview[2]; +ID3D11RenderTargetView *shadowmap_rtview[2]; +texid_t D3D11_GetShadowMap(int id) +{ + d3d11texture_t *tex = &shadowmap_texture[id]; + texid_t tid; + tid.ref = &tex->com; + if (!tex->view) + { + D3D11_SHADER_RESOURCE_VIEW_DESC desc; + desc.Format = shadowfmts[shadowfmt][0]; + desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + desc.Texture2D.MostDetailedMip = 0; + desc.Texture2D.MipLevels = -1; + ID3D11Device_CreateShaderResourceView(pD3DDev11, (ID3D11Resource *)tex->tex2d, &desc, &tex->view); + } + tid.ptr = NULL; + return tid; +} +void D3D11_TerminateShadowMap(void) +{ + int i; + for (i = 0; i < sizeof(shadowmap_texture)/sizeof(shadowmap_texture[0]); i++) + { + if (shadowmap_dsview[i]) + ID3D11DepthStencilView_Release(shadowmap_dsview[i]); + shadowmap_dsview[i] = NULL; + if (shadowmap_texture[i].tex2d) + ID3D11DepthStencilView_Release(shadowmap_texture[i].tex2d); + shadowmap_texture[i].tex2d = NULL; + } +} +void D3D11_BeginShadowMap(int id, int w, int h) +{ + D3D11_TEXTURE2D_DESC texdesc; + HRESULT hr; + + if (!shadowmap_dsview[id] && !shadowmap_rtview[id]) + { + memset(&texdesc, 0, sizeof(texdesc)); + + texdesc.Width = w; + texdesc.Height = h; + texdesc.MipLevels = 1; + texdesc.ArraySize = 1; + texdesc.Format = shadowfmts[shadowfmt][1]; + texdesc.SampleDesc.Count = 1; + texdesc.SampleDesc.Quality = 0; + texdesc.Usage = D3D11_USAGE_DEFAULT; + texdesc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; + texdesc.CPUAccessFlags = 0; + texdesc.MiscFlags = 0; + + if (shadowfmt == 2) + texdesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + + // Create the texture + hr = ID3D11Device_CreateTexture2D(pD3DDev11, &texdesc, NULL, &shadowmap_texture[id].tex2d); + if (FAILED(hr)) + Sys_Error("Failed to create depth texture\n"); + + + if (shadowfmt == 2) + { + hr = ID3D11Device_CreateRenderTargetView(pD3DDev11, (ID3D11Resource *)shadowmap_texture[id].tex2d, NULL, &shadowmap_rtview[id]); + } + else + { + D3D11_DEPTH_STENCIL_VIEW_DESC rtdesc; + rtdesc.Format = shadowfmts[shadowfmt][2]; + rtdesc.Flags = 0; + rtdesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + rtdesc.Texture2D.MipSlice = 0; + hr = ID3D11Device_CreateDepthStencilView(pD3DDev11, (ID3D11Resource *)shadowmap_texture[id].tex2d, &rtdesc, &shadowmap_dsview[id]); + } + if (FAILED(hr)) + Sys_Error("ID3D11Device_CreateDepthStencilView failed\n"); + } + if (shadowfmt == 2) + { + float colours[4] = {0, 1, 0, 0}; + colours[0] = frandom(); + colours[1] = frandom(); + colours[2] = frandom(); + ID3D11DeviceContext_OMSetRenderTargets(d3ddevctx, 1, &shadowmap_rtview[id], shadowmap_dsview[id]); + ID3D11DeviceContext_ClearRenderTargetView(d3ddevctx, shadowmap_rtview[id], colours); + } + else + { + ID3D11DeviceContext_OMSetRenderTargets(d3ddevctx, 0, NULL, shadowmap_dsview[id]); + ID3D11DeviceContext_ClearDepthStencilView(d3ddevctx, shadowmap_dsview[id], D3D11_CLEAR_DEPTH, 1.0f, 0); + } +} +void D3D11_EndShadowMap(void) +{ + extern ID3D11RenderTargetView *fb_backbuffer; + extern ID3D11DepthStencilView *fb_backdepthstencil; + ID3D11DeviceContext_OMSetRenderTargets(d3ddevctx, 1, &fb_backbuffer, fb_backdepthstencil); +} +#endif + #endif diff --git a/engine/d3d/d3d11_shader.c b/engine/d3d/d3d11_shader.c index db1fda58..2db6cfae 100644 --- a/engine/d3d/d3d11_shader.c +++ b/engine/d3d/d3d11_shader.c @@ -31,7 +31,6 @@ DECLARE_INTERFACE_(INTERFACE, IUnknown) #undef INTERFACE #define INTERFACE ID3DBlob - DECLARE_INTERFACE_(INTERFACE, IUnknown) { STDMETHOD(QueryInterface)(THIS_ REFIID iid, LPVOID *ppv) PURE; @@ -40,6 +39,10 @@ DECLARE_INTERFACE_(INTERFACE, IUnknown) STDMETHOD_(LPVOID, GetBufferPointer)(THIS) PURE; STDMETHOD_(SIZE_T, GetBufferSize)(THIS) PURE; }; +#define ID3DBlob_GetBufferPointer(b) b->lpVtbl->GetBufferPointer(b) +#define ID3DBlob_Release(b) b->lpVtbl->Release(b) +#define ID3DBlob_GetBufferSize(b) b->lpVtbl->GetBufferSize(b) +#undef INTERFACE HRESULT (WINAPI *pD3DCompile) ( LPCVOID pSrcData, @@ -57,20 +60,34 @@ HRESULT (WINAPI *pD3DCompile) ( static dllhandle_t *shaderlib; - -void D3D11Shader_Init(void) +D3D_FEATURE_LEVEL d3dfeaturelevel; +qboolean D3D11Shader_Init(unsigned int flevel) { - dllfunction_t funcs[] = + //FIXME: if the feature level is below 10, make sure the compiler supports all the right targets etc + int ver; + dllfunction_t funcsold[] = { {(void**)&pD3DCompile, "D3DCompileFromMemory"}, {NULL,NULL} }; + dllfunction_t funcsnew[] = + { + {(void**)&pD3DCompile, "D3DCompile"}, + {NULL,NULL} + }; + + for (ver = 43; ver >= 33; ver--) + { + shaderlib = Sys_LoadLibrary(va("D3dcompiler_%i.dll", ver), (ver>=40)?funcsnew:funcsold); + if (shaderlib) + break; + } if (!shaderlib) - shaderlib = Sys_LoadLibrary("D3dcompiler_34.dll", funcs); + return false; - if (!shaderlib) - return; + d3dfeaturelevel = flevel; + return true; } HRESULT STDMETHODCALLTYPE d3dinclude_Close(ID3DInclude *this, LPCVOID pData) @@ -92,6 +109,7 @@ HRESULT STDMETHODCALLTYPE d3dinclude_Open(ID3DInclude *this, D3D_INCLUDE_TYPE In "float3 e_light_ambient; float pad1;\n" "float3 e_light_dir; float pad2;\n" "float3 e_light_mul; float pad3;\n" + "float4 e_lmscale[4];\n" "};\n" "cbuffer fteviewdefs : register(b1)\n" "{\n" @@ -99,6 +117,24 @@ HRESULT STDMETHODCALLTYPE d3dinclude_Open(ID3DInclude *this, D3D_INCLUDE_TYPE In "matrix m_projection;\n" "float3 v_eyepos; float v_time;\n" "};\n" + "cbuffer ftelightdefs : register(b2)\n" + "{\n" + "matrix l_cubematrix;\n" + "float3 l_lightposition; float padl1;\n" + "float3 l_colour; float padl2;\n" + "float3 l_lightcolourscale;float l_lightradius;\n" + "float4 l_shadowmapproj;\n" + "float2 l_shadowmapscale; float2 padl3;\n" + "};\n" + ; + *ppData = strdup(defstruct); + *pBytes = strlen(*ppData); + return S_OK; + } + if (!strcmp(pFileName, "sys/skeletal.h")) + { + static const char *defstruct = + "" ; *ppData = strdup(defstruct); *pBytes = strlen(*ppData); @@ -132,12 +168,60 @@ typedef struct byte_vec4_t colorsb; } vbovdata_t; +void D3D11Shader_DeleteProgram(program_t *prog) +{ + ID3D11InputLayout *layout; + ID3D11PixelShader *frag; + ID3D11VertexShader *vert; + int permu; + for (permu = 0; permu < PERMUTATIONS; permu++) + { + vert = prog->permu[permu].handle.hlsl.vert; + frag = prog->permu[permu].handle.hlsl.frag; + layout = prog->permu[permu].handle.hlsl.layout; + if (vert) + ID3D11VertexShader_Release(vert); + if (frag) + ID3D11PixelShader_Release(frag); + if (layout) + ID3D11InputLayout_Release(layout); + } +} + qboolean D3D11Shader_CreateProgram (program_t *prog, const char *name, int permu, char **precompilerconstants, char *vert, char *frag) { + char *vsformat; + char *fsformat; D3D_SHADER_MACRO defines[64]; ID3DBlob *vcode = NULL, *fcode = NULL, *errors = NULL; qboolean success = false; + if (d3dfeaturelevel >= D3D_FEATURE_LEVEL_11_0) //and 11.1 + { + vsformat = "vs_5_0"; + fsformat = "ps_5_0"; + } + else if (d3dfeaturelevel >= D3D_FEATURE_LEVEL_10_1) + { + vsformat = "vs_4_1"; + fsformat = "ps_4_1"; + } + else if (d3dfeaturelevel >= D3D_FEATURE_LEVEL_10_0) + { + vsformat = "vs_4_0"; + fsformat = "ps_4_0"; + } + else if (d3dfeaturelevel >= D3D_FEATURE_LEVEL_9_3) + { //dx10-compatible output for 9.3 hardware + vsformat = "vs_4_0_level_9_3"; + fsformat = "ps_4_0_level_9_3"; + } + else + { //dx10-compatible output for 9.1|9.2 hardware + vsformat = "vs_4_0_level_9_1"; + fsformat = "ps_4_0_level_9_1"; + } + prog->permu[permu].handle.hlsl.vert = NULL; prog->permu[permu].handle.hlsl.frag = NULL; prog->permu[permu].handle.hlsl.layout = NULL; @@ -159,10 +243,17 @@ qboolean D3D11Shader_CreateProgram (program_t *prog, const char *name, int permu defines[consts].Definition = __DATE__; consts++; + defines[consts].Name = Z_StrDup("LEVEL"); + defines[consts].Definition = Z_StrDup(va("0x%x", d3dfeaturelevel)); + consts++; + for (; *precompilerconstants; precompilerconstants++) { - defines[consts].Name = NULL; - defines[consts].Definition = NULL; + char *t = *precompilerconstants; + t = COM_Parse(t); + t = COM_Parse(t); + defines[consts].Name = Z_StrDup(com_token); + defines[consts].Definition = t?Z_StrDup(t):NULL; consts++; } @@ -172,36 +263,40 @@ qboolean D3D11Shader_CreateProgram (program_t *prog, const char *name, int permu success = true; defines[0].Name = "VERTEX_SHADER"; - if (FAILED(pD3DCompile(vert, strlen(vert), name, defines, &myd3dinclude, "main", "vs_4_0", 0, 0, &vcode, &errors))) + if (FAILED(pD3DCompile(vert, strlen(vert), name, defines, &myd3dinclude, "main", vsformat, 0, 0, &vcode, &errors))) success = false; else { - if (FAILED(ID3D11Device_CreateVertexShader(pD3DDev11, vcode->lpVtbl->GetBufferPointer(vcode), vcode->lpVtbl->GetBufferSize(vcode), NULL, (ID3D11VertexShader**)&prog->permu[permu].handle.hlsl.vert))) + if (FAILED(ID3D11Device_CreateVertexShader(pD3DDev11, ID3DBlob_GetBufferPointer(vcode), ID3DBlob_GetBufferSize(vcode), NULL, (ID3D11VertexShader**)&prog->permu[permu].handle.hlsl.vert))) success = false; } if (errors) { - char *messages = errors->lpVtbl->GetBufferPointer(errors); - Con_Printf("%s", messages); - errors->lpVtbl->Release(errors); + char *messages = ID3DBlob_GetBufferPointer(errors); + Con_Printf("vertex shader:\n%s", messages); + ID3DBlob_Release(errors); } defines[0].Name = "FRAGMENT_SHADER"; - if (FAILED(pD3DCompile(frag, strlen(frag), name, defines, &myd3dinclude, "main", "ps_4_0", 0, 0, &fcode, &errors))) + if (FAILED(pD3DCompile(frag, strlen(frag), name, defines, &myd3dinclude, "main", fsformat, 0, 0, &fcode, &errors))) success = false; else { - if (FAILED(ID3D11Device_CreatePixelShader(pD3DDev11, fcode->lpVtbl->GetBufferPointer(fcode), fcode->lpVtbl->GetBufferSize(fcode), NULL, (ID3D11PixelShader**)&prog->permu[permu].handle.hlsl.frag))) + if (FAILED(ID3D11Device_CreatePixelShader(pD3DDev11, ID3DBlob_GetBufferPointer(fcode), ID3DBlob_GetBufferSize(fcode), NULL, (ID3D11PixelShader**)&prog->permu[permu].handle.hlsl.frag))) success = false; } if (errors) { - char *messages = errors->lpVtbl->GetBufferPointer(errors); - Con_Printf("%s", messages); - errors->lpVtbl->Release(errors); + char *messages = ID3DBlob_GetBufferPointer(errors); + Con_Printf("fragment shader:\n%s", messages); + ID3DBlob_Release(errors); } - + while(consts-->2) + { + Z_Free((void*)defines[consts].Name); + Z_Free((void*)defines[consts].Definition); + } if (success) { @@ -291,7 +386,7 @@ qboolean D3D11Shader_CreateProgram (program_t *prog, const char *name, int permu decl[elements].InstanceDataStepRate = 0; elements++; */ - if (FAILED(ID3D11Device_CreateInputLayout(pD3DDev11, decl, elements, vcode->lpVtbl->GetBufferPointer(vcode), vcode->lpVtbl->GetBufferSize(vcode), (ID3D11InputLayout**)&prog->permu[permu].handle.hlsl.layout))) + if (FAILED(ID3D11Device_CreateInputLayout(pD3DDev11, decl, elements, ID3DBlob_GetBufferPointer(vcode), ID3DBlob_GetBufferSize(vcode), (ID3D11InputLayout**)&prog->permu[permu].handle.hlsl.layout))) { Con_Printf("HLSL Shader %s requires unsupported inputs\n", name); success = false; @@ -299,43 +394,14 @@ qboolean D3D11Shader_CreateProgram (program_t *prog, const char *name, int permu } if (vcode) - vcode->lpVtbl->Release(vcode); + ID3DBlob_Release(vcode); if (fcode) - fcode->lpVtbl->Release(fcode); + ID3DBlob_Release(fcode); } return success; } -/* -static int D3D11Shader_FindUniform_(LPD3DXCONSTANTTABLE ct, char *name) -{ - if (ct) - { - UINT dc = 1; - D3DXCONSTANT_DESC d; - if (!FAILED(ct->lpVtbl->GetConstantDesc(ct, name, &d, &dc))) - return d.RegisterIndex; - } - return -1; -} -*/ int D3D11Shader_FindUniform(union programhandle_u *h, int type, char *name) { -#if 0 - int offs; - - if (!type || type == 1) - { - offs = D3D11Shader_FindUniform_(h->hlsl.ctabv, name); - if (offs >= 0) - return offs; - } - if (!type || type == 2) - { - offs = D3D11Shader_FindUniform_(h->hlsl.ctabf, name); - if (offs >= 0) - return offs; - } -#endif return -1; } #endif diff --git a/engine/d3d/d3d_image.c b/engine/d3d/d3d_image.c index daa899a7..2c3fe340 100644 --- a/engine/d3d/d3d_image.c +++ b/engine/d3d/d3d_image.c @@ -365,6 +365,52 @@ static void D3D9_LoadTexture_8(d3dtexture_t *tex, unsigned char *data, unsigned D3D9_LoadTexture_32(tex, trans, width, height, flags); } +static void genNormalMap(unsigned int *nmap, qbyte *pixels, int w, int h, float scale) +{ + int i, j, wr, hr; + unsigned char r, g, b; + float sqlen, reciplen, nx, ny, nz; + + const float oneOver255 = 1.0f/255.0f; + + float c, cx, cy, dcx, dcy; + + wr = w; + hr = h; + + for (i=0; i Added support for big endian. + } + } +} + void D3D9_Upload (texid_t tex, char *name, enum uploadfmt fmt, void *data, void *palette, int width, int height, unsigned int flags) { switch (fmt) @@ -419,12 +465,30 @@ texid_t D3D9_LoadTexture (char *identifier, int width, int height, enum uploadfm case TF_8PAL24: case TF_8PAL32: break; - } tex = d3d_lookup_texture(identifier); switch (fmt) { + case TF_HEIGHT8PAL: + { + texid_t t; + extern cvar_t r_shadow_bumpscale_basetexture; + unsigned int *norm = malloc(width*height*4); + genNormalMap(norm, data, width, height, r_shadow_bumpscale_basetexture.value); + t = D3D9_LoadTexture(identifier, width, height, TF_RGBA32, data, flags); + free(norm); + return t; + } + case TF_HEIGHT8: + { + extern cvar_t r_shadow_bumpscale_basetexture; + unsigned int *norm = malloc(width*height*4); + genNormalMap(norm, data, width, height, r_shadow_bumpscale_basetexture.value); + D3D9_LoadTexture_32(tex, norm, width, height, flags); + free(norm); + return tex->tex; + } case TF_SOLID8: case TF_TRANS8: case TF_H2_T7G1: @@ -439,7 +503,7 @@ texid_t D3D9_LoadTexture (char *identifier, int width, int height, enum uploadfm D3D9_LoadTexture_32(tex, data, width, height, flags); return tex->tex; default: - OutputDebugString(va("D3D9_LoadTexture doesn't support fmt %i", fmt)); + OutputDebugString(va("D3D9_LoadTexture doesn't support fmt %i (%s)\n", fmt, identifier)); return r_nulltex; } } diff --git a/engine/d3d/vid_d3d.c b/engine/d3d/vid_d3d.c index dabc25ac..152870b5 100644 --- a/engine/d3d/vid_d3d.c +++ b/engine/d3d/vid_d3d.c @@ -76,7 +76,7 @@ RECT window_rect; int window_x, window_y; -void BuildGammaTable (float g, float c); +/*void BuildGammaTable (float g, float c); static void D3D9_VID_GenPaletteTables (unsigned char *palette) { extern unsigned short ramps[3][256]; @@ -146,7 +146,7 @@ static void D3D9_VID_GenPaletteTables (unsigned char *palette) if (pD3DDev9) IDirect3DDevice9_SetGammaRamp(pD3DDev9, 0, D3DSGR_NO_CALIBRATION, (D3DGAMMARAMP *)ramps); } - +*/ typedef enum {MS_WINDOWED, MS_FULLSCREEN, MS_FULLDIB, MS_UNINIT} modestate_t; static modestate_t modestate; @@ -719,7 +719,7 @@ static qboolean D3D9_VID_Init(rendererstate_t *info, unsigned char *palette) GetWindowRect(mainwindow, &window_rect); - D3D9_VID_GenPaletteTables(palette); +// D3D9_VID_GenPaletteTables(palette); { extern qboolean mouseactive; @@ -864,8 +864,6 @@ static void (D3D9_VID_SetWindowCaption) (char *msg) SetWindowText(mainwindow, msg); } -void d3dx_ortho(float *m); - void D3D9_Set2D (void) { float m[16]; diff --git a/engine/d3d/vid_d3d11.c b/engine/d3d/vid_d3d11.c index 9071797b..90de1b11 100644 --- a/engine/d3d/vid_d3d11.c +++ b/engine/d3d/vid_d3d11.c @@ -47,9 +47,6 @@ DEFINE_QGUID(qIID_ID3D11Texture2D,0x6f15aaf2,0xd208,0x4e89,0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c); -//static void D3D11_GetBufferSize(int *width, int *height); //not defined -static void resetD3D11(void); -//static LPDIRECT3D11 pD3D; ID3D11Device *pD3DDev11; ID3D11DeviceContext *d3ddevctx; IDXGISwapChain *d3dswapchain; @@ -58,7 +55,6 @@ ID3D11RenderTargetView *fb_backbuffer; ID3D11DepthStencilView *fb_backdepthstencil; void *d3d11mod; -float d3d_trueprojection[16]; qboolean vid_initializing; @@ -81,77 +77,59 @@ int window_x, window_y; static void released3dbackbuffer(void); static qboolean resetd3dbackbuffer(int width, int height); -void BuildGammaTable (float g, float c); -static void D3D11_VID_GenPaletteTables (unsigned char *palette) +#if 0//def _DEBUG +#include +const GUID IID_IDXGIDebug = { 0x119E7452,0xDE9E,0x40fe, { 0x88,0x06,0x88,0xF9,0x0C,0x12,0xB4,0x41 } }; + +const GUID DXGI_DEBUG_ALL = { 0xe48ae283, 0xda80, 0x490b, {0x87, 0xe6, 0x43, 0xe9, 0xa9, 0xcf, 0xda, 0x8 }}; + +void DoDXGIDebug(void) { - extern unsigned short ramps[3][256]; - qbyte *pal; - unsigned r,g,b; - unsigned v; - unsigned short i; - unsigned *table; - extern qbyte gammatable[256]; + IDXGIDebug *dbg = NULL; - if (palette) + HRESULT (WINAPI *pDXGIGetDebugInterface)(REFIID riid, void **ppDebug); + dllfunction_t dxdidebugfuncs[] = { - extern cvar_t v_contrast; - BuildGammaTable(v_gamma.value, v_contrast.value); - - // - // 8 8 8 encoding - // - if (1)//vid_hardwaregamma.value) - { - // don't built in the gamma table - - pal = palette; - table = d_8to24rgbtable; - for (i=0 ; i<256 ; i++) - { - r = pal[0]; - g = pal[1]; - b = pal[2]; - pal += 3; - - // v = (255<<24) + (r<<16) + (g<<8) + (b<<0); - // v = (255<<0) + (r<<8) + (g<<16) + (b<<24); - v = (255<<24) + (r<<0) + (g<<8) + (b<<16); - *table++ = v; - } - d_8to24rgbtable[255] &= 0xffffff; // 255 is transparent - } - else - { - //computer has no hardware gamma (poor suckers) increase table accordingly - - pal = palette; - table = d_8to24rgbtable; - for (i=0 ; i<256 ; i++) - { - r = gammatable[pal[0]]; - g = gammatable[pal[1]]; - b = gammatable[pal[2]]; - pal += 3; - - // v = (255<<24) + (r<<16) + (g<<8) + (b<<0); - // v = (255<<0) + (r<<8) + (g<<16) + (b<<24); - v = (255<<24) + (r<<0) + (g<<8) + (b<<16); - *table++ = v; - } - d_8to24rgbtable[255] &= 0xffffff; // 255 is transparent - } - - if (LittleLong(1) != 1) - { - for (i=0 ; i<256 ; i++) - d_8to24rgbtable[i] = LittleLong(d_8to24rgbtable[i]); - } + {(void**)&pDXGIGetDebugInterface, "DXGIGetDebugInterface"}, + {NULL} + }; + pDXGIGetDebugInterface = NULL; + Sys_LoadLibrary("dxgidebug", dxdidebugfuncs); + pDXGIGetDebugInterface(&IID_IDXGIDebug, &dbg); + if (dbg) + { + IDXGIDebug_ReportLiveObjects(dbg, DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL); + IDXGIDebug_Release(dbg); } +} +#else +#define DoDXGIDebug() +#endif -// if (pD3DDev11) - // d3dswapchain->Set - //IDirect3DDevice11_SetGammaRamp(); -// IDirect3DDevice9_SetGammaRamp(pD3DDev9, 0, D3DSGR_NO_CALIBRATION, (D3DGAMMARAMP *)ramps); +char *D3D_NameForResult(HRESULT hr) +{ + if (hr == DXGI_ERROR_DEVICE_REMOVED && pD3DDev11) + hr = ID3D11Device_GetDeviceRemovedReason(pD3DDev11); + + switch(hr) + { + case E_OUTOFMEMORY: return "E_OUTOFMEMORY"; + case E_NOINTERFACE: return "E_NOINTERFACE"; + case DXGI_ERROR_DEVICE_HUNG: return "DXGI_ERROR_DEVICE_HUNG"; + case DXGI_ERROR_DEVICE_REMOVED: return "DXGI_ERROR_DEVICE_REMOVED"; + case DXGI_ERROR_DEVICE_RESET: return "DXGI_ERROR_DEVICE_RESET"; + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: return "DXGI_ERROR_DRIVER_INTERNAL_ERROR"; + case DXGI_ERROR_INVALID_CALL: return "DXGI_ERROR_INVALID_CALL"; + default: return va("%x", hr); + } +} + +static void D3D11_PresentOrCrash(void) +{ + extern cvar_t _vid_wait_override; + HRESULT hr = IDXGISwapChain_Present(d3dswapchain, _vid_wait_override.ival, 0); + if (FAILED(hr)) + Sys_Error("IDXGISwapChain_Present: %s\n", D3D_NameForResult(hr)); } typedef enum {MS_WINDOWED, MS_FULLSCREEN, MS_FULLDIB, MS_UNINIT} modestate_t; @@ -422,7 +400,6 @@ static LRESULT WINAPI D3D11_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR vid.pixelwidth = window_rect.right - window_rect.left; vid.pixelheight = window_rect.bottom - window_rect.top; resetd3dbackbuffer(vid.pixelwidth, vid.pixelheight); - resetD3D11(); D3D11BE_Reset(false); lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); break; @@ -477,36 +454,6 @@ static LRESULT WINAPI D3D11_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR /* return 1 if handled message, 0 if not */ return lRet; } -static void resetD3D11(void) -{ -#if 0 - HRESULT res; - res = IDirect3DDevice9_Reset(pD3DDev9, &d3dpp); - if (FAILED(res)) - { - Con_Printf("IDirect3DDevice9_Reset failed (%u)\n", res&0xffff); - return; - } - - - /*clear the screen to black as soon as we start up, so there's no lingering framebuffer state*/ - IDirect3DDevice9_BeginScene(pD3DDev9); - IDirect3DDevice9_Clear(pD3DDev9, 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); - IDirect3DDevice9_EndScene(pD3DDev9); - IDirect3DDevice9_Present(pD3DDev9, NULL, NULL, NULL, NULL); - - - - - - - - //IDirect3DDevice9_SetRenderState(pD3DDev9, D3DRENDERSTATE_DITHERENABLE, FALSE); - //IDirect3DDevice9_SetRenderState(pD3DDev9, D3DRENDERSTATE_SPECULARENABLE, FALSE); - //IDirect3DDevice9_SetRenderState(pD3DDev9, D3DRENDERSTATE_TEXTUREPERSPECTIVE, TRUE); - IDirect3DDevice9_SetRenderState(pD3DDev9, D3DRS_LIGHTING, FALSE); -#endif -} #if (WINVER < 0x500) && !defined(__GNUC__) typedef struct tagMONITORINFO @@ -576,23 +523,46 @@ static qboolean resetd3dbackbuffer(int width, int height) static qboolean initD3D11Device(HWND hWnd, rendererstate_t *info, PFN_D3D11_CREATE_DEVICE_AND_SWAP_CHAIN func, IDXGIAdapter *adapt) { int flags = D3D11_CREATE_DEVICE_SINGLETHREADED; + D3D_DRIVER_TYPE drivertype; DXGI_SWAP_CHAIN_DESC scd; D3D_FEATURE_LEVEL flevel, flevels[] = { + //D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_3, - D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1 + + //FIXME: need npot. +// D3D_FEATURE_LEVEL_9_3, +// D3D_FEATURE_LEVEL_9_2, +// D3D_FEATURE_LEVEL_9_1 }; memset(&scd, 0, sizeof(scd)); + if (!stricmp(info->subrenderer, "debug")) + flags |= D3D11_CREATE_DEVICE_DEBUG; + + if (!stricmp(info->subrenderer, "warp")) + drivertype = D3D_DRIVER_TYPE_WARP; + else if (!stricmp(info->subrenderer, "ref")) + drivertype = D3D_DRIVER_TYPE_REFERENCE; + else if (!stricmp(info->subrenderer, "hw")) + drivertype = D3D_DRIVER_TYPE_HARDWARE; + else if (!stricmp(info->subrenderer, "null")) + drivertype = D3D_DRIVER_TYPE_NULL; + else if (!stricmp(info->subrenderer, "software")) + drivertype = D3D_DRIVER_TYPE_SOFTWARE; + else if (!stricmp(info->subrenderer, "unknown")) + drivertype = D3D_DRIVER_TYPE_UNKNOWN; + else + drivertype = adapt?D3D_DRIVER_TYPE_UNKNOWN:D3D_DRIVER_TYPE_HARDWARE; + + //for stereo support, we would have to rewrite all of this in a way that would make us dependant upon windows 8 or 7+platform update, which would exclude vista. scd.BufferDesc.Width = info->width; scd.BufferDesc.Height = info->height; scd.BufferDesc.RefreshRate.Numerator = 0; scd.BufferDesc.RefreshRate.Denominator = 0; - scd.BufferCount = 1; //back buffer count + scd.BufferCount = 1+info->triplebuffer; //back buffer count scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //32bit colour scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scd.OutputWindow = hWnd; @@ -604,14 +574,16 @@ static qboolean initD3D11Device(HWND hWnd, rendererstate_t *info, PFN_D3D11_CREA // flags |= D3D11_CREATE_DEVICE_DEBUG; #endif - if (adapt) + if (drivertype == D3D_DRIVER_TYPE_UNKNOWN && adapt) { DXGI_ADAPTER_DESC adesc; - adapt->lpVtbl->GetDesc(adapt, &adesc); + IDXGIAdapter_GetDesc(adapt, &adesc); Con_Printf("D3D11 Adaptor: %S\n", adesc.Description); } + else + adapt = NULL; - if (FAILED(func(adapt, adapt?D3D_DRIVER_TYPE_UNKNOWN:D3D_DRIVER_TYPE_HARDWARE, NULL, flags, + if (FAILED(func(adapt, drivertype, NULL, flags, flevels, sizeof(flevels)/sizeof(flevels[0]), D3D11_SDK_VERSION, &scd, @@ -634,7 +606,11 @@ static qboolean initD3D11Device(HWND hWnd, rendererstate_t *info, PFN_D3D11_CREA } vid.numpages = scd.BufferCount; - D3D11Shader_Init(); + if (!D3D11Shader_Init(flevel)) + { + Con_Printf("Unable to intialise a suitable HLSL compiler, please install the DirectX runtime.\n"); + return false; + } return true; } @@ -643,8 +619,8 @@ static void initD3D11(HWND hWnd, rendererstate_t *info) static dllhandle_t *d3d11dll; static dllhandle_t *dxgi; static PFN_D3D11_CREATE_DEVICE_AND_SWAP_CHAIN fnc; - static HRESULT (*pCreateDXGIFactory1)(REFIID riid, void **ppFactory); - IID factiid = {0x770aae78, 0xf26f, 0x4dba, 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87}; + static HRESULT (WINAPI *pCreateDXGIFactory1)(IID *riid, void **ppFactory); + static IID factiid = {0x770aae78, 0xf26f, 0x4dba, 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87}; IDXGIFactory1 *fact = NULL; IDXGIAdapter *adapt = NULL; dllfunction_t d3d11funcs[] = @@ -658,32 +634,37 @@ static void initD3D11(HWND hWnd, rendererstate_t *info) {NULL} }; - if (!d3d11mod) - d3d11mod = Sys_LoadLibrary("d3d11", d3d11funcs); if (!dxgi) dxgi = Sys_LoadLibrary("dxgi", dxgifuncs); + if (!d3d11mod) + d3d11mod = Sys_LoadLibrary("d3d11", d3d11funcs); if (!d3d11mod) return; if (pCreateDXGIFactory1) { - pCreateDXGIFactory1(&factiid, &fact); + HRESULT hr; + hr = pCreateDXGIFactory1(&factiid, &fact); + if (FAILED(hr)) + Con_Printf("CreateDXGIFactory1 failed: %s\n", D3D_NameForResult(hr)); if (fact) { - fact->lpVtbl->EnumAdapters(fact, 0, &adapt); + IDXGIFactory1_EnumAdapters(fact, 0, &adapt); } } initD3D11Device(hWnd, info, fnc, adapt); + if (adapt) + IDXGIAdapter_Release(adapt); if (fact) { //DXGI SUCKS and fucks up alt+tab every single time. its pointless to go from fullscreen to fullscreen-with-taskbar-obscuring-half-the-window. //I'm just going to handle that stuff myself. - fact->lpVtbl->MakeWindowAssociation(fact, hWnd, DXGI_MWA_NO_WINDOW_CHANGES|DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN); - fact->lpVtbl->Release(fact); + IDXGIFactory1_MakeWindowAssociation(fact, hWnd, DXGI_MWA_NO_WINDOW_CHANGES|DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN); + IDXGIFactory1_Release(fact); } } @@ -739,9 +720,12 @@ static qboolean D3D11_VID_Init(rendererstate_t *info, unsigned char *palette) // Try as specified. + DoDXGIDebug(); + initD3D11(mainwindow, info); if (!pD3DDev11) { + DoDXGIDebug(); Con_Printf("No suitable D3D11 device found\n"); return false; } @@ -749,6 +733,8 @@ static qboolean D3D11_VID_Init(rendererstate_t *info, unsigned char *palette) if (info->fullscreen) IDXGISwapChain_SetFullscreenState(d3dswapchain, true, NULL); + vid.pixelwidth = width; + vid.pixelheight = height; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { @@ -760,29 +746,13 @@ static qboolean D3D11_VID_Init(rendererstate_t *info, unsigned char *palette) ShowWindow(mainwindow, SW_SHOWNORMAL); -// IDirect3DDevice9_Clear(pD3DDev9, 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); -// IDirect3DDevice9_BeginScene(pD3DDev9); -// IDirect3DDevice9_EndScene(pD3DDev9); -// IDirect3DDevice9_Present(pD3DDev9, NULL, NULL, NULL, NULL); - - - -// pD3DX->lpVtbl->GetBufferSize((void*)pD3DX, &width, &height); - vid.pixelwidth = width; - vid.pixelheight = height; - - vid.width = width; - vid.height = height; + vid.width = vid.pixelwidth; + vid.height = vid.pixelheight; vid_initializing = false; -// IDirect3DDevice9_SetRenderState(pD3DDev9, D3DRS_LIGHTING, FALSE); - GetWindowRect(mainwindow, &window_rect); - - D3D11_VID_GenPaletteTables(palette); - { extern qboolean mouseactive; mouseactive = false; @@ -843,10 +813,15 @@ static void (D3D11_VID_DeInit) (void) IDXGISwapChain_Release(d3dswapchain); d3dswapchain = NULL; if (pD3DDev11) - pD3DDev11->lpVtbl->Release(pD3DDev11); + ID3D11Device_Release(pD3DDev11); pD3DDev11 = NULL; + if (d3ddevctx) - d3ddevctx->lpVtbl->Release(d3ddevctx); + { + ID3D11DeviceContext_ClearState(d3ddevctx); + ID3D11DeviceContext_Flush(d3ddevctx); + ID3D11DeviceContext_Release(d3ddevctx); + } d3ddevctx = NULL; if (mainwindow) @@ -854,98 +829,77 @@ static void (D3D11_VID_DeInit) (void) DestroyWindow(mainwindow); mainwindow = NULL; } + + DoDXGIDebug(); } static qboolean D3D11_VID_ApplyGammaRamps(unsigned short *ramps) { return false; } -static char *(D3D11_VID_GetRGBInfo) (int prepad, int *truevidwidth, int *truevidheight) +static char *D3D11_VID_GetRGBInfo(int prepad, int *truevidwidth, int *truevidheight) { - return NULL; -#if 0 - IDirect3DSurface9 *backbuf, *surf; - D3DLOCKED_RECT rect; - D3DSURFACE_DESC desc; - int i, j, c; - qbyte *ret = NULL; - qbyte *p; - - /*DON'T read the front buffer. - this function can be used by the quakeworld remote screenshot 'snap' feature, - so DO NOT read the frontbuffer because it can show other information than just quake to third parties*/ - - if (!FAILED(IDirect3DDevice9_GetRenderTarget(pD3DDev9, 0, &backbuf))) + //don't directly map the frontbuffer, as that can hold other things. + //create a texture, copy the (gpu)backbuffer to that (cpu)texture + //then map the (cpu)texture and copy out the parts we need, reordering as needed. + D3D11_MAPPED_SUBRESOURCE lock; + qbyte *rgb, *in, *r = NULL; + unsigned int x,y; + D3D11_TEXTURE2D_DESC texDesc; + ID3D11Texture2D *texture; + ID3D11Resource *backbuffer; + texDesc.ArraySize = 1; + texDesc.BindFlags = 0; + texDesc.CPUAccessFlags = 0; + texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + texDesc.Width = vid.pixelwidth; + texDesc.Height = vid.pixelheight; + texDesc.MipLevels = 1; + texDesc.MiscFlags = 0; + texDesc.SampleDesc.Count = 1; + texDesc.SampleDesc.Quality = 0; + texDesc.Usage = D3D11_USAGE_STAGING; + texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + if (FAILED(ID3D11Device_CreateTexture2D(pD3DDev11, &texDesc, 0, &texture))) + return NULL; + ID3D11RenderTargetView_GetResource(fb_backbuffer, &backbuffer); + ID3D11DeviceContext_CopyResource(d3ddevctx, (ID3D11Resource*)texture, backbuffer); + ID3D11Resource_Release(backbuffer); + if (!FAILED(ID3D11DeviceContext_Map(d3ddevctx, (ID3D11Resource*)texture, 0, D3D11_MAP_READ, 0, &lock))) { - if (!FAILED(IDirect3DSurface9_GetDesc(backbuf, &desc))) - if (desc.Format == D3DFMT_X8R8G8B8 || desc.Format == D3DFMT_A8R8G8B8) - if (!FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(pD3DDev9, - desc.Width, desc.Height, desc.Format, - D3DPOOL_SYSTEMMEM, &surf, NULL)) - ) + r = rgb = BZ_Malloc(prepad + 3 * vid.pixelwidth * vid.pixelheight); + r += prepad; + for (y = vid.pixelheight; y-- > 0; ) { - - if (!FAILED(IDirect3DDevice9_GetRenderTargetData(pD3DDev9, backbuf, surf))) - if (!FAILED(IDirect3DSurface9_LockRect(surf, &rect, NULL, D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_READONLY|D3DLOCK_NOSYSLOCK))) + in = lock.pData; + in += y * lock.RowPitch; + for (x = 0; x < vid.pixelwidth; x++, rgb+=3, in+=4) { - ret = BZ_Malloc(prepad + desc.Width*desc.Height*3); - if (ret) - { - // read surface rect and convert 32 bgra to 24 rgb and flip - c = prepad+desc.Width*desc.Height*3; - p = (qbyte *)rect.pBits; - - for (i=c-(3*desc.Width); i>=prepad; i-=(3*desc.Width)) - { - for (j=0; jlpVtbl->RSSetViewports(d3ddevctx, 1, &vport); + ID3D11DeviceContext_RSSetViewports(d3ddevctx, 1, &vport); D3D11BE_SetupViewCBuffer(); } -/* -static int d3d11error(int i) -{ - if (FAILED(i))// != D3D_OK) - Con_Printf("D3D error: %i\n", i); - return i; -} -*/ static void (D3D11_SCR_UpdateScreen) (void) { - extern cvar_t _vid_wait_override; //extern int keydown[]; //extern cvar_t vid_conheight; int uimenu; @@ -983,7 +928,6 @@ static void (D3D11_SCR_UpdateScreen) (void) ID3D11DeviceContext_ClearRenderTargetView(d3ddevctx, fb_backbuffer, colours); } -#if 1 if (d3d_resized) { extern cvar_t vid_conautoscale, vid_conwidth; @@ -992,22 +936,9 @@ static void (D3D11_SCR_UpdateScreen) (void) // force width/height to be updated //vid.pixelwidth = window_rect.right - window_rect.left; //vid.pixelheight = window_rect.bottom - window_rect.top; -/* D3DVID_UpdateWindowStatus(mainwindow); - - released3dbackbuffer(); - IDXGISwapChain_ResizeBuffers(d3dswapchain, 0, 0, 0, DXGI_FORMAT_UNKNOWN, 0); - - D3D11BE_Reset(true); - vid.pixelwidth = window_rect.right - window_rect.left; - vid.pixelheight = window_rect.bottom - window_rect.top; - resetd3dbackbuffer(vid.pixelwidth, vid.pixelheight); - resetD3D11(); - D3D11BE_Reset(false); -*/ Cvar_ForceCallback(&vid_conautoscale); Cvar_ForceCallback(&vid_conwidth); } -#endif if (scr_disabled_for_loading) { @@ -1023,7 +954,7 @@ static void (D3D11_SCR_UpdateScreen) (void) SCR_DrawLoading (); scr_drawloading = false; // IDirect3DDevice9_EndScene(pD3DDev9); - IDXGISwapChain_Present(d3dswapchain, _vid_wait_override.ival, 0); + D3D11_PresentOrCrash(); RSpeedEnd(RSPEED_TOTALREFRESH); return; } @@ -1073,7 +1004,7 @@ static void (D3D11_SCR_UpdateScreen) (void) #endif // R2D_BrightenScreen(); // IDirect3DDevice9_EndScene(pD3DDev9); - IDXGISwapChain_Present(d3dswapchain, _vid_wait_override.ival, 0); + D3D11_PresentOrCrash(); RSpeedEnd(RSPEED_TOTALREFRESH); return; } @@ -1141,10 +1072,7 @@ static void (D3D11_SCR_UpdateScreen) (void) RSpeedEnd(RSPEED_TOTALREFRESH); RSpeedShow(); - -// d3d11error(IDirect3DDevice9_EndScene(pD3DDev9)); - - IDXGISwapChain_Present(d3dswapchain, _vid_wait_override.ival, 0); + D3D11_PresentOrCrash(); window_center_x = (window_rect.left + window_rect.right)/2; window_center_y = (window_rect.top + window_rect.bottom)/2; @@ -1214,14 +1142,6 @@ static void D3D11_SetupViewPort(void) w = x2 - x; h = y2 - y; -// vport.X = x; -// vport.Y = y; -// vport.Width = w; -// vport.Height = h; -// vport.MinZ = 0; -// vport.MaxZ = 1; -// IDirect3DDevice9_SetViewport(pD3DDev9, &vport); - fov_x = r_refdef.fov_x;//+sin(cl.time)*5; fov_y = r_refdef.fov_y;//-sin(cl.time+1)*5; @@ -1235,57 +1155,50 @@ static void D3D11_SetupViewPort(void) /*view matrix*/ Matrix4x4_CM_ModelViewMatrixFromAxis(r_refdef.m_view, vpn, vright, vup, r_refdef.vieworg); -// d3d11error(IDirect3DDevice9_SetTransform(pD3DDev9, D3DTS_VIEW, (D3DMATRIX*)r_refdef.m_view)); - - /*d3d projection matricies scale depth to 0 to 1*/ - Matrix4x4_CM_Projection_Inf(d3d_trueprojection, fov_x, fov_y, gl_mindist.value/2); -// d3d11error(IDirect3DDevice9_SetTransform(pD3DDev9, D3DTS_PROJECTION, (D3DMATRIX*)d3d_trueprojection)); - /*ogl projection matricies scale depth to -1 to 1, and I would rather my code used consistant culling*/ Matrix4x4_CM_Projection_Inf(r_refdef.m_projection, fov_x, fov_y, gl_mindist.value); } static void (D3D11_R_RenderView) (void) { + float x, x2, y, y2; + D3D11_SetupViewPort(); //unlike gl, we clear colour beforehand, because that seems more sane. //always clear depth ID3D11DeviceContext_ClearDepthStencilView(d3ddevctx, fb_backdepthstencil, D3D11_CLEAR_DEPTH, 1, 0); //is it faster to clear the stencil too? + x = (r_refdef.vrect.x * (int)vid.pixelwidth)/(int)vid.width; + x2 = (r_refdef.vrect.x + r_refdef.vrect.width) * (int)vid.pixelwidth/(int)vid.width; + y = (r_refdef.vrect.y * (int)vid.pixelheight)/(int)vid.height; + y2 = (r_refdef.vrect.y + r_refdef.vrect.height) * (int)vid.pixelheight/(int)vid.height; + r_refdef.pxrect.x = floor(x); + r_refdef.pxrect.y = floor(y); + r_refdef.pxrect.width = (int)ceil(x2) - r_refdef.pxrect.x; + r_refdef.pxrect.height = (int)ceil(y2) - r_refdef.pxrect.y; + R_SetFrustum (r_refdef.m_projection, r_refdef.m_view); RQ_BeginFrame(); +// if (!(r_refdef.flags & Q2RDF_NOWORLDMODEL)) +// { +// if (cl.worldmodel) +// P_DrawParticles (); +// } + if (!(r_refdef.flags & Q2RDF_NOWORLDMODEL)) - { - if (cl.worldmodel) - P_DrawParticles (); - } + if (!r_worldentity.model || r_worldentity.model->needload || !cl.worldmodel) + { + D3D11_Set2D (); + R2D_ImageColours(0, 0, 0, 1); + R2D_FillBlock(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height); + R2D_ImageColours(1, 1, 1, 1); + return; + } Surf_DrawWorld(); RQ_RenderBatchClear(); D3D11_Set2D (); } -void (D3D11_R_NewMap) (void); -void (D3D11_R_PreNewMap) (void); - -void (D3D11_R_PushDlights) (void); -void (D3D11_R_AddStain) (vec3_t org, float red, float green, float blue, float radius); -void (D3D11_R_LessenStains) (void); - -qboolean (D3D11_VID_Init) (rendererstate_t *info, unsigned char *palette); -void (D3D11_VID_DeInit) (void); -void (D3D11_VID_SetPalette) (unsigned char *palette); -void (D3D11_VID_ShiftPalette) (unsigned char *palette); -char *(D3D11_VID_GetRGBInfo) (int prepad, int *truevidwidth, int *truevidheight); -void (D3D11_VID_SetWindowCaption) (char *msg); - -void (D3D11_SCR_UpdateScreen) (void); - - - - - - - rendererinfo_t d3d11rendererinfo = { "Direct3D11", diff --git a/engine/dotnet2005/ftequake.sln b/engine/dotnet2005/ftequake.sln index 9bd678e3..f85ec0a3 100644 --- a/engine/dotnet2005/ftequake.sln +++ b/engine/dotnet2005/ftequake.sln @@ -8,6 +8,9 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "botlib", "botlib.vcproj", "{0018E098-B12A-4E4D-9B22-6772DA287080}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fteqcc", "..\qclib\dotnet2005\qcc.vcproj", "{2866F783-6B44-4655-A38D-D53874037454}" + ProjectSection(ProjectDependencies) = postProject + {9767E236-8454-44E9-8999-CD5BDAFBE9BA} = {9767E236-8454-44E9-8999-CD5BDAFBE9BA} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qtvprox", "..\..\fteqtv\dotnet2005\qtvprox.vcproj", "{62669E6C-7E18-4E4D-BA54-DFBE29E7D24E}" EndProject @@ -314,7 +317,6 @@ Global {4735677B-6D5A-4BE6-A945-CB32DEADBEEF}.D3DDebug|Win32.ActiveCfg = Debug|Win32 {4735677B-6D5A-4BE6-A945-CB32DEADBEEF}.D3DDebug|x64.ActiveCfg = Release|Win32 {4735677B-6D5A-4BE6-A945-CB32DEADBEEF}.D3DRelease|Win32.ActiveCfg = Release|Win32 - {4735677B-6D5A-4BE6-A945-CB32DEADBEEF}.D3DRelease|Win32.Build.0 = Release|Win32 {4735677B-6D5A-4BE6-A945-CB32DEADBEEF}.D3DRelease|x64.ActiveCfg = Release|Win32 {4735677B-6D5A-4BE6-A945-CB32DEADBEEF}.Debug Dedicated Server|Win32.ActiveCfg = Debug|Win32 {4735677B-6D5A-4BE6-A945-CB32DEADBEEF}.Debug Dedicated Server|x64.ActiveCfg = Debug|Win32 diff --git a/engine/dotnet2005/ftequake.vcproj b/engine/dotnet2005/ftequake.vcproj index 2f6463a1..0791153e 100644 --- a/engine/dotnet2005/ftequake.vcproj +++ b/engine/dotnet2005/ftequake.vcproj @@ -279,6 +279,7 @@ LinkIncremental="2" SuppressStartupBanner="true" AdditionalLibraryDirectories="../libs/dxsdk7/lib" + GenerateManifest="false" IgnoreDefaultLibraryNames="libc.lib;msvcrt.lib" GenerateDebugInformation="true" GenerateMapFile="true" @@ -635,6 +636,7 @@ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops" UseOfMFC="0" ATLMinimizesCRunTimeLibraryUsage="false" + CharacterSet="0" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -29465,6 +29635,23 @@ /> + + + + + + + diff --git a/engine/gl/gl_backend.c b/engine/gl/gl_backend.c index b151d452..5ca69a67 100644 --- a/engine/gl/gl_backend.c +++ b/engine/gl/gl_backend.c @@ -963,14 +963,17 @@ int GLBE_SetupForShadowMap(texid_t shadowmaptex, int texwidth, int texheight, fl { extern cvar_t r_shadow_shadowmapping_bias; extern cvar_t r_shadow_shadowmapping_nearclip; + float n = r_shadow_shadowmapping_nearclip.value; + float f = shaderstate.lightradius; + float b = r_shadow_shadowmapping_bias.value; //FIXME: this is used for rendering, not for shadow generation! #define SHADOWMAP_SIZE 512 //projection frustum, slots 10+11. scaled by 0.5 //lightshadowmapproj is a projection matrix packed into a vec4, with various texture scaling stuff built in. shaderstate.lightshadowmapproj[0] = shadowscale * (1.0-(1.0/texwidth)) * 0.5/3.0; shaderstate.lightshadowmapproj[1] = shadowscale * (1.0-(1.0/texheight)) * 0.5/2.0; - shaderstate.lightshadowmapproj[2] = 0.5*(shaderstate.lightradius+r_shadow_shadowmapping_nearclip.value)/(r_shadow_shadowmapping_nearclip.value-shaderstate.lightradius); - shaderstate.lightshadowmapproj[3] = (shaderstate.lightradius*r_shadow_shadowmapping_nearclip.value)/(r_shadow_shadowmapping_nearclip.value-shaderstate.lightradius) - 0.5*r_shadow_shadowmapping_bias.value*r_shadow_shadowmapping_nearclip.value*(1024/texheight); + shaderstate.lightshadowmapproj[2] = 0.5*(f+n)/(n-f); + shaderstate.lightshadowmapproj[3] = (f*n)/(n-f) - b*n*(1024/texheight); shaderstate.lightshadowmapscale[0] = 1.0/(SHADOWMAP_SIZE*3); shaderstate.lightshadowmapscale[1] = 1.0/(SHADOWMAP_SIZE*2); @@ -1252,6 +1255,7 @@ void GenerateFogTexture(texid_t *tex, float density, float zscale) f = 0; if (f > 1) f = 1; + f *= (float)t / (FOGT-1); fogdata[t*FOGS + s][0] = 255; fogdata[t*FOGS + s][1] = 255; @@ -1319,6 +1323,7 @@ void GLBE_Init(void) shaderstate.dummybatch.lightmap[i] = -1; #ifdef RTLIGHTS + Sh_CheckSettings(); if (r_shadow_realtime_dlight.ival || r_shadow_realtime_world.ival) { if (r_shadow_shadowmapping.ival) @@ -1420,6 +1425,7 @@ static void tcgen_fog(float *st, unsigned int numverts, float *xyz, mfog_t *fog) int i; float z; + float eye, point; vec4_t zmat; //generate a simple matrix to calc only the projected z coord @@ -1430,11 +1436,29 @@ static void tcgen_fog(float *st, unsigned int numverts, float *xyz, mfog_t *fog) Vector4Scale(zmat, shaderstate.fogfar, zmat); + if (fog->visibleplane) + eye = (DotProduct(r_refdef.vieworg, fog->visibleplane->normal) - fog->visibleplane->dist); + else + eye = 1; +// if (eye < 1) +// eye = 1; + for (i = 0 ; i < numverts ; i++, xyz += sizeof(vecV_t)/sizeof(vec_t), st += 2 ) { z = DotProduct(xyz, zmat) + zmat[3]; st[0] = z; st[1] = realtime - (int)realtime; + + if (fog->visibleplane) + point = (DotProduct(xyz, fog->visibleplane->normal) - fog->visibleplane->dist); + else + point = 1; + if (eye < 0) + st[1] = (point < 0)?1:0; + else + st[1] = point / (point - eye); + if (st[1] > 31/32.0) + st[1] = 31/32.0; } } @@ -3443,10 +3467,10 @@ void GLBE_Scissor(srect_t *rect) { qglScissor( floor(r_refdef.pxrect.x + rect->x*r_refdef.pxrect.width), - floor((r_refdef.pxrect.y + rect->y*r_refdef.pxrect.height) - r_refdef.pxrect.height), + floor((r_refdef.pxrect.y + rect->y*r_refdef.pxrect.height) - r_refdef.pxrect.maxheight), ceil(rect->width * r_refdef.pxrect.width), ceil(rect->height * r_refdef.pxrect.height)); - qglEnable(GL_SCISSOR_TEST); +// qglEnable(GL_SCISSOR_TEST); if (qglDepthBoundsEXT) { @@ -4156,6 +4180,9 @@ static void GLBE_SubmitMeshesSortList(batch_t *sortlist) if (batch->shader->flags & SHADER_NODLIGHT) if (shaderstate.mode == BEM_LIGHT) continue; + if (batch->shader->flags & SHADER_NOSHADOWS) + if (shaderstate.mode == BEM_DEPTHONLY) + continue; if (batch->shader->flags & SHADER_SKY) { if (shaderstate.mode == BEM_STANDARD || shaderstate.mode == BEM_DEPTHDARK) @@ -4182,7 +4209,8 @@ static void GLBE_SubmitMeshesSortList(batch_t *sortlist) if ((batch->shader->flags & SHADER_HASREFLECT) && gl_config.ext_framebuffer_objects) { - vrect_t orect = r_refdef.vrect, oprect = r_refdef.pxrect; + vrect_t orect = r_refdef.vrect; + pxrect_t oprect = r_refdef.pxrect; if (!shaderstate.tex_reflection.num) { shaderstate.tex_reflection = GL_AllocNewTexture("***tex_reflection***", vid.pixelwidth/2, vid.pixelheight/2, 0); @@ -4200,10 +4228,11 @@ static void GLBE_SubmitMeshesSortList(batch_t *sortlist) r_refdef.vrect.width = vid.width/2; r_refdef.vrect.height = vid.height/2; r_refdef.pxrect.x = 0; + r_refdef.pxrect.y = 0; r_refdef.pxrect.width = vid.pixelwidth/2; r_refdef.pxrect.height = vid.pixelheight/2; - r_refdef.pxrect.y = r_refdef.pxrect.height; - qglViewport (r_refdef.pxrect.x, r_refdef.pxrect.height-r_refdef.pxrect.y, r_refdef.pxrect.width, r_refdef.pxrect.height); + r_refdef.pxrect.maxheight = vid.pixelheight/2; + GL_ViewportUpdate(); GL_ForceDepthWritable(); qglClearColor(0, 0, 0, 0); qglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -4211,13 +4240,14 @@ static void GLBE_SubmitMeshesSortList(batch_t *sortlist) GLBE_RenderToTexture(r_nulltex, r_nulltex, r_nulltex, r_nulltex, false); r_refdef.vrect = orect; r_refdef.pxrect = oprect; - qglViewport (r_refdef.pxrect.x, r_refdef.pxrect.height-r_refdef.pxrect.y, r_refdef.pxrect.width, r_refdef.pxrect.height); + GL_ViewportUpdate(); } if (batch->shader->flags & (SHADER_HASREFRACT|SHADER_HASREFRACTDEPTH)) { if (r_refract_fboival) { - vrect_t ovrect = r_refdef.vrect, oprect = r_refdef.pxrect; + vrect_t ovrect = r_refdef.vrect; + pxrect_t oprect = r_refdef.pxrect; GL_ForceDepthWritable(); if (!shaderstate.tex_refraction.num) @@ -4252,10 +4282,11 @@ static void GLBE_SubmitMeshesSortList(batch_t *sortlist) r_refdef.vrect.width = vid.width/2; r_refdef.vrect.height = vid.height/2; r_refdef.pxrect.x = 0; + r_refdef.pxrect.y = 0; r_refdef.pxrect.width = vid.pixelwidth/2; r_refdef.pxrect.height = vid.pixelheight/2; - r_refdef.pxrect.y = r_refdef.pxrect.height; - qglViewport (r_refdef.pxrect.x, r_refdef.pxrect.height-r_refdef.pxrect.y, r_refdef.pxrect.width, r_refdef.pxrect.height); + r_refdef.pxrect.maxheight = vid.pixelheight/2; + GL_ViewportUpdate(); GL_ForceDepthWritable(); qglClearColor(0, 0, 0, 0); @@ -4265,14 +4296,15 @@ static void GLBE_SubmitMeshesSortList(batch_t *sortlist) r_refdef.vrect = ovrect; r_refdef.pxrect = oprect; - qglViewport (r_refdef.pxrect.x, r_refdef.pxrect.height-r_refdef.pxrect.y, r_refdef.pxrect.width, r_refdef.pxrect.height); + GL_ViewportUpdate(); } else GLR_DrawPortal(batch, cl.worldmodel->batches, 3); } if ((batch->shader->flags & SHADER_HASRIPPLEMAP) && gl_config.ext_framebuffer_objects) { - vrect_t orect = r_refdef.vrect, oprect = r_refdef.pxrect; + vrect_t orect = r_refdef.vrect; + pxrect_t oprect = r_refdef.pxrect; if (!shaderstate.tex_ripplemap.num) { //FIXME: can we use RGB8 instead? @@ -4290,10 +4322,11 @@ static void GLBE_SubmitMeshesSortList(batch_t *sortlist) r_refdef.vrect.width = vid.width/2; r_refdef.vrect.height = vid.height/2; r_refdef.pxrect.x = 0; + r_refdef.pxrect.y = 0; r_refdef.pxrect.width = vid.pixelwidth/2; r_refdef.pxrect.height = vid.pixelheight/2; - r_refdef.pxrect.y = r_refdef.pxrect.height; - qglViewport (r_refdef.pxrect.x, r_refdef.pxrect.height-r_refdef.pxrect.y, r_refdef.pxrect.width, r_refdef.pxrect.height); + r_refdef.pxrect.maxheight = vid.pixelheight/2; + GL_ViewportUpdate(); qglClearColor(0, 0, 0, 0); qglClear(GL_COLOR_BUFFER_BIT); @@ -4307,7 +4340,7 @@ static void GLBE_SubmitMeshesSortList(batch_t *sortlist) r_refdef.vrect = orect; r_refdef.pxrect = oprect; - qglViewport (r_refdef.pxrect.x, r_refdef.pxrect.height-r_refdef.pxrect.y, r_refdef.pxrect.width, r_refdef.pxrect.height); + GL_ViewportUpdate(); } BE_SelectMode(oldbem); } @@ -4705,7 +4738,7 @@ void GLBE_DrawWorld (qboolean drawworld, qbyte *vis) } #ifdef RTLIGHTS - if (vis && r_shadow_realtime_world.ival) + if (drawworld && r_shadow_realtime_world.ival) shaderstate.identitylighting = r_shadow_realtime_world_lightmaps.value; else #endif diff --git a/engine/gl/gl_bloom.c b/engine/gl/gl_bloom.c index 2f5822f0..955456a6 100644 --- a/engine/gl/gl_bloom.c +++ b/engine/gl/gl_bloom.c @@ -189,7 +189,7 @@ void R_BloomBlend (void) /*grab the screen, because we failed to do it earlier*/ GL_MTBind(0, GL_TEXTURE_2D, scrtex); - qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r_refdef.pxrect.x, r_refdef.pxrect.y - r_refdef.pxrect.height, r_refdef.pxrect.width, r_refdef.pxrect.height); + qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r_refdef.pxrect.x, r_refdef.pxrect.maxheight - (r_refdef.pxrect.y+r_refdef.pxrect.height), r_refdef.pxrect.width, r_refdef.pxrect.height); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -228,6 +228,8 @@ void R_BloomBlend (void) qglViewport (0, 0, texwidth[i], texheight[i]); R2D_ScalePic(0, vid.height, vid.width, -(int)vid.height, bloomblur); } + r_worldentity.glowmod[0] = 0; + r_worldentity.glowmod[1] = 0; GL_Set2D(false); diff --git a/engine/gl/gl_draw.c b/engine/gl/gl_draw.c index 53ab33b7..0843b140 100644 --- a/engine/gl/gl_draw.c +++ b/engine/gl/gl_draw.c @@ -166,7 +166,7 @@ typedef struct gltexture_s { texcom_t com; texid_t texnum; - char identifier[64]; + char identifier[MAX_QPATH]; int bpp; unsigned int flags; struct gltexture_s *next; @@ -595,12 +595,13 @@ void GL_Set2D (qboolean flipped) Matrix4x4_Identity(r_refdef.m_view); } r_refdef.pxrect.x = 0; + r_refdef.pxrect.y = 0; r_refdef.pxrect.width = vid.pixelwidth; r_refdef.pxrect.height = vid.pixelheight; - r_refdef.pxrect.y = r_refdef.pxrect.height; + r_refdef.pxrect.maxheight = vid.pixelheight; r_refdef.time = realtime; /*flush that gl state*/ - qglViewport (0, 0, vid.pixelwidth, vid.pixelheight); + GL_ViewportUpdate(); if (qglLoadMatrixf) { diff --git a/engine/gl/gl_font.c b/engine/gl/gl_font.c index 77fec4b6..5627eec3 100644 --- a/engine/gl/gl_font.c +++ b/engine/gl/gl_font.c @@ -1370,7 +1370,7 @@ int Font_LineBreaks(conchar_t *start, conchar_t *end, int maxpixelwidth, int max for (px=0, l=0 ; px <= maxpixelwidth; ) { l++; - if (start+l >= end || (start[l-1]&CON_CHARMASK) == '\n') + if (start+l >= end || (start[l-1]&(CON_CHARMASK|CON_HIDDEN)) == '\n') break; px = Font_CharEndCoord(font, px, start[l]); } @@ -1397,7 +1397,7 @@ int Font_LineBreaks(conchar_t *start, conchar_t *end, int maxpixelwidth, int max if (start == end) break; - if ((*start&CON_CHARMASK) == '\n'||!l) + if ((*start&(CON_CHARMASK|CON_HIDDEN)) == '\n'||!l) start++; // skip the \n } diff --git a/engine/gl/gl_heightmap.c b/engine/gl/gl_heightmap.c index d3072d55..ab24feb3 100644 --- a/engine/gl/gl_heightmap.c +++ b/engine/gl/gl_heightmap.c @@ -21,6 +21,7 @@ It also means for explosions and local stuff, the server will merely restate cha */ cvar_t mod_terrain_networked = CVARD("mod_terrain_networked", "0", "Terrain edits are networked. Clients will download sections on demand, and servers will notify clients of changes."); cvar_t mod_terrain_defaulttexture = CVARD("mod_terrain_defaulttexture", "", "Newly created terrain tiles will use this texture. This should generally be updated by the terrain editor."); +cvar_t mod_terrain_savever = CVARD("mod_terrain_savever", "", "Which terrain section version to write if terrain was edited."); /* terminology: @@ -56,12 +57,16 @@ int Surf_NewLightmaps(int count, int width, int height, qboolean deluxe); #define HMLMSTRIDE (LMCHUNKS*SECTTEXSIZE) #define SECTION_MAGIC (*(int*)"HMMS") -#define SECTION_VER 1 +#define SECTION_VER_DEFAULT 1 /*simple version history: ver=0 SECTHEIGHTSIZE=16 ver=1 SECTHEIGHTSIZE=17 (oops, makes holes more usable) + (holes in this format are no longer supported) +ver=2 + uses deltas instead of absolute values + variable length image names */ #define TGS_NOLOAD 0 @@ -75,8 +80,10 @@ ver=1 enum { //these flags can be found on disk - TSF_HASWATER = 1u<<0, + TSF_HASWATER_V0 = 1u<<0, //no longer flagged. TSF_HASCOLOURS = 1u<<1, + TSF_HASHEIGHTS = 1u<<2, + TSF_HASSHADOW = 1u<<3, //these flags are found only on disk TSF_COMPRESSED = 1u<<31, @@ -90,6 +97,11 @@ enum #define TSF_INTERNAL (TSF_RELIGHT|TSF_DIRTY|TSF_EDITED|TSF_NOTIFY|TSF_FAILEDLOAD) }; +enum +{ + TMF_SCALE = 1u<<0, + //what else do we want? alpha? colormod perhaps? +}; typedef struct { @@ -105,12 +117,12 @@ typedef struct typedef struct { unsigned int flags; - char texname[4][32]; //FIXME: 32 is not enough. make variable length. - unsigned int texmap[SECTTEXSIZE][SECTTEXSIZE]; //FIXME: RLE? at least strip out unused channels. + char texname[4][32]; + unsigned int texmap[SECTTEXSIZE][SECTTEXSIZE]; float heights[SECTHEIGHTSIZE*SECTHEIGHTSIZE]; - unsigned short holes; //FIXME: use 16 shorts? 8 bytes? WHAT DO YOU WANT FROM ME?!? + unsigned short holes; unsigned short reserved0; - float waterheight; //needs separate holes. possibly multiple sets of water. maybe a heightmap. independant shader name. + float waterheight; float minh; float maxh; int ents_num; @@ -147,18 +159,31 @@ typedef struct hmpolyset_s mesh_t *amesh; vbo_t vbo; } hmpolyset_t; +struct hmwater_s +{ + struct hmwater_s *next; + unsigned int contentmask; + qboolean simple; + float minheight; + float maxheight; + shader_t *shader; + qbyte holes[8]; + float heights[9*9]; +}; typedef struct { link_t recycle; int sx, sy; float heights[SECTHEIGHTSIZE*SECTHEIGHTSIZE]; - unsigned short holes; + unsigned char holes[8]; unsigned int flags; - float waterheight; + float maxh_cull; //includes water+mesh heights float minh, maxh; struct heightmap_s *hmmod; + struct hmwater_s *water; + #ifndef SERVERONLY pvscache_t pvscache; vec4_t colours[SECTHEIGHTSIZE*SECTHEIGHTSIZE]; //FIXME: make bytes @@ -185,13 +210,13 @@ typedef struct typedef struct heightmap_s { char path[MAX_QPATH]; + char watershadername[MAX_QPATH]; //typically the name of the ocean or whatever. int firstsegx, firstsegy; int maxsegx, maxsegy; //tex/cull sections float sectionsize; //each section is this big, in world coords hmcluster_t *cluster[MAXCLUSTERS*MAXCLUSTERS]; shader_t *skyshader; shader_t *shader; - shader_t *watershader; mesh_t skymesh; mesh_t *askymesh; unsigned int exteriorcontents; @@ -227,7 +252,6 @@ typedef struct heightmap_s #endif } heightmap_t; - #ifndef SERVERONLY static void ted_dorelight(heightmap_t *hm); #endif @@ -390,6 +414,10 @@ static char *Terr_DiskBlockName(heightmap_t *hm, int sx, int sy) sy &= CHUNKLIMIT-1; sx /= SECTIONSPERBLOCK; sy /= SECTIONSPERBLOCK; + if (sx >= CHUNKBIAS/SECTIONSPERBLOCK) + sx |= 0xffffff00; + if (sy >= CHUNKBIAS/SECTIONSPERBLOCK) + sy |= 0xffffff00; return va("maps/%s/block_%s_%s.hms", hm->path, genextendedhex(sx, xpart), genextendedhex(sy, ypart)); } static char *Terr_DiskSectionName(heightmap_t *hm, int sx, int sy) @@ -516,6 +544,26 @@ static hmsection_t *Terr_GenerateSection(heightmap_t *hm, int sx, int sy) return s; } +static void *Terr_GenerateWater(hmsection_t *s, float maxheight) +{ + int i; + struct hmwater_s *w; + w = Z_Malloc(sizeof(*s->water)); + w->next = s->water; + s->water = w; +#ifndef SERVERONLY + w->shader = R_RegisterCustom (s->hmmod->watershadername, SUF_NONE, Shader_DefaultWaterShader, NULL); +#endif + w->simple = true; + memset(w->holes, 0, sizeof(w->holes)); + for (i = 0; i < 9*9; i++) + w->heights[i] = maxheight; + w->maxheight = w->minheight = maxheight; + if (s->maxh_cull < w->maxheight) + s->maxh_cull = w->maxheight; + return w; +} + static void *Terr_ReadV1(heightmap_t *hm, hmsection_t *s, void *ptr, int len) { dsmesh_t *dm; @@ -524,15 +572,18 @@ static void *Terr_ReadV1(heightmap_t *hm, hmsection_t *s, void *ptr, int len) int i; unsigned int flags = LittleLong(ds->flags); - s->flags |= flags & ~TSF_INTERNAL; + s->flags |= flags & ~(TSF_INTERNAL|TSF_HASWATER_V0); for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) { s->heights[i] = LittleFloat(ds->heights[i]); } s->minh = ds->minh; s->maxh = ds->maxh; - s->waterheight = ds->waterheight; - s->holes = ds->holes; + if (flags & TSF_HASWATER_V0) + Terr_GenerateWater(s, ds->waterheight); + + memset(s->holes, 0, sizeof(s->holes)); +// s->holes = ds->holes; ptr = ds+1; @@ -630,12 +681,477 @@ static void *Terr_ReadV1(heightmap_t *hm, hmsection_t *s, void *ptr, int len) return ptr; } + + + +struct terrstream_s +{ + qbyte *buffer; + int maxsize; + int pos; +}; +//I really hope these get inlined properly. +static int Terr_Read_SInt(struct terrstream_s *strm) +{ + int val; + strm->pos = (strm->pos + sizeof(val)-1) & ~(sizeof(val)-1); + val = *(int*)(strm->buffer+strm->pos); + strm->pos += sizeof(val); + return LittleLong(val); +} +static qbyte Terr_Read_Byte(struct terrstream_s *strm) +{ + qbyte val; + val = *(qbyte*)(strm->buffer+strm->pos); + strm->pos += sizeof(val); + return val; +} +static float Terr_Read_Float(struct terrstream_s *strm) +{ + float val; + strm->pos = (strm->pos + sizeof(val)-1) & ~(sizeof(val)-1); + val = *(float*)(strm->buffer+strm->pos); + strm->pos += sizeof(val); + return LittleFloat(val); +} +static char *Terr_Read_String(struct terrstream_s *strm, char *val, int maxlen) +{ + int len = strlen(strm->buffer + strm->pos); + maxlen = min(len, maxlen-1); //truncate + memcpy(val, strm->buffer + strm->pos, maxlen); + val[maxlen] = 0; + strm->pos += len+1; + return val; +} +#ifndef SERVERONLY +static void Terr_Write_SInt(struct terrstream_s *strm, int val) +{ + val = LittleLong(val); + strm->pos = (strm->pos + sizeof(val)-1) & ~(sizeof(val)-1); + *(int*)(strm->buffer+strm->pos) = val; + strm->pos += sizeof(val); +} +static void Terr_Write_Byte(struct terrstream_s *strm, qbyte val) +{ + *(qbyte*)(strm->buffer+strm->pos) = val; + strm->pos += sizeof(val); +} +static void Terr_Write_Float(struct terrstream_s *strm, float val) +{ + val = LittleFloat(val); + strm->pos = (strm->pos + sizeof(val)-1) & ~(sizeof(val)-1); + *(float*)(strm->buffer+strm->pos) = val; + strm->pos += sizeof(val); +} +static void Terr_Write_String(struct terrstream_s *strm, char *val) +{ + int len = strlen(val)+1; + memcpy(strm->buffer + strm->pos, val, len); + strm->pos += len; +} +static void Terr_TrimWater(hmsection_t *s) +{ + int i; + struct hmwater_s *w, **link; + + for (link = &s->water; (w = *link); link = &(*link)->next) + { + //one has a height above the terrain? + for (i = 0; i < 9*9; i++) + if (w->heights[i] > s->minh) + break; + if (i == 9*9) + { + *link = w->next; + Z_Free(w); + continue; + } + } +} +static void Terr_SaveV2(heightmap_t *hm, hmsection_t *s, vfsfile_t *f, int sx, int sy) +{ + qbyte buffer[65536], last, delta, *lm; + struct terrstream_s strm = {buffer, sizeof(buffer), 0}; + unsigned int flags = s->flags; + int i, j, x, y; + struct hmwater_s *w; + + flags &= ~(TSF_INTERNAL); + flags &= ~(TSF_HASCOLOURS|TSF_HASHEIGHTS|TSF_HASSHADOW); + + for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) + { + if (s->colours[i][0] != 1 || s->colours[i][1] != 1 || s->colours[i][2] != 1 || s->colours[i][3] != 1) + { + flags |= TSF_HASCOLOURS; + break; + } + } + for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) + { + if (s->heights[i] != s->heights[0]) + { + flags |= TSF_HASHEIGHTS; + break; + } + } + + lm = lightmap[s->lightmap]->lightmaps; + lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes; + for (y = 0; y < SECTTEXSIZE; y++) + { + for (x = 0; x < SECTTEXSIZE; x++) + { + if (lm[x*4+3] != 255) + { + flags |= TSF_HASSHADOW; + y = SECTTEXSIZE; + break; + } + } + lm += (HMLMSTRIDE)*lightmap_bytes; + } + + //write the flags so the loader knows what to load + Terr_Write_SInt(&strm, flags); + + //if heights are compressed, only the first is present. + if (!(flags & TSF_HASHEIGHTS)) + Terr_Write_Float(&strm, s->heights[0]); + else + { + for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) + Terr_Write_Float(&strm, s->heights[i]); + } + + for (i = 0; i < sizeof(s->holes); i++) + Terr_Write_Byte(&strm, s->holes[i]); + + Terr_TrimWater(s); + for (j = 0, w = s->water; w; j++) + w = w->next; + Terr_Write_SInt(&strm, j); + for (i = 0; i < j; i++) + { + char *shadername = w->shader->name; + int fl = 0; + + if (strcmp(shadername, hm->watershadername)) + fl |= 1; + for (x = 0; x < 8; x++) + if (w->holes[x]) + break; + fl |= ((x==8)?0:2); + for (x = 0; x < 9*9; x++) + if (w->heights[x] != w->heights[0]) + break; + fl |= ((x==9*9)?0:4); + + + Terr_Write_SInt(&strm, fl); + Terr_Write_SInt(&strm, w->contentmask); + if (fl & 1) + Terr_Write_String(&strm, shadername); + if (fl & 2) + { + for (x = 0; x < 8; x++) + Terr_Write_Byte(&strm, w->holes[x]); + } + if (fl & 4) + { + for (x = 0; x < 9*9; x++) + Terr_Write_Float(&strm, w->heights[x]); + } + else + Terr_Write_Float(&strm, w->heights[0]); + } + + if (flags & TSF_HASCOLOURS) + { + //FIXME: bytes? channels? + for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) + { + Terr_Write_Float(&strm, s->colours[i][0]); + Terr_Write_Float(&strm, s->colours[i][1]); + Terr_Write_Float(&strm, s->colours[i][2]); + Terr_Write_Float(&strm, s->colours[i][3]); + } + } + + for (j = 0; j < 4; j++) + Terr_Write_String(&strm, s->texname[j]); + for (j = 0; j < 4; j++) + { + if (j == 3) + { + //only write the channel if it has actual data + if (!(flags & TSF_HASSHADOW)) + continue; + } + else + { + //only write the data if there's actually a texture. + //its not meant to be possible to delete a texture without deleting its data too. + // + if (!*s->texname[2-j]) + continue; + } + + //write the channel + last = 0; + lm = lightmap[s->lightmap]->lightmaps; + lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes; + for (y = 0; y < SECTTEXSIZE; y++) + { + for (x = 0; x < SECTTEXSIZE; x++) + { + delta = lm[x*4+j] - last; + last = lm[x*4+j]; + Terr_Write_Byte(&strm, delta); + } + lm += (HMLMSTRIDE)*lightmap_bytes; + } + } + + Terr_Write_SInt(&strm, s->numents); + for (i = 0; i < s->numents; i++) + { + unsigned int mf; + mf = 0; + if (s->ents[i].scale != 1) + mf |= TMF_SCALE; + + //make sure we don't overflow. we should always be aligned at this point. + if (strm.pos > strm.maxsize/2) + { + VFS_WRITE(f, strm.buffer, strm.pos); + strm.pos = 0; + } + + if (s->ents[i].model) + Terr_Write_String(&strm, s->ents[i].model->name); + else + Terr_Write_String(&strm, "*invalid"); + Terr_Write_Float(&strm, s->ents[i].origin[0]+(CHUNKBIAS-sx)*hm->sectionsize); + Terr_Write_Float(&strm, s->ents[i].origin[1]+(CHUNKBIAS-sy)*hm->sectionsize); + Terr_Write_Float(&strm, s->ents[i].origin[2]); + Terr_Write_Float(&strm, s->ents[i].axis[0][0]); + Terr_Write_Float(&strm, s->ents[i].axis[0][1]); + Terr_Write_Float(&strm, s->ents[i].axis[0][2]); + Terr_Write_Float(&strm, s->ents[i].axis[1][0]); + Terr_Write_Float(&strm, s->ents[i].axis[1][1]); + Terr_Write_Float(&strm, s->ents[i].axis[1][2]); + Terr_Write_Float(&strm, s->ents[i].axis[2][0]); + Terr_Write_Float(&strm, s->ents[i].axis[2][1]); + Terr_Write_Float(&strm, s->ents[i].axis[2][2]); + if (mf & TMF_SCALE) + Terr_Write_Float(&strm, s->ents[i].scale); + } + + //reset it in case the buffer is getting a little full + strm.pos = (strm.pos + sizeof(int)-1) & ~(sizeof(int)-1); + VFS_WRITE(f, strm.buffer, strm.pos); + strm.pos = 0; +} +#endif +static void *Terr_ReadV2(heightmap_t *hm, hmsection_t *s, void *ptr, int len) +{ + char modelname[MAX_QPATH]; + struct terrstream_s strm = {ptr, len, 0}; + float f; + int i, j, x, y; + qbyte *lm, delta, last; + unsigned int flags = Terr_Read_SInt(&strm); + qboolean present; + + s->flags |= flags & ~TSF_INTERNAL; + if (flags & TSF_HASHEIGHTS) + { + s->minh = s->maxh = s->heights[0] = Terr_Read_Float(&strm); + for (i = 1; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) + { + f = Terr_Read_Float(&strm); + if (s->minh > f) + s->minh = f; + if (s->maxh < f) + s->maxh = f; + s->heights[i] = f; + } + } + else + { + s->minh = s->maxh = f = Terr_Read_Float(&strm); + for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) + s->heights[i] = f; + } + + for (i = 0; i < sizeof(s->holes); i++) + s->holes[i] = Terr_Read_Byte(&strm); + + j = Terr_Read_SInt(&strm); + for (i = 0; i < j; i++) + { + struct hmwater_s *w = Z_Malloc(sizeof(*w)); + char shadername[MAX_QPATH]; + int fl = Terr_Read_SInt(&strm); + w->next = s->water; + s->water = w; + w->contentmask = Terr_Read_SInt(&strm); + if (fl & 1) + Terr_Read_String(&strm, shadername, sizeof(shadername)); + else + Q_strncpyz(shadername, hm->watershadername, sizeof(hm->watershadername)); +#ifndef SERVERONLY +// CL_CheckOrEnqueDownloadFile(shadername, NULL, 0); + w->shader = R_RegisterCustom (shadername, SUF_NONE, Shader_DefaultWaterShader, NULL); +#endif + if (fl & 2) + { + for (x = 0; x < 8; x++) + w->holes[i] = Terr_Read_Byte(&strm); + } + if (fl & 4) + { + for (x = 0; x < 9*9; x++) + { + w->heights[x] = Terr_Read_Float(&strm); + } + } + else + { //all heights the same can be used as a way to compress the data + w->minheight = w->maxheight = Terr_Read_Float(&strm); + for (x = 0; x < 9*9; x++) + w->heights[x] = w->minheight = w->maxheight; + } + } + + //dedicated server can stop reading here. + +#ifndef SERVERONLY + if (flags & TSF_HASCOLOURS) + { + for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) + { + s->colours[i][0] = Terr_Read_Float(&strm); + s->colours[i][1] = Terr_Read_Float(&strm); + s->colours[i][2] = Terr_Read_Float(&strm); + s->colours[i][3] = Terr_Read_Float(&strm); + } + } + else + { + for (i = 0; i < SECTHEIGHTSIZE*SECTHEIGHTSIZE; i++) + { + s->colours[i][0] = 1; + s->colours[i][1] = 1; + s->colours[i][2] = 1; + s->colours[i][3] = 1; + } + } + + for (j = 0; j < 4; j++) + Terr_Read_String(&strm, s->texname[j], sizeof(s->texname[j])); + for (j = 0; j < 4; j++) + { + if (j == 3) + present = !!(flags & TSF_HASSHADOW); + else + present = !!(*s->texname[2-j]); + + if (present) + { + //read the channel + if (s->lightmap >= 0) + { + last = 0; + lm = lightmap[s->lightmap]->lightmaps; + lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes; + for (y = 0; y < SECTTEXSIZE; y++) + { + for (x = 0; x < SECTTEXSIZE; x++) + { + delta = Terr_Read_Byte(&strm); + last += delta; + lm[x*4+j] = last; + } + lm += (HMLMSTRIDE)*lightmap_bytes; + } + } + else + { + strm.pos += SECTTEXSIZE*SECTTEXSIZE; + } + } + else if (s->lightmap >= 0) + { + last = ((j==3)?255:0); + lm = lightmap[s->lightmap]->lightmaps; + lm += (s->lmy * HMLMSTRIDE + s->lmx) * lightmap_bytes; + for (y = 0; y < SECTTEXSIZE; y++) + { + for (x = 0; x < SECTTEXSIZE; x++) + lm[x*4+j] = last; + lm += (HMLMSTRIDE)*lightmap_bytes; + } + } + } + + /*load any static ents*/ + s->numents = Terr_Read_SInt(&strm); + if (s->maxents) + BZ_Free(s->ents); + s->maxents = s->numents; + if (s->maxents) + s->ents = BZ_Malloc(sizeof(*s->ents) * s->maxents); + else + s->ents = NULL; + if (!s->ents) + s->numents = s->maxents = 0; + for (i = 0; i < s->numents; i++) + { + unsigned int mf; + mf = Terr_Read_SInt(&strm); + memset(&s->ents[i], 0, sizeof(s->ents[i])); + s->ents[i].model = Mod_FindName(Terr_Read_String(&strm, modelname, sizeof(modelname))); + s->ents[i].origin[0] = Terr_Read_Float(&strm); + s->ents[i].origin[1] = Terr_Read_Float(&strm); + s->ents[i].origin[2] = Terr_Read_Float(&strm); + s->ents[i].axis[0][0] = Terr_Read_Float(&strm); + s->ents[i].axis[0][1] = Terr_Read_Float(&strm); + s->ents[i].axis[0][2] = Terr_Read_Float(&strm); + s->ents[i].axis[1][0] = Terr_Read_Float(&strm); + s->ents[i].axis[1][1] = Terr_Read_Float(&strm); + s->ents[i].axis[1][2] = Terr_Read_Float(&strm); + s->ents[i].axis[2][0] = Terr_Read_Float(&strm); + s->ents[i].axis[2][1] = Terr_Read_Float(&strm); + s->ents[i].axis[2][2] = Terr_Read_Float(&strm); + s->ents[i].scale = (mf&TMF_SCALE)?Terr_Read_Float(&strm):1; + + s->ents[i].drawflags = SCALE_ORIGIN_ORIGIN; + s->ents[i].playerindex = -1; + s->ents[i].origin[0] += (s->sx-CHUNKBIAS)*hm->sectionsize; + s->ents[i].origin[1] += (s->sy-CHUNKBIAS)*hm->sectionsize; + s->ents[i].shaderRGBAf[0] = 1; + s->ents[i].shaderRGBAf[1] = 1; + s->ents[i].shaderRGBAf[2] = 1; + s->ents[i].shaderRGBAf[3] = 1; + + if (!s->ents[i].model) + { + s->numents--; + i--; + } + } +#endif + return ptr; +} + static void Terr_GenerateDefault(heightmap_t *hm, hmsection_t *s) { int i; s->flags |= TSF_FAILEDLOAD; - s->holes = 0; + memset(s->holes, 0, sizeof(s->holes)); #ifndef SERVERONLY Q_strncpyz(s->texname[0], "", sizeof(s->texname[0])); @@ -674,6 +1190,7 @@ static void Terr_GenerateDefault(heightmap_t *hm, hmsection_t *s) s->colours[i][2] = 1; s->colours[i][3] = 1; } + s->mesh.colors4f_array[0] = s->colours; #endif #if 0//def DEBUG @@ -765,7 +1282,6 @@ static void Terr_GenerateDefault(heightmap_t *hm, hmsection_t *s) static hmsection_t *Terr_ReadSection(heightmap_t *hm, int ver, int sx, int sy, void *filebase, unsigned int filelen) { void *ptr = filebase; - unsigned int flags; hmsection_t *s; hmcluster_t *cluster; @@ -775,13 +1291,6 @@ static hmsection_t *Terr_ReadSection(heightmap_t *hm, int ver, int sx, int sy, v cluster = hm->cluster[clusternum] = Z_Malloc(sizeof(*cluster)); s = cluster->section[(sx%MAXSECTIONS) + (sy%MAXSECTIONS)*MAXSECTIONS]; - if (!ptr || ver != SECTION_VER) - { - filelen = 0; - flags = 0; - ptr = NULL; - } - if (!s) { s = Terr_GenerateSection(hm, sx, sy); @@ -793,15 +1302,14 @@ static hmsection_t *Terr_ReadSection(heightmap_t *hm, int ver, int sx, int sy, v Terr_InitLightmap(s, false); #endif - if (ptr) - { - - /*load the heights*/ + s->flags &= ~TSF_FAILEDLOAD; + if (ptr && ver == 1) Terr_ReadV1(hm, s, ptr, filelen); - s->flags &= ~TSF_FAILEDLOAD; - } + else if (ptr && ver == 2) + Terr_ReadV2(hm, s, ptr, filelen); else { + s->flags |= TSF_FAILEDLOAD; // s->flags |= TSF_RELIGHT; Terr_GenerateDefault(hm, s); } @@ -921,6 +1429,11 @@ static void Terr_LoadSection(heightmap_t *hm, hmsection_t *s, int sx, int sy, un FS_FreeFile(diskimage); } +#ifdef ADT + if (Terr_ImportADT(hm, sx, sy, flags)) + return; +#endif + //generate a dummy one Terr_ReadSection(hm, 0, sx, sy, NULL, 0); @@ -940,16 +1453,18 @@ static void Terr_SaveV1(heightmap_t *hm, hmsection_t *s, vfsfile_t *f, int sx, i dsection_v1_t ds; vec4_t dcolours[SECTHEIGHTSIZE*SECTHEIGHTSIZE]; int nothing = 0; + float waterheight = s->minh; + struct hmwater_s *w = s->water; memset(&ds, 0, sizeof(ds)); memset(&dm, 0, sizeof(dm)); //mask off the flags which are only valid in memory - ds.flags = s->flags & ~(TSF_INTERNAL); + ds.flags = s->flags & ~(TSF_INTERNAL|TSF_HASWATER_V0); //kill the haswater flag if its entirely above any possible water anyway. - if (s->waterheight < s->minh) - ds.flags &= ~TSF_HASWATER; + if (w) + ds.flags |= TSF_HASWATER_V0; ds.flags &= ~TSF_HASCOLOURS; //recalculated Q_strncpyz(ds.texname[0], s->texname[0], sizeof(ds.texname[0])); @@ -982,8 +1497,8 @@ static void Terr_SaveV1(heightmap_t *hm, hmsection_t *s, vfsfile_t *f, int sx, i dcolours[i][0] = dcolours[i][1] = dcolours[i][2] = dcolours[i][3] = LittleFloat(1); } } - ds.waterheight = s->waterheight; - ds.holes = s->holes; + ds.waterheight = w?w->heights[4*8+4]:s->minh; + ds.holes = 0;//s->holes; ds.minh = s->minh; ds.maxh = s->maxh; ds.ents_num = s->numents; @@ -1013,6 +1528,14 @@ static void Terr_SaveV1(heightmap_t *hm, hmsection_t *s, vfsfile_t *f, int sx, i VFS_WRITE(f, ¬hing, pad); } } + +static void Terr_Save(heightmap_t *hm, hmsection_t *s, vfsfile_t *f, int sx, int sy, int ver) +{ + if (ver == 1) + Terr_SaveV1(hm, s, f, sx, sy); + else if (ver == 2) + Terr_SaveV2(hm, s, f, sx, sy); +} #endif //doesn't clear edited/dirty flags or anything @@ -1024,6 +1547,9 @@ static qboolean Terr_SaveSection(heightmap_t *hm, hmsection_t *s, int sx, int sy vfsfile_t *f; char *fname; int x, y; + int writever = mod_terrain_savever.ival; + if (!writever) + writever = SECTION_VER_DEFAULT; //if its invalid or doesn't contain all the data... if (!s || s->lightmap < 0) return true; @@ -1057,7 +1583,7 @@ static qboolean Terr_SaveSection(heightmap_t *hm, hmsection_t *s, int sx, int sy memset(&dbh, 0, sizeof(dbh)); dbh.magic = LittleLong(SECTION_MAGIC); - dbh.ver = LittleLong(SECTION_VER | 0x80000000); + dbh.ver = LittleLong(writever | 0x80000000); VFS_WRITE(f, &dbh, sizeof(dbh)); for (y = 0; y < SECTIONSPERBLOCK; y++) { @@ -1067,7 +1593,7 @@ static qboolean Terr_SaveSection(heightmap_t *hm, hmsection_t *s, int sx, int sy if (s) { dbh.offset[y*SECTIONSPERBLOCK + x] = VFS_TELL(f); - Terr_SaveV1(hm, s, f, sx+x, sy+y); + Terr_Save(hm, s, f, sx+x, sy+y, writever); s->flags &= ~TSF_EDITED; } else @@ -1095,9 +1621,9 @@ static qboolean Terr_SaveSection(heightmap_t *hm, hmsection_t *s, int sx, int sy memset(&dsh, 0, sizeof(dsh)); dsh.magic = SECTION_MAGIC; - dsh.ver = SECTION_VER; + dsh.ver = writever; VFS_WRITE(f, &dsh, sizeof(dsh)); - Terr_SaveV1(hm, s, f, sx, sy); + Terr_Save(hm, s, f, sx, sy, writever); VFS_CLOSE(f); } return true; @@ -1436,14 +1962,15 @@ void Terr_FreeModel(model_t *mod) } } #ifndef SERVERONLY -void Terr_DrawTerrainWater(heightmap_t *hm, float *mins, float *maxs, float waterz, float r, float g, float b, float a) +void Terr_DrawTerrainWater(heightmap_t *hm, float *mins, float *maxs, struct hmwater_s *w) { scenetris_t *t; int flags = BEF_NOSHADOWS; int firstv; + int y, x; //need to filter by height too, or reflections won't work properly. - if (cl_numstris && cl_stris[cl_numstris-1].shader == hm->watershader && cl_stris[cl_numstris-1].flags == flags && cl_strisvertv[cl_stris[cl_numstris-1].firstvert][2] == waterz) + if (cl_numstris && cl_stris[cl_numstris-1].shader == w->shader && cl_stris[cl_numstris-1].flags == flags && cl_strisvertv[cl_stris[cl_numstris-1].firstvert][2] == w->maxheight) { t = &cl_stris[cl_numstris-1]; } @@ -1455,7 +1982,7 @@ void Terr_DrawTerrainWater(heightmap_t *hm, float *mins, float *maxs, float wate cl_stris = BZ_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris); } t = &cl_stris[cl_numstris++]; - t->shader = hm->watershader; + t->shader = w->shader; t->flags = flags; t->firstidx = cl_numstrisidx; t->firstvert = cl_numstrisvert; @@ -1463,64 +1990,115 @@ void Terr_DrawTerrainWater(heightmap_t *hm, float *mins, float *maxs, float wate t->numidx = 0; } - if (cl_numstrisidx+12 > cl_maxstrisidx) + if (!w->simple) { - cl_maxstrisidx=cl_numstrisidx+12 + 64; - cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + float step = (maxs[0] - mins[0]) / 8; + if (cl_numstrisidx+9*9*6 > cl_maxstrisidx) + { + cl_maxstrisidx=cl_numstrisidx+12 + 9*9*6*4; + cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + if (cl_numstrisvert+9*9 > cl_maxstrisvert) + { + cl_maxstrisvert+=9*9+64; + cl_strisvertv = BZ_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = BZ_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = BZ_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + + firstv = t->numvert; + for (y = 0; y < 9; y++) + { + for (x = 0; x < 9; x++) + { + cl_strisvertv[cl_numstrisvert][0] = mins[0] + step*x; + cl_strisvertv[cl_numstrisvert][1] = mins[1] + step*y; + cl_strisvertv[cl_numstrisvert][2] = w->heights[x + y*8]; + cl_strisvertt[cl_numstrisvert][0] = cl_strisvertv[cl_numstrisvert][0]/64; + cl_strisvertt[cl_numstrisvert][1] = cl_strisvertv[cl_numstrisvert][1]/64; + Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1) + cl_numstrisvert++; + } + } + for (y = 0; y < 8; y++) + { + for (x = 0; x < 8; x++) + { + if (w->holes[y] & (1u<numidx = cl_numstrisidx - t->firstidx; + t->numvert = cl_numstrisvert - t->firstvert; } - if (cl_numstrisvert+4 > cl_maxstrisvert) + else { - cl_maxstrisvert+=64; - cl_strisvertv = BZ_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); - cl_strisvertt = BZ_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); - cl_strisvertc = BZ_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + if (cl_numstrisidx+12 > cl_maxstrisidx) + { + cl_maxstrisidx=cl_numstrisidx+12 + 64; + cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + if (cl_numstrisvert+4 > cl_maxstrisvert) + { + cl_maxstrisvert+=64; + cl_strisvertv = BZ_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = BZ_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = BZ_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + + { + VectorSet(cl_strisvertv[cl_numstrisvert], mins[0], mins[1], w->maxheight); + Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1) + Vector2Set(cl_strisvertt[cl_numstrisvert], mins[0]/64, mins[1]/64); + cl_numstrisvert++; + + VectorSet(cl_strisvertv[cl_numstrisvert], mins[0], maxs[1], w->maxheight); + Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1) + Vector2Set(cl_strisvertt[cl_numstrisvert], mins[0]/64, maxs[1]/64); + cl_numstrisvert++; + + VectorSet(cl_strisvertv[cl_numstrisvert], maxs[0], maxs[1], w->maxheight); + Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1) + Vector2Set(cl_strisvertt[cl_numstrisvert], maxs[0]/64, maxs[1]/64); + cl_numstrisvert++; + + VectorSet(cl_strisvertv[cl_numstrisvert], maxs[0], mins[1], w->maxheight); + Vector4Set(cl_strisvertc[cl_numstrisvert], 1,1,1,1) + Vector2Set(cl_strisvertt[cl_numstrisvert], maxs[0]/64, mins[1]/64); + cl_numstrisvert++; + } + + + firstv = t->numvert; + + /*build the triangles*/ + cl_strisidx[cl_numstrisidx++] = firstv + 0; + cl_strisidx[cl_numstrisidx++] = firstv + 1; + cl_strisidx[cl_numstrisidx++] = firstv + 2; + + cl_strisidx[cl_numstrisidx++] = firstv + 0; + cl_strisidx[cl_numstrisidx++] = firstv + 2; + cl_strisidx[cl_numstrisidx++] = firstv + 3; + + cl_strisidx[cl_numstrisidx++] = firstv + 3; + cl_strisidx[cl_numstrisidx++] = firstv + 2; + cl_strisidx[cl_numstrisidx++] = firstv + 1; + + cl_strisidx[cl_numstrisidx++] = firstv + 3; + cl_strisidx[cl_numstrisidx++] = firstv + 1; + cl_strisidx[cl_numstrisidx++] = firstv + 0; + + + t->numidx = cl_numstrisidx - t->firstidx; + t->numvert = cl_numstrisvert - t->firstvert; } - - { - VectorSet(cl_strisvertv[cl_numstrisvert], mins[0], mins[1], waterz); - Vector4Set(cl_strisvertc[cl_numstrisvert], r, g, b, a); - Vector2Set(cl_strisvertt[cl_numstrisvert], mins[0]/64, mins[1]/64); - cl_numstrisvert++; - - VectorSet(cl_strisvertv[cl_numstrisvert], mins[0], maxs[1], waterz); - Vector4Set(cl_strisvertc[cl_numstrisvert], r, g, b, a); - Vector2Set(cl_strisvertt[cl_numstrisvert], mins[0]/64, maxs[1]/64); - cl_numstrisvert++; - - VectorSet(cl_strisvertv[cl_numstrisvert], maxs[0], maxs[1], waterz); - Vector4Set(cl_strisvertc[cl_numstrisvert], r, g, b, a); - Vector2Set(cl_strisvertt[cl_numstrisvert], maxs[0]/64, maxs[1]/64); - cl_numstrisvert++; - - VectorSet(cl_strisvertv[cl_numstrisvert], maxs[0], mins[1], waterz); - Vector4Set(cl_strisvertc[cl_numstrisvert], r, g, b, a); - Vector2Set(cl_strisvertt[cl_numstrisvert], maxs[0]/64, mins[1]/64); - cl_numstrisvert++; - } - - - firstv = t->numvert; - - /*build the triangles*/ - cl_strisidx[cl_numstrisidx++] = firstv + 0; - cl_strisidx[cl_numstrisidx++] = firstv + 1; - cl_strisidx[cl_numstrisidx++] = firstv + 2; - - cl_strisidx[cl_numstrisidx++] = firstv + 0; - cl_strisidx[cl_numstrisidx++] = firstv + 2; - cl_strisidx[cl_numstrisidx++] = firstv + 3; - - cl_strisidx[cl_numstrisidx++] = firstv + 3; - cl_strisidx[cl_numstrisidx++] = firstv + 2; - cl_strisidx[cl_numstrisidx++] = firstv + 1; - - cl_strisidx[cl_numstrisidx++] = firstv + 3; - cl_strisidx[cl_numstrisidx++] = firstv + 1; - cl_strisidx[cl_numstrisidx++] = firstv + 0; - - - t->numidx = cl_numstrisidx - t->firstidx; - t->numvert = cl_numstrisvert - t->firstvert; } static void Terr_RebuildMesh(model_t *model, hmsection_t *s, int x, int y) @@ -1558,12 +2136,14 @@ static void Terr_RebuildMesh(model_t *model, hmsection_t *s, int x, int y) for (vx = 0; vx < SECTHEIGHTSIZE-1; vx++) { float st[2], inst[2]; -#if SECTHEIGHTSIZE >= 4 +#if SECTHEIGHTSIZE == 17 int holebit; + int holerow; //skip generation of the mesh above holes - holebit = 1u<<(vx/(SECTHEIGHTSIZE>>2) + (vy/(SECTHEIGHTSIZE>>2))*4); - if (s->holes & holebit) + holerow = vy/(SECTHEIGHTSIZE>>1); + holebit = 1u<<(vx/(SECTHEIGHTSIZE>>1)); + if (s->holes[holerow] & holebit) continue; #endif @@ -1784,12 +2364,14 @@ static void Terr_RebuildMesh(model_t *model, hmsection_t *s, int x, int y) float d1,d2; #endif - #if SECTHEIGHTSIZE >= 4 + #if SECTHEIGHTSIZE == 17 + int holerow; int holebit; //skip generation of the mesh above holes - holebit = 1u<<(vx/(SECTHEIGHTSIZE>>2) + (vy/(SECTHEIGHTSIZE>>2))*4); - if (s->holes & holebit) + holerow = vy/(SECTHEIGHTSIZE>>1); + holebit = 1u<<(vx/(SECTHEIGHTSIZE>>1)); + if (s->holes[holerow] & holebit) continue; #endif v = vx + vy*(SECTHEIGHTSIZE); @@ -1835,9 +2417,7 @@ static void Terr_RebuildMesh(model_t *model, hmsection_t *s, int x, int y) mins[2] = s->minh; maxs[0] = (x+1-CHUNKBIAS) * hm->sectionsize; maxs[1] = (y+1-CHUNKBIAS) * hm->sectionsize; - maxs[2] = s->maxh; - if (s->flags & TSF_HASWATER) - maxs[2] = max(maxs[2], s->waterheight); + maxs[2] = s->maxh_cull; model->funcs.FindTouchedLeafs(model, &s->pvscache, mins, maxs); } @@ -1916,6 +2496,7 @@ void Terr_DrawInBounds(struct tdibctx *ctx, int x, int y, int w, int h) { vec3_t mins, maxs; hmsection_t *s; + struct hmwater_s *wa; int i; batch_t *b; heightmap_t *hm = ctx->hm; @@ -1968,9 +2549,12 @@ void Terr_DrawInBounds(struct tdibctx *ctx, int x, int y, int w, int h) //chuck out any batches for models in this section for (i = 0; i < s->numents; i++) { + vec3_t dist; + float a; model_t *model = s->ents[i].model; if (!model) continue; + if (model->needload == 1) { if (hm->beinglazy) @@ -1980,6 +2564,21 @@ void Terr_DrawInBounds(struct tdibctx *ctx, int x, int y, int w, int h) } if (model->needload) continue; + + VectorSubtract(s->ents[i].origin, r_origin, dist); + a = VectorLength(dist); + a = 1024 - a + model->radius*16; + a /= model->radius; + if (a < 0) + continue; + if (a >= 1) + { + a = 1; + s->ents[i].flags &= ~Q2RF_TRANSLUCENT; + } + else + s->ents[i].flags |= Q2RF_TRANSLUCENT; + s->ents[i].shaderRGBAf[3] = a; switch(model->type) { case mod_alias: @@ -1991,13 +2590,13 @@ void Terr_DrawInBounds(struct tdibctx *ctx, int x, int y, int w, int h) } } - if (s->flags & TSF_HASWATER) + for (wa = s->water; wa; wa = wa->next) { - mins[2] = s->waterheight; - maxs[2] = s->waterheight; + mins[2] = wa->minheight; + maxs[2] = wa->maxheight; if (!R_CullBox(mins, maxs)) { - Terr_DrawTerrainWater(hm, mins, maxs, s->waterheight, 1, 1, 1, 1); + Terr_DrawTerrainWater(hm, mins, maxs, wa); } } @@ -2258,8 +2857,11 @@ unsigned int Heightmap_PointContentsHM(heightmap_t *hm, float clipmipsz, vec3_t float x, y; float z, tz; int sx, sy; + unsigned int holerow; unsigned int holebit; hmsection_t *s; + struct hmwater_s *w; + unsigned int contents; const float wbias = CHUNKBIAS * hm->sectionsize; sx = (org[0]+wbias)/hm->sectionsize; @@ -2284,8 +2886,9 @@ unsigned int Heightmap_PointContentsHM(heightmap_t *hm, float clipmipsz, vec3_t sx = x; x-=sx; sy = y; y-=sy; - holebit = 1u<<(sx/(SECTHEIGHTSIZE>>2) + (sy/(SECTHEIGHTSIZE>>2))*4); - if (s->holes & (1u<>1); + holebit = 1u<<(sx/(SECTHEIGHTSIZE>>1)); + if (s->holes[holerow] & (1u<flags & TSF_HASWATER) - if (z < s->waterheight) - return FTECONTENTS_WATER; - return FTECONTENTS_EMPTY; + + contents = FTECONTENTS_EMPTY; + for (w = s->water; w; w = w->next) + { + if (w->holes[holerow] & (1u<maxheight) //FIXME + contents |= w->contentmask; + } + return contents; } unsigned int Heightmap_PointContents(model_t *model, vec3_t axis[3], vec3_t org) @@ -2503,6 +3112,7 @@ static void Heightmap_Trace_Square(hmtrace_t *tr, int tx, int ty) #endif int sx, sy; hmsection_t *s; + unsigned int holerow; unsigned int holebit; sx = tx/(SECTHEIGHTSIZE-1); @@ -2563,8 +3173,9 @@ static void Heightmap_Trace_Square(hmtrace_t *tr, int tx, int ty) tx = tx % (SECTHEIGHTSIZE-1); ty = ty % (SECTHEIGHTSIZE-1); - holebit = 1u<<(tx/(SECTHEIGHTSIZE>>2) + (ty/(SECTHEIGHTSIZE>>2))*4); - if (s->holes & holebit) + holerow = ty/(SECTHEIGHTSIZE>>1); + holebit = 1u<<(tx/(SECTHEIGHTSIZE>>1)); + if (s->holes[holerow] & holebit) return; //no collision with holes switch(tr->hm->mode) @@ -2970,15 +3581,16 @@ static void ted_dorelight(heightmap_t *hm) } static void ted_sethole(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w) { + unsigned int row = idx>>4; unsigned int bit; unsigned int mask; - mask = 1u<= 1) bit = mask; else bit = 0; s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED; - s->holes = (s->holes & ~mask) | bit; + s->holes[row] = (s->holes[row] & ~mask) | bit; } static void ted_heighttally(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w) { @@ -3009,6 +3621,28 @@ static void ted_heightset(void *ctx, hmsection_t *s, int idx, float wx, float wy s->heights[idx] = *(float*)ctx; } +static void ted_waterset(void *ctx, hmsection_t *s, int idx, float wx, float wy, float strength) +{ + struct hmwater_s *w = s->water; + if (!w) + w = Terr_GenerateWater(s, *(float*)ctx); + s->flags |= TSF_NOTIFY|TSF_DIRTY|TSF_EDITED; + + //FIXME: water doesn't render properly. don't let people make dodgy water regions because they can't see it. + //this is temp code. + //for (idx = 0; idx < 9*9; idx++) + //w->heights[idx] = *(float*)ctx; + //end fixme + + w->heights[idx] = *(float*)ctx; + if (w->minheight > w->heights[idx]) + w->minheight = w->heights[idx]; + if (w->maxheight < w->heights[idx]) + w->maxheight = w->heights[idx]; + + //FIXME: what about holes? +} + static void ted_mixconcentrate(void *ctx, hmsection_t *s, int idx, float wx, float wy, float w) { unsigned char *lm = ted_getlightmap(s, idx); @@ -3363,7 +3997,10 @@ void QCBUILTIN PF_terrain_edit(pubprogfuncs_t *prinst, struct globalvars_s *pr_g return; ted_sethole(&quant, s, (x&3) + (y&3)*4, x/4, y/4, 0); } - */ ted_itterate(hm, tid_linear, pos, radius, 1, 4, ted_sethole, &quant); + */ + pos[0] -= 0.5 * hm->sectionsize / 8; + pos[1] -= 0.5 * hm->sectionsize / 8; + ted_itterate(hm, tid_linear, pos, radius, 1, 8, ted_sethole, &quant); break; case ter_height_set: ted_itterate(hm, tid_linear, pos, radius, 1, SECTHEIGHTSIZE, ted_heightset, &quant); @@ -3396,20 +4033,7 @@ void QCBUILTIN PF_terrain_edit(pubprogfuncs_t *prinst, struct globalvars_s *pr_g ted_itterate(hm, tid_exponential, pos, radius, 1, SECTHEIGHTSIZE, ted_heightsmooth, &tally); break; case ter_water_set: - { - int x, y; - hmsection_t *s; - x = pos[0] / hm->sectionsize; - y = pos[1] / hm->sectionsize; - x = bound(hm->firstsegx, x, hm->maxsegy-1); - y = bound(hm->firstsegy, y, hm->maxsegy-1); - - s = Terr_GetSection(hm, x, y, TGS_LOAD); - if (!s) - return; - s->flags |= TSF_HASWATER|TSF_EDITED; - s->waterheight = quant; - } + ted_itterate(hm, tid_linear, pos, radius, 1, 9, ted_waterset, &quant); break; case ter_lower: quant *= -1; @@ -3603,6 +4227,7 @@ void Terr_FinishTerrain(heightmap_t *hm, char *shadername, char *skyname) #ifndef SERVERONLY if (qrenderer != QR_NONE) { + Q_strncpyz(hm->watershadername, va("water/%s", hm->path), sizeof(hm->watershadername)); if (skyname) hm->skyshader = R_RegisterCustom(va("skybox_%s", skyname), SUF_NONE, Shader_DefaultSkybox, NULL); else @@ -3643,6 +4268,9 @@ void Terr_FinishTerrain(heightmap_t *hm, char *shadername, char *skyname) "{\n" "map $shadowmap\n" "}\n" + "{\n" + "map $lightcubemap\n" + "}\n" //woo, one glsl to rule them all "program terrain#RTLIGHT\n" "}\n" @@ -3685,23 +4313,6 @@ void Terr_FinishTerrain(heightmap_t *hm, char *shadername, char *skyname) ); break; } - - - hm->watershader = R_RegisterCustom ("*water1", SUF_LIGHTMAP, NULL, NULL); - if (!hm->watershader) - hm->watershader = R_RegisterCustom ("warp/terrain", SUF_NONE, Shader_DefaultBSPQ2, NULL); - if (!TEXVALID(hm->watershader->defaulttextures.base)) - hm->watershader->defaulttextures.base = R_LoadHiResTexture("terwater", NULL, IF_NOALPHA); - if (!TEXVALID(hm->watershader->defaulttextures.bump)) - hm->watershader->defaulttextures.bump = R_LoadBumpmapTexture("terwater_bump", NULL); - if (!TEXVALID(hm->watershader->defaulttextures.bump)) - { - unsigned char dat[64*64] = {0}; - int i; - for (i = 0; i < 64*64; i++) - dat[i] = rand()&15; - hm->watershader->defaulttextures.bump = R_LoadTexture8BumpPal("terwater_bump", 64, 64, dat, 0); - } } #endif } @@ -3716,7 +4327,7 @@ qboolean QDECL Terr_LoadTerrainModel (model_t *mod, void *buffer) COM_FileBase(mod->name, shadername, sizeof(shadername)); strcpy(shadername, "terrainshader"); - strcpy(skyname, "night"); + strcpy(skyname, "sky1"); buffer = COM_Parse(buffer); if (strcmp(com_token, "terrain")) @@ -3960,8 +4571,13 @@ void Mod_Terrain_Reload_f(void) void Terr_Init(void) { +#ifdef M2 + GL_M2_Init(); +#endif + Cvar_Register(&mod_terrain_networked, "Terrain"); Cvar_Register(&mod_terrain_defaulttexture, "Terrain"); + Cvar_Register(&mod_terrain_savever, "Terrain"); Cmd_AddCommand("mod_terrain_create", Mod_Terrain_Create_f); Cmd_AddCommand("mod_terrain_reload", Mod_Terrain_Reload_f); #ifndef SERVERONLY diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c index ecf636ba..80e4f4d6 100644 --- a/engine/gl/gl_model.c +++ b/engine/gl/gl_model.c @@ -654,6 +654,8 @@ model_t *Mod_FindName (char *name) { if (mod_numknown == MAX_MOD_KNOWN) Sys_Error ("mod_numknown == MAX_MOD_KNOWN"); + if (strlen(name) >= sizeof(mod->name)) + Sys_Error ("model name is too long: %s", name); memset(mod, 0, sizeof(model_t)); //clear the old model as the renderers use the same globals strcpy (mod->name, name); mod->needload = true; diff --git a/engine/gl/gl_rlight.c b/engine/gl/gl_rlight.c index 91786e81..f7bcbf0c 100644 --- a/engine/gl/gl_rlight.c +++ b/engine/gl/gl_rlight.c @@ -860,6 +860,7 @@ void R_SaveRTLights_f(void) vfsfile_t *f; unsigned int i; char fname[MAX_QPATH]; + char sysname[MAX_OSPATH]; vec3_t ang; COM_StripExtension(cl.worldmodel->name, fname, sizeof(fname)); strncat(fname, ".rtlights", MAX_QPATH-1); @@ -896,7 +897,9 @@ void R_SaveRTLights_f(void) )); } VFS_CLOSE(f); - Con_Printf("rtlights saved to %s\n", fname); + + FS_NativePath(fname, FS_GAMEONLY, sysname, sizeof(sysname)); + Con_Printf("rtlights saved to %s\n", sysname); } void R_ReloadRTLights_f(void) diff --git a/engine/gl/gl_rmain.c b/engine/gl/gl_rmain.c index 02c37cca..214948ea 100644 --- a/engine/gl/gl_rmain.c +++ b/engine/gl/gl_rmain.c @@ -383,21 +383,21 @@ void R_SetupGL (float stereooffset) // x = r_refdef.vrect.x * (int)vid.pixelwidth/(int)vid.width; x2 = (r_refdef.vrect.x + r_refdef.vrect.width) * (int)vid.pixelwidth/(int)vid.width; - y = (vid.height-r_refdef.vrect.y) * (int)vid.pixelheight/(int)vid.height; - y2 = ((int)vid.height - (r_refdef.vrect.y + r_refdef.vrect.height)) * (int)vid.pixelheight/(int)vid.height; + y = (r_refdef.vrect.y) * (int)vid.pixelheight/(int)vid.height; + y2 = (r_refdef.vrect.y + r_refdef.vrect.height) * (int)vid.pixelheight/(int)vid.height; // fudge around because of frac screen scale if (x > 0) x--; if (x2 < vid.pixelwidth) x2++; - if (y2 < 0) - y2--; - if (y < vid.pixelheight) - y++; + if (y2 < vid.pixelheight) + y2++; + if (y > 0) + y--; w = x2 - x; - h = y - y2; + h = y2 - y; fov_x = r_refdef.fov_x;//+sin(cl.time)*5; fov_y = r_refdef.fov_y;//-sin(cl.time+1)*5; @@ -413,8 +413,9 @@ void R_SetupGL (float stereooffset) r_refdef.pxrect.y = y; r_refdef.pxrect.width = w; r_refdef.pxrect.height = h; + r_refdef.pxrect.maxheight = vid.pixelheight; - qglViewport (x, y2, w, h); + GL_ViewportUpdate(); if (r_waterwarp.value<0 && (r_viewcontents & FTECONTENTS_FLUID)) { @@ -1140,7 +1141,7 @@ qboolean R_RenderScene_Cubemap(void) vec3_t saveang; vrect_t vrect; - vrect_t prect; + pxrect_t prect; shader_t *shader; int facemask; @@ -1274,27 +1275,28 @@ qboolean R_RenderScene_Cubemap(void) } r_refdef.vrect = vrect; + r_refdef.pxrect = prect; - qglViewport (prect.x, vid.pixelheight - (prect.y+prect.height), prect.width, prect.height); - + //GL_ViewportUpdate(); + GL_Set2D(false); // go 2d - qglMatrixMode(GL_PROJECTION); +/* qglMatrixMode(GL_PROJECTION); qglPushMatrix(); qglLoadIdentity (); qglOrtho (0, vid.width, vid.height, 0, -99999, 99999); qglMatrixMode(GL_MODELVIEW); qglPushMatrix(); qglLoadIdentity (); - +*/ // draw it through the shader R2D_Image(0, 0, vid.width, vid.height, -0.5, 0.5, 0.5, -0.5, shader); //revert the matricies - qglMatrixMode(GL_PROJECTION); +/* qglMatrixMode(GL_PROJECTION); qglPopMatrix(); qglMatrixMode(GL_MODELVIEW); qglPopMatrix(); - +*/ return true; } @@ -1318,7 +1320,7 @@ void GLR_RenderView (void) } if (!(r_refdef.flags & Q2RDF_NOWORLDMODEL)) - if (!r_worldentity.model || !cl.worldmodel) + if (!r_worldentity.model || r_worldentity.model->needload || !cl.worldmodel) { GL_DoSwap(); diff --git a/engine/gl/gl_screen.c b/engine/gl/gl_screen.c index 86562fef..e47ed896 100644 --- a/engine/gl/gl_screen.c +++ b/engine/gl/gl_screen.c @@ -67,6 +67,8 @@ void GLSCR_UpdateScreen (void) qboolean noworld; RSpeedMark(); + r_refdef.pxrect.maxheight = vid.pixelheight; + vid.numpages = 2 + vid_triplebuffer.value; if (scr_disabled_for_loading) diff --git a/engine/gl/gl_shader.c b/engine/gl/gl_shader.c index f22f10c4..702b7425 100644 --- a/engine/gl/gl_shader.c +++ b/engine/gl/gl_shader.c @@ -830,6 +830,8 @@ static void Shader_FogParms ( shader_t *shader, shaderpass_t *pass, char **ptr ) shader->fog_dist = 128.0f; } shader->fog_dist = 1.0f / shader->fog_dist; + + shader->flags |= SHADER_NODLIGHT|SHADER_NOSHADOWS; } static void Shader_SurfaceParm ( shader_t *shader, shaderpass_t *pass, char **ptr ) @@ -1143,10 +1145,10 @@ static qboolean Shader_LoadPermutations(char *name, program_t *prog, char *scrip } typedef struct sgeneric_s { + program_t prog; struct sgeneric_s *next; char *name; qboolean failed; - program_t prog; } sgeneric_t; static sgeneric_t *sgenerics; struct sbuiltin_s @@ -1435,6 +1437,10 @@ void Shader_UnloadProg(program_t *prog) // D3DShader_DeleteProgram(&prog->handle[p].hlsl); } } +#endif +#ifdef D3D11QUAKE + if (qrenderer == QR_DIRECT3D11) + D3D11Shader_DeleteProgram(prog); #endif free(prog); } @@ -1449,7 +1455,7 @@ static void Shader_FlushGenerics(void) if (g->prog.refs == 1) { g->prog.refs--; - free(g); + Shader_UnloadProg(&g->prog); } else Con_Printf("generic shader still used\n"); @@ -1963,10 +1969,14 @@ static void Shader_ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr { Shader_SLProgramName(shader,pass,ptr,qrenderer); } -static void Shader_HLSLProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) +static void Shader_HLSL9ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) { Shader_SLProgramName(shader,pass,ptr,QR_DIRECT3D9); } +static void Shader_HLSL11ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) +{ + Shader_SLProgramName(shader,pass,ptr,QR_DIRECT3D11); +} static void Shader_ProgramParam ( shader_t *shader, shaderpass_t *pass, char **ptr ) { @@ -2242,7 +2252,8 @@ static shaderkey_t shaderkeys[] = {"lpp_light", Shader_Prelight}, {"glslprogram", Shader_GLSLProgramName}, {"program", Shader_ProgramName}, //gl or d3d - {"hlslprogram", Shader_HLSLProgramName}, //for d3d + {"hlslprogram", Shader_HLSL9ProgramName}, //for d3d + {"hlsl11program", Shader_HLSL11ProgramName}, //for d3d {"param", Shader_ProgramParam}, //legacy {"bemode", Shader_BEMode}, @@ -2334,6 +2345,7 @@ static qboolean Shaderpass_MapGen (shader_t *shader, shaderpass_t *pass, char *t { pass->texgen = T_GEN_SHADOWMAP; pass->tcgen = TC_GEN_BASE; //FIXME: moo! + pass->flags |= SHADER_PASS_DEPTHCMP; } else if (!Q_stricmp (tname, "$lightcubemap")) { @@ -4581,6 +4593,10 @@ char *Shader_DefaultBSPWater(char *shortname) } } +void Shader_DefaultWaterShader(char *shortname, shader_t *s, const void *args) +{ + Shader_DefaultScript(shortname, s, Shader_DefaultBSPWater(shortname)); +} void Shader_DefaultBSPQ2(char *shortname, shader_t *s, const void *args) { if (!strncmp(shortname, "sky/", 4)) diff --git a/engine/gl/gl_shadow.c b/engine/gl/gl_shadow.c index 258c8b72..06b68812 100644 --- a/engine/gl/gl_shadow.c +++ b/engine/gl/gl_shadow.c @@ -25,6 +25,12 @@ extern LPDIRECT3DDEVICE9 pD3DDev9; void D3D9BE_Cull(unsigned int sflags); void D3D9BE_RenderShadowBuffer(unsigned int numverts, IDirect3DVertexBuffer9 *vbuf, unsigned int numindicies, IDirect3DIndexBuffer9 *ibuf); #endif +#ifdef D3D11QUAKE +void D3D11BE_GenerateShadowBuffer(void **vbuf, vecV_t *verts, int numverts, void **ibuf, index_t *indicies, int numindicies); +void D3D11BE_RenderShadowBuffer(unsigned int numverts, void *vbuf, unsigned int numindicies, void *ibuf); +void D3D11_DestroyShadowBuffer(void *vbuf, void *ibuf); +void D3D11BE_DoneShadows(void); +#endif void GLBE_RenderShadowBuffer(unsigned int numverts, int vbo, vecV_t *verts, unsigned numindicies, int ibo, index_t *indicies); static void SHM_Shutdown(void); @@ -149,8 +155,12 @@ typedef struct shadowmesh_s GLuint vebo[2]; #endif #ifdef D3D9QUAKE - IDirect3DVertexBuffer9 *d3d_vbuffer; - IDirect3DIndexBuffer9 *d3d_ibuffer; + IDirect3DVertexBuffer9 *d3d9_vbuffer; + IDirect3DIndexBuffer9 *d3d9_ibuffer; +#endif +#ifdef D3D11QUAKE + void *d3d11_vbuffer; + void *d3d11_ibuffer; #endif } shadowmesh_t; @@ -356,28 +366,34 @@ static void SH_FreeShadowMesh_(shadowmesh_t *sm) { case QR_NONE: case QR_SOFTWARE: - case QR_DIRECT3D11: default: break; - case QR_OPENGL: #ifdef GLQUAKE + case QR_OPENGL: if (qglDeleteBuffersARB) qglDeleteBuffersARB(2, sm->vebo); sm->vebo[0] = 0; sm->vebo[1] = 0; -#endif break; - case QR_DIRECT3D9: +#endif #ifdef D3D9QUAKE - if (sm->d3d_ibuffer) - IDirect3DIndexBuffer9_Release(sm->d3d_ibuffer); - sm->d3d_ibuffer = NULL; - if (sm->d3d_vbuffer) - IDirect3DVertexBuffer9_Release(sm->d3d_vbuffer); - sm->d3d_vbuffer = NULL; -#endif + case QR_DIRECT3D9: + if (sm->d3d9_ibuffer) + IDirect3DIndexBuffer9_Release(sm->d3d9_ibuffer); + sm->d3d9_ibuffer = NULL; + if (sm->d3d9_vbuffer) + IDirect3DVertexBuffer9_Release(sm->d3d9_vbuffer); + sm->d3d9_vbuffer = NULL; break; +#endif +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + D3D11_DestroyShadowBuffer(sm->d3d11_vbuffer, sm->d3d11_ibuffer); + sm->d3d11_vbuffer = NULL; + sm->d3d11_ibuffer = NULL; + break; +#endif } } void SH_FreeShadowMesh(shadowmesh_t *sm) @@ -501,12 +517,13 @@ static struct shadowmesh_s *SHM_FinishShadowMesh(dlight_t *dl) { case QR_NONE: case QR_SOFTWARE: - case QR_DIRECT3D11: default: break; case QR_OPENGL: #ifdef GLQUAKE + if (!qglGenBuffersARB) + return sh_shmesh; qglGenBuffersARB(2, sh_shmesh->vebo); GL_DeselectVAO(); @@ -522,19 +539,24 @@ static struct shadowmesh_s *SHM_FinishShadowMesh(dlight_t *dl) if (sh_shmesh->numindicies && sh_shmesh->numverts) { void *map; - IDirect3DDevice9_CreateIndexBuffer(pD3DDev9, sizeof(index_t) * sh_shmesh->numindicies, 0, D3DFMT_QINDEX, D3DPOOL_MANAGED, &sh_shmesh->d3d_ibuffer, NULL); - IDirect3DIndexBuffer9_Lock(sh_shmesh->d3d_ibuffer, 0, sizeof(index_t) * sh_shmesh->numindicies, &map, D3DLOCK_DISCARD); + IDirect3DDevice9_CreateIndexBuffer(pD3DDev9, sizeof(index_t) * sh_shmesh->numindicies, 0, D3DFMT_QINDEX, D3DPOOL_MANAGED, &sh_shmesh->d3d9_ibuffer, NULL); + IDirect3DIndexBuffer9_Lock(sh_shmesh->d3d9_ibuffer, 0, sizeof(index_t) * sh_shmesh->numindicies, &map, D3DLOCK_DISCARD); memcpy(map, sh_shmesh->indicies, sizeof(index_t) * sh_shmesh->numindicies); - IDirect3DIndexBuffer9_Unlock(sh_shmesh->d3d_ibuffer); + IDirect3DIndexBuffer9_Unlock(sh_shmesh->d3d9_ibuffer); - IDirect3DDevice9_CreateVertexBuffer(pD3DDev9, sizeof(vecV_t) * sh_shmesh->numverts, D3DUSAGE_WRITEONLY, 0, D3DPOOL_MANAGED, &sh_shmesh->d3d_vbuffer, NULL); - IDirect3DVertexBuffer9_Lock(sh_shmesh->d3d_vbuffer, 0, sizeof(vecV_t) * sh_shmesh->numverts, &map, D3DLOCK_DISCARD); + IDirect3DDevice9_CreateVertexBuffer(pD3DDev9, sizeof(vecV_t) * sh_shmesh->numverts, D3DUSAGE_WRITEONLY, 0, D3DPOOL_MANAGED, &sh_shmesh->d3d9_vbuffer, NULL); + IDirect3DVertexBuffer9_Lock(sh_shmesh->d3d9_vbuffer, 0, sizeof(vecV_t) * sh_shmesh->numverts, &map, D3DLOCK_DISCARD); memcpy(map, sh_shmesh->verts, sizeof(vecV_t) * sh_shmesh->numverts); - IDirect3DVertexBuffer9_Unlock(sh_shmesh->d3d_vbuffer); + IDirect3DVertexBuffer9_Unlock(sh_shmesh->d3d9_vbuffer); } #endif break; +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + D3D11BE_GenerateShadowBuffer(&sh_shmesh->d3d11_vbuffer, sh_shmesh->verts, sh_shmesh->numverts, &sh_shmesh->d3d11_ibuffer, sh_shmesh->indicies, sh_shmesh->numindicies); + break; +#endif } Z_Free(sh_shmesh->verts); @@ -2059,6 +2081,9 @@ void GL_EndRenderBuffer_DepthOnly(int restorefbo, texid_t depthtexture, int texs qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize); } } +#endif + +void D3D11BE_BeginShadowmapFace(void); //determine the 5 bounding points of a shadowmap light projection side //needs to match Sh_GenShadowFace @@ -2098,6 +2123,11 @@ static void Sh_GenShadowFace(dlight_t *l, shadowmesh_t *smesh, int face, int sms texture_t *tex; int tno; +/* if (face >= 3) + face -= 3; + else + face += 3; +*/ switch(face) { case 0: @@ -2155,21 +2185,21 @@ static void Sh_GenShadowFace(dlight_t *l, shadowmesh_t *smesh, int face, int sms if (l->fov) { - r_refdef.pxrect.x = 0; + r_refdef.pxrect.x = (SHADOWMAP_SIZE-smsize)/2; r_refdef.pxrect.width = smsize; r_refdef.pxrect.height = smsize; - r_refdef.pxrect.y = 0; + r_refdef.pxrect.y = (SHADOWMAP_SIZE-smsize)/2; + r_refdef.pxrect.maxheight = SHADOWMAP_SIZE; } else { r_refdef.pxrect.x = (face%3 * SHADOWMAP_SIZE) + (SHADOWMAP_SIZE-smsize)/2; r_refdef.pxrect.width = smsize; r_refdef.pxrect.height = smsize; - r_refdef.pxrect.y = r_refdef.pxrect.height - (((face>=3)*SHADOWMAP_SIZE) + (SHADOWMAP_SIZE-smsize)/2); + r_refdef.pxrect.y = (((face<3)*SHADOWMAP_SIZE) + (SHADOWMAP_SIZE-smsize)/2); + r_refdef.pxrect.maxheight = SHADOWMAP_SIZE*2; } - qglViewport (r_refdef.pxrect.x, r_refdef.pxrect.height-r_refdef.pxrect.y, r_refdef.pxrect.width, r_refdef.pxrect.height); - R_SetFrustum(proj, r_refdef.m_view); #ifdef DBG_COLOURNOTDEPTH @@ -2180,16 +2210,25 @@ static void Sh_GenShadowFace(dlight_t *l, shadowmesh_t *smesh, int face, int sms BE_SelectEntity(&r_worldentity); - GL_CullFace(SHADER_CULL_FRONT); + switch(qrenderer) + { #ifdef GLQUAKE - if (qrenderer == QR_OPENGL) - { + case QR_OPENGL: + GL_ViewportUpdate(); + GL_CullFace(SHADER_CULL_FRONT); GLBE_RenderShadowBuffer(smesh->numverts, smesh->vebo[0], smesh->verts, smesh->numindicies, smesh->vebo[1], smesh->indicies); - } - else + break; #endif - { +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + //d3d render targets are upside down + r_refdef.pxrect.y = r_refdef.pxrect.maxheight -(r_refdef.pxrect.y+r_refdef.pxrect.height); + D3D11BE_BeginShadowmapFace(); + D3D11BE_RenderShadowBuffer(smesh->numverts, smesh->d3d11_vbuffer, smesh->numindicies, smesh->d3d11_ibuffer); + break; +#endif + default: //FIXME: should be able to merge batches between textures+lightmaps. for (tno = 0; tno < smesh->numbatches; tno++) { @@ -2200,6 +2239,7 @@ static void Sh_GenShadowFace(dlight_t *l, shadowmesh_t *smesh, int face, int sms continue; BE_DrawMesh_List(tex->shader, smesh->batches[tno].count, smesh->batches[tno].s, cl.worldmodel->shadowbatches[tno].vbo, &tex->shader->defaulttextures, 0); } + break; } //fixme: this walks through the entity lists up to 6 times per frame. @@ -2207,16 +2247,21 @@ static void Sh_GenShadowFace(dlight_t *l, shadowmesh_t *smesh, int face, int sms { default: break; - case QR_OPENGL: #ifdef GLQUAKE + case QR_OPENGL: GLBE_BaseEntTextures(); -#endif break; - case QR_DIRECT3D9: +#endif #ifdef D3D9QUAKE + case QR_DIRECT3D9: D3D9BE_BaseEntTextures(); -#endif break; +#endif +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + D3D11BE_BaseEntTextures(); + break; +#endif } /* @@ -2245,12 +2290,17 @@ static void Sh_GenShadowFace(dlight_t *l, shadowmesh_t *smesh, int face, int sms */ } +void D3D11_BeginShadowMap(int id, int w, int h); +void D3D11_EndShadowMap(void); + +void D3D11BE_SetupForShadowMap(dlight_t *dl, qboolean isspot, int texwidth, int texheight, float shadowscale); + qboolean Sh_GenShadowMap (dlight_t *l, qbyte *lvis, int smsize) { - int restorefbo; + int restorefbo = 0; int f; float oproj[16], oview[16]; - vrect_t oprect; + pxrect_t oprect; shadowmesh_t *smesh; int isspot = (l->fov != 0); extern cvar_t r_shadow_shadowmapping_precision; @@ -2299,69 +2349,83 @@ qboolean Sh_GenShadowMap (dlight_t *l, qbyte *lvis, int smsize) if (!sidevisible) return false; - if (!TEXVALID(shadowmap[isspot])) - { - if (isspot) - { - shadowmap[isspot] = GL_AllocNewTexture("***shadowmap2dspot***", SHADOWMAP_SIZE, SHADOWMAP_SIZE, 0); - GL_MTBind(0, GL_TEXTURE_2D, shadowmap[isspot]); -#ifdef DBG_COLOURNOTDEPTH - qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SHADOWMAP_SIZE, SHADOWMAP_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); -#else - qglTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16_ARB, SHADOWMAP_SIZE, SHADOWMAP_SIZE, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); -#endif - } - else - { - shadowmap[isspot] = GL_AllocNewTexture("***shadowmap2dcube***", SHADOWMAP_SIZE*3, SHADOWMAP_SIZE*2, 0); - GL_MTBind(0, GL_TEXTURE_2D, shadowmap[isspot]); -#ifdef DBG_COLOURNOTDEPTH - qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SHADOWMAP_SIZE*3, SHADOWMAP_SIZE*2, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); -#else - qglTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16_ARB, SHADOWMAP_SIZE*3, SHADOWMAP_SIZE*2, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); -#endif - } - - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -#if 1//def DBG_COLOURNOTDEPTH - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); -#else - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#endif - - //in case we're using shadow samplers - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL); - qglTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE); - } - - smesh = SHM_BuildShadowMesh(l, lvis, NULL, SMT_SHADOWMAP); - - /*set framebuffer*/ - GL_BeginRenderBuffer_DepthOnly(shadowmap[isspot]); - restorefbo = GLBE_SetupForShadowMap(shadowmap[isspot], isspot?smsize:smsize*3, isspot?smsize:smsize*2, (smsize-4) / (float)SHADOWMAP_SIZE); - - BE_Scissor(NULL); - qglViewport(0, 0, smsize*3, smsize*2); - qglClear (GL_DEPTH_BUFFER_BIT); -#ifdef DBG_COLOURNOTDEPTH - qglClearColor(0,1,0,1); - qglClear (GL_COLOR_BUFFER_BIT); -#endif - memcpy(oproj, r_refdef.m_projection, sizeof(oproj)); memcpy(oview, r_refdef.m_view, sizeof(oview)); oprect = r_refdef.pxrect; + smesh = SHM_BuildShadowMesh(l, lvis, NULL, SMT_SHADOWMAP); Matrix4x4_CM_Projection_Far(r_refdef.m_projection, l->fov?l->fov:90, l->fov?l->fov:90, r_shadow_shadowmapping_nearclip.value, l->radius); - if (!gl_config.nofixedfunc) + + switch(qrenderer) { - qglMatrixMode(GL_PROJECTION); - qglLoadMatrixf(r_refdef.m_projection); - qglMatrixMode(GL_MODELVIEW); +#ifdef GLQUAKE + case QR_OPENGL: + if (!TEXVALID(shadowmap[isspot])) + { + if (isspot) + { + shadowmap[isspot] = GL_AllocNewTexture("***shadowmap2dspot***", SHADOWMAP_SIZE, SHADOWMAP_SIZE, 0); + GL_MTBind(0, GL_TEXTURE_2D, shadowmap[isspot]); + #ifdef DBG_COLOURNOTDEPTH + qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SHADOWMAP_SIZE, SHADOWMAP_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + #else + qglTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16_ARB, SHADOWMAP_SIZE, SHADOWMAP_SIZE, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); + #endif + } + else + { + shadowmap[isspot] = GL_AllocNewTexture("***shadowmap2dcube***", SHADOWMAP_SIZE*3, SHADOWMAP_SIZE*2, 0); + GL_MTBind(0, GL_TEXTURE_2D, shadowmap[isspot]); + #ifdef DBG_COLOURNOTDEPTH + qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SHADOWMAP_SIZE*3, SHADOWMAP_SIZE*2, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + #else + qglTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16_ARB, SHADOWMAP_SIZE*3, SHADOWMAP_SIZE*2, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); + #endif + } + + qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + #if 1//def DBG_COLOURNOTDEPTH + qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + #else + qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + #endif + + //in case we're using shadow samplers + qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); + qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL); + qglTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE); + } + + /*set framebuffer*/ + GL_BeginRenderBuffer_DepthOnly(shadowmap[isspot]); + restorefbo = GLBE_SetupForShadowMap(shadowmap[isspot], isspot?smsize:smsize*3, isspot?smsize:smsize*2, (smsize-4) / (float)SHADOWMAP_SIZE); + + BE_Scissor(NULL); + qglViewport(0, 0, smsize*3, smsize*2); + qglClear (GL_DEPTH_BUFFER_BIT); +#ifdef DBG_COLOURNOTDEPTH + qglClearColor(0,1,0,1); + qglClear (GL_COLOR_BUFFER_BIT); +#endif + + if (!gl_config.nofixedfunc) + { + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf(r_refdef.m_projection); + qglMatrixMode(GL_MODELVIEW); + } + break; +#endif +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + D3D11_BeginShadowMap(isspot, (isspot?SHADOWMAP_SIZE:(SHADOWMAP_SIZE*3)), (isspot?SHADOWMAP_SIZE:(SHADOWMAP_SIZE*2))); + +// BE_Scissor(&rect); + break; +#endif } r_refdef.externalview = true; //never any viewmodels @@ -2376,27 +2440,39 @@ qboolean Sh_GenShadowMap (dlight_t *l, qbyte *lvis, int smsize) } } - /*end framebuffer*/ - GL_EndRenderBuffer_DepthOnly(restorefbo, shadowmap[isspot], smsize); - memcpy(r_refdef.m_view, oview, sizeof(r_refdef.m_view)); memcpy(r_refdef.m_projection, oproj, sizeof(r_refdef.m_projection)); + r_refdef.pxrect = oprect; - if (!gl_config.nofixedfunc) - { - qglMatrixMode(GL_PROJECTION); - qglLoadMatrixf(r_refdef.m_projection); - qglMatrixMode(GL_MODELVIEW); - qglLoadMatrixf(r_refdef.m_view); - } - - qglViewport(r_refdef.pxrect.x, r_refdef.pxrect.y-r_refdef.pxrect.height, r_refdef.pxrect.width, r_refdef.pxrect.height); - r_refdef.flipcull = oldflip; r_refdef.externalview = oldexternalview; R_SetFrustum(r_refdef.m_projection, r_refdef.m_view); + switch(qrenderer) + { +#ifdef GLQUAKE + case QR_OPENGL: + /*end framebuffer*/ + GL_EndRenderBuffer_DepthOnly(restorefbo, shadowmap[isspot], smsize); + if (!gl_config.nofixedfunc) + { + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf(r_refdef.m_projection); + qglMatrixMode(GL_MODELVIEW); + qglLoadMatrixf(r_refdef.m_view); + } + GL_ViewportUpdate(); + break; +#endif +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + D3D11_EndShadowMap(); + D3D11BE_DoneShadows(); + break; +#endif + } + return true; } @@ -2406,6 +2482,7 @@ static void Sh_DrawShadowMapLight(dlight_t *l, vec3_t colour, qbyte *vvis) qbyte *lvis; srect_t rect; int smsize; + qboolean isspot; if (R_CullSphere(l->origin, l->radius)) { @@ -2455,7 +2532,8 @@ static void Sh_DrawShadowMapLight(dlight_t *l, vec3_t colour, qbyte *vvis) lvis = NULL; - if (l->fov != 0) + isspot = l->fov != 0; + if (isspot) smsize = SHADOWMAP_SIZE; else { @@ -2472,8 +2550,13 @@ static void Sh_DrawShadowMapLight(dlight_t *l, vec3_t colour, qbyte *vvis) smsize = bound(16, lodlinear, SHADOWMAP_SIZE); } - if (!BE_SelectDLight(l, colour, l->fov?LSHADER_SPOT:LSHADER_SMAP)) - return; //can't get a shader loaded +#ifdef D3D11QUAKE + if (qrenderer == QR_DIRECT3D11) + D3D11BE_SetupForShadowMap(l, isspot, isspot?smsize:smsize*3, isspot?smsize:smsize*2, (smsize-4) / (float)SHADOWMAP_SIZE); +#endif + + if (!BE_SelectDLight(l, colour, isspot?LSHADER_SPOT:LSHADER_SMAP)) + return; if (!Sh_GenShadowMap(l, lvis, smsize)) return; @@ -2488,40 +2571,8 @@ static void Sh_DrawShadowMapLight(dlight_t *l, vec3_t colour, qbyte *vvis) Sh_DrawEntLighting(l, colour); } -#endif - - -// warning: ‘Sh_WorldLightingPass’ defined but not used -/* -static void Sh_WorldLightingPass(void) -{ - msurface_t *s; - int i; - int ve; - - ve = 0; - for (i = 0; i < cl.worldmodel->numsurfaces; i++) - { - s = &cl.worldmodel->surfaces[i]; - if(s->visframe != r_framecount) - continue; - - if (ve != s->texinfo->texture->vbo.vboe) - { - ve = s->texinfo->texture->vbo.vboe; - - GL_SelectVBO(s->texinfo->texture->vbo.vbocoord); - GL_SelectEBO(s->texinfo->texture->vbo.vboe); - qglVertexPointer(3, GL_FLOAT, sizeof(vecV_t), s->texinfo->texture->vbo.coord); - } - qglDrawRangeElements(GL_TRIANGLES, s->mesh->vbofirstvert, s->mesh->numvertexes, s->mesh->numindexes, GL_INDEX_TYPE, (index_t*)(s->mesh->vbofirstelement*sizeof(index_t))); - RQuantAdd(RQUANT_LITFACES, s->mesh->numindexes); - } -} -*/ - /* draws faces facing the light Note: Backend mode must have been selected in advance, as must the light to light from @@ -2553,16 +2604,21 @@ static void Sh_DrawEntLighting(dlight_t *light, vec3_t colour) { default: break; - case QR_OPENGL: #ifdef GLQUAKE + case QR_OPENGL: GLBE_BaseEntTextures(); -#endif break; - case QR_DIRECT3D9: +#endif #ifdef D3D9QUAKE + case QR_DIRECT3D9: D3D9BE_BaseEntTextures(); -#endif break; +#endif +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + D3D11BE_BaseEntTextures(); + break; +#endif } } } @@ -2696,25 +2752,31 @@ static void Sh_DrawStencilLightShadows(dlight_t *dl, qbyte *lvis, qbyte *vvis, q { case QR_NONE: case QR_SOFTWARE: - case QR_DIRECT3D11: default: break; - case QR_DIRECT3D9: +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + D3D11BE_RenderShadowBuffer(sm->numverts, sm->d3d11_vbuffer, sm->numindicies, sm->d3d11_ibuffer); + break; +#endif #ifdef D3D9QUAKE - D3D9BE_RenderShadowBuffer(sm->numverts, sm->d3d_vbuffer, sm->numindicies, sm->d3d_ibuffer); -#endif + case QR_DIRECT3D9: + D3D9BE_RenderShadowBuffer(sm->numverts, sm->d3d9_vbuffer, sm->numindicies, sm->d3d9_ibuffer); break; - case QR_OPENGL: +#endif #ifdef GLQUAKE + case QR_OPENGL: GLBE_RenderShadowBuffer(sm->numverts, sm->vebo[0], sm->verts, sm->numindicies, sm->vebo[1], sm->indicies); -#endif break; +#endif } } if (!r_drawentities.value) return; + if (qrenderer == QR_DIRECT3D11) + return; #ifdef GLQUAKE if (gl_config.nofixedfunc) return; /*hackzone*/ @@ -3230,6 +3292,65 @@ void Com_ParseVector(char *str, vec3_t out) out[2] = atof(com_token); } +void Sh_CheckSettings(void) +{ + qboolean canstencil = false, cansmap = false; + r_shadow_shadowmapping.ival = r_shadow_shadowmapping.value; + r_shadow_realtime_world.ival = r_shadow_realtime_world.value; + r_shadow_realtime_dlight.ival = r_shadow_realtime_dlight.value; + + switch(qrenderer) + { +#ifdef GLQUAKE + case QR_OPENGL: + if (gl_config.arb_shader_objects && gl_config.ext_framebuffer_objects && gl_config.arb_shadow) + cansmap = true; + if (gl_stencilbits) + canstencil = true; + break; +#endif +#ifdef D3D9QUAKE + case QR_DIRECT3D9: + #ifndef GLQUAKE + //the code still has a lot of ifdefs, so will crash if you try it in a merged build. + //its not really usable in d3d-only builds either, so no great loss. + canstencil = true; + #endif + break; +#endif +#ifdef D3D11QUAKE + case QR_DIRECT3D11: + cansmap = true; + break; +#endif + default: + r_shadow_realtime_world.ival = 0; + r_shadow_realtime_dlight.ival = 0; + return; + } + + if (!canstencil && !cansmap) + { + //no shadow methods available at all. + Con_Printf("Missing GL extensions: realtime lighting is not possible.\n"); + r_shadow_realtime_world.ival = 0; + r_shadow_realtime_dlight.ival = 0; + } + else if (!canstencil || !cansmap) + { + //only one shadow method + if (!!r_shadow_shadowmapping.ival != cansmap) + { + Con_Printf("Missing GL extensions: forcing shadowmapping %s.\n", cansmap?"on":"off"); + r_shadow_shadowmapping.ival = cansmap; + } + } + else + { + //both shadow methods available. + } +} + void Sh_DrawLights(qbyte *vis) { vec3_t colour; @@ -3237,44 +3358,6 @@ void Sh_DrawLights(qbyte *vis) int i; unsigned int ignoreflags; - switch(qrenderer) - { -#ifdef GLQUAKE - case QR_OPENGL: - if (r_shadow_shadowmapping.ival) - { //if we've no glsl or fbos, shadowmapping ain't possible, so don't use it. - if (!gl_config.arb_shader_objects || !gl_config.ext_framebuffer_objects || !gl_config.arb_shadow) - { - //disable stuff if we can't cope with it - Con_Printf("Missing GL extensions: switching off shadowmapping.\n"); - r_shadow_shadowmapping.ival = 0; - } - } - if (!r_shadow_shadowmapping.ival) - { //if we're using stencil shadows, give up if there's no stencil buffer - if (!gl_stencilbits) - { - Con_Printf("Missing GL extensions: switching off realtime lighting.\n"); - r_shadow_realtime_world.ival = 0; - r_shadow_realtime_dlight.ival = 0; - } - } - break; -#endif -#ifdef D3D9QUAKE - case QR_DIRECT3D9: - r_shadow_shadowmapping.ival = 0; - #ifdef GLQUAKE - //the code still has a lot of ifdefs, so will crash if you try it in a merged build. - //its not really usable in d3d-only builds either, so no great loss. - return; - #endif - break; -#endif - default: - return; - } - if (r_shadow_realtime_world.modified || r_shadow_realtime_world_shadows.modified || r_shadow_realtime_dlight.modified || @@ -3287,6 +3370,7 @@ void Sh_DrawLights(qbyte *vis) r_shadow_realtime_dlight_shadows.modified = r_shadow_shadowmapping.modified = false; + Sh_CheckSettings(); //make sure the lighting is reloaded Sh_PreGenerateLights(); } @@ -3337,9 +3421,7 @@ void Sh_DrawLights(qbyte *vis) } else if ((dl->flags & LFLAG_SHADOWMAP) || r_shadow_shadowmapping.ival) { -#ifdef GLQUAKE Sh_DrawShadowMapLight(dl, colour, vis); -#endif } else { diff --git a/engine/gl/gl_vidcommon.c b/engine/gl/gl_vidcommon.c index 878c5b81..0ecc687c 100644 --- a/engine/gl/gl_vidcommon.c +++ b/engine/gl/gl_vidcommon.c @@ -726,8 +726,8 @@ void GL_CheckExtensions (void *(*getglfunction) (char *name)) gl_config.arb_texture_cube_map = GL_CheckExtension("GL_ARB_texture_cube_map"); #if !defined(GL_STATIC) - /*vbos*/ - if (gl_config.gles && gl_config.glversion >= 2) + /*vbos, were made core in gl1.5 or gles2.0*/ + if ((gl_config.gles && gl_config.glversion >= 2) || (!gl_config.gles && (gl_major_version > 1 || (gl_major_version == 1 && gl_minor_version >= 5)))) { qglGenBuffersARB = (void *)getglext("glGenBuffers"); qglDeleteBuffersARB = (void *)getglext("glDeleteBuffers"); diff --git a/engine/gl/gl_vidlinuxglx.c b/engine/gl/gl_vidlinuxglx.c index 9342a88c..cadc6972 100644 --- a/engine/gl/gl_vidlinuxglx.c +++ b/engine/gl/gl_vidlinuxglx.c @@ -203,7 +203,11 @@ static qboolean x11_initlib(void) if (!x11.lib) { +#ifdef __CYGWIN__ + x11.lib = Sys_LoadLibrary("cygX11-6.dll", x11_functable); +#else x11.lib = Sys_LoadLibrary("libX11.so.6", x11_functable); +#endif if (!x11.lib) x11.lib = Sys_LoadLibrary("libX11", x11_functable); @@ -472,7 +476,7 @@ char clipboard_buffer[SYS_CLIPBOARD_SIZE]; /*-----------------------------------------------------------------------*/ -static void *gllibrary; +static dllhandle_t *gllibrary; XVisualInfo* (*qglXChooseVisual) (Display *dpy, int screen, int *attribList); void (*qglXSwapBuffers) (Display *dpy, GLXDrawable drawable); @@ -483,34 +487,36 @@ void *(*qglXGetProcAddress) (char *name); void GLX_CloseLibrary(void) { - dlclose(gllibrary); + Sys_CloseLibrary(gllibrary); gllibrary = NULL; } qboolean GLX_InitLibrary(char *driver) { + dllfunction_t funcs[] = + { + {(void*)&qglXChooseVisual, "glXChooseVisual"}, + {(void*)&qglXSwapBuffers, "glXSwapBuffers"}, + {(void*)&qglXMakeCurrent, "glXMakeCurrent"}, + {(void*)&qglXCreateContext, "glXCreateContext"}, + {(void*)&qglXDestroyContext, "glXDestroyContext"}, + {NULL, NULL} + }; + if (driver && *driver) - gllibrary = dlopen(driver, RTLD_LAZY); + gllibrary = Sys_LoadLibrary(driver, funcs); else gllibrary = NULL; if (!gllibrary) //I hate this. - gllibrary = dlopen("libGL.so.1", RTLD_LAZY); + gllibrary = Sys_LoadLibrary("libGL.so.1", funcs); if (!gllibrary) - gllibrary = dlopen("libGL.so", RTLD_LAZY); + gllibrary = Sys_LoadLibrary("libGL", funcs); if (!gllibrary) return false; - qglXChooseVisual = dlsym(gllibrary, "glXChooseVisual"); - qglXSwapBuffers = dlsym(gllibrary, "glXSwapBuffers"); - qglXMakeCurrent = dlsym(gllibrary, "glXMakeCurrent"); - qglXCreateContext = dlsym(gllibrary, "glXCreateContext"); - qglXDestroyContext = dlsym(gllibrary, "glXDestroyContext"); - qglXGetProcAddress = dlsym(gllibrary, "glXGetProcAddress"); + qglXGetProcAddress = Sys_GetAddressForName(gllibrary, "glXGetProcAddress"); if (!qglXGetProcAddress) - qglXGetProcAddress = dlsym(gllibrary, "glXGetProcAddressARB"); - - if (!qglXSwapBuffers && !qglXDestroyContext && !qglXCreateContext && !qglXMakeCurrent && !qglXChooseVisual) - return false; + qglXGetProcAddress = Sys_GetAddressForName(gllibrary, "glXGetProcAddressARB"); return true; } @@ -525,7 +531,7 @@ void *GLX_GetSymbol(char *name) symb = NULL; if (!symb) - symb = dlsym(gllibrary, name); + symb = Sys_GetAddressForName(gllibrary, name); return symb; } @@ -1431,7 +1437,7 @@ qboolean X11VID_Init (rendererstate_t *info, unsigned char *palette, int psl) { #ifdef USE_EGL case PSL_EGL: - if (!EGL_LoadLibrary(info->glrenderer)) + if (!EGL_LoadLibrary(info->subrenderer)) { Con_Printf("couldn't load EGL library\n"); return false; @@ -1439,7 +1445,7 @@ qboolean X11VID_Init (rendererstate_t *info, unsigned char *palette, int psl) break; #endif case PSL_GLX: - if (!GLX_InitLibrary(info->glrenderer)) + if (!GLX_InitLibrary(info->subrenderer)) { Con_Printf("Couldn't intialise GLX\nEither your drivers are not installed or you need to specify the library name with the gl_driver cvar\n"); return false; @@ -1526,7 +1532,7 @@ qboolean X11VID_Init (rendererstate_t *info, unsigned char *palette, int psl) if ((!(fullscreenflags & FULLSCREEN_VMODE) || vm.usemode <= 0) && X_CheckWMFullscreenAvailable()) fullscreenflags |= FULLSCREEN_WM; else - fullscreenflags |= FULLSCREEN_LEGACY; + fullscreenflags |= FULLSCREEN_LEGACY; } else if (sys_parentwindow) { @@ -1573,7 +1579,7 @@ qboolean X11VID_Init (rendererstate_t *info, unsigned char *palette, int psl) CL_UpdateWindowTitle(); /*make it visible*/ - if (fullscreen & FULLSCREEN_VMODE) + if (fullscreenflags & FULLSCREEN_VMODE) { x11.pXRaiseWindow(vid_dpy, vid_window); x11.pXWarpPointer(vid_dpy, None, vid_window, 0, 0, 0, 0, 0, 0); diff --git a/engine/gl/gl_vidnt.c b/engine/gl/gl_vidnt.c index 63263711..12c6bbc4 100644 --- a/engine/gl/gl_vidnt.c +++ b/engine/gl/gl_vidnt.c @@ -1081,7 +1081,7 @@ qboolean VID_AttachGL (rendererstate_t *info) do { TRACE(("dbg: VID_AttachGL: GLInitialise\n")); - if (GLInitialise(info->glrenderer)) + if (GLInitialise(info->subrenderer)) { maindc = GetDC(mainwindow); TRACE(("dbg: VID_AttachGL: bSetupPixelFormat\n")); @@ -1090,13 +1090,13 @@ qboolean VID_AttachGL (rendererstate_t *info) ReleaseDC(mainwindow, maindc); } - if (!*info->glrenderer || !stricmp(info->glrenderer, "opengl32.dll") || !stricmp(info->glrenderer, "opengl32")) //go for windows system dir if we failed with the default. Should help to avoid the 3dfx problem. + if (!*info->subrenderer || !stricmp(info->subrenderer, "opengl32.dll") || !stricmp(info->subrenderer, "opengl32")) //go for windows system dir if we failed with the default. Should help to avoid the 3dfx problem. { char systemgl[MAX_OSPATH+1]; GetSystemDirectory(systemgl, sizeof(systemgl)-1); strncat(systemgl, "\\", sizeof(systemgl)-1); - if (*info->glrenderer) - strncat(systemgl, info->glrenderer, sizeof(systemgl)-1); + if (*info->subrenderer) + strncat(systemgl, info->subrenderer, sizeof(systemgl)-1); else strncat(systemgl, "opengl32.dll", sizeof(systemgl)-1); TRACE(("dbg: VID_AttachGL: GLInitialise (system dir specific)\n")); diff --git a/engine/gl/glquake.h b/engine/gl/glquake.h index 18b0f0ee..743d10f3 100644 --- a/engine/gl/glquake.h +++ b/engine/gl/glquake.h @@ -420,14 +420,6 @@ void R_BloomBlend(void); void R_InitBloomTextures(void); #endif -// -// gl_rsurf.c -// -#ifdef GLQUAKE -void GL_LoadShaders(void); -#endif - - // // gl_ngraph.c // @@ -436,6 +428,12 @@ void R_NetGraph (void); #if defined(GLQUAKE) +//updates the viewport correctly. +//pxrect.y is relative to the top. +//gl requires viewports specified relative to the bottom. +//so we need to do a little extra maths, which keeps confusing me, so one macro to ensure consistancy. +#define GL_ViewportUpdate() qglViewport(r_refdef.pxrect.x, r_refdef.pxrect.maxheight-(r_refdef.pxrect.y+r_refdef.pxrect.height), r_refdef.pxrect.width, r_refdef.pxrect.height) + #ifdef GL_STATIC //these are the functions that are valid in gles2. //other functions should never actually be used. diff --git a/engine/gl/shader.h b/engine/gl/shader.h index 35c88ab5..57023d9a 100644 --- a/engine/gl/shader.h +++ b/engine/gl/shader.h @@ -278,16 +278,21 @@ typedef struct shaderpass_s { enum { SHADER_PASS_CLAMP = 1<<0, //needed for d3d's sampler states, infects image flags SHADER_PASS_NEAREST = 1<<1, //needed for d3d's sampler states, infects image flags - SHADER_PASS_NOMIPMAP = 1<<2, //infects image flags - SHADER_PASS_NOCOLORARRAY = 1<< 3, + SHADER_PASS_DEPTHCMP = 1<<2, //needed for d3d's sampler states + SHADER_PASS_NOMIPMAP = 1<<3, //infects image flags + SHADER_PASS_NOCOLORARRAY = 1<< 4, //FIXME: remove these - SHADER_PASS_VIDEOMAP = 1 << 4, - SHADER_PASS_DETAIL = 1 << 5, - SHADER_PASS_LIGHTMAP = 1 << 6, - SHADER_PASS_DELUXMAP = 1 << 7, - SHADER_PASS_ANIMMAP = 1 << 8 + SHADER_PASS_VIDEOMAP = 1 << 5, + SHADER_PASS_DETAIL = 1 << 6, + SHADER_PASS_LIGHTMAP = 1 << 7, + SHADER_PASS_DELUXMAP = 1 << 8, + SHADER_PASS_ANIMMAP = 1 << 9 } flags; + +#ifdef D3D11QUAKE + void *becache; //cache for blendstate objects. +#endif } shaderpass_t; typedef struct @@ -414,9 +419,13 @@ union programhandle_u { void *vert; void *frag; - void *ctabf; - void *ctabv; - void *layout; + #ifdef D3D9QUAKE + void *ctabf; + void *ctabv; + #endif + #ifdef D3D11QUAKE + void *layout; + #endif } hlsl; #endif }; @@ -513,6 +522,7 @@ struct shader_s SHADER_HASNORMALMAP = 1 << 22, //says that we need to load a normalmap texture SHADER_HASRIPPLEMAP = 1 << 23, //water surface disturbances for water splashes SHADER_HASGLOSS = 1 << 24, // + SHADER_NOSHADOWS = 1 << 25, //don't cast shadows } flags; program_t *prog; @@ -552,6 +562,7 @@ void Shader_DefaultSkinShell(char *shortname, shader_t *s, const void *args); void Shader_DefaultBSPLM(char *shortname, shader_t *s, const void *args); void Shader_DefaultBSPQ1(char *shortname, shader_t *s, const void *args); void Shader_DefaultBSPQ2(char *shortname, shader_t *s, const void *args); +void Shader_DefaultWaterShader(char *shortname, shader_t *s, const void *args); void Shader_DefaultSkybox(char *shortname, shader_t *s, const void *args); void Shader_DefaultCinematic(char *shortname, shader_t *s, const void *args); void Shader_DefaultScript(char *shortname, shader_t *s, const void *args); @@ -641,8 +652,9 @@ void D3D11BE_SelectEntity(entity_t *ent); qboolean D3D11BE_SelectDLight(dlight_t *dl, vec3_t colour, unsigned int lmode); qboolean D3D11Shader_CreateProgram (program_t *prog, const char *name, int permu, char **precompilerconstants, char *vert, char *frag); +void D3D11Shader_DeleteProgram(program_t *prog); int D3D11Shader_FindUniform(union programhandle_u *h, int type, char *name); -void D3D11Shader_Init(void); +qboolean D3D11Shader_Init(unsigned int featurelevel); void D3D11BE_Reset(qboolean before); void D3D11BE_SetupViewCBuffer(void); void D3D11_UploadLightmap(lightmapinfo_t *lm); @@ -668,10 +680,12 @@ int GLBE_SetupForShadowMap(texid_t shadowmaptex, int texwidth, int texheight, fl //Called from shadowmapping code into backend void GLBE_BaseEntTextures(void); void D3D9BE_BaseEntTextures(void); +void D3D11BE_BaseEntTextures(void); //prebuilds shadow volumes void Sh_PreGenerateLights(void); //Draws lights, called from the backend void Sh_DrawLights(qbyte *vis); +void Sh_CheckSettings(void); void SH_FreeShadowMesh(struct shadowmesh_s *sm); //frees all memory void Sh_Shutdown(void); diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c index 80d72714..152b53f8 100644 --- a/engine/http/httpclient.c +++ b/engine/http/httpclient.c @@ -517,10 +517,10 @@ static qboolean HTTP_DL_Work(struct dl_download *dl) if (con->gzip) { -#ifdef NPFTE - Con_Printf("HTTP: no support for gzipped files \"%s\"\n", dl->localname); -#else +#if !defined(NPFTE) && defined(AVAIL_ZLIB) con->file = FS_OpenTemp(); +#else + Con_Printf("HTTP: no support for gzipped files \"%s\"\n", dl->localname); #endif } else @@ -618,7 +618,7 @@ static qboolean HTTP_DL_Work(struct dl_download *dl) dl->status = DL_FAILED; else { -#ifndef NPFTE +#if !defined(NPFTE) && defined(AVAIL_ZLIB) if (con->gzip) { VFS_SEEK(con->file, 0); @@ -714,7 +714,7 @@ void HTTPDL_Establish(struct dl_download *dl) "Content-Length: %i\r\n" "Content-Type: %s\r\n" "Connection: close\r\n" -#ifndef NPFTE +#if !defined(NPFTE) && defined(AVAIL_ZLIB) "Accept-Encoding: gzip\r\n" #endif "User-Agent: "FULLENGINENAME"\r\n" @@ -730,7 +730,7 @@ void HTTPDL_Establish(struct dl_download *dl) "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Connection: close\r\n" -#ifndef NPFTE +#if !defined(NPFTE) && defined(AVAIL_ZLIB) "Accept-Encoding: gzip\r\n" #endif "User-Agent: "FULLENGINENAME"\r\n" diff --git a/engine/qclib/execloop.h b/engine/qclib/execloop.h index 26e715ef..83e224f6 100644 --- a/engine/qclib/execloop.h +++ b/engine/qclib/execloop.h @@ -513,7 +513,7 @@ reeval: d16 = ED_GlobalAtOfs16(progfuncs, st->a); f = ED_FieldAtOfs(progfuncs, OPB->_int + progfuncs->funcs.fieldadjust); pr_xstatement = st-pr_statements; - if (PR_RunWarning(&progfuncs->funcs, "assignment to read-only entity in %s (%s.%s)", PR_StringToNative(&progfuncs->funcs, pr_xfunction->s_name), d16?PR_StringToNative(&progfuncs->funcs, d16->s_name):NULL, f?f->name:NULL)) + if (PR_RunWarning(&progfuncs->funcs, "assignment to read-only entity %i in %s (%s.%s)", OPA->edict, PR_StringToNative(&progfuncs->funcs, pr_xfunction->s_name), d16?PR_StringToNative(&progfuncs->funcs, d16->s_name):NULL, f?f->name:NULL)) { st--; goto cont; @@ -542,7 +542,7 @@ reeval: { pr_xstatement = st-pr_statements; - if (PR_RunWarning (&progfuncs->funcs, "OP_LOAD references invalid entity in %s", PR_StringToNative(&progfuncs->funcs, pr_xfunction->s_name))) + if (PR_RunWarning (&progfuncs->funcs, "OP_LOAD references invalid entity %i in %s", OPA->edict, PR_StringToNative(&progfuncs->funcs, pr_xfunction->s_name))) { st--; goto cont; @@ -561,7 +561,7 @@ reeval: if ((unsigned)OPA->edict >= (unsigned)sv_num_edicts) { pr_xstatement = st-pr_statements; - PR_RunError (&progfuncs->funcs, "OP_LOAD_V references invalid entity in %s", PR_StringToNative(&progfuncs->funcs, pr_xfunction->s_name)); + PR_RunError (&progfuncs->funcs, "OP_LOAD_V references invalid entity %i in %s", OPA->edict, PR_StringToNative(&progfuncs->funcs, pr_xfunction->s_name)); } ed = PROG_TO_EDICT(progfuncs, OPA->edict); #ifdef PARANOID @@ -601,7 +601,7 @@ reeval: case OP_IF_F: RUNAWAYCHECK(); - if (OPA->_int) + if (OPA->_float) st += (sofs)st->b - 1; // offset the s++ break; diff --git a/engine/qclib/initlib.c b/engine/qclib/initlib.c index d5a5fbe8..878930ce 100644 --- a/engine/qclib/initlib.c +++ b/engine/qclib/initlib.c @@ -562,7 +562,7 @@ int PDECL PR_GetFuncArgCount(pubprogfuncs_t *ppf, func_t func) } } -func_t PDECL PR_FindFunc(pubprogfuncs_t *ppf, char *funcname, progsnum_t pnum) +func_t PDECL PR_FindFunc(pubprogfuncs_t *ppf, const char *funcname, progsnum_t pnum) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; dfunction_t *f=NULL; @@ -616,45 +616,54 @@ func_t PDECL PR_FindFunc(pubprogfuncs_t *ppf, char *funcname, progsnum_t pnum) return 0; } -void PDECL QC_FindPrefixedGlobals(pubprogfuncs_t *ppf, char *prefix, void (PDECL *found) (pubprogfuncs_t *progfuncs, char *name, union eval_s *val, etype_t type) ) +void PDECL QC_FindPrefixedGlobals(pubprogfuncs_t *ppf, int pnum, char *prefix, void (PDECL *found) (pubprogfuncs_t *progfuncs, char *name, union eval_s *val, etype_t type, void *ctx), void *ctx) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int i; ddef16_t *def16; ddef32_t *def32; int len = strlen(prefix); - unsigned int pnum; - for (pnum = 0; pnum < maxprogs; pnum++) + if (pnum == PR_CURRENT) + pnum = pr_typecurrent; + if (pnum == PR_ANY) { - if (!pr_progstate[pnum].progs) - continue; - - switch(pr_progstate[pnum].structtype) + for (pnum = 0; (unsigned)pnum < maxprogs; pnum++) { - case PST_DEFAULT: - case PST_KKQWSV: - for (i=1 ; inumglobaldefs ; i++) - { - def16 = &pr_progstate[pnum].globaldefs16[i]; - if (!strncmp(def16->s_name+progfuncs->funcs.stringtable,prefix, len)) - found(&progfuncs->funcs, def16->s_name+progfuncs->funcs.stringtable, (eval_t *)&pr_progstate[pnum].globals[def16->ofs], def16->type); - } - break; - case PST_QTEST: - case PST_FTE32: - for (i=1 ; inumglobaldefs ; i++) - { - def32 = &pr_progstate[pnum].globaldefs32[i]; - if (!strncmp(def32->s_name+progfuncs->funcs.stringtable,prefix, len)) - found(&progfuncs->funcs, def32->s_name+progfuncs->funcs.stringtable, (eval_t *)&pr_progstate[pnum].globals[def32->ofs], def32->type); - } - break; + if (!pr_progstate[pnum].progs) + continue; + QC_FindPrefixedGlobals(ppf, pnum, prefix, found, ctx); } + return; + } + + if (!pr_progstate[pnum].progs) + return; + + switch(pr_progstate[pnum].structtype) + { + case PST_DEFAULT: + case PST_KKQWSV: + for (i=1 ; inumglobaldefs ; i++) + { + def16 = &pr_progstate[pnum].globaldefs16[i]; + if (!strncmp(def16->s_name+progfuncs->funcs.stringtable,prefix, len)) + found(&progfuncs->funcs, def16->s_name+progfuncs->funcs.stringtable, (eval_t *)&pr_progstate[pnum].globals[def16->ofs], def16->type, ctx); + } + break; + case PST_QTEST: + case PST_FTE32: + for (i=1 ; inumglobaldefs ; i++) + { + def32 = &pr_progstate[pnum].globaldefs32[i]; + if (!strncmp(def32->s_name+progfuncs->funcs.stringtable,prefix, len)) + found(&progfuncs->funcs, def32->s_name+progfuncs->funcs.stringtable, (eval_t *)&pr_progstate[pnum].globals[def32->ofs], def32->type, ctx); + } + break; } } -eval_t *PDECL PR_FindGlobal(pubprogfuncs_t *ppf, char *globname, progsnum_t pnum, etype_t *type) +eval_t *PDECL PR_FindGlobal(pubprogfuncs_t *ppf, const char *globname, progsnum_t pnum, etype_t *type) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int i; diff --git a/engine/qclib/pr_edict.c b/engine/qclib/pr_edict.c index 4d89577c..c8170af6 100644 --- a/engine/qclib/pr_edict.c +++ b/engine/qclib/pr_edict.c @@ -251,7 +251,7 @@ fdef_t *PDECL ED_FieldInfo (pubprogfuncs_t *ppf, unsigned int *count) ED_FindField ============ */ -fdef_t *ED_FindField (progfuncs_t *progfuncs, char *name) +fdef_t *ED_FindField (progfuncs_t *progfuncs, const char *name) { unsigned int i; @@ -315,7 +315,7 @@ unsigned int ED_FindGlobalOfs (progfuncs_t *progfuncs, char *name) return 0; } -ddef16_t *ED_FindGlobalFromProgs16 (progfuncs_t *progfuncs, char *name, progsnum_t prnum) +ddef16_t *ED_FindGlobalFromProgs16 (progfuncs_t *progfuncs, const char *name, progsnum_t prnum) { ddef16_t *def; unsigned int i; @@ -328,7 +328,7 @@ ddef16_t *ED_FindGlobalFromProgs16 (progfuncs_t *progfuncs, char *name, progsnum } return NULL; } -ddef32_t *ED_FindGlobalFromProgs32 (progfuncs_t *progfuncs, char *name, progsnum_t prnum) +ddef32_t *ED_FindGlobalFromProgs32 (progfuncs_t *progfuncs, const char *name, progsnum_t prnum) { ddef32_t *def; unsigned int i; @@ -342,7 +342,7 @@ ddef32_t *ED_FindGlobalFromProgs32 (progfuncs_t *progfuncs, char *name, progsnum return NULL; } -ddef16_t *ED_FindTypeGlobalFromProgs16 (progfuncs_t *progfuncs, char *name, progsnum_t prnum, int type) +ddef16_t *ED_FindTypeGlobalFromProgs16 (progfuncs_t *progfuncs, const char *name, progsnum_t prnum, int type) { ddef16_t *def; unsigned int i; @@ -366,7 +366,7 @@ ddef16_t *ED_FindTypeGlobalFromProgs16 (progfuncs_t *progfuncs, char *name, prog } -ddef32_t *ED_FindTypeGlobalFromProgs32 (progfuncs_t *progfuncs, char *name, progsnum_t prnum, int type) +ddef32_t *ED_FindTypeGlobalFromProgs32 (progfuncs_t *progfuncs, const char *name, progsnum_t prnum, int type) { ddef32_t *def; unsigned int i; @@ -419,7 +419,7 @@ unsigned int *ED_FindGlobalOfsFromProgs (progfuncs_t *progfuncs, char *name, pro ED_FindFunction ============ */ -dfunction_t *ED_FindFunction (progfuncs_t *progfuncs, char *name, progsnum_t *prnum, progsnum_t fromprogs) +dfunction_t *ED_FindFunction (progfuncs_t *progfuncs, const char *name, progsnum_t *prnum, progsnum_t fromprogs) { dfunction_t *func; unsigned int i; @@ -1042,7 +1042,7 @@ void ED_Count (progfuncs_t *progfuncs) ED_NewString ============= */ -char *PDECL ED_NewString (pubprogfuncs_t *ppf, char *string, int minlength) +char *PDECL ED_NewString (pubprogfuncs_t *ppf, const char *string, int minlength, pbool demarkup) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; char *newc, *new_p; @@ -1060,7 +1060,7 @@ char *PDECL ED_NewString (pubprogfuncs_t *ppf, char *string, int minlength) for (i=0 ; i< l ; i++) { - if (string[i] == '\\' && i < l-1 && string[i+1] != 0) + if (demarkup && string[i] == '\\' && i < l-1 && string[i+1] != 0) { i++; if (string[i] == 'n') @@ -1097,7 +1097,7 @@ pbool PDECL ED_ParseEval (pubprogfuncs_t *ppf, eval_t *eval, int type, char *s) switch (type & ~DEF_SAVEGLOBAL) { case ev_string: - st = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, s, 0)); + st = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, s, 0, true)); eval->string = st; break; @@ -1181,7 +1181,7 @@ pbool ED_ParseEpair (progfuncs_t *progfuncs, int qcptr, unsigned int fldofs, int switch (type) { case ev_string: - st = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, s, 0)); + st = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, s, 0, true)); *(string_t *)(progfuncs->funcs.stringtable + qcptr) = st; break; diff --git a/engine/qclib/pr_exec.c b/engine/qclib/pr_exec.c index 1117c403..66bc891a 100644 --- a/engine/qclib/pr_exec.c +++ b/engine/qclib/pr_exec.c @@ -783,7 +783,7 @@ char *PDECL PR_EvaluateDebugString(pubprogfuncs_t *ppf, char *key) switch (type&~DEF_SAVEGLOBAL) { case ev_string: - *(string_t *)val = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, assignment, 0)); + *(string_t *)val = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, assignment, 0, true)); break; case ev_float: diff --git a/engine/qclib/progsint.h b/engine/qclib/progsint.h index bf0d4c35..5649f652 100644 --- a/engine/qclib/progsint.h +++ b/engine/qclib/progsint.h @@ -365,7 +365,7 @@ void PR_Profile_f (void); struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *progfuncs); void PDECL ED_Free (pubprogfuncs_t *progfuncs, struct edict_s *ed); -char *PDECL ED_NewString (pubprogfuncs_t *progfuncs, char *string, int minlength); +char *PDECL ED_NewString (pubprogfuncs_t *ppf, const char *string, int minlength, pbool demarkup); // returns a copy of the string allocated from the server's string heap void PDECL ED_Print (pubprogfuncs_t *progfuncs, struct edict_s *ed); @@ -462,15 +462,15 @@ struct qcthread_s *PDECL PR_ForkStack (pubprogfuncs_t *progfuncs); void PDECL PR_ResumeThread (pubprogfuncs_t *progfuncs, struct qcthread_s *thread); void PDECL PR_AbortStack (pubprogfuncs_t *progfuncs); -eval_t *PDECL PR_FindGlobal(pubprogfuncs_t *prfuncs, char *globname, progsnum_t pnum, etype_t *type); -ddef16_t *ED_FindTypeGlobalFromProgs16 (progfuncs_t *progfuncs, char *name, progsnum_t prnum, int type); -ddef32_t *ED_FindTypeGlobalFromProgs32 (progfuncs_t *progfuncs, char *name, progsnum_t prnum, int type); -ddef16_t *ED_FindGlobalFromProgs16 (progfuncs_t *progfuncs, char *name, progsnum_t prnum); -ddef32_t *ED_FindGlobalFromProgs32 (progfuncs_t *progfuncs, char *name, progsnum_t prnum); -fdef_t *ED_FindField (progfuncs_t *progfuncs, char *name); +eval_t *PDECL PR_FindGlobal(pubprogfuncs_t *prfuncs, const char *globname, progsnum_t pnum, etype_t *type); +ddef16_t *ED_FindTypeGlobalFromProgs16 (progfuncs_t *progfuncs, const char *name, progsnum_t prnum, int type); +ddef32_t *ED_FindTypeGlobalFromProgs32 (progfuncs_t *progfuncs, const char *name, progsnum_t prnum, int type); +ddef16_t *ED_FindGlobalFromProgs16 (progfuncs_t *progfuncs, const char *name, progsnum_t prnum); +ddef32_t *ED_FindGlobalFromProgs32 (progfuncs_t *progfuncs, const char *name, progsnum_t prnum); +fdef_t *ED_FindField (progfuncs_t *progfuncs, const char *name); fdef_t *ED_FieldAtOfs (progfuncs_t *progfuncs, unsigned int ofs); -dfunction_t *ED_FindFunction (progfuncs_t *progfuncs, char *name, progsnum_t *pnum, progsnum_t fromprogs); -func_t PDECL PR_FindFunc(pubprogfuncs_t *progfncs, char *funcname, progsnum_t pnum); +dfunction_t *ED_FindFunction (progfuncs_t *progfuncs, const char *name, progsnum_t *pnum, progsnum_t fromprogs); +func_t PDECL PR_FindFunc(pubprogfuncs_t *progfncs, const char *funcname, progsnum_t pnum); void PDECL PR_Configure (pubprogfuncs_t *progfncs, size_t addressable_size, int max_progs); int PDECL PR_InitEnts(pubprogfuncs_t *progfncs, int maxents); char *PR_ValueString (progfuncs_t *progfuncs, etype_t type, eval_t *val, pbool verbose); diff --git a/engine/qclib/progslib.h b/engine/qclib/progslib.h index 83609a66..5aaf9bd3 100644 --- a/engine/qclib/progslib.h +++ b/engine/qclib/progslib.h @@ -105,8 +105,8 @@ struct pubprogfuncs_s char *(PDECL *saveent) (pubprogfuncs_t *prinst, char *buf, int *size, int maxsize, struct edict_s *ed); //will save just one entities vars struct edict_s *(PDECL *restoreent) (pubprogfuncs_t *prinst, char *buf, int *size, struct edict_s *ed); //will restore the entity that had it's values saved (can use NULL for ed) - union eval_s *(PDECL *FindGlobal) (pubprogfuncs_t *prinst, char *name, progsnum_t num, etype_t *type); //find a pointer to the globals value - char *(PDECL *AddString) (pubprogfuncs_t *prinst, char *val, int minlength); //dump a string into the progs memory (for setting globals and whatnot) + union eval_s *(PDECL *FindGlobal) (pubprogfuncs_t *prinst, const char *name, progsnum_t num, etype_t *type); //find a pointer to the globals value + char *(PDECL *AddString) (pubprogfuncs_t *prinst, const char *val, int minlength, pbool demarkup); //dump a string into the progs memory (for setting globals and whatnot) void *(PDECL *Tempmem) (pubprogfuncs_t *prinst, int ammount, char *whatfor); //grab some mem for as long as the progs stays loaded union eval_s *(PDECL *GetEdictFieldValue)(pubprogfuncs_t *prinst, struct edict_s *ent, char *name, evalc_t *s); //get an entityvar (cache it) and return the possible values @@ -154,7 +154,7 @@ struct pubprogfuncs_s int (PDECL *QueryField) (pubprogfuncs_t *prinst, unsigned int fieldoffset, etype_t *type, char **name, evalc_t *fieldcache); //find info on a field definition at an offset void (PDECL *EntClear) (pubprogfuncs_t *progfuncs, struct edict_s *e); - void (PDECL *FindPrefixGlobals) (pubprogfuncs_t *progfuncs, char *prefix, void (PDECL *found) (pubprogfuncs_t *progfuncs, char *name, union eval_s *val, etype_t type) ); + void (PDECL *FindPrefixGlobals) (pubprogfuncs_t *progfuncs, int prnum, char *prefix, void (PDECL *found) (pubprogfuncs_t *progfuncs, char *name, union eval_s *val, etype_t type, void *ctx), void *ctx); void *(PDECL *AddressableAlloc) (pubprogfuncs_t *progfuncs, unsigned int ammount); /*returns memory within the qc block, use stringtoprogs to get a usable qc pointer/string*/ @@ -272,7 +272,7 @@ typedef union eval_s #define PR_FindFunction(pf, name, num) (*pf->FindFunction) (pf, name, num) #define PR_FindGlobal(pf, name, progs, type) (*pf->FindGlobal) (pf, name, progs, type) -#define PR_AddString(pf, ed, len) (*pf->AddString) (pf, ed, len) +#define PR_AddString(pf, ed, len, demarkup) (*pf->AddString) (pf, ed, len, demarkup) #define PR_Alloc(pf,size,whatfor) (*pf->Tempmem) (pf, size, whatfor) #define PR_AddressableAlloc(pf,size) (*pf->AddressableAlloc) (pf, size) #define PR_AddressableFree(pf,mem) (*pf->AddressableFree) (pf, mem) @@ -306,7 +306,7 @@ typedef union eval_s #define PR_SetStringOfs(p,o,s) (G_INT(o) = s - p->stringtable) */ //#define PR_SetString(p, s) ((s&&*s)?(s - p->stringtable):0) -#define PR_NewString(p, s, l) PR_SetString(p, PR_AddString(p, s, l)) +#define PR_NewString(p, s, l) PR_SetString(p, PR_AddString(p, s, l, false)) /**/ #define ev_prog ev_integer diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index d69809a5..55078ed1 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -11037,8 +11037,17 @@ void QCC_PR_ParseDefs (char *classname) } else { - QCC_PR_CheckToken("#"); - QCC_PR_Lex(); + if (type->type == ev_string && QCC_PR_CheckName("_")) + { + QCC_PR_Expect("("); + QCC_PR_Lex(); + QCC_PR_Expect(")"); + } + else + { + QCC_PR_CheckToken("#"); + QCC_PR_Lex(); + } } continue; } diff --git a/engine/qclib/qcc_pr_lex.c b/engine/qclib/qcc_pr_lex.c index 56ea5818..4ce60fcd 100644 --- a/engine/qclib/qcc_pr_lex.c +++ b/engine/qclib/qcc_pr_lex.c @@ -1487,16 +1487,7 @@ void QCC_PR_LexString (void) if (len >= sizeof(pr_immediate_string)-1) QCC_Error(ERR_INVALIDSTRINGIMMEDIATE, "String length exceeds %i", sizeof(pr_immediate_string)-1); - while(*pr_file_p && qcc_iswhite(*pr_file_p)) - { - if (*pr_file_p == '\n') - { - pr_file_p++; - QCC_PR_NewLine(false); - } - else - pr_file_p++; - } + QCC_PR_LexWhitespace(); if (*pr_file_p == '\"') //have annother go { pr_file_p++; diff --git a/engine/server/net_preparse.c b/engine/server/net_preparse.c index 66d34c77..dbd9ec27 100644 --- a/engine/server/net_preparse.c +++ b/engine/server/net_preparse.c @@ -1378,7 +1378,7 @@ void NPP_NQWriteEntity(int dest, short data) //replacement write func (nq to qw) if (!bufferlen) Con_Printf("NQWriteEntity: Messages should start with WriteByte\n"); - if (majortype == svc_temp_entity && data >= 0 && data <= sv.allocated_client_slots) + if (majortype == svc_temp_entity && data > 0 && data <= sv.allocated_client_slots) if (svs.clients[data-1].viewent) data = svs.clients[data-1].viewent; diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index d08bc6a0..83a3d82f 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -1007,7 +1007,7 @@ progsnum_t AddProgs(char *name) Con_Printf("Loaded %s\n", name); - PR_AutoCvarSetup(svprogfuncs); + PR_ProgsAdded(svprogfuncs, num, name); if (!svs.numprogs) { @@ -2261,6 +2261,11 @@ static void QCBUILTIN PF_setorigin (pubprogfuncs_t *prinst, struct globalvars_s float *org; e = G_EDICT(prinst, OFS_PARM0); + if (e->readonly) + { + Con_Printf("setorigin on entity %i\n", e->entnum); + return; + } org = G_VECTOR(OFS_PARM1); VectorCopy (org, e->v->origin); World_LinkEdict (&sv.world, (wedict_t*)e, false); @@ -2291,6 +2296,11 @@ static void QCBUILTIN PF_setsize (pubprogfuncs_t *prinst, struct globalvars_s *p } return; } + if (e->readonly) + { + Con_Printf("setsize on entity %i\n", e->entnum); + return; + } min = G_VECTOR(OFS_PARM1); max = G_VECTOR(OFS_PARM2); VectorCopy (min, e->v->mins); @@ -2313,6 +2323,12 @@ void PF_setmodel_Internal (pubprogfuncs_t *prinst, edict_t *e, char *m) int i; model_t *mod; + if (e->readonly) + { + Con_Printf("setmodel on entity %i\n", e->entnum); + return; + } + // check to see if model was properly precached if (!m || !*m) i = 0; @@ -2338,7 +2354,7 @@ void PF_setmodel_Internal (pubprogfuncs_t *prinst, edict_t *e, char *m) sv.strings.model_precache[i] = m; //in a qvm, we expect the caller to have used a static location. else #endif - m = sv.strings.model_precache[i] = PR_AddString(prinst, m, 0); + m = sv.strings.model_precache[i] = PR_AddString(prinst, m, 0, false); if (!strcmp(m + strlen(m) - 4, ".bsp")) //always precache bsps sv.models[i] = Mod_FindName(m); Con_Printf("WARNING: SV_ModelIndex: model %s not precached\n", m); @@ -3829,7 +3845,7 @@ int PF_precache_model_Internal (pubprogfuncs_t *prinst, char *s, qboolean queryo sv.strings.model_precache[i] = s; else #endif - sv.strings.model_precache[i] = PR_AddString(prinst, s, 0); + sv.strings.model_precache[i] = PR_AddString(prinst, s, 0, false); s = sv.strings.model_precache[i]; if (!strcmp(s + strlen(s) - 4, ".bsp") || sv_gameplayfix_setmodelrealbox.ival) sv.models[i] = Mod_ForName(s, false); @@ -3918,7 +3934,7 @@ void QCBUILTIN PF_precache_vwep_model (pubprogfuncs_t *prinst, struct globalvars sv.strings.vw_model_precache[i] = s; else #endif - sv.strings.vw_model_precache[i] = PR_AddString(prinst, s, 0); + sv.strings.vw_model_precache[i] = PR_AddString(prinst, s, 0, false); return; } if (!strcmp(sv.strings.vw_model_precache[i], s)) diff --git a/engine/server/pr_q1qvm.c b/engine/server/pr_q1qvm.c index a08ad3fe..689be6c3 100755 --- a/engine/server/pr_q1qvm.c +++ b/engine/server/pr_q1qvm.c @@ -463,7 +463,7 @@ static eval_t *QDECL Q1QVMPF_GetEdictFieldValue(pubprogfuncs_t *pf, edict_t *e, return NULL; } -static eval_t *QDECL Q1QVMPF_FindGlobal (pubprogfuncs_t *prinst, char *name, progsnum_t num, etype_t *type) +static eval_t *QDECL Q1QVMPF_FindGlobal (pubprogfuncs_t *prinst, const char *name, progsnum_t num, etype_t *type) { return NULL; } diff --git a/engine/server/savegame.c b/engine/server/savegame.c index 527593fb..fa5bb05f 100644 --- a/engine/server/savegame.c +++ b/engine/server/savegame.c @@ -454,14 +454,14 @@ void LoadModelsAndSounds(vfsfile_t *f) char str[32768]; int i; - sv.strings.model_precache[0] = PR_AddString(svprogfuncs, "", 0); + sv.strings.model_precache[0] = PR_AddString(svprogfuncs, "", 0, false); for (i=1; i < MAX_MODELS; i++) { VFS_GETS(f, str, sizeof(str)); if (!*str) break; - sv.strings.model_precache[i] = PR_AddString(svprogfuncs, str, 0); + sv.strings.model_precache[i] = PR_AddString(svprogfuncs, str, 0, false); } if (i == MAX_MODELS) { @@ -709,8 +709,8 @@ qboolean SV_LoadLevelCache(char *savename, char *level, char *startspot, qboolea ent = NULL; svs.clients[i].edict = ent; - svs.clients[i].name = PR_AddString(svprogfuncs, svs.clients[i].namebuf, sizeof(svs.clients[i].namebuf)); - svs.clients[i].team = PR_AddString(svprogfuncs, svs.clients[i].teambuf, sizeof(svs.clients[i].teambuf)); + svs.clients[i].name = PR_AddString(svprogfuncs, svs.clients[i].namebuf, sizeof(svs.clients[i].namebuf), false); + svs.clients[i].team = PR_AddString(svprogfuncs, svs.clients[i].teambuf, sizeof(svs.clients[i].teambuf), false); if (ent) svs.clients[i].playerclass = ent->xv->playerclass; diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index f2e88fd6..3deb7e5c 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -1761,7 +1761,7 @@ void SV_Serverinfo_f (void) return; } - Con_TPrintf (TL_STARKEYPROTECTED); + Con_Printf ("Can't set * keys\n"); return; } Q_strncpyz(value, Cmd_Argv(2), sizeof(value)); @@ -1820,7 +1820,7 @@ void SV_Localinfo_f (void) Info_RemoveNonStarKeys(localinfo); return; } - Con_TPrintf (TL_STARKEYPROTECTED); + Con_Printf ("Can't set * keys\n"); return; } old = Info_ValueForKey(localinfo, Cmd_Argv(1)); diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index 35a941b5..d9a3bc08 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -69,7 +69,7 @@ int SV_ModelIndex (char *name) sv.strings.model_precache[i] = name; else #endif - sv.strings.model_precache[i] = PR_AddString(svprogfuncs, name, 0); + sv.strings.model_precache[i] = PR_AddString(svprogfuncs, name, 0, false); if (!strcmp(name + strlen(name) - 4, ".bsp")) sv.models[i] = Mod_FindName(sv.strings.model_precache[i]); @@ -1027,10 +1027,10 @@ void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean us strcpy(sv.strings.sound_precache[0], ""); sv.strings.model_precache[0] = ""; - sv.strings.model_precache[1] = PR_AddString(svprogfuncs, sv.modelname, 0); + sv.strings.model_precache[1] = PR_AddString(svprogfuncs, sv.modelname, 0, false); for (i=1 ; inumsubmodels ; i++) { - sv.strings.model_precache[1+i] = PR_AddString(svprogfuncs, localmodels[i], 0); + sv.strings.model_precache[1+i] = PR_AddString(svprogfuncs, localmodels[i], 0, false); sv.models[i+1] = Mod_ForName (localmodels[i], false); } @@ -1134,8 +1134,8 @@ void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean us else #endif { - svs.clients[i].name = PR_AddString(svprogfuncs, svs.clients[i].namebuf, sizeof(svs.clients[i].namebuf)); - svs.clients[i].team = PR_AddString(svprogfuncs, svs.clients[i].teambuf, sizeof(svs.clients[i].teambuf)); + svs.clients[i].name = PR_AddString(svprogfuncs, svs.clients[i].namebuf, sizeof(svs.clients[i].namebuf), false); + svs.clients[i].team = PR_AddString(svprogfuncs, svs.clients[i].teambuf, sizeof(svs.clients[i].teambuf), false); } #ifdef PEXT_CSQC diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 26a12c5e..bd91b674 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -1946,7 +1946,7 @@ client_t *SVC_DirectConnect(void) s = Info_ValueForKey(userinfo[0], "challenge"); if (!strncmp(s, DISTRIBUTION, strlen(DISTRIBUTION))) - challenge = atoi(s+3); + challenge = atoi(s+strlen(DISTRIBUTION)); else challenge = atoi(s); diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index 27cf25e9..f4a33929 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -2300,7 +2300,7 @@ void SV_SendClientMessages (void) #ifdef Q3SERVER if (svs.gametype == GT_QUAKE3) { - for (i=0, c = svs.clients ; istate <= cs_zombie) continue; diff --git a/engine/server/sv_sys_unix.c b/engine/server/sv_sys_unix.c index 118dc550..6618e7fc 100644 --- a/engine/server/sv_sys_unix.c +++ b/engine/server/sv_sys_unix.c @@ -711,7 +711,7 @@ int main(int argc, char *argv[]) } #endif - parms.basedir = "."; + parms.basedir = "./"; SV_Init (&parms); diff --git a/engine/server/sv_sys_win.c b/engine/server/sv_sys_win.c index 9d710965..c1ac3556 100644 --- a/engine/server/sv_sys_win.c +++ b/engine/server/sv_sys_win.c @@ -929,7 +929,7 @@ void StartQuakeServer(void) *COM_SkipPath(bindir) = 0; parms.binarydir = bindir; - parms.basedir = "."; + parms.basedir = "./"; SV_Init (&parms); diff --git a/engine/server/svmodel.c b/engine/server/svmodel.c index 35da7534..6c5625e8 100644 --- a/engine/server/svmodel.c +++ b/engine/server/svmodel.c @@ -395,6 +395,8 @@ model_t *Mod_FindName (char *name) { if (mod_numknown == MAX_MOD_KNOWN) SV_Error ("mod_numknown == MAX_MOD_KNOWN"); + if (strlen(name) >= sizeof(mod->name)) + Sys_Error ("model name is too long: %s", name); strcpy (mod->name, name); mod->needload = true; mod_numknown++; diff --git a/engine/sw/sw_backend.c b/engine/sw/sw_backend.c index 806a4472..c11abb90 100644 --- a/engine/sw/sw_backend.c +++ b/engine/sw/sw_backend.c @@ -487,7 +487,7 @@ static void SWBE_SubmitMeshesSortList(batch_t *sortlist) continue; if (batch->flags & BEF_NODLIGHT) - if (shaderstate.mode == BEM_LIGHT || shaderstate.mode == BEM_SMAPLIGHT) + if (shaderstate.mode == BEM_LIGHT) continue; if (batch->flags & BEF_NOSHADOWS) if (shaderstate.mode == BEM_STENCIL) @@ -501,7 +501,7 @@ static void SWBE_SubmitMeshesSortList(batch_t *sortlist) if (batch->shader->flags & SHADER_NODRAW) continue; if (batch->shader->flags & SHADER_NODLIGHT) - if (shaderstate.mode == BEM_LIGHT || shaderstate.mode == BEM_SMAPLIGHT) + if (shaderstate.mode == BEM_LIGHT) continue; if (batch->shader->flags & SHADER_SKY) { @@ -606,7 +606,7 @@ void SWBE_DrawWorld(qboolean drawworld, qbyte *vis) shaderstate.wbatch = 0; } - BE_GenModelBatches(batches); + BE_GenModelBatches(batches, NULL, shaderstate.mode); // R_GenDlightBatches(batches); shaderstate.curentity = NULL; diff --git a/engine/web/gl_vidweb.c b/engine/web/gl_vidweb.c index dc4c678f..bf116ab8 100644 --- a/engine/web/gl_vidweb.c +++ b/engine/web/gl_vidweb.c @@ -23,7 +23,7 @@ static void VID_Resized(int width, int height) extern cvar_t vid_conautoscale, vid_conwidth; vid.pixelwidth = width; vid.pixelheight = height; -Con_Printf("Resized: %i %i\n", vid.pixelwidth, vid.pixelheight); +//Con_Printf("Resized: %i %i\n", vid.pixelwidth, vid.pixelheight); Cvar_ForceCallback(&vid_conautoscale); Cvar_ForceCallback(&vid_conwidth); diff --git a/plugins/Makefile b/plugins/Makefile index c757ffef..a0667aec 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -19,6 +19,18 @@ PLUG_CFLAGS= $(OUT_DIR)/fteplug_avplug$(PLUG_NATIVE_EXT): avplug/libavformat/avformat.h endif +#cygwin uses dll naming. +ifeq ($(FTE_TARGET),cygwin) +ifeq ($(BITS),64) +PLUG_DEFFILE=plugin.def +PLUG_NATIVE_EXT=amd.dll +endif +ifneq ($(BITS),64) +PLUG_DEFFILE=plugin.def +PLUG_NATIVE_EXT=x86.dll +endif +endif + #if they're not on windows, we'll try asking the compiler directly #the check to see if its already set is to avoid asking msvc, which would probably break things. ifeq ($(PLUG_NATIVE_EXT),) @@ -103,8 +115,8 @@ $(OUT_DIR)/fteplug_mpq$(PLUG_NATIVE_EXT): mpq/fs_mpq.c mpq/blast.c plugin.c qvm_ $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -o $(OUT_DIR)/fteplug_mpq$(PLUG_NATIVE_EXT) -shared $(PLUG_CFLAGS) -Impq $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) native: $(OUT_DIR)/fteplug_mpq$(PLUG_NATIVE_EXT) -$(OUT_DIR)/fteplug_xmpp$(PLUG_NATIVE_EXT): jabber/jabberclient.c jabber/jingle.c jabber/xml.c plugin.c qvm_api.c ../engine/common/sha1.c emailnot/md5.c - $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -o $(OUT_DIR)/fteplug_xmpp$(PLUG_NATIVE_EXT) -shared $(PLUG_CFLAGS) -Ijabber $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) +$(OUT_DIR)/fteplug_xmpp$(PLUG_NATIVE_EXT): jabber/jabberclient.c jabber/jingle.c jabber/sift.c jabber/xml.c plugin.c qvm_api.c ../engine/common/sha1.c emailnot/md5.c + $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -o $(OUT_DIR)/fteplug_xmpp$(PLUG_NATIVE_EXT) -shared $(PLUG_CFLAGS) -Ijabber $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) -lresolv native: $(OUT_DIR)/fteplug_xmpp$(PLUG_NATIVE_EXT) diff --git a/plugins/avplug/avdecode.c b/plugins/avplug/avdecode.c index 6a70b1dd..f9543b67 100644 --- a/plugins/avplug/avdecode.c +++ b/plugins/avplug/avdecode.c @@ -189,6 +189,28 @@ static void *AVDec_Create(char *medianame) ctx->pFormatCtx->pb = ioctx; } + /* +small how-to note for if I ever try to add support for voice-and-video rtp decoding. +this stuff is presumably needed to handle ICE+stun+ports etc. +I prolly need to hack around with adding rtcp too. :s + +rtsp: Add support for depacketizing RTP data via custom IO + +To use this, set sdpflags=custom_io to the sdp demuxer. During +the avformat_open_input call, the SDP is read from the AVFormatContext +AVIOContext (ctx->pb) - after the avformat_open_input call, +during the av_read_frame() calls, the same ctx->pb is used for reading +packets (and sending back RTCP RR packets). + +Normally, one would use this with a read-only AVIOContext for the +SDP during the avformat_open_input call, then close that one and +replace it with a read-write one for the packets after the +avformat_open_input call has returned. + +This allows using the RTP depacketizers as "pure" demuxers, without +having them tied to the libavformat network IO. + */ + // Open video file if(avformat_open_input(&ctx->pFormatCtx, medianame, NULL, NULL)==0) diff --git a/plugins/emailnot/md5.c b/plugins/emailnot/md5.c index 5802a5c5..3a155ce2 100644 --- a/plugins/emailnot/md5.c +++ b/plugins/emailnot/md5.c @@ -393,8 +393,6 @@ char *MD5_ToHex(char *input, int inputlen, char *ret, int retlen) } char *MD5_ToBinary(char *input, int inputlen, char *ret, int retlen) { - int v, i; - unsigned char digest[16]; MD5_CTX ctx; if (retlen < 16) diff --git a/plugins/jabber/jabbercl.vcproj b/plugins/jabber/jabbercl.vcproj index 2b64bac2..62b73bf4 100644 --- a/plugins/jabber/jabbercl.vcproj +++ b/plugins/jabber/jabbercl.vcproj @@ -41,6 +41,7 @@ PreprocessorDefinitions="FTEPLUGIN" EnableFunctionLevelLinking="true" BrowseInformation="1" + WarningLevel="3" DebugInformationFormat="4" /> + + diff --git a/plugins/jabber/jabberclient.c b/plugins/jabber/jabberclient.c index 3ef88bac..cdc0cf82 100644 --- a/plugins/jabber/jabberclient.c +++ b/plugins/jabber/jabberclient.c @@ -8,6 +8,8 @@ Network limitations: auth mechanism: oauth2(tls+nontls) or plain(tls-only). no digests supported, so mitm can easily grab your password if they use certificate authority hackery, so DO NOT log in from work. oauth2: I've registered a clientid for use with googletalk's network, but the whole web-browser-is-required crap makes it near unusable. We'll try it if they omit a password. otherwise a complete implementation. + other users appear unresponsive and permanently away. this is a wtf on google's part and not something I can trivially work around. these people are really offline but have previously used 'google hangouts', and google insist on the UI nightmare infecting other clients too. + appears to hack avatar vcards into all presence messages, which is just an interesting thing to note as it seems to keep fucking up resulting in extra queries for avatar images. facebook: username: foobar@chat.facebook.com @@ -16,6 +18,7 @@ Network limitations: no roster control completely untested. I've no interest in signing up to be tracked constantly (but somehow google is okay... go figure... I guess I'm just trying to avoid a double-whammy) oauth2: no idea where to register a clientid, or what the correct addresses are. a google search implies they don't do refresh tokens properly. sticking with digest-md5 should work. + *should* work for chat. msn: username: foobar@messenger.live.com (NOT foobar@live.com - this will timeout) @@ -35,13 +38,21 @@ Network limitations: may have self-signed certificate issues, depends on installation. client compat: + hangouts: + UI nightmare infects the entire network and thus other clients also. + voip not supported. does not advertise any extensions and thus no voip. + no file transfer support. + googletalk: - implements old version of jingle. voice calls not compatible. + impossible to download from google any more. completely unsupported. + implements old version of jingle. voice calls appear to not work. + does not support SI file transfer. not tested by me. pidgin: - (linux) has issues with jingle+ice, and can be made to crash. voip uses speex. pidgin's ice seems vulnerable to dropped packets. + (linux) has issues with jingle+ice, and can easily be made to crash. voip uses speex. pidgin's ice seems vulnerable to dropped packets. (windows) doesn't support voice calls + file transfer works. otherwise works. */ @@ -240,9 +251,94 @@ qboolean NET_DNSLookup_SRV(char *host, char *out, int outlen) return false; } #else +#include +#include qboolean NET_DNSLookup_SRV(char *host, char *out, int outlen) { - return false; + int questions; + int answers; + qbyte answer[512]; + qbyte dname[512]; + int len, i; + static qboolean inited; + qbyte *msg, *eom, *cp; + + len = res_query(host, C_IN, T_SRV, &answer, sizeof(answer)); + if (len < 12) + { + Con_Printf("srv lookup failed for %s\n", host); + return false; + } + + eom = answer+len; + + questions = (answer[4]<<8) | answer[5]; + answers = (answer[6]<<8) | answer[7]; +// id @ 0 +// bits @ 2 +// questioncount@4 +/// answer count@6 +// nameserver record count @8 +// additional record count @10 + +// questions@12 +// answers@12+sizeof(questions) + + if (answers < 1) + return false; + + msg = answer+12; + + while(questions --> 0) + { + dn_expand(answer, eom, msg, dname, sizeof(dname)); +// Con_Printf("Skip question %s\n", dname); + i = dn_skipname(msg, eom); + if (i <= 0) + return false; + msg += i; + msg += 2;//query type + msg += 2;//query class + } + + while(answers --> 0) + { + i = dn_expand(answer, eom, msg, dname, sizeof(dname)); +// i = dn_skipname(msg, eom); + msg += i; + msg += 2;//query type + msg += 2;//query class + msg += 4;//ttl + i = (msg[0]<<8) | msg[1]; + msg+=2; + //noone tried to send the wrong type then, woo. + if (!strcmp(dname, host)) + { + int port; + //we're not serving to other dns servers, and it seems they're already getting randomized, so just grab the first without rerandomizing. + msg += 2;//priority + msg += 2;//weight + port = (msg[0]<<8) | msg[1]; + msg += 2;//port + dn_expand(answer, eom, msg, dname, sizeof(dname)); + Q_snprintf(out, outlen, "[%s]:%i", dname, port); +// Con_Printf("Resolved to %s\n", out); + return true; + } + dn_expand(answer, eom, msg, out, outlen); +// Con_Printf("Ignoring resolution to %s\n", out); + msg += i; + } + +//type (2 octets) +//class (2 octets) +//TTL (4 octets) +//resource data length (2 octets) +//resource data (variable length) + + if (i < 0) + return false; + return true; } #endif @@ -291,7 +387,7 @@ void Base64_Add(char *s, int len) Base64_Byte(*us++); } -void Base64_Finish(void) +char *Base64_Finish(void) { //output is always a multiple of four @@ -323,6 +419,8 @@ void Base64_Finish(void) base64_len = 0; //for next time (use strlen) base64_bits = 0; base64_cur = 0; + + return base64; } //decode a base64 byte to a 0-63 value. Cannot cope with =. @@ -340,6 +438,7 @@ static int Base64_DecodeByte(char byt) return 63; return -1; } +//FIXME: we should be able to skip whitespace. int Base64_Decode(char *out, int outlen, char *src, int srclen) { int len = 0; @@ -385,6 +484,24 @@ void JCL_Command(int accid, char *consolename); void JCL_LoadConfig(void); void JCL_WriteConfig(void); +struct { + char *names; + unsigned int cap; +} capnames[] = +{ + {"avatars", CAP_AVATARS}, + {"jingle_voice", CAP_VOICE}, + {"jingle_video", CAP_VIDEO}, + {"google_voice", CAP_GOOGLE_VOICE}, + {"quake_invite", CAP_GAMEINVITE}, + {"poke", CAP_POKE}, +#ifdef FILETRANSFERS + {"si_filetransfer", CAP_SIFT}, +#endif + {NULL} +}; + + qintptr_t JCL_ExecuteCommand(qintptr_t *args) { char cmd[256]; @@ -405,6 +522,7 @@ qintptr_t JCL_ExecuteCommand(qintptr_t *args) } qintptr_t JCL_ConsoleLink(qintptr_t *args); +qintptr_t JCL_ConsoleLinkMouseOver(qintptr_t *args); qintptr_t JCL_ConExecuteCommand(qintptr_t *args); qintptr_t JCL_Frame(qintptr_t *args); @@ -423,6 +541,7 @@ qintptr_t Plug_Init(qintptr_t *args) Con_Printf("XMPP Plugin Loaded. For help, use: ^[/"COMMANDPREFIX" /help^]\n"); Plug_Export("ConsoleLink", JCL_ConsoleLink); + Plug_Export("ConsoleLinkMouseOver", JCL_ConsoleLinkMouseOver); if (!Plug_Export("ConExecuteCommand", JCL_ConExecuteCommand)) { @@ -700,7 +819,7 @@ static int sasl_scramsha1_challenge(jclient_t *jcl, char *in, int inlen, char *o //sasl SCRAM-SHA-1 challenge //send back the same 'r' attribute buf_t saslchal; - int l, i; + int i; buf_t salt; buf_t csn; buf_t itr; @@ -708,22 +827,12 @@ static int sasl_scramsha1_challenge(jclient_t *jcl, char *in, int inlen, char *o buf_t sigkey; char salted_password[20]; char proof[20]; - char proof64[30]; char clientkey[20]; char storedkey[20]; char clientsignature[20]; char *username = jcl->username; char *password = jcl->password; -#if 0 - /*hack zone*/ - in = "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096"; - inlen = strlen(in); - strcpy(jcl->authnonce, "wvDh8bTUrSc=");//"fyko+d2lbbFgONRv9qkxdawL"); - username = "user"; - password = "pencil"; - //should result in "c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=" -#endif saslchal.len = 0; buf_cat(&saslchal, in, inlen); @@ -734,7 +843,7 @@ static int sasl_scramsha1_challenge(jclient_t *jcl, char *in, int inlen, char *o salt.len = Base64_Decode(salt.buf, sizeof(salt.buf), salt.buf, salt.len); - //FIXME: should we validate that csn is prefixed with our cnonce? + //FIXME: we should validate that csn is prefixed with our cnonce //this is the first part of the message we're about to send, with no proof. //c(channel) is mandatory but nulled and forms part of the hash @@ -837,7 +946,6 @@ void Q_strlcat_urlencode(char *d, const char *s, int n) } static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) { - char msg[4096]; char proto[256]; char host[256]; char resource[256]; @@ -922,7 +1030,7 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) Q_strlcat(url, "&login_hint=", sizeof(url)); Q_strlcat_urlencode(url, jcl->oauth2.useraccount, sizeof(url)); - Con_Printf("Please visit ^[^4%s\\url\\%s^] and then enter:\n^[/"COMMANDPREFIX"%i /oa2token ^]\nNote: you can right-click the link to copy it to your browser, and you can use ctrl+v to paste the resulting auth token as part of the given command.", url, url, jcl->accountnum); + Con_Printf("Please visit ^[^4%s\\url\\%s^] and then enter:\n^[/"COMMANDPREFIX"%i /oa2token ^]\nNote: you can right-click the link to copy it to your browser, and you can use ctrl+v to paste the resulting auth token as part of the given command.\n", url, url, jcl->accountnum); //wait for user to act. return -2; @@ -989,7 +1097,7 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) if (l < 0 || l > rl) l = rl; x = XML_FromJSON(NULL, "oauth2", result, &l, rl); - XML_ConPrintTree(x, 1); + XML_ConPrintTree(x, "", 1); free(jcl->oauth2.accesstoken); free(jcl->oauth2.refreshtoken); jcl->oauth2.accesstoken = strdup(XML_GetChildBody(x, "access_token", "")); @@ -1054,7 +1162,7 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) if (l < 0 || l > rl) l = rl; x = XML_FromJSON(NULL, "oauth2", result, &l, rl); - XML_ConPrintTree(x, 1); + XML_ConPrintTree(x, "", 1); newrefresh = XML_GetChildBody(x, "refresh_token", NULL); free(jcl->oauth2.accesstoken); @@ -1114,12 +1222,13 @@ https://oauth.live.com/authorize?client_id=000000004C07035A&scope=wl.messenger,w struct subtree_s; void JCL_AddClientMessagef(jclient_t *jcl, char *fmt, ...); -qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres); +qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres, qboolean create); void JCL_GeneratePresence(jclient_t *jcl, qboolean force); struct iq_s *JCL_SendIQf(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, struct subtree_s *tree, struct iq_s *iq), char *iqtype, char *target, char *fmt, ...); struct iq_s *JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree, struct iq_s *iq), char *iqtype, char *target, xmltree_t *node, qboolean destroynode); void JCL_CloseConnection(jclient_t *jcl, qboolean reconnect); void JCL_JoinMUCChat(jclient_t *jcl, char *room, char *server, char *myhandle, char *password); +static qboolean JCL_BuddyVCardReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq); void JCL_GenLink(jclient_t *jcl, char *out, int outlen, char *action, char *context, char *contextres, char *sid, char *txtfmt, ...) { @@ -1127,10 +1236,12 @@ void JCL_GenLink(jclient_t *jcl, char *out, int outlen, char *action, char *cont qboolean textonly = false; *out = 0; if (!strchr(txtfmt, '%')) - { + { //protect against potential bugs and exploits. Q_strlcpy(out, "bad link text", outlen); return; } + //FIXME: validate that there is no \\ markup within any section that would break the link. + //FIXME: validate that ^[ and ^] are not used, as that would also mess things up. add markup for every ^? if (textonly) { va_start(argptr, txtfmt); @@ -1189,10 +1300,121 @@ char *TrimResourceFromJid(char *jid) return NULL; } +qintptr_t JCL_ConsoleLinkMouseOver(qintptr_t *args) +{ + jclient_t *jcl; +// char text[256]; + char link[256]; + char who[256]; + char what[256]; + char which[256]; + char *actiontext; + int i; + buddy_t *b, *me; + bresource_t *br; + float x = *(float*)&args[0]; + float y = *(float*)&args[1]; + +// pCmd_Argv(0, text, sizeof(text)); + pCmd_Argv(1, link, sizeof(link)); + + JCL_Info_ValueForKey(link, "xmpp", who, sizeof(who)); + JCL_Info_ValueForKey(link, "xmppact", what, sizeof(what)); + JCL_Info_ValueForKey(link, "xmppacc", which, sizeof(which)); + + if (!*who) + return false; + + i = atoi(which); + i = bound(0, i, sizeof(jclients)/sizeof(jclients[0])); + jcl = jclients[i]; + + x += 16; + + if (!jcl) + return false; + + if (jcl->status != JCL_ACTIVE) + { + pDraw_String(x, y, "^&C0You are currently offline"); + return true; + } + + if (!strcmp(what, "pauth")) + actiontext = "Befriend"; + else if (!strcmp(what, "pdeny")) + actiontext = "Decline"; +#ifdef FILETRANSFERS + else if (!strcmp(what, "fauth") && (jcl->enabledcapabilities & CAP_SIFT)) + actiontext = "Receive"; + else if (!strcmp(what, "fdeny") && (jcl->enabledcapabilities & CAP_SIFT)) + actiontext = "Decline"; +#endif +#ifdef JINGLE + else if (!strcmp(what, "jauth") && (jcl->enabledcapabilities & (CAP_GAMEINVITE|CAP_VOICE|CAP_VIDEO|CAP_GOOGLE_VOICE))) + actiontext = "Answer"; + else if (!strcmp(what, "jdeny") && (jcl->enabledcapabilities & (CAP_GAMEINVITE|CAP_VOICE|CAP_VIDEO|CAP_GOOGLE_VOICE))) + actiontext = "Hang Up"; + else if (!strcmp(what, "join") && (jcl->enabledcapabilities & CAP_GAMEINVITE)) + actiontext = "Join Game"; + else if (!strcmp(what, "invite") && (jcl->enabledcapabilities & CAP_GAMEINVITE)) + actiontext = "Invite To Game"; + else if (!strcmp(what, "call") && (jcl->enabledcapabilities & (CAP_VOICE|CAP_GOOGLE_VOICE))) + actiontext = "Call"; + else if (!strcmp(what, "vidcall") && (jcl->enabledcapabilities & CAP_VIDEO)) + actiontext = "Video Call"; +#endif + else if (!strcmp(what, "mucjoin")) + actiontext = "Join Chat:"; + else if ((*who && !*what) || !strcmp(what, "msg")) + actiontext = "Chat With"; + else + return false; + + JCL_FindBuddy(jcl, who, &b, &br, false); + if (!b) + return false; + JCL_FindBuddy(jcl, jcl->jid, &me, NULL, true); + + if (jcl->enabledcapabilities & CAP_AVATARS) + { + if (b->vcardphotochanged && b->friended) + { + b->vcardphotochanged = false; + Con_DPrintf("Querying %s's photo\n", b->accountdomain); + JCL_SendIQf(jcl, JCL_BuddyVCardReply, "get", b->accountdomain, ""); + } + if (b->image) + { + //xep-0153: The image height and width SHOULD be between thirty-two (32) and ninety-six (96) pixels; the recommended size is sixty-four (64) pixels high and sixty-four (64) pixels wide. + //96 just feels far too large for a game that was origionally running at a resolution of 320*200. + //FIXME: we should proably respect the image's aspect ratio... +#define IMGSIZE 96/2 + pDraw_Image (x, y, IMGSIZE, IMGSIZE, 0, 0, 1, 1, b->image); + x += IMGSIZE+8; + } + } + + pDraw_String(x, y, va("^&F0%s ^2%s", actiontext, b->name)); + y+=8; + pDraw_String(x, y, va("^&F0%s", b->accountdomain)); + y+=8; + if (br) + { + pDraw_String(x, y, va("^&F0 %s", br->resource)); + y+=8; + } + if (b == me) + pDraw_String(x, y, "^&90" "You"); + else if (!b->friended) + pDraw_String(x, y, "^&C0" "Unknown"); + y+=8; + + return true; +} qintptr_t JCL_ConsoleLink(qintptr_t *args) { jclient_t *jcl; - char text[256]; char link[256]; char who[256]; char what[256]; @@ -1226,54 +1448,61 @@ qintptr_t JCL_ConsoleLink(qintptr_t *args) return true; } #ifdef FILETRANSFERS - else if (!strcmp(what, "fauth")) + else if (!strcmp(what, "fauth") && (jcl->enabledcapabilities & CAP_SIFT)) { JCL_Info_ValueForKey(link, "xmppsid", what, sizeof(what)); if (jcl && jcl->status == JCL_ACTIVE) - JCL_FT_AcceptFile(jcl, atoi(what), true); + XMPP_FT_AcceptFile(jcl, atoi(what), true); return true; } - else if (!strcmp(what, "fdeny")) + else if (!strcmp(what, "fdeny") && (jcl->enabledcapabilities & CAP_SIFT)) { JCL_Info_ValueForKey(link, "xmppsid", what, sizeof(what)); if (jcl && jcl->status == JCL_ACTIVE) - JCL_FT_AcceptFile(jcl, atoi(what), false); + XMPP_FT_AcceptFile(jcl, atoi(what), false); return true; } #endif #ifdef JINGLE - else if (!strcmp(what, "jauth")) + //jauth/jdeny are used to accept/cancel all jingle/gingle content types. + else if (!strcmp(what, "jauth") && (jcl->enabledcapabilities & (CAP_VOICE|CAP_VIDEO|CAP_GAMEINVITE|CAP_GOOGLE_VOICE))) { JCL_Info_ValueForKey(link, "xmppsid", what, sizeof(what)); if (jcl && jcl->status == JCL_ACTIVE) JCL_Join(jcl, who, what, true, ICEP_INVALID); return true; } - else if (!strcmp(what, "jdeny")) + else if (!strcmp(what, "jdeny") && (jcl->enabledcapabilities & (CAP_VOICE|CAP_VIDEO|CAP_GAMEINVITE|CAP_GOOGLE_VOICE))) { JCL_Info_ValueForKey(link, "xmppsid", what, sizeof(what)); if (jcl && jcl->status == JCL_ACTIVE) JCL_Join(jcl, who, what, false, ICEP_INVALID); return true; } - else if (!strcmp(what, "join")) + else if (!strcmp(what, "join") && (jcl->enabledcapabilities & CAP_GAMEINVITE)) { if (jcl && jcl->status == JCL_ACTIVE) JCL_Join(jcl, who, NULL, true, ICEP_QWCLIENT); return true; } - else if (!strcmp(what, "invite")) + else if (!strcmp(what, "invite") && (jcl->enabledcapabilities & CAP_GAMEINVITE)) { if (jcl && jcl->status == JCL_ACTIVE) JCL_Join(jcl, who, NULL, true, ICEP_QWSERVER); return true; } - else if (!strcmp(what, "call")) + else if (!strcmp(what, "call") && (jcl->enabledcapabilities & (CAP_VOICE|CAP_GOOGLE_VOICE))) { if (jcl && jcl->status == JCL_ACTIVE) JCL_Join(jcl, who, NULL, true, ICEP_VOICE); return true; } + else if (!strcmp(what, "vidcall") && (jcl->enabledcapabilities & (CAP_VIDEO))) + { + if (jcl && jcl->status == JCL_ACTIVE) + JCL_Join(jcl, who, NULL, true, ICEP_VIDEO); + return true; + } #endif else if (!strcmp(what, "mucjoin")) { //conference/chat join @@ -1288,14 +1517,16 @@ qintptr_t JCL_ConsoleLink(qintptr_t *args) buddy_t *b; bresource_t *br; - JCL_FindBuddy(jcl, *who?who:jcl->defaultdest, &b, &br); - f = b->name; - b->defaultresource = br; + if (JCL_FindBuddy(jcl, *who?who:jcl->defaultdest, &b, &br, true)) + { + f = b->name; + b->defaultresource = br; - if (BUILTINISVALID(Con_SubPrint)) - pCon_SubPrint(f, ""); - if (BUILTINISVALID(Con_SetActive)) - pCon_SetActive(f); + if (BUILTINISVALID(Con_SubPrint)) + pCon_SubPrint(f, ""); + if (BUILTINISVALID(Con_SetActive)) + pCon_SetActive(f); + } } return true; } @@ -1368,7 +1599,7 @@ void JCL_FlushOutgoing(jclient_t *jcl) jcl->outbuflen -= sent; } else if (sent < 0) - Con_Printf("Error sending\n"); + Con_Printf("XMPP: Error sending\n"); // else // Con_Printf("Unable to send anything\n"); } @@ -1563,6 +1794,7 @@ jclient_t *JCL_ConnectXML(xmltree_t *acc) }; jclient_t *jcl; xmltree_t *oauth2; + xmltree_t *features; char oauthname[256]; jcl = malloc(sizeof(jclient_t)); @@ -1572,11 +1804,14 @@ jclient_t *JCL_ConnectXML(xmltree_t *acc) memset(jcl, 0, sizeof(jclient_t)); jcl->socket = -1; + jcl->enabledcapabilities = CAP_DEFAULTENABLEDCAPS; + jcl->accountnum = atoi(XML_GetParameter(acc, "id", "1")); //make sure dependant properties are listed beneath their dependancies... jcl->forcetls = atoi(XML_GetChildBody(acc, "forcetls", "1")); - jcl->streamdebug = !!atoi(XML_GetChildBody(acc, "streamdebug", "0")); + jcl->streamdebug = atoi(XML_GetChildBody(acc, "streamdebug", "0")); + jcl->streamdebug = bound(0, jcl->streamdebug, 2); Q_strlcpy(jcl->serveraddr, XML_GetChildBody(acc, "serveraddr", ""), sizeof(jcl->serveraddr)); jcl->serverport = atoi(XML_GetChildBody(acc, "serverport", (jcl->forcetls==2)?"5223":"5222")); Q_strlcpy(jcl->username, XML_GetChildBody(acc, "username", "user"), sizeof(jcl->username)); @@ -1634,12 +1869,31 @@ jclient_t *JCL_ConnectXML(xmltree_t *acc) jcl->allowauth_scramsha1 = atoi(XML_GetChildBody(acc, "allowauth_scram_sha_1", "1")); jcl->allowauth_oauth2 = atoi(XML_GetChildBody(acc, "allowauth_oauth2", jcl->oauth2.saslmethod?"1":"0")); + jcl->savepassword = atoi(XML_GetChildBody(acc, "savepassword", "0")); + + features = XML_ChildOfTree(acc, "features", 0); + if (features && XML_GetParameter(features, "ver", JCL_BUILD)) + { + char *val; + int j; + for (j = 0; capnames[j].names; j++) + { + val = XML_GetChildBody(features, capnames[j].names, NULL); + if (val) + { + if (atoi(val)) + jcl->enabledcapabilities |= capnames[j].cap; + else + jcl->enabledcapabilities &= ~capnames[j].cap; + } + } + } + return jcl; } jclient_t *JCL_Connect(int accnum, char *server, int forcetls, char *account, char *password) { - char srvserver[256]; char gamename[64]; jclient_t *jcl; char *domain; @@ -1743,7 +1997,7 @@ void JCL_ForgetBuddy(jclient_t *jcl, buddy_t *buddy, bresource_t *bres) } //FIXME: add flags to avoid creation -qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres) +qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres, qboolean create) { char name[256]; char *res; @@ -1766,12 +2020,13 @@ qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t * if (!strcmp(b->accountdomain, name)) break; } - if (!b) + if (!b && create) { b = malloc(sizeof(*b) + strlen(name)); memset(b, 0, sizeof(*b)); b->next = jcl->buddies; jcl->buddies = b; +// b->vcardphotochanged = true; //don't know what it is, query their photo as needed. google sucks, and things stop working. strcpy(b->accountdomain, name); Q_strlcpy(b->name, name, sizeof(b->name)); //default } @@ -1783,7 +2038,7 @@ qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t * if (!strcmp(r->resource, res)) break; } - if (!r) + if (!r && create) { r = malloc(sizeof(*r) + strlen(res)); memset(r, 0, sizeof(*r)); @@ -1795,7 +2050,10 @@ qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t * } else if (bres) *bres = NULL; - return false; + + if (bres) + return *bres != NULL; + return *buddy != NULL; } void JCL_IQTimeouts(jclient_t *jcl) @@ -1873,6 +2131,71 @@ struct iq_s *JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl XML_Destroy(node); return n; } + +qboolean XMPP_NewGoogleMailsReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) +{ + int i, j; + xmltree_t *mailbox = XML_ChildOfTreeNS(tree, "google:mail:notify", "mailbox", 0); +// char *url = XML_GetParameter(mailbox, "url", ""); +// int totalmatched = atoi(XML_GetParameter(mailbox, "total-matched", "0")); +// char *resulttime = XML_GetParameter(mailbox, "result-time", ""); + xmltree_t *mailthread; + xmltree_t *senders, *sender; + char *subject; + char *sendername; + char *cleanup; + char *mailurl; + + for (i = 0; ; i++) + { + mailthread = XML_ChildOfTree(mailbox, "mail-thread-info", i); + if (!mailthread) + break; + +// tid = XML_GetParameter(mailthread, "tid", ""); + mailurl = XML_GetParameter(mailthread, "url", ""); +// participation = XML_GetParameter(mailthread, "participation", ""); +// messages = XML_GetParameter(mailthread, "messages", ""); +// date = XML_GetParameter(mailthread, "date", ""); +// labels = XML_GetChildBody(mailthread, "labels", ""); + subject = XML_GetChildBody(mailthread, "subject", ""); + if (!*subject) + subject = XML_GetChildBody(mailthread, "snippet", ""); + + senders = XML_ChildOfTree(mailthread, "senders", 0); + for (j = 0; ; j++) + { + sender = XML_ChildOfTree(senders, "sender", j); + if (!sender) + break; +// address = XML_GetParameter(sender, "address", ""); + sendername = XML_GetParameter(sender, "name", ""); + if (!*sendername) + sendername = XML_GetParameter(sender, "address", ""); +// originator = XML_GetParameter(sender, "originator", ""); + if (atoi(XML_GetParameter(sender, "unread", "1"))) + { + //we trust the server to not feed us gibberish like \r or \n chars. + //however, other chars may be problematic and could break/hack the link markup. + for (cleanup = sendername; (cleanup = strchr(cleanup, '^')) != NULL; ) + *cleanup = ' '; + for (cleanup = sendername; (cleanup = strchr(cleanup, '\\')) != NULL; ) + *cleanup = '/'; + for (cleanup = subject; (cleanup = strchr(cleanup, '^')) != NULL; ) + *cleanup = ' '; + for (cleanup = subject; (cleanup = strchr(cleanup, '\\')) != NULL; ) + *cleanup = '/'; + for (cleanup = mailurl; (cleanup = strstr(cleanup, "^]")) != NULL; ) + *cleanup = '_'; //FIXME: %5E + for (cleanup = mailurl; (cleanup = strchr(cleanup, '\\')) != NULL; ) + *cleanup = '/'; //FIXME: %5C + Con_Printf("^[^4New spam from %s: %s\\url\\%s^]\n", sendername, subject, mailurl); + } + } + } + return true; +} + static void JCL_RosterUpdate(jclient_t *jcl, xmltree_t *listp) { xmltree_t *i; @@ -1883,7 +2206,7 @@ static void JCL_RosterUpdate(jclient_t *jcl, xmltree_t *listp) char *name = XML_GetParameter(i, "name", ""); char *jid = XML_GetParameter(i, "jid", ""); // char *sub = XML_GetParameter(i, "subscription", ""); - JCL_FindBuddy(jcl, jid, &buddy, NULL); + JCL_FindBuddy(jcl, jid, &buddy, NULL, true); if (*name) Q_strlcpy(buddy->name, name, sizeof(buddy->name)); @@ -1923,68 +2246,189 @@ static qboolean JCL_BindReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) } return false; } -static qboolean JCL_VCardReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) +static qboolean JCL_BuddyVCardReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) { - xmltree_t *vc, *fn, *nickname; + char photodata[65536]; + xmltree_t *vc, *photo, *photobinval; +// char *nickname; + char *photomime; + + buddy_t *b; + char *from = XML_GetParameter(tree, "from", jcl->domain); + + JCL_FindBuddy(jcl, from, &b, NULL, false); + if (!b) + { + Con_DPrintf("unknown vcard from %s\n", from); + return false; + } + + vc = XML_ChildOfTree(tree, "vCard", 0); + if (!vc) + { + Con_DPrintf("invalid vcard from %s\n", from); + return false; + } +// nickname = XML_GetChildBody(vc, "NICKNAME", NULL); +// if (nickname) +// Q_strlcpy(b->name, nickname, sizeof(b->name)); + + photo = XML_ChildOfTree(vc, "PHOTO", 0); + photobinval = XML_ChildOfTree(photo, "BINVAL", 0); + + if (jcl->enabledcapabilities & CAP_AVATARS) + { + if (photobinval) + { + unsigned int photosize = Base64_Decode(photodata, sizeof(photodata), photobinval->body, strlen(photobinval->body)); + photomime = XML_GetChildBody(photo, "TYPE", ""); + //xep-0153: If the is something other than image/gif, image/jpeg, or image/png, it SHOULD be ignored. + if (strcmp(photomime, "image/png") && strcmp(photomime, "image/jpeg") && strcmp(photomime, "image/gif")) + photomime = ""; + b->image = pDraw_LoadImageData(va("xmpp/%s", b->accountdomain), photomime, photodata, photosize); + Con_DPrintf("vcard photo updated from %s\n", from); + } + else + { + b->image = pDraw_LoadImageData(va("xmpp/%s", b->accountdomain), "", NULL, 0); + Con_DPrintf("vcard photo invalidated from %s\n", from); + } + } + else + Con_DPrintf("vcard photo ignored from %s\n", from); + + return true; +} +static qboolean JCL_MyVCardReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) +{ + char photodata[65536]; + char digest[20]; + xmltree_t *vc, *fn, *nickname, *photo, *photobinval; + + //make sure our image is loaded etc + JCL_BuddyVCardReply(jcl, tree, iq); + vc = XML_ChildOfTree(tree, "vCard", 0); fn = XML_ChildOfTree(vc, "FN", 0); nickname = XML_ChildOfTree(vc, "NICKNAME", 0); + photo = XML_ChildOfTree(vc, "PHOTO", 0); + photobinval = XML_ChildOfTree(photo, "BINVAL", 0); + if (!tree || !photobinval) + { + //server doesn't support vcards? + if (jcl->vcardphotohashstatus != VCP_NONE) + { + jcl->vcardphotohashstatus = VCP_NONE; + jcl->vcardphotohashchanged = true; + *jcl->vcardphotohash = 0; + } + } + else + { + char *hex = "0123456789abcdef"; + int photosize; + int digestsize; + photosize = Base64_Decode(photodata, sizeof(photodata), photobinval->body, strlen(photobinval->body)); + digestsize = SHA1(digest, sizeof(digest), photodata, photosize); + if (jcl->vcardphotohashstatus != VCP_KNOWN || memcmp(jcl->vcardphotohash, digest, sizeof(jcl->vcardphotohash))) + { + memcpy(jcl->vcardphotohash, digest, sizeof(jcl->vcardphotohash)); + jcl->vcardphotohashchanged = true; + jcl->vcardphotohashstatus = VCP_KNOWN; + } + } + if (nickname && *nickname->body) Q_strlcpy(jcl->localalias, nickname->body, sizeof(jcl->localalias)); else if (fn && *fn->body) Q_strlcpy(jcl->localalias, fn->body, sizeof(jcl->localalias)); return true; } +static qboolean JCL_ServerFeatureReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) +{ + xmltree_t *query = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/disco#info", "query", 0); + xmltree_t *feature; + char *featurename; + int f; + qboolean gmail = false; + + if (!query) + return false; + + for (f = 0; ; f++) + { + feature = XML_ChildOfTree(query, "feature", f); + if (!feature) + break; + featurename = XML_GetParameter(feature, "var", ""); + if (!strcmp(featurename, "google:mail:notify")) + gmail = true; + else + { + Con_DPrintf("Server supports feature %s\n", featurename); + } + } + + if (gmail) + JCL_SendIQf(jcl, XMPP_NewGoogleMailsReply, "get", NULL, ""); + + return true; +} static qboolean JCL_SessionReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) { JCL_SendIQf(jcl, JCL_RosterReply, "get", NULL, ""); - JCL_SendIQf(jcl, JCL_VCardReply, "get", NULL, ""); + JCL_SendIQf(jcl, JCL_MyVCardReply, "get", NULL, ""); + JCL_SendIQf(jcl, JCL_ServerFeatureReply, "get", jcl->domain, ""); return true; } - -static char *caps[] = +static struct +{ + char *name; + unsigned int withcap; +} caps[] = { #if 1 - "http://jabber.org/protocol/caps", - "http://jabber.org/protocol/disco#info", -// "http://jabber.org/protocol/disco#items", + {"http://jabber.org/protocol/caps"}, + {"http://jabber.org/protocol/disco#info"}, +// {"http://jabber.org/protocol/disco#items"}, - "jabber:iq:version", + {"jabber:iq:version"}, #ifdef JINGLE - "urn:xmpp:jingle:1", - QUAKEMEDIAXMLNS, + {"urn:xmpp:jingle:1", CAP_GAMEINVITE|CAP_VOICE|CAP_VIDEO}, + {QUAKEMEDIAXMLNS, CAP_GAMEINVITE}, #ifdef VOIP #ifdef VOIP_LEGACY - "http://www.google.com/xmpp/protocol/session", //so google's non-standard clients can chat with us - "http://www.google.com/xmpp/protocol/voice/v1", //so google's non-standard clients can chat with us - // "http://www.google.com/xmpp/protocol/camera/v1", //can send video - // "http://www.google.com/xmpp/protocol/video/v1", //can receive video + {"http://www.google.com/xmpp/protocol/session", CAP_GOOGLE_VOICE}, //so google's non-standard clients can chat with us + {"http://www.google.com/xmpp/protocol/voice/v1", CAP_GOOGLE_VOICE}, //so google's non-standard clients can chat with us + {"http://www.google.com/xmpp/protocol/camera/v1", CAP_GOOGLE_VOICE}, //can send video +// {"http://www.google.com/xmpp/protocol/video/v1", CAP_GOOGLE_VOICE}, //can receive video #endif #ifndef VOIP_LEGACY_ONLY - "urn:xmpp:jingle:apps:rtp:1", - "urn:xmpp:jingle:apps:rtp:audio", + {"urn:xmpp:jingle:apps:rtp:1", CAP_VOICE|CAP_VIDEO}, + {"urn:xmpp:jingle:apps:rtp:audio", CAP_VOICE}, + {"urn:xmpp:jingle:apps:rtp:video", CAP_VIDEO}, #endif #endif //"urn:xmpp:jingle:apps:rtp:video",//we don't support rtp video chat - "urn:xmpp:jingle:transports:raw-udp:1", + {"urn:xmpp:jingle:transports:raw-udp:1", CAP_GAMEINVITE|CAP_VOICE|CAP_VIDEO}, #ifndef NOICE - "urn:xmpp:jingle:transports:ice-udp:1", + {"urn:xmpp:jingle:transports:ice-udp:1", CAP_GAMEINVITE|CAP_VOICE|CAP_VIDEO}, #endif #endif #ifndef Q3_VM - "urn:xmpp:time", + {"urn:xmpp:time"}, #endif - "urn:xmpp:ping", //FIXME: I'm not keen on this. I only added support to stop errors from pidgin when trying to debug. - "urn:xmpp:attention:0", //poke. + {"urn:xmpp:ping"}, //FIXME: I'm not keen on this. I only added support to stop errors from pidgin when trying to debug. + {"urn:xmpp:attention:0"}, //poke. //file transfer #ifdef FILETRANSFERS - "http://jabber.org/protocol/si", - "http://jabber.org/protocol/si/profile/file-transfer", - "http://jabber.org/protocol/ibb", - "http://jabber.org/protocol/bytestreams", + {"http://jabber.org/protocol/si", CAP_SIFT}, + {"http://jabber.org/protocol/si/profile/file-transfer", CAP_SIFT}, + {"http://jabber.org/protocol/ibb", CAP_SIFT}, + {"http://jabber.org/protocol/bytestreams", CAP_SIFT}, #endif #else //for testing, this is the list of features pidgin supports (which is the other client I'm testing against). @@ -2028,17 +2472,19 @@ static char *caps[] = "http://jabber.org/protocol/nick+notify", "http://jabber.org/protocol/ibb", #endif - NULL + {NULL} }; -static void buildcaps(char *out, int outlen) +static void buildcaps(jclient_t *jcl, char *out, int outlen) { int i; Q_strncpyz(out, "", outlen); - for (i = 0; caps[i]; i++) + for (i = 0; caps[i].name; i++) { + if (!(caps[i].withcap & jcl->enabledcapabilities)) + continue; Q_strlcat(out, "", outlen); } } @@ -2049,17 +2495,19 @@ static int qsortcaps(const void *va, const void *vb) return strcmp(a, b); } int SHA1(char *digest, int maxdigestsize, char *string, int stringlen); -char *buildcapshash(void) +char *buildcapshash(jclient_t *jcl) { int i, l; char out[8192]; int outlen = sizeof(out); unsigned char digest[64]; Q_strlcpy(out, "client/pc//FTEQW<", outlen); - qsort(caps, sizeof(caps)/sizeof(caps[0]) - 1, sizeof(char*), qsortcaps); - for (i = 0; caps[i]; i++) + qsort(caps, sizeof(caps)/sizeof(caps[0]) - 1, sizeof(caps[0]), qsortcaps); + for (i = 0; caps[i].name; i++) { - Q_strlcat(out, caps[i], outlen); + if (!(caps[i].withcap & jcl->enabledcapabilities)) + continue; + Q_strlcat(out, caps[i].name, outlen); Q_strlcat(out, "<", outlen); } l = SHA1(digest, sizeof(digest), out, strlen(out)); @@ -2069,6 +2517,61 @@ char *buildcapshash(void) return base64; } +//xep-0115 1.4+ +//xep-0153 +char *buildcapsvcardpresence(jclient_t *jcl, char *caps, size_t sizeofcaps) +{ + char *vcard; + char *voiceext = ""; //xep-0115 1.0 +#ifdef VOIP_LEGACY + if (jcl->enabledcapabilities & CAP_GOOGLE_VOICE) + voiceext = " ext='voice-v1 camera-v1 video-v1'"; +#endif + + Q_snprintf(caps, sizeofcaps, + "" + , buildcapshash(jcl), voiceext); + + //xep-0153 + vcard = caps+strlen(caps); + sizeofcaps -= strlen(caps); + if (jcl->vcardphotohashstatus == VCP_NONE) + { + //let other people know that we don't have one. yay. pointless. whatever. + Q_snprintf(vcard, sizeofcaps, + ""); + } + else if (jcl->vcardphotohashstatus == VCP_KNOWN) + { + char *hex = "0123456789abcdef"; + char inhex[41]; + int i, o; + for (i = 0, o = 0; i < sizeof(jcl->vcardphotohash); i++) + { + inhex[o++] = hex[jcl->vcardphotohash[(i>>4) & 0xf]]; + inhex[o++] = hex[jcl->vcardphotohash[(i>>0) & 0xf]]; + } + inhex[o] = 0; + + //if we know the vcard hash, we must tell other people what it is or if its changed, etc. + Q_snprintf(vcard, sizeofcaps, + "%s", inhex); + } + else + { + //always include a vcard update tag. + //this says that we won't corrupt other resource's vcard. + //note that googletalk seems to hack the current vcard hash anyway. don't test this feature on that network. + Q_snprintf(vcard, sizeofcaps, + ""); + } + return caps; +} + void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) { qboolean unparsable = true; @@ -2081,7 +2584,7 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) //FIXME: block from people who we don't know. id = XML_GetParameter(tree, "id", ""); - from = XML_GetParameter(tree, "from", ""); + from = XML_GetParameter(tree, "from", jcl->domain); // to = XML_GetParameter(tree, "to", ""); f = XML_GetParameter(tree, "type", ""); @@ -2097,8 +2600,8 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) char *node = XML_GetParameter(ot, "node", NULL); unparsable = false; - buildcaps(msg, sizeof(msg)); - Q_snprintf(hashednode, sizeof(hashednode),DISCONODE"#%s", buildcapshash()); + buildcaps(jcl, msg, sizeof(msg)); + Q_snprintf(hashednode, sizeof(hashednode),DISCONODE"#%s", buildcapshash(jcl)); if (!node || !strcmp(node, hashednode)) { @@ -2110,7 +2613,7 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) "", from, id, node?node:hashednode, msg); } #ifdef VOIP_LEGACY - else if (!strcmp(node, DISCONODE"#voice-v1")) + else if (!strcmp(node, DISCONODE"#voice-v1") && (jcl->enabledcapabilities & CAP_GOOGLE_VOICE)) { JCL_AddClientMessagef(jcl, "" @@ -2119,6 +2622,24 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) "" "", from, id, node, msg); } + else if (!strcmp(node, DISCONODE"#camera-v1") && (jcl->enabledcapabilities & CAP_GOOGLE_VOICE)) + { + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "", from, id, node, msg); + } + else if (!strcmp(node, DISCONODE"#video-v1") && (jcl->enabledcapabilities & CAP_GOOGLE_VOICE)) + { + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "", from, id, node, msg); + } #endif else { @@ -2220,7 +2741,7 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) unparsable = false; Con_Printf("Unsupported iq get\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); //tell them OH NOES, instead of requiring some timeout. Q_snprintf(msg, sizeof(msg), @@ -2235,256 +2756,41 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) else if (!strcmp(f, "set")) { xmltree_t *c; + #ifdef FILETRANSFERS -/* - ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/bytestreams", "query", 0); - if (ot) - { - struct ft_s *ft; - char *sid = XML_GetParameter(ot, "sid", ""); - for (ft = jcl->ft; ft; ft = ft->next) - { - if (!strcmp(ft->sid, sid) && !strcmp(ft->with, from)) - { - if (ft->allowed && !ft->begun && ft->transmitting == false) - { - char *jid; - char *host; - int port; - char *req; - char digest[20]; - char domain[41]; - char *hex="0123456789abcdef"; - int j, i; - for (i = 0; ; i++) - { - c = XML_ChildOfTree(ot, "streamhost", i); - if (!c) - break; - jid = XML_GetParameter(c, "jid", ""); - host = XML_GetParameter(c, "host", ""); - port = atoi(XML_GetParameter(c, "port", "0")); - if (port <= 0 || port > 65535) - continue; - ft->stream = pNet_TCPConnect(host, port); - if (ft->stream == -1) - continue; - - //'authenticate' with socks5 proxy. - pNet_Send(ft->stream, "\x05\0x1\x00", 3); - - //sid+requester(them)+target(us) - req = va("%s%s%s", ft->sid, ft->with, jcl->jid); - SHA1(digest, sizeof(digest), req, strlen(req)); - //in hex - for (req = domain, j=0; j < 20; j++) - { - *req++ = hex[(digest[j]>>4) & 0xf]; - *req++ = hex[(digest[j]>>0) & 0xf]; - } - *req = 0; - - //connect with hostname(3). - req = va("\x05\0x1\x00\x03%s\x00\x00", domain); - pNet_Send(ft->stream, req, strlen(domain)+6); - break; - } - } - } - } - } -*/ - ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "open", 0); - if (ot) - { - struct ft_s *ft; - char *sid = XML_GetParameter(ot, "sid", ""); - int blocksize = atoi(XML_GetParameter(ot, "block-size", "4096")); //technically this is required. - char *stanza = XML_GetParameter(ot, "stanza", "iq"); - for (ft = jcl->ft; ft; ft = ft->next) - { - if (!strcmp(ft->sid, sid) && !strcmp(ft->with, from)) - { - if (ft->allowed && !ft->begun && ft->transmitting == false) - { - if (blocksize > 65536 || strcmp(stanza, "iq")) - { //blocksize: MUST NOT be greater than 65535 - JCL_AddClientMessagef(jcl, - "" - "" - "" - "" - "" - , id, from); - } - else if (blocksize > 4096) - { //ask for smaller chunks - JCL_AddClientMessagef(jcl, - "" - "" - "" - "" - "" - , id, from); - } - else - { //it looks okay - pFS_Open(ft->fname, &ft->file, 2); - ft->method = FT_IBB; - ft->blocksize = blocksize; - ft->begun = true; - //if its okay... - JCL_AddClientMessagef(jcl, "", id, from); - } - break; - } - } - } - } - ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "close", 0); - if (ot) - { - struct ft_s **link, *ft; - char *sid = XML_GetParameter(ot, "sid", ""); - for (link = &jcl->ft; *link; link = &(*link)->next) - { - ft = *link; - if (!strcmp(ft->sid, sid) && !strcmp(ft->with, from)) - { - if (ft->begun && ft->method == FT_IBB) - { - int size; - pFS_Close(ft->file); - if (ft->transmitting) - { - if (ft->eof) - Con_Printf("Sent \"%s\" to \"%s\"\n", ft->fname, ft->with); - else - Con_Printf("%s aborted transfer of \"%s\"\n", from, ft->fname); - } - else - { - size = pFS_Open(ft->fname, &ft->file, 1); - pFS_Close(ft->file); - if (size == ft->size) - Con_Printf("Received file \"%s\" successfully\n", ft->fname); - else - Con_Printf("%s aborted transfer of \"%s\"\n", from, ft->fname); - } - *link = ft->next; - free(ft); - //if its okay... - JCL_AddClientMessagef(jcl, "", id, from); - return; - } - } - } - } - ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "data", 0); - if (ot) - { - char block[65536]; - char *sid = XML_GetParameter(ot, "sid", ""); - unsigned short seq = atoi(XML_GetParameter(ot, "seq", "0")); - int blocksize; - struct ft_s *ft; - for (ft = jcl->ft; ft; ft = ft->next) - { - if (!strcmp(ft->sid, sid) && !ft->transmitting) - { - blocksize = Base64_Decode(block, sizeof(block), ot->body, strlen(ot->body)); - if (blocksize && blocksize <= ft->blocksize) - { - pFS_Write(ft->file, block, blocksize); - JCL_AddClientMessagef(jcl, "", id, from); - return; - } - else - Con_Printf("XMPP: Invalid blocksize in file transfer from %s\n", from); - break; - } - } - } - - ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/si", "si", 0); - if (ot) - { - char *profile = XML_GetParameter(ot, "profile", ""); - unparsable = false; - - if (!strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer")) - { - char *s; - xmltree_t *repiq, *repsi, *c; - char *mimetype = XML_GetParameter(ot, "mime-type", "text/plain"); - char *sid = XML_GetParameter(ot, "id", ""); - xmltree_t *file = XML_ChildOfTreeNS(ot, "http://jabber.org/protocol/si/profile/file-transfer", "file", 0); - char *fname = XML_GetParameter(file, "name", "file.txt"); - char *date = XML_GetParameter(file, "date", ""); - char *md5hash = XML_GetParameter(file, "hash", ""); - int fsize = strtoul(XML_GetParameter(file, "size", "0"), NULL, 0); - char *desc = XML_GetChildBody(file, "desc", ""); - char authlink[512]; - char denylink[512]; - - //file transfer offer - struct ft_s *ft = malloc(sizeof(*ft) + strlen(from)+1); - memset(ft, 0, sizeof(*ft)); - ft->next = jcl->ft; - jcl->ft = ft; - ft->privateid = ++jcl->privateidseq; - - ft->transmitting = false; - Q_strlcpy(ft->iqid, id, sizeof(ft->iqid)); - Q_strlcpy(ft->sid, sid, sizeof(ft->sid)); - Q_strlcpy(ft->fname, fname, sizeof(ft->sid)); - Base64_Decode(ft->md5hash, sizeof(ft->md5hash), md5hash, strlen(md5hash)); - ft->size = fsize; - ft->file = -1; - ft->with = (char*)(ft+1); - strcpy(ft->with, from); - ft->method = (fsize > 1024*128)?FT_BYTESTREAM:FT_IBB; //favour bytestreams for large files. for small files, just use ibb as it saves sorting out proxies. - ft->begun = false; - - //FIXME: make bytestreams work. - ft->method = FT_IBB; - - JCL_GenLink(jcl, authlink, sizeof(authlink), "fauth", from, NULL, va("%i", ft->privateid), "%s", "Accept"); - JCL_GenLink(jcl, denylink, sizeof(denylink), "fdeny", from, NULL, va("%i", ft->privateid), "%s", "Deny"); - - Con_Printf("%s has offered to send you \"%s\" (%i bytes). %s %s\n", from, fname, fsize, authlink, denylink); - } - else - { - //profile not understood - JCL_AddClientMessagef(jcl, - "" - "" - "" - "" - "" - "", from, id); - } - } + if (XMPP_FT_ParseIQSet(jcl, from, id, tree)) + return; #endif + c = XML_ChildOfTree(tree, "query", 0); - if (c && !strcmp(c->xmlns, "jabber:iq:roster")) + if (c && !strcmp(c->xmlns, "jabber:iq:roster") && !strcmp(from, jcl->domain)) { unparsable = false; JCL_RosterUpdate(jcl, c); } + //google-specific - new mail notifications. + c = XML_ChildOfTree(tree, "new-mail", 0); + if (c && !strcmp(c->xmlns, "google:mail:notify") && !strcmp(from, jcl->domain)) + { + JCL_AddClientMessagef(jcl, "", from, id); + JCL_SendIQf(jcl, XMPP_NewGoogleMailsReply, "get", "", ""); + return; + } + #ifdef JINGLE c = XML_ChildOfTreeNS(tree, "urn:xmpp:jingle:1", "jingle", 0); - if (c) + if (c && (jcl->enabledcapabilities & (CAP_GAMEINVITE|CAP_VOICE|CAP_VIDEO))) unparsable = !JCL_ParseJingle(jcl, c, from, id); #ifdef VOIP_LEGACY c = XML_ChildOfTreeNS(tree, "http://www.google.com/session", "session", 0); - if (c) + if (c && (jcl->enabledcapabilities & (CAP_GOOGLE_VOICE))) unparsable = !JCL_HandleGoogleSession(jcl, c, from, id); #endif #endif + + if (unparsable) { char msg[2048]; @@ -2503,7 +2809,7 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) { char *id = XML_GetParameter(tree, "id", ""); struct iq_s **link, *iq; - char *from = XML_GetParameter(tree, "from", ""); + char *from = XML_GetParameter(tree, "from", jcl->domain); unparsable = false; for (link = &jcl->pendingiqs; *link; link = &(*link)->next) { @@ -2521,7 +2827,7 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) if (!iq->callback(jcl, !strcmp(f, "error")?NULL:tree, iq)) { Con_Printf("Invalid iq result\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); } } free(iq); @@ -2529,7 +2835,7 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) else { Con_Printf("Unrecognised iq result\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); } } @@ -2537,18 +2843,18 @@ void JCL_ParseIQ(jclient_t *jcl, xmltree_t *tree) { unparsable = false; Con_Printf("Unrecognised iq type\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); } } void JCL_ParseMessage(jclient_t *jcl, xmltree_t *tree) { xmltree_t *ot; qboolean unparsable = true; - char *f = XML_GetParameter(tree, "from", NULL); + char *f = XML_GetParameter(tree, "from", jcl->domain); char *type = XML_GetParameter(tree, "type", "normal"); - char *ctx; + char *ctx = f; - if (f && !strcmp(f, jcl->jid)) + if (!strcmp(f, jcl->jid)) unparsable = false; else { @@ -2558,19 +2864,21 @@ void JCL_ParseMessage(jclient_t *jcl, xmltree_t *tree) bresource_t *br; Q_strlcpy(jcl->defaultdest, f, sizeof(jcl->defaultdest)); - JCL_FindBuddy(jcl, f, &b, &br); - ctx = b->name; - if (!strcmp(type, "groupchat")) - f = br->resource; - else if (b->chatroom) //need to use the full resource for private chat within a room + if (JCL_FindBuddy(jcl, f, &b, &br, true)) { - ctx = f; - f = br->resource; - } - else - { - f = b->name; - b->defaultresource = br; + ctx = b->name; + if (!strcmp(type, "groupchat")) + f = br->resource; + else if (b->chatroom) //need to use the full resource for private chat within a room + { + ctx = f; + f = br->resource; + } + else + { + f = b->name; + b->defaultresource = br; + } } } @@ -2637,19 +2945,21 @@ void JCL_ParseMessage(jclient_t *jcl, xmltree_t *tree) xmltree_t *inv = XML_ChildOfTree(ot, "invite", 0); if (inv) { - char *who = XML_GetParameter(inv, "from", ""); + char *who = XML_GetParameter(inv, "from", jcl->domain); char *reason = XML_GetChildBody(inv, "reason", NULL); char *password = XML_GetChildBody(ot, "password", 0); char link[512]; buddy_t *b; - JCL_FindBuddy(jcl, f, &b, NULL); - if (b->chatroom) - return; //we already know about it. don't spam. - JCL_GenLink(jcl, link, sizeof(link), "mucjoin", f, NULL, password, "%s", f); - if (reason) - Con_SubPrintf(ctx, "* ^2%s^7 has invited you to join %s: %s.\n", who, link, reason); - else - Con_SubPrintf(ctx, "* ^2%s^7 has invited you to join %s.\n", who, link); + if (JCL_FindBuddy(jcl, f, &b, NULL, true)) + { + if (b->chatroom) + return; //we already know about it. don't spam. + JCL_GenLink(jcl, link, sizeof(link), "mucjoin", f, NULL, password, "%s", f); + if (reason) + Con_SubPrintf(ctx, "* ^2%s^7 has invited you to join %s: %s.\n", who, link, reason); + else + Con_SubPrintf(ctx, "* ^2%s^7 has invited you to join %s.\n", who, link); + } return; //ignore any body/jabber:x:conference } } @@ -2695,18 +3005,19 @@ void JCL_ParseMessage(jclient_t *jcl, xmltree_t *tree) if (jcl->streamdebug) { Con_Printf("Received a message without a body\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); } } } } -unsigned int JCL_ParseCaps(char *account, char *resource, xmltree_t *query) +unsigned int JCL_ParseCaps(jclient_t *jcl, char *account, char *resource, xmltree_t *query) { xmltree_t *feature; unsigned int caps = 0; qboolean rtp = false; qboolean rtpaudio = false; + qboolean rtpvideo = false; qboolean quake = false; qboolean ice = false; qboolean raw = false; @@ -2727,6 +3038,8 @@ unsigned int JCL_ParseCaps(char *account, char *resource, xmltree_t *query) #ifndef VOIP_LEGACY_ONLY if (!strcmp(var, "urn:xmpp:jingle:apps:rtp:audio")) rtpaudio = true; + if (!strcmp(var, "urn:xmpp:jingle:apps:rtp:video")) + rtpvideo = true; #endif if (!strcmp(var, "urn:xmpp:jingle:apps:rtp:1")) rtp = true; //kinda implied, but ensures version is okay @@ -2739,9 +3052,7 @@ unsigned int JCL_ParseCaps(char *account, char *resource, xmltree_t *query) #ifdef VOIP_LEGACY if (!strcmp(var, "http://www.google.com/xmpp/protocol/voice/v1")) - caps |= CAP_FUGOOG_VOICE; //legacy crap, so we can call google's official clients, which do not follow current xmpp standards. - if (!strcmp(var, "http://www.google.com/xmpp/protocol/session")) - caps |= CAP_FUGOOG_SESSION; //legacy crap, so we can call google's official clients, which do not follow current xmpp standards. + caps |= CAP_GOOGLE_VOICE; //legacy crap, so we can call google's official clients, which do not follow current xmpp standards. #endif if (!strcmp(var, "http://jabber.org/protocol/si")) @@ -2756,12 +3067,16 @@ unsigned int JCL_ParseCaps(char *account, char *resource, xmltree_t *query) { if (rtpaudio && rtp) caps |= CAP_VOICE; + if (rtpvideo && rtp) + caps |= CAP_VIDEO; if (quake) - caps |= CAP_INVITE; + caps |= CAP_GAMEINVITE; } if (si && sift && ibb) caps |= CAP_SIFT; + caps &= jcl->enabledcapabilities; + return caps; } @@ -2769,20 +3084,21 @@ void JCL_CheckClientCaps(jclient_t *jcl, buddy_t *buddy, bresource_t *bres); qboolean JCL_ClientDiscoInfo(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) { xmltree_t *query = XML_ChildOfTree(tree, "query", 0); - xmltree_t *feature; - char *var; - int i = 0; unsigned int caps = 0; buddy_t *b, *ob; bresource_t *r, *or; - JCL_FindBuddy(jcl, iq->to, &b, &r); + if (!JCL_FindBuddy(jcl, iq->to, &b, &r, true)) + return false; if (!query) - caps = CAP_QUERYFAILED; + { + caps &= ~(CAP_QUERYING|CAP_QUERIED); + caps |= CAP_QUERYFAILED; + } else { // XML_ConPrintTree(tree, 0); - caps = JCL_ParseCaps(b->accountdomain, r->resource, query); + caps = JCL_ParseCaps(jcl, b->accountdomain, r->resource, query); } if (b && r) @@ -2792,7 +3108,7 @@ qboolean JCL_ClientDiscoInfo(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) // Con_Printf("%s/%s caps = %x\n", b->accountdomain, r->resource, caps); if (!(r->caps & CAP_QUERIED)) r->caps = CAP_QUERIED; //reset it - r->caps |= CAP_QUERIED | caps; + r->caps |= caps; //as we got a valid response, make sure other resources running the same software get the same caps for (ob = jcl->buddies; ob; ob = ob->next) @@ -2845,6 +3161,7 @@ void JCL_CheckClientCaps(jclient_t *jcl, buddy_t *buddy, bresource_t *bres) buddy_t *b; bresource_t *r; char extname[64], *ext; + qboolean googlefuckedup; //ignore it if we're already asking them... if (bres->caps & (CAP_QUERYING|CAP_QUERIED|CAP_QUERYFAILED)) @@ -2884,14 +3201,28 @@ void JCL_CheckClientCaps(jclient_t *jcl, buddy_t *buddy, bresource_t *bres) // Con_Printf("%s/%s querying (%s#%s)\n", buddy->accountdomain, bres->resource, bres->client_node, bres->client_ver); //okay, this is the first time we've seen that software version. ask it what it supports, and hope we get a valid response... bres->caps = CAP_QUERYING; + bres->buggycaps = 0; JCL_SendIQf(jcl, JCL_ClientDiscoInfo, "get", va("%s/%s", buddy->accountdomain, bres->resource), "", bres->client_node, bres->client_ver); + //one of google's nodes. ONLY google get this fucked up evil hack because they're the only ones that are arrogant enough to not bother to query what that 'ext' actually means - and then to not even bother to tell other clients. + //every other client is expected to have its act together and not fuck up like this. + googlefuckedup = !!strstr(bres->client_node, "google.com") || !!strstr(bres->client_node, "android.com"); + //and ask for info about each extension too. which should only be used if the specified version isn't a hash. if (bres->client_hash && !*bres->client_hash) { for (ext = bres->client_ext; ext; ) { ext = JCL_ParseOut(ext, extname, sizeof(extname)); +#ifdef VOIP_LEGACY + if (googlefuckedup) + { + //work around repeated bugs in google's various clients. + if (!strcmp(extname, "voice-v1")) + bres->buggycaps |= CAP_GOOGLE_VOICE; + } +#endif + if (*extname) JCL_SendIQf(jcl, JCL_ClientDiscoInfo, "get", va("%s/%s", buddy->accountdomain, bres->resource), "", bres->client_node, extname); } @@ -2902,7 +3233,7 @@ void JCL_ParsePresence(jclient_t *jcl, xmltree_t *tree) buddy_t *buddy; bresource_t *bres; - char *from = XML_GetParameter(tree, "from", ""); + char *from = XML_GetParameter(tree, "from", jcl->domain); xmltree_t *show = XML_ChildOfTree(tree, "show", 0); xmltree_t *status = XML_ChildOfTree(tree, "status", 0); xmltree_t *quake = XML_ChildOfTree(tree, "quake", 0); @@ -2915,7 +3246,7 @@ void JCL_ParsePresence(jclient_t *jcl, xmltree_t *tree) char oldbstatus[128]; char oldfstatus[128]; - if (quake && !strcmp(quake->xmlns, "fteqw.com:game")) + if (quake && !strcmp(quake->xmlns, "fteqw.com:game") && (jcl->enabledcapabilities & CAP_GAMEINVITE)) { serverip = XML_GetParameter(quake, "serverip", NULL); servermap = XML_GetParameter(quake, "servermap", NULL); @@ -2923,6 +3254,7 @@ void JCL_ParsePresence(jclient_t *jcl, xmltree_t *tree) if (type && !strcmp(type, "subscribe")) { + //they want us to let them see us. char pauth[512], pdeny[512]; JCL_GenLink(jcl, startconvo, sizeof(startconvo), NULL, from, NULL, NULL, "%s", from); JCL_GenLink(jcl, pauth, sizeof(pauth), "pauth", from, NULL, NULL, "%s", "Authorize"); @@ -2931,25 +3263,28 @@ void JCL_ParsePresence(jclient_t *jcl, xmltree_t *tree) } else if (type && !strcmp(type, "subscribed")) { + //they allowed us to add them. JCL_GenLink(jcl, startconvo, sizeof(startconvo), NULL, from, NULL, NULL, "%s", from); Con_Printf("%s is now your friend!\n", startconvo); } else if (type && !strcmp(type, "unsubscribe")) { + //they removed us. JCL_GenLink(jcl, startconvo, sizeof(startconvo), NULL, from, NULL, NULL, "%s", from); Con_Printf("%s has unfriended you\n", startconvo); } else if (type && !strcmp(type, "unsubscribed")) { + //we removed them. JCL_GenLink(jcl, startconvo, sizeof(startconvo), NULL, from, NULL, NULL, "%s", from); - Con_Printf("%s is no longer unfriended you\n", startconvo); + Con_Printf("%s is no longer your friend\n", startconvo); } else { - JCL_FindBuddy(jcl, from, &buddy, &bres); + JCL_FindBuddy(jcl, from, &buddy, &bres, true); if (!bres) { - JCL_FindBuddy(jcl, va("%s/", from), &buddy, &bres); + JCL_FindBuddy(jcl, va("%s/", from), &buddy, &bres, true); } JCL_GenLink(jcl, startconvo, sizeof(startconvo), NULL, from, NULL, NULL, "%s", buddy->name); @@ -2982,6 +3317,29 @@ void JCL_ParsePresence(jclient_t *jcl, xmltree_t *tree) } else { + xmltree_t *vcu; + char *photohash; + buddy_t *me; + vcu = XML_ChildOfTreeNS(tree, "vcard-temp:x:update", "x", 0); + photohash = XML_GetChildBody(vcu, "photo", buddy->vcardphotohash); + if (strcmp(buddy->vcardphotohash, photohash)) + { + Con_DPrintf("%s changed their photo from \"%s\" to \"%s\"\n", from, buddy->vcardphotohash, photohash); + + Q_strlcpy(buddy->vcardphotohash, photohash, sizeof(buddy->vcardphotohash)); + buddy->vcardphotochanged = true; + } + + JCL_FindBuddy(jcl, jcl->jid, &me, NULL, true); + if (buddy == me) + { + if (strcmp(buddy->vcardphotohash, jcl->vcardphotohash)) + { + jcl->vcardphotohashstatus = VCP_UNKNOWN; + JCL_SendIQf(jcl, JCL_MyVCardReply, "get", NULL, ""); + } + } + Q_strlcpy(bres->bstatus, (show && *show->body)?show->body:"present", sizeof(bres->bstatus)); if (caps) @@ -3042,7 +3400,7 @@ void JCL_ParsePresence(jclient_t *jcl, xmltree_t *tree) else { Con_Printf("Weird presence:\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); } } } @@ -3055,7 +3413,6 @@ int JCL_ClientFrame(jclient_t *jcl) { int pos; xmltree_t *tree, *ot; - char *f; int ret; qboolean unparsable; @@ -3070,6 +3427,9 @@ int JCL_ClientFrame(jclient_t *jcl) if (ret < 0) { Con_Printf("XMPP: socket error\n"); + if (jcl->socket != -1) + pNet_Close(jcl->socket); + jcl->socket = -1; return JCL_KILL; } @@ -3123,7 +3483,7 @@ int JCL_ClientFrame(jclient_t *jcl) tree = XML_Parse(jcl->bufferedinmessage, &pos, jcl->instreampos, true, ""); } - if (jcl->streamdebug) + if (jcl->streamdebug == 2) { char t = jcl->bufferedinmessage[pos]; jcl->bufferedinmessage[pos] = 0; @@ -3177,7 +3537,7 @@ int JCL_ClientFrame(jclient_t *jcl) pos = 0; tree = XML_Parse(jcl->bufferedinmessage, &pos, jcl->instreampos, false, jcl->defaultnamespace); - if (jcl->streamdebug && tree) + if (jcl->streamdebug == 2 && tree) { char t = jcl->bufferedinmessage[pos]; jcl->bufferedinmessage[pos] = 0; @@ -3196,6 +3556,9 @@ int JCL_ClientFrame(jclient_t *jcl) // Con_Printf("read\n"); // XML_ConPrintTree(tree, 0); + if (jcl->streamdebug == 1) + XML_ConPrintTree(tree, "xmppin", 0); + jcl->timeout = jclient_curtime + 60*1000; unparsable = true; @@ -3213,8 +3576,6 @@ int JCL_ClientFrame(jclient_t *jcl) jcl->connected = true; JCL_WriteConfig(); - -// JCL_AddClientMessageString(jcl, ""); } @@ -3239,7 +3600,7 @@ int JCL_ClientFrame(jclient_t *jcl) if (BUILTINISVALID(Net_SetTLSClient)) { Con_Printf("XMPP: Unable to switch to TLS. You are probably being man-in-the-middle attacked.\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); } else { @@ -3293,7 +3654,7 @@ int JCL_ClientFrame(jclient_t *jcl) else { Con_Printf("XMPP: No suitable auth methods. Unable to connect.\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); XML_Destroy(tree); return JCL_KILL; } @@ -3373,21 +3734,34 @@ int JCL_ClientFrame(jclient_t *jcl) } else if (!strcmp(tree->name, "error")) { - ot = XML_ChildOfTree(tree, "see-other-host", 0); - if (ot) + xmltree_t *condition; + condition = XML_ChildOfTree(tree, "success", 0); + if (!condition) + condition = XML_ChildOfTree(tree, "see-other-host", 0); + if (!condition) + condition = XML_ChildOfTree(tree, "invalid-xml", 0); + + if (condition && !strcmp(condition->name, "see-other-host")) { //msn needs this, apparently - Q_strlcpy(jcl->redirserveraddr, ot->body, sizeof(jcl->redirserveraddr)); + Q_strlcpy(jcl->redirserveraddr, condition->body, sizeof(jcl->redirserveraddr)); JCL_CloseConnection(jcl, true); if (!JCL_Reconnect(jcl)) return JCL_KILL; return JCL_CONTINUE; } + else if (condition && !strcmp(condition->name, "success")) + { + Con_Printf("XMPP error: success\n"); + unparsable = false; + } else { ot = XML_ChildOfTree(tree, "text", 0); if (ot) - Con_Printf("XMPP: %s\n", ot->body); + Con_Printf("XMPP error: %s\n", ot->body); + else if (condition) + Con_Printf("XMPP error: %s\n", condition->name); else Con_Printf("XMPP: Unknown error\n"); @@ -3434,7 +3808,7 @@ int JCL_ClientFrame(jclient_t *jcl) else { Con_Printf("JCL unrecognised stanza: %s\n", tree->name); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); } memmove(jcl->bufferedinmessage, jcl->bufferedinmessage+pos, jcl->bufferedinammount-pos); @@ -3444,7 +3818,7 @@ int JCL_ClientFrame(jclient_t *jcl) if (unparsable) { Con_Printf("XMPP: Input corrupt, urecognised, or unusable. Disconnecting.\n"); - XML_ConPrintTree(tree, 0); + XML_ConPrintTree(tree, "", 0); XML_Destroy(tree); return JCL_KILL; } @@ -3523,22 +3897,14 @@ void JCL_GeneratePresence(jclient_t *jcl, qboolean force) } } - if (force || strcmp(jcl->curquakeserver, *servermap?servermap:serveraddr)) + if (force || jcl->vcardphotohashchanged || strcmp(jcl->curquakeserver, *servermap?servermap:serveraddr)) { - char caps[256]; + char caps[512]; + jcl->vcardphotohashchanged = false; Q_strlcpy(jcl->curquakeserver, *servermap?servermap:serveraddr, sizeof(jcl->curquakeserver)); Con_DPrintf("Sending presence %s\n", jcl->curquakeserver); - //xep-0115 1.4+ - Q_snprintf(caps, sizeof(caps), - "", buildcapshash()); + buildcapsvcardpresence(jcl, caps, sizeof(caps)); if (!*jcl->curquakeserver) JCL_AddClientMessagef(jcl, @@ -3598,6 +3964,18 @@ qintptr_t JCL_Frame(qintptr_t *args) JCL_CloseConnection(jcl, true); else JCL_FlushOutgoing(jcl); + + if (stat == JCL_DONE) + { + XMPP_FT_Frame(jcl); + + if (jclient_curtime > jcl->timeout) + { + //client needs to send something valid as a keep-alive so routers don't silently forget mappings. + JCL_SendIQf(jcl, NULL, "get", NULL, ""); + jcl->timeout = jclient_curtime + 60*1000; + } + } } #ifdef JINGLE @@ -3611,8 +3989,8 @@ qintptr_t JCL_Frame(qintptr_t *args) void JCL_WriteConfig(void) { - xmltree_t *m, *n, *oauth2; - int i; + xmltree_t *m, *n, *oauth2, *features; + int i, j; qhandle_t config; @@ -3626,9 +4004,11 @@ void JCL_WriteConfig(void) n = XML_CreateNode(m, "account", "", ""); XML_AddParameteri(n, "id", i); - XML_CreateNode(n, "streamdebug", "", jcl->streamdebug?"1":"0"); + Q_snprintf(foo, sizeof(foo), "%i", jcl->streamdebug); + XML_CreateNode(n, "streamdebug", "", foo); Q_snprintf(foo, sizeof(foo), "%i", jcl->forcetls); XML_CreateNode(n, "forcetls", "", foo); + XML_CreateNode(n, "savepassword", "", jcl->savepassword?"1":"0"); XML_CreateNode(n, "allowauth_plain_nontls", "", jcl->allowauth_plainnontls?"1":"0"); XML_CreateNode(n, "allowauth_plain_tls", "", jcl->allowauth_plaintls?"1":"0"); XML_CreateNode(n, "allowauth_digest_md5", "", jcl->allowauth_digestmd5?"1":"0"); @@ -3659,6 +4039,13 @@ void JCL_WriteConfig(void) XML_CreateNode(n, "serverport", "", foo); XML_CreateNode(n, "certificatedomain", "", jcl->certificatedomain); XML_CreateNode(n, "inactive", "", jcl->status == JCL_INACTIVE?"1":"0"); + + features = XML_CreateNode(n, "features", "", ""); + XML_AddParameter(features, "ver", JCL_BUILD); + for (j = 0; capnames[j].names; j++) + { + XML_CreateNode(features, capnames[j].names, "", (jcl->enabledcapabilities & capnames[j].cap)?"1":"0"); + } } } @@ -3764,7 +4151,7 @@ static void JCL_PrintBuddyStatus(char *console, jclient_t *jcl, buddy_t *b, bres else Con_SubPrintf(console, "%s", r->bstatus); - if ((r->caps & CAP_INVITE) && !r->servertype) + if ((r->caps & CAP_GAMEINVITE) && !r->servertype) { char invitelink[512]; JCL_GenLink(jcl, invitelink, sizeof(invitelink), "invite", b->accountdomain, r->resource, NULL, "%s", "Invite"); @@ -3776,10 +4163,10 @@ static void JCL_PrintBuddyStatus(char *console, jclient_t *jcl, buddy_t *b, bres JCL_GenLink(jcl, calllink, sizeof(calllink), "call", b->accountdomain, r->resource, NULL, "%s", "Call"); Con_SubPrintf(console, " %s", calllink); } - else if ((r->caps & CAP_FUGOOG_VOICE) && (r->caps & CAP_FUGOOG_SESSION)) + else if ((r->caps|r->buggycaps) & CAP_GOOGLE_VOICE) { char calllink[512]; - JCL_GenLink(jcl, calllink, sizeof(calllink), "call", b->accountdomain, r->resource, NULL, "%s", "Call(NS)"); + JCL_GenLink(jcl, calllink, sizeof(calllink), "call", b->accountdomain, r->resource, NULL, "%s", "Call"); Con_SubPrintf(console, " %s", calllink); } } @@ -3790,6 +4177,7 @@ void JCL_PrintBuddyList(char *console, jclient_t *jcl, qboolean all) struct c2c_s *c2c; struct ft_s *ft; char convolink[512], actlink[512]; + int c; if (!jcl->buddies) Con_SubPrintf(console, "You have no friends\n"); for (b = jcl->buddies; b; b = b->next) @@ -3833,25 +4221,35 @@ void JCL_PrintBuddyList(char *console, jclient_t *jcl, qboolean all) Con_SubPrintf(console, "Active sessions:\n"); for (c2c = jcl->c2c; c2c; c2c = c2c->next) { - JCL_FindBuddy(jcl, c2c->with, &b, &r); - switch(c2c->mediatype) + qboolean voice = false, video = false, server = false, client = false; + JCL_FindBuddy(jcl, c2c->with, &b, &r, true); + for (c = 0; c < c2c->contents; c++) { - case ICEP_VOICE: - JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, b->accountdomain, r->resource, NULL, "%s", b->name); - JCL_GenLink(jcl, actlink, sizeof(actlink), "jdeny", c2c->with, NULL, c2c->sid, "%s", "Hang Up"); - Con_SubPrintf(console, " %s: voice %s\n", convolink, actlink); - break; - case ICEP_QWSERVER: - JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, b->accountdomain, r->resource, NULL, "%s", b->name); - JCL_GenLink(jcl, actlink, sizeof(actlink), "jdeny", c2c->with, NULL, c2c->sid, "%s", "Kick"); - Con_SubPrintf(console, " %s: server %s\n", convolink, actlink); - break; - case ICEP_QWCLIENT: - JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, b->accountdomain, r->resource, NULL, "%s", b->name); - JCL_GenLink(jcl, actlink, sizeof(actlink), "jdeny", c2c->with, NULL, c2c->sid, "%s", "Disconnect"); - Con_SubPrintf(console, " %s: client %s\n", convolink, actlink); - break; + switch(c2c->content[c].mediatype) + { + case ICEP_VOICE: voice = true; break; + case ICEP_VIDEO: video = true; break; + case ICEP_QWSERVER: server = true; break; + case ICEP_QWCLIENT: client = true; break; + } } + JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, b->accountdomain, r->resource, NULL, "%s", b->name); + Con_SubPrintf(console, " %s: ", convolink); + if (video) + Con_SubPrintf(console, "video "); + if (voice) + Con_SubPrintf(console, "voice "); + if (server) + Con_SubPrintf(console, "server "); + if (client) + Con_SubPrintf(console, "client "); + if (server) + JCL_GenLink(jcl, actlink, sizeof(actlink), "jdeny", c2c->with, NULL, c2c->sid, "%s", "Kick"); + else if (video || voice) + JCL_GenLink(jcl, actlink, sizeof(actlink), "jdeny", c2c->with, NULL, c2c->sid, "%s", "Hang Up"); + else + JCL_GenLink(jcl, actlink, sizeof(actlink), "jdeny", c2c->with, NULL, c2c->sid, "%s", "Disconnect"); + Con_SubPrintf(console, "%s\n", actlink); } #endif @@ -3860,7 +4258,7 @@ void JCL_PrintBuddyList(char *console, jclient_t *jcl, qboolean all) Con_SubPrintf(console, "Active file transfers:\n"); for (ft = jcl->ft; ft; ft = ft->next) { - JCL_FindBuddy(jcl, ft->with, &b, &r); + JCL_FindBuddy(jcl, ft->with, &b, &r, true); JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, b->accountdomain, r->resource, NULL, "%s", b->name); JCL_GenLink(jcl, actlink, sizeof(actlink), "fdeny", ft->with, NULL, ft->sid, "%s", "Cancel"); Con_SubPrintf(console, " %s: %s\n", convolink, ft->fname); @@ -3874,7 +4272,8 @@ void JCL_SendMessage(jclient_t *jcl, char *to, char *msg) char *con; buddy_t *b; bresource_t *br; - JCL_FindBuddy(jcl, to, &b, &br); + if (!JCL_FindBuddy(jcl, to, &b, &br, true)) + return; if (b->chatroom) { if (br) @@ -3915,7 +4314,8 @@ void JCL_AttentionMessage(jclient_t *jcl, char *to, char *msg) xmltree_t *m; char *s; - JCL_FindBuddy(jcl, to, &b, &br); + if (!JCL_FindBuddy(jcl, to, &b, &br, true)) + return; if (!br) br = b->defaultresource; if (!br) @@ -4023,207 +4423,13 @@ void JCL_JoinMUCChat(jclient_t *jcl, char *room, char *server, char *myhandle, c Q_snprintf(roomserverhandle, sizeof(roomserverhandle), "%s@%s/%s", room, server, myhandle); else Q_snprintf(roomserverhandle, sizeof(roomserverhandle), "%s/%s", room, myhandle); - JCL_FindBuddy(jcl, roomserverhandle, &b, &r); + if (!JCL_FindBuddy(jcl, roomserverhandle, &b, &r, true)) + return; b->chatroom = true; - Q_snprintf(caps, sizeof(caps), - "", buildcapshash()); + buildcapsvcardpresence(jcl, caps, sizeof(caps)); JCL_AddClientMessagef(jcl, "%s%s", roomserverhandle, password, caps); } -#ifdef FILETRANSFERS -void JCL_FT_AcceptFile(jclient_t *jcl, int fileid, qboolean accept) -{ - struct ft_s *ft, **link; - char *s; - xmltree_t *repiq, *repsi, *c; - - for (link = &jcl->ft; ft=*link; link = &(*link)->next) - { - if (ft->privateid == fileid) - break; - } - if (!ft) - { - Con_Printf("File not known\n"); - return; - } - - if (!accept) - { - Con_Printf("Declining file \"%s\" from \"%s\" (%i bytes)\n", ft->fname, ft->with, ft->size); - - if (!ft->allowed) - { - JCL_AddClientMessagef(jcl, - "" - "" - "" - "Offer Declined" - "" - "", ft->with, ft->iqid); - } - else - { - //FIXME: send a proper cancel - } - - pFS_Close(ft->file); - *link = ft->next; - free(ft); - } - else - { - Con_Printf("Receiving file \"%s\" from \"%s\" (%i bytes)\n", ft->fname, ft->with, ft->size); - ft->allowed = true; - - //generate a response. - //FIXME: we ought to delay response until after we prompt. - repiq = XML_CreateNode(NULL, "iq", "", ""); - XML_AddParameter(repiq, "type", "result"); - XML_AddParameter(repiq, "to", ft->with); - XML_AddParameter(repiq, "id", ft->iqid); - repsi = XML_CreateNode(repiq, "si", "http://jabber.org/protocol/si", ""); - XML_CreateNode(repsi, "file", "http://jabber.org/protocol/si/profile/file-transfer", ""); //I don't really get the point of this. - c = XML_CreateNode(repsi, "feature", "http://jabber.org/protocol/feature-neg", ""); - c = XML_CreateNode(c, "x", "jabber:x:data", ""); - XML_AddParameter(c, "type", "submit"); - c = XML_CreateNode(c, "field", "", ""); - XML_AddParameter(c, "var", "stream-method"); - if (ft->method == FT_IBB) - c = XML_CreateNode(c, "value", "", "http://jabber.org/protocol/ibb"); - else if (ft->method == FT_BYTESTREAM) - c = XML_CreateNode(c, "value", "", "http://jabber.org/protocol/bytestreams"); - - s = XML_GenerateString(repiq, false); - JCL_AddClientMessageString(jcl, s); - free(s); - XML_Destroy(repiq); - } -} - -qboolean JCL_FT_IBBChunked(jclient_t *jcl, xmltree_t *x, struct iq_s *iq) -{ - char *from = XML_GetParameter(x, "from", ""); - struct ft_s *ft = iq->usrptr, **link, *v; - for (link = &jcl->ft; (v=*link); link = &(*link)->next) - { - if (v == ft) - { - //its still valid - if (x) - { - char rawbuf[4096]; - int sz; - sz = pFS_Read(ft->file, rawbuf, ft->blocksize); - Base64_Add(rawbuf, sz); - Base64_Finish(); - - if (sz > 0) - { - ft->sizedone += sz; - if (ft->sizedone == ft->size) - ft->eof = true; - } - - if (sz && strlen(base64)) - { - x = XML_CreateNode(NULL, "data", "http://jabber.org/protocol/ibb", base64); - XML_AddParameteri(x, "seq", ft->seq++); - XML_AddParameter(x, "sid", ft->sid); - JCL_SendIQNode(jcl, JCL_FT_IBBChunked, "set", from, x, true)->usrptr = ft; - return true; - } - //else eof - } - - //errored or ended - - if (x) - Con_Printf("Transfer of %s to %s completed\n", ft->fname, ft->with); - else - Con_Printf("%s aborted %s\n", ft->with, ft->fname); - x = XML_CreateNode(NULL, "close", "http://jabber.org/protocol/ibb", ""); - XML_AddParameter(x, "sid", ft->sid); - JCL_SendIQNode(jcl, NULL, "set", from, x, true)->usrptr = ft; - - //errored - pFS_Close(ft->file); - *link = ft->next; - free(ft); - return true; - } - } - return true; //the ack can come after the bytestream has already finished sending. don't warn about that. -} -qboolean JCL_FT_IBBBegun(jclient_t *jcl, xmltree_t *x, struct iq_s *iq) -{ - struct ft_s *ft = iq->usrptr, **link, *v; - for (link = &jcl->ft; (v=*link); link = &(*link)->next) - { - if (v == ft) - { - //its still valid - if (!x) - { - Con_Printf("%s aborted %s\n", ft->with, ft->fname); - //errored - pFS_Close(ft->file); - *link = ft->next; - free(ft); - } - else - { - ft->begun = true; - ft->method = FT_IBB; - JCL_FT_IBBChunked(jcl, x, iq); - } - return true; - } - } - return false; -} -qboolean JCL_FT_OfferAcked(jclient_t *jcl, xmltree_t *x, struct iq_s *iq) -{ - struct ft_s *ft = iq->usrptr, **link, *v; - - for (link = &jcl->ft; (v=*link); link = &(*link)->next) - { - if (v == ft) - { - //its still valid - if (!x) - { - Con_Printf("%s doesn't want %s\n", ft->with, ft->fname); - //errored - pFS_Close(ft->file); - *link = ft->next; - free(ft); - } - else - { - xmltree_t *xo; - Con_Printf("%s accepted %s\n", ft->with, ft->fname); - xo = XML_CreateNode(NULL, "open", "http://jabber.org/protocol/ibb", ""); - XML_AddParameter(xo, "sid", ft->sid); - XML_AddParameteri(xo, "block-size", ft->blocksize); - //XML_AddParameter(xo, "stanza", "iq"); - - JCL_SendIQNode(jcl, JCL_FT_IBBBegun, "set", XML_GetParameter(x, "from", ""), xo, true)->usrptr = ft; - } - return true; - } - } - return false; -} -#endif - void XMPP_Menu_Password(jclient_t *acc) { int y; @@ -4231,13 +4437,17 @@ void XMPP_Menu_Password(jclient_t *acc) "{\n" "menuclear\n" "if (option == \"SignIn\")\n" - "{\n"COMMANDPREFIX" /password ${0}\n}\n" + "{\n" + COMMANDPREFIX" /set savepassword $_t1\n" + COMMANDPREFIX" /password ${0}\n" + "}\n" "}\n", false); y = 36; pCmd_AddText(va("menutext 48 %i \"^sXMPP Sign In\"\n", y), false); y+=16; pCmd_AddText(va("menutext 48 %i \"^sPlease provide your password for\"\n", y), false); y+=16; pCmd_AddText(va("menueditpriv 48 %i \"%s@%s\" \"example\"\n", y, acc->username, acc->domain), false);y+=16; + pCmd_AddText(va("set _t1 0\nmenucheck 48 %i \"Save Password\" _t1 1\n", y), false); y+=16; pCmd_AddText(va("menutext 48 %i \"Sign In\" SignIn\n", y), false); pCmd_AddText(va("menutext 256 %i \"Cancel\" cancel\n", y), false); } @@ -4383,6 +4593,19 @@ void JCL_Command(int accid, char *console) if (jcl->status == JCL_INACTIVE) jcl->status = JCL_DEAD; } + else if (!strcmp(arg[0]+1, "set")) + { + if (!strcmp(arg[1], "savepassword")) + jcl->savepassword = atoi(arg[2]); + else if (!strcmp(arg[1], "avatars")) + jcl->enabledcapabilities = (jcl->enabledcapabilities & ~CAP_AVATARS) | (atoi(arg[2])?CAP_AVATARS:0); + else if (!strcmp(arg[1], "debug")) + jcl->streamdebug = atoi(arg[2]); + else if (!strcmp(arg[1], "resource")) + Q_strlcpy(jcl->resource, arg[2], sizeof(jcl->resource)); + else + Con_SubPrintf(console, "Sorry, setting not recognised.\n", arg[0]); + } else if (!strcmp(arg[0]+1, "password")) { Q_strncpyz(jcl->password, arg[1], sizeof(jcl->password)); @@ -4463,10 +4686,13 @@ void JCL_Command(int accid, char *console) #ifdef FILETRANSFERS else if (!strcmp(arg[0]+1, "sendfile")) { - xmltree_t *xfile, *xsi, *c; char *fname = arg[1]; - //file transfer offer - struct ft_s *ft; + + if (!(jcl->enabledcapabilities & CAP_SIFT)) + { + Con_SubPrintf(console, "XMPP: file transfers are not enabled for this account. Edit your config.\n"); + return; + } if (!*fname || strchr(fname, '*')) { @@ -4477,41 +4703,7 @@ void JCL_Command(int accid, char *console) { JCL_ToJID(jcl, *arg[2]?arg[2]:console, nname, sizeof(nname), true); - Con_SubPrintf(console, "Offering %s to %s.\n", fname, nname); - - ft = malloc(sizeof(*ft)); - memset(ft, 0, sizeof(*ft)); - ft->next = jcl->ft; - jcl->ft = ft; - ft->allowed = true; - ft->transmitting = true; - ft->blocksize = 4096; - Q_strlcpy(ft->fname, fname, sizeof(ft->fname)); - Q_snprintf(ft->sid, sizeof(ft->sid), "%x%s", rand(), ft->fname); - Q_strlcpy(ft->md5hash, "", sizeof(ft->md5hash)); - ft->size = pFS_Open(ft->fname, &ft->file, 1); - ft->with = strdup(nname); - ft->method = FT_IBB; - ft->begun = false; - - //generate an offer. - xsi = XML_CreateNode(NULL, "si", "http://jabber.org/protocol/si", ""); - XML_AddParameter(xsi, "profile", "http://jabber.org/protocol/si/profile/file-transfer"); - XML_AddParameter(xsi, "id", ft->sid); - //XML_AddParameter(xsi, "mime-type", "text/plain"); - xfile = XML_CreateNode(xsi, "file", "http://jabber.org/protocol/si/profile/file-transfer", ""); //I don't really get the point of this. - XML_AddParameter(xfile, "name", ft->fname); - XML_AddParameteri(xfile, "size", ft->size); - c = XML_CreateNode(xsi, "feature", "http://jabber.org/protocol/feature-neg", ""); - c = XML_CreateNode(c, "x", "jabber:x:data", ""); - XML_AddParameter(c, "type", "form"); - c = XML_CreateNode(c, "field", "", ""); - XML_AddParameter(c, "var", "stream-method"); - XML_AddParameter(c, "type", "listsingle"); - XML_CreateNode(XML_CreateNode(c, "option", "", ""), "value", "", "http://jabber.org/protocol/ibb"); - // XML_CreateNode(XML_CreateNode(c, "option", "", ""), "value", "", "http://jabber.org/protocol/bytestreams"); - - JCL_SendIQNode(jcl, JCL_FT_OfferAcked, "set", nname, xsi, true)->usrptr = ft; + XMPP_FT_SendFile(jcl, console, nname, fname); } } #endif @@ -4525,10 +4717,12 @@ void JCL_Command(int accid, char *console) buddy_t *b; bresource_t *r; Q_snprintf(roomserverhandle, sizeof(roomserverhandle), "%s@%s/%s", arg[1], arg[2], arg[3]); - JCL_FindBuddy(jcl, roomserverhandle, &b, &r); - b->chatroom = true; - JCL_AddClientMessagef(jcl, "", roomserverhandle); - JCL_ForgetBuddy(jcl, b, NULL); + if (JCL_FindBuddy(jcl, roomserverhandle, &b, &r, false)) + { + b->chatroom = true; + JCL_AddClientMessagef(jcl, "", roomserverhandle); + JCL_ForgetBuddy(jcl, b, NULL); + } } else if (!strcmp(arg[0]+1, "slap")) { @@ -4581,7 +4775,7 @@ void JCL_Command(int accid, char *console) } else { - if (jcl) + if (jcl && jcl->status == JCL_ACTIVE) { msg = imsg; diff --git a/plugins/jabber/jingle.c b/plugins/jabber/jingle.c index b01d61a4..79526daf 100644 --- a/plugins/jabber/jingle.c +++ b/plugins/jabber/jingle.c @@ -1,14 +1,25 @@ #include "xmpp.h" #ifdef JINGLE -static struct c2c_s *JCL_JingleCreateSession(jclient_t *jcl, char *with, bresource_t *bres, qboolean creator, char *sid, int method, int mediatype) +static struct c2c_s *JCL_JingleAddContentToSession(jclient_t *jcl, struct c2c_s *c2c, char *with, bresource_t *bres, qboolean creator, char *sid, char *cname, int method, int mediatype) { struct icestate_s *ice = NULL; - struct c2c_s *c2c; char generatedname[64]; char stunhost[256]; + int c; if (!bres) return NULL; + + //make sure we can add more contents to this session + //block dupe content names. + if (c2c) + { + if (c2c->contents == sizeof(c2c->content) / sizeof(c2c->content[0])) + return NULL; + for (c = 0; c < c2c->contents; c++) + if (!strcmp(c2c->content[c].name, cname)) + return NULL; + } if (piceapi) ice = piceapi->ICE_Create(NULL, sid, with, method, mediatype); @@ -17,7 +28,10 @@ static struct c2c_s *JCL_JingleCreateSession(jclient_t *jcl, char *with, bresour piceapi->ICE_Get(ice, "sid", generatedname, sizeof(generatedname)); sid = generatedname; - if (creator) + //the controlling role MUST be assumed by the initiator and the controlled role MUST be assumed by the responder + piceapi->ICE_Set(ice, "controller", creator?"1":"0"); + + if (creator && mediatype == ICEP_VOICE) { //note: the engine will ignore codecs it does not support. piceapi->ICE_Set(ice, "codec96", "speex@16000"); //wide @@ -29,16 +43,27 @@ static struct c2c_s *JCL_JingleCreateSession(jclient_t *jcl, char *with, bresour else return NULL; //no way to get the local ip otherwise, which means things won't work proper - c2c = malloc(sizeof(*c2c) + strlen(sid)); - memset(c2c, 0, sizeof(*c2c)); - c2c->next = jcl->c2c; - jcl->c2c = c2c; - strcpy(c2c->sid, sid); + if (!c2c) + { + c2c = malloc(sizeof(*c2c) + strlen(sid)); + memset(c2c, 0, sizeof(*c2c)); + c2c->next = jcl->c2c; + jcl->c2c = c2c; + strcpy(c2c->sid, sid); //safe due to trailing space. + + c2c->with = strdup(with); + c2c->peercaps = bres->caps; + c2c->creator = creator; + } + + Q_strlcpy(c2c->content[c2c->contents].name, cname, sizeof(c2c->content[c2c->contents].name)); + c2c->content[c2c->contents].method = method; + c2c->content[c2c->contents].mediatype = mediatype; + + //copy out the interesting parameters + c2c->content[c2c->contents].ice = ice; + c2c->contents++; - c2c->peercaps = bres->caps; - c2c->mediatype = mediatype; - c2c->creator = creator; - c2c->method = method; //query dns to see if there's a stunserver hosted by the same domain //nslookup -querytype=SRV _stun._udp.example.com @@ -60,14 +85,11 @@ static struct c2c_s *JCL_JingleCreateSession(jclient_t *jcl, char *with, bresour piceapi->ICE_Set(ice, "stunport", "19302"); piceapi->ICE_Set(ice, "stunip", "stun.l.google.com"); } - - //copy out the interesting parameters - c2c->with = strdup(with); - c2c->ice = ice; return c2c; } static qboolean JCL_JingleAcceptAck(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) { + int i; struct c2c_s *c2c; if (tree) { @@ -75,8 +97,11 @@ static qboolean JCL_JingleAcceptAck(jclient_t *jcl, xmltree_t *tree, struct iq_s { if (c2c == iq->usrptr) { - if (c2c->ice) - piceapi->ICE_Set(c2c->ice, "state", STRINGIFY(ICE_CONNECTING)); + for (i = 0; i < c2c->contents; i++) + { + if (c2c->content[i].ice) + piceapi->ICE_Set(c2c->content[i].ice, "state", STRINGIFY(ICE_CONNECTING)); + } } } } @@ -129,6 +154,52 @@ static void JCL_PopulateAudioDescription(xmltree_t *description, struct icestate } } +enum +{ + JE_ACKNOWLEDGE, + JE_UNSUPPORTED, + JE_OUTOFORDER, + JE_TIEBREAK, + JE_UNKNOWNSESSION, + JE_UNSUPPORTEDINFO +}; +static void JCL_JingleError(jclient_t *jcl, xmltree_t *tree, char *from, char *id, int type) +{ + switch(type) + { + case JE_ACKNOWLEDGE: + JCL_AddClientMessagef(jcl, + "", from, id); + break; + case JE_UNSUPPORTED: + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "", id, from); + break; + case JE_UNKNOWNSESSION: + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "" + "", id, from); + break; + case JE_UNSUPPORTEDINFO: + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "" + "", id, from); + break; + } +} + /* sends a jingle message to the peer. action should be one of multiple things: @@ -141,16 +212,17 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) { qboolean result; xmltree_t *jingle; - struct icestate_s *ice = c2c->ice; + int c; +// struct icestate_s *ice = c2c->ice; qboolean wasaccept = false; int transportmode = ICEM_ICE; int cap = c2c->peercaps; - if (!ice) + if (!c2c->contents) action = "session-terminate"; #ifdef VOIP_LEGACY - if ((cap & CAP_FUGOOG_VOICE) && (cap & CAP_FUGOOG_SESSION) && !(cap & CAP_VOICE)) + if ((cap & CAP_GOOGLE_VOICE) && !(cap & CAP_VOICE)) { //legacy crap for google compatibility. if (!strcmp(action, "session-initiate")) @@ -161,7 +233,8 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) XML_AddParameter(session, "id", c2c->sid); XML_AddParameter(session, "initiator", jcl->jid); XML_AddParameter(session, "type", "initiate"); - JCL_PopulateAudioDescription(description, ice); + for (c = 0; c < c2c->contents; c++) + JCL_PopulateAudioDescription(description, c2c->content[c].ice); JCL_SendIQNode(jcl, NULL, "set", c2c->with, session, true); } @@ -173,15 +246,17 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) XML_AddParameter(session, "id", c2c->sid); XML_AddParameter(session, "initiator", c2c->with); XML_AddParameter(session, "type", "accept"); - JCL_PopulateAudioDescription(description, ice); + for (c = 0; c < c2c->contents; c++) + JCL_PopulateAudioDescription(description, c2c->content[c].ice); JCL_SendIQNode(jcl, JCL_JingleAcceptAck, "set", c2c->with, session, true)->usrptr = c2c; c2c->accepted = true; } else if (!strcmp(action, "transport-info")) { - struct icecandinfo_s *c; - while ((c = piceapi->ICE_GetLCandidateInfo(ice))) +/*FIXME + struct icecandinfo_s *ca; + while ((ca = piceapi->ICE_GetLCandidateInfo(ice))) { xmltree_t *session = XML_CreateNode(NULL, "session", "http://www.google.com/session", ""); @@ -190,7 +265,7 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) XML_AddParameter(session, "type", "candidates"); //one per message, apparently - if (c) + if (ca) { char *ctypename[]={"local", "stun", "stun", "relay"}; char *pref[]={"1", "0.9", "0.5", "0.2"}; @@ -201,20 +276,21 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) piceapi->ICE_Get(ice, "lufrag", uname, sizeof(uname)); piceapi->ICE_Get(ice, "lpwd", pwd, sizeof(pwd)); - XML_AddParameter(candidate, "address", c->addr); - XML_AddParameteri(candidate, "port", c->port); - XML_AddParameter(candidate, "name", (c->component==1)?"rtp":"rtcp"); + XML_AddParameter(candidate, "address", ca->addr); + XML_AddParameteri(candidate, "port", ca->port); + XML_AddParameter(candidate, "name", (ca->component==1)?"rtp":"rtcp"); XML_AddParameter(candidate, "username", uname); XML_AddParameter(candidate, "password", pwd); - XML_AddParameter(candidate, "preference", pref[c->type]); //FIXME: c->priority + XML_AddParameter(candidate, "preference", pref[ca->type]); //FIXME: c->priority XML_AddParameter(candidate, "protocol", "udp"); - XML_AddParameter(candidate, "type", ctypename[c->type]); - XML_AddParameteri(candidate, "generation", c->generation); - XML_AddParameteri(candidate, "network", c->network); + XML_AddParameter(candidate, "type", ctypename[ca->type]); + XML_AddParameteri(candidate, "generation", ca->generation); + XML_AddParameteri(candidate, "network", ca->network); } JCL_SendIQNode(jcl, NULL, "set", c2c->with, session, true); } +*/Con_Printf("No idea how to write candidates for gingle\n"); } else if (!strcmp(action, "session-terminate")) { @@ -234,8 +310,12 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) break; } } - if (c2c->ice) - piceapi->ICE_Close(c2c->ice); + for (c = 0; c < c2c->contents; c++) + { + if (c2c->content[c].ice) + piceapi->ICE_Close(c2c->content[c].ice); + c2c->content[c].ice = NULL; + } return false; } @@ -266,107 +346,123 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) break; } } - if (c2c->ice) - piceapi->ICE_Close(c2c->ice); + for (c = 0; c < c2c->contents; c++) + { + if (c2c->content[c].ice) + piceapi->ICE_Close(c2c->content[c].ice); + c2c->content[c].ice = NULL; + } result = false; } else { - xmltree_t *content = XML_CreateNode(jingle, "content", "", ""); - result = true; - - if (!strcmp(action, "session-accept")) + for (c = 0; c < c2c->contents; c++) { - if (c2c->method == transportmode) + xmltree_t *content = XML_CreateNode(jingle, "content", "", ""); + struct icestate_s *ice = c2c->content[c].ice; + XML_AddParameter(content, "name", c2c->content[c].name); + + if (!strcmp(action, "session-accept")) { - XML_AddParameter(jingle, "responder", jcl->jid); - c2c->accepted = wasaccept = true; + if (c2c->content[c].method == transportmode) + XML_AddParameter(jingle, "responder", jcl->jid); + else + action = "transport-replace"; } - else - action = "transport-replace"; - } - { - xmltree_t *description; - xmltree_t *transport; - if (transportmode == ICEM_RAW) { - transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:raw-udp:1", ""); + xmltree_t *description; + xmltree_t *transport; + if (transportmode == ICEM_RAW) { - xmltree_t *candidate; - struct icecandinfo_s *b = NULL; - struct icecandinfo_s *c; - while ((c = piceapi->ICE_GetLCandidateInfo(ice))) + transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:raw-udp:1", ""); { - if (!b || b->priority < c->priority) - b = c; - } - if (b) - { - candidate = XML_CreateNode(transport, "candidate", "", ""); - XML_AddParameter(candidate, "ip", b->addr); - XML_AddParameteri(candidate, "port", b->port); - XML_AddParameter(candidate, "id", b->candidateid); - XML_AddParameteri(candidate, "generation", b->generation); - XML_AddParameteri(candidate, "component", b->component); + xmltree_t *candidate; + struct icecandinfo_s *b = NULL; + struct icecandinfo_s *c; + while ((c = piceapi->ICE_GetLCandidateInfo(ice))) + { + if (!b || b->priority < c->priority) + b = c; + } + if (b) + { + candidate = XML_CreateNode(transport, "candidate", "", ""); + XML_AddParameter(candidate, "ip", b->addr); + XML_AddParameteri(candidate, "port", b->port); + XML_AddParameter(candidate, "id", b->candidateid); + XML_AddParameteri(candidate, "generation", b->generation); + XML_AddParameteri(candidate, "component", b->component); + } } } - } - else if (transportmode == ICEM_ICE) - { - char val[64]; - transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:ice-udp:1", ""); - piceapi->ICE_Get(ice, "lufrag", val, sizeof(val)); - XML_AddParameter(transport, "ufrag", val); - piceapi->ICE_Get(ice, "lpwd", val, sizeof(val)); - XML_AddParameter(transport, "pwd", val); + else if (transportmode == ICEM_ICE) { - struct icecandinfo_s *c; - while ((c = piceapi->ICE_GetLCandidateInfo(ice))) + char val[64]; + transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:ice-udp:1", ""); + piceapi->ICE_Get(ice, "lufrag", val, sizeof(val)); + XML_AddParameter(transport, "ufrag", val); + piceapi->ICE_Get(ice, "lpwd", val, sizeof(val)); + XML_AddParameter(transport, "pwd", val); { - char *ctypename[]={"host", "srflx", "prflx", "relay"}; - xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", ""); - XML_AddParameter(candidate, "type", ctypename[c->type]); - XML_AddParameter(candidate, "protocol", "udp"); //is this not just a little bit redundant? ice-udp? seriously? - XML_AddParameteri(candidate, "priority", c->priority); - XML_AddParameteri(candidate, "port", c->port); - XML_AddParameteri(candidate, "network", c->network); - XML_AddParameter(candidate, "ip", c->addr); - XML_AddParameter(candidate, "id", c->candidateid); - XML_AddParameteri(candidate, "generation", c->generation); - XML_AddParameteri(candidate, "foundation", c->foundation); - XML_AddParameteri(candidate, "component", c->component); + struct icecandinfo_s *c; + while ((c = piceapi->ICE_GetLCandidateInfo(ice))) + { + char *ctypename[]={"host", "srflx", "prflx", "relay"}; + xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", ""); + XML_AddParameter(candidate, "type", ctypename[c->type]); + XML_AddParameter(candidate, "protocol", "udp"); //is this not just a little bit redundant? ice-udp? seriously? + XML_AddParameteri(candidate, "priority", c->priority); + XML_AddParameteri(candidate, "port", c->port); + XML_AddParameteri(candidate, "network", c->network); + XML_AddParameter(candidate, "ip", c->addr); + XML_AddParameter(candidate, "id", c->candidateid); + XML_AddParameteri(candidate, "generation", c->generation); + XML_AddParameteri(candidate, "foundation", c->foundation); + XML_AddParameteri(candidate, "component", c->component); + } } } - } + if (strcmp(action, "transport-info")) + { #ifdef VOIP - if (c2c->mediatype == ICEP_VOICE) - { - xmltree_t *payload; - int i; + if (c2c->content[c].mediatype == ICEP_VOICE) + { + XML_AddParameter(content, "senders", "both"); + XML_AddParameter(content, "creator", "initiator"); - XML_AddParameter(content, "senders", "both"); - XML_AddParameter(content, "name", "audio-session"); - XML_AddParameter(content, "creator", "initiator"); + description = XML_CreateNode(content, "description", "urn:xmpp:jingle:apps:rtp:1", ""); + XML_AddParameter(description, "media", MEDIATYPE_AUDIO); - description = XML_CreateNode(content, "description", "urn:xmpp:jingle:apps:rtp:1", ""); - XML_AddParameter(description, "media", "audio"); + JCL_PopulateAudioDescription(description, ice); + } + else if (c2c->content[c].mediatype == ICEP_VIDEO) + { + XML_AddParameter(content, "senders", "both"); + XML_AddParameter(content, "creator", "initiator"); - JCL_PopulateAudioDescription(description, ice); - } + description = XML_CreateNode(content, "description", "urn:xmpp:jingle:apps:rtp:1", ""); + XML_AddParameter(description, "media", MEDIATYPE_VIDEO); + + //JCL_PopulateAudioDescription(description, ice); + } #endif - else - { - description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, ""); - XML_AddParameter(description, "media", QUAKEMEDIATYPE); - if (c2c->mediatype == ICEP_QWSERVER) - XML_AddParameter(description, "host", "me"); - else if (c2c->mediatype == ICEP_QWCLIENT) - XML_AddParameter(description, "host", "you"); + else + { + description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, ""); + XML_AddParameter(description, "media", MEDIATYPE_QUAKE); + if (c2c->content[c].mediatype == ICEP_QWSERVER) + XML_AddParameter(description, "host", "me"); + else if (c2c->content[c].mediatype == ICEP_QWCLIENT) + XML_AddParameter(description, "host", "you"); + } + } } } + if (!strcmp(action, "session-accept")) + c2c->accepted = wasaccept = true; } XML_AddParameter(jingle, "action", action); @@ -380,18 +476,22 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) void JCL_JingleTimeouts(jclient_t *jcl, qboolean killall) { + int c; struct c2c_s *c2c; for (c2c = jcl->c2c; c2c; c2c = c2c->next) { - struct icecandinfo_s *lc; - if (c2c->method == ICEM_ICE) + for (c = 0; c < c2c->contents; c++) { - char bah[2]; - piceapi->ICE_Get(c2c->ice, "newlc", bah, sizeof(bah)); - if (atoi(bah)) + if (c2c->content[c].method == ICEM_ICE) { - Con_DPrintf("Sending updated local addresses\n"); - JCL_JingleSend(jcl, c2c, "transport-info"); + char bah[2]; + piceapi->ICE_Get(c2c->content[c].ice, "newlc", bah, sizeof(bah)); + if (atoi(bah)) + { + Con_DPrintf("Sending updated local addresses\n"); + JCL_JingleSend(jcl, c2c, "transport-info"); + break; + } } } } @@ -400,15 +500,18 @@ void JCL_JingleTimeouts(jclient_t *jcl, qboolean killall) void JCL_Join(jclient_t *jcl, char *target, char *sid, qboolean allow, int protocol) { struct c2c_s *c2c = NULL, **link; - xmltree_t *jingle; - struct icestate_s *ice; char autotarget[256]; buddy_t *b; bresource_t *br; + int c; if (!jcl) return; - JCL_FindBuddy(jcl, target, &b, &br); + if (!JCL_FindBuddy(jcl, target, &b, &br, true)) + { + Con_Printf("user/resource not known\n"); + return; + } if (!br) br = b->defaultresource; if (!br) @@ -425,12 +528,18 @@ void JCL_Join(jclient_t *jcl, char *target, char *sid, qboolean allow, int proto target = autotarget; } - for (link = &jcl->c2c; *link; link = &(*link)->next) + for (link = &jcl->c2c; *link && !c2c; link = &(*link)->next) { - if (!strcmp((*link)->with, target) && (!sid || !strcmp((*link)->sid, sid)) && ((*link)->mediatype == protocol || protocol == ICEP_INVALID)) + if (!strcmp((*link)->with, target) && (!sid || !strcmp((*link)->sid, sid))) { - c2c = *link; - break; + if (protocol == ICEP_INVALID) + c2c = *link; + else + { + for (c = 0; c < (*link)->contents; c++) + if ((*link)->content[c].mediatype == protocol) + c2c = *link; + } } } if (allow) @@ -440,7 +549,9 @@ void JCL_Join(jclient_t *jcl, char *target, char *sid, qboolean allow, int proto if (!sid) { char convolink[512], hanguplink[512]; - c2c = JCL_JingleCreateSession(jcl, target, br, true, sid, DEFAULTICEMODE, ((protocol == ICEP_INVALID)?ICEP_QWCLIENT:protocol)); + if (protocol == ICEP_INVALID) + protocol = ICEP_QWCLIENT; + c2c = JCL_JingleAddContentToSession(jcl, NULL, target, br, true, sid, "foobar2000", DEFAULTICEMODE, protocol); JCL_JingleSend(jcl, c2c, "session-initiate"); JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, target, NULL, NULL, "%s", target); @@ -484,58 +595,78 @@ void JCL_Join(jclient_t *jcl, char *target, char *sid, qboolean allow, int proto static void JCL_JingleParsePeerPorts(jclient_t *jcl, struct c2c_s *c2c, xmltree_t *inj, char *from, char *sid) { - xmltree_t *incontent = XML_ChildOfTree(inj, "content", 0); - xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0); + xmltree_t *incontent; + xmltree_t *intransport; xmltree_t *incandidate; struct icecandinfo_s rem; - int i; + struct icestate_s *ice; + int i, contid; + char *cname; if (!c2c->sid) return; - if (!intransport) - return; - - if (!c2c->ice) - return; - if (strcmp(c2c->with, from) || strcmp(c2c->sid, sid)) { Con_Printf("%s is trying to mess with our connections...\n", from); return; } - piceapi->ICE_Set(c2c->ice, "rufrag", XML_GetParameter(intransport, "ufrag", "")); - piceapi->ICE_Set(c2c->ice, "rpwd", XML_GetParameter(intransport, "pwd", "")); - - for (i = 0; (incandidate = XML_ChildOfTree(intransport, "candidate", i)); i++) + //a message can contain multiple contents + for (contid = 0; ; contid++) { - char *s; - memset(&rem, 0, sizeof(rem)); - Q_strlcpy(rem.addr, XML_GetParameter(incandidate, "ip", ""), sizeof(rem.addr)); - Q_strlcpy(rem.candidateid, XML_GetParameter(incandidate, "id", ""), sizeof(rem.candidateid)); + incontent = XML_ChildOfTree(inj, "content", contid); + if (!incontent) + break; + cname = XML_GetParameter(incontent, "name", ""); - s = XML_GetParameter(incandidate, "type", ""); - if (s && !strcmp(s, "srflx")) - rem.type = ICE_SRFLX; - else if (s && !strcmp(s, "prflx")) - rem.type = ICE_PRFLX; - else if (s && !strcmp(s, "relay")) - rem.type = ICE_RELAY; - else - rem.type = ICE_HOST; - rem.port = atoi(XML_GetParameter(incandidate, "port", "0")); - rem.priority = atoi(XML_GetParameter(incandidate, "priority", "0")); - rem.network = atoi(XML_GetParameter(incandidate, "network", "0")); - rem.generation = atoi(XML_GetParameter(incandidate, "generation", "0")); - rem.foundation = atoi(XML_GetParameter(incandidate, "foundation", "0")); - rem.component = atoi(XML_GetParameter(incandidate, "component", "0")); - s = XML_GetParameter(incandidate, "protocol", "udp"); - if (s && !strcmp(s, "udp")) - rem.transport = 0; - else - rem.transport = 0; - piceapi->ICE_AddRCandidateInfo(c2c->ice, &rem); + //find which content this node refers to. + ice = NULL; + for (i = 0; i < c2c->contents; i++) + if (!strcmp(c2c->content[i].name, cname)) + ice = c2c->content[i].ice; + if (!ice) + { + //err... this content doesn't exist? + continue; + } + + intransport = XML_ChildOfTree(incontent, "transport", 0); + if (!intransport) + continue; //err, I guess it wasn't a transport update then (or related). + + piceapi->ICE_Set(ice, "rufrag", XML_GetParameter(intransport, "ufrag", "")); + piceapi->ICE_Set(ice, "rpwd", XML_GetParameter(intransport, "pwd", "")); + + for (i = 0; (incandidate = XML_ChildOfTree(intransport, "candidate", i)); i++) + { + char *s; + memset(&rem, 0, sizeof(rem)); + Q_strlcpy(rem.addr, XML_GetParameter(incandidate, "ip", ""), sizeof(rem.addr)); + Q_strlcpy(rem.candidateid, XML_GetParameter(incandidate, "id", ""), sizeof(rem.candidateid)); + + s = XML_GetParameter(incandidate, "type", ""); + if (s && !strcmp(s, "srflx")) + rem.type = ICE_SRFLX; + else if (s && !strcmp(s, "prflx")) + rem.type = ICE_PRFLX; + else if (s && !strcmp(s, "relay")) + rem.type = ICE_RELAY; + else + rem.type = ICE_HOST; + rem.port = atoi(XML_GetParameter(incandidate, "port", "0")); + rem.priority = atoi(XML_GetParameter(incandidate, "priority", "0")); + rem.network = atoi(XML_GetParameter(incandidate, "network", "0")); + rem.generation = atoi(XML_GetParameter(incandidate, "generation", "0")); + rem.foundation = atoi(XML_GetParameter(incandidate, "foundation", "0")); + rem.component = atoi(XML_GetParameter(incandidate, "component", "0")); + s = XML_GetParameter(incandidate, "protocol", "udp"); + if (s && !strcmp(s, "udp")) + rem.transport = 0; + else + rem.transport = 0; + piceapi->ICE_AddRCandidateInfo(ice, &rem); + } } } #ifdef VOIP_LEGACY @@ -544,6 +675,7 @@ static void JCL_JingleParsePeerPorts_GoogleSession(jclient_t *jcl, struct c2c_s xmltree_t *incandidate; struct icecandinfo_s rem; int i; + int c; if (strcmp(c2c->with, from) || strcmp(c2c->sid, sid)) { @@ -554,9 +686,7 @@ static void JCL_JingleParsePeerPorts_GoogleSession(jclient_t *jcl, struct c2c_s if (!c2c->sid) return; - if (!c2c->ice) - return; - + //with google's session api, every session uses a single set of ports. for (i = 0; (incandidate = XML_ChildOfTree(inj, "candidate", i)); i++) { char *s; @@ -579,9 +709,9 @@ static void JCL_JingleParsePeerPorts_GoogleSession(jclient_t *jcl, struct c2c_s rem.generation = atoi(XML_GetParameter(incandidate, "generation", "0")); rem.foundation = atoi(XML_GetParameter(incandidate, "foundation", "0")); s = XML_GetParameter(incandidate, "name", "rtp"); - if (strcmp(s, "rtp")) + if (!strcmp(s, "rtp")) rem.component = 1; - else if (strcmp(s, "rtcp")) + else if (!strcmp(s, "rtcp")) rem.component = 2; else rem.component = 0; @@ -590,7 +720,10 @@ static void JCL_JingleParsePeerPorts_GoogleSession(jclient_t *jcl, struct c2c_s rem.transport = 0; else rem.transport = 0; - piceapi->ICE_AddRCandidateInfo(c2c->ice, &rem); + + for (c = 0; c < c2c->contents; c++) + if (c2c->content[c].ice) + piceapi->ICE_AddRCandidateInfo(c2c->content[c].ice, &rem); } } static qboolean JCL_JingleHandleInitiate_GoogleSession(jclient_t *jcl, xmltree_t *inj, char *from) @@ -599,10 +732,7 @@ static qboolean JCL_JingleHandleInitiate_GoogleSession(jclient_t *jcl, xmltree_t char *descriptionxmlns = indescription?indescription->xmlns:""; char *sid = XML_GetParameter(inj, "id", ""); - xmltree_t *jingle; - struct icestate_s *ice; qboolean accepted = false; - enum icemode_e imode; char *response = "terminate"; char *offer = "pwn you"; char *autocvar = "xmpp_autoaccepthax"; @@ -627,224 +757,221 @@ static qboolean JCL_JingleHandleInitiate_GoogleSession(jclient_t *jcl, xmltree_t if (mt == ICEP_INVALID) return false; - JCL_FindBuddy(jcl, from, &b, &br); + if (!JCL_FindBuddy(jcl, from, &b, &br, true)) + return false; //FIXME: if both people try to establish a connection to the other simultaneously, the higher session id is meant to be canceled, and the lower accepted automagically. - c2c = JCL_JingleCreateSession(jcl, from, br, false, - sid, + c2c = JCL_JingleAddContentToSession(jcl, NULL, from, br, false, + sid, "the mystical magical content name", ICEM_ICE, mt ); - if (!c2c) - return false; - c2c->peercaps = CAP_FUGOOG_VOICE|CAP_FUGOOG_SESSION; - - if (c2c->mediatype == ICEP_VOICE) + if (c2c) { - qboolean okay = false; - int i = 0; - xmltree_t *payload; - //chuck it at the engine and see what sticks. at least one must... - while((payload = XML_ChildOfTree(indescription, "payload-type", i++))) + int c = c2c->contents-1; + c2c->peercaps = CAP_GOOGLE_VOICE; + + if (mt == ICEP_VOICE) { - char *name = XML_GetParameter(payload, "name", ""); - char *clock = XML_GetParameter(payload, "clockrate", ""); - char *id = XML_GetParameter(payload, "id", ""); - char parm[64]; - char val[64]; - //note: the engine will ignore codecs it does not support, returning false. - if (!stricmp(name, "SPEEX")) + qboolean okay = false; + int i = 0; + xmltree_t *payload; + //chuck it at the engine and see what sticks. at least one must... + while((payload = XML_ChildOfTree(indescription, "payload-type", i++))) { - Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); - Q_snprintf(val, sizeof(val), "speex@%i", atoi(clock)); - okay |= piceapi->ICE_Set(c2c->ice, parm, val); + char *name = XML_GetParameter(payload, "name", ""); + char *clock = XML_GetParameter(payload, "clockrate", ""); + char *id = XML_GetParameter(payload, "id", ""); + char parm[64]; + char val[64]; + //note: the engine will ignore codecs it does not support, returning false. + if (!stricmp(name, "SPEEX")) + { + Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); + Q_snprintf(val, sizeof(val), "speex@%i", atoi(clock)); + okay |= piceapi->ICE_Set(c2c->content[c].ice, parm, val); + } + else if (!stricmp(name, "OPUS")) + { + Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); + okay |= piceapi->ICE_Set(c2c->content[c].ice, parm, "opus"); + } } - else if (!stricmp(name, "OPUS")) + //don't do it if we couldn't successfully set any codecs, because the engine doesn't support the ones that were listed, or something. + //we really ought to give a reason, but we're rude. + if (!okay) { - Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); - okay |= piceapi->ICE_Set(c2c->ice, parm, "opus"); + char convolink[512]; + JCL_JingleSend(jcl, c2c, "terminate"); + JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name); + Con_SubPrintf(b->name, "%s does not support any compatible audio codecs, and is unable to call you.\n", convolink); + return false; } } - //don't do it if we couldn't successfully set any codecs, because the engine doesn't support the ones that were listed, or something. - //we really ought to give a reason, but we're rude. - if (!okay) + + if (mt != ICEP_INVALID) { char convolink[512]; - JCL_JingleSend(jcl, c2c, "session-terminate"); JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name); - Con_SubPrintf(b->name, "%s does not support any compatible audio codecs, and is unable to call you.\n", convolink); - return false; + if (!pCvar_GetFloat(autocvar)) + { + char authlink[512]; + char denylink[512]; + JCL_GenLink(jcl, authlink, sizeof(authlink), "jauth", from, NULL, sid, "%s", "Accept"); + JCL_GenLink(jcl, denylink, sizeof(denylink), "jdeny", from, NULL, sid, "%s", "Reject"); + + //show a prompt for it, send the reply when the user decides. + Con_SubPrintf(b->name, + "%s %s. %s %s\n", convolink, offer, authlink, denylink); + pCon_SetActive(b->name); + return true; + } + else + { + Con_SubPrintf(b->name, "Auto-accepting session from %s\n", convolink); + response = "accept"; + } } } - - if (c2c->mediatype != ICEP_INVALID) - { - char convolink[512]; - JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name); - if (!pCvar_GetFloat(autocvar)) - { - char authlink[512]; - char denylink[512]; - JCL_GenLink(jcl, authlink, sizeof(authlink), "jauth", from, NULL, sid, "%s", "Accept"); - JCL_GenLink(jcl, denylink, sizeof(denylink), "jdeny", from, NULL, sid, "%s", "Reject"); - - //show a prompt for it, send the reply when the user decides. - Con_SubPrintf(b->name, - "%s %s. %s %s\n", convolink, offer, authlink, denylink); - pCon_SetActive(b->name); - return true; - } - else - { - Con_SubPrintf(b->name, "Auto-accepting session from %s\n", convolink); - response = "session-accept"; - } - } - JCL_JingleSend(jcl, c2c, response); return true; } #endif -static qboolean JCL_JingleHandleInitiate(jclient_t *jcl, xmltree_t *inj, char *from) +static struct c2c_s *JCL_JingleHandleInitiate(jclient_t *jcl, xmltree_t *inj, char *from) { - xmltree_t *incontent = XML_ChildOfTree(inj, "content", 0); - xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0); - xmltree_t *indescription = XML_ChildOfTree(incontent, "description", 0); - char *transportxmlns = intransport?intransport->xmlns:""; - char *descriptionxmlns = indescription?indescription->xmlns:""; - char *descriptionmedia = XML_GetParameter(indescription, "media", ""); + xmltree_t *gamecontent = NULL; + xmltree_t *audiocontent = NULL; + xmltree_t *videocontent = NULL; char *sid = XML_GetParameter(inj, "sid", ""); - xmltree_t *jingle; - struct icestate_s *ice; qboolean accepted = false; - enum icemode_e imode; - char *response = "session-terminate"; - char *offer = "pwn you"; - char *autocvar = "xmpp_autoaccepthax"; + qboolean okay; char *initiator; struct c2c_s *c2c = NULL; int mt = ICEP_INVALID; + int i, c; buddy_t *b; bresource_t *br; //FIXME: add support for session forwarding so that we might forward the connection to the real server. for now we just reject it. initiator = XML_GetParameter(inj, "initiator", ""); if (strcmp(initiator, from)) - return false; + return NULL; - if (incontent && !strcmp(descriptionmedia, QUAKEMEDIATYPE) && !strcmp(descriptionxmlns, QUAKEMEDIAXMLNS)) + //reject it if we don't know them. + if (!JCL_FindBuddy(jcl, from, &b, &br, false)) + return NULL; + + for (i = 0; ; i++) { - char *host = XML_GetParameter(indescription, "host", "you"); - if (!strcmp(host, "you")) + xmltree_t *incontent = XML_ChildOfTree(inj, "content", i); + char *cname = XML_GetParameter(incontent, "name", ""); + xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0); + xmltree_t *indescription = XML_ChildOfTree(incontent, "description", 0); + char *transportxmlns = intransport?intransport->xmlns:""; + char *descriptionxmlns = indescription?indescription->xmlns:""; + char *descriptionmedia = XML_GetParameter(indescription, "media", ""); + if (!incontent) + break; + + mt = ICEP_INVALID; + + if (incontent && !strcmp(descriptionmedia, MEDIATYPE_QUAKE) && !strcmp(descriptionxmlns, QUAKEMEDIAXMLNS)) { - mt = ICEP_QWSERVER; - offer = "wants to join your game"; - autocvar = "xmpp_autoacceptjoins"; + char *host = XML_GetParameter(indescription, "host", "you"); + if (!strcmp(host, "you")) + mt = ICEP_QWSERVER; + else if (!strcmp(host, "me")) + mt = ICEP_QWCLIENT; } - else if (!strcmp(host, "me")) + if (incontent && !strcmp(descriptionmedia, MEDIATYPE_AUDIO) && !strcmp(descriptionxmlns, "urn:xmpp:jingle:apps:rtp:1")) + mt = ICEP_VOICE; + if (incontent && !strcmp(descriptionmedia, MEDIATYPE_VIDEO) && !strcmp(descriptionxmlns, "urn:xmpp:jingle:apps:rtp:1")) + mt = ICEP_VIDEO; + + if (mt == ICEP_INVALID) + continue; + + + c2c = JCL_JingleAddContentToSession(jcl, NULL, from, br, false, sid, cname, + strcmp(transportxmlns, "urn:xmpp:jingle:transports:raw-udp:1")?ICEM_ICE:ICEM_RAW, + mt + ); + if (!c2c) + continue; + c = c2c->contents-1; + + okay = false; + if (mt == ICEP_VOICE) { - mt = ICEP_QWCLIENT; - offer = "wants to invite you to thier game"; - autocvar = "xmpp_autoacceptinvites"; - } - } - if (incontent && !strcmp(descriptionmedia, "audio") && !strcmp(descriptionxmlns, "urn:xmpp:jingle:apps:rtp:1")) - { - mt = ICEP_VOICE; - offer = "is trying to call you"; - autocvar = "xmpp_autoacceptvoice"; - } - if (mt == ICEP_INVALID) - return false; - - JCL_FindBuddy(jcl, from, &b, &br); - - //FIXME: if both people try to establish a connection to the other simultaneously, the higher session id is meant to be canceled, and the lower accepted automagically. - - c2c = JCL_JingleCreateSession(jcl, from, br, false, - sid, - strcmp(transportxmlns, "urn:xmpp:jingle:transports:raw-udp:1")?ICEM_ICE:ICEM_RAW, - mt - ); - if (!c2c) - return false; - - c2c->peercaps &= ~(CAP_FUGOOG_SESSION|CAP_FUGOOG_VOICE); - - if (c2c->mediatype == ICEP_VOICE) - { - qboolean okay = false; - int i = 0; - xmltree_t *payload; - //chuck it at the engine and see what sticks. at least one must... - while((payload = XML_ChildOfTree(indescription, "payload-type", i++))) - { - char *name = XML_GetParameter(payload, "name", ""); - char *clock = XML_GetParameter(payload, "clockrate", ""); - char *id = XML_GetParameter(payload, "id", ""); - char parm[64]; - char val[64]; - //note: the engine will ignore codecs it does not support, returning false. - if (!stricmp(name, "SPEEX")) + int i = 0; + xmltree_t *payload; + //chuck it at the engine and see what sticks. at least one must... + while((payload = XML_ChildOfTree(indescription, "payload-type", i++))) { - Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); - Q_snprintf(val, sizeof(val), "speex@%i", atoi(clock)); - okay |= piceapi->ICE_Set(c2c->ice, parm, val); - } - else if (!stricmp(name, "OPUS")) - { - Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); - okay |= piceapi->ICE_Set(c2c->ice, parm, "opus"); + char *name = XML_GetParameter(payload, "name", ""); + char *clock = XML_GetParameter(payload, "clockrate", ""); + char *id = XML_GetParameter(payload, "id", ""); + char parm[64]; + char val[64]; + //note: the engine will ignore codecs it does not support, returning false. + if (!stricmp(name, "SPEEX")) + { + Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); + Q_snprintf(val, sizeof(val), "speex@%i", atoi(clock)); + okay |= piceapi->ICE_Set(c2c->content[c].ice, parm, val); + } + else if (!stricmp(name, "OPUS")) + { + Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id)); + okay |= piceapi->ICE_Set(c2c->content[c].ice, parm, "opus"); + } } } + else + okay = true; //don't do it if we couldn't successfully set any codecs, because the engine doesn't support the ones that were listed, or something. //we really ought to give a reason, but we're rude. if (!okay) { char convolink[512]; - JCL_JingleSend(jcl, c2c, "session-terminate"); JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name); - Con_SubPrintf(b->name, "%s does not support any compatible audio codecs, and is unable to call you.\n", convolink); - return false; + Con_SubPrintf(b->name, "%s does not support any compatible codecs, and is unable to call you.\n", convolink); + + if (c2c->content[c].ice) + piceapi->ICE_Close(c2c->content[c].ice); + c2c->content[c].ice = NULL; + c2c->contents--; } } + if (!c2c) + return NULL; - JCL_JingleParsePeerPorts(jcl, c2c, inj, from, sid); - - if (c2c->mediatype != ICEP_INVALID) + if (!c2c->contents) { - char convolink[512]; - JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name); - if (!pCvar_GetFloat(autocvar)) + if (jcl->c2c == c2c) { - char authlink[512]; - char denylink[512]; - JCL_GenLink(jcl, authlink, sizeof(authlink), "jauth", from, NULL, sid, "%s", "Accept"); - JCL_GenLink(jcl, denylink, sizeof(denylink), "jdeny", from, NULL, sid, "%s", "Reject"); - - //show a prompt for it, send the reply when the user decides. - Con_SubPrintf(b->name, - "%s %s. %s %s\n", convolink, offer, authlink, denylink); - pCon_SetActive(b->name); - return true; + jcl->c2c = c2c->next; + free(c2c); } else - { - Con_SubPrintf(b->name, "Auto-accepting session from %s\n", convolink); - response = "session-accept"; - } + Con_Printf("^1error in "__FILE__" %i\n", __LINE__); + return NULL; } - JCL_JingleSend(jcl, c2c, response); - return true; + //if they speak this, we never want to speak gingle at them! + c2c->peercaps &= ~(CAP_GOOGLE_VOICE); + + JCL_JingleParsePeerPorts(jcl, c2c, inj, from, sid); + return c2c; } static qboolean JCL_JingleHandleSessionTerminate(jclient_t *jcl, xmltree_t *tree, struct c2c_s *c2c, struct c2c_s **link, buddy_t *b) { xmltree_t *reason = XML_ChildOfTree(tree, "reason", 0); + int c; if (!c2c) { Con_Printf("Received session-terminate without an active session\n"); @@ -868,8 +995,9 @@ static qboolean JCL_JingleHandleSessionTerminate(jclient_t *jcl, xmltree_t *tree // XML_ConPrintTree(tree, 0); - if (c2c->ice) - piceapi->ICE_Close(c2c->ice); + for (c = 0; c < c2c->contents; c++) + if (c2c->content[c].ice) + piceapi->ICE_Close(c2c->content[c].ice); free(c2c); return true; } @@ -899,6 +1027,7 @@ static qboolean JCL_JingleHandleSessionAccept(jclient_t *jcl, xmltree_t *tree, c else { char *responder = XML_GetParameter(tree, "responder", from); + int c; if (strcmp(responder, from)) { return false; @@ -910,13 +1039,14 @@ static qboolean JCL_JingleHandleSessionAccept(jclient_t *jcl, xmltree_t *tree, c c2c->accepted = true; //if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted - if (c2c->ice) - piceapi->ICE_Set(c2c->ice, "state", STRINGIFY(ICE_CONNECTING)); + for (c = 0; c < c2c->contents; c++) + if (c2c->content[c].ice) + piceapi->ICE_Set(c2c->content[c].ice, "state", STRINGIFY(ICE_CONNECTING)); } return true; } #ifdef VOIP_LEGACY -qboolean JCL_HandleGoogleSession(jclient_t *jcl, xmltree_t *tree, char *from, char *id) +qboolean JCL_HandleGoogleSession(jclient_t *jcl, xmltree_t *tree, char *from, char *iqid) { char *sid = XML_GetParameter(tree, "id", ""); char *type = XML_GetParameter(tree, "type", ""); @@ -934,14 +1064,16 @@ qboolean JCL_HandleGoogleSession(jclient_t *jcl, xmltree_t *tree, char *from, ch break; } } - JCL_FindBuddy(jcl, from, &b, NULL); + if (!JCL_FindBuddy(jcl, from, &b, NULL, true)) + return false; if (c2c && strcmp(c2c->with, from)) { Con_Printf("%s is trying to mess with our connections...\n", from); return false; } - XML_ConPrintTree(tree, 0); + Con_Printf("google session with %s:\n", from); + XML_ConPrintTree(tree, "", 0); if (!strcmp(type, "accept")) { @@ -971,55 +1103,10 @@ qboolean JCL_HandleGoogleSession(jclient_t *jcl, xmltree_t *tree, char *from, ch } JCL_AddClientMessagef(jcl, - "", from, id); + "", from, iqid); return true; } #endif -enum -{ - JE_ACKNOWLEDGE, - JE_UNSUPPORTED, - JE_OUTOFORDER, - JE_TIEBREAK, - JE_UNKNOWNSESSION, - JE_UNSUPPORTEDINFO -}; -void JCL_JingleError(jclient_t *jcl, xmltree_t *tree, char *from, char *id, int type) -{ - switch(type) - { - case JE_ACKNOWLEDGE: - JCL_AddClientMessagef(jcl, - "", from, id); - break; - case JE_UNSUPPORTED: - JCL_AddClientMessagef(jcl, - "" - "" - "" - "" - "", id, from); - break; - case JE_UNKNOWNSESSION: - JCL_AddClientMessagef(jcl, - "" - "" - "" - "" - "" - "", id, from); - break; - case JE_UNSUPPORTEDINFO: - JCL_AddClientMessagef(jcl, - "" - "" - "" - "" - "" - "", id, from); - break; - } -} qboolean JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id) { char *action = XML_GetParameter(tree, "action", ""); @@ -1038,7 +1125,8 @@ qboolean JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id) } } - JCL_FindBuddy(jcl, from, &b, NULL); + if (!JCL_FindBuddy(jcl, from, &b, NULL, true)) + return false; //validate sender if (c2c && strcmp(c2c->with, from)) @@ -1173,9 +1261,81 @@ qboolean JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id) // Con_Printf("Peer initiating connection!\n"); // XML_ConPrintTree(tree, 0); - JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE); + - JCL_JingleHandleInitiate(jcl, tree, from); + c2c = JCL_JingleHandleInitiate(jcl, tree, from); + + if (c2c) + { + qboolean voice = false, video = false, server = false, client = false; + char *offer; + char convolink[512]; + int c; + qboolean doprompt = false; + JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE); + + for (c = 0; c < c2c->contents; c++) + { + switch(c2c->content[c].mediatype) + { + case ICEP_INVALID: break; + case ICEP_VOICE: voice = true; doprompt |= !pCvar_GetFloat("xmpp_autoacceptvoice"); break; + case ICEP_VIDEO: video = true; doprompt |= !pCvar_GetFloat("xmpp_autoacceptvoice"); break; + case ICEP_QWSERVER: server = true; doprompt |= !pCvar_GetFloat("xmpp_autoacceptjoins"); break; + case ICEP_QWCLIENT: client = true; doprompt |= !pCvar_GetFloat("xmpp_autoacceptinvites"); break; + default: doprompt |= true; break; + } + } + + if (video) + { + if (server) + offer = "wants to join your game (with ^1video^7)"; + else if (client) + offer = "wants to invite you to thier game (with ^1video^7)"; + else + offer = "is trying to ^1video^7 call you"; + } + else if (voice) + { + if (server) + offer = "wants to join your game (with voice)"; + else if (client) + offer = "wants to invite you to thier game (with voice)"; + else + offer = "is trying to call you"; + } + else + { + if (server) + offer = "wants to join your game"; + else if (client) + offer = "wants to invite you to thier game"; + else + offer = "is trying to waste your time"; + } + + JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name); + if (doprompt) + { + char authlink[512]; + char denylink[512]; + JCL_GenLink(jcl, authlink, sizeof(authlink), "jauth", from, NULL, sid, "%s", "Accept"); + JCL_GenLink(jcl, denylink, sizeof(denylink), "jdeny", from, NULL, sid, "%s", "Reject"); + + //show a prompt for it, send the reply when the user decides. + Con_SubPrintf(b->name, + "%s %s. %s %s\n", convolink, offer, authlink, denylink); + pCon_SetActive(b->name); + } + else + { + Con_SubPrintf(b->name, "Auto-accepting session from %s\n", convolink); + JCL_Join(jcl, from, sid, true, ICEP_INVALID); + } + } + else + JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED); } else { diff --git a/plugins/jabber/sift.c b/plugins/jabber/sift.c new file mode 100644 index 00000000..9899f072 --- /dev/null +++ b/plugins/jabber/sift.c @@ -0,0 +1,607 @@ +#include "xmpp.h" + +#ifdef FILETRANSFERS + +void XMPP_FT_Frame(jclient_t *jcl) +{ + static char *hex = "0123456789abcdef"; + char digest[20]; + char domain[sizeof(digest)*2+1]; + int j; + char *req; + struct ft_s *ft; + for (ft = jcl->ft; ft; ft = ft->next) + { + if (ft->streamstatus == STRM_IDLE) + continue; + + if (ft->stream < 0) + { + if (ft->nexthost > 0) + { + ft->nexthost--; + ft->stream = pNet_TCPConnect(ft->streamhosts[ft->nexthost].host, ft->streamhosts[ft->nexthost].port); + if (ft->stream == -1) + continue; + + ft->streamstatus = STRM_AUTH; + + //'authenticate' with socks5 proxy. tell it that we only support 'authless'. + pNet_Send(ft->stream, "\x05\x01\x00", 3); + } + else + { + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "", ft->iqid, ft->with); + ft->streamstatus = STRM_IDLE; + } + continue; + } + else + { + char data[8192]; + int len; + if (ft->streamstatus == STRM_ACTIVE) + { + len = pNet_Recv(ft->stream, data, sizeof(data)-1); + if (len > 0) + pFS_Write(ft->file, data, len); + } + else + { + len = pNet_Recv(ft->stream, data, sizeof(data)-1); + if (len > 0) + { + if (ft->streamstatus == STRM_AUTH) + { + if (len == 2 && data[0] == 0x05 && data[1] == 0x00) + { + //server has accepted us, woo. + //sid+requester(them)+target(us) + req = va("%s%s%s", ft->sid, ft->with, jcl->jid); + SHA1(digest, sizeof(digest), req, strlen(req)); + //in hex + for (req = domain, j=0; j < 20; j++) + { + *req++ = hex[(digest[j]>>4) & 0xf]; + *req++ = hex[(digest[j]>>0) & 0xf]; + } + *req = 0; + + //connect with hostname(3). + req = va("\x05\x01%c\x03""%c%s%c%c", 0, strlen(domain), domain, 0, 0); + pNet_Send(ft->stream, req, strlen(domain)+7); + ft->streamstatus = STRM_AUTHED; + } + else + len = -1; + } + else if (ft->streamstatus == STRM_AUTHED) + { + if (data[0] == 0x05 && data[1] == 0x00) + { + if (pFS_Open(ft->fname, &ft->file, 2) < 0) + { + len = -1; + JCL_AddClientMessagef(jcl, "", ft->iqid, ft->with); + } + else + { + ft->streamstatus = STRM_ACTIVE; + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "", ft->iqid, ft->with, ft->sid, ft->streamhosts[ft->nexthost].jid); + } + } + else + len = -1; //err, this is someone else... + } + } + } + + if (len == -1) + { + pNet_Close(ft->stream); + ft->stream = -1; + if (ft->streamstatus == STRM_ACTIVE) + { + int size; + if (ft->file != -1) + pFS_Close(ft->file); + ft->file = -1; + + size = pFS_Open(ft->fname, &ft->file, 1); + if (ft->file != -1) + pFS_Close(ft->file); + if (size == ft->size) + { + Con_Printf("File Transfer Completed\n"); + continue; + } + else + { + Con_Printf("File Transfer Aborted by peer\n"); + continue; + } + } + Con_Printf("File Transfer connection to %s:%i failed\n", ft->streamhosts[ft->nexthost].host, ft->streamhosts[ft->nexthost].port); + } + } + } +} + +void XMPP_FT_AcceptFile(jclient_t *jcl, int fileid, qboolean accept) +{ + struct ft_s *ft, **link; + char *s; + xmltree_t *repiq, *repsi, *c; + + for (link = &jcl->ft; ft=*link; link = &(*link)->next) + { + if (ft->privateid == fileid) + break; + } + if (!ft) + { + Con_Printf("File not known\n"); + return; + } + + if (!accept) + { + Con_Printf("Declining file \"%s\" from \"%s\" (%i bytes)\n", ft->fname, ft->with, ft->size); + + if (!ft->allowed) + { + JCL_AddClientMessagef(jcl, + "" + "" + "" + "Offer Declined" + "" + "", ft->with, ft->iqid); + } + else + { + //FIXME: send a proper cancel + } + + if (ft->file != -1) + pFS_Close(ft->file); + *link = ft->next; + free(ft); + } + else + { + Con_Printf("Receiving file \"%s\" from \"%s\" (%i bytes)\n", ft->fname, ft->with, ft->size); + ft->allowed = true; + + //generate a response. + //FIXME: we ought to delay response until after we prompt. + repiq = XML_CreateNode(NULL, "iq", "", ""); + XML_AddParameter(repiq, "type", "result"); + XML_AddParameter(repiq, "to", ft->with); + XML_AddParameter(repiq, "id", ft->iqid); + repsi = XML_CreateNode(repiq, "si", "http://jabber.org/protocol/si", ""); + XML_CreateNode(repsi, "file", "http://jabber.org/protocol/si/profile/file-transfer", ""); //I don't really get the point of this. + c = XML_CreateNode(repsi, "feature", "http://jabber.org/protocol/feature-neg", ""); + c = XML_CreateNode(c, "x", "jabber:x:data", ""); + XML_AddParameter(c, "type", "submit"); + c = XML_CreateNode(c, "field", "", ""); + XML_AddParameter(c, "var", "stream-method"); + if (ft->method == FT_IBB) + c = XML_CreateNode(c, "value", "", "http://jabber.org/protocol/ibb"); + else if (ft->method == FT_BYTESTREAM) + c = XML_CreateNode(c, "value", "", "http://jabber.org/protocol/bytestreams"); + + s = XML_GenerateString(repiq, false); + JCL_AddClientMessageString(jcl, s); + free(s); + XML_Destroy(repiq); + } +} + +static qboolean XMPP_FT_IBBChunked(jclient_t *jcl, xmltree_t *x, struct iq_s *iq) +{ + char *from = XML_GetParameter(x, "from", jcl->domain); + struct ft_s *ft = iq->usrptr, **link, *v; + for (link = &jcl->ft; (v=*link); link = &(*link)->next) + { + if (v == ft && !strcmp(ft->with, from)) + { + //its still valid + if (x) + { + char *base64; + char rawbuf[4096]; + int sz; + sz = pFS_Read(ft->file, rawbuf, ft->blocksize); + Base64_Add(rawbuf, sz); + base64 = Base64_Finish(); + + if (sz > 0) + { + ft->sizedone += sz; + if (ft->sizedone == ft->size) + ft->eof = true; + } + + if (sz && strlen(base64)) + { + x = XML_CreateNode(NULL, "data", "http://jabber.org/protocol/ibb", base64); + XML_AddParameteri(x, "seq", ft->seq++); + XML_AddParameter(x, "sid", ft->sid); + JCL_SendIQNode(jcl, XMPP_FT_IBBChunked, "set", from, x, true)->usrptr = ft; + return true; + } + //else eof + } + + //errored or ended + + if (x) + Con_Printf("Transfer of %s to %s completed\n", ft->fname, ft->with); + else + Con_Printf("%s aborted %s\n", ft->with, ft->fname); + x = XML_CreateNode(NULL, "close", "http://jabber.org/protocol/ibb", ""); + XML_AddParameter(x, "sid", ft->sid); + JCL_SendIQNode(jcl, NULL, "set", from, x, true)->usrptr = ft; + + //errored + if (ft->file != -1) + pFS_Close(ft->file); + *link = ft->next; + free(ft); + return true; + } + } + return true; //the ack can come after the bytestream has already finished sending. don't warn about that. +} +static qboolean XMPP_FT_IBBBegun(jclient_t *jcl, xmltree_t *x, struct iq_s *iq) +{ + struct ft_s *ft = iq->usrptr, **link, *v; + char *from = XML_GetParameter(x, "from", jcl->domain); + for (link = &jcl->ft; (v=*link); link = &(*link)->next) + { + if (v == ft && !strcmp(ft->with, from)) + { + //its still valid + if (!x) + { + Con_Printf("%s aborted %s\n", ft->with, ft->fname); + //errored + if (ft->file != -1) + pFS_Close(ft->file); + *link = ft->next; + free(ft); + } + else + { + ft->begun = true; + ft->method = FT_IBB; + XMPP_FT_IBBChunked(jcl, x, iq); + } + return true; + } + } + return false; +} +qboolean XMPP_FT_OfferAcked(jclient_t *jcl, xmltree_t *x, struct iq_s *iq) +{ + struct ft_s *ft = iq->usrptr, **link, *v; + char *from = XML_GetParameter(x, "from", jcl->domain); + for (link = &jcl->ft; (v=*link); link = &(*link)->next) + { + if (v == ft && !strcmp(ft->with, from)) + { + //its still valid + if (!x) + { + Con_Printf("%s doesn't want %s\n", ft->with, ft->fname); + //errored + if (ft->file != -1) + pFS_Close(ft->file); + *link = ft->next; + free(ft); + } + else + { + xmltree_t *xo; + Con_Printf("%s accepted %s\n", ft->with, ft->fname); + xo = XML_CreateNode(NULL, "open", "http://jabber.org/protocol/ibb", ""); + XML_AddParameter(xo, "sid", ft->sid); + XML_AddParameteri(xo, "block-size", ft->blocksize); + //XML_AddParameter(xo, "stanza", "iq"); + + JCL_SendIQNode(jcl, XMPP_FT_IBBBegun, "set", XML_GetParameter(x, "from", jcl->domain), xo, true)->usrptr = ft; + } + return true; + } + } + return false; +} + +void XMPP_FT_SendFile(jclient_t *jcl, char *console, char *to, char *fname) +{ + xmltree_t *xsi, *xfile, *c; + struct ft_s *ft; + + Con_SubPrintf(console, "Offering %s to %s.\n", fname, to); + + ft = malloc(sizeof(*ft)); + memset(ft, 0, sizeof(*ft)); + ft->stream = -1; + ft->next = jcl->ft; + jcl->ft = ft; + ft->allowed = true; + ft->transmitting = true; + ft->blocksize = 4096; + Q_strlcpy(ft->fname, fname, sizeof(ft->fname)); + Q_snprintf(ft->sid, sizeof(ft->sid), "%x%s", rand(), ft->fname); + Q_strlcpy(ft->md5hash, "", sizeof(ft->md5hash)); + ft->size = pFS_Open(ft->fname, &ft->file, 1); + ft->with = strdup(to); + ft->method = FT_IBB; + ft->begun = false; + + //generate an offer. + xsi = XML_CreateNode(NULL, "si", "http://jabber.org/protocol/si", ""); + XML_AddParameter(xsi, "profile", "http://jabber.org/protocol/si/profile/file-transfer"); + XML_AddParameter(xsi, "id", ft->sid); + //XML_AddParameter(xsi, "mime-type", "text/plain"); + xfile = XML_CreateNode(xsi, "file", "http://jabber.org/protocol/si/profile/file-transfer", ""); //I don't really get the point of this. + XML_AddParameter(xfile, "name", ft->fname); + XML_AddParameteri(xfile, "size", ft->size); + c = XML_CreateNode(xsi, "feature", "http://jabber.org/protocol/feature-neg", ""); + c = XML_CreateNode(c, "x", "jabber:x:data", ""); + XML_AddParameter(c, "type", "form"); + c = XML_CreateNode(c, "field", "", ""); + XML_AddParameter(c, "var", "stream-method"); + XML_AddParameter(c, "type", "listsingle"); + XML_CreateNode(XML_CreateNode(c, "option", "", ""), "value", "", "http://jabber.org/protocol/ibb"); +// XML_CreateNode(XML_CreateNode(c, "option", "", ""), "value", "", "http://jabber.org/protocol/bytestreams"); + + JCL_SendIQNode(jcl, XMPP_FT_OfferAcked, "set", to, xsi, true)->usrptr = ft; +} + +qboolean XMPP_FT_ParseIQSet(jclient_t *jcl, char *iqfrom, char *iqid, xmltree_t *tree) +{ + xmltree_t *ot; + + //if file transfers are not enabled in this build, reject all iqs + if (!(jcl->enabledcapabilities & CAP_SIFT)) + return false; + + ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/bytestreams", "query", 0); + if (ot) + { + xmltree_t *c; + struct ft_s *ft; + char *sid = XML_GetParameter(ot, "sid", ""); + for (ft = jcl->ft; ft; ft = ft->next) + { + if (!strcmp(ft->sid, sid) && !strcmp(ft->with, iqfrom)) + { + if (ft->allowed && !ft->begun && ft->transmitting == false) + { + char *jid; + char *host; + int port; + char *mode = XML_GetParameter(ot, "mode", "tcp"); + int i; + if (strcmp(mode, "tcp")) + break; + for (i = 0; ; i++) + { + c = XML_ChildOfTree(ot, "streamhost", i); + if (!c) + break; + + jid = XML_GetParameter(c, "jid", ""); + host = XML_GetParameter(c, "host", ""); + port = atoi(XML_GetParameter(c, "port", "0")); + if (port <= 0 || port > 65535) + continue; + if (ft->nexthost < sizeof(ft->streamhosts) / sizeof(ft->streamhosts[ft->nexthost])) + { + Q_strlcpy(ft->streamhosts[ft->nexthost].jid, jid, sizeof(ft->streamhosts[ft->nexthost].jid)); + Q_strlcpy(ft->streamhosts[ft->nexthost].host, host, sizeof(ft->streamhosts[ft->nexthost].host)); + ft->streamhosts[ft->nexthost].port = port; + ft->nexthost++; + } + } + ft->streamstatus = STRM_AUTH; + //iq gets acked when we finished connecting to the proxy. *then* the data can flow + Q_strlcpy(ft->iqid, iqid, sizeof(ft->iqid)); + return true; + } + } + } + JCL_AddClientMessagef(jcl, "", iqid, iqfrom); + return true; + } + + ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "open", 0); + if (ot) + { + struct ft_s *ft; + char *sid = XML_GetParameter(ot, "sid", ""); + int blocksize = atoi(XML_GetParameter(ot, "block-size", "4096")); //technically this is required. + char *stanza = XML_GetParameter(ot, "stanza", "iq"); + for (ft = jcl->ft; ft; ft = ft->next) + { + if (!strcmp(ft->sid, sid) && !strcmp(ft->with, iqfrom)) + { + if (ft->allowed && !ft->begun && ft->transmitting == false) + { + if (blocksize > 65536 || strcmp(stanza, "iq")) + { //blocksize: MUST NOT be greater than 65535 + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "" + , iqid, iqfrom); + } + else if (blocksize > 4096) + { //ask for smaller chunks + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "" + , iqid, iqfrom); + } + else + { //it looks okay + pFS_Open(ft->fname, &ft->file, 2); + ft->method = FT_IBB; + ft->blocksize = blocksize; + ft->begun = true; + //if its okay... + JCL_AddClientMessagef(jcl, "", iqid, iqfrom); + } + break; + } + } + } + return false; + } + ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "close", 0); + if (ot) + { + struct ft_s **link, *ft; + char *sid = XML_GetParameter(ot, "sid", ""); + for (link = &jcl->ft; *link; link = &(*link)->next) + { + ft = *link; + if (!strcmp(ft->sid, sid) && !strcmp(ft->with, iqfrom)) + { + if (ft->begun && ft->method == FT_IBB) + { + int size; + if (ft->file != -1) + pFS_Close(ft->file); + if (ft->transmitting) + { + if (ft->eof) + Con_Printf("Sent \"%s\" to \"%s\"\n", ft->fname, ft->with); + else + Con_Printf("%s aborted transfer of \"%s\"\n", iqfrom, ft->fname); + } + else + { + size = pFS_Open(ft->fname, &ft->file, 1); + if (ft->file != -1) + pFS_Close(ft->file); + if (size == ft->size) + Con_Printf("Received file \"%s\" successfully\n", ft->fname); + else + Con_Printf("%s aborted transfer of \"%s\"\n", iqfrom, ft->fname); + } + *link = ft->next; + free(ft); + //if its okay... + JCL_AddClientMessagef(jcl, "", iqid, iqfrom); + return true; + } + } + } + return false; //some kind of error + } + ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/ibb", "data", 0); + if (ot) + { + char block[65536]; + char *sid = XML_GetParameter(ot, "sid", ""); + unsigned short seq = atoi(XML_GetParameter(ot, "seq", "0")); + int blocksize; + struct ft_s *ft; + for (ft = jcl->ft; ft; ft = ft->next) + { + if (!strcmp(ft->sid, sid) && !ft->transmitting) + { + blocksize = Base64_Decode(block, sizeof(block), ot->body, strlen(ot->body)); + if (blocksize && blocksize <= ft->blocksize) + { + pFS_Write(ft->file, block, blocksize); + JCL_AddClientMessagef(jcl, "", iqid, iqfrom); + return true; + } + else + Con_Printf("XMPP: Invalid blocksize in file transfer from %s\n", iqfrom); + break; + } + } + return true; + } + + ot = XML_ChildOfTreeNS(tree, "http://jabber.org/protocol/si", "si", 0); + if (ot) + { + char *profile = XML_GetParameter(ot, "profile", ""); + + if (!strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer")) + { + char *mimetype = XML_GetParameter(ot, "mime-type", "text/plain"); + char *sid = XML_GetParameter(ot, "id", ""); + xmltree_t *file = XML_ChildOfTreeNS(ot, "http://jabber.org/protocol/si/profile/file-transfer", "file", 0); + char *fname = XML_GetParameter(file, "name", "file.txt"); + char *date = XML_GetParameter(file, "date", ""); + char *md5hash = XML_GetParameter(file, "hash", ""); + int fsize = strtoul(XML_GetParameter(file, "size", "0"), NULL, 0); + char *desc = XML_GetChildBody(file, "desc", ""); + char authlink[512]; + char denylink[512]; + + //file transfer offer + struct ft_s *ft = malloc(sizeof(*ft) + strlen(iqfrom)+1); + memset(ft, 0, sizeof(*ft)); + ft->stream = -1; + ft->next = jcl->ft; + jcl->ft = ft; + ft->privateid = ++jcl->privateidseq; + + ft->transmitting = false; + Q_strlcpy(ft->iqid, iqid, sizeof(ft->iqid)); + Q_strlcpy(ft->sid, sid, sizeof(ft->sid)); + Q_strlcpy(ft->fname, fname, sizeof(ft->sid)); + Base64_Decode(ft->md5hash, sizeof(ft->md5hash), md5hash, strlen(md5hash)); + ft->size = fsize; + ft->file = -1; + ft->with = (char*)(ft+1); + strcpy(ft->with, iqfrom); + ft->method = (fsize > 1024*128)?FT_BYTESTREAM:FT_IBB; //favour bytestreams for large files. for small files, just use ibb as it saves sorting out proxies. + ft->begun = false; + + JCL_GenLink(jcl, authlink, sizeof(authlink), "fauth", iqfrom, NULL, va("%i", ft->privateid), "%s", "Accept"); + JCL_GenLink(jcl, denylink, sizeof(denylink), "fdeny", iqfrom, NULL, va("%i", ft->privateid), "%s", "Deny"); + + Con_Printf("%s has offered to send you \"%s\" (%i bytes). %s %s\n", iqfrom, fname, fsize, authlink, denylink); + } + else + { + //profile not understood + JCL_AddClientMessagef(jcl, + "" + "" + "" + "" + "" + "", iqfrom, iqid); + } + return true; + } + + return false; +} +#endif diff --git a/plugins/jabber/xml.c b/plugins/jabber/xml.c index 38124c36..9d257432 100644 --- a/plugins/jabber/xml.c +++ b/plugins/jabber/xml.c @@ -2,6 +2,9 @@ #include "xml.h" +//fixme +void (*Con_TrySubPrint)(const char *conname, const char *message); + void XML_Destroy(xmltree_t *t); char *XML_GetParameter(xmltree_t *t, char *paramname, char *def) @@ -563,7 +566,7 @@ char *XML_GetChildBody(xmltree_t *t, char *paramname, char *def) return def; } -void XML_ConPrintTree(xmltree_t *t, int indent) +void XML_ConPrintTree(xmltree_t *t, char *subconsole, int indent) { int start, c, chunk; struct buf_ctx buf = {NULL, 0, 0}; @@ -577,7 +580,7 @@ void XML_ConPrintTree(xmltree_t *t, int indent) chunk = 128; c = buf.buf[start+chunk]; buf.buf[start+chunk] = 0; - Con_Printf("%s", buf.buf+start); + Con_TrySubPrint(subconsole, buf.buf+start); buf.buf[start+chunk] = c; start += chunk; @@ -649,8 +652,6 @@ static qboolean XML_ParseString(char *msg, int *pos, int max, char *out, int out xmltree_t *XML_FromJSON(xmltree_t *t, char *name, char *json, int *jsonpos, int jsonlen) { char child[4096]; - char *start; - char *end; XML_SkipWhite(json, jsonpos, jsonlen); if (*jsonpos < jsonlen && json[*jsonpos] == '{') diff --git a/plugins/jabber/xml.h b/plugins/jabber/xml.h index 14bd495a..7b6929d8 100644 --- a/plugins/jabber/xml.h +++ b/plugins/jabber/xml.h @@ -32,6 +32,6 @@ void XML_Destroy(xmltree_t *t); xmltree_t *XML_ChildOfTree(xmltree_t *t, char *name, int childnum); xmltree_t *XML_ChildOfTreeNS(xmltree_t *t, char *xmlns, char *name, int childnum); char *XML_GetChildBody(xmltree_t *t, char *paramname, char *def); -void XML_ConPrintTree(xmltree_t *t, int indent); +void XML_ConPrintTree(xmltree_t *t, char *subconsole, int indent); xmltree_t *XML_FromJSON(xmltree_t *t, char *name, char *json, int *jsonpos, int jsonlen); \ No newline at end of file diff --git a/plugins/jabber/xmpp.h b/plugins/jabber/xmpp.h index 82ee232c..186d2e00 100644 --- a/plugins/jabber/xmpp.h +++ b/plugins/jabber/xmpp.h @@ -6,7 +6,7 @@ //configuration //#define NOICE //if defined, we only do simple raw udp connections. -//#define FILETRANSFERS //IBB only, speeds suck. autoaccept is forced on. no protection from mods stuffcmding sendfile commands. needs more extensive testing +#define FILETRANSFERS //IBB only, speeds suck. autoaccept is forced on. no protection from mods stuffcmding sendfile commands. needs more extensive testing #define QUAKECONNECT //including quake ICE connections (depends upon jingle) #define VOIP //enables voice chat (depends upon jingle) #define VOIP_LEGACY //enables google-only voice chat compat. google have not kept up with the standardisation of jingle (aka: gingle). @@ -24,27 +24,35 @@ #define JCL_BUILD "3" #define DEFAULTDOMAIN "" #define DEFAULTRESOURCE "Quake" -#define QUAKEMEDIATYPE "quake" #define QUAKEMEDIAXMLNS "http://fteqw.com/protocol/quake" #define DISCONODE "http://fteqw.com/ftexmpp" //some sort of client identifier #define DEFAULTICEMODE ICEM_ICE +#define MEDIATYPE_QUAKE "quake" +#define MEDIATYPE_VIDEO "video" +#define MEDIATYPE_AUDIO "audio" + #define JCL_MAXMSGLEN 10000 //values are not on the wire or anything -#define CAP_QUERYING (1<<0) //we've sent a query and are waiting for the response. -#define CAP_QUERIED (1<<1) //feature capabilities are actually know. -#define CAP_QUERYFAILED (1<<2) //caps request failed due to bad hash or some such. -#define CAP_VOICE (1<<3) //supports voice +#define CAP_VOICE (1u<<0) //supports voice +#define CAP_GOOGLE_VOICE (1u<<1) //google supports some old non-standard version of jingle. +#define CAP_VIDEO (1u<<2) //supports video calls -#define CAP_INVITE (1<<4) //supports game invites. -#define CAP_POKE (1<<5) //can be slapped. -#define CAP_SIFT (1<<6) //non-jingle file transfers +#define CAP_GAMEINVITE (1u<<3) //supports game invites. custom/private protocol +#define CAP_POKE (1u<<4) //can be slapped. +#define CAP_SIFT (1u<<5) //non-jingle file transfers +#define CAP_AVATARS (1u<<6) //can enable querying for user avatars, but cannot disable advertising our own. -#define CAP_FUGOOG_VOICE (1<<7) //fuck you, google. -#define CAP_FUGOOG_SESSION (1<<8) //fuck you, google. +//not actually capabilities, but just down to how we handle querying them. +#define CAP_QUERYING (1u<<29) //we've sent a query and are waiting for the response. +#define CAP_QUERIED (1u<<30) //feature capabilities are actually know. +#define CAP_QUERYFAILED (1u<<31) //caps request failed due to bad hash or some such. + +//features that default to disabled. +#define CAP_DEFAULTENABLEDCAPS (CAP_VOICE/*|CAP_VIDEO*/|CAP_GAMEINVITE|CAP_POKE|CAP_AVATARS/*|CAP_SIFT*/|CAP_GOOGLE_VOICE) typedef struct bresource_s { @@ -53,6 +61,7 @@ typedef struct bresource_s char server[256]; int servertype; //0=none, 1=already a client, 2=joinable + unsigned int buggycaps; unsigned int caps; char *client_node; //vendor name char *client_ver; //cap hash value @@ -70,8 +79,11 @@ typedef struct buddy_s int defaulttimestamp; qboolean friended; qboolean chatroom; //chatrooms are bizzare things that need special handling. + qboolean vcardphotochanged; char name[256]; + char vcardphotohash[41]; + qhandle_t image; struct buddy_s *next; char accountdomain[1]; //no resource on there @@ -91,6 +103,8 @@ typedef struct jclient_s } status; unsigned int timeout; //reconnect/ping timer + unsigned int enabledcapabilities; + qhandle_t socket; //we buffer output for times when the outgoing socket is full. @@ -116,6 +130,7 @@ typedef struct jclient_s char resource[256]; char certificatedomain[256]; int forcetls; //-1=off, 0=ifpossible, 1=fail if can't upgrade, 2=old-style tls + qboolean savepassword; qboolean allowauth_plainnontls; //allow plain plain qboolean allowauth_plaintls; //allow tls plain qboolean allowauth_digestmd5; //allow digest-md5 auth @@ -124,6 +139,14 @@ typedef struct jclient_s char jid[256]; //this is our full username@domain/resource string char localalias[256];//this is what's shown infront of outgoing messages. >> by default until we can get our name. + char vcardphotohash[20]; //20-byte sha1 hash. + enum + { + VCP_UNKNOWN, + VCP_NONE, + VCP_KNOWN + } vcardphotohashstatus; + qboolean vcardphotohashchanged; //forces a presence send. char authnonce[256]; int authmode; @@ -134,7 +157,7 @@ typedef struct jclient_s qboolean connected; //fully on server and authed and everything. qboolean issecure; //tls enabled (either upgraded or initially) - qboolean streamdebug; //echo the stream to subconsoles + int streamdebug; //echo the stream to subconsoles qboolean preapproval; //server supports presence preapproval @@ -169,15 +192,20 @@ typedef struct jclient_s struct c2c_s { struct c2c_s *next; - enum iceproto_e mediatype; - enum icemode_e method; //ICE_RAW or ICE_ICE. this is what the peer asked for. updated if we degrade it. qboolean accepted; //connection is going qboolean creator; //true if we're the creator. unsigned int peercaps; - struct icestate_s *ice; - char *peeraddr; - int peerport; + struct + { + char name[64]; //uniquely identifies the content within the session. + enum iceproto_e mediatype; + enum icemode_e method; //ICE_RAW or ICE_ICE. this is what the peer asked for. updated if we degrade it. + struct icestate_s *ice; + char *peeraddr; + int peerport; + } content[3]; + int contents; char *with; char sid[1]; @@ -205,8 +233,26 @@ typedef struct jclient_s qboolean transmitting; //we're offering qboolean allowed; //if false, don't handshake the transfer + struct + { + char jid[128]; + char host[40]; + int port; + } streamhosts[16]; + int nexthost; enum { + STRM_IDLE, + STRM_AUTH, + STRM_AUTHED, + STRM_ACTIVE + } streamstatus; + char indata[64]; //only for handshake data + int inlen; + + enum + { + FT_NOTSTARTED, FT_IBB, //in-band bytestreams FT_BYTESTREAM //aka: relay } method; @@ -228,7 +274,8 @@ qboolean NET_DNSLookup_SRV(char *host, char *out, int outlen); //xmpp functionality struct iq_s *JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree, struct iq_s *iq), char *iqtype, char *target, xmltree_t *node, qboolean destroynode); void JCL_AddClientMessagef(jclient_t *jcl, char *fmt, ...); -qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres); +void JCL_AddClientMessageString(jclient_t *jcl, char *msg); +qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres, qboolean create); //quake functionality void JCL_GenLink(jclient_t *jcl, char *out, int outlen, char *action, char *context, char *contextres, char *sid, char *txtfmt, ...); @@ -241,4 +288,12 @@ void JCL_JingleTimeouts(jclient_t *jcl, qboolean killall); qboolean JCL_HandleGoogleSession(jclient_t *jcl, xmltree_t *tree, char *from, char *id); qboolean JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id); -void JCL_FT_AcceptFile(jclient_t *jcl, int fileid, qboolean accept); \ No newline at end of file +void XMPP_FT_AcceptFile(jclient_t *jcl, int fileid, qboolean accept); +qboolean XMPP_FT_OfferAcked(jclient_t *jcl, xmltree_t *x, struct iq_s *iq); +qboolean XMPP_FT_ParseIQSet(jclient_t *jcl, char *iqfrom, char *iqid, xmltree_t *tree); +void XMPP_FT_SendFile(jclient_t *jcl, char *console, char *to, char *fname); +void XMPP_FT_Frame(jclient_t *jcl); + +void Base64_Add(char *s, int len); +char *Base64_Finish(void); +int Base64_Decode(char *out, int outlen, char *src, int srclen); diff --git a/plugins/plugin.c b/plugins/plugin.c index 9608c0ef..46d5a077 100644 --- a/plugins/plugin.c +++ b/plugins/plugin.c @@ -148,6 +148,12 @@ BUILTIN(void, LocalSound, (char *soundname)); BUILTIN(void, GetPluginName, (int plugnum, char *buffer, int bufsize)); #undef ARGNAMES +#define ARGNAMES ,name,mime,data,datalen +BUILTINR(qhandle_t, Draw_LoadImageData, (char *name, char *mime, void *data, unsigned int datalen)); //force-replace a texture. +#undef ARGNAMES +#define ARGNAMES ,name,shaderscript +BUILTINR(qhandle_t, Draw_LoadImageShader, (char *name, char *shaderscript)); //some shader script +#undef ARGNAMES #define ARGNAMES ,name,iswadimage BUILTINR(qhandle_t, Draw_LoadImage, (char *name, qboolean iswadimage)); //wad image is ONLY for loading out of q1 gfx.wad #undef ARGNAMES @@ -160,8 +166,11 @@ BUILTIN(void, Draw_Line, (float x1, float y1, float x2, float y2)); #define ARGNAMES ,PASSFLOAT(x),PASSFLOAT(y),PASSFLOAT(w),PASSFLOAT(h) BUILTIN(void, Draw_Fill, (float x, float y, float w, float h)); #undef ARGNAMES -#define ARGNAMES ,x,y,characture -BUILTIN(void, Draw_Character, (int x, int y, unsigned int characture)); +#define ARGNAMES ,x,y,character +BUILTIN(void, Draw_Character, (int x, int y, unsigned int character)); +#undef ARGNAMES +#define ARGNAMES ,PASSFLOAT(x),PASSFLOAT(y),string +BUILTIN(void, Draw_String, (float x, float y, char *string)); #undef ARGNAMES #define ARGNAMES ,palcol BUILTIN(void, Draw_Colourp, (int palcol)); @@ -366,11 +375,14 @@ void Plug_InitStandardBuiltins(void) CHECKBUILTIN(GetLocationName); //drawing routines + CHECKBUILTIN(Draw_LoadImageData); + CHECKBUILTIN(Draw_LoadImageShader); CHECKBUILTIN(Draw_LoadImage); CHECKBUILTIN(Draw_Image); CHECKBUILTIN(Draw_Line); CHECKBUILTIN(Draw_Fill); CHECKBUILTIN(Draw_Character); + CHECKBUILTIN(Draw_String); CHECKBUILTIN(Draw_Colourp); CHECKBUILTIN(Draw_Colour3f); CHECKBUILTIN(Draw_Colour4f); diff --git a/plugins/plugin.h b/plugins/plugin.h index f948ad0a..300ae0e8 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -217,11 +217,14 @@ EBUILTIN(void, Menu_Control, (int mnum)); #define MENU_GRAB 1 EBUILTIN(int, Key_GetKeyCode, (char *keyname)); -EBUILTIN(qhandle_t, Draw_LoadImage, (char *name, qboolean iswadimage)); //wad image is ONLY for loading out of q1 gfx.wad +EBUILTIN(qhandle_t, Draw_LoadImageData, (char *name, char *mime, void *data, unsigned int datasize)); //load/replace a named texture +EBUILTIN(qhandle_t, Draw_LoadImageShader, (char *name, char *defaultshader)); //loads a shader. +EBUILTIN(qhandle_t, Draw_LoadImage, (char *name, qboolean iswadimage)); //wad image is ONLY for loading out of q1 gfx.wad. loads a shader. EBUILTIN(int, Draw_Image, (float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t image)); EBUILTIN(void, Draw_Fill, (float x, float y, float w, float h)); EBUILTIN(void, Draw_Line, (float x1, float y1, float x2, float y2)); EBUILTIN(void, Draw_Character, (int x, int y, unsigned int character)); +EBUILTIN(void, Draw_String, (float x, float y, char *string)); EBUILTIN(void, Draw_Colourp, (int palcol)); EBUILTIN(void, Draw_Colour3f, (float r, float g, float b)); EBUILTIN(void, Draw_Colour4f, (float r, float g, float b, float a));