diff --git a/CMakeLists.txt b/CMakeLists.txt index c29d0de31..c70192ab4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1143,7 +1143,9 @@ ELSE() fteqtv/source.c fteqtv/bsp.c fteqtv/rcon.c + fteqtv/relay.c fteqtv/mdfour.c + engine/common/md5.c fteqtv/crc.c fteqtv/control.c fteqtv/forward.c @@ -1155,9 +1157,9 @@ ELSE() ) SET_TARGET_PROPERTIES(qtv PROPERTIES COMPILE_DEFINITIONS "${FTE_REVISON}") IF(WIN32) - TARGET_LINK_LIBRARIES(qtv ws2_32 winmm ${SYS_LIBS}) + TARGET_LINK_LIBRARIES(qtv ws2_32 winmm ${SYS_LIBS} ${ZLIB_LIBRARIES}) ELSE() - TARGET_LINK_LIBRARIES(qtv ${SYS_LIBS}) + TARGET_LINK_LIBRARIES(qtv ${SYS_LIBS} ${ZLIB_LIBRARIES}) ENDIF() SET(INSTALLTARGS ${INSTALLTARGS} qtv) ENDIF() diff --git a/engine/Makefile b/engine/Makefile index 9466f7686..637cfabd2 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -48,8 +48,8 @@ endif ifeq ($(SVNREVISION),) #try subversion firstly... - SVN_VERSION:=$(shell test -d $(BASE_DIR)/../.svn && svnversion $(BASE_DIR)) - SVN_DATE:=$(shell test -d $(BASE_DIR)/../.svn && cd $(BASE_DIR) && svn info --show-item last-changed-date --no-newline) +# SVN_VERSION:=$(shell test -d $(BASE_DIR)/../.svn && svnversion $(BASE_DIR)) +# SVN_DATE:=$(shell test -d $(BASE_DIR)/../.svn && cd $(BASE_DIR) && svn info --show-item last-changed-date --no-newline) # ifeq (,$(SVN_VERSION)) # #grab the svn version from git-svn (assuming no other modifications). this fails when there's extra commits (probably a good thing). # SVN_VERSION=$(shell test -d $(BASE_DIR)/../.git && git svn find-rev `git rev-parse HEAD`) @@ -86,7 +86,7 @@ PNGVER=1.6.40 OGGVER=1.3.4 VORBISVER=1.3.7 VULKANVER=1.3.275.0 -SDL2VER=2.26.4 +SDL2VER=2.30.4 SCINTILLAVER=373 OPUSVER=1.3.1 SPEEXVER=1.2.0 @@ -1053,6 +1053,7 @@ endif ifeq (1,$(LINK_ZLIB)) CLIENTLIBFLAGS+=-DZLIB_STATIC CLIENTLDDEPS+=-lz + SERVERLDDEPS+=-lz #and deflate64, because why not. ifneq ("$(wildcard $(ARCHLIBS)/infback9.h)","") @@ -1122,7 +1123,7 @@ GLCL_OBJS=$(GL_OBJS) $(D3DGL_OBJS) $(GLQUAKE_OBJS) gl_vidsdl.o snd_sdl.o cd_sdl. GL_EXE_NAME=../$(EXE_NAME)-gl$(FTE_FULLTARGET) GLCL_EXE_NAME=../$(EXE_NAME)cl-gl$(FTE_FULLTARGET) -#SDLCONFIG:=libs/sdl2_mingw/$(CC_MACHINE)/bin/sdl2-config --prefix=libs/sdl2_mingw/$(CC_MACHINE) +#SDLCONFIG:=libs-$(ARCH)/sdl2_mingw/$(CC_MACHINE)/bin/sdl2-config --prefix=libs-$(ARCH)/sdl2_mingw/$(CC_MACHINE) ifdef windir GL_LDFLAGS=$(GLLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` VK_LDFLAGS=$(VKLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` @@ -1163,11 +1164,12 @@ M_CFLAGS=-DFTE_SDL $(VKCFLAGS) $(GLCFLAGS) -DMULTITHREAD `$(SDLCONFIG) --cflags` QCC_DIR=qcc$(BITS) ifeq (,$(findstring NO_ZLIB,$(CFLAGS))) - SV_LDFLAGS+=-lz - GL_LDFLAGS+=-lz - VK_LDFLAGS+=-lz - M_LDFLAGS+=-lz - QCC_LDFLAGS+=-L$(ARCHLIBS) -lz + SV_LDFLAGS+=-lz + GL_LDFLAGS+=-lz + VK_LDFLAGS+=-lz + M_LDFLAGS+=-lz + QCC_LDFLAGS+=-L$(ARCHLIBS) -lz + QTV_LDFLAGS+=-lz endif @@ -1176,80 +1178,87 @@ endif #FTE_TARGET=win32_SDL | FTE_TARGET=win64_SDL (MinGW32 + SDL | MinGW64 + SDL) ifeq (win_SDL,$(findstring win,$(FTE_TARGET))$(findstring _SDL,$(FTE_TARGET))) - DO_CMAKE+=-DCMAKE_SYSTEM_NAME=Windows -DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM="NEVER" + DO_CMAKE+=-DCMAKE_SYSTEM_NAME=Windows -DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM="NEVER" - ifneq (,$(findstring win64,$(FTE_TARGET))) - BITS=64 - endif + ifneq (,$(findstring win64,$(FTE_TARGET))) + BITS=64 + endif - EXEPOSTFIX=.exe + EXEPOSTFIX=.exe - CC_MACHINE:=$(shell $(CC) -dumpmachine) - ARCH_PREDEP=$(BASE_DIR)/libs/SDL2-$(SDL2VER)/$(CC_MACHINE)/bin/sdl2-config - SDLCONFIG=$(ARCH_PREDEP) --prefix=$(BASE_DIR)/libs/SDL2-$(SDL2VER)/$(CC_MACHINE) - ARCH_CFLAGS=`$(SDLCONFIG) --cflags` + CC_MACHINE:=$(shell $(CC) -dumpmachine) + ARCH_PREDEP=$(BASE_DIR)/libs-$(ARCH)/SDL2-$(SDL2VER)/$(CC_MACHINE)/bin/sdl2-config + SDLCONFIG=$(ARCH_PREDEP) --prefix=$(BASE_DIR)/libs-$(ARCH)/SDL2-$(SDL2VER)/$(CC_MACHINE) + CLIENTLIBFLAGS+=`$(SDLCONFIG) --cflags` - #the defaults for sdl come first - GLCL_OBJS=$(GL_OBJS) $(D3DGL_OBJS) $(GLQUAKE_OBJS) gl_vidsdl.o snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(LTO_END) resources.o $(LTO_START) - GL_EXE_NAME=../$(EXE_NAME)-sdl-gl$(BITS)$(EXEPOSTFIX) - GLCL_EXE_NAME=../$(EXE_NAME)-sdl-glcl$(BITS)$(EXEPOSTFIX) - ifdef windir - GL_LDFLAGS=$(GLLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` - VK_LDFLAGS=$(GLLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` - M_LDFLAGS=$(MLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` - SV_LDFLAGS=-lm -lmingw32 -lws2_32 -lwinmm `$(SDLCONFIG) --static-libs` - QCC_LDFLAGS= - else - GL_LDFLAGS=$(IMAGELDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(GLLDFLAGS) `$(SDLCONFIG) --libs` - VK_LDFLAGS=$(IMAGELDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(GLLDFLAGS) `$(SDLCONFIG) --libs` - M_LDFLAGS=$(IMAGELDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(MLDFLAGS) `$(SDLCONFIG) --libs` - SV_LDFLAGS=-lm -lmingw32 -lws2_32 -lwinmm `$(SDLCONFIG) --libs` - QCC_LDFLAGS= - endif - - GL_CFLAGS=-DFTE_SDL $(GLCFLAGS) $(CLIENTLIBFLAGS) $(DX7SDK) - - GLB_DIR=gl_mgw_sdl$(BITS) - GLCL_DIR=glcl_mgw_sdl$(BITS) - - SV_OBJS=$(COMMON_OBJS) $(SERVER_OBJS) $(PROGS_OBJS) $(WINDOWSSERVERONLY_OBJS) $(LTO_END) resources.o $(LTO_START) - SV_EXE_NAME=../$(EXE_NAME)-sdl-sv$(BITS)$(EXEPOSTFIX) - SV_CFLAGS=$(SERVER_ONLY_CFLAGS) -DFTE_SDL + #the defaults for sdl come first + GLCL_OBJS=$(GL_OBJS) $(D3DGL_OBJS) $(GLQUAKE_OBJS) gl_vidsdl.o snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(LTO_END) resources.o $(LTO_START) + GL_EXE_NAME=../$(EXE_NAME)-sdl-gl$(BITS)$(EXEPOSTFIX) + GLCL_EXE_NAME=../$(EXE_NAME)-sdl-glcl$(BITS)$(EXEPOSTFIX) + ifneq ($(SDL_STATIC),0) + #statically link by default. dlls suck. + GL_LDFLAGS=$(GLLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` + VK_LDFLAGS=$(GLLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` + M_LDFLAGS=$(MLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` + D3D_LDFLAGS=$(MLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` + SV_LDFLAGS=-lm -lmingw32 -lws2_32 -lwinmm `$(SDLCONFIG) --static-libs` + QCC_LDFLAGS= + else + #or dynamically link when SDL_STATIC=0 + GL_LDFLAGS=$(IMAGELDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(GLLDFLAGS) `$(SDLCONFIG) --libs` + VK_LDFLAGS=$(IMAGELDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(GLLDFLAGS) `$(SDLCONFIG) --libs` + M_LDFLAGS=$(IMAGELDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(MLDFLAGS) `$(SDLCONFIG) --libs` + D3D_LDFLAGS=$(IMAGELDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(MLDFLAGS) `$(SDLCONFIG) --libs` + SV_LDFLAGS=-lm -lmingw32 -lws2_32 -lwinmm `$(SDLCONFIG) --libs` + QCC_LDFLAGS= + endif - MINGL_DIR=mingl_sdlwin$(BITS) - MINGL_EXE_NAME=../$(EXE_NAME)-sdl-mingl$(BITS)$(EXEPOSTFIX) + GL_CFLAGS=-DFTE_SDL $(GLCFLAGS) $(CLIENTLIBFLAGS) $(DX7SDK) - MB_DIR=m_mgw_sdl$(BITS) - M_EXE_NAME=../$(EXE_NAME)-sdl$(BITS)$(EXEPOSTFIX) + GLB_DIR=gl_mgw_sdl$(BITS) + GLCL_DIR=glcl_mgw_sdl$(BITS) + + #don't use sdl at all for dedicated servers. it'd break running it as a console program etc. its not officially supported, but included anyway for ease of use. + SV_DIR=sv_winsdl$(BITS) + SV_OBJS=$(COMMON_OBJS) $(SERVER_OBJS) $(PROGS_OBJS) $(WINDOWSSERVERONLY_OBJS) fs_win32.o $(LTO_END) resources.o $(LTO_START) + SV_EXE_NAME=../$(EXE_NAME)-sdl-sv$(BITS)$(EXEPOSTFIX) + SV_LDFLAGS=-lm -lole32 -lws2_32 -lwinmm + SV_CFLAGS=$(SERVER_ONLY_CFLAGS) -mconsole #-DFTE_SDL + + MINGL_DIR=mingl_sdlwin$(BITS) + MINGL_EXE_NAME=../$(EXE_NAME)-sdl-mingl$(BITS)$(EXEPOSTFIX) + + MB_DIR=m_mgw_sdl$(BITS) + M_EXE_NAME=../$(EXE_NAME)-sdl$(BITS)$(EXEPOSTFIX) #with d3d... - #MCL_OBJS=$(D3DGL_OBJS) $(GLQUAKE_OBJS) $(SOFTWARE_OBJS) $(D3DQUAKE_OBJS) gl_vidsdl.o snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(LTO_END) resources.o $(LTO_START) - #M_CFLAGS=$(D3DCFLAGS) $(VKCFLAGS) $(GLCFLAGS) -DFTE_SDL $(CLIENTLIBFLAGS) $(DX7SDK) + #MCL_OBJS=$(D3DGL_OBJS) $(GLQUAKE_OBJS) $(SOFTWARE_OBJS) $(D3DQUAKE_OBJS) gl_vidsdl.o snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(LTO_END) resources.o $(LTO_START) + #M_CFLAGS=$(D3DCFLAGS) $(VKCFLAGS) $(GLCFLAGS) -DFTE_SDL $(CLIENTLIBFLAGS) $(DX7SDK) #without d3d... - MCL_OBJS=$(D3DGL_OBJS) $(GLQUAKE_OBJS) $(SOFTWARE_OBJS) gl_vidsdl.o snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(LTO_END) resources.o $(LTO_START) - M_CFLAGS=$(VKCFLAGS) $(GLCFLAGS) -DFTE_SDL $(CLIENTLIBFLAGS) $(DX7SDK) + MCL_OBJS=$(D3DGL_OBJS) $(GLQUAKE_OBJS) $(SOFTWARE_OBJS) gl_vidsdl.o snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(LTO_END) resources.o $(LTO_START) + M_CFLAGS=$(VKCFLAGS) $(GLCFLAGS) -DFTE_SDL $(CLIENTLIBFLAGS) $(DX7SDK) - D3DCL_OBJS=$(D3DQUAKE_OBJS) snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(D3DGL_OBJS) $(LTO_END) resources.o $(LTO_START) - D3D_EXE_NAME=../$(EXE_NAME)-sdl-d3d$(BITS)$(EXEPOSTFIX) - D3DCL_EXE_NAME=../$(EXE_NAME)-sdl-d3dcl$(BITS)$(EXEPOSTFIX) - D3D_LDFLAGS=$(IMAGELDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 - D3D_CFLAGS=$(D3DCFLAGS) -DFTE_SDL -DNO_XFLIP $(CLIENTLIBFLAGS) $(DX7SDK) - D3DB_DIR=sdl_d3d_mgw$(BITS) - D3DCL_DIR=sdl_d3dcl_mgw$(BITS) + #unsupported, video code has winmsg/input junk in it. + D3DCL_OBJS=$(D3DQUAKE_OBJS) snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(D3DGL_OBJS) $(LTO_END) resources.o $(LTO_START) + D3D_EXE_NAME=../$(EXE_NAME)-sdl-d3d$(BITS)$(EXEPOSTFIX) + D3DCL_EXE_NAME=../$(EXE_NAME)-sdl-d3dcl$(BITS)$(EXEPOSTFIX) + D3D_CFLAGS=$(D3DCFLAGS) -DFTE_SDL -DNO_XFLIP $(CLIENTLIBFLAGS) $(DX7SDK) + D3DB_DIR=sdl_d3d_mgw$(BITS) + D3DCL_DIR=sdl_d3dcl_mgw$(BITS) - VKCL_OBJS=$(VKQUAKE_OBJS) gl_bloom.o gl_vidsdl.o snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(D3DGL_OBJS) $(LTO_END) resources.o $(LTO_START) - VK_EXE_NAME=../$(EXE_NAME)-sdl-vk$(BITS)$(EXEPOSTFIX) - VKCL_EXE_NAME=../$(EXE_NAME)-sdl-vkcl$(BITS)$(EXEPOSTFIX) - VK_CFLAGS=$(VKCFLAGS) -DFTE_SDL -DNO_XFLIP $(CLIENTLIBFLAGS) $(DX7SDK) - VKB_DIR=sdl_vk_mgw$(BITS) - VKCL_DIR=sdl_vkcl_mgw$(BITS) + VKCL_OBJS=$(VKQUAKE_OBJS) gl_bloom.o gl_vidsdl.o snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(D3DGL_OBJS) $(LTO_END) resources.o $(LTO_START) + VK_EXE_NAME=../$(EXE_NAME)-sdl-vk$(BITS)$(EXEPOSTFIX) + VKCL_EXE_NAME=../$(EXE_NAME)-sdl-vkcl$(BITS)$(EXEPOSTFIX) + VK_CFLAGS=$(VKCFLAGS) -DFTE_SDL -DNO_XFLIP $(CLIENTLIBFLAGS) $(DX7SDK) + VKB_DIR=sdl_vk_mgw$(BITS) + VKCL_DIR=sdl_vkcl_mgw$(BITS) - ifeq ($(shell echo $(FTE_TARGET)|grep -E -i -v "win32.*sdl"),) - GL_CFLAGS+= -D_MINGW_VFPRINTF - VK_CFLAGS+= -D_MINGW_VFPRINTF - D3D_CFLAGS+= -D_MINGW_VFPRINTF - M_CFLAGS+= -D_MINGW_VFPRINTF - endif + ifeq ($(shell echo $(FTE_TARGET)|grep -E -i -v "win32.*sdl"),) + GL_CFLAGS+= -D_MINGW_VFPRINTF + VK_CFLAGS+= -D_MINGW_VFPRINTF + D3D_CFLAGS+= -D_MINGW_VFPRINTF + M_CFLAGS+= -D_MINGW_VFPRINTF + endif endif #FTE_TARGET=vc (Visual C) @@ -1417,7 +1426,7 @@ ifeq (win,$(findstring win,$(FTE_TARGET))$(findstring _SDL,$(FTE_TARGET))) BASELDFLAGS+=-lcomctl32 EXEPOSTFIX=.exe - QTV_LDFLAGS=-lws2_32 -lwinmm + QTV_LDFLAGS+=-lws2_32 -lwinmm SV_EXE_NAME=../$(EXE_NAME)sv$(BITS)$(EXEPOSTFIX) SV_LDFLAGS=-lws2_32 -lwinmm -lole32 @@ -2191,7 +2200,6 @@ clean: distclean: clean -rm -f droid/ftekeystore - -rm -f -r libs/SDL2-$(SDL2VER) @@ -2328,11 +2336,11 @@ droid-help: @-echo @-echo "Note that 'make droid-rel' will automatically generate a keystore. If you forget the password, just do a 'make dist-clean'." -$(BASE_DIR)/libs/SDL2-$(SDL2VER)/i686-w64-mingw32/bin/sdl2-config: +$(BASE_DIR)/libs-$(ARCH)/SDL2-$(SDL2VER)/i686-w64-mingw32/bin/sdl2-config: wget http://www.libsdl.org/release/SDL2-devel-$(SDL2VER)-mingw.tar.gz -O $(BASE_DIR)/sdl2.tar.gz - cd $(BASE_DIR)/libs && tar -xvzf $(BASE_DIR)/sdl2.tar.gz + cd $(BASE_DIR)/libs-$(ARCH) && tar -xvzf $(BASE_DIR)/sdl2.tar.gz rm $(BASE_DIR)/sdl2.tar.gz -$(BASE_DIR)/libs/SDL2-$(SDL2VER)/x86_64-w64-mingw32/bin/sdl2-config: $(BASE_DIR)/libs/SDL2-$(SDL2VER)/i686-w64-mingw32/bin/sdl2-config +$(BASE_DIR)/libs-$(ARCH)/SDL2-$(SDL2VER)/x86_64-w64-mingw32/bin/sdl2-config: $(BASE_DIR)/libs-$(ARCH)/SDL2-$(SDL2VER)/i686-w64-mingw32/bin/sdl2-config @@ -2456,9 +2464,11 @@ QTV_OBJECTS= \ bsp.c \ rcon.c \ mdfour.c \ + md5.c \ crc.c \ control.c \ forward.c \ + relay.c \ pmove.c \ menu.c \ msg.c \ diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 93e1dda6f..b302d476e 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -2174,28 +2174,29 @@ void CL_Record_f (void) static int QDECL CompleteDemoList (const char *name, qofs_t flags, time_t mtime, void *parm, searchpathfuncs_t *spath) { struct xcommandargcompletioncb_s *ctx = parm; - ctx->cb(name, NULL, NULL, ctx); + const char *ext = NULL; + ext = COM_GetFileExtension(name, ext); + if (!Q_strcasecmp(ext, ".gz")) + ext = COM_GetFileExtension(name, ext); + if ( +#ifdef NQPROT + !Q_strcasecmp(ext, ".dem") || !Q_strcasecmp(ext, ".dem.gz") || +#endif +#ifdef Q2CLIENT + !Q_strcasecmp(ext, ".dm2") || !Q_strcasecmp(ext, ".dm2.gz") || +#endif + !Q_strcasecmp(ext, ".qwd") || !Q_strcasecmp(ext, ".qwd.gz") || + !Q_strcasecmp(ext, ".mvd") || !Q_strcasecmp(ext, ".mvd.gz")) +//FIXME: enumerate .zip and .dz files too. + { + ctx->cb(name, NULL, NULL, ctx); + } return true; } void CL_DemoList_c(int argn, const char *partial, struct xcommandargcompletioncb_s *ctx) { if (argn == 1) - { - COM_EnumerateFiles(va("%s*.qwd", partial), CompleteDemoList, ctx); - COM_EnumerateFiles(va("%s*.qwd.gz", partial), CompleteDemoList, ctx); -#ifdef NQPROT - COM_EnumerateFiles(va("%s*.dem", partial), CompleteDemoList, ctx); - COM_EnumerateFiles(va("%s*.dem.gz", partial), CompleteDemoList, ctx); -#endif - COM_EnumerateFiles(va("%s*.mvd", partial), CompleteDemoList, ctx); - COM_EnumerateFiles(va("%s*.mvd.gz", partial), CompleteDemoList, ctx); - - COM_EnumerateFiles(va("%s*.dm2", partial), CompleteDemoList, ctx); - COM_EnumerateFiles(va("%s*.dm2.gz", partial), CompleteDemoList, ctx); - - //fixme: show files in both .zip and .dz -// COM_EnumerateFiles(va("%s*.dz", partial), CompleteDemoList, ctx); - } + COM_EnumerateFiles(va("%s*", partial), CompleteDemoList, ctx); } /* ==================== @@ -2302,6 +2303,8 @@ void CL_PlayDemo_f (void) if (cls.state == ca_demostart) cls.state = ca_disconnected; + else + cls.demonum = -1; //not via CL_NextDemo, don't confuse the user by playing random other demos. #ifdef WEBCLIENT #if 1 @@ -3322,6 +3325,8 @@ void CL_TimeDemo_f (void) return; } + cls.demonum = -1; //stop the demo reel. the user will probably want to read the results. + CL_PlayDemo_f (); if (cls.state != ca_demostart) diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 584131089..ccecc1e4c 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -623,7 +623,7 @@ void CLFTE_ReadDelta(unsigned int entnum, entity_state_t *news, entity_state_t * { unsigned int predbits = 0; unsigned int bits; - + bits = MSG_ReadByte(); if (bits & UF_EXTEND1) bits |= MSG_ReadByte()<<8; @@ -4507,10 +4507,9 @@ void CL_LinkPacketEntities (void) //DP extension. .modelflags (which is sent in the high parts of effects) allows to specify exactly the q1-compatible flags. //the extra bit allows for setting to 0. //note that hexen2 has additional flags which cannot be expressed. - if (state->effects & 0xff800000) - modelflags = state->effects>>24; - else - modelflags = model->flags; + modelflags = state->effects>>24; + if (!(state->effects & EF_NOMODELFLAGS)) + modelflags |= model->flags; } #ifdef HAVE_LEGACY @@ -4990,7 +4989,6 @@ CL_ParsePlayerinfo */ extern int parsecountmod, oldparsecountmod; extern double parsecounttime; -int lastplayerinfo; void CL_ParseClientdata (void); void CL_MVDUpdateSpectator(void) @@ -5005,12 +5003,15 @@ void CLQW_ParsePlayerinfo (void) unsigned int flags; player_info_t *info; player_state_t *state, *oldstate; - int num; + unsigned int num; int i; int newf; vec3_t org, dist; - lastplayerinfo = num = MSG_ReadByte (); + if (cls.fteprotocolextensions2&PEXT2_LONGINDEXES) + num = MSG_ReadUInt64 (); + else + num = MSG_ReadByte (); if (num >= MAX_CLIENTS) Host_EndGame ("CL_ParsePlayerinfo: bad num"); diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 4055d78ef..6ac969918 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -44,6 +44,7 @@ static void CL_ForceStopDownload (qboolean finish); qboolean noclip_anglehack; // remnant from old quake int startuppending; +extern int r_blockvidrestart; void Host_FinishLoading(void); @@ -926,12 +927,20 @@ char *CL_TryingToConnect(void) if (!connectinfo.trying) return NULL; - if (connectinfo.numadr >= 1 && connectinfo.adr[0].prot == NP_KEXLAN) + if (connectinfo.numadr < 1) + ; + else if (connectinfo.adr[0].prot == NP_KEXLAN +#ifdef SUPPORT_ICE + || connectinfo.adr[0].type == NA_ICE +#endif + ) { char status[1024]; if (NET_GetConnectionCertificate(cls.sockets, &connectinfo.adr[0], QCERT_LOBBYSTATUS, status, sizeof(status))>0) return va("%s\n%s", cls.servername, status); } + else if (connectinfo.adr[0].prot == NP_RTC_TCP || connectinfo.adr[0].prot == NP_RTC_TLS) + return va("%s\n%s", cls.servername, "Waiting for broker connection"); return cls.servername; } @@ -994,11 +1003,11 @@ static void CL_ResolveServer(void *vctx, void *data, size_t a, size_t b) { struct resolvectx_s *ctx = vctx; - //stupid logic for targ@prox2@prox1 chaining. just disable it if there's weird ws:// or whatever in there. + //stupid logic for targ@prox2@[ws[s]://]prox1 chaining. just disable it if there's weird ws:// or whatever in there. //FIXME: really shouldn't be in there const char *res = strrchr(ctx->servername, '/'); const char *host = strrchr(ctx->servername+1, '@'); - if (host && !res) + if (host && (!res || res > host)) host++; else host = ctx->servername; @@ -1038,7 +1047,6 @@ void CL_CheckForResend (void) double t1, t2; int contype = 0; qboolean keeptrying = true; - extern int r_blockvidrestart; netadr_t *to; #ifdef HAVE_SERVER @@ -1355,7 +1363,7 @@ void CL_CheckForResend (void) Cvar_ForceSet(&cl_servername, ""); return; } - if (startuppending || r_blockvidrestart) + if (startuppending || r_blockvidrestart || FS_DownloadingPackage()) return; //don't send connect requests until we've actually initialised fully. this isn't a huge issue, but makes the startup prints a little more sane. if (connectinfo.time && realtime - connectinfo.time < 5.0) @@ -1406,7 +1414,7 @@ void CL_CheckForResend (void) Cvar_ForceSet(&cl_servername, cls.servername); - if (!connectinfo.numadr || !cls.sockets) + if (!connectinfo.numadr || !cls.sockets || connectinfo.resolving) return; //nothing to do yet... if (!connectinfo.clogged) connectinfo.time = realtime+t2-t1; // for retransmit requests @@ -1439,11 +1447,22 @@ void CL_CheckForResend (void) connectinfo.clogged = false; if (connectinfo.tries == 0 && connectinfo.nextadr < connectinfo.numadr) - if (!NET_EnsureRoute(cls.sockets, "conn", &connectinfo.peercred, cls.servername, to, true)) + { + //stupid logic for targ@prox2@[ws[s]://]prox1 chaining. just disable it if there's weird ws:// or whatever in there. + //FIXME: really shouldn't be in there + const char *res = strrchr(cls.servername, '/'); + const char *host = strrchr(cls.servername+1, '@'); + if (host && (!res || res > host)) + host++; + else + host = cls.servername; + + if (!NET_EnsureRoute(cls.sockets, "conn", &connectinfo.peercred, host, to, true)) { CL_ConnectAbort ("Unable to establish connection to %s\n", cls.servername); return; } + } if (to->prot == NP_DGRAM) connectinfo.nextadr++; //cycle hosts with each ping (if we got multiple). @@ -1577,13 +1596,31 @@ void CL_CheckForResend (void) } } -static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum coninfomode_e mode, enum coninfospec_e spec) +static void CL_BeginServerConnect(char *chain, int port, qboolean noproxy, enum coninfomode_e mode, enum coninfospec_e spec) { - const char *schemeend = strstr(host, "://"); - char *arglist; + const char *host = chain; + const char *schemeend; + char *arglist, *c; + size_t presize; - Q_strncpyz(cls.serverurl, host, sizeof(cls.serverurl)); + Q_strncpyz(cls.serverurl, chain, sizeof(cls.serverurl)); + for (c = chain; *c; c++) + { + if (*c == '@') + host=c+1; + else if (*c == '/' || *c == '?') + break; //stop if we find some path weirdness (like an authority). + } + presize = host-chain; + if (presize >= sizeof(cls.servername)) + { + CL_ConnectAbort("server address too long"); + return; //no, get lost. panic. + } + memcpy(cls.servername, chain, presize); + + schemeend = strstr(host, "://"); if (schemeend) { //"qw:tcp://host/observe" @@ -1601,7 +1638,7 @@ static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum c scheme = NULL; //qw:// or q3:// something that's just noise here. if (scheme && scheme->flags&URISCHEME_NEEDSRESOURCE) { - Q_strncpyz (cls.servername, schemestart, sizeof(cls.servername)); //oh. will probably be okay then + Q_strncpyz (cls.servername+presize, schemestart, sizeof(cls.servername)-presize); //oh. will probably be okay then arglist = NULL; } else @@ -1639,9 +1676,9 @@ static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum c } } if (scheme) //preserve the scheme, the netchan cares. - Q_strncpyz (cls.servername, schemestart, sizeof(cls.servername)); //probably some game-specific mess that we don't know + Q_strncpyz (cls.servername+presize, schemestart, sizeof(cls.servername)-presize); //probably some game-specific mess that we don't know else - Q_strncpyz (cls.servername, schemeend+3, sizeof(cls.servername)); //probably some game-specific mess that we don't know + Q_strncpyz (cls.servername+presize, schemeend+3, sizeof(cls.servername)-presize); //probably some game-specific mess that we don't know arglist = strchr(cls.servername, '?'); } } @@ -1650,10 +1687,10 @@ static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum c if (!strncmp(host, "localhost", 9)) noproxy = true; //FIXME: resolve the address here or something so that we don't end up using a proxy for lan addresses. - if (strstr(host, "://") || !*cl_proxyaddr.string || noproxy) - Q_strncpyz (cls.servername, host, sizeof(cls.servername)); + if (strstr(host, "://") || *host == '/' || !*cl_proxyaddr.string || noproxy) + Q_strncpyz (cls.servername+presize, host, sizeof(cls.servername)-presize); else - Q_snprintfz(cls.servername, sizeof(cls.servername), "%s@%s", host, cl_proxyaddr.string); + Q_snprintfz(cls.servername+presize, sizeof(cls.servername)-presize, "%s@%s", host, cl_proxyaddr.string); arglist = strchr(cls.servername, '?'); } @@ -3486,6 +3523,10 @@ Contents allows \n escape character */ void CL_Packet_f (void) { +#ifdef FTE_TARGET_WEB + //either this creates some expensive alternative rtc connection that screws us over, or just generally fails. don't allow it. + Con_Printf (CON_WARNING "Ignoring 'packet %s' request.\n", Cmd_Argv(1)); +#else char send[2048]; int i, l; char *in, *out; @@ -3593,6 +3634,7 @@ void CL_Packet_f (void) cls.realip_ident = atoi(Cmd_Argv(2)); Z_Free(temp); } +#endif } @@ -3681,6 +3723,8 @@ void CL_Startdemos_f (void) for (i=1 ; i1 || candtls > 1) && !NET_IsEncrypted(&net_from)) + if ((candtls && net_enable_dtls.ival) && net_from.prot == NP_DGRAM && (connectinfo.peercred.hash || net_enable_dtls.ival>1 || candtls > 1) && !NET_IsEncrypted(&net_from)) { //c2s getchallenge //s2c c%u\0DTLS=$candtls @@ -4640,7 +4683,8 @@ void CL_ConnectionlessPacket (void) //happens in demos if (c == svc_disconnect && cls.demoplayback != DPB_NONE && net_from.type == NA_INVALID) { - Host_EndGame ("End of Demo"); + CL_NextDemo(); + Host_EndGame (NULL); //end of demo. return; } @@ -5405,13 +5449,13 @@ void CL_Fog_f(void) cl.fog[ftype].time += 1; //fitz: - //if (Cmd_Argc() >= 6) cl.fog_time += atof(Cmd_Argv(5)); + //if (Cmd_Argc() >= 6) cl.fog[ftype].time += atof(Cmd_Argv(5)); //dp: if (Cmd_Argc() >= 6) cl.fog[ftype].alpha = atof(Cmd_Argv(5)); if (Cmd_Argc() >= 7) cl.fog[ftype].depthbias = atof(Cmd_Argv(6)); - //if (Cmd_Argc() >= 8) cl.fog.end = atof(Cmd_Argv(7)); - //if (Cmd_Argc() >= 9) cl.fog.height = atof(Cmd_Argv(8)); - //if (Cmd_Argc() >= 10) cl.fog.fadedepth = atof(Cmd_Argv(9)); + //if (Cmd_Argc() >= 8) cl.fog[ftype].end = atof(Cmd_Argv(7)); + //if (Cmd_Argc() >= 9) cl.fog[ftype].height = atof(Cmd_Argv(8)); + //if (Cmd_Argc() >= 10) cl.fog[ftype].fadedepth = atof(Cmd_Argv(9)); if (Cmd_FromGamecode()) cl.fog_locked = !!cl.fog[ftype].density; @@ -5924,8 +5968,8 @@ void CL_Init (void) Cmd_AddCommandD ("showpic", SCR_ShowPic_Script_f, "showpic [width] [height] [touchcommand]\nDisplays an image onscreen, that potentially has a key binding attached to it when clicked/touched.\nzone should be one of: TL, TR, BL, BR, MM, TM, BM, ML, MR. This serves as an extra offset to move the image around the screen without any foreknowledge of the screen resolution."); Cmd_AddCommandD ("showpic_removeall", SCR_ShowPic_Remove_f, "removes any pictures inserted with the showpic command."); - Cmd_AddCommand ("startdemos", CL_Startdemos_f); - Cmd_AddCommand ("demos", CL_Demos_f); + Cmd_AddCommandD ("startdemos", CL_Startdemos_f, "Sets the demoreel list, but does not start playing them (use the 'demos' command for that)"); + Cmd_AddCommandD ("demos", CL_Demos_f, "Starts playing the demo reel."); Cmd_AddCommand ("stopdemo", CL_Stopdemo_f); Cmd_AddCommand ("skins", Skin_Skins_f); @@ -6074,16 +6118,24 @@ NORETURN void VARGS Host_EndGame (const char *message, ...) va_list argptr; char string[1024]; - va_start (argptr,message); - vsnprintf (string,sizeof(string)-1, localtext(message),argptr); - va_end (argptr); + if (message) + { + va_start (argptr,message); + vsnprintf (string,sizeof(string)-1, localtext(message),argptr); + va_end (argptr); + } + else + *string = 0; COM_AssertMainThread(string); SCR_EndLoadingPlaque(); - Con_TPrintf ("^&C0Host_EndGame: %s\n", string); - Con_Printf ("\n"); + if (message) + { + Con_TPrintf ("^&C0Host_EndGame: %s\n", string); + Con_Printf ("\n"); + } SCR_EndLoadingPlaque(); @@ -7027,7 +7079,6 @@ double Host_Frame (double time) float maxfps; qboolean maxfpsignoreserver; qboolean idle; - extern int r_blockvidrestart; static qboolean hadwork; unsigned int vrflags; qboolean mustrenderbeforeread; @@ -7668,7 +7719,9 @@ void CL_ExecInitialConfigs(char *resetcommand, qboolean fullvidrestart) com_parseutf8.ival = com_parseutf8.value; //if the renderer is already up and running, be prepared to reload content to match the new conback/font/etc - if (fullvidrestart) + if (r_blockvidrestart) + ; + else if (fullvidrestart) Cbuf_AddText ("vid_restart\n", RESTRICT_LOCAL); else if (qrenderer != QR_NONE) Cbuf_AddText ("vid_reload\n", RESTRICT_LOCAL); @@ -7701,7 +7754,6 @@ void Host_FinishLoading(void) { int i; extern qboolean r_forceheadless; - extern int r_blockvidrestart; if (r_blockvidrestart == true) { //1 means we need to init the filesystem diff --git a/engine/client/cl_master.h b/engine/client/cl_master.h index 17d9a35e1..3a28c5064 100644 --- a/engine/client/cl_master.h +++ b/engine/client/cl_master.h @@ -34,6 +34,7 @@ enum masterprotocol_e #define SS_KEEPINFO (1<<6u) #define SS_GETINFO (1<<7u) //explicitly query via getinfo #define SS_PROXY (1<<8u) //qizmo/qwfwd/qtv/eztv +#define SS_RELAY (1<<9u) //supports the \prx\nexthop relay thing, and pingstatus requests for connectbr. #define PING_DEAD 0xffff //default ping value to denote servers that are not responding. #define PING_UNKNOWN 0xfffe //these servers are considered up, but we can't query them directly so can't determine the final ping from here. diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index ac2103924..2e1acef6b 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -27,7 +27,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. void CL_GetNumberedEntityInfo (int num, float *org, float *ang); void CLDP_ParseDarkPlaces5Entities(void); void CLH2_ParseEntities(void); -static void CL_SetStatNumeric (int pnum, int stat, int ivalue, float fvalue); +static void CL_SetStatNumeric (int pnum, unsigned int stat, int ivalue, float fvalue); #define CL_SetStatInt(pnum,stat,ival) do{int thevalue=ival; CL_SetStatNumeric(pnum,stat,thevalue,thevalue);}while(0) #define CL_SetStatFloat(pnum,stat,fval) do{float thevalue=fval; CL_SetStatNumeric(pnum,stat,thevalue,thevalue);}while(0) static qboolean CL_CheckModelResources (char *name); @@ -37,7 +37,10 @@ static char *CLNQ_ParseProQuakeMessage (char *s); static void DLC_Poll(qdownload_t *dl); static void CL_ProcessUserInfo (int slot, player_info_t *player); static void CL_ParseStuffCmd(char *msg, int destsplit); -static void Con_HexDump(qbyte *packet, size_t len, size_t badoffset); +void Con_HexDump(qbyte *packet, size_t len, size_t badoffset); + +#define MSG_ReadBigIndex() ((cls.fteprotocolextensions2&PEXT2_LONGINDEXES)?(unsigned int)MSG_ReadUInt64():MSG_ReadByte ()) +#define MSG_ReadPlayer() MSG_ReadBigIndex() #ifdef NQPROT char *cl_dp_packagenames; @@ -3412,9 +3415,12 @@ static void CLQW_ParseServerData (void) if (cls.fteprotocolextensions2 & PEXT2_MAXPLAYERS) { - cl.allocated_client_slots = MSG_ReadByte(); + cl.allocated_client_slots = MSG_ReadPlayer(); if (cl.allocated_client_slots > MAX_CLIENTS) + { + Con_Printf(CON_ERROR"Server has too many client slots (%u > %u)\n", cl.allocated_client_slots, MAX_CLIENTS); cl.allocated_client_slots = MAX_CLIENTS; + } } cl.gametime = MSG_ReadFloat(); @@ -3441,7 +3447,7 @@ static void CLQW_ParseServerData (void) else if (cls.fteprotocolextensions2 & PEXT2_MAXPLAYERS) { // qboolean spec = false; - cl.allocated_client_slots = MSG_ReadByte(); + cl.allocated_client_slots = MSG_ReadPlayer(); if (cl.allocated_client_slots > MAX_CLIENTS) { Con_Printf(CON_ERROR"Server has too many client slots (%u > %u)\n", cl.allocated_client_slots, MAX_CLIENTS); @@ -4041,14 +4047,14 @@ static void CLNQ_ParseServerData(void) //Doesn't change gamedir - use with caut if (cls.qex) { - cl.allocated_client_slots = MSG_ReadByte(); + cl.allocated_client_slots = MSG_ReadPlayer(); str = MSG_ReadString(); } else { if (cls.fteprotocolextensions2 & PEXT2_PREDINFO) str = MSG_ReadString(); - cl.allocated_client_slots = MSG_ReadByte(); + cl.allocated_client_slots = MSG_ReadPlayer(); } if (str) { @@ -5847,10 +5853,10 @@ CL_UpdateUserinfo */ static void CL_UpdateUserinfo (void) { - int slot; + unsigned int slot; player_info_t *player; - slot = MSG_ReadByte (); + slot = MSG_ReadPlayer(); if (slot >= MAX_CLIENTS) Host_EndGame ("CL_ParseServerMessage: svc_updateuserinfo > MAX_SCOREBOARD"); @@ -5874,7 +5880,7 @@ static void CL_UpdateUserinfo (void) static void CL_ParseSetInfoBlob (void) { - qbyte slot = MSG_ReadByte(); + unsigned int slot = MSG_ReadPlayer(); char *key = MSG_ReadString(); size_t keysize; unsigned int offset = MSG_ReadLong(); @@ -5918,12 +5924,12 @@ CL_SetInfo */ static void CL_ParseSetInfo (void) { - int slot; + unsigned int slot; player_info_t *player; char *val; char key[512]; - slot = MSG_ReadByte (); + slot = MSG_ReadPlayer (); MSG_ReadStringBuffer(key, sizeof(key)); val = MSG_ReadString(); @@ -6100,7 +6106,7 @@ static void CL_SetStatMovevar(int pnum, int stat, int ivalue, float value) #endif //the two values are expected to be the same, they're just both provided for precision. -static void CL_SetStatNumeric (int pnum, int stat, int ivalue, float fvalue) +static void CL_SetStatNumeric (int pnum, unsigned int stat, int ivalue, float fvalue) { if (stat < 0 || stat >= MAX_CL_STATS) return; @@ -6188,6 +6194,84 @@ static void CL_SetStatString (int pnum, int stat, const char *value) cl.playerview[pnum].statsstr[stat] = Z_StrDup(value); } } + +/* +//if we're going to 'spend' another byte for longer indexes, we might as well spend an extra 4 bits on the type too, allowing for 64bit types etc. +static void CL_ParseExtendedStat(int destsplit) +{ +//float/double/sint/uint +//string + quint64_t id = MSG_ReadUInt64(); + unsigned int type; + type = id&0xf; + id>>=4; //we're never going to have that many stats. + switch(type) + { + case ev_void: //might as well. + CL_SetStatNumeric(destsplit, id, 0, 0); + break; + case ev_string: + CL_SetStatString(destsplit, id, MSG_ReadString()); + break; + case ev_float: + { + float f = MSG_ReadFloat(); + CL_SetStatNumeric(destsplit, id, f, f); + } + break; + case ev_vector: + { + float f; + f = MSG_ReadFloat();CL_SetStatNumeric(destsplit, id+0, f, f); + f = MSG_ReadFloat();CL_SetStatNumeric(destsplit, id+1, f, f); + f = MSG_ReadFloat();CL_SetStatNumeric(destsplit, id+2, f, f); + } + break; + case ev_entity: + { + unsigned int i = MSGCL_ReadEntity(); + CL_SetStatNumeric(destsplit, id, i, i); + } + break; +// case ev_field: +// case ev_function: +// case ev_pointer: + case ev_integer: + { + signed int i = MSG_ReadLong(); + CL_SetStatNumeric(destsplit, id, i, i); + } + break; + case ev_uint: + { + unsigned int i = MSG_ReadLong(); + CL_SetStatNumeric(destsplit, id, i, i); + } + break; + case ev_int64: + { + qint64_t i = MSG_ReadInt64(); + CL_SetStatNumeric(destsplit, id, i, i); + } + break; + case ev_uint64: + { + quint64_t i = MSG_ReadUInt64(); + CL_SetStatNumeric(destsplit, id, i, i); + } + break; + case ev_double: + { + double f = MSG_ReadDouble(); + CL_SetStatNumeric(destsplit, id, f, f); + } + break; + default: + Host_EndGame("CL_ParseExtendedStat: type %i is unsupported", type); + break; + } +}*/ + /* ============== CL_MuzzleFlash @@ -7293,7 +7377,7 @@ static void CL_ParsePrecache(void) } } -static void Con_HexDump(qbyte *packet, size_t len, size_t badoffset) +void Con_HexDump(qbyte *packet, size_t len, size_t badoffset) { int i; int pos; @@ -7514,6 +7598,7 @@ void CLEZ_ParseHiddenDemoMessage(void) } } + #define SHOWNETEOM(x) if(cl_shownet.value>=2)Con_Printf ("%3i:%s\n", MSG_GetReadCount(), x); #define SHOWNET(x) if(cl_shownet.value>=2)Con_Printf ("%3i:%s\n", MSG_GetReadCount()-1, x); #define SHOWNET2(x, y) if(cl_shownet.value>=2)Con_Printf ("%3i:%3i:%s\n", MSG_GetReadCount()-1, y, x); @@ -7526,6 +7611,7 @@ void CLQW_ParseServerMessage (void) { int cmd; char *s; + unsigned int u; int i, j; int destsplit; vec3_t ang; @@ -7652,7 +7738,8 @@ void CLQW_ParseServerMessage (void) } else if (cls.demoplayback) { - CL_Disconnect_f(); + CL_Disconnect(NULL); + CL_NextDemo(); return; } else if (cls.state == ca_connected) @@ -7832,32 +7919,32 @@ void CLQW_ParseServerMessage (void) case svc_updatefrags: Sbar_Changed (); - i = MSG_ReadByte (); - if (i >= MAX_CLIENTS) + u = MSG_ReadPlayer(); + if (u >= MAX_CLIENTS) Host_EndGame ("CL_ParseServerMessage: svc_updatefrags > MAX_SCOREBOARD"); - cl.players[i].frags = MSG_ReadShort (); + cl.players[u].frags = MSG_ReadShort (); break; case svc_updateping: - i = MSG_ReadByte (); - if (i >= MAX_CLIENTS) + u = MSG_ReadPlayer(); + if (u >= MAX_CLIENTS) Host_EndGame ("CL_ParseServerMessage: svc_updateping > MAX_SCOREBOARD"); - cl.players[i].ping = MSG_ReadShort (); + cl.players[u].ping = MSG_ReadShort (); break; case svc_updatepl: - i = MSG_ReadByte (); - if (i >= MAX_CLIENTS) + u = MSG_ReadPlayer(); + if (u >= MAX_CLIENTS) Host_EndGame ("CL_ParseServerMessage: svc_updatepl > MAX_SCOREBOARD"); - cl.players[i].pl = MSG_ReadByte (); + cl.players[u].pl = MSG_ReadByte (); break; case svc_updateentertime: // time is sent over as seconds ago - i = MSG_ReadByte (); - if (i >= MAX_CLIENTS) + u = MSG_ReadPlayer(); + if (u >= MAX_CLIENTS) Host_EndGame ("CL_ParseServerMessage: svc_updateentertime > MAX_SCOREBOARD"); - cl.players[i].realentertime = realtime - MSG_ReadFloat (); + cl.players[u].realentertime = realtime - MSG_ReadFloat (); break; case svc_spawnbaseline: @@ -7938,6 +8025,9 @@ void CLQW_ParseServerMessage (void) f = MSG_ReadFloat(); CL_SetStatNumeric (destsplit, i, f, f); break; +/* case svcfte_updatebigstat: + CL_ParseExtendedStat(); + break;*/ case svc_spawnstaticsound: CL_ParseStaticSound (false); @@ -8967,7 +9057,7 @@ static qboolean CLNQ_ParseNQPrints(char *s) return false; } -static void CLNQ_CheckPlayerIsSpectator(int i) +static void CLNQ_CheckPlayerIsSpectator(unsigned int i) { cl.players[i].spectator = (cl.players[i].frags==-999) || //DP mods tend to use -999 @@ -9002,6 +9092,7 @@ static void CLNQ_CheckPlayerIsSpectator(int i) static qboolean CLH2_ParseServerSubMessage (int cmd) { const int destsplit = 0; + unsigned int u; int i,j; const int svch2_first = svch2_particle2; static const char *svc_h2strings[] = @@ -9150,10 +9241,10 @@ static qboolean CLH2_ParseServerSubMessage (int cmd) MSG_ReadByte(); //handle break; case svch2_updateclass: - i = MSG_ReadByte(); + u = MSG_ReadPlayer(); j = MSG_ReadByte(); - if (i < MAX_CLIENTS) - InfoBuf_SetValueForKey(&cl.players[i].userinfo, "cl_playerclass", va("%i", j)); + if (u < MAX_CLIENTS) + InfoBuf_SetValueForKey(&cl.players[u].userinfo, "cl_playerclass", va("%i", j)); break; case svch2_updateinv: cmd = MSG_ReadByte(); @@ -9273,6 +9364,7 @@ void CLNQ_ParseServerMessage (void) const int destsplit = 0; int cmd; char *s; + unsigned int u; int i, j; vec3_t ang; unsigned int cmdstart; @@ -9363,7 +9455,8 @@ void CLNQ_ParseServerMessage (void) break; case svc_disconnect: - CL_Disconnect("Server disconnected"); + CL_Disconnect(cls.demoplayback?NULL:"Server disconnected"); //don't show any errors on end-of-demo. + CL_NextDemo(); return; case svc_centerprint: @@ -9575,57 +9668,57 @@ void CLNQ_ParseServerMessage (void) case svc_updatename: Sbar_Changed (); - i = MSG_ReadByte (); - if (i >= MAX_CLIENTS) + u = MSG_ReadPlayer (); + if (u >= MAX_CLIENTS) MSG_ReadString(); else { - strcpy(cl.players[i].name, MSG_ReadString()); - if (*cl.players[i].name) - cl.players[i].userid = i+1; - InfoBuf_SetValueForKey(&cl.players[i].userinfo, "name", cl.players[i].name); + strcpy(cl.players[u].name, MSG_ReadString()); + if (*cl.players[u].name) + cl.players[u].userid = u+1; + InfoBuf_SetValueForKey(&cl.players[u].userinfo, "name", cl.players[u].name); if (!cl.nqplayernamechanged) cl.nqplayernamechanged = realtime+2; - CLNQ_CheckPlayerIsSpectator(i); + CLNQ_CheckPlayerIsSpectator(u); } break; case svc_updatefrags: Sbar_Changed (); - i = MSG_ReadByte (); - if (i >= MAX_CLIENTS) + u = MSG_ReadPlayer (); + if (u >= MAX_CLIENTS) MSG_ReadShort(); else { - cl.players[i].frags = MSG_ReadShort(); - CLNQ_CheckPlayerIsSpectator(i); + cl.players[u].frags = MSG_ReadShort(); + CLNQ_CheckPlayerIsSpectator(u); } break; case svc_updatecolors: { int a; - i = MSG_ReadByte (); + u = MSG_ReadPlayer (); a = MSG_ReadByte (); - if (i < cl.allocated_client_slots) + if (u < cl.allocated_client_slots) { -// cl.players[i].rtopcolor = a&0x0f; -// cl.players[i].rbottomcolor = (a&0xf0)>>4; -// sprintf(cl.players[i].team, "%2d", cl.players[i].rbottomcolor); +// cl.players[u].rtopcolor = a&0x0f; +// cl.players[u].rbottomcolor = (a&0xf0)>>4; +// sprintf(cl.players[u].team, "%2d", cl.players[u].rbottomcolor); - InfoBuf_SetValueForKey(&cl.players[i].userinfo, "topcolor", va("%i", (a&0xf0)>>4)); - InfoBuf_SetValueForKey(&cl.players[i].userinfo, "bottomcolor", va("%i", (a&0x0f))); - InfoBuf_SetValueForKey(&cl.players[i].userinfo, "team", va("%i", (a&0x0f)+1)); - CL_ProcessUserInfo (i, &cl.players[i]); + InfoBuf_SetValueForKey(&cl.players[u].userinfo, "topcolor", va("%i", (a&0xf0)>>4)); + InfoBuf_SetValueForKey(&cl.players[u].userinfo, "bottomcolor", va("%i", (a&0x0f))); + InfoBuf_SetValueForKey(&cl.players[u].userinfo, "team", va("%i", (a&0x0f)+1)); + CL_ProcessUserInfo (u, &cl.players[u]); -// CLNQ_CheckPlayerIsSpectator(i); +// CLNQ_CheckPlayerIsSpectator(u); #ifdef QWSKINS if (cls.state == ca_active) - Skin_Find (&cl.players[i]); - if (i == cl.playerview[destsplit].playernum) + Skin_Find (&cl.players[u]); + if (u == cl.playerview[destsplit].playernum) Skin_FlushPlayers(); - CL_NewTranslation (i); + CL_NewTranslation (u); #endif Sbar_Changed (); } diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index fe77d9d4f..5b7b30218 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -2537,7 +2537,12 @@ void SCR_SetUpToDrawConsole (void) else { //nothing happening, make sure the console is visible or something. if (!scr_drawloading) - Key_Dest_Add(kdm_console); + { + if (SCR_GetLoadingStage() == LS_NONE && CL_TryingToConnect()) //if we're trying to connect, make sure there's a loading/connecting screen showing instead of forcing the menu visible + SCR_SetLoadingStage(LS_CONNECTION); + else + Key_Dest_Add(kdm_console); + } legacyfullscreen = true; } } diff --git a/engine/client/client.h b/engine/client/client.h index c3a4f0ce7..ad84b15ae 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -375,7 +375,7 @@ typedef struct typedef enum { ca_disconnected, // full screen console with no connection -ca_demostart, // starting up a demo +ca_demostart, // waiting to start up a demo (still disconnected but there should be a playdemo command in the cbuf somewhere so don't do other stuff) ca_connected, // netchan_t established, waiting for svc_serverdata ca_onserver, // processing data lists, donwloading, etc ca_active // everything is in, so frames can be rendered diff --git a/engine/client/fragstats.c b/engine/client/fragstats.c index 714ed58f6..95ab671a7 100644 --- a/engine/client/fragstats.c +++ b/engine/client/fragstats.c @@ -86,7 +86,7 @@ static cvar_t r_tracker_fadetime = CVARCD("r_tracker_fadetime", "1", TrackerCall static cvar_t r_tracker_x = CVARCD("r_tracker_x", "0.5", TrackerCallback, "left position of the r_tracker messages, as a fraction of the screen's width, eg 0.5\n"); static cvar_t r_tracker_y = CVARCD("r_tracker_y", "0.333", TrackerCallback, "top position of the r_tracker messages, as a fraction of the screen's height, eg 0.333\n"); static cvar_t r_tracker_w = CVARCD("r_tracker_w", "0.5", TrackerCallback, "width of the r_tracker messages, as a fraction of the screen's width, eg 0.5\n"); -static cvar_t r_tracker_lines = CVARCD("r_tracker_lines", "8", TrackerCallback, "number of r_tracker messages to display\n"); +static cvar_t r_tracker_lines = CVARAFCD("r_tracker_lines", "8", "r_tracker_messages", 0, TrackerCallback, "number of r_tracker messages to display\n"); static void Tracker_Update(console_t *tracker) { tracker->notif_l = tracker->maxlines = max(1,r_tracker_lines.ival); diff --git a/engine/client/in_generic.c b/engine/client/in_generic.c index 20977da08..c3f78686a 100644 --- a/engine/client/in_generic.c +++ b/engine/client/in_generic.c @@ -1051,7 +1051,7 @@ void IN_MoveJoystick(struct joy_s *joy, float *movements, int pnum, float framet VectorScale(jlook, 360*cl_movespeedkey.value, jlook); VectorScale(jstrafe, 360*cl_movespeedkey.value, jstrafe); } - VectorScale(jlook, 360*frametime, jlook); + VectorScale(jlook, 360*frametime*in_sensitivityscale, jlook); if (!movements) //if this is null, gamecode should still get inputs, just no camera looking or anything. return; diff --git a/engine/client/in_sdl.c b/engine/client/in_sdl.c index 45e41e543..054846e77 100644 --- a/engine/client/in_sdl.c +++ b/engine/client/in_sdl.c @@ -1127,26 +1127,11 @@ static unsigned int tbl_sdltoquakemouse[] = #include static qboolean usesteamosk; #endif -#endif -void Sys_SendKeyEvents(void) -{ - SDL_Event event; - int axis, j; -#ifdef HAVE_SDL_TEXTINPUT +void INS_SetOSK(int osk) +{ static SDL_bool active = false; - SDL_bool osk = Key_Dest_Has(kdm_console|kdm_cwindows|kdm_message); - if (Key_Dest_Has(kdm_prompt|kdm_menu)) - { - j = Menu_WantOSK(); - if (j < 0) - osk |= sys_osk.ival; - else - osk |= j; - } - else if (Key_Dest_Has(kdm_game)) - osk |= sys_osk.ival; - if (osk) + if (osk && sdlwindow) { SDL_Rect rect; rect.x = 0; @@ -1181,8 +1166,41 @@ void Sys_SendKeyEvents(void) // Con_Printf("OSK shown... killed\n"); } } +} +#else +void INS_SetOSK(int osk) +{ +} +#endif +void Sys_SendKeyEvents(void) +{ + SDL_Event event; + int axis, j; + +#ifdef HAVE_SERVER + if (isDedicated) + { + SV_GetConsoleCommands (); + return; + } #endif +#ifdef HAVE_SDL_TEXTINPUT + { + SDL_bool osk = Key_Dest_Has(kdm_console|kdm_cwindows|kdm_message); + if (Key_Dest_Has(kdm_prompt|kdm_menu)) + { + j = Menu_WantOSK(); + if (j < 0) + osk |= sys_osk.ival; + else + osk |= j; + } + else if (Key_Dest_Has(kdm_game)) + osk |= sys_osk.ival; + INS_SetOSK(osk); + } +#endif while(SDL_PollEvent(&event)) { @@ -1202,9 +1220,10 @@ void Sys_SendKeyEvents(void) SDL_Vulkan_GetDrawableSize(sdlwindow, &window_width, &window_height); //get the proper physical size. if (vid.pixelwidth != window_width || vid.pixelheight != window_height) vk.neednewswapchain = true; + break; } - else #endif + if (qrenderer == QR_OPENGL) { #if SDL_VERSION_ATLEAST(2,0,1) SDL_GL_GetDrawableSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); //get the proper physical size. diff --git a/engine/client/keys.c b/engine/client/keys.c index 3988d6249..9228b56e5 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -1133,7 +1133,7 @@ void Key_DefaultLinkClicked(console_t *con, char *text, char *info) c = Info_ValueForKey(info, "playaudio"); if (*c && !strchr(c, ';') && !strchr(c, '\n')) { - S_StartSound(0, INT_MAX-1, S_PrecacheSound(c), NULL, NULL, 1, ATTN_NONE, 0, 0, CF_NOSPACIALISE); + S_StartSound(0, 0x7ffffffe, S_PrecacheSound(c), NULL, NULL, 1, ATTN_NONE, 0, 0, CF_NOSPACIALISE); return; } c = Info_ValueForKey(info, "desc"); @@ -3081,21 +3081,40 @@ void Key_Event (unsigned int devid, int key, unsigned int unicode, qboolean down // if (cls.demoplayback && cls.demoplayback != DPB_MVD && conkey && !Key_Dest_Has(~kdm_game)) { - switch (key) - { //these keys don't force the menu to appear while playing the demo reel - case K_LSHIFT: - case K_RSHIFT: - case K_LALT: - case K_RALT: - case K_LCTRL: -// case K_RCTRL: - break; - default: - dc = keybindings[key][modifierstate]; - //toggleconsole or +showFOO keys should do their regular bind action - //demo_jump/demo_setspeed/demo_nudge should be allowed too. - if (!dc || (strcmp(dc, "toggleconsole") && strncmp(dc, "+show", 5) && strncmp(dc, "demo_", 5))) - { + dc = keybindings[key][modifierstate]; + if (!dc || (strcmp(dc, "toggleconsole") && strncmp(dc, "+show", 5) && strncmp(dc, "demo_", 5))) + { + extern cvar_t cl_demospeed; + switch (key) + { //these keys don't force the menu to appear while playing the demo reel + case K_LSHIFT: + case K_RSHIFT: + case K_LALT: + case K_RALT: + case K_LCTRL: + // case K_RCTRL: + break; + //demo modifiers... + case K_DOWNARROW: + case K_GP_DPAD_DOWN: + Cvar_SetValue(&cl_demospeed, max(cl_demospeed.value - 0.1, 0)); + Con_Printf("playback speed: %g%%\n", cl_demospeed.value*100); + return; + case K_UPARROW: + case K_GP_DPAD_UP: + Cvar_SetValue(&cl_demospeed, min(cl_demospeed.value + 0.1, 10)); + Con_Printf("playback speed: %g%%\n", cl_demospeed.value*100); + return; + case K_LEFTARROW: + case K_GP_DPAD_LEFT: + Cbuf_AddText("demo_jump -10", RESTRICT_LOCAL); //expensive. + return; + case K_RIGHTARROW: + case K_GP_DPAD_RIGHT: + Cbuf_AddText("demo_jump +10", RESTRICT_LOCAL); + return; + //any other key + default: M_ToggleMenu_f (); return; } diff --git a/engine/client/m_download.c b/engine/client/m_download.c index 80b008ad1..2d618c1b1 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -407,7 +407,7 @@ static void PM_ValidateAuthenticity(package_t *p, enum hashvalidation_e validate //this is temporary code and should be removed once everything else has been fixed. //ignore the signature (flag as accepted) for any packages with all mirrors on our own update site. //we can get away with this because we enforce a known certificate for the download. - if (!COM_CheckParm("-notlstrust")) + if (!COM_CheckParm("-notlstrust") && p->mirror[0]) { conchar_t musite[256], *e; char site[256]; @@ -962,7 +962,7 @@ static qboolean PM_CheckFeature(const char *feature, const char **featurename, c if (!strcmp(feature, "24bit")) return *featurename="24bit Textures", *concommand="seta gl_load24bit 1\n", gl_load24bit.ival; if (!strcmp(feature, "md3")) - return *featurename="Replacement Models", *concommand="seta r_replacemodels md3 md2\n", !!strstr(r_replacemodels.string, "md3"); + return *featurename="Replacement Models", *concommand="seta r_replacemodels md3 md2 md5mesh\n", !!strstr(r_replacemodels.string, "md3"); if (!strcmp(feature, "rtlights")) return *featurename="Realtime Dynamic Lights", *concommand="seta r_shadow_realtime_dlight 1\n", r_shadow_realtime_dlight.ival||r_shadow_realtime_world.ival; if (!strcmp(feature, "rtworld")) diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 2942bf9f4..4deaf8fa2 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -3008,7 +3008,7 @@ void M_Menu_Video_f (void) static const char *srgbvalues[] = { "0", "1", "2", "-1", NULL}; -#ifdef ANDROID +#if defined(ANDROID) && !defined(FTE_SDL) extern cvar_t sys_orientation; static const char *orientationopts[] = { "Auto", @@ -3027,6 +3027,15 @@ void M_Menu_Video_f (void) NULL }; #else + extern cvar_t vid_fullscreen; + static const char *fullscreenopts[] = { + "Windowed", + "Fullscreen", + "Borderless Windowed", + NULL + }; + static const char *fullscreenvalues[] = {"0", "1", "2", NULL}; +#endif extern cvar_t vid_renderer; static const char *rendererops[] = { @@ -3087,16 +3096,6 @@ void M_Menu_Video_f (void) NULL }; - extern cvar_t vid_fullscreen; - static const char *fullscreenopts[] = { - "Windowed", - "Fullscreen", - "Borderless Windowed", - NULL - }; - static const char *fullscreenvalues[] = {"0", "1", "2", NULL}; -#endif - static const char *aaopts[] = { "1x", "2x", @@ -3245,10 +3244,10 @@ void M_Menu_Video_f (void) MB_TEXT("^Ue080^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue082", true), MB_CMD("Apply Settings", M_VideoApply, "Restart video and apply renderer, display, and 2D resolution options."), MB_SPACING(4), -#ifdef ANDROID + MB_COMBOCVAR("Renderer", vid_renderer, rendererops, renderervalues, NULL), +#if defined(ANDROID) && !defined(FTE_SDL) MB_COMBOCVAR("Orientation", sys_orientation, orientationopts, orientationvalues, NULL), #else - MB_COMBOCVAR("Renderer", vid_renderer, rendererops, renderervalues, NULL), MB_COMBOCVARRETURN("Display Mode", vid_fullscreen, fullscreenopts, fullscreenvalues, info->dispmode, vid_fullscreen.description), #endif MB_COMBOCVAR("MSAA", vid_multisample, aaopts, aavalues, NULL), diff --git a/engine/client/m_single.c b/engine/client/m_single.c index 800d4a950..bb4246504 100644 --- a/engine/client/m_single.c +++ b/engine/client/m_single.c @@ -316,6 +316,53 @@ void M_Menu_Load_f (void) #endif +static qboolean M_SingleParseMapDBEpisodes(emenu_t *menu, int *y, qboolean bigfont) +{ //use the remaster's episode selection lists. + size_t sz; + char *file = FS_LoadMallocFile("mapdb.json", &sz); + if (file) + { + json_t *j = JSON_Parse(file); + json_t *episodes = JSON_FindChild(j, "episodes"), *e; + int i = 0; + while ((e=JSON_GetIndexed(episodes, i++))) + { + char namebuf[MAX_QPATH]; + char cmdbuf[MAX_QPATH]; + const char *command = JSON_GetString(e, "command", cmdbuf,sizeof(cmdbuf), NULL); + const char *name = JSON_GetString(e, "name", namebuf,sizeof(namebuf), NULL); + if (!command) + { + command = JSON_GetString(e, "dir", cmdbuf,sizeof(cmdbuf), NULL); + if (command) + command = va("gamedir %s; map start", command); + } + if (!command) + continue; + name = TL_Translate(com_language, name); + if (name && command) + { + menubutton_t *b; + if (bigfont) + b = MC_AddConsoleCommandQBigFont(menu, 72, *y, name, va("closemenu;disconnect;maxclients 1;spectator \"\";samelevel \"\";deathmatch \"\";set_calc coop ($cl_splitscreen>0);%s\n", command)), *y += 20-8; + else if (JSON_GetInteger(e, "needsClassSelect", false)) + b = MC_AddConsoleCommand (menu, 64, 260, *y, name, va("menu_single class %s\n", command)); + else if (JSON_GetInteger(e, "needsSkillSelect", false)) + b = MC_AddConsoleCommand (menu, 64, 260, *y, name, va("menu_single skill %s\n", command)); + else + b = MC_AddConsoleCommand (menu, 64, 260, *y, name, va("closemenu; skill 0;deathmatch 0; set_calc coop ($cl_splitscreen>0);%s\n",command)); + *y+=8; + if (!menu->selecteditem) + menu->selecteditem = (menuoption_t*)b; + } + } + JSON_Destroy(j); + FS_FreeFile(file); + return true; + } + return false; +} + void M_Menu_SinglePlayer_f (void) { emenu_t *menu; @@ -347,25 +394,41 @@ void M_Menu_SinglePlayer_f (void) { #ifdef Q2CLIENT case MGT_QUAKE2: - menu = M_CreateMenu(0); + { + int y = 40; + const char *command = "newgame"; + menu = M_CreateMenu(0); - MC_AddCenterPicture(menu, 4, 24, "pics/m_banner_game"); + MC_AddCenterPicture(menu, 4, 24, "pics/m_banner_game"); - //quake2 uses the 'newgame' alias, which controls the intro video and then start map. - menu->selecteditem = (menuoption_t*) - MC_AddConsoleCommand (menu, 64, 170, 40, "Easy", va("closemenu; skill 0;deathmatch 0; set_calc coop ($cl_splitscreen>0);newgame\n")); - MC_AddConsoleCommand (menu, 64, 170, 48, "Medium", va("closemenu; skill 1;deathmatch 0; set_calc coop ($cl_splitscreen>0);newgame\n")); - MC_AddConsoleCommand (menu, 64, 170, 56, "Hard", va("closemenu; skill 2;deathmatch 0; set_calc coop ($cl_splitscreen>0);newgame\n")); + if (!strncmp(Cmd_Argv(1), "skill", 5)) + command = Cmd_Argv(2); + else + M_SingleParseMapDBEpisodes(menu, &y, false); + + if (!menu->selecteditem) + { //quake2 uses the 'newgame' alias, which controls the intro video and then start map. + menu->selecteditem = (menuoption_t*) + MC_AddConsoleCommand (menu, 64, 170, y, "Easy", va("closemenu; skill 0;deathmatch 0; set_calc coop ($cl_splitscreen>0);%s\n",command)); y+=8; + MC_AddConsoleCommand (menu, 64, 170, y, "Medium", va("closemenu; skill 1;deathmatch 0; set_calc coop ($cl_splitscreen>0);%s\n",command)); y+=8; + MC_AddConsoleCommand (menu, 64, 170, y, "Hard", va("closemenu; skill 2;deathmatch 0; set_calc coop ($cl_splitscreen>0);%s\n",command)); y+=8; + } + if (strncmp(Cmd_Argv(1), "skill", 5)) + { #ifdef SAVEDGAMES - MC_AddConsoleCommand (menu, 64, 170, 72, "Load Game", "menu_load\n"); - MC_AddConsoleCommand (menu, 64, 170, 80, "Save Game", "menu_save\n"); + y+=8; + MC_AddConsoleCommand (menu, 64, 170, y, "Load Game", "menu_load\n"); y+=8; + MC_AddConsoleCommand (menu, 64, 170, y, "Save Game", "menu_save\n"); y+=8; #endif #if MAX_SPLITS > 1 - b = (menubutton_t*)MC_AddCvarCombo(menu, 72, 170, 96, localtext("Splitscreen"), &cl_splitscreen, splitopts, splitvals); + y+=8; + MC_AddCvarCombo(menu, 72, 170, y, localtext("Splitscreen"), &cl_splitscreen, splitopts, splitvals); #endif + } - menu->cursoritem = (menuoption_t*)MC_AddWhiteText(menu, 48, 0, 40, NULL, false); + menu->cursoritem = (menuoption_t*)MC_AddWhiteText(menu, 48, 0, 40, NULL, false); + } return; #endif #ifdef HEXEN2 @@ -510,15 +573,21 @@ void M_Menu_SinglePlayer_f (void) default: if (QBigFontWorks()) { + int y = 32; menu = M_CreateMenu(0); MC_AddPicture(menu, 16, 4, 32, 144, "gfx/qplaque.lmp"); MC_AddCenterPicture(menu, 4, 24, "gfx/ttl_sgl.lmp"); - menu->selecteditem = (menuoption_t*) - MC_AddConsoleCommandQBigFont (menu, 72, 32, "New Game", "closemenu;disconnect;maxclients 1;spectator \"\";samelevel \"\";deathmatch \"\";set_calc coop ($cl_splitscreen>0);startmap_sp\n"); + if (M_SingleParseMapDBEpisodes(menu, &y, true)) + y += 20; + else + { + menu->selecteditem = (menuoption_t*) + MC_AddConsoleCommandQBigFont (menu, 72, y, "New Game", "closemenu;disconnect;maxclients 1;spectator \"\";samelevel \"\";deathmatch \"\";set_calc coop ($cl_splitscreen>0);startmap_sp\n"); y += 20; + } #ifdef SAVEDGAMES - MC_AddConsoleCommandQBigFont (menu, 72, 52, "Load Game", "menu_load\n"); - MC_AddConsoleCommandQBigFont (menu, 72, 72, "Save Game", "menu_save\n"); + MC_AddConsoleCommandQBigFont (menu, 72, y, "Load Game", "menu_load\n"); y+=20; + MC_AddConsoleCommandQBigFont (menu, 72, y, "Save Game", "menu_save\n"); y+=20; #endif menu->cursoritem = (menuoption_t*)MC_AddCursor(menu, &resel, 54, 32); diff --git a/engine/client/net_master.c b/engine/client/net_master.c index 4da4e693e..e0d02fcdb 100644 --- a/engine/client/net_master.c +++ b/engine/client/net_master.c @@ -938,6 +938,7 @@ void Master_SetupSockets(void) static void CL_MasterListParse(netadrtype_t adrtype, int type, qboolean slashpad); static int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favorite); +static void CL_ReadPingList(void); #else void Master_SetupSockets(void) { @@ -2392,6 +2393,12 @@ void Master_CheckPollSockets(void) continue; } #endif + if (!strncmp(s, "pinglist", 8)) //parse a bit more... + { + net_message.currentbit = (c+8-1)<<3; + CL_ReadPingList(); + continue; + } net_message.currentbit = c; @@ -2632,6 +2639,23 @@ static qboolean MasterInfo_ReadProtocol(serverinfo_t *info, const char *infostri info->special |= SS_NETQUAKE; else if (*token == 'x') info->special |= SS_QEPROT; + + else if (*token == 't') + info->special |= SS_PROXY; //qtv + else if (*token == 'r') + { +#if POLLTOTALSOCKETS>0 + char *msg = "\xff\xff\xff\xffpingstatus ext"; + if (info->peers) + { //forget the old, to let them timeout. + Z_Free(info->peers); + info->peers = NULL; + info->numpeers = 0; + } + NET_SendPollPacket(strlen(msg), msg, info->adr); +#endif + info->special |= SS_PROXY|SS_RELAY; //qwfwd relay, ask it for its pinglist + } else continue; break; @@ -3297,6 +3321,86 @@ void MasterInfo_AddPlayer(netadr_t *serveradr, char *name, int ping, int frags, mplayers = p; } +static void CL_ReadPingListEntry(serverinfo_t *info, netadrtype_t type, size_t *maxpeers) +{ + serverinfo_t *peer; + unsigned short ping; + int i; + netadr_t pa; + char adr[MAX_ADR_SIZE]; + memset(&pa, 0, sizeof(pa)); + + pa.type = type; + if (type == NA_IP) + { + for (i = 0; i < countof(pa.address.ip); i++) + pa.address.ip[i] = MSG_ReadByte(); + } + else if (type == NA_IPV6) + { + for (i = 0; i < countof(pa.address.ip6); i++) + pa.address.ip6[i] = MSG_ReadByte(); + } + else + { + Sys_Error("CL_ReadPingListEntry: Unsupported netadrtype_t\n"); + return; //error... + } + pa.port = htons(MSG_ReadShort()); //little endian... stored into a network-endian variable... + ping = MSG_ReadShort(); + + if (NET_ClassifyAddress(&pa, NULL) >= ASCOPE_NET) + { + peer = Master_InfoForServer(&pa, NULL); + if (!peer) + { + //generate some lame peer node that we can use. + peer = Z_Malloc(sizeof(serverinfo_t)); + peer->adr = pa; + peer->sends = 1; + peer->special = SS_QUAKEWORLD; + peer->refreshtime = 0; + peer->ping = PING_DEAD; + Q_snprintfz(peer->name, sizeof(peer->name), "%s p", Master_ServerToString(adr, sizeof(adr), peer)); + peer->next = firstserver; + firstserver = peer; + } + + for (i = 0; i < info->numpeers; i++) + { + if (info->peers[i].peer == peer) + break; + } + if (i == *maxpeers) + { //need a new one + info->numpeers = i+1; + Z_ReallocElements((void**)&info->peers, maxpeers, info->numpeers+64, sizeof(*info->peers)); + } + info->peers[i].peer = peer; + info->peers[i].ping = ping; + } +} +static void CL_ReadPingList(void) +{ + serverinfo_t *info = Master_InfoForServer(&net_from, NULL); + size_t count = info->numpeers; + for(;;) + { + int type = MSG_ReadByte(); + if (type == '\\') + CL_ReadPingListEntry(info, NA_IP, &count); + else if (type == '/') + CL_ReadPingListEntry(info, NA_IPV6, &count); + else + break; //don't know, don't corrupt it. + } + if (count > info->numpeers) + { //trim it... + Z_ReallocElements((void**)&info->peers, &count, info->numpeers, sizeof(*info->peers)); + info->numpeers = count; + } +} + //we got told about a server, parse it's info static int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favorite) { @@ -3361,52 +3465,12 @@ static int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolea if (!*Info_ValueForKey(msg, "hostname")) { //qq, you suck //this is a proxy peer list, not an actual serverinfo update. - unsigned char *ptr = net_message.data + 5; - int remaining = net_message.cursize - 5; - struct peers_s *peer; - netadr_t pa; - memset(&pa, 0, sizeof(pa)); - remaining /= 8; - - //Master_ServerToString(adr, sizeof(adr), info); - - Z_Free(info->peers); - info->numpeers = 0; - peer = info->peers = Z_Malloc(sizeof(*peer)*remaining); - + size_t count = info->numpeers; + int remaining = (net_message.cursize - 5) / 8; + net_message.currentbit = (5)<<3; while (remaining --> 0) - { - pa.type = NA_IP; - pa.address.ip[0] = *ptr++; - pa.address.ip[1] = *ptr++; - pa.address.ip[2] = *ptr++; - pa.address.ip[3] = *ptr++; - - pa.port = *ptr++<<8; - pa.port |= *ptr++; - peer->ping = *ptr++; - peer->ping |= *ptr++<<8; - - if (NET_ClassifyAddress(&pa, NULL) >= ASCOPE_NET) - { - peer->peer = Master_InfoForServer(&pa, NULL); - if (!peer->peer) - { - //generate some lame peer node that we can use. - peer->peer = Z_Malloc(sizeof(serverinfo_t)); - peer->peer->adr = pa; - peer->peer->sends = 1; - peer->peer->special = SS_QUAKEWORLD; - peer->peer->refreshtime = 0; - peer->peer->ping = PING_DEAD; - peer->peer->next = firstserver; - Q_snprintfz(peer->peer->name, sizeof(peer->peer->name), "%s p", Master_ServerToString(adr, sizeof(adr), peer->peer)); - firstserver = peer->peer; - } - peer++; - info->numpeers++; - } - } + CL_ReadPingListEntry(info, NA_IP, &count); + info->numpeers = count; return false; } } @@ -3456,16 +3520,21 @@ static int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolea if (*Info_ValueForKey(msg, "*qtv") || *Info_ValueForKey(msg, "*QTV")) info->special |= SS_PROXY|SS_FTESERVER; //qtv - if (!strcmp(Info_ValueForKey(msg, "*progs"), "666") && !strcmp(Info_ValueForKey(msg, "*version"), "2.91")) + else if (!strcmp(Info_ValueForKey(msg, "*progs"), "666") && !strcmp(Info_ValueForKey(msg, "*version"), "2.91")) info->special |= SS_PROXY; //qizmo - if (!Q_strncmp(Info_ValueForKey(msg, "*version"), "qwfwd", 5)) + else if (!Q_strncmp(Info_ValueForKey(msg, "*version"), "qwfwd", 5)) { - char *msg = "\xff\xff\xff\xffpingstatus"; + char *msg = "\xff\xff\xff\xffpingstatus ext"; NET_SendPollPacket(strlen(msg), msg, info->adr); - - info->special |= SS_PROXY; //qwfwd + if (info->peers) + { //let em time out + Z_Free(info->peers); + info->peers = NULL; + info->numpeers = 0; + } + info->special |= SS_PROXY|SS_RELAY; //qwfwd } - if (!Q_strncasecmp(Info_ValueForKey(msg, "*version"), "qtv ", 4)) + else if (!Q_strncasecmp(Info_ValueForKey(msg, "*version"), "qtv ", 4)) info->special |= SS_PROXY; //eztv token = Info_ValueForKey(msg, "map"); diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index 365e3d7f5..a19fddf37 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -622,6 +622,10 @@ static void QCBUILTIN PF_Fixme (pubprogfuncs_t *prinst, struct globalvars_s *pr_ prinst->RunError(prinst, "\nBuiltin %i:%s not implemented.\nCSQC is not compatible.", binum, fname); PR_BIError (prinst, "bulitin not implemented"); } +static void QCBUILTIN PF_Ignore (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + G_INT(OFS_RETURN) = 0; +} static void QCBUILTIN PF_NoCSQC (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { int binum; @@ -647,7 +651,7 @@ static void QCBUILTIN PF_checkbuiltin (pubprogfuncs_t *prinst, struct globalvars { //qc defines the function at least. nothing weird there... if (builtinno > 0 && builtinno < prinst->parms->numglobalbuiltins) { - if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme || prinst->parms->globalbuiltins[builtinno] == PF_NoCSQC) + if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme || prinst->parms->globalbuiltins[builtinno] == PF_Ignore || prinst->parms->globalbuiltins[builtinno] == PF_NoCSQC) G_FLOAT(OFS_RETURN) = false; //the builtin with that number isn't defined. else { @@ -837,7 +841,10 @@ static qboolean CopyCSQCEdictToEntity(csqcedict_t *fte_restrict in, entity_t *ft { VectorCopy(in->v->angles, out->angles); if (model && model->type == mod_alias) + { out->angles[0] *= r_meshpitch.value; + out->angles[2] *= r_meshroll.value; + } AngleVectors(out->angles, out->axis[0], out->axis[1], out->axis[2]); VectorInverse(out->axis[1]); @@ -2713,6 +2720,8 @@ static void QCBUILTIN PF_R_RenderScene(pubprogfuncs_t *prinst, struct globalvars World_RBE_Start(&csqc_world); } + R2D_ImageColours(1,1,1,1); //apparently does matter. + if (cl.worldmodel) R_PushDlights (); diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index 83278e27f..5c6da859b 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -1570,6 +1570,10 @@ void QCBUILTIN PF_clientstate (pubprogfuncs_t *prinst, struct globalvars_s *pr_g G_FLOAT(OFS_RETURN) = 1/*nq ca_disconnected*/; } +static void QCBUILTIN PF_Ignore (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + G_INT(OFS_RETURN) = 0; +} //too specific to the prinst's builtins. static void QCBUILTIN PF_Fixme (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -1595,7 +1599,7 @@ static void QCBUILTIN PF_checkbuiltin (pubprogfuncs_t *prinst, struct globalvars { //qc defines the function at least. nothing weird there... if (builtinno > 0 && builtinno < prinst->parms->numglobalbuiltins) { - if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme) + if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme || prinst->parms->globalbuiltins[builtinno] == PF_Ignore) G_FLOAT(OFS_RETURN) = false; //the builtin with that number isn't defined. else { diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c index c3f8c8b1b..e339604c7 100644 --- a/engine/client/r_surf.c +++ b/engine/client/r_surf.c @@ -2355,7 +2355,7 @@ void Surf_GenBrushBatches(batch_t **batches, entity_t *ent) } #ifdef BEF_PUSHDEPTH - if (r_pushdepth) + if (r_pushdepth && model->submodelof == r_worldentity.model) bef = BEF_PUSHDEPTH; else bef = 0; diff --git a/engine/client/roq_read.c b/engine/client/roq_read.c index 5ca678391..e58456022 100644 --- a/engine/client/roq_read.c +++ b/engine/client/roq_read.c @@ -157,7 +157,7 @@ static void apply_vector_2x2(roq_info *ri, int x, int y, roq_cell_rgba *cell) int idxa = (y * ri->width) + x; int idxb = 0; - int *ptra = (int*) &ri->rgba[0][idxa][0]; + int *ptra = (int*) ri->rgba[0][idxa]; int *ptrb = (int*) &cell->p[idxb]; ptra[0] = ptrb[0]; @@ -175,7 +175,7 @@ static void apply_vector_4x4(roq_info *ri, int x, int y, roq_cell_rgba *cell) int idxa = (y * ri->width) + x; int idxb = 0; - int *ptra = (int*) &ri->rgba[0][idxa][0]; + int *ptra = (int*) ri->rgba[0][idxa]; int *ptrb = (int*) &cell->p[idxb]; int i; @@ -202,8 +202,8 @@ static void apply_motion_4x4(roq_info *ri, int x, int y, unsigned char mv, char int idxa = (y * ri->width) + x; int idxb = (my * ri->width) + mx; - int *ptra = (int*) &ri->rgba[0][idxa][0]; - int *ptrb = (int*) &ri->rgba[1][idxb][0]; + int *ptra = (int*) ri->rgba[0][idxa]; + int *ptrb = (int*) ri->rgba[1][idxb]; int i; for(i = 0; i < 4; i++) { @@ -227,8 +227,8 @@ static void apply_motion_8x8(roq_info *ri, int x, int y, unsigned char mv, char int idxa = (y * ri->width) + x; int idxb = (my * ri->width) + mx; - int *ptra = (int*) &ri->rgba[0][idxa][0]; - int *ptrb = (int*) &ri->rgba[1][idxb][0]; + int *ptra = (int*) ri->rgba[0][idxa]; + int *ptrb = (int*) ri->rgba[1][idxb]; int i; for(i = 0; i < 8; i++) { @@ -337,7 +337,7 @@ int i; #define LIMIT(x) ((((x) > 0xffffff) ? 0xff0000 : (((x) <= 0xffff) ? 0 : (x) & 0xff0000)) >> 16) void roq_cells_to_rgba(roq_info *ri) { - char *pptr; + unsigned char *pptr; int i, r, g, b, y, u, v, t; for(i = 0; i < 256; i++) { pptr = ri->cells_rgba[i].p; diff --git a/engine/client/sys_sdl.c b/engine/client/sys_sdl.c index 97832d338..fed409041 100644 --- a/engine/client/sys_sdl.c +++ b/engine/client/sys_sdl.c @@ -58,10 +58,24 @@ void Sys_RecentServer(char *command, char *target, char *title, char *desc) { } +#if defined(__linux__) || defined(BSD) +qboolean Sys_RandomBytes(qbyte *string, int len) +{ + qboolean res = false; + int fd = open("/dev/urandom", 0); + if (fd != -1) + { + res = (read(fd, string, len) == len); + close(fd); + } + return res; +} +#else qboolean Sys_RandomBytes(qbyte *string, int len) { return false; } +#endif static void ApplyColour(unsigned int chrflags) { @@ -786,6 +800,9 @@ int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const //blink window if possible (it's not) void Sys_ServerActivity(void) { +#if SDL_VERSION_ATLEAST(2,0,16) + SDL_FlashWindow(sdlwindow, SDL_FLASH_BRIEFLY); +#endif } void Sys_CloseLibrary(dllhandle_t *lib) @@ -909,9 +926,129 @@ int VARGS Sys_DebugLog(char *file, char *fmt, ...) }; +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +#ifdef _WIN32 +static qboolean gotconsole; +static HANDLE con_stdin; qboolean Sys_InitTerminal(void) { + gotconsole = AllocConsole(); //failure is okay if we already had one. + con_stdin = CreateFile("CONIN$", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + freopen("CON", "w", stdout); //unfuck the stdout too. + return true; +} +char *Sys_ConsoleInput(void) +{ //NET_Sleep won't sleep on this handle, so expect it to be sluggish. + DWORD numevents, i; + while (GetNumberOfConsoleInputEvents(con_stdin, &numevents) && numevents>0) + { + static char text[256]; + static int textlen; + INPUT_RECORD event[1]; //longer might miss presses. especially with delays. + if (numevents > countof(event)) + numevents = countof(event); + if (ReadConsoleInputW(con_stdin, event, numevents, &numevents)) + { + for(i = 0; i < numevents; i++) + { + if (event[i].EventType == KEY_EVENT && event[i].Event.KeyEvent.bKeyDown && event[i].Event.KeyEvent.uChar.UnicodeChar) + { + if (textlen >= countof(text)) + textlen = countof(text)-1; //don't overflow. + text[textlen] = event[i].Event.KeyEvent.uChar.UnicodeChar; + if (text[textlen] == '\r') + { + text[textlen] = 0; //caller will add its own \n + printf("\r]%s\n", text); + textlen = 0; //start from the start + fflush(stdout); + return text; + } + textlen++; + text[textlen] = 0; //caller will add its own \n + printf("\r]%s", text); + fflush(stdout); + } + } + } + } + return NULL; +} +void Sys_CloseTerminal (void) +{ + if (gotconsole) + { //don't close our initial one. don't detach that way. + FreeConsole(); + gotconsole = false; + } + if (con_stdin) + { + CloseHandle(con_stdin); + con_stdin = NULL; + } +} +#elif defined(__unix__) && !defined(__ANDROID__) +static qbyte noconinput; +qboolean Sys_InitTerminal(void) +{ + if (COM_CheckParm("-nostdin")) + noconinput = true; + if (noconinput) + return true; //they okayed it, let it start regardless. + if (isatty(STDIN_FILENO)) + return true; + Con_Printf(CON_WARNING"Sys_InitTerminal: not started from a tty\n"); //no easy way to kill it otherwise. + return false; +} +char *Sys_ConsoleInput(void) +{ + static char text[256]; + char *nl; + + if (noconinput) + return NULL; + +#if defined(__linux__) && defined(_DEBUG) + { + int fl = fcntl (STDIN_FILENO, F_GETFL, 0); + if (!(fl & FNDELAY)) + { + fcntl(STDIN_FILENO, F_SETFL, fl | FNDELAY); +// Sys_Printf(CON_WARNING "stdin flags became blocking - gdb bug?\n"); + } + } +#endif + + if (!fgets(text, sizeof(text), stdin)) + { + if (errno == EIO) + { + Sys_Printf(CON_WARNING "Backgrounded, ignoring stdin\n"); + noconinput |= 2; + } + return NULL; + } + nl = strchr(text, '\n'); + if (!nl) //err? wut? + return NULL; + *nl = 0; + + return text; +} +void Sys_CloseTerminal (void) +{ +} +#else +qboolean Sys_InitTerminal(void) +{ + Con_Printf(CON_WARNING"Sys_InitTerminal: not implemented in this build.\n"); return false; //Sys_ConsoleInput cannot work, so return false here. } char *Sys_ConsoleInput(void) @@ -921,13 +1058,6 @@ char *Sys_ConsoleInput(void) void Sys_CloseTerminal (void) { } - -#ifdef _WIN32 -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#include #endif #ifdef FTE_TARGET_WEB @@ -970,6 +1100,32 @@ int QDECL main(int argc, char **argv) if (parms.binarydir) Sys_Printf("Binary is located at \"%s\"\n", parms.binarydir); + +#ifdef HAVE_CLIENT + if (COM_CheckParm ("-dedicated")) + isDedicated = true; + if (isDedicated) //compleate denial to switch to anything else - many of the client structures are not initialized. + { + float delay; + + SV_Init (&parms); + + if (!Sys_InitTerminal()) + Con_Printf(CON_WARNING"Stdin unavailable\n"); + + delay = SV_Frame(); + while (1) + { + if (!isDedicated) + Sys_Error("Dedicated was cleared"); + NET_Sleep(delay, false); + delay = SV_Frame(); + } + return EXIT_FAILURE; + } +#endif + + Host_Init (&parms); oldtime = Sys_DoubleTime (); diff --git a/engine/client/vid_headless.c b/engine/client/vid_headless.c index ac6fc9ac9..422223bd6 100644 --- a/engine/client/vid_headless.c +++ b/engine/client/vid_headless.c @@ -10,6 +10,9 @@ #else #include #endif +#ifdef FTE_SDL +#include +#endif static void Headless_Draw_Init(void) { @@ -172,7 +175,9 @@ static qboolean Headless_SCR_UpdateScreen (void) { if (!cls.timedemo) { -#if defined(_WIN32) && !defined(FTE_SDL) +#ifdef FTE_SDL + SDL_Delay(100); +#elif defined(_WIN32) Sleep(100); #else usleep(100*1000); diff --git a/engine/client/view.c b/engine/client/view.c index 0dba588e2..d7e8072e9 100644 --- a/engine/client/view.c +++ b/engine/client/view.c @@ -517,7 +517,7 @@ void V_ParseDamage (playerview_t *pv) if (count < 10) count = 10; -#ifdef ANDROID +#if defined(ANDROID) && !defined(FTE_SDL) //later versions of android might support strength values, but the simple standard interface is duration only. Sys_Vibrate(count); #endif diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 39b388a46..809e238d8 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -673,6 +673,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define FTE_LITTLE_ENDIAN #endif #endif +#elif defined(__LITTLE_ENDIAN__) + #define FTE_LITTLE_ENDIAN +#elif defined(__BIG_ENDIAN__) + #define FTE_BIG_ENDIAN #endif #ifdef _MSC_VER diff --git a/engine/common/cmd.c b/engine/common/cmd.c index cf22c1784..e27d5fe68 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -874,7 +874,7 @@ static void Cmd_Exec_f (void) s+=3; } - if (!strcmp(name, "config.cfg") || !strcmp(name, "q3config.cfg") || !strcmp(name, fs_manifest->mainconfig)) + if (!strcmp(name, "config.cfg") || !strcmp(name, "q3config.cfg") || (*fs_manifest->mainconfig && !strcmp(name, fs_manifest->mainconfig))) { char *restart; //if the config is from id1 and the default.cfg was from some mod, make sure the default.cfg overrides the config. @@ -4148,7 +4148,10 @@ static void Cmd_WriteConfig_f(void) filename = Cmd_Argv(1); if (!*filename) { - Q_strncpyz(fname, fs_manifest->mainconfig, sizeof(fname)); + if (*fs_manifest->mainconfig) + Q_strncpyz(fname, fs_manifest->mainconfig, sizeof(fname)); + else + Q_strncpyz(fname, "config.cfg", sizeof(fname)); //write SOMETHING. #if defined(CL_MASTER) && defined(HAVE_CLIENT) MasterInfo_WriteServers(); diff --git a/engine/common/common.c b/engine/common/common.c index 5e0de0fe1..b977f8d07 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -5662,39 +5662,150 @@ static void COM_Version_f (void) Con_Printf("%s\n", version_string()); #ifdef FTE_BRANCH - Con_Printf("Branch: "STRINGIFY(FTE_BRANCH)"\n"); - Con_Printf("Revision: %s - %s\n",STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE)); + Con_Printf("^3Branch:^7 "STRINGIFY(FTE_BRANCH)"\n"); + Con_Printf("^3Revision:^7 %s - %s\n",STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE)); #elif defined(SVNREVISION) && defined(SVNDATE) if (!strncmp(STRINGIFY(SVNREVISION), "git-", 4)) - Con_Printf("GIT Revision: %s - %s\n",STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE)); + Con_Printf("^3GIT Revision:^7 %s - %s\n",STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE)); else - Con_Printf("SVN Revision: %s - %s\n",STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE)); + Con_Printf("^3SVN Revision:^7 %s - %s\n",STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE)); #else - Con_TPrintf ("Exe: %s %s\n", __DATE__, __TIME__); + Con_TPrintf ("^3Exe:^7 %s %s\n", __DATE__, __TIME__); #ifdef SVNREVISION if (!strncmp(STRINGIFY(SVNREVISION), "git-", 4)) - Con_Printf("GIT Revision: %s\n",STRINGIFY(SVNREVISION)); + Con_Printf("^3GIT Revision:^7 %s\n",STRINGIFY(SVNREVISION)); else if (strcmp(STRINGIFY(SVNREVISION), "-")) - Con_Printf("SVN Revision: %s\n",STRINGIFY(SVNREVISION)); + Con_Printf("^3SVN Revision:^7 %s\n",STRINGIFY(SVNREVISION)); #endif #endif #ifdef CONFIG_FILE_NAME - Con_Printf("Build config: %s\n\n", COM_SkipPath(STRINGIFY(CONFIG_FILE_NAME))); + Con_Printf("^3Build config:^7 %s\n\n", COM_SkipPath(STRINGIFY(CONFIG_FILE_NAME))); #endif -#ifdef _DEBUG - Con_Printf("debug build\n"); -#endif + Con_Printf("^3Build type:^7"); #ifdef MINIMAL - Con_Printf("minimal build\n"); + Con_Printf("minimal\n"); #endif #ifdef CLIENTONLY - Con_Printf("client-only build\n"); + Con_Printf(" client-only\n"); #endif #ifdef SERVERONLY - Con_Printf("dedicated server build\n"); + Con_Printf(" dedicated\n"); +#endif +#ifdef _DEBUG + Con_Printf(" debug"); #else - Con_Printf("Renderers:"); + Con_Printf(" release"); +#endif + Con_Printf("\n"); + +#ifdef FTE_SDL + Con_Printf("^3SDL version:^7 %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); +#endif + +// Don't print both as a 64bit MinGW built client +#if defined(__MINGW32__) + Con_Printf("Compiled with MinGW32/64 version: %i.%i\n",__MINGW32_MAJOR_VERSION, __MINGW32_MINOR_VERSION); +#endif + +#ifdef __CYGWIN__ + Con_Printf("Compiled with Cygwin\n"); +#endif + +#ifdef FTE_TARGET_WEB + Con_Printf("Compiled with emscripten %i.%i.%i\n", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__); +#endif + +#ifdef __clang__ + Con_Printf("^3Compiler:^7 clang %i.%i.%i (%s)\n",__clang_major__, __clang_minor__, __clang_patchlevel__, __VERSION__); +#elif defined(__GNUC__) + Con_Printf("^3Compiler:^7 GCC %i.%i.%i (%s)\n",__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__, __VERSION__); + + #ifdef __OPTIMIZE__ + #ifdef __OPTIMIZE_SIZE__ + Con_Printf("Optimized for size\n"); + #else + Con_Printf("Optimized for speed\n"); + #endif + #endif + + #ifdef __NO_INLINE__ + Con_Printf("^3GCC Optimization:^7 Functions currently not inlined into their callers\n"); + #else + Con_Printf("^3GCC Optimization:^7 Functions currently inlined into their callers\n"); + #endif +#elif defined(_MSC_VER) + if (_MSC_VER == 600) { Con_Printf( "^3Compiler:^7 C Compiler version 6.0\n"); } + else if (_MSC_VER == 700) { Con_Printf( "^3Compiler:^7 C/C++ compiler version 7.0\n"); } + else if (_MSC_VER == 800) { Con_Printf( "^3Compiler:^7 Visual C++, Windows, version 1.0 or Visual C++, 32-bit, version 1.0\n"); } + else if (_MSC_VER == 900) { Con_Printf( "^3Compiler:^7 Visual C++, Windows, version 2.0 or Visual C++, 32-bit, version 2.x\n"); } + else if (_MSC_VER == 1000) { Con_Printf("^3Compiler:^7 Visual C++, 32-bit, version 4.0\n"); } + else if (_MSC_VER == 1020) { Con_Printf("^3Compiler:^7 Visual C++, 32-bit, version 4.2\n"); } + else if (_MSC_VER == 1100) { Con_Printf("^3Compiler:^7 Visual C++, 32-bit, version 5.0\n"); } + else if (_MSC_VER == 1200) { Con_Printf("^3Compiler:^7 Visual C++, 32-bit, version 6.0\n"); } + else if (_MSC_VER == 1300) { Con_Printf("^3Compiler:^7 Visual C++, version 7.0\n"); } + else if (_MSC_VER == 1310) { Con_Printf("^3Compiler:^7 Visual C++ 2003, version 7.1\n"); } + else if (_MSC_VER == 1400) { Con_Printf("^3Compiler:^7 Visual C++ 2005, version 8.0\n"); } + else if (_MSC_VER == 1500) { Con_Printf("^3Compiler:^7 Visual C++ 2008, version 9.0\n"); } + else if (_MSC_VER == 1600) { Con_Printf("^3Compiler:^7 Visual C++ 2010, version 10.0\n"); } + else if (_MSC_VER == 1700) { Con_Printf("^3Compiler:^7 Visual C++ 2012, version 11.0\n"); } + else if (_MSC_VER == 1800) { Con_Printf("^3Compiler:^7 Visual C++ 2013, version 12.0\n"); } + else if (_MSC_VER == 1900) { Con_Printf("^3Compiler:^7 Visual C++ 2015, version 14.0\n"); } + else if (_MSC_VER >= 1910 && _MSC_VER < 1920) { Con_Printf("^3Compiler:^7 Visual C++ 2017, version 14.1x\n"); } + else if (_MSC_VER >= 1920 && _MSC_VER < 1930) { Con_Printf("^3Compiler:^7 Visual C++ 2019, version 14.2x\n"); } + else + { +#ifdef _MSC_BUILD + Con_Printf("^3Compiler:^7 Unknown Microsoft C++ compiler: %i %i %i\n",_MSC_VER, _MSC_FULL_VER, _MSC_BUILD); +#else + Con_Printf("^3Compiler:^7 Unknown Microsoft C++ compiler: %i %i\n",_MSC_VER, _MSC_FULL_VER); +#endif + } +#endif + + Con_Printf("^3CPU Arch:^7 " PLATFORM " " ARCH_CPU_POSTFIX +#ifdef ARCH_ALTCPU_POSTFIX + "/"ARCH_ALTCPU_POSTFIX +#endif + ); +#ifdef __AVX512F__ + Con_Printf(" AVX512"); +#elif defined(__AVX2__) + Con_Printf(" AVX2"); +#elif defined (__AVX__) + Con_Printf(" AVX"); +#elif defined (__SSE4_2__) + Con_Printf(" SSE4.2"); +#elif defined (__SSE4_1__) + Con_Printf(" SSE4.1"); +#elif defined (__SSE3__) + Con_Printf(" SSE3"); +#elif defined(_M_IX86_FP) && _M_IX86_FP == 2 //32bit only - always enabled for amd64 + Con_Printf(" SSE2"); +#elif defined(_M_IX86_FP) && _M_IX86_FP == 1 //32bit only - always enabled for amd64 + Con_Printf(" SSE"); +#elif defined(_M_IX86_FP) && _M_IX86_FP == 0 //32bit only - always enabled for amd64 + Con_Printf(" x87"); +#endif + Con_Printf("\n"); + +#ifdef _M_IX86 + Con_Printf("^3x86 optimized for:^7 "); + + if (_M_IX86 == 600) { Con_Printf("Blend or Pentium Pro, Pentium II and Pentium III"); } + else if (_M_IX86 == 500) { Con_Printf("Pentium"); } + else if (_M_IX86 == 400) { Con_Printf("486"); } + else if (_M_IX86 == 300) { Con_Printf("386"); } + else + { + Con_Printf("Unknown (%i)\n",_M_IX86); + } + + Con_Printf("\n"); +#endif + +#ifdef HAVE_CLIENT + Con_Printf("^3Renderers:^7"); #ifdef GLQUAKE #ifdef GLESONLY #ifdef FTE_TARGET_WEB //shuld we be just asking the video code for a list?... @@ -5724,120 +5835,14 @@ static void COM_Version_f (void) Con_Printf("\n"); #endif -#ifdef QCJIT - Con_Printf("QuakeC just-in-time compiler (QCJIT) enabled\n"); -#endif - -#ifdef FTE_SDL - Con_Printf("SDL version: %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); -#endif - -// Don't print both as a 64bit MinGW built client -#if defined(__MINGW32__) - Con_Printf("Compiled with MinGW32/64 version: %i.%i\n",__MINGW32_MAJOR_VERSION, __MINGW32_MINOR_VERSION); -#endif - -#ifdef __CYGWIN__ - Con_Printf("Compiled with Cygwin\n"); -#endif - -#ifdef FTE_TARGET_WEB - Con_Printf("Compiled with emscripten %i.%i.%i\n", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__); -#endif - -#ifdef __clang__ - Con_Printf("Compiled with clang version: %i.%i.%i (%s)\n",__clang_major__, __clang_minor__, __clang_patchlevel__, __VERSION__); -#elif defined(__GNUC__) - Con_Printf("Compiled with GCC version: %i.%i.%i (%s)\n",__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__, __VERSION__); - - #ifdef __OPTIMIZE__ - #ifdef __OPTIMIZE_SIZE__ - Con_Printf("Optimized for size\n"); - #else - Con_Printf("Optimized for speed\n"); - #endif - #endif - - #ifdef __NO_INLINE__ - Con_Printf("GCC Optimization: Functions currently not inlined into their callers\n"); - #else - Con_Printf("GCC Optimization: Functions currently inlined into their callers\n"); - #endif -#endif - -#ifdef _WIN64 - Con_Printf("Compiled for 64bit windows\n"); -#endif -#if defined(_M_AMD64) || defined(__amd64__) || defined(__x86_64__) - #ifdef __ILP32__ - Con_Printf("Compiled for AMD64 compatible cpus (x32)\n"); - #else - Con_Printf("Compiled for AMD64 compatible cpus\n"); - #endif -#endif - -#ifdef _M_IX86 - Con_Printf("x86 optimized for: "); - - if (_M_IX86 == 600) { Con_Printf("Blend or Pentium Pro, Pentium II and Pentium III"); } - else if (_M_IX86 == 500) { Con_Printf("Pentium"); } - else if (_M_IX86 == 400) { Con_Printf("486"); } - else if (_M_IX86 == 300) { Con_Printf("386"); } - else - { - Con_Printf("Unknown (%i)\n",_M_IX86); - } - - Con_Printf("\n"); -#endif - -#ifdef _M_IX86_FP - if (_M_IX86_FP == 0) { Con_Printf("SSE & SSE2 instructions disabled\n"); } - else if (_M_IX86_FP == 1) { Con_Printf("SSE instructions enabled\n"); } - else if (_M_IX86_FP == 2) { Con_Printf("SSE2 instructions enabled\n"); } - else - { - Con_Printf("Unknown Arch specified: %i\n",_M_IX86_FP); - } -#endif - -#ifdef _MSC_VER - if (_MSC_VER == 600) { Con_Printf("C Compiler version 6.0\n"); } - else if (_MSC_VER == 700) { Con_Printf("C/C++ compiler version 7.0\n"); } - else if (_MSC_VER == 800) { Con_Printf("Visual C++, Windows, version 1.0 or Visual C++, 32-bit, version 1.0\n"); } - else if (_MSC_VER == 900) { Con_Printf("Visual C++, Windows, version 2.0 or Visual C++, 32-bit, version 2.x\n"); } - else if (_MSC_VER == 1000) { Con_Printf("Visual C++, 32-bit, version 4.0\n"); } - else if (_MSC_VER == 1020) { Con_Printf("Visual C++, 32-bit, version 4.2\n"); } - else if (_MSC_VER == 1100) { Con_Printf("Visual C++, 32-bit, version 5.0\n"); } - else if (_MSC_VER == 1200) { Con_Printf("Visual C++, 32-bit, version 6.0\n"); } - else if (_MSC_VER == 1300) { Con_Printf("Visual C++, version 7.0\n"); } - else if (_MSC_VER == 1310) { Con_Printf("Visual C++ 2003, version 7.1\n"); } - else if (_MSC_VER == 1400) { Con_Printf("Visual C++ 2005, version 8.0\n"); } - else if (_MSC_VER == 1500) { Con_Printf("Visual C++ 2008, version 9.0\n"); } - else if (_MSC_VER == 1600) { Con_Printf("Visual C++ 2010, version 10.0\n"); } - else if (_MSC_VER == 1700) { Con_Printf("Visual C++ 2012, version 11.0\n"); } - else if (_MSC_VER == 1800) { Con_Printf("Visual C++ 2013, version 12.0\n"); } - else if (_MSC_VER == 1900) { Con_Printf("Visual C++ 2015, version 14.0\n"); } - else if (_MSC_VER >= 1910 && _MSC_VER < 1920) { Con_Printf("Visual C++ 2017, version 14.1x\n"); } - else if (_MSC_VER >= 1920 && _MSC_VER < 1930) { Con_Printf("Visual C++ 2019, version 14.2x\n"); } - else - { -#ifdef _MSC_BUILD - Con_Printf("Unknown Microsoft C++ compiler: %i %i %i\n",_MSC_VER, _MSC_FULL_VER, _MSC_BUILD); -#else - Con_Printf("Unknown Microsoft C++ compiler: %i %i\n",_MSC_VER, _MSC_FULL_VER); -#endif - } -#endif - #ifdef MULTITHREAD #ifdef LOADERTHREAD - Con_Printf("multithreading: enabled (loader enabled)\n"); + Con_Printf("^3multithreading:^7 enabled (loader enabled)\n"); #else - Con_Printf("multithreading: enabled (no loader)\n"); + Con_Printf("^3multithreading:^7 enabled (no loader)\n"); #endif #else - Con_Printf("multithreading: disabled\n"); + Con_Printf("^3multithreading^7: disabled\n"); #endif //print out which libraries are disabled @@ -5886,7 +5891,7 @@ static void COM_Version_f (void) Con_Printf("^3Audio Decoders:^7"); #ifdef FTE_TARGET_WEB - Con_DPrintf(" javascript"); + Con_Printf(" Browser"); #endif #ifndef AVAIL_OGGVORBIS Con_DPrintf(" ^h(disabled: Ogg Vorbis)^7"); @@ -5950,6 +5955,9 @@ static void COM_Version_f (void) #endif #ifdef ENGINE_ROUTING Con_Printf(" routing"); +#endif +#ifdef QCJIT + Con_Printf(" qcjit"); #endif Con_Printf("\n"); diff --git a/engine/common/net.h b/engine/common/net.h index e4c45af8d..c604e96cd 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -73,7 +73,7 @@ typedef struct netadr_s netadrtype_t type; netproto_t prot; - unsigned short port; + unsigned short port; //stored as network-endian. unsigned short connum; //which quake connection/socket the address is talking about. 1-based. 0 is unspecified. this is NOT used for address equivelency. unsigned int scopeid; //ipv6 interface id thing. diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index 7b578064d..05c3989ce 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -224,23 +224,22 @@ unsigned int Net_PextMask(unsigned int protover, qboolean fornq) mask |= PEXT2_REPLACEMENTDELTAS; if (/*fornq &&*/ pext_predinfo.ival) mask |= PEXT2_PREDINFO; + + if (pext_vrinputs.ival) + mask |= PEXT2_VRINPUTS; + + if (pext_lerptime.ival) + mask |= PEXT2_LERPTIME; + + mask |= PEXT2_NEWSIZEENCODING; //use if we can } if (pext_infoblobs.ival) mask |= PEXT2_INFOBLOBS; - if (pext_vrinputs.ival) - mask |= PEXT2_VRINPUTS; - - if (pext_lerptime.ival) - mask |= PEXT2_LERPTIME; - if (MAX_CLIENTS != QWMAX_CLIENTS) mask |= PEXT2_MAXPLAYERS; - if (mask & PEXT2_REPLACEMENTDELTAS) - mask |= PEXT2_NEWSIZEENCODING; //use if we can - mask |= PEXT2_STUNAWARE; if (fornq) diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c index 9caee2f52..6e73c4e87 100644 --- a/engine/common/net_ice.c +++ b/engine/common/net_ice.c @@ -68,7 +68,7 @@ typedef struct //attributes #define STUNATTR_MAPPED_ADDRESS 0x0001 #define STUNATTR_USERNAME 0x0006 -#define STUNATTR_MESSAGEINTEGRITIY 0x0008 +#define STUNATTR_MSGINTEGRITIY_SHA1 0x0008 #define STUNATTR_ERROR_CODE 0x0009 //#define STUNATTR_CHANNELNUMBER 0x000c //TURN #define STUNATTR_LIFETIME 0x000d //TURN @@ -81,6 +81,8 @@ typedef struct //#define STUNATTR_EVEN_PORT 0x0018 //TURN #define STUNATTR_REQUESTED_TRANSPORT 0x0019 //TURN #define STUNATTR_DONT_FRAGMENT 0x001a //TURN +#define STUNATTR_MSGINTEGRITIY_SHA2_256 0x001C +#define STUNATTR_PASSWORD_ALGORITHM 0x001D //yay, screw md5 #define STUNATTR_XOR_MAPPED_ADDRESS 0x0020 #define STUNATTR_ICE_PRIORITY 0x0024 //ICE #define STUNATTR_ICE_USE_CANDIDATE 0x0025 //ICE @@ -502,10 +504,13 @@ static qboolean TURN_AddXorAddressAttrib(sizebuf_t *buf, unsigned int attr, neta MSG_WriteByte(buf, ((qbyte*)&to->address)[aofs+i] ^ (buf->data+4)[i]); return true; } +void Con_HexDump(qbyte *packet, size_t len, size_t badoffset); static qboolean TURN_AddAuth(sizebuf_t *buf, struct iceserver_s *srv) { //adds auth info to a stun packet unsigned short len; - char integrity[20]; + char integrity[DIGEST_MAXSIZE]; + hashfunc_t *hash = &hash_sha1; + hashfunc_t *pwdhash = &hash_md5; if (!srv->user || !srv->nonce || !srv->realm) return false; @@ -530,18 +535,38 @@ static qboolean TURN_AddAuth(sizebuf_t *buf, struct iceserver_s *srv) if (len&3) SZ_Write (buf, "\0\0\0\0", 4-(len&3)); + if (pwdhash != &hash_md5) + { + MSG_WriteShort(buf, BigShort(STUNATTR_PASSWORD_ALGORITHM)); + len = strlen(srv->nonce); + MSG_WriteShort(buf, 4); + if (pwdhash == &hash_md5) + MSG_WriteShort(buf, 1); + else if (pwdhash == &hash_sha2_256) + MSG_WriteShort(buf, 2); + else + return false; //not defined... panic. + MSG_WriteShort(buf, 0); //paramlength + //no params. + } + //message integrity is a bit annoying - buf->data[2] = ((buf->cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute - buf->data[3] = ((buf->cursize+4+sizeof(integrity)-20)>>0)&0xff; + buf->data[2] = ((buf->cursize+4+hash->digestsize-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + buf->data[3] = ((buf->cursize+4+hash->digestsize-20)>>0)&0xff; //but the hash is to the start of the attribute's header { //long-term credentials do stuff weird. char *tmpkey = va("%s:%s:%s", srv->user, srv->realm, srv->auth); - CalcHash(&hash_md5, integrity,16, tmpkey, strlen(tmpkey)); + len = CalcHash(pwdhash, integrity,sizeof(integrity), tmpkey, strlen(tmpkey)); } - CalcHMAC(&hash_sha1, integrity, sizeof(integrity), buf->data, buf->cursize, integrity,16); - MSG_WriteShort(buf, BigShort(STUNATTR_MESSAGEINTEGRITIY)); - MSG_WriteShort(buf, BigShort(sizeof(integrity))); //sha1 key length - SZ_Write(buf, integrity, sizeof(integrity)); //integrity data + len = CalcHMAC(hash, integrity, sizeof(integrity), buf->data, buf->cursize, integrity,len); + if (hash == &hash_sha2_256) + MSG_WriteShort(buf, BigShort(STUNATTR_MSGINTEGRITIY_SHA2_256)); + else if (hash == &hash_sha1) + MSG_WriteShort(buf, BigShort(STUNATTR_MSGINTEGRITIY_SHA1)); + else + return false; //not defined! + MSG_WriteShort(buf, BigShort(len)); //integrity length + SZ_Write(buf, integrity, len); //integrity data return true; } @@ -993,7 +1018,7 @@ static qboolean ICE_SendSpam(struct icestate_s *con) data[3] = ((buf.cursize+4+sizeof(integ)-20)>>0)&0xff; //but the hash is to the start of the attribute's header CalcHMAC(&hash_sha1, integ, sizeof(integ), data, buf.cursize, con->rpwd, strlen(con->rpwd)); - MSG_WriteShort(&buf, BigShort(STUNATTR_MESSAGEINTEGRITIY)); //MESSAGE-INTEGRITY + MSG_WriteShort(&buf, BigShort(STUNATTR_MSGINTEGRITIY_SHA1)); //MESSAGE-INTEGRITY MSG_WriteShort(&buf, BigShort(20)); //sha1 key length SZ_Write(&buf, integ, sizeof(integ)); //integrity data @@ -1041,7 +1066,6 @@ static qboolean ICE_SendSpam(struct icestate_s *con) return false; } -extern ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr); #ifdef HAVE_TCP struct turntcp_connection_s { //this sends packets only to the relay, and accepts them only from there too. all packets must be stun packets (for byte-count framing) @@ -1243,7 +1267,7 @@ static void ICE_ToStunServer(struct icestate_s *con, struct iceserver_s *srv) memset(&localadr, 0, sizeof(localadr)); localadr.type = srv->addr.type; localadr.prot = srv->addr.prot; - srv->con = FTENET_Datagram_EstablishConnection(collection, srv->realm, localadr); + srv->con = FTENET_Datagram_EstablishConnection(collection, srv->realm, localadr, NULL); } if (!srv->con) { @@ -2172,7 +2196,7 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch { #ifndef SERVERONLY if (con->proto == ICEP_QWCLIENT) - CL_Transfer(&con->qadr); //okay, the client should be using this ice connection now. + CL_Transfer(&con->qadr); //okay, the client should be using this ice connection now. FIXME: this should only switch them over if they're still trying to use the aerlier broker. #endif #ifdef HAVE_DTLS if (con->mode == ICEM_WEBRTC) @@ -2447,7 +2471,8 @@ static char *ICE_CandidateToSDP(struct icecandidate_s *can, char *value, size_t can->info.port, ICE_GetCandidateType(&can->info) ); - Q_strncatz(value, va(" generation %i", can->info.generation), valuelen); + if (can->info.generation) + Q_strncatz(value, va(" generation %i", can->info.generation), valuelen); //firefox doesn't like this. if (can->info.type != ICE_HOST) { if (net_ice_relayonly.ival) @@ -2725,8 +2750,10 @@ static void ICE_PrintSummary(struct icestate_s *con, qboolean islisten) } static void ICE_Debug(struct icestate_s *con) { + const char *addrclass; struct icecandidate_s *can; char buf[65536]; + int i; ICE_Get(con, "state", buf, sizeof(buf)); Con_Printf("ICE [%s] (%s):\n", con->friendlyname, buf); if (con->brokerless) @@ -2750,17 +2777,33 @@ static void ICE_Debug(struct icestate_s *con) Con_Printf("peer:\n"S_COLOR_YELLOW"%s\n", buf); } + Con_Printf(" servers:\n"); + for (i = 0; i < con->servers; i++) + { + const char *status = "?"; + switch(con->server[i].state) + { + case TURN_UNINITED: status = "uninited"; break; + case TURN_HAVE_NONCE: status = "registering"; break; + case TURN_ALLOCATED: status = "allocated"; break; + case TURN_TERMINATING: status = "terminating"; break; + } + NET_AdrToString(buf,sizeof(buf), &con->server[i].addr); + Con_Printf(" %s:%s %s realm=%s user=%s auth=%s\n", con->server[i].isstun?"stun":"turn", buf, status, con->server[i].realm, con->server[i].user?con->server[i].user:"", con->server[i].auth?"":""); + } + Con_Printf(" local:\n"); for (can = con->lc; can; can = can->next) { ICE_CandidateToSDP(can, buf, sizeof(buf)); if (con->chosenpeer.type!=NA_INVALID && con->chosenpeer.connum == can->info.network) - Con_Printf(S_COLOR_GREEN" %s\n", buf); + Con_Printf(S_COLOR_GREEN " %s"S_COLOR_GRAY" \n", buf); else if (can->dirty) - Con_Printf(S_COLOR_RED" %s\n", buf); + Con_Printf(S_COLOR_RED " %s"S_COLOR_GRAY" \n", buf); else Con_Printf(S_COLOR_YELLOW" %s\n", buf); } + Con_Printf(" remote:\n"); for (can = con->rc; can; can = can->next) { @@ -2768,12 +2811,14 @@ static void ICE_Debug(struct icestate_s *con) if (can->reachable) { if (con->chosenpeer.type!=NA_INVALID && NET_CompareAdr(&can->peer,&con->chosenpeer)) - Con_Printf(S_COLOR_GREEN" %s\n", buf); + Con_Printf(S_COLOR_GREEN " %s"S_COLOR_GRAY" \n", buf); else - Con_Printf(S_COLOR_YELLOW" %s\n", buf); + Con_Printf(S_COLOR_YELLOW" %s"S_COLOR_GRAY" \n", buf); } + else if (NET_ClassifyAddress(&can->peer, &addrclass) < ASCOPE_TURN_REQUIRESCOPE) + Con_Printf(S_COLOR_RED" %s"S_COLOR_GRAY" \n", buf, addrclass); else - Con_Printf(S_COLOR_RED" %s\n", buf); + Con_Printf(S_COLOR_RED" %s"S_COLOR_GRAY" \n", buf); } } static void ICE_Show_f(void) @@ -4157,7 +4202,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) switch(attrval) { case STUNATTR_USERNAME: - case STUNATTR_MESSAGEINTEGRITIY: + case STUNATTR_MSGINTEGRITIY_SHA1: break; default: if (attrval & 0x8000) @@ -4455,7 +4500,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) err = (((qbyte*)attr)[6]*100) + (((qbyte*)attr)[7]%100); } break; - case STUNATTR_MESSAGEINTEGRITIY: + case STUNATTR_MSGINTEGRITIY_SHA1: break; } alen = (alen+3)&~3; @@ -4548,7 +4593,8 @@ qboolean ICE_WasStun(ftenet_connections_t *col) s = &con->server[network]; if (s->stunrnd[0] == stun->transactid[0] && s->stunrnd[1] == stun->transactid[1] && s->stunrnd[2] == stun->transactid[2] && NET_CompareAdr(&net_from, &s->addr)) break; - Con_Printf("Stale transaction id (got %x, expected %x)\n", stun->transactid[0], s->stunrnd[0]); + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"Stale transaction id (got %x, expected %x)\n", stun->transactid[0], s->stunrnd[0]); } } if (!con) @@ -4575,7 +4621,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) lifetime = BigLong(*(int*)(attr+1)); break; // case STUNATTR_SOFTWARE: - case STUNATTR_MESSAGEINTEGRITIY: + case STUNATTR_MSGINTEGRITIY_SHA1: // case STUNATTR_FINGERPRINT: break; default: @@ -4653,13 +4699,13 @@ qboolean ICE_WasStun(ftenet_connections_t *col) s->stunretry = Sys_Milliseconds(); if (net_ice_debug.ival >= 1) - Con_Printf("[%s]: %s: TURN error code %u : %s\n", con->friendlyname, NET_AdrToString(sender, sizeof(sender), &net_from), err, errmsg); + Con_Printf(S_COLOR_GRAY"[%s]: %s: TURN error code %u : %s\n", con->friendlyname, NET_AdrToString(sender, sizeof(sender), &net_from), err, errmsg); } else if (err == 403/*forbidden*/) //something bad... { s->state = TURN_UNINITED, s->stunretry = Sys_Milliseconds() + 60*1000; if (net_ice_debug.ival >= 1) - Con_Printf("[%s]: %s: TURN error code %u : %s\n", con->friendlyname, NET_AdrToString(sender, sizeof(sender), &net_from), err, errmsg); + Con_Printf(CON_ERROR"[%s]: %s: TURN error code %u : %s\n", con->friendlyname, NET_AdrToString(sender, sizeof(sender), &net_from), err, errmsg); } else if (err == 401 && s->state == TURN_UNINITED && s->nonce) //failure when sending auth... give up for a min { //this happens from initial auth. we need to reply with the real auth request now. @@ -4778,7 +4824,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) // Con_Printf("Stun username = \"%s\"\n", username); } break; - case STUNATTR_MESSAGEINTEGRITIY: + case STUNATTR_MSGINTEGRITIY_SHA1: memcpy(integrity, attr+1, sizeof(integrity)); integritypos = (char*)(attr+1); break; @@ -5020,13 +5066,13 @@ qboolean ICE_WasStun(ftenet_connections_t *col) if (lpwd) { //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; + data[2] = ((buf.cursize+4+hash_sha1.digestsize-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + data[3] = ((buf.cursize+4+hash_sha1.digestsize-20)>>0)&0xff; //but the hash is to the start of the attribute's header CalcHMAC(&hash_sha1, integrity, sizeof(integrity), data, buf.cursize, lpwd, strlen(lpwd)); - MSG_WriteShort(&buf, BigShort(STUNATTR_MESSAGEINTEGRITIY)); - MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length - SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data + MSG_WriteShort(&buf, BigShort(STUNATTR_MSGINTEGRITIY_SHA1)); + MSG_WriteShort(&buf, BigShort(hash_sha1.digestsize)); //sha1 key length + SZ_Write(&buf, integrity, hash_sha1.digestsize); //integrity data } #endif @@ -5104,11 +5150,56 @@ int ICE_GetPeerCertificate(netadr_t *to, enum certprops_e prop, char *out, size_ { #ifdef HAVE_DTLS struct icestate_s *con; + int i, c; for (con = icelist; con; con = con->next) { if (NET_CompareAdr(to, &con->qadr)) { - if (con->dtlsstate && con->dtlsfuncs->GetPeerCertificate) + if (prop==QCERT_LOBBYSTATUS) + { + *out = 0; + switch(con->state) + { + case ICE_INACTIVE: + Q_strncpyz(out, "idle", outsize); + break; + case ICE_FAILED: + Q_strncpyz(out, "Failed", outsize); + break; + case ICE_GATHERING: + Q_strncpyz(out, "Gathering", outsize); + break; + case ICE_CONNECTING: + for (i = 0, c = false; i < con->servers; i++) + if (!con->server[i].isstun) + { + if (con->server[i].state == TURN_ALLOCATED) + break; + c = true; + } + if (i == con->servers) + { + if (net_ice_relayonly.ival) + Q_strncpyz(out, "Probing ("CON_ERROR"NO TURN SERVER"CON_DEFAULT")", outsize); //can't work, might still get an allocation though. + else if (c) + Q_strncpyz(out, "Probing ("CON_WARNING"waiting for TURN allocation"CON_DEFAULT")", outsize); //still good for latency. not for privacy though. + else + Q_strncpyz(out, "Probing ("CON_WARNING"no relay configured"CON_DEFAULT")", outsize); //still good for latency. not for privacy though. + } + else + Q_strncpyz(out, "Probing ("S_COLOR_GREEN"with fallback"CON_DEFAULT")", outsize); //we have a relay for a fallback, all is good, hopefully. except we're still at this stage... + break; + case ICE_CONNECTED: //past the ICE stage (but maybe not the dtls+sctp layers, these should be less likely to fail, but dtls versions may become an issue) + //if (con->dtlsstate && notokay) + if (con->sctp && !con->sctp->o.writable) + Q_strncpyz(out, "Establishing", outsize); //will also block for the dtls channel of course. its not as easy check the dtls layer. + else + Q_strncpyz(out, "Established", outsize); + break; + } + return strlen(out); + } + else if (con->dtlsstate && con->dtlsfuncs->GetPeerCertificate) return con->dtlsfuncs->GetPeerCertificate(con->dtlsstate, prop, out, outsize); else if (prop==QCERT_ISENCRYPTED && con->dtlsstate) return 0; @@ -5408,6 +5499,25 @@ static void FTENET_ICE_Refresh(ftenet_ice_connection_t *b, int cl, struct icesta FTENET_ICE_SplurgeCmd(b, ICEMSG_CANDIDATE, cl, buf); } } +static void Buf_ReadString(const char **data, const char *end, char *out, size_t outsize) +{ + const char *in = *data; + char c; + outsize--; //count the null early. + while (in < end) + { + c = *in++; + if (!c) + break; + if (outsize) + { + outsize--; + *out++ = c; + } + } + *out = 0; + *data = in; +} static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon) { json_t *json; @@ -5598,31 +5708,41 @@ handleerror: break; case ICEMSG_NEWPEER: //relay connection established with a new peer //note that the server ought to wait for an offer from the client before replying with any ice state, but it doesn't really matter for our use-case. - if (b->generic.islisten) { -// Con_DPrintf("Client connecting: %s\n", data); - if (cl < 1024 && cl >= b->numclients) - { //looks like a new one... but don't waste memory - Z_ReallocElements((void**)&b->clients, &b->numclients, cl+1, sizeof(b->clients[0])); - } - if (cl >= 0 && cl < b->numclients) + char peer[MAX_QPATH]; + char relay[MAX_QPATH]; + char *s; + Buf_ReadString(&data, b->in+ofs+len, peer, sizeof(peer)); + Buf_ReadString(&data, b->in+ofs+len, relay, sizeof(relay)); + + if (b->generic.islisten) { - FTENET_ICE_Establish(b, (len>3)?data:NULL, cl, &b->clients[cl].ice); - - if (net_ice_debug.ival) - Con_Printf(S_COLOR_GRAY"[%s]: New client spotted...\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?"); + // Con_DPrintf("Client connecting: %s\n", data); + if (cl < 1024 && cl >= b->numclients) + { //looks like a new one... but don't waste memory + Z_ReallocElements((void**)&b->clients, &b->numclients, cl+1, sizeof(b->clients[0])); + } + if (cl >= 0 && cl < b->numclients) + { + FTENET_ICE_Establish(b, *peer?peer:NULL, cl, &b->clients[cl].ice); + for (s = relay; (s=COM_Parse(s)); ) + iceapi.Set(b->clients[cl].ice, "server", com_token); + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: New client spotted...\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?"); + } + else if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: New client spotted, but index is unusable\n", "?"); + } + else + { + //Con_DPrintf("Server found: %s\n", data); + FTENET_ICE_Establish(b, *peer?peer:NULL, cl, &b->ice); + b->serverid = cl; + for (s = relay; (s=COM_Parse(s)); ) + iceapi.Set(b->ice, "server", com_token); + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Meta channel to game server now open\n", b->ice?b->ice->friendlyname:"?"); } - else if (net_ice_debug.ival) - Con_Printf(S_COLOR_GRAY"[%s]: New client spotted, but index is unusable\n", "?"); - } - else - { -// Con_DPrintf("Server found: %s\n", data); - FTENET_ICE_Establish(b, (len>3)?data:NULL, cl, &b->ice); - b->serverid = cl; - - if (net_ice_debug.ival) - Con_Printf(S_COLOR_GRAY"[%s]: Meta channel to game server now open\n", b->ice?b->ice->friendlyname:"?"); } break; case ICEMSG_OFFER: //we received an offer from a client diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index fdf77bd43..deba6a23c 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -1594,8 +1594,8 @@ static const struct urischeme_s urischemes[] = { #ifdef HAVE_PACKET {"udp://", NP_DGRAM, NA_INVALID}, //placeholder for dgram rather than an actual family. - {"udp4//", NP_DGRAM, NA_IP}, - {"udp6//", NP_DGRAM, NA_IPV6}, + {"udp4://", NP_DGRAM, NA_IP}, + {"udp6://", NP_DGRAM, NA_IPV6}, {"ipx://", NP_DGRAM, NA_IPX}, //compat with qtv. we don't have any way to exclude specific protocols though. @@ -1612,40 +1612,40 @@ static const struct urischeme_s urischemes[] = #ifdef TCPCONNECT {"tcp://", NP_STREAM, NA_INVALID}, //placeholder for dgram rather than an actual family. - {"tcp4//", NP_STREAM, NA_IP}, - {"tcp6//", NP_STREAM, NA_IPV6}, + {"tcp4://", NP_STREAM, NA_IP}, + {"tcp6://", NP_STREAM, NA_IPV6}, {"spx://", NP_STREAM, NA_IPX}, - {"ws://", NP_WS, NA_INVALID, true}, + {"ws://", NP_WS, NA_INVALID, URISCHEME_NEEDSRESOURCE}, #ifdef HAVE_SSL - {"wss://", NP_WSS, NA_INVALID, true}, + {"wss://", NP_WSS, NA_INVALID, URISCHEME_NEEDSRESOURCE}, {"tls://", NP_TLS, NA_INVALID}, #endif #elif defined(HAVE_WEBSOCKCL) - {"ws://", NP_WS, NA_WEBSOCKET, true}, - {"wss://", NP_WSS, NA_WEBSOCKET, true}, - {"tcp://", NP_WS, NA_WEBSOCKET, true}, //fake it - {"tls://", NP_WSS, NA_WEBSOCKET, true}, //fake it + {"ws://", NP_WS, NA_WEBSOCKET, URISCHEME_NEEDSRESOURCE}, + {"wss://", NP_WSS, NA_WEBSOCKET, URISCHEME_NEEDSRESOURCE}, + {"tcp://", NP_WS, NA_WEBSOCKET, URISCHEME_NEEDSRESOURCE}, //fake it + {"tls://", NP_WSS, NA_WEBSOCKET, URISCHEME_NEEDSRESOURCE}, //fake it #endif #ifdef HAVE_DTLS {"dtls://", NP_DTLS, NA_INVALID}, #endif #if defined(SUPPORT_ICE) || defined(HAVE_WEBSOCKCL) - {"ice://", NP_RTC_TCP, NA_INVALID, true}, - {"rtc://", NP_RTC_TCP, NA_INVALID, true}, - {"ices://", NP_RTC_TLS, NA_INVALID, true}, - {"rtcs://", NP_RTC_TLS, NA_INVALID, true}, + {"ice://", NP_RTC_TCP, NA_INVALID, URISCHEME_NEEDSRESOURCE}, + {"rtc://", NP_RTC_TCP, NA_INVALID, URISCHEME_NEEDSRESOURCE}, + {"ices://", NP_RTC_TLS, NA_INVALID, URISCHEME_NEEDSRESOURCE}, + {"rtcs://", NP_RTC_TLS, NA_INVALID, URISCHEME_NEEDSRESOURCE}, #endif #ifdef IRCCONNECT - {"irc://", NP_IRC, NA_INVALID, true}, //should have been handled explicitly, if supported. + {"irc://", NP_IRC, NA_INVALID, URISCHEME_NEEDSRESOURCE}, //should have been handled explicitly, if supported. #endif #ifdef UNIXSOCKETS - {"udg://", NP_DGRAM, NA_UNIX, true}, + {"udg://", NP_DGRAM, NA_UNIX, URISCHEME_NEEDSRESOURCE}, #ifdef TCPCONNECT - {"unix://", NP_STREAM, NA_UNIX, true}, + {"unix://", NP_STREAM, NA_UNIX, URISCHEME_NEEDSRESOURCE}, #endif #endif }; @@ -6033,10 +6033,23 @@ qboolean FTENET_TCP_HTTPResponse(ftenet_tcp_stream_t *st, httparg_t arg[WCATTR_C return true; } +static int FTENET_TCP_WebRTCIncludeRelay(char *buffer, size_t bufsize, ftenet_tcp_stream_t *list, ftenet_tcp_stream_t *receipient) +{ + int len; + *buffer = 0; +#ifdef SV_MASTER + SVM_SelectRelay(&receipient->remoteaddr, receipient->webrtc.resource, buffer,bufsize); +#endif + len = strlen(buffer); + buffer[len++] = 0; //always add a null to end the list. + return len; +} + void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_stream_t *client, ftenet_tcp_stream_t *server) { qbyte buffer[256]; int trynext = 0; + int len = 0; ftenet_tcp_stream_t *o; if (client->webrtc.clientnum < 0) client->webrtc.clientnum = 0; @@ -6054,23 +6067,37 @@ void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_strea if (server) { //and tell them both, if the server is actually up - int o = client->remoteaddr.prot; - buffer[0] = ICEMSG_NEWPEER; - buffer[1] = (client->webrtc.clientnum>>0)&0xff; - buffer[2] = (client->webrtc.clientnum>>8)&0xff; -// buffer[3] = (client->webrtc.clientnum>>16)&0xff; -// buffer[4] = (client->webrtc.clientnum>>24)&0xff; - client->remoteaddr.prot = 0; - NET_BaseAdrToString(buffer+3, sizeof(buffer)-3, &client->remoteaddr); //let the server know who's trying to connect to them. for ip bans. - client->remoteaddr.prot = o; - FTENET_TCP_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, 3+strlen(buffer+3)); + buffer[len++] = ICEMSG_NEWPEER; + buffer[len++] = (client->webrtc.clientnum>>0)&0xff; + buffer[len++] = (client->webrtc.clientnum>>8)&0xff; +// buffer[len++] = (client->webrtc.clientnum>>16)&0xff; +// buffer[len++] = (client->webrtc.clientnum>>24)&0xff; - buffer[0] = ICEMSG_NEWPEER; - buffer[1] = 0xff; - buffer[2] = 0xff; -// buffer[3] = 0xff; -// buffer[4] = 0xff; - FTENET_TCP_WebSocket_Splurge(client, WS_PACKETTYPE_BINARYFRAME, buffer, 3); + //write the client's address, kinda + if (client->remoteaddr.type == NA_IP) //anonymise it. hopefully still enough of an address to ban. + Q_snprintfz(buffer+len, sizeof(buffer)-len, "%i.%i", client->remoteaddr.address.ip[0], client->remoteaddr.address.ip[1]); + else if (client->remoteaddr.type == NA_IPV6) //anonymise it. we don't really know how big an allocation their router got... so include the first 4 bytes and hash the rest to compensate somewhat. most of it'll probably random though. this is messy. the server will be identifying connections more by index. + Q_snprintfz(buffer+len, sizeof(buffer)-len, "%04x:%04x-%04x", client->remoteaddr.address.ip6[0]|client->remoteaddr.address.ip6[1], client->remoteaddr.address.ip6[2]|client->remoteaddr.address.ip6[3], 0xffffu&CalcHashInt(&hash_sha1, client->remoteaddr.address.ip6+4, sizeof(client->remoteaddr.address.ip6)-4)); + else + { //generically shove the client's address into the broker->server packet + int o = client->remoteaddr.prot; + client->remoteaddr.prot = 0; + NET_BaseAdrToString(buffer+len, sizeof(buffer)-len, &client->remoteaddr); //let the server know who's trying to connect to them. for ip bans. + client->remoteaddr.prot = o; + } + len += strlen(buffer+len)+1; + len += FTENET_TCP_WebRTCIncludeRelay(buffer+len,sizeof(buffer)-len, list, server); + FTENET_TCP_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, len); + + len = 0; + buffer[len++] = ICEMSG_NEWPEER; + buffer[len++] = 0xff; + buffer[len++] = 0xff; +// buffer[len++] = 0xff; +// buffer[len++] = 0xff; + buffer[len++] = 0; //no remote peer name info... + len += FTENET_TCP_WebRTCIncludeRelay(buffer+len,sizeof(buffer)-len, list, server); + FTENET_TCP_WebSocket_Splurge(client, WS_PACKETTYPE_BINARYFRAME, buffer, len); } } @@ -7272,7 +7299,7 @@ restart: //gotos are evil. I am evil. live with it. } } if (!o) - Con_DPrintf("Unable to relay\n"); + Con_DPrintf("Unable to relay p%i to %s\n", st->inbuffer[payoffs+0], (st->clienttype == TCPC_WEBRTC_CLIENT)?"server":"client"); } net_message.cursize = 0; } @@ -8838,7 +8865,50 @@ static void FTENET_WebRTC_Callback(void *ctxp, int ctxi, int/*enum icemsgtype_s* //Con_Printf("To Broker: %i %i %s\n", evtype, ctxi, data); emscriptenfte_ws_send(wsc->brokersock, net_message_buffer, o-net_message_buffer); } -static int FTENET_WebRTC_Create(qboolean initiator, ftenet_websocket_connection_t *wsc, int clid) +static void FTENET_WebRTC_AddICEServer(char *config, size_t sizeofconfig, qboolean *first, const char *uri) +{ + //we don't do the ?foo stuff properly (RFCs say only ?transport= and only for stun) + char *s = strchr(uri, '?'), *next; + const char *transport = NULL; + const char *user = NULL; + const char *auth = NULL; + char tmp[256]; + for (;s;s=next) + { + *s++ = 0; + next = strchr(s, '?'); + if (next) + *next = 0; + + if (!strncmp(s, "transport=", 10)) + transport = s+10; + else if (!strncmp(s, "user=", 5)) + user = s+5; + else if (!strncmp(s, "auth=", 5)) + auth = s+5; + else if (!strncmp(s, "fam=", 4)) + ; + } + + if (!strncmp(uri, "turn:", 5) || !strncmp(uri, "turns:", 6)) + if (!user || !auth) + return; + + if (*first) + *first = false; + else + Q_strncatz(config, ",", sizeofconfig); + if (transport) + Q_strncatz(config, va("\n{\"urls\":[\"%s?transport=%s\"]", COM_QuotedString(uri, tmp,sizeof(tmp), true), transport), sizeofconfig); + else + Q_strncatz(config, va("\n{\"urls\":[\"%s\"]", COM_QuotedString(uri, tmp,sizeof(tmp), true)), sizeofconfig); + if (user) + Q_strncatz(config, va(",\"username\":\"%s\"", COM_QuotedString(user, tmp,sizeof(tmp), true)), sizeofconfig); + if (auth) + Q_strncatz(config, va(",\"credential\":\"%s\"", COM_QuotedString(auth, tmp,sizeof(tmp), true)), sizeofconfig); + Q_strncatz(config, "}", sizeofconfig); +} +static int FTENET_WebRTC_Create(qboolean initiator, ftenet_websocket_connection_t *wsc, int clid, const char *relays) { int fd; char config[4096], tmp[256]; @@ -8889,48 +8959,14 @@ static int FTENET_WebRTC_Create(qboolean initiator, ftenet_websocket_connection_ Q_strncatz(config, va("{\"urls\":[\"stun:%s\"]}", COM_QuotedString(com_token, tmp,sizeof(tmp), true)), sizeof(config)); } + //add any user-specified ice servers for(servers = net_ice_servers.string; (servers=COM_Parse(servers)); ) - { - //we don't do the ?foo stuff properly (RFCs say only ?transport= and only for stun) - char *s = strchr(com_token, '?'), *next; - const char *transport = NULL; - const char *user = NULL; - const char *auth = NULL; - for (;s;s=next) - { - *s++ = 0; - next = strchr(s, '?'); - if (next) - *next = 0; + FTENET_WebRTC_AddICEServer(config, sizeof(config), &first, com_token); - if (!strncmp(s, "transport=", 10)) - transport = s+10; - else if (!strncmp(s, "user=", 5)) - user = s+5; - else if (!strncmp(s, "auth=", 5)) - auth = s+5; - else if (!strncmp(s, "fam=", 4)) - ; - } + //add any auto-config ones. + for(servers = relays; (servers=COM_Parse(servers)); ) + FTENET_WebRTC_AddICEServer(config, sizeof(config), &first, com_token); - if (!strncmp(com_token, "turn:", 5) || !strncmp(com_token, "turns:", 6)) - if (!user || !auth) - continue; - - if (first) - first = false; - else - Q_strncatz(config, ",", sizeof(config)); - if (transport) - Q_strncatz(config, va("\n{\"urls\":[\"%s?transport=%s\"]", COM_QuotedString(com_token, tmp,sizeof(tmp), true), transport), sizeof(config)); - else - Q_strncatz(config, va("\n{\"urls\":[\"%s\"]", COM_QuotedString(com_token, tmp,sizeof(tmp), true)), sizeof(config)); - if (user) - Q_strncatz(config, va(",\"username\":\"%s\"", COM_QuotedString(user, tmp,sizeof(tmp), true)), sizeof(config)); - if (auth) - Q_strncatz(config, va(",\"credential\":\"%s\"", COM_QuotedString(auth, tmp,sizeof(tmp), true)), sizeof(config)); - Q_strncatz(config, "}", sizeof(config)); - } Q_strncatz(config, va("]" // ",\"bundlePolicy\":\"max-bundle\"" ",\"iceTransportPolicy\":\"%s\"" @@ -8946,6 +8982,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) { ftenet_websocket_connection_t *wsc = (void*)gcon; size_t i; + char id[256]; if (wsc->heartbeat < realtime) FTENET_WebRTC_Heartbeat(wsc); @@ -8983,7 +9020,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) { int cmd; short cl; - const char *s; + const char *s, *relays; char *p; MSG_BeginReading(&net_message, msg_nullnetprim); @@ -9032,6 +9069,9 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) Con_Printf("Listening on %s\n", wsc->remoteadr.address.websocketurl); break; case ICEMSG_NEWPEER: //connection established with a new peer + /*peer*/ MSG_ReadString(); + relays = MSG_ReadString(); + if (wsc->generic.islisten) { if (cl < 1024 && cl >= wsc->numclients) @@ -9047,21 +9087,20 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) } if (cl < wsc->numclients) { - char id[256]; Q_snprintfz(id, sizeof(id), "/%i_%x", cl+1, rand()); if (wsc->clients[cl].datasock != INVALID_SOCKET) emscriptenfte_ws_close(wsc->clients[cl].datasock); memcpy(&wsc->clients[cl].remoteadr, &wsc->remoteadr, sizeof(netadr_t)); Q_strncatz(wsc->clients[cl].remoteadr.address.websocketurl, id, sizeof(wsc->clients[cl].remoteadr.address.websocketurl)); wsc->clients[cl].remoteadr.port = htons(cl+1); - wsc->clients[cl].datasock = FTENET_WebRTC_Create(false, wsc, cl); + wsc->clients[cl].datasock = FTENET_WebRTC_Create(false, wsc, cl, relays); } } else { if (wsc->datasock != INVALID_SOCKET) emscriptenfte_ws_close(wsc->datasock); - wsc->datasock = FTENET_WebRTC_Create(true, wsc, cl); + wsc->datasock = FTENET_WebRTC_Create(true, wsc, cl, relays); } break; case ICEMSG_OFFER: //we received an offer from a client @@ -9636,11 +9675,11 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, cons case NP_WSS: case NP_TLS: case NP_STREAM: - if (!adrstring) + if (!adrstring || !*adrstring) adrstring = NET_AdrToString(temp, sizeof(temp), adr); //urgh if (!FTENET_AddToCollection_Ptr(collection, routename, adrstring, adr, peerinfo)) return false; - Con_Printf("Establishing connection to %s\n", temp); + Con_Printf("Establishing connection to \"%s\"\n", adrstring); break; #if defined(SUPPORT_ICE) || defined(FTE_TARGET_WEB) case NP_RTC_TCP: diff --git a/engine/common/netinc.h b/engine/common/netinc.h index 1199240b0..8b606d954 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -443,6 +443,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col); 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_generic_connection_t *FTENET_ICE_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr, const struct dtlspeercred_s *peerinfo); +extern ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(ftenet_connections_t *col, const char *address, netadr_t adr, const struct dtlspeercred_s *peerinfo); enum icemsgtype_s { //shared by rtcpeers+broker ICEMSG_PEERLOST=0, //other side dropped connection diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index d6719439e..cc6a1b264 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -924,19 +924,19 @@ void QCBUILTIN PF_json_find_object_child(pubprogfuncs_t *prinst, struct globalva #ifdef FTE_TARGET_WEB #include -#endif +//FIXME: make sure the module is signed/'local'/trusted void QCBUILTIN PF_js_run_script(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { -#ifdef FTE_TARGET_WEB const char *jscript = PR_GetStringOfs(prinst, OFS_PARM0); const char *ret; ret = emscripten_run_script_string(jscript); if (ret) G_INT(OFS_RETURN) = PR_TempString(prinst, ret); else -#endif G_INT(OFS_RETURN) = 0; } +#endif + //////////////////////////////////////////////////// //model functions @@ -2002,24 +2002,31 @@ void QCBUILTIN PF_cvar_setf (pubprogfuncs_t *prinst, struct globalvars_s *pr_glo //float(string name, string value) registercvar void QCBUILTIN PF_registercvar (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { - const char *name, *value; - int flags = (prinst->callargc>2)?G_FLOAT(OFS_PARM2):0; - value = PR_GetStringOfs(prinst, OFS_PARM0); + const char *name = PR_GetStringOfs(prinst, OFS_PARM0); + const char *value = (prinst->callargc>2)?PR_GetStringOfs(prinst, OFS_PARM1):""; + int dpflags = (prinst->callargc>2)?G_FLOAT(OFS_PARM2):0; + int realflags = 0; + name = PR_GetStringOfs(prinst, OFS_PARM0); - if (Cvar_FindVar(value)) + if (dpflags) + { //this is a DP extension, so uses DP's internal cvar flags. + //which is stupid when cvar_type reports a different set of flags. + //if (dpflags & (1<<0)) dpflags &= ~(1<<0), blocked from realflags |= avialable only to ; + if (dpflags & (1<<4)) dpflags &= ~(1<<4), realflags |= CVAR_CHEAT; + if (dpflags & (1<<5)) dpflags &= ~(1<<5), realflags |= CVAR_ARCHIVE; + if (dpflags & (1<<8)) dpflags &= ~(1<<8), realflags |= CVAR_SERVERINFO; + if (dpflags & (1<<9)) dpflags &= ~(1<<9), realflags |= CVAR_USERINFO; + if (dpflags & (1<<11)) dpflags &= ~(1<<11), realflags |= CVAR_NOUNSAFEEXPAND; + if (dpflags) + Con_Printf(CON_WARNING"WARNING: Unknown flags passed to registercvar(\"%s\", \"%s\", %x)\n", name, value, dpflags); + } + + if (Cvar_FindVar(name)) G_FLOAT(OFS_RETURN) = 0; else { - name = value; - if (prinst->callargc > 1) - value = PR_GetStringOfs(prinst, OFS_PARM1); - else - value = ""; - - flags &= CVAR_ARCHIVE; - // archive? - if (Cvar_Get(name, value, CVAR_USERCREATED|flags, "QC created vars")) + if (Cvar_Get(name, value, CVAR_USERCREATED|realflags, "QC created vars")) G_FLOAT(OFS_RETURN) = 1; else G_FLOAT(OFS_RETURN) = 0; diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index 72072c815..849f0d147 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -225,6 +225,7 @@ enum CVAR_TYPEFLAG_ENGINE =1u<<3, //cvar was created by the engine itself (not user/mod created) CVAR_TYPEFLAG_HASDESCRIPTION=1u<<4, //cvar_description will return something (hopefully) useful CVAR_TYPEFLAG_READONLY =1u<<5, //cvar may not be changed by qc. + //any extras added here should be shared with DP. }; void QCBUILTIN PF_cvar_type (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_uri_escape (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); @@ -584,7 +585,11 @@ void QCBUILTIN PF_json_find_object_child (pubprogfuncs_t *prinst, struct globalv void QCBUILTIN PF_json_get_length (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_json_get_child_at_index (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_json_get_name (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +#ifdef FTE_TARGET_WEB void QCBUILTIN PF_js_run_script (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +#else +#define PF_js_run_script PF_Ignore +#endif void QCBUILTIN PF_base64encode(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_base64decode(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); diff --git a/engine/common/protocol.h b/engine/common/protocol.h index d3fee7401..97fd0fc69 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -93,6 +93,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define PEXT2_DEPRECATEDORNEW (PEXT2_INFOBLOBS|PEXT2_VRINPUTS|PEXT2_LERPTIME) //extensions that are outdated #define PEXT2_MVDSUPPORT (PEXT2_CLIENTSUPPORT&~PEXT2_DEPRECATED&~PEXT2_STUNAWARE) //pext2 extensions to use when recording mvds. +#define PEXT2_LONGINDEXES 0 //boosts the maximum player+stat index. + //EzQuake/Mvdsv extensions. (use ezquake name, to avoid confusion about .mvd format and its protocol differences) #define EZPEXT1_FLOATENTCOORDS 0x00000001 //quirky - doesn't apply to broadcasts, just players+ents. this gives more precision, but will bug out if you try using it to increase map bounds in ways that may not be immediately apparent. iiuc this was added instead of fixing some inconsistent rounding... #define EZPEXT1_SETANGLEREASON 0x00000002 //specifies the reason for an svc_setangles call. the mvdsv implementation will fuck over any mods that writebyte them. we'd need to modify our preparse stuff to work around the issue. @@ -1265,7 +1267,7 @@ typedef struct entity_state_s qbyte glowcolour; qbyte scale; //4.4 precision - char fatness; + char fatness; //1/16th qbyte hexen2flags; qbyte abslight; @@ -1902,7 +1904,7 @@ typedef struct q1usercmd_s #define RENDER_VIEWMODEL 4 #define RENDER_EXTERIORMODEL 8 #define RENDER_LOWPRECISION 16 // send as low precision coordinates to save bandwidth -#define RENDER_COLORMAPPED 32 +#define RENDER_COLORMAPPED 32 //networked colormap field is a direct (top<<4)|bottom value rather than a player slot (the |1024 thing d does) //#define RENDER_WORLDOBJECT 64 #define RENDER_COMPLEXANIMATION 128 diff --git a/engine/common/translate.c b/engine/common/translate.c index b3e86f841..c8cc495b8 100644 --- a/engine/common/translate.c +++ b/engine/common/translate.c @@ -10,6 +10,8 @@ //translate is english->lang //untranslate is lang->english for console commands. +static void FilterPurge(void); +static void FilterInit(const char *file); int com_language; char sys_language[64] = ""; @@ -23,8 +25,14 @@ static void QDECL TL_LanguageChanged(struct cvar_s *var, char *oldvalue) cvar_t language = CVARAFCD("lang", sys_language, "prvm_language", CVAR_USERINFO|CVAR_NORESET/*otherwise gamedir switches will be annoying*/, TL_LanguageChanged, "This cvar contains the language_dialect code of your language, used to find localisation strings."); +static void Filter_Reload_f(void) +{ + +// FilterInit( +} void TranslateInit(void) { + Cmd_AddCommand("com_reloadfilter", Filter_Reload_f); Cvar_Register(&language, "Internationalisation"); } @@ -44,6 +52,7 @@ void TL_Shutdown(void) PO_Close(languages[j].po_qex); languages[j].po_qex = NULL; } + FilterPurge(); } static int TL_LoadLanguage(char *lang) @@ -681,3 +690,140 @@ void TL_Reformat(int language, char *out, size_t outsize, size_t numargs, const } *out = 0; } + +#include +static qbyte *filter[256]; //one list per lead char, simple optimisation instead of some big decision tree. +static qbyte *filtermem; +static int FilterCompareWords(const void *v1, const void *v2) +{ + const char *s1 = *(const char*const*)v1; + const char *s2 = *(const char*const*)v2; + return strcmp(s2,s1); +} +static void FilterPurge(void) +{ + memset(filter, 0, sizeof(filter)); + free(filtermem); + filtermem = NULL; +} +static void FilterInit(const char *file) +{ + qbyte *tempmem = malloc(strlen(file)+1); + qbyte *tempmemstart = tempmem; + const char **words; + size_t count = 1, i, l; + size_t bytes; + const char *c; + + FilterPurge(); + + for (c = file; *c; c++) + if (*c == '\n') + count++; + + words = malloc(sizeof(qbyte*)*count); + count = 0; + for (c = file; *c; ) + { + while (*c == '\n') + c++; //don't add 0-byte strings... + words[count] = tempmem; + for (; *c; c++) + { + if (*c == ' ') + continue; //block even if they omit the spaces. + if (*c == '\n') + break; + *tempmem++ = tolower(*c); + } + *tempmem++ = 0; + count++; + } + qsort(words, count, sizeof(words[0]), FilterCompareWords); //sort by lead byte... and longest first... + i = 0; + for (i = 0, bytes = 0; i < count; i++) + bytes += strlen(words[i]); + bytes += countof(filter); + filtermem = malloc(bytes); + + for (l = countof(filter), i = 0; l-- > 0; ) + { + if (i < count && words[i][0] == l) + { + filter[l] = filtermem; + while (i < count && *words[i] == l) + { //second copy... urgh. can forget the first char and replace with a length. + *filtermem++ = strlen(words[i]+1); + memcpy(filtermem, words[i]+1, filtermem[-1]); //just the text, no null needed. tighly packed. + filtermem += filtermem[-1]; + i++; + } + *filtermem++ = 0; + } + else + filter[l] = NULL; + } + free(tempmemstart); + free(words); +} +#define whiteish(c) (c == ',' || c == '.' || c == ' ' || c == '\t' || c == '\r' || c == '\n') +char *FilterObsceneString(const qbyte *in, char *outbuf, size_t bufsize) +{ //input must be utf-8... if there's any ^ crap in there then strip it first. no bypassing filters with colour codes. + char *ret = outbuf; + if (strlen(in) >= bufsize) + Sys_Error("output buffer too small!"); +restart: + while (*in) + { + qbyte c = tolower(*in); + if (filter[c]) + { + qbyte *m = filter[c]; + while (*m) + { //for each word starting with this letter... + const qbyte *test = in+1; + qbyte len = *m; + const qbyte *match = m+1; + m += 1+len; + while (*test) + { //don't let 'foo bar' through when 'foobar' is a bad word. + if (whiteish(*test)) + { + test++; + continue; + } + + if (tolower(*test) == *match) + { + test++, match++; + if (--len == 0) + { //a match. + if (*test && !whiteish(*test)) + break; //assassinate! + while (test > in) + { //censor it. + *outbuf = "#*@$"[(outbuf-ret)&3]; + outbuf++; + in++; + } + goto restart; //double breaks suck + } + continue; + } + break; + } + } + } + while (*in) + { + if (whiteish(*in)) + { + *outbuf++ = *in++; + break; + } + *outbuf++ = *in++; + } + } + *outbuf++ = 0; //make sure its null terminated. + return ret; +} diff --git a/engine/gl/gl_font.c b/engine/gl/gl_font.c index 167480a17..142ce407e 100644 --- a/engine/gl/gl_font.c +++ b/engine/gl/gl_font.c @@ -2349,6 +2349,10 @@ struct font_s *Font_LoadFont(const char *fontfilename, float vheight, float scal f = Z_Malloc(sizeof(*f)); f->outline = outline; f->scale = scale; + if (height < 1) //doesn't make sense. especially negatives... + height = 1; + if (height > 128) + height = 128; //limit possible damage... we use alloca a bit so don't let the stack get abused too much. f->charheight = height; f->truecharheight = height; f->flags = flags; diff --git a/engine/gl/gl_ngraph.c b/engine/gl/gl_ngraph.c index c58ea824e..80d74c8be 100644 --- a/engine/gl/gl_ngraph.c +++ b/engine/gl/gl_ngraph.c @@ -163,7 +163,7 @@ void R_NetGraph (void) COM_ParseFunString(CON_WHITEMASK, va(" in: %.1f %.0fb\n", pi, bi), line, sizeof(line), false); Draw_ExpandedString(font_console, x, y, line); y += Font_CharVHeight(font_console); - COM_ParseFunString(CON_WHITEMASK, va(" out: %.1f %.0fb\n", po, bo), line, sizeof(line), false); + COM_ParseFunString(CON_WHITEMASK, va(" out: %.1f %.0fb mtu:%u\n", po, bo, cls.netchan.mtu_cur), line, sizeof(line), false); Draw_ExpandedString(font_console, x, y, line); y += Font_CharVHeight(font_console); } diff --git a/engine/gl/gl_rlight.c b/engine/gl/gl_rlight.c index bd9513879..39f64cd01 100644 --- a/engine/gl/gl_rlight.c +++ b/engine/gl/gl_rlight.c @@ -645,7 +645,7 @@ void R_GenDlightBatches(batch_t *batches[]) "deferredlight\n" "surfaceparm nodlight\n" "{\n" - "program lpp_light\n" + "program lpp_light#USE_ARB_SHADOW\n" "blendfunc gl_one gl_one\n" "nodepthtest\n" "map $gbuffer0\n" //depth diff --git a/engine/gl/gl_rmain.c b/engine/gl/gl_rmain.c index 7da3a9f17..ec1289c54 100644 --- a/engine/gl/gl_rmain.c +++ b/engine/gl/gl_rmain.c @@ -72,7 +72,6 @@ extern cvar_t r_tessellation; extern cvar_t gl_ati_truform_type; extern cvar_t r_tessellation_level; -extern cvar_t gl_blendsprites; extern cvar_t r_portaldrawplanes; extern cvar_t r_portalonly; diff --git a/engine/gl/gl_shadow.c b/engine/gl/gl_shadow.c index 8d6d3f10e..a101c91fe 100644 --- a/engine/gl/gl_shadow.c +++ b/engine/gl/gl_shadow.c @@ -3588,7 +3588,7 @@ static qboolean Sh_DrawStencilLight(dlight_t *dl, vec3_t colour, vec3_t axis[3], return true; } #else -#define Sh_DrawStencilLight Sh_DrawShadowlessLight +#define Sh_DrawStencilLight(dl,rgb,axis,vvis) Sh_DrawShadowlessLight(dl,rgb,axis,vvis,LSHADER_STANDARD) #endif qboolean Sh_CullLight(dlight_t *dl, qbyte *vvis) diff --git a/engine/gl/gl_vidcommon.c b/engine/gl/gl_vidcommon.c index 0b940e2be..36e39332b 100644 --- a/engine/gl/gl_vidcommon.c +++ b/engine/gl/gl_vidcommon.c @@ -281,7 +281,6 @@ void (APIENTRY *qglPatchParameteriARB)(GLenum pname, GLint value); //core in gl4 FTEPFNGLACTIVESTENCILFACEEXTPROC qglActiveStencilFaceEXT; -#define GLchar char #if defined(_DEBUG) && !defined(DEBUG) #define DEBUG #endif diff --git a/engine/gl/gl_vidnt.c b/engine/gl/gl_vidnt.c index 32627ab0e..d806ed017 100644 --- a/engine/gl/gl_vidnt.c +++ b/engine/gl/gl_vidnt.c @@ -2401,7 +2401,7 @@ static BOOL CheckForcePixelFormat(rendererstate_t *info) } } // iAttribute[iAttributes++] = WGL_ALPHA_BITS_ARB; iAttribute[iAttributes++] = 2; - iAttribute[iAttributes++] = WGL_DEPTH_BITS_ARB; iAttribute[iAttributes++] = info->depthbits?info->depthbits:16; + iAttribute[iAttributes++] = WGL_DEPTH_BITS_ARB; iAttribute[iAttributes++] = info->depthbits?info->depthbits:24; iAttribute[iAttributes++] = WGL_STENCIL_BITS_ARB; iAttribute[iAttributes++] = 8; iAttribute[iAttributes++] = WGL_DOUBLE_BUFFER_ARB; iAttribute[iAttributes++] = GL_TRUE; iAttribute[iAttributes++] = WGL_STEREO_ARB; iAttribute[iAttributes++] = info->stereo; diff --git a/engine/gl/gl_vidsdl.c b/engine/gl/gl_vidsdl.c index 03bd58420..417083e39 100644 --- a/engine/gl/gl_vidsdl.c +++ b/engine/gl/gl_vidsdl.c @@ -23,6 +23,7 @@ #else SDL_Surface *sdlsurf; #endif +void INS_SetOSK(int osk); #include "vr.h" @@ -659,7 +660,7 @@ void GLVID_DeInit (void) vid.activeapp = false; IN_DeactivateMouse(); - + INS_SetOSK(false); #if SDL_VERSION_ATLEAST(2,0,0) SDL_SetWindowGammaRamp(sdlwindow, NULL, NULL, NULL); diff --git a/engine/gl/glquake.h b/engine/gl/glquake.h index 6c9eaac7e..463927be9 100644 --- a/engine/gl/glquake.h +++ b/engine/gl/glquake.h @@ -125,6 +125,10 @@ void ModBrush_LoadGLStuff(void *ctx, void *data, size_t a, size_t b); //data === void GL_InitFogTexture(void); +#ifndef GL_VERSION_2_0 +#define GLchar char +#endif + // Function prototypes for the Texture Object Extension routines typedef GLboolean (APIENTRY *ARETEXRESFUNCPTR)(GLsizei, const GLuint *, const GLboolean *); diff --git a/engine/gl/glsupp.h b/engine/gl/glsupp.h index 9296169c0..7187c5999 100644 --- a/engine/gl/glsupp.h +++ b/engine/gl/glsupp.h @@ -571,7 +571,7 @@ typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); #define GL_SAMPLER_2D_RECT_ARB 0x8B63 #define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 // dont know if these two should go somewhere better: -#if 1//def __APPLE__ +#ifdef __APPLE__ typedef void *GLhandleARB; //Royally Fucked. #else typedef unsigned int GLhandleARB; diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 44da0f89e..504f7bfaf 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -2637,6 +2637,7 @@ void PR_LocalInfoChanged(char *name, char *oldivalue, char *newvalue) } void PR_PreShutdown(void) { + sv.mapchangelocked = true; //don't let the mod fuck over stuff like `disconnect`. its meant to be shutting down, not switching maps. if (svprogfuncs && gfuncs.SV_Shutdown && sv.state) { func_t f = gfuncs.SV_Shutdown; @@ -7431,7 +7432,7 @@ static void QCBUILTIN PF_checkbuiltin (pubprogfuncs_t *prinst, struct globalvars { //qc defines the function at least. nothing weird there... if (builtinno > 0 && builtinno < prinst->parms->numglobalbuiltins) { - if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme) + if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme || prinst->parms->globalbuiltins[builtinno] == PF_Ignore) G_FLOAT(OFS_RETURN) = false; //the builtin with that number isn't defined. else { @@ -11243,7 +11244,7 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"cos", PF_Fixme, 0, 0, 0, 39, "float(float)"}, {"sqrt", PF_Fixme, 0, 0, 0, 40, "float(float)"}, {"randomvector", PF_Fixme, 0, 0, 0, 41, "vector()"}, - {"registercvar", PF_Fixme, 0, 0, 0, 42, D("float(string name, string value, float flags)", "Creates the cvar if it didn't already exist. This presents issues for setting those cvars via startup configs of course, and autocvars are easier but I suppose they don't get any flags (which are ignored anyway, of course).")}, + {"registercvar", PF_Fixme, 0, 0, 0, 42, D("float(string name, string value, optional float flags)", "Creates the cvar if it didn't already exist. This presents issues for setting those cvars via startup configs of course, and autocvars are easier but I suppose they don't get any flags (which are ignored anyway, of course).")}, {"min", PF_Fixme, 0, 0, 0, 43, "float(float,...)"}, {"max", PF_Fixme, 0, 0, 0, 44, "float(float,...)"}, {"bound", PF_Fixme, 0, 0, 0, 45, "float(float min,float value,float max)"}, @@ -11572,7 +11573,7 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"randomvec", PF_randomvector, 0, 0, 0, 91, D("vector()", "Returns a vector with random values. Each axis is independantly a value between -1 and 1 inclusive.")}, {"getlight", PF_sv_getlight, 0, 0, 0, 92, D("DEP_SSQC(\"Broken on dedicated servers, ignores rtlights/etc\") vector(vector org)", "Computes the RGB lighting at the specified position.")},// (DP_QC_GETLIGHT), - {"registercvar", PF_registercvar, 0, 0, 0, 93, D("float(string cvarname, string defaultvalue)", "Creates a new cvar on the fly. If it does not already exist, it will be given the specified value. If it does exist, this is a no-op.\nThis builtin has the limitation that it does not apply to configs or commandlines. Such configs will need to use the set or seta command causing this builtin to be a noop.\nIn engines that support it, you will generally find the autocvar feature easier and more efficient to use.")}, + {"registercvar", PF_registercvar, 0, 0, 0, 93, D("float(string cvarname, string defaultvalue, optional float flags)", "Creates a new cvar on the fly. If it does not already exist, it will be given the specified value. If it does exist, this is a no-op.\nThis builtin has the limitation that it does not apply to configs or commandlines. Such configs will need to use the set or seta command causing this builtin to be a noop.\nIn engines that support it, you will generally find the autocvar feature easier and more efficient to use.")}, {"min", PF_min, 0, 0, 0, 94, D("float(float a, float b, ...)", "Returns the lowest value of its arguments.")},// (DP_QC_MINMAXBOUND) {"max", PF_max, 0, 0, 0, 95, D("float(float a, float b, ...)", "Returns the highest value of its arguments.")},// (DP_QC_MINMAXBOUND) {"bound", PF_bound, 0, 0, 0, 96, D("float(float minimum, float val, float maximum)", "Returns val, unless minimum is higher, or maximum is less.")},// (DP_QC_MINMAXBOUND) @@ -12265,8 +12266,8 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs // {"particlethemefree",PF_Fixme, 0, 0, 0, 526, D("void()","Resets the particle theme slot to defaults, and marks it as uninitialised (so themesave might reallocate it)")}, // {"particle", PF_Fixme, 0, 0, 0, 527, D("float(vector org, vector vel, optional float theme)","Spawns a particle at the specified position+speed. If theme is specified the other properties come from a theme slot, otherwise they're read from globals.")}, // {"delayedparticle", PF_Fixme, 0, 0, 0, 528, D("float(vector org, vector vel, float delay, float collisiondelay, optional float theme)","Basically just extra args for 'particle'.")}, - {"loadfromdata", PF_loadfromdata, 0, 0, 0, 529, D("void(string s)", "Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead.")}, - {"loadfromfile", PF_loadfromfile, 0, 0, 0, 530, D("void(string s)", "Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead.")}, + {"loadfromdata", PF_loadfromdata, 0, 0, 0, 529, D("void(string s)", "Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. No spawn functions will be called.")}, + {"loadfromfile", PF_loadfromfile, 0, 0, 0, 530, D("void(string s)", "Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. No spawn functions will be called.")}, {"setpause", PF_setpause, 0, 0, 0, 531, D("void(float pause)", "SSQC: Sets whether the server should or should not be paused.\n" "CSQC: Only works in singleplayer, suitable for menu auto-pause. To pause in multiplayer use eg localcmd(\"cmd pause\n\") to ask the server side to pause.\n" "Pause state between modules will be ORed, along with engine reasons for auto pausing.")}, diff --git a/engine/server/server.h b/engine/server/server.h index a5da50947..398ebd0d8 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -1401,6 +1401,7 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname, const c void SVM_AddBrokerGame(const char *brokerid, const char *info); void SVM_RemoveBrokerGame(const char *brokerid); qboolean SVM_FixupServerAddress(netadr_t *adr, struct dtlspeercred_s *cred); +void SVM_SelectRelay(netadr_t *benefitiary, const char *brokerid, char *out, size_t outsize); void FTENET_TCP_ICEResponse(struct ftenet_connections_s *col, int type, const char *cid, const char *sdp); diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index d70df0940..0d5bc8fa0 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -2885,7 +2885,15 @@ void SV_WritePlayersToClient (client_t *client, client_frame_t *frame, edict_t * vent = ent; - + if (vent->xv->customizeentityforclient) + { + globalvars_t *pr_globals = PR_globals(svprogfuncs, PR_CURRENT); + pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, vent); + pr_global_struct->other = (clent?EDICT_TO_PROG(svprogfuncs, clent):0); + PR_ExecuteProgram(svprogfuncs, vent->xv->customizeentityforclient); + if(!G_FLOAT(OFS_RETURN)) + continue; + } #ifdef NQPROT diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index eb7a5d6e3..bd7355dc2 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -1013,7 +1013,7 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, { //.map is commented out because quite frankly, they're a bit annoying when the engine loads the gpled start.map when really you wanted to just play the damn game intead of take it apart. //if you want to load a .map, just use 'map foo.map' instead. - char *exts[] = {"%s", "maps/%s", "maps/%s.bsp", "maps/%s.d3dbsp", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ "maps/%s.bsp.gz", "maps/%s.bsp.xz", NULL}, *e; + char *exts[] = {"%s", "maps/%s", "maps/%s.bsp", "maps/%s.d3dbsp", "maps/%s.cm", "maps/%s.hmp", "maps/%s.bsp.gz", "maps/%s.bsp.xz", "maps/%s.map", NULL}, *e; int depth, bestdepth = FDEPTH_MISSING; flocation_t loc; time_t filetime; @@ -1057,7 +1057,6 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, mod = NULL; } } - if (!strncmp(sv.modelname, "maps/", 5)) Q_strncpyz (svs.name, sv.modelname+5, sizeof(svs.name)); else @@ -1583,7 +1582,10 @@ MSV_OpenUserDatabase(); else InfoBuf_SetValueForStarKey(&svs.info, "*entfile", ""); - file = Mod_GetEntitiesString(sv.world.worldmodel); + if (usecinematic) + file = NULL; + else + file = Mod_GetEntitiesString(sv.world.worldmodel); if (!file) file = ""; diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 1ca0430a4..df0bb0c0b 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -2195,7 +2195,7 @@ void SV_ClientProtocolExtensionsChanged(client_t *client) extern cvar_t pext_ezquake_nochunks; extern cvar_t pext_ezquake_verfortrans; s = InfoBuf_ValueForKey(&client->userinfo, "*client"); - if (!strncmp(s, "ezQuake", 7) || !strncmp(s, "FortressOne", 11)) + if (!strncmp(s, "ezQuake", 7)) { s = COM_Parse(s); //skip name-of-fork COM_Parse(s); //tokenize the version @@ -5860,7 +5860,11 @@ void SV_InitLocal (void) static cvar_t qws_fullname = CVARF("qws_fullname", FULLENGINENAME, CVAR_NOSET ); static cvar_t qws_version = CVARF("qws_version", STRINGIFY(FTE_VER_MAJOR)"."STRINGIFY(FTE_VER_MINOR),CVAR_NOSET ); static cvar_t qws_buildnum = CVARF("qws_buildnum", STRINGIFY(SVNREVISION), CVAR_NOSET ); +#ifdef FTE_TARGET_WEB + static cvar_t qws_platform = CVARF("qws_platform", PLATFORM, CVAR_NOSET ); +#else static cvar_t qws_platform = CVARF("qws_platform", PLATFORM "-" ARCH_CPU_POSTFIX, CVAR_NOSET ); +#endif static cvar_t qws_builddate = CVARF("qws_builddate",STRINGIFY(SVNDATE), CVAR_NOSET ); static cvar_t qws_homepage = CVARF("qws_homepage", ENGINEWEBSITE, CVAR_NOSET ); Cvar_Register(&qws_name, "Server Info"); diff --git a/engine/server/sv_master.c b/engine/server/sv_master.c index 410d2fa7b..78e76bcd3 100644 --- a/engine/server/sv_master.c +++ b/engine/server/sv_master.c @@ -31,10 +31,11 @@ #define QUAKE3PROTOCOLNAME "Quake3" -#define PREFIX_SECURE(issecure) ((issecure)?"🛡":"⚠️")//shield, vs yellow warning -#define PREFIX_NEEDPASS(needpass) (((needpass)&1)?"🔒":"") //padlock, vs no indicator. -#define PREFIX_COOP(iscoop) (((iscoop)&1)?"☮":"") //coop: peace sign, deathmatch:no indicator. - +#define PREFIX_SECURE(srv) ((srv)->secure?"🛡":"⚠️")//shield, vs yellow warning +#define PREFIX_NEEDPASS(srv) (((srv)->needpass&1)?"🔒":"") //padlock, vs no indicator. +#define PREFIX_TYPE(srv) ( ((srv)->type&1)?"☮"/*coop: peace sign, deathmatch:no indicator. */ :\ + ((srv)->type&2)?"📺"/*tv symbol*/ :\ + "") enum gametypes_e { GT_FFA=0, @@ -50,9 +51,9 @@ typedef struct svm_server_s { unsigned int bots; //non-human players unsigned int clients; //human players unsigned int maxclients; //limit of bots+clients, but not necessarily spectators. - int secure:1; - int needpass:1; - int coop:1; + unsigned int secure:1; + unsigned int needpass:1; + unsigned int type:4; char hostname[64]; //just for our own listings. char mapname[16]; //just for our own listings. char gamedir[16]; //again... @@ -649,6 +650,8 @@ static void SVM_GatherServerRule(void *ctx, const char *key, const char *val) char niceval[256]; if (rules->lines == countof(rules->line)) return; //overflow + if (*key == '_') + val = ""; //was meant to be private... lets show that its there, just not what it is. QuakeCharsToHTML(niceval, sizeof(niceval), val, false); if (!Q_snprintfz(rules->blob+rules->blobofs, sizeof(rules->blob)-rules->blobofs, "%s%s\n", key, niceval)) { @@ -685,7 +688,7 @@ void SVM_Generate_ServerinfoEntry(vfsfile_t *f, const char *masteraddr, svm_serv VFS_PRINTF(f, "%s%s%s%s%s%s%s%s%u/%u\n", server->game?server->game->name:"Unknown", query?"?":"", query?query:"", server->game?server->game->name:"Unknown", //game column url, //address column - PREFIX_SECURE(server->secure), PREFIX_NEEDPASS(server->needpass), PREFIX_COOP(server->coop), hostname, //hostname column + PREFIX_SECURE(server), PREFIX_NEEDPASS(server), PREFIX_TYPE(server), hostname, //hostname column server->gamedir, server->mapname, server->clients, server->maxclients); VFS_PRINTF(f, "\n"); VFS_PRINTF(f, "
\n"); @@ -723,7 +726,7 @@ static vfsfile_t *SVM_Generate_RoomServerinfo(const char **mimetype, const char else { VFS_PRINTF(f, "\n"); - VFS_PRINTF(f, "\n"); + VFS_PRINTF(f, "\n"); VFS_PRINTF(f, "\n", serveraddr); VFS_PRINTF(f, "
GameAddressHostnameMod dirMapnamePlayers
GameAddressHostnameGamedirMapnamePlayers
?%s????/?
\n"); } @@ -744,8 +747,12 @@ static vfsfile_t *SVM_Generate_AddrServerinfo(const char **mimetype, const char VFS_PRINTF(f, "%s", master_css); VFS_PRINTF(f, "

Single Server Info

\n"); +#if 1 + count = NET_StringToAdr_NoDNS(serveraddr, 0, adr)?1:0; +#else //FIXME: block dns lookups here? count = NET_StringToAdr2(serveraddr, 0, adr, countof(adr), NULL); +#endif while(count-->0) { server = SVM_GetServer(&adr[count]); @@ -754,12 +761,14 @@ static vfsfile_t *SVM_Generate_AddrServerinfo(const char **mimetype, const char else { VFS_PRINTF(f, "\n"); - VFS_PRINTF(f, "\n"); + VFS_PRINTF(f, "\n"); VFS_PRINTF(f, "\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &adr[count])); VFS_PRINTF(f, "
GameAddressHostnameMod dirMapnamePlayers
GameAddressHostnameGamedirMapnamePlayers
?%s????/?
\n"); } } + VFS_PRINTF(f, "
Other Protocols\n"); + *mimetype = "text/html"; return f; } @@ -820,7 +829,7 @@ vfsfile_t *SVM_Generate_Serverlist(const char **mimetype, const char *masteraddr infourl = url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr); preurl = "/server/"; } - VFS_PRINTF(f, "%s%s%s%s%s%s%s%u", preurl,infourl, url, PREFIX_SECURE(server->secure), PREFIX_NEEDPASS(server->needpass), PREFIX_COOP(server->coop), hostname, server->gamedir, server->mapname, server->clients); + VFS_PRINTF(f, "%s%s%s%s%s%s%s%u", preurl,infourl, url, PREFIX_SECURE(server), PREFIX_NEEDPASS(server), PREFIX_TYPE(server), hostname, server->gamedir, server->mapname, server->clients); if (server->bots) VFS_PRINTF(f, "+%ub", server->bots); VFS_PRINTF(f, "/%u", server->maxclients); @@ -842,11 +851,13 @@ vfsfile_t *SVM_Generate_Serverlist(const char **mimetype, const char *masteraddr VFS_PRINTF(f, ", %u bot%s", (unsigned)bots, bots==1?"":"s"); if (specs) VFS_PRINTF(f, ", %u spectator%s", (unsigned)specs, specs==1?"":"s"); - VFS_PRINTF(f, "\n"); + VFS_PRINTF(f, "
\n"); } else VFS_PRINTF(f, "Protocol '%s' is not known\n", gamename); + VFS_PRINTF(f, "
Other Protocols\n"); + *mimetype = "text/html"; return f; } @@ -902,7 +913,7 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname, const c return f; } -static svm_game_t *SVM_GameFromBrokerID(const char **brokerid) +static svm_game_t *SVM_GameFromBrokerID(const char **brokerid, qboolean create) { //broker id is /GAMENAME/SERVERNAME size_t l; char name[128]; @@ -918,7 +929,7 @@ static svm_game_t *SVM_GameFromBrokerID(const char **brokerid) *brokerid = ++in; else Q_strncpyz(name, "unspecified", sizeof(name)); - return SVM_FindGame(name, true); + return SVM_FindGame(name, create); } static svm_server_t *SVM_FindBrokerHost(const char *brokerid) { @@ -938,7 +949,7 @@ static svm_server_t *SVM_FindBrokerHost(const char *brokerid) void SVM_RemoveBrokerGame(const char *brokerid) { svm_server_t *s, **link; - svm_game_t *game = SVM_GameFromBrokerID(&brokerid); + svm_game_t *game = SVM_GameFromBrokerID(&brokerid, false); if (!game) { Con_Printf("SVM_RemoveBrokerGame: failed to find game for brokered server: %s\n", brokerid); @@ -964,8 +975,9 @@ void SVM_RemoveBrokerGame(const char *brokerid) } void SVM_AddBrokerGame(const char *brokerid, const char *info) { - svm_game_t *game = SVM_GameFromBrokerID(&brokerid); + svm_game_t *game = SVM_GameFromBrokerID(&brokerid, true); svm_server_t *server = SVM_FindBrokerHost(brokerid); + char *s; if (!server) { if (!game) @@ -993,16 +1005,26 @@ void SVM_AddBrokerGame(const char *brokerid, const char *info) else Con_DPrintf("heartbeat(update - %s): /%s\n", game->name, brokerid); - server->protover = atoi(Info_ValueForKey(info, "protocol")); - server->maxclients = atoi(Info_ValueForKey(info, "maxclients")); + s = Info_ValueForKey(info, "sv_maxclients"); + if (!*s) + s = Info_ValueForKey(info, "maxclients"); + server->maxclients = atoi(s); server->clients = atoi(Info_ValueForKey(info, "clients")); server->secure = !!*Info_ValueForKey(info, "*fp"); server->needpass = atoi(Info_ValueForKey(info, "needpass")); - server->coop = atoi(Info_ValueForKey(info, "coop")); - if (!server->coop) + server->type = atoi(Info_ValueForKey(info, "coop")); + if (!server->type) { //deathmatch 0 also means coop 1... servers that report neither are probably annoying DP servers that report nothing useful and should default to DM. const char *v = Info_ValueForKey(info, "deathmatch"); - server->coop = *v && !atoi(v); + server->type = *v && !atoi(v); + } + server->protover = strtol(Info_ValueForKey(info, "protocol"), &s, 0); + for (; *s; s++) + { + if (*s == 't') //turn + server->type |= 2; + else if (*s == 'f') //usable for qwfwd + server->type |= 4; } Q_strncpyz(server->hostname, Info_ValueForKey(info, "hostname"), sizeof(server->hostname)); Q_strncpyz(server->gamedir, Info_ValueForKey(info, "modname"), sizeof(server->gamedir)); @@ -1015,6 +1037,53 @@ void SVM_AddBrokerGame(const char *brokerid, const char *info) Q_strncpyz(server->rules, info, sizeof(server->rules)); } +void SVM_SelectRelay(netadr_t *benefitiary, const char *brokerid, char *out, size_t outsize) +{ + char username[128]; + char pass[128]; + char key[128]; + char adrbuf[64]; + qbyte dig[DIGEST_MAXSIZE]; + size_t keysize; + svm_server_t *server; + svm_game_t *game = SVM_GameFromBrokerID(&brokerid, false); + int count; + if (!game) + return; //nope. + + for (count = 0, server = game->firstserver; server; server = server->next) + { + if (server->needpass) + continue; //nope, not interested. + if (server->type & 8) //acting as a turn relay... + count++; + } + if (!count) + return; //none for you... + + count = rand()%count; //pick one at random... FIXME: fix closest. + + for (server = game->firstserver; server; server = server->next) + { + if (server->needpass) + continue; //nope, not interested. + if (server->type & 8) //acting as a turn relay... + { + if (count-->0) + continue; //we didn't pick this one, keep going. + + //we need a username. this includes a timestamp to ensure it can expire. + Q_snprintfz(username,sizeof(username), "%"PRIi64":%s", (quint64_t)time(NULL), brokerid); + //we need a password too... its based upon our username and a secret key also known only to the relay. + keysize = Base64_DecodeBlock(Info_ValueForKey(server->rules, "_turnkey"),NULL, key,sizeof(key)); + keysize = CalcHMAC(&hash_sha1, dig,sizeof(dig), username,strlen(username), key,keysize); + pass[Base64_EncodeBlock(dig,keysize, pass,sizeof(pass)-1)] = 0; + //and spit out the url (with our ?user= and ?auth= bits added. + Q_snprintfz(out,outsize, "turn:%s?user=%s?auth=%s", NET_AdrToString(adrbuf,sizeof(adrbuf), &server->adr), username, pass); + return; + } + } +} static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numclients, int numbots, int numspecs, double validuntil) { @@ -1308,6 +1377,8 @@ static void SVM_ProcessUDPPacket(void) { //dp/q3/etc are annoying, but we can query from an emphemerial socket to check NAT rules. sizebuf_t sb; netadr_t a; + char cookie[64]; + char tmp[64]; char ourchallenge[256]; SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); @@ -1321,12 +1392,23 @@ static void SVM_ProcessUDPPacket(void) if (!SVM_SwitchQuerySocket()) //changes net_from to use a different master-side port so their firewall sees us as someone else a.type = NA_INVALID; - //send a packet from our alternative port + *cookie = 0; + s = COM_Parse(s); + if (!strcmp(com_token, "FTEMaster")) + { + while ((s = COM_Parse(s))) + { + if (!strncmp(com_token, "c=", 2)) + Q_snprintfz(cookie, sizeof(cookie), " %s a=%s", com_token, NET_AdrToString(tmp,sizeof(tmp), &net_from)); + } + } + + //send a packet from our alternative port, to see if their firewall/NAT is open memset(&sb, 0, sizeof(sb)); sb.maxsize = sizeof(net_message_buffer); sb.data = net_message_buffer; MSG_WriteLong(&sb, -1); - MSG_WriteString(&sb, va("getinfo %s\n", ourchallenge)); + MSG_WriteString(&sb, va("getinfo %s %s\n", ourchallenge,cookie)); sb.cursize--; NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); @@ -1342,7 +1424,7 @@ static void SVM_ProcessUDPPacket(void) } } } - else if (!strcmp(com_token, "infoResponse")) + else if (!strcmp(com_token, "infoResponse") || !strcmp(com_token, "statusResponse")) { char ourchallenge[256]; int clients, bots, specs; @@ -1355,7 +1437,7 @@ static void SVM_ProcessUDPPacket(void) unknownresp = *chal=='?'; chal += unknownresp?1:0; SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); - if (!strcmp(chal, ourchallenge)) + if (!strcmp(chal, ourchallenge)) //someone's trying to spoof it, to give it the wrong *fp or whatever. { bots = atoi(Info_ValueForKey(s, "bots")); clients = atoi(Info_ValueForKey(s, "clients")); @@ -1369,21 +1451,39 @@ static void SVM_ProcessUDPPacket(void) srv = SVM_Heartbeat(game, &net_from, clients,bots,specs, svm.time + sv_heartbeattimeout.ival); if (srv) { - Q_strncpyz(srv->rules, s, sizeof(srv->rules)); + if (unknownresp) + { //retain _ keys that won't be included in unchallenged responses. + char *turnkey = Info_ValueForKey(srv->rules, "_turnkey"); + Q_strncpyz(srv->rules, s, sizeof(srv->rules)); + Info_SetValueForKey(srv->rules, "_turnkey", turnkey, sizeof(srv->rules)); + } + else + Q_strncpyz(srv->rules, s, sizeof(srv->rules)); Info_RemoveKey(srv->rules, "challenge"); //prevent poisoning if (developer.ival) + { + Con_Printf("Update from %s:\n", NET_AdrToString(ourchallenge,sizeof(ourchallenge), &net_from)); Info_Print(s, "\t"); - if (game) - srv->protover = atoi(Info_ValueForKey(s, "protocol")); + } srv->maxclients = atoi(Info_ValueForKey(s, "sv_maxclients")); srv->secure = !!*Info_ValueForKey(s, "*fp"); srv->needpass = atoi(Info_ValueForKey(s, "needpass")); - srv->coop = atoi(Info_ValueForKey(s, "coop")); - if (!srv->coop) + srv->type = atoi(Info_ValueForKey(s, "coop")); + if (!srv->type) { //deathmatch 0 also means coop 1... servers that report neither are probably annoying DP servers that report nothing useful and should default to DM. const char *v = Info_ValueForKey(s, "deathmatch"); - srv->coop = *v && !atoi(v); + srv->type = *v && !atoi(v); } + srv->protover = strtol(Info_ValueForKey(s, "protocol"), &s, 0); + for (; *s; s++) + { + if (*s == 't') + srv->type |= 2; + else if (*s == 'f') + srv->type |= 4; + } + if (*Info_ValueForKey(srv->rules, "_turnkey")) + srv->type |= 8; Q_strncpyz(srv->hostname, Info_ValueForKey(s, "hostname"), sizeof(srv->hostname)); Q_strncpyz(srv->gamedir, Info_ValueForKey(s, "modname"), sizeof(srv->gamedir)); Q_strncpyz(srv->mapname, Info_ValueForKey(s, "mapname"), sizeof(srv->mapname)); @@ -1412,6 +1512,7 @@ static void SVM_ProcessUDPPacket(void) { //quakeworld heartbeat int players; sizebuf_t sb; + char *nonce; s = MSG_ReadStringLine(); //sequence = atoi(s); s = MSG_ReadStringLine(); @@ -1421,6 +1522,7 @@ static void SVM_ProcessUDPPacket(void) //placeholder listing... SVM_Heartbeat(NULL, &net_from, players,0,0, svm.time + sv_heartbeattimeout.ival); + nonce = MSG_ReadStringLine(); //added a nonce, so the status can contain a private/shared key so the master can know how to generate acceptable passwords for turn proxies. SVM_SwitchQuerySocket(); //send it a proper query. We'll fill in the other details on response. @@ -1430,6 +1532,8 @@ static void SVM_ProcessUDPPacket(void) MSG_WriteLong(&sb, -1); MSG_WriteString(&sb, va("status %i\n", 15)); sb.cursize--; + if (*nonce) + MSG_WriteString(&sb, nonce); NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); } else if (*com_token == M2C_MASTER_REPLY && !com_token[1]) @@ -1555,11 +1659,11 @@ static void SVM_ProcessUDPPacket(void) srv->maxclients = atoi(Info_ValueForKey(s, "maxclients")); srv->secure = !!*Info_ValueForKey(s, "*fp"); srv->needpass = atoi(Info_ValueForKey(s, "needpass")); - srv->coop = atoi(Info_ValueForKey(s, "coop")); - if (!srv->coop) + srv->type = atoi(Info_ValueForKey(s, "coop")); + if (!srv->type) { //deathmatch 0 also means coop 1... servers that report neither are probably annoying proxies servers that report nothing useful and should default to DM. const char *v = Info_ValueForKey(s, "deathmatch"); - srv->coop = *v && !atoi(v); + srv->type = *v && !atoi(v); } Q_strncpyz(srv->hostname, Info_ValueForKey(s, "hostname"), sizeof(srv->hostname)); Q_strncpyz(srv->gamedir, Info_ValueForKey(s, "*gamedir"), sizeof(srv->gamedir)); @@ -1896,7 +2000,7 @@ void SV_Init (struct quakeparms_s *parms) float SV_Frame (void) { float sleeptime; - realtime = Sys_DoubleTime(); + svm.time = realtime = Sys_DoubleTime(); while (1) { const char *cmd = Sys_ConsoleInput (); diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index 221c94c92..7abd36784 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -2007,8 +2007,9 @@ typedef struct { } eval; int statnum; } qcstat_t; -qcstat_t qcstats[MAX_CL_STATS]; -int numqcstats; +static qcstat_t qcstats[MAX_CL_STATS]; +static unsigned int numqcstats; +static unsigned int highestqcstat; static void SV_QCStatEval(int type, const char *name, evalc_t *field, eval_t *global, int statnum) { int i; @@ -2100,6 +2101,7 @@ void SV_QCStatFieldIdx(int type, unsigned int fieldindex, int statnum) void SV_ClearQCStats(void) { numqcstats = 0; + highestqcstat = MAX_QW_STATS; } extern cvar_t dpcompat_stats; @@ -2163,8 +2165,9 @@ void SV_UpdateQCStats(edict_t *ent, int *statsi, char const** statss, float *sta } /*this function calculates the current stat values for the given client*/ -void SV_CalcClientStats(client_t *client, int statsi[MAX_CL_STATS], float statsf[MAX_CL_STATS], const char **statss) +static unsigned int SV_CalcClientStats(client_t *client, int statsi[MAX_CL_STATS], float statsf[MAX_CL_STATS], const char **statss) { + unsigned int m = highestqcstat; edict_t *ent; ent = client->edict; memset (statsi, 0, sizeof(int)*MAX_CL_STATS); @@ -2289,11 +2292,15 @@ void SV_CalcClientStats(client_t *client, int statsi[MAX_CL_STATS], float statsf statsfi[STAT_MOVEVARS_STEPHEIGHT] = *sv_stepheight.string?sv_stepheight.value:PM_DEFAULTSTEPHEIGHT; statsfi[STAT_MOVEVARS_AIRACCEL_QW] = 1; //we're a quakeworld engine... statsfi[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = 0; + + if (m < 256) + m = 256; } #endif SV_UpdateQCStats(ent, statsi, statss, statsf); } + return m; } /* @@ -2309,14 +2316,14 @@ void SV_UpdateClientStats (client_t *client, int pnum, sizebuf_t *msg, client_fr int statsi[MAX_CL_STATS]; float statsf[MAX_CL_STATS]; const char *statss[MAX_CL_STATS]; - int i, m; + unsigned int i, m; /*figure out what the stat values should be*/ - SV_CalcClientStats(client, statsi, statsf, statss); - - m = MAX_QW_STATS; + m = SV_CalcClientStats(client, statsi, statsf, statss); if ((client->fteprotocolextensions & (PEXT_HEXEN2|PEXT_CSQC)) || client->protocol == SCP_DARKPLACES6 || client->protocol == SCP_DARKPLACES7) - m = MAX_CL_STATS; + m = min(m,256); + else + m = min(m,MAX_QW_STATS); if (client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) { @@ -3759,10 +3766,6 @@ void SV_SendMVDMessage(void) msg.allowoverflow = true; msg.overflowed = false; - m = MAX_QW_STATS; - if (demo.recorder.fteprotocolextensions & (PEXT_HEXEN2|PEXT_CSQC)) - m = MAX_CL_STATS; - for (i=0, c = svs.clients ; istate != cs_spawned) @@ -3772,7 +3775,11 @@ void SV_SendMVDMessage(void) continue; /*figure out what the stat values should be*/ - SV_CalcClientStats(c, statsi, statsf, statss); + m = SV_CalcClientStats(c, statsi, statsf, statss); + if (demo.recorder.fteprotocolextensions & (PEXT_HEXEN2|PEXT_CSQC)) + m = min(m,MAX_CL_STATS); + else + m = min(m,MAX_QW_STATS); //FIXME we should do something about the packet overhead here. each MVDWrite_Begin is a separate packet! diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 79db4a615..023721278 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -188,7 +188,11 @@ qboolean SV_CheckRealIP(client_t *client, qboolean force) if (client->realip_status == -1) return true; //this client timed out. - if (realtime - client->connection_started > sv_realip_timeout.value) + //if they're using some weird protocol just give up right away. + if (realtime - client->connection_started > sv_realip_timeout.value || + client->netchan.remote_address.prot != NP_DGRAM || !( + (client->netchan.remote_address.type == NA_IP&&*sv_realiphostname_ipv4.string) || + (client->netchan.remote_address.type == NA_IPV6&&sv_realiphostname_ipv6.string))) { if (client->realip_status > 0) SV_PrintToClient(client, PRINT_HIGH, "Couldn't verify your real ip\n"); diff --git a/engine/shaders/glsl/lpp_light.glsl b/engine/shaders/glsl/lpp_light.glsl index 19a268110..82481bd65 100644 --- a/engine/shaders/glsl/lpp_light.glsl +++ b/engine/shaders/glsl/lpp_light.glsl @@ -5,8 +5,6 @@ //FIXME: !!permu FOG !!samps shadowmap 2 -#define USE_ARB_SHADOW - #include "sys/defs.h" #include "sys/pcf.h" @@ -66,7 +64,6 @@ void main () //fixme: cubemap filters float shadows = ShadowmapFilter(s_shadowmap, cubeaxis); - lightColour *= atten; out_diff = vec4(lightColour * (l_lightcolourscale.x + l_lightcolourscale.y*lightDiffuse*shadows), 1.0); out_spec = vec4(lightColour * l_lightcolourscale.z*spec*shadows, 1.0); diff --git a/engine/shaders/glsl/lpp_wall.glsl b/engine/shaders/glsl/lpp_wall.glsl index 24fa046e1..27c9eb2bf 100644 --- a/engine/shaders/glsl/lpp_wall.glsl +++ b/engine/shaders/glsl/lpp_wall.glsl @@ -1,6 +1,7 @@ !!ver 100 150 !!permu BUMP //for offsetmapping rather than bumpmapping (real bumps are handled elsewhere) !!cvarf r_glsl_offsetmapping_scale +!!samps diffuse specular fullbright lightmap !!samps 2 //the final defered lighting pass. diff --git a/engine/web/prejs.js b/engine/web/prejs.js index 306ab54b5..16de9e47f 100644 --- a/engine/web/prejs.js +++ b/engine/web/prejs.js @@ -68,8 +68,8 @@ if (typeof Module['files'] !== "undefined" && Object.keys(Module['files']).lengt xhr.open("GET", ab); xhr.onload = function () { - if (curfile == n) - curfile = undefined; + if (Module['curfile'] == n) + Module['curfile'] = undefined; if (this.status >= 200 && this.status < 300) { let b = FTEH.h[_emscriptenfte_buf_createfromarraybuf(this.response)]; @@ -84,7 +84,7 @@ if (typeof Module['files'] !== "undefined" && Object.keys(Module['files']).lengt { if (typeof Module['curfile'] == "undefined") Module['curfile'] = n; //take it. - if (Module['setStatus'] && curfile==n) + if (Module['setStatus'] && Module['curfile']==n) Module['setStatus'](n + ' (' + e.loaded + '/' + e.total + ')'); }; xhr.onerror = function () @@ -158,7 +158,7 @@ if (!Module['arguments']) //the html can be explicit about its args if it sets t } } - if (Module['manifest'] != "") + if (Module['manifest'] != undefined) Module['arguments'] = Module['arguments'].concat(['-manifest', Module['manifest']]); //registerProtocolHandler needs to be able to pass it through to us... so only allow it if we're parsing args from the url. diff --git a/fteqtv/control.c b/fteqtv/control.c index 8e2824306..f47871bc2 100644 --- a/fteqtv/control.c +++ b/fteqtv/control.c @@ -130,14 +130,14 @@ unsigned char *FS_ReadFile(char *gamedir, char *filename, unsigned int *size) #endif int _cdecl SortFilesByDate(const void *a, const void *b) { - if (((availdemo_t*)a)->time < ((availdemo_t*)b)->time) + if (((const availdemo_t*)a)->time < ((const availdemo_t*)b)->time) return 1; - if (((availdemo_t*)a)->time > ((availdemo_t*)b)->time) + if (((const availdemo_t*)a)->time > ((const availdemo_t*)b)->time) return -1; - if (((availdemo_t*)a)->smalltime < ((availdemo_t*)b)->smalltime) + if (((const availdemo_t*)a)->smalltime < ((const availdemo_t*)b)->smalltime) return 1; - if (((availdemo_t*)a)->smalltime > ((availdemo_t*)b)->smalltime) + if (((const availdemo_t*)a)->smalltime > ((const availdemo_t*)b)->smalltime) return -1; return 0; } @@ -263,6 +263,8 @@ void Cluster_Run(cluster_t *cluster, qboolean dowait) } } + TURN_AddFDs(cluster, &socketset, &m); + for (pend = cluster->pendingproxies; pend; pend = pend->next) { if (pend->sock != INVALID_SOCKET && pend->sock < FD_SETSIZE) @@ -377,6 +379,8 @@ void Cluster_Run(cluster_t *cluster, qboolean dowait) QTV_Run(old); } + TURN_CheckFDs(cluster); + SV_FindProxies(cluster->tcpsocket[0], cluster, NULL); //look for any other proxies wanting to muscle in on the action. SV_FindProxies(cluster->tcpsocket[1], cluster, NULL); //look for any other proxies wanting to muscle in on the action. @@ -515,6 +519,20 @@ int main(int argc, char **argv) strcpy(cluster->hostname, DEFAULT_HOSTNAME); cluster->maxproxies = -1; + //master protocol setup + cluster->protocolname = strdup("FTE-Quake"); + cluster->protocolver = 3; + strlcpy(cluster->master, "master.frag-net.com:27950", sizeof(cluster->master)); //default to eukara's master server. + cluster->mastersendtime = cluster->curtime; + + cluster->relayenabled = true; //allow qtv + cluster->pingtreeenabled = false; //spammy. + cluster->turnenabled = false; //leave turn off by default. we need to know a usable inbound port range, we can't depend on just outgoing ephemerial ones. misconfigured relays will result in failures so don't default this to on. + +#ifdef HAVE_EPOLL + cluster->epfd = epoll_create1(0); +#endif + strcpy(cluster->demodir, "qw/demos/"); Sys_Printf(cluster, "QTV "QTV_VERSION_STRING"\n"); @@ -538,7 +556,7 @@ int main(int argc, char **argv) Net_TCPListen(cluster, 1, SG_UNIX); Sys_Printf(cluster, "\n" - "Welcome to FTEQTV\n" + "Welcome to QTV\n" "Please type\n" "qtv server:port\n" " to connect to a tcp server.\n" diff --git a/fteqtv/httpsv.c b/fteqtv/httpsv.c index ea5d26fc1..9f1b6d696 100644 --- a/fteqtv/httpsv.c +++ b/fteqtv/httpsv.c @@ -1187,7 +1187,7 @@ char *HTTPSV_GetMethod(cluster_t *cluster, oproxy_t *pend) { //if (!strcmp(wsprot, "quake")) //webquake. we don't support this! (no OOB and missing header flags and some screwy sequence numbers) if (!strcmp(wsprot, "fteqw") || //as a client - (!strcmp(wsprot, "faketcp") && !urilen)) //as a qtv proxy (eztv style, but websocked). we are NOT proxying tcp. require a qtv handshake over the resulting websocket connection. + (!strcmp(wsprot, "faketcp") && urilen==1&&!strncmp(uri,"/",1))) //as a qtv proxy (eztv style, but websocked). we are NOT proxying tcp. require a qtv handshake over the resulting websocket connection. break; //break out on the first one we know. this is the recommended way... } diff --git a/fteqtv/msg.c b/fteqtv/msg.c index fba428bac..781a88edd 100644 --- a/fteqtv/msg.c +++ b/fteqtv/msg.c @@ -25,6 +25,14 @@ unsigned short ReadShort(netmsg_t *b) return b1 | (b2<<8); } +unsigned short ReadBigShort(netmsg_t *b) +{ + int b1, b2; + b1 = ReadByte(b); + b2 = ReadByte(b); + + return (b1<<8) | b2; +} unsigned int ReadLong(netmsg_t *b) { int s1, s2; @@ -33,6 +41,14 @@ unsigned int ReadLong(netmsg_t *b) return s1 | (s2<<16); } +unsigned int ReadBigLong(netmsg_t *b) +{ + unsigned int s1, s2; + s1 = ReadBigShort(b); + s2 = ReadBigShort(b); + + return (s1<<16) | s2; +} unsigned int BigLong(unsigned int val) { @@ -117,6 +133,11 @@ void WriteShort(netmsg_t *b, unsigned short l) WriteByte(b, (l&0x00ff)>>0); WriteByte(b, (l&0xff00)>>8); } +void WriteBigShort(netmsg_t *b, unsigned short l) +{ + WriteByte(b, (l&0xff00)>>8); + WriteByte(b, (l&0x00ff)>>0); +} void WriteLong(netmsg_t *b, unsigned int l) { WriteByte(b, (l&0x000000ff)>>0); @@ -124,6 +145,13 @@ void WriteLong(netmsg_t *b, unsigned int l) WriteByte(b, (l&0x00ff0000)>>16); WriteByte(b, (l&0xff000000)>>24); } +void WriteBigLong(netmsg_t *b, unsigned int l) +{ + WriteByte(b, (l&0xff000000)>>24); + WriteByte(b, (l&0x00ff0000)>>16); + WriteByte(b, (l&0x0000ff00)>>8); + WriteByte(b, (l&0x000000ff)>>0); +} void WriteFloat(netmsg_t *b, float f) { union { @@ -168,7 +196,7 @@ void WriteData(netmsg_t *b, const void *data, int length) return; buf = (unsigned char*)b->data+b->cursize; for (i = 0; i < length; i++) - *buf++ = ((unsigned char*)data)[i]; + *buf++ = ((const unsigned char*)data)[i]; b->cursize+=length; } void WriteCoordf(netmsg_t *b, unsigned int pext, float fl) diff --git a/fteqtv/parse.c b/fteqtv/parse.c index 323004d8c..f0780e557 100644 --- a/fteqtv/parse.c +++ b/fteqtv/parse.c @@ -126,6 +126,7 @@ static void ParseServerData(sv_t *tv, netmsg_t *m, int to, unsigned int playerma tv->pext1 = 0; tv->pext2 = 0; + tv->pexte = 0; //when it comes to QTV, the proxy 'blindly' forwards the data after parsing the header, so we need to support EVERYTHING the original server might. //and if we don't, then we might have troubles. @@ -209,6 +210,12 @@ static void ParseServerData(sv_t *tv, netmsg_t *m, int to, unsigned int playerma if (protocol & ~supported) Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_FTE2 (%x) not supported\n", protocol & ~supported); continue; + case PROTOCOL_VERSION_EZQUAKE1: + tv->pexte = protocol = ReadLong(m); + supported = PEXTE_HIDDENMESSAGES; + if (protocol & ~supported) + Sys_Printf(tv->cluster, "ParseMessage: Unsupported MVD1 protocol flags %#x\n", protocol); + continue; case PROTOCOL_VERSION_HUFFMAN: Sys_Printf(tv->cluster, "ParseMessage: PROTOCOL_VERSION_HUFFMAN not supported\n"); ParseError(m); @@ -240,7 +247,7 @@ static void ParseServerData(sv_t *tv, netmsg_t *m, int to, unsigned int playerma ParseError(m); return; default: - Sys_Printf(tv->cluster, "ParseMessage: Unknown protocol version %x\n", protocol); + Sys_Printf(tv->cluster, "ParseMessage: Unknown protocol version %#x\n", protocol); ParseError(m); return; } diff --git a/fteqtv/protocol.h b/fteqtv/protocol.h index 482769ec6..e696775ba 100644 --- a/fteqtv/protocol.h +++ b/fteqtv/protocol.h @@ -211,6 +211,7 @@ enum { #define PROTOCOL_VERSION_FTE (('F'<<0) + ('T'<<8) + ('E'<<16) + ('X' << 24)) //fte extensions. #define PROTOCOL_VERSION_FTE2 (('F'<<0) + ('T'<<8) + ('E'<<16) + ('2' << 24)) //fte extensions. +#define PROTOCOL_VERSION_EZQUAKE1 (('M'<<0) + ('V'<<8) + ('D'<<16) + ('1' << 24)) //ezquake/mvdsv extensions #define PROTOCOL_VERSION_HUFFMAN (('H'<<0) + ('U'<<8) + ('F'<<16) + ('F' << 24)) //packet compression #define PROTOCOL_VERSION_VARLENGTH (('v'<<0) + ('l'<<8) + ('e'<<16) + ('n' << 24)) //variable length handshake #define PROTOCOL_VERSION_FRAGMENT (('F'<<0) + ('R'<<8) + ('A'<<16) + ('G' << 24)) //supports fragmentation/packets larger than 1450 @@ -260,6 +261,7 @@ enum { #define PEXT2_INFOBLOBS 0x00000080 //serverinfo+userinfo lengths can be MUCH higher (protocol is unbounded, but expect low sanity limits on userinfo), and contain nulls etc. //#define PEXT2_PK3DOWNLOADS 0x10000000 //retrieve a list of pk3s/pk3s/paks for downloading (with optional URL and crcs) +#define PEXTE_HIDDENMESSAGES 0x20 //random demo metadata... //flags on entities #define U_ORIGIN1 (1<<9) diff --git a/fteqtv/qtv.h b/fteqtv/qtv.h index 32bc99cdd..68c8e3e76 100644 --- a/fteqtv/qtv.h +++ b/fteqtv/qtv.h @@ -162,6 +162,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include #include + #include #include #include #include @@ -173,6 +174,13 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define ioctlsocket ioctl #define closesocket close + + #if defined(__linux__) && !defined(ANDROID) +// #define HAVE_EPOLL + #endif + #ifdef HAVE_EPOLL + #include + #endif #elif (defined(__MORPHOS__) && !defined(ixemul)) #include #include @@ -180,6 +188,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include #include + #include #include #include @@ -247,14 +256,15 @@ typedef struct void (*process) (void *context, const void *data, size_t datasize); void (*terminate) (unsigned char *digest, void *context); } hashfunc_t; +extern hashfunc_t hash_md5; extern hashfunc_t hash_sha1; /*extern hashfunc_t hash_sha2_224; extern hashfunc_t hash_sha2_256; extern hashfunc_t hash_sha2_384; extern hashfunc_t hash_sha2_512;*/ -#define HMAC HMAC_quake //stop conflicts... size_t CalcHash(hashfunc_t *hash, unsigned char *digest, size_t maxdigestsize, const unsigned char *string, size_t stringlen); -size_t HMAC(hashfunc_t *hashfunc, unsigned char *digest, size_t maxdigestsize, const unsigned char *data, size_t datalen, const unsigned char *key, size_t keylen); +unsigned int CalcHashInt(const hashfunc_t *hash, const void *data, size_t datasize); +size_t CalcHMAC(hashfunc_t *hashfunc, unsigned char *digest, size_t maxdigestsize, const unsigned char *data, size_t datalen, const unsigned char *key, size_t keylen); #ifdef LIBQTV @@ -667,6 +677,7 @@ struct sv_s { //details about a server connection (also known as stream) qboolean usequakeworldprotocols; unsigned int pext1; unsigned int pext2; + unsigned int pexte; int challenge; unsigned short qport; int isconnected; @@ -786,6 +797,8 @@ enum SG_UNIX, SOCKETGROUPS }; + +typedef struct turnclient_s turnclient_t; struct cluster_s { SOCKET qwdsocket[SOCKETGROUPS]; //udp + quakeworld protocols SOCKET tcpsocket[SOCKETGROUPS]; //tcp listening socket (for mvd and listings and stuff) @@ -798,6 +811,26 @@ struct cluster_s { unsigned int mastersequence; unsigned int curtime; +#ifdef HAVE_EPOLL + int epfd; +#endif + unsigned int numrelays; + turnclient_t *turns; + char chalkey[64]; //to identify the master properly. probably kinda pointless. base64 encoded. + unsigned char turnkey[32]; //raw key shared with broker to prove TURN identity was given by broker. NOTE: we are not verifying each, so we depend on clockskew to prevent any longterm abuse. there's no accounts anywhere though so anyone can get a key if they ask properly. + qboolean turnenabled; + unsigned short turn_minport, turn_maxport; //set to 0 to let the OS decide. + char *protocolname; + int protocolver; + unsigned char turn_ipv4[4]; + unsigned char turn_ipv6[16]; + unsigned int numpeers; + struct relaypeer_s *relaypeer; + unsigned int relay_lastping; + unsigned int relay_lastquery; + qboolean relayenabled; + qboolean pingtreeenabled; + viewer_t *viewers; int numviewers; sv_t *servers; @@ -875,7 +908,9 @@ enum { unsigned char ReadByte(netmsg_t *b); unsigned short ReadShort(netmsg_t *b); +unsigned short ReadBigShort(netmsg_t *b); unsigned int ReadLong(netmsg_t *b); +unsigned int ReadBigLong(netmsg_t *b); float ReadFloat(netmsg_t *b); void ReadString(netmsg_t *b, char *string, int maxlen); float ReadCoord(netmsg_t *b, unsigned int pext); @@ -905,7 +940,9 @@ float ReadFloat(netmsg_t *b); void ReadString(netmsg_t *b, char *string, int maxlen); void WriteByte(netmsg_t *b, unsigned char c); void WriteShort(netmsg_t *b, unsigned short l); +void WriteBigShort(netmsg_t *b, unsigned short l); void WriteLong(netmsg_t *b, unsigned int l); +void WriteBigLong(netmsg_t *b, unsigned int l); void WriteFloat(netmsg_t *b, float f); void WriteCoord(netmsg_t *b, float c, unsigned int pext); void WriteAngle(netmsg_t *b, float a, unsigned int pext); @@ -1018,6 +1055,15 @@ void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen); void Menu_Enter(cluster_t *cluster, viewer_t *viewer, int buttonnum); void Menu_Draw(cluster_t *cluster, viewer_t *viewer); +//relay.c +void TURN_CheckFDs(cluster_t *cluster); +void TURN_AddFDs(cluster_t *cluster, fd_set *set, int *m); +qboolean TURN_IsRequest(cluster_t *cluster, netmsg_t *m, netadr_t *from); //handles both TURN/STUN packets, and relays inbound qwfwd connections too. +void Fwd_NewQWFwd(cluster_t *cluster, netadr_t *from, char *targ); //creates a new qwfwd context. +void TURN_RelayStatus(cmdctxt_t *ctx); +void Fwd_PingStatus(cluster_t *cluster, netadr_t *from, qboolean ext); +void Fwd_ParseServerList(cluster_t *cluster, netmsg_t *m, int af); +void Fwd_PingResponse(cluster_t *cluster, netadr_t *from); #ifdef __cplusplus } diff --git a/fteqtv/qw.c b/fteqtv/qw.c index 99ee522a6..54d314d4a 100644 --- a/fteqtv/qw.c +++ b/fteqtv/qw.c @@ -20,8 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "qtv.h" #include - -#include "bsd_string.h" +#include static const filename_t ConnectionlessModelList[] = {{""}, {"maps/start.bsp"}, {"progs/player.mdl"}, {""}}; static const filename_t ConnectionlessSoundList[] = {{""}, {""}}; @@ -183,6 +182,11 @@ void BuildServerData(sv_t *tv, netmsg_t *msg, int servercount, viewer_t *viewer) WriteLong(msg, PROTOCOL_VERSION_FTE2); WriteLong(msg, tv->pext2); } + if (tv->pexte) + { + WriteLong(msg, PROTOCOL_VERSION_EZQUAKE1); + WriteLong(msg, tv->pexte); + } } WriteLong(msg, PROTOCOL_VERSION); WriteLong(msg, servercount); @@ -710,13 +714,40 @@ void QW_SetViewersServer(cluster_t *cluster, viewer_t *viewer, sv_t *sv) } //fixme: will these want to have state?.. -int NewChallenge(netadr_t *addr) +int NewChallenge(cluster_t *cluster, netadr_t *addr) { - return 4; + unsigned int r = 0, l; + unsigned char *digest; + void *ctx; + hashfunc_t *func = &hash_sha1; + static time_t t; + + //reminder: Challenges exist so clients can't spoof their source address and waste our ram without us being able to ban them without banning everyone. + size_t sz = 0; + if (((struct sockaddr*)addr->sockaddr)->sa_family == AF_INET) + sz = sizeof(struct sockaddr_in); + else if (((struct sockaddr*)addr->sockaddr)->sa_family == AF_INET6) + sz = sizeof(struct sockaddr_in6); + //else error + + ctx = alloca(func->contextsize); + func->init(ctx); + if (!t) //must be constant, so only do this if its still 0. + t = time(NULL); + func->process(ctx, addr, sz); //hash their address primarily. + func->process(ctx, cluster->turnkey, sizeof(cluster->turnkey)); //might not be set... + func->process(ctx, &t, sizeof(t)); //extra privacy, sizeof doesn't matter as its only our process that cares + //func->process(ctx, cluster, sizeof(cluster)); //a random pointer too, because zomgwtf + + digest = alloca(func->digestsize); + func->terminate(digest, ctx); + for (l = 0; l < func->digestsize; l++) + r ^= digest[l]<<((l%sizeof(r))*8); + return r; } -qboolean ChallengePasses(netadr_t *addr, int challenge) +qboolean ChallengePasses(cluster_t *cluster, netadr_t *addr, int challenge) { - if (challenge == 4) + if (challenge == NewChallenge(cluster, addr)) return true; return false; } @@ -936,7 +967,8 @@ void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) char qport[32]; char challenge[32]; - char infostring[256]; + char infostring[1024]; + char prx[256]; int i; connectmessage+=11; @@ -945,12 +977,19 @@ void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) connectmessage = COM_ParseToken(connectmessage, challenge, sizeof(challenge), ""); connectmessage = COM_ParseToken(connectmessage, infostring, sizeof(infostring), ""); - if (!ChallengePasses(addr, atoi(challenge))) + if (!ChallengePasses(cluster, addr, atoi(challenge))) { Netchan_OutOfBandPrint(cluster, *addr, "n" "Bad challenge"); return; } + Info_ValueForKey(infostring, "prx", prx,sizeof(prx)); + if (*prx) + { + Fwd_NewQWFwd(cluster, addr, prx); + return; + } + viewer = malloc(sizeof(viewer_t)); if (!viewer) @@ -1169,6 +1208,109 @@ void QTV_Status(cluster_t *cluster, netadr_t *from) WriteByte(&msg, 0); NET_SendPacket(cluster, NET_ChooseSocket(cluster->qwdsocket, from, *from), msg.cursize, msg.data, *from); } +static void QTV_GetInfo(cluster_t *cluster, netadr_t *from, char *args) +{ + //ftemaster support + char challenge[256], tmp[64]; + char protocolname[MAX_QPATH]; + char buffer[8192]; + netmsg_t msg; + qboolean authed = false; + InitNetMsg(&msg, buffer, sizeof(buffer)); + + args = COM_ParseToken(args, challenge, sizeof(challenge), ""); + while((args = COM_ParseToken(args, tmp, sizeof(tmp), ""))) + { + if (!strncmp(tmp, "c=",2) && !strcmp(tmp+2, cluster->chalkey)) + authed = true; //they're able to read our outgoing packets. assume not intercepted (at least blocks spoofed packets). should really use (d)tls. this is more to protect our resources than anything else though, so doesn't need to be strong. + else if (!strncmp(tmp, "a=",2) && authed) + { + netadr_t adr; + if (NET_StringToAddr(tmp+2, &adr, 0)) + { //master told us our IP. we can use that to report to turn clients + if (((struct sockaddr*)&adr.sockaddr)->sa_family == AF_INET) + memcpy(cluster->turn_ipv4, &((struct sockaddr_in*)&adr.sockaddr)->sin_addr, 4); + else if (((struct sockaddr*)&adr.sockaddr)->sa_family == AF_INET6) + memcpy(cluster->turn_ipv6, &((struct sockaddr_in6*)&adr.sockaddr)->sin6_addr, 16); + } + } + } + COM_ParseToken(cluster->protocolname?cluster->protocolname:"FTE-Quake", protocolname, sizeof(protocolname), ""); //we can only report one, so report the first. + + //response packet header + WriteLong(&msg, ~0u); +// if (fullstatus) +// WriteString2(&msg, "statusResponse\n"); +// else + WriteString2(&msg, "infoResponse\n"); + + //first line contains the serverinfo, or some form of it + WriteString2(&msg, "\\*QTV\\"); WriteString2(&msg, QTV_VERSION_STRING); +// WriteString2(&msg, "\\*fp\\"); WriteString2(&msg, hash(cert)); + if (authed) + { //only reported to the master server to generate time-based auth tokens. + tobase64(tmp,sizeof(tmp), cluster->turnkey, sizeof(cluster->turnkey)); + WriteString2(&msg, "\\_turnkey\\"); WriteString2(&msg, tmp); + } + WriteString2(&msg, "\\challenge\\"); WriteString2(&msg, challenge); + WriteString2(&msg, "\\gamename\\"); WriteString2(&msg, protocolname); + snprintf(tmp, sizeof(tmp), "%i%s", cluster->protocolname?cluster->protocolver:3, "t"); //'w':quakeworld, 'n'/'d':netquake, 'x':qe, 't':qtv, 'r':turnrelay, 'f':fwd + WriteString2(&msg, "\\protocol\\"); WriteString2(&msg, tmp); + WriteString2(&msg, "\\clients\\"); WriteString2(&msg, "0"); + WriteString2(&msg, "\\sv_maxclients\\"); WriteString2(&msg, "0"); + WriteString2(&msg, "\\modname\\"); WriteString2(&msg, "QTV"); + WriteString2(&msg, "\\mapname\\"); WriteString2(&msg, "QTV"); + WriteString2(&msg, "\\hostname\\"); WriteString2(&msg, cluster->hostname); + snprintf(tmp, sizeof(tmp), "%i", cluster->tcplistenportnum); + WriteString2(&msg, "\\sv_port_tcp\\"); WriteString2(&msg, tmp); + + /*if (fullstatus) + { + client_t *cl; + char *start = resp; + + if (resp != response+sizeof(response)) + { + resp[-1] = '\n'; //replace the null terminator that we already wrote + + //on the following lines we have an entry for each client + for (i=0 ; istate == cs_connected || cl->state == cs_spawned || cl->name[0]) && !cl->spectator) + { + Q_strncpyz(resp, va( + "%d %d \"%s\" \"%s\"\n" + , + cl->old_frags, + SV_CalcPing(cl, false), + cl->team, + cl->name + ), sizeof(response) - (resp-response)); + resp += strlen(resp); + } + } + + *resp++ = 0; //this might not be a null + if (resp == response+sizeof(response)) + { + //we're at the end of the buffer, it's full. bummer + //replace 12 bytes with infoResponse + memcpy(response+4, "infoResponse", 12); + //move down by len(statusResponse)-len(infoResponse) bytes + memmove(response+4+12, response+4+14, resp-response-(4+14)); + start -= 14-12; //fix this pointer + + resp = start; + resp[-1] = 0; //reset the \n + } + } + }*/ + + WriteByte(&msg, 0); + + NET_SendPacket(cluster, NET_ChooseSocket(cluster->qwdsocket, from, *from), msg.cursize, msg.data, *from); +} void QTV_StatusResponse(cluster_t *cluster, char *msg, netadr_t *from) { @@ -1284,10 +1426,21 @@ void ConnectionlessPacket(cluster_t *cluster, netadr_t *from, netmsg_t *m) QTV_Status(cluster, from); return; } + if (!strncmp(buffer, "getinfo", 7)) + { + QTV_GetInfo(cluster, from, buffer+7); + return; + } if (!strncmp(buffer, "getchallenge", 12)) { - i = NewChallenge(from); - Netchan_OutOfBandPrint(cluster, *from, "c%i", i); + i = NewChallenge(cluster, from); + if (!cluster->relayenabled) + Netchan_OutOfBandPrint(cluster, *from, "c%i", i); + else + { //special response to say we don't support dtls, but can proxy it, so use dtlsconnect without needing to send any private info until the final target is determined. + snprintf(buffer, sizeof(buffer), "c%i%cDTLS\xff\xff\xff\xff", i, 0); //PROTOCOL_VERSION_DTLSUPGRADE + Netchan_OutOfBand(cluster, *from, strlen(buffer)+9, buffer); + } return; } if (!strncmp(buffer, "connect 28 ", 11)) @@ -1298,6 +1451,57 @@ void ConnectionlessPacket(cluster_t *cluster, netadr_t *from, netmsg_t *m) NewQWClient(cluster, from, buffer); return; } + if (!strncmp(buffer, "getserversExtResponse", 21) && cluster->pingtreeenabled) + { //q3-style serverlist response + m->readpos = 4+21; + Fwd_ParseServerList(cluster, m, -1); + return; + } + if (!strncmp(buffer, "d\n", 2) && cluster->pingtreeenabled) + { //legacy qw serverlist response + m->readpos = 4+2; + Fwd_ParseServerList(cluster, m, AF_INET); + return; + } + if (!strcmp(buffer, "l") && cluster->pingtreeenabled) + { //qw ping response + Fwd_PingResponse(cluster, from); + return; + } + if (!strncmp(buffer, "pingstatus", 10) && cluster->pingtreeenabled) + { + int ext = false; + char arg[64]; + if (buffer[10] == ' ') + { + char *s = buffer + 11; + while (*s) + { + s = COM_ParseToken(s, arg,sizeof(arg), ""); // + if (!strcmp(arg, "ext")) + ext = true; + } + } + Fwd_PingStatus(cluster, from, ext); + return; + } + if (!strncmp(buffer, "dtlsconnect ", 12) && cluster->relayenabled) + { //dtlsconnect challenge [finalip@middleip@targetip] + char challenge[64]; + char *s = COM_ParseToken(buffer+12, challenge,sizeof(challenge), ""); // + if (ChallengePasses(cluster, from, atoi(challenge))) + { + while(*s == ' ') + s++; + Fwd_NewQWFwd(cluster, from, s); //will send a challenge to the target. + //the relay code will pass the response to the client triggering a new dtlsconnect. + //eventually punching all the way through to the target which will respond with a dtlsopened. + //the client will then be free to send its dtls handshakes, with the server's certificate matched against the fingerprint reported by the master. + //this should ensure there's no tampering. + //note that we cannot read any disconnect hints when they're encrypted, so we'll be depending on timeouts (which also avoids malicious disconnect spoofs, yay?) + } + return; + } // if (buffer[0] == 'l' && (!buffer[1] || buffer[1] == '\n')) // { // Sys_Printf(cluster, "Ack from %s\n", ); @@ -4277,6 +4481,10 @@ void QW_ProcessUDPPacket(cluster_t *cluster, netmsg_t *m, netadr_t from) if (*(int*)m->data == -1) { //connectionless message + if (TURN_IsRequest(cluster, m, &from)) + return; + m->readpos = 0; + ConnectionlessPacket(cluster, &from, m); return; } @@ -4297,7 +4505,7 @@ void QW_ProcessUDPPacket(cluster_t *cluster, netmsg_t *m, netadr_t from) { if (v->netchan.isnqprotocol) { - if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 0)) + if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 1)) { if (NQNetchan_Process(cluster, &v->netchan, m)) { @@ -4314,6 +4522,7 @@ void QW_ProcessUDPPacket(cluster_t *cluster, netmsg_t *m, netadr_t from) QTV_Run(v->server); } } + return; } } else @@ -4355,15 +4564,20 @@ void QW_ProcessUDPPacket(cluster_t *cluster, netmsg_t *m, netadr_t from) QTV_Run(v->server); } } - break; + return; } } } - if (!v && cluster->allownqclients) + m->readpos = 0; + + if (TURN_IsRequest(cluster, m, &from)) + return; + m->readpos = 0; + + if (cluster->allownqclients) { unsigned int ctrl; //NQ connectionless packet? - m->readpos = 0; ctrl = ReadLong(m); ctrl = SwapLong(ctrl); if (ctrl & NETFLAG_CTL) @@ -4410,7 +4624,7 @@ void QW_ProcessUDPPacket(cluster_t *cluster, netmsg_t *m, netadr_t from) { if (v->netchan.isnqprotocol) { - if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 0)) + if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 1)) { Sys_Printf(cluster, "Dup connect from %s\n", v->name); v->drop = true; @@ -4446,7 +4660,11 @@ void QW_TCPConnection(cluster_t *cluster, oproxy_t *sock, char *initialstreamnam free(initialstreamname); } else - { + { //okay, we're adding this as a client + //try and disable nagle, we don't really want to be wasting time not sending anything. + int _true = 1; + setsockopt(sock->sock, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); + tc->sock = sock->sock; tc->websocket = sock->websocket; //copy it over @@ -4487,7 +4705,12 @@ void QW_UpdateUDPStuff(cluster_t *cluster) { if (NET_StringToAddr(cluster->master, &from, 27000)) { - sprintf(buffer, "a\n%i\n0\n", cluster->mastersequence++); //fill buffer with a heartbeat + if (cluster->turnenabled) + sprintf(buffer, "\377\377\377\377""heartbeat FTEMaster c=%s\n", cluster->chalkey); //fill buffer with a heartbeat + else if (cluster->protocolname) + sprintf(buffer, "\377\377\377\377""heartbeat Darkplaces\n"); //older, broader compatibility. + else + sprintf(buffer, "a\n%i\n0\n", cluster->mastersequence++); //fill buffer with a heartbeat //why is there no \xff\xff\xff\xff ?.. NET_SendPacket(cluster, NET_ChooseSocket(cluster->qwdsocket, &from, from), strlen(buffer), buffer, from); } @@ -4510,8 +4733,8 @@ void QW_UpdateUDPStuff(cluster_t *cluster) break; continue; } - from.tcpcon = NULL; - read = recvfrom(cluster->qwdsocket[socketno], buffer, sizeof(buffer), 0, (struct sockaddr*)&from.sockaddr, (unsigned*)&fromsize); + memset(&from, 0, sizeof(from)); + read = recvfrom(cluster->qwdsocket[socketno], buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&from.sockaddr, (unsigned*)&fromsize); if (read < 0) //it's bad. { @@ -4523,13 +4746,24 @@ void QW_UpdateUDPStuff(cluster_t *cluster) if (read <= 5) //otherwise it's a runt or bad. { - continue; + if (read == 1 && *buffer == 'l') + { //ffs. easier to just fix it up here. + buffer[0] = + buffer[1] = + buffer[2] = + buffer[3] = 0xff; + buffer[4] = 'l'; + read = 5; + } + else + continue; } m.cursize = read; m.data = buffer; m.readpos = 0; + buffer[m.cursize] = 0; //make sure its null terminated. QW_ProcessUDPPacket(cluster, &m, from); } diff --git a/fteqtv/rcon.c b/fteqtv/rcon.c index 5d8997132..7eb6597d4 100644 --- a/fteqtv/rcon.c +++ b/fteqtv/rcon.c @@ -410,6 +410,7 @@ void Cmd_Master(cmdctxt_t *ctx) void Cmd_UDPPort(cmdctxt_t *ctx) { int newp = atoi(Cmd_Argv(ctx, 1)); + ctx->cluster->qwlistenportnum = newp; NET_InitUDPSocket(ctx->cluster, newp, SG_IPV6); NET_InitUDPSocket(ctx->cluster, newp, SG_IPV4); } @@ -600,9 +601,13 @@ void Cmd_Say(cmdctxt_t *ctx) void Cmd_Status(cmdctxt_t *ctx) { Cmd_Printf(ctx, "QTV Status:\n"); - Cmd_Printf(ctx, " %i sources\n", ctx->cluster->numservers); - Cmd_Printf(ctx, " %i viewers\n", ctx->cluster->numviewers); - Cmd_Printf(ctx, " %i proxies\n", ctx->cluster->numproxies); + Cmd_Printf(ctx, " %i sources%s\n", ctx->cluster->numservers, ctx->cluster->nouserconnects?" (admin only)":" (user allowed)"); + Cmd_Printf(ctx, " %i udp clients %s\n", ctx->cluster->numviewers, ctx->cluster->allownqclients?" (qw+nq)":" (qw only)"); + if (ctx->cluster->maxproxies) + Cmd_Printf(ctx, " %i tcp clients (of %i)\n", ctx->cluster->numproxies, ctx->cluster->maxproxies); + else + Cmd_Printf(ctx, " %i tcp clients\n", ctx->cluster->numproxies); + TURN_RelayStatus(ctx); Cmd_Printf(ctx, "Common Options:\n"); Cmd_Printf(ctx, " Hostname %s\n", ctx->cluster->hostname); @@ -610,7 +615,7 @@ void Cmd_Status(cmdctxt_t *ctx) if (ctx->cluster->chokeonnotupdated) Cmd_Printf(ctx, " Choke\n"); if (ctx->cluster->lateforward) - Cmd_Printf(ctx, " Late forwarding\n"); + Cmd_Printf(ctx, " Late forwarding (delayed streams)\n"); if (!ctx->cluster->notalking) Cmd_Printf(ctx, " Talking allowed\n"); if (ctx->cluster->nobsp) @@ -621,7 +626,6 @@ void Cmd_Status(cmdctxt_t *ctx) Cmd_Printf(ctx, " tcp port %i\n", ctx->cluster->tcplistenportnum); if (ctx->cluster->qwdsocket[SG_IPV4] != INVALID_SOCKET || ctx->cluster->qwdsocket[SG_IPV6] != INVALID_SOCKET) Cmd_Printf(ctx, " udp port %i\n", ctx->cluster->qwlistenportnum); - Cmd_Printf(ctx, " user connections are %sallowed\n", ctx->cluster->nouserconnects?"NOT ":""); Cmd_Printf(ctx, "\n"); @@ -1227,7 +1231,110 @@ void Cmd_Watch(cmdctxt_t *ctx) } #endif +#ifdef __linux__ +#include +qboolean Sys_RandomBytes(unsigned char *out, int len) +{ + qboolean res; + int fd = open("/dev/urandom", 0); + res = (read(fd, out, len) == len); + close(fd); + return res; +} +#else +qboolean Sys_RandomBytes(unsigned char *out, int len) +{ + return false; +} +#endif +static void Cmd_Turn(cmdctxt_t *ctx) +{ + if (Cmd_Argc(ctx) < 2) + { + if (ctx->cluster->turnenabled && ctx->cluster->turn_minport) + Cmd_Printf(ctx, "turn is enabled, using ports %i-%i\n", ctx->cluster->turn_minport, ctx->cluster->turn_maxport); + else if (ctx->cluster->turnenabled) + Cmd_Printf(ctx, "turn is enabled, using ephemerial ports\n"); + else + Cmd_Printf(ctx, "turn is disabled\n"); + return; + } + if (!Cmd_IsLocal(ctx)) + { + Cmd_Printf(ctx, "turn support may not be configured remotely\n"); + return; + } + + if (Cmd_Argc(ctx) >= 3) + { //two args - assume a two number range, so turn it on. + ctx->cluster->turnenabled = true; + ctx->cluster->turn_minport = atoi(Cmd_Argv(ctx, 1)); + ctx->cluster->turn_maxport = atoi(Cmd_Argv(ctx, 2)); + } + else if ( atoi(Cmd_Argv(ctx, 1))) //a boolean. turn it back on.. + ctx->cluster->turnenabled = true; //switch it back on with whatever port range it previously had. probably 0-0 for ephemerial. probably bad for the relay's firewalls... + else + ctx->cluster->turnenabled = false; //and off. + + if (!*ctx->cluster->chalkey && ctx->cluster->turnenabled) + { + unsigned char chalkey[12]; + if (!Sys_RandomBytes(chalkey, sizeof(chalkey)) || + !Sys_RandomBytes(ctx->cluster->turnkey, sizeof(ctx->cluster->turnkey))) + { + Cmd_Printf(ctx, "no random generator\n"); + ctx->cluster->turnenabled = false; + return; + } + tobase64(ctx->cluster->chalkey,sizeof(ctx->cluster->chalkey), chalkey, sizeof(chalkey)); + } + + if (ctx->cluster->turnenabled && ctx->cluster->turn_minport) + Cmd_Printf(ctx, "turn keys updated, using ports %i-%i\n", ctx->cluster->turn_minport, ctx->cluster->turn_maxport); + else if (ctx->cluster->turnenabled) + Cmd_Printf(ctx, "turn keys updated, using ephemerial ports\n"); + else + Cmd_Printf(ctx, "turn disabled\n"); +} +static void Cmd_Relay(cmdctxt_t *ctx) +{ + if (Cmd_Argc(ctx) >= 2) + { + if (Cmd_IsLocal(ctx)) + { + Cmd_Printf(ctx, "relay support may not be configured remotely\n"); + return; + } + switch(atoi(Cmd_Argv(ctx, 1))) + { + case 0: + ctx->cluster->relayenabled = ctx->cluster->pingtreeenabled = false; + Cmd_Printf(ctx, "turn disabled\n"); + break; + case 1: + ctx->cluster->relayenabled = ctx->cluster->pingtreeenabled = true; + break; + default: + ctx->cluster->relayenabled = true; + ctx->cluster->pingtreeenabled = false; + break; + } + } + + if (ctx->cluster->relayenabled && ctx->cluster->pingtreeenabled) + Cmd_Printf(ctx, "relay is enabled (with pinging)\n"); + else if (ctx->cluster->relayenabled) + Cmd_Printf(ctx, "relay is enabled, WITHOUT pinging\n"); + else + Cmd_Printf(ctx, "relay is disabled\n"); +} +static void Cmd_ProtocolName(cmdctxt_t *ctx) +{ + free(ctx->cluster->protocolname); + ctx->cluster->protocolname = strdup(Cmd_Argv(ctx, 1)); + ctx->cluster->protocolver = atoi(Cmd_Argv(ctx, 2)); +} typedef struct rconcommands_s { char *name; @@ -1301,6 +1408,11 @@ const rconcommands_t rconcommands[] = {"initialdelay",0, 1, Cmd_InitialDelay, "Specifies the duration for which new connections will be buffered. Large values prevents players from spectating their enemies as a cheap wallhack."}, {"slowdelay", 0, 1, Cmd_SlowDelay, "If a server is not sending enough data, the proxy will delay parsing for this long."}, + {"turn", 0, 1, Cmd_Turn, "Controls whether we accept turn requests."}, + {"relay", 0, 1, Cmd_Relay, "Controls whether we accept qwfwd-style relay requests."}, + {"qwfwd", 0, 1, Cmd_Relay}, + {"protocolname",0, 1, Cmd_ProtocolName, "Protocol Name:Version used to register with master."}, + {"halt", 1, 0, Cmd_Halt, "disables a stream, preventing it from reconnecting until someone tries watching it anew. Boots current spectators"}, {"disable", 1, 0, Cmd_Halt}, diff --git a/fteqtv/relay.c b/fteqtv/relay.c new file mode 100644 index 000000000..a21b9c110 --- /dev/null +++ b/fteqtv/relay.c @@ -0,0 +1,1373 @@ +/* +Copyright (C) 2024 'Spoike'. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the included (GNU.txt) GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* +we have two types of relay here. +1) TURN - blind(ish) forwarding. + standard protocol for use as ICE/WebRTC relays. + see TURN_IS_NOT_BLIND +2) qwfwd - compatible with qqshka's fork of the original qwfwd. + relay incercepts `prx` userinfo in the connect requests to forward to the real peer. + unlike qqshka's version we relay the next hop's extension handshakes instead of doing them on the proxy. + we can also relay 'dtlsconnect' requests. + for use with `connectbr` and compat with things that expect it. +*/ + +#include "qtv.h" +#include +#include +#include + +#include "bsd_string.h" + +#define QWFWDTIMEOUT (30*1000) //how long the relay will stay open for when doing qw-specific forwarding. +#define TURN_IS_NOT_BLIND //filters what's allowed through - we must have seen a stun binding request/response from the peer before we allow non-binding traffic through. This should help protect the owner's lan a bit. +#if defined(_DEBUG) && !defined(LIBQTV) //unsafe stuff I'm leaving enabled in debug builds cos debugging is painful when they're protected. + #define ALLOWPRIVATE //allow relaying to private lan addresses, so I can test with hosting on localhost +#endif + +#define QRY_SERVERLISTINTERVAL 60*1000 //interval between asking for server listings. +#define QRY_REPINGINTERVAL 60*1000 //don't ever spam any server faster than this. +#define QRY_PINGINTERVAL 100 //time between outgoing pings +#define QRY_TIMEOUT 20*60*1000 //servers will be removed if not seen in any server listings for this long. + + +static void Fwd_DoPing(cluster_t *cluster); +#define countof(x) (sizeof(x)/sizeof((x)[0])) + +struct turnclient_s +{ //this is the end that opened the connection + struct turnclient_s *next; + + SOCKET remotesock; //the udp socket we're sending to + struct { + unsigned int timeout; //reset to 5 mins on refresh. + netadr_t remoteaddr; +#ifdef TURN_IS_NOT_BLIND + qboolean seenstun; //this is for ICE support. so we can impose a rule that the peer MUST have sent a stun packet before we allow the client to send any non-stun packets. this reduces the chance of being abused for attacks. can potentially still ddos with stun, but shouldn't be an amplification at least. +#endif + } remotes[8]; + int udpsockid; //-1 for private tcp + SOCKET clientsock; //stoopid tcp clients... + netadr_t relayaddr; //yay for udp... + netadr_t clientaddr; //yay for udp... + unsigned int timeout; + char *username; //username they authed with. may not switch users once established. + char *auth; //auth token generated by the master (recomputed). + + unsigned char key[64]; + unsigned int keysize; + + qboolean isfwd; +}; + +typedef struct +{ + unsigned short msgtype; + unsigned short msglen; + unsigned int magiccookie; + unsigned int transactid[3]; +} stunhdr_t; +//class +#define STUN_REQUEST 0x0000 +#define STUN_REPLY 0x0100 +#define STUN_ERROR 0x0110 +#define STUN_INDICATION 0x0010 +//request +#define STUN_BINDING 0x0001 +#define STUN_ALLOCATE 0x0003 //TURN +#define STUN_REFRESH 0x0004 //TURN +#define STUN_SEND 0x0006 //TURN send indications contain data to forward +#define STUN_DATA 0x0007 //TURN data indications contain reply data. +#define STUN_CREATEPERM 0x0008 //TURN +#define STUN_CHANBIND 0x0009 //TURN + +//misc stuff... +#define STUN_MAGIC_COOKIE 0x2112a442 + +//attributes +#define STUNATTR_MAPPED_ADDRESS 0x0001 +//#define STUNATTR_RESPONSE_ADDRESS 0x0002 +//#define STUNATTR_CHANGE_REQUEST 0x0003 +//#define STUNATTR_SOURCE_ADDRESS 0x0004 +//#define STUNATTR_CHANGED_ADDRESS 0x0005 +#define STUNATTR_USERNAME 0x0006 +//#define STUNATTR_PASSWORD 0x0007 +#define STUNATTR_MSGINTEGRITIY_SHA1 0x0008 +#define STUNATTR_ERROR_CODE 0x0009 +//#define STUNATTR_UNKNOWN_ATTRIBUTES 0x000a +//#define STUNATTR_REFLECTED_FROM 0x000b +//#define STUNATTR_CHANNELNUMBER 0x000c //TURN +#define STUNATTR_LIFETIME 0x000d //TURN +//#define STUNATTR_ 0x000e +//#define STUNATTR_ 0x000f +//#define STUNATTR_ 0x0010 +//#define STUNATTR_ 0x0011 +#define STUNATTR_XOR_PEER_ADDRESS 0x0012 //TURN +#define STUNATTR_DATA 0x0013 //TURN +#define STUNATTR_REALM 0x0014 //TURN +#define STUNATTR_NONCE 0x0015 //TURN +#define STUNATTR_XOR_RELAYED_ADDRESS 0x0016 //TURN +#define STUNATTR_REQUESTED_ADDRFAM 0x0017 //TURN +//#define STUNATTR_EVEN_PORT 0x0018 //TURN +#define STUNATTR_REQUESTED_TRANSPORT 0x0019 //TURN +#define STUNATTR_DONT_FRAGMENT 0x001a //TURN +#define STUNATTR_MSGINTEGRITIY_SHA2_256 0x001c +#define STUNATTR_XOR_MAPPED_ADDRESS 0x0020 +//#define STUNATTR_ICE_PRIORITY 0x0024 //ICE +//#define STUNATTR_ICE_USE_CANDIDATE 0x0025 //ICE + +//0x8000 attributes are optional, and may be silently ignored without issue. +#define STUNATTR_ADDITIONAL_ADDRFAM 0x8000 //TURN -- listen for ipv6 in addition to ipv4 +#define STUNATTR_SOFTWARE 0x8022 //TURN +#define STUNATTR_FINGERPRINT 0x8028 +//#define STUNATTR_ICE_CONTROLLED 0x8029 //ICE +//#define STUNATTR_ICE_CONTROLLING 0x802A //ICE + +static void TURN_Send(cluster_t *cluster, netmsg_t *m, netadr_t *clientadr) +{ + netadr_t sockaddr; + SOCKET sock = NET_ChooseSocket(cluster->qwdsocket, &sockaddr, *clientadr); + unsigned char *bytes = m->data; + //update the size. + bytes[2] = (m->cursize-20)>>8; + bytes[3] = (m->cursize-20)&0xff; + //send it. + NET_SendPacket(cluster, sock, m->cursize, bytes, sockaddr); +} +static int TURN_FindPermission(cluster_t *cluster, struct turnclient_s *t, netadr_t *peeraddr) +{ //we only allow packets to/from authorised peers. + int i; + for (i = 0; i < countof(t->remotes); i++) + { + if ((signed int)(t->remotes[i].timeout-cluster->curtime) < 0) + continue; //look for a live one... + if (Net_CompareAddress(&t->remotes[i].remoteaddr,peeraddr, 0,1)) + return i; + } + return -1; +} +static qboolean TURN_Permissable(netadr_t *adr) +{ //we only allow packets to/from authorised peers. we don't worry about inbound ports to give symetric-nat peers a chance. + if (((struct sockaddr *)adr->sockaddr)->sa_family == AF_INET) + { + struct sockaddr_in *a = (struct sockaddr_in *)&adr->sockaddr; + unsigned int ip = a->sin_addr.s_addr; + if ((ip&BigLong(0xffff0000)) == BigLong(0xA9FE0000)) //169.254.x.x/16 + return false;//link-local + else if ((ip&BigLong(0xff000000)) == BigLong(0x0a000000)) //10.x.x.x/8 + return false;//private + else if ((ip&BigLong(0xff000000)) == BigLong(0x7f000000)) //127.x.x.x/8 + return false;//localhost + else if ((ip&BigLong(0xfff00000)) == BigLong(0xac100000)) //172.16.x.x/12 + return false;//private + else if ((ip&BigLong(0xffff0000)) == BigLong(0xc0a80000)) //192.168.x.x/16 + return false;//private +// else if ((ip&BigLong(0xffc00000)) == BigLong(0x64400000)) //100.64.x.x/10 +// return false;//CGNAT + else if (ip == BigLong(0x00000000)) //0.0.0.0/32 + return false;//inaddr_any + } + if (((struct sockaddr *)adr->sockaddr)->sa_family == AF_INET6) + { + struct sockaddr_in6 *a = (struct sockaddr_in6 *)&adr->sockaddr; + unsigned char *ip6 = a->sin6_addr.s6_addr; + if ((*(int*)ip6&BigLong(0xffc00000)) == BigLong(0xfe800000)) //fe80::/10 + return false;//link-local + else if ((*(int*)ip6&BigLong(0xfe000000)) == BigLong(0xfc00000)) //fc::/7 + return false;//ULA/private + else if (*(int*)ip6 == BigLong(0x20010000)) //2001::/32 + return false;//toredo + else if ((*(int*)ip6&BigLong(0xffff0000)) == BigLong(0x20020000)) //2002::/16 + return false;//6to4 + else if (memcmp(ip6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1", 16) == 0) //::1 + return false;//localhost + else if (memcmp(ip6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) == 0) //:: + return false;//inaddr_any + else if (memcmp(ip6, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12) == 0) //::ffff:x.y.z.w + return false; //no bypassing ipv4 checks + } + return true; +} +#ifdef ALLOWPRIVATE +static qboolean TURN_PermissableIgnore(netadr_t *adr) +{ + if (TURN_Permissable(adr)) + return true; + + //its private... but we're allowing it anyway - with warning + fprintf(stderr, "WARNING: Allowing relay to access private address\n"); + return true; +} +#define TURN_Permissable TURN_PermissableIgnore +#endif + +void TURN_AddFDs(cluster_t *cluster, fd_set *set, int *m) +{ + struct turnclient_s *t; + for (t = cluster->turns; t; t = t->next) + { + if (t->remotesock < FD_SETSIZE) + { + FD_SET(t->remotesock, set); + if (t->remotesock >= *m) + *m = t->remotesock+1; + } + } +} +static void TURN_AddXorAddress(netmsg_t *m, int atrtype, netadr_t *adr) +{ + unsigned short xoredport; + unsigned int xoredaddr; + unsigned char *xor = ((unsigned char*)m->data+4); + if (((struct sockaddr *)adr->sockaddr)->sa_family == AF_INET) + { + struct sockaddr_in *out = (struct sockaddr_in *)&adr->sockaddr; + WriteBigShort(m, atrtype); + WriteBigShort(m, 4+4); + WriteBigShort(m, 1); //ipv4 + xoredport = out->sin_port ^ *(unsigned short*)xor; WriteData(m, &xoredport, sizeof(xoredport)); + xoredaddr = out->sin_addr.s_addr ^ *(unsigned int*)xor; WriteData(m, &xoredaddr, sizeof(xoredaddr)); + } + if (((struct sockaddr *)adr->sockaddr)->sa_family == AF_INET6) + { + struct sockaddr_in6 *out = (struct sockaddr_in6 *)&adr->sockaddr; + if (((unsigned short*)&out->sin6_addr)[0] == 0 && + ((unsigned short*)&out->sin6_addr)[1] == 0 && + ((unsigned short*)&out->sin6_addr)[2] == 0 && + ((unsigned short*)&out->sin6_addr)[3] == 0 && + ((unsigned short*)&out->sin6_addr)[4] == 0 && + ((unsigned short*)&out->sin6_addr)[5] == 0xffff) + { //we're listening on a hybrid socket, so if we get an ipv4 packet reply with a proper ipv4 address. + WriteBigShort(m, atrtype); + WriteBigShort(m, 4+4); + WriteBigShort(m, 1); //ipv4 + xoredport = out->sin6_port ^ *(unsigned short*)xor; WriteData(m, &xoredport, sizeof(xoredport)); + xoredaddr = ((unsigned int*)&out->sin6_addr)[3] ^ *(unsigned int*)xor; WriteData(m, &xoredaddr, sizeof(xoredaddr)); + } + else + { + WriteBigShort(m, atrtype); + WriteBigShort(m, 4+16); + WriteBigShort(m, 2); //ipv6 + xoredport = out->sin6_port ^ *(unsigned short*)xor; WriteData(m, &xoredport, sizeof(xoredport)); + xoredaddr = ((unsigned int*)&out->sin6_addr)[0] ^ ((unsigned int*)xor)[0]; WriteData(m, &xoredaddr, sizeof(xoredaddr)); + xoredaddr = ((unsigned int*)&out->sin6_addr)[1] ^ ((unsigned int*)xor)[1]; WriteData(m, &xoredaddr, sizeof(xoredaddr)); + xoredaddr = ((unsigned int*)&out->sin6_addr)[2] ^ ((unsigned int*)xor)[2]; WriteData(m, &xoredaddr, sizeof(xoredaddr)); + xoredaddr = ((unsigned int*)&out->sin6_addr)[3] ^ ((unsigned int*)xor)[3]; WriteData(m, &xoredaddr, sizeof(xoredaddr)); + } + } +} +static void TURN_ReadXorAddress(netmsg_t *m, netadr_t *adr) +{ + int type = ReadBigShort(m)&0xff; + unsigned char *xor = ((unsigned char*)m->data+4); + memset(adr, 0, sizeof(*adr)); + if (type == 1) + { + struct sockaddr_in *out = (struct sockaddr_in *)&adr->sockaddr; + memset(out, 0, sizeof(*out)); + out->sin_family = AF_INET; + out->sin_port = *(unsigned short*)((unsigned char*)m->data+m->readpos) ^ *(unsigned short*)xor; + m->readpos += 2; + out->sin_addr.s_addr = *(unsigned int*)((unsigned char*)m->data+m->readpos) ^ *(unsigned int*)xor; + m->readpos += 4; + } + else if (type == 2) + { + struct sockaddr_in6 *out = (struct sockaddr_in6 *)&adr->sockaddr; + memset(out, 0, sizeof(*out)); + out->sin6_family = AF_INET6; + out->sin6_port = *(unsigned short*)((unsigned char*)m->data+m->cursize) ^ *(unsigned short*)xor; + m->readpos += 2; + ((unsigned int*)&out->sin6_addr)[0] = ((unsigned int*)((unsigned char*)m->data+m->readpos))[0] ^ ((unsigned int*)xor)[0]; + ((unsigned int*)&out->sin6_addr)[1] = ((unsigned int*)((unsigned char*)m->data+m->readpos))[1] ^ ((unsigned int*)xor)[1]; + ((unsigned int*)&out->sin6_addr)[2] = ((unsigned int*)((unsigned char*)m->data+m->readpos))[2] ^ ((unsigned int*)xor)[2]; + ((unsigned int*)&out->sin6_addr)[3] = ((unsigned int*)((unsigned char*)m->data+m->readpos))[3] ^ ((unsigned int*)xor)[3]; + m->readpos += 16; + } +} +#ifdef TURN_IS_NOT_BLIND +static qboolean TURN_PacketIsStun(unsigned char *msg, size_t sz) +{ + //used to check if a packet going through the relay is a stun packet. + //we require the peer to 'reply' with a stun packet before we allow non-stun packets through. + //this prevents us from being used to attack other services, for the most part. + if (sz < 20) + return false; //too small for even just the header + if ((((msg[0]<<8)|msg[1])&~STUN_ERROR) != STUN_BINDING) + return false; //not valid for opening the connection... don't let turn allocation requests through... + if (((msg[2]<<8)|(msg[3]<<0)) != sz-20) + return false; //bad size + if (((msg[4]<<24)|(msg[5]<<16)|(msg[6]<<8)|(msg[7]<<0)) != STUN_MAGIC_COOKIE) + return false; //could actually be stun, but w/e + + //that's probably enough checks. + return true; +} +static qboolean TURN_PacketIsBanned(unsigned char *msg, size_t sz) +{ + //if it looks like a stun packet, and isn't a binding, assume its a TURN packet and block it. no nesting turn packets over turn. + if (sz >= 20) + if (((msg[4]<<24)|(msg[5]<<16)|(msg[6]<<8)|(msg[7]<<0)) == STUN_MAGIC_COOKIE) + if (((msg[0]<<8)|msg[1]) != STUN_BINDING) + if (((msg[2]<<8)|(msg[3]<<0)) == sz-20) + return true; + + if (TURN_PacketIsStun(msg,sz)) + return false; //allow the ICE probes. + if (sz && (msg[0] >= 20 && msg[0] <= 63)) + return false; //always allow dtls + return true; //block everything else. we're expecting these to be run by arbitrary third parties, so unencrypted stuff is --ed. +} +#endif + +void TURN_CheckFDs(cluster_t *cluster) +{ + int ofs = 20 + 4+20 + 4; + char buf[8192]; + int len; + int addrlen; + struct turnclient_s *t, **link; + netadr_t from = {NULL}; + +//FIXME: use epoll or something. this loop is stupid. + for (link = &cluster->turns; (t = *link);) + { + if ((int)(t->timeout-cluster->curtime) < 0) + { //check timeouts, kill if expired. + cluster->numrelays--; +//printf("relay %s timeout\n", t->username); + *link = t->next; //remove it + closesocket(t->remotesock); + free(t); + continue; + } + + link = &t->next; + addrlen = sizeof(from.sockaddr); + len = recvfrom(t->remotesock, buf+ofs, sizeof(buf)-ofs, 0, (struct sockaddr*)&from.sockaddr, &addrlen); + if (len > 0) + { + int perm = TURN_FindPermission(cluster, t, &from); + if (perm < 0) + { +//Sys_Printf(cluster, "TURN: (inbound) peer not authorised\n"); + continue; + } + + if (t->isfwd) + { //just directly forward it to the client. + netadr_t fup; + if (len > 4 && *(unsigned int*)(buf+ofs) == ~0) + t->remotes[perm].seenstun = true; //proper out of band packets, woo... assume its okay. + else if (!t->remotes[perm].seenstun) + continue; //err... its giving weird responses... + NET_SendPacket(cluster, NET_ChooseSocket(cluster->qwdsocket, &fup, t->clientaddr), len, buf+ofs, fup); + continue; + } + +#ifdef TURN_IS_NOT_BLIND + if (!t->remotes[perm].seenstun) + { + if (TURN_PacketIsStun(buf+ofs, len)) + t->remotes[perm].seenstun = true; //its an ICE/stun binding packet. peer is a genuine ice peer, open it up. + else + continue; //don't let anything through at all until we've seen a stun-binding packet from the peer (required as part of ICE). + } + else if (TURN_PacketIsBanned(buf+ofs, len)) + continue; +#endif + + { + netmsg_t o = {0}; + o.maxsize = ofs; + ofs -= 4; + if (((struct sockaddr*)from.sockaddr)->sa_family == AF_INET) + ofs -= 4+8; + else if (((struct sockaddr*)from.sockaddr)->sa_family == AF_INET6) + ofs -= 4+20; + ofs -= 20; + o.maxsize -= ofs; + o.data = buf+ofs; + o.cursize=0; + WriteBigShort(&o, STUN_DATA|STUN_INDICATION); + WriteBigShort(&o, 0); //size (filled in later) + WriteBigLong(&o, STUN_MAGIC_COOKIE); + WriteBigLong(&o, 0); + WriteBigLong(&o, 0); + WriteBigLong(&o, 0); + TURN_AddXorAddress(&o, STUNATTR_XOR_PEER_ADDRESS, &from); + WriteBigShort(&o, STUNATTR_DATA); + WriteBigShort(&o, len); + //should be at our original write pos now... + o.cursize += len; + o.maxsize = sizeof(buf)-ofs; + while(o.cursize&3) + WriteByte(&o, 0); //pad it to 4 bytes, to make chrome happy. + + TURN_Send(cluster, &o, &t->clientaddr); + } + } + } + + Fwd_DoPing(cluster); +} + +static struct turnclient_s *TURN_Allocate(cluster_t *cluster, netadr_t *clientadr, int fam, const char *username, const char *realm) +{ + unsigned char dig[64]; + struct turnclient_s *t; + SOCKET sock; + + size_t i; + int pf; + struct sockaddr *address; + struct sockaddr_in address4; + struct sockaddr_in6 address6; + socklen_t addrlen; + + unsigned long nonblocking = true; + unsigned long v6only = false; + unsigned short *port; + unsigned int tries = 5; + + switch(fam) + { + case 2: + pf = PF_INET6; + memset(&address6, 0, sizeof(address6)); + address6.sin6_family = AF_INET6; + port = &address6.sin6_port; + address = (struct sockaddr*)&address6; + addrlen = sizeof(address6); + break; + case 1: + pf = PF_INET; + address4.sin_family = AF_INET; + address4.sin_addr.s_addr = INADDR_ANY; + port = &address4.sin_port; + address = (struct sockaddr*)&address4; + addrlen = sizeof(address4); + break; + default: + return NULL; //erk + } + if ((sock = socket (pf, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + return NULL; + +#if defined(FD_SETSIZE) && !defined(HAVE_EPOLL) + if (sock >= FD_SETSIZE) + { //'select' cannot cope with this fd. dom't bug out. + closesocket(sock); + return NULL; + } +#endif + + if (ioctlsocket (sock, FIONBIO, &nonblocking) == -1) + { + closesocket(sock); + return NULL; + } + + if (pf == AF_INET6) + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&v6only, sizeof(v6only)); + +#if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE) + //win32 is so fucked up + setsockopt(newsocket, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&_true, sizeof(_true)); +#endif + + for (tries = 5; ; tries--) + { + if (!realm) //qwfwd always uses ephemerial ports. they're outgoing and thus don't need special attention to work around the relay's firewall. + *port = 0; + else if (cluster->turn_maxport <= cluster->turn_minport) //something screwy + *port = htons(cluster->turn_minport); + else + *port = htons(cluster->turn_minport + rand()%(cluster->turn_maxport-cluster->turn_minport)); //pick a random port from the allowed range (constrained by firewall/router settings). + if (bind (sock, (void *)address, addrlen) != -1) + break; //success. + + if (tries <= 0 || *port==0) + { //ran out of attempts to pick a random usable port. + closesocket(sock); + return NULL; + } + } + + t = calloc(1, sizeof(*t) + 64 + strlen(username)+1); + t->remotesock = sock; + + memcpy(t->relayaddr.sockaddr, address, addrlen); + getsockname(sock, (struct sockaddr*)t->relayaddr.sockaddr, &addrlen); //find how it was actually bound + + //report the public address reported by the master. + if (((struct sockaddr *)t->relayaddr.sockaddr)->sa_family == AF_INET) + memcpy(&((struct sockaddr_in *)t->relayaddr.sockaddr)->sin_addr, cluster->turn_ipv4, sizeof(cluster->turn_ipv4)); + else if (((struct sockaddr *)t->relayaddr.sockaddr)->sa_family == AF_INET6) + memcpy(&((struct sockaddr_in6 *)t->relayaddr.sockaddr)->sin6_addr, cluster->turn_ipv6, sizeof(cluster->turn_ipv6)); + + t->clientaddr = *clientadr; + + t->timeout = cluster->curtime + 5*60*1000; + for (i = 0; i < countof(t->remotes); i++) + t->remotes[i].timeout = cluster->curtime - 1; //some time in the past. + + t->auth = (char*)(t+1); + t->username = t->auth+64; + strcpy(t->username, username); + tobase64(t->auth,64, dig, CalcHMAC(&hash_sha1, dig,sizeof(dig), t->username,strlen(t->username), cluster->turnkey,sizeof(cluster->turnkey))); + + //compute our 'long-term' key. stoopid md5. so we can send the right responses. + if (realm) + { + hashfunc_t *pwdhash = &hash_md5; + size_t usersz = strlen(t->username); + size_t realmsz = strlen(realm); + size_t authsz = strlen(t->auth); + char *tmpkey = alloca(usersz + realmsz + authsz + 3); + memcpy(tmpkey+0, t->username, usersz); + tmpkey[usersz] = ':'; + memcpy(tmpkey+usersz+1, realm, realmsz); + tmpkey[usersz+1+realmsz] = ':'; + memcpy(tmpkey+usersz+1+realmsz+1, t->auth, authsz); + tmpkey[usersz+1+realmsz+1+authsz] = '\0'; + t->keysize = CalcHash(pwdhash, t->key,sizeof(t->key), tmpkey,strlen(tmpkey)); + } + else + t->isfwd = true; + + cluster->numrelays++; + t->next = cluster->turns; + cluster->turns = t; + +#ifdef HAVE_EPOLL + { + struct epoll_event ev; + ev.data.ptr = t; + ev.events = EPOLLIN; + epoll_ctl(cluster->epfd, EPOLL_CTL_ADD, t->remotesock, &ev); + } +#endif + return t; +} + +static size_t TURN_GenerateNonce(netadr_t *adr, char *buf, size_t bufsize) +{ //needs to be reproducible. + //also pretty much needs to be base64. stoopid stun rules. + char key[64]; + size_t keysize; + hashfunc_t *func = &hash_sha1; //any. + void *ctx = alloca(func->contextsize); + if (sizeof(key) < func->digestsize) + return 0; //panic + func->init(ctx); + func->process(ctx, "weewoo", 6); + if (((struct sockaddr *)adr->sockaddr)->sa_family == AF_INET) + { + struct sockaddr_in *out = (struct sockaddr_in *)&adr->sockaddr; + func->process(ctx, (void*)out,sizeof(*out)); + } + else if (((struct sockaddr *)adr->sockaddr)->sa_family == AF_INET6) + { + struct sockaddr_in6 *out = (struct sockaddr_in6 *)&adr->sockaddr; + func->process(ctx, (void*)out,sizeof(*out)); + } + else + return 0; //shouldn't be possible... should probably put an assert here. + + func->terminate(key, ctx); + keysize = func->digestsize; + + tobase64(buf,bufsize, key,keysize); + return strlen(buf); +} +static qboolean TURN_ValidateNonce(unsigned char *nonce, netadr_t *adr) +{ + unsigned char buf[20]; + size_t insz = (nonce?nonce[-1]:0); //read the attribute's length. lazily. + size_t needsz = TURN_GenerateNonce(adr, buf, sizeof(buf)); + if (insz != needsz) + return false; //screwy size... reject it. + else + return !memcmp(nonce, buf, needsz); +} +static qboolean TURN_ValidateIntegrity(cluster_t *cluster, netmsg_t *m, const unsigned char *user, const unsigned char *realm, const unsigned char *integritycheck, const struct turnclient_s *t) +{ + int attroffset = (integritycheck-(unsigned char*)m->data); + unsigned char needintegrity[32]; + + size_t keysize; + unsigned char key[32]; + char authbuf[64]; + char *auth; + + hashfunc_t *pwdhash = &hash_md5; + hashfunc_t *hash; + if (((integritycheck[-4]<<8)|integritycheck[-3]) == STUNATTR_MSGINTEGRITIY_SHA1) + hash = &hash_sha1; +// else if (((integritycheck[-4]<<8)|integritycheck[-3]) == STUNATTR_MSGINTEGRITIY_SHA2_256) +// hash = &hash_sha2_256; + else + return false; //can't validate this + + if (((integritycheck[-2]<<8)|integritycheck[-1]) != hash->digestsize) + return false; //nope... screwy. + + if (t) + { //we already validated the username... make sure the connection stays using the same one. + if (strcmp(user, t->username)) + return false; //err... it changed? no, get lost. + auth = t->auth; //still using the same password, too + } + else + { //validate the username... we just check the timestamp bit. + time_t timestamp = strtoull(user, NULL, 0); + time_t now = time(NULL); + unsigned char dig[32]; + + if (timestamp > now+10) + return false; //clockskew? reject timestamps in the future... + if (timestamp+60*60 < now) + return false; //more than an hour old, you'll need to get a new authorisation. + + //figure out what the correct auth string should be. + keysize = CalcHMAC(&hash_sha1, dig,sizeof(dig), user,strlen(user), cluster->turnkey,sizeof(cluster->turnkey)); + tobase64(authbuf,sizeof(authbuf), dig, keysize); + auth = authbuf; //this should match what the client was told by the broker. + } + + //compute our 'long-term' key. stoopid md5. + { + size_t usersz = strlen(user); + size_t realmsz = strlen(realm); + size_t authsz = strlen(auth); + char *tmpkey = alloca(usersz + realmsz + authsz + 3); + memcpy(tmpkey+0, user, usersz); + tmpkey[usersz] = ':'; + memcpy(tmpkey+usersz+1, realm, realmsz); + tmpkey[usersz+1+realmsz] = ':'; + memcpy(tmpkey+usersz+1+realmsz+1, auth, authsz); + tmpkey[usersz+1+realmsz+1+authsz] = '\0'; + keysize = CalcHash(pwdhash, key,sizeof(key), tmpkey,strlen(tmpkey)); + } + + //message integrity is a bit annoying + ((unsigned char*)m->data)[2] = ((attroffset+hash->digestsize-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + ((unsigned char*)m->data)[3] = ((attroffset+hash->digestsize-20)>>0)&0xff; + attroffset-=4; //but the hash is to the start of the attribute's header + + //compute the hmac that we're actually checking, success if it matches. + keysize = CalcHMAC(hash, needintegrity, sizeof(needintegrity), m->data, attroffset, key,keysize); + return !memcmp(needintegrity, integritycheck, keysize); +} +static void TURN_AddIntegrity(cluster_t *cluster, netmsg_t *m, const struct turnclient_s *t) +{ + size_t keysize; + hashfunc_t *hash = &hash_sha1; + unsigned char integrity[64]; + unsigned int crc; + + if (t) + { + //message integrity is a bit annoying + ((unsigned char*)m->data)[2] = ((m->cursize+4+hash->digestsize-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + ((unsigned char*)m->data)[3] = ((m->cursize+4+hash->digestsize-20)>>0)&0xff; + keysize = CalcHMAC(hash, integrity, sizeof(integrity), m->data, m->cursize, t->key,t->keysize); + WriteBigShort(m, STUNATTR_MSGINTEGRITIY_SHA1); + WriteBigShort(m, keysize); + WriteData(m, integrity, keysize); + } + +#ifndef NO_ZLIB + ((unsigned char*)m->data)[2] = ((m->cursize+8-20)>>8)&0xff; //dummy length + ((unsigned char*)m->data)[3] = ((m->cursize+8-20)>>0)&0xff; + crc = crc32(0, m->data, m->cursize)^0x5354554e; + WriteBigShort(m, STUNATTR_FINGERPRINT); + WriteBigShort(m, sizeof(crc)); + WriteBigLong(m, crc); +#endif +} +static qboolean TURN_ValidateFingerprint(cluster_t *cluster, netmsg_t *m, unsigned int needcrc) +{ //this just verifies that its actually a correct non-corrupted STUN packet. otherwise assume some other multiplexed protocol. + unsigned int crc; + + if (m->cursize != m->readpos) + return false; //must be the last attribute. so don't actually need to fiddle the size. + + ((unsigned char*)m->data)[2] = (((m->readpos-20)>>8)&0xff); //restore the size (integrity might have fucked with it) + ((unsigned char*)m->data)[3] = (((m->readpos-20)>>0)&0xff); + crc = crc32(0, m->data, m->readpos-8)^0x5354554e; + if (needcrc == crc) + return true; //needs to match. + return false; //oops. +} +qboolean TURN_IsRequest(cluster_t *cluster, netmsg_t *m, netadr_t *from) +{ + stunhdr_t hdr; + unsigned short atr, len, prot; + unsigned int nextread; + unsigned int error = 0; + void *data; + int fam = 1; + size_t datasize; + size_t lifetime; + netadr_t peeraddr[8]; + int inpeers; + struct turnclient_s *t; + unsigned char *innonce, *inrealm, *inuser, *inintegrity; + unsigned char noncebuf[64]; + + //try and identify them first. + for (t = cluster->turns; t; t = t->next) + { + if (Net_CompareAddress(&t->clientaddr, from, 0, 1)) + { //forward it as-is... mostly. + if (!t->isfwd) //turn... check the packet for stun/turn stuff. we now know their connection. woo. + break; + if (m->cursize>=11 && !memcmp("\xff\xff\xff\xff""connect ", m->data, 11)) + { //connect ver qport challenge info + //we need to pop one proxy from the 'prx' list so the next proxy doesn't try to connect to itself. + char userinfo[1024]; + char prx[256]; + char *s = (char*)m->data+11; + char *at; + char *infostart; + size_t pre,infolen,trail; + + //parse it a bit + s = COM_ParseToken(s, noncebuf,sizeof(noncebuf), ""); //read the ver + if (atoi(noncebuf)!=28) + return true; //that protocol ain't supported... nor allowed. get lost. we won't be able to block it over dtls but we also shouldn't need to care (yay for using dtlsconnect instead, which leaks no userinfo beyond the target chain) + s = COM_ParseToken(s, noncebuf,sizeof(noncebuf), ""); //read the qport + infostart = s= COM_ParseToken(s, noncebuf,sizeof(noncebuf), ""); //read the challenge (probably not ours) + s = COM_ParseToken(s, userinfo,sizeof(userinfo), ""); //read the challenge (probably not ours) + + //change the userinfo + Info_ValueForKey(userinfo, "prx", prx,sizeof(prx)); + at = strrchr(prx, '@'); + if (!*prx) + return true; //err... no next hop? we're meant to be handling it? wtf? + else if (at) + *at = 0; //strip off the last proxy (the one we're meant to be proxying to) + else + *prx = 0; //clear it entirely. next hop is the final server. lucky client. + Info_SetValueForStarKey(userinfo, "prx", prx, sizeof(userinfo)); + //we could set some "*clientip" key, appending 'from', so the server can use that instead of realip mess, but that'd require the server to trust us when banning. + + //hack up the packet to include the new info (with any trailing data still in place, ready for the next hop to see. + pre = (infostart-(char*)m->data); + infolen = strlen(userinfo); + trail = m->cursize-(s-(char*)m->data); + m->cursize = pre+2+infolen+1+trail; + if (m->cursize > m->maxsize) + return true; //don't crash. + memmove(&((char*)m->data)[pre+infolen+3], s, trail); + ((char*)m->data)[pre] = ' '; + ((char*)m->data)[pre+1] = '\"'; + memcpy(&((char*)m->data)[pre+2], userinfo, infolen); + ((char*)m->data)[pre+2+infolen] = '\"'; + //should have wiped the userinfo part... + //((char*)m->data)[m->cursize] = 0; //for debugging. + } + else if (m->cursize>=15 && !memcmp("\xff\xff\xff\xff""dtlsconnect ", m->data, 15)) + { //dtlsconnect challenge target + //we need to strip the target. the connect will then be hidden from us. + char *peer = COM_ParseToken((char*)m->data+15, noncebuf,sizeof(noncebuf), ""); //read the peer's challenge. + char *at = strrchr(peer, '@'); + if (!*peer) + return true; //no next hop? wtf? + peer = (at?at:peer); + m->cursize = peer-(char*)m->data; //just truncate it. + + //while (*peer == '@' || *peer == ' ') + // peer++; + //NET_StringToAddr(peer, &peeraddr[0], 27500); + //if (!Net_CompareAddress(&peeraddr[0], t->remotes[0].remoteaddr)) + // return true; //tried connecting to somewhere else... doesn't make sense. + } + t->timeout = t->remotes[0].timeout = cluster->curtime + QWFWDTIMEOUT; //renew it from c2s packets. + if (!t->remotes[0].seenstun && m->cursize < 4 && *(unsigned int*)m->data!=~0) + return true; //don't allow it through until we get a proper response from the target to know its actually valid and not a ddos. + NET_SendPacket(cluster, t->remotesock, m->cursize, m->data, t->remotes[0].remoteaddr); + return true; + } + } + + if (m->cursize < 20 || !cluster->turnenabled) + return false; + hdr.msgtype = ReadBigShort(m); +// if (hdr.msgtype != STUN_ALLOCATE) +// return false; + hdr.msglen = ReadBigShort(m); + if (20+hdr.msglen != m->cursize) + return false; //nope... sized wrong. must be something else. + hdr.magiccookie = ReadBigLong(m); + if (hdr.magiccookie != STUN_MAGIC_COOKIE) + return false; //might be an older version, but we don't care. don't let it multiplex badly. + hdr.transactid[0] = ReadBigLong(m); + hdr.transactid[1] = ReadBigLong(m); + hdr.transactid[2] = ReadBigLong(m); + + t = NULL; + prot = 0; + lifetime = 0; + data = NULL; + datasize = 0; + inuser = inrealm = innonce = inintegrity = NULL; + inpeers = 0; + +//Sys_Printf(cluster, "TURN: msgtype %x\n", hdr.msgtype); + for(; m->readpos < m->cursize; m->readpos = nextread) + { + atr = ReadBigShort(m); + len = ReadBigShort(m); + nextread = m->readpos + ((len+3)&~3); + if (nextread > m->cursize) + return false; //nope, corrupt +//Sys_Printf(cluster, " TURN: attribute %04x\n", atr); + if (atr == STUNATTR_FINGERPRINT) + { +#ifdef NO_ZLIB + continue; +#else + if (!TURN_ValidateFingerprint(cluster, m, ReadBigLong(m))) + { +//Sys_Printf(cluster, " TURN: Invalid fingerprint\n"); + return false; + } +#endif + } + /*else if (atr == STUNATTR_MSGINTEGRITIY_SHA2_256 && (!inintegrity || (inintegrity[-4]<<8)|inintegrity[-3]==STUNATTR_MSGINTEGRITIY_SHA1)) + { + inintegrity = (char*)m->data + m->readpos; + }*/ + else if (inintegrity) + { +//Sys_Printf(cluster, " TURN: Ignoring post-integrity %04x\n", atr); + continue; //anything after the integrity must be ignored (except those things above). + } + else if (atr == STUNATTR_MSGINTEGRITIY_SHA1) + inintegrity = (char*)m->data + m->readpos; + else if (atr == STUNATTR_REQUESTED_TRANSPORT) + prot = ReadBigLong(m)>>24; + else if (atr == STUNATTR_LIFETIME) + lifetime = ReadBigLong(m)*1000; + else if (atr == STUNATTR_REQUESTED_ADDRFAM) + fam = ReadBigLong(m); + else if (atr == STUNATTR_ADDITIONAL_ADDRFAM) + { //optional + //fam = ReadBigLong(m); + } + else if (atr == STUNATTR_DONT_FRAGMENT) + ; + else if (atr == STUNATTR_SOFTWARE) + ; + else if (atr == STUNATTR_USERNAME) + inuser = (char*)m->data + m->readpos; + else if (atr == STUNATTR_REALM) + inrealm = (char*)m->data + m->readpos; + else if (atr == STUNATTR_NONCE) + innonce = (char*)m->data + m->readpos; + else if (atr == STUNATTR_DATA && hdr.msgtype == (STUN_INDICATION|STUN_SEND)) + { + data = (char*)m->data + m->readpos; + datasize = len; + } + else if (atr == STUNATTR_XOR_PEER_ADDRESS && ( + hdr.msgtype == (STUN_INDICATION|STUN_SEND) || + hdr.msgtype == STUN_CREATEPERM)) + { + if (inpeers < countof(peeraddr)) + TURN_ReadXorAddress(m, &peeraddr[inpeers++]); //FIXME: we should support multiple of these. + } + else if (atr & 0x8000) + { + continue; //unknown optional attributes + } + else + error = 420; + } + if (!error && prot != 17 && hdr.msgtype == STUN_ALLOCATE) //only udp supported between relay and remote peer. + error = 442; + + if (!error) + { + if (hdr.msgtype == STUN_CREATEPERM || hdr.msgtype == STUN_REFRESH || hdr.msgtype == STUN_ALLOCATE) + { + if (!inuser || !inrealm || !innonce || !inintegrity) + error = 401; //something is null... + else if (!TURN_ValidateNonce(innonce, from)) + error = 438; //'Stale Nonce' shouldn't really be happening... client somehow changed address? + else if (t && strcmp(inuser, t->username)) + error = 441; //'Wrong Credentials' - no changing usernames! + else if (!TURN_ValidateIntegrity(cluster, m, inuser, inrealm, inintegrity, t)) + { +//Sys_Printf(cluster, " TURN: Validation failed\n"); + error = 401; //'Unauthorised' + } + else if (!t && hdr.msgtype == STUN_ALLOCATE) + { + t = TURN_Allocate(cluster, from, fam, inuser, inrealm); + if (!t) + error = 508; //'Insufficient Capacity' + } + } + } + if (!t && !error) + error = 437; //'Allocation Mismatch' + + if (hdr.msgtype == STUN_ALLOCATE || hdr.msgtype == STUN_REFRESH || hdr.msgtype == STUN_CREATEPERM) + { + struct{ + stunhdr_t hdr; + unsigned int data[64]; + } pkt; + netmsg_t o = {0, 0, sizeof(pkt), &pkt}; + + if (t && !error && hdr.msgtype == STUN_CREATEPERM) + { //FIXME: request could include multiple peers + int i, p; + for (p = 0; p < inpeers; p++) + if (!TURN_Permissable(&peeraddr[p])) + error = 403; //no relaying to localhost/etc. no bypassing firewalls. + if (!error) + { + for (p = 0; p < inpeers; p++) + { + for (i = 0; i < countof(t->remotes); i++) + { + if ((signed int)(t->remotes[i].timeout-cluster->curtime) < 0) + continue; //look for a live one... + if (Net_CompareAddress(&t->remotes[i].remoteaddr, &peeraddr[p], 0,1)) + { + t->remotes[i].timeout = cluster->curtime + 5*60*1000; //bump it. + break; + } + } + if (i == countof(t->remotes)) + { + for (i = 0; i < countof(t->remotes); i++) + { + if ((signed int)(t->remotes[i].timeout-cluster->curtime) < 0) + { //this one's dead... + t->remotes[i].remoteaddr = peeraddr[p]; + t->remotes[i].timeout = cluster->curtime + 5*60*1000; + break; + } + } + if (i == countof(t->remotes)) + error = 508; //'Insufficient Capacity' + } + } + } + } + + if (error) + WriteBigShort(&o, STUN_ERROR|hdr.msgtype); + else + WriteBigShort(&o, STUN_REPLY|hdr.msgtype); + WriteBigShort(&o, 0); + WriteBigLong(&o, hdr.magiccookie); + WriteBigLong(&o, hdr.transactid[0]); + WriteBigLong(&o, hdr.transactid[1]); + WriteBigLong(&o, hdr.transactid[2]); + if (error) + { + //WriteBigShort(STUNATTR_SOFTWARE); + WriteBigShort(&o, STUNATTR_ERROR_CODE); + WriteBigShort(&o, 4); + WriteBigLong(&o, ((error/100)<<8) | (error%100)); //this is stupid. + if (error == 420) + ;//WriteBigShort(STUNATTR_UNKNOWN_ATTRIBUTES); + + if ((error == 401 && !innonce) || error == 438) + { + char *realm = "fteqtv"; + size_t sz = TURN_GenerateNonce(from, noncebuf, sizeof(noncebuf)); + WriteBigShort(&o, STUNATTR_NONCE); + WriteBigShort(&o, sz); + WriteData(&o, noncebuf, sz); + while (o.cursize&3) + WriteByte(&o, 0); + + sz = strlen(realm); + WriteBigShort(&o, STUNATTR_REALM); + WriteBigShort(&o, sz); + WriteData(&o, realm, sz); + while (o.cursize&3) + WriteByte(&o, 0); + } + } + else if (t) + { + if (hdr.msgtype == STUN_ALLOCATE || hdr.msgtype == STUN_REFRESH) + { + if (lifetime > 5*60*1000) + lifetime = 5*60*1000; + if (lifetime && t->timeout < lifetime) + t->timeout = lifetime; + } + lifetime = t->timeout; + //WriteBigShort(STUNATTR_SOFTWARE); + if (hdr.msgtype == STUN_ALLOCATE || hdr.msgtype == STUN_REFRESH) + { + WriteBigShort(&o, STUNATTR_LIFETIME); + WriteBigShort(&o, 4); + WriteBigLong(&o, lifetime/1000); + } + if (hdr.msgtype == STUN_ALLOCATE) + { + TURN_AddXorAddress(&o, STUNATTR_XOR_RELAYED_ADDRESS, &t->relayaddr); + TURN_AddXorAddress(&o, STUNATTR_XOR_MAPPED_ADDRESS, from); + } + if (hdr.msgtype == STUN_ALLOCATE || hdr.msgtype == STUN_REFRESH || hdr.msgtype == STUN_CREATEPERM) + TURN_AddIntegrity(cluster, &o, t); + } + else + return true; //wut? + + TURN_Send(cluster, &o, from); + return true; + } + else if (hdr.msgtype == (STUN_INDICATION|STUN_SEND)) + { + //just sending to the other end + if (t && inpeers == 1) + { + int perm = TURN_FindPermission(cluster, t, &peeraddr[0]); //sends do NOT refresh. + if (perm >= 0) + { +#ifdef TURN_IS_NOT_BLIND + if (!t->remotes[perm].seenstun && !TURN_PacketIsStun(data, datasize)) + ; //don't relay non-stun packets until we know the peer is running a stun server too. + else if (TURN_PacketIsBanned(data, datasize)) + ; + else +#endif + NET_SendPacket(cluster, t->remotesock, datasize, data, peeraddr[0]); + } + } + return true; + } + else + return false; //dunno what that rubbish is. +} + +void Fwd_NewQWFwd(cluster_t *cluster, netadr_t *from, char *targ) +{ + char *rechallenge = "\xff\xff\xff\xffgetchallenge\n"; + struct turnclient_s *t; + netadr_t adr; + char *at = strrchr(targ, '@'); + if (at) + targ = at+1; //we only care about the next hop here, not the full route. + + if (NET_StringToAddr(targ, &adr, 27500)) + { + if (!cluster->relayenabled) + { + Netchan_OutOfBandPrint(cluster, *from, "n" "Relay not enabled.\n"); + return; //don't allow it. + } + else if (!TURN_Permissable(&adr)) + { //don't route to 127.* or 192.168.* etc. + Netchan_OutOfBandPrint(cluster, *from, "n" "Target address is private\n"); + return; + } + else + t = TURN_Allocate(cluster, from, (((struct sockaddr*)adr.sockaddr)->sa_family==AF_INET6)?2:1, "fwd", NULL); + if (t) + { + t->remotes[0].remoteaddr = adr; + t->remotes[0].timeout = cluster->curtime + QWFWDTIMEOUT; + +//Netchan_OutOfBandPrint(cluster, *from, "n" "relay established\n"); + + //send a quick challenge to the remote as if it came from the client. the client can then deal with it when it comes back, instead of waiting for the client's next getchallenge timeout. + NET_SendPacket(cluster, t->remotesock, strlen(rechallenge), rechallenge, t->remotes[0].remoteaddr); + + return; + } + else + Netchan_OutOfBandPrint(cluster, *from, "n" "Unable to set up relay\n"); //ran out of FDs? + } + else + Netchan_OutOfBandPrint(cluster, *from, "n" "Unanle to resolve target address: %s\n", targ); +} + +struct relaypeer_s +{ + netadr_t adr; + unsigned int lastalive; //last time it was reported by a master (removed after timeout) + unsigned int lastpong; //when we last saw a response from them + unsigned int lastping; //timestamp we last sent a ping + unsigned int curping; //ping time from last pong (or PING_UNRESPONSIVE) +#define PING_UNRESPONSIVE (~0u) + struct relaypeer_s *next; +}; +static void Fwd_AddPeer(cluster_t *cluster, netadr_t *a) +{ + struct relaypeer_s *rp; + //this loop could be improved with a hash table. + for (rp = cluster->relaypeer; rp; rp = rp->next) + { + if (Net_CompareAddress(&rp->adr, a, 0, 1)) + break; + } + + if (!rp) + { //add it... + if (!TURN_Permissable(a)) + return; //unless its a private address. + rp = malloc(sizeof(*rp)); + rp->adr = *a; + rp->lastpong = cluster->curtime; + rp->lastping = cluster->curtime+0x80000000; //something that's expired. + rp->curping = PING_UNRESPONSIVE; //don't know yet. + rp->next = cluster->relaypeer; + cluster->relaypeer = rp; + + cluster->numpeers += 1; + } + + rp->lastalive = cluster->curtime; //mark as still alive. +} +void Fwd_ParseServerList(cluster_t *cluster, netmsg_t *m, int af) +{ //response from master + netadr_t a; + int j; + char t; + while(m->readpos < m->cursize) + { + if (af == AF_INET) + t = '\\'; + else if (af == AF_INET6) + t = '/'; + else + t = ReadByte(m); //fancy protocol that has leading ids + + if (t == '\\') + { //ipv4 + struct sockaddr_in *o = (struct sockaddr_in *)a.sockaddr; + if (m->readpos+6 > m->cursize) + break; //eof? + memset(&a, 0, sizeof(a)); + o->sin_family = AF_INET; + for (j = 0; j < 4; j++) + ((char*)&o->sin_addr)[j] = ReadByte(m); + ((char*)&o->sin_port)[0] = ReadByte(m); + ((char*)&o->sin_port)[1] = ReadByte(m); + } + else if (t == '/') + { //ipv6 + struct sockaddr_in6 *o = (struct sockaddr_in6 *)a.sockaddr; + if (m->readpos+18 > m->cursize) + break; //eof? + memset(&a, 0, sizeof(a)); + o->sin6_family = AF_INET6; + for (j = 0; j < 16; j++) + ((char*)&o->sin6_addr)[j] = ReadByte(m); + ((char*)&o->sin6_port)[0] = ReadByte(m); + ((char*)&o->sin6_port)[1] = ReadByte(m); + + continue; //pingstatus does not support ipv6, so don't bother pinging any. + } + else + break; //erk? + Fwd_AddPeer(cluster, &a); + } +} +void Fwd_PingResponse(cluster_t *cluster, netadr_t *from) +{ + struct relaypeer_s *rp; + //this loop could be improved with a hash table. + for (rp = cluster->relaypeer; rp; rp = rp->next) + { + if (Net_CompareAddress(&rp->adr, from, 0, 1)) + { + //we have no way to verify the peer. + //its probably better to allow an attacker to force a large ping rather than a low one. its more obvious. + //the real target should respond eventually giving an upper bound for the ping value. + //don't extend aliveness though, if a peer stops heartbeating then we just let it hide. only the master responses may extend its lifetime. + rp->curping = cluster->curtime - rp->lastping; + rp->lastpong = cluster->curtime; + return; + } + } +} +static void Fwd_DoPing(cluster_t *cluster) +{ + struct relaypeer_s *rp, **l; + if (!cluster->pingtreeenabled || !cluster->relayenabled) + return; //don't spam at all. + if (cluster->curtime-cluster->relay_lastping < QRY_PINGINTERVAL) + return; //don't burst + cluster->relay_lastping = cluster->curtime; + + if (cluster->curtime-cluster->relay_lastquery >= QRY_SERVERLISTINTERVAL) + { + netadr_t adr; + cluster->relay_lastquery = cluster->curtime; + if (NET_StringToAddr(cluster->master, &adr, 27950)) + { + if (((struct sockaddr_in *)adr.sockaddr)->sin_family == AF_INET && + ((struct sockaddr_in *)adr.sockaddr)->sin_port == htons(27000)) + { + netadr_t realadr; + NET_SendPacket (cluster, NET_ChooseSocket(cluster->qwdsocket, &realadr, adr), 2, "c\n", realadr); //legacy. screwy. no 0xff prefix. + } + else + Netchan_OutOfBandPrint(cluster, adr, "getserversExt %s %u empty full"/*" ipv6"*/, cluster->protocolname, cluster->protocolver); + } + return; //space it out with pings. + } + + for (l = &cluster->relaypeer; (rp=*l);) + { + if (cluster->curtime-rp->lastping >= QRY_REPINGINTERVAL) //its been long enough since this one was pinged... + { + if (cluster->curtime - rp->lastalive > QRY_TIMEOUT) + { //its been too long. cut our losses. its dead jim. + *l = rp->next; + free(rp); + cluster->numpeers -= 1; + continue; + } + + //okay, we're pinging this one. + rp->lastping = cluster->curtime; + Netchan_OutOfBandPrint(cluster, rp->adr, "k"); //tiny unrealistic ping... + + //unlink it... walk em till the end, and then insert there. if there's 10000 servers then it'll just ping slower... much slower... and fail to report them all in a single udp packet, but hey. + for (*l = rp->next; *l; l = &(*l)->next) + ; + *l = rp; + rp->next = NULL; + + return; //only ping one. + } + l = &rp->next; + } +} +void Fwd_PingStatus(cluster_t *cluster, netadr_t *from, qboolean ext) +{ + struct relaypeer_s *rp; + char buffer[8192]; + netmsg_t send; + netadr_t fixup; + int j; + int resetsize; + + if (!cluster->pingtreeenabled || !cluster->relayenabled) + return; //don't bother responding. + + InitNetMsg (&send, buffer, sizeof(buffer)); + + //small prefix... + WriteLong (&send, -1); // -1 sequence means out of band + if (ext) + WriteString2 (&send, "pinglist"); + else + WriteByte (&send, 'n'); // 'a2c_print'... yeah, this is fucked. + + resetsize = send.cursize; + + for (rp = cluster->relaypeer; rp; rp = rp->next) + { + if (rp->lastping > 0x7fff) + continue; //don't report dead ones. don't do negatives either! + if (((struct sockaddr*)rp->adr.sockaddr)->sa_family == AF_INET) + { + struct sockaddr_in *a = ((struct sockaddr_in*)rp->adr.sockaddr); + if (send.cursize > 1400) + { + NET_SendPacket (cluster, NET_ChooseSocket(cluster->qwdsocket, &fixup, *from), send.cursize, send.data, fixup); + send.cursize = resetsize; + } + + if (ext) + WriteByte (&send, '\\'); + for (j = 0; j < 4; j++) + WriteByte (&send, ((char*)&a->sin_addr)[j]); + WriteShort(&send, ntohs(a->sin_port)); + WriteShort(&send, rp->curping); + } + else if (((struct sockaddr*)rp->adr.sockaddr)->sa_family == AF_INET6) + { + struct sockaddr_in6 *a = ((struct sockaddr_in6*)rp->adr.sockaddr); + if (send.cursize > 1400) + { + NET_SendPacket (cluster, NET_ChooseSocket(cluster->qwdsocket, &fixup, *from), send.cursize, send.data, fixup); + send.cursize = resetsize; + } + + if (ext) + WriteByte (&send, '/'); + else + continue; + for (j = 0; j < 16; j++) + WriteByte (&send, ((char*)&a->sin6_addr)[j]); + WriteShort(&send, ntohs(a->sin6_port)); + WriteShort(&send, rp->curping); + } + } + +// send the datagram + NET_SendPacket (cluster, NET_ChooseSocket(cluster->qwdsocket, &fixup, *from), send.cursize, send.data, fixup); +} + +void TURN_RelayStatus(cmdctxt_t *ctx) +{ + cluster_t *cluster = ctx->cluster; + unsigned int live=0, unreach=0, dead=0; + struct relaypeer_s *rp; + unsigned int turns=0, fwds=0; + turnclient_t *t; + for (t = cluster->turns; t; t = t->next) + { + if (t->isfwd) + fwds++; + else + turns++; + } + for (rp = cluster->relaypeer; rp; rp = rp->next) + { + if (rp->curping == PING_UNRESPONSIVE) + unreach++; //never got a response. + else if (cluster->curtime-rp->lastpong > 125*1000) + dead++; //no response in the last 2 mins + else + live++; + } + + Cmd_Printf(ctx, " %i relays (%i TURN%s, %i fwd%s)\n", cluster->numrelays, turns, cluster->turnenabled?"":"[OFF]", fwds, cluster->relayenabled?"":"[OFF]"); + if (ctx->cluster->turnenabled) + Cmd_Printf(ctx, " relaying through %i.%i.%i.%i %i-%i\n", ctx->cluster->turn_ipv4[0],ctx->cluster->turn_ipv4[1],ctx->cluster->turn_ipv4[2],ctx->cluster->turn_ipv4[3], ctx->cluster->turn_minport, ctx->cluster->turn_maxport); + else if (cluster->relayenabled) + Cmd_Printf(ctx, " relaying through %i.%i.%i.%i\n", ctx->cluster->turn_ipv4[0],ctx->cluster->turn_ipv4[1],ctx->cluster->turn_ipv4[2],ctx->cluster->turn_ipv4[3]); + if (cluster->relayenabled) + { + if (cluster->pingtreeenabled) + Cmd_Printf(ctx, " %i peers (%i live, %i stale, %i unreach)\n", cluster->numpeers, live, dead, unreach); + else + Cmd_Printf(ctx, " pinging disabled\n"); + } +} \ No newline at end of file diff --git a/fteqtv/source.c b/fteqtv/source.c index 37e4faedc..cf11c9634 100644 --- a/fteqtv/source.c +++ b/fteqtv/source.c @@ -109,8 +109,8 @@ qboolean NET_StringToAddr (char *s, netadr_t *sadr, int defaultport) } else #endif -#if 0//def IPPROTO_IPV6 - if (getaddrinfo) +#ifndef _WIN32 + if (1) {//ipv6 method (can return ipv4 addresses too) struct addrinfo *addrinfo, *pos; struct addrinfo udp6hint; @@ -124,12 +124,30 @@ qboolean NET_StringToAddr (char *s, netadr_t *sadr, int defaultport) udp6hint.ai_socktype = SOCK_DGRAM; udp6hint.ai_protocol = IPPROTO_UDP; - port = s + strlen(s); - while(port >= s) + if (*s == '[') { - if (*port == ':') - break; - port--; + s++; + colon = strchr(s, ']'); + if (!colon || colon-s >= sizeof(copy)) + return false; //too long to handle. + memcpy(copy, s, colon-s); + copy[colon-s] = 0; + colon++; + if (*colon == ':') + port = colon; + else + port = NULL; + s = copy; + } + else + { + port = s + strlen(s); + while(port >= s) + { + if (*port == ':') + break; + port--; + } } if (port == s) @@ -137,30 +155,31 @@ qboolean NET_StringToAddr (char *s, netadr_t *sadr, int defaultport) if (port) { len = port - s; - if (len > sizeof(dupbase)) - len = sizeof(dupbase); - strlcpy(dupbase, s, len); + if (len > sizeof(dupbase)-1) + len = sizeof(dupbase)-1; + memcpy(dupbase, s, len); + dupbase[len] = 0; error = getaddrinfo(dupbase, port+1, &udp6hint, &addrinfo); } else - error = EAI_NONAME; + error = EAI_NONAME, addrinfo=NULL; if (error) //failed, try string with no port. error = getaddrinfo(s, NULL, &udp6hint, &addrinfo); //remember, this func will return any address family that could be using the udp protocol... (ip4 or ip6) if (error) { return false; } - ((struct sockaddr*)sadr)->sa_family = 0; + ((struct sockaddr*)sadr->sockaddr)->sa_family = 0; for (pos = addrinfo; pos; pos = pos->ai_next) { switch(pos->ai_family) { case AF_INET6: - if (((struct sockaddr_in *)sadr)->sin_family == AF_INET6) + if (((struct sockaddr_in *)sadr->sockaddr)->sin_family == AF_INET6) break; //first one should be best... //fallthrough case AF_INET: - memcpy(sadr, addrinfo->ai_addr, addrinfo->ai_addrlen); + memcpy(sadr->sockaddr, addrinfo->ai_addr, addrinfo->ai_addrlen); if (pos->ai_family == AF_INET) goto dblbreak; //don't try finding any more, this is quake, they probably prefer ip4... break; @@ -168,7 +187,7 @@ qboolean NET_StringToAddr (char *s, netadr_t *sadr, int defaultport) } dblbreak: pfreeaddrinfo (addrinfo); - if (!((struct sockaddr*)sadr)->sa_family) //none suitablefound + if (!((struct sockaddr*)sadr->sockaddr)->sa_family) //none suitablefound return false; } else @@ -209,7 +228,37 @@ qboolean Net_CompareAddress(netadr_t *s1, netadr_t *s2, int qp1, int qp2) { struct sockaddr *g1=(void*)s1->sockaddr, *g2=(void*)s2->sockaddr; if (g1->sa_family != g2->sa_family) + { //urgh... + if (g1->sa_family == AF_INET6 && g2->sa_family == AF_INET && ( + ((unsigned int*)&((struct sockaddr_in6 *)g1)->sin6_addr)[0] == 0 && + ((unsigned int*)&((struct sockaddr_in6 *)g1)->sin6_addr)[1] == 0 && + ((unsigned short*)&((struct sockaddr_in6 *)g1)->sin6_addr)[4] == 0 && + ((unsigned short*)&((struct sockaddr_in6 *)g1)->sin6_addr)[5] == 0xffff)) + { + struct sockaddr_in6 *i1=(void*)s1->sockaddr; + struct sockaddr_in *i2=(void*)s2->sockaddr; + if (((unsigned int*)&i1->sin6_addr)[3] != *(unsigned int*)&i2->sin_addr) + return false; + if (i1->sin6_port != i2->sin_port && qp1 != qp2) //allow qports to match instead of ports, if required. + return false; + return true; + } + if (g1->sa_family == AF_INET && g2->sa_family == AF_INET6 && ( + ((unsigned int*)&((struct sockaddr_in6 *)g2)->sin6_addr)[0] == 0 && + ((unsigned int*)&((struct sockaddr_in6 *)g2)->sin6_addr)[1] == 0 && + ((unsigned short*)&((struct sockaddr_in6 *)g2)->sin6_addr)[4] == 0 && + ((unsigned short*)&((struct sockaddr_in6 *)g2)->sin6_addr)[5] == 0xffff)) + { + struct sockaddr_in6 *i1=(void*)s2->sockaddr; + struct sockaddr_in *i2=(void*)s1->sockaddr; + if (((unsigned int*)&i1->sin6_addr)[3] != *(unsigned int*)&i2->sin_addr) + return false; + if (i1->sin6_port != i2->sin_port && qp1 != qp2) //allow qports to match instead of ports, if required. + return false; + return true; + } return false; + } switch(g1->sa_family) { default: @@ -2410,7 +2459,11 @@ void QTV_Run(sv_t *qtv) switch(qtv->buffer[1]&dem_mask) { case dem_multiple: - ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1]&dem_mask, (buffer[lengthofs-4]<<0) + (buffer[lengthofs-3]<<8) + (buffer[lengthofs-2]<<16) + (buffer[lengthofs-1]<<24)); + if ((qtv->pexte&PEXTE_HIDDENMESSAGES) && + 0 == (buffer[lengthofs-4]<<0) + (buffer[lengthofs-3]<<8) + (buffer[lengthofs-2]<<16) + (buffer[lengthofs-1]<<24)) + ; //fucked hidden message crap. don't trip up on it. + else + ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1]&dem_mask, (buffer[lengthofs-4]<<0) + (buffer[lengthofs-3]<<8) + (buffer[lengthofs-2]<<16) + (buffer[lengthofs-1]<<24)); break; case dem_single: case dem_stats: diff --git a/plugins/cef/cef.c b/plugins/cef/cef.c index c9d55fa5b..b564f6ca5 100644 --- a/plugins/cef/cef.c +++ b/plugins/cef/cef.c @@ -1737,6 +1737,18 @@ static void VARGS Cef_Key (void *ctx, int code, int unicode, int event) return; } + if (code == K_TOUCH) + { //FIXME + cef_release(host); + return; + } + if (code == K_TOUCHSLIDE || code == K_TOUCHTAP || code == K_TOUCHLONG) + { + cef_release(host); + return; //has to do its own + } + + //handle mouse wheels if (code == K_MWHEELUP || code == K_MWHEELDOWN) { diff --git a/plugins/quake3/clq3_parse.c b/plugins/quake3/clq3_parse.c index ae4fdb988..e1dd7226c 100644 --- a/plugins/quake3/clq3_parse.c +++ b/plugins/quake3/clq3_parse.c @@ -978,7 +978,7 @@ void CLQ3_SendAuthPacket(struct ftenet_connections_s *socket, netadr_t *gameserv //send the auth packet //this should be the right code, but it doesn't work. - if (gameserver->type == NA_IP) + if (gameserver->type == NA_IP && gameserver->prot == NP_DGRAM) { char *key = cvarfuncs->GetNVFDG("cl_cdkey", "", CVAR_ARCHIVE, "Quake3 auth", "Q3 Compat")->string; netadr_t authaddr;