From 0c8ad17f7cb743bbe1fe1f80bf97ad3d0608837b Mon Sep 17 00:00:00 2001 From: Spoike Date: Wed, 20 Sep 2017 11:27:13 +0000 Subject: [PATCH] Fix various compiler warnings. Added sv_guidkey cvar, allowing cross-server guid key generation (although it lacks auth). Support .ico, because we can. preliminary support for sdl 2.0.6's vulkan stuff. will wait till its actually released before its properly used. Fix capturedemo. videomap should typically use premultiplied alpha, apparently. Updated sound drivers. No more old drivers. Better cvar registration. More drivers optionally support float output. Added certificate log for dtls connections. Rewrote font char cache, now supports full unicode char range, not just ucs-2. Attempt to support FreeType 2.5+ rgba fonts. XMPP now supports carbons, and shows avatars in conversations. Updated xmpp's scram auth to be more strict, including the plus variation (hopefully), to block evil tls proxies. ffmpeg plugin now uses the decoupled api for decoding too. Cef plugin updated to support fte-scheme post data properly, as well as request/response headers (like cross-origin). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5148 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/Makefile | 50 +- engine/client/cl_ents.c | 2 + engine/client/cl_input.c | 6 +- engine/client/cl_main.c | 57 +- engine/client/cl_parse.c | 13 +- engine/client/cl_plugin.inc | 12 +- engine/client/cl_screen.c | 39 +- engine/client/client.h | 16 + engine/client/console.c | 56 +- engine/client/image.c | 254 ++- engine/client/in_generic.c | 728 ++++---- engine/client/in_sdl.c | 33 +- engine/client/m_download.c | 16 +- engine/client/m_mp3.c | 76 +- engine/client/p_script.c | 9 + engine/client/pr_clcmd.c | 9 +- engine/client/pr_csqc.c | 32 +- engine/client/renderer.c | 4 +- engine/client/roq_read.c | 3 +- engine/client/snd_al.c | 6 +- engine/client/snd_alsa.c | 25 +- engine/client/snd_directx.c | 27 +- engine/client/snd_dma.c | 115 +- engine/client/snd_droid.c | 293 ++-- engine/client/snd_linux.c | 76 +- engine/client/snd_macos.c | 511 +++--- engine/client/snd_mix.c | 105 +- engine/client/snd_morphos.c | 20 +- engine/client/snd_sblaster.c | 21 +- engine/client/snd_sdl.c | 65 +- engine/client/snd_sndio.c | 74 +- engine/client/snd_wasapi.c | 212 ++- engine/client/snd_win.c | 40 +- engine/client/snd_xaudio.c | 35 +- engine/client/sound.h | 26 +- engine/client/sys_sdl.c | 3 + engine/client/sys_win.c | 5 +- engine/client/view.c | 5 +- engine/common/bothdefs.h | 12 +- engine/common/cmd.c | 4 +- engine/common/common.c | 8 +- engine/common/common.h | 9 +- engine/common/config_wastes.h | 1 + engine/common/console.h | 1 + engine/common/fs_dzip.c | 2 +- engine/common/fs_win32.c | 4 + engine/common/log.c | 171 ++ engine/common/net_ice.c | 6 +- engine/common/net_ssl_gnutls.c | 331 +++- engine/common/net_ssl_winsspi.c | 2503 ++++++++++++++------------- engine/common/net_wins.c | 2 +- engine/common/netinc.h | 1 + engine/common/plugin.c | 33 +- engine/common/pr_bgcmd.c | 55 +- engine/common/sha1.c | 72 +- engine/d3d/d3d11_backend.c | 4 +- engine/d3d/d3d11_image.c | 3 + engine/d3d/d3d8_backend.c | 21 +- engine/dotnet2005/emscripten.vcproj | 2 +- engine/dotnet2005/ftequake.sln | 1 + engine/gl/gl_bloom.c | 2 +- engine/gl/gl_font.c | 458 +++-- engine/gl/gl_heightmap.c | 8 +- engine/gl/gl_rlight.c | 3 +- engine/gl/gl_shader.c | 25 +- engine/gl/gl_vidcommon.c | 9 +- engine/gl/gl_viddroid.c | 6 +- engine/gl/gl_vidlinuxglx.c | 20 +- engine/gl/gl_vidmacos.c | 18 +- engine/gl/gl_vidnt.c | 13 +- engine/gl/gl_vidsdl.c | 333 +++- engine/gl/glquake.h | 9 +- engine/nacl/snd_ppapi.c | 92 +- engine/qclib/pr_exec.c | 1 - engine/qclib/qcc.h | 2 +- engine/qclib/qcc_pr_comp.c | 4 +- engine/qclib/qcc_pr_lex.c | 2 +- engine/qclib/qccgui.c | 8 +- engine/server/pr_cmds.c | 27 +- engine/server/sv_ccmds.c | 9 +- engine/server/sv_ents.c | 4 - engine/server/sv_main.c | 35 +- engine/server/sv_user.c | 2 + engine/shaders/glsl/lpp_light.glsl | 4 + engine/vk/vk_backend.c | 9 +- engine/vk/vk_init.c | 62 +- engine/vk/vkrenderer.h | 2 +- engine/web/gl_vidweb.c | 3 +- fteqtv/control.c | 1240 ++++++------- plugins/avplug/avdecode.c | 156 +- plugins/cef/cef.c | 443 ++++- plugins/jabber/jabberclient.c | 909 +++++++--- plugins/jabber/jingle.c | 4 +- plugins/jabber/sift.c | 2 +- plugins/jabber/xml.c | 2 + plugins/jabber/xmpp.h | 75 +- plugins/plugin.c | 4 + plugins/plugin.h | 1 + specs/particles.txt | 141 +- 99 files changed, 6612 insertions(+), 3860 deletions(-) diff --git a/engine/Makefile b/engine/Makefile index 936e8b894..1423b51b0 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -15,7 +15,10 @@ else BASE_DIR:=$(realpath .) endif -SVNREVISION:=-DSVNREVISION=$(shell test -d $(BASE_DIR)/../.svn && svnversion $(BASE_DIR) || echo -) +ifeq ($(SVNREVISION),) + SVNREVISION:=-DSVNREVISION=$(shell test -d $(BASE_DIR)/../.svn && svnversion $(BASE_DIR) || echo -) +endif +MAKE:=$(MAKE) SVNREVISION=$(SVNREVISION) WHOAMI:=$(shell whoami) @@ -875,11 +878,13 @@ GLCL_EXE_NAME=../$(EXE_NAME)cl-gl$(FTE_FULLTARGET) #SDLCONFIG:=libs/sdl2_mingw/$(CC_MACHINE)/bin/sdl2-config --prefix=libs/sdl2_mingw/$(CC_MACHINE) ifdef windir GL_LDFLAGS=$(GLLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` + VK_LDFLAGS=$(VKLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` M_LDFLAGS=$(MLDFLAGS) -lmingw32 -lws2_32 `$(SDLCONFIG) --static-libs` SV_LDFLAGS=`$(SDLCONFIG) --static-libs` else GL_LDFLAGS=$(GLLDFLAGS) $(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) `$(SDLCONFIG) --static-libs` - M_LDFLAGS=$(MLDFLAGS) `$(SDLCONFIG) --static-libs` $(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) + VK_LDFLAGS=$(VKLDFLAGS) $(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) `$(SDLCONFIG) --static-libs` + M_LDFLAGS=$(MLDFLAGS) $(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) `$(SDLCONFIG) --static-libs` SV_LDFLAGS=`$(SDLCONFIG) --static-libs` endif GL_CFLAGS=-DFTE_SDL $(GLCFLAGS) `$(SDLCONFIG) --cflags` @@ -887,6 +892,9 @@ GLB_DIR=gl_$(FTE_FULLTARGET) GLCL_DIR=glcl_$(FTE_FULLTARGET) SV_DIR?=sv_$(FTE_FULLTARGET) +VKCL_OBJS=$(VKQUAKE_OBJS) $(BOTLIB_OBJS) $(SPEEX_OBJS) snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(D3DGL_OBJS) $(LTO_END) +VK_CFLAGS=-DFTE_SDL $(VKCFLAGS) `$(SDLCONFIG) --cflags` + SV_OBJS=$(COMMON_OBJS) $(SERVER_OBJS) $(PROGS_OBJS) $(SERVERONLY_OBJS) $(BOTLIB_OBJS) SV_EXE_NAME=../$(EXE_NAME)-sv$(FTE_FULLTARGET) SV_CFLAGS=-DFTE_SDL `$(SDLCONFIG) --cflags` $(SERVER_ONLY_CFLAGS) @@ -904,6 +912,7 @@ 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 endif @@ -998,20 +1007,19 @@ ifeq (win_SDL,$(findstring win,$(FTE_TARGET))$(findstring _SDL,$(FTE_TARGET))) 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=$(MINGW_LIBS_DIR)/libz.a -lm -lmingw32 -lws2_32 -lwinmm `$(SDLCONFIG) --static-libs` QCC_LDFLAGS=$(MINGW_LIBS_DIR)/libz.a else GL_LDFLAGS=$(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(GLLDFLAGS) `$(SDLCONFIG) --libs` - M_LDFLAGS=$(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) -lws2_32 -lmingw32 -mwindows -ldxguid -lwinmm -lole32 $(MLDFLAGS) `$(SDL_CONFIG) --libs` $(IMAGELDFLAGS) + VK_LDFLAGS=$(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(GLLDFLAGS) `$(SDLCONFIG) --libs` + M_LDFLAGS=$(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 $(MLDFLAGS) `$(SDLCONFIG) --libs` SV_LDFLAGS=$(MINGW_LIBS_DIR)/libz.a -lm -lmingw32 -lws2_32 -lwinmm `$(SDLCONFIG) --libs` QCC_LDFLAGS=$(MINGW_LIBS_DIR)/libz.a endif GL_CFLAGS=-DFTE_SDL -I$(MINGW_LIBS_DIR)/ -I$(MINGW_LIBS_DIR) -I$(LIBS_DIR) $(GLCFLAGS) $(LIBVORBISFILE_STATIC) $(DX7SDK) $(SPEEXCFLAGS) - ifeq ($(shell echo $(FTE_TARGET)|grep -E -i -v "win32.*sdl"),) - GL_CFLAGS+= -D_MINGW_VFPRINTF - endif GLB_DIR=gl_mgw_sdl$(BITS) GLCL_DIR=glcl_mgw_sdl$(BITS) @@ -1025,39 +1033,35 @@ ifeq (win_SDL,$(findstring win,$(FTE_TARGET))$(findstring _SDL,$(FTE_TARGET))) MB_DIR=m_mgw_sdl$(BITS) M_EXE_NAME=../$(EXE_NAME)-sdl$(BITS)$(EXEPOSTFIX) - MCL_OBJS=$(D3DGL_OBJS) $(GLQUAKE_OBJS) $(SOFTWARE_OBJS) $(D3DQUAKE_OBJS) $(BOTLIB_OBJS) $(SPEEX_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) -DFTE_SDL -I$(LIBS_DIR) -I$(MINGW_LIBS_DIR)/ -I$(MINGW_LIBS_DIR) $(GLCFLAGS) $(LIBVORBISFILE_STATIC) -D_MERGED_SDL $(DX7SDK) $(SPEEXCFLAGS) - - ifeq ($(shell echo $(FTE_TARGET)|grep -E -i -v "win32.*sdl"),) - M_CFLAGS+= -D_MINGW_VFPRINTF - endif - - M_LDFLAGS=$(GLLDFLAGS) $(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) - +#with d3d... + #MCL_OBJS=$(D3DGL_OBJS) $(GLQUAKE_OBJS) $(SOFTWARE_OBJS) $(D3DQUAKE_OBJS) $(BOTLIB_OBJS) $(SPEEX_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 -I$(LIBS_DIR) -I$(LIBS_DIR) -I$(MINGW_LIBS_DIR)/ -I$(MINGW_LIBS_DIR) $(LIBVORBISFILE_STATIC) $(DX7SDK) $(SPEEXCFLAGS) +#without d3d... + MCL_OBJS=$(D3DGL_OBJS) $(GLQUAKE_OBJS) $(SOFTWARE_OBJS) $(BOTLIB_OBJS) $(SPEEX_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 -I$(LIBS_DIR) -I$(LIBS_DIR) -I$(MINGW_LIBS_DIR)/ -I$(MINGW_LIBS_DIR) $(LIBVORBISFILE_STATIC) $(DX7SDK) $(SPEEXCFLAGS) D3DCL_OBJS=$(D3DQUAKE_OBJS) $(BOTLIB_OBJS) $(SPEEX_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) $(OGGVORBISLDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 D3D_CFLAGS=$(D3DCFLAGS) -DFTE_SDL -DNO_XFLIP -I$(LIBS_DIR) -I$(MINGW_LIBS_DIR)/ -I$(MINGW_LIBS_DIR) $(LIBVORBISFILE_STATIC) $(DX7SDK) $(SPEEXCFLAGS) - ifeq ($(shell echo $(FTE_TARGET)|grep -E -i -v "win32.*sdl"),) - D3D_CFLAGS+= -D_MINGW_VFPRINTF - endif D3DB_DIR=sdl_d3d_mgw$(BITS) D3DCL_DIR=sdl_d3dcl_mgw$(BITS) - VKCL_OBJS=$(D3DQUAKE_OBJS) $(BOTLIB_OBJS) $(SPEEX_OBJS) snd_sdl.o cd_sdl.o sys_sdl.o in_sdl.o snd_directx.o $(D3DGL_OBJS) $(LTO_END) resources.o $(LTO_START) + VKCL_OBJS=$(VKQUAKE_OBJS) $(BOTLIB_OBJS) $(SPEEX_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_LDFLAGS=$(IMAGELDFLAGS) $(OGGVORBISLDFLAGS) -lws2_32 -lmingw32 $(SDL_LDFLAGS) -mwindows -ldxguid -lwinmm -lole32 VK_CFLAGS=$(VKCFLAGS) -DFTE_SDL -DNO_XFLIP -I$(LIBS_DIR) -I$(MINGW_LIBS_DIR)/ -I$(MINGW_LIBS_DIR) $(LIBVORBISFILE_STATIC) $(DX7SDK) $(SPEEXCFLAGS) - ifeq ($(shell echo $(FTE_TARGET)|grep -E -i -v "win32.*sdl"),) - VK_CFLAGS+= -D_MINGW_VFPRINTF - endif 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 endif #FTE_TARGET=vc (Visual C) diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 2877d4a9e..00e59c2af 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -867,7 +867,9 @@ void CLFTE_ParseEntities(void) qboolean isvalid = false; qboolean removeflag; int inputframe = cls.netchan.incoming_sequence; +#if defined(QUAKESTATS) || defined(NQPROT) int i; +#endif // int i; // for (i = cl.validsequence+1; i < cls.netchan.incoming_sequence; i++) diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index e2a34fcbd..91643faf1 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -1902,7 +1902,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) #ifdef HLCLIENT if (!CLHL_BuildUserInput(msecstouse, &independantphysics[0])) #endif - for (plnum = 0; plnum < cl.splitclients; plnum++) + for (plnum = 0; plnum < (cl.splitclients?cl.splitclients:1); plnum++) { vec3_t mousemovements; CL_AdjustAngles (plnum, frametime); @@ -1934,6 +1934,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) if (fullsend && (cl_pendingcmd[plnum].msec > 12.9 && cl_pendingcmd[plnum].msec < 13) && cls.maxfps == 77) cl_pendingcmd[plnum].msec = 13; } + msecstouse = cl_pendingcmd[0].msec; //the main loop isn't allowed to send if (runningindepphys && mainloop) @@ -1945,9 +1946,6 @@ void CL_SendCmd (double frametime, qboolean mainloop) if (!fullsend) return; // when we're actually playing we try to match netfps exactly to avoid gameplay problems - if (msecstouse == 12) - msecstouse = 13; - // if (msecstouse > 127) // Con_Printf("%i\n", msecstouse, msecs); diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 1f44f109d..741a12441 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -157,7 +157,7 @@ cvar_t cl_gunangley = CVAR("cl_gunangley", "0"); cvar_t cl_gunanglez = CVAR("cl_gunanglez", "0"); cvar_t cl_proxyaddr = CVAR("cl_proxyaddr", ""); -cvar_t cl_sendguid = CVARD("cl_sendguid", "0", "Send a randomly generated 'globally unique' id to servers, which can be used by servers for score rankings and stuff. Different servers will see different guids. Delete the 'qkey' file in order to appear as a different user.\nIf set to 2, all servers will see the same guid. Be warned that this can show other people the guid that you're using."); +cvar_t cl_sendguid = CVARD("cl_sendguid", "", "Send a randomly generated 'globally unique' id to servers, which can be used by servers for score rankings and stuff. Different servers will see different guids. Delete the 'qkey' file in order to appear as a different user.\nIf set to 2, all servers will see the same guid. Be warned that this can show other people the guid that you're using."); cvar_t cl_downloads = CVARFD("cl_downloads", "1", CVAR_NOTFROMSERVER, "Allows you to block all automatic downloads."); cvar_t cl_download_csprogs = CVARFD("cl_download_csprogs", "1", CVAR_NOTFROMSERVER, "Download updated client gamecode if available. Warning: If you clear this to avoid downloading vm code, you should also clear cl_download_packages."); cvar_t cl_download_redirection = CVARFD("cl_download_redirection", "2", CVAR_NOTFROMSERVER, "Follow download redirection to download packages instead of individual files. Also allows the server to send nearly arbitary download commands.\n2: allows redirection only to named packages files (and demos/*.mvd), which is a bit safer."); @@ -452,7 +452,7 @@ void CL_SupportedFTEExtensions(int *pext1, int *pext2) } #endif -char *CL_GUIDString(netadr_t *adr) +char *CL_GUIDString(netadr_t *adr, const char *guidstring) { static qbyte buf[2048]; static int buflen; @@ -461,7 +461,16 @@ char *CL_GUIDString(netadr_t *adr) void *blocks[2]; int lens[2]; - if (!cl_sendguid.ival) + if (!*cl_sendguid.string && guidstring && *guidstring) + { + serveraddr[0] = '#'; //leading hash is to stop servers from being able to scrape from other servers. + Q_strncpyz(serveraddr+1, guidstring, sizeof(serveraddr)-1); + } + else if (cl_sendguid.ival == 2) + *serveraddr = 0; + else if (cl_sendguid.ival) + NET_AdrToString(serveraddr, sizeof(serveraddr), adr); + else return NULL; if (*connectinfo.guid && connectinfo.istransfer) @@ -498,11 +507,6 @@ char *CL_GUIDString(netadr_t *adr) } } - if (cl_sendguid.ival == 2) - *serveraddr = 0; - else - NET_AdrToString(serveraddr, sizeof(serveraddr), adr); - blocks[0] = buf;lens[0] = buflen; blocks[1] = serveraddr;lens[1] = strlen(serveraddr); Com_BlocksChecksum(2, blocks, lens, (void*)digest); @@ -522,8 +526,9 @@ void CL_SendConnectPacket (netadr_t *to, int mtu, #ifdef PROTOCOL_VERSION_FTE int ftepext, int ftepext2, #endif - int compressioncrc - /*, ...*/) //dmw new parms + int compressioncrc, + const char *guidhash + /*, ...*/) { extern cvar_t qport; netadr_t addr; @@ -683,7 +688,7 @@ void CL_SendConnectPacket (netadr_t *to, int mtu, #endif connectinfo.compresscrc = 0; - info = CL_GUIDString(to); + info = CL_GUIDString(to, guidhash); if (info) Q_strncatz(data, va("0x%x \"%s\"\n", PROTOCOL_INFO_GUID, info), sizeof(data)); @@ -714,17 +719,18 @@ void CL_CheckForResend (void) qboolean keeptrying = true; char *host; extern int r_blockvidrestart; - const char *lbp; #ifndef CLIENTONLY if (!cls.state && (!connectinfo.trying || sv.state != ss_clustermode) && sv.state) { + const char *lbp; #ifdef NQPROT qboolean proquakeangles = false; #endif #ifdef NETPREPARSE extern cvar_t dpcompat_nopreparse; #endif + extern cvar_t sv_guidhash; memset(&connectinfo, 0, sizeof(connectinfo)); Q_strncpyz (cls.servername, "internalserver", sizeof(cls.servername)); Cvar_ForceSet(&cl_servername, cls.servername); @@ -945,7 +951,7 @@ void CL_CheckForResend (void) { if (!connectinfo.challenge) connectinfo.challenge = rand(); - CL_SendConnectPacket (NULL, 8192-16, connectinfo.fteext1, connectinfo.fteext2, false); + CL_SendConnectPacket (NULL, 8192-16, connectinfo.fteext1, connectinfo.fteext2, 0, sv_guidhash.string); } return; @@ -976,7 +982,11 @@ void CL_CheckForResend (void) if (connectinfo.time && realtime - connectinfo.time < 5.0) return; - NET_InitClient(false); +#ifdef HAVE_DTLS + if (connectinfo.dtlsupgrade != DTLS_ACTIVE) +#endif + //FIXME: this is switching ports far too much + NET_InitClient(false); t1 = Sys_DoubleTime (); if (!connectinfo.istransfer) @@ -2833,6 +2843,7 @@ void CL_ConnectionlessPacket (void) #ifdef HAVE_DTLS qboolean candtls = false; #endif + char guidhash[256]; s = MSG_ReadString (); COM_Parse(s); @@ -2867,7 +2878,7 @@ void CL_ConnectionlessPacket (void) connectinfo.protocol = CP_QUAKE3; connectinfo.challenge = atoi(s+17); - CL_SendConnectPacket (&net_from, 0, 0, 0, 0/*, ...*/); + CL_SendConnectPacket (&net_from, 0, 0, 0, 0/*, ...*/, NULL); } else { @@ -2993,6 +3004,17 @@ void CL_ConnectionlessPacket (void) c = MSG_ReadLong();/*ident*/ switch(c) { + case PROTOCOL_INFO_GUID: + if (len > sizeof(guidhash)-1) + { + MSG_ReadData(guidhash, sizeof(guidhash)); + MSG_ReadSkip(len-sizeof(guidhash)); + len = sizeof(guidhash)-1; + } + else + MSG_ReadData(guidhash, len); + guidhash[len] = 0; + break; default: MSG_ReadSkip(len); /*payload*/ break; @@ -3007,11 +3029,12 @@ void CL_ConnectionlessPacket (void) case PROTOCOL_VERSION_FTE2: pext2 = l; break; case PROTOCOL_VERSION_FRAGMENT: mtu = l; break; #ifdef HAVE_DTLS - case PROTOCOL_VERSION_DTLSUPGRADE: candtls = l; break; //0:not enabled. 1:use if you want. 2:require it. + case PROTOCOL_VERSION_DTLSUPGRADE: candtls = l; break; //0:not enabled. 1:explicit use allowed. 2:favour it. 3: require it #endif #ifdef HUFFNETWORK case PROTOCOL_VERSION_HUFFMAN: huffcrc = l; break; #endif + case PROTOCOL_INFO_GUID: Q_snprintfz(guidhash, sizeof(guidhash), "0x%x", l); break; default: break; } @@ -3041,7 +3064,7 @@ void CL_ConnectionlessPacket (void) } #endif - CL_SendConnectPacket (&net_from, mtu, pext, pext2, huffcrc/*, ...*/); + CL_SendConnectPacket (&net_from, mtu, pext, pext2, huffcrc/*, ...*/, guidhash); return; } #ifdef Q2CLIENT diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index e7e11c635..04c871477 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -35,10 +35,12 @@ static char *CLNQ_ParseProQuakeMessage (char *s); static void DLC_Poll(qdownload_t *dl); static void CL_ProcessUserInfo (int slot, player_info_t *player); +#ifdef NQPROT static char cl_dp_csqc_progsname[128]; static int cl_dp_csqc_progssize; static int cl_dp_csqc_progscrc; static int cl_dp_serverextension_download; +#endif #ifdef AVAIL_ZLIB #ifndef ZEXPORT @@ -178,6 +180,7 @@ static char *svc_qwstrings[] = "???", }; +#ifdef NQPROT static char *svc_nqstrings[] = { "nqsvc_bad", @@ -278,6 +281,7 @@ static char *svc_nqstrings[] = "NEW PROTOCOL(87)", //87 "NEW PROTOCOL(88)" //88 }; +#endif extern cvar_t requiredownloads, cl_standardchat, msg_filter, msg_filter_frags, msg_filter_pickups, cl_countpendingpl, cl_download_mapsrc; int oldparsecountmod; @@ -2650,6 +2654,7 @@ qboolean CL_ParseOOBDownload(void) return true; } +#ifdef NQPROT static void CLDP_ParseDownloadData(void) { qdownload_t *dl = cls.download; @@ -2781,6 +2786,7 @@ static void CLDP_ParseDownloadFinished(char *s) CL_RequestNextDownload (); } +#endif static vfsfile_t *upload_file; static qbyte *upload_data; @@ -2870,6 +2876,7 @@ void CL_StopUpload(void) upload_pos = upload_size = 0; } +#if 0 //in case we ever want to add any uploads other than snaps static qboolean CL_StartUploadFile(char *filename) { if (!COM_CheckParm("-fileul")) @@ -2897,6 +2904,7 @@ static qboolean CL_StartUploadFile(char *filename) } return false; } +#endif /* ===================================================================== @@ -2905,9 +2913,6 @@ static qboolean CL_StartUploadFile(char *filename) ===================================================================== */ -#ifdef CLIENTONLY -static float nextdemotime; -#endif void CL_ClearParseState(void) { @@ -4340,6 +4345,7 @@ static void CL_ParseBaselineDelta (void) memcpy(cl_baselines + es.number, &es, sizeof(es)); } +#ifdef Q2CLIENT static void CLQ2_Precache_f (void) { Model_CheckDownloads(); @@ -4353,6 +4359,7 @@ static void CLQ2_Precache_f (void) CG_Start(); #endif } +#endif diff --git a/engine/client/cl_plugin.inc b/engine/client/cl_plugin.inc index 8350f6eb6..ecbd35a73 100644 --- a/engine/client/cl_plugin.inc +++ b/engine/client/cl_plugin.inc @@ -147,12 +147,12 @@ static qintptr_t VARGS Plug_Draw_LoadImageData(void *offset, quintptr_t mask, co { // name = va("%s", name); - t = Image_FindTexture(name, NULL, IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); + t = Image_FindTexture(name, NULL, IF_PREMULTIPLYALPHA|IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); if (!TEXVALID(t)) - t = Image_CreateTexture(name, NULL, IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); + t = Image_CreateTexture(name, NULL, IF_PREMULTIPLYALPHA|IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); if (TEXVALID(t)) { - Image_Upload(t, TF_RGBA32, rgbdata, NULL, width, height, IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); + Image_Upload(t, TF_RGBA32, rgbdata, NULL, width, height, IF_PREMULTIPLYALPHA|IF_NOMIPMAP|IF_UIPIC|IF_CLAMP); ret = Plug_Draw_LoadImage(name, 3, NULL); } @@ -1118,6 +1118,10 @@ static qintptr_t VARGS Plug_Con_GetConsoleString(void *offset, quintptr_t mask, { Q_strncpyz(value, con->title, size); } + else if (!strcmp(attrib, "icon")) + { + Q_strncpyz(value, con->icon, size); + } else if (!strcmp(attrib, "prompt")) { Q_strncpyz(value, con->prompt, size); @@ -1152,6 +1156,8 @@ static qintptr_t VARGS Plug_Con_SetConsoleString(void *offset, quintptr_t mask, Con_Footerf(con, false, "%s", value); else if (!strcmp(attrib, "title")) Q_strncpyz(con->title, value, sizeof(con->title)); + else if (!strcmp(attrib, "icon")) + Q_strncpyz(con->icon, value, sizeof(con->icon)); else if (!strcmp(attrib, "prompt")) Q_strncpyz(con->prompt, value, sizeof(con->prompt)); else if (!strcmp(attrib, "backimage")) diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index 17869206e..95a9143be 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -2275,7 +2275,7 @@ int Image_WritePNG (char *filename, enum fs_relative fsroot, int compression, vo #endif qboolean WriteBMPFile(char *filename, enum fs_relative fsroot, qbyte *in, int instride, int width, int height, uploadfmt_t fmt); -qboolean WriteTGA(char *filename, enum fs_relative fsroot, qbyte *fte_restrict rgb_buffer, int bytestride, int width, int height, enum uploadfmt fmt) +qboolean WriteTGA(char *filename, enum fs_relative fsroot, const qbyte *fte_restrict rgb_buffer, int bytestride, int width, int height, enum uploadfmt fmt) { size_t c, i; vfsfile_t *vfs; @@ -2321,7 +2321,7 @@ qboolean WriteTGA(char *filename, enum fs_relative fsroot, qbyte *fte_restrict r { //if we're upside down, lets just use an upside down tga. rgb_buffer += bytestride*(height-1); bytestride = -bytestride; - //now we can just do everything else in-place + //now we can just do everything without worrying about rows } else //our data is top-down, set up the header to also be top-down. header[17] = 0x20; @@ -2330,45 +2330,44 @@ qboolean WriteTGA(char *filename, enum fs_relative fsroot, qbyte *fte_restrict r { //can just directly write it //bgr24, bgra24 c = width*height*opx; + + VFS_WRITE(vfs, header, sizeof(header)); + VFS_WRITE(vfs, rgb_buffer, c); } else { + qbyte *fte_restrict rgb_out = malloc(width*opx*height); + //no need to swap alpha, and if we're just swapping alpha will be fine in-place. if (rgb) { //rgb24, rgbx32, rgba32 - qbyte tmp[3]; - // compact in place, and swap + // compact, and swap c = width*height; for (i=0 ; iwnd_x, w->wnd_y, w->wnd_w, w->wnd_h); R2D_ImageColours(1, 1, 1, 1); + //fixme: scale up this font... + Draw_FunStringWidth(w->wnd_x, w->wnd_y, w->title, w->wnd_w-top, 2, (con_curwindow==w)?true:false); + Draw_FunStringWidth(w->wnd_x+w->wnd_w-top, w->wnd_y, "X", top, 2, ((w->buttonsdown == CB_CLOSE && w->mousecursor[0] > w->wnd_w-(8+top) && w->mousecursor[1] < top) || (con_curwindow==w && w->mousecursor[0] >= w->wnd_w-(8+top) && w->mousecursor[0] < w->wnd_w-8 && w->mousecursor[1] >= 0 && w->mousecursor[1] < 8))?true:false); + if (w->backshader || *w->backimage) { shader_t *shader = w->backshader; @@ -2362,7 +2375,10 @@ void Con_DrawConsole (int lines, qboolean noback) shader = w->backshader = R_RegisterPic(w->backimage, NULL);// R_RegisterCustom(w->backimage, SUF_NONE, Shader_DefaultCinematic, w->backimage); if (shader) { - int top = 8; + float backx = w->wnd_x+8; + float backy = w->wnd_y+top; + float backw = w->wnd_w-16; + float backh = w->wnd_h-8-top; #ifdef HAVE_MEDIA_DECODER cin_t *cin = R_ShaderGetCinematic(shader); if (cin) @@ -2372,8 +2388,8 @@ void Con_DrawConsole (int lines, qboolean noback) { float x = 0; // float r = x+w->wnd_w-16; - const char *buttons[] = {"bck", "fwd", "rld", "home", ((w->linebuffered == Con_Navigate)?(char*)key_lines[edit_line]:url)}; - const char *buttoncmds[] = {"cmd:back", "cmd:forward", "cmd:refresh", ENGINEWEBSITE, NULL}; + const char *buttons[] = {"bck", "fwd", "rld", "home", "", ((w->linebuffered == Con_Navigate)?(char*)key_lines[edit_line]:url)}; + const char *buttoncmds[] = {"cmd:back", "cmd:forward", "cmd:refresh", ENGINEWEBSITE, NULL, NULL}; float tw; int i, fl; @@ -2382,6 +2398,14 @@ void Con_DrawConsole (int lines, qboolean noback) if (i == countof(buttons)-1) tw = ~0u; else if (i == countof(buttons)-2) + { + tw = 8+8; + if (*w->icon) + R2D_Image(w->wnd_x+8+x, w->wnd_y+top, tw, tw, 0, 0, 1, 1, R_RegisterPic(w->icon, NULL)); + else + tw = 0; + } + else if (i == countof(buttons)-3) tw = 40; else tw = 32; @@ -2407,22 +2431,38 @@ void Con_DrawConsole (int lines, qboolean noback) x += tw; } top += 8; + backy += 8; + backh -= 8; } - Media_Send_Resize(cin, ((w->wnd_w-16.0)*(int)vid.rotpixelwidth) / (float)vid.width, ((w->wnd_h-8-top)*(int)vid.rotpixelheight) / (float)vid.height); - Media_Send_MouseMove(cin, (w->mousecursor[0]) / (w->wnd_w-16.0), (w->mousecursor[1]-top) / (w->wnd_h-8.0-top)); + //convert these to pixels. + backx = (backx*(int)vid.rotpixelwidth) / (float)vid.width; + backy = (backy*(int)vid.rotpixelheight) / (float)vid.height; + backw = (backw*(int)vid.rotpixelwidth) / (float)vid.width; + backh = (backh*(int)vid.rotpixelheight) / (float)vid.height; + //snap to pixels. this avoids issues with linear filtering + backx = (int)backx; + backy = (int)backy; + backw = (int)backw; + backh = (int)backh; + Media_Send_Resize(cin, backw, backh); + //convert back to screen coords now. + backx = (backx*(int)vid.width) / (float)vid.rotpixelwidth; + backy = (backy*(int)vid.height) / (float)vid.rotpixelheight; + backw = (backw*(int)vid.width) / (float)vid.rotpixelwidth; + backh = (backh*(int)vid.height) / (float)vid.rotpixelheight; + + Media_Send_MouseMove(cin, (w->mousecursor[0]) / backw, (w->mousecursor[1]-top) / backh); if (con_curwindow==w) Media_Send_Command(cin, "cmd:focus"); else Media_Send_Command(cin, "cmd:unfocus"); } #endif - R2D_Image(w->wnd_x+8, w->wnd_y+top, w->wnd_w-16, w->wnd_h-8-top, 0, 0, 1, 1, shader); + R2D_Image(backx, backy, backw, backh, 0, 0, 1, 1, shader); } } - Draw_FunStringWidth(w->wnd_x, w->wnd_y, w->title, w->wnd_w-8, 2, (con_curwindow==w)?true:false); - Draw_FunStringWidth(w->wnd_x+w->wnd_w-8, w->wnd_y, "X", 8, 2, ((w->buttonsdown == CB_CLOSE && w->mousecursor[0] > w->wnd_w-16 && w->mousecursor[1] < 8) || (con_curwindow==w && w->mousecursor[0] >= w->wnd_w-16 && w->mousecursor[0] < w->wnd_w-8 && w->mousecursor[1] >= 0 && w->mousecursor[1] < 8))?true:false); w->unseentext = false; } else diff --git a/engine/client/image.c b/engine/client/image.c index d7d263161..27149a267 100644 --- a/engine/client/image.c +++ b/engine/client/image.c @@ -35,7 +35,7 @@ char *r_defaultimageextensions = ; static void QDECL R_ImageExtensions_Callback(struct cvar_s *var, char *oldvalue); cvar_t r_imageexensions = CVARCD("r_imageexensions", NULL, R_ImageExtensions_Callback, "The list of image file extensions which fte should attempt to load."); -cvar_t r_image_downloadsizelimit = CVARFD("r_image_downloadsizelimit", "64000", CVAR_NOTFROMSERVER, "The maximum allowed file size of images loaded from a web-based url. 0 disables completely, while empty imposes no limit."); +cvar_t r_image_downloadsizelimit = CVARFD("r_image_downloadsizelimit", "131072", CVAR_NOTFROMSERVER, "The maximum allowed file size of images loaded from a web-based url. 0 disables completely, while empty imposes no limit."); extern cvar_t gl_lerpimages; extern cvar_t gl_picmip2d; extern cvar_t gl_picmip; @@ -717,14 +717,18 @@ void (PNGAPI *qpng_read_end) PNGARG((png_structp png_ptr, png_infop info_ptr)) P void (PNGAPI *qpng_read_image) PNGARG((png_structp png_ptr, png_bytepp image)) PSTATIC(png_read_image); png_byte (PNGAPI *qpng_get_bit_depth) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_bit_depth); png_byte (PNGAPI *qpng_get_channels) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_channels); -png_size_t (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); +#if PNG_LIBPNG_VER < 10400 + png_uint_32 (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); +#else + png_size_t (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); +#endif void (PNGAPI *qpng_read_update_info) PNGARG((png_structp png_ptr, png_infop info_ptr)) PSTATIC(png_read_update_info); void (PNGAPI *qpng_set_strip_16) PNGARG((png_structp png_ptr)) PSTATIC(png_set_strip_16); void (PNGAPI *qpng_set_expand) PNGARG((png_structp png_ptr)) PSTATIC(png_set_expand); void (PNGAPI *qpng_set_gray_to_rgb) PNGARG((png_structp png_ptr)) PSTATIC(png_set_gray_to_rgb); void (PNGAPI *qpng_set_tRNS_to_alpha) PNGARG((png_structp png_ptr)) PSTATIC(png_set_tRNS_to_alpha); png_uint_32 (PNGAPI *qpng_get_valid) PNGARG((png_const_structp png_ptr, png_const_infop info_ptr, png_uint_32 flag)) PSTATIC(png_get_valid); -#if PNG_LIBPNG_VER > 10400 +#if PNG_LIBPNG_VER >= 10400 void (PNGAPI *qpng_set_expand_gray_1_2_4_to_8) PNGARG((png_structp png_ptr)) PSTATIC(png_set_expand_gray_1_2_4_to_8); #else void (PNGAPI *qpng_set_gray_1_2_4_to_8) PNGARG((png_structp png_ptr)) PSTATIC(png_set_gray_1_2_4_to_8); @@ -2050,12 +2054,6 @@ qbyte *ReadPCXPalette(qbyte *buf, int len, qbyte *out) typedef struct bmpheader_s { -/* unsigned short Type;*/ - unsigned int Size; - unsigned short Reserved1; - unsigned short Reserved2; - unsigned int OffsetofBMPBits; - unsigned int SizeofBITMAPINFOHEADER; signed int Width; signed int Height; @@ -2079,20 +2077,13 @@ typedef struct bmpheaderv4_s unsigned int Gamma[3]; } bmpheaderv4_t; -qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) +static qbyte *ReadRawBMPFile(qbyte *buf, int length, int *width, int *height, size_t OffsetofBMPBits) { unsigned int i; bmpheader_t h; qbyte *data; - if (buf[0] != 'B' || buf[1] != 'M') - return NULL; - - memcpy(&h, (bmpheader_t *)(buf+2), sizeof(h)); - h.Size = LittleLong(h.Size); - h.Reserved1 = LittleShort(h.Reserved1); - h.Reserved2 = LittleShort(h.Reserved2); - h.OffsetofBMPBits = LittleLong(h.OffsetofBMPBits); + memcpy(&h, (bmpheader_t *)buf, sizeof(h)); h.SizeofBITMAPINFOHEADER = LittleLong(h.SizeofBITMAPINFOHEADER); h.Width = LittleLong(h.Width); h.Height = LittleLong(h.Height); @@ -2107,8 +2098,9 @@ qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) if (h.Compression) //RLE? BITFIELDS (gah)? return NULL; - if (length < h.Size) - return NULL; //truncated... + + if (!OffsetofBMPBits) + h.Height /= 2; //icons are weird. *width = h.Width; *height = h.Height; @@ -2125,15 +2117,18 @@ qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) if (h.Width&1) return NULL; - data = buf+2; + data = buf; data += sizeof(h); for (i = 0; i < h.NumofColorIndices; i++) { - pal[i] = data[i*4+0] + (data[i*4+1]<<8) + (data[i*4+2]<<16) + (255/*data[i*4+3]*/<<16); + pal[i] = data[i*4+2] + (data[i*4+1]<<8) + (data[i*4+0]<<16) + (255/*data[i*4+3]*/<<24); } - buf += h.OffsetofBMPBits; + if (OffsetofBMPBits) + buf += OffsetofBMPBits; + else + buf = data+h.NumofColorIndices*4; data32 = BZ_Malloc(h.Width * h.Height*4); for (y = 0; y < h.Height; y++) { @@ -2143,7 +2138,22 @@ qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) data32[i++] = pal[buf[x]>>4]; data32[i++] = pal[buf[x]&15]; } - buf += h.Width>>1; + buf += (h.Width+1)>>1; + } + + if (!OffsetofBMPBits) + { + for (y = 0; y < h.Height; y++) + { + i = (h.Height-1-y) * (h.Width); + for (x = 0; x < h.Width; x++) + { + if (buf[x>>3]&(1<<(7-(x&7)))) + data32[i] &= 0x00ffffff; + i++; + } + buf += (h.Width+7)>>3; + } } return (qbyte *)data32; @@ -2158,7 +2168,7 @@ qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) if (h.NumofColorIndices>256) return NULL; - data = buf+2; + data = buf; data += sizeof(h); for (i = 0; i < h.NumofColorIndices; i++) @@ -2166,7 +2176,10 @@ qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) pal[i] = data[i*4+2] + (data[i*4+1]<<8) + (data[i*4+0]<<16) + (255/*data[i*4+3]*/<<24); } - buf += h.OffsetofBMPBits; + if (OffsetofBMPBits) + buf += OffsetofBMPBits; + else + buf += h.SizeofBITMAPINFOHEADER + h.NumofColorIndices*4; data32 = BZ_Malloc(h.Width * h.Height*4); for (y = 0; y < h.Height; y++) { @@ -2179,12 +2192,30 @@ qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) buf += h.Width; } + if (!OffsetofBMPBits) + { + for (y = 0; y < h.Height; y++) + { + i = (h.Height-1-y) * (h.Width); + for (x = 0; x < h.Width; x++) + { + if (buf[x>>3]&(1<<(7-(x&7)))) + data32[i] &= 0x00ffffff; + i++; + } + buf += (h.Width+7)>>3; + } + } + return (qbyte *)data32; } else if (h.BitCount == 24) //24 bit... no 16? { int x, y; - buf += h.OffsetofBMPBits; + if (OffsetofBMPBits) + buf += OffsetofBMPBits; + else + buf += h.SizeofBITMAPINFOHEADER; data = BZ_Malloc(h.Width * h.Height*4); for (y = 0; y < h.Height; y++) { @@ -2202,12 +2233,50 @@ qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) return data; } + else if (h.BitCount == 32) + { + int x, y; + if (OffsetofBMPBits) + buf += OffsetofBMPBits; + else + buf += h.SizeofBITMAPINFOHEADER; + data = BZ_Malloc(h.Width * h.Height*4); + for (y = 0; y < h.Height; y++) + { + i = (h.Height-1-y) * (h.Width); + for (x = 0; x < h.Width; x++) + { + data[i*4+0] = buf[x*4+2]; + data[i*4+1] = buf[x*4+1]; + data[i*4+2] = buf[x*4+0]; + data[i*4+3] = buf[x*4+3]; + i++; + } + buf += h.Width*4; + } + + return data; + } else return NULL; return NULL; } +qbyte *ReadBMPFile(qbyte *buf, int length, int *width, int *height) +{ + unsigned short Type = buf[0] | (buf[1]<<8); + unsigned short Size = buf[2] | (buf[3]<<8) | (buf[4]<<16) | (buf[5]<<24); +// unsigned short Reserved1 = buf[6] | (buf[7]<<8); +// unsigned short Reserved2 = buf[8] | (buf[9]<<8); + unsigned short OffsetofBMPBits = buf[10] | (buf[11]<<8) | (buf[12]<<16) | (buf[13]<<24); + if (Type != ('B'|('M'<<8))) + return NULL; + if (Size > length) + return NULL; //it got truncated at some point + return ReadRawBMPFile(buf + 14, length-14, width, height, OffsetofBMPBits - 14); +} + qboolean WriteBMPFile(char *filename, enum fs_relative fsroot, qbyte *in, int instride, int width, int height, uploadfmt_t fmt) { int y; @@ -2218,6 +2287,7 @@ qboolean WriteBMPFile(char *filename, enum fs_relative fsroot, qbyte *in, int in int outstride; int bits = 32; int extraheadersize = sizeof(h4); + size_t fsize; memset(&h4, 0, sizeof(h4)); h4.ColourSpace[0] = 'W'; @@ -2274,10 +2344,10 @@ qboolean WriteBMPFile(char *filename, enum fs_relative fsroot, qbyte *in, int in outstride = width * (bits/8); outstride = (outstride+3)&~3; //bmp pads rows to a multiple of 4 bytes. - h.Size = 2+sizeof(h)+extraheadersize + outstride*height; - h.Reserved1 = 0; - h.Reserved2 = 0; - h.OffsetofBMPBits = 2+sizeof(h)+extraheadersize; //yes, this is misaligned. +// h.Size = 14+sizeof(h)+extraheadersize + outstride*height; +// h.Reserved1 = 0; +// h.Reserved2 = 0; +// h.OffsetofBMPBits = 2+sizeof(h)+extraheadersize; //yes, this is misaligned. h.SizeofBITMAPINFOHEADER = (sizeof(h)-12)+extraheadersize; h.Width = width; h.Height = height; @@ -2294,14 +2364,38 @@ qboolean WriteBMPFile(char *filename, enum fs_relative fsroot, qbyte *in, int in in += instride*(height-1); instride *= -1; - out = data = BZ_Malloc(h.Size); + fsize = 14+sizeof(h)+extraheadersize + outstride*height; //size + out = data = BZ_Malloc(fsize); + //Type *out++ = 'B'; *out++ = 'M'; + //Size + *out++ = fsize&0xff; + *out++ = (fsize>>8)&0xff; + *out++ = (fsize>>16)&0xff; + *out++ = (fsize>>24)&0xff; + //Reserved1 + y = 0; + *out++ = y&0xff; + *out++ = (y>>8)&0xff; + //Reserved1 + y = 0; + *out++ = y&0xff; + *out++ = (y>>8)&0xff; + //OffsetofBMPBits + y = 2+sizeof(h)+extraheadersize; //yes, this is misaligned. + *out++ = y&0xff; + *out++ = (y>>8)&0xff; + *out++ = (y>>16)&0xff; + *out++ = (y>>24)&0xff; + //bmpheader memcpy(out, &h, sizeof(h)); out += sizeof(h); + //v4 header memcpy(out, &h4, extraheadersize); out += extraheadersize; + //data for (y = 0; y < height; y++) { memcpy(out, in, width * (bits/8)); @@ -2310,12 +2404,69 @@ qboolean WriteBMPFile(char *filename, enum fs_relative fsroot, qbyte *in, int in in += instride; } - COM_WriteFile(filename, fsroot, data, h.Size); + COM_WriteFile(filename, fsroot, data, fsize); BZ_Free(data); return true; } +static qbyte *ReadICOFile(qbyte *buf, int length, int *width, int *height, const char *fname) +{ + qbyte *ret; + size_t imgcount = buf[4] | (buf[5]<<8); + struct + { + qbyte bWidth; + qbyte bHeight; + qbyte bColorCount; + qbyte bReserved; + unsigned short wPlanes; + unsigned short wBitCount; + unsigned short dwSize_low; + unsigned short dwSize_high; + unsigned short dwOffset_low; + unsigned short dwOffset_high; + } *img = (void*)(buf+6), *bestimg = NULL; + size_t bestpixels = 0; + size_t bestdepth = 0; + + //always favour the png first + for (imgcount = buf[4] | (buf[5]<<8), img = (void*)(buf+6); imgcount-->0; img++) + { + size_t cc = img->wBitCount; + size_t px = (img->bWidth?img->bWidth:256) * (img->bHeight?img->bHeight:256); + if (!cc) //if that was omitted, try and guess it based on raw image size. this is an over estimate. + cc = 8 * (bestimg->dwSize_low | (bestimg->dwSize_high<<16)) / px; + + if (!bestimg || cc > bestdepth || (cc == bestdepth && px > bestpixels)) + { + bestimg = img; + bestdepth = cc; + bestpixels = px; + } + } + + if (bestimg) + { + qbyte *indata = buf + (bestimg->dwOffset_low | (bestimg->dwOffset_high<<16)); + size_t insize = (bestimg->dwSize_low | (bestimg->dwSize_high<<16)); +#ifdef AVAIL_PNGLIB + if (insize > 4 && (indata[0] == 137 && indata[1] == 'P' && indata[2] == 'N' && indata[3] == 'G') && (ret = ReadPNGFile(indata, insize, width, height, fname))) + { + TRACE(("dbg: Read32BitImageFile: icon png\n")); + return ret; + } + else +#endif + if ((ret = ReadRawBMPFile(indata, insize, width, height, 0))) + { + TRACE(("dbg: Read32BitImageFile: icon png\n")); + return ret; + } + } + + return NULL; +} #ifndef NPFTE @@ -2784,7 +2935,13 @@ qbyte *Read32BitImageFile(qbyte *buf, int len, int *width, int *height, qboolean if (len > 2 && (buf[0] == 'B' && buf[1] == 'M') && (data = ReadBMPFile(buf, len, width, height))) { - TRACE(("dbg: Read32BitImageFile: bitmap\n")); + TRACE(("dbg: Read32BitImageFile: bmp\n")); + return data; + } + + if (len > 6 && buf[0]==0&&buf[1]==0 && buf[2]==1&&buf[3]==0 && (data = ReadICOFile(buf, len, width, height, fname))) + { + TRACE(("dbg: Read32BitImageFile: ico\n")); return data; } @@ -4220,12 +4377,29 @@ static qboolean Image_GenMip0(struct pendingtextureinfo *mips, unsigned int flag mips->mip[0].data = rgbadata; else { - mips->mip[0].data = BZ_Malloc(((mips->mip[0].width+3)&~3)*mips->mip[0].height*4); - //FIXME: should be sRGB-aware, but probably not a common path on hardware that can actually do srgb. - Image_ResampleTexture(rgbadata, imgwidth, imgheight, mips->mip[0].data, mips->mip[0].width, mips->mip[0].height); - if (freedata) - BZ_Free(rgbadata); - freedata = true; + switch(mips->encoding) + { + case PTI_RGBA8: + case PTI_RGBX8: + case PTI_BGRA8: + case PTI_BGRX8: + case PTI_RGBA8_SRGB: + case PTI_RGBX8_SRGB: + case PTI_BGRA8_SRGB: + case PTI_BGRX8_SRGB: + mips->mip[0].data = BZ_Malloc(((mips->mip[0].width+3)&~3)*mips->mip[0].height*4); + //FIXME: should be sRGB-aware, but probably not a common path on hardware that can actually do srgb. + Image_ResampleTexture(rgbadata, imgwidth, imgheight, mips->mip[0].data, mips->mip[0].width, mips->mip[0].height); + if (freedata) + BZ_Free(rgbadata); + freedata = true; + break; + default: //scaling not supported... + mips->mip[0].data = rgbadata; + mips->mip[0].width = imgwidth; + mips->mip[0].height = imgheight; + break; + } } } else diff --git a/engine/client/in_generic.c b/engine/client/in_generic.c index 7232b941e..e57db370e 100644 --- a/engine/client/in_generic.c +++ b/engine/client/in_generic.c @@ -3,14 +3,14 @@ #include "quakedef.h" -extern qboolean mouse_active; - -static cvar_t m_filter = CVARF("m_filter", "0", CVAR_ARCHIVE); +extern qboolean mouse_active; + +static cvar_t m_filter = CVARF("m_filter", "0", CVAR_ARCHIVE); static cvar_t m_forcewheel = CVARD("m_forcewheel", "1", "0: ignore mousewheels in apis where it is abiguous.\n1: Use mousewheel when it is treated as a third axis. Motion above a threshold is ignored, to avoid issues with an unknown threshold.\n2: Like 1, but excess motion is retained. The threshold specifies exact z-axis distance per notice."); -static cvar_t m_forcewheel_threshold = CVARD("m_forcewheel_threshold", "32", "Mousewheel graduations smaller than this will not trigger mousewheel deltas."); -static cvar_t m_strafeonright = CVARFD("m_strafeonright", "1", CVAR_ARCHIVE, "If 1, touching the right half of the touchscreen will strafe/move, while the left side will turn."); -static cvar_t m_fatpressthreshold = CVARFD("m_fatpressthreshold", "0.2", CVAR_ARCHIVE, "How fat your thumb has to be to register a fat press (touchscreens)."); -static cvar_t m_touchmajoraxis = CVARFD("m_touchmajoraxis", "1", CVAR_ARCHIVE, "When using a touchscreen, use only the major axis for strafing."); +static cvar_t m_forcewheel_threshold = CVARD("m_forcewheel_threshold", "32", "Mousewheel graduations smaller than this will not trigger mousewheel deltas."); +static cvar_t m_strafeonright = CVARFD("m_strafeonright", "1", CVAR_ARCHIVE, "If 1, touching the right half of the touchscreen will strafe/move, while the left side will turn."); +static cvar_t m_fatpressthreshold = CVARFD("m_fatpressthreshold", "0.2", CVAR_ARCHIVE, "How fat your thumb has to be to register a fat press (touchscreens)."); +static cvar_t m_touchmajoraxis = CVARFD("m_touchmajoraxis", "1", CVAR_ARCHIVE, "When using a touchscreen, use only the major axis for strafing."); static cvar_t m_slidethreshold = CVARFD("m_slidethreshold", "10", CVAR_ARCHIVE, "How far your finger needs to move to be considered a slide event (touchscreens)."); static cvar_t m_accel = CVARAFD("m_accel", "0", "cl_mouseAccel", CVAR_ARCHIVE, "Values >0 will amplify mouse movement proportional to velocity. Small values have great effect. A lot of good Quake Live players use around the 0.1-0.2 mark, but this depends on your mouse CPI and polling rate."); @@ -44,25 +44,25 @@ void QDECL joyaxiscallback(cvar_t *var, char *oldvalue) var->ival = 1*sign; else if (!Q_strcasecmp(end, "back") || !Q_strcasecmp(end, "moveback")) var->ival = 1*sign*-1; - else if (!Q_strcasecmp(end, "lookup") || !Q_strcasecmp(end, "pitchup")) - var->ival = 2*sign; - else if (!Q_strcasecmp(end, "lookdown") || !Q_strcasecmp(end, "pitchdown")) - var->ival = 2*sign*-1; - else if (!Q_strcasecmp(end, "moveright")) - var->ival = 3*sign; - else if (!Q_strcasecmp(end, "moveleft")) - var->ival = 3*sign*-1; - else if (!Q_strcasecmp(end, "right") || !Q_strcasecmp(end, "turnright")) - var->ival = 4*sign; - else if (!Q_strcasecmp(end, "left") || !Q_strcasecmp(end, "turnleft")) - var->ival = 4*sign*1; - else if (!Q_strcasecmp(end, "up") || !Q_strcasecmp(end, "moveup")) - var->ival = 5*sign; - else if (!Q_strcasecmp(end, "down") || !Q_strcasecmp(end, "movedown")) - var->ival = 5*sign*-1; - else if (!Q_strcasecmp(end, "rollright")) + else if (!Q_strcasecmp(end, "lookup") || !Q_strcasecmp(end, "pitchup")) + var->ival = 2*sign; + else if (!Q_strcasecmp(end, "lookdown") || !Q_strcasecmp(end, "pitchdown")) + var->ival = 2*sign*-1; + else if (!Q_strcasecmp(end, "moveright")) + var->ival = 3*sign; + else if (!Q_strcasecmp(end, "moveleft")) + var->ival = 3*sign*-1; + else if (!Q_strcasecmp(end, "right") || !Q_strcasecmp(end, "turnright")) + var->ival = 4*sign; + else if (!Q_strcasecmp(end, "left") || !Q_strcasecmp(end, "turnleft")) + var->ival = 4*sign*1; + else if (!Q_strcasecmp(end, "up") || !Q_strcasecmp(end, "moveup")) + var->ival = 5*sign; + else if (!Q_strcasecmp(end, "down") || !Q_strcasecmp(end, "movedown")) + var->ival = 5*sign*-1; + else if (!Q_strcasecmp(end, "rollright")) var->ival = 6*sign; - else if (!Q_strcasecmp(end, "rollleft")) + else if (!Q_strcasecmp(end, "rollleft")) var->ival = 6*sign*-1; } @@ -180,175 +180,175 @@ struct mouse_s float wheeldelta; int down; unsigned int updates; //tracks updates per second -} ptr[MAXPOINTERS]; -int touchcursor; //the cursor follows whichever finger was most recently pressed in preference to any mouse also on the same system - -#define MAXJOYAXIS 6 -#define MAXJOYSTICKS 8 -struct joy_s -{ - unsigned int qdeviceid; - float axis[MAXJOYAXIS]; -} joy[MAXJOYSTICKS]; - -void IN_Shutdown(void) -{ - INS_Shutdown(); -} - -void IN_ReInit(void) -{ - int i; - - for (i = 0; i < MAXPOINTERS; i++) - { - memset(&ptr[i], 0, sizeof(ptr[i])); - ptr[i].type = M_INVALID; - ptr[i].qdeviceid = i; - } - - for (i = 0; i < MAXJOYSTICKS; i++) - { - memset(&joy[i], 0, sizeof(joy[i])); - joy[i].qdeviceid = i; - } - - INS_ReInit(); -} - -struct remapctx -{ - char *type; - char *devicename; - unsigned int newdevid; - unsigned int found; - unsigned int failed; -}; -static void IN_DeviceIDs_DoRemap(void *vctx, const char *type, const char *devicename, unsigned int *qdevid) -{ - struct remapctx *ctx = vctx; - - if (!strcmp(ctx->type, type)) - if (!strcmp(ctx->devicename, devicename)) - { - if (qdevid) - *qdevid = ctx->newdevid; - else - ctx->failed = true; - ctx->found++; - } -} -void IN_DeviceIDs_Enumerate(void *ctx, const char *type, const char *devicename, unsigned int *qdevid) -{ - char buf[8192]; - devicename = COM_QuotedString(devicename, buf, sizeof(buf), false); - if (!qdevid) - Con_Printf("%s\t%s\t%s\n", type, "N/A", devicename); - else if (*qdevid == DEVID_UNSET) - Con_Printf("%s\t%s\t%s\n", type, "Unset", devicename); - else - Con_Printf("%s\t%u\t%s\n", type, *qdevid, devicename); -} - -void IN_DeviceIDs_f(void) -{ - struct remapctx ctx; - - if (Cmd_Argc() > 3) - { - ctx.failed = false; - ctx.found = 0; - ctx.type = Cmd_Argv(1); - ctx.newdevid = strtoul(Cmd_Argv(2), NULL, 0); - ctx.devicename = Cmd_Argv(3); - INS_EnumerateDevices(&ctx, IN_DeviceIDs_DoRemap); - - if (ctx.failed) - Con_Printf("device cannot be remapped\n"); - else if (!ctx.found) - Con_Printf("%s \"%s\" not known\n", ctx.type, ctx.devicename); - else if (!cl_warncmd.ival) - Con_Printf("device remapped\n"); - } - else - { - Con_Printf("Type\tMapping\tName\n"); - INS_EnumerateDevices(NULL, IN_DeviceIDs_Enumerate); - } -} - -float IN_DetermineMouseRate(void) -{ - float time = Sys_DoubleTime(); - static float timer; - static float last; - if (fabs(time - timer) > 1) - { - timer = time; - last = ptr[0].updates; - ptr[0].updates = 0; - } - return last; -} - -void IN_Init(void) -{ - int i; +} ptr[MAXPOINTERS]; +int touchcursor; //the cursor follows whichever finger was most recently pressed in preference to any mouse also on the same system + +#define MAXJOYAXIS 6 +#define MAXJOYSTICKS 8 +struct joy_s +{ + unsigned int qdeviceid; + float axis[MAXJOYAXIS]; +} joy[MAXJOYSTICKS]; + +void IN_Shutdown(void) +{ + INS_Shutdown(); +} + +void IN_ReInit(void) +{ + int i; + + for (i = 0; i < MAXPOINTERS; i++) + { + memset(&ptr[i], 0, sizeof(ptr[i])); + ptr[i].type = M_INVALID; + ptr[i].qdeviceid = i; + } + + for (i = 0; i < MAXJOYSTICKS; i++) + { + memset(&joy[i], 0, sizeof(joy[i])); + joy[i].qdeviceid = i; + } + + INS_ReInit(); +} + +struct remapctx +{ + char *type; + char *devicename; + unsigned int newdevid; + unsigned int found; + unsigned int failed; +}; +static void IN_DeviceIDs_DoRemap(void *vctx, const char *type, const char *devicename, unsigned int *qdevid) +{ + struct remapctx *ctx = vctx; + + if (!strcmp(ctx->type, type)) + if (!strcmp(ctx->devicename, devicename)) + { + if (qdevid) + *qdevid = ctx->newdevid; + else + ctx->failed = true; + ctx->found++; + } +} +void IN_DeviceIDs_Enumerate(void *ctx, const char *type, const char *devicename, unsigned int *qdevid) +{ + char buf[8192]; + devicename = COM_QuotedString(devicename, buf, sizeof(buf), false); + if (!qdevid) + Con_Printf("%s\t%s\t%s\n", type, "N/A", devicename); + else if (*qdevid == DEVID_UNSET) + Con_Printf("%s\t%s\t%s\n", type, "Unset", devicename); + else + Con_Printf("%s\t%u\t%s\n", type, *qdevid, devicename); +} + +void IN_DeviceIDs_f(void) +{ + struct remapctx ctx; + + if (Cmd_Argc() > 3) + { + ctx.failed = false; + ctx.found = 0; + ctx.type = Cmd_Argv(1); + ctx.newdevid = strtoul(Cmd_Argv(2), NULL, 0); + ctx.devicename = Cmd_Argv(3); + INS_EnumerateDevices(&ctx, IN_DeviceIDs_DoRemap); + + if (ctx.failed) + Con_Printf("device cannot be remapped\n"); + else if (!ctx.found) + Con_Printf("%s \"%s\" not known\n", ctx.type, ctx.devicename); + else if (!cl_warncmd.ival) + Con_Printf("device remapped\n"); + } + else + { + Con_Printf("Type\tMapping\tName\n"); + INS_EnumerateDevices(NULL, IN_DeviceIDs_Enumerate); + } +} + +float IN_DetermineMouseRate(void) +{ + float time = Sys_DoubleTime(); + static float timer; + static float last; + if (fabs(time - timer) > 1) + { + timer = time; + last = ptr[0].updates; + ptr[0].updates = 0; + } + return last; +} + +void IN_Init(void) +{ + int i; events_avail = 0; - events_used = 0; - - Cvar_Register (&m_filter, "input controls"); + events_used = 0; + + Cvar_Register (&m_filter, "input controls"); Cvar_Register (&m_forcewheel, "Input Controls"); - Cvar_Register (&m_forcewheel_threshold, "Input Controls"); - Cvar_Register (&m_strafeonright, "input controls"); - Cvar_Register (&m_fatpressthreshold, "input controls"); - Cvar_Register (&m_slidethreshold, "input controls"); - Cvar_Register (&m_touchmajoraxis, "input controls"); - Cvar_Register (&m_accel, "input controls"); - Cvar_Register (&m_accel_style, "input controls"); - Cvar_Register (&m_accel_power, "input controls"); - Cvar_Register (&m_accel_offset, "input controls"); - Cvar_Register (&m_accel_senscap, "input controls"); - - for (i = 0; i < 6; i++) - { - Cvar_Register (&joy_advaxis[i], "input controls"); - Cvar_Register (&joy_advaxisscale[i], "input controls"); - - Cvar_ForceCallback(&joy_advaxis[i]); - } - for (i = 0; i < 3; i++) - { - Cvar_Register (&joy_anglesens[i], "input controls"); - Cvar_Register (&joy_movesens[i], "input controls"); - Cvar_Register (&joy_anglethreshold[i], "input controls"); - Cvar_Register (&joy_movethreshold[i], "input controls"); - } - Cvar_Register (&joy_exponent, "input controls"); - Cvar_Register (&joy_radialdeadzone, "input controls"); - - Cmd_AddCommand ("in_deviceids", IN_DeviceIDs_f); - - INS_Init(); -} - -//tells the keys.c code whether the cursor is currently active, causing mouse clicks instead of binds. -qboolean IN_MouseDevIsTouch(unsigned int devid) -{ - if (devid < MAXPOINTERS) - return ptr[devid].type == M_TOUCH; - return false; -} -//there was no ui to click on at least... -//translates MOUSE1 press events into begin-look-or-strafe events. -//translates to MOUSE2 accordingly -//returns 0 if it ate it completely. -int IN_TranslateMButtonPress(unsigned int devid) -{ - int ret; - if (!ptr[devid].down) - { - //set the cursor-pressed state, so we begin to look/strafe around + Cvar_Register (&m_forcewheel_threshold, "Input Controls"); + Cvar_Register (&m_strafeonright, "input controls"); + Cvar_Register (&m_fatpressthreshold, "input controls"); + Cvar_Register (&m_slidethreshold, "input controls"); + Cvar_Register (&m_touchmajoraxis, "input controls"); + Cvar_Register (&m_accel, "input controls"); + Cvar_Register (&m_accel_style, "input controls"); + Cvar_Register (&m_accel_power, "input controls"); + Cvar_Register (&m_accel_offset, "input controls"); + Cvar_Register (&m_accel_senscap, "input controls"); + + for (i = 0; i < 6; i++) + { + Cvar_Register (&joy_advaxis[i], "input controls"); + Cvar_Register (&joy_advaxisscale[i], "input controls"); + + Cvar_ForceCallback(&joy_advaxis[i]); + } + for (i = 0; i < 3; i++) + { + Cvar_Register (&joy_anglesens[i], "input controls"); + Cvar_Register (&joy_movesens[i], "input controls"); + Cvar_Register (&joy_anglethreshold[i], "input controls"); + Cvar_Register (&joy_movethreshold[i], "input controls"); + } + Cvar_Register (&joy_exponent, "input controls"); + Cvar_Register (&joy_radialdeadzone, "input controls"); + + Cmd_AddCommand ("in_deviceids", IN_DeviceIDs_f); + + INS_Init(); +} + +//tells the keys.c code whether the cursor is currently active, causing mouse clicks instead of binds. +qboolean IN_MouseDevIsTouch(unsigned int devid) +{ + if (devid < MAXPOINTERS) + return ptr[devid].type == M_TOUCH; + return false; +} +//there was no ui to click on at least... +//translates MOUSE1 press events into begin-look-or-strafe events. +//translates to MOUSE2 accordingly +//returns 0 if it ate it completely. +int IN_TranslateMButtonPress(unsigned int devid) +{ + int ret; + if (!ptr[devid].down) + { + //set the cursor-pressed state, so we begin to look/strafe around ptr[devid].down = 1; ptr[devid].moveddist = 0; ptr[devid].downpos[0] = ptr[devid].oldpos[0]; @@ -362,14 +362,14 @@ int IN_TranslateMButtonPress(unsigned int devid) //this is the key binding that the press should use ret = (m_strafeonright.ival && ptr[devid].downpos[0] > vid.pixelwidth/2)?K_MOUSE2:K_MOUSE1; } - - return ret; -} - -/*a 'pointer' is either a multitouch pointer, or a separate device -note that mice use the keyboard button api, but separate devices*/ -void IN_Commands(void) -{ + + return ret; +} + +/*a 'pointer' is either a multitouch pointer, or a separate device +note that mice use the keyboard button api, but separate devices*/ +void IN_Commands(void) +{ struct eventlist_s *ev; INS_Commands(); @@ -513,10 +513,10 @@ void IN_Commands(void) break; } events_used++; - } -} - -void IN_MoveMouse(struct mouse_s *mouse, float *movements, int pnum, float frametime) + } +} + +void IN_MoveMouse(struct mouse_s *mouse, float *movements, int pnum, float frametime) { int mx, my; double mouse_x, mouse_y, mouse_deltadist; @@ -619,38 +619,38 @@ void IN_MoveMouse(struct mouse_s *mouse, float *movements, int pnum, float frame if (mouse->type == M_TOUCH) { - if (m_strafeonright.ival && mouse->downpos[0] > vid.pixelwidth/2 && movements != NULL && !Key_Dest_Has(~kdm_game)) - { - //if they're strafing, calculate the speed to move at based upon their displacement - if (mouse->down) - { - mx = mouse->oldpos[0] - (vid.pixelwidth*3)/4; - my = mouse->oldpos[1] - (vid.pixelheight*3)/4; - - //mx = (mouse->oldpos[0] - mouse->downpos[0])*0.1; - //my = (mouse->oldpos[1] - mouse->downpos[1])*0.1; - } - else - { - mx = 0; - my = 0; - } - - if (m_touchmajoraxis.ival) - { - //major axis only - if (abs(mx) > abs(my)) - my = 0; - else - mx = 0; - } - - strafe_x = true; - strafe_y = true; - } - else - { - strafe_x = false; + if (m_strafeonright.ival && mouse->downpos[0] > vid.pixelwidth/2 && movements != NULL && !Key_Dest_Has(~kdm_game)) + { + //if they're strafing, calculate the speed to move at based upon their displacement + if (mouse->down) + { + mx = mouse->oldpos[0] - (vid.pixelwidth*3)/4; + my = mouse->oldpos[1] - (vid.pixelheight*3)/4; + + //mx = (mouse->oldpos[0] - mouse->downpos[0])*0.1; + //my = (mouse->oldpos[1] - mouse->downpos[1])*0.1; + } + else + { + mx = 0; + my = 0; + } + + if (m_touchmajoraxis.ival) + { + //major axis only + if (abs(mx) > abs(my)) + my = 0; + else + mx = 0; + } + + strafe_x = true; + strafe_y = true; + } + else + { + strafe_x = false; strafe_y = false; //boost sensitivity so that the default works okay. @@ -797,35 +797,35 @@ void IN_MoveMouse(struct mouse_s *mouse, float *movements, int pnum, float frame else movements[0] -= m_forward.value * mouse_y; } -} - -//rescales threshold-1 down 0-1 -static float joydeadzone(float mag, float deadzone) -{ - if (mag > 1) //erg? - mag = 1; - if (mag > deadzone) - { - mag -= deadzone; - mag = mag / (1.f-deadzone); - } - else - mag = 0; - return mag; -} - -void IN_MoveJoystick(struct joy_s *joy, float *movements, int pnum, float frametime) -{ - float mag; - vec3_t jlook, jstrafe; +} + +//rescales threshold-1 down 0-1 +static float joydeadzone(float mag, float deadzone) +{ + if (mag > 1) //erg? + mag = 1; + if (mag > deadzone) + { + mag -= deadzone; + mag = mag / (1.f-deadzone); + } + else + mag = 0; + return mag; +} + +void IN_MoveJoystick(struct joy_s *joy, float *movements, int pnum, float frametime) +{ + float mag; + vec3_t jlook, jstrafe; + + int wpnum, i; + for (i = 0; i < MAXJOYAXIS; i++) + if (joy->axis[i]) + break; + if (i == MAXJOYAXIS) + return; - int wpnum, i; - for (i = 0; i < MAXJOYAXIS; i++) - if (joy->axis[i]) - break; - if (i == MAXJOYAXIS) - return; - /*each device will be processed when its player comes to be processed*/ wpnum = cl.splitclients; if (wpnum < 1) @@ -835,91 +835,91 @@ void IN_MoveJoystick(struct joy_s *joy, float *movements, int pnum, float framet else wpnum = joy->qdeviceid % wpnum; if (wpnum != pnum) - return; - - memset(jstrafe, 0, sizeof(jstrafe)); - memset(jlook, 0, sizeof(jlook)); - - for (i = 0; i < 6; i++) - { - int ax = joy_advaxis[i].ival; - switch(ax) - { - default: - case 0: //dead axis - break; - case 1: - case 3: - case 5: - jstrafe[(ax-1)/2] += joy->axis[i] * joy_advaxisscale[i].value; - break; - case -1: - case -3: - case -5: - jstrafe[(-ax-1)/2] -= joy->axis[i] * joy_advaxisscale[i].value; - - case 2: - case 4: - case 6: - jlook[(ax-2)/2] += joy->axis[i] * joy_advaxisscale[i].value; - break; - case -2: - case -4: - case -6: - jlook[(-ax-2)/2] -= joy->axis[i] * joy_advaxisscale[i].value; - break; - } - } - - //uses a radial deadzone for x+y axis, and separate out the z axis, just because most controllers are 2d affairs with any 3rd axis being a separate knob. - //deadzone values are stolen from microsoft's xinput documentation. they seem quite large to me - I guess that means that xbox controllers are just dodgy imprecise crap with excessive amounts of friction and finger grease. - - if (joy_radialdeadzone.ival) - { - mag = joydeadzone(sqrt(jlook[0]*jlook[0] + jlook[1]*jlook[1]), sqrt(joy_anglethreshold[0].value*joy_anglethreshold[0].value + joy_anglethreshold[1].value*joy_anglethreshold[1].value)); - mag = pow(mag, joy_exponent.value); - jlook[0] *= mag; - jlook[1] *= mag; - mag = joydeadzone(fabs(jlook[2]), joy_anglethreshold[2].value); - jlook[2] *= mag; - - mag = joydeadzone(sqrt(jstrafe[0]*jstrafe[0] + jstrafe[1]*jstrafe[1]), sqrt(joy_movethreshold[0].value*joy_movethreshold[0].value + joy_movethreshold[1].value*joy_movethreshold[1].value)); - mag = pow(mag, joy_exponent.value); - jstrafe[0] *= mag; - jstrafe[1] *= mag; - mag = joydeadzone(fabs(jstrafe[2]), joy_movethreshold[2].value); - jstrafe[2] *= mag; - } - else - { - for (i = 0; i < 3; i++) - { - mag = joydeadzone(fabs(jlook[i]), joy_anglethreshold[i].value); - mag = pow(mag, joy_exponent.value); - jlook[i] *= mag; - - mag = joydeadzone(fabs(jstrafe[i]), joy_movethreshold[i].value); - mag = pow(mag, joy_exponent.value); - jstrafe[i] *= mag; - } - } - - if (Key_Dest_Has(~kdm_game)) - { - VectorClear(jlook); - VectorClear(jstrafe); - } - + return; + + memset(jstrafe, 0, sizeof(jstrafe)); + memset(jlook, 0, sizeof(jlook)); + + for (i = 0; i < 6; i++) + { + int ax = joy_advaxis[i].ival; + switch(ax) + { + default: + case 0: //dead axis + break; + case 1: + case 3: + case 5: + jstrafe[(ax-1)/2] += joy->axis[i] * joy_advaxisscale[i].value; + break; + case -1: + case -3: + case -5: + jstrafe[(-ax-1)/2] -= joy->axis[i] * joy_advaxisscale[i].value; + + case 2: + case 4: + case 6: + jlook[(ax-2)/2] += joy->axis[i] * joy_advaxisscale[i].value; + break; + case -2: + case -4: + case -6: + jlook[(-ax-2)/2] -= joy->axis[i] * joy_advaxisscale[i].value; + break; + } + } + + //uses a radial deadzone for x+y axis, and separate out the z axis, just because most controllers are 2d affairs with any 3rd axis being a separate knob. + //deadzone values are stolen from microsoft's xinput documentation. they seem quite large to me - I guess that means that xbox controllers are just dodgy imprecise crap with excessive amounts of friction and finger grease. + + if (joy_radialdeadzone.ival) + { + mag = joydeadzone(sqrt(jlook[0]*jlook[0] + jlook[1]*jlook[1]), sqrt(joy_anglethreshold[0].value*joy_anglethreshold[0].value + joy_anglethreshold[1].value*joy_anglethreshold[1].value)); + mag = pow(mag, joy_exponent.value); + jlook[0] *= mag; + jlook[1] *= mag; + mag = joydeadzone(fabs(jlook[2]), joy_anglethreshold[2].value); + jlook[2] *= mag; + + mag = joydeadzone(sqrt(jstrafe[0]*jstrafe[0] + jstrafe[1]*jstrafe[1]), sqrt(joy_movethreshold[0].value*joy_movethreshold[0].value + joy_movethreshold[1].value*joy_movethreshold[1].value)); + mag = pow(mag, joy_exponent.value); + jstrafe[0] *= mag; + jstrafe[1] *= mag; + mag = joydeadzone(fabs(jstrafe[2]), joy_movethreshold[2].value); + jstrafe[2] *= mag; + } + else + { + for (i = 0; i < 3; i++) + { + mag = joydeadzone(fabs(jlook[i]), joy_anglethreshold[i].value); + mag = pow(mag, joy_exponent.value); + jlook[i] *= mag; + + mag = joydeadzone(fabs(jstrafe[i]), joy_movethreshold[i].value); + mag = pow(mag, joy_exponent.value); + jstrafe[i] *= mag; + } + } + + if (Key_Dest_Has(~kdm_game)) + { + VectorClear(jlook); + VectorClear(jstrafe); + } + if (in_speed.state[pnum] & 1) { VectorScale(jlook, 360*cl_movespeedkey.value, jlook); VectorScale(jstrafe, 360*cl_movespeedkey.value, jstrafe); - } - VectorScale(jlook, 360*frametime, jlook); - + } + VectorScale(jlook, 360*frametime, jlook); + if (!movements) //if this is null, gamecode should still get inputs, just no camera looking or anything. - return; - + return; + //angle changes cl.playerview[pnum].viewanglechange[PITCH] += joy_anglesens[0].value * jlook[0]; cl.playerview[pnum].viewanglechange[YAW] += joy_anglesens[1].value * jlook[1]; @@ -928,25 +928,25 @@ void IN_MoveJoystick(struct joy_s *joy, float *movements, int pnum, float framet if (in_mlook.state[pnum] & 1) V_StopPitchDrift (&cl.playerview[pnum]); - //movement + //movement movements[0] += joy_movesens[0].value * cl_forwardspeed.value * jstrafe[0]; - movements[1] += joy_movesens[1].value * cl_sidespeed.value * jstrafe[1]; - movements[2] += joy_movesens[2].value * cl_upspeed.value * jstrafe[2]; -} - -void IN_Move (float *movements, int pnum, float frametime) -{ - int i; - INS_Move(movements, pnum); - for (i = 0; i < MAXPOINTERS; i++) - IN_MoveMouse(&ptr[i], movements, pnum, frametime); - - for (i = 0; i < MAXJOYSTICKS; i++) - IN_MoveJoystick(&joy[i], movements, pnum, frametime); -} - -void IN_JoystickAxisEvent(unsigned int devid, int axis, float value) -{ + movements[1] += joy_movesens[1].value * cl_sidespeed.value * jstrafe[1]; + movements[2] += joy_movesens[2].value * cl_upspeed.value * jstrafe[2]; +} + +void IN_Move (float *movements, int pnum, float frametime) +{ + int i; + INS_Move(movements, pnum); + for (i = 0; i < MAXPOINTERS; i++) + IN_MoveMouse(&ptr[i], movements, pnum, frametime); + + for (i = 0; i < MAXJOYSTICKS; i++) + IN_MoveJoystick(&joy[i], movements, pnum, frametime); +} + +void IN_JoystickAxisEvent(unsigned int devid, int axis, float value) +{ struct eventlist_s *ev = in_newevent(); if (!ev) return; @@ -954,8 +954,8 @@ void IN_JoystickAxisEvent(unsigned int devid, int axis, float value) ev->devid = devid; ev->joy.axis = axis; ev->joy.value = value; - in_finishevent(); -} + in_finishevent(); +} void IN_KeyEvent(unsigned int devid, int down, int keycode, int unicode) { diff --git a/engine/client/in_sdl.c b/engine/client/in_sdl.c index 2cdbb1090..cf14af191 100644 --- a/engine/client/in_sdl.c +++ b/engine/client/in_sdl.c @@ -2,6 +2,11 @@ #include +#if SDL_VERSION_ATLEAST(2,0,6) && defined(VKQUAKE) +#include +#include "../vk/vkrenderer.h" +#endif + #if SDL_MAJOR_VERSION >=2 SDL_Window *sdlwindow; #else @@ -776,15 +781,27 @@ void Sys_SendKeyEvents(void) default: break; case SDL_WINDOWEVENT_SIZE_CHANGED: - #if SDL_PATCHLEVEL >= 1 - SDL_GL_GetDrawableSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); //get the proper physical size. - #else - SDL_GetWindowSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); - #endif +#if SDL_VERSION_ATLEAST(2,0,6) && defined(VKQUAKE) + if (qrenderer == QR_VULKAN) { - extern cvar_t vid_conautoscale, vid_conwidth; //make sure the screen is updated properly. - Cvar_ForceCallback(&vid_conautoscale); - Cvar_ForceCallback(&vid_conwidth); + unsigned window_width, window_height; + 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; + } + else +#endif + { + #if SDL_PATCHLEVEL >= 1 + SDL_GL_GetDrawableSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); //get the proper physical size. + #else + SDL_GetWindowSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); + #endif + { + extern cvar_t vid_conautoscale, vid_conwidth; //make sure the screen is updated properly. + Cvar_ForceCallback(&vid_conautoscale); + Cvar_ForceCallback(&vid_conwidth); + } } break; case SDL_WINDOWEVENT_FOCUS_GAINED: diff --git a/engine/client/m_download.c b/engine/client/m_download.c index 95f1140e0..eb933632b 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -187,7 +187,9 @@ static char *manifestpackages; //metapackage named by the manicfest. static char *declinedpackages; //metapackage named by the manicfest. static int domanifestinstall; //SECURITY_MANIFEST_* +#ifdef PLUGINS static qboolean pluginpromptshown; //so we only show prompts for new externally-installed plugins once, instead of every time the file is reloaded. +#endif static qboolean doautoupdate; //updates will be marked (but not applied without the user's actions) static qboolean pkg_updating; //when flagged, further changes are blocked until completion. @@ -2550,6 +2552,7 @@ void PM_ManifestPackage(const char *metaname, int security) void PM_Command_f(void) { + size_t i; package_t *p; const char *act = Cmd_Argv(1); const char *key; @@ -2706,8 +2709,13 @@ void PM_Command_f(void) { PM_RevertChanges(); } + else if (!strcmp(act, "update")) + { //flush package cache, make a new request. + for (i = 0; i < numdownloadablelists; i++) + downloadablelist[i].received = 0; + } else if (!strcmp(act, "upgrade")) - { + { //auto-mark any updated packages. unsigned int changes = PM_MarkUpdates(); if (changes) { @@ -2718,7 +2726,7 @@ void PM_Command_f(void) Con_Printf("Already using latest versions of all packages\n"); } else if (!strcmp(act, "add") || !strcmp(act, "get") || !strcmp(act, "install") || !strcmp(act, "enable")) - { + { //FIXME: make sure this updates. int arg = 2; for (arg = 2; arg < Cmd_Argc(); arg++) { @@ -2732,7 +2740,7 @@ void PM_Command_f(void) PM_PrintChanges(); } else if (!strcmp(act, "reinstall")) - { + { //fixme: favour the current verson. int arg = 2; for (arg = 2; arg < Cmd_Argc(); arg++) { @@ -2784,7 +2792,7 @@ void PM_Command_f(void) PM_PrintChanges(); } else - Con_Printf("%s: Unknown action %s\nShould be one of list, show, search, revert, add, rem, del, changes, apply\n", Cmd_Argv(0), act); + Con_Printf("%s: Unknown action %s\nShould be one of list, show, search, upgrade, revert, add, rem, del, changes, apply\n", Cmd_Argv(0), act); } qboolean PM_FindUpdatedEngine(char *syspath, size_t syspathsize) diff --git a/engine/client/m_mp3.c b/engine/client/m_mp3.c index 3431718c9..b29b5e6d3 100644 --- a/engine/client/m_mp3.c +++ b/engine/client/m_mp3.c @@ -2434,9 +2434,7 @@ cin_t *Media_StartCin(char *name) if (!cin) cin = Media_Plugin_TryLoad(name); #endif - if (!cin) - Con_Printf("Unable to decode \"%s\"\n", name); - else + if (cin) cin->filmstarttime = realtime; return cin; } @@ -2465,7 +2463,6 @@ qboolean Media_BeginNextFilm(void) } videoshader = R_RegisterCustom(sname, SUF_NONE, Shader_DefaultCinematic, p->name); - Z_Free(p); cin = R_ShaderGetCinematic(videoshader); if (cin) @@ -2474,16 +2471,15 @@ qboolean Media_BeginNextFilm(void) Media_Send_Reset(cin); if (cin->changestream) cin->changestream(cin, "cmd:focus"); - - return true; } else { + Con_Printf("Unable to play cinematic %s\n", p->name); R_UnloadShader(videoshader); videoshader = NULL; - - return false; } + Z_Free(p); + return !!videoshader; } qboolean Media_StopFilm(qboolean all) { @@ -2515,6 +2511,8 @@ qboolean Media_StopFilm(qboolean all) } //for q2 cinematic-maps. + //q2 sends 'nextserver' when the client's cinematics end so that the game can progress to the next map. + //qc might want to make use of it too, but its probably best to just send a playfilm command at the start of the map and then just wait it out. maybe treat nextserver as an unpause request. if (!videoshader && cls.state == ca_active) { CL_SendClientCommand(true, "nextserver %i", cl.servercount); @@ -2693,7 +2691,7 @@ void Media_Send_KeyEvent(cin_t *cin, int button, int unicode, int event) cin = R_ShaderGetCinematic(videoshader); if (!cin) return; - if (cin->key) + if (cin->key) cin->key(cin, button, unicode, event); else if (button == K_SPACE && !event) { @@ -2819,8 +2817,19 @@ void Media_PlayFilm_f (void) void Media_PlayVideoWindowed_f (void) { char *videomap = Cmd_Argv(1); + shader_t *s; + console_t *con; + if (!qrenderer) + return; + s = R_RegisterCustom(va("consolevid_%s", videomap), SUF_NONE, Shader_DefaultCinematic, videomap); + if (!R_ShaderGetCinematic(s)) + { + R_UnloadShader(s); + Con_Printf("Unable to load video %s\n", videomap); + return; + } - console_t *con = Con_Create(videomap, 0); + con = Con_Create(videomap, 0); if (!con) return; con->parseflags = PFS_FORCEUTF8; @@ -2834,7 +2843,7 @@ void Media_PlayVideoWindowed_f (void) Q_strncpyz(con->backimage, "", sizeof(con->backimage)); if (con->backshader) R_UnloadShader(con->backshader); - con->backshader = R_RegisterCustom(va("consolevid_%s", videomap), SUF_NONE, Shader_DefaultCinematic, videomap); + con->backshader = s; Con_SetActive(con); } @@ -3526,9 +3535,8 @@ void Media_RecordFrame (void) buffer = qglMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB); if (buffer) { - qbyte *firstrow = (offscreen_queue[frame].stride<0)?buffer - offscreen_queue[frame].stride*(offscreen_queue[frame].height-1):buffer; //FIXME: thread these (with audio too, to avoid races) - currentcapture_funcs->capture_video(currentcapture_ctx, offscreen_captureframe, firstrow, offscreen_queue[frame].stride, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); + currentcapture_funcs->capture_video(currentcapture_ctx, offscreen_captureframe, buffer, offscreen_queue[frame].stride, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); qglUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); } offscreen_captureframe++; @@ -3536,12 +3544,14 @@ void Media_RecordFrame (void) frame = captureframe%countof(offscreen_queue); //if we have no pbo yet, create one. - if (!offscreen_queue[frame].pbo_handle) + if (!offscreen_queue[frame].pbo_handle || offscreen_queue[frame].width != vid.fbpwidth || offscreen_queue[frame].height != vid.fbpheight) { int imagesize = 0; + if (offscreen_queue[frame].pbo_handle) + qglDeleteBuffersARB(1, &offscreen_queue[frame].pbo_handle); offscreen_queue[frame].format = offscreen_format; - offscreen_queue[frame].width = vid.pixelwidth; - offscreen_queue[frame].height = vid.pixelheight; + offscreen_queue[frame].width = vid.fbpwidth; + offscreen_queue[frame].height = vid.fbpheight; switch(offscreen_queue[frame].format) { case TF_BGR24: @@ -3556,13 +3566,13 @@ void Media_RecordFrame (void) break; } - offscreen_queue[frame].stride = vid.pixelwidth*-imagesize;//gl is upside down + offscreen_queue[frame].stride = vid.fbpwidth*-imagesize;//gl is upside down imagesize *= offscreen_queue[frame].width * offscreen_queue[frame].height; qglGenBuffersARB(1, &offscreen_queue[frame].pbo_handle); qglBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, offscreen_queue[frame].pbo_handle); - qglBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, imagesize, NULL, GL_STATIC_READ_ARB); + qglBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, imagesize, NULL, GL_STREAM_READ_ARB); } //get the gpu to copy the texture into the pbo. the driver should pipeline this read until we actually map the pbo, hopefully avoiding stalls @@ -3671,7 +3681,7 @@ static unsigned int MSD_GetDMAPos(soundcardinfo_t *sc) s = captureframe*(sc->sn.speed*captureframeinterval); -// s >>= (sc->sn.samplebits/8) - 1; +// s >>= sc->sn.samplebytes - 1; s *= sc->sn.numchannels; return s; } @@ -3701,7 +3711,7 @@ static void MSD_Submit(soundcardinfo_t *sc, int start, int end) if (framestosubmit > maxframes) framestosubmit = maxframes; - bytesperframe = sc->sn.numchannels*sc->sn.samplebits/8; + bytesperframe = sc->sn.numchannels*sc->sn.samplebytes; offset = (lastpos % (sc->sn.samples/sc->sn.numchannels)); @@ -3759,7 +3769,22 @@ void Media_InitFakeSoundDevice (int speed, int channels, int samplebits) sc->sn.samples = speed*0.5; sc->sn.speed = speed; - sc->sn.samplebits = samplebits; + switch(samplebits) + { + case 32: + sc->sn.samplebytes = 4; + sc->sn.sampleformat = QSF_F32; + break; + default: + case 16: + sc->sn.samplebytes = 2; + sc->sn.sampleformat = QSF_S16; + break; + case 8: + sc->sn.samplebytes = 1; + sc->sn.sampleformat = QSF_U8; + break; + } sc->sn.samplepos = 0; sc->sn.numchannels = channels; sc->inactive_sound = true; @@ -3767,7 +3792,7 @@ void Media_InitFakeSoundDevice (int speed, int channels, int samplebits) sc->sn.samples -= sc->sn.samples%1152; //truncate slightly to keep vfw happy. sc->samplequeue = -1; - sc->sn.buffer = (unsigned char *) BZ_Malloc(sc->sn.samples*sc->sn.numchannels*(sc->sn.samplebits/8)); + sc->sn.buffer = (unsigned char *) BZ_Malloc(sc->sn.samples*sc->sn.numchannels*sc->sn.samplebytes); Z_ReallocElements((void**)&sc->channel, &sc->max_chans, MAX_DYNAMIC_CHANNELS+NUM_AMBIENTS+NUM_MUSICS, sizeof(*sc->channel)); @@ -3801,8 +3826,7 @@ void Media_StopRecordFilm_f (void) buffer = qglMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB); if (buffer) { - qbyte *firstrow = (offscreen_queue[frame].stride<0)?buffer - offscreen_queue[frame].stride*(offscreen_queue[frame].height-1):buffer; - currentcapture_funcs->capture_video(currentcapture_ctx, offscreen_captureframe, firstrow, offscreen_queue[frame].stride, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); + currentcapture_funcs->capture_video(currentcapture_ctx, offscreen_captureframe, buffer, offscreen_queue[frame].stride, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); qglUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); } offscreen_captureframe++; @@ -3931,8 +3955,8 @@ static void Media_RecordFilm (char *recordingname, qboolean demo) capturingfbo = true; capturetexture = R2D_RT_Configure("$democapture", capturewidth.ival, captureheight.ival, TF_BGRA32, RT_IMAGEFLAGS); captureoldfbo = GLBE_FBO_Update(&capturefbo, FBO_RB_DEPTH|(Sh_StencilShadowsActive()?FBO_RB_STENCIL:0), &capturetexture, 1, r_nulltex, capturewidth.ival, captureheight.ival, 0); - vid.fbpwidth = capturewidth.ival; - vid.fbpheight = captureheight.ival; + vid.fbpwidth = capturetexture->width; + vid.fbpheight = capturetexture->height; vid.framebuffer = capturetexture; } #endif diff --git a/engine/client/p_script.c b/engine/client/p_script.c index fdea8eb7a..c83e401bf 100644 --- a/engine/client/p_script.c +++ b/engine/client/p_script.c @@ -1376,7 +1376,16 @@ void P_ParticleEffect_f(void) } else if (!strcmp(var, "alpha")) + { ptype->alpha = atof(value); + if (Cmd_Argc()>2) + ptype->alpharand = atof(Cmd_Argv(2)) - ptype->alpha; + if (Cmd_Argc()>3) + { + ptype->alphachange = atof(Cmd_Argv(3)); + setalphadelta = true; + } + } else if (!strcmp(var, "alpharand")) ptype->alpharand = atof(value); #ifndef NOLEGACY diff --git a/engine/client/pr_clcmd.c b/engine/client/pr_clcmd.c index cdbc521e8..494f6d377 100644 --- a/engine/client/pr_clcmd.c +++ b/engine/client/pr_clcmd.c @@ -496,7 +496,7 @@ void QCBUILTIN PF_cs_media_create (pubprogfuncs_t *prinst, struct globalvars_s * "videomap %s\n" "rgbgen vertex\n" "alphagen vertex\n" - "blendfunc blend\n" + "blendfunc gl_one gl_one_minus_src_alpha\n" "nodepth\n" "}\n" "}\n", @@ -537,17 +537,20 @@ void QCBUILTIN PF_cs_media_command (pubprogfuncs_t *prinst, struct globalvars_s return; Media_Send_Command(cin, command); } -// #490 float(string name, float key, float eventtype) gecko_keyevent +// #490 float(string name, float key, float eventtype, optional float charcode) gecko_keyevent void QCBUILTIN PF_cs_media_keyevent (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { const char *shader = PR_GetStringOfs(prinst, OFS_PARM0); int key = G_FLOAT(OFS_PARM1); int eventtype = G_FLOAT(OFS_PARM2); + int charcode = (prinst->callargc>3)?G_FLOAT(OFS_PARM3):((key>127)?0:key); cin_t *cin; cin = R_ShaderFindCinematic(shader); + G_FLOAT(OFS_RETURN) = 0; if (!cin) return; - Media_Send_KeyEvent(cin, MP_TranslateQCtoFTECodes(key), (key>127)?0:key, eventtype); + Media_Send_KeyEvent(cin, MP_TranslateQCtoFTECodes(key), charcode, eventtype); + G_FLOAT(OFS_RETURN) = 1; } // #491 void(string name, float x, float y) gecko_mousemove void QCBUILTIN PF_cs_media_mousemove (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index 7854b3c75..d55a1225f 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -348,9 +348,9 @@ static void CSQC_FindGlobals(qboolean nofuncs) { etype_t etype = ev_void; if (!csqcg.trace_surfaceflagsi) - csqcg.trace_surfaceflagsi = PR_FindGlobal(csqcprogs, "trace_surfaceflags", 0, &etype); + csqcg.trace_surfaceflagsi = (int*)PR_FindGlobal(csqcprogs, "trace_surfaceflags", 0, &etype); if (!csqcg.trace_endcontentsi) - csqcg.trace_endcontentsi = PR_FindGlobal(csqcprogs, "trace_endcontents", 0, &etype); + csqcg.trace_endcontentsi = (int*)PR_FindGlobal(csqcprogs, "trace_endcontents", 0, &etype); } #else if (!csqcg.trace_surfaceflagsf && !csqcg.trace_surfaceflagsi) @@ -1885,14 +1885,24 @@ void QCBUILTIN PF_R_GetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_ case VF_SIZE_X: *r = r_refdef.grect.width; + if (csqc_isdarkplaces) + *r *= (float)vid.pixelwidth / vid.width; break; case VF_SIZE_Y: *r = r_refdef.grect.height; + if (csqc_isdarkplaces) + *r *= (float)vid.pixelheight / vid.height; break; case VF_SIZE: r[0] = r_refdef.grect.width; r[1] = r_refdef.grect.height; r[2] = 0; + + if (csqc_isdarkplaces) + { + r[0] *= (float)vid.pixelwidth / vid.width; + r[1] *= (float)vid.pixelheight / vid.height; + } break; case VF_MIN_X: @@ -2076,6 +2086,12 @@ void QCBUILTIN PF_R_SetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_ r_refdef.grect.width = p[0]; r_refdef.grect.height = p[1]; r_refdef.dirty |= RDFD_FOV; + + if (csqc_isdarkplaces) + { + r_refdef.grect.width *= (float)vid.width / vid.pixelwidth; + r_refdef.grect.height *= (float)vid.height / vid.pixelheight; + } break; case VF_MIN_X: @@ -7671,8 +7687,16 @@ qboolean CSQC_DrawView(void) { void *pr_globals = PR_globals(csqcprogs, PR_CURRENT); - G_FLOAT(OFS_PARM0) = vid.width; - G_FLOAT(OFS_PARM1) = vid.height; + if (csqc_isdarkplaces) + { //fucked for compatibility. + G_FLOAT(OFS_PARM0) = vid.pixelwidth; + G_FLOAT(OFS_PARM1) = vid.pixelheight; + } + else + { + G_FLOAT(OFS_PARM0) = vid.width; + G_FLOAT(OFS_PARM1) = vid.height; + } G_FLOAT(OFS_PARM2) = !Key_Dest_Has(kdm_emenu) && !r_refdef.eyeoffset[0] && !r_refdef.eyeoffset[1]; if (csqcg.f_updateviewloading && cls.state && cls.state < ca_active) diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 379f87bb3..135f2e9bd 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -1124,8 +1124,10 @@ extern rendererinfo_t swrendererinfo; #ifdef VKQUAKE extern rendererinfo_t vkrendererinfo; //rendererinfo_t headlessvkrendererinfo; +#if defined(_WIN32) && defined(GLQUAKE) && !defined(FTE_SDL) extern rendererinfo_t nvvkrendererinfo; #endif +#endif #ifdef HEADLESSQUAKE extern rendererinfo_t headlessrenderer; #endif @@ -1154,7 +1156,7 @@ rendererinfo_t *rendererinfo[] = #endif #ifdef VKQUAKE &vkrendererinfo, - #if defined(_WIN32) && defined(GLQUAKE) + #if defined(_WIN32) && defined(GLQUAKE) && !defined(FTE_SDL) &nvvkrendererinfo, #endif #endif diff --git a/engine/client/roq_read.c b/engine/client/roq_read.c index 9eb897729..c5acdc254 100644 --- a/engine/client/roq_read.c +++ b/engine/client/roq_read.c @@ -309,7 +309,8 @@ int i; if((fp = FS_OpenVFS(fname, "rb", FS_GAME)) == NULL) { - return NULL; + if((fp = FS_OpenVFS(va("video/%s.roq", fname), "rb", FS_GAME)) == NULL) //for q3 compat + return NULL; } if((ri = BZF_Malloc(sizeof(roq_info))) == NULL) diff --git a/engine/client/snd_al.c b/engine/client/snd_al.c index c318a2424..01139fd13 100644 --- a/engine/client/snd_al.c +++ b/engine/client/snd_al.c @@ -484,7 +484,7 @@ qboolean OpenAL_LoadCache(unsigned int *bufptr, sfxcache_t *sc, float volume) return true; } -void OpenAL_CvarInit(void) +static void QDECL OpenAL_CvarInit(void) { Cvar_Register(&s_al_debug, SOUNDVARS); Cvar_Register(&s_al_use_reverb, SOUNDVARS); @@ -1347,6 +1347,7 @@ static qboolean QDECL OpenAL_InitCard(soundcardinfo_t *sc, const char *devname) sc->inactive_sound = true; sc->selfpainting = true; + sc->sn.sampleformat = QSF_EXTERNALMIXER; OnChangeALSettings(NULL, NULL); @@ -1397,7 +1398,8 @@ sounddriver_t OPENAL_Output = { SDRVNAME, OpenAL_InitCard, - OpenAL_Enumerate + OpenAL_Enumerate, + OpenAL_CvarInit }; diff --git a/engine/client/snd_alsa.c b/engine/client/snd_alsa.c index 30022f92e..5903f32cc 100755 --- a/engine/client/snd_alsa.c +++ b/engine/client/snd_alsa.c @@ -137,7 +137,7 @@ static void ALSA_RW_Submit (soundcardinfo_t *sc, int start, int end) unsigned int frames, offset, ringsize; unsigned chunk; int result; - int stride = sc->sn.numchannels * (sc->sn.samplebits/8); + int stride = sc->sn.numchannels * sc->sn.samplebytes; while(1) { @@ -323,7 +323,20 @@ static qboolean QDECL ALSA_InitCard (soundcardinfo_t *sc, const char *pcmname) Con_Printf ("ALSA: Using PCM %s.\n", pcmname); #if 1 - err = psnd_pcm_set_params(pcm, ((sc->sn.samplebits==8)?SND_PCM_FORMAT_U8:SND_PCM_FORMAT_S16), (mmap?SND_PCM_ACCESS_MMAP_INTERLEAVED:SND_PCM_ACCESS_RW_INTERLEAVED), sc->sn.numchannels, sc->sn.speed, true, 0.04*1000000); + if (!sc->sn.sampleformat) + sc->sn.sampleformat = (sc->sn.samplebytes==1)?QSF_U8:QSF_S16; + switch(sc->sn.sampleformat) + { + case QSF_U8: err = SND_PCM_FORMAT_U8; break; + case QSF_S8: err = SND_PCM_FORMAT_S8; break; + case QSF_S16: err = SND_PCM_FORMAT_S16; break; + case QSF_F32: err = SND_PCM_FORMAT_FLOAT; break; + default: + Con_Printf (CON_ERROR "ALSA: unsupported sample format %i\n", sc->sn.sampleformat); + goto error; + } + + err = psnd_pcm_set_params(pcm, err, (mmap?SND_PCM_ACCESS_MMAP_INTERLEAVED:SND_PCM_ACCESS_RW_INTERLEAVED), sc->sn.numchannels, sc->sn.speed, true, 0.04*1000000); if (0 > err) { Con_Printf (CON_ERROR "ALSA: error setting params. %s\n", psnd_strerror (err)); @@ -332,7 +345,7 @@ static qboolean QDECL ALSA_InitCard (soundcardinfo_t *sc, const char *pcmname) // sc->sn.numchannels = stereo; // sc->sn.samplepos = 0; -// sc->sn.samplebits = bps; +// sc->sn.samplebytes = bps/8; sc->samplequeue = buffer_size = 2048; #else @@ -351,7 +364,7 @@ static qboolean QDECL ALSA_InitCard (soundcardinfo_t *sc, const char *pcmname) } // get sample bit size - bps = sc->sn.samplebits; + bps = sc->sn.samplebytes*8; { snd_pcm_format_t spft; if (bps == 16) @@ -456,7 +469,7 @@ static qboolean QDECL ALSA_InitCard (soundcardinfo_t *sc, const char *pcmname) sc->sn.numchannels = stereo; sc->sn.samplepos = 0; - sc->sn.samplebits = bps; + sc->sn.samplebytes = bps/8; buffer_size = sc->sn.samples / stereo; if (buffer_size) @@ -509,7 +522,7 @@ static qboolean QDECL ALSA_InitCard (soundcardinfo_t *sc, const char *pcmname) sc->Submit = ALSA_RW_Submit; sc->samplequeue = sc->sn.samples; - sc->sn.buffer = malloc(sc->sn.samples * (sc->sn.samplebits/8)); + sc->sn.buffer = malloc(sc->sn.samples * sc->sn.samplebytes); err = psnd_pcm_prepare(pcm); if (0 > err) diff --git a/engine/client/snd_directx.c b/engine/client/snd_directx.c index 794424382..e6cbc2077 100644 --- a/engine/client/snd_directx.c +++ b/engine/client/snd_directx.c @@ -531,11 +531,8 @@ static unsigned int DSOUND_GetDMAPos(soundcardinfo_t *sc) IDirectSoundBuffer_GetCurrentPosition(dh->pDSBuf, &mmtime, &dwWrite); s = mmtime - dh->mmstarttime; - - s /= (sc->sn.samplebits/8); - + s /= sc->sn.samplebytes; s %= (sc->sn.samples); - return s; } @@ -657,15 +654,25 @@ static int DSOUND_InitCard_Internal (soundcardinfo_t *sc, char *cardname) sc->sn.numchannels = 1; } - if (sc->sn.samplebits == 32) - { //FTE does not support 32bit int audio, rather we interpret samplebits 32 as floats. + switch(sc->sn.samplebytes) + { + case 4: + //FTE does not support 32bit int audio, rather we interpret samplebits 32 as floats. format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; format.Format.cbSize = 22; memcpy(&format.SubFormat, &QKSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)); + sc->sn.sampleformat = QSF_F32; + break; + case 2: + sc->sn.sampleformat = QSF_S16; + break; + case 1: + sc->sn.sampleformat = QSF_U8; + break; } format.Format.nChannels = sc->sn.numchannels; - format.Format.wBitsPerSample = sc->sn.samplebits; + format.Format.wBitsPerSample = sc->sn.samplebytes*8; format.Format.nSamplesPerSec = sc->sn.speed; format.Format.nBlockAlign = format.Format.nChannels * format.Format.wBitsPerSample / 8; format.Format.nAvgBytesPerSec = format.Format.nSamplesPerSec * format.Format.nBlockAlign; @@ -842,7 +849,7 @@ static int DSOUND_InitCard_Internal (soundcardinfo_t *sc, char *cardname) } sc->sn.numchannels = format.Format.nChannels; - sc->sn.samplebits = format.Format.wBitsPerSample; + sc->sn.samplebytes = format.Format.wBitsPerSample/8; sc->sn.speed = format.Format.nSamplesPerSec; if (DS_OK != IDirectSoundBuffer_GetCaps (dh->pDSBuf, &dsbcaps)) @@ -902,7 +909,7 @@ static int DSOUND_InitCard_Internal (soundcardinfo_t *sc, char *cardname) Con_DPrintf(" %d channel(s)\n" " %d bits/sample\n" " %d bytes/sec\n", - sc->sn.numchannels, sc->sn.samplebits, sc->sn.speed); + sc->sn.numchannels, sc->sn.samplebytes*8, sc->sn.speed); // initialize the buffer @@ -938,7 +945,7 @@ static int DSOUND_InitCard_Internal (soundcardinfo_t *sc, char *cardname) IDirectSoundBuffer_GetCurrentPosition(dh->pDSBuf, &dh->mmstarttime, &dwWrite); IDirectSoundBuffer_Play(dh->pDSBuf, 0, 0, DSBPLAY_LOOPING); - sc->sn.samples = dh->gSndBufSize/(sc->sn.samplebits/8); + sc->sn.samples = dh->gSndBufSize/sc->sn.samplebytes; sc->sn.samplepos = 0; sc->sn.buffer = NULL; diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index f033958e9..edb5a4b9c 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -220,7 +220,7 @@ void S_SoundInfo_f(void) for (sc = sndcardinfo; sc; sc = sc->next) { Con_Printf("Audio Device: %s\n", sc->name); - Con_Printf(" %d channels, %gkhz, %d bit audio%s\n", sc->sn.numchannels, sc->sn.speed/1000.0, sc->sn.samplebits, sc->selfpainting?", threaded":""); + Con_Printf(" %d channels, %gkhz, %d bit audio%s\n", sc->sn.numchannels, sc->sn.speed/1000.0, sc->sn.samplebytes*8, sc->selfpainting?", threaded":""); Con_Printf(" %d samples in buffer\n", sc->sn.samples); for (i = 0, active = 0, known = 0; i < sc->total_chans; i++) { @@ -1697,18 +1697,24 @@ extern sounddriver_t OPENAL_Output; #ifdef __DJGPP__ extern sounddriver_t SBLASTER_Output; #endif - -sounddriver pSNDIO_InitCard; -sounddriver pOSS_InitCard; -sounddriver pMacOS_InitCard; -sounddriver pSDL_InitCard; -sounddriver pWAV_InitCard; -sounddriver pDroid_InitCard; -sounddriver pAHI_InitCard; -#ifdef NACL -extern sounddriver pPPAPI_InitCard; +#if defined(_WIN32) && !defined(WINRT) && !defined(FTE_SDL) +extern sounddriver_t WaveOut_Output; #endif +#ifdef MACOSX +sounddriver_t MacOS_AudioOutput; //prefered on mac +#endif +#ifdef ANDROID +sounddriver_t Droid_AudioOutput; //prefered on android (java thread) +#endif +#if defined(__MORPHOS__) +sounddriver_t AHI_AudioOutput; //prefered on morphos +#endif +#ifdef NACL +extern sounddriver_t PPAPI_AudioOutput; //nacl +#endif +sounddriver_t SNDIO_AudioOutput; //bsd + //in order of preference static sounddriver_t *outputdrivers[] = { @@ -1731,37 +1737,35 @@ static sounddriver_t *outputdrivers[] = #ifdef __linux__ &ALSA_Output, //pure shite #endif - &OSS_Output, //good, but not likely to work any more + &OSS_Output, //good, but not likely to work any more on linux (unlike every other unix system with a decent opengl driver) #ifdef __DJGPP__ &SBLASTER_Output, //zomgwtfdos? #endif +#if defined(_WIN32) && !defined(WINRT) && !defined(FTE_SDL) + &WaveOut_Output, //doesn't work properly in vista, etc. +#endif + +#ifdef MACOSX + &MacOS_AudioOutput, //prefered on mac +#endif +#ifdef ANDROID + &Droid_AudioOutput, //prefered on android (java thread) +#endif +#if defined(__MORPHOS__) + &AHI_AudioOutput, //prefered on morphos +#endif +#ifdef NACL + &PPAPI_AudioOutput, //google's native client +#endif + &SNDIO_AudioOutput, //prefered on OpenBSD + #endif NULL }; -typedef struct { - char *name; - sounddriver *ptr; -} sdriver_t; -static sdriver_t olddrivers[] = { -#ifdef HAVE_MIXER -//in order of preference - {"MacOS", &pMacOS_InitCard}, //prefered on mac - {"Droid", &pDroid_InitCard}, //prefered on android (java thread) - {"AHI", &pAHI_InitCard}, //prefered on morphos -#ifdef NACL - {"PPAPI", &pPPAPI_InitCard}, //google's native client -#endif - {"SNDIO", &pSNDIO_InitCard}, //prefered on OpenBSD - - {"WaveOut", &pWAV_InitCard}, //doesn't work properly in vista, etc. -#endif - {NULL, NULL} -}; static soundcardinfo_t *SNDDMA_Init(char *driver, char *device, int seat) { soundcardinfo_t *sc = Z_Malloc(sizeof(soundcardinfo_t)); - sdriver_t *od; sounddriver_t *sd; int i; int st; @@ -1803,10 +1807,12 @@ static soundcardinfo_t *SNDDMA_Init(char *driver, char *device, int seat) sc->sn.numchannels = 1; // set requested sample bits - if (snd_samplebits.ival >= 16) - sc->sn.samplebits = 16; + if (snd_samplebits.ival >= 32) + sc->sn.samplebytes = 4; + else if (snd_samplebits.ival >= 16) + sc->sn.samplebytes = 2; else - sc->sn.samplebits = 8; + sc->sn.samplebytes = 1; // set requested buffer size if (snd_buffersize.ival > 0) @@ -1826,7 +1832,12 @@ static soundcardinfo_t *SNDDMA_Init(char *driver, char *device, int seat) st = (**sd->InitCard)(sc, device); if (st) { -cardinited: + if (!sc->sn.sampleformat) + { + Con_TPrintf("S_Startup: Ignoring soundcard %s due to unspecified sample format.\n", sc->name); + S_ShutdownCard(sc); + continue; + } S_DefaultSpeakerConfiguration(sc); if (snd_speed) { //if the sample speeds of multiple soundcards do not match, it'll fail. @@ -1834,7 +1845,7 @@ cardinited: { Con_TPrintf("S_Startup: Ignoring soundcard %s due to mismatched sample speeds.\n", sc->name); S_ShutdownCard(sc); - continue; + return NULL; } } else @@ -1849,21 +1860,6 @@ cardinited: } } - for (i = 0; olddrivers[i].name; i++) - { - od = &olddrivers[i]; - if (!driver || !Q_strcasecmp(od->name, driver)) - { - //skip drivers which are not present. - if (!*od->ptr) - continue; - - st = (**od->ptr)(sc, device?atoi(device):0); - if (st == 1) - goto cardinited; - } - } - S_ShutdownCard(sc); if (!driver) @@ -2152,7 +2148,7 @@ S_Init */ void S_Init (void) { - int p; + int p, i; Con_DPrintf("\nSound Initialization\n"); @@ -2208,9 +2204,12 @@ void S_Init (void) mixermutex = Sys_CreateMutex(); #endif -#ifdef AVAIL_OPENAL - OpenAL_CvarInit(); -#endif + for (i = 0; outputdrivers[i]; i++) + { + sounddriver_t *sd = outputdrivers[i]; + if (sd && sd->name && sd->RegisterCvars) + sd->RegisterCvars(); + } if (COM_CheckParm("-nosound")) { @@ -3072,7 +3071,7 @@ static void S_ClearBuffer (soundcardinfo_t *sc) if (!sound_started || !sc->sn.buffer) return; - if (sc->sn.samplebits == 8) + if (sc->sn.sampleformat == QSF_U8) clear = 0x80; else clear = 0; @@ -3081,7 +3080,7 @@ static void S_ClearBuffer (soundcardinfo_t *sc) buffer = sc->Lock(sc, &dummy); if (buffer) { - Q_memset(buffer, clear, sc->sn.samples * sc->sn.samplebits/8); + Q_memset(buffer, clear, sc->sn.samples * sc->sn.samplebytes); sc->Unlock(sc, buffer); } } diff --git a/engine/client/snd_droid.c b/engine/client/snd_droid.c index 621e111f8..01fd71b64 100644 --- a/engine/client/snd_droid.c +++ b/engine/client/snd_droid.c @@ -1,139 +1,154 @@ -/* -this file is basically a copy of the SDL one -java code has a function or two which just periodically calls us to ask us to dump out audio for it -*/ -#include "quakedef.h" -#include -#include - -static soundcardinfo_t *sys_sc = NULL; -extern int sys_soundflags; - -//called by the java code when it wants to know what sort of AudioTrack format to use. -JNIEXPORT jint JNICALL Java_com_fteqw_FTEDroidEngine_audioinfo(JNIEnv *env, jclass this, jint arg) -{ - soundcardinfo_t *sc = sys_sc; - if (!sc) - return 0; - - switch(arg) - { - case 1: - return sc->sn.numchannels; - case 2: - return sc->sn.samplebits; - default: - return sc->sn.speed; - } -} - -extern int S_GetMixerTime(soundcardinfo_t *sc); -//transfer the 'dma' buffer into the buffer it requests, called from a dedicated sound thread created by the java code. -JNIEXPORT jint JNICALL Java_com_fteqw_FTEDroidEngine_paintaudio(JNIEnv *env, jclass this, jbyteArray stream, jint len) -{ - int offset = 0; - soundcardinfo_t *sc = sys_sc; - int framesz; - - if (sc) - { - int buffersize = sc->sn.samples*sc->sn.samplebits/8; - - int curtime = S_GetMixerTime(sc); - framesz = sc->sn.numchannels * sc->sn.samplebits/8; - - S_PaintChannels (sc, curtime + (len / framesz)); - - if (len > buffersize) - { - len = buffersize; //whoa nellie! - } - - if (len + sc->snd_sent%buffersize > buffersize) - { //buffer will wrap, fill in the rest - (*env)->SetByteArrayRegion(env, stream, offset, buffersize - (sc->snd_sent%buffersize), (char*)sc->sn.buffer + (sc->snd_sent%buffersize)); - offset += buffersize - (sc->snd_sent%buffersize); - sc->snd_sent += buffersize - (sc->snd_sent%buffersize); - len -= buffersize - (sc->snd_sent%buffersize); - if (len < 0) /*this must be impossible, surely?*/ - len = 0; - } - //and finish from the start - (*env)->SetByteArrayRegion(env, stream, offset, len, (char*)sc->sn.buffer + (sc->snd_sent%buffersize)); - offset += len; - sc->snd_sent += len; - } - else - offset = len; /*so the playback thread ends up blocked properly*/ - return offset; -} - - -static void Droid_Shutdown(soundcardinfo_t *sc) -{ - //fixme: what if we're currently inside Java_com_fteqw_FTEDroidEngine_paintaudio? - sys_sc = NULL; - free(sc->sn.buffer); - sys_soundflags = 0; -} - -//return the number of samples that have already been submitted to the device. -static unsigned int Droid_GetDMAPos(soundcardinfo_t *sc) -{ - sc->sn.samplepos = sc->snd_sent / (sc->sn.samplebits/8); - return sc->sn.samplepos; -} - -static void Droid_UnlockBuffer(soundcardinfo_t *sc, void *buffer) -{ -} - -static void *Droid_LockBuffer(soundcardinfo_t *sc, unsigned int *sampidx) -{ - return sc->sn.buffer; -} - -/* -static void Droid_SetEnvironmentReverb(soundcardinfo_t *sc, qboolean uw) -{ -} -*/ - -static void Droid_Submit(soundcardinfo_t *sc, int start, int end) -{ -} - -//on android, 16bit audio is 'guarenteed'. -//8bit is not guarenteed. -//there's no reference to sample rates. I assume 44.1khz will always work, though we want to avoid that cpu+mem load if we can -//nor any guarentee about channels supported. I assume mono will always work. -static int Droid_InitCard (soundcardinfo_t *sc, int cardnum) -{ - if (sys_sc) - return 2; - - sc->selfpainting = true; -// sc->sn.speed = 11025; -// sc->sn.samplebits = 16; -// sc->sn.numchannels = 1; - - /*internal buffer should have 1 sec audio*/ - sc->sn.samples = sc->sn.speed*sc->sn.numchannels; - - sc->Lock = Droid_LockBuffer; - sc->Unlock = Droid_UnlockBuffer; -// sc->SetEnvironmentReverb = Droid_SetEnvironmentReverb; - sc->Submit = Droid_Submit; - sc->Shutdown = Droid_Shutdown; - sc->GetDMAPos = Droid_GetDMAPos; - - sc->sn.buffer = malloc(sc->sn.samples*sc->sn.samplebits/8); - - sys_sc = sc; - - sys_soundflags = 3; - - return 1; -} -int (*pDroid_InitCard) (soundcardinfo_t *sc, int cardnum) = &Droid_InitCard; - +/* +this file is basically a copy of the SDL one +java code has a function or two which just periodically calls us to ask us to dump out audio for it +*/ +#include "quakedef.h" +#include +#include + +static soundcardinfo_t *sys_sc = NULL; +extern int sys_soundflags; + +//called by the java code when it wants to know what sort of AudioTrack format to use. +JNIEXPORT jint JNICALL Java_com_fteqw_FTEDroidEngine_audioinfo(JNIEnv *env, jclass this, jint arg) +{ + soundcardinfo_t *sc = sys_sc; + if (!sc) + return 0; + + switch(arg) + { + case 1: + return sc->sn.numchannels; + case 2: + return sc->sn.samplebytes*8; + default: + return sc->sn.speed; + } +} + +extern int S_GetMixerTime(soundcardinfo_t *sc); +//transfer the 'dma' buffer into the buffer it requests, called from a dedicated sound thread created by the java code. +JNIEXPORT jint JNICALL Java_com_fteqw_FTEDroidEngine_paintaudio(JNIEnv *env, jclass this, jbyteArray stream, jint len) +{ + int offset = 0; + soundcardinfo_t *sc = sys_sc; + int framesz; + + if (sc) + { + int buffersize = sc->sn.samples*sc->sn.samplebytes; + + int curtime = S_GetMixerTime(sc); + framesz = sc->sn.numchannels * sc->sn.samplebytes; + + S_PaintChannels (sc, curtime + (len / framesz)); + + if (len > buffersize) + { + len = buffersize; //whoa nellie! + } + + if (len + sc->snd_sent%buffersize > buffersize) + { //buffer will wrap, fill in the rest + (*env)->SetByteArrayRegion(env, stream, offset, buffersize - (sc->snd_sent%buffersize), (char*)sc->sn.buffer + (sc->snd_sent%buffersize)); + offset += buffersize - (sc->snd_sent%buffersize); + sc->snd_sent += buffersize - (sc->snd_sent%buffersize); + len -= buffersize - (sc->snd_sent%buffersize); + if (len < 0) /*this must be impossible, surely?*/ + len = 0; + } + //and finish from the start + (*env)->SetByteArrayRegion(env, stream, offset, len, (char*)sc->sn.buffer + (sc->snd_sent%buffersize)); + offset += len; + sc->snd_sent += len; + } + else + offset = len; /*so the playback thread ends up blocked properly*/ + return offset; +} + + +static void Droid_Shutdown(soundcardinfo_t *sc) +{ + //fixme: what if we're currently inside Java_com_fteqw_FTEDroidEngine_paintaudio? + sys_sc = NULL; + free(sc->sn.buffer); + sys_soundflags = 0; +} + +//return the number of samples that have already been submitted to the device. +static unsigned int Droid_GetDMAPos(soundcardinfo_t *sc) +{ + sc->sn.samplepos = sc->snd_sent / sc->sn.samplebytes; + return sc->sn.samplepos; +} + +static void Droid_UnlockBuffer(soundcardinfo_t *sc, void *buffer) +{ +} + +static void *Droid_LockBuffer(soundcardinfo_t *sc, unsigned int *sampidx) +{ + return sc->sn.buffer; +} + +/* +static void Droid_SetEnvironmentReverb(soundcardinfo_t *sc, qboolean uw) +{ +} +*/ + +static void Droid_Submit(soundcardinfo_t *sc, int start, int end) +{ +} + +//on android, 16bit audio is 'guarenteed'. +//8bit is not guarenteed. +//there's no reference to sample rates. I assume 44.1khz will always work, though we want to avoid that cpu+mem load if we can +//nor any guarentee about channels supported. I assume mono will always work. +static qboolean Droid_InitCard (soundcardinfo_t *sc, const char *cardname) +{ + if (sys_sc) + return false; //can only cope with one device. + if (cardname && *cardname) + return false; //only the default device + + sc->selfpainting = true; +// sc->sn.speed = 11025; +// sc->sn.samplebytes = 2; +// sc->sn.numchannels = 1; + + if (sc->sn.samplebytes == 1) + sc->sn.sampleformat = QSF_U8; + else /*if (sc->sn.samplebytes == 2)*/ + { + sc->sn.samplebytes = 2; + sc->sn.sampleformat = QSF_S16; + } + + /*internal buffer should have 1 sec audio*/ + sc->sn.samples = sc->sn.speed*sc->sn.numchannels; + + sc->Lock = Droid_LockBuffer; + sc->Unlock = Droid_UnlockBuffer; +// sc->SetEnvironmentReverb = Droid_SetEnvironmentReverb; + sc->Submit = Droid_Submit; + sc->Shutdown = Droid_Shutdown; + sc->GetDMAPos = Droid_GetDMAPos; + + sc->sn.buffer = malloc(sc->sn.samples*sc->sn.samplebytes); + + sys_sc = sc; + + sys_soundflags = 3; + + return 1; +} + +sounddriver_t Droid_AudioOutput = +{ + "Android", + Droid_InitCard, + NULL +}; diff --git a/engine/client/snd_linux.c b/engine/client/snd_linux.c index ab5e8554d..dd6c7ae11 100644 --- a/engine/client/snd_linux.c +++ b/engine/client/snd_linux.c @@ -14,6 +14,11 @@ #include #endif +#ifndef AFMT_FLOAT + #define AFMT_FLOAT 0x00004000 //OSS4 supports it, but linux is too shit to define it. +#endif + + static int tryrates[] = { 11025, 22051, 44100, 8000, 48000 }; static unsigned int OSS_MMap_GetDMAPos(soundcardinfo_t *sc) @@ -30,9 +35,9 @@ static unsigned int OSS_MMap_GetDMAPos(soundcardinfo_t *sc) sc->audio_fd = -1; return 0; } -// shm->samplepos = (count.bytes / (shm->samplebits / 8)) & (shm->samples-1); +// shm->samplepos = (count.bytes / shm->samplebytes) & (shm->samples-1); // fprintf(stderr, "%d \r", count.ptr); - sc->sn.samplepos = count.ptr / (sc->sn.samplebits / 8); + sc->sn.samplepos = count.ptr / sc->sn.samplebytes; } return sc->sn.samplepos; @@ -48,7 +53,7 @@ static unsigned int OSS_Alsa_GetDMAPos(soundcardinfo_t *sc) if (ioctl (sc->audio_fd, SNDCTL_DSP_GETOSPACE, &info) != -1) { bytes = sc->snd_sent + info.bytes; - sc->sn.samplepos = bytes / (sc->sn.samplebits / 8); + sc->sn.samplepos = bytes / sc->sn.samplebytes; } return sc->sn.samplepos; } @@ -61,12 +66,12 @@ static void OSS_Alsa_Submit(soundcardinfo_t *sc, int start, int end) int result; /*we can't change the data that was already written*/ - bytes = end * sc->sn.numchannels * (sc->sn.samplebits/8); + bytes = end * sc->sn.numchannels * sc->sn.samplebytes; bytes -= sc->snd_sent; if (!bytes) return; - ringsize = sc->sn.samples * (sc->sn.samplebits/8); + ringsize = sc->sn.samples * sc->sn.samplebytes; chunk = bytes; offset = sc->snd_sent % ringsize; @@ -99,7 +104,7 @@ static void OSS_Shutdown(soundcardinfo_t *sc) if (sc->Submit == OSS_Alsa_Submit) free(sc->sn.buffer); /*if using alsa-compat, just free the buffer*/ else - munmap(sc->sn.buffer, sc->sn.samples * (sc->sn.samplebits/8)); + munmap(sc->sn.buffer, sc->sn.samples * sc->sn.samplebytes); } if (sc->audio_fd != -1) close(sc->audio_fd); @@ -214,20 +219,15 @@ static qboolean OSS_InitCard(soundcardinfo_t *sc, const char *snddev) sc->sn.numchannels = 1; #endif -//choose bits // ask the device what it supports ioctl(sc->audio_fd, SNDCTL_DSP_GETFMTS, &fmt); - if (!(fmt & AFMT_S16_LE) && sc->sn.samplebits > 8) - sc->sn.samplebits = 8; // they asked for 16bit (the default) but their card does not support it - if (!(fmt & AFMT_U8) && sc->sn.samplebits == 8) - { //their card doesn't support 8bit which we're trying to use. - Con_Printf(CON_ERROR "OSS: No needed sample formats supported\n"); - OSS_Shutdown(sc); - return false; - } - if (sc->sn.samplebits == 16) + + //choose a format + if (sc->sn.samplebytes >= 4 && (fmt & AFMT_FLOAT)) { - rc = AFMT_S16_LE; + sc->sn.samplebytes = 4; + sc->sn.sampleformat = QSF_F32; + rc = AFMT_FLOAT; rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETFMT, &rc); if (rc < 0) { @@ -237,8 +237,24 @@ static qboolean OSS_InitCard(soundcardinfo_t *sc, const char *snddev) return false; } } - else if (sc->sn.samplebits == 8) + else if (sc->sn.samplebytes >= 2 && (fmt & AFMT_S16_NE)) { + sc->sn.samplebytes = 2; + sc->sn.sampleformat = QSF_S16; + rc = AFMT_S16_NE; + rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETFMT, &rc); + if (rc < 0) + { + perror(snddev); + Con_Printf(CON_ERROR "OSS: Could not support 16-bit data. Try 8-bit.\n"); + OSS_Shutdown(sc); + return false; + } + } + else if (/*sc->sn.samplebytes == 1 && */(fmt & AFMT_U8)) + { + sc->sn.samplebytes = 1; + sc->sn.sampleformat = QSF_U8; rc = AFMT_U8; rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETFMT, &rc); if (rc < 0) @@ -249,10 +265,24 @@ static qboolean OSS_InitCard(soundcardinfo_t *sc, const char *snddev) return false; } } + else if (/*sc->sn.samplebytes == 1 && */(fmt & AFMT_S8)) + { + sc->sn.samplebytes = 1; + sc->sn.sampleformat = QSF_S8; + rc = AFMT_S8; + rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETFMT, &rc); + if (rc < 0) + { + perror(snddev); + Con_Printf(CON_ERROR "OSS: Could not support 8-bit data.\n"); + OSS_Shutdown(sc); + return false; + } + } else { perror(snddev); - Con_Printf(CON_ERROR "OSS: %d-bit sound not supported.\n", sc->sn.samplebits); + Con_Printf(CON_ERROR "OSS: %d-bit sound not supported.\n", sc->sn.samplebytes*8); OSS_Shutdown(sc); return false; } @@ -286,7 +316,7 @@ static qboolean OSS_InitCard(soundcardinfo_t *sc, const char *snddev) return false; } sc->sn.samples = info.fragstotal * info.fragsize; - sc->sn.samples /= (sc->sn.samplebits/8); + sc->sn.samples /= sc->sn.samplebytes; /*samples is the number of samples*channels */ // memory map the dma buffer @@ -297,7 +327,7 @@ static qboolean OSS_InitCard(soundcardinfo_t *sc, const char *snddev) } else if ((caps & DSP_CAP_TRIGGER) && (caps & DSP_CAP_MMAP)) { - sc->sn.buffer = (unsigned char *) mmap(NULL, sc->sn.samples*(sc->sn.samplebits/8), PROT_WRITE, MAP_FILE|MAP_SHARED, sc->audio_fd, 0); + sc->sn.buffer = (unsigned char *) mmap(NULL, sc->sn.samples*sc->sn.samplebytes, PROT_WRITE, MAP_FILE|MAP_SHARED, sc->audio_fd, 0); if (sc->sn.buffer == MAP_FAILED) { Con_Printf("%s: device reported mmap capability, but mmap failed.\n", snddev); @@ -315,9 +345,9 @@ static qboolean OSS_InitCard(soundcardinfo_t *sc, const char *snddev) { sc->sn.buffer = NULL; - sc->samplequeue = info.bytes / (sc->sn.samplebits/8); + sc->samplequeue = info.bytes / sc->sn.samplebytes; sc->sn.samples*=2; - sc->sn.buffer = malloc(sc->sn.samples*(sc->sn.samplebits/8)); + sc->sn.buffer = malloc(sc->sn.samples*sc->sn.samplebytes); sc->Submit = OSS_Alsa_Submit; sc->GetDMAPos = OSS_Alsa_GetDMAPos; } diff --git a/engine/client/snd_macos.c b/engine/client/snd_macos.c index b3403c94b..586008625 100644 --- a/engine/client/snd_macos.c +++ b/engine/client/snd_macos.c @@ -1,253 +1,258 @@ -/* - - Copyright (C) 2001-2002 A Nourai - Copyright (C) 2006 Jacek Piszczek (Mac OSX port) - - 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. -*/ - -#include "quakedef.h" -#include "sound.h" -#include -#include - -// Jacek: -// coreaudio is poorly documented so I'm not 100% sure the code below -// is correct :( - -struct MacOSSound_Private -{ - AudioUnit gOutputUnit; - unsigned int readpos; -}; - -static OSStatus AudioRender(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumberFrames, - AudioBufferList *ioData) -{ - soundcardinfo_t *sc = inRefCon; - struct MacOSSound_Private *pdata = sc->handle; - - int start = pdata->readpos; - int buffersize = sc->sn.samples * (sc->sn.samplebits/8); - int bytes = ioData->mBuffers[0].mDataByteSize; - int remaining; - - start %= buffersize; - if (start + bytes > buffersize) - { - remaining = bytes; - bytes = buffersize - start; - remaining -= bytes; - } - else - { - remaining = 0; - } - - memcpy(ioData->mBuffers[0].mData, sc->sn.buffer + start, bytes); - memcpy((char*)ioData->mBuffers[0].mData+bytes, sc->sn.buffer, remaining); - - pdata->readpos += inNumberFrames*sc->sn.numchannels * (sc->sn.samplebits/8); - - return noErr; -} - -static void MacOS_Shutdown(soundcardinfo_t *sc) -{ - struct MacOSSound_Private *pdata = sc->handle; - sc->handle = NULL; - if (!pdata) - return; - - // stop playback - AudioOutputUnitStop (pdata->gOutputUnit); - - // release the unit - AudioUnitUninitialize (pdata->gOutputUnit); - - // free the unit - CloseComponent (pdata->gOutputUnit); - - // free the buffer memory - Z_Free(sc->sn.buffer); - Z_Free(pdata); -} - -static unsigned int MacOS_GetDMAPos(soundcardinfo_t *sc) -{ - struct MacOSSound_Private *pdata = sc->handle; - sc->sn.samplepos = pdata->readpos/(sc->sn.samplebits/8); - return sc->sn.samplepos; -} - -static void MacOS_Submit(soundcardinfo_t *sc) -{ -} - -static void *MacOS_Lock(soundcardinfo_t *sc, unsigned int *sampidx) -{ - return sc->sn.buffer; -} - -static void MacOS_Unlock(soundcardinfo_t *sc, void *buffer) -{ -} - -static int MacOS_InitCard(soundcardinfo_t *sc, int cardnum) -{ - ComponentResult err = noErr; - - if (cardnum) - return 2; /* no more */ - - struct MacOSSound_Private *pdata = Z_Malloc(sizeof(*pdata)); - if (!pdata) - return FALSE; - - // Open the default output unit - ComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_DefaultOutput; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - Component comp = FindNextComponent(NULL, &desc); - if (comp == NULL) - { - Con_Printf("FindNextComponent failed\n"); - Z_Free(pdata); - return FALSE; - } - - err = OpenAComponent(comp, &pdata->gOutputUnit); - if (comp == NULL) - { - Con_Printf("OpenAComponent failed\n"); - Z_Free(pdata); - return FALSE; - } - - // Set up a callback function to generate output to the output unit - AURenderCallbackStruct input; - input.inputProc = AudioRender; - input.inputProcRefCon = sc; - - err = AudioUnitSetProperty ( pdata->gOutputUnit, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, - 0, - &input, - sizeof(input)); - if (err) - { - Con_Printf("AudioUnitSetProperty failed\n"); - CloseComponent(pdata->gOutputUnit); - Z_Free(pdata); - return FALSE; - } - - // describe our audio data - AudioStreamBasicDescription streamFormat; - streamFormat.mSampleRate = sc->sn.speed; - streamFormat.mFormatID = kAudioFormatLinearPCM; - streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian - | kLinearPCMFormatFlagIsPacked; - //| kAudioFormatFlagIsNonInterleaved; - streamFormat.mFramesPerPacket = 1; - streamFormat.mChannelsPerFrame = 2; - streamFormat.mBitsPerChannel = 16; - if (streamFormat.mBitsPerChannel >= 16) - streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; - else - streamFormat.mFormatFlags |= 0; - - streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame * (streamFormat.mBitsPerChannel/8); - streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket; - - err = AudioUnitSetProperty (pdata->gOutputUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - 0, - &streamFormat, - sizeof(AudioStreamBasicDescription)); - if (err) - { - Con_Printf("AudioUnitSetProperty failed\n"); - CloseComponent(pdata->gOutputUnit); - Z_Free(pdata); - return FALSE; - } - - // set the shm structure - sc->sn.speed = streamFormat.mSampleRate; - sc->sn.samplebits = streamFormat.mBitsPerChannel; - sc->sn.numchannels = streamFormat.mChannelsPerFrame; - sc->sn.samples = 256 * 1024; - sc->sn.buffer = Z_Malloc(sc->sn.samples*sc->sn.samplebits/8); - - int i; - for (i = 0; i < sc->sn.samples*sc->sn.samplebits/8; i++) - sc->sn.buffer[i] = rand(); - - if (sc->sn.buffer == 0) - { - Con_Printf("Malloc failed - cannot allocate sound buffer\n"); - CloseComponent(pdata->gOutputUnit); - Z_Free(pdata); - return FALSE; - } - - // Initialize unit - err = AudioUnitInitialize(pdata->gOutputUnit); - if (err) - { - Con_Printf("AudioOutputInitialize failed\n"); - CloseComponent(pdata->gOutputUnit); - Z_Free(sc->sn.buffer); - Z_Free(pdata); - return FALSE; - } - - // start playing :) - err = AudioOutputUnitStart (pdata->gOutputUnit); - if (err) - { - Con_Printf("AudioOutputUnitStart failed\n"); - AudioUnitUninitialize (pdata->gOutputUnit); - CloseComponent(pdata->gOutputUnit); - Z_Free(sc->sn.buffer); - Z_Free(pdata); - return FALSE; - } - - sc->handle = pdata; - sc->Lock = MacOS_Lock; - sc->Unlock = MacOS_Unlock; - sc->Submit = MacOS_Submit; - sc->GetDMAPos = MacOS_GetDMAPos; - sc->Shutdown = MacOS_Shutdown; - - Con_Printf("Sound initialised\n"); - return TRUE; -} - -sounddriver pMacOS_InitCard = &MacOS_InitCard; - +/* + + Copyright (C) 2001-2002 A Nourai + Copyright (C) 2006 Jacek Piszczek (Mac OSX port) + + 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. +*/ + +#include "quakedef.h" +#include "sound.h" +#include +#include + +// Jacek: +// coreaudio is poorly documented so I'm not 100% sure the code below +// is correct :( + +struct MacOSSound_Private +{ + AudioUnit gOutputUnit; + unsigned int readpos; +}; + +static OSStatus AudioRender(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + soundcardinfo_t *sc = inRefCon; + struct MacOSSound_Private *pdata = sc->handle; + + int start = pdata->readpos; + int buffersize = sc->sn.samples * sc->sn.samplebytes; + int bytes = ioData->mBuffers[0].mDataByteSize; + int remaining; + + start %= buffersize; + if (start + bytes > buffersize) + { + remaining = bytes; + bytes = buffersize - start; + remaining -= bytes; + } + else + { + remaining = 0; + } + + memcpy(ioData->mBuffers[0].mData, sc->sn.buffer + start, bytes); + memcpy((char*)ioData->mBuffers[0].mData+bytes, sc->sn.buffer, remaining); + + pdata->readpos += inNumberFrames*sc->sn.numchannels * sc->sn.samplebytes; + + return noErr; +} + +static void MacOS_Shutdown(soundcardinfo_t *sc) +{ + struct MacOSSound_Private *pdata = sc->handle; + sc->handle = NULL; + if (!pdata) + return; + + // stop playback + AudioOutputUnitStop (pdata->gOutputUnit); + + // release the unit + AudioUnitUninitialize (pdata->gOutputUnit); + + // free the unit + CloseComponent (pdata->gOutputUnit); + + // free the buffer memory + Z_Free(sc->sn.buffer); + Z_Free(pdata); +} + +static unsigned int MacOS_GetDMAPos(soundcardinfo_t *sc) +{ + struct MacOSSound_Private *pdata = sc->handle; + sc->sn.samplepos = pdata->readpos/sc->sn.samplebytes; + return sc->sn.samplepos; +} + +static void MacOS_Submit(soundcardinfo_t *sc) +{ +} + +static void *MacOS_Lock(soundcardinfo_t *sc, unsigned int *sampidx) +{ + return sc->sn.buffer; +} + +static void MacOS_Unlock(soundcardinfo_t *sc, void *buffer) +{ +} + +static qboolean MacOS_InitCard(soundcardinfo_t *sc, const char *cardname) +{ + ComponentResult err = noErr; + + if (cardname && *cardname) + return false; //only the default device will be used for now. + + struct MacOSSound_Private *pdata = Z_Malloc(sizeof(*pdata)); + if (!pdata) + return FALSE; + + // Open the default output unit + ComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + Component comp = FindNextComponent(NULL, &desc); + if (comp == NULL) + { + Con_Printf("FindNextComponent failed\n"); + Z_Free(pdata); + return FALSE; + } + + err = OpenAComponent(comp, &pdata->gOutputUnit); + if (comp == NULL) + { + Con_Printf("OpenAComponent failed\n"); + Z_Free(pdata); + return FALSE; + } + + // Set up a callback function to generate output to the output unit + AURenderCallbackStruct input; + input.inputProc = AudioRender; + input.inputProcRefCon = sc; + + err = AudioUnitSetProperty ( pdata->gOutputUnit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &input, + sizeof(input)); + if (err) + { + Con_Printf("AudioUnitSetProperty failed\n"); + CloseComponent(pdata->gOutputUnit); + Z_Free(pdata); + return FALSE; + } + + // describe our audio data + AudioStreamBasicDescription streamFormat; + streamFormat.mSampleRate = sc->sn.speed; + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian + | kLinearPCMFormatFlagIsPacked; + //| kAudioFormatFlagIsNonInterleaved; + streamFormat.mFramesPerPacket = 1; + streamFormat.mChannelsPerFrame = 2; + streamFormat.mBitsPerChannel = 16; + if (streamFormat.mBitsPerChannel >= 16) + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + else + streamFormat.mFormatFlags |= 0; + + streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame * (streamFormat.mBitsPerChannel/8); + streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket; + + err = AudioUnitSetProperty (pdata->gOutputUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof(AudioStreamBasicDescription)); + if (err) + { + Con_Printf("AudioUnitSetProperty failed\n"); + CloseComponent(pdata->gOutputUnit); + Z_Free(pdata); + return FALSE; + } + + // set the shm structure + sc->sn.speed = streamFormat.mSampleRate; + sc->sn.samplebytes = streamFormat.mBitsPerChannel/8; + sc->sn.sampleformat = QCF_S16; + sc->sn.numchannels = streamFormat.mChannelsPerFrame; + sc->sn.samples = 256 * 1024; + sc->sn.buffer = Z_Malloc(sc->sn.samples*sc->sn.samplebytes); + + int i; + for (i = 0; i < sc->sn.samples*sc->sn.samplebytes; i++) + sc->sn.buffer[i] = rand(); + + if (sc->sn.buffer == 0) + { + Con_Printf("Malloc failed - cannot allocate sound buffer\n"); + CloseComponent(pdata->gOutputUnit); + Z_Free(pdata); + return FALSE; + } + + // Initialize unit + err = AudioUnitInitialize(pdata->gOutputUnit); + if (err) + { + Con_Printf("AudioOutputInitialize failed\n"); + CloseComponent(pdata->gOutputUnit); + Z_Free(sc->sn.buffer); + Z_Free(pdata); + return FALSE; + } + + // start playing :) + err = AudioOutputUnitStart (pdata->gOutputUnit); + if (err) + { + Con_Printf("AudioOutputUnitStart failed\n"); + AudioUnitUninitialize (pdata->gOutputUnit); + CloseComponent(pdata->gOutputUnit); + Z_Free(sc->sn.buffer); + Z_Free(pdata); + return FALSE; + } + + sc->handle = pdata; + sc->Lock = MacOS_Lock; + sc->Unlock = MacOS_Unlock; + sc->Submit = MacOS_Submit; + sc->GetDMAPos = MacOS_GetDMAPos; + sc->Shutdown = MacOS_Shutdown; + + Con_Printf("Sound initialised\n"); + return TRUE; +} + +sounddriver_t MacOS_AudioOutput = +{ + "CoreAudio", + MacOS_InitCard, + NULL +}; diff --git a/engine/client/snd_mix.c b/engine/client/snd_mix.c index 0b8d903f9..28c852270 100644 --- a/engine/client/snd_mix.c +++ b/engine/client/snd_mix.c @@ -52,57 +52,86 @@ void S_TransferPaintBuffer(soundcardinfo_t *sc, int endtime) if (!pbuf) return; - if (sc->sn.samplebits == 16) + switch(sc->sn.sampleformat) { - short *out = (short *) pbuf; - while (count) + case QSF_INVALID: //erk... + case QSF_EXTERNALMIXER: //shouldn't reach this. + break; + case QSF_U8: { - for (i = 0; i < numc; i++) + unsigned char *out = (unsigned char *) pbuf; + while (count) { - val = *p++;// * snd_vol) >> 8; - if (val > 0x7fff) - val = 0x7fff; - else if (val < (short)0x8000) - val = (short)0x8000; - out[out_idx] = val; - out_idx = (out_idx + 1) % outlimit; + for (i = 0; i < numc; i++) + { + val = *p++;// * snd_vol) >> 8; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (val>>8) + 128; + out_idx = (out_idx + 1) % outlimit; + } + p += MAXSOUNDCHANNELS - numc; + count -= numc; } - p += MAXSOUNDCHANNELS - numc; - count -= numc; } - } - else if (sc->sn.samplebits == 8) - { - unsigned char *out = (unsigned char *) pbuf; - while (count) + break; + case QSF_S8: { - for (i = 0; i < numc; i++) + char *out = (char *) pbuf; + while (count) { - val = *p++;// * snd_vol) >> 8; - if (val > 0x7fff) - val = 0x7fff; - else if (val < (short)0x8000) - val = (short)0x8000; - out[out_idx] = (val>>8) + 128; - out_idx = (out_idx + 1) % outlimit; + for (i = 0; i < numc; i++) + { + val = *p++;// * snd_vol) >> 8; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (val>>8); + out_idx = (out_idx + 1) % outlimit; + } + p += MAXSOUNDCHANNELS - numc; + count -= numc; } - p += MAXSOUNDCHANNELS - numc; - count -= numc; } - } - else if (sc->sn.samplebits == 32) - { - float *out = (float *) pbuf; - while (count) + break; + case QSF_S16: { - for (i = 0; i < numc; i++) + short *out = (short *) pbuf; + while (count) { - out[out_idx] = *p++ * (1.0 / 32768); - out_idx = (out_idx + 1) % outlimit; + for (i = 0; i < numc; i++) + { + val = *p++;// * snd_vol) >> 8; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = val; + out_idx = (out_idx + 1) % outlimit; + } + p += MAXSOUNDCHANNELS - numc; + count -= numc; } - p += MAXSOUNDCHANNELS - numc; - count -= numc; } + break; + case QSF_F32: + { + float *out = (float *) pbuf; + while (count) + { + for (i = 0; i < numc; i++) + { + out[out_idx] = *p++ * (1.0 / 32768); + out_idx = (out_idx + 1) % outlimit; + } + p += MAXSOUNDCHANNELS - numc; + count -= numc; + } + } + break; } sc->Unlock(sc, pbuf); diff --git a/engine/client/snd_morphos.c b/engine/client/snd_morphos.c index a9939d693..6c721d722 100644 --- a/engine/client/snd_morphos.c +++ b/engine/client/snd_morphos.c @@ -115,7 +115,7 @@ static void AHI_Submit(soundcardinfo_t *sc) { } -static int AHI_InitCard(soundcardinfo_t *sc, int cardnum) +static qboolean AHI_InitCard(soundcardinfo_t *sc, const char *cardname) { struct AHIdata *ad; @@ -129,8 +129,8 @@ static int AHI_InitCard(soundcardinfo_t *sc, int cardnum) struct AHISampleInfo sample; - if (cardnum) - return 2; /* Which means "no more cards" */ + if (cardname && *cardname) + return false; /* only allow the default audio device */ ad = AllocVec(sizeof(*ad), MEMF_ANY); if (ad) @@ -171,7 +171,7 @@ static int AHI_InitCard(soundcardinfo_t *sc, int cardnum) channels = 2; sc->sn.speed = speed; - sc->sn.samplebits = bits; + sc->sn.samplebytes = bits/8; sc->sn.numchannels = channels; sc->sn.samples = speed*channels; @@ -194,6 +194,7 @@ static int AHI_InitCard(soundcardinfo_t *sc, int cardnum) else sample.ahisi_Type = AHIST_S16S; } + sc->sn.sampleformat = (bits==8)?QSF_S8:QSF_S16; sample.ahisi_Address = ad->samplebuffer; sample.ahisi_Length = (speed*(bits/8)*channels)/AHI_SampleFrameSize(sample.ahisi_Type); @@ -236,7 +237,7 @@ static int AHI_InitCard(soundcardinfo_t *sc, int cardnum) Con_Printf("Using AHI mode \"%s\" for audio output\n", sc->name); Con_Printf("Channels: %d bits: %d frequency: %d\n", channels, bits, speed); - return 1; + return true; } } } @@ -256,7 +257,12 @@ static int AHI_InitCard(soundcardinfo_t *sc, int cardnum) FreeVec(ad); } - return 0; + return false; } -sounddriver pAHI_InitCard = &AHI_InitCard; +sounddriver_t AHI_AudioOutput = +{ + "AHI", + AHI_InitCard, + NULL +}; diff --git a/engine/client/snd_sblaster.c b/engine/client/snd_sblaster.c index 2d4ecfaeb..fb54fe147 100644 --- a/engine/client/snd_sblaster.c +++ b/engine/client/snd_sblaster.c @@ -359,7 +359,7 @@ static unsigned int SBLASTER_GetDMAPos(soundcardinfo_t *sc) outportb(0xc, 0); count = inportb(dma*2+1); count += inportb(dma*2+1) << 8; - if (sc->sn.samplebits == 16) + if (sc->sn.samplebytes == 2) count /= 2; count = sc->sn.samples - (count+1); } @@ -368,7 +368,7 @@ static unsigned int SBLASTER_GetDMAPos(soundcardinfo_t *sc) outportb(0xd8, 0); count = inportb(0xc0+(dma-4)*4+2); count += inportb(0xc0+(dma-4)*4+2) << 8; - if (sc->sn.samplebits == 8) + if (sc->sn.samplebytes == 1) count *= 2; count = sc->sn.samples - (count+1); } @@ -523,23 +523,28 @@ static qboolean SBLASTER_InitCard(soundcardinfo_t *sc, const char *pcmname) { if (sc->sn.numchannels != 1) sc->sn.numchannels = 2; - if (sc->sn.samplebits != 8) - sc->sn.samplebits = 16; + if (sc->sn.samplebytes != 1) + sc->sn.samplebytes = 2; } // version 3 cards (sb pro) do 8 bit stereo else if (dsp_version == 3) { if (sc->sn.numchannels != 1) sc->sn.numchannels = 2; - sc->sn.samplebits = 8; + sc->sn.samplebytes = 1; } // v2 cards do 8 bit mono else { sc->sn.numchannels = 1; - sc->sn.samplebits = 8; + sc->sn.samplebytes = 1; } + if (sc->sn.samplebytes == 2) + sc->sn.sampleformat = QSF_S16; + else + sc->sn.sampleformat = QSF_U8; + sc->Lock = SBLASTER_LockBuffer; sc->Unlock = SBLASTER_UnlockBuffer; sc->Shutdown = SBLASTER_Shutdown; @@ -560,10 +565,10 @@ static qboolean SBLASTER_InitCard(soundcardinfo_t *sc, const char *pcmname) dma_size = size; memset(dma_buffer, 0, dma_size); - sc->sn.samples = size/(sc->sn.samplebits/8); + sc->sn.samples = size/sc->sn.samplebytes; sc->sn.samplepos = 0; sc->sn.buffer = (unsigned char *) dma_buffer; - sc->sn.samples = size/(sc->sn.samplebits/8); + sc->sn.samples = size/sc->sn.samplebytes; StartDMA(); StartSB(sc); diff --git a/engine/client/snd_sdl.c b/engine/client/snd_sdl.c index c594b8bd2..fe07722cb 100644 --- a/engine/client/snd_sdl.c +++ b/engine/client/snd_sdl.c @@ -18,12 +18,18 @@ #define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 #define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 #define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 +#define AUDIO_U8 0x0008 +#define AUDIO_S8 0x8008 #define AUDIO_S16LSB 0x8010 #define AUDIO_S16MSB 0x9010 +#define AUDIO_F32LSB 0x8120 +#define AUDIO_F32MSB 0x9120 #if __BYTE_ORDER == __BIG_ENDIAN #define AUDIO_S16SYS AUDIO_S16MSB +#define AUDIO_F32SYS AUDIO_F32MSB #else #define AUDIO_S16SYS AUDIO_S16LSB +#define AUDIO_F32SYS AUDIO_F32LSB #endif #define SDLCALL QDECL @@ -129,8 +135,11 @@ static void SSDL_Shutdown(soundcardinfo_t *sc) Con_DPrintf("Shutdown SDL sound\n"); #if SDL_MAJOR_VERSION >= 2 - SDL_PauseAudioDevice(sc->audio_fd, 1); - SDL_CloseAudioDevice(sc->audio_fd); + if (sc->audio_fd) + { + SDL_PauseAudioDevice(sc->audio_fd, 1); + SDL_CloseAudioDevice(sc->audio_fd); + } #else SDL_CloseAudio(); #endif @@ -142,7 +151,7 @@ static void SSDL_Shutdown(soundcardinfo_t *sc) } static unsigned int SSDL_GetDMAPos(soundcardinfo_t *sc) { - sc->sn.samplepos = sc->snd_sent / (sc->sn.samplebits/8); + sc->sn.samplepos = sc->snd_sent / sc->sn.samplebytes; return sc->sn.samplepos; } @@ -154,12 +163,12 @@ static void VARGS SSDL_Paint(void *userdata, qbyte *stream, int len) #ifdef SELFPAINT sc->sn.buffer = stream; - sc->sn.samples = len / (sc->sn.samplebits/8); + sc->sn.samples = len / sc->sn.samplebytes; sc->samplequeue = sc->sn.samples; S_MixerThread(sc); sc->snd_sent += len; #else - int buffersize = sc->sn.samples*(sc->sn.samplebits/8); + int buffersize = sc->sn.samples*sc->sn.samplebytes; if (len > buffersize) { @@ -220,26 +229,32 @@ static qboolean QDECL SDL_InitCard(soundcardinfo_t *sc, const char *devicename) desired.freq = sc->sn.speed; desired.channels = sc->sn.numchannels; //fixme! desired.samples = 0x0200; //'Good values seem to range between 512 and 8192 inclusive, depending on the application and CPU speed.' - desired.format = AUDIO_S16SYS; desired.callback = (void*)SSDL_Paint; desired.userdata = sc; memcpy(&obtained, &desired, sizeof(obtained)); #if SDL_MAJOR_VERSION >= 2 - sc->audio_fd = SDL_OpenAudioDevice(devicename, false, &desired, &obtained, (sndcardinfo?0:SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) | SDL_AUDIO_ALLOW_CHANNELS_CHANGE); + desired.format = AUDIO_F32SYS; //most modern audio APIs favour float audio nowadays. + sc->audio_fd = SDL_OpenAudioDevice(devicename, false, &desired, &obtained, (sndcardinfo?0:SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) | SDL_AUDIO_ALLOW_CHANNELS_CHANGE | SDL_AUDIO_ALLOW_FORMAT_CHANGE); if (!sc->audio_fd) { Con_Printf("SDL_OpenAudioDevice(%s) failed: couldn't open sound device (%s).\n", devicename?devicename:"default", SDL_GetError()); return false; } + if (obtained.format != AUDIO_U8 && obtained.format != AUDIO_S8 && obtained.format != AUDIO_S16SYS && obtained.format != AUDIO_F32SYS) + { //can't cope with that... try again but force the format (so SDL converts) + SDL_CloseAudioDevice(sc->audio_fd); + sc->audio_fd = SDL_OpenAudioDevice(devicename, false, &desired, &obtained, (sndcardinfo?0:SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) | SDL_AUDIO_ALLOW_CHANNELS_CHANGE); + } if (devicename) Con_Printf("Initing SDL audio device '%s'.\n", devicename); else Con_Printf("Initing default SDL audio device.\n"); #else + desired.format = AUDIO_S16SYS; if (sndcardinfo) return false; //SDL1 only supports opening one audio device at a time. the existing one might not be sdl, but I don't care. - if ( SDL_OpenAudio(&desired, &obtained) < 0 ) + if (SDL_OpenAudio(&desired, &obtained) < 0) { Con_Printf("SDL_OpenAudio failed: couldn't open sound device (%s).\n", SDL_GetError()); return false; @@ -248,21 +263,49 @@ static qboolean QDECL SDL_InitCard(soundcardinfo_t *sc, const char *devicename) #endif sc->sn.numchannels = obtained.channels; sc->sn.speed = obtained.freq; - sc->sn.samplebits = obtained.format&0xff; sc->sn.samples = 32768;//*sc->sn.numchannels; //doesn't really matter, so long as it's higher than obtained.samples + switch(obtained.format) + { + case AUDIO_U8: + sc->sn.samplebytes = 1; + sc->sn.sampleformat = QSF_U8; + break; + case AUDIO_S8: + sc->sn.samplebytes = 1; + sc->sn.sampleformat = QSF_S8; + break; + case AUDIO_S16SYS: + sc->sn.samplebytes = 2; + sc->sn.sampleformat = QSF_S16; + break; + case AUDIO_F32SYS: + sc->sn.samplebytes = 4; + sc->sn.sampleformat = QSF_F32; + break; + default: + //unsupported. shouldn't have obtained that. +#if SDL_MAJOR_VERSION >= 2 + SDL_CloseAudioDevice(sc->audio_fd); + sc->audio_fd = 0; +#else + SDL_CloseAudio(); +#endif + break; + } + #ifdef SELFPAINT sc->selfpainting = true; #endif Con_DPrintf("channels: %i\n", sc->sn.numchannels); Con_DPrintf("Speed: %i\n", sc->sn.speed); - Con_DPrintf("Samplebits: %i\n", sc->sn.samplebits); + Con_DPrintf("Samplebits: %i\n", sc->sn.samplebytes*8); Con_DPrintf("SDLSamples: %i (low for latency)\n", obtained.samples); Con_DPrintf("FakeSamples: %i\n", sc->sn.samples); #ifndef SELFPAINT - sc->sn.buffer = malloc(sc->sn.samples*sc->sn.samplebits/8); + sc->sn.buffer = malloc(sc->sn.samples*sc->sn.samplebytes); #endif Con_DPrintf("Got sound %i-%i\n", obtained.freq, obtained.format); diff --git a/engine/client/snd_sndio.c b/engine/client/snd_sndio.c index 4d3670f1f..12b6d1070 100644 --- a/engine/client/snd_sndio.c +++ b/engine/client/snd_sndio.c @@ -37,24 +37,19 @@ struct sndio_private size_t dma_buffer_size, dma_ptr; }; -static int sndio_init(soundcardinfo_t *, int); +static qboolean sndio_init(soundcardinfo_t *, const char *); static void *sndio_lock(soundcardinfo_t *); static void sndio_unlock(soundcardinfo_t *, void *); static void sndio_shutdown(soundcardinfo_t *); static unsigned int sndio_getdmapos(soundcardinfo_t *); static void sndio_submit(soundcardinfo_t *, int, int); static void sndio_setunderwater(soundcardinfo_t *sc, qboolean underwater); //simply a stub. Any ideas how to actually implement this properly? - + static void sndio_setunderwater(soundcardinfo_t *sc, qboolean underwater) //simply a stub. Any ideas how to actually implement this properly? { } - -#define SND_ERROR 0 -#define SND_LOADED 1 -#define SND_NOMORE 2 //like error, but doesn't try the next card. -static int -sndio_init(soundcardinfo_t *sc, int cardnum) +static qboolean sndio_init(soundcardinfo_t *sc, const char *cardname) { struct sndio_private *sp; struct sio_par par; @@ -63,22 +58,22 @@ sndio_init(soundcardinfo_t *sc, int cardnum) int i; Con_DPrintf("sndio_init called\n"); - if (cardnum) - return SND_NOMORE; + if (cardname && *cardname) + return false; //only support the default device for now. sp = calloc(sizeof(struct sndio_private), 1); if (sp == NULL) { Con_Printf("Could not get mem"); - return SND_ERROR; + return false; } - + Con_DPrintf("trying to open sp->hdl\n"); sp->hdl = sio_open(SIO_DEVANY, SIO_PLAY, 1); if (sp->hdl == NULL) { Con_Printf("Could not open sndio device\n"); - return SND_NOMORE; + return false; } Con_DPrintf("Opened sndio\n"); sc->GetDMAPos = sndio_getdmapos; @@ -88,33 +83,44 @@ sndio_init(soundcardinfo_t *sc, int cardnum) sc->Unlock = sndio_unlock; sc->SetWaterDistortion = sndio_setunderwater; sc->handle = sp; - + sio_initpar(&par); par.rate = sc->sn.speed; - par.bits = sc->sn.samplebits; + par.bits = (sc->sn.samplebytes==1)?8:16; par.sig = 1; par.le = SIO_LE_NATIVE; par.pchan = sc->sn.numchannels; par.appbufsz = par.rate / 20; /* 1/20 second latency */ - + if (!sio_setpar(sp->hdl, &par) || !sio_getpar(sp->hdl, &par)) { Con_Printf("Error setting audio parameters\n"); sio_close(sp->hdl); - return SND_ERROR; + return false; } if ((par.pchan != 1 && par.pchan != 2) || (par.bits != 16 || par.sig != 1)) { Con_Printf("Could not set appropriate audio parameters\n"); sio_close(sp->hdl); - return SND_ERROR; + return false; + } + + if (par.bits == 16) + { + sc->sn.sampleformat = QSF_S16; + sc->sn.samplebytes = 2; + } + else if (par.bits == 8) + { + sc->sn.sampleformat = QSF_U8; + sc->sn.samplebytes = 1; } /* sc->sn.speed = par.rate; sc->sn.numchannels = par.pchan; sc->sn.samplebits = par.bits; */ - + /* * find the smallest power of two larger than the buffer size * and use it as the internal buffer's size @@ -122,36 +128,36 @@ sndio_init(soundcardinfo_t *sc, int cardnum) for (i = 1; i < par.appbufsz; i <<= 1) ; /* nothing */ sc->sn.samples = i * par.pchan; - - sp->dma_buffer_size = sc->sn.samples * sc->sn.samplebits / 8; + + sp->dma_buffer_size = sc->sn.samples * sc->sn.samplebytes; sc->sn.buffer = calloc(1, sp->dma_buffer_size); if (sc->sn.buffer == NULL) { Con_Printf("Could not allocate audio ring buffer\n"); - return SND_ERROR; + return false; } dma_ptr = 0; if (!sio_start(sp->hdl)) { Con_Printf("Could not start audio\n"); sio_close(sp->hdl); - return SND_ERROR; + return false; } sc->sn.samplepos = 0; - + Con_DPrintf("sc->sn.speed = %d, par.rate = %d\n", sc->sn.speed, par.rate); - Con_DPrintf("sc->sn.samplebits = %d, par.bits = %d\n", sc->sn.samplebits, par.bits); + Con_DPrintf("sc->sn.samplebits = %d, par.bits = %d\n", sc->sn.samplebytes*8, par.bits); Con_DPrintf("sc->sn.numchannels = %d, par.pchan = %d\n", sc->sn.numchannels, par.pchan); Con_DPrintf("sc->sn.samples = %d, par.pchan = %d\n", sc->sn.samples, par.pchan); Con_DPrintf("dma_buffer_size = %d\n", sp->dma_buffer_size); - return SND_LOADED; + return true; } static void * sndio_lock(soundcardinfo_t *sc, unsigned int *sampidx) { - return sc->sn.buffer; + return sc->sn.buffer; } static void @@ -163,7 +169,7 @@ static void sndio_shutdown(soundcardinfo_t *sc) { struct sndio_private *sp = sc->handle; - + sio_close(sp->hdl); free(sc->sn.buffer); sc->sn.buffer = NULL; @@ -174,7 +180,7 @@ static unsigned int sndio_getdmapos(soundcardinfo_t *sc) { struct sndio_private *sp = sc->handle; - sc->sn.samplepos = dma_ptr / (sc->sn.samplebits / 8); + sc->sn.samplepos = dma_ptr / sc->sn.samplebytes; return sc->sn.samplepos; } @@ -185,7 +191,7 @@ sndio_submit(soundcardinfo_t *sc, int startcount, int endcount) struct sndio_private *sp = sc->handle; size_t count, todo, avail; int n; - + n = sio_pollfd(sp->hdl, &pfd, POLLOUT); while (poll(&pfd, n, 0) < 0 && errno == EINTR) ; @@ -207,4 +213,10 @@ sndio_submit(soundcardinfo_t *sc, int startcount, int endcount) } } -int (*pSNDIO_InitCard) (soundcardinfo_t *sc, int cardnum) = &sndio_init; +sounddriver_t SNDIO_AudioOutput = +{ + "sndio", + sndio_init, + NULL +}; + diff --git a/engine/client/snd_wasapi.c b/engine/client/snd_wasapi.c index b34a92f05..37800c8bf 100644 --- a/engine/client/snd_wasapi.c +++ b/engine/client/snd_wasapi.c @@ -31,6 +31,18 @@ FORCE_DEFINE_GUID(IID_IAudioRenderClient, 0xF294ACFC, 0x3146, 0x4483, 0xA7, 0x FORCE_DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); FORCE_DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +static cvar_t wasapi_forcerate = CVARD("wasapi_forcerate", "0", "Attempts to force snd_khz instead of using the system's default channel count.\nFor this to work, you will need to set wasapi_exclusive 1"); +static cvar_t wasapi_forcechannels = CVARD("wasapi_forcechannels", "0", "Attempts to force snd_numchannels instead of using the system's default channel count.\nFor this to work, you will need to set wasapi_exclusive 1"); +static cvar_t wasapi_exclusive = CVARD("wasapi_exclusive", "0", "When set, attempts to take exclusive control of the output device, to the detriment of other programs (causing errors or even crashes in them).\nExclusive mode leaves the game free to change the hardware's playback details, instead of being required to use a single system-wide 'mixer' rate.\nIt should also reduce latency a little."); +static cvar_t wasapi_buffersize = CVAR("wasapi_buffersize", "0.01"); +static void QDECL WASAPI_RegisterCvars(void) +{ + Cvar_Register(&wasapi_forcerate, "WASAPI audio output"); + Cvar_Register(&wasapi_forcechannels, "WASAPI audio output"); + Cvar_Register(&wasapi_exclusive, "WASAPI audio output"); + Cvar_Register(&wasapi_buffersize, "WASAPI audio output"); +} + static void *WASAPI_Lock(soundcardinfo_t *sc, unsigned int *startoffset) { return sc->sn.buffer; @@ -55,19 +67,28 @@ static void WASAPI_Shutdown(soundcardinfo_t *sc) sc->thread = NULL; } -static qboolean WASAPI_AcceptableFormat(soundcardinfo_t *sc, IAudioClient *dev, WAVEFORMATEX *pwfx) +static qboolean WASAPI_AcceptableFormat(soundcardinfo_t *sc, IAudioClient *dev, WAVEFORMATEX *pwfx, qboolean isexclusive) { if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE && !memcmp(&((WAVEFORMATEXTENSIBLE*)pwfx)->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) && pwfx->wBitsPerSample == 32) - { - sc->sn.samplebits = 32;//oo, floating point audio. I guess this means we can have fun with clamping, right? + { //oo, floating point audio. I guess this means we can have fun with clamping, right? + sc->sn.samplebytes = 4; + sc->sn.sampleformat = QSF_F32; } else if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE && memcmp(&((WAVEFORMATEXTENSIBLE*)pwfx)->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID))) { Con_Printf("WASAPI: unsupported sample type\n"); return false; //we only support pcm / floats } - else if (pwfx->wBitsPerSample == 8 || pwfx->wBitsPerSample == 16) - sc->sn.samplebits = pwfx->wBitsPerSample; //these sample sizes work + else if (pwfx->wBitsPerSample == 8) + { + sc->sn.samplebytes = 1; + sc->sn.sampleformat = QSF_U8; + } + else if (pwfx->wBitsPerSample == 16) + { + sc->sn.samplebytes = 2; + sc->sn.sampleformat = QSF_S16; + } else { Con_Printf("WASAPI: unsupported sample size\n"); @@ -83,7 +104,7 @@ static qboolean WASAPI_AcceptableFormat(soundcardinfo_t *sc, IAudioClient *dev, sc->sn.numchannels = pwfx->nChannels; sc->sn.speed = pwfx->nSamplesPerSec; - Con_Printf("WASAPI: %i channel %ibit %ukhz\n", sc->sn.numchannels, pwfx->wBitsPerSample, (unsigned int)pwfx->nSamplesPerSec); + Con_Printf("WASAPI: %i channel %ibit %ukhz%s\n", sc->sn.numchannels, pwfx->wBitsPerSample, (unsigned int)pwfx->nSamplesPerSec, isexclusive?" exclusive":" non-exclusive"); return true; } static qboolean WASAPI_DetermineFormat(soundcardinfo_t *sc, IAudioClient *dev, qboolean exclusive, WAVEFORMATEX **ret) @@ -93,7 +114,7 @@ static qboolean WASAPI_DetermineFormat(soundcardinfo_t *sc, IAudioClient *dev, q if (!SUCCEEDED(dev->lpVtbl->GetMixFormat(dev, &pwfx))) return false; - if (snd_speed || Cvar_Get("wasapi_forcerate", "0", 0, "WASAPI audio output")->ival) + if (snd_speed || wasapi_forcerate.ival) { //if some other driver has already committed us to a set rate, we need to drive wasapi at that rate too. //this may cause failures later in this function. Con_Printf("WASAPI: overriding sampler rate\n"); @@ -101,7 +122,7 @@ static qboolean WASAPI_DetermineFormat(soundcardinfo_t *sc, IAudioClient *dev, q pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign; } - if (Cvar_Get("wasapi_forcechannels", "0", 0, "WASAPI audio output")->ival) + if (wasapi_forcechannels.ival) { Con_Printf("WASAPI: overriding channels\n"); @@ -150,8 +171,7 @@ static qboolean WASAPI_DetermineFormat(soundcardinfo_t *sc, IAudioClient *dev, q pwfx = pwfx2; } } - - if (!WASAPI_AcceptableFormat(sc, dev, pwfx)) + if (!WASAPI_AcceptableFormat(sc, dev, pwfx, false)) return false; } else @@ -170,6 +190,8 @@ static qboolean WASAPI_DetermineFormat(soundcardinfo_t *sc, IAudioClient *dev, q if (FAILED(dev->lpVtbl->IsFormatSupported(dev, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, pwfx, NULL))) { + //fixme: try float audio... + //try to switch over to 24bit pcm (although with no more 16bit audio) /* pwfx->wBitsPerSample = 24; pwfx->nBlockAlign = pwfx->wBitsPerSample/8 * pwfx->nChannels; @@ -184,112 +206,128 @@ static qboolean WASAPI_DetermineFormat(soundcardinfo_t *sc, IAudioClient *dev, q } } } - - if (!WASAPI_AcceptableFormat(sc, dev, pwfx)) + if (!WASAPI_AcceptableFormat(sc, dev, pwfx, true)) return false; } - + *ret = pwfx; return true; } +static IMMDevice *WASAPI_GetDevice(soundcardinfo_t *sc) +{ + IMMDeviceEnumerator *pEnumerator = NULL; + IMMDevice *pDevice = NULL; + CoInitialize(NULL); //sigh. + if (SUCCEEDED(CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void**)&pEnumerator))) + { + if (*sc->name) + { + WCHAR wname[256]; + pEnumerator->lpVtbl->GetDevice(pEnumerator, widen(wname, sizeof(wname), sc->name), &pDevice); + } + else + pEnumerator->lpVtbl->GetDefaultAudioEndpoint(pEnumerator, eRender, eConsole, &pDevice); + pEnumerator->lpVtbl->Release(pEnumerator); + } + return pDevice; +} static int WASAPI_Thread(void *arg) { soundcardinfo_t *sc = arg; qboolean inited = false; // REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; - IMMDeviceEnumerator *pEnumerator = NULL; - IMMDevice *pDevice = NULL; IAudioClient *pAudioClient = NULL; IAudioRenderClient *pRenderClient = NULL; UINT32 bufferFrameCount = 0; HANDLE hEvent = NULL; WAVEFORMATEX *pwfx; - qboolean exclusive = Cvar_Get("wasapi_exclusive", "1", 0, "WASAPI audio output")->ival; + qboolean exclusive = wasapi_exclusive.ival; void *cond = sc->handle; //main thread will wait for us to finish initing, so lets do that... - CoInitialize(NULL); - if (SUCCEEDED(CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void**)&pEnumerator))) - if (SUCCEEDED(pEnumerator->lpVtbl->GetDefaultAudioEndpoint(pEnumerator, eRender, eConsole, &pDevice))) - if (SUCCEEDED(pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient))) + IMMDevice *pDevice = WASAPI_GetDevice(sc); + if (pDevice) { - if (!WASAPI_DetermineFormat(sc, pAudioClient, exclusive, &pwfx)) + if (SUCCEEDED(pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient))) { - Con_Printf("WASAPI: unable to determine mutually supported audio format\n"); - } - else - { - - - if (sc->sn.samplebits && (!snd_speed || sc->sn.speed == snd_speed)) + if (!WASAPI_DetermineFormat(sc, pAudioClient, exclusive, &pwfx)) { - HRESULT hr; - REFERENCE_TIME buffersize = REFTIMES_PER_SEC * Cvar_Get("wasapi_buffersize", "0.01", 0, "WASAPI audio output")->ival; - if (exclusive) - pAudioClient->lpVtbl->GetDevicePeriod(pAudioClient, NULL, &buffersize); + Con_Printf("WASAPI: unable to determine mutually supported audio format\n"); + } + else + { + if (sc->sn.samplebytes && (!snd_speed || sc->sn.speed == snd_speed)) + { + HRESULT hr; + REFERENCE_TIME buffersize = REFTIMES_PER_SEC * wasapi_buffersize.value; + if (exclusive) + pAudioClient->lpVtbl->GetDevicePeriod(pAudioClient, NULL, &buffersize); - hr = pAudioClient->lpVtbl->Initialize(pAudioClient, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buffersize, (exclusive?buffersize:0), pwfx, NULL); + hr = pAudioClient->lpVtbl->Initialize(pAudioClient, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buffersize, (exclusive?buffersize:0), pwfx, NULL); - if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) - { //this is stupid, but does what the documentation says should be done. - if (SUCCEEDED(pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount))) - { - if (pAudioClient) - pAudioClient->lpVtbl->Release(pAudioClient); - pAudioClient = NULL; - if (SUCCEEDED(pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient))) + if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) + { //this is stupid, but does what the documentation says should be done. + if (SUCCEEDED(pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount))) { - buffersize = (REFERENCE_TIME)((10000.0 * 1000 / pwfx->nSamplesPerSec * bufferFrameCount) + 0.5); - hr = pAudioClient->lpVtbl->Initialize(pAudioClient, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buffersize, (exclusive?buffersize:0), pwfx, NULL); + if (pAudioClient) + pAudioClient->lpVtbl->Release(pAudioClient); + pAudioClient = NULL; + if (SUCCEEDED(pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient))) + { + buffersize = (REFERENCE_TIME)((10000.0 * 1000 / pwfx->nSamplesPerSec * bufferFrameCount) + 0.5); + hr = pAudioClient->lpVtbl->Initialize(pAudioClient, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buffersize, (exclusive?buffersize:0), pwfx, NULL); + } + } + } + + if (SUCCEEDED(hr)) + { + hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (hEvent) + { + pAudioClient->lpVtbl->SetEventHandle(pAudioClient, hEvent); + if (SUCCEEDED(pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount))) + if (SUCCEEDED(pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioRenderClient, (void**)&pRenderClient))) + inited = true; + } + } + else + { + switch(hr) + { + case AUDCLNT_E_UNSUPPORTED_FORMAT: + Con_Printf("WASAPI Initialize: AUDCLNT_E_UNSUPPORTED_FORMAT\n"); + break; + case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: + Con_Printf("WASAPI Initialize: AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED\n"); + break; + case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: + Con_Printf("WASAPI Initialize: AUDCLNT_E_EXCLUSIVE_MODE_ONLY\n"); + break; + case AUDCLNT_E_DEVICE_IN_USE: + Con_Printf("WASAPI Initialize: AUDCLNT_E_DEVICE_IN_USE\n"); + break; + case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: + Con_Printf("WASAPI Initialize: AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED\n"); + break; + case E_INVALIDARG: + Con_Printf("WASAPI Initialize: E_INVALIDARG\n"); + break; + default: + Con_Printf("pAudioClient->lpVtbl->Initialize failed (%x)\n", (unsigned int)hr); } } } - if (SUCCEEDED(hr)) - { - hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - if (hEvent) - { - pAudioClient->lpVtbl->SetEventHandle(pAudioClient, hEvent); - if (SUCCEEDED(pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount))) - if (SUCCEEDED(pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioRenderClient, (void**)&pRenderClient))) - inited = true; - } - } - else - { - switch(hr) - { - case AUDCLNT_E_UNSUPPORTED_FORMAT: - Con_Printf("WASAPI Initialize: AUDCLNT_E_UNSUPPORTED_FORMAT\n"); - break; - case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: - Con_Printf("WASAPI Initialize: AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED\n"); - break; - case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: - Con_Printf("WASAPI Initialize: AUDCLNT_E_EXCLUSIVE_MODE_ONLY\n"); - break; - case AUDCLNT_E_DEVICE_IN_USE: - Con_Printf("WASAPI Initialize: AUDCLNT_E_DEVICE_IN_USE\n"); - break; - case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: - Con_Printf("WASAPI Initialize: AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED\n"); - break; - case E_INVALIDARG: - Con_Printf("WASAPI Initialize: E_INVALIDARG\n"); - break; - default: - Con_Printf("pAudioClient->lpVtbl->Initialize failed (%x)\n", (unsigned int)hr); - } - } + CoTaskMemFree(pwfx); } - - CoTaskMemFree(pwfx); } + pDevice->lpVtbl->Release(pDevice); + pDevice = NULL; } if (inited) @@ -359,19 +397,12 @@ static int WASAPI_Thread(void *arg) pRenderClient->lpVtbl->Release(pRenderClient); if (pAudioClient) pAudioClient->lpVtbl->Release(pAudioClient); - if (pDevice) - pDevice->lpVtbl->Release(pDevice); - if (pEnumerator) - pEnumerator->lpVtbl->Release(pEnumerator); return 0; } static qboolean QDECL WASAPI_InitCard (soundcardinfo_t *sc, const char *cardname) { void *cond; - if (cardname && *cardname) - return false; //we don't support explicit devices at this time - Q_strncpyz(sc->name, cardname?cardname:"", sizeof(sc->name)); sc->selfpainting = true; @@ -504,7 +535,8 @@ sounddriver_t WASAPI_Output = { AUDIODRIVERNAME, WASAPI_InitCard, - WASAPI_Enumerate + WASAPI_Enumerate, + WASAPI_RegisterCvars }; #endif \ No newline at end of file diff --git a/engine/client/snd_win.c b/engine/client/snd_win.c index a10d8616f..3391757d0 100644 --- a/engine/client/snd_win.c +++ b/engine/client/snd_win.c @@ -142,14 +142,8 @@ how many sample are required to fill it up. static unsigned int WAV_GetDMAPos(soundcardinfo_t *sc) { int s; - s = sc->snd_sent * WAV_BUFFER_SIZE; - - - s >>= (sc->sn.samplebits/8) - 1; - -// s = (s/shm->numchannels % (shm->samples-1))*shm->numchannels; - + s >>= sc->sn.samplebytes - 1; return s; } @@ -194,7 +188,7 @@ static void WAV_Submit(soundcardinfo_t *sc, int start, int end) else chunkstosubmit = 4 + (sc->sn.speed/6000); - while (((sc->snd_sent - sc->snd_completed) >> ((sc->sn.samplebits/8) - 1)) < chunkstosubmit) + while (((sc->snd_sent - sc->snd_completed) >> (sc->sn.samplebytes - 1)) < chunkstosubmit) { h = wh->lpWaveHdr + ( sc->snd_sent&WAV_MASK ); @@ -224,15 +218,15 @@ SNDDM_InitWav Crappy windows multimedia base ================== */ -int WAV_InitCard (soundcardinfo_t *sc, int cardnum) +qboolean WAV_InitCard (soundcardinfo_t *sc, const char *cardname) { WAVEFORMATEX format; int i; HRESULT hr; wavhandle_t *wh; - if (cardnum != 0) - return 2; //we only support one card, at the moment. + if (*cardname) + return false; //we only support one card, at the moment. wh = sc->handle = Z_Malloc(sizeof(wavhandle_t)); @@ -242,15 +236,21 @@ int WAV_InitCard (soundcardinfo_t *sc, int cardnum) if (sc->sn.speed > 48000) // limit waveout to 48000 until that buffer issue gets solved sc->sn.speed = 48000; - if (sc->sn.samplebits > 16) - sc->sn.samplebits = 16; + if (sc->sn.samplebytes > 2) + { + sc->sn.samplebytes = 2; + sc->sn.sampleformat = QSF_S16; + } else - sc->sn.samplebits = 8; + { + sc->sn.samplebytes = 1; + sc->sn.sampleformat = QSF_U8; + } memset (&format, 0, sizeof(format)); format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = sc->sn.numchannels; - format.wBitsPerSample = sc->sn.samplebits; + format.wBitsPerSample = sc->sn.samplebytes*8; format.nSamplesPerSec = sc->sn.speed; format.nBlockAlign = format.nChannels *format.wBitsPerSample / 8; @@ -350,7 +350,7 @@ int WAV_InitCard (soundcardinfo_t *sc, int cardnum) } } - sc->sn.samples = wh->gSndBufSize/(sc->sn.samplebits/8); + sc->sn.samples = wh->gSndBufSize/sc->sn.samplebytes; sc->sn.samplepos = 0; sc->sn.buffer = (unsigned char *) wh->lpData; Q_strncpyz(sc->name, "wav out", sizeof(sc->name)); @@ -364,5 +364,11 @@ int WAV_InitCard (soundcardinfo_t *sc, int cardnum) return true; } -int (*pWAV_InitCard) (soundcardinfo_t *sc, int cardnum) = &WAV_InitCard; +//int (*pWAV_InitCard) (soundcardinfo_t *sc, int cardnum) = &WAV_InitCard; +sounddriver_t WaveOut_Output = +{ + "WaveOut", + WAV_InitCard, + NULL +}; #endif diff --git a/engine/client/snd_xaudio.c b/engine/client/snd_xaudio.c index cd69f78e7..b5c43b32f 100644 --- a/engine/client/snd_xaudio.c +++ b/engine/client/snd_xaudio.c @@ -2,13 +2,14 @@ //frankly, xaudio2 gives nothing over directsound, unless we're getting it to do all the mixing instead. which gets really messy and far too involved. //I suppose it has a use with WINRT... although that doesn't apply to any actual users. +//(on xp, its actually implemented as a wrapper over directsound, so why even bother. on vista+ its implemented as a wrapper over wasapi) //we're lazy and don't do any special threading, this makes it inferior to the directsound implementation - potentially, the callback feature could allow for slightly lower latencies. //also no reverb (fixme: XAUDIO2FX_REVERB_PARAMETERS). -//dxsdk = 2.7 = win7+ -//w8sdk = 2.8 = win8+ -//w10sdk = 2.9 = win10+ +//dxsdk = 2.7 = win7+ (redistributable) +//w8sdk = 2.8 = win8+ (system component, not available on vista/win7) +//w10sdk = 2.9 = win10+ (system component, not available on vista/win7/win8/win8.1) #if defined(AVAIL_XAUDIO2) && !defined(SERVERONLY) #include "winquake.h" @@ -58,11 +59,11 @@ static void XAUDIO_Submit(soundcardinfo_t *sc, int start, int end) XAUDIO2_BUFFER buf; //determine total buffer size - int buffersize = sc->sn.samples*sc->sn.samplebits/8; + int buffersize = sc->sn.samples*sc->sn.samplebytes; //determine time offsets in bytes - start *= sc->sn.numchannels*sc->sn.samplebits/8; - end *= sc->sn.numchannels*sc->sn.samplebits/8; + start *= sc->sn.numchannels*sc->sn.samplebytes; + end *= sc->sn.numchannels*sc->sn.samplebytes; while (start < end) { @@ -70,14 +71,14 @@ static void XAUDIO_Submit(soundcardinfo_t *sc, int start, int end) break; //o.O that's not meant to happen memset(&buf, 0, sizeof(buf)); buf.AudioBytes = end - start; - if (buf.AudioBytes > xa->subbuffersize * sc->sn.numchannels*sc->sn.samplebits/8) + if (buf.AudioBytes > xa->subbuffersize * sc->sn.numchannels*sc->sn.samplebytes) { - if (buf.AudioBytes < xa->subbuffersize * sc->sn.numchannels*sc->sn.samplebits/8) + if (buf.AudioBytes < xa->subbuffersize * sc->sn.numchannels*sc->sn.samplebytes) { //dma code should ensure that only multiples of 'samplequeue' are processed. Con_Printf("XAudio2 underrun\n"); break; } - buf.AudioBytes = xa->subbuffersize * sc->sn.numchannels*sc->sn.samplebits/8; + buf.AudioBytes = xa->subbuffersize * sc->sn.numchannels*sc->sn.samplebytes; } buf.pAudioData = xa->bufferstart + (start%buffersize); if ((qbyte*)buf.pAudioData + buf.AudioBytes > xa->bufferstart + buffersize) @@ -114,12 +115,12 @@ void WINAPI XAUDIO_CB_OnVoiceError (IXAudio2VoiceCallback *ths, void* pBufferCon static IXAudio2VoiceCallbackVtbl cbvtbl = { XAUDIO_CB_OnVoiceProcessingPassStart, - XAUDIO_CB_OnVoiceProcessingPassEnd, - XAUDIO_CB_OnStreamEnd, - XAUDIO_CB_OnBufferStart, - XAUDIO_CB_OnBufferEnd, - XAUDIO_CB_OnLoopEnd, - XAUDIO_CB_OnVoiceError + XAUDIO_CB_OnVoiceProcessingPassEnd, + XAUDIO_CB_OnStreamEnd, + XAUDIO_CB_OnBufferStart, + XAUDIO_CB_OnBufferEnd, + XAUDIO_CB_OnLoopEnd, + XAUDIO_CB_OnVoiceError }; static qboolean QDECL XAUDIO_InitCard(soundcardinfo_t *sc, const char *cardname) @@ -141,7 +142,7 @@ static qboolean QDECL XAUDIO_InitCard(soundcardinfo_t *sc, const char *cardname) wfmt.Format.wFormatTag = WAVE_FORMAT_PCM; wfmt.Format.nChannels = sc->sn.numchannels; wfmt.Format.nSamplesPerSec = sc->sn.speed; - wfmt.Format.wBitsPerSample = sc->sn.samplebits; + wfmt.Format.wBitsPerSample = sc->sn.samplebytes*8; wfmt.Format.nBlockAlign = wfmt.Format.nChannels * (wfmt.Format.wBitsPerSample / 8); wfmt.Format.nAvgBytesPerSec = wfmt.Format.nSamplesPerSec * wfmt.Format.nBlockAlign; wfmt.Format.cbSize = 0; @@ -159,7 +160,7 @@ static qboolean QDECL XAUDIO_InitCard(soundcardinfo_t *sc, const char *cardname) sc->sn.samples = xa->buffercount * xa->subbuffersize * sc->sn.numchannels; - xa->bufferstart = BZ_Malloc(sc->sn.samples * (sc->sn.samplebits/8)); + xa->bufferstart = BZ_Malloc(sc->sn.samples * sc->sn.samplebytes); if (xa->bufferstart) { diff --git a/engine/client/sound.h b/engine/client/sound.h index 4d02cd940..0621dfc4e 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -80,14 +80,21 @@ typedef struct sfxcache_s typedef struct { -// qboolean gamealive; -// qboolean soundalive; -// qboolean splitbuffer; int numchannels; // this many samples per frame int samples; // mono samples in buffer (individual, non grouped) -// int submission_chunk; // don't mix less than this # int samplepos; // in mono samples - int samplebits; //FIXME: replace with a format enum (with separate framebytes field for lazyness). + int samplebytes; // per channel (NOT per frame) + enum + { + QSF_INVALID, //not selected yet... + QSF_EXTERNALMIXER, //this sample format is totally irrelevant as this device uses some sort of external mixer. + QSF_U8, //FIXME: more unsigned formats need changes to S_ClearBuffer + QSF_S8, //signed 8bit format is actually quite rare. + QSF_S16, //normal format +// QSF_X8_S24, //upper 8 bits unused. hopefully we don't need any packed thing +// QSF_S32, //lower 8 bits probably unused. this makes overflow detection messy. + QSF_F32, //modern mixers can use SSE/SIMD stuff, and we can skip clamping so this can be quite nippy. + } sampleformat; int speed; // this many frames per second unsigned char *buffer; // pointer to mixed pcm buffer (not directly used by mixer) } dma_t; @@ -278,11 +285,6 @@ void SNDVC_MicInput(qbyte *buffer, int samples, int freq, int width); -#ifdef AVAIL_OPENAL -void OpenAL_CvarInit(void); -#endif - - // ==================================================================== // User-setable variables // ==================================================================== @@ -338,8 +340,9 @@ typedef struct const char *name; //must be a single token, with no : qboolean (QDECL *InitCard) (soundcardinfo_t *sc, const char *cardname); //NULL for default device. qboolean (QDECL *Enumerate) (void (QDECL *callback) (const char *drivername, const char *devicecode, const char *readablename)); + void (QDECL *RegisterCvars) (void); } sounddriver_t; -typedef int (*sounddriver) (soundcardinfo_t *sc, int cardnum); +/*typedef int (*sounddriver) (soundcardinfo_t *sc, int cardnum); extern sounddriver pOPENAL_InitCard; extern sounddriver pDSOUND_InitCard; extern sounddriver pALSA_InitCard; @@ -348,6 +351,7 @@ extern sounddriver pOSS_InitCard; extern sounddriver pSDL_InitCard; extern sounddriver pWAV_InitCard; extern sounddriver pAHI_InitCard; +*/ struct soundcardinfo_s { //windows has one defined AFTER directsound char name[256]; //a description of the card. diff --git a/engine/client/sys_sdl.c b/engine/client/sys_sdl.c index bc91a3dad..ea169206b 100644 --- a/engine/client/sys_sdl.c +++ b/engine/client/sys_sdl.c @@ -11,6 +11,9 @@ #ifndef WIN32 #include #include +#ifdef __unix__ +#include +#endif #else #include #endif diff --git a/engine/client/sys_win.c b/engine/client/sys_win.c index 7e953ab6d..3087078b6 100644 --- a/engine/client/sys_win.c +++ b/engine/client/sys_win.c @@ -475,7 +475,7 @@ dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) Con_Printf("Error ERROR_PROC_NOT_FOUND loading %s\n", name); break; default: - Con_Printf("Error %u loading %s\n", err, name); + Con_Printf("Error %u loading %s\n", (unsigned)err, name); break; } @@ -1845,6 +1845,7 @@ char *Sys_GetClipboard(void) { cp = (cp&0x3ff)<<10; cp |= *clipWText++ & 0x3ff; + cp += 0x10000; } else cp = 0xFFFDu; @@ -4258,7 +4259,7 @@ void *WIN_CreateCursor(const char *filename, float hotx, float hoty, float scale COM_FileExtension(filename, ext, sizeof(ext)); Q_strncatz(aname, "_alpha.", sizeof(aname)); Q_strncatz(aname, ext, sizeof(aname)); - alphsize = FS_LoadFile(filename, &alph); + alphsize = FS_LoadFile(filename, (void**)&alph); if (alph) { if ((alphadata = Read32BitImageFile(alph, alphsize, &alpha_width, &alpha_height, &hasalpha, aname))) diff --git a/engine/client/view.c b/engine/client/view.c index bf8e1a61d..135bf9ee6 100644 --- a/engine/client/view.c +++ b/engine/client/view.c @@ -1858,6 +1858,7 @@ msurface_t *Mod_GetSurfaceNearPoint(model_t *model, vec3_t point); char *Shader_GetShaderBody(shader_t *s, char *fname, size_t fnamesize); extern vec3_t nametagorg[MAX_CLIENTS]; extern qboolean nametagseen[MAX_CLIENTS]; +extern cvar_t r_showshaders, r_showfields, r_projection; void R_DrawNameTags(void) { int i; @@ -1866,8 +1867,6 @@ void R_DrawNameTags(void) char *ourteam; int ourcolour; - extern cvar_t r_showshaders, r_showfields, r_projection; - if (r_projection.ival) //we don't actually know how to transform the points unless the projection is coded in advance. and it isn't. return; if (cls.protocol == CP_QUAKE2) @@ -1985,6 +1984,7 @@ void R_DrawNameTags(void) } } +#if defined(CSQC_DAT) || !defined(CLIENTONLY) if (r_showshaders.ival && cl.worldmodel && cl.worldmodel->loadstate == MLS_LOADED) { trace_t trace; @@ -2012,6 +2012,7 @@ void R_DrawNameTags(void) str = "hit nothing"; R_DrawTextField(r_refdef.vrect.x + r_refdef.vrect.width/4, r_refdef.vrect.y, r_refdef.vrect.width/2, r_refdef.vrect.height, str, CON_WHITEMASK, CPRINT_LALIGN, font_default, scale); } +#endif if (((!r_refdef.playerview->spectator && !cls.demoplayback) || !scr_autoid.ival) && (!cl.teamplay || !scr_autoid_team.ival)) return; diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 7638b18ac..ad036310c 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -108,13 +108,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define AVAIL_OGGVORBIS #endif -#if !defined(NO_DIRECTX) && !defined(NODIRECTX) && defined(_WIN32) +#ifdef WINRT + #define AVAIL_XAUDIO2 + #define AVAIL_WASAPI +#elif !defined(NO_DIRECTX) && !defined(NODIRECTX) && defined(_WIN32) #define AVAIL_DINPUT #define AVAIL_DSOUND #define AVAIL_WASAPI -#endif -#ifdef WINRT - #define AVAIL_XAUDIO2 + //#define AVAIL_XAUDIO2 //gcc doesn't provide any headers #endif #define AVAIL_XZDEC @@ -568,7 +569,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #if defined(HAVE_GNUTLS) || defined(HAVE_WINSSPI) //FIXME: HAVE_WINSSPI does not work as a server. //FIXME: advertising dtls without a valid certificate will probably bug out if a client tries to auto-upgrade. -// #define HAVE_DTLS + //FIXME: we don't cache server certs + #define HAVE_DTLS #endif #if defined(USE_SQLITE) || defined(USE_MYSQL) diff --git a/engine/common/cmd.c b/engine/common/cmd.c index f40216bb1..cb51d9f01 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -2137,9 +2137,9 @@ struct cmdargcompletionctx_s match_t *match; const char *desc; }; -void Cmd_CompleteCheckArg(const char *value, void *vctx) //compare cumulative strings and join the result +void Cmd_CompleteCheckArg(const char *value, struct xcommandargcompletioncb_s *vctx) //compare cumulative strings and join the result { - struct cmdargcompletionctx_s *ctx = vctx; + struct cmdargcompletionctx_s *ctx = (struct cmdargcompletionctx_s*)vctx; match_t *match = ctx->match; const char *desc = ctx->desc; diff --git a/engine/common/common.c b/engine/common/common.c index c895406ba..08e8c74ad 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -2395,11 +2395,13 @@ unsigned int unicode_decode(int *error, const void *in, char **out, qboolean mar unsigned int charcode; if (markup && ((char*)in)[0] == '^' && ((char*)in)[1] == 'U' && ishexcode(((char*)in)[2]) && ishexcode(((char*)in)[3]) && ishexcode(((char*)in)[4]) && ishexcode(((char*)in)[5])) { + *error = 0; *out = (char*)in + 6; charcode = (dehex(((char*)in)[2]) << 12) | (dehex(((char*)in)[3]) << 8) | (dehex(((char*)in)[4]) << 4) | (dehex(((char*)in)[5]) << 0); } else if (markup && ((char*)in)[0] == '^' && ((char*)in)[1] == '{') { + *error = 0; *out = (char*)in + 2; charcode = 0; while (ishexcode(**out)) @@ -3344,10 +3346,6 @@ conchar_t *COM_ParseFunString(conchar_t defaultflags, const char *str, conchar_t } continue; } - else if (str[1] == 'a') - { - ext ^= CON_2NDCHARSETTEXT; - } else if (str[1] == 'b') { ext ^= CON_BLINKTEXT; @@ -3359,7 +3357,7 @@ conchar_t *COM_ParseFunString(conchar_t defaultflags, const char *str, conchar_t else ext = defaultflags; } - else if (str[1] == 'm') + else if (str[1] == 'm'||str[1] == 'a') ext ^= CON_2NDCHARSETTEXT; else if (str[1] == 'h') ext ^= CON_HALFALPHA; diff --git a/engine/common/common.h b/engine/common/common.h index b7ca5ebbd..8a1c43051 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -734,8 +734,12 @@ qbyte COM_BlockSequenceCheckByte (qbyte *base, int length, int sequence, unsigne qbyte COM_BlockSequenceCRCByte (qbyte *base, int length, int sequence); qbyte Q2COM_BlockSequenceCRCByte (qbyte *base, int length, int sequence); -int SHA1(char *digest, int maxdigestsize, const char *string, int stringlen); -int SHA1_HMAC(unsigned char *digest, int maxdigestsize, const unsigned char *data, int datalen, const unsigned char *key, int keylen); +typedef size_t hashfunc_t(unsigned char *digest, size_t maxdigestsize, size_t numstrings, const unsigned char **strings, size_t *stringlens); +hashfunc_t SHA1_m; +//int SHA1_m(char *digest, size_t maxdigestsize, size_t numstrings, const char **strings, size_t *stringlens); +//#define SHA1(digest,maxdigestsize,string,stringlen) SHA1_m(digest, maxdigestsize, 1, &string, &stringlen) +int SHA1(unsigned char *digest, int 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); int version_number(void); char *version_string(void); @@ -769,6 +773,7 @@ void Log_Init(void); void Log_ShutDown(void); void IPLog_Add(const char *ip, const char *name); //for associating player ip addresses with names. qboolean IPLog_Merge_File(const char *fname); +qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize); /*used by and for botlib and q3 gamecode*/ diff --git a/engine/common/config_wastes.h b/engine/common/config_wastes.h index 650f6ed6f..723c02024 100644 --- a/engine/common/config_wastes.h +++ b/engine/common/config_wastes.h @@ -44,6 +44,7 @@ #define MULTITHREAD //misc basic multithreading - dsound, downloads, basic stuff that's unlikely to have race conditions. #endif #define LOADERTHREAD //worker threads for loading misc stuff. falls back on main thread if not supported. +//#define USEAREAGRID //world collision optimisation. REQUIRED for performance with xonotic. hopefully it helps a few other mods too. #define NOBUILTINMENUS #define NOLEGACY //just spike trying to kill off crappy crap... diff --git a/engine/common/console.h b/engine/common/console.h index 9ac7a01a4..0094fda30 100644 --- a/engine/common/console.h +++ b/engine/common/console.h @@ -139,6 +139,7 @@ typedef struct console_s char name[128]; char title[128]; char prompt[128]; + char icon[MAX_QPATH]; //should really dynamically allocate this stuff. char backimage[MAX_QPATH]; shader_t *backshader; float wnd_x; diff --git a/engine/common/fs_dzip.c b/engine/common/fs_dzip.c index f80724c1b..d706f1978 100644 --- a/engine/common/fs_dzip.c +++ b/engine/common/fs_dzip.c @@ -856,7 +856,7 @@ uInt dem_uncompress_block(decodectx_t *dc) if (cfields & 4) { cam2 += getlong(inptr); inptr += 4; } outlen = 0; - insert_msg(&a1,4); + a1 = 0/*length*/; insert_msg(&a1,4); a1 = cnvlong(cam0); insert_msg(&a1,4); a1 = cnvlong(cam1); insert_msg(&a1,4); a1 = cnvlong(cam2); insert_msg(&a1,4); diff --git a/engine/common/fs_win32.c b/engine/common/fs_win32.c index 9623c5c1c..2c809987a 100644 --- a/engine/common/fs_win32.c +++ b/engine/common/fs_win32.c @@ -250,7 +250,11 @@ static int QDECL VFSW32_WriteBytes (struct vfsfile_s *file, const void *buffer, } if (!WriteFile(intfile->hand, buffer, bytestoread, &written, NULL)) + { +// DWORD err = GetLastError(); + // ERROR_INVALID_USER_BUFFER or ERROR_NOT_ENOUGH_MEMORY return 0; + } return written; } static qboolean QDECL VFSW32_Seek (struct vfsfile_s *file, qofs_t pos) diff --git a/engine/common/log.c b/engine/common/log.c index d18d59150..83930d819 100644 --- a/engine/common/log.c +++ b/engine/common/log.c @@ -613,6 +613,171 @@ static void IPLog_Merge_f(void) if (!IPLog_Merge_File(fname)) Con_Printf("unable to read %s\n", fname); } + +#ifndef SERVERONLY +struct certlog_s +{ + link_t l; + char *hostname; + size_t certsize; + qbyte cert[1]; +}; +static link_t certlog; +static struct certlog_s *CertLog_Find(const char *hostname) +{ + struct certlog_s *l; + for (l = (struct certlog_s*)certlog.next ; l != (struct certlog_s*)&certlog ; l = (struct certlog_s*)l->l.next) + { + if (!strcmp(l->hostname, hostname)) + return l; + } + return NULL; +} +static void CertLog_Update(const char *hostname, const void *cert, size_t certsize) +{ + struct certlog_s *l = CertLog_Find(hostname); + if (l) + { + RemoveLink(&l->l); + Z_Free(l); + } + l = Z_Malloc(sizeof(*l) + certsize + strlen(hostname)); + l->certsize = certsize; + l->hostname = l->cert + l->certsize; + memcpy(l->cert, cert, certsize); + strcpy(l->hostname, hostname); + InsertLinkAfter(&l->l, &certlog); +} +static void CertLog_Write(void) +{ + struct certlog_s *l; + vfsfile_t *f = NULL;//FS_OpenVFS("knowncerts.txt", "wb", FS_ROOT); + if (f) + { + VFS_PRINTF(f, "version 1.0\n"); + + for (l=(struct certlog_s*)certlog.next ; l != (struct certlog_s*)&certlog ; l = (struct certlog_s*)l->l.next) + { + char certhex[32768]; + size_t i; + const char *hex = "0123456789abcdef"; + for (i = 0; i < l->certsize; i++) + { + certhex[i*2+0] = hex[l->cert[i]>>4]; + certhex[i*2+1] = hex[l->cert[i]&0xf]; + } + certhex[i*2] = 0; + VFS_PRINTF(f, "%s \"%s\"\n", l->hostname, certhex); + } + } +} +static void CertLog_Purge(void) +{ + while (certlog.next != &certlog) + { + struct certlog_s *l = (struct certlog_s*)certlog.next; + RemoveLink(&l->l); + Z_Free(l); + } +} +static int hexdecode(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return 0; +} +static void CertLog_Import(const char *filename) +{ + char addressstring[512]; + char certhex[32768]; + char certdata[16384]; + char line[65536], *l; + size_t i, certsize; + vfsfile_t *f = FS_OpenVFS(filename, "rb", FS_ROOT); + //CertLog_Purge(); + VFS_GETS(f, line, sizeof(line)); + if (strncmp(line, "version 1.", 10)) + return; //unsupported... + while (VFS_GETS(f, line, sizeof(line))) + { + l = line; + l = COM_ParseOut(l, addressstring, sizeof(addressstring)); + l = COM_ParseOut(l, certhex, sizeof(certhex)); + + certsize = 0; + for (i = 0; certsize < sizeof(certdata); i++) + { + if (!certhex[(i<<1)+0] || !certhex[(i<<1)+1]) + break; + certdata[certsize++] = (hexdecode(certhex[(i<<1)+0])<<4)|hexdecode(certhex[(i<<1)+1]); + } + CertLog_Update(addressstring, certdata, certsize); + } +} +static void CertLog_UntrustAll_f(void) +{ + CertLog_Purge(); +} +static void CertLog_Import_f(void) +{ + const char *fname = Cmd_Argv(1); + if (!*fname) + fname = "knowncerts.txt"; + CertLog_Import(fname); +} +struct certprompt_s +{ + char *hostname; + + size_t certsize; + qbyte cert[1]; +}; +static struct certprompt_s *certlog_curprompt; +static void CertLog_Add_Prompted(void *vctx, int button) +{ + struct certprompt_s *ctx = vctx; + if (button == 0) //button_yes / button_left + { + CertLog_Update(ctx->hostname, ctx->cert, ctx->certsize); + CertLog_Write(); + } + else + CL_Disconnect(); + + certlog_curprompt = NULL; +} +qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize) +{ + struct certlog_s *l = CertLog_Find(hostname); + + if (certlog_curprompt) + return false; + + if (!l || l->certsize != certsize || memcmp(l->cert, cert, certsize)) + { + if (qrenderer) + { + struct certprompt_s *ctx = certlog_curprompt = Z_Malloc(sizeof(*ctx)+certsize + strlen(hostname)); + ctx->hostname = ctx->cert + certsize; + ctx->certsize = certsize; + memcpy(ctx->cert, cert, certsize); + strcpy(ctx->hostname, hostname); + + if (!l) + M_Menu_Prompt(CertLog_Add_Prompted, ctx, "Server certificate is new", "Trust", NULL, "Disconnect"); + else + M_Menu_Prompt(CertLog_Add_Prompted, ctx, "^1Server certificate HAS CHANGED\nZomg\nFlee in Terror", "ReTrust", NULL, "Disconnect"); + } + return false; //can't connect yet... + } + return true; +} +#endif + void Log_ShutDown(void) { IPLog_Dump("iplog.txt"); @@ -660,4 +825,10 @@ void Log_Init(void) if (COM_CheckParm("-condebug")) Cvar_ForceSet(&log_enable[LOG_CONSOLE], "1"); + +#ifndef SERVERONLY + ClearLink(&certlog); + Cmd_AddCommand("dtls_untrustall", CertLog_UntrustAll_f); + Cmd_AddCommand("dtls_importtrust", CertLog_Import_f); +#endif } diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c index 648fb2ac4..3918eb804 100644 --- a/engine/common/net_ice.c +++ b/engine/common/net_ice.c @@ -467,7 +467,7 @@ static qboolean ICE_SendSpam(struct icestate_s *con) data[2] = ((buf.cursize+4+sizeof(integ)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute data[3] = ((buf.cursize+4+sizeof(integ)-20)>>0)&0xff; //but the hash is to the start of the attribute's header - SHA1_HMAC(integ, sizeof(integ), data, buf.cursize, con->rpwd, strlen(con->rpwd)); + HMAC(SHA1_m, integ, sizeof(integ), data, buf.cursize, con->rpwd, strlen(con->rpwd)); MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY MSG_WriteShort(&buf, BigShort(20)); //sha1 key length SZ_Write(&buf, integ, sizeof(integ)); //integrity data @@ -1369,7 +1369,7 @@ qboolean ICE_WasStun(netsrc_t netsrc) char key[20]; //the hmac is a bit weird. the header length includes the integrity attribute's length, but the checksum doesn't even consider the attribute header. stun->msglen = BigShort(integritypos+sizeof(integrity) - (char*)stun - sizeof(*stun)); - SHA1_HMAC(key, sizeof(key), (qbyte*)stun, integritypos-4 - (char*)stun, con->lpwd, strlen(con->lpwd)); + HMAC(SHA1_m, key, sizeof(key), (qbyte*)stun, integritypos-4 - (char*)stun, con->lpwd, strlen(con->lpwd)); if (memcmp(key, integrity, sizeof(integrity))) { Con_DPrintf("Integrity is bad! needed %x got %x\n", *(int*)key, *(int*)integrity); @@ -1514,7 +1514,7 @@ qboolean ICE_WasStun(netsrc_t netsrc) data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; //but the hash is to the start of the attribute's header - SHA1_HMAC(integrity, sizeof(integrity), data, buf.cursize, con->lpwd, strlen(con->lpwd)); + HMAC(SHA1_m, integrity, sizeof(integrity), data, buf.cursize, con->lpwd, strlen(con->lpwd)); MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data diff --git a/engine/common/net_ssl_gnutls.c b/engine/common/net_ssl_gnutls.c index b22ba89c0..ccbf2a235 100644 --- a/engine/common/net_ssl_gnutls.c +++ b/engine/common/net_ssl_gnutls.c @@ -11,6 +11,8 @@ #endif #ifdef HAVE_GNUTLS +#define privname "privkey.pem" +#define pubname "cert.pem" #if defined(_WIN32) && !defined(MINGW) && 0 @@ -94,36 +96,38 @@ typedef enum typedef int (VARGS gnutls_certificate_verify_function)(gnutls_session_t session); #else -#include -#if GNUTLS_VERSION_MAJOR >= 3 && defined(HAVE_DTLS) - #include -#else - #undef HAVE_DTLS -#endif -#define gnutls_connection_end_t unsigned int + #include + #include + #include + #if GNUTLS_VERSION_MAJOR >= 3 && defined(HAVE_DTLS) + #include + #else + #undef HAVE_DTLS + #endif + #define gnutls_connection_end_t unsigned int - #if GNUTLS_VERSION_MAJOR < 3 || (GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR < 3) - #define GNUTLS_SONUM 26 //cygwin or something. - #endif - #if GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR == 3 - #define GNUTLS_SOPREFIX "-deb0" //not sure what this is about. - #define GNUTLS_SONUM 28 //debian jessie - #endif - #if GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR == 4 - #define GNUTLS_SONUM 30 //ubuntu 16.04 - #endif - #if GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR == 5 - #define GNUTLS_SONUM 30 //debian stretch - #endif - #if GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR > 5 - #define GNUTLS_SONUM 30 //no idea what the future holds. maybe we'll be lucky... - #endif - #ifndef GNUTLS_SONUM - #pragma message "GNUTLS version not recognised. Will probably not be loadable." - #endif - #ifndef GNUTLS_SOPREFIX - #define GNUTLS_SOPREFIX - #endif + #if GNUTLS_VERSION_MAJOR < 3 || (GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR < 3) + #define GNUTLS_SONUM 26 //cygwin or something. + #endif + #if GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR == 3 + #define GNUTLS_SOPREFIX "-deb0" //not sure what this is about. + #define GNUTLS_SONUM 28 //debian jessie + #endif + #if GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR == 4 + #define GNUTLS_SONUM 30 //ubuntu 16.04 + #endif + #if GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR == 5 + #define GNUTLS_SONUM 30 //debian stretch + #endif + #if GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR > 5 + #define GNUTLS_SONUM 30 //no idea what the future holds. maybe we'll be lucky... + #endif + #ifndef GNUTLS_SONUM + #pragma message "GNUTLS version not recognised. Will probably not be loadable." + #endif + #ifndef GNUTLS_SOPREFIX + #define GNUTLS_SOPREFIX + #endif #endif #if GNUTLS_VERSION_MAJOR >= 3 @@ -155,6 +159,7 @@ static ssize_t (VARGS *qgnutls_record_recv)(gnutls_session_t session, void *data static void (VARGS *qgnutls_certificate_set_verify_function)(gnutls_certificate_credentials_t cred, gnutls_certificate_verify_function *func); static void *(VARGS *qgnutls_session_get_ptr)(gnutls_session_t session); static void (VARGS *qgnutls_session_set_ptr)(gnutls_session_t session, void *ptr); +int (VARGS *qgnutls_session_channel_binding)(gnutls_session_t session, gnutls_channel_binding_t cbtype, gnutls_datum_t * cb); #ifdef GNUTLS_HAVE_SYSTEMTRUST static int (VARGS *qgnutls_certificate_set_x509_system_trust)(gnutls_certificate_credentials_t cred); #else @@ -172,6 +177,7 @@ static int (VARGS *qgnutls_x509_crt_import)(gnutls_x509_crt_t cert, const gnutls #endif static const gnutls_datum_t *(VARGS *qgnutls_certificate_get_peers)(gnutls_session_t session, unsigned int * list_size); static gnutls_certificate_type_t (VARGS *qgnutls_certificate_type_get)(gnutls_session_t session); +static void *(VARGS *qgnutls_malloc)(size_t); static void (VARGS *qgnutls_free)(void * ptr); static int (VARGS *qgnutls_server_name_set)(gnutls_session_t session, gnutls_server_name_type_t type, const void * name, size_t name_length); @@ -184,6 +190,27 @@ static void (VARGS *qgnutls_dtls_prestate_set)(gnutls_session_t session, gnutls_ static void (VARGS *qgnutls_dtls_set_mtu)(gnutls_session_t session, unsigned int mtu); #endif +static unsigned int (VARGS *qgnutls_sec_param_to_pk_bits)(gnutls_pk_algorithm_t algo, gnutls_sec_param_t param); +static int (VARGS *qgnutls_x509_crt_init)(gnutls_x509_crt_t * cert); +static void (VARGS *qgnutls_x509_crt_deinit)(gnutls_x509_crt_t cert); +static int (VARGS *qgnutls_x509_crt_set_version)(gnutls_x509_crt_t crt, unsigned int version); +static int (VARGS *qgnutls_x509_crt_set_activation_time)(gnutls_x509_crt_t cert, time_t act_time); +static int (VARGS *qgnutls_x509_crt_set_expiration_time)(gnutls_x509_crt_t cert, time_t exp_time); +static int (VARGS *qgnutls_x509_crt_set_serial)(gnutls_x509_crt_t cert, const void *serial, size_t serial_size); +static int (VARGS *qgnutls_x509_crt_set_dn)(gnutls_x509_crt_t crt, const char *dn, const char **err); +static int (VARGS *qgnutls_x509_crt_set_issuer_dn)(gnutls_x509_crt_t crt, const char *dn, const char **err); +static int (VARGS *qgnutls_x509_crt_set_key)(gnutls_x509_crt_t crt, gnutls_x509_privkey_t key); +static int (VARGS *qgnutls_x509_crt_export2)(gnutls_x509_crt_t cert, gnutls_x509_crt_fmt_t format, gnutls_datum_t * out); +static int (VARGS *qgnutls_x509_privkey_init)(gnutls_x509_privkey_t * key); +static void (VARGS *qgnutls_x509_privkey_deinit)(gnutls_x509_privkey_t key); +static int (VARGS *qgnutls_x509_privkey_generate)(gnutls_x509_privkey_t key, gnutls_pk_algorithm_t algo, unsigned int bits, unsigned int flags); +static int (VARGS *qgnutls_x509_privkey_export2)(gnutls_x509_privkey_t key, gnutls_x509_crt_fmt_t format, gnutls_datum_t * out); +static int (VARGS *qgnutls_x509_crt_privkey_sign)(gnutls_x509_crt_t crt, gnutls_x509_crt_t issuer, gnutls_privkey_t issuer_key, gnutls_digest_algorithm_t dig, unsigned int flags); +static int (VARGS *qgnutls_privkey_init)(gnutls_privkey_t * key); +static void (VARGS *qgnutls_privkey_deinit)(gnutls_privkey_t key); +static int (VARGS *qgnutls_privkey_import_x509)(gnutls_privkey_t pkey, gnutls_x509_privkey_t key, unsigned int flags); +static int (VARGS *qgnutls_certificate_set_x509_key_mem)(gnutls_certificate_credentials_t res, const gnutls_datum_t * cert, const gnutls_datum_t * key, gnutls_x509_crt_fmt_t type); + static qboolean Init_GNUTLS(void) { #ifdef GNUTLS_HAVE_SYSTEMTRUST @@ -217,6 +244,30 @@ static qboolean Init_GNUTLS(void) #endif +#define GNUTLS_X509_STUFF \ + GNUTLS_FUNC(gnutls_sec_param_to_pk_bits) \ + GNUTLS_FUNC(gnutls_x509_crt_init) \ + GNUTLS_FUNC(gnutls_x509_crt_deinit) \ + GNUTLS_FUNC(gnutls_x509_crt_set_version) \ + GNUTLS_FUNC(gnutls_x509_crt_set_activation_time) \ + GNUTLS_FUNC(gnutls_x509_crt_set_expiration_time) \ + GNUTLS_FUNC(gnutls_x509_crt_set_serial) \ + GNUTLS_FUNC(gnutls_x509_crt_set_dn) \ + GNUTLS_FUNC(gnutls_x509_crt_set_issuer_dn) \ + GNUTLS_FUNC(gnutls_x509_crt_set_key) \ + GNUTLS_FUNC(gnutls_x509_crt_export2) \ + GNUTLS_FUNC(gnutls_x509_privkey_init) \ + GNUTLS_FUNC(gnutls_x509_privkey_deinit) \ + GNUTLS_FUNC(gnutls_x509_privkey_generate2) \ + GNUTLS_FUNC(gnutls_x509_privkey_export2) \ + GNUTLS_FUNC(gnutls_x509_crt_privkey_sign) \ + GNUTLS_FUNC(gnutls_privkey_init) \ + GNUTLS_FUNC(gnutls_privkey_deinit) \ + GNUTLS_FUNC(gnutls_privkey_import_x509) \ + GNUTLS_FUNC(gnutls_certificate_set_x509_key_mem) + + + #define GNUTLS_FUNCS \ GNUTLS_FUNC(gnutls_bye) \ GNUTLS_FUNC(gnutls_perror) \ @@ -243,7 +294,8 @@ static qboolean Init_GNUTLS(void) GNUTLS_FUNC(gnutls_certificate_type_get) \ GNUTLS_FUNC(gnutls_free) \ GNUTLS_FUNC(gnutls_server_name_set) \ - GNUTLS_DTLS_STUFF + GNUTLS_DTLS_STUFF \ + GNUTLS_X509_STUFF #ifdef GNUTLS_DYNAMIC dllhandle_t *hmod; @@ -274,6 +326,7 @@ static qboolean Init_GNUTLS(void) {(void**)&qgnutls_certificate_set_verify_function, "gnutls_certificate_set_verify_function"}, {(void**)&qgnutls_session_get_ptr, "gnutls_session_get_ptr"}, {(void**)&qgnutls_session_set_ptr, "gnutls_session_set_ptr"}, + {(void**)&qgnutls_session_channel_binding, "gnutls_session_channel_binding"}, #ifdef GNUTLS_HAVE_SYSTEMTRUST {(void**)&qgnutls_certificate_set_x509_system_trust, "gnutls_certificate_set_x509_system_trust"}, #else @@ -291,6 +344,7 @@ static qboolean Init_GNUTLS(void) #endif {(void**)&qgnutls_certificate_get_peers, "gnutls_certificate_get_peers"}, {(void**)&qgnutls_certificate_type_get, "gnutls_certificate_type_get"}, + {(void**)&qgnutls_malloc, "gnutls_malloc"}, {(void**)&qgnutls_free, "gnutls_free"}, {(void**)&qgnutls_server_name_set, "gnutls_server_name_set"}, @@ -303,6 +357,27 @@ static qboolean Init_GNUTLS(void) {(void**)&qgnutls_dtls_set_mtu, "gnutls_dtls_set_mtu"}, #endif + {(void**)&qgnutls_sec_param_to_pk_bits, "gnutls_sec_param_to_pk_bits"}, + {(void**)&qgnutls_x509_crt_init, "gnutls_x509_crt_init"}, + {(void**)&qgnutls_x509_crt_deinit, "gnutls_x509_crt_deinit"}, + {(void**)&qgnutls_x509_crt_set_version, "gnutls_x509_crt_set_version"}, + {(void**)&qgnutls_x509_crt_set_activation_time, "gnutls_x509_crt_set_activation_time"}, + {(void**)&qgnutls_x509_crt_set_expiration_time, "gnutls_x509_crt_set_expiration_time"}, + {(void**)&qgnutls_x509_crt_set_serial, "gnutls_x509_crt_set_serial"}, + {(void**)&qgnutls_x509_crt_set_dn, "gnutls_x509_crt_set_dn"}, + {(void**)&qgnutls_x509_crt_set_issuer_dn, "gnutls_x509_crt_set_issuer_dn"}, + {(void**)&qgnutls_x509_crt_set_key, "gnutls_x509_crt_set_key"}, + {(void**)&qgnutls_x509_crt_export2, "gnutls_x509_crt_export2"}, + {(void**)&qgnutls_x509_privkey_init, "gnutls_x509_privkey_init"}, + {(void**)&qgnutls_x509_privkey_deinit, "gnutls_x509_privkey_deinit"}, + {(void**)&qgnutls_x509_privkey_generate, "gnutls_x509_privkey_generate"}, + {(void**)&qgnutls_x509_privkey_export2, "gnutls_x509_privkey_export2"}, + {(void**)&qgnutls_x509_crt_privkey_sign, "gnutls_x509_crt_privkey_sign"}, + {(void**)&qgnutls_privkey_init, "gnutls_privkey_init"}, + {(void**)&qgnutls_privkey_deinit, "gnutls_privkey_deinit"}, + {(void**)&qgnutls_privkey_import_x509, "gnutls_privkey_import_x509"}, + {(void**)&qgnutls_certificate_set_x509_key_mem, "gnutls_certificate_set_x509_key_mem"}, + {NULL, NULL} }; @@ -375,6 +450,36 @@ static struct {"triptohell.info", sizeof(triptohell_certdata), triptohell_certdata}, {"fte.triptohell.info", sizeof(fte_triptohell_certdata), fte_triptohell_certdata}, }; + +static int SSL_CheckUserTrust(gnutls_session_t session, gnutlsfile_t *file, int ret) +{ +#ifndef SERVERONLY + //when using dtls, we expect self-signed certs and persistent trust. + if (file->datagram) + { + qbyte *certdata; + size_t certsize; + unsigned int certcount, j; + const gnutls_datum_t *const certlist = qgnutls_certificate_get_peers(session, &certcount); + for (certsize = 0, j = 0; j < certcount; j++) + certsize += certlist[j].size; + certdata = malloc(certsize); + for (certsize = 0, j = 0; j < certcount; j++) + { + memcpy(certdata+certsize, certlist[j].data, certlist[j].size); + certsize += certlist[j].size; + } + if (CertLog_ConnectOkay(file->certname, certdata, certsize)) + ret = 0; //user has previously authorised it. + else + ret = GNUTLS_E_CERTIFICATE_ERROR; //user didn't trust it yet + free(certdata); + } +#endif + + return ret; +} + static int QDECL SSL_CheckCert(gnutls_session_t session) { gnutlsfile_t *file = qgnutls_session_get_ptr (session); @@ -438,6 +543,8 @@ static int QDECL SSL_CheckCert(gnutls_session_t session) if (preverified && (certstatus&~GNUTLS_CERT_EXPIRED) == (GNUTLS_CERT_INVALID|GNUTLS_CERT_SIGNER_NOT_FOUND)) return 0; if (certstatus == 0) + return SSL_CheckUserTrust(session, file, 0); + if (certstatus == (GNUTLS_CERT_INVALID|GNUTLS_CERT_SIGNER_NOT_FOUND) && SSL_CheckUserTrust(session, file, GNUTLS_E_CERTIFICATE_ERROR)) return 0; type = qgnutls_certificate_type_get (session); @@ -463,6 +570,8 @@ static int QDECL SSL_CheckCert(gnutls_session_t session) if (preverified && certstatus == (GNUTLS_CERT_INVALID|GNUTLS_CERT_SIGNER_NOT_FOUND)) return 0; if (certstatus == 0) + return SSL_CheckUserTrust(session, file, 0); + if (certstatus == (GNUTLS_CERT_INVALID|GNUTLS_CERT_SIGNER_NOT_FOUND) && SSL_CheckUserTrust(session, file, GNUTLS_E_CERTIFICATE_ERROR)) return 0; if (certstatus & GNUTLS_CERT_SIGNER_NOT_FOUND) @@ -708,6 +817,137 @@ static gnutls_certificate_credentials_t xcred[2]; static gnutls_datum_t cookie_key; #endif +qboolean SSL_LoadPrivateCert(gnutls_certificate_credentials_t cred) +{ + int ret = -1; + gnutls_datum_t priv, pub; + vfsfile_t *privf = FS_OpenVFS(privname, "rb", FS_ROOT); + vfsfile_t *pubf = FS_OpenVFS(pubname, "rb", FS_ROOT); + + memset(&priv, 0, sizeof(priv)); + memset(&pub, 0, sizeof(pub)); + + if (!privf || !pubf) + { //not found? generate a new one. + //FIXME: how to deal with race conditions with multiple servers on the same host? + //delay till the first connection? we at least write both files at the sameish time. + //even so they might get different certs the first time the server(s) run. + gnutls_x509_privkey_t key; + gnutls_x509_crt_t cert; + char serial[64]; + const char *errstr; + gnutls_pk_algorithm_t privalgo = GNUTLS_PK_RSA; + + if (privf)VFS_CLOSE(privf);privf=NULL; + if (pubf)VFS_CLOSE(pubf);pubf=NULL; + + Con_Printf("Generating new GNUTLS key+cert...\n"); + + qgnutls_x509_privkey_init(&key); + ret = qgnutls_x509_privkey_generate(key, privalgo, qgnutls_sec_param_to_pk_bits(privalgo, GNUTLS_SEC_PARAM_HIGH), 0); + if (ret < 0) + Con_Printf("gnutls_x509_privkey_generate2 failed: %i\n", ret); + ret = qgnutls_x509_privkey_export2(key, GNUTLS_X509_FMT_PEM, &priv); + if (ret < 0) + Con_Printf("gnutls_x509_privkey_export2 failed: %i\n", ret); + + //stoopid browsers insisting that serial numbers are different even on throw-away self-signed certs. + //we should probably just go and make our own root ca/master. post it a cert and get a signed one (with sequential serial) back or something. + //we'll probably want something like that for client certs anyway, for stat tracking. + Q_snprintfz(serial, sizeof(serial), "%u", (unsigned)time(NULL)); + + qgnutls_x509_crt_init(&cert); + qgnutls_x509_crt_set_version(cert, 1); + qgnutls_x509_crt_set_activation_time(cert, time(NULL)-1); + qgnutls_x509_crt_set_expiration_time(cert, time(NULL)+(time_t)10*365*24*60*60); + qgnutls_x509_crt_set_serial(cert, serial, strlen(serial)); + if (qgnutls_x509_crt_set_dn(cert, "CN=localhost", &errstr) < 0) + Con_Printf("gnutls_x509_crt_set_dn failed: %s\n", errstr); + if (qgnutls_x509_crt_set_issuer_dn(cert, "CN=localhost", &errstr) < 0) + Con_Printf("gnutls_x509_crt_set_issuer_dn failed: %s\n", errstr); +// qgnutls_x509_crt_set_key_usage(cert, GNUTLS_KEY_KEY_ENCIPHERMENT|GNUTLS_KEY_DATA_ENCIPHERMENT|); + qgnutls_x509_crt_set_key(cert, key); + + /*sign it with our private key*/ + { + gnutls_privkey_t akey; + qgnutls_privkey_init(&akey); + qgnutls_privkey_import_x509(akey, key, GNUTLS_PRIVKEY_IMPORT_COPY); + ret = qgnutls_x509_crt_privkey_sign(cert, cert, akey, GNUTLS_DIG_SHA256, 0); + if (ret < 0) + Con_Printf("gnutls_x509_crt_privkey_sign failed: %i\n", ret); + qgnutls_privkey_deinit(akey); + } + ret = qgnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_PEM, &pub); + if (ret < 0) + Con_Printf("gnutls_x509_crt_export2 failed: %i\n", ret); + qgnutls_x509_crt_deinit(cert); + qgnutls_x509_privkey_deinit(key); + + if (priv.size && pub.size) + { + privf = FS_OpenVFS(privname, "wb", FS_ROOT); + if (privf) + { + Con_Printf("Wrote %s\n", privname); + VFS_WRITE(privf, priv.data, priv.size); + VFS_CLOSE(privf); + } + memset(priv.data, 0, priv.size); + qgnutls_free(priv.data); + memset(&priv, 0, sizeof(priv)); + + pubf = FS_OpenVFS(pubname, "wb", FS_ROOT); + if (pubf) + { + Con_Printf("Wrote %s\n", pubname); + VFS_WRITE(pubf, pub.data, pub.size); + VFS_CLOSE(pubf); + } + qgnutls_free(pub.data); + memset(&pub, 0, sizeof(pub)); + + privf = FS_OpenVFS(privname, "rb", FS_ROOT); + pubf = FS_OpenVFS(pubname, "rb", FS_ROOT); + + Con_Printf("Certificate generated\n"); + } + } + + if (privf && pubf) + { + //read the two files now + priv.size = VFS_GETLEN(privf); + priv.data = qgnutls_malloc(priv.size+1); + if (priv.size != VFS_READ(privf, priv.data, priv.size)) + priv.size = 0; + priv.data[priv.size] = 0; + + pub.size = VFS_GETLEN(pubf); + pub.data = qgnutls_malloc(pub.size+1); + if (pub.size != VFS_READ(pubf, pub.data, pub.size)) + pub.size = 0; + pub.data[pub.size] = 0; + } + + //FIXME: extend the expiration time if its old? + + if (priv.size && pub.size) + { //submit them to gnutls + ret = qgnutls_certificate_set_x509_key_mem(cred, &pub, &priv, GNUTLS_X509_FMT_PEM); + if (ret < 0) + Con_Printf("gnutls_certificate_set_x509_key_mem failed: %i\n", ret); + } + else + Con_Printf("Unable to read/generate cert\n"); + + memset(priv.data, 0, priv.size);//just in case. FIXME: we didn't scrub the filesystem code. libc has its own caches etc. lets hope that noone comes up with some way to scrape memory remotely (although if they can inject code then we've lost either way so w/e) + qgnutls_free(priv.data); + qgnutls_free(pub.data); + + return ret>=0; +} + qboolean SSL_InitGlobal(qboolean isserver) { static int initstatus[2]; @@ -742,6 +982,10 @@ qboolean SSL_InitGlobal(qboolean isserver) if (isserver) { +#if 1 + if (!SSL_LoadPrivateCert(xcred[isserver])) + initstatus[isserver] = -1; +#else int ret = -1; char keyfile[MAX_OSPATH]; char certfile[MAX_OSPATH]; @@ -754,6 +998,7 @@ qboolean SSL_InitGlobal(qboolean isserver) Con_Printf("No certificate or key was found in %s and %s\n", certfile, keyfile); initstatus[isserver] = -1; } +#endif } else qgnutls_certificate_set_verify_function (xcred[isserver], SSL_CheckCert); @@ -846,6 +1091,28 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean isserver return &newf->funcs; } +int TLS_GetChannelBinding(vfsfile_t *vf, qbyte *binddata, size_t *bindsize) +{ + gnutls_datum_t cb; + gnutlsfile_t *f = (gnutlsfile_t*)vf; + if (vf->Close != SSL_Close) + return -1; //err, not a tls connection. + + if (qgnutls_session_channel_binding(f->session, GNUTLS_CB_TLS_UNIQUE, &cb)) + { //error of some kind + //if the error is because of the other side not supporting it, then we should return 0 here. + return -1; + } + else + { + if (cb.size > *bindsize) + return 0; //overflow + *bindsize = cb.size; + memcpy(binddata, cb.data, cb.size); + return 1; + } +} + #ifdef HAVE_DTLS @@ -1017,13 +1284,13 @@ static const dtlsfuncs_t dtlsfuncs_gnutls = GNUDTLS_Received, GNUDTLS_Timeouts, }; -const dtlsfuncs_t *DTLS_InitServer(void) +const dtlsfuncs_t *GNUDTLS_InitServer(void) { if (!SSL_InitGlobal(true)) return NULL; //unable to init a server certificate. don't allow dtls to init. return &dtlsfuncs_gnutls; } -const dtlsfuncs_t *DTLS_InitClient(void) +const dtlsfuncs_t *GNUDTLS_InitClient(void) { return &dtlsfuncs_gnutls; } diff --git a/engine/common/net_ssl_winsspi.c b/engine/common/net_ssl_winsspi.c index 7e1e645b8..73402c366 100644 --- a/engine/common/net_ssl_winsspi.c +++ b/engine/common/net_ssl_winsspi.c @@ -1,584 +1,588 @@ -#include "quakedef.h" -#if defined(HAVE_WINSSPI) - -/*regarding HAVE_DTLS -DTLS1.0 is supported from win8 onwards -Its also meant to be supported from some RDP server patch on win7, but I can't get it to work. -I've given up for now. -*/ - -cvar_t *tls_ignorecertificateerrors; - -#include "winquake.h" -#define SECURITY_WIN32 -#include -#include -#include - -#define SP_PROT_TLS1_1_SERVER 0x00000100 -#define SP_PROT_TLS1_1_CLIENT 0x00000200 - -#define SP_PROT_TLS1_2_SERVER 0x00000400 -#define SP_PROT_TLS1_2_CLIENT 0x00000800 - -#define SP_PROT_DTLS_SERVER 0x00010000 -#define SP_PROT_DTLS_CLIENT 0x00020000 - -//avoid the use of outdated/insecure protocols -//so no ssl2/ssl3 -#define USE_PROT_SERVER (SP_PROT_TLS1_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER) -#define USE_PROT_CLIENT (SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT) - -#define USE_PROT_DGRAM_SERVER (SP_PROT_DTLS_SERVER) -#define USE_PROT_DGRAM_CLIENT (SP_PROT_DTLS_CLIENT) - -#ifndef szOID_RSA_SHA512RSA -#define szOID_RSA_SHA512RSA "1.2.840.113549.1.1.13" -#endif -#ifndef SCH_CRED_SNI_CREDENTIAL -#define SCH_CRED_SNI_CREDENTIAL 0x00080000 -#endif - -#define SEC_I_MESSAGE_FRAGMENT 0x00090364L -#define SEC_E_INVALID_PARAMETER 0x8009035DL - - -//hungarian ensures we hit no macros. -static struct -{ - dllhandle_t *lib; - SECURITY_STATUS (WINAPI *pDecryptMessage) (PCtxtHandle,PSecBufferDesc,ULONG,PULONG); - SECURITY_STATUS (WINAPI *pEncryptMessage) (PCtxtHandle,ULONG,PSecBufferDesc,ULONG); - SECURITY_STATUS (WINAPI *pAcquireCredentialsHandleA) (SEC_CHAR*,SEC_CHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp); -// SECURITY_STATUS (WINAPI *pInitializeSecurityContextA) (PCredHandle,PCtxtHandle,SEC_CHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); - SECURITY_STATUS (WINAPI *pInitializeSecurityContextW) (PCredHandle,PCtxtHandle,SEC_WCHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); - SECURITY_STATUS (WINAPI *pAcceptSecurityContext) (PCredHandle,PCtxtHandle,PSecBufferDesc,unsigned long,unsigned long,PCtxtHandle,PSecBufferDesc,unsigned long SEC_FAR *,PTimeStamp); - SECURITY_STATUS (WINAPI *pCompleteAuthToken) (PCtxtHandle,PSecBufferDesc); - SECURITY_STATUS (WINAPI *pQueryContextAttributesA) (PCtxtHandle,ULONG,PVOID); - SECURITY_STATUS (WINAPI *pFreeCredentialsHandle) (PCredHandle); - SECURITY_STATUS (WINAPI *pDeleteSecurityContext) (PCtxtHandle); -} secur; -static struct -{ - dllhandle_t *lib; - BOOL (WINAPI *pCertGetCertificateChain) (HCERTCHAINENGINE,PCCERT_CONTEXT,LPFILETIME,HCERTSTORE,PCERT_CHAIN_PARA,DWORD,LPVOID,PCCERT_CHAIN_CONTEXT*); - BOOL (WINAPI *pCertVerifyCertificateChainPolicy) (LPCSTR,PCCERT_CHAIN_CONTEXT,PCERT_CHAIN_POLICY_PARA,PCERT_CHAIN_POLICY_STATUS); - void (WINAPI *pCertFreeCertificateChain) (PCCERT_CHAIN_CONTEXT); - DWORD (WINAPI *pCertNameToStrA) (DWORD dwCertEncodingType, PCERT_NAME_BLOB pName, DWORD dwStrType, LPCSTR psz, DWORD csz); +#include "quakedef.h" +#if defined(HAVE_WINSSPI) - PCCERT_CONTEXT (WINAPI *pCertCreateSelfSignCertificate) (HCRYPTPROV,PCERT_NAME_BLOB,DWORD,PCRYPT_KEY_PROV_INFO,PCRYPT_ALGORITHM_IDENTIFIER,PSYSTEMTIME,PSYSTEMTIME,PCERT_EXTENSIONS); - BOOL (WINAPI *pCertStrToNameA) (DWORD,LPCSTR,DWORD,void *,BYTE *,DWORD *,LPCSTR *); -} crypt; -void SSL_Init(void) -{ - dllfunction_t secur_functable[] = - { - {(void**)&secur.pDecryptMessage, "DecryptMessage"}, - {(void**)&secur.pEncryptMessage, "EncryptMessage"}, - {(void**)&secur.pAcquireCredentialsHandleA, "AcquireCredentialsHandleA"}, -// {(void**)&secur.pInitializeSecurityContextA, "InitializeSecurityContextA"}, - {(void**)&secur.pInitializeSecurityContextW, "InitializeSecurityContextW"}, - {(void**)&secur.pAcceptSecurityContext, "AcceptSecurityContext"}, - {(void**)&secur.pCompleteAuthToken, "CompleteAuthToken"}, - {(void**)&secur.pQueryContextAttributesA, "QueryContextAttributesA"}, - {(void**)&secur.pFreeCredentialsHandle, "FreeCredentialsHandle"}, - {(void**)&secur.pDeleteSecurityContext, "DeleteSecurityContext"}, - {NULL, NULL} - }; - - dllfunction_t crypt_functable[] = - { - {(void**)&crypt.pCertGetCertificateChain, "CertGetCertificateChain"}, - {(void**)&crypt.pCertVerifyCertificateChainPolicy, "CertVerifyCertificateChainPolicy"}, - {(void**)&crypt.pCertFreeCertificateChain, "CertFreeCertificateChain"}, - {(void**)&crypt.pCertNameToStrA, "CertNameToStrA"}, - {(void**)&crypt.pCertCreateSelfSignCertificate, "CertCreateSelfSignCertificate"}, - {(void**)&crypt.pCertStrToNameA, "CertStrToNameA"}, - {NULL, NULL} - }; - - tls_ignorecertificateerrors = Cvar_Get("tls_ignorecertificateerrors", "0", CVAR_NOTFROMSERVER, "TLS"); - - if (!secur.lib) - secur.lib = Sys_LoadLibrary("secur32.dll", secur_functable); - if (!crypt.lib) - crypt.lib = Sys_LoadLibrary("crypt32.dll", crypt_functable); -} -qboolean SSL_Inited(void) -{ - return !!secur.lib && !!crypt.lib; -} - -#define MessageAttribute (ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_MANUAL_CRED_VALIDATION) - -struct sslbuf -{ - size_t datasize; - char *data; - size_t avail; -}; - -typedef struct { - vfsfile_t funcs; - vfsfile_t *stream; - - wchar_t wpeername[256]; - qboolean datagram; - enum - { - HS_ESTABLISHED, - - HS_ERROR, - - HS_STARTCLIENT, - HS_CLIENT, - - HS_STARTSERVER, - HS_SERVER - } handshaking; - - struct sslbuf outraw; - struct sslbuf outcrypt; - struct sslbuf inraw; - struct sslbuf incrypt; - - - CredHandle cred; - SecHandle sechnd; - int headersize, footersize; - char headerdata[1024], footerdata[1024]; - -#ifdef HAVE_DTLS - void *cbctx; - void (*transmit)(void *cbctx, qbyte *data, size_t datasize); -#endif -} sslfile_t; - -static int SSPI_ExpandBuffer(struct sslbuf *buf, size_t bytes) -{ - if (bytes < buf->datasize) - return buf->datasize; - Z_ReallocElements((void**)&buf->data, &buf->datasize, bytes, 1); - return bytes; -} - -static int SSPI_CopyIntoBuffer(struct sslbuf *buf, const void *data, unsigned int bytes, qboolean expand) -{ - if (bytes > buf->datasize - buf->avail) - { - if (!expand || SSPI_ExpandBuffer(buf, buf->avail + bytes + 1024) < buf->avail + bytes) - bytes = buf->datasize - buf->avail; - } - memcpy(buf->data + buf->avail, data, bytes); - buf->avail += bytes; - - return bytes; -} - -static void SSPI_Error(sslfile_t *f, char *error, ...) -{ - va_list argptr; - char string[1024]; - va_start (argptr, error); - vsnprintf (string,sizeof(string)-1, error,argptr); - va_end (argptr); - - f->handshaking = HS_ERROR; - if (*string) - Sys_Printf("%s", string); - if (f->stream) - VFS_CLOSE(f->stream); - - secur.pDeleteSecurityContext(&f->sechnd); - secur.pFreeCredentialsHandle(&f->cred); - f->stream = NULL; -} - -static void SSPI_TryFlushCryptOut(sslfile_t *f) -{ - int sent; - if (f->outcrypt.avail) - { -#ifdef HAVE_DTLS - if (f->transmit) - { - f->transmit(f->cbctx, f->outcrypt.data, f->outcrypt.avail); - f->outcrypt.avail = 0; - return; - } -#endif - sent = VFS_WRITE(f->stream, f->outcrypt.data, f->outcrypt.avail); - } - else - return; - - if (sent > 0) - { - memmove(f->outcrypt.data, f->outcrypt.data + sent, f->outcrypt.avail - sent); - f->outcrypt.avail -= sent; - } -} - -static int SSPI_CheckNewInCrypt(sslfile_t *f) -{ - int newd; - if (!f->stream) - return -1; - newd = VFS_READ(f->stream, f->incrypt.data+f->incrypt.avail, f->incrypt.datasize - f->incrypt.avail); - if (newd < 0) - return newd; - else - f->incrypt.avail += newd; - - return 0; -} - -//convert inbound crypt->data -static void SSPI_Decode(sslfile_t *f) -{ - SECURITY_STATUS ss; - SecBufferDesc BuffDesc; - SecBuffer SecBuff[4]; - ULONG ulQop = 0; - SecBuffer *extra = NULL; - int i; - - if (!f->incrypt.avail) - return; - - BuffDesc.ulVersion = SECBUFFER_VERSION; - BuffDesc.cBuffers = countof(SecBuff); - BuffDesc.pBuffers = SecBuff; - - SecBuff[0].BufferType = SECBUFFER_DATA; - SecBuff[0].cbBuffer = f->incrypt.avail; - SecBuff[0].pvBuffer = f->incrypt.data; - - SecBuff[1].BufferType = SECBUFFER_EMPTY; //space for header - SecBuff[2].BufferType = SECBUFFER_EMPTY; //space for footer - SecBuff[3].BufferType = SECBUFFER_EMPTY; //space for extra marker - - ss = secur.pDecryptMessage(&f->sechnd, &BuffDesc, 0, &ulQop); - - if (ss < 0) - { - if (ss == SEC_E_INCOMPLETE_MESSAGE) - { - if (f->incrypt.avail == f->incrypt.datasize) - SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024); - return; //no error if its incomplete, we can just get more data later on. - } - switch(ss) - { - case SEC_E_DECRYPT_FAILURE: SSPI_Error(f, "DecryptMessage failed: SEC_E_DECRYPT_FAILURE\n", ss); break; - case SEC_E_INVALID_HANDLE: SSPI_Error(f, "DecryptMessage failed: SEC_E_INVALID_HANDLE\n"); break; - default: SSPI_Error(f, "DecryptMessage failed: %0#lx\n", ss); break; - } - return; - } - - for (i = 0; i < BuffDesc.cBuffers; i++) - { - switch(SecBuff[i].BufferType) - { - case SECBUFFER_DATA: - if (SSPI_CopyIntoBuffer(&f->inraw, SecBuff[i].pvBuffer, SecBuff[i].cbBuffer, true) != SecBuff[i].cbBuffer) - SSPI_Error(f, "outraw buffer overflowed\n"); - break; - case SECBUFFER_EXTRA: - if (extra) - SSPI_Error(f, "multiple extra buffers\n"); - extra = &SecBuff[i]; - break; - case SECBUFFER_EMPTY: - case SECBUFFER_MISSING: - case SECBUFFER_STREAM_TRAILER: - case SECBUFFER_STREAM_HEADER: - break; - default: - SSPI_Error(f, "got unexpected buffer type\n"); - break; - } - } - - //retain the extra. if there's no extra then mark it so. - if (extra) - { - memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - extra->cbBuffer), extra->cbBuffer); - f->incrypt.avail = extra->cbBuffer; - } - else - f->incrypt.avail = 0; -} - -//convert outgoing data->crypt -static void SSPI_Encode(sslfile_t *f) -{ - SECURITY_STATUS ss; - SecBufferDesc BuffDesc; - SecBuffer SecBuff[4]; - ULONG ulQop = 0; - - if (f->outcrypt.avail) - { - SSPI_TryFlushCryptOut(f); - if (f->outcrypt.avail) - return; //don't flood too much - } - - - //don't corrupt the handshake data. - if (f->handshaking) - return; - - if (!f->outraw.avail) - return; - - BuffDesc.ulVersion = SECBUFFER_VERSION; - BuffDesc.cBuffers = 4; - BuffDesc.pBuffers = SecBuff; - - SecBuff[0].BufferType = SECBUFFER_STREAM_HEADER; - SecBuff[0].cbBuffer = f->headersize; - SecBuff[0].pvBuffer = f->headerdata; - - SecBuff[1].BufferType = SECBUFFER_DATA; - SecBuff[1].cbBuffer = f->outraw.avail; - SecBuff[1].pvBuffer = f->outraw.data; - - SecBuff[2].BufferType = SECBUFFER_STREAM_TRAILER; - SecBuff[2].cbBuffer = f->footersize; - SecBuff[2].pvBuffer = f->footerdata; - - SecBuff[3].BufferType = SECBUFFER_EMPTY; - SecBuff[3].cbBuffer = 0; - SecBuff[3].pvBuffer = NULL; - - ss = secur.pEncryptMessage(&f->sechnd, ulQop, &BuffDesc, 0); - - if (ss < 0) - { - SSPI_Error(f, "EncryptMessage failed\n"); - return; - } - - f->outraw.avail = 0; - - //fixme: these should be made non-fatal. - if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[0].pvBuffer, SecBuff[0].cbBuffer, true) < SecBuff[0].cbBuffer) - { - SSPI_Error(f, "crypt buffer overflowed\n"); - return; - } - if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[1].pvBuffer, SecBuff[1].cbBuffer, true) < SecBuff[1].cbBuffer) - { - SSPI_Error(f, "crypt buffer overflowed\n"); - return; - } - if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[2].pvBuffer, SecBuff[2].cbBuffer, true) < SecBuff[2].cbBuffer) - { - SSPI_Error(f, "crypt buffer overflowed\n"); - return; - } - - SSPI_TryFlushCryptOut(f); -} - -//these are known sites that use self-signed certificates, or are special enough that we don't trust corporate networks to hack in their own certificate authority for a proxy/mitm -//old static const qbyte triptohell_certdata[933] = "\x30\x82\x03\xa1\x30\x82\x02\x89\xa0\x03\x02\x01\x02\x02\x09\x00\x8b\xd0\x05\x63\x62\xd1\x6a\xe3\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x67\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x42\x44\x31\x0c\x30\x0a\x06\x03\x55\x04\x08\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x07\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x0a\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x0b\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x03\x0c\x03\x42\x61\x64\x31\x12\x30\x10\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x01\x16\x03\x42\x61\x64\x30\x1e\x17\x0d\x31\x34\x31\x32\x32\x34\x32\x32\x34\x32\x34\x37\x5a\x17\x0d\x32\x34\x31\x32\x32\x31\x32\x32\x34\x32\x34\x37\x5a\x30\x67\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x42\x44\x31\x0c\x30\x0a\x06\x03\x55\x04\x08\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x07\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x0a\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x0b\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x03\x0c\x03\x42\x61\x64\x31\x12\x30\x10\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x01\x16\x03\x42\x61\x64\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xaf\x10\x33\xfa\x39\xf5\xae\x2c\x91\x0e\x20\xe6\x3c\x5c\x7c\x1e\xeb\x16\x50\x2f\x05\x30\xfe\x67\xee\xa9\x00\x54\xd9\x4a\x86\xe6\xba\x80\xfb\x1a\x80\x08\x7e\x7b\x13\xe5\x1a\x18\xc9\xd4\x70\xbd\x5d\xc4\x38\xef\x64\xf1\x90\x2c\x53\x49\x93\x24\x36\x3e\x11\x59\x69\xa6\xdf\x37\xb2\x54\x82\x28\x3e\xdd\x30\x75\xa0\x18\xd8\xe1\xf5\x52\x73\x12\x5b\x37\x68\x1c\x59\xbd\x8c\x73\x66\x47\xbc\xcb\x9c\xfe\x38\x92\x8f\x74\xe9\xd1\x2f\x96\xd2\x5d\x6d\x11\x59\xb2\xdc\xbd\x8c\x37\x5b\x22\x76\x98\xe7\xbe\x08\xef\x1e\x99\xc4\xa9\x77\x2c\x9c\x0e\x08\x3c\x8e\xab\x97\x0c\x6a\xd7\x03\xab\xfd\x4a\x1e\x95\xb2\xc2\x9c\x3a\x16\x65\xd7\xaf\x45\x5f\x6e\xe7\xce\x51\xba\xa0\x60\x43\x0e\x07\xc5\x0b\x0a\x82\x05\x26\xc4\x92\x0a\x27\x5b\xfc\x57\x6c\xdf\xe2\x54\x8a\xef\x38\xf1\xf8\xc4\xf8\x51\x16\x27\x1f\x78\x89\x7c\x5b\xd7\x53\xcd\x9b\x54\x2a\xe6\x71\xee\xe4\x56\x2e\xa4\x09\x1a\x61\xf7\x0f\x97\x22\x94\xd7\xef\x21\x6c\xe6\x81\xfb\x54\x5f\x09\x92\xac\xd2\x7c\xab\xd5\xa9\x81\xf4\xc9\xb7\xd6\xbf\x68\xf8\x4f\xdc\xf3\x60\xa3\x3b\x29\x92\x9e\xdd\xa2\xa3\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x19\xed\xd0\x7b\x16\xaf\xb5\x0c\x9a\xe8\xd3\x46\x2e\x3c\x64\x29\xb6\xc1\x73\x5a\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x19\xed\xd0\x7b\x16\xaf\xb5\x0c\x9a\xe8\xd3\x46\x2e\x3c\x64\x29\xb6\xc1\x73\x5a\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x82\x01\x01\x00\x62\xa7\x26\xeb\xd4\x03\x29\x9c\x09\x33\x69\x7a\x9c\x65\x68\xec\x4c\xb9\x06\xeb\x1e\x51\x6f\x78\x20\xdc\xf6\x44\x5e\x06\x6e\x53\x87\x73\xe6\x14\x15\xb9\x17\x74\x67\xe0\x4e\x48\x38\xbc\x1c\xbd\xd0\xad\xd6\xbd\x8c\xf0\x3a\xe0\x13\x73\x19\xad\x8b\x79\x68\x67\x65\x9b\x7a\x4c\x81\xfb\xd9\x92\x77\x89\xb5\xb0\x53\xb0\xa5\xf7\x2d\x8e\x29\x60\x31\xd1\x9b\x2f\x63\x8a\x5f\x64\xc1\x61\xd5\xb7\xdf\x70\x3b\x2b\xf6\x1a\x96\xb9\xa7\x08\xca\x87\xa6\x8c\x60\xca\x6e\xd7\xee\xba\xef\x89\x0b\x93\xd5\xfd\xfc\x14\xba\xef\x27\xba\x90\x11\x90\xf7\x25\x70\xe7\x4e\xf4\x9c\x13\x27\xc1\xa7\x8e\xd9\x66\x43\x72\x20\x5b\xe1\x5c\x73\x74\xf5\x33\xf2\xa5\xf6\xe1\xd5\xac\xf3\x67\x5c\xe7\xd4\x0a\x8d\x91\x73\x03\x3e\x9d\xbc\x96\xc3\x0c\xdb\xd5\x77\x6e\x76\x44\x69\xaf\x24\x0f\x4f\x8b\x47\x36\x8b\xc3\xd6\x36\xdd\x26\x5a\x9c\xdd\x9c\x43\xee\x29\x43\xdd\x75\x2f\x19\x52\xfc\x1d\x24\x9c\x13\x29\x99\xa0\x6d\x7a\x95\xcc\xa0\x58\x86\xd8\xc5\xb9\xa3\xc2\x3d\x64\x1d\x85\x8a\xca\x53\x55\x8e\x9a\x6d\xc9\x91\x73\xf4\xe1\xe1\xa4\x9b\x76\xfc\x7f\x63\xc2\xb9\x23"; -static const qbyte fte_triptohell_certdata[917] = "\x30\x82\x03\x91\x30\x82\x02\x79\xa0\x03\x02\x01\x02\x02\x09\x00\xb5\x71\x47\x8d\x5e\x66\xf1\xd9\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x5f\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x11\x30\x0f\x06\x03\x55\x04\x08\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x11\x30\x0f\x06\x03\x55\x04\x07\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x0c\x30\x0a\x06\x03\x55\x04\x0a\x0c\x03\x46\x54\x45\x31\x1c\x30\x1a\x06\x03\x55\x04\x03\x0c\x13\x66\x74\x65\x2e\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x30\x1e\x17\x0d\x31\x34\x31\x32\x32\x35\x30\x30\x35\x38\x31\x34\x5a\x17\x0d\x31\x37\x30\x33\x30\x34\x30\x30\x35\x38\x31\x34\x5a\x30\x5f\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x11\x30\x0f\x06\x03\x55\x04\x08\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x11\x30\x0f\x06\x03\x55\x04\x07\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x0c\x30\x0a\x06\x03\x55\x04\x0a\x0c\x03\x46\x54\x45\x31\x1c\x30\x1a\x06\x03\x55\x04\x03\x0c\x13\x66\x74\x65\x2e\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xdd\xb8\x7c\x69\x3d\x63\x95\xe3\x88\x15\xfd\xad\x93\x5e\x6b\x97\xfb\x74\xba\x1f\x83\x33\xe5\x8a\x8d\x8f\xb0\xbf\xf9\xd3\xa1\x2c\x65\x53\xa7\xef\xd3\x0f\xdc\x03\x60\x0a\x40\xef\xa8\xef\x3f\xb3\xd9\x8d\x31\x39\x12\x8a\xd8\x0e\x24\x8f\xe5\x58\x26\x86\x4c\x76\x6c\x59\x9a\xab\xea\x1c\x3d\xfb\x62\x62\xad\xaf\xd6\x00\x33\x76\x2d\xbb\xeb\xe8\xec\xb4\x76\x4f\xb0\xbe\xcf\xf0\x46\x94\x40\x02\x99\xd4\xb2\x71\x71\xd6\xf5\x1f\xc3\x4f\x1e\x1e\xb4\x0d\x82\x49\xc4\xa2\xdc\xae\x6f\x4e\x3a\xf9\x0e\xdd\xf4\xd2\x53\xe3\xe7\x7d\x58\x79\xf4\xce\x1f\x6c\xac\x81\x8c\x8c\xe1\x03\x5b\x22\x56\x92\x19\x4f\x74\xc0\x36\x41\xac\x1b\xfa\x9e\xf7\x2a\x0f\xd6\x4b\xcc\x9a\xca\x67\x87\xb7\x95\xdf\xb7\xd4\x7d\x8c\xcc\xa9\x25\xde\xdd\x8c\x1b\xd7\x32\xf2\x84\x25\x46\x7b\x10\x55\xf9\x80\xfd\x5d\xad\xab\xf9\x4c\x1f\xc0\xa5\xd1\x3f\x01\x86\x4d\xfa\x57\xab\x7a\x6d\xec\xf1\xdb\xf4\xad\xf2\x33\xcd\xa0\xed\xfe\x1b\x27\x55\x56\xba\x8c\x47\x70\x16\xd5\x75\x17\x8e\x80\xaa\x49\x5e\x93\x83\x1d\x6f\x1f\x2c\xf7\xa7\x64\xe6\x2e\x88\x8e\xff\x70\x5a\x41\x52\xae\x93\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x4e\x76\x4a\xce\x7b\x45\x14\x39\xeb\x9c\x28\x56\xb5\x7b\x8a\x18\x6f\x22\x17\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x4e\x76\x4a\xce\x7b\x45\x14\x39\xeb\x9c\x28\x56\xb5\x7b\x8a\x18\x6f\x22\x17\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x48\x22\x65\xed\x2e\xc5\xed\xbb\xe9\x40\x6c\x80\xc4\x63\x19\xd1\x00\xb4\x30\x34\x17\x7c\x7c\xbd\x1b\xc5\xa9\x43\x0c\x92\x6e\xd6\x2d\x11\x6c\x0d\xa6\xda\x30\xe9\xf7\x46\x7b\x01\xe4\x53\x23\xae\x88\xd1\xf2\xed\xca\x84\x06\x19\x97\xb9\x06\xfb\xda\xec\x72\x2d\x15\x20\xd2\x8f\x66\xad\xb5\xdd\x4b\x4f\xdf\x7e\xaf\xa3\x6c\x7f\x53\x32\x8f\xe2\x19\x5c\x44\x98\x86\x31\xee\xb4\x03\xe7\x27\xa1\x83\xab\xc3\xce\xb4\x9a\x01\xbe\x8c\x64\x2e\x2b\xe3\x4e\x55\xdf\x95\xeb\x16\x87\xbd\xfa\x11\xa2\x3e\x38\x92\x97\x36\xe9\x65\x60\xf3\xac\x68\x44\xb3\x51\x54\x3a\x42\xa8\x98\x9b\xee\x1b\x9e\x79\x6a\xaf\xc0\xbe\x41\xc4\xb1\x96\x42\xd9\x94\xef\x49\x5b\xbe\x2d\x04\xb9\xfb\x92\xbb\xdc\x0e\x29\xfd\xee\xa9\x68\x09\xf9\x9f\x69\x8b\x3d\xe1\x4b\xee\x24\xf9\xfe\x02\x3a\x0a\xb8\xcd\x6c\x07\x43\xa9\x4a\xe7\x03\x34\x2e\x72\xa7\x81\xaa\x40\xa9\x98\x5d\x97\xee\x2a\x99\xc6\x8f\xe8\x6f\x98\xa2\x85\xc9\x0d\x04\x19\x43\x6a\xd3\xc7\x15\x4c\x4b\xbc\xa5\xb8\x9f\x38\xf3\x43\x83\x0c\xef\x97\x6e\xa6\x20\xde\xc5\xd3\x1e\x3e\x5d\xcd\x58\x3d\x5c\x55\x7a\x90\x94"; -static const qbyte triptohell_certdata[933] = "\x30\x82\x03\xa1\x30\x82\x02\x89\xa0\x03\x02\x01\x02\x02\x09\x00\xea\xb7\x13\xcf\x55\xe5\xe8\x8c\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x67\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x11\x30\x0f\x06\x03\x55\x04\x08\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x11\x30\x0f\x06\x03\x55\x04\x07\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x18\x30\x16\x06\x03\x55\x04\x0a\x0c\x0f\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x31\x18\x30\x16\x06\x03\x55\x04\x03\x0c\x0f\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x30\x1e\x17\x0d\x31\x34\x31\x32\x32\x35\x30\x30\x35\x38\x33\x37\x5a\x17\x0d\x31\x37\x30\x33\x30\x34\x30\x30\x35\x38\x33\x37\x5a\x30\x67\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x11\x30\x0f\x06\x03\x55\x04\x08\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x11\x30\x0f\x06\x03\x55\x04\x07\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x18\x30\x16\x06\x03\x55\x04\x0a\x0c\x0f\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x31\x18\x30\x16\x06\x03\x55\x04\x03\x0c\x0f\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xd8\x77\x62\xf6\x74\xa7\x75\xde\xda\x09\xae\x9e\x76\x7a\xc6\x2a\xcf\x9a\xbe\xc6\xb9\x6d\xe2\xca\x0f\x2d\x95\xb8\x89\x93\xf7\x50\x64\x92\x7d\x95\x34\xe4\x6e\xef\x52\x56\xef\x13\x9a\x3a\xae\x84\x5b\x57\x82\x04\x86\x74\xbd\x4e\x38\x32\x56\x00\xd6\x34\x9c\x23\xd6\x81\x8e\x29\x77\x45\x61\x20\xdf\x28\xf8\xe5\x61\x83\xec\xe6\xa0\x1a\x75\xa8\x3b\x53\x6f\xc4\x09\x61\x66\x3a\xf0\x81\xbf\x2c\xf5\x8e\xf1\xe2\x35\xe4\x24\x7f\x16\xcc\xce\x60\xa2\x42\x6e\xc2\x3a\x29\x75\x6c\x79\xb0\x99\x9c\xe2\xfe\x27\x32\xb6\xf7\x0d\x71\xfd\x62\x9d\x54\x7c\x40\xb2\xf5\xa0\xa4\x25\x31\x8d\x65\xfd\x3f\x3b\x9b\x7e\x84\x74\x17\x3c\x1f\xec\x50\xcf\x75\xb8\x5c\xca\xfc\x0f\xe8\x47\xd8\x64\xec\x5f\x6c\x45\x9a\x55\x49\x97\x3f\xcb\x49\x34\x71\x0a\x12\x13\xbc\x3d\x53\x81\x17\x9a\x92\x44\x91\x07\xc2\xef\x6d\x64\x86\x5d\xfd\x67\xd5\x99\x38\x95\x46\x74\x6d\xb6\xbf\x29\xc9\x5b\xac\xb1\x46\xd6\x9e\x57\x5c\x7b\x24\x91\xf4\x7c\xe4\x01\x31\x8c\xec\x79\x94\xb7\x3f\xd2\x93\x6d\xe2\x69\xbe\x61\x44\x2e\x8f\x1a\xdc\xa8\x97\xf5\x81\x8e\x0c\xe1\x00\xf2\x71\x51\xf3\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x18\xb2\x6b\x63\xcc\x17\x54\xf6\xf0\xb6\x9e\x62\xa4\x35\xcf\x47\x74\x13\x29\xbf\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x18\xb2\x6b\x63\xcc\x17\x54\xf6\xf0\xb6\x9e\x62\xa4\x35\xcf\x47\x74\x13\x29\xbf\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x7f\x24\x18\x8a\x79\xee\xf9\xeb\xed\x29\x1e\x21\x15\x8a\x53\xc9\xb7\xec\x30\xc4\x85\x9f\x45\x85\x26\x36\xb7\x07\xf3\xf1\xff\x3b\x89\x05\x0a\xd4\x30\x68\x31\x68\x33\xdd\xf6\x58\xa3\x85\x9f\x49\x50\x76\x9a\xc5\x79\x13\xe1\x4d\x67\x0c\xf3\x92\xf0\x1d\x02\x1f\xc4\x5c\xd4\xa1\x0c\x57\xdf\x46\x84\x43\x9f\xb0\xe2\x91\x62\xa8\xe0\x86\x0d\x47\xe1\xd9\x60\x01\xc4\xe0\xda\x6f\x06\x0a\xad\x38\xf3\x66\x68\xc5\xe2\x66\x3e\x47\x83\x65\x64\xcd\xff\xf3\xbb\xa7\xfa\x23\xf1\x82\x5e\x06\x6a\x91\x37\x51\xcd\xb9\x95\x20\x89\xff\xa1\x54\xb2\x76\xcf\x8e\xe1\xcd\x13\x93\x13\xd1\xda\x0d\x0d\xbc\x0f\xd5\x11\x26\xd6\xaf\x60\x0f\x4d\x8a\x4f\x28\xee\x6c\xf1\x99\xdc\xed\x16\xdc\x87\x26\xfd\x23\x8a\xb8\xb0\x20\x0e\xe2\x32\xf5\x8e\xb0\x65\x98\x13\xb8\x4b\x39\x7c\x8c\x98\xa2\x29\x75\x48\x3a\x89\xf9\x61\x77\x6c\x2d\x84\x41\x40\x17\xa6\x50\xc5\x09\x63\x10\xe7\x09\xd4\x5c\xdd\x0e\x71\x16\xaf\xb1\x32\xe4\xc0\xe6\xea\xfd\x26\x55\x07\x40\x95\x84\x48\x62\x04\x10\x92\xb2\xd9\x27\xfb\x8a\xf3\x7c\xe6\xfe\xd4\xfc\xa6\x33\x79\x01\x5c\xc3\x1f\x80\xa8\xf3"; -static struct -{ - wchar_t *hostname; - unsigned int datasize; - const qbyte *data; - //FIXME: include expiry information - //FIXME: add alternative when one is about to expire -} knowncerts[] = { - {L"triptohell.info", sizeof(triptohell_certdata), triptohell_certdata}, - {L"fte.triptohell.info", sizeof(fte_triptohell_certdata), fte_triptohell_certdata}, - {NULL} -}; - -char *narrowen(char *out, size_t outlen, wchar_t *wide); -static DWORD VerifyKnownCertificates(DWORD status, wchar_t *domain, qbyte *data, size_t datasize, qboolean datagram) -{ - int i; - if (datagram) - { - Con_Printf("FIXME: Ring of trust not yet implemented\n"); - if (status == CERT_E_UNTRUSTEDROOT) - { - Con_Printf("Allowing (probably) self-signed cert.\n"); - status = SEC_E_OK; - } - return status; - } - for (i = 0; knowncerts[i].hostname; i++) - { - if (!wcscmp(domain, knowncerts[i].hostname)) - { -#ifdef _DEBUG - if (!knowncerts[i].data) - { +/*regarding HAVE_DTLS +DTLS1.0 is supported from win8 onwards +Its also meant to be supported from some RDP server patch on win7, but I can't get it to work. +I've given up for now. +*/ + +cvar_t *tls_ignorecertificateerrors; + +#include "winquake.h" +#define SECURITY_WIN32 +#include +#include +#include + +#define SP_PROT_TLS1_1_SERVER 0x00000100 +#define SP_PROT_TLS1_1_CLIENT 0x00000200 + +#define SP_PROT_TLS1_2_SERVER 0x00000400 +#define SP_PROT_TLS1_2_CLIENT 0x00000800 + +#define SP_PROT_DTLS_SERVER 0x00010000 +#define SP_PROT_DTLS_CLIENT 0x00020000 + +//avoid the use of outdated/insecure protocols +//so no ssl2/ssl3 +#define USE_PROT_SERVER (SP_PROT_TLS1_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER) +#define USE_PROT_CLIENT (SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT) + +#define USE_PROT_DGRAM_SERVER (SP_PROT_DTLS_SERVER) +#define USE_PROT_DGRAM_CLIENT (SP_PROT_DTLS_CLIENT) + +#ifndef szOID_RSA_SHA512RSA +#define szOID_RSA_SHA512RSA "1.2.840.113549.1.1.13" +#endif +#ifndef SCH_CRED_SNI_CREDENTIAL +#define SCH_CRED_SNI_CREDENTIAL 0x00080000 +#endif + +#define SEC_I_MESSAGE_FRAGMENT 0x00090364L +#define SEC_E_INVALID_PARAMETER 0x8009035DL + + +//hungarian ensures we hit no macros. +static struct +{ + dllhandle_t *lib; + SECURITY_STATUS (WINAPI *pDecryptMessage) (PCtxtHandle,PSecBufferDesc,ULONG,PULONG); + SECURITY_STATUS (WINAPI *pEncryptMessage) (PCtxtHandle,ULONG,PSecBufferDesc,ULONG); + SECURITY_STATUS (WINAPI *pAcquireCredentialsHandleA) (SEC_CHAR*,SEC_CHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp); +// SECURITY_STATUS (WINAPI *pInitializeSecurityContextA) (PCredHandle,PCtxtHandle,SEC_CHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); + SECURITY_STATUS (WINAPI *pInitializeSecurityContextW) (PCredHandle,PCtxtHandle,SEC_WCHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); + SECURITY_STATUS (WINAPI *pAcceptSecurityContext) (PCredHandle,PCtxtHandle,PSecBufferDesc,unsigned long,unsigned long,PCtxtHandle,PSecBufferDesc,unsigned long SEC_FAR *,PTimeStamp); + SECURITY_STATUS (WINAPI *pCompleteAuthToken) (PCtxtHandle,PSecBufferDesc); + SECURITY_STATUS (WINAPI *pQueryContextAttributesA) (PCtxtHandle,ULONG,PVOID); + SECURITY_STATUS (WINAPI *pFreeCredentialsHandle) (PCredHandle); + SECURITY_STATUS (WINAPI *pDeleteSecurityContext) (PCtxtHandle); +} secur; +static struct +{ + dllhandle_t *lib; + BOOL (WINAPI *pCertGetCertificateChain) (HCERTCHAINENGINE,PCCERT_CONTEXT,LPFILETIME,HCERTSTORE,PCERT_CHAIN_PARA,DWORD,LPVOID,PCCERT_CHAIN_CONTEXT*); + BOOL (WINAPI *pCertVerifyCertificateChainPolicy) (LPCSTR,PCCERT_CHAIN_CONTEXT,PCERT_CHAIN_POLICY_PARA,PCERT_CHAIN_POLICY_STATUS); + void (WINAPI *pCertFreeCertificateChain) (PCCERT_CHAIN_CONTEXT); + DWORD (WINAPI *pCertNameToStrA) (DWORD dwCertEncodingType, PCERT_NAME_BLOB pName, DWORD dwStrType, LPCSTR psz, DWORD csz); + + PCCERT_CONTEXT (WINAPI *pCertCreateSelfSignCertificate) (HCRYPTPROV,PCERT_NAME_BLOB,DWORD,PCRYPT_KEY_PROV_INFO,PCRYPT_ALGORITHM_IDENTIFIER,PSYSTEMTIME,PSYSTEMTIME,PCERT_EXTENSIONS); + BOOL (WINAPI *pCertStrToNameA) (DWORD,LPCSTR,DWORD,void *,BYTE *,DWORD *,LPCSTR *); +} crypt; +void SSL_Init(void) +{ + dllfunction_t secur_functable[] = + { + {(void**)&secur.pDecryptMessage, "DecryptMessage"}, + {(void**)&secur.pEncryptMessage, "EncryptMessage"}, + {(void**)&secur.pAcquireCredentialsHandleA, "AcquireCredentialsHandleA"}, +// {(void**)&secur.pInitializeSecurityContextA, "InitializeSecurityContextA"}, + {(void**)&secur.pInitializeSecurityContextW, "InitializeSecurityContextW"}, + {(void**)&secur.pAcceptSecurityContext, "AcceptSecurityContext"}, + {(void**)&secur.pCompleteAuthToken, "CompleteAuthToken"}, + {(void**)&secur.pQueryContextAttributesA, "QueryContextAttributesA"}, + {(void**)&secur.pFreeCredentialsHandle, "FreeCredentialsHandle"}, + {(void**)&secur.pDeleteSecurityContext, "DeleteSecurityContext"}, + {NULL, NULL} + }; + + dllfunction_t crypt_functable[] = + { + {(void**)&crypt.pCertGetCertificateChain, "CertGetCertificateChain"}, + {(void**)&crypt.pCertVerifyCertificateChainPolicy, "CertVerifyCertificateChainPolicy"}, + {(void**)&crypt.pCertFreeCertificateChain, "CertFreeCertificateChain"}, + {(void**)&crypt.pCertNameToStrA, "CertNameToStrA"}, + {(void**)&crypt.pCertCreateSelfSignCertificate, "CertCreateSelfSignCertificate"}, + {(void**)&crypt.pCertStrToNameA, "CertStrToNameA"}, + {NULL, NULL} + }; + + tls_ignorecertificateerrors = Cvar_Get("tls_ignorecertificateerrors", "0", CVAR_NOTFROMSERVER, "TLS"); + + if (!secur.lib) + secur.lib = Sys_LoadLibrary("secur32.dll", secur_functable); + if (!crypt.lib) + crypt.lib = Sys_LoadLibrary("crypt32.dll", crypt_functable); +} +qboolean SSL_Inited(void) +{ + return !!secur.lib && !!crypt.lib; +} + +#define MessageAttribute (ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_MANUAL_CRED_VALIDATION) + +struct sslbuf +{ + size_t datasize; + char *data; + size_t avail; +}; + +typedef struct { + vfsfile_t funcs; + vfsfile_t *stream; + + wchar_t wpeername[256]; + qboolean datagram; + enum + { + HS_ESTABLISHED, + + HS_ERROR, + + HS_STARTCLIENT, + HS_CLIENT, + + HS_STARTSERVER, + HS_SERVER + } handshaking; + + struct sslbuf outraw; + struct sslbuf outcrypt; + struct sslbuf inraw; + struct sslbuf incrypt; + + + CredHandle cred; + SecHandle sechnd; + int headersize, footersize; + char headerdata[1024], footerdata[1024]; + +#ifdef HAVE_DTLS + void *cbctx; + neterr_t (*transmit)(void *cbctx, const qbyte *data, size_t datasize); +#endif +} sslfile_t; + +static int SSPI_ExpandBuffer(struct sslbuf *buf, size_t bytes) +{ + if (bytes < buf->datasize) + return buf->datasize; + Z_ReallocElements((void**)&buf->data, &buf->datasize, bytes, 1); + return bytes; +} + +static int SSPI_CopyIntoBuffer(struct sslbuf *buf, const void *data, unsigned int bytes, qboolean expand) +{ + if (bytes > buf->datasize - buf->avail) + { + if (!expand || SSPI_ExpandBuffer(buf, buf->avail + bytes + 1024) < buf->avail + bytes) + bytes = buf->datasize - buf->avail; + } + memcpy(buf->data + buf->avail, data, bytes); + buf->avail += bytes; + + return bytes; +} + +static void SSPI_Error(sslfile_t *f, char *error, ...) +{ + va_list argptr; + char string[1024]; + va_start (argptr, error); + vsnprintf (string,sizeof(string)-1, error,argptr); + va_end (argptr); + + f->handshaking = HS_ERROR; + if (*string) + Sys_Printf("%s", string); + if (f->stream) + VFS_CLOSE(f->stream); + + secur.pDeleteSecurityContext(&f->sechnd); + secur.pFreeCredentialsHandle(&f->cred); + f->stream = NULL; +} + +static void SSPI_TryFlushCryptOut(sslfile_t *f) +{ + int sent; + if (f->outcrypt.avail) + { +#ifdef HAVE_DTLS + if (f->transmit) + { + f->transmit(f->cbctx, f->outcrypt.data, f->outcrypt.avail); + f->outcrypt.avail = 0; + return; + } +#endif + sent = VFS_WRITE(f->stream, f->outcrypt.data, f->outcrypt.avail); + } + else + return; + + if (sent > 0) + { + memmove(f->outcrypt.data, f->outcrypt.data + sent, f->outcrypt.avail - sent); + f->outcrypt.avail -= sent; + } +} + +static int SSPI_CheckNewInCrypt(sslfile_t *f) +{ + int newd; + if (!f->stream) + return -1; + newd = VFS_READ(f->stream, f->incrypt.data+f->incrypt.avail, f->incrypt.datasize - f->incrypt.avail); + if (newd < 0) + return newd; + else + f->incrypt.avail += newd; + + return 0; +} + +//convert inbound crypt->data +static void SSPI_Decode(sslfile_t *f) +{ + SECURITY_STATUS ss; + SecBufferDesc BuffDesc; + SecBuffer SecBuff[4]; + ULONG ulQop = 0; + SecBuffer *extra = NULL; + int i; + + if (!f->incrypt.avail) + return; + + BuffDesc.ulVersion = SECBUFFER_VERSION; + BuffDesc.cBuffers = countof(SecBuff); + BuffDesc.pBuffers = SecBuff; + + SecBuff[0].BufferType = SECBUFFER_DATA; + SecBuff[0].cbBuffer = f->incrypt.avail; + SecBuff[0].pvBuffer = f->incrypt.data; + + SecBuff[1].BufferType = SECBUFFER_EMPTY; //space for header + SecBuff[2].BufferType = SECBUFFER_EMPTY; //space for footer + SecBuff[3].BufferType = SECBUFFER_EMPTY; //space for extra marker + + ss = secur.pDecryptMessage(&f->sechnd, &BuffDesc, 0, &ulQop); + + if (ss < 0) + { + if (ss == SEC_E_INCOMPLETE_MESSAGE) + { + if (f->incrypt.avail == f->incrypt.datasize) + SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024); + return; //no error if its incomplete, we can just get more data later on. + } + switch(ss) + { + case SEC_E_DECRYPT_FAILURE: SSPI_Error(f, "DecryptMessage failed: SEC_E_DECRYPT_FAILURE\n", ss); break; + case SEC_E_INVALID_HANDLE: SSPI_Error(f, "DecryptMessage failed: SEC_E_INVALID_HANDLE\n"); break; + default: SSPI_Error(f, "DecryptMessage failed: %0#lx\n", ss); break; + } + return; + } + + for (i = 0; i < BuffDesc.cBuffers; i++) + { + switch(SecBuff[i].BufferType) + { + case SECBUFFER_DATA: + if (SSPI_CopyIntoBuffer(&f->inraw, SecBuff[i].pvBuffer, SecBuff[i].cbBuffer, true) != SecBuff[i].cbBuffer) + SSPI_Error(f, "outraw buffer overflowed\n"); + break; + case SECBUFFER_EXTRA: + if (extra) + SSPI_Error(f, "multiple extra buffers\n"); + extra = &SecBuff[i]; + break; + case SECBUFFER_EMPTY: + case SECBUFFER_MISSING: + case SECBUFFER_STREAM_TRAILER: + case SECBUFFER_STREAM_HEADER: + break; + default: + SSPI_Error(f, "got unexpected buffer type\n"); + break; + } + } + + //retain the extra. if there's no extra then mark it so. + if (extra) + { + memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - extra->cbBuffer), extra->cbBuffer); + f->incrypt.avail = extra->cbBuffer; + } + else + f->incrypt.avail = 0; +} + +//convert outgoing data->crypt +static void SSPI_Encode(sslfile_t *f) +{ + SECURITY_STATUS ss; + SecBufferDesc BuffDesc; + SecBuffer SecBuff[4]; + ULONG ulQop = 0; + + if (f->outcrypt.avail) + { + SSPI_TryFlushCryptOut(f); + if (f->outcrypt.avail) + return; //don't flood too much + } + + + //don't corrupt the handshake data. + if (f->handshaking) + return; + + if (!f->outraw.avail) + return; + + BuffDesc.ulVersion = SECBUFFER_VERSION; + BuffDesc.cBuffers = 4; + BuffDesc.pBuffers = SecBuff; + + SecBuff[0].BufferType = SECBUFFER_STREAM_HEADER; + SecBuff[0].cbBuffer = f->headersize; + SecBuff[0].pvBuffer = f->headerdata; + + SecBuff[1].BufferType = SECBUFFER_DATA; + SecBuff[1].cbBuffer = f->outraw.avail; + SecBuff[1].pvBuffer = f->outraw.data; + + SecBuff[2].BufferType = SECBUFFER_STREAM_TRAILER; + SecBuff[2].cbBuffer = f->footersize; + SecBuff[2].pvBuffer = f->footerdata; + + SecBuff[3].BufferType = SECBUFFER_EMPTY; + SecBuff[3].cbBuffer = 0; + SecBuff[3].pvBuffer = NULL; + + ss = secur.pEncryptMessage(&f->sechnd, ulQop, &BuffDesc, 0); + + if (ss < 0) + { + SSPI_Error(f, "EncryptMessage failed\n"); + return; + } + + f->outraw.avail = 0; + + //fixme: these should be made non-fatal. + if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[0].pvBuffer, SecBuff[0].cbBuffer, true) < SecBuff[0].cbBuffer) + { + SSPI_Error(f, "crypt buffer overflowed\n"); + return; + } + if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[1].pvBuffer, SecBuff[1].cbBuffer, true) < SecBuff[1].cbBuffer) + { + SSPI_Error(f, "crypt buffer overflowed\n"); + return; + } + if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[2].pvBuffer, SecBuff[2].cbBuffer, true) < SecBuff[2].cbBuffer) + { + SSPI_Error(f, "crypt buffer overflowed\n"); + return; + } + + SSPI_TryFlushCryptOut(f); +} + +//these are known sites that use self-signed certificates, or are special enough that we don't trust corporate networks to hack in their own certificate authority for a proxy/mitm +//old static const qbyte triptohell_certdata[933] = "\x30\x82\x03\xa1\x30\x82\x02\x89\xa0\x03\x02\x01\x02\x02\x09\x00\x8b\xd0\x05\x63\x62\xd1\x6a\xe3\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x67\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x42\x44\x31\x0c\x30\x0a\x06\x03\x55\x04\x08\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x07\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x0a\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x0b\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x03\x0c\x03\x42\x61\x64\x31\x12\x30\x10\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x01\x16\x03\x42\x61\x64\x30\x1e\x17\x0d\x31\x34\x31\x32\x32\x34\x32\x32\x34\x32\x34\x37\x5a\x17\x0d\x32\x34\x31\x32\x32\x31\x32\x32\x34\x32\x34\x37\x5a\x30\x67\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x42\x44\x31\x0c\x30\x0a\x06\x03\x55\x04\x08\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x07\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x0a\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x0b\x0c\x03\x42\x61\x64\x31\x0c\x30\x0a\x06\x03\x55\x04\x03\x0c\x03\x42\x61\x64\x31\x12\x30\x10\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x01\x16\x03\x42\x61\x64\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xaf\x10\x33\xfa\x39\xf5\xae\x2c\x91\x0e\x20\xe6\x3c\x5c\x7c\x1e\xeb\x16\x50\x2f\x05\x30\xfe\x67\xee\xa9\x00\x54\xd9\x4a\x86\xe6\xba\x80\xfb\x1a\x80\x08\x7e\x7b\x13\xe5\x1a\x18\xc9\xd4\x70\xbd\x5d\xc4\x38\xef\x64\xf1\x90\x2c\x53\x49\x93\x24\x36\x3e\x11\x59\x69\xa6\xdf\x37\xb2\x54\x82\x28\x3e\xdd\x30\x75\xa0\x18\xd8\xe1\xf5\x52\x73\x12\x5b\x37\x68\x1c\x59\xbd\x8c\x73\x66\x47\xbc\xcb\x9c\xfe\x38\x92\x8f\x74\xe9\xd1\x2f\x96\xd2\x5d\x6d\x11\x59\xb2\xdc\xbd\x8c\x37\x5b\x22\x76\x98\xe7\xbe\x08\xef\x1e\x99\xc4\xa9\x77\x2c\x9c\x0e\x08\x3c\x8e\xab\x97\x0c\x6a\xd7\x03\xab\xfd\x4a\x1e\x95\xb2\xc2\x9c\x3a\x16\x65\xd7\xaf\x45\x5f\x6e\xe7\xce\x51\xba\xa0\x60\x43\x0e\x07\xc5\x0b\x0a\x82\x05\x26\xc4\x92\x0a\x27\x5b\xfc\x57\x6c\xdf\xe2\x54\x8a\xef\x38\xf1\xf8\xc4\xf8\x51\x16\x27\x1f\x78\x89\x7c\x5b\xd7\x53\xcd\x9b\x54\x2a\xe6\x71\xee\xe4\x56\x2e\xa4\x09\x1a\x61\xf7\x0f\x97\x22\x94\xd7\xef\x21\x6c\xe6\x81\xfb\x54\x5f\x09\x92\xac\xd2\x7c\xab\xd5\xa9\x81\xf4\xc9\xb7\xd6\xbf\x68\xf8\x4f\xdc\xf3\x60\xa3\x3b\x29\x92\x9e\xdd\xa2\xa3\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x19\xed\xd0\x7b\x16\xaf\xb5\x0c\x9a\xe8\xd3\x46\x2e\x3c\x64\x29\xb6\xc1\x73\x5a\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x19\xed\xd0\x7b\x16\xaf\xb5\x0c\x9a\xe8\xd3\x46\x2e\x3c\x64\x29\xb6\xc1\x73\x5a\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x82\x01\x01\x00\x62\xa7\x26\xeb\xd4\x03\x29\x9c\x09\x33\x69\x7a\x9c\x65\x68\xec\x4c\xb9\x06\xeb\x1e\x51\x6f\x78\x20\xdc\xf6\x44\x5e\x06\x6e\x53\x87\x73\xe6\x14\x15\xb9\x17\x74\x67\xe0\x4e\x48\x38\xbc\x1c\xbd\xd0\xad\xd6\xbd\x8c\xf0\x3a\xe0\x13\x73\x19\xad\x8b\x79\x68\x67\x65\x9b\x7a\x4c\x81\xfb\xd9\x92\x77\x89\xb5\xb0\x53\xb0\xa5\xf7\x2d\x8e\x29\x60\x31\xd1\x9b\x2f\x63\x8a\x5f\x64\xc1\x61\xd5\xb7\xdf\x70\x3b\x2b\xf6\x1a\x96\xb9\xa7\x08\xca\x87\xa6\x8c\x60\xca\x6e\xd7\xee\xba\xef\x89\x0b\x93\xd5\xfd\xfc\x14\xba\xef\x27\xba\x90\x11\x90\xf7\x25\x70\xe7\x4e\xf4\x9c\x13\x27\xc1\xa7\x8e\xd9\x66\x43\x72\x20\x5b\xe1\x5c\x73\x74\xf5\x33\xf2\xa5\xf6\xe1\xd5\xac\xf3\x67\x5c\xe7\xd4\x0a\x8d\x91\x73\x03\x3e\x9d\xbc\x96\xc3\x0c\xdb\xd5\x77\x6e\x76\x44\x69\xaf\x24\x0f\x4f\x8b\x47\x36\x8b\xc3\xd6\x36\xdd\x26\x5a\x9c\xdd\x9c\x43\xee\x29\x43\xdd\x75\x2f\x19\x52\xfc\x1d\x24\x9c\x13\x29\x99\xa0\x6d\x7a\x95\xcc\xa0\x58\x86\xd8\xc5\xb9\xa3\xc2\x3d\x64\x1d\x85\x8a\xca\x53\x55\x8e\x9a\x6d\xc9\x91\x73\xf4\xe1\xe1\xa4\x9b\x76\xfc\x7f\x63\xc2\xb9\x23"; +static const qbyte fte_triptohell_certdata[917] = "\x30\x82\x03\x91\x30\x82\x02\x79\xa0\x03\x02\x01\x02\x02\x09\x00\xb5\x71\x47\x8d\x5e\x66\xf1\xd9\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x5f\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x11\x30\x0f\x06\x03\x55\x04\x08\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x11\x30\x0f\x06\x03\x55\x04\x07\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x0c\x30\x0a\x06\x03\x55\x04\x0a\x0c\x03\x46\x54\x45\x31\x1c\x30\x1a\x06\x03\x55\x04\x03\x0c\x13\x66\x74\x65\x2e\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x30\x1e\x17\x0d\x31\x34\x31\x32\x32\x35\x30\x30\x35\x38\x31\x34\x5a\x17\x0d\x31\x37\x30\x33\x30\x34\x30\x30\x35\x38\x31\x34\x5a\x30\x5f\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x11\x30\x0f\x06\x03\x55\x04\x08\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x11\x30\x0f\x06\x03\x55\x04\x07\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x0c\x30\x0a\x06\x03\x55\x04\x0a\x0c\x03\x46\x54\x45\x31\x1c\x30\x1a\x06\x03\x55\x04\x03\x0c\x13\x66\x74\x65\x2e\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xdd\xb8\x7c\x69\x3d\x63\x95\xe3\x88\x15\xfd\xad\x93\x5e\x6b\x97\xfb\x74\xba\x1f\x83\x33\xe5\x8a\x8d\x8f\xb0\xbf\xf9\xd3\xa1\x2c\x65\x53\xa7\xef\xd3\x0f\xdc\x03\x60\x0a\x40\xef\xa8\xef\x3f\xb3\xd9\x8d\x31\x39\x12\x8a\xd8\x0e\x24\x8f\xe5\x58\x26\x86\x4c\x76\x6c\x59\x9a\xab\xea\x1c\x3d\xfb\x62\x62\xad\xaf\xd6\x00\x33\x76\x2d\xbb\xeb\xe8\xec\xb4\x76\x4f\xb0\xbe\xcf\xf0\x46\x94\x40\x02\x99\xd4\xb2\x71\x71\xd6\xf5\x1f\xc3\x4f\x1e\x1e\xb4\x0d\x82\x49\xc4\xa2\xdc\xae\x6f\x4e\x3a\xf9\x0e\xdd\xf4\xd2\x53\xe3\xe7\x7d\x58\x79\xf4\xce\x1f\x6c\xac\x81\x8c\x8c\xe1\x03\x5b\x22\x56\x92\x19\x4f\x74\xc0\x36\x41\xac\x1b\xfa\x9e\xf7\x2a\x0f\xd6\x4b\xcc\x9a\xca\x67\x87\xb7\x95\xdf\xb7\xd4\x7d\x8c\xcc\xa9\x25\xde\xdd\x8c\x1b\xd7\x32\xf2\x84\x25\x46\x7b\x10\x55\xf9\x80\xfd\x5d\xad\xab\xf9\x4c\x1f\xc0\xa5\xd1\x3f\x01\x86\x4d\xfa\x57\xab\x7a\x6d\xec\xf1\xdb\xf4\xad\xf2\x33\xcd\xa0\xed\xfe\x1b\x27\x55\x56\xba\x8c\x47\x70\x16\xd5\x75\x17\x8e\x80\xaa\x49\x5e\x93\x83\x1d\x6f\x1f\x2c\xf7\xa7\x64\xe6\x2e\x88\x8e\xff\x70\x5a\x41\x52\xae\x93\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x4e\x76\x4a\xce\x7b\x45\x14\x39\xeb\x9c\x28\x56\xb5\x7b\x8a\x18\x6f\x22\x17\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x4e\x76\x4a\xce\x7b\x45\x14\x39\xeb\x9c\x28\x56\xb5\x7b\x8a\x18\x6f\x22\x17\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x48\x22\x65\xed\x2e\xc5\xed\xbb\xe9\x40\x6c\x80\xc4\x63\x19\xd1\x00\xb4\x30\x34\x17\x7c\x7c\xbd\x1b\xc5\xa9\x43\x0c\x92\x6e\xd6\x2d\x11\x6c\x0d\xa6\xda\x30\xe9\xf7\x46\x7b\x01\xe4\x53\x23\xae\x88\xd1\xf2\xed\xca\x84\x06\x19\x97\xb9\x06\xfb\xda\xec\x72\x2d\x15\x20\xd2\x8f\x66\xad\xb5\xdd\x4b\x4f\xdf\x7e\xaf\xa3\x6c\x7f\x53\x32\x8f\xe2\x19\x5c\x44\x98\x86\x31\xee\xb4\x03\xe7\x27\xa1\x83\xab\xc3\xce\xb4\x9a\x01\xbe\x8c\x64\x2e\x2b\xe3\x4e\x55\xdf\x95\xeb\x16\x87\xbd\xfa\x11\xa2\x3e\x38\x92\x97\x36\xe9\x65\x60\xf3\xac\x68\x44\xb3\x51\x54\x3a\x42\xa8\x98\x9b\xee\x1b\x9e\x79\x6a\xaf\xc0\xbe\x41\xc4\xb1\x96\x42\xd9\x94\xef\x49\x5b\xbe\x2d\x04\xb9\xfb\x92\xbb\xdc\x0e\x29\xfd\xee\xa9\x68\x09\xf9\x9f\x69\x8b\x3d\xe1\x4b\xee\x24\xf9\xfe\x02\x3a\x0a\xb8\xcd\x6c\x07\x43\xa9\x4a\xe7\x03\x34\x2e\x72\xa7\x81\xaa\x40\xa9\x98\x5d\x97\xee\x2a\x99\xc6\x8f\xe8\x6f\x98\xa2\x85\xc9\x0d\x04\x19\x43\x6a\xd3\xc7\x15\x4c\x4b\xbc\xa5\xb8\x9f\x38\xf3\x43\x83\x0c\xef\x97\x6e\xa6\x20\xde\xc5\xd3\x1e\x3e\x5d\xcd\x58\x3d\x5c\x55\x7a\x90\x94"; +static const qbyte triptohell_certdata[933] = "\x30\x82\x03\xa1\x30\x82\x02\x89\xa0\x03\x02\x01\x02\x02\x09\x00\xea\xb7\x13\xcf\x55\xe5\xe8\x8c\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x67\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x11\x30\x0f\x06\x03\x55\x04\x08\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x11\x30\x0f\x06\x03\x55\x04\x07\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x18\x30\x16\x06\x03\x55\x04\x0a\x0c\x0f\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x31\x18\x30\x16\x06\x03\x55\x04\x03\x0c\x0f\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x30\x1e\x17\x0d\x31\x34\x31\x32\x32\x35\x30\x30\x35\x38\x33\x37\x5a\x17\x0d\x31\x37\x30\x33\x30\x34\x30\x30\x35\x38\x33\x37\x5a\x30\x67\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x11\x30\x0f\x06\x03\x55\x04\x08\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x11\x30\x0f\x06\x03\x55\x04\x07\x0c\x08\x4e\x65\x77\x20\x59\x6f\x72\x6b\x31\x18\x30\x16\x06\x03\x55\x04\x0a\x0c\x0f\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x31\x18\x30\x16\x06\x03\x55\x04\x03\x0c\x0f\x74\x72\x69\x70\x74\x6f\x68\x65\x6c\x6c\x2e\x69\x6e\x66\x6f\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xd8\x77\x62\xf6\x74\xa7\x75\xde\xda\x09\xae\x9e\x76\x7a\xc6\x2a\xcf\x9a\xbe\xc6\xb9\x6d\xe2\xca\x0f\x2d\x95\xb8\x89\x93\xf7\x50\x64\x92\x7d\x95\x34\xe4\x6e\xef\x52\x56\xef\x13\x9a\x3a\xae\x84\x5b\x57\x82\x04\x86\x74\xbd\x4e\x38\x32\x56\x00\xd6\x34\x9c\x23\xd6\x81\x8e\x29\x77\x45\x61\x20\xdf\x28\xf8\xe5\x61\x83\xec\xe6\xa0\x1a\x75\xa8\x3b\x53\x6f\xc4\x09\x61\x66\x3a\xf0\x81\xbf\x2c\xf5\x8e\xf1\xe2\x35\xe4\x24\x7f\x16\xcc\xce\x60\xa2\x42\x6e\xc2\x3a\x29\x75\x6c\x79\xb0\x99\x9c\xe2\xfe\x27\x32\xb6\xf7\x0d\x71\xfd\x62\x9d\x54\x7c\x40\xb2\xf5\xa0\xa4\x25\x31\x8d\x65\xfd\x3f\x3b\x9b\x7e\x84\x74\x17\x3c\x1f\xec\x50\xcf\x75\xb8\x5c\xca\xfc\x0f\xe8\x47\xd8\x64\xec\x5f\x6c\x45\x9a\x55\x49\x97\x3f\xcb\x49\x34\x71\x0a\x12\x13\xbc\x3d\x53\x81\x17\x9a\x92\x44\x91\x07\xc2\xef\x6d\x64\x86\x5d\xfd\x67\xd5\x99\x38\x95\x46\x74\x6d\xb6\xbf\x29\xc9\x5b\xac\xb1\x46\xd6\x9e\x57\x5c\x7b\x24\x91\xf4\x7c\xe4\x01\x31\x8c\xec\x79\x94\xb7\x3f\xd2\x93\x6d\xe2\x69\xbe\x61\x44\x2e\x8f\x1a\xdc\xa8\x97\xf5\x81\x8e\x0c\xe1\x00\xf2\x71\x51\xf3\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x18\xb2\x6b\x63\xcc\x17\x54\xf6\xf0\xb6\x9e\x62\xa4\x35\xcf\x47\x74\x13\x29\xbf\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x18\xb2\x6b\x63\xcc\x17\x54\xf6\xf0\xb6\x9e\x62\xa4\x35\xcf\x47\x74\x13\x29\xbf\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x7f\x24\x18\x8a\x79\xee\xf9\xeb\xed\x29\x1e\x21\x15\x8a\x53\xc9\xb7\xec\x30\xc4\x85\x9f\x45\x85\x26\x36\xb7\x07\xf3\xf1\xff\x3b\x89\x05\x0a\xd4\x30\x68\x31\x68\x33\xdd\xf6\x58\xa3\x85\x9f\x49\x50\x76\x9a\xc5\x79\x13\xe1\x4d\x67\x0c\xf3\x92\xf0\x1d\x02\x1f\xc4\x5c\xd4\xa1\x0c\x57\xdf\x46\x84\x43\x9f\xb0\xe2\x91\x62\xa8\xe0\x86\x0d\x47\xe1\xd9\x60\x01\xc4\xe0\xda\x6f\x06\x0a\xad\x38\xf3\x66\x68\xc5\xe2\x66\x3e\x47\x83\x65\x64\xcd\xff\xf3\xbb\xa7\xfa\x23\xf1\x82\x5e\x06\x6a\x91\x37\x51\xcd\xb9\x95\x20\x89\xff\xa1\x54\xb2\x76\xcf\x8e\xe1\xcd\x13\x93\x13\xd1\xda\x0d\x0d\xbc\x0f\xd5\x11\x26\xd6\xaf\x60\x0f\x4d\x8a\x4f\x28\xee\x6c\xf1\x99\xdc\xed\x16\xdc\x87\x26\xfd\x23\x8a\xb8\xb0\x20\x0e\xe2\x32\xf5\x8e\xb0\x65\x98\x13\xb8\x4b\x39\x7c\x8c\x98\xa2\x29\x75\x48\x3a\x89\xf9\x61\x77\x6c\x2d\x84\x41\x40\x17\xa6\x50\xc5\x09\x63\x10\xe7\x09\xd4\x5c\xdd\x0e\x71\x16\xaf\xb1\x32\xe4\xc0\xe6\xea\xfd\x26\x55\x07\x40\x95\x84\x48\x62\x04\x10\x92\xb2\xd9\x27\xfb\x8a\xf3\x7c\xe6\xfe\xd4\xfc\xa6\x33\x79\x01\x5c\xc3\x1f\x80\xa8\xf3"; +static struct +{ + wchar_t *hostname; + unsigned int datasize; + const qbyte *data; + //FIXME: include expiry information + //FIXME: add alternative when one is about to expire +} knowncerts[] = { + {L"triptohell.info", sizeof(triptohell_certdata), triptohell_certdata}, + {L"fte.triptohell.info", sizeof(fte_triptohell_certdata), fte_triptohell_certdata}, + {NULL} +}; + +char *narrowen(char *out, size_t outlen, wchar_t *wide); +static DWORD VerifyKnownCertificates(DWORD status, wchar_t *domain, qbyte *data, size_t datasize, qboolean datagram) +{ + int i; + if (datagram) + { + if (status == CERT_E_UNTRUSTEDROOT || SUCCEEDED(status)) + { +#ifndef SERVERONLY + char realdomain[256]; + if (CertLog_ConnectOkay(narrowen(realdomain, sizeof(realdomain), domain), data, datasize)) + status = SEC_E_OK; + else +#endif + status = TRUST_E_FAIL; + } + return status; + } + for (i = 0; knowncerts[i].hostname; i++) + { + if (!wcscmp(domain, knowncerts[i].hostname)) + { +#ifdef _DEBUG + if (!knowncerts[i].data) + { int j; Con_Printf("%ls cert %i bytes\n", domain, datasize); Con_Printf("\"", datasize); for (j = 0; j < datasize; j++) Con_Printf("\\x%02x", data[j]); - Con_Printf("\"\n", datasize); - - Con_Printf("\n", datasize); + Con_Printf("\"\n", datasize); + + Con_Printf("\n", datasize); for (j = 0; j < datasize; j++) - Con_Printf("%c", data[j]); - continue; - } -#endif - if (knowncerts[i].datasize == datasize && !memcmp(data, knowncerts[i].data, datasize)) - { //what we know about matched - if (status == CERT_E_UNTRUSTEDROOT) - status = SEC_E_OK; - break; - } - else - { - if (status != CERT_E_EXPIRED) - Con_Printf("%ls has an unexpected certificate\n", domain); - if (status == SEC_E_OK) //we (think) we know better. - status = TRUST_E_FAIL; - } - } - } - return status; -} - -static DWORD VerifyServerCertificate(PCCERT_CONTEXT pServerCert, PWSTR pwszServerName, DWORD dwCertFlags, qboolean datagram) -{ - HTTPSPolicyCallbackData polHttps; - CERT_CHAIN_POLICY_PARA PolicyPara; - CERT_CHAIN_POLICY_STATUS PolicyStatus; - CERT_CHAIN_PARA ChainPara; - PCCERT_CHAIN_CONTEXT pChainContext; - DWORD Status; - LPSTR rgszUsages[] = - { - szOID_PKIX_KP_SERVER_AUTH, - szOID_SERVER_GATED_CRYPTO, - szOID_SGC_NETSCAPE - }; - DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR); - - if(pServerCert == NULL) - return SEC_E_WRONG_PRINCIPAL; - if(!*pwszServerName) - return SEC_E_WRONG_PRINCIPAL; - - // Build certificate chain. - memset(&ChainPara, 0, sizeof(ChainPara)); - ChainPara.cbSize = sizeof(ChainPara); - ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; - ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages; - ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; - - if (!crypt.pCertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara, 0, NULL, &pChainContext)) - { - Status = GetLastError(); - Sys_Printf("Error %#lx returned by CertGetCertificateChain!\n", Status); - } - else - { - // Validate certificate chain. - memset(&polHttps, 0, sizeof(HTTPSPolicyCallbackData)); - polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); - polHttps.dwAuthType = AUTHTYPE_SERVER; - polHttps.fdwChecks = dwCertFlags; - polHttps.pwszServerName = pwszServerName; - - memset(&PolicyPara, 0, sizeof(PolicyPara)); - PolicyPara.cbSize = sizeof(PolicyPara); - PolicyPara.pvExtraPolicyPara = &polHttps; - - memset(&PolicyStatus, 0, sizeof(PolicyStatus)); - PolicyStatus.cbSize = sizeof(PolicyStatus); - - if (!crypt.pCertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus)) - { - Status = GetLastError(); - Sys_Printf("Error %#lx returned by CertVerifyCertificateChainPolicy!\n", Status); - } - else - { - Status = VerifyKnownCertificates(PolicyStatus.dwError, pwszServerName, pServerCert->pbCertEncoded, pServerCert->cbCertEncoded, datagram); - if (Status) - { - char fmsg[512]; - char *err; - switch (Status) - { - case CERT_E_EXPIRED: err = "CERT_E_EXPIRED"; break; - case CERT_E_VALIDITYPERIODNESTING: err = "CERT_E_VALIDITYPERIODNESTING"; break; - case CERT_E_ROLE: err = "CERT_E_ROLE"; break; - case CERT_E_PATHLENCONST: err = "CERT_E_PATHLENCONST"; break; - case CERT_E_CRITICAL: err = "CERT_E_CRITICAL"; break; - case CERT_E_PURPOSE: err = "CERT_E_PURPOSE"; break; - case CERT_E_ISSUERCHAINING: err = "CERT_E_ISSUERCHAINING"; break; - case CERT_E_MALFORMED: err = "CERT_E_MALFORMED"; break; - case CERT_E_UNTRUSTEDROOT: err = "CERT_E_UNTRUSTEDROOT"; break; - case CERT_E_CHAINING: err = "CERT_E_CHAINING"; break; - case TRUST_E_FAIL: err = "TRUST_E_FAIL"; break; - case CERT_E_REVOKED: err = "CERT_E_REVOKED"; break; - case CERT_E_UNTRUSTEDTESTROOT: err = "CERT_E_UNTRUSTEDTESTROOT"; break; - case CERT_E_REVOCATION_FAILURE: err = "CERT_E_REVOCATION_FAILURE"; break; - case CERT_E_CN_NO_MATCH: - err = fmsg; - Q_strncpyz(fmsg, "Certificate is for ", sizeof(fmsg)); - crypt.pCertNameToStrA(X509_ASN_ENCODING, &pServerCert->pCertInfo->Subject, 0, fmsg+strlen(fmsg), sizeof(fmsg)-strlen(fmsg)); - break; - case CERT_E_WRONG_USAGE: err = "CERT_E_WRONG_USAGE"; break; - default: err = "(unknown)"; break; - } - Con_Printf("Error verifying certificate for '%ls': %s\n", pwszServerName, err); - - if (tls_ignorecertificateerrors->ival) - { - Con_Printf("pretending it didn't happen... (tls_ignorecertificateerrors is set)\n"); - Status = SEC_E_OK; - } - } - else - Status = SEC_E_OK; - } - crypt.pCertFreeCertificateChain(pChainContext); - } - - return Status; -} -static PCCERT_CONTEXT SSPI_GetServerCertificate(void) -{ - static PCCERT_CONTEXT ret; - char *issuertext = "CN=127.0.0.1, O=\"FTE QuakeWorld\", OU=Testing, C=TR"; - CERT_NAME_BLOB issuerblob; - + Con_Printf("%c", data[j]); + continue; + } +#endif + if (knowncerts[i].datasize == datasize && !memcmp(data, knowncerts[i].data, datasize)) + { //what we know about matched + if (status == CERT_E_UNTRUSTEDROOT) + status = SEC_E_OK; + break; + } + else + { + if (status != CERT_E_EXPIRED) + Con_Printf("%ls has an unexpected certificate\n", domain); + if (status == SEC_E_OK) //we (think) we know better. + status = TRUST_E_FAIL; + } + } + } + return status; +} + +static DWORD VerifyServerCertificate(PCCERT_CONTEXT pServerCert, PWSTR pwszServerName, DWORD dwCertFlags, qboolean datagram) +{ + HTTPSPolicyCallbackData polHttps; + CERT_CHAIN_POLICY_PARA PolicyPara; + CERT_CHAIN_POLICY_STATUS PolicyStatus; + CERT_CHAIN_PARA ChainPara; + PCCERT_CHAIN_CONTEXT pChainContext; + DWORD Status; + LPSTR rgszUsages[] = + { + szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE + }; + DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR); + + if(pServerCert == NULL) + return SEC_E_WRONG_PRINCIPAL; + if(!*pwszServerName) + return SEC_E_WRONG_PRINCIPAL; + + // Build certificate chain. + memset(&ChainPara, 0, sizeof(ChainPara)); + ChainPara.cbSize = sizeof(ChainPara); + ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages; + ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; + + if (!crypt.pCertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara, 0, NULL, &pChainContext)) + { + Status = GetLastError(); + Sys_Printf("Error %#lx returned by CertGetCertificateChain!\n", Status); + } + else + { + // Validate certificate chain. + memset(&polHttps, 0, sizeof(HTTPSPolicyCallbackData)); + polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); + polHttps.dwAuthType = AUTHTYPE_SERVER; + polHttps.fdwChecks = dwCertFlags; + polHttps.pwszServerName = pwszServerName; + + memset(&PolicyPara, 0, sizeof(PolicyPara)); + PolicyPara.cbSize = sizeof(PolicyPara); + PolicyPara.pvExtraPolicyPara = &polHttps; + + memset(&PolicyStatus, 0, sizeof(PolicyStatus)); + PolicyStatus.cbSize = sizeof(PolicyStatus); + + if (!crypt.pCertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus)) + { + Status = GetLastError(); + Sys_Printf("Error %#lx returned by CertVerifyCertificateChainPolicy!\n", Status); + } + else + { + Status = VerifyKnownCertificates(PolicyStatus.dwError, pwszServerName, pServerCert->pbCertEncoded, pServerCert->cbCertEncoded, datagram); + if (Status) + { + char fmsg[512]; + char *err; + switch (Status) + { + case CERT_E_EXPIRED: err = "CERT_E_EXPIRED"; break; + case CERT_E_VALIDITYPERIODNESTING: err = "CERT_E_VALIDITYPERIODNESTING"; break; + case CERT_E_ROLE: err = "CERT_E_ROLE"; break; + case CERT_E_PATHLENCONST: err = "CERT_E_PATHLENCONST"; break; + case CERT_E_CRITICAL: err = "CERT_E_CRITICAL"; break; + case CERT_E_PURPOSE: err = "CERT_E_PURPOSE"; break; + case CERT_E_ISSUERCHAINING: err = "CERT_E_ISSUERCHAINING"; break; + case CERT_E_MALFORMED: err = "CERT_E_MALFORMED"; break; + case CERT_E_UNTRUSTEDROOT: err = "CERT_E_UNTRUSTEDROOT"; break; + case CERT_E_CHAINING: err = "CERT_E_CHAINING"; break; + case TRUST_E_FAIL: err = "TRUST_E_FAIL"; break; + case CERT_E_REVOKED: err = "CERT_E_REVOKED"; break; + case CERT_E_UNTRUSTEDTESTROOT: err = "CERT_E_UNTRUSTEDTESTROOT"; break; + case CERT_E_REVOCATION_FAILURE: err = "CERT_E_REVOCATION_FAILURE"; break; + case CERT_E_CN_NO_MATCH: + err = fmsg; + Q_strncpyz(fmsg, "Certificate is for ", sizeof(fmsg)); + crypt.pCertNameToStrA(X509_ASN_ENCODING, &pServerCert->pCertInfo->Subject, 0, fmsg+strlen(fmsg), sizeof(fmsg)-strlen(fmsg)); + break; + case CERT_E_WRONG_USAGE: err = "CERT_E_WRONG_USAGE"; break; + default: err = "(unknown)"; break; + } + Con_Printf("Error verifying certificate for '%ls': %s\n", pwszServerName, err); + + if (tls_ignorecertificateerrors->ival) + { + Con_Printf("pretending it didn't happen... (tls_ignorecertificateerrors is set)\n"); + Status = SEC_E_OK; + } + } + else + Status = SEC_E_OK; + } + crypt.pCertFreeCertificateChain(pChainContext); + } + + return Status; +} +static PCCERT_CONTEXT SSPI_GetServerCertificate(void) +{ + static PCCERT_CONTEXT ret; + char *issuertext = "CN=127.0.0.1, O=\"FTE QuakeWorld\", OU=Testing, C=TR"; + CERT_NAME_BLOB issuerblob; + CRYPT_ALGORITHM_IDENTIFIER sigalg; SYSTEMTIME expiredate; if (ret) return ret; - memset(&sigalg, 0, sizeof(sigalg)); - sigalg.pszObjId = szOID_RSA_SHA512RSA; + memset(&sigalg, 0, sizeof(sigalg)); + sigalg.pszObjId = szOID_RSA_SHA512RSA; GetSystemTime(&expiredate); - expiredate.wYear += 2; //2 years hence. woo - - - memset(&issuerblob, 0, sizeof(issuerblob)); + expiredate.wYear += 2; //2 years hence. woo + + + memset(&issuerblob, 0, sizeof(issuerblob)); crypt.pCertStrToNameA(X509_ASN_ENCODING, issuertext, CERT_X500_NAME_STR, NULL, issuerblob.pbData, &issuerblob.cbData, NULL); issuerblob.pbData = Z_Malloc(issuerblob.cbData); - crypt.pCertStrToNameA(X509_ASN_ENCODING, issuertext, CERT_X500_NAME_STR, NULL, issuerblob.pbData, &issuerblob.cbData, NULL); - + crypt.pCertStrToNameA(X509_ASN_ENCODING, issuertext, CERT_X500_NAME_STR, NULL, issuerblob.pbData, &issuerblob.cbData, NULL); + ret = crypt.pCertCreateSelfSignCertificate( 0, &issuerblob, @@ -588,10 +592,10 @@ static PCCERT_CONTEXT SSPI_GetServerCertificate(void) NULL, &expiredate, NULL - ); - if (!ret) - { //try and downgrade the signature algo if it failed. - sigalg.pszObjId = szOID_RSA_SHA1RSA; + ); + if (!ret) + { //try and downgrade the signature algo if it failed. + sigalg.pszObjId = szOID_RSA_SHA1RSA; ret = crypt.pCertCreateSelfSignCertificate( 0, &issuerblob, @@ -601,671 +605,712 @@ static PCCERT_CONTEXT SSPI_GetServerCertificate(void) NULL, &expiredate, NULL - ); - } - - Z_Free(issuerblob.pbData); - return ret; -} - -static void SSPI_GenServerCredentials(sslfile_t *f) -{ - SECURITY_STATUS ss; - TimeStamp Lifetime; - SCHANNEL_CRED SchannelCred; - PCCERT_CONTEXT cred; - - memset(&SchannelCred, 0, sizeof(SchannelCred)); - SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; - SchannelCred.grbitEnabledProtocols = f->datagram?USE_PROT_DGRAM_SERVER:USE_PROT_SERVER; - SchannelCred.dwFlags |= SCH_CRED_NO_SYSTEM_MAPPER|SCH_CRED_DISABLE_RECONNECTS; /*don't use windows login info or anything*/ - - cred = SSPI_GetServerCertificate(); - SchannelCred.cCreds = 1; - SchannelCred.paCred = &cred; - - if (!cred) - { - SSPI_Error(f, "Unable to load/generate certificate\n"); - return; - } - - ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_INBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime); - if (ss < 0) - { - SSPI_Error(f, "AcquireCredentialsHandle failed\n"); - return; - } -} - -static void SSPI_Handshake (sslfile_t *f) -{ - SECURITY_STATUS ss; - TimeStamp Lifetime; - SecBufferDesc OutBuffDesc; - SecBuffer OutSecBuff[8]; - SecBufferDesc InBuffDesc; - SecBuffer InSecBuff[8]; - ULONG ContextAttributes; - SCHANNEL_CRED SchannelCred; - int i; - qboolean retries = 5; - -// char buf1[128]; -// char buf2[128]; - -retry: - - if (f->outcrypt.avail) - { - //don't let things build up too much - SSPI_TryFlushCryptOut(f); - if (f->outcrypt.avail) - return; - } - - //FIXME: skip this if we've had no new data since last time - - OutBuffDesc.ulVersion = SECBUFFER_VERSION; - OutBuffDesc.cBuffers = countof(OutSecBuff); - OutBuffDesc.pBuffers = OutSecBuff; - - OutSecBuff[0].BufferType = SECBUFFER_TOKEN; - OutSecBuff[0].cbBuffer = f->outcrypt.datasize - f->outcrypt.avail; - OutSecBuff[0].pvBuffer = f->outcrypt.data + f->outcrypt.avail; - - for (i = 0; i < OutBuffDesc.cBuffers; i++) - { - OutSecBuff[i].BufferType = SECBUFFER_EMPTY; - OutSecBuff[i].pvBuffer = NULL; - OutSecBuff[i].cbBuffer = 0; - } - - if (f->handshaking == HS_ERROR) - return; //gave up. - else if (f->handshaking == HS_STARTCLIENT) - { - //no input data yet. - f->handshaking = HS_CLIENT; - - memset(&SchannelCred, 0, sizeof(SchannelCred)); - SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; - SchannelCred.grbitEnabledProtocols = f->datagram?USE_PROT_DGRAM_CLIENT:USE_PROT_CLIENT; - SchannelCred.dwFlags |= SCH_CRED_SNI_CREDENTIAL | SCH_CRED_NO_DEFAULT_CREDS; /*don't use windows login info or anything*/ - - ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime); - if (ss < 0) - { - SSPI_Error(f, "AcquireCredentialsHandle failed\n"); - return; - } - - ss = secur.pInitializeSecurityContextW (&f->cred, NULL, f->wpeername, MessageAttribute|(f->datagram?ISC_REQ_DATAGRAM:ISC_REQ_STREAM), 0, SECURITY_NATIVE_DREP, NULL, 0, &f->sechnd, &OutBuffDesc, &ContextAttributes, &Lifetime); - } - else if (f->handshaking == HS_CLIENT) - { - //only if we actually have data. - if (!f->incrypt.avail && !f->datagram) - return; - - InBuffDesc.ulVersion = SECBUFFER_VERSION; - InBuffDesc.cBuffers = 4; - InBuffDesc.pBuffers = InSecBuff; - - i = 0; - - if (f->incrypt.avail) - { - InSecBuff[i].BufferType = SECBUFFER_TOKEN; - InSecBuff[i].cbBuffer = f->incrypt.avail; - InSecBuff[i].pvBuffer = f->incrypt.data; - i++; - } - - for (; i < InBuffDesc.cBuffers; i++) - { - InSecBuff[i].BufferType = SECBUFFER_EMPTY; - InSecBuff[i].pvBuffer = NULL; - InSecBuff[i].cbBuffer = 0; - } - - ss = secur.pInitializeSecurityContextW (&f->cred, &f->sechnd, NULL, MessageAttribute|(f->datagram?ISC_REQ_DATAGRAM:ISC_REQ_STREAM), 0, SECURITY_NETWORK_DREP, &InBuffDesc, 0, &f->sechnd, &OutBuffDesc, &ContextAttributes, &Lifetime); - - if (ss == SEC_E_INCOMPLETE_MESSAGE) - { -// Con_Printf("SEC_E_INCOMPLETE_MESSAGE\n"); - if (!f->datagram && f->incrypt.avail == f->incrypt.datasize) - SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024); - return; - } - else if (ss == SEC_E_INVALID_TOKEN) - { -// Con_Printf("SEC_E_INVALID_TOKEN\n"); - if (f->datagram) - return; //our udp protocol may have non-dtls packets mixed in. besides, we don't want to die from spoofed packets. - } -// else if (ss == SEC_I_MESSAGE_FRAGMENT) -// Con_Printf("SEC_I_MESSAGE_FRAGMENT\n"); -// else if (ss == SEC_I_CONTINUE_NEEDED) -// Con_Printf("SEC_I_CONTINUE_NEEDED\n"); -// else -// Con_Printf("InitializeSecurityContextA %x\n", ss); - - - //any extra data should still remain for the next time around. this might be more handshake data or payload data. - if (InSecBuff[1].BufferType == SECBUFFER_EXTRA) - { - memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - InSecBuff[1].cbBuffer), InSecBuff[1].cbBuffer); - f->incrypt.avail = InSecBuff[1].cbBuffer; - } - else f->incrypt.avail = 0; - } - else if (f->handshaking == HS_STARTSERVER || f->handshaking == HS_SERVER) - { - //only if we actually have data. - if (!f->incrypt.avail) - return; - - InBuffDesc.ulVersion = SECBUFFER_VERSION; - InBuffDesc.cBuffers = countof(InSecBuff); - InBuffDesc.pBuffers = InSecBuff; - i = 0; - - if (f->incrypt.avail) - { - InSecBuff[i].BufferType = SECBUFFER_TOKEN; - InSecBuff[i].cbBuffer = f->incrypt.avail; - InSecBuff[i].pvBuffer = f->incrypt.data; - i++; - } - - for (; i < InBuffDesc.cBuffers; i++) - { - InSecBuff[i].BufferType = SECBUFFER_EMPTY; - InSecBuff[i].pvBuffer = NULL; - InSecBuff[i].cbBuffer = 0; - } - - i = 1; - OutSecBuff[i++].BufferType = SECBUFFER_EXTRA; - OutSecBuff[i++].BufferType = 17/*SECBUFFER_ALERT*/; - -#define ServerMessageAttribute (ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY /*| ASC_REQ_EXTENDED_ERROR*/ | ASC_REQ_ALLOCATE_MEMORY) - - ss = secur.pAcceptSecurityContext(&f->cred, (f->handshaking==HS_SERVER)?&f->sechnd:NULL, &InBuffDesc, - ServerMessageAttribute|(f->datagram?ASC_REQ_DATAGRAM:ASC_REQ_STREAM), SECURITY_NETWORK_DREP, &f->sechnd, - &OutBuffDesc, &ContextAttributes, NULL); - if (ss == SEC_E_INVALID_TOKEN) - { -// Con_Printf("SEC_E_INVALID_TOKEN\n"); - if (f->datagram) - return; - } - else if (ss == SEC_E_INCOMPLETE_MESSAGE) - { -// Con_Printf("SEC_E_INCOMPLETE_MESSAGE\n"); - if (!f->datagram && f->incrypt.avail == f->incrypt.datasize) - SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024); - return; - } -// else -// Con_Printf("InitializeSecurityContextA %x\n", ss); - f->handshaking = HS_SERVER; - - //any extra data should still remain for the next time around. this might be more handshake data or payload data. - if (InSecBuff[1].BufferType == SECBUFFER_EXTRA) - { - memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - InSecBuff[1].cbBuffer), InSecBuff[1].cbBuffer); - f->incrypt.avail = InSecBuff[1].cbBuffer; - } - else f->incrypt.avail = 0; - } - else - return; - - - if (ss == SEC_I_INCOMPLETE_CREDENTIALS) - { - SSPI_Error(f, "server requires credentials\n"); - return; - } - - if (ss < 0) - { - switch(ss) - { - case SEC_E_ALGORITHM_MISMATCH: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_ALGORITHM_MISMATCH\n"); break; - case SEC_E_INVALID_HANDLE: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_INVALID_HANDLE\n"); break; - case SEC_E_ILLEGAL_MESSAGE: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE\n"); break; - case SEC_E_INVALID_TOKEN: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_INVALID_TOKEN\n"); break; - case SEC_E_INVALID_PARAMETER: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_INVALID_PARAMETER\n"); break; - default: SSPI_Error(f, "InitializeSecurityContext failed: %lx\n", (long)ss); break; - } - return; - } - - if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss)) - { - ss = secur.pCompleteAuthToken (&f->sechnd, &OutBuffDesc); - if (ss < 0) - { - SSPI_Error(f, "CompleteAuthToken failed\n"); - return; - } - } - - //its all okay and established if we get this far. - if (ss == SEC_E_OK) - { - SecPkgContext_StreamSizes strsizes; - CERT_CONTEXT *remotecert; - - secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_STREAM_SIZES, &strsizes); - f->headersize = strsizes.cbHeader; - f->footersize = strsizes.cbTrailer; - if (f->handshaking != HS_SERVER) - { //server takes an annonymous client. client expects a proper certificate. - if (*f->wpeername) - { - ss = secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &remotecert); - if (ss != SEC_E_OK) - { - f->handshaking = HS_ERROR; - SSPI_Error(f, "unable to read server's certificate\n"); - return; - } - if (VerifyServerCertificate(remotecert, f->wpeername, 0, f->datagram)) - { - f->handshaking = HS_ERROR; - SSPI_Error(f, "Error validating certificante\n"); - return; - } - } - else - Sys_Printf("SSL/TLS Server name not specified, skipping verification\n"); - } - - f->handshaking = HS_ESTABLISHED; - } - - //send early, send often. -#ifdef HAVE_DTLS - if (f->transmit) - { - for (i = 0; i < OutBuffDesc.cBuffers; i++) - if (OutSecBuff[i].BufferType == SECBUFFER_TOKEN && OutSecBuff[i].cbBuffer) - f->transmit(f->cbctx, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer); - } - else -#endif - { - i = 0; - if (SSPI_CopyIntoBuffer(&f->outcrypt, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer, true) < OutSecBuff[i].cbBuffer) - { - SSPI_Error(f, "crypt overflow\n"); - return; - } - SSPI_TryFlushCryptOut(f); - } - - if (f->handshaking == HS_ESTABLISHED) - SSPI_Encode(f); - else if (ss == SEC_I_MESSAGE_FRAGMENT) //looks like we can connect faster if we loop when we get this result. - if (retries --> 0) - goto retry; -} - -static int QDECL SSPI_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) -{ - sslfile_t *f = (sslfile_t *)file; - int err = SSPI_CheckNewInCrypt(f); - - if (f->handshaking) - { - SSPI_Handshake(f); - return err; - } - - SSPI_Encode(f); - - SSPI_Decode(f); - - bytestoread = min(bytestoread, f->inraw.avail); - if (bytestoread) - { - memcpy(buffer, f->inraw.data, bytestoread); - f->inraw.avail -= bytestoread; - memmove(f->inraw.data, f->inraw.data + bytestoread, f->inraw.avail); - } - else - { - if (err) - return err; - } - return bytestoread; -} -static int QDECL SSPI_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite) -{ - sslfile_t *f = (sslfile_t *)file; - - //don't endlessly accept data faster than we can push it out. - //we'll buffer a little, but don't go overboard - if (f->outcrypt.avail > 8192) - return false; - - bytestowrite = SSPI_CopyIntoBuffer(&f->outraw, buffer, bytestowrite, false); - - if (f->handshaking) - { - SSPI_CheckNewInCrypt(f); //make sure its ticking over - SSPI_Handshake(f); - } - else - { - SSPI_Encode(f); - } - - return bytestowrite; -} -static qboolean QDECL SSPI_Seek (struct vfsfile_s *file, qofs_t pos) -{ - SSPI_Error((sslfile_t*)file, "unable to seek on streams\n"); - return false; -} -static qofs_t QDECL SSPI_Tell (struct vfsfile_s *file) -{ - SSPI_Error((sslfile_t*)file, "unable to seek on streams\n"); - return 0; -} -static qofs_t QDECL SSPI_GetLen (struct vfsfile_s *file) -{ - return 0; -} -static qboolean QDECL SSPI_Close (struct vfsfile_s *file) -{ - sslfile_t *f = (sslfile_t *)file; - qboolean success = f->stream != NULL; - SSPI_Error(f, ""); - - Z_Free(f->outraw.data); - Z_Free(f->outcrypt.data); - Z_Free(f->inraw.data); - Z_Free(f->incrypt.data); - - Z_Free(f); - return success; -} - -#include -vfsfile_t *FS_OpenSSL(const char *servername, vfsfile_t *source, qboolean server) -{ - sslfile_t *newf; - int i = 0; - int err; - unsigned int c; - const char *localname, *peername; - - if (!source || !SSL_Inited()) - { - if (source) - VFS_CLOSE(source); - return NULL; - } - if (server) - { - localname = servername; - peername = ""; - } - else - { - localname = ""; - peername = servername; - } - -/* - if (server) //unsupported - { - VFS_CLOSE(source); - return NULL; - } -*/ - - newf = Z_Malloc(sizeof(*newf)); - while(*peername) - { - c = utf8_decode(&err, peername, (void*)&peername); - if (c > WCHAR_MAX) - err = true; //no 16bit surrogates. they're evil. - else if (i == sizeof(newf->wpeername)/sizeof(newf->wpeername[0]) - 1) - err = true; //no space to store it - else - newf->wpeername[i++] = c; - if (err) - { - Z_Free(newf); - VFS_CLOSE(source); - return NULL; - } - } - newf->wpeername[i] = 0; - - newf->handshaking = server?HS_STARTSERVER:HS_STARTCLIENT; - newf->stream = source; - newf->funcs.Close = SSPI_Close; - newf->funcs.Flush = NULL; - newf->funcs.GetLen = SSPI_GetLen; - newf->funcs.ReadBytes = SSPI_ReadBytes; - newf->funcs.Seek = SSPI_Seek; - newf->funcs.Tell = SSPI_Tell; - newf->funcs.WriteBytes = SSPI_WriteBytes; - newf->funcs.seekstyle = SS_UNSEEKABLE; - - SSPI_ExpandBuffer(&newf->outraw, 8192); - SSPI_ExpandBuffer(&newf->outcrypt, 8192); - SSPI_ExpandBuffer(&newf->inraw, 8192); - SSPI_ExpandBuffer(&newf->incrypt, 8192); - - if (server) - SSPI_GenServerCredentials(newf); - - return &newf->funcs; -} - - -#include "netinc.h" -#if 0 -struct fakedtls_s -{ - void *cbctx; - neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize); -}; -static void *FAKEDTLS_CreateContext(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) -{ - struct fakedtls_s *ctx = Z_Malloc(sizeof(*ctx)); - ctx->cbctx = cbctx; - ctx->push = push; - return ctx; -} -static void FAKEDTLS_DestroyContext(void *vctx) -{ - Z_Free(vctx); -} -static neterr_t FAKEDTLS_Transmit(void *vctx, const qbyte *data, size_t datasize) -{ - struct fakedtls_s *ctx = vctx; - neterr_t r; - *(int*)data ^= 0xdeadbeef; - r = ctx->push(ctx->cbctx, data, datasize); - *(int*)data ^= 0xdeadbeef; - return r; -} -static neterr_t FAKEDTLS_Received(void *ctx, qbyte *data, size_t datasize) -{ - *(int*)data ^= 0xdeadbeef; - return NETERR_SENT; -} -static neterr_t FAKEDTLS_Timeouts(void *ctx) -{ -// fakedtls_s *f = (fakedtls_s *)ctx; - return NETERR_SENT; -} -static const dtlsfuncs_t dtlsfuncs_fakedtls = -{ - FAKEDTLS_CreateContext, + ); + } + + Z_Free(issuerblob.pbData); + return ret; +} + +static void SSPI_GenServerCredentials(sslfile_t *f) +{ + SECURITY_STATUS ss; + TimeStamp Lifetime; + SCHANNEL_CRED SchannelCred; + PCCERT_CONTEXT cred; + + memset(&SchannelCred, 0, sizeof(SchannelCred)); + SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; + SchannelCred.grbitEnabledProtocols = f->datagram?USE_PROT_DGRAM_SERVER:USE_PROT_SERVER; + SchannelCred.dwFlags |= SCH_CRED_NO_SYSTEM_MAPPER|SCH_CRED_DISABLE_RECONNECTS; /*don't use windows login info or anything*/ + + cred = SSPI_GetServerCertificate(); + SchannelCred.cCreds = 1; + SchannelCred.paCred = &cred; + + if (!cred) + { + SSPI_Error(f, "Unable to load/generate certificate\n"); + return; + } + + ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_INBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime); + if (ss < 0) + { + SSPI_Error(f, "AcquireCredentialsHandle failed\n"); + return; + } +} + +static void SSPI_Handshake (sslfile_t *f) +{ + SECURITY_STATUS ss; + TimeStamp Lifetime; + SecBufferDesc OutBuffDesc; + SecBuffer OutSecBuff[8]; + SecBufferDesc InBuffDesc; + SecBuffer InSecBuff[8]; + ULONG ContextAttributes; + SCHANNEL_CRED SchannelCred; + int i; + qboolean retries = 5; + +// char buf1[128]; +// char buf2[128]; + +retry: + + if (f->outcrypt.avail) + { + //don't let things build up too much + SSPI_TryFlushCryptOut(f); + if (f->outcrypt.avail) + return; + } + + //FIXME: skip this if we've had no new data since last time + + OutBuffDesc.ulVersion = SECBUFFER_VERSION; + OutBuffDesc.cBuffers = countof(OutSecBuff); + OutBuffDesc.pBuffers = OutSecBuff; + + OutSecBuff[0].BufferType = SECBUFFER_TOKEN; + OutSecBuff[0].cbBuffer = f->outcrypt.datasize - f->outcrypt.avail; + OutSecBuff[0].pvBuffer = f->outcrypt.data + f->outcrypt.avail; + + for (i = 0; i < OutBuffDesc.cBuffers; i++) + { + OutSecBuff[i].BufferType = SECBUFFER_EMPTY; + OutSecBuff[i].pvBuffer = NULL; + OutSecBuff[i].cbBuffer = 0; + } + + if (f->handshaking == HS_ERROR) + return; //gave up. + else if (f->handshaking == HS_STARTCLIENT) + { + //no input data yet. + f->handshaking = HS_CLIENT; + + memset(&SchannelCred, 0, sizeof(SchannelCred)); + SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; + SchannelCred.grbitEnabledProtocols = f->datagram?USE_PROT_DGRAM_CLIENT:USE_PROT_CLIENT; + SchannelCred.dwFlags |= SCH_CRED_SNI_CREDENTIAL | SCH_CRED_NO_DEFAULT_CREDS; /*don't use windows login info or anything*/ + + ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime); + if (ss < 0) + { + SSPI_Error(f, "AcquireCredentialsHandle failed\n"); + return; + } + + ss = secur.pInitializeSecurityContextW (&f->cred, NULL, f->wpeername, MessageAttribute|(f->datagram?ISC_REQ_DATAGRAM:ISC_REQ_STREAM), 0, SECURITY_NATIVE_DREP, NULL, 0, &f->sechnd, &OutBuffDesc, &ContextAttributes, &Lifetime); + } + else if (f->handshaking == HS_CLIENT) + { + //only if we actually have data. + if (!f->incrypt.avail && !f->datagram) + return; + + InBuffDesc.ulVersion = SECBUFFER_VERSION; + InBuffDesc.cBuffers = 4; + InBuffDesc.pBuffers = InSecBuff; + + i = 0; + + if (f->incrypt.avail) + { + InSecBuff[i].BufferType = SECBUFFER_TOKEN; + InSecBuff[i].cbBuffer = f->incrypt.avail; + InSecBuff[i].pvBuffer = f->incrypt.data; + i++; + } + + for (; i < InBuffDesc.cBuffers; i++) + { + InSecBuff[i].BufferType = SECBUFFER_EMPTY; + InSecBuff[i].pvBuffer = NULL; + InSecBuff[i].cbBuffer = 0; + } + + ss = secur.pInitializeSecurityContextW (&f->cred, &f->sechnd, NULL, MessageAttribute|(f->datagram?ISC_REQ_DATAGRAM:ISC_REQ_STREAM), 0, SECURITY_NETWORK_DREP, &InBuffDesc, 0, &f->sechnd, &OutBuffDesc, &ContextAttributes, &Lifetime); + + if (ss == SEC_E_INCOMPLETE_MESSAGE) + { +// Con_Printf("SEC_E_INCOMPLETE_MESSAGE\n"); + if (!f->datagram && f->incrypt.avail == f->incrypt.datasize) + SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024); + return; + } + else if (ss == SEC_E_INVALID_TOKEN) + { +// Con_Printf("SEC_E_INVALID_TOKEN\n"); + if (f->datagram) + return; //our udp protocol may have non-dtls packets mixed in. besides, we don't want to die from spoofed packets. + } +// else if (ss == SEC_I_MESSAGE_FRAGMENT) +// Con_Printf("SEC_I_MESSAGE_FRAGMENT\n"); +// else if (ss == SEC_I_CONTINUE_NEEDED) +// Con_Printf("SEC_I_CONTINUE_NEEDED\n"); +// else +// Con_Printf("InitializeSecurityContextA %x\n", ss); + + + //any extra data should still remain for the next time around. this might be more handshake data or payload data. + if (InSecBuff[1].BufferType == SECBUFFER_EXTRA) + { + memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - InSecBuff[1].cbBuffer), InSecBuff[1].cbBuffer); + f->incrypt.avail = InSecBuff[1].cbBuffer; + } + else f->incrypt.avail = 0; + } + else if (f->handshaking == HS_STARTSERVER || f->handshaking == HS_SERVER) + { + //only if we actually have data. + if (!f->incrypt.avail) + return; + + InBuffDesc.ulVersion = SECBUFFER_VERSION; + InBuffDesc.cBuffers = countof(InSecBuff); + InBuffDesc.pBuffers = InSecBuff; + i = 0; + + if (f->incrypt.avail) + { + InSecBuff[i].BufferType = SECBUFFER_TOKEN; + InSecBuff[i].cbBuffer = f->incrypt.avail; + InSecBuff[i].pvBuffer = f->incrypt.data; + i++; + } + + for (; i < InBuffDesc.cBuffers; i++) + { + InSecBuff[i].BufferType = SECBUFFER_EMPTY; + InSecBuff[i].pvBuffer = NULL; + InSecBuff[i].cbBuffer = 0; + } + + i = 1; + OutSecBuff[i++].BufferType = SECBUFFER_EXTRA; + OutSecBuff[i++].BufferType = 17/*SECBUFFER_ALERT*/; + +#define ServerMessageAttribute (ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY /*| ASC_REQ_EXTENDED_ERROR*/ | ASC_REQ_ALLOCATE_MEMORY) + + ss = secur.pAcceptSecurityContext(&f->cred, (f->handshaking==HS_SERVER)?&f->sechnd:NULL, &InBuffDesc, + ServerMessageAttribute|(f->datagram?ASC_REQ_DATAGRAM:ASC_REQ_STREAM), SECURITY_NETWORK_DREP, &f->sechnd, + &OutBuffDesc, &ContextAttributes, NULL); + if (ss == SEC_E_INVALID_TOKEN) + { +// Con_Printf("SEC_E_INVALID_TOKEN\n"); + if (f->datagram) + return; + } + else if (ss == SEC_E_INCOMPLETE_MESSAGE) + { +// Con_Printf("SEC_E_INCOMPLETE_MESSAGE\n"); + if (!f->datagram && f->incrypt.avail == f->incrypt.datasize) + SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024); + return; + } +// else +// Con_Printf("InitializeSecurityContextA %x\n", ss); + f->handshaking = HS_SERVER; + + //any extra data should still remain for the next time around. this might be more handshake data or payload data. + if (InSecBuff[1].BufferType == SECBUFFER_EXTRA) + { + memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - InSecBuff[1].cbBuffer), InSecBuff[1].cbBuffer); + f->incrypt.avail = InSecBuff[1].cbBuffer; + } + else f->incrypt.avail = 0; + } + else + return; + + + if (ss == SEC_I_INCOMPLETE_CREDENTIALS) + { + SSPI_Error(f, "server requires credentials\n"); + return; + } + + if (ss < 0) + { + switch(ss) + { + case SEC_E_ALGORITHM_MISMATCH: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_ALGORITHM_MISMATCH\n"); break; + case SEC_E_INVALID_HANDLE: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_INVALID_HANDLE\n"); break; + case SEC_E_ILLEGAL_MESSAGE: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE\n"); break; + case SEC_E_INVALID_TOKEN: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_INVALID_TOKEN\n"); break; + case SEC_E_INVALID_PARAMETER: SSPI_Error(f, "InitializeSecurityContext failed: SEC_E_INVALID_PARAMETER\n"); break; + default: SSPI_Error(f, "InitializeSecurityContext failed: %lx\n", (long)ss); break; + } + return; + } + + if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss)) + { + ss = secur.pCompleteAuthToken (&f->sechnd, &OutBuffDesc); + if (ss < 0) + { + SSPI_Error(f, "CompleteAuthToken failed\n"); + return; + } + } + + //its all okay and established if we get this far. + if (ss == SEC_E_OK) + { + SecPkgContext_StreamSizes strsizes; + CERT_CONTEXT *remotecert; + + secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_STREAM_SIZES, &strsizes); + f->headersize = strsizes.cbHeader; + f->footersize = strsizes.cbTrailer; + if (f->handshaking != HS_SERVER) + { //server takes an annonymous client. client expects a proper certificate. + if (*f->wpeername) + { + ss = secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &remotecert); + if (ss != SEC_E_OK) + { + f->handshaking = HS_ERROR; + SSPI_Error(f, "unable to read server's certificate\n"); + return; + } + if (VerifyServerCertificate(remotecert, f->wpeername, 0, f->datagram)) + { + f->handshaking = HS_ERROR; + SSPI_Error(f, "Error validating certificante\n"); + return; + } + } + else + Sys_Printf("SSL/TLS Server name not specified, skipping verification\n"); + } + + f->handshaking = HS_ESTABLISHED; + } + + //send early, send often. +#ifdef HAVE_DTLS + if (f->transmit) + { + for (i = 0; i < OutBuffDesc.cBuffers; i++) + if (OutSecBuff[i].BufferType == SECBUFFER_TOKEN && OutSecBuff[i].cbBuffer) + f->transmit(f->cbctx, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer); + } + else +#endif + { + i = 0; + if (SSPI_CopyIntoBuffer(&f->outcrypt, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer, true) < OutSecBuff[i].cbBuffer) + { + SSPI_Error(f, "crypt overflow\n"); + return; + } + SSPI_TryFlushCryptOut(f); + } + + if (f->handshaking == HS_ESTABLISHED) + SSPI_Encode(f); + else if (ss == SEC_I_MESSAGE_FRAGMENT) //looks like we can connect faster if we loop when we get this result. + if (retries --> 0) + goto retry; +} + +static int QDECL SSPI_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) +{ + sslfile_t *f = (sslfile_t *)file; + int err = SSPI_CheckNewInCrypt(f); + + if (f->handshaking) + { + SSPI_Handshake(f); + return err; + } + + SSPI_Encode(f); + + SSPI_Decode(f); + + bytestoread = min(bytestoread, f->inraw.avail); + if (bytestoread) + { + memcpy(buffer, f->inraw.data, bytestoread); + f->inraw.avail -= bytestoread; + memmove(f->inraw.data, f->inraw.data + bytestoread, f->inraw.avail); + } + else + { + if (err) + return err; + } + return bytestoread; +} +static int QDECL SSPI_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite) +{ + sslfile_t *f = (sslfile_t *)file; + + //don't endlessly accept data faster than we can push it out. + //we'll buffer a little, but don't go overboard + if (f->outcrypt.avail > 8192) + return false; + + bytestowrite = SSPI_CopyIntoBuffer(&f->outraw, buffer, bytestowrite, false); + + if (f->handshaking) + { + SSPI_CheckNewInCrypt(f); //make sure its ticking over + SSPI_Handshake(f); + } + else + { + SSPI_Encode(f); + } + + return bytestowrite; +} +static qboolean QDECL SSPI_Seek (struct vfsfile_s *file, qofs_t pos) +{ + SSPI_Error((sslfile_t*)file, "unable to seek on streams\n"); + return false; +} +static qofs_t QDECL SSPI_Tell (struct vfsfile_s *file) +{ + SSPI_Error((sslfile_t*)file, "unable to seek on streams\n"); + return 0; +} +static qofs_t QDECL SSPI_GetLen (struct vfsfile_s *file) +{ + return 0; +} +static qboolean QDECL SSPI_Close (struct vfsfile_s *file) +{ + sslfile_t *f = (sslfile_t *)file; + qboolean success = f->stream != NULL; + SSPI_Error(f, ""); + + Z_Free(f->outraw.data); + Z_Free(f->outcrypt.data); + Z_Free(f->inraw.data); + Z_Free(f->incrypt.data); + + Z_Free(f); + return success; +} + +#include +vfsfile_t *FS_OpenSSL(const char *servername, vfsfile_t *source, qboolean server) +{ + sslfile_t *newf; + int i = 0; + int err; + unsigned int c; +// const char *localname; + const char *peername; + + if (!source || !SSL_Inited()) + { + if (source) + VFS_CLOSE(source); + return NULL; + } + if (server) + { +// localname = servername; + peername = ""; + } + else + { +// localname = ""; + peername = servername; + } + +/* + if (server) //unsupported + { + VFS_CLOSE(source); + return NULL; + } +*/ + + newf = Z_Malloc(sizeof(*newf)); + while(*peername) + { + c = utf8_decode(&err, peername, (void*)&peername); + if (c > WCHAR_MAX) + err = true; //no 16bit surrogates. they're evil. + else if (i == sizeof(newf->wpeername)/sizeof(newf->wpeername[0]) - 1) + err = true; //no space to store it + else + newf->wpeername[i++] = c; + if (err) + { + Z_Free(newf); + VFS_CLOSE(source); + return NULL; + } + } + newf->wpeername[i] = 0; + + newf->handshaking = server?HS_STARTSERVER:HS_STARTCLIENT; + newf->stream = source; + newf->funcs.Close = SSPI_Close; + newf->funcs.Flush = NULL; + newf->funcs.GetLen = SSPI_GetLen; + newf->funcs.ReadBytes = SSPI_ReadBytes; + newf->funcs.Seek = SSPI_Seek; + newf->funcs.Tell = SSPI_Tell; + newf->funcs.WriteBytes = SSPI_WriteBytes; + newf->funcs.seekstyle = SS_UNSEEKABLE; + + SSPI_ExpandBuffer(&newf->outraw, 8192); + SSPI_ExpandBuffer(&newf->outcrypt, 8192); + SSPI_ExpandBuffer(&newf->inraw, 8192); + SSPI_ExpandBuffer(&newf->incrypt, 8192); + + if (server) + SSPI_GenServerCredentials(newf); + + return &newf->funcs; +} + +#ifndef SECPKG_ATTR_UNIQUE_BINDINGS +#define SECPKG_ATTR_UNIQUE_BINDINGS 25 +typedef struct _SecPkgContext_Bindings +{ + unsigned long BindingsLength; + SEC_CHANNEL_BINDINGS *Bindings; +} SecPkgContext_Bindings, *PSecPkgContext_Bindings; +#endif +int TLS_GetChannelBinding(vfsfile_t *vf, qbyte *binddata, size_t *bindsize) +{ + int ret; + sslfile_t *f = (sslfile_t*)vf; + SecPkgContext_Bindings bindings; + if (vf->Close != SSPI_Close) + return -2; //not one of ours. + + bindings.BindingsLength = 0; + bindings.Bindings = NULL; + ret = 0; + switch(secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_UNIQUE_BINDINGS, &bindings)) + { + case SEC_E_OK: + if (bindings.Bindings->cbApplicationDataLength <= *bindsize) + { + //will contain 'tls-unique:BINARYDATA' + *bindsize = bindings.Bindings->cbApplicationDataLength-11; + memcpy(binddata, ((unsigned char*) bindings.Bindings) + bindings.Bindings->dwApplicationDataOffset+11, bindings.Bindings->cbApplicationDataLength-11); + ret = 1; + } + //FIXME: leak + //secur.pFreeContextBuffer(bindings.Bindings); + break; + case SEC_E_UNSUPPORTED_FUNCTION: + ret = -1; //schannel doesn't support it. too old an OS, I guess. + break; + default: + break; + } + return ret; +} + +#include "netinc.h" +#if 0 +struct fakedtls_s +{ + void *cbctx; + neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize); +}; +static void *FAKEDTLS_CreateContext(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) +{ + struct fakedtls_s *ctx = Z_Malloc(sizeof(*ctx)); + ctx->cbctx = cbctx; + ctx->push = push; + return ctx; +} +static void FAKEDTLS_DestroyContext(void *vctx) +{ + Z_Free(vctx); +} +static neterr_t FAKEDTLS_Transmit(void *vctx, const qbyte *data, size_t datasize) +{ + struct fakedtls_s *ctx = vctx; + neterr_t r; + *(int*)data ^= 0xdeadbeef; + r = ctx->push(ctx->cbctx, data, datasize); + *(int*)data ^= 0xdeadbeef; + return r; +} +static neterr_t FAKEDTLS_Received(void *ctx, qbyte *data, size_t datasize) +{ + *(int*)data ^= 0xdeadbeef; + return NETERR_SENT; +} +static neterr_t FAKEDTLS_Timeouts(void *ctx) +{ +// fakedtls_s *f = (fakedtls_s *)ctx; + return NETERR_SENT; +} +static const dtlsfuncs_t dtlsfuncs_fakedtls = +{ + FAKEDTLS_CreateContext, FAKEDTLS_DestroyContext, FAKEDTLS_Transmit, FAKEDTLS_Received, - FAKEDTLS_Timeouts, -}; -const dtlsfuncs_t *FAKEDTLS_InitServer(void) -{ - return &dtlsfuncs_fakedtls; -} -const dtlsfuncs_t *FAKEDTLS_InitClient(void) -{ - return &dtlsfuncs_fakedtls; -} -#elif defined(HAVE_DTLS) -static void *SSPI_DTLS_CreateContext(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) -{ - int i = 0; - sslfile_t *ctx; - if (!SSL_Inited()) - return NULL; - - ctx = Z_Malloc(sizeof(*ctx)); - ctx->datagram = true; - ctx->handshaking = isserver?HS_STARTSERVER:HS_STARTCLIENT; - ctx->cbctx = cbctx; - ctx->transmit = push; - - while(*remotehost) - { - int err; - int c = utf8_decode(&err, remotehost, (void*)&remotehost); - if (c > WCHAR_MAX) - err = true; //no 16bit surrogates. they're evil. - else if (i == sizeof(ctx->wpeername)/sizeof(ctx->wpeername[0]) - 1) - err = true; //no space to store it - else - ctx->wpeername[i++] = c; - if (err) - { - Z_Free(ctx); - return NULL; - } - } - ctx->wpeername[i] = 0; - - SSPI_ExpandBuffer(&ctx->outraw, 8192); - SSPI_ExpandBuffer(&ctx->outcrypt, 65536); - SSPI_ExpandBuffer(&ctx->inraw, 8192); - SSPI_ExpandBuffer(&ctx->incrypt, 65536); - - if (isserver) - SSPI_GenServerCredentials(ctx); - else - SSPI_Handshake(ctx); //begin the initial handshake now - return ctx; -} - -static void SSPI_DTLS_DestroyContext(void *vctx) -{ - SSPI_Close(vctx); -} - - -static neterr_t SSPI_DTLS_Transmit(void *ctx, const qbyte *data, size_t datasize) -{ - int ret; - sslfile_t *f = (sslfile_t *)ctx; - -//Con_Printf("DTLS_Transmit: %i\n", datasize); - - //sspi likes writing over the source data. make sure nothing is hurt by copying it out first. - f->outraw.avail = 0; - SSPI_CopyIntoBuffer(&f->outraw, data, datasize, true); - - if (f->handshaking) - { - SSPI_Handshake(f); - - if (f->handshaking == HS_ERROR) - ret = NETERR_DISCONNECTED; - ret = NETERR_CLOGGED; //not ready yet - } - else - { - SSPI_Encode(f); - ret = NETERR_SENT; - } - - return ret; -} - -static neterr_t SSPI_DTLS_Received(void *ctx, qbyte *data, size_t datasize) -{ - int ret; - sslfile_t *f = (sslfile_t *)ctx; - -//Con_Printf("DTLS_Received: %i\n", datasize); - - f->incrypt.data = data; - f->incrypt.avail = f->incrypt.datasize = datasize; - - if (f->handshaking) - { - SSPI_Handshake(f); - ret = NETERR_CLOGGED; //not ready yet - - if (f->handshaking == HS_ERROR) - ret = NETERR_DISCONNECTED; - } - else - { - SSPI_Decode(f); - ret = NETERR_SENT; - - memcpy(net_message_buffer, f->inraw.data, f->inraw.avail); - net_message.cursize = f->inraw.avail; - f->inraw.avail = 0; - - net_message_buffer[net_message.cursize] = 0; -// Con_Printf("returning %i bytes: %s\n", net_message.cursize, net_message_buffer); - } - f->incrypt.data = NULL; - return ret; -} -static neterr_t SSPI_DTLS_Timeouts(void *ctx) -{ - sslfile_t *f = (sslfile_t *)ctx; - if (f->handshaking) - { -// SSPI_Handshake(f); - return NETERR_CLOGGED; - } - return NETERR_SENT; -} - -static const dtlsfuncs_t dtlsfuncs_schannel = -{ - SSPI_DTLS_CreateContext, + FAKEDTLS_Timeouts, +}; +const dtlsfuncs_t *FAKEDTLS_InitServer(void) +{ + return &dtlsfuncs_fakedtls; +} +const dtlsfuncs_t *FAKEDTLS_InitClient(void) +{ + return &dtlsfuncs_fakedtls; +} +#elif defined(HAVE_DTLS) +static void *SSPI_DTLS_CreateContext(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) +{ + int i = 0; + sslfile_t *ctx; + if (!SSL_Inited()) + return NULL; + + ctx = Z_Malloc(sizeof(*ctx)); + ctx->datagram = true; + ctx->handshaking = isserver?HS_STARTSERVER:HS_STARTCLIENT; + ctx->cbctx = cbctx; + ctx->transmit = push; + + while(*remotehost) + { + int err; + int c = utf8_decode(&err, remotehost, (void*)&remotehost); + if (c > WCHAR_MAX) + err = true; //no 16bit surrogates. they're evil. + else if (i == sizeof(ctx->wpeername)/sizeof(ctx->wpeername[0]) - 1) + err = true; //no space to store it + else + ctx->wpeername[i++] = c; + if (err) + { + Z_Free(ctx); + return NULL; + } + } + ctx->wpeername[i] = 0; + + SSPI_ExpandBuffer(&ctx->outraw, 8192); + SSPI_ExpandBuffer(&ctx->outcrypt, 65536); + SSPI_ExpandBuffer(&ctx->inraw, 8192); + SSPI_ExpandBuffer(&ctx->incrypt, 65536); + + if (isserver) + SSPI_GenServerCredentials(ctx); + else + SSPI_Handshake(ctx); //begin the initial handshake now + return ctx; +} + +static void SSPI_DTLS_DestroyContext(void *vctx) +{ + SSPI_Close(vctx); +} + + +static neterr_t SSPI_DTLS_Transmit(void *ctx, const qbyte *data, size_t datasize) +{ + int ret; + sslfile_t *f = (sslfile_t *)ctx; + +//Con_Printf("DTLS_Transmit: %i\n", datasize); + + //sspi likes writing over the source data. make sure nothing is hurt by copying it out first. + f->outraw.avail = 0; + SSPI_CopyIntoBuffer(&f->outraw, data, datasize, true); + + if (f->handshaking) + { + SSPI_Handshake(f); + + if (f->handshaking == HS_ERROR) + ret = NETERR_DISCONNECTED; + ret = NETERR_CLOGGED; //not ready yet + } + else + { + SSPI_Encode(f); + ret = NETERR_SENT; + } + + return ret; +} + +static neterr_t SSPI_DTLS_Received(void *ctx, qbyte *data, size_t datasize) +{ + int ret; + sslfile_t *f = (sslfile_t *)ctx; + +//Con_Printf("DTLS_Received: %i\n", datasize); + + f->incrypt.data = data; + f->incrypt.avail = f->incrypt.datasize = datasize; + + if (f->handshaking) + { + SSPI_Handshake(f); + ret = NETERR_CLOGGED; //not ready yet + + if (f->handshaking == HS_ERROR) + ret = NETERR_DISCONNECTED; + } + else + { + SSPI_Decode(f); + ret = NETERR_SENT; + + memcpy(net_message_buffer, f->inraw.data, f->inraw.avail); + net_message.cursize = f->inraw.avail; + f->inraw.avail = 0; + + net_message_buffer[net_message.cursize] = 0; +// Con_Printf("returning %i bytes: %s\n", net_message.cursize, net_message_buffer); + } + f->incrypt.data = NULL; + return ret; +} +static neterr_t SSPI_DTLS_Timeouts(void *ctx) +{ + sslfile_t *f = (sslfile_t *)ctx; + if (f->handshaking) + { +// SSPI_Handshake(f); + return NETERR_CLOGGED; + } + return NETERR_SENT; +} + +static const dtlsfuncs_t dtlsfuncs_schannel = +{ + SSPI_DTLS_CreateContext, SSPI_DTLS_DestroyContext, SSPI_DTLS_Transmit, SSPI_DTLS_Received, - SSPI_DTLS_Timeouts, -}; -const dtlsfuncs_t *SSPI_DTLS_InitServer(void) -{ - //FIXME: at this point, schannel is still returning errors when I try acting as a server. - //so just block any attempt to use this as a server. - //clients don't need/get certs. - return NULL; -} -const dtlsfuncs_t *SSPI_DTLS_InitClient(void) -{ - return &dtlsfuncs_schannel; -} -#endif - -#endif + SSPI_DTLS_Timeouts, +}; +const dtlsfuncs_t *SSPI_DTLS_InitServer(void) +{ + //FIXME: at this point, schannel is still returning errors when I try acting as a server. + //so just block any attempt to use this as a server. + //clients don't need/get certs. + return NULL; +} +const dtlsfuncs_t *SSPI_DTLS_InitClient(void) +{ + return &dtlsfuncs_schannel; +} +#endif + +#endif diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index f7e3cd0c5..c8afb04ac 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -5033,7 +5033,7 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isse ftenet_tcpconnect_connection_t *newcon; unsigned long _true = true; - int newsocket; + SOCKET newsocket; qboolean tls = (adr.prot == NP_TLS || adr.prot == NP_WSS); #ifndef HAVE_SSL diff --git a/engine/common/netinc.h b/engine/common/netinc.h index 359e71751..0cce0ea03 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -347,6 +347,7 @@ qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *na int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses); vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server); +int TLS_GetChannelBinding(vfsfile_t *stream, qbyte *data, size_t *datasize); //datasize should be preinitialised to the max length allowed. -1 for not implemented. 0 for peer problems. 1 for success #ifdef HAVE_PACKET vfsfile_t *FS_OpenTCPSocket(SOCKET socket, qboolean conpending, const char *peername); //conpending allows us to reject any writes until the connection has succeeded #endif diff --git a/engine/common/plugin.c b/engine/common/plugin.c index 137a7d522..62395c06f 100644 --- a/engine/common/plugin.c +++ b/engine/common/plugin.c @@ -1093,6 +1093,36 @@ qintptr_t VARGS Plug_Net_SetTLSClient(void *offset, quintptr_t mask, const qintp } return 0; } + +qintptr_t VARGS Plug_Net_GetTLSBinding(void *offset, quintptr_t mask, const qintptr_t *arg) +{ + pluginstream_t *stream; + unsigned int handle = VM_LONG(arg[0]); + qbyte *binddata = VM_POINTER(arg[1]); + unsigned int *bindsize = VM_POINTER(arg[2]); + size_t sz; + int r; + if (VM_OOB(arg[2], sizeof(int))) + return -2; + if (VM_OOB(arg[1], *bindsize)) + return -2; + if (handle < 0 || handle >= pluginstreamarraylen || pluginstreamarray[handle].plugin != currentplug) + { + Con_Printf("Plug_Net_GetTLSBinding: socket does not belong to you (or is invalid)\n"); + return -2; + } + stream = &pluginstreamarray[handle]; + if (stream->type != STREAM_VFS) + { //not a socket - invalid + Con_Printf("Plug_Net_GetTLSBinding: Not a socket handle\n"); + return -2; + } + + sz = *bindsize; + r = TLS_GetChannelBinding(stream->vfs, binddata, &sz); + *bindsize = sz; + return r; +} #endif #endif @@ -1607,6 +1637,7 @@ void Plug_Initialise(qboolean fromgamedir) Plug_RegisterBuiltin("Net_TCPConnect", Plug_Net_TCPConnect, 0); #ifdef HAVE_SSL Plug_RegisterBuiltin("Net_SetTLSClient", Plug_Net_SetTLSClient, 0); + Plug_RegisterBuiltin("Net_GetTLSBinding", Plug_Net_GetTLSBinding, 0); #endif Plug_RegisterBuiltin("Net_Recv", Plug_Net_Recv, 0); Plug_RegisterBuiltin("Net_Send", Plug_Net_Send, 0); @@ -1746,7 +1777,7 @@ qboolean Plug_ConsoleLinkMouseOver(float x, float y, char *text, char *info) char *ptr; ptr = (char*)COM_QuotedString(text, buffer, sizeof(buffer)-10, false); ptr += strlen(ptr); - *ptr = ' '; + *ptr++ = ' '; COM_QuotedString(info, ptr, sizeof(buffer)-(ptr-buffer), false); Cmd_TokenizeString(buffer, false, false); diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index 889e97be0..9aa8600ed 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -4502,14 +4502,14 @@ void QCBUILTIN PF_uri_get (pubprogfuncs_t *prinst, struct globalvars_s *pr_glob G_FLOAT(OFS_RETURN) = 0; #else world_t *w = prinst->parms->user; - struct dl_download *dl; + struct dl_download *dl = NULL; const unsigned char *url = PR_GetStringOfs(prinst, OFS_PARM0); float id = G_FLOAT(OFS_PARM1); const char *mimetype = (prinst->callargc >= 3)?PR_GetStringOfs(prinst, OFS_PARM2):""; const char *dataorsep = (prinst->callargc >= 4)?PR_GetStringOfs(prinst, OFS_PARM3):""; int strbufid = (prinst->callargc >= 5)?G_FLOAT(OFS_PARM4):0; - //float cryptokey = (prinst->callargc >= 5)?G_FLOAT(OFS_PARM5):0; //DP feature, not supported in FTE. + //float cryptokeyidx = (prinst->callargc >= 5)?G_FLOAT(OFS_PARM5):0; //DP feature, not supported in FTE. adds a X-D0-Blind-ID-Detached-Signature header signing [postdata\0]querystring if (!pr_enable_uriget.ival) { @@ -4528,20 +4528,57 @@ void QCBUILTIN PF_uri_get (pubprogfuncs_t *prinst, struct globalvars_s *pr_glob if (*mimetype) { - const char *data; Con_DPrintf("PF_uri_post(%s,%g)\n", url, id); if (strbufid) { - //convert the string buffer into a simple string using dataorsep as a separator - //not supported at this time - dl = NULL; - Con_DPrintf("PF_uri_post: stringbuffers not supported\n"); + size_t bufno = strbufid-BUFSTRBASE; + if (bufno < strbufmax && strbuflist[bufno].prinst == prinst) + { + const char *glue = dataorsep; + unsigned int gluelen = strlen(dataorsep); + size_t datalen, l, i; + char **strings; + char *data; + + //count neededlength + strings = strbuflist[bufno].strings; + for (i = 0, datalen = 0; i < strbuflist[bufno].used; i++) + { + if (strings[i]) + { + if (datalen) + datalen += gluelen; + datalen += strlen(strings[i]); + } + } + + //concat it, with dataorsep separating each element + data = malloc(datalen+1); + for (i = 0, datalen = 0; i < strbuflist[bufno].used; i++) + { + if (strings[i]) + { + if (datalen) + { + memcpy(data+datalen, glue, gluelen); + datalen += gluelen; + } + l = strlen(strings[i]); + memcpy(data+datalen, strings[i], l); + datalen += l; + } + } + + //add the null and send it + data[datalen] = 0; + dl = HTTP_CL_Put(url, mimetype, data, strlen(data), PR_uri_get_callback); + free(data); + } } else { //simple data post. - data = dataorsep; - dl = HTTP_CL_Put(url, mimetype, data, strlen(data), PR_uri_get_callback); + dl = HTTP_CL_Put(url, mimetype, dataorsep, strlen(dataorsep), PR_uri_get_callback); } } else diff --git a/engine/common/sha1.c b/engine/common/sha1.c index f9ee56b1a..9c4e5d118 100644 --- a/engine/common/sha1.c +++ b/engine/common/sha1.c @@ -179,7 +179,7 @@ memset(&finalcount, 0, 8); } -int SHA1(char *digest, int maxdigestsize, const char *string, int stringlen) +size_t SHA1(unsigned char *digest, size_t maxdigestsize, const unsigned char *string, size_t stringlen) { SHA1_CTX context; if (maxdigestsize < DIGEST_SIZE) @@ -192,6 +192,21 @@ int SHA1(char *digest, int maxdigestsize, const char *string, int stringlen) return DIGEST_SIZE; } +size_t SHA1_m(unsigned char *digest, size_t maxdigestsize, size_t numstrings, const unsigned char **strings, size_t *stringlens) +{ + size_t i; + SHA1_CTX context; + if (maxdigestsize < DIGEST_SIZE) + return 0; + + SHA1Init(&context); + for (i = 0; i < numstrings; i++) + SHA1Update(&context, (unsigned char*) strings[i], stringlens[i]); + SHA1Final(digest, &context); + + return DIGEST_SIZE; +} + /* hmac-sha1.c -- hashed message authentication codes Copyright (C) 2005, 2006 Free Software Foundation, Inc. @@ -225,56 +240,45 @@ static void memxor(char *dest, const char *src, size_t length) } } -int SHA1_HMAC(unsigned char *digest, int maxdigestsize, - const unsigned char *data, int datalen, - const unsigned char *key, int keylen) +typedef size_t hashfunc_t(unsigned char *digest, size_t maxdigestsize, size_t numstrings, const unsigned char **strings, size_t *stringlens); +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) { - SHA1_CTX inner; - SHA1_CTX outer; - char optkeybuf[DIGEST_SIZE]; - char block[64]; - char innerhash[DIGEST_SIZE]; +#define HMAC_DIGEST_MAXSIZE 20 + char optkeybuf[HMAC_DIGEST_MAXSIZE]; + char innerhash[HMAC_DIGEST_MAXSIZE]; - if (maxdigestsize < DIGEST_SIZE) - return 0; + char block[64]; + int innerhashsize; /* Reduce the key's size, so that it is never larger than a block. */ - if (keylen > 64) + if (keylen > sizeof(block)) { - SHA1_CTX keyhash; - - SHA1Init (&keyhash); - SHA1Update (&keyhash, key, keylen); - SHA1Final (optkeybuf, &keyhash); - - key = optkeybuf; - keylen = sizeof(optkeybuf); + keylen = hashfunc(optkeybuf, sizeof(optkeybuf), 1, &key, &keylen); + key=optkeybuf; } /* Compute INNERHASH from KEY and IN. */ - SHA1Init (&inner); - memset (block, IPAD, sizeof (block)); memxor (block, key, keylen); - SHA1Update (&inner, block, 64); - SHA1Update (&inner, data, datalen); - - SHA1Final (innerhash, &inner); + { + const unsigned char *strings_i[2] = {block, data}; + size_t stringlens_i[2] = {sizeof(block), datalen}; + innerhashsize = hashfunc(innerhash, sizeof(innerhash), 2, strings_i, stringlens_i); + } /* Compute result from KEY and INNERHASH. */ - SHA1Init (&outer); - memset (block, OPAD, sizeof (block)); memxor (block, key, keylen); - SHA1Update (&outer, block, 64); - SHA1Update (&outer, innerhash, 20); - - SHA1Final (digest, &outer); - - return DIGEST_SIZE; + { + const unsigned char *strings_o[2] = {block, innerhash}; + size_t stringlens_o[2] = {sizeof(block), innerhashsize}; + return hashfunc(digest, maxdigestsize, 2, strings_o, stringlens_o); + } } diff --git a/engine/d3d/d3d11_backend.c b/engine/d3d/d3d11_backend.c index 71a37a7b3..9e77c4c09 100644 --- a/engine/d3d/d3d11_backend.c +++ b/engine/d3d/d3d11_backend.c @@ -1148,8 +1148,8 @@ static void SelectPassTexture(unsigned int tu, const shaderpass_t *pass) static void colourgenbyte(const shaderpass_t *pass, int cnt, byte_vec4_t *srcb, vec4_t *srcf, byte_vec4_t *dst, const mesh_t *mesh) { #define D3DCOLOR unsigned int -#define D3DCOLOR_ARGB(a,r,g,b) ((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff))) -#define D3DCOLOR_COLORVALUE(r,g,b,a) D3DCOLOR_RGBA((DWORD)((r)*255.f),(DWORD)((g)*255.f),(DWORD)((b)*255.f),(DWORD)((a)*255.f)) +#define D3DCOLOR_ARGB(a,r,g,b) ((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff))) +#define D3DCOLOR_COLORVALUE(r,g,b,a) D3DCOLOR_RGBA((DWORD)((r)*255.f),(DWORD)((g)*255.f),(DWORD)((b)*255.f),(DWORD)((a)*255.f)) #define D3DCOLOR_RGBA(r,g,b,a) D3DCOLOR_ARGB(a,r,g,b) D3DCOLOR block; diff --git a/engine/d3d/d3d11_image.c b/engine/d3d/d3d11_image.c index 2560a511f..77e4fb271 100644 --- a/engine/d3d/d3d11_image.c +++ b/engine/d3d/d3d11_image.c @@ -304,6 +304,9 @@ void D3D11_UploadLightmap(lightmapinfo_t *lm) case TF_RGBA32: mips.encoding = PTI_RGBX8; break; + default: + Sys_Error("D3D11_UploadLightmap: Unsupported format"); + return; } mips.mipcount = 1; D3D11_LoadTextureMips(tex, &mips); diff --git a/engine/d3d/d3d8_backend.c b/engine/d3d/d3d8_backend.c index 28213e533..f5a759f06 100644 --- a/engine/d3d/d3d8_backend.c +++ b/engine/d3d/d3d8_backend.c @@ -2,7 +2,7 @@ #include "glquake.h" #include "gl_draw.h" #ifdef D3D8QUAKE -//#define FIXME +//#define FIXME //for Eukara to fix, if he ever wants to get the d3d8 renderer working fully. #include "shader.h" #if !defined(HMONITOR_DECLARED) && (WINVER < 0x0500) #define HMONITOR_DECLARED @@ -48,6 +48,7 @@ extern float d3d_trueprojection[16]; static void BE_RotateForEntity (const entity_t *e, const model_t *mod); /*========================================== tables for deforms =====================================*/ +#define R_FastSin(x) sin((x)*(2*M_PI)) #define frand() (rand()*(1.0/RAND_MAX)) #define FTABLE_SIZE 1024 #define FTABLE_CLAMP(x) (((int)((x)*FTABLE_SIZE) & (FTABLE_SIZE-1))) @@ -59,6 +60,7 @@ static float r_squaretable[FTABLE_SIZE]; static float r_sawtoothtable[FTABLE_SIZE]; static float r_inversesawtoothtable[FTABLE_SIZE]; +#if FIXME static float *FTableForFunc ( unsigned int func ) { switch (func) @@ -82,6 +84,7 @@ static float *FTableForFunc ( unsigned int func ) //bad values allow us to crash (so I can debug em) return NULL; } +#endif static void FTable_Init(void) { @@ -110,6 +113,7 @@ static void FTable_Init(void) } } +#if FIXME typedef vec3_t mat3_t[3]; static mat3_t axisDefault={{1, 0, 0}, {0, 1, 0}, @@ -139,6 +143,7 @@ static int Matrix3_Compare(const mat3_t in, const mat3_t out) { return !memcmp(in, out, sizeof(mat3_t)); } +#endif /*================================================*/ @@ -708,6 +713,7 @@ static void SelectPassTexture(unsigned int tu, shaderpass_t *pass) } } +#if FIXME static void colourgenbyte(const shaderpass_t *pass, int cnt, byte_vec4_t *srcb, vec4_t *srcf, byte_vec4_t *dst, const mesh_t *mesh) { D3DCOLOR block; @@ -1025,9 +1031,11 @@ static unsigned int BE_GenerateColourMods(unsigned int vertcount, const shaderpa #endif return ret; } +#endif /*********************************************************************************************************/ /*========================================== texture coord generation =====================================*/ +#if FIXME static void tcgen_environment(float *st, unsigned int numverts, float *xyz, float *normal) { int i; @@ -1105,7 +1113,6 @@ static void tcmod(const tcmod_t *tcmod, int cnt, const float *src, float *dst, c float t1, t2; float cost, sint; int j; -#define R_FastSin(x) sin((x)*(2*M_PI)) switch (tcmod->type) { case SHADER_TCMOD_ROTATE: @@ -1188,7 +1195,7 @@ static void GenerateTCMods(const shaderpass_t *pass, float *dest) mesh_t *mesh; unsigned int mno; // unsigned int fvertex = 0; //unused variable - int i; +// int i; float *src; float *out; for (mno = 0; mno < shaderstate.nummeshes; mno++) @@ -1459,7 +1466,7 @@ static void deformgen(const deformv_t *deformv, int cnt, vecV_t *src, vecV_t *ds // break; } } - +#endif /*does not do the draw call, does not consider indicies (except for billboard generation) */ @@ -1489,7 +1496,7 @@ static qboolean BE_DrawMeshChain_SetupPass(shaderpass_t *pass, unsigned int vert /*all meshes in a chain must have the same features*/ vdec = D3DFVF_QVBO; - allocvertexbuffer(shaderstate.dynvbo_buff, shaderstate.dynvbo_size, &shaderstate.dynvbo_offs, &map, vertcount*sizeof(*map)); + allocvertexbuffer(shaderstate.dynvbo_buff, shaderstate.dynvbo_size, &shaderstate.dynvbo_offs, (void**)&map, vertcount*sizeof(*map)); *vertfirst = (shaderstate.dynvbo_offs - vertcount*sizeof(*map))/sizeof(*map); @@ -1590,6 +1597,7 @@ static void BE_SubmitMeshChain(unsigned int firstvert, unsigned int vertcount, u RQuantAdd(RQUANT_PRIMITIVEINDICIES, idxcount); } +#ifdef FIXME static void R_FetchPlayerColour(unsigned int cv, vec3_t rgb) { int i; @@ -1623,7 +1631,6 @@ static void R_FetchPlayerColour(unsigned int cv, vec3_t rgb) }*/ } -#ifdef FIXME static void BE_ApplyUniforms(program_t *prog, int permu) { struct programpermu_s *perm = &prog->permu[permu]; @@ -2364,7 +2371,7 @@ static void D3D8BE_GenBatchVBOs(vbo_t **vbochain, batch_t *firstbatch, batch_t * for (i = 0; i < m->numvertexes; i++) { VectorCopy(m->xyz_array[i], vbovdata->coord); - vbovdata->coord[3] = 1; + //vbovdata->coord[3] = 1; Vector2Copy(m->st_array[i], vbovdata->tc[0]); Vector2Copy(m->lmst_array[0][i], vbovdata->tc[1]); Vector4Scale(m->colors4f_array[0][i], 255, vbovdata->colorsb); diff --git a/engine/dotnet2005/emscripten.vcproj b/engine/dotnet2005/emscripten.vcproj index 0748e5702..13d36ae72 100644 --- a/engine/dotnet2005/emscripten.vcproj +++ b/engine/dotnet2005/emscripten.vcproj @@ -23,7 +23,7 @@ > #include FT_FREETYPE_H +#ifndef FT_LOAD_COLOR +#define FT_LOAD_COLOR (1L<<20) +#endif + static FT_Library fontlib; @@ -48,11 +52,22 @@ FT_Error (VARGS *pFT_Init_FreeType) (FT_Library *alibrary); FT_Error (VARGS *pFT_Load_Char) (FT_Face face, FT_ULong char_code, FT_Int32 load_flags); FT_UInt (VARGS *pFT_Get_Char_Index) (FT_Face face, FT_ULong charcode); FT_Error (VARGS *pFT_Set_Pixel_Sizes) (FT_Face face, FT_UInt pixel_width, FT_UInt pixel_height); +FT_Error (VARGS *pFT_Select_Size) (FT_Face face, FT_Int strike_index); FT_Error (VARGS *pFT_New_Face) (FT_Library library, const char *pathname, FT_Long face_index, FT_Face *aface); FT_Error (VARGS *pFT_New_Memory_Face) (FT_Library library, const FT_Byte* file_base, FT_Long file_size, FT_Long face_index, FT_Face *aface); FT_Error (VARGS *pFT_Done_Face) (FT_Face face); +#else +typedef unsigned int FT_Pixel_Mode; //for consistency even without freetype support. #endif +#ifndef FT_PIXEL_MODE_GRAY +#define FT_PIXEL_MODE_GRAY 2 +#endif +#ifndef FT_PIXEL_MODE_BGRA +#define FT_PIXEL_MODE_BGRA 7 //added in FT 2.5 +#endif +#define FT_PIXEL_MODE_RGBA_SA (~(FT_Pixel_Mode)0) //RGBA, straight alpha. not in freetype. + static const char *imgs[] = { //0xe10X @@ -158,18 +173,25 @@ static const char *imgs[] = "e16f" }; -#define FONTCHARS (1<<16) -#define FONTPLANES (1<<2) //this is total, not per font. -#define PLANEIDXTYPE unsigned short -#define CHARIDXTYPE unsigned short +#define FONT_MAXCHARS 0x110000 //as defined by UTF-16, and thus applied to all unicode because UTF-16 is the crappy limited one. +#define FONTBLOCKS ((FONT_MAXCHARS+FONTBLOCKSIZE-1)/FONTBLOCKSIZE) +#define FONTBLOCKSIZE 0x100 //must be power-of-two +#define FONTBLOCKMASK (FONTBLOCKSIZE-1) +#define FONTIMAGES (1<<2) //this is total, not per font. +#define FIMAGEIDXTYPE unsigned char +#define CHARIDXTYPE unsigned int -#define INVALIDPLANE ((1<<(8*sizeof(PLANEIDXTYPE)))-1) -#define BITMAPPLANE ((1<<(8*sizeof(PLANEIDXTYPE)))-2) -#define DEFAULTPLANE ((1<<(8*sizeof(PLANEIDXTYPE)))-3) -#define SINGLEPLANE ((1<<(8*sizeof(PLANEIDXTYPE)))-4) -#define TRACKERIMAGE ((1<<(8*sizeof(PLANEIDXTYPE)))-5) -#define PLANEWIDTH (1<<8) -#define PLANEHEIGHT PLANEWIDTH +#define INVALIDPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-1) +#define BITMAPPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-2) +#define DEFAULTPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-3) +#define SINGLEPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-4) +#define TRACKERIMAGE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-5) +#define FIMAGEWIDTH (1<<10) +#define FIMAGEHEIGHT FIMAGEWIDTH + +#define FONTPLANES FONTIMAGES +#define PLANEWIDTH FIMAGEWIDTH +#define PLANEHEIGHT FIMAGEHEIGHT #ifdef AVAIL_FREETYPE //windows' font linking allows linking multiple extra fonts to a main font. @@ -203,18 +225,20 @@ typedef struct font_s { struct charcache_s *nextchar; - PLANEIDXTYPE texplane; + FIMAGEIDXTYPE texplane; unsigned char advance; //how wide this char is, when drawn - char pad; + unsigned short block; //to quickly find the char again - unsigned char bmx; - unsigned char bmy; + //glyph offset+sizes. I guess these are not strictly needed, but whatever + unsigned short bmx; + unsigned short bmy; unsigned char bmw; unsigned char bmh; + //positions inside the atlas short top; short left; - } chars[FONTCHARS]; + } *chars[FONTBLOCKS]; char name[MAX_OSPATH]; short charheight; @@ -231,15 +255,15 @@ typedef struct font_s //shared between fonts. typedef struct { - texid_t texnum[FONTPLANES]; + texid_t texnum[FONTIMAGES]; texid_t defaultfont; texid_t trackerimage; unsigned char plane[PLANEWIDTH*PLANEHEIGHT][4]; //tracks the current plane - PLANEIDXTYPE activeplane; - unsigned char planerowx; - unsigned char planerowy; - unsigned char planerowh; + FIMAGEIDXTYPE activeplane; + unsigned short planerowx; + unsigned short planerowy; + unsigned short planerowh; qboolean planechanged; struct charcache_s *oldestchar; @@ -492,14 +516,54 @@ void Font_FlushPlane(void) fontplanes.newestchar = NULL; } +static struct charcache_s *Font_GetCharIfLoaded(font_t *f, unsigned int charidx) +{ + struct charcache_s *c = f->chars[charidx/FONTBLOCKSIZE]; + if (c) + { + c += charidx&FONTBLOCKMASK; + if (c->texplane == INVALIDPLANE) + c = NULL; + } + return c; +} +static struct charcache_s *Font_GetCharStore(font_t *f, unsigned int charidx) +{ //should only be called if generating a char cache + struct charcache_s *c; + size_t i; + c = f->chars[charidx/FONTBLOCKSIZE]; + if (!c) + { + c = Z_Malloc(sizeof(struct charcache_s) * FONTBLOCKSIZE); + f->chars[charidx/FONTBLOCKSIZE] = c; + for (i = 0; i < FONTBLOCKSIZE; i++) + { + c[i].texplane = INVALIDPLANE; + } + } + c += charidx&FONTBLOCKMASK; + c->block = charidx/FONTBLOCKSIZE; + return c; +} +static struct charcache_s *Font_CopyChar(font_t *f, unsigned int oldcharidx, unsigned int newcharidx) +{ + struct charcache_s *new, *old = Font_GetCharIfLoaded(f, oldcharidx); + if (!old) + return NULL; + new = Font_GetCharStore(f, newcharidx); + *new = *old; + new->block = newcharidx/FONTBLOCKSIZE; + return new; +} + //loads a new image into a given character slot for the given font. //note: make sure it doesn't already exist or things will get cyclic //alphaonly says if its a greyscale image. false means rgba. -static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, int alphaonly, void *data, unsigned int bmw, unsigned int bmh, unsigned int pitch) +static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, FT_Pixel_Mode pixelmode, void *data, unsigned int bmw, unsigned int bmh, unsigned int pitch) { int x, y; unsigned char *out; - struct charcache_s *c = &f->chars[charidx]; + struct charcache_s *c = Font_GetCharStore(f, charidx); int pad = 0; #define BORDERCOLOUR 0 @@ -534,8 +598,8 @@ static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, in fontplanes.planerowx += bmw+pad*2; out = (unsigned char *)&fontplanes.plane[c->bmx+((int)c->bmy-pad)*PLANEHEIGHT]; - if (alphaonly) - { + if (pixelmode == FT_PIXEL_MODE_GRAY) + { //8bit font for (y = -pad; y < 0; y++) { for (x = -pad; x < bmw+pad; x++) @@ -563,8 +627,8 @@ static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, in out += PLANEWIDTH*4; } } - else - { + else if (pixelmode == FT_PIXEL_MODE_RGBA_SA) + { //rgba font pitch*=4; for (y = -pad; y < 0; y++) { @@ -590,10 +654,132 @@ static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, in out += PLANEWIDTH*4; } } + else if (pixelmode == FT_PIXEL_MODE_BGRA) + { //bgra srgb font, already premultiplied + for (y = -pad; y < 0; y++) + { + for (x = -pad; x < bmw+pad; x++) + *(unsigned int *)&out[x*4] = BORDERCOLOUR; + out += PLANEWIDTH*4; + } + for (; y < bmh; y++) + { + for (x = -pad; x < 0; x++) + *(unsigned int *)&out[x*4] = BORDERCOLOUR; + for (; x < bmw; x++) + { + out[x*4+0] = ((unsigned char*)data)[x*4+2]; + out[x*4+1] = ((unsigned char*)data)[x*4+1]; + out[x*4+2] = ((unsigned char*)data)[x*4+0]; + out[x*4+3] = ((unsigned char*)data)[x*4+3]; + } + for (; x < bmw+pad; x++) + *(unsigned int *)&out[x*4] = BORDERCOLOUR; + data = (char*)data + pitch; + out += PLANEWIDTH*4; + } + for (; y < bmh+pad; y++) + { + for (x = -pad; x < bmw+pad; x++) + *(unsigned int *)&out[x*4] = BORDERCOLOUR; + out += PLANEWIDTH*4; + } + } fontplanes.planechanged = true; return c; } +unsigned short hex[16] = { +/*0*/ (7<<0)|(5<<3)|(5<<6)|(5<<9)|(7<<12), +/*1*/ (2<<0)|(2<<3)|(2<<6)|(2<<9)|(2<<12), +/*2*/ (7<<0)|(1<<3)|(7<<6)|(4<<9)|(7<<12), +/*3*/ (7<<0)|(1<<3)|(3<<6)|(1<<9)|(7<<12), +/*4*/ (5<<0)|(5<<3)|(7<<6)|(1<<9)|(1<<12), +/*5*/ (7<<0)|(4<<3)|(7<<6)|(1<<9)|(7<<12), +/*6*/ (4<<0)|(4<<3)|(7<<6)|(5<<9)|(7<<12), +/*7*/ (7<<0)|(1<<3)|(1<<6)|(1<<9)|(1<<12), +/*8*/ (7<<0)|(5<<3)|(7<<6)|(5<<9)|(7<<12), +/*9*/ (7<<0)|(5<<3)|(7<<6)|(1<<9)|(1<<12), +/*A*/ (7<<0)|(5<<3)|(7<<6)|(5<<9)|(5<<12), +/*B*/ (6<<0)|(5<<3)|(7<<6)|(5<<9)|(6<<12), +/*C*/ (7<<0)|(5<<3)|(4<<6)|(5<<9)|(7<<12), +/*D*/ (6<<0)|(5<<3)|(5<<6)|(5<<9)|(6<<12), +/*E*/ (7<<0)|(4<<3)|(6<<6)|(4<<9)|(7<<12), +/*F*/ (7<<0)|(4<<3)|(6<<6)|(4<<9)|(4<<12) +}; +static struct charcache_s *Font_LoadPlaceholderGlyph(font_t *f, CHARIDXTYPE charidx) +{ + struct charcache_s *c; + unsigned int out[169*4], i, o, g, b, w, h, d, n; + d = (f->charheight >= 11); + if (d) + { //two rows + h = 11; + if (charidx > 0xffff) + n = 3; + else if (charidx > 0xff) + n = 2; + else + n = 1; + w = n*4-1; + n*=2; + } + else + { //single row. bye bye fixed-width. + if (charidx > 0xfffff) + n = 6; + else if (charidx > 0xffff) + n = 5; + else if (charidx > 0xfff) + n = 4; + else if (charidx > 0xff) + n = 3; + else + n = 2; + w = n*4-1; + h = 5; + } + + //figure out if we can get away with giving it a little more border to boost readability + b = (h+2 < f->charheight); + w += b*2; + h += b*2; + + memset(out, 0xff, sizeof(out)); + + for (i = 0; i < n; i++) + { + g = hex[0xf & (charidx>>(i<<2))]; + o = b + w*b; + if (d) + { //stradle them over the two rows. + if (i >= (n>>1)) + o += 4*(n-i-1); + else + { + o += 4*((n>>1)-i-1); + o += w * 6; + } + } + else + o += 4*(n-i-1); //just arrange them in order + for (; g; g>>=3, o+=w) + { + if (g & 4) out[o+0] = 0xff0000ff; + if (g & 2) out[o+1] = 0xff0000ff; + if (g & 1) out[o+2] = 0xff0000ff; + } + } + c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA_SA, out, w, h, w); + if (c) + { + c->advance = w+1; + c->left = 0; + c->top = (f->charheight-h-1)/2; + } + return c; +} + //loads the given charidx for the given font, importing from elsewhere if needed. static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) { @@ -629,7 +815,7 @@ static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) } s+=128; } - c = Font_LoadGlyphData(f, charidx, false, img, 8*scale, 8*scale, 8*scale); + c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA_SA, img, 8*scale, 8*scale, 8*scale); if (c) { c->advance = 8*scale; @@ -691,7 +877,7 @@ static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) } } - c = Font_LoadGlyphData(f, charidx, false, img, nw, nh, 64); + c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA_SA, img, nw, nh, 64); if (c) { c->left = 0; @@ -704,7 +890,7 @@ static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) /*make tab invisible*/ if (charidx == '\t' || charidx == '\n') { - c = &f->chars[charidx]; + c = &f->chars[charidx/FONTBLOCKSIZE][charidx&FONTBLOCKMASK]; c->left = 0; c->advance = f->charheight; c->top = 0; @@ -724,41 +910,56 @@ static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) for (file = 0; file < f->ftfaces; file++) { FT_Face face = f->face[file]->face; - if (f->face[file]->activeheight != f->charheight) - { - f->face[file]->activeheight = f->charheight; - pFT_Set_Pixel_Sizes(face, 0, f->charheight); - } - if (charidx == 0xfffe || pFT_Get_Char_Index(face, charidx)) //ignore glyph 0 (undefined) - if (pFT_Load_Char(face, charidx, FT_LOAD_RENDER) == 0) - { - FT_GlyphSlot slot; - FT_Bitmap *bm; - slot = face->glyph; - bm = &slot->bitmap; - c = Font_LoadGlyphData(f, charidx, true, bm->buffer, bm->width, bm->rows, bm->pitch); - - if (c) +// if (f->face[file]->activeheight) + if (f->face[file]->activeheight != f->charheight) { - c->advance = slot->advance.x >> 6; - c->left = slot->bitmap_left; - c->top = f->charheight*3/4 - slot->bitmap_top; - return c; + f->face[file]->activeheight = f->charheight; +// if (FT_HAS_FIXED_SIZES(face)) +// pFT_Select_Size(face, 0); +// else + pFT_Set_Pixel_Sizes(face, 0, f->charheight); + } + if (charidx == 0xfffe || pFT_Get_Char_Index(face, charidx)) //ignore glyph 0 (undefined) + if (pFT_Load_Char(face, charidx, FT_LOAD_RENDER|FT_LOAD_COLOR) == 0) + { + FT_GlyphSlot slot; + FT_Bitmap *bm; + + slot = face->glyph; + bm = &slot->bitmap; + if (!f->face[file]->activeheight && bm->pixel_mode == FT_PIXEL_MODE_BGRA) + { + unsigned int *out = alloca(f->charheight*f->charheight*4); + Image_ResampleTexture((void*)bm->buffer, bm->width, bm->rows, out, f->charheight, f->charheight); + c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, out, f->charheight, f->charheight, f->charheight*4); + if (c) + { + c->advance = f->charheight; + c->left = 0; + c->top = 0; + return c; + } + } + else + { + c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, bm->buffer, bm->width, bm->rows, bm->pitch); + + if (c) + { + c->advance = slot->advance.x >> 6; + c->left = slot->bitmap_left; + c->top = f->charheight*3/4 - slot->bitmap_top; + return c; + } + } } - } } } #endif if (charidx == '\r') - { - if (f->chars[charidx|0xe000].texplane != INVALIDPLANE) - { - f->chars[charidx] = f->chars[charidx|0xe000]; - return &f->chars[charidx]; - } - } + return Font_CopyChar(f, charidx|0xe000, charidx); return NULL; } @@ -768,12 +969,12 @@ static struct charcache_s *Font_GetChar(font_t *f, unsigned int codepoint) { CHARIDXTYPE charidx; struct charcache_s *c; - if (codepoint > CON_CHARMASK) + if (codepoint > FONT_MAXCHARS) charidx = 0xfffd; else charidx = codepoint; - c = &f->chars[charidx]; - if (c->texplane == INVALIDPLANE) + c = Font_GetCharIfLoaded(f, charidx); + if (!c) { if (charidx >= TRACKERFIRST && charidx < TRACKERFIRST+100) { @@ -806,23 +1007,25 @@ static struct charcache_s *Font_GetChar(font_t *f, unsigned int codepoint) charidx = wc2koi_table[charidx - 0x400]; if (charidx != '?') { - c = &f->chars[charidx]; - if (c->texplane == INVALIDPLANE) + c = Font_GetCharIfLoaded(f, charidx); + if (!c) c = Font_TryLoadGlyph(f, charidx); } } + if (!c) + c = Font_LoadPlaceholderGlyph(f, charidx); if (!c) { charidx = 0xfffd; //unicode's replacement char - c = &f->chars[charidx]; - if (c->texplane == INVALIDPLANE) + c = Font_GetCharIfLoaded(f, charidx); + if (!c) c = Font_TryLoadGlyph(f, charidx); } if (!c) { charidx = '?'; //meh - c = &f->chars[charidx]; - if (c->texplane == INVALIDPLANE) + c = Font_GetCharIfLoaded(f, charidx); + if (!c) c = Font_TryLoadGlyph(f, charidx); } } @@ -840,6 +1043,9 @@ qboolean Font_LoadFreeTypeFont(struct font_s *f, int height, const char *fontfil if (!*fontfilename) return false; + if (height < 1) + height = 1; + //ran out of font slots. if (f->ftfaces == MAX_FTFACES) return false; @@ -856,12 +1062,24 @@ qboolean Font_LoadFreeTypeFont(struct font_s *f, int height, const char *fontfil if (!fontlib) { +#if 0 + pFT_Init_FreeType = FT_Init_FreeType; + pFT_Load_Char = FT_Load_Char; + pFT_Get_Char_Index = FT_Get_Char_Index; + pFT_Set_Pixel_Sizes = FT_Set_Pixel_Sizes; + pFT_Select_Size = FT_Select_Size; + pFT_New_Face = FT_New_Face; + pFT_New_Memory_Face = FT_New_Memory_Face; + pFT_Init_FreeType = FT_Init_FreeType; + pFT_Done_Face = FT_Done_Face; +#else dllfunction_t ft2funcs[] = { {(void**)&pFT_Init_FreeType, "FT_Init_FreeType"}, {(void**)&pFT_Load_Char, "FT_Load_Char"}, {(void**)&pFT_Get_Char_Index, "FT_Get_Char_Index"}, {(void**)&pFT_Set_Pixel_Sizes, "FT_Set_Pixel_Sizes"}, + {(void**)&pFT_Select_Size, "FT_Select_Size"}, {(void**)&pFT_New_Face, "FT_New_Face"}, {(void**)&pFT_New_Memory_Face, "FT_New_Memory_Face"}, {(void**)&pFT_Init_FreeType, "FT_Init_FreeType"}, @@ -873,7 +1091,9 @@ qboolean Font_LoadFreeTypeFont(struct font_s *f, int height, const char *fontfil triedtoloadfreetype = true; #ifdef _WIN32 - fontmodule = Sys_LoadLibrary("freetype6", ft2funcs); + fontmodule = Sys_LoadLibrary("libfreetype-6", ft2funcs); + if (!fontmodule) + fontmodule = Sys_LoadLibrary("freetype6", ft2funcs); #else fontmodule = Sys_LoadLibrary("libfreetype.so.6", ft2funcs); #endif @@ -882,6 +1102,7 @@ qboolean Font_LoadFreeTypeFont(struct font_s *f, int height, const char *fontfil Con_DPrintf("Couldn't load freetype library.\n"); return false; } +#endif error = pFT_Init_FreeType(&fontlib); if (error) { @@ -952,7 +1173,13 @@ qboolean Font_LoadFreeTypeFont(struct font_s *f, int height, const char *fontfil #endif if (!error) { - error = pFT_Set_Pixel_Sizes(face, 0, height); + if (FT_HAS_FIXED_SIZES(face)) + { + height = 0; //will need to rescale manually I guess + error = pFT_Select_Size(face, 0); + } + else + error = pFT_Set_Pixel_Sizes(face, 0, height); if (!error) { /*success!*/ @@ -1181,11 +1408,13 @@ static texid_t Font_LoadDefaultConchars(void) COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING); if (TEXLOADED(tex)) return tex; +#ifdef HEXEN2 tex = Font_LoadHexen2Conchars(true); if (tex && tex->status == TEX_LOADING) COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING); if (TEXLOADED(tex)) return tex; +#endif tex = Font_LoadFallbackConchars(); if (tex && tex->status == TEX_LOADING) COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING); @@ -1246,6 +1475,7 @@ struct font_s *Font_LoadFont(float vheight, const char *fontfilename) char *parms; int height = ((vheight * vid.rotpixelheight)/vid.height) + 0.5; char facename[MAX_QPATH]; + struct charcache_s *c; Q_strncpy(facename, fontfilename, sizeof(facename)); @@ -1391,39 +1621,37 @@ struct font_s *Font_LoadFont(float vheight, const char *fontfilename) for (i = 0x00; i <= 0xff; i++) { - f->chars[i].advance = (height*3)/4; - f->chars[i].left = 0; - f->chars[i].top = 0; - f->chars[i].nextchar = 0; //these chars are not linked in - f->chars[i].pad = 0; - f->chars[i].texplane = BITMAPPLANE; /*if its a 'raster' font, don't use the default chars, always use the raster images*/ + c = Font_GetCharStore(f, i); + c->advance = (height*3)/4; + c->left = 0; + c->top = 0; + c->nextchar = 0; //these chars are not linked in + c->texplane = BITMAPPLANE; /*if its a 'raster' font, don't use the default chars, always use the raster images*/ if (i >= 'a' && i <= 'z') { - f->chars[i].bmx = ((i - 64)&15)*8; - f->chars[i].bmy = ((i - 64)/16)*8; - f->chars[i].bmh = 8; - f->chars[i].bmw = 8; + c->bmx = ((i - 64)&15)*8; + c->bmy = ((i - 64)/16)*8; + c->bmh = 8; + c->bmw = 8; } else if (i >= 32 && i < 96) { - f->chars[i].bmx = ((i - 32)&15)*8; - f->chars[i].bmy = ((i - 32)/16)*8; - f->chars[i].bmh = 8; - f->chars[i].bmw = 8; + c->bmx = ((i - 32)&15)*8; + c->bmy = ((i - 32)/16)*8; + c->bmh = 8; + c->bmw = 8; } else { - f->chars[i].bmh = 0; - f->chars[i].bmw = 0; - f->chars[i].bmx = 0; - f->chars[i].bmy = 0; + c->bmh = 0; + c->bmw = 0; + c->bmx = 0; + c->bmy = 0; } - } - for (i = 0xe000; i <= 0xe0ff; i++) - { - f->chars[i] = f->chars[i&0xff]; + + Font_CopyChar(f, i, i|0xe0ff); } return f; } @@ -1486,21 +1714,22 @@ struct font_s *Font_LoadFont(float vheight, const char *fontfilename) for ( ; i < 32; i++) { - f->chars[i].texplane = INVALIDPLANE; +// f->chars[i].texplane = INVALIDPLANE; } /*force it to load, even if there's nothing there*/ for ( ; i < 128; i++) { - f->chars[i].advance = f->charheight; - f->chars[i].bmh = PLANEWIDTH/16; - f->chars[i].bmw = PLANEWIDTH/16; - f->chars[i].bmx = (i&15)*(PLANEWIDTH/16); - f->chars[i].bmy = (i/16)*(PLANEWIDTH/16); - f->chars[i].left = 0; - f->chars[i].top = 0; - f->chars[i].nextchar = 0; //these chars are not linked in - f->chars[i].pad = 0; - f->chars[i].texplane = BITMAPPLANE; + c = Font_GetCharStore(f, i); + + c->advance = f->charheight; + c->bmh = PLANEWIDTH/16; + c->bmw = PLANEWIDTH/16; + c->bmx = (i&15)*(PLANEWIDTH/16); + c->bmy = (i/16)*(PLANEWIDTH/16); + c->left = 0; + c->top = 0; + c->nextchar = 0; //these chars are not linked in + c->texplane = BITMAPPLANE; } } @@ -1521,24 +1750,19 @@ struct font_s *Font_LoadFont(float vheight, const char *fontfilename) f->singletexture = fontplanes.defaultfont; } - for (; i < FONTCHARS; i++) - { - f->chars[i].texplane = INVALIDPLANE; - } - /*pack the default chars into it*/ for (i = 0xe000; i <= 0xe0ff; i++) { - f->chars[i].advance = f->charheight; - f->chars[i].bmh = PLANEWIDTH/16; - f->chars[i].bmw = PLANEWIDTH/16; - f->chars[i].bmx = (i&15)*(PLANEWIDTH/16); - f->chars[i].bmy = ((i/16)*(PLANEWIDTH/16)) & 0xff; - f->chars[i].left = 0; - f->chars[i].top = 0; - f->chars[i].nextchar = 0; //these chars are not linked in - f->chars[i].pad = 0; - f->chars[i].texplane = defaultplane; + c = Font_GetCharStore(f, i); + c->advance = f->charheight; + c->bmh = PLANEWIDTH/16; + c->bmw = PLANEWIDTH/16; + c->bmx = ((i&15))*(PLANEWIDTH/16); + c->bmy = ((i&0xf0)/16)*(PLANEWIDTH/16); + c->left = 0; + c->top = 0; + c->nextchar = 0; //these chars are not linked in + c->texplane = defaultplane; } return f; } @@ -1559,7 +1783,7 @@ void Font_Free(struct font_s *f) for (link = &fontplanes.oldestchar; *link; ) { c = *link; - if (c >= f->chars && c <= f->chars + FONTCHARS) + if (f->chars[c->block] && c >= f->chars[c->block] && c <= f->chars[c->block] + FONTBLOCKSIZE) { c = c->nextchar; if (!c) diff --git a/engine/gl/gl_heightmap.c b/engine/gl/gl_heightmap.c index d146ce3df..44ec04e09 100644 --- a/engine/gl/gl_heightmap.c +++ b/engine/gl/gl_heightmap.c @@ -4871,6 +4871,11 @@ void QCBUILTIN PF_terrain_edit(pubprogfuncs_t *prinst, struct globalvars_s *pr_g int idx = G_INT(OFS_PARM1); int id; const char *newvals; + if (idx >= MAX_EDICTS) //we need some sanity limit... many ents will get removed like lights so this one isn't quite correct, but it'll be in the right sort of ballpark. + { + G_INT(OFS_RETURN) = 0; + return; + } //if there's no ents, then that's a problem. make sure that there's at least a worldspawn. if (!mod->numentityinfo) Mod_ParseEntities(mod); @@ -4902,7 +4907,8 @@ void QCBUILTIN PF_terrain_edit(pubprogfuncs_t *prinst, struct globalvars_s *pr_g else { newvals = NULL; - mod->entityinfo[idx].id = 0; + if (idx < mod->numentityinfo) + mod->entityinfo[idx].id = 0; } #ifndef CLIENTONLY diff --git a/engine/gl/gl_rlight.c b/engine/gl/gl_rlight.c index f6c69a205..bdfecf1df 100644 --- a/engine/gl/gl_rlight.c +++ b/engine/gl/gl_rlight.c @@ -712,7 +712,6 @@ qboolean R_ImportRTLights(const char *entlump) //failing that, it will insert lights with some crappy fixed radius around only all 'classname light' entities, without any colours or anything, vanilla only. //such lights are ONLY created if they're not near some other existing light (like a static entity one). //this can result in FTE having noticably more and bigger lights than tenebrae. shadowmapping doesn't help performance either. - float lightmaplevel = -1; COM_Parse(entlump); if (!strcmp(com_token, "Version")) @@ -929,7 +928,7 @@ qboolean R_ImportRTLights(const char *entlump) else if (entnum == 0 && !strcmp("lightmapbright", key)) { //tenebrae compat. this overrides r_shadow_realtime_world_lightmap - lightmaplevel = atof(value); + r_shadow_realtime_world_lightmaps.value = atof(value); } } if (!islight) diff --git a/engine/gl/gl_shader.c b/engine/gl/gl_shader.c index f0f555f77..32e420b6f 100644 --- a/engine/gl/gl_shader.c +++ b/engine/gl/gl_shader.c @@ -2850,9 +2850,9 @@ static void Shaderpass_VideoMap (shader_t *shader, shaderpass_t *pass, char **pt pass->cin = Media_StartCin(token); if (!pass->cin) - pass->cin = Media_StartCin(va("video/%s.roq", token)); - if (!pass->cin) + { Con_DPrintf (CON_WARNING "(shader %s) Couldn't load video %s\n", shader->name, token); + } if (pass->cin) { @@ -4362,6 +4362,7 @@ void Shader_Programify (shader_t *s) char *prog = NULL; const char *mask; char args[1024]; + qboolean eightbit = false; /* enum { T_UNKNOWN, @@ -4437,12 +4438,20 @@ void Shader_Programify (shader_t *s) else if (modellighting) { pass = modellighting; - prog = "defaultskin"; + eightbit = r_softwarebanding && (qrenderer == QR_OPENGL) && sh_config.progs_supported; + if (eightbit) + prog = "defaultskin#EIGHTBIT"; + else + prog = "defaultskin"; } else if (lightmap) { pass = modellighting; - prog = "defaultwall"; + eightbit = r_softwarebanding && (qrenderer == QR_OPENGL || qrenderer == QR_VULKAN) && sh_config.progs_supported; + if (eightbit) + prog = "defaultwall#EIGHTBIT"; + else + prog = "defaultwall"; } else if (vertexlighting) { @@ -4487,7 +4496,13 @@ void Shader_Programify (shader_t *s) } else { - s->passes[s->numpasses++].texgen = T_GEN_DIFFUSE; + if (eightbit) + { + s->passes[s->numpasses].anim_frames[0] = R_LoadColourmapImage(); + s->passes[s->numpasses++].texgen = T_GEN_SINGLEMAP; + } + else + s->passes[s->numpasses++].texgen = T_GEN_DIFFUSE; s->flags |= SHADER_HASDIFFUSE; } } diff --git a/engine/gl/gl_vidcommon.c b/engine/gl/gl_vidcommon.c index 5474ee3df..b7d4cc0a2 100644 --- a/engine/gl/gl_vidcommon.c +++ b/engine/gl/gl_vidcommon.c @@ -186,6 +186,8 @@ void (APIENTRY *qglBufferDataARB)(GLenum target, GLsizei size, const void* data, void (APIENTRY *qglBufferSubDataARB)(GLenum target, GLint offset, GLsizei size, void* data); void *(APIENTRY *qglMapBufferARB)(GLenum target, GLenum access); GLboolean (APIENTRY *qglUnmapBufferARB)(GLenum target); + +void *(APIENTRY *qglMapBufferRange)(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); #endif void (APIENTRY *qglGenVertexArrays)(GLsizei n, GLuint *arrays); @@ -855,6 +857,9 @@ void GL_CheckExtensions (void *(*getglfunction) (char *name)) qglMapBufferARB = NULL; qglUnmapBufferARB = NULL; } + + //ARB_map_buffer_range: core in gl3.0/gles3.0, the extension is backported, and thus no ARB postfix on functions. + qglMapBufferRange = (void *)getglext("glMapBufferRange"); #endif #ifdef GL_STATIC @@ -2620,7 +2625,7 @@ static void GLSlang_ProgAutoFields(program_t *prog, const char *progname, cvar_t } //the vid routines have initialised a window, and now they are giving us a reference to some of of GetProcAddress to get pointers to the funcs. -void GL_Init(rendererstate_t *info, void *(*getglfunction) (char *name)) +qboolean GL_Init(rendererstate_t *info, void *(*getglfunction) (char *name)) { #ifndef GL_STATIC qglBindTexture = (void *)getglcore("glBindTexture"); //for compleateness. core in 1.1. needed by fte. @@ -2942,6 +2947,8 @@ void GL_Init(rendererstate_t *info, void *(*getglfunction) (char *name)) sh_config.nv_tex_env_combine4 = gl_config.nv_tex_env_combine4; sh_config.env_add = gl_config.env_add; } + + return true; } diff --git a/engine/gl/gl_viddroid.c b/engine/gl/gl_viddroid.c index 52a4ac861..aee656ff7 100644 --- a/engine/gl/gl_viddroid.c +++ b/engine/gl/gl_viddroid.c @@ -84,8 +84,7 @@ qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) vid.activeapp = true; - GL_Init(info, GLES_GetSymbol); - return true; + return GL_Init(info, GLES_GetSymbol); } #else @@ -174,8 +173,7 @@ qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) vid.pixelwidth = w; vid.pixelheight = h; - GL_Init(info, GLES_GetSymbol); - return true; + return GL_Init(info, GLES_GetSymbol); } void GLVID_SwapBuffers(void) diff --git a/engine/gl/gl_vidlinuxglx.c b/engine/gl/gl_vidlinuxglx.c index 8516acace..2fc50bba2 100644 --- a/engine/gl/gl_vidlinuxglx.c +++ b/engine/gl/gl_vidlinuxglx.c @@ -2527,7 +2527,8 @@ qboolean X11VID_Init (rendererstate_t *info, unsigned char *palette, int psl) return false; } - GL_Init(info, &GLX_GetSymbol); + if (!GL_Init(info, &GLX_GetSymbol)) + return false; break; #ifdef USE_EGL case PSL_EGL: @@ -2537,19 +2538,26 @@ qboolean X11VID_Init (rendererstate_t *info, unsigned char *palette, int psl) GLVID_Shutdown(); return false; } - GL_Init(info, &EGL_Proc); + if (!GL_Init(info, &EGL_Proc)) + return false; break; #endif #endif #ifdef VKQUAKE case PSL_VULKAN: #ifdef VK_USE_PLATFORM_XLIB_KHR - if (VK_Init(info, VK_KHR_XLIB_SURFACE_EXTENSION_NAME, XVK_SetupSurface_XLib, NULL)) - break; + { + const char *extnames[] = {VK_KHR_XLIB_SURFACE_EXTENSION_NAME, NULL}; + if (VK_Init(info, extnames, XVK_SetupSurface_XLib, NULL)) + break; + } #endif #ifdef VK_USE_PLATFORM_XCB_KHR - if (x11xcb_initlib() && VK_Init(info, VK_KHR_XCB_SURFACE_EXTENSION_NAME, XVK_SetupSurface_XCB, NULL)) - break; + { + const char *extnames[] = {VK_KHR_XCB_SURFACE_EXTENSION_NAME, NULL}; + if (x11xcb_initlib() && VK_Init(info, extnames, XVK_SetupSurface_XCB, NULL)) + break; + } #endif Con_Printf("Failed to create a vulkan context.\n"); GLVID_Shutdown(); diff --git a/engine/gl/gl_vidmacos.c b/engine/gl/gl_vidmacos.c index 8876be915..3d818925f 100644 --- a/engine/gl/gl_vidmacos.c +++ b/engine/gl/gl_vidmacos.c @@ -66,11 +66,11 @@ qboolean GLVID_Init(rendererstate_t *info, unsigned char *palette) vid.numpages = 2; - // initialise the NSApplication and the screen + // initialise the NSApplication and the screen initCocoa(info); - // calculate the conwidth AFTER the screen has been opened + // calculate the conwidth AFTER the screen has been opened if (vid.pixelwidth <= 640) { vid.width = vid.pixelwidth; @@ -81,23 +81,23 @@ qboolean GLVID_Init(rendererstate_t *info, unsigned char *palette) vid.width = vid.pixelwidth/2; vid.height = vid.pixelheight/2; } - + if ((i = COM_CheckParm("-conwidth")) && i + 1 < com_argc) { vid.width = Q_atoi(com_argv[i + 1]); - + // pick a conheight that matches with correct aspect vid.height = vid.width * 3 / 4; } - + vid.width &= 0xfff8; // make it a multiple of eight - + if ((i = COM_CheckParm("-conheight")) && i + 1 < com_argc) vid.height = Q_atoi(com_argv[i + 1]); - + if (vid.width < 320) vid.width = 320; - + if (vid.height < 200) vid.height = 200; @@ -182,7 +182,7 @@ void GLVID_SwapBuffers(void) void GLVID_SetDeviceGammaRamp(unsigned short *ramps) { - cocoaGamma(ramps,ramps+256,ramps+512); + cocoaGamma(ramps,ramps+256,ramps+512); } void GLVID_ShiftPalette(unsigned char *p) diff --git a/engine/gl/gl_vidnt.c b/engine/gl/gl_vidnt.c index 424aef05c..940fdfcd2 100644 --- a/engine/gl/gl_vidnt.c +++ b/engine/gl/gl_vidnt.c @@ -128,7 +128,6 @@ static qboolean VID_SetFullDIBMode (rendererstate_t *info); //-1 on bpp or hz fo #endif #ifdef WTHREAD static HANDLE windowthread; -static void *windowmutex; #endif static DEVMODE gdevmode; @@ -821,6 +820,7 @@ static void Win32VK_Present(struct vkframe *theframe) static qboolean Win32VK_AttachVulkan (rendererstate_t *info) { //make sure we can get a valid renderer. + const char *extnames[] = {VK_KHR_WIN32_SURFACE_EXTENSION_NAME, NULL}; #ifdef VK_NO_PROTOTYPES hInstVulkan = NULL; if (!hInstVulkan) @@ -835,7 +835,7 @@ static qboolean Win32VK_AttachVulkan (rendererstate_t *info) vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) GetProcAddress(hInstVulkan, "vkGetInstanceProcAddr"); #endif - return VK_Init(info, VK_KHR_WIN32_SURFACE_EXTENSION_NAME, Win32VK_CreateSurface, Win32VK_Present); + return VK_Init(info, extnames, Win32VK_CreateSurface, Win32VK_Present); } #endif @@ -1481,7 +1481,8 @@ static int GLVID_SetMode (rendererstate_t *info, unsigned char *palette) } } - GL_Init(info, getglfunc); + if (!GL_Init(info, getglfunc)) + return false; qSwapBuffers(maindc); #ifdef VKQUAKE @@ -1501,7 +1502,8 @@ static int GLVID_SetMode (rendererstate_t *info, unsigned char *palette) stat = EGL_Init (info, palette, mainwindow, maindc); if (stat) - GL_Init(info, &EGL_Proc); + if (GL_Init(info, &EGL_Proc)) + return false; } break; #endif @@ -2109,6 +2111,9 @@ void GLVID_SwapBuffers (void) #endif #ifdef VKQUAKE case MODE_VULKAN: +#ifdef USE_WGL + case MODE_NVVULKAN: +#endif //FIXME: force a buffer swap now (might be called while loading (eg: q3), where we won't get a chance to redraw for a bit) break; #endif diff --git a/engine/gl/gl_vidsdl.c b/engine/gl/gl_vidsdl.c index 08589d042..ba17678f4 100644 --- a/engine/gl/gl_vidsdl.c +++ b/engine/gl/gl_vidsdl.c @@ -1,14 +1,27 @@ #include "quakedef.h" -#include "glquake.h" #include #include +#ifdef GLQUAKE + #include "glquake.h" + #define OPENGL_SDL +#endif + #if SDL_MAJOR_VERSION >= 2 -SDL_Window *sdlwindow; -static SDL_GLContext *sdlcontext; + #if SDL_VERSION_ATLEAST(2,0,6) + #ifdef VKQUAKE + #include + #include "../vk/vkrenderer.h" + #define VULKAN_SDL + #endif + #endif + SDL_Window *sdlwindow; + #ifdef OPENGL_SDL + static SDL_GLContext *sdlcontext; + #endif #else -SDL_Surface *sdlsurf; + SDL_Surface *sdlsurf; #endif extern cvar_t vid_vsync; @@ -34,7 +47,7 @@ unsigned short intitialgammaramps[3][256]; qboolean mouseactive; extern qboolean mouseusedforgui; - +#ifdef OPENGL_SDL static void *GLVID_getsdlglfunction(char *functionname) { #ifdef GL_STATIC @@ -44,6 +57,7 @@ static void *GLVID_getsdlglfunction(char *functionname) return SDL_GL_GetProcAddress(functionname); #endif } +#endif #if SDL_MAJOR_VERSION >= 2 void *GLVID_CreateCursor (const char *filename, float hotx, float hoty, float scale) @@ -101,7 +115,7 @@ void GLVID_DestroyCursor (void *cursor) #endif -qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) +static qboolean SDLVID_Init (rendererstate_t *info, unsigned char *palette, r_qrenderer_t qrenderer) { int flags = 0; @@ -110,72 +124,91 @@ qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) SDL_SetVideoMode(0, 0, 0, 0); //to get around some SDL bugs #endif +#ifdef OPENGL_SDL + if (qrenderer == QR_OPENGL) + { #if SDL_MAJOR_VERSION >= 2 - SDL_GL_LoadLibrary(NULL); + SDL_GL_LoadLibrary(NULL); #endif - if (info->bpp >= 32) - { - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); //technically we don't always need stencil support. - } - else - { - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); - } - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - if (info->stereo) - SDL_GL_SetAttribute(SDL_GL_STEREO, 1); - -#if 0//SDL_MAJOR_VERSION >= 2 - //FIXME: this stuff isn't part of info. - //this means it shouldn't be exposed to the menu or widely advertised. - if (*vid_gl_context_version.string) - { - int major, minor; - char *ver = vid_gl_context_version.string; - major = strtoul(ver, &ver, 10); - if (*ver == '.') + if (info->bpp >= 32) { - ver++; - minor = strtoul(ver, &ver, 10); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); //technically we don't always need stencil support. } else - minor = 0; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor); - } - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, - (vid_gl_context_debug.ival?SDL_GL_CONTEXT_DEBUG_FLAG:0) | - (vid_gl_context_forwardcompatible.ival?SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG:0) | - 0); + { + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); + } + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - if (vid_gl_context_es.ival) - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - else if (vid_gl_context_compatibility.ival) - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); - else - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + if (info->stereo) + SDL_GL_SetAttribute(SDL_GL_STEREO, 1); + +#if 0//SDL_MAJOR_VERSION >= 2 + //FIXME: this stuff isn't part of info. + //this means it shouldn't be exposed to the menu or widely advertised. + if (*vid_gl_context_version.string) + { + int major, minor; + char *ver = vid_gl_context_version.string; + major = strtoul(ver, &ver, 10); + if (*ver == '.') + { + ver++; + minor = strtoul(ver, &ver, 10); + } + else + minor = 0; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor); + } + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, + (vid_gl_context_debug.ival?SDL_GL_CONTEXT_DEBUG_FLAG:0) | + (vid_gl_context_forwardcompatible.ival?SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG:0) | + 0); + + if (vid_gl_context_es.ival) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + else if (vid_gl_context_compatibility.ival) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); + else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); #endif - if (info->multisample) - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, info->multisample); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + if (info->multisample) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, info->multisample); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + } } +#endif #if SDL_MAJOR_VERSION >= 2 + switch(qrenderer) + { + default: + break; +#ifdef OPENGL_SDL + case QR_OPENGL: + flags |= SDL_WINDOW_OPENGL; + break; +#endif +#ifdef VULKAN_SDL + case QR_VULKAN: + flags |= SDL_WINDOW_VULKAN; + break; +#endif + } if (info->fullscreen) flags |= SDL_WINDOW_FULLSCREEN; - flags |= SDL_WINDOW_OPENGL; flags |= SDL_WINDOW_RESIZABLE; flags |= SDL_WINDOW_INPUT_GRABBED; flags |= SDL_WINDOW_SHOWN; @@ -189,19 +222,38 @@ qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) return false; } CL_UpdateWindowTitle(); - #if SDL_PATCHLEVEL >= 1 - SDL_GL_GetDrawableSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); //get the proper physical size. - #else - SDL_GetWindowSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); - #endif - sdlcontext = SDL_GL_CreateContext(sdlwindow); - if (!sdlcontext) + switch(qrenderer) { - Con_Printf("Couldn't initialize GL context: %s\n", SDL_GetError()); - return false; +#ifdef OPENGL_SDL +#if SDL_PATCHLEVEL >= 1 + case QR_OPENGL: + SDL_GL_GetDrawableSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); //get the proper physical size. + break; +#endif +#endif +#ifdef VULKAN_SDL + case QR_VULKAN: + SDL_Vulkan_GetDrawableSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); + break; +#endif + default: + SDL_GetWindowSize(sdlwindow, &vid.pixelwidth, &vid.pixelheight); + break; } +#ifdef OPENGL_SDL + if (qrenderer == QR_OPENGL) + { + sdlcontext = SDL_GL_CreateContext(sdlwindow); + if (!sdlcontext) + { + Con_Printf("Couldn't initialize GL context: %s\n", SDL_GetError()); + return false; + } + } +#endif + { SDL_Surface *iconsurf; #include "bymorphed.h" @@ -230,10 +282,6 @@ qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) #endif vid.activeapp = true; - GL_Init(info, GLVID_getsdlglfunction); - - qglViewport (0, 0, vid.pixelwidth, vid.pixelheight); - mouseactive = false; if (vid_isfullscreen) IN_ActivateMouse(); @@ -272,6 +320,17 @@ qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) return true; } +#ifdef OPENGL_SDL +qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) +{ + if (SDLVID_Init(info, palette, QR_OPENGL)) + { + return GL_Init(info, GLVID_getsdlglfunction); + } + return false; +} +#endif + void GLVID_DeInit (void) { vid.activeapp = false; @@ -281,7 +340,21 @@ void GLVID_DeInit (void) #if SDL_MAJOR_VERSION >= 2 SDL_SetWindowGammaRamp(sdlwindow, NULL, NULL, NULL); - SDL_GL_DeleteContext(sdlcontext); + switch(qrenderer) + { +#ifdef OPENGL_SDL + case QR_OPENGL: + SDL_GL_DeleteContext(sdlcontext); + break; +#endif +#ifdef VULKAN_SDL + case QR_VULKAN: + + break; +#endif + default: + break; + } SDL_DestroyWindow(sdlwindow); sdlwindow = NULL; #else @@ -294,22 +367,31 @@ void GLVID_DeInit (void) void GLVID_SwapBuffers (void) { -#if SDL_MAJOR_VERSION >= 2 - if (vid_vsync.modified) + switch(qrenderer) { - if (*vid_vsync.string) +#ifdef OPENGL_SDL + case QR_OPENGL: +#if SDL_MAJOR_VERSION >= 2 + if (vid_vsync.modified) { - //if swap_tear isn't supported, try without. - if (SDL_GL_SetSwapInterval(vid_vsync.ival) == -1 && vid_vsync.ival < 0) - SDL_GL_SetSwapInterval(-vid_vsync.ival); + if (*vid_vsync.string) + { + //if swap_tear isn't supported, try without. + if (SDL_GL_SetSwapInterval(vid_vsync.ival) == -1 && vid_vsync.ival < 0) + SDL_GL_SetSwapInterval(-vid_vsync.ival); + } + vid_vsync.modified = false; } - vid_vsync.modified = false; - } - SDL_GL_SwapWindow(sdlwindow); + SDL_GL_SwapWindow(sdlwindow); #else - SDL_GL_SwapBuffers(); + SDL_GL_SwapBuffers(); #endif + break; +#endif + default: + break; + } if (!vid_isfullscreen) @@ -381,6 +463,7 @@ qboolean GLVID_ApplyGammaRamps (unsigned int gammarampsize, unsigned short *ramp return true; } #endif + return false; } void GLVID_SetCaption(const char *text) @@ -394,3 +477,87 @@ void GLVID_SetCaption(const char *text) +#ifdef VULKAN_SDL +static qboolean VKSDL_CreateSurface(void) +{ + return SDL_Vulkan_CreateSurface(sdlwindow, vk.instance, &vk.surface); +} +static qboolean VKVID_Init (rendererstate_t *info, unsigned char *palette) +{ + unsigned extcount; + const char **extnames; + if (!SDLVID_Init(info, palette, QR_VULKAN)) + return false; + if (!SDL_Vulkan_GetInstanceExtensions(sdlwindow, &extcount, NULL)) + return false; + extnames = alloca(sizeof(*extnames)*(extcount+1)); + if (!SDL_Vulkan_GetInstanceExtensions(sdlwindow, &extcount, extnames)) + return false; + + vkGetInstanceProcAddr = SDL_Vulkan_GetVkGetInstanceProcAddr(); + if (!VK_Init(info, extnames, VKSDL_CreateSurface, NULL)) + { + SDL_ShowSimpleMessageBox(0, "FTEQuake", extnames[1], sdlwindow); + return false; + } + return true; +} +rendererinfo_t vkrendererinfo = +{ + "Vulkan-SDL", + { + "vk", + "Vulkan" + }, + QR_VULKAN, + + VK_Draw_Init, + VK_Draw_Shutdown, + + VK_UpdateFiltering, + VK_LoadTextureMips, + VK_DestroyTexture, + + VK_R_Init, + VK_R_DeInit, + VK_R_RenderView, + + VKVID_Init, + GLVID_DeInit, + GLVID_SwapBuffers, + GLVID_ApplyGammaRamps, + NULL, + NULL, + NULL, + GLVID_SetCaption, + VKVID_GetRGBInfo, + + VK_SCR_UpdateScreen, + + VKBE_SelectMode, + VKBE_DrawMesh_List, + VKBE_DrawMesh_Single, + VKBE_SubmitBatch, + VKBE_GetTempBatch, + VKBE_DrawWorld, + VKBE_Init, + VKBE_GenBrushModelVBO, + VKBE_ClearVBO, + VKBE_UploadAllLightmaps, + VKBE_SelectEntity, + VKBE_SelectDLight, + VKBE_Scissor, + VKBE_LightCullModel, + + VKBE_VBO_Begin, + VKBE_VBO_Data, + VKBE_VBO_Finish, + VKBE_VBO_Destroy, + + VKBE_RenderToTextureUpdate2d, + + "no more" +}; +#else +rendererinfo_t vkrendererinfo; +#endif diff --git a/engine/gl/glquake.h b/engine/gl/glquake.h index d5b5c3261..08b735b26 100644 --- a/engine/gl/glquake.h +++ b/engine/gl/glquake.h @@ -713,6 +713,13 @@ extern void (APIENTRY *qglBufferSubDataARB)(GLenum target, GLint offset, GLsizei extern void *(APIENTRY *qglMapBufferARB)(GLenum target, GLenum access); extern GLboolean (APIENTRY *qglUnmapBufferARB)(GLenum target); +#define GLintptr qintptr_t +#define GLsizeiptr quintptr_t +#ifndef GL_MAP_READ_BIT +#define GL_MAP_READ_BIT 1 +#endif +void *(APIENTRY *qglMapBufferRange)(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); + #endif extern void (APIENTRY *qglGenQueriesARB)(GLsizei n, GLuint *ids); extern void (APIENTRY *qglDeleteQueriesARB)(GLsizei n, const GLuint *ids); @@ -1078,7 +1085,7 @@ void GL_SelectProgram(int program); -void GL_Init(rendererstate_t *info, void *(*getglfunction) (char *name)); +qboolean GL_Init(rendererstate_t *info, void *(*getglfunction) (char *name)); #endif diff --git a/engine/nacl/snd_ppapi.c b/engine/nacl/snd_ppapi.c index 5a176032f..708eb6aa4 100644 --- a/engine/nacl/snd_ppapi.c +++ b/engine/nacl/snd_ppapi.c @@ -19,63 +19,64 @@ static void PPAPI_audio_callback(void *sample_buffer, uint32_t len, soundcardinfo_t *sc = user_data; unsigned int framesz; if (sc) - { - int curtime = S_GetMixerTime(sc); - framesz = sc->sn.numchannels * sc->sn.samplebits/8; - - //might as well dump it directly... - sc->sn.buffer = sample_buffer; - sc->sn.samples = len / (sc->sn.samplebits/8); - S_PaintChannels (sc, curtime + (len / framesz)); - sc->sn.samples = 0; - sc->sn.buffer = NULL; - + { + int curtime = S_GetMixerTime(sc); + framesz = sc->sn.numchannels * sc->sn.samplebytes; + + //might as well dump it directly... + sc->sn.buffer = sample_buffer; + sc->sn.samples = len / sc->sn.samplebytes; + S_PaintChannels (sc, curtime + (len / framesz)); + sc->sn.samples = 0; + sc->sn.buffer = NULL; + sc->snd_sent += len; } } -static void PPAPI_Shutdown(soundcardinfo_t *sc) -{ - audio_interface->StopPlayback((PP_Resource)sc->handle); - ppb_core->ReleaseResource((PP_Resource)sc->handle); -} - -static unsigned int PPAPI_GetDMAPos(soundcardinfo_t *sc) -{ - sc->sn.samplepos = sc->snd_sent / (sc->sn.samplebits/8); - return sc->sn.samplepos; -} - -static void PPAPI_UnlockBuffer(soundcardinfo_t *sc, void *buffer) -{ -} - -static void *PPAPI_LockBuffer(soundcardinfo_t *sc, unsigned int *sampidx) -{ - *sampidx = 0; - return sc->sn.buffer; -} - -static void PPAPI_Submit(soundcardinfo_t *sc, int start, int end) -{ +static void PPAPI_Shutdown(soundcardinfo_t *sc) +{ + audio_interface->StopPlayback((PP_Resource)sc->handle); + ppb_core->ReleaseResource((PP_Resource)sc->handle); } -int PPAPI_InitCard (soundcardinfo_t *sc, int cardnum) +static unsigned int PPAPI_GetDMAPos(soundcardinfo_t *sc) +{ + sc->sn.samplepos = sc->snd_sent / sc->sn.samplebytes; + return sc->sn.samplepos; +} + +static void PPAPI_UnlockBuffer(soundcardinfo_t *sc, void *buffer) +{ +} + +static void *PPAPI_LockBuffer(soundcardinfo_t *sc, unsigned int *sampidx) +{ + *sampidx = 0; + return sc->sn.buffer; +} + +static void PPAPI_Submit(soundcardinfo_t *sc, int start, int end) +{ +} + +static qboolean PPAPI_InitCard (soundcardinfo_t *sc, const char *cardname) { PP_Resource config; int framecount; /*I'm not aware of any limits on the number of 'devices' we can create, but virtual devices on the same physical device are utterly pointless, so don't load more than one*/ - if (cardnum != 0) - return 2; + if (cardname && *cardname) + return false; //only use the default device /*the docs say only two sample rates are allowed*/ if (sc->sn.speed <= 44100) sc->sn.speed = 44100; else sc->sn.speed = 48000; - /*we can't choose these two settings*/ - sc->sn.samplebits = 16; + /*we can't choose these settings at all*/ + sc->sn.samplebytes = 2; + sc->sn.sampleformat = QSF_S16; sc->sn.numchannels = 2; #ifdef PPB_AUDIO_CONFIG_INTERFACE_1_1 @@ -107,10 +108,15 @@ int PPAPI_InitCard (soundcardinfo_t *sc, int cardnum) if (sc->handle) { if (audio_interface->StartPlayback((PP_Resource)sc->handle)) - return 1; + return true; } } - return 0; + return false; } -int (*pPPAPI_InitCard) (soundcardinfo_t *sc, int cardnum) = &PPAPI_InitCard; \ No newline at end of file +sounddriver_t PPAPI_AudioOutput = +{ + "Pepper", + PPAPI_InitCard, + NULL +}; diff --git a/engine/qclib/pr_exec.c b/engine/qclib/pr_exec.c index 39c1a8051..cf7eb168b 100644 --- a/engine/qclib/pr_exec.c +++ b/engine/qclib/pr_exec.c @@ -1555,7 +1555,6 @@ static int PR_NoDebugVM(progfuncs_t *fte_restrict progfuncs) ofs = strlen(stack); PR_SaveCallStack (progfuncs, stack, &ofs, sizeof(stack)); PR_RunError (&progfuncs->funcs, stack); - free(stack); return -1; } #endif diff --git a/engine/qclib/qcc.h b/engine/qclib/qcc.h index adc93f5b1..4a2649d08 100644 --- a/engine/qclib/qcc.h +++ b/engine/qclib/qcc.h @@ -512,7 +512,7 @@ typedef struct pbool used:1; pbool evil:1; pbool varg:1; - char *fromfile; + const char *fromfile; int fromline; int namelen; diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index b851a8f26..95ceb4810 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -14244,13 +14244,13 @@ void QCC_PR_ParseDefs (char *classname, pbool fatal) /*aliasof =*/ QCC_PR_ParseName(); else if (pr_token_type == tt_immediate) { - /*aliasof =*/ pr_immediate_string; + //aliasof = copy(pr_immediate_string); QCC_PR_Lex(); } QCC_PR_Expect(")"); } else if (QCC_PR_CheckName("accumulate")) - doweak = dowrap = true; + doweak = dowrap = true; //FIXME: should instead append to the previous function, I think, which requires not finishing it properly until later, or something. else { QCC_PR_ParseWarning(0, "Unknown attribute \"%s\"", pr_token); diff --git a/engine/qclib/qcc_pr_lex.c b/engine/qclib/qcc_pr_lex.c index f388b65d2..49a151b66 100644 --- a/engine/qclib/qcc_pr_lex.c +++ b/engine/qclib/qcc_pr_lex.c @@ -2931,7 +2931,7 @@ so if present, the preceeding \\\n and following \\\n must become an actual \n i cnst->evil = true; preprocessorhack = true; } - else// if (preprocessorhack) + else if (preprocessorhack) { *d++ = '\n'; preprocessorhack = false; diff --git a/engine/qclib/qccgui.c b/engine/qclib/qccgui.c index 9106f85f2..5e135d0e1 100644 --- a/engine/qclib/qccgui.c +++ b/engine/qclib/qccgui.c @@ -4589,14 +4589,18 @@ void (PNGAPI *qpng_read_end) PNGARG((png_structp png_ptr, png_infop info_ptr)) P void (PNGAPI *qpng_read_image) PNGARG((png_structp png_ptr, png_bytepp image)) PSTATIC(png_read_image); png_byte (PNGAPI *qpng_get_bit_depth) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_bit_depth); png_byte (PNGAPI *qpng_get_channels) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_channels); -png_size_t (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); +#if PNG_LIBPNG_VER < 10400 + png_uint_32 (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); +#else + png_size_t (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); +#endif void (PNGAPI *qpng_read_update_info) PNGARG((png_structp png_ptr, png_infop info_ptr)) PSTATIC(png_read_update_info); void (PNGAPI *qpng_set_strip_16) PNGARG((png_structp png_ptr)) PSTATIC(png_set_strip_16); void (PNGAPI *qpng_set_expand) PNGARG((png_structp png_ptr)) PSTATIC(png_set_expand); void (PNGAPI *qpng_set_gray_to_rgb) PNGARG((png_structp png_ptr)) PSTATIC(png_set_gray_to_rgb); void (PNGAPI *qpng_set_tRNS_to_alpha) PNGARG((png_structp png_ptr)) PSTATIC(png_set_tRNS_to_alpha); png_uint_32 (PNGAPI *qpng_get_valid) PNGARG((png_const_structp png_ptr, png_const_infop info_ptr, png_uint_32 flag)) PSTATIC(png_get_valid); -#if PNG_LIBPNG_VER > 10400 +#if PNG_LIBPNG_VER >= 10400 void (PNGAPI *qpng_set_expand_gray_1_2_4_to_8) PNGARG((png_structp png_ptr)) PSTATIC(png_set_expand_gray_1_2_4_to_8); #else void (PNGAPI *qpng_set_gray_1_2_4_to_8) PNGARG((png_structp png_ptr)) PSTATIC(png_set_gray_1_2_4_to_8); diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index b04491416..4593467bc 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -6894,12 +6894,21 @@ void log(string name, float console, string text) static void QCBUILTIN PF_logtext(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { char name[MAX_OSPATH]; - const char *text; + const char *otext, *text; vfsfile_t *file; + char unitext[8192], *out = unitext; + int err; snprintf(name, MAX_OSPATH, "%s.log", PR_GetStringOfs(prinst, OFS_PARM0)); - text = PF_VarString(prinst, 2, pr_globals); - PR_CleanText(text); + otext = text = PF_VarString(prinst, 2, pr_globals); + while (*text) + { + int cp = unicode_decode(&err, text, (char**)&text, false); + if ((cp >= 0xe000 && cp < 0xe100) || cp == '\r') + cp = readable2[cp&0xff]; //dequake it + out += utf8_encode(out, cp, sizeof(unitext)-1-(out-unitext)); + } + *out++ = 0; file = FS_OpenVFS(name, "ab", FS_GAME); if (file == NULL) @@ -6908,12 +6917,12 @@ static void QCBUILTIN PF_logtext(pubprogfuncs_t *prinst, struct globalvars_s *pr } else { - VFS_WRITE(file, text, strlen(text)); + VFS_WRITE(file, unitext, out-unitext); VFS_CLOSE (file); } if (G_FLOAT(OFS_PARM1)) - Con_Printf("%s", text); + Con_Printf("%s", otext); } #endif @@ -10654,10 +10663,10 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"strreplace", PF_strreplace, 0, 0, 0, 484, "string(string search, string replace, string subject)"},//DP_QC_STRREPLACE {"strireplace", PF_strireplace, 0, 0, 0, 485, "string(string search, string replace, string subject)"},//DP_QC_STRREPLACE {"getsurfacepointattribute",PF_getsurfacepointattribute,0,0,0, 486, "vector(entity e, float s, float n, float a)"},//DP_QC_GETSURFACEPOINTATTRIBUTE - {"gecko_create", PF_Fixme, 0, 0, 0, 487, D("float(string name)", "Create a new 'browser tab' shader with the specified name that can then be drawn via drawpic (shader should not already exist - including from map/model textures or disk). In order to function correctly, this builtin depends upon external plugins being available. Use gecko_navigate to navigate it to a page of your choosing.")},//DP_GECKO_SUPPORT + {"gecko_create", PF_Fixme, 0, 0, 0, 487, D("float(string name, optional string initialURI)", "Create a new 'browser tab' shader with the specified name that can then be drawn via drawpic (shader should not already exist - including from map/model textures or disk). In order to function correctly, this builtin depends upon external plugins being available. Use gecko_navigate to navigate it to a page of your choosing.")},//DP_GECKO_SUPPORT {"gecko_destroy", PF_Fixme, 0, 0, 0, 488, D("void(string name)", "Destroy a shader.")},//DP_GECKO_SUPPORT {"gecko_navigate", PF_Fixme, 0, 0, 0, 489, D("void(string name, string URI)", "Sends a command to the media decoder attached to the specified shader. In the case of a browser decoder, this changes the url that the browser displays. 'cmd:[un]focus' will tell the decoder that it has focus.")},//DP_GECKO_SUPPORT - {"gecko_keyevent", PF_Fixme, 0, 0, 0, 490, D("float(string name, float key, float eventtype)", "Send a key event to a media decoder. This applies only to interactive decoders like browsers.")},//DP_GECKO_SUPPORT + {"gecko_keyevent", PF_Fixme, 0, 0, 0, 490, D("float(string name, float key, float eventtype, optional float charcode)", "Send a key event to a media decoder. This applies only to interactive decoders like browsers.")},//DP_GECKO_SUPPORT {"gecko_mousemove", PF_Fixme, 0, 0, 0, 491, D("void(string name, float x, float y)", "Sets a media decoder shader's mouse position. Values should be 0-1.")},//DP_GECKO_SUPPORT {"gecko_resize", PF_Fixme, 0, 0, 0, 492, D("void(string name, float w, float h)", "Request to resize a media decoder.")},//DP_GECKO_SUPPORT {"gecko_get_texture_extent",PF_Fixme, 0, 0, 0, 493, D("vector(string name)", "Retrieves a media decoder current image pixel sizes.")},//DP_GECKO_SUPPORT @@ -10691,7 +10700,7 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"uri_unescape", PF_uri_unescape, 0, 0, 0, 511, "string(string in)"},//DP_QC_URI_ESCAPE {"num_for_edict", PF_num_for_edict, 0, 0, 0, 512, "float(entity ent)"},//DP_QC_NUM_FOR_EDICT {"uri_get", PF_uri_get, 0, 0, 0, 513, D("#define uri_post uri_get\nfloat(string uril, float id, optional string postmimetype, optional string postdata)", "uri_get() gets content from an URL and calls a callback \"uri_get_callback\" with it set as string; an unique ID of the transfer is returned\nreturns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string\nFor a POST request, you will typically want the postmimetype set to application/x-www-form-urlencoded.\nFor a GET request, omit the mime+data entirely.\nConsult your webserver/php/etc documentation for best-practise.")},//DP_QC_URI_GET - {"uri_post", PF_uri_get, 0, 0, 0, 513, D("float(string uril, float id, optional string postmimetype, optional string postdata)", "uri_get() gets content from an URL and calls a callback \"uri_get_callback\" with it set as string; an unique ID of the transfer is returned\nreturns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string"), true},//DP_QC_URI_POST + {"uri_post", PF_uri_get, 0, 0, 0, 513, D("float(string uril, float id, optional string postmimetype, optional string postdata, optional float strbuf)", "uri_get() gets content from an URL and calls a callback \"uri_get_callback\" with it set as string; an unique ID of the transfer is returned\nreturns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string"), true},//DP_QC_URI_POST {"tokenize_console",PF_tokenize_console,0, 0, 0, 514, D("float(string str)", "Tokenize a string exactly as the console's tokenizer would do so. The regular tokenize builtin became bastardized for convienient string parsing, which resulted in a large disparity that can be exploited to bypass checks implemented in a naive SV_ParseClientCommand function, therefore you can use this builtin to make sure it exactly matches.")}, {"argv_start_index",PF_argv_start_index,0, 0, 0, 515, D("float(float idx)", "Returns the character index that the tokenized arg started at.")}, {"argv_end_index", PF_argv_end_index, 0, 0, 0, 516, D("float(float idx)", "Returns the character index that the tokenized arg stopped at.")}, @@ -11539,6 +11548,8 @@ void PR_DumpPlatform_f(void) {"CSQC_Parse_TempEntity", "float()", CS, "Please don't use this. Use CSQC_Parse_Event and multicasts instead."}, {"GameCommand", "void(string cmdtext)", CS|MENU}, + {"Cef_GeneratePage", "string(string uri, string method, string postdata, __in string requestheaders, __inout string responseheaders)", QW|NQ|CS|MENU, "Provides an entrypoint to generate pages for the CEF plugin from within QC. Headers are \n-separated key/value pairs (use tokenizebyseparator)."}, +// {"HTTP_GeneratePage", "string(string uri, string method, string postdata, __in string requestheaders, __inout string responseheaders)", QW|NQ, "Provides an entrypoint to generate pages for pages requested over http (sv_listen_tcp+net_enable_http). Headers are \n-separated key/value pairs (use tokenizebyseparator)."}, {"init", "void(float prevprogs)", QW|NQ|CS, "Part of FTE_MULTIPROGS. Called as soon as a progs is loaded, called at a time when entities are not valid. This is the only time when it is safe to call addprogs without field assignment. As it is also called as part of addprogs, this also gives you a chance to hook functions in modules that are already loaded (via externget+externget)."}, {"initents", "void()", QW|NQ|CS, "Part of FTE_MULTIPROGS. Called after fields have been finalized. This is the first point at which it is safe to call spawn(), and is called before any entity fields have been parsed. You can use this entrypoint to send notifications to other modules."}, diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index c6c31db98..8991ce0d1 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -1808,7 +1808,10 @@ static void SV_Status_f (void) float pi, po, bi, bo; int columns = 80; - extern cvar_t sv_listen_qw, sv_listen_nq, sv_listen_dp; + extern cvar_t sv_listen_qw; +#ifdef NQPROT + extern cvar_t sv_listen_nq, sv_listen_dp; +#endif #ifdef QWOVERQ3 extern cvar_t sv_listen_q3; #endif @@ -1874,8 +1877,8 @@ static void SV_Status_f (void) if (sv_listen_q3.ival) Con_Printf(" Q3"); #endif #ifdef HAVE_DTLS - if (sv_listen_dtls.ival >= 2) - Con_Printf(" +DTLS"); + if (sv_listen_dtls.ival >= 3) + Con_Printf(" DTLS-only"); else if (sv_listen_dtls.ival) Con_Printf(" DTLS"); #endif diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index 0a47ab675..c67f755be 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -1933,7 +1933,6 @@ void SVDP_EmitEntitiesUpdate (client_t *client, client_frame_t *frame, packet_en { unsigned int bits; int outno, outmax = frame->maxresend; - qboolean overflow = false; struct resendinfo_s *resend = frame->resend; MSG_WriteByte(msg, svcdp_entities); @@ -1950,10 +1949,7 @@ void SVDP_EmitEntitiesUpdate (client_t *client, client_frame_t *frame, packet_en if (!bits) continue; if (msg->cursize + 50 > msg->maxsize) - { - overflow = true; break; /*give up if it gets full. FIXME: bone data is HUGE.*/ - } if (outno >= outmax) { //expand the frames. may need some copying... SV_ExpandNackFrames(client, outno+1); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 64cbc979d..12cc7d752 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -115,6 +115,7 @@ cvar_t allow_download_other = CVARD("allow_download_other", "0", "0 blocks down extern cvar_t sv_allow_splitscreen; +cvar_t sv_guidhash = CVARD("sv_guidkey", "", "If set, clients will calculate their GUID values against this string instead of the server's IP address. This allows consistency between multiple servers (for stats tracking), but do NOT treat the client's GUID as something that is secure."); cvar_t sv_serverip = CVARD("sv_serverip", "", "Set this cvar to the server's public ip address if the server is behind a firewall and cannot detect its own public address. Providing a port is required if the firewall/nat remaps it, but is otherwise optional."); cvar_t sv_public = CVAR("sv_public", "0"); cvar_t sv_listen_qw = CVARAFD("sv_listen_qw", "1", "sv_listen", 0, "Specifies whether normal clients are allowed to connect."); @@ -124,7 +125,7 @@ cvar_t sv_listen_dp = CVARD("sv_listen_dp", "0", "Allows the server to respond cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0"); #endif #ifdef HAVE_DTLS -cvar_t sv_listen_dtls = CVARCD("net_enable_dtls", "", SV_Listen_Dtls_Changed, "Controls serverside dtls support.\n0: dtls blocked, not advertised.\n1: available in desired.\n2: used where possible.\n3: disallow non-dtls clients (sv_port_tcp should be eg tls://[::]:27500 to also disallow unencrypted tcp connections)."); +cvar_t sv_listen_dtls = CVARCD("net_enable_dtls", "", SV_Listen_Dtls_Changed, "Controls serverside dtls support.\n0: dtls blocked, not advertised.\n1: available in desired.\n2: used where possible (recommended setting).\n3: disallow non-dtls clients (sv_port_tcp should be eg tls://[::]:27500 to also disallow unencrypted tcp connections)."); #endif cvar_t sv_reportheartbeats = CVAR("sv_reportheartbeats", "1"); cvar_t sv_highchars = CVAR("sv_highchars", "1"); @@ -1633,6 +1634,24 @@ qboolean SVC_GetChallenge (qboolean respond_dp) over+=sizeof(lng); } #endif + if (*sv_guidhash.string +#ifdef HAVE_DTLS + && (sv_listen_dtls.ival < 3 || net_from.prot == NP_DTLS) +#endif + ) + { + lng = LittleLong(PROTOCOL_VERSION_VARLENGTH); + memcpy(over, &lng, sizeof(lng)); + over+=sizeof(lng); + lng = strlen(sv_guidhash.string); + memcpy(over, &lng, sizeof(lng)); + over+=sizeof(lng); + lng = LittleLong(PROTOCOL_INFO_GUID); + memcpy(over, &lng, sizeof(lng)); + over+=sizeof(lng); + memcpy(over, sv_guidhash.string, strlen(sv_guidhash.string)); + over+=strlen(sv_guidhash.string); + } } if (respond_dp) @@ -1661,6 +1680,7 @@ qboolean SVC_GetChallenge (qboolean respond_dp) return true; } +#ifdef SVRANKING static void VARGS SV_OutOfBandPrintf (int q2, netadr_t *adr, char *format, ...) { va_list argptr; @@ -1705,6 +1725,7 @@ static void VARGS SV_OutOfBandTPrintf (int q2, netadr_t *adr, int language, tran Netchan_OutOfBand (NS_SERVER, adr, strlen(string), (qbyte *)string); } +#endif qboolean SV_ChallengePasses(int challenge) { @@ -1721,6 +1742,7 @@ qboolean SV_ChallengePasses(int challenge) return false; } +#ifdef NQPROT //DP sends us a getchallenge followed by a CCREQ_CONNECT at about the same time. //this means that DP clients tend to connect as generic NQ clients. //and because DP _REQUIRES_ sv_bigcoords, they tend to end up being given fitz/rmq protocols @@ -1739,6 +1761,7 @@ static qboolean SV_ChallengeRecent(void) } return false; } +#endif void VARGS SV_RejectMessage(int protocol, char *format, ...) { @@ -5154,11 +5177,11 @@ void SV_InitLocal (void) Cvar_Register(&sv_autosave, cvargroup_servercontrol); #endif #endif - Cmd_AddCommand ("savegame_legacy", SV_LegacySavegame_f); - Cmd_AddCommandAD ("savegame", SV_Savegame_f, SV_Savegame_c, NULL); - Cmd_AddCommandAD ("loadgame", SV_Loadgame_f, SV_Savegame_c, NULL); - Cmd_AddCommandAD ("save", SV_Savegame_f, SV_Savegame_c, NULL); - Cmd_AddCommandAD ("load", SV_Loadgame_f, SV_Savegame_c, NULL); + Cmd_AddCommandAD ("savegame_legacy", SV_LegacySavegame_f, SV_Savegame_c, "Saves the game in a format compatible with vanilla Quake. Anything not supported by that format will be lost."); + Cmd_AddCommandAD ("savegame", SV_Savegame_f, SV_Savegame_c, "Saves the game to the named location."); + Cmd_AddCommandAD ("loadgame", SV_Loadgame_f, SV_Savegame_c, "Loads an existing saved game."); + Cmd_AddCommandAD ("save", SV_Savegame_f, SV_Savegame_c, "Saves the game to the named location."); + Cmd_AddCommandAD ("load", SV_Loadgame_f, SV_Savegame_c, "Loads an existing saved game."); SV_MVDInit(); diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 2b95c4eb4..1ec9cc8bd 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -2026,6 +2026,7 @@ void SV_Begin_Core(client_t *split) } } +#ifndef NOLEGACY split->dp_ping = NULL; split->dp_pl = NULL; if (progstype == PROG_NQ) @@ -2033,6 +2034,7 @@ void SV_Begin_Core(client_t *split) split->dp_ping = (float*)sv.world.progs->GetEdictFieldValue(sv.world.progs, sv_player, "ping", ev_float, NULL); split->dp_pl = (float*)sv.world.progs->GetEdictFieldValue(sv.world.progs, sv_player, "ping_packetloss", ev_float, NULL); } +#endif } /* diff --git a/engine/shaders/glsl/lpp_light.glsl b/engine/shaders/glsl/lpp_light.glsl index b3d2bb87d..1f2d36603 100644 --- a/engine/shaders/glsl/lpp_light.glsl +++ b/engine/shaders/glsl/lpp_light.glsl @@ -34,6 +34,7 @@ uniform mat4 l_cubematrix; #endif uniform sampler2DShadow s_shadowmap; +//FIXME: shadowmaps need to be atlased! uniform vec4 l_shadowmapproj; //light projection matrix info uniform vec2 l_shadowmapscale; //xy are the texture scale, z is 1, w is the scale. vec3 ShadowmapCoord(vec4 cubeproj) @@ -179,6 +180,9 @@ void main () float nDotL = dot(norm, lightDir); float lightDiffuse = max(0.0, nDotL) * atten; + /*calc specular term*/ + //fixme + //fixme: apply fog //fixme: output a specular term //fixme: cubemap filters diff --git a/engine/vk/vk_backend.c b/engine/vk/vk_backend.c index 8be849003..ff37c01ab 100644 --- a/engine/vk/vk_backend.c +++ b/engine/vk/vk_backend.c @@ -1143,8 +1143,9 @@ qboolean VK_LoadBlob(program_t *prog, void *blobdata, const char *name) prog->pipelines = NULL; //generated as needed, depending on blend states etc. return true; } -static void VKBE_ReallyDeleteProg(program_t *prog) +static void VKBE_ReallyDeleteProg(void *vprog) { //nothing else is refering to this data any more, its safe to obliterate it. + program_t *prog = vprog; struct pipeline_s *pipe; while(prog->pipelines) { @@ -5030,7 +5031,7 @@ static qboolean BE_GenerateRefraction(batch_t *batch, shader_t *bs) extern cvar_t r_refractreflect_scale; float oldil; int oldbem; - struct vk_rendertarg *targ; +// struct vk_rendertarg *targ; //these flags require rendering some view as an fbo if (r_refdef.recurse) return false; @@ -5042,7 +5043,7 @@ static qboolean BE_GenerateRefraction(batch_t *batch, shader_t *bs) return false; //multisample rendering can't deal with this. oldbem = shaderstate.mode; oldil = shaderstate.identitylighting; - targ = vk.rendertarg; +// targ = vk.rendertarg; if (bs->flags & SHADER_HASREFLECT) { @@ -5812,7 +5813,7 @@ void VKBE_DestroyShadowBuffer(struct vk_shadowbuffer *buf) { if (buf && buf->isstatic) { - struct vk_shadowbuffer_destroy *ctx = VK_AtFrameEnd(VKBE_DestroyShadowBuffer_Delayed, buf, sizeof(*buf)); + VK_AtFrameEnd(VKBE_DestroyShadowBuffer_Delayed, buf, sizeof(*buf)); Z_Free(buf); } } diff --git a/engine/vk/vk_init.c b/engine/vk/vk_init.c index 5e5cb801b..7027980b1 100644 --- a/engine/vk/vk_init.c +++ b/engine/vk/vk_init.c @@ -17,6 +17,11 @@ extern cvar_t vk_khr_push_descriptor; extern cvar_t vid_srgb, vid_vsync, vid_triplebuffer, r_stereo_method, vid_multisample; void R2D_Console_Resize(void); +#ifndef MULTITHREAD +#define Sys_LockConditional(c) +#define Sys_UnlockConditional(c) +#endif + const char *vklayerlist[] = { #if 1 @@ -142,11 +147,16 @@ void VK_DestroyVkTexture(vk_image_t *img) if (img->memory) vkFreeMemory(vk.device, img->memory, vkallocationcb); } +static void VK_DestroyVkTexture_Delayed(void *w) +{ + VK_DestroyVkTexture(w); +} static void VK_DestroySwapChain(void) { uint32_t i; +#ifdef MULTITHREAD if (vk.submitcondition) { Sys_LockConditional(vk.submitcondition); @@ -159,6 +169,7 @@ static void VK_DestroySwapChain(void) Sys_WaitOnThread(vk.submitthread); vk.submitthread = NULL; } +#endif while (vk.work) { Sys_LockConditional(vk.submitcondition); @@ -787,7 +798,7 @@ void VK_CreateSampler(unsigned int flags, vk_image_t *img) VkAssert(vkCreateSampler(vk.device, &lmsampinfo, NULL, &img->sampler)); } -static void VK_DestroySampler(struct vk_frameend *w) +static void VK_DestroySampler(void *w) { VkSampler s = *(VkSampler*)w; vkDestroySampler(vk.device, s, vkallocationcb); @@ -1228,7 +1239,7 @@ qboolean VK_LoadTextureMips (texid_t tex, const struct pendingtextureinfo *mips) tex->vkimage->encoding != mips->encoding || tex->vkimage->type != mips->type) { - VK_AtFrameEnd(VK_DestroyVkTexture, tex->vkimage, sizeof(*tex->vkimage)); + VK_AtFrameEnd(VK_DestroyVkTexture_Delayed, tex->vkimage, sizeof(*tex->vkimage)); // vkDeviceWaitIdle(vk.device); //erk, we can't cope with a commandbuffer poking the texture while things happen // VK_FencedCheck(); // VK_DestroyVkTexture(tex->vkimage); @@ -1322,7 +1333,6 @@ qboolean VK_LoadTextureMips (texid_t tex, const struct pendingtextureinfo *mips) bci.size = 0; for (i = 0; i < mipcount; i++) { - VkImageSubresource subres = {0}; VkBufferImageCopy region; //figure out the number of 'blocks' in the image. //for non-compressed formats this is just the width directly. @@ -2318,7 +2328,9 @@ void VKVID_QueueGetRGBData (void (*gotrgbdata) (void *rgbdata, intptr_t bytest VkAssert(vkCreateBuffer(vk.device, &bci, vkallocationcb, &capt->buffer)); vkGetBufferMemoryRequirements(vk.device, capt->buffer, &mem_reqs); memAllocInfo.allocationSize = mem_reqs.size; - memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_CACHED_BIT); + if (memAllocInfo.memoryTypeIndex == ~0u) + memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); VkAssert(vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &capt->memory)); VkAssert(vkBindBufferMemory(vk.device, capt->buffer, capt->memory, 0)); @@ -2849,6 +2861,7 @@ qboolean VK_SCR_UpdateScreen (void) if (vk.neednewswapchain && !vk.frame) { +#ifdef MULTITHREAD //kill the thread if (vk.submitthread) { @@ -2858,6 +2871,7 @@ qboolean VK_SCR_UpdateScreen (void) Sys_WaitOnThread(vk.submitthread); vk.submitthread = NULL; } +#endif //make sure any work is actually done BEFORE the swapchain gets destroyed while (vk.work) { @@ -2871,10 +2885,12 @@ qboolean VK_SCR_UpdateScreen (void) VK_CreateSwapChain(); vk.neednewswapchain = false; +#ifdef MULTITHREAD if (vk.allowsubmissionthread && (vk_submissionthread.ival || !*vk_submissionthread.string)) { vk.submitthread = Sys_CreateThread("vksubmission", VK_Submit_Thread, NULL, THREADP_HIGHEST, 0); } +#endif } if (!VK_SCR_GrabBackBuffer()) @@ -3225,6 +3241,7 @@ static void VK_Submit_DoWork(void) } } +#ifdef MULTITHREAD //oh look. a thread. //nvidia's drivers seem to like doing a lot of blocking in queuesubmit and queuepresent(despite the whole QUEUE thing). //so thread this work so the main thread doesn't have to block so much. @@ -3241,6 +3258,7 @@ int VK_Submit_Thread(void *arg) Sys_UnlockConditional(vk.submitcondition); return true; } +#endif void VK_Submit_Work(VkCommandBuffer cmdbuf, VkSemaphore semwait, VkPipelineStageFlags semwaitstagemask, VkSemaphore semsignal, VkFence fencesignal, struct vkframe *presentframe, struct vk_fencework *fencedwork) { @@ -3262,9 +3280,11 @@ void VK_Submit_Work(VkCommandBuffer cmdbuf, VkSemaphore semwait, VkPipelineStage ; *link = work; +#ifdef MULTITHREAD if (vk.submitthread && !vk.neednewswapchain) Sys_ConditionSignal(vk.submitcondition); else +#endif VK_Submit_DoWork(); Sys_UnlockConditional(vk.submitcondition); } @@ -3335,7 +3355,7 @@ void VK_CheckTextureFormats(void) } //initialise the vulkan instance, context, device, etc. -qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*createSurface)(void), void (*dopresent)(struct vkframe *theframe)) +qboolean VK_Init(rendererstate_t *info, const char **sysextnames, qboolean (*createSurface)(void), void (*dopresent)(struct vkframe *theframe)) { VkQueueFamilyProperties *queueprops; VkResult err; @@ -3366,7 +3386,9 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat for (e = 0; e < countof(knowndevexts); e++) *knowndevexts[e].flag = false; +#ifdef MULTITHREAD vk.allowsubmissionthread = true; +#endif vk.neednewswapchain = true; vk.triplebuffer = info->triplebuffer; vk.vsync = info->wait; @@ -3390,7 +3412,7 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat //try and enable some instance extensions... { qboolean surfext = false; - uint32_t count, i; + uint32_t count, i, j; VkExtensionProperties *ext; vkEnumerateInstanceExtensionProperties(NULL, &count, NULL); ext = malloc(sizeof(*ext)*count); @@ -3401,21 +3423,27 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat extensions[extensions_count++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME; else if (!strcmp(ext[i].extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) extensions[extensions_count++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; - else if (sysextname && !strcmp(ext[i].extensionName, sysextname)) - { - extensions[extensions_count++] = sysextname; - vk.khr_swapchain = true; - } - else if (sysextname && !strcmp(ext[i].extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) + else if (sysextnames && !strcmp(ext[i].extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) { extensions[extensions_count++] = VK_KHR_SURFACE_EXTENSION_NAME; surfext = true; } + else if (sysextnames) + { + for (j = 0; sysextnames[j]; j++) + { + if (!strcmp(ext[i].extensionName, sysextnames[j])) + { + extensions[extensions_count++] = sysextnames[j]; + vk.khr_swapchain = true; + } + } + } } free(ext); - if (sysextname && (!vk.khr_swapchain || !surfext)) + if (sysextnames && (!vk.khr_swapchain || !surfext)) { - Con_Printf("Vulkan instance driver lacks support for %s\n", sysextname); + Con_Printf("Vulkan instance driver lacks support for %s\n", sysextnames[0]); return false; } } @@ -3873,7 +3901,9 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat else //16bit depth is guarenteed in vulkan vk.depthformat = VK_FORMAT_D16_UNORM; +#ifdef MULTITHREAD vk.submitcondition = Sys_CreateConditional(); +#endif { VkPipelineCacheCreateInfo pci = {VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO}; @@ -3888,10 +3918,12 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat { vk.neednewswapchain = false; +#ifdef MULTITHREAD if (vk.allowsubmissionthread && (vk_submissionthread.ival || !*vk_submissionthread.string)) { vk.submitthread = Sys_CreateThread("vksubmission", VK_Submit_Thread, NULL, THREADP_HIGHEST, 0); } +#endif } return true; } @@ -3935,8 +3967,10 @@ void VK_Shutdown(void) vkDestroySurfaceKHR(vk.instance, vk.surface, vkallocationcb); if (vk.instance) vkDestroyInstance(vk.instance, vkallocationcb); +#ifdef MULTITHREAD if (vk.submitcondition) Sys_DestroyConditional(vk.submitcondition); +#endif memset(&vk, 0, sizeof(vk)); diff --git a/engine/vk/vkrenderer.h b/engine/vk/vkrenderer.h index 8950104d1..d4738188b 100644 --- a/engine/vk/vkrenderer.h +++ b/engine/vk/vkrenderer.h @@ -378,7 +378,7 @@ uint32_t vk_find_memory_require(uint32_t typeBits, VkFlags requirements_mask); void VK_DoPresent(struct vkframe *theframe); -qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*createSurface)(void), void (*dopresent)(struct vkframe *theframe)); +qboolean VK_Init(rendererstate_t *info, const char **sysextname, qboolean (*createSurface)(void), void (*dopresent)(struct vkframe *theframe)); void VK_Shutdown(void); void VK_R_BloomBlend (texid_t source, int x, int y, int w, int h); diff --git a/engine/web/gl_vidweb.c b/engine/web/gl_vidweb.c index d3ad1ded0..64f781182 100644 --- a/engine/web/gl_vidweb.c +++ b/engine/web/gl_vidweb.c @@ -287,7 +287,8 @@ qboolean GLVID_Init (rendererstate_t *info, unsigned char *palette) vid.activeapp = true; - GL_Init(info, GLVID_getsdlglfunction); + if (!GL_Init(info, GLVID_getsdlglfunction)) + return false; qglViewport (0, 0, vid.pixelwidth, vid.pixelheight); diff --git a/fteqtv/control.c b/fteqtv/control.c index cb55dcf68..f0dff23b5 100644 --- a/fteqtv/control.c +++ b/fteqtv/control.c @@ -1,640 +1,640 @@ -/* -Contains the control routines that handle both incoming and outgoing stuff -*/ - -#include "qtv.h" -#include -#include "bsd_string.h" - -#ifndef _WIN32 -#include +/* +Contains the control routines that handle both incoming and outgoing stuff +*/ + +#include "qtv.h" +#include +#include "bsd_string.h" + +#ifndef _WIN32 +#include #include #else -#include -#endif - -// char *date = "Oct 24 1996"; -static const char *date = __DATE__ ; -static const char *mon[12] = -{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; -static char mond[12] = -{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - -// returns days since Oct 24 1996 -int build_number( void ) -{ - int m; - int d = 0; - int y; - int b; - - for (m = 0; m < 11; m++) - { - if (strncmp( &date[0], mon[m], 3 ) == 0) - break; - d += mond[m]; - } - - d += atoi( &date[4] ) - 1; - - y = atoi( &date[7] ) - 1900; - - b = d + (int)((y - 1) * 365.25); - - if (((y % 4) == 0) && m > 1) - { - b += 1; - } - - b -= 35778; // Dec 16 1998 - - return b; -} - - - -typedef struct { - char name[56]; - int offset; - int length; -} pakfile; -// PACK, offset, lengthofpakfiles -FILE *FindInPaks(char *gamedir, char *filename, int *size) -{ - FILE *f; - char fname[1024]; - int i, j; - int numfiles; - unsigned int header[3]; - - pakfile pf; - - for (i = 0; ; i++) - { - sprintf(fname, "%s/pak%i.pak", gamedir, i); - f = fopen(fname, "rb"); - if (!f) - return NULL; //ran out of possible pak files. - - fread(header, 1, sizeof(header), f); - if (header[0] != *(unsigned int*)"PACK") - { //err... hmm. - fclose(f); - continue; - } - numfiles = LittleLong(header[2])/sizeof(pakfile); - fseek(f, LittleLong(header[1]), SEEK_SET); - for (j = 0; j < numfiles; j++) - { - fread(&pf, 1, sizeof(pf), f); - if (!strcmp(pf.name, filename)) - { - fseek(f, LittleLong(pf.offset), 0); - if (size) - *size = LittleLong(pf.length); - return f; - } - } - fclose(f); - //not found - } - return NULL; -} - -unsigned char *FS_ReadFile2(char *gamedir, char *filename, unsigned int *sizep) -{ - int size; - unsigned char *data; - - FILE *f; - char fname[1024]; - - if (!*filename) - return NULL; - - //try and read it straight out of the file system - sprintf(fname, "%s/%s", gamedir, filename); - f = fopen(fname, "rb"); - if (!f) - f = fopen(filename, "rb"); //see if we're being run from inside the gamedir - if (!f) - { - f = FindInPaks(gamedir, filename, &size); - if (!f) - f = FindInPaks("id1", filename, &size); - if (!f) - { - return NULL; - } - } - else - { - fseek(f, 0, SEEK_END); - size = ftell(f); - fseek(f, 0, SEEK_SET); - } - data = malloc(size); - if (data) - fread(data, 1, size, f); - fclose(f); - - if (sizep) - *sizep = size; - return data; -} - -unsigned char *FS_ReadFile(char *gamedir, char *filename, unsigned int *size) -{ - unsigned char *data; - if (!gamedir || !*gamedir || !strcmp(gamedir, "qw")) - data = NULL; - else - data = FS_ReadFile2(gamedir, filename, size); - if (!data) - { - data = FS_ReadFile2("qw", filename, size); - if (!data) - { - data = FS_ReadFile2("id1", filename, size); - if (!data) - { - return NULL; - } - } - } - return data; -} - -#ifndef _WIN32 -#define _cdecl -#endif -int _cdecl SortFilesByDate(const void *a, const void *b) -{ - if (((availdemo_t*)a)->time < ((availdemo_t*)b)->time) - return 1; - if (((availdemo_t*)a)->time > ((availdemo_t*)b)->time) - return -1; - - if (((availdemo_t*)a)->smalltime < ((availdemo_t*)b)->smalltime) - return 1; - if (((availdemo_t*)a)->smalltime > ((availdemo_t*)b)->smalltime) - return -1; - return 0; -} - -void Cluster_BuildAvailableDemoList(cluster_t *cluster) -{ - cluster->availdemoscount = 0; - -#ifdef _WIN32 - { - WIN32_FIND_DATA ffd; - HANDLE h; - char path[512]; - snprintf(path, sizeof(path), "%s*.mvd", cluster->demodir); - h = FindFirstFile(path, &ffd); - if (h != INVALID_HANDLE_VALUE) - { - do - { - if (cluster->availdemoscount == sizeof(cluster->availdemos)/sizeof(cluster->availdemos[0])) - break; - strlcpy(cluster->availdemos[cluster->availdemoscount].name, ffd.cFileName, sizeof(cluster->availdemos[0].name)); - cluster->availdemos[cluster->availdemoscount].size = ffd.nFileSizeLow; - cluster->availdemos[cluster->availdemoscount].time = ffd.ftLastWriteTime.dwHighDateTime; - cluster->availdemos[cluster->availdemoscount].smalltime = ffd.ftLastWriteTime.dwLowDateTime; - cluster->availdemoscount++; - } while(FindNextFile(h, &ffd)); - FindClose(h); - } - } -#else - { - DIR *dir; - struct dirent *ent; - struct stat sb; - char fullname[512]; - dir = opendir(cluster->demodir); //yeek! - if (dir) - { - for(;;) - { - if (cluster->availdemoscount == sizeof(cluster->availdemos)/sizeof(cluster->availdemos[0])) - break; - ent = readdir(dir); - if (!ent) - break; - if (*ent->d_name == '.') - continue; //ignore 'hidden' files - snprintf(fullname, sizeof(fullname), "%s%s", cluster->demodir, ent->d_name); - if (stat(fullname, &sb)) - continue; //some kind of error - strlcpy(cluster->availdemos[cluster->availdemoscount].name, ent->d_name, sizeof(cluster->availdemos[0].name)); - cluster->availdemos[cluster->availdemoscount].size = sb.st_size; - cluster->availdemos[cluster->availdemoscount].time = sb.st_mtime; - cluster->availdemoscount++; - } - closedir(dir); - } - else - Sys_Printf(cluster, "Couldn't open dir for demo listings\n"); - } -#endif - - qsort(cluster->availdemos, cluster->availdemoscount, sizeof(cluster->availdemos[0]), SortFilesByDate); -} - -void Cluster_Run(cluster_t *cluster, qboolean dowait) -{ - oproxy_t *pend, *pend2, *pend3; - sv_t *sv, *old; - - int m; - struct timeval timeout; - fd_set socketset; - - if (dowait) - { - - FD_ZERO(&socketset); - m = 0; - if (cluster->qwdsocket[0] != INVALID_SOCKET) - { - FD_SET(cluster->qwdsocket[0], &socketset); - if (cluster->qwdsocket[0] >= m) - m = cluster->qwdsocket[0]+1; +#include +#endif + +// char *date = "Oct 24 1996"; +static const char *date = __DATE__ ; +static const char *mon[12] = +{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +static char mond[12] = +{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +// returns days since Oct 24 1996 +int build_number( void ) +{ + int m; + int d = 0; + int y; + int b; + + for (m = 0; m < 11; m++) + { + if (strncmp( &date[0], mon[m], 3 ) == 0) + break; + d += mond[m]; + } + + d += atoi( &date[4] ) - 1; + + y = atoi( &date[7] ) - 1900; + + b = d + (int)((y - 1) * 365.25); + + if (((y % 4) == 0) && m > 1) + { + b += 1; + } + + b -= 35778; // Dec 16 1998 + + return b; +} + + + +typedef struct { + char name[56]; + int offset; + int length; +} pakfile; +// PACK, offset, lengthofpakfiles +FILE *FindInPaks(char *gamedir, char *filename, int *size) +{ + FILE *f; + char fname[1024]; + int i, j; + int numfiles; + unsigned int header[3]; + + pakfile pf; + + for (i = 0; ; i++) + { + sprintf(fname, "%s/pak%i.pak", gamedir, i); + f = fopen(fname, "rb"); + if (!f) + return NULL; //ran out of possible pak files. + + fread(header, 1, sizeof(header), f); + if (header[0] != *(unsigned int*)"PACK") + { //err... hmm. + fclose(f); + continue; + } + numfiles = LittleLong(header[2])/sizeof(pakfile); + fseek(f, LittleLong(header[1]), SEEK_SET); + for (j = 0; j < numfiles; j++) + { + fread(&pf, 1, sizeof(pf), f); + if (!strcmp(pf.name, filename)) + { + fseek(f, LittleLong(pf.offset), 0); + if (size) + *size = LittleLong(pf.length); + return f; + } + } + fclose(f); + //not found + } + return NULL; +} + +unsigned char *FS_ReadFile2(char *gamedir, char *filename, unsigned int *sizep) +{ + int size; + unsigned char *data; + + FILE *f; + char fname[1024]; + + if (!*filename) + return NULL; + + //try and read it straight out of the file system + sprintf(fname, "%s/%s", gamedir, filename); + f = fopen(fname, "rb"); + if (!f) + f = fopen(filename, "rb"); //see if we're being run from inside the gamedir + if (!f) + { + f = FindInPaks(gamedir, filename, &size); + if (!f) + f = FindInPaks("id1", filename, &size); + if (!f) + { + return NULL; + } + } + else + { + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + } + data = malloc(size); + if (data) + fread(data, 1, size, f); + fclose(f); + + if (sizep) + *sizep = size; + return data; +} + +unsigned char *FS_ReadFile(char *gamedir, char *filename, unsigned int *size) +{ + unsigned char *data; + if (!gamedir || !*gamedir || !strcmp(gamedir, "qw")) + data = NULL; + else + data = FS_ReadFile2(gamedir, filename, size); + if (!data) + { + data = FS_ReadFile2("qw", filename, size); + if (!data) + { + data = FS_ReadFile2("id1", filename, size); + if (!data) + { + return NULL; + } + } + } + return data; +} + +#ifndef _WIN32 +#define _cdecl +#endif +int _cdecl SortFilesByDate(const void *a, const void *b) +{ + if (((availdemo_t*)a)->time < ((availdemo_t*)b)->time) + return 1; + if (((availdemo_t*)a)->time > ((availdemo_t*)b)->time) + return -1; + + if (((availdemo_t*)a)->smalltime < ((availdemo_t*)b)->smalltime) + return 1; + if (((availdemo_t*)a)->smalltime > ((availdemo_t*)b)->smalltime) + return -1; + return 0; +} + +void Cluster_BuildAvailableDemoList(cluster_t *cluster) +{ + cluster->availdemoscount = 0; + +#ifdef _WIN32 + { + WIN32_FIND_DATA ffd; + HANDLE h; + char path[512]; + snprintf(path, sizeof(path), "%s*.mvd", cluster->demodir); + h = FindFirstFile(path, &ffd); + if (h != INVALID_HANDLE_VALUE) + { + do + { + if (cluster->availdemoscount == sizeof(cluster->availdemos)/sizeof(cluster->availdemos[0])) + break; + strlcpy(cluster->availdemos[cluster->availdemoscount].name, ffd.cFileName, sizeof(cluster->availdemos[0].name)); + cluster->availdemos[cluster->availdemoscount].size = ffd.nFileSizeLow; + cluster->availdemos[cluster->availdemoscount].time = ffd.ftLastWriteTime.dwHighDateTime; + cluster->availdemos[cluster->availdemoscount].smalltime = ffd.ftLastWriteTime.dwLowDateTime; + cluster->availdemoscount++; + } while(FindNextFile(h, &ffd)); + FindClose(h); + } + } +#else + { + DIR *dir; + struct dirent *ent; + struct stat sb; + char fullname[512]; + dir = opendir(cluster->demodir); //yeek! + if (dir) + { + for(;;) + { + if (cluster->availdemoscount == sizeof(cluster->availdemos)/sizeof(cluster->availdemos[0])) + break; + ent = readdir(dir); + if (!ent) + break; + if (*ent->d_name == '.') + continue; //ignore 'hidden' files + snprintf(fullname, sizeof(fullname), "%s%s", cluster->demodir, ent->d_name); + if (stat(fullname, &sb)) + continue; //some kind of error + strlcpy(cluster->availdemos[cluster->availdemoscount].name, ent->d_name, sizeof(cluster->availdemos[0].name)); + cluster->availdemos[cluster->availdemoscount].size = sb.st_size; + cluster->availdemos[cluster->availdemoscount].time = sb.st_mtime; + cluster->availdemoscount++; + } + closedir(dir); + } + else + Sys_Printf(cluster, "Couldn't open dir for demo listings\n"); + } +#endif + + qsort(cluster->availdemos, cluster->availdemoscount, sizeof(cluster->availdemos[0]), SortFilesByDate); +} + +void Cluster_Run(cluster_t *cluster, qboolean dowait) +{ + oproxy_t *pend, *pend2, *pend3; + sv_t *sv, *old; + + int m; + struct timeval timeout; + fd_set socketset; + + if (dowait) + { + + FD_ZERO(&socketset); + m = 0; + if (cluster->qwdsocket[0] != INVALID_SOCKET) + { + FD_SET(cluster->qwdsocket[0], &socketset); + if (cluster->qwdsocket[0] >= m) + m = cluster->qwdsocket[0]+1; } if (cluster->qwdsocket[1] != INVALID_SOCKET) { FD_SET(cluster->qwdsocket[1], &socketset); if (cluster->qwdsocket[1] >= m) m = cluster->qwdsocket[1]+1; - } - - for (sv = cluster->servers; sv; sv = sv->next) - { - if (sv->usequakeworldprotocols && sv->sourcesock != INVALID_SOCKET) - { - FD_SET(sv->sourcesock, &socketset); - if (sv->sourcesock >= m) - m = sv->sourcesock+1; - } - } - - #ifndef _WIN32 - #ifndef STDIN - #define STDIN 0 - #endif - FD_SET(STDIN, &socketset); - if (STDIN >= m) - m = STDIN+1; - #endif - - if (cluster->viewserver) - { - timeout.tv_sec = 0; - timeout.tv_usec = 1000; - } - else - { - timeout.tv_sec = 10/1000; - timeout.tv_usec = (100%1000)*1000; - } - - m = select(m, &socketset, NULL, NULL, &timeout); - -#ifdef _WIN32 - for (;;) - { - char buffer[8192]; - char *result; - char c; - - if (!_kbhit()) - break; - c = _getch(); - - if (c == '\n' || c == '\r') - { - Sys_Printf(cluster, "\n"); - if (cluster->inputlength) - { - cluster->commandinput[cluster->inputlength] = '\0'; - result = Rcon_Command(cluster, NULL, cluster->commandinput, buffer, sizeof(buffer), true); - Sys_Printf(cluster, "%s", result); - cluster->inputlength = 0; - cluster->commandinput[0] = '\0'; - } - } - else if (c == '\b') - { - if (cluster->inputlength > 0) - { - Sys_Printf(cluster, "%c", c); - Sys_Printf(cluster, " ", c); - Sys_Printf(cluster, "%c", c); - - cluster->inputlength--; - cluster->commandinput[cluster->inputlength] = '\0'; - } - } - else - { - Sys_Printf(cluster, "%c", c); - if (cluster->inputlength < sizeof(cluster->commandinput)-1) - { - cluster->commandinput[cluster->inputlength++] = c; - cluster->commandinput[cluster->inputlength] = '\0'; - } - } - } -#else - if (FD_ISSET(STDIN, &socketset)) - { - char buffer[8192]; - char *result; - cluster->inputlength = read (0, cluster->commandinput, sizeof(cluster->commandinput)); - if (cluster->inputlength >= 1) - { - cluster->commandinput[cluster->inputlength-1] = 0; // rip off the /n and terminate - cluster->inputlength--; - - if (cluster->inputlength) - { - cluster->commandinput[cluster->inputlength] = '\0'; - result = Rcon_Command(cluster, NULL, cluster->commandinput, buffer, sizeof(buffer), true); - printf("%s", result); - cluster->inputlength = 0; - cluster->commandinput[0] = '\0'; - } - } - } -#endif - } - - - - cluster->curtime = Sys_Milliseconds(); - - for (sv = cluster->servers; sv; ) - { - old = sv; - sv = sv->next; - QTV_Run(old); - } - + } + + for (sv = cluster->servers; sv; sv = sv->next) + { + if (sv->usequakeworldprotocols && sv->sourcesock != INVALID_SOCKET) + { + FD_SET(sv->sourcesock, &socketset); + if (sv->sourcesock >= m) + m = sv->sourcesock+1; + } + } + + #ifndef _WIN32 + #ifndef STDIN + #define STDIN 0 + #endif + FD_SET(STDIN, &socketset); + if (STDIN >= m) + m = STDIN+1; + #endif + + if (cluster->viewserver) + { + timeout.tv_sec = 0; + timeout.tv_usec = 1000; + } + else + { + timeout.tv_sec = 10/1000; + timeout.tv_usec = (100%1000)*1000; + } + + m = select(m, &socketset, NULL, NULL, &timeout); + +#ifdef _WIN32 + for (;;) + { + char buffer[8192]; + char *result; + char c; + + if (!_kbhit()) + break; + c = _getch(); + + if (c == '\n' || c == '\r') + { + Sys_Printf(cluster, "\n"); + if (cluster->inputlength) + { + cluster->commandinput[cluster->inputlength] = '\0'; + result = Rcon_Command(cluster, NULL, cluster->commandinput, buffer, sizeof(buffer), true); + Sys_Printf(cluster, "%s", result); + cluster->inputlength = 0; + cluster->commandinput[0] = '\0'; + } + } + else if (c == '\b') + { + if (cluster->inputlength > 0) + { + Sys_Printf(cluster, "%c", c); + Sys_Printf(cluster, " ", c); + Sys_Printf(cluster, "%c", c); + + cluster->inputlength--; + cluster->commandinput[cluster->inputlength] = '\0'; + } + } + else + { + Sys_Printf(cluster, "%c", c); + if (cluster->inputlength < sizeof(cluster->commandinput)-1) + { + cluster->commandinput[cluster->inputlength++] = c; + cluster->commandinput[cluster->inputlength] = '\0'; + } + } + } +#else + if (FD_ISSET(STDIN, &socketset)) + { + char buffer[8192]; + char *result; + cluster->inputlength = read (0, cluster->commandinput, sizeof(cluster->commandinput)); + if (cluster->inputlength >= 1) + { + cluster->commandinput[cluster->inputlength-1] = 0; // rip off the /n and terminate + cluster->inputlength--; + + if (cluster->inputlength) + { + cluster->commandinput[cluster->inputlength] = '\0'; + result = Rcon_Command(cluster, NULL, cluster->commandinput, buffer, sizeof(buffer), true); + printf("%s", result); + cluster->inputlength = 0; + cluster->commandinput[0] = '\0'; + } + } + } +#endif + } + + + + cluster->curtime = Sys_Milliseconds(); + + for (sv = cluster->servers; sv; ) + { + old = sv; + sv = sv->next; + QTV_Run(old); + } + 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. - - QW_UpdateUDPStuff(cluster); - - while(cluster->pendingproxies) - { - pend2 = cluster->pendingproxies->next; - if (SV_ReadPendingProxy(cluster, cluster->pendingproxies)) - cluster->pendingproxies = pend2; - else - break; - } - if (cluster->pendingproxies) - { - for(pend = cluster->pendingproxies; pend && pend->next; ) - { - pend2 = pend->next; - pend3 = pend2->next; - if (SV_ReadPendingProxy(cluster, pend2)) - { - pend->next = pend3; - pend = pend3; - } - else - { - pend = pend2; - } - } - } -} - - - - - -void DoCommandLine(cluster_t *cluster, int argc, char **argv) -{ - int i; - char commandline[8192]; - char *result; - char *arg; - char buffer[8192]; - -//exec the - commands - commandline[0] = '\0'; - for (i = 1; i <= argc; i++) - { - if (i == argc) - arg = ""; - else - { - arg = argv[i]; - if (!arg) //NeXT can do this supposedly - arg = ""; - } - if(i == argc || *arg == '+' || *arg == '-') - { - if (commandline[0] == '-') - { - result = Rcon_Command(cluster, NULL, commandline+1, buffer, sizeof(buffer), true); - Sys_Printf(cluster, "%s", result); - } - - commandline[0] = '\0'; - } - strcat(commandline, arg); - strcat(commandline, " "); - } - -//exec the configs - result = Rcon_Command(cluster, NULL, "exec qtv.cfg", buffer, sizeof(buffer), true); - Sys_Printf(cluster, "%s", result); - - -//exec the + commands - commandline[0] = '\0'; - for (i = 1; i <= argc; i++) - { - if (i == argc) - arg = ""; - else - { - arg = argv[i]; - if (!arg) //NeXT can do this supposedly - arg = ""; - } - if(i == argc || *arg == '+' || *arg == '-') - { - if (commandline[0] == '+') - { - result = Rcon_Command(cluster, NULL, commandline+1, buffer, sizeof(buffer), true); - Sys_Printf(cluster, "%s", result); - } - - commandline[0] = '\0'; - } - strcat(commandline, arg); - strcat(commandline, " "); - } -} - -#ifndef LIBQTV -int main(int argc, char **argv) -{ - cluster_t *cluster; - -// soundtest(); - -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif - -#ifdef _WIN32 - { - WSADATA discard; - WSAStartup(MAKEWORD(1,1), &discard); - } -#endif - - cluster = malloc(sizeof(*cluster)); - if (cluster) - { - memset(cluster, 0, sizeof(*cluster)); - + SV_FindProxies(cluster->tcpsocket[1], cluster, NULL); //look for any other proxies wanting to muscle in on the action. + + QW_UpdateUDPStuff(cluster); + + while(cluster->pendingproxies) + { + pend2 = cluster->pendingproxies->next; + if (SV_ReadPendingProxy(cluster, cluster->pendingproxies)) + cluster->pendingproxies = pend2; + else + break; + } + if (cluster->pendingproxies) + { + for(pend = cluster->pendingproxies; pend && pend->next; ) + { + pend2 = pend->next; + pend3 = pend2->next; + if (SV_ReadPendingProxy(cluster, pend2)) + { + pend->next = pend3; + pend = pend3; + } + else + { + pend = pend2; + } + } + } +} + + + + + +void DoCommandLine(cluster_t *cluster, int argc, char **argv) +{ + int i; + char commandline[8192]; + char *result; + char *arg; + char buffer[8192]; + +//exec the - commands + commandline[0] = '\0'; + for (i = 1; i <= argc; i++) + { + if (i == argc) + arg = ""; + else + { + arg = argv[i]; + if (!arg) //NeXT can do this supposedly + arg = ""; + } + if(i == argc || *arg == '+' || *arg == '-') + { + if (commandline[0] == '-') + { + result = Rcon_Command(cluster, NULL, commandline+1, buffer, sizeof(buffer), true); + Sys_Printf(cluster, "%s", result); + } + + commandline[0] = '\0'; + } + strcat(commandline, arg); + strcat(commandline, " "); + } + +//exec the configs + result = Rcon_Command(cluster, NULL, "exec qtv.cfg", buffer, sizeof(buffer), true); + Sys_Printf(cluster, "%s", result); + + +//exec the + commands + commandline[0] = '\0'; + for (i = 1; i <= argc; i++) + { + if (i == argc) + arg = ""; + else + { + arg = argv[i]; + if (!arg) //NeXT can do this supposedly + arg = ""; + } + if(i == argc || *arg == '+' || *arg == '-') + { + if (commandline[0] == '+') + { + result = Rcon_Command(cluster, NULL, commandline+1, buffer, sizeof(buffer), true); + Sys_Printf(cluster, "%s", result); + } + + commandline[0] = '\0'; + } + strcat(commandline, arg); + strcat(commandline, " "); + } +} + +#ifndef LIBQTV +int main(int argc, char **argv) +{ + cluster_t *cluster; + +// soundtest(); + +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + +#ifdef _WIN32 + { + WSADATA discard; + WSAStartup(MAKEWORD(1,1), &discard); + } +#endif + + cluster = malloc(sizeof(*cluster)); + if (cluster) + { + memset(cluster, 0, sizeof(*cluster)); + cluster->qwdsocket[0] = INVALID_SOCKET; - cluster->qwdsocket[1] = INVALID_SOCKET; + cluster->qwdsocket[1] = INVALID_SOCKET; cluster->tcpsocket[0] = INVALID_SOCKET; - cluster->tcpsocket[1] = INVALID_SOCKET; - cluster->anticheattime = 1*1000; - cluster->tooslowdelay = 100; - cluster->qwlistenportnum = 0; - cluster->allownqclients = true; - strcpy(cluster->hostname, DEFAULT_HOSTNAME); - cluster->buildnumber = build_number(); - cluster->maxproxies = -1; - - strcpy(cluster->demodir, "qw/demos/"); - - Sys_Printf(cluster, "QTV Build %i.\n", cluster->buildnumber); - - DoCommandLine(cluster, argc, argv); - - if (!cluster->numservers) - { //probably running on a home user's computer - if (cluster->qwdsocket[0] == INVALID_SOCKET && cluster->qwdsocket[1] == INVALID_SOCKET && !cluster->qwlistenportnum) + cluster->tcpsocket[1] = INVALID_SOCKET; + cluster->anticheattime = 1*1000; + cluster->tooslowdelay = 100; + cluster->qwlistenportnum = 0; + cluster->allownqclients = true; + strcpy(cluster->hostname, DEFAULT_HOSTNAME); + cluster->buildnumber = build_number(); + cluster->maxproxies = -1; + + strcpy(cluster->demodir, "qw/demos/"); + + Sys_Printf(cluster, "QTV Build %i.\n", cluster->buildnumber); + + DoCommandLine(cluster, argc, argv); + + if (!cluster->numservers) + { //probably running on a home user's computer + if (cluster->qwdsocket[0] == INVALID_SOCKET && cluster->qwdsocket[1] == INVALID_SOCKET && !cluster->qwlistenportnum) { cluster->qwlistenportnum = 27599; NET_InitUDPSocket(cluster, cluster->qwlistenportnum, true); - NET_InitUDPSocket(cluster, cluster->qwlistenportnum, false); - } - if (cluster->tcpsocket[0] == INVALID_SOCKET && cluster->tcpsocket[1] == INVALID_SOCKET && !cluster->tcplistenportnum) + NET_InitUDPSocket(cluster, cluster->qwlistenportnum, false); + } + if (cluster->tcpsocket[0] == INVALID_SOCKET && cluster->tcpsocket[1] == INVALID_SOCKET && !cluster->tcplistenportnum) { cluster->tcplistenportnum = 27599; Net_TCPListen(cluster, cluster->tcplistenportnum, true); - Net_TCPListen(cluster, cluster->tcplistenportnum, false); - } - - Sys_Printf(cluster, "\n" - "Welcome to FTEQTV\n" - "Please type\n" - "qtv server:port\n" - " to connect to a tcp server.\n" - "qw server:port\n" - " to connect to a regular qw server.\n" - "demo qw/example.mvd\n" - " to play a demo from an mvd.\n" - "\n"); - } - -// Cluster_BuildAvailableDemoList(cluster); - - while (!cluster->wanttoexit) - { - Cluster_Run(cluster, true); -#ifdef VIEWER - DemoViewer_Update(cluster->viewserver); -#endif - } - - free(cluster); - } - - return 0; -} -#endif - -void QTV_Printf(sv_t *qtv, char *fmt, ...) -{ - va_list argptr; - char string[2048]; - - va_start (argptr, fmt); - vsnprintf (string, sizeof(string)-1, fmt,argptr); - string[sizeof(string)-1] = 0; - va_end (argptr); - - if (qtv->silentstream) - return; - - Sys_Printf(qtv->cluster, "%s", string); -} - -//#ifdef LIBQTV -//#ifndef _WIN32 -//#define _cdecl -//#endif -//void _cdecl Con_Printf(char *fmt, ...); -//#endif - -void Sys_Printf(cluster_t *cluster, char *fmt, ...) -{ - va_list argptr; - char string[2048]; - unsigned char *t; - - va_start (argptr, fmt); - vsnprintf (string, sizeof(string)-1, fmt,argptr); - string[sizeof(string)-1] = 0; - va_end (argptr); - -//#ifdef LIBQTV -// Con_Printf("QTV: %s", string); -//#endif - - for (t = (unsigned char*)string; *t; t++) - { - if (*t >= 146 && *t < 156) - *t = *t - 146 + '0'; - if (*t == 143) - *t = '.'; - if (*t == 157 || *t == 158 || *t == 159) - *t = '-'; - if (*t >= 128) - *t -= 128; - if (*t == 16) - *t = '['; - if (*t == 17) - *t = ']'; - if (*t == 29) - *t = '-'; - if (*t == 30) - *t = '-'; - if (*t == 31) - *t = '-'; - if (*t == '\a') //doh. :D - *t = ' '; - } - - printf("%s", string); -} - + Net_TCPListen(cluster, cluster->tcplistenportnum, false); + } + + Sys_Printf(cluster, "\n" + "Welcome to FTEQTV\n" + "Please type\n" + "qtv server:port\n" + " to connect to a tcp server.\n" + "qw server:port\n" + " to connect to a regular qw server.\n" + "demo qw/example.mvd\n" + " to play a demo from an mvd.\n" + "\n"); + } + +// Cluster_BuildAvailableDemoList(cluster); + + while (!cluster->wanttoexit) + { + Cluster_Run(cluster, true); +#ifdef VIEWER + DemoViewer_Update(cluster->viewserver); +#endif + } + + free(cluster); + } + + return 0; +} +#endif + +void QTV_Printf(sv_t *qtv, char *fmt, ...) +{ + va_list argptr; + char string[2048]; + + va_start (argptr, fmt); + vsnprintf (string, sizeof(string)-1, fmt,argptr); + string[sizeof(string)-1] = 0; + va_end (argptr); + + if (qtv->silentstream) + return; + + Sys_Printf(qtv->cluster, "%s", string); +} + +//#ifdef LIBQTV +//#ifndef _WIN32 +//#define _cdecl +//#endif +//void _cdecl Con_Printf(char *fmt, ...); +//#endif + +void Sys_Printf(cluster_t *cluster, char *fmt, ...) +{ + va_list argptr; + char string[2048]; + unsigned char *t; + + va_start (argptr, fmt); + vsnprintf (string, sizeof(string)-1, fmt,argptr); + string[sizeof(string)-1] = 0; + va_end (argptr); + +//#ifdef LIBQTV +// Con_Printf("QTV: %s", string); +//#endif + + for (t = (unsigned char*)string; *t; t++) + { + if (*t >= 146 && *t < 156) + *t = *t - 146 + '0'; + if (*t == 143) + *t = '.'; + if (*t == 157 || *t == 158 || *t == 159) + *t = '-'; + if (*t >= 128) + *t -= 128; + if (*t == 16) + *t = '['; + if (*t == 17) + *t = ']'; + if (*t == 29) + *t = '-'; + if (*t == 30) + *t = '-'; + if (*t == 31) + *t = '-'; + if (*t == '\a') //doh. :D + *t = ' '; + } + + printf("%s", string); +} + diff --git a/plugins/avplug/avdecode.c b/plugins/avplug/avdecode.c index 7cba888da..0d1d7ca2e 100644 --- a/plugins/avplug/avdecode.c +++ b/plugins/avplug/avdecode.c @@ -7,6 +7,7 @@ #include "libavutil/imgutils.h" #define TARGET_FFMPEG (LIBAVFORMAT_VERSION_MICRO >= 100) +#define HAVE_DECOUPLED_API (LIBAVCODEC_VERSION_MAJOR>57 || (LIBAVCODEC_VERSION_MAJOR==57&&LIBAVCODEC_VERSION_MINOR>=36)) //between av 52.31 and 54.35, lots of constants etc got renamed to gain an extra AV_ prefix. /* @@ -224,16 +225,29 @@ having them tied to the libavformat network IO. if(avformat_find_stream_info(ctx->pFormatCtx, NULL)>=0) { ctx->audioStream=-1; - for(i=0; ipFormatCtx->nb_streams; i++) + for(i=0; ipFormatCtx->nb_streams && ctx->audioStream==-1; i++) + { +#if LIBAVFORMAT_VERSION_MAJOR >= 57 + if(ctx->pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO) +#else if(ctx->pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO) - { +#endif ctx->audioStream=i; - break; - } + } if(ctx->audioStream!=-1) { +#if LIBAVFORMAT_VERSION_MAJOR >= 57 + pCodec=avcodec_find_decoder(ctx->pFormatCtx->streams[ctx->audioStream]->codecpar->codec_id); + ctx->pACodecCtx = avcodec_alloc_context3(pCodec); + if (avcodec_parameters_to_context(ctx->pACodecCtx, ctx->pFormatCtx->streams[ctx->audioStream]->codecpar) < 0) + { + avcodec_free_context(&ctx->pACodecCtx); + pCodec = NULL; + } +#else ctx->pACodecCtx=ctx->pFormatCtx->streams[ctx->audioStream]->codec; pCodec=avcodec_find_decoder(ctx->pACodecCtx->codec_id); +#endif ctx->pAFrame=av_frame_alloc(); if(pCodec!=NULL && ctx->pAFrame && avcodec_open2(ctx->pACodecCtx, pCodec, NULL) >= 0) @@ -245,22 +259,32 @@ having them tied to the libavformat network IO. } ctx->videoStream=-1; - for(i=0; ipFormatCtx->nb_streams; i++) + for(i=0; ipFormatCtx->nb_streams && ctx->videoStream==-1; i++) + { +#if LIBAVFORMAT_VERSION_MAJOR >= 57 + if(ctx->pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO) +#else if(ctx->pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) - { +#endif ctx->videoStream=i; - break; - } + } if(ctx->videoStream!=-1) { - // Get a pointer to the codec context for the video stream +#if LIBAVFORMAT_VERSION_MAJOR >= 57 + pCodec=avcodec_find_decoder(ctx->pFormatCtx->streams[ctx->videoStream]->codecpar->codec_id); + ctx->pVCodecCtx = avcodec_alloc_context3(pCodec); + if (avcodec_parameters_to_context(ctx->pVCodecCtx, ctx->pFormatCtx->streams[ctx->videoStream]->codecpar) < 0) + { + avcodec_free_context(&ctx->pVCodecCtx); + pCodec = NULL; + } +#else ctx->pVCodecCtx=ctx->pFormatCtx->streams[ctx->videoStream]->codec; + pCodec=avcodec_find_decoder(ctx->pVCodecCtx->codec_id); +#endif ctx->num = ctx->pFormatCtx->streams[ctx->videoStream]->time_base.num; ctx->denum = ctx->pFormatCtx->streams[ctx->videoStream]->time_base.den; - // Find the decoder for the video stream - pCodec=avcodec_find_decoder(ctx->pVCodecCtx->codec_id); - // Open codec if(pCodec!=NULL && avcodec_open2(ctx->pVCodecCtx, pCodec, NULL) >= 0) { @@ -285,7 +309,10 @@ static qboolean VARGS AVDec_DisplayFrame(void *vctx, qboolean nosound, qboolean { struct decctx *ctx = (struct decctx*)vctx; AVPacket packet; +#if HAVE_DECOUPLED_API +#else int frameFinished; +#endif qboolean repainted = false; int64_t curtime; @@ -304,6 +331,101 @@ static qboolean VARGS AVDec_DisplayFrame(void *vctx, qboolean nosound, qboolean return false; } +#if HAVE_DECOUPLED_API + if(packet.stream_index==ctx->videoStream) + { + avcodec_send_packet(ctx->pVCodecCtx, &packet); + + while(0==avcodec_receive_frame(ctx->pVCodecCtx, ctx->pVFrame)) + { + //rescale+convert it to what we're rendering (no more yuv) + ctx->pScaleCtx = sws_getCachedContext(ctx->pScaleCtx, ctx->pVCodecCtx->width, ctx->pVCodecCtx->height, ctx->pVCodecCtx->pix_fmt, ctx->width, ctx->height, AV_PIX_FMT_BGRA, SWS_POINT, 0, 0, 0); + sws_scale(ctx->pScaleCtx, (void*)ctx->pVFrame->data, ctx->pVFrame->linesize, 0, ctx->pVCodecCtx->height, &ctx->rgb_data, &ctx->rgb_linesize); + + ctx->lasttime = av_frame_get_best_effort_timestamp(ctx->pVFrame); + repainted = true; + } + } + else if(packet.stream_index==ctx->audioStream && !nosound) + { + avcodec_send_packet(ctx->pACodecCtx, &packet); + while(0==avcodec_receive_frame(ctx->pACodecCtx, ctx->pAFrame)) + { + int width = 2; + int channels = ctx->pACodecCtx->channels; + unsigned int auddatasize = av_samples_get_buffer_size(NULL, ctx->pACodecCtx->channels, ctx->pAFrame->nb_samples, ctx->pACodecCtx->sample_fmt, 1); + void *auddata = ctx->pAFrame->data[0]; + switch(ctx->pACodecCtx->sample_fmt) + { + default: + auddatasize = 0; + break; + case AV_SAMPLE_FMT_U8P: + auddatasize /= channels; + channels = 1; + case AV_SAMPLE_FMT_U8: + width = 1; + break; + case AV_SAMPLE_FMT_S16P: + auddatasize /= channels; + channels = 1; + case AV_SAMPLE_FMT_S16: + width = 2; + break; + + case AV_SAMPLE_FMT_FLTP: + auddatasize /= channels; + channels = 1; + case AV_SAMPLE_FMT_FLT: + //FIXME: support float audio internally. + { + float *in = (void*)auddata; + signed short *out = (void*)auddata; + int v; + unsigned int i; + for (i = 0; i < auddatasize/sizeof(*in); i++) + { + v = (short)(in[i]*32767); + if (v < -32767) + v = -32767; + else if (v > 32767) + v = 32767; + out[i] = v; + } + auddatasize/=2; + width = 2; + } + + case AV_SAMPLE_FMT_DBLP: + auddatasize /= channels; + channels = 1; + case AV_SAMPLE_FMT_DBL: + { + double *in = (double*)auddata; + signed short *out = (void*)auddata; + int v; + unsigned int i; + for (i = 0; i < auddatasize/sizeof(*in); i++) + { + v = (short)(in[i]*32767); + if (v < -32767) + v = -32767; + else if (v > 32767) + v = 32767; + out[i] = v; + } + auddatasize/=4; + width = 2; + } + break; + } + pS_RawAudio(-1, auddata, ctx->pACodecCtx->sample_rate, auddatasize/(channels*width), channels, width, 1); + } + } + + av_packet_unref(&packet); +#else + // Is this a packet from the video stream? if(packet.stream_index==ctx->videoStream) { @@ -423,6 +545,7 @@ static qboolean VARGS AVDec_DisplayFrame(void *vctx, qboolean nosound, qboolean // Free the packet that was allocated by av_read_frame av_packet_unref(&packet); +#endif } if (forcevideo || repainted) @@ -451,16 +574,11 @@ static void AVDec_ChangeStream(void *vctx, char *newstream) static void AVDec_Rewind(void *vctx) { struct decctx *ctx = (struct decctx*)vctx; - if (ctx->videoStream >= 0) + if (ctx->lasttime != -1) { - av_seek_frame(ctx->pFormatCtx, ctx->videoStream, 0, AVSEEK_FLAG_FRAME|AVSEEK_FLAG_BACKWARD); + av_seek_frame(ctx->pFormatCtx, -1, 0, AVSEEK_FLAG_FRAME|AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers(ctx->pVCodecCtx); } - if (ctx->audioStream >= 0) - { - av_seek_frame(ctx->pFormatCtx, ctx->audioStream, 0, AVSEEK_FLAG_FRAME|AVSEEK_FLAG_BACKWARD); - avcodec_flush_buffers(ctx->pACodecCtx); - } ctx->lasttime = -1; } diff --git a/plugins/cef/cef.c b/plugins/cef/cef.c index 9489adf0b..0fbddaf06 100644 --- a/plugins/cef/cef.c +++ b/plugins/cef/cef.c @@ -35,6 +35,14 @@ #define cef_v8context_get_current_context pcef_v8context_get_current_context #define cef_post_task pcef_post_task #define cef_request_context_create_context pcef_request_context_create_context +#define cef_string_multimap_alloc pcef_string_multimap_alloc +#define cef_string_multimap_append pcef_string_multimap_append +#define cef_string_multimap_size pcef_string_multimap_size +#define cef_string_multimap_key pcef_string_multimap_key +#define cef_string_multimap_value pcef_string_multimap_value +#define cef_string_multimap_free pcef_string_multimap_free +#define cef_string_list_size pcef_string_list_size +#define cef_string_list_value pcef_string_list_value #define cef_addref(ptr) (ptr)->base.add_ref(&(ptr)->base) #define cef_release(ptr) (((ptr)->base.release)(&(ptr)->base)) @@ -62,6 +70,17 @@ static cef_process_message_t* (*cef_process_message_create)(const cef_string_t* static cef_v8context_t* (*cef_v8context_get_current_context)(void); //typical C++ programmers omitted the void. static int (*cef_post_task)(cef_thread_id_t threadId, cef_task_t* task); static cef_request_context_t* (*cef_request_context_create_context)(const cef_request_context_settings_t* settings, cef_request_context_handler_t* handler); +static cef_string_multimap_t (*cef_string_multimap_alloc)(void); +static int (*cef_string_multimap_append)(cef_string_multimap_t map, const cef_string_t* key, const cef_string_t* value); +static size_t (*cef_string_multimap_size)(cef_string_multimap_t map); +static int (*cef_string_multimap_key)(cef_string_multimap_t map, size_t index, cef_string_t* key); +static int (*cef_string_multimap_value)(cef_string_multimap_t map, size_t index, cef_string_t* value); +static void (*cef_string_multimap_free)(cef_string_multimap_t map); +static size_t (*cef_string_list_size)(cef_string_list_t list); +static int (*cef_string_list_value)(cef_string_list_t list, size_t index, cef_string_t* value); + + + #ifndef CEF_VERSION //old builds lack this #define CEF_VERSION "cef"STRINGIFY(CEF_VERSION_MAJOR)"."STRINGIFY(CEF_REVISION)"."STRINGIFY(CHROME_VERSION_BUILD) @@ -164,16 +183,21 @@ vmcvar_t cef_devtools = {"cef_devtools", "0", "browser settings", 0}; static char plugname[MAX_OSPATH]; static char *newconsole; -static void setcefstring(char *str, cef_string_t *r) +/*static void setcefstring(char *str, cef_string_t *r) { cef_string_from_utf8(str, strlen(str), r); -} +}*/ static cef_string_t makecefstring(char *str) { cef_string_t r = {NULL}; cef_string_from_utf8(str, strlen(str), &r); return r; } +static cef_string_t *makecefstringptr(char *str, cef_string_t *ptr) +{ + cef_string_from_utf8(str, strlen(str), ptr); + return ptr; +} static char *Info_JSONify (char *s, char *o, size_t outlen) { @@ -247,6 +271,7 @@ typedef struct cef_display_handler_t display_handler; cef_request_handler_t request_handler; cef_life_span_handler_t life_span_handler; + cef_context_menu_handler_t context_menu_handler; cef_browser_t *thebrowser; void *videodata; @@ -256,7 +281,9 @@ typedef struct int desiredwidth; int desiredheight; char *consolename; //for internal plugin use. + qboolean fullscreen; cef_string_utf8_t currenturl; + cef_string_utf8_t currenticon; cef_string_utf8_t currenttitle; cef_string_utf8_t currentstatus; @@ -278,6 +305,7 @@ static int browser_release(browser_t *br) if (br->videodata) free(br->videodata); cef_string_utf8_clear(&br->currenturl); + cef_string_utf8_clear(&br->currenticon); cef_string_utf8_clear(&br->currenttitle); cef_string_utf8_clear(&br->currentstatus); @@ -302,6 +330,7 @@ browser_subs(render_handler); browser_subs(display_handler); browser_subs(request_handler); browser_subs(life_span_handler); +browser_subs(context_menu_handler); #undef browser_subs //client methods @@ -317,6 +346,12 @@ static cef_life_span_handler_t *CEF_CALLBACK browser_get_life_span_handler(cef_c cef_addref(&br->life_span_handler); return &br->life_span_handler; } +static cef_context_menu_handler_t *CEF_CALLBACK browser_get_context_menu_handler(cef_client_t *self) +{ + browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, client)); + cef_addref(&br->context_menu_handler); + return &br->context_menu_handler; +} static cef_display_handler_t *CEF_CALLBACK browser_get_display_handler(cef_client_t *self) { browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, client)); @@ -332,7 +367,9 @@ static cef_request_handler_t *CEF_CALLBACK browser_get_request_handler(cef_clien static qboolean browser_handle_query(const char *req, char *buffer, size_t buffersize) { - if (!strncmp(req, "getcvar_", 8)) + if (!req) + return false; + else if (!strncmp(req, "getcvar_", 8)) { Cvar_Update(&cef_allowcvars); if (cef_allowcvars.value && pCvar_GetString(req+8, buffer, buffersize)) @@ -462,7 +499,7 @@ static int CEF_CALLBACK browser_on_process_message_received(cef_client_t* self, cef_string_t str = {NULL}; cef_list_value_t *args = message->get_argument_list(message); cef_string_userfree_t cmdunusable = args->get_string(args, 0); - cef_string_to_utf8(cmdunusable->str, cmdunusable->length, &queryname); + cef_string_to_utf8(cmdunusable?cmdunusable->str:NULL, cmdunusable?cmdunusable->length:0, &queryname); id1 = args->get_int(args, 2); id2 = args->get_int(args, 3); @@ -478,13 +515,14 @@ static int CEF_CALLBACK browser_on_process_message_received(cef_client_t* self, { str = makecefstring(buffer); args->set_string(args, 1, &str); + cef_string_clear(&str); } else args->set_null(args, 1); cef_release(args); cef_string_utf8_clear(&queryname); - cef_string_clear(&str); - cef_string_userfree_free(cmdunusable); + if (cmdunusable) + cef_string_userfree_free(cmdunusable); browser->send_process_message(browser, source_process, reply); handled = true; } @@ -519,8 +557,22 @@ static void CEF_CALLBACK browser_on_paint(cef_render_handler_t *self, cef_browse if (type != PET_VIEW) return; - if (br->videowidth != width || br->videoheight != height) + /* + if (pbocontext) { + if (!PBO_Lock(pbocontext, width, height, &lost, rgba)) + return; + if (lost) + PBO_Update(pbocontext, buffer, width, height, stride); + else while (dirtyRectsCount --> 0) + PBO_Update(pbocontext, (char*)buffer+(width*dirtyRects->y + dirtyRects->x)*4), dirtyRects->width, dirtyRects->height, width*4); + PBO_Unlock(pbocontext); + } + else + */ + + if (br->videowidth != width || br->videoheight != height) + { //copy the entire thing. if (br->videodata) free(br->videodata); br->videowidth = width; @@ -529,7 +581,7 @@ static void CEF_CALLBACK browser_on_paint(cef_render_handler_t *self, cef_browse memcpy(br->videodata, buffer, width * height * 4); } else - { + { //try to save cpu time by copying only the dirty parts while (dirtyRectsCount --> 0) { if (width == dirtyRects->width && height == dirtyRects->height) @@ -568,6 +620,23 @@ static void CEF_CALLBACK browser_on_before_close(cef_life_span_handler_t* self, cef_release(browser); } +//context_menu_handler methods +static void CEF_CALLBACK browser_on_before_context_menu(struct _cef_context_menu_handler_t* self, struct _cef_browser_t* browser, struct _cef_frame_t* frame, struct _cef_context_menu_params_t* params, struct _cef_menu_model_t* model) +{ +// browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, context_menu_handler)); + + //wipe whatever elements libcef thinks it should add + model->clear(model); + + //don't bother adding any new ones. + + + cef_release(browser); + cef_release(frame); + cef_release(params); + cef_release(model); +} + //display_handler methods //redirect console.log messages to quake's console, but only display them if we've got developer set. static int CEF_CALLBACK browser_on_console_message(cef_display_handler_t* self, cef_browser_t* browser, const cef_string_t* message, const cef_string_t* source, int line) @@ -599,12 +668,48 @@ static void CEF_CALLBACK browser_on_title_change(cef_display_handler_t* self, ce cef_release(browser); } +static void CEF_CALLBACK browser_on_favicon_urlchange(cef_display_handler_t* self, cef_browser_t* browser, cef_string_list_t favicon) +{ + browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler)); + cef_string_t str = {NULL}; + + if (favicon) + { + //size_t opts = cef_string_list_size(favicon); + cef_string_list_value(favicon, 0, &str); + } + + cef_string_to_utf8(str.str, str.length, &br->currenticon); + cef_string_clear(&str); + + if (br->consolename) + pCon_SetConsoleString(br->consolename, "icon", br->currenticon.str?br->currenticon.str:""); + + cef_release(browser); +} +static void CEF_CALLBACK browser_on_fullscreenmode_change(cef_display_handler_t* self, cef_browser_t* browser, int fullscreen) +{ + browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler)); + br->fullscreen = fullscreen; + + if (br->consolename) + pCon_SetConsoleFloat(br->consolename, "fullscreen", br->fullscreen); + + cef_release(browser); +} static void CEF_CALLBACK browser_on_address_change(cef_display_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, const cef_string_t* url) { browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler)); cef_string_to_utf8(url->str, url->length, &br->currenturl); + if (br->currenticon.length) + { + cef_string_utf8_clear(&br->currenticon); + if (br->consolename) + pCon_SetConsoleString(br->consolename, "icon", br->currenticon.str?br->currenticon.str:""); + } + //FIXME: should probably make sure its the root frame // Con_Printf("new url: %s\n", url.ToString().c_str()); cef_release(browser); @@ -680,24 +785,74 @@ browser_subs(render_handler); browser_subs(display_handler); browser_subs(request_handler); browser_subs(life_span_handler); +browser_subs(context_menu_handler); #undef browser_subs - nb->client.get_life_span_handler = browser_get_life_span_handler; - nb->client.get_render_handler = browser_get_render_handler; + nb->client.get_context_menu_handler = browser_get_context_menu_handler; + nb->client.get_dialog_handler = NULL; nb->client.get_display_handler = browser_get_display_handler; + nb->client.get_download_handler = NULL; + nb->client.get_drag_handler = NULL; + nb->client.get_find_handler = NULL; + nb->client.get_focus_handler = NULL; + nb->client.get_geolocation_handler = NULL; + nb->client.get_jsdialog_handler = NULL; + nb->client.get_keyboard_handler = NULL; + nb->client.get_life_span_handler = browser_get_life_span_handler; + nb->client.get_load_handler = NULL; + nb->client.get_render_handler = browser_get_render_handler; nb->client.get_request_handler = browser_get_request_handler; nb->client.on_process_message_received = browser_on_process_message_received; + +// nb->render_handler.get_accessibility_handler = NULL; + nb->render_handler.get_root_screen_rect = NULL; nb->render_handler.get_view_rect = browser_get_view_rect; + nb->render_handler.get_screen_point = NULL; + nb->render_handler.get_screen_info = NULL; + nb->render_handler.on_popup_show = NULL; + nb->render_handler.on_popup_size = NULL; nb->render_handler.on_paint = browser_on_paint; - nb->display_handler.on_console_message = browser_on_console_message; - nb->display_handler.on_title_change = browser_on_title_change; + nb->render_handler.on_cursor_change = NULL; + nb->render_handler.start_dragging = NULL; + nb->render_handler.update_drag_cursor = NULL; + nb->render_handler.on_scroll_offset_changed = NULL; +// nb->render_handler.on_ime_composition_range_changed = NULL; + nb->display_handler.on_address_change = browser_on_address_change; + nb->display_handler.on_title_change = browser_on_title_change; + nb->display_handler.on_favicon_urlchange = browser_on_favicon_urlchange; + nb->display_handler.on_fullscreen_mode_change = browser_on_fullscreenmode_change; nb->display_handler.on_tooltip = browser_on_tooltip; nb->display_handler.on_status_message = browser_on_status_message; + nb->display_handler.on_console_message = browser_on_console_message; + nb->request_handler.on_before_browse = browser_on_before_browse; + nb->request_handler.on_open_urlfrom_tab = NULL; + nb->request_handler.on_before_resource_load = NULL; + nb->request_handler.get_resource_handler = NULL; + nb->request_handler.on_resource_redirect = NULL; + nb->request_handler.on_resource_response = NULL; + nb->request_handler.get_resource_response_filter = NULL; + nb->request_handler.on_resource_load_complete = NULL; + nb->request_handler.get_auth_credentials = NULL; + nb->request_handler.on_quota_request = NULL; + nb->request_handler.on_protocol_execution = NULL; //FIXME: should implement. + nb->request_handler.on_certificate_error = NULL; +// nb->request_handler.on_select_client_certificate = NULL; //we have no such certs + nb->request_handler.on_plugin_crashed = NULL; + nb->request_handler.on_render_view_ready = NULL; nb->request_handler.on_render_process_terminated = browser_on_render_process_terminated; + + nb->life_span_handler.on_before_popup = NULL; + nb->life_span_handler.on_after_created = NULL; + nb->life_span_handler.do_close = NULL; nb->life_span_handler.on_before_close = browser_on_before_close; + nb->context_menu_handler.on_before_context_menu = browser_on_before_context_menu; + nb->context_menu_handler.run_context_menu = NULL; //fixme: implement a working context menu somehow + nb->context_menu_handler.on_context_menu_command = NULL; //for custom context things, like opening in a new window... + nb->context_menu_handler.on_context_menu_dismissed = NULL; // + nb->desiredwidth = 640; nb->desiredheight = 480; @@ -766,9 +921,14 @@ static cef_render_process_handler_t* CEF_CALLBACK app_get_render_process_handler static void CEF_CALLBACK app_on_register_custom_schemes(struct _cef_app_t* self, cef_scheme_registrar_t* registrar) { cef_string_t fte = makecefstring("fte"); - registrar->add_custom_scheme(registrar, &fte, false, true, true + registrar->add_custom_scheme(registrar, &fte + , /*is_standard*/true + , /*is_local*/false + , /*is_display_isolated*/true #if CEF_COMMIT_NUMBER >= 1658 //not sure what number it is. - , true, true, false + , /*is_secure*/true + , /*is_cors_enabled*/true //display_isolated means that we shouldn't be getting requests from http anyway, so we don't need CORS protection. + , /*is_csp_bypassing*/false #endif ); cef_string_clear(&fte); @@ -926,7 +1086,7 @@ static int CEF_CALLBACK fsfunc_execute(cef_v8handler_t* self, const cef_string_t // key.length = wcslen(key.str); // *exception = makecefstring("SOME KIND OF EXCEPTION!"); - *retval = makev8string("OH LOOK! A STRING!"); + *retval = makev8string(""); if (argumentsCount) { @@ -955,7 +1115,8 @@ static int CEF_CALLBACK fsfunc_execute(cef_v8handler_t* self, const cef_string_t browser->send_process_message(browser, PID_BROWSER, msg); - cef_string_userfree_free(setting); + if (setting) + cef_string_userfree_free(setting); } else { @@ -1011,7 +1172,7 @@ typedef struct size_t offset; size_t datasize; unsigned int resultcode; - cef_string_t mimetype; + char *responseheaders; } fteresource_t; static void CEF_CALLBACK resource_handler_addref(cef_base_t* self) { @@ -1027,45 +1188,93 @@ static int CEF_CALLBACK resource_handler_release(cef_base_t* self) VFS_CLOSE(rh->fh); if (rh->data) free(rh->data); - cef_string_clear(&rh->mimetype); + free(rh->responseheaders); free(rh); return true; } return false; } +static void res_catfield_l(char **const orig, const char *key, int kl, const char *val, int vl) +{ + size_t ol = *orig?strlen(*orig):0; + char *n; + if (ol) + { + n = malloc(ol+1+kl+1+vl+1); + memcpy(n, *orig, ol); + n[ol++] = '\n'; + } + else + n = malloc(kl+1+vl+1); + memcpy(n+ol, key, kl); + ol+=kl; + n[ol++] = '\n'; + memcpy(n+ol, val, vl); + ol+=vl; + n[ol++] = 0; + free(*orig); + *orig = n; +} +static void res_catfield(char **const orig, const char *key, const char *val) +{ + res_catfield_l(orig, key, strlen(key), val, strlen(val)); +} +static void res_catfield_csuf(char **const orig, const char *key, cef_string_userfree_t cs) +{ + cef_string_utf8_t u8 = {NULL}; + cef_string_to_utf8(cs->str, cs->length, &u8); + res_catfield_l(orig, key, strlen(key), u8.str, u8.length); + cef_string_utf8_clear(&u8); +} +static void res_catfield_cs(char **const orig, cef_string_t *key, cef_string_t *val) +{ + cef_string_utf8_t keyu8 = {NULL}; + cef_string_utf8_t valu8 = {NULL}; + cef_string_to_utf8(key->str, key->length, &keyu8); + cef_string_to_utf8(val->str, val->length, &valu8); + res_catfield_l(orig, keyu8.str, keyu8.length, valu8.str, valu8.length); + cef_string_utf8_clear(&keyu8); + cef_string_utf8_clear(&valu8); +} static int CEF_CALLBACK resource_handler_process_request(cef_resource_handler_t* self, cef_request_t* request, cef_callback_t* callback) { fteresource_t *rh = (fteresource_t*)((char*)self - offsetof(fteresource_t, rh)); - cef_string_userfree_t url = request->get_url(request); - cef_string_utf8_t u8_url = {NULL}; + cef_string_userfree_t url = request->get_url(request), method; + cef_post_data_t *postdata; + size_t numelements; + cef_post_data_element_t *elements[1]; + size_t postsize; + char *postbytes; + char *q; + char *e; + cef_string_utf8_t u8_url = {NULL}, u8={NULL}; cef_string_t ext = {NULL}; cef_string_to_utf8(url->str, url->length, &u8_url); rh->resultcode = 404; -// cef_string_userfree_t method = request->get_method(request); -// _cef_post_data_t postdata = request->get_post_data(request); - //hack at the url to hide the + q = strchr(u8_url.str, '?'); + if (q) + *q = 0; + for(e = q?q:u8_url.str+strlen(u8_url.str); e > u8_url.str; ) { - char *q = strchr(u8_url.str, '?'); - char *e; - if (q) - *q = 0; - for(e = q?q:u8_url.str+strlen(u8_url.str); e > u8_url.str; ) + e--; + if (*e == '/') + break; //no extension + if (*e == '.') { - e--; - if (*e == '/') - break; //no extension - if (*e == '.') - { - e++; - cef_string_from_utf8(e, strlen(e), &ext); - break; - } + e++; + cef_string_from_utf8(e, strlen(e), &ext); + break; } } + res_catfield(&rh->responseheaders, "Access-Control-Allow-Origin", "fte://data"); + res_catfield(&rh->responseheaders, "Access-Control-Allow-Origin", "fte://csqc"); + res_catfield(&rh->responseheaders, "Access-Control-Allow-Origin", "fte://ssqc"); + res_catfield(&rh->responseheaders, "Access-Control-Allow-Origin", "fte://menu"); + //sandboxed to the same dir that qc can fopen/fwrite. //(also blocks any fancy http:// parsing that an engine might do) if (!strncmp(u8_url.str, "fte://data/", 11)) @@ -1074,14 +1283,17 @@ static int CEF_CALLBACK resource_handler_process_request(cef_resource_handler_t* if (rh->fh) { cef_string_userfree_t mt = cef_get_mime_type(&ext); - cef_string_copy(mt->str, mt->length, &rh->mimetype); - cef_string_userfree_free(mt); + if (mt) + { + res_catfield_csuf(&rh->responseheaders, "Content-Type", mt); + cef_string_userfree_free(mt); + } rh->resultcode = 200; } else { rh->resultcode = 404; - setcefstring("text/html", &rh->mimetype); + res_catfield(&rh->responseheaders, "Content-Type", "text/html"); rh->data = strdup("not foundFile not found within game filesystem."); rh->datasize = strlen(rh->data); } @@ -1090,16 +1302,21 @@ static int CEF_CALLBACK resource_handler_process_request(cef_resource_handler_t* { struct pubprogfuncs_s *progs; const char *page; + const char *respheaders = NULL; + const char *reqheaders = NULL; if (ext.str) { cef_string_userfree_t mt = cef_get_mime_type(&ext); if (mt) { - cef_string_copy(mt->str, mt->length, &rh->mimetype); + res_catfield_csuf((char**)&respheaders, "Content-Type", mt); cef_string_userfree_free(mt); } } + if(q) + *q = '?'; //put it back so the qc can get the full url. + rh->resultcode = 404; if (!BUILTINISVALID(PR_GetVMInstance)) @@ -1120,13 +1337,58 @@ static int CEF_CALLBACK resource_handler_process_request(cef_resource_handler_t* { void *pr_globals = PR_globals(progs, PR_CURRENT); ((string_t *)pr_globals)[OFS_PARM0] = progs->TempString(progs, u8_url.str+11); - ((string_t *)pr_globals)[OFS_PARM1] = 0;//FIXME: method. PR_TempString(csqcprogs, "")); - ((string_t *)pr_globals)[OFS_PARM2] = 0;//FIXME: post data + + method = request->get_method(request); + cef_string_to_utf8(method->str, method->length, &u8); + ((string_t *)pr_globals)[OFS_PARM1] = progs->TempString(progs, u8.str); + cef_string_userfree_free(method); + cef_string_utf8_clear(&u8); + + postdata = request->get_post_data(request); + if (postdata) + { + numelements = countof(elements); + memset(elements, 0, sizeof(elements)); + postdata->get_elements(postdata, &numelements, elements); + postsize = elements[0]->get_bytes_count(elements[0]); + postbytes = malloc(postsize+1); + elements[0]->get_bytes(elements[0], postsize, postbytes); + postbytes[postsize] = 0; + ((string_t *)pr_globals)[OFS_PARM2] = progs->TempString(progs, postbytes); + free(postbytes); + cef_release(elements[0]); + } + else + ((string_t *)pr_globals)[OFS_PARM2] = 0; + + { + size_t i, elems; + cef_string_t key = {NULL}, val = {NULL}; + cef_string_multimap_t hmap = cef_string_multimap_alloc(); + request->get_header_map(request, hmap); + elems = cef_string_multimap_size(hmap); + for (i = 0; i < elems; i++) + { + cef_string_multimap_key(hmap, i, &key); + cef_string_multimap_key(hmap, i, &val); + res_catfield_cs(&rh->responseheaders, &key, &val); + cef_string_clear(&key); + cef_string_clear(&val); + } + cef_string_multimap_free(hmap); + } + + ((string_t *)pr_globals)[OFS_PARM3] = reqheaders?progs->TempString(progs, reqheaders):0; //request heders + ((string_t *)pr_globals)[OFS_PARM4] = rh->responseheaders?progs->TempString(progs, rh->responseheaders):0; //response headers + ((string_t *)pr_globals)[OFS_PARM5] = 0; + ((string_t *)pr_globals)[OFS_PARM6] = 0; + ((string_t *)pr_globals)[OFS_PARM7] = 0; progs->ExecuteProgram(progs, func); if (((string_t *)pr_globals)[OFS_RETURN]) { page = progs->StringToNative(progs, ((string_t *)pr_globals)[OFS_RETURN]); + respheaders = progs->StringToNative(progs, ((string_t *)pr_globals)[OFS_PARM4]); rh->resultcode = 200; } else @@ -1138,6 +1400,9 @@ static int CEF_CALLBACK resource_handler_process_request(cef_resource_handler_t* else page = "not foundThat QCVM is not running"; + if (*respheaders == '\n') + respheaders++; + rh->responseheaders = strdup(respheaders); //FIXME: only return any data if we were successful OR the mime is text/html rh->data = strdup(page); rh->datasize = strlen(rh->data); @@ -1145,7 +1410,7 @@ static int CEF_CALLBACK resource_handler_process_request(cef_resource_handler_t* else { rh->resultcode = 403; - setcefstring("text/html", &rh->mimetype); + res_catfield(&rh->responseheaders, "Content-Type", "text/html"); rh->data = strdup("forbiddenTry here Or try here"); rh->datasize = strlen(rh->data); } @@ -1158,9 +1423,24 @@ static int CEF_CALLBACK resource_handler_process_request(cef_resource_handler_t* cef_release(request); return 1; //failure is reported as an http error code rather than an exception } +static char *strseps(char *str, char *chars) +{ //find the next separator + char *best = str+strlen(str); + char *c; + if (*str) + while(*chars) + { + c = strchr(str, *chars++); + if (c && c < best) + best = c; + } + return best; +} static void CEF_CALLBACK resource_handler_get_response_headers(cef_resource_handler_t* self, cef_response_t* response, int64* response_length, cef_string_t* redirectUrl) { fteresource_t *rh = (fteresource_t*)((char*)self - offsetof(fteresource_t, rh)); + cef_string_multimap_t *hmap; + cef_string_t key = {NULL}, val={NULL}; if (rh->fh) *response_length = VFS_GETLEN(rh->fh); @@ -1169,9 +1449,38 @@ static void CEF_CALLBACK resource_handler_get_response_headers(cef_resource_hand else *response_length = -1; - response->set_mime_type(response, &rh->mimetype); + hmap = cef_string_multimap_alloc(); + if (rh->responseheaders) + { + char *start; + char *sep; + char *nl; + for (start = rh->responseheaders; *start; ) + { + sep = strseps(start, ":\n"); + nl = strseps(sep+1, "\n"); + + cef_string_from_utf8(start, sep-start, &key); + if (*sep) + sep++; + cef_string_from_utf8(sep, nl-sep, &val); + + cef_string_multimap_append(hmap, &key, &val); + if (*nl) + start = nl+1; + else + break; + } + } + + cef_string_multimap_append(hmap, makecefstringptr("Access-Control-Allow-Origin", &key), makecefstringptr("fte://data", &val)); + response->set_header_map(response, hmap); + +// response->set_mime_type(response, &rh->mimetype); response->set_status(response, rh->resultcode); + cef_string_clear(&key); + cef_string_clear(&val); cef_release(response); } static int CEF_CALLBACK resource_handler_read_response(cef_resource_handler_t* self, void* data_out, int bytes_to_read, int* bytes_read, cef_callback_t* callback) @@ -1226,7 +1535,10 @@ static cef_resource_handler_t* CEF_CALLBACK scheme_handler_factory_create(cef_sc rh->rh.process_request = resource_handler_process_request; rh->rh.get_response_headers = resource_handler_get_response_headers; rh->rh.read_response = resource_handler_read_response; + rh->rh.can_get_cookie = NULL; + rh->rh.can_set_cookie = NULL; rh->rh.cancel = resource_handler_cancel; + cef_addref(&rh->rh); cef_release(browser); @@ -1294,7 +1606,10 @@ cef_request_context_t *Cef_GetRequestContext(void) return ret; } -static void *Cef_Create(const char *name) +static qboolean Cef_Init(qboolean engineprocess); + +struct mediacallbacks_s; //todo... +static void *Cef_Create(const char *name, struct mediacallbacks_s *callbacks) { cef_window_info_t window_info = {0}; cef_browser_settings_t browserSettings = {sizeof(browserSettings)}; @@ -1323,6 +1638,9 @@ static void *Cef_Create(const char *name) cef_main_args_t mainargs = {0}; cef_settings_t settings = {sizeof(settings)}; + if (!Cef_Init(true)) + return NULL; + //const char *ua = "FTEBrowser"; //cef_string_from_utf8(ua, strlen(ua), &settings.user_agent); @@ -1331,8 +1649,11 @@ static void *Cef_Create(const char *name) if (pFS_NativePath("cef_debug.log", FS_ROOT, utf8, sizeof(utf8))) cef_string_from_utf8(utf8, strlen(utf8), &settings.log_file); - // CefString(&settings.resources_dir_path).FromASCII(""); - // CefString(&settings.locales_dir_path).FromASCII(""); + if (pFS_NativePath("", FS_BINARYPATH, utf8, sizeof(utf8))) + cef_string_from_utf8(utf8, strlen(utf8), &settings.resources_dir_path); + + if (pFS_NativePath("locales", FS_BINARYPATH, utf8, sizeof(utf8))) + cef_string_from_utf8(utf8, strlen(utf8), &settings.locales_dir_path); #ifdef _WIN32 { @@ -1348,7 +1669,7 @@ static void *Cef_Create(const char *name) #else settings.log_severity = LOGSEVERITY_DISABLE; #endif - settings.background_color = 0xffffffff; + settings.background_color = 0x00ffffff; // settings.single_process = true; #ifdef _WIN32 // settings.multi_threaded_message_loop = true; //fixme: use this. @@ -1388,7 +1709,7 @@ static void *Cef_Create(const char *name) // browserSettings.file_access_from_file_urls = STATE_DISABLED; browserSettings.remote_fonts = STATE_DISABLED; browserSettings.plugins = STATE_DISABLED; - browserSettings.background_color = 0xffffffff; + browserSettings.background_color = 0x00ffffff; window_info.windowless_rendering_enabled = true; memset(&window_info.parent_window, 0, sizeof(window_info.parent_window)); @@ -1453,6 +1774,12 @@ static void *Cef_Create(const char *name) return (void*)newbrowser; } + +static void *Cef_CreateOld(const char *name) +{ + return Cef_Create(name, NULL); +} + static qboolean VARGS Cef_DisplayFrame(void *ctx, qboolean nosound, qboolean forcevideo, double mediatime, void (QDECL *uploadtexture)(void *ectx, uploadfmt_t fmt, int width, int height, void *data, void *palette), void *ectx) { browser_t *browser = (browser_t*)ctx; @@ -1731,6 +2058,8 @@ qboolean VARGS Cef_GetProperty (void *ctx, const char *field, char *out, size_t ret = browser->currenttitle.str; else if (!strcmp(field, "status")) ret = browser->currentstatus.str; + else if (!strcmp(field, "icon")) + ret = browser->currenticon.str; if (ret) { @@ -1869,6 +2198,14 @@ static qboolean Cef_Init(qboolean engineprocess) {(void **)&cef_v8context_get_current_context, "cef_v8context_get_current_context"}, {(void **)&cef_post_task, "cef_post_task"}, {(void **)&cef_request_context_create_context, "cef_request_context_create_context"}, + {(void **)&cef_string_multimap_alloc, "cef_string_multimap_alloc"}, + {(void **)&cef_string_multimap_append, "cef_string_multimap_append"}, + {(void **)&cef_string_multimap_size, "cef_string_multimap_size"}, + {(void **)&cef_string_multimap_key, "cef_string_multimap_key"}, + {(void **)&cef_string_multimap_value, "cef_string_multimap_value"}, + {(void **)&cef_string_multimap_free, "cef_string_multimap_free"}, + {(void **)&cef_string_list_size, "cef_string_list_size"}, + {(void **)&cef_string_list_value, "cef_string_list_value"}, {NULL} }; if (!Sys_LoadLibrary("libcef", ceffuncs)) @@ -1965,7 +2302,8 @@ qintptr_t Plug_Init(qintptr_t *args) decoderfuncs.structsize = sizeof(media_decoder_funcs_t); decoderfuncs.drivername = "cef"; - decoderfuncs.createdecoder = Cef_Create; + decoderfuncs.createdecoder = Cef_CreateOld; +// decoderfuncs.createdecoderCB = Cef_Create; decoderfuncs.decodeframe = Cef_DisplayFrame; decoderfuncs.shutdown = Cef_Destroy; decoderfuncs.cursormove = Cef_CursorMove; @@ -1981,9 +2319,6 @@ qintptr_t Plug_Init(qintptr_t *args) return false; } - if (!Cef_Init(true)) - return false; - if (Plug_Export("ExecuteCommand", Cef_ExecuteCommand)) pCmd_AddCommand("cef"); diff --git a/plugins/jabber/jabberclient.c b/plugins/jabber/jabberclient.c index 60aab5694..7cd24fbc0 100644 --- a/plugins/jabber/jabberclient.c +++ b/plugins/jabber/jabberclient.c @@ -60,10 +60,12 @@ client compat: #include #include "xmpp.h" +//#define USE_GOOGLE_MAIL_NOTIFY + #ifdef DEFAULTDOMAIN #define EXAMPLEDOMAIN DEFAULTDOMAIN //used in examples / default text field (but not otherwise assumed when omitted) #else - #define EXAMPLEDOMAIN "gmail.com" //used in examples + #define EXAMPLEDOMAIN "example.com" //used in examples #endif @@ -79,14 +81,23 @@ enum ACT_NONE, ACT_OAUTH, ACT_NEWACCOUNT, - ACT_PASSWORD, + ACT_SETAUSERNAME, + ACT_SETADOMAIN, + ACT_SETASERVER, + ACT_SETARESOURCE, + ACT_SETAPASSWORD, ACT_ADDFRIEND, - ACT_SETALIAS, + ACT_SETBALIAS, } jclient_action; #define BUDDYLISTTITLE "Buddy List" - +#define COL_NAME_THEM "^1" //red +#define COL_NAME_US "^5" //cyan +#define COL_TEXT_THEM "^7" //white +#define COL_TEXT_US "^3" //yellow +#define IMG_FB_THEM "gfx/menudot1.lmp" +#define IMG_FB_US "gfx/menuplyr.lmp" #define Q_strncpyz(o, i, l) do {strncpy(o, i, l-1);o[l-1]='\0';}while(0) @@ -630,38 +641,39 @@ jclient_t *jclients[8]; int jclient_curtime; int jclient_poketime; -typedef struct +typedef struct saslmethod_s { char *method; - int (*sasl_initial)(jclient_t *jcl, char *buf, int bufsize); - int (*sasl_challenge)(jclient_t *jcl, char *inbuf, int insize, char *outbuf, int outsize); + int (*sasl_initial)(struct sasl_ctx_s *ctx, char *buf, int bufsize); + int (*sasl_challenge)(struct sasl_ctx_s *ctx, char *inbuf, int insize, char *outbuf, int outsize); + int (*sasl_success)(struct sasl_ctx_s *ctx, char *inbuf, int insize); } saslmethod_t; //#define OAUTH_CLIENT_ID_MSN "0" #ifdef OAUTH_CLIENT_ID_MSN -static int sasl_plain_initial(jclient_t *jcl, char *buf, int bufsize) +static int sasl_plain_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { //"https://oauth.live.com/authorize?client_id=" OAUTH_CLIENT_ID_MSN "&scope=wl.messenger,wl.basic,wl.offline_access,wl.contacts_create,wl.share&response_type=token&redirect_uri=http://localhost/"; } #endif //\0username\0password -static int sasl_plain_initial(jclient_t *jcl, char *buf, int bufsize) +static int sasl_plain_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { int len = 0; - if (jcl->issecure?jcl->allowauth_plaintls:jcl->allowauth_plainnontls) + if (ctx->issecure?ctx->allowauth_plaintls:ctx->allowauth_plainnontls) { - if (!*jcl->password) + if (!*ctx->password_plain) return -2; //realm isn't specified buf[len++] = 0; - memcpy(buf+len, jcl->username, strlen(jcl->username)); - len += strlen(jcl->username); + memcpy(buf+len, ctx->username, strlen(ctx->username)); + len += strlen(ctx->username); buf[len++] = 0; - memcpy(buf+len, jcl->password, strlen(jcl->password)); - len += strlen(jcl->password); + memcpy(buf+len, ctx->password_plain, strlen(ctx->password_plain)); + len += strlen(ctx->password_plain); return len; } @@ -713,17 +725,17 @@ static int saslattr(char *out, int outlen, char *srcbuf, int srclen, char *arg) return 0; } -static int sasl_digestmd5_initial(jclient_t *jcl, char *buf, int bufsize) +static int sasl_digestmd5_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { - if (jcl->allowauth_digestmd5) + if (ctx->allowauth_digestmd5) { - if (!*jcl->password) + if (!*ctx->password_plain && ctx->password_hash_size != 16) return -2; //FIXME: randomize the cnonce and check the auth key //although really I'm not entirely sure what the point is. //if we just authenticated with a mitm attacker relay, we're screwed either way. - strcpy(jcl->authnonce, "abcdefghijklmnopqrstuvwxyz"); + strcpy(ctx->digest.authnonce, "abcdefghijklmnopqrstuvwxyz"); //nothing. server does the initial data. return 0; } @@ -731,11 +743,10 @@ static int sasl_digestmd5_initial(jclient_t *jcl, char *buf, int bufsize) } char *MD5_ToHex(char *input, int inputlen, char *ret, int retlen); char *MD5_ToBinary(char *input, int inputlen, char *ret, int retlen); -static int sasl_digestmd5_challenge(jclient_t *jcl, char *in, int inlen, char *out, int outlen) +static int sasl_digestmd5_challenge(struct sasl_ctx_s *ctx, char *in, int inlen, char *out, int outlen) { - char *username = jcl->username; - char *password = jcl->password; - char *cnonce = jcl->authnonce; + char *username = ctx->username; + char *cnonce = ctx->digest.authnonce; char rspauth[512]; char realm[512]; char nonce[512]; @@ -767,11 +778,26 @@ static int sasl_digestmd5_challenge(jclient_t *jcl, char *in, int inlen, char *o saslattr(algorithm, sizeof(algorithm), in, inlen, "algorithm"); if (!*realm) - Q_strlcpy(realm, jcl->domain, sizeof(realm)); + Q_strlcpy(realm, ctx->domain, sizeof(realm)); Q_snprintf(digesturi, sizeof(digesturi), "xmpp/%s", realm); - Q_snprintf(X, sizeof(X), "%s:%s:%s", username, realm, password); - MD5_ToBinary(X, strlen(X), Y, sizeof(Y)); + + Q_snprintf(X, sizeof(X), "%s:%s:", username, realm); + if (ctx->password_hash_size == 16 && !strcmp(X, ctx->password_validity)) + memcpy(Y, ctx->password_hash, 16); //use the hashed password, instead of the (missing) plain one + else if (ctx->password_plain) + { + Q_strlcpy(ctx->password_validity, X, sizeof(ctx->password_validity)); + + Q_snprintf(X, sizeof(X), "%s:%s:%s", username, realm, ctx->password_plain); + MD5_ToBinary(X, strlen(X), Y, sizeof(Y)); + + ctx->password_hash_size = 16; + memcpy(ctx->password_hash, Y, 16); //save that hash for later. + } + else + return -1; //err, we didn't have a password... + memcpy(A1, Y, 16); if (*authzid) Q_snprintf(A1+16, sizeof(A1)-16, ":%s:%s:%s", nonce, cnonce, authzid); @@ -793,25 +819,81 @@ static int sasl_digestmd5_challenge(jclient_t *jcl, char *in, int inlen, char *o return strlen(out); } -static int sasl_scramsha1_initial(jclient_t *jcl, char *buf, int bufsize) -{ - if (jcl->allowauth_scramsha1) - { - if (!*jcl->password) - return -2; - strcpy(jcl->authnonce, "abcdefghijklmnopqrstuvwxyz"); //FIXME: should be random, to validate that the server knows our password too - - Q_snprintf(buf, bufsize, "n,,n=%s,r=%s", jcl->username, jcl->authnonce); - return strlen(buf); - } - return -1; -} - typedef struct { int len; char buf[512]; } buf_t; +static int sasl_scram_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize, hashfunc_t *hashfunc, size_t hashsize, qboolean plus) +{ + if (ctx->allowauth_scramsha1) + { + unsigned int t; + buf_t bindingdata; + int bs; + if (!*ctx->password_plain && ctx->password_hash_size != hashsize) + return -2; + +#if 1//FIXME: test that we're compliant when we're verifying channel bindings. + //the channel binding thing helps to avoid MITM attacks by tying the auth to the tls keys. + //a MITM attacker will have different binding ids each side, resulting in screwed hashes (they would need to use the public keys (which would require knowing private keys too), or something). + if (ctx->issecure) + { + bindingdata.len = sizeof(bindingdata.buf); + if (BUILTINISVALID(Net_GetTLSBinding)) + bs = pNet_GetTLSBinding(ctx->socket, bindingdata.buf, &bindingdata.len); + else + bs = -1; + } + else +#endif + { + bindingdata.len = 0; + bs = -1; + } + //couldn't use plus for some reason + if (bs < 0) + { //not implemented by our tls implementation. + if (plus) + return -1; //can't auth with this mechanism + Q_strlcpy(ctx->scram.authcbindtype, "n", sizeof(ctx->scram.authcbindtype)); + } + else if (plus && bs > 0) + { //both sides should support it. + Q_strlcpy(ctx->scram.authcbindtype, "p=tls-unique", sizeof(ctx->scram.authcbindtype)); + Base64_Add(bindingdata.buf, bindingdata.len); + Q_snprintf(ctx->scram.authcbinding, sizeof(ctx->scram.authcbinding), "%s", Base64_Finish()); + } + else + { //we support it, but the server does not appear to (we failed the plus method, or it just wasn't listed). + //server will fail the auth if it can do channel bindings (assuming someone stripped the plus method). + Q_strlcpy(ctx->scram.authcbindtype, "y", sizeof(ctx->scram.authcbindtype)); + } + + //FIXME: this should be more random, to validate that the server actually knows our password too + //we can't really do anything until we've already signed in, so why bother? + t = pSys_Milliseconds(); + Base64_Add((void*)&t, sizeof(t)); + Base64_Add("0123456789abcdef", 16); + Base64_Add((void*)&jclient_curtime, sizeof(jclient_curtime)); + strcpy(ctx->scram.authnonce, Base64_Finish()); + ctx->scram.hashfunc = hashfunc; + ctx->scram.hashsize = hashsize; + + Q_snprintf(buf, bufsize, "%s,,n=%s,r=%s", ctx->scram.authcbindtype, ctx->username, ctx->scram.authnonce); + return strlen(buf); + } + return -1; +} +static int sasl_scramsha1minus_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) +{ + return sasl_scram_initial(ctx, buf, bufsize, SHA1_m, 20, false); +} +static int sasl_scramsha1plus_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) +{ + return sasl_scram_initial(ctx, buf, bufsize, SHA1_m, 20, true); +} + static void buf_cat(buf_t *buf, char *data, int len) { memcpy(buf->buf + buf->len, data, len); @@ -822,27 +904,32 @@ static void buf_cats(buf_t *buf, char *data) { buf_cat(buf, data, strlen(data)); } -static void SHA1_Hi(char *out, char *password, int passwordlen, buf_t *salt, int times) +static size_t HMAC_Hi(hashfunc_t *hashfunc, char *out, char *password, int passwordlen, buf_t *salt, int times) { - char prev[20]; + size_t digestsize; + char prev[64]; int i, j; //first iteration is special buf_cat(salt, "\0\0\0\1", 4); - SHA1_HMAC(prev, sizeof(prev), salt->buf, salt->len, password, passwordlen); - memcpy(out, prev, sizeof(prev)); + digestsize = HMAC(hashfunc, prev, sizeof(prev), salt->buf, salt->len, password, passwordlen); + memcpy(out, prev, digestsize); //later iterations just use the previous iteration for (i = 1; i < times; i++) { - SHA1_HMAC(prev, sizeof(prev), prev, sizeof(prev), password, passwordlen); + HMAC(hashfunc, prev, digestsize, prev, digestsize, password, passwordlen); - for (j = 0; j < sizeof(prev); j++) + for (j = 0; j < digestsize; j++) out[j] ^= prev[j]; } + return digestsize; } -static int sasl_scramsha1_challenge(jclient_t *jcl, char *in, int inlen, char *out, int outlen) +static int sasl_scram_challenge(struct sasl_ctx_s *ctx, char *in, int inlen, char *out, int outlen) { +#define MAX_DIGEST_SIZE 20 + size_t digestsize = ctx->scram.hashsize; + hashfunc_t *func = ctx->scram.hashfunc; //sasl SCRAM-SHA-1 challenge //send back the same 'r' attribute buf_t saslchal; @@ -852,13 +939,15 @@ static int sasl_scramsha1_challenge(jclient_t *jcl, char *in, int inlen, char *o buf_t itr; buf_t final; buf_t sigkey; - char salted_password[20]; - char proof[20]; - char clientkey[20]; - char storedkey[20]; - char clientsignature[20]; - char *username = jcl->username; - char *password = jcl->password; + unsigned char salted_password[MAX_DIGEST_SIZE]; + unsigned char proof[MAX_DIGEST_SIZE]; + unsigned char clientkey[MAX_DIGEST_SIZE]; + unsigned char serverkey[MAX_DIGEST_SIZE]; + unsigned char storedkey[MAX_DIGEST_SIZE]; + unsigned char clientsignature[MAX_DIGEST_SIZE]; + char *username = ctx->username; + char validationstr[256]; + const unsigned char *tmp; saslchal.len = 0; buf_cat(&saslchal, in, inlen); @@ -869,16 +958,19 @@ static int sasl_scramsha1_challenge(jclient_t *jcl, char *in, int inlen, char *o itr.len = saslattr(itr.buf, sizeof(itr.buf), saslchal.buf, saslchal.len, "i"); salt.len = Base64_Decode(salt.buf, sizeof(salt.buf), salt.buf, salt.len); - - //FIXME: we should validate that csn is prefixed with our cnonce + + //csn MUST be prefixed with out authnone, to avoid mess. + if (strncmp(csn.buf, ctx->scram.authnonce, strlen(ctx->scram.authnonce))) + return -1; //this is the first part of the message we're about to send, with no proof. //c(channel) is mandatory but nulled and forms part of the hash final.len = 0; buf_cats(&final, "c="); - Base64_Add("n,,", 3); - Base64_Finish(); - buf_cat(&final, base64, strlen(base64)); + Base64_Add(ctx->scram.authcbindtype, strlen(ctx->scram.authcbindtype)); + Base64_Add(",,", 2); + Base64_Add(ctx->scram.authcbinding, strlen(ctx->scram.authcbinding)); + buf_cats(&final, Base64_Finish()); buf_cats(&final, ",r="); buf_cat(&final, csn.buf, csn.len); @@ -887,29 +979,62 @@ static int sasl_scramsha1_challenge(jclient_t *jcl, char *in, int inlen, char *o buf_cats(&sigkey, "n="); buf_cats(&sigkey, username); buf_cats(&sigkey, ",r="); - buf_cats(&sigkey, jcl->authnonce); + buf_cats(&sigkey, ctx->scram.authnonce); buf_cats(&sigkey, ","); buf_cat(&sigkey, saslchal.buf, saslchal.len); buf_cats(&sigkey, ","); buf_cat(&sigkey, final.buf, final.len); - SHA1_Hi(salted_password, password, strlen(password), &salt, atoi(itr.buf)); - SHA1_HMAC(clientkey, sizeof(clientkey), "Client Key", strlen("Client Key"), salted_password, sizeof(salted_password)); - SHA1(storedkey, sizeof(storedkey), clientkey, sizeof(clientkey)); //FIXME: switch the account's plain password to store this digest instead (with salt+itr). - SHA1_HMAC(clientsignature, sizeof(clientsignature), sigkey.buf, sigkey.len, storedkey, sizeof(storedkey)); + Base64_Add(salt.buf, salt.len); + Q_snprintf(validationstr, sizeof(validationstr), "%i:%s", atoi(itr.buf), Base64_Finish()); + if (ctx->password_hash_size == digestsize && !strcmp(validationstr, ctx->password_validity)) + { + //restore the hash + memcpy(salted_password, ctx->password_hash, digestsize); + } + else if (*ctx->password_plain) + { + ctx->password_hash_size = HMAC_Hi(func, salted_password, ctx->password_plain, strlen(ctx->password_plain), &salt, atoi(itr.buf)); - for (i = 0; i < sizeof(proof); i++) + //save that hash for later. + Q_strlcpy(ctx->password_validity, validationstr, sizeof(ctx->password_validity)); + memcpy(ctx->password_hash, salted_password, ctx->password_hash_size); + } + else + return -2; //panic. password not known any more. the server should not be changing salt/itr. + + HMAC(func, clientkey, sizeof(clientkey), "Client Key", strlen("Client Key"), salted_password, digestsize); +//Note: if we wanted to be fancy, we could store both clientkey and serverkey instead of salted_password, but I'm not sure there's all that much point. + tmp = clientkey; + func(storedkey, sizeof(storedkey), 1, &tmp, &digestsize); + HMAC(func, clientsignature, sizeof(clientsignature), sigkey.buf, sigkey.len, storedkey, digestsize); + + for (i = 0; i < digestsize; i++) proof[i] = clientkey[i] ^ clientsignature[i]; - Base64_Add(proof, sizeof(proof)); + Base64_Add(proof, digestsize); Base64_Finish(); + //to validate the server... + HMAC(func, serverkey, sizeof(serverkey), "Server Key", strlen("Server Key"), salted_password, sizeof(salted_password)); + HMAC(func, ctx->scram.authvhash, sizeof(ctx->scram.authvhash), sigkey.buf, sigkey.len, serverkey, sizeof(serverkey)); //aka:serversignature //"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=" Q_snprintf(out, outlen, "%s,p=%s", final.buf, base64); return strlen(out); } +static int sasl_scram_final(struct sasl_ctx_s *ctx, char *in, int inlen) +{ + buf_t valid; + + valid.len = saslattr(valid.buf, sizeof(valid.buf), in, inlen, "v"); + valid.len = Base64_Decode(valid.buf, sizeof(valid.buf), valid.buf, valid.len); + if (valid.len != 20 || memcmp(ctx->scram.authvhash, valid.buf, valid.len)) + return -1; //server didn't give us the right answer. this is NOT the server we're looking for. give up now. + return true; +} + void URL_Split(char *url, char *proto, int protosize, char *host, int hostsize, char *res, int ressize) { char *s; @@ -971,7 +1096,7 @@ void Q_strlcat_urlencode(char *d, const char *s, int n) } *d = 0; } -static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) +static int sasl_oauth2_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { char proto[256]; char host[256]; @@ -981,27 +1106,27 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) xmltree_t *x; - if (*jcl->password) + if (*ctx->password_plain) return -1; - if (0)//*jcl->password) + if (0)//*jcl->password_plain) { char body[4096]; char header[4096]; - URL_Split(jcl->oauth2.refreshurl, proto, sizeof(proto), host, sizeof(host), resource, sizeof(resource)); + URL_Split(ctx->oauth2.refreshurl, proto, sizeof(proto), host, sizeof(host), resource, sizeof(resource)); *body = 0; Q_strlcat(body, "client_id=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.clientid, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.clientid, sizeof(body)); Q_strlcat(body, "&client_secret=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.clientsecret, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.clientsecret, sizeof(body)); Q_strlcat(body, "&", sizeof(body)); Q_strlcat(body, "grant_type=password&username=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.useraccount, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.useraccount, sizeof(body)); Q_strlcat(body, "&password=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->password, sizeof(body)); + Q_strlcat_urlencode(body, ctx->password_plain, sizeof(body)); Q_strlcat(body, "&response_type=code", sizeof(body)); @@ -1009,7 +1134,7 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) Q_strlcat_urlencode(body, "urn:ietf:wg:oauth:2.0:oob", sizeof(body)); Q_strlcat(body, "&scope=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.scope, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.scope, sizeof(body)); Q_snprintf(header, sizeof(header), "POST %s HTTP/1.1\r\n" @@ -1042,29 +1167,29 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) } //if we have nothing, load up a browser to ask for the first token - if (!*jcl->oauth2.refreshtoken && !*jcl->oauth2.authtoken) + if (!*ctx->oauth2.refreshtoken && !*ctx->oauth2.authtoken) { char url[4096]; *url = 0; - Q_strlcat(url, jcl->oauth2.obtainurl, sizeof(url)); + Q_strlcat(url, ctx->oauth2.obtainurl, sizeof(url)); Q_strlcat(url, "?redirect_uri=", sizeof(url)); Q_strlcat_urlencode(url, "urn:ietf:wg:oauth:2.0:oob", sizeof(url)); Q_strlcat(url, "&%72esponse_type=code&client_id=", sizeof(url)); //%72 = r. fucking ezquake colour codes. works with firefox anyway. no idea if that's the server changing it to an r or not. :s - Q_strlcat_urlencode(url, jcl->oauth2.clientid, sizeof(url)); + Q_strlcat_urlencode(url, ctx->oauth2.clientid, sizeof(url)); Q_strlcat(url, "&scope=", sizeof(url)); - Q_strlcat_urlencode(url, jcl->oauth2.scope, sizeof(url)); + Q_strlcat_urlencode(url, ctx->oauth2.scope, sizeof(url)); Q_strlcat(url, "&access_type=offline", sizeof(url)); Q_strlcat(url, "&login_hint=", sizeof(url)); - Q_strlcat_urlencode(url, jcl->oauth2.useraccount, sizeof(url)); + Q_strlcat_urlencode(url, ctx->oauth2.useraccount, sizeof(url)); - Con_Printf("Please visit ^[^4%s\\url\\%s^] and then enter:\n^[/"COMMANDPREFIX"%i /oa2token ^]\nNote: you can right-click the link to copy it to your browser, and you can use ctrl+v to paste the resulting auth token as part of the given command.\n", url, url, jcl->accountnum); +// Con_Printf("Please visit ^[^4%s\\url\\%s^] and then enter:\n^[/"COMMANDPREFIX"%i /oa2token ^]\nNote: you can right-click the link to copy it to your browser, and you can use ctrl+v to paste the resulting auth token as part of the given command.\n", url, url, jcl->accountnum); //wait for user to act. return -2; } //refresh token is not known, try and get one - if (!*jcl->oauth2.refreshtoken && *jcl->oauth2.authtoken) + if (!*ctx->oauth2.refreshtoken && *ctx->oauth2.authtoken) { xmltree_t *x; char body[4096]; @@ -1074,16 +1199,16 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) *body = 0; Q_strlcat(body, "code=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.authtoken, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.authtoken, sizeof(body)); Q_strlcat(body, "&client_id=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.clientid, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.clientid, sizeof(body)); Q_strlcat(body, "&client_secret=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.clientsecret, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.clientsecret, sizeof(body)); Q_strlcat(body, "&redirect_uri=", sizeof(body)); Q_strlcat_urlencode(body, "urn:ietf:wg:oauth:2.0:oob", sizeof(body)); Q_strlcat(body, "&grant_type=", sizeof(body)); Q_strlcat_urlencode(body, "authorization_code", sizeof(body)); - URL_Split(jcl->oauth2.refreshurl, proto, sizeof(proto), host, sizeof(host), resource, sizeof(resource)); + URL_Split(ctx->oauth2.refreshurl, proto, sizeof(proto), host, sizeof(host), resource, sizeof(resource)); Q_snprintf(header, sizeof(header), "POST %s HTTP/1.1\r\n" @@ -1125,20 +1250,20 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) l = rl; x = XML_FromJSON(NULL, "oauth2", result, &l, rl); XML_ConPrintTree(x, "", 1); - free(jcl->oauth2.accesstoken); - free(jcl->oauth2.refreshtoken); - jcl->oauth2.accesstoken = strdup(XML_GetChildBody(x, "access_token", "")); -// jcl->oauth2.token_type = strdup(XML_GetChildBody(x, "token_type", "")); -// jcl->oauth2.expires_in = strdup(XML_GetChildBody(x, "expires_in", "")); - jcl->oauth2.refreshtoken = strdup(XML_GetChildBody(x, "refresh_token", "")); + free(ctx->oauth2.accesstoken); + free(ctx->oauth2.refreshtoken); + ctx->oauth2.accesstoken = strdup(XML_GetChildBody(x, "access_token", "")); +// ctx->oauth2.token_type = strdup(XML_GetChildBody(x, "token_type", "")); +// ctx->oauth2.expires_in = strdup(XML_GetChildBody(x, "expires_in", "")); + ctx->oauth2.refreshtoken = strdup(XML_GetChildBody(x, "refresh_token", "")); //in theory, the auth token is no longer valid/needed - free(jcl->oauth2.authtoken); - jcl->oauth2.authtoken = strdup(""); + free(ctx->oauth2.authtoken); + ctx->oauth2.authtoken = strdup(""); } //refresh our refresh token, obtaining a usable sign-in token at the same time. - else if (!*jcl->oauth2.accesstoken) + else if (!*ctx->oauth2.accesstoken) { char body[4096]; char header[4096]; @@ -1148,14 +1273,14 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) *body = 0; Q_strlcat(body, "client_id=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.clientid, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.clientid, sizeof(body)); Q_strlcat(body, "&client_secret=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.clientsecret, sizeof(body)); + Q_strlcat_urlencode(body, ctx->oauth2.clientsecret, sizeof(body)); Q_strlcat(body, "&grant_type=", sizeof(body)); Q_strlcat_urlencode(body, "refresh_token", sizeof(body)); Q_strlcat(body, "&refresh_token=", sizeof(body)); - Q_strlcat_urlencode(body, jcl->oauth2.refreshtoken, sizeof(body)); - URL_Split(jcl->oauth2.refreshurl, proto, sizeof(proto), host, sizeof(host), resource, sizeof(resource)); + Q_strlcat_urlencode(body, ctx->oauth2.refreshtoken, sizeof(body)); + URL_Split(ctx->oauth2.refreshurl, proto, sizeof(proto), host, sizeof(host), resource, sizeof(resource)); Q_snprintf(header, sizeof(header), "POST %s HTTP/1.1\r\n" @@ -1192,38 +1317,38 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) // XML_ConPrintTree(x, "", 1); newrefresh = XML_GetChildBody(x, "refresh_token", NULL); - free(jcl->oauth2.accesstoken); - jcl->oauth2.accesstoken = strdup(XML_GetChildBody(x, "access_token", "")); - if (newrefresh || !*jcl->oauth2.accesstoken) + free(ctx->oauth2.accesstoken); + ctx->oauth2.accesstoken = strdup(XML_GetChildBody(x, "access_token", "")); + if (newrefresh || !*ctx->oauth2.accesstoken) { - free(jcl->oauth2.refreshtoken); - jcl->oauth2.refreshtoken = strdup(XML_GetChildBody(x, "refresh_token", "")); + free(ctx->oauth2.refreshtoken); + ctx->oauth2.refreshtoken = strdup(XML_GetChildBody(x, "refresh_token", "")); } -// jcl->oauth2.token_type = strdup(XML_GetChildBody(x, "token_type", "")); -// jcl->oauth2.expires_in = strdup(XML_GetChildBody(x, "expires_in", "")); +// ctx->oauth2.token_type = strdup(XML_GetChildBody(x, "token_type", "")); +// ctx->oauth2.expires_in = strdup(XML_GetChildBody(x, "expires_in", "")); //refresh token may mutate. follow the mutation. } - else if (*jcl->oauth2.accesstoken) + else if (*ctx->oauth2.accesstoken) Con_Printf("XMPP: Using explicit access token\n"); - if (*jcl->oauth2.accesstoken) + if (*ctx->oauth2.accesstoken) { int len = 0; - if (*jcl->oauth2.useraccount) + if (*ctx->oauth2.useraccount) { //realm isn't specified buf[len++] = 0; - memcpy(buf+len, jcl->oauth2.useraccount, strlen(jcl->oauth2.useraccount)); - len += strlen(jcl->oauth2.useraccount); + memcpy(buf+len, ctx->oauth2.useraccount, strlen(ctx->oauth2.useraccount)); + len += strlen(ctx->oauth2.useraccount); buf[len++] = 0; } - memcpy(buf+len, jcl->oauth2.accesstoken, strlen(jcl->oauth2.accesstoken)); - len += strlen(jcl->oauth2.accesstoken); + memcpy(buf+len, ctx->oauth2.accesstoken, strlen(ctx->oauth2.accesstoken)); + len += strlen(ctx->oauth2.accesstoken); Con_Printf("XMPP: Signing in\n"); - free(jcl->oauth2.accesstoken); - jcl->oauth2.accesstoken = strdup(""); + free(ctx->oauth2.accesstoken); + ctx->oauth2.accesstoken = strdup(""); return len; } @@ -1234,10 +1359,15 @@ static int sasl_oauth2_initial(jclient_t *jcl, char *buf, int bufsize) //in descending priority order saslmethod_t saslmethods[] = { - {"SCRAM-SHA-1", sasl_scramsha1_initial, sasl_scramsha1_challenge}, //lots of unreadable hashing - {"DIGEST-MD5", sasl_digestmd5_initial, sasl_digestmd5_challenge}, //kinda silly - {"PLAIN", sasl_plain_initial, NULL}, //realm\0username\0password - {NULL, sasl_oauth2_initial, NULL} //potentially avoids having to ask+store their password. a browser is required to obtain auth token for us. +// {"SCRAM-SHA-512-PLUS", sasl_scramsha512plus_initial, sasl_scram_challenge, sasl_scram_final}, //lots of unreadable hashing, with added channel bindings +// {"SCRAM-SHA-256-PLUS", sasl_scramsha256plus_initial, sasl_scram_challenge, sasl_scram_final}, //lots of unreadable hashing, with added channel bindings + {"SCRAM-SHA-1-PLUS", sasl_scramsha1plus_initial, sasl_scram_challenge, sasl_scram_final}, //lots of unreadable hashing, with added channel bindings +// {"SCRAM-SHA-512", sasl_scramsha512minus_initial, sasl_scram_challenge, sasl_scram_final}, //lots of unreadable hashing +// {"SCRAM-SHA-256", sasl_scramsha256minus_initial, sasl_scram_challenge, sasl_scram_final}, //lots of unreadable hashing + {"SCRAM-SHA-1", sasl_scramsha1minus_initial, sasl_scram_challenge, sasl_scram_final}, //lots of unreadable hashing + {"DIGEST-MD5", sasl_digestmd5_initial, sasl_digestmd5_challenge, NULL}, //kinda silly + {"PLAIN", sasl_plain_initial, NULL, NULL}, //realm\0username\0password + {NULL, sasl_oauth2_initial, NULL, NULL} //potentially avoids having to ask+store their password. a browser is required to obtain auth token for us. }; /* @@ -1401,7 +1531,7 @@ qintptr_t JCL_ConsoleLinkMouseOver(qintptr_t *args) JCL_FindBuddy(jcl, who, &b, &br, false); if (!b) return false; - JCL_FindBuddy(jcl, jcl->jid, &me, NULL, true); + JCL_FindBuddy(jcl, jcl->fulljid, &me, NULL, true); if ((jcl->enabledcapabilities & CAP_AVATARS) && BUILTINISVALID(Draw_LoadImageData)) { @@ -1594,6 +1724,110 @@ qintptr_t JCL_ConsoleLink(qintptr_t *args) jclient_action_buddy = NULL; jclient_action = ACT_NEWACCOUNT; } + else if (!strcmp(what, "setausername")) + { + pCon_SetConsoleFloat(BUDDYLISTTITLE, "linebuffered", true); + pCon_SetConsoleString(BUDDYLISTTITLE, "footer", "Please enter your user name"); + jclient_action_cl = jcl; + jclient_action_buddy = NULL; + jclient_action = ACT_SETAUSERNAME; + } + else if (!strcmp(what, "setadomain")) + { + pCon_SetConsoleFloat(BUDDYLISTTITLE, "linebuffered", true); + pCon_SetConsoleString(BUDDYLISTTITLE, "footer", "Please enter the domain for which your account is valid"); + jclient_action_cl = jcl; + jclient_action_buddy = NULL; + jclient_action = ACT_SETADOMAIN; + } + else if (!strcmp(what, "setaserver")) + { + pCon_SetConsoleFloat(BUDDYLISTTITLE, "linebuffered", true); + pCon_SetConsoleString(BUDDYLISTTITLE, "footer", "Please enter the server to connect to."); + jclient_action_cl = jcl; + jclient_action_buddy = NULL; + jclient_action = ACT_SETASERVER; + } + else if (!strcmp(what, "setaresource")) + { + pCon_SetConsoleFloat(BUDDYLISTTITLE, "linebuffered", true); + pCon_SetConsoleString(BUDDYLISTTITLE, "footer", "Please enter some resource name."); + jclient_action_cl = jcl; + jclient_action_buddy = NULL; + jclient_action = ACT_SETARESOURCE; + } + else if (!strcmp(what, "setapassword")) + { + pCon_SetConsoleFloat(BUDDYLISTTITLE, "linebuffered", true); + pCon_SetConsoleString(BUDDYLISTTITLE, "footer", "Please enter your XMPP account name\neg: example@"EXAMPLEDOMAIN); + jclient_action_cl = jcl; + jclient_action_buddy = NULL; + jclient_action = ACT_SETAPASSWORD; + } + else if (!strcmp(what, "setasavepassword")) + jcl->savepassword = !jcl->savepassword; + else if (!strcmp(what, "accopts")) + { + if (jcl) + { + char footer[2048]; + char link[512]; + if (jcl->status == JCL_INACTIVE) + JCL_GenLink(jcl, link, sizeof(link), "forgetacc", NULL, NULL, NULL, "%s", "Forget Account"); + else if (jcl->status == JCL_DEAD) + JCL_GenLink(jcl, link, sizeof(link), "disconnect", NULL, NULL, NULL, "%s", "Disable"); + else + JCL_GenLink(jcl, link, sizeof(link), "disconnect", NULL, NULL, NULL, "%s", "Disconnect"); + Q_strlcpy(footer, "\n", sizeof(footer)); + Q_strlcat(footer, link, sizeof(footer)); + + if (jcl->status == JCL_INACTIVE) + JCL_GenLink(jcl, link, sizeof(link), "connect", NULL, NULL, NULL, "%s", "Connect"); + else if (jcl->status == JCL_DEAD) + JCL_GenLink(jcl, link, sizeof(link), "connect", NULL, NULL, NULL, "%s", "Reconnect"); + else if (jcl->status == JCL_ACTIVE) + JCL_GenLink(jcl, link, sizeof(link), "addfriend", NULL, NULL, NULL, "%s", "Add Friend"); + else + *link = 0; + + if (*link) + { + Q_strlcat(footer, "\n", sizeof(footer)); + Q_strlcat(footer, link, sizeof(footer)); + } + + if (jcl->status == JCL_INACTIVE) + { + JCL_GenLink(jcl, link, sizeof(link), "setausername", NULL, NULL, NULL, "Username: %s", jcl->username); + Q_strlcat(footer, "\n", sizeof(footer)); + Q_strlcat(footer, link, sizeof(footer)); + + JCL_GenLink(jcl, link, sizeof(link), "setadomain", NULL, NULL, NULL, "Domain: %s", jcl->domain); + Q_strlcat(footer, "\n", sizeof(footer)); + Q_strlcat(footer, link, sizeof(footer)); + + JCL_GenLink(jcl, link, sizeof(link), "setaserver", NULL, NULL, NULL, "Server: %s", *jcl->serveraddr?jcl->serveraddr:""); + Q_strlcat(footer, "\n", sizeof(footer)); + Q_strlcat(footer, link, sizeof(footer)); + + JCL_GenLink(jcl, link, sizeof(link), "setaresource", NULL, NULL, NULL, "Resource: %s", jcl->resource); + Q_strlcat(footer, "\n", sizeof(footer)); + Q_strlcat(footer, link, sizeof(footer)); + } +// if (jcl->status == JCL_INACTIVE) + { + JCL_GenLink(jcl, link, sizeof(link), "setapassword", NULL, NULL, NULL, "Password: %s", jcl->sasl.password_plain); + Q_strlcat(footer, "\n", sizeof(footer)); + Q_strlcat(footer, link, sizeof(footer)); + + JCL_GenLink(jcl, link, sizeof(link), "setasavepassword", NULL, NULL, NULL, "Save Password: %s", jcl->savepassword?"true":"false"); + Q_strlcat(footer, "\n", sizeof(footer)); + Q_strlcat(footer, link, sizeof(footer)); + } + + pCon_SetConsoleString(BUDDYLISTTITLE, "footer", footer); + } + } else if (!strcmp(what, "buddyopts")) { char footer[2048]; @@ -1760,8 +1994,8 @@ qintptr_t JCL_ConExecuteCommand(qintptr_t *args) jcl = jclient_action_cl; if (jcl) { - free(jcl->oauth2.authtoken); - jcl->oauth2.authtoken = strdup(args); + free(jcl->sasl.oauth2.authtoken); + jcl->sasl.oauth2.authtoken = strdup(args); if (jcl->status == JCL_INACTIVE) jcl->status = JCL_DEAD; } @@ -1787,13 +2021,28 @@ qintptr_t JCL_ConExecuteCommand(qintptr_t *args) pCon_SetConsoleString(BUDDYLISTTITLE, "footer", "Please enter password"); jclient_action_cl = jclients[i]; jclient_action_buddy = NULL; - jclient_action = ACT_PASSWORD; + jclient_action = ACT_SETAPASSWORD; return true; } break; - case ACT_PASSWORD: + case ACT_SETAUSERNAME: + Q_strncpyz(jclient_action_cl->username, args, sizeof(jclient_action_cl->username)); + break; + case ACT_SETADOMAIN: + Q_strncpyz(jclient_action_cl->domain, args, sizeof(jclient_action_cl->domain)); + break; + case ACT_SETASERVER: + Q_strncpyz(jclient_action_cl->serveraddr, args, sizeof(jclient_action_cl->serveraddr)); + break; + case ACT_SETARESOURCE: + Q_strncpyz(jclient_action_cl->resource, args, sizeof(jclient_action_cl->resource)); + break; + case ACT_SETAPASSWORD: if (*args) - Q_strncpyz(jclient_action_cl->password, args, sizeof(jclient_action_cl->password)); + { + Q_strncpyz(jclient_action_cl->sasl.password_plain, args, sizeof(jclient_action_cl->sasl.password_plain)); + jclient_action_cl->sasl.password_hash_size = 0; //invalidate it + } if (jclient_action_cl->status == JCL_INACTIVE) jclient_action_cl->status = JCL_DEAD; jclient_action = ACT_NONE; @@ -1802,7 +2051,7 @@ qintptr_t JCL_ConExecuteCommand(qintptr_t *args) if (*args) XMPP_AddFriend(jclient_action_cl, args, ""); break; - case ACT_SETALIAS: + case ACT_SETBALIAS: Q_strncpyz(jclient_action_buddy->name, args, sizeof(jclient_action_buddy->name)); JCL_SendIQf(jclient_action_cl, NULL, "set", NULL, "", jclient_action_buddy->accountdomain, jclient_action_buddy->name); break; @@ -1935,7 +2184,7 @@ qboolean JCL_Reconnect(jclient_t *jcl) jcl->instreampos = 0; jcl->bufferedinammount = 0; Q_strlcpy(jcl->localalias, ">>", sizeof(jcl->localalias)); - jcl->authmode = -1; + jcl->sasl.authmethod = NULL; if (*jcl->redirserveraddr) serveraddr = jcl->redirserveraddr; @@ -2088,7 +2337,7 @@ jclient_t *JCL_ConnectXML(xmltree_t *acc) Q_strlcpy(jcl->domain, XML_GetChildBody(acc, "domain", "localhost"), sizeof(jcl->domain)); Q_strlcpy(jcl->resource, XML_GetChildBody(acc, "resource", ""), sizeof(jcl->resource)); - //half these networks seem to have weird domains. especially microsoft. + //half of these networks seem to have weird domains. especially microsoft. if (strchr(jcl->username, '@')) Q_strlcpy(oauthname, jcl->username, sizeof(oauthname)); else @@ -2099,18 +2348,18 @@ jclient_t *JCL_ConnectXML(xmltree_t *acc) break; } oauth2 = XML_ChildOfTree(acc, "oauth2", 0); - Q_strlcpy(jcl->oauth2.saslmethod, XML_GetParameter(oauth2, "method", oa->saslmethod), sizeof(jcl->oauth2.saslmethod)); - Q_strlcpy(jcl->oauth2.obtainurl, XML_GetChildBody(oauth2, "obtain-url", oa->obtainurl), sizeof(jcl->oauth2.obtainurl)); - Q_strlcpy(jcl->oauth2.refreshurl, XML_GetChildBody(oauth2, "refresh-url", oa->refreshurl), sizeof(jcl->oauth2.refreshurl)); - Q_strlcpy(jcl->oauth2.clientid, XML_GetChildBody(oauth2, "client-id", oa->clientid), sizeof(jcl->oauth2.clientid)); - Q_strlcpy(jcl->oauth2.clientsecret, XML_GetChildBody(oauth2, "client-secret", oa->clientsecret), sizeof(jcl->oauth2.clientsecret)); - jcl->oauth2.scope = strdup(XML_GetChildBody(oauth2, "scope", oa->scope)); - jcl->oauth2.useraccount = strdup(XML_GetChildBody(oauth2, "authname", oauthname)); - jcl->oauth2.authtoken = strdup(XML_GetChildBody(oauth2, "auth-token", "")); - jcl->oauth2.refreshtoken = strdup(XML_GetChildBody(oauth2, "refresh-token", "")); - jcl->oauth2.accesstoken = strdup(XML_GetChildBody(oauth2, "access-token", "")); + Q_strlcpy(jcl->sasl.oauth2.saslmethod, XML_GetParameter(oauth2, "method", oa->saslmethod), sizeof(jcl->sasl.oauth2.saslmethod)); + Q_strlcpy(jcl->sasl.oauth2.obtainurl, XML_GetChildBody(oauth2, "obtain-url", oa->obtainurl), sizeof(jcl->sasl.oauth2.obtainurl)); + Q_strlcpy(jcl->sasl.oauth2.refreshurl, XML_GetChildBody(oauth2, "refresh-url", oa->refreshurl), sizeof(jcl->sasl.oauth2.refreshurl)); + Q_strlcpy(jcl->sasl.oauth2.clientid, XML_GetChildBody(oauth2, "client-id", oa->clientid), sizeof(jcl->sasl.oauth2.clientid)); + Q_strlcpy(jcl->sasl.oauth2.clientsecret, XML_GetChildBody(oauth2, "client-secret", oa->clientsecret), sizeof(jcl->sasl.oauth2.clientsecret)); + jcl->sasl.oauth2.scope = strdup(XML_GetChildBody(oauth2, "scope", oa->scope)); + jcl->sasl.oauth2.useraccount = strdup(XML_GetChildBody(oauth2, "authname", oauthname)); + jcl->sasl.oauth2.authtoken = strdup(XML_GetChildBody(oauth2, "auth-token", "")); + jcl->sasl.oauth2.refreshtoken = strdup(XML_GetChildBody(oauth2, "refresh-token", "")); + jcl->sasl.oauth2.accesstoken = strdup(XML_GetChildBody(oauth2, "access-token", "")); - Q_strlcpy(jcl->password, XML_GetChildBody(acc, "password", ""), sizeof(jcl->password)); + Q_strlcpy(jcl->sasl.password_plain, XML_GetChildBody(acc, "password", ""), sizeof(jcl->sasl.password_plain)); if (!*jcl->resource) { //the default resource matches the game that they're trying to play. @@ -2133,11 +2382,11 @@ jclient_t *JCL_ConnectXML(xmltree_t *acc) } Q_strlcpy(jcl->certificatedomain, XML_GetChildBody(acc, "certificatedomain", jcl->domain), sizeof(jcl->certificatedomain)); jcl->status = atoi(XML_GetChildBody(acc, "inactive", "0"))?JCL_INACTIVE:JCL_DEAD; - jcl->allowauth_plainnontls = atoi(XML_GetChildBody(acc, "allowauth_plain_nontls", "0")); - jcl->allowauth_plaintls = atoi(XML_GetChildBody(acc, "allowauth_plain_tls", "1")); //required 1 for googletalk, otherwise I'd set it to 0. - jcl->allowauth_digestmd5 = atoi(XML_GetChildBody(acc, "allowauth_digest_md5", "1")); - jcl->allowauth_scramsha1 = atoi(XML_GetChildBody(acc, "allowauth_scram_sha_1", "1")); - jcl->allowauth_oauth2 = atoi(XML_GetChildBody(acc, "allowauth_oauth2", jcl->oauth2.saslmethod?"1":"0")); + jcl->sasl.allowauth_plainnontls = atoi(XML_GetChildBody(acc, "allowauth_plain_nontls", "0")); + jcl->sasl.allowauth_plaintls = atoi(XML_GetChildBody(acc, "allowauth_plain_tls", "1")); //required 1 for googletalk, otherwise I'd set it to 0. + jcl->sasl.allowauth_digestmd5 = atoi(XML_GetChildBody(acc, "allowauth_digest_md5", "1")); + jcl->sasl.allowauth_scramsha1 = atoi(XML_GetChildBody(acc, "allowauth_scram_sha_1", "1")); + jcl->sasl.allowauth_oauth2 = atoi(XML_GetChildBody(acc, "allowauth_oauth2", jcl->sasl.oauth2.saslmethod?"1":"0")); jcl->savepassword = atoi(XML_GetChildBody(acc, "savepassword", "0")); @@ -2643,15 +2892,15 @@ struct iq_s *JCL_SendIQ(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xm if (*target) { - if (*jcl->jid) - JCL_AddClientMessagef(jcl, "", iqtype, iq->id, jcl->jid, target); + if (*jcl->fulljid) + JCL_AddClientMessagef(jcl, "", iqtype, iq->id, jcl->fulljid, target); else JCL_AddClientMessagef(jcl, "", iqtype, iq->id, target); } else { - if (*jcl->jid) - JCL_AddClientMessagef(jcl, "", iqtype, iq->id, jcl->jid); + if (*jcl->fulljid) + JCL_AddClientMessagef(jcl, "", iqtype, iq->id, jcl->fulljid); else JCL_AddClientMessagef(jcl, "", iqtype, iq->id); } @@ -2681,6 +2930,7 @@ struct iq_s *JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl return n; } +#ifdef USE_GOOGLE_MAIL_NOTIFY qboolean XMPP_NewGoogleMailsReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) { int i, j; @@ -2744,6 +2994,16 @@ qboolean XMPP_NewGoogleMailsReply(jclient_t *jcl, xmltree_t *tree, struct iq_s * } return true; } +#endif + +qboolean XMPP_CarbonsEnabledReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) +{ + //don't care. if we don't get carbons then no real loss. + //xmltree_t *etype, *error = XML_ChildOfTreeNS(tree, NULL, "error", 0); + //etype = XML_ChildOfTreeNS(error, NULL, "forbidden", 0); + //etype = XML_ChildOfTreeNS(error, NULL, "not-allowed", 0); + return true; +} static void JCL_RosterUpdate(jclient_t *jcl, xmltree_t *listp, char *from) { @@ -2804,9 +3064,9 @@ static qboolean JCL_BindReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) if (c) { char myjid[512]; - Q_strlcpy(jcl->jid, c->body, sizeof(jcl->jid)); - JCL_GenLink(jcl, myjid, sizeof(myjid), NULL, jcl->jid, NULL, NULL, "%s", jcl->jid); - Con_DPrintf("Bound to jid %s\n", jcl->jid); + Q_strlcpy(jcl->fulljid, c->body, sizeof(jcl->fulljid)); + JCL_GenLink(jcl, myjid, sizeof(myjid), NULL, jcl->fulljid, NULL, NULL, "%s", jcl->fulljid); + Con_DPrintf("Bound to jid %s\n", jcl->fulljid); return true; } } @@ -2968,6 +3228,8 @@ static qboolean JCL_MyVCardReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *i Q_strlcpy(jcl->localalias, nickname->body, sizeof(jcl->localalias)); else if (fn && *fn->body) Q_strlcpy(jcl->localalias, fn->body, sizeof(jcl->localalias)); + else + Q_strlcpy(jcl->localalias, jcl->barejid, sizeof(jcl->localalias)); //barejid or just username? return true; } static qboolean JCL_ServerFeatureReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) @@ -2976,7 +3238,10 @@ static qboolean JCL_ServerFeatureReply(jclient_t *jcl, xmltree_t *tree, struct i xmltree_t *feature; char *featurename; int f; +#ifdef USE_GOOGLE_MAIL_NOTIFY qboolean gmail = false; +#endif + qboolean carbons = false; if (!query) return false; @@ -2987,23 +3252,32 @@ static qboolean JCL_ServerFeatureReply(jclient_t *jcl, xmltree_t *tree, struct i if (!feature) break; featurename = XML_GetParameter(feature, "var", ""); +#ifdef USE_GOOGLE_MAIL_NOTIFY if (!strcmp(featurename, "google:mail:notify")) gmail = true; else +#endif + if (!strcmp(featurename, "urn:xmpp:carbons:2")) + carbons = true; + else { Con_DPrintf("Server supports feature %s\n", featurename); } } +#ifdef USE_GOOGLE_MAIL_NOTIFY if (gmail) JCL_SendIQf(jcl, XMPP_NewGoogleMailsReply, "get", NULL, ""); +#endif + if (carbons) + JCL_SendIQf(jcl, XMPP_CarbonsEnabledReply, "set", NULL, ""); return true; } static qboolean JCL_SessionReply(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) { JCL_SendIQf(jcl, JCL_RosterReply, "get", NULL, ""); - JCL_SendIQf(jcl, JCL_MyVCardReply, "get", NULL, ""); + JCL_SendIQf(jcl, JCL_MyVCardReply, "get", jcl->barejid, ""); JCL_SendIQf(jcl, JCL_ServerFeatureReply, "get", jcl->domain, ""); return true; } @@ -3027,7 +3301,7 @@ static struct #ifdef VOIP_LEGACY {"http://www.google.com/xmpp/protocol/session", CAP_GOOGLE_VOICE}, //so google's non-standard clients can chat with us {"http://www.google.com/xmpp/protocol/voice/v1", CAP_GOOGLE_VOICE}, //so google's non-standard clients can chat with us - {"http://www.google.com/xmpp/protocol/camera/v1", CAP_GOOGLE_VOICE}, //can send video +// {"http://www.google.com/xmpp/protocol/camera/v1", CAP_GOOGLE_VOICE}, //can send video // {"http://www.google.com/xmpp/protocol/video/v1", CAP_GOOGLE_VOICE}, //can receive video #endif #ifndef VOIP_LEGACY_ONLY @@ -3035,8 +3309,8 @@ static struct {"urn:xmpp:jingle:apps:rtp:audio", CAP_VOICE}, {"urn:xmpp:jingle:apps:rtp:video", CAP_VIDEO}, #endif +// {"urn:xmpp:jingle:apps:rtp:video", CAP_VIDEO}, //we don't support rtp video chat #endif - //"urn:xmpp:jingle:apps:rtp:video",//we don't support rtp video chat {"urn:xmpp:jingle:transports:raw-udp:1", CAP_GAMEINVITE|CAP_VOICE|CAP_VIDEO}, #ifndef NOICE {"urn:xmpp:jingle:transports:ice-udp:1", CAP_GAMEINVITE|CAP_VOICE|CAP_VIDEO}, @@ -3055,6 +3329,16 @@ static struct {"http://jabber.org/protocol/ibb", CAP_SIFT}, {"http://jabber.org/protocol/bytestreams", CAP_SIFT}, #endif + + {"urn:xmpp:avatar:data"}, + {"urn:xmpp:avatar:metadata"}, + {"urn:xmpp:avatar:metadata+notify"}, +// {"http://jabber.org/protocol/mood"}, +// {"http://jabber.org/protocol/mood+notify"}, +/// {"http://jabber.org/protocol/tune"}, +// {"http://jabber.org/protocol/tune+notify"}, + {"http://jabber.org/protocol/nick"}, + {"http://jabber.org/protocol/nick+notify"}, #else //for testing, this is the list of features pidgin supports (which is the other client I'm testing against). @@ -3106,7 +3390,7 @@ static void buildcaps(jclient_t *jcl, char *out, int outlen) for (i = 0; caps[i].name; i++) { - if (!(caps[i].withcap & jcl->enabledcapabilities)) + if (caps[i].withcap && !(caps[i].withcap & jcl->enabledcapabilities)) continue; Q_strlcat(out, "chatroom) return; //we already know about it. don't spam. @@ -3685,10 +4040,20 @@ void JCL_ParseMessage(jclient_t *jcl, xmltree_t *tree) unparsable = false; JCL_GenLink(jcl, link, sizeof(link), "mucjoin", chatjit, NULL, password, "%s", chatjit); - if (reason) - XMPP_ConversationPrintf(ctx, f, "* ^2%s^7 has invited you to join %s: %s.\n", f, link, reason); + if (sent) + { + if (reason) + XMPP_ConversationPrintf(ctx, f, "* You have invited ^2%s^7 to join %s: %s.\n", f, link, reason); + else + XMPP_ConversationPrintf(ctx, f, "* You have invited ^2%s^7 to join %s.\n", f, link); + } else - XMPP_ConversationPrintf(ctx, f, "* ^2%s^7 has invited you to join %s.\n", f, link); + { + if (reason) + XMPP_ConversationPrintf(ctx, f, "* ^2%s^7 has invited you to join %s: %s.\n", f, link, reason); + else + XMPP_ConversationPrintf(ctx, f, "* ^2%s^7 has invited you to join %s.\n", f, link); + } if (BUILTINISVALID(Con_SetActive)) pCon_SetActive(ctx); return; //ignore any body @@ -3700,13 +4065,30 @@ void JCL_ParseMessage(jclient_t *jcl, xmltree_t *tree) unparsable = false; if (f) { - if (!strncmp(ot->body, "/me ", 4)) - XMPP_ConversationPrintf(ctx, f, "* ^2%s^7%s\n", f, ot->body+3); + if (sent) + { + if (!strncmp(ot->body, "/me ", 4)) + XMPP_ConversationPrintf(ctx, f, "* "COL_NAME_US"%s"COL_TEXT_US"%s\n", jcl->localalias, ot->body+3); + else if (showicon) + XMPP_ConversationPrintf(ctx, f, "^[\\img\\xmpp/%s.png\\fbimg\\"IMG_FB_US"\\w\\32\\h\\32^]"COL_NAME_US"%s"COL_TEXT_US":\v%s\n", jcl->barejid, jcl->localalias, ot->body); + else + XMPP_ConversationPrintf(ctx, f, COL_NAME_US"%s"COL_TEXT_US": %s\n", jcl->localalias, ot->body); + } else - XMPP_ConversationPrintf(ctx, f, "^2%s^7: %s\n", f, ot->body); + { + if (!strncmp(ot->body, "/me ", 4)) + XMPP_ConversationPrintf(ctx, f, "* "COL_NAME_THEM"%s"COL_TEXT_THEM"%s\n", f, ot->body+3); + else if (showicon) + XMPP_ConversationPrintf(ctx, f, "^[\\img\\xmpp/%s.png\\fbimg\\"IMG_FB_THEM"\\w\\32\\h\\32^]"COL_NAME_THEM"%s"COL_TEXT_THEM":\v%s\n", showicon, f, ot->body); + else + XMPP_ConversationPrintf(ctx, f, COL_NAME_THEM"%s"COL_TEXT_THEM": %s\n", f, ot->body); + } } else - XMPP_ConversationPrintf(ctx, f, "NOTICE: %s\n", ot->body); + { + if (!sent) + XMPP_ConversationPrintf(ctx, f, "NOTICE: %s\n", ot->body); + } if (BUILTINISVALID(LocalSound)) pLocalSound("misc/talk.wav"); @@ -3724,6 +4106,64 @@ void JCL_ParseMessage(jclient_t *jcl, xmltree_t *tree) } } +#if 0 +static qboolean JCL_ValidateCaps(xmltree_t *query, const char *node, const char *ver, const char *hash) +{ + const char *respnode = XML_GetParameter(query, "node", ""); + int l; + char out[8192]; + int outlen = sizeof(out); + unsigned char digest[64]; + const char *features[1024]; + size_t i, numfeatures = 0; + size_t nlen = strlen(node); + if (strcmp(hash, "sha-1")) + return false; //unable to validate. + if (strncmp(respnode, node, nlen)) + return false; //the client's name changed... + respnode+=nlen; + if (*respnode++ != '#') + return false; //o.O + if (strcmp(respnode, ver)) + return false; //the client's name changed... + for(i = 0; i < 64; i++) + { + xmltree_t *ident = XML_ChildOfTree(query, "identity", i); + if (ident) + { + const char *category = XML_GetParameter(ident, "category", ""); + const char *name = XML_GetParameter(ident, "name", ""); + const char *type = XML_GetParameter(ident, "type", ""); + const char *lang = XML_GetParameter(ident, "xml:lang", ""); + Q_snprintf(out, outlen, "%s/%s/%s/%s<", category, type, lang, name); + } + else + break; + } + for(i = 0; i < countof(features); i++) + { + xmltree_t *feature = XML_ChildOfTree(query, "feature", i); + if (feature) + { + const char *var = XML_GetParameter(feature, "var", ""); + features[numfeatures++] = var; + } + else + break; + } +// qsort(caps, sizeof(caps)/sizeof(caps[0]) - 1, sizeof(caps[0]), qsortcaps); + for (i = 0; i < numfeatures; i++) + { + Q_strlcat(out, features[i], outlen); + Q_strlcat(out, "<", outlen); + } + //fixme: add any form crap + l = SHA1(digest, sizeof(digest), out, strlen(out)); + for (i = 0; i < l; i++) + Base64_Byte(digest[i]); + return !strcmp(respnode, Base64_Finish()); //make sure its mostly valid. +} +#endif unsigned int JCL_ParseCaps(jclient_t *jcl, char *account, char *resource, xmltree_t *query) { xmltree_t *feature; @@ -3811,7 +4251,10 @@ qboolean JCL_ClientDiscoInfo(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) else { // XML_ConPrintTree(tree, 0); - caps = JCL_ParseCaps(jcl, b->accountdomain, r->resource, query); +// if (!JCL_ValidateCaps(query, r->client_node, r->client_ver, r->client_hash)) +// caps = 0; +// else + caps = JCL_ParseCaps(jcl, b->accountdomain, r->resource, query); } if (b && r) @@ -4062,7 +4505,7 @@ void JCL_ParsePresence(jclient_t *jcl, xmltree_t *tree) buddy->vcardphotochanged = true; } - JCL_FindBuddy(jcl, jcl->jid, &me, NULL, true); + JCL_FindBuddy(jcl, jcl->fulljid, &me, NULL, true); if (buddy == me) { if (strcmp(buddy->vcardphotohash, jcl->vcardphotohash)) @@ -4297,6 +4740,8 @@ int JCL_ClientFrame(jclient_t *jcl, char **error) unparsable = true; if (!strcmp(tree->name, "features")) { + Q_snprintf(jcl->barejid, sizeof(jcl->barejid), "%s@%s", jcl->username, jcl->domain); + Q_snprintf(jcl->fulljid, sizeof(jcl->fulljid), "%s@%s/%s", jcl->username, jcl->domain, jcl->resource); if ((ot=XML_ChildOfTree(tree, "bind", 0))) { unparsable = false; @@ -4342,18 +4787,30 @@ int JCL_ClientFrame(jclient_t *jcl, char **error) XML_Destroy(tree); return JCL_KILL; } + + //init some sasl fields from the jcl state. + jcl->sasl.username = jcl->username; //auth user name + jcl->sasl.domain = jcl->domain; //auth domain + jcl->sasl.issecure = jcl->issecure; //says that its connected over tls, and that sock is the tls connection. + jcl->sasl.socket = jcl->socket; //just for channel bindings + jcl->sasl.authmethod = NULL; //unknown at this point + + //attempt to use a method based on the ones that we prefer. for (sm = 0; sm < sizeof(saslmethods)/sizeof(saslmethods[0]); sm++) { - method = saslmethods[sm].method?saslmethods[sm].method:jcl->oauth2.saslmethod; + method = saslmethods[sm].method?saslmethods[sm].method:jcl->sasl.oauth2.saslmethod; if (!*method) continue; for (m = ot->child; m; m = m->sibling) { if (!strcmp(m->body, method)) { - outlen = saslmethods[sm].sasl_initial(jcl, out, sizeof(out)); + outlen = saslmethods[sm].sasl_initial(&jcl->sasl, out, sizeof(out)); if (outlen >= 0) + { + jcl->sasl.authmethod = &saslmethods[sm]; break; + } if (outlen == -2) needpass = true; } @@ -4362,7 +4819,7 @@ int JCL_ClientFrame(jclient_t *jcl, char **error) break; } - if (outlen < 0) + if (outlen < 0 || !jcl->sasl.authmethod) { XML_Destroy(tree); //can't authenticate for some reason @@ -4374,13 +4831,12 @@ int JCL_ClientFrame(jclient_t *jcl, char **error) if (outlen >= 0) { - jcl->authmode = sm; Base64_Add(out, outlen); Base64_Finish(); Con_DPrintf("XMPP: Authing with %s%s.\n", method, jcl->issecure?" over tls":" without encription"); JCL_AddClientMessagef(jcl, "%s", method, base64); unparsable = false; @@ -4401,14 +4857,14 @@ int JCL_ClientFrame(jclient_t *jcl, char **error) } } } - else if (!strcmp(tree->name, "challenge") && !strcmp(tree->xmlns, "urn:ietf:params:xml:ns:xmpp-sasl") && jcl->authmode >= 0) + else if (!strcmp(tree->name, "challenge") && !strcmp(tree->xmlns, "urn:ietf:params:xml:ns:xmpp-sasl") && jcl->sasl.authmethod) { char in[512]; int inlen; char out[512]; int outlen; inlen = Base64_Decode(in, sizeof(in), tree->body, strlen(tree->body)); - outlen = saslmethods[jcl->authmode].sasl_challenge(jcl, in, inlen, out, sizeof(out)); + outlen = jcl->sasl.authmethod->sasl_challenge(&jcl->sasl, in, inlen, out, sizeof(out)); if (outlen < 0) { *error = "Unable to auth with server"; @@ -4511,8 +4967,21 @@ int JCL_ClientFrame(jclient_t *jcl, char **error) return JCL_KILL; } } - else if (!strcmp(tree->name, "success")) + else if (!strcmp(tree->name, "success") && !strcmp(tree->xmlns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + if (!jcl->sasl.authmethod || jcl->sasl.authmethod->sasl_success) + { + char in[512]; + int inlen; + inlen = Base64_Decode(in, sizeof(in), tree->body, strlen(tree->body)); + if (!jcl->sasl.authmethod || jcl->sasl.authmethod->sasl_success(&jcl->sasl, in, inlen) < 0) + { + *error = "Server validation failed"; + XML_Destroy(tree); + return JCL_KILL; + } + } + //Restart everything, basically, AGAIN! (third time lucky?) jcl->bufferedinammount = 0; jcl->instreampos = 0; @@ -4649,26 +5118,26 @@ void JCL_GeneratePresence(jclient_t *jcl, qboolean force) if (!*jcl->curquakeserver) JCL_AddClientMessagef(jcl, - "" + "" "%s" "%s" - "", prior, caps); + "", jcl->fulljid, prior, caps); else if (*servermap) //if we're running a server, say so JCL_AddClientMessagef(jcl, - "" + "" "%s" "" "%s" "" - , prior, servermap, caps); + , jcl->fulljid, prior, servermap, caps); else //if we're connected to a server, say so JCL_AddClientMessagef(jcl, - "" + "" "%s" "" "%s" "" - , prior, jcl->curquakeserver, caps); + , jcl->fulljid, prior, jcl->curquakeserver, caps); } } @@ -4756,7 +5225,10 @@ static void JCL_RegenerateBuddyList(qboolean force) Con_SubPrintf(console, "%s.\n", *jcl->errormsg?jcl->errormsg:"Connect failed", jcl->accountnum); else if (jcl->status == JCL_AUTHING) Con_SubPrintf(console, "Connecting... Please wait.\n"); - + + JCL_GenLink(jcl, convolink, sizeof(convolink), "accopts", NULL, NULL, NULL, "%s", "Options"); + Con_SubPrintf(console, "%s\n", convolink); +/* if (jcl->status == JCL_INACTIVE) JCL_GenLink(jcl, convolink, sizeof(convolink), "forgetacc", NULL, NULL, NULL, "%s", "Forget Account"); else if (jcl->status == JCL_DEAD) @@ -4774,16 +5246,17 @@ static void JCL_RegenerateBuddyList(qboolean force) JCL_GenLink(jcl, convolink, sizeof(convolink), "connect", NULL, NULL, NULL, "%s", "Reconnect"); Con_SubPrintf(console, " %s", convolink); } +*/ if (jcl->status != JCL_ACTIVE) Con_SubPrintf(console, "\n"); else { qboolean youarealoner = true; - JCL_GenLink(jcl, convolink, sizeof(convolink), "addfriend", NULL, NULL, NULL, "%s", "Add Friend"); - Con_SubPrintf(console, " %s\n", convolink); +// JCL_GenLink(jcl, convolink, sizeof(convolink), "addfriend", NULL, NULL, NULL, "%s", "Add Friend"); +// Con_SubPrintf(console, " %s\n", convolink); - JCL_FindBuddy(jcl, jcl->jid, &me, NULL, true); + JCL_FindBuddy(jcl, jcl->fulljid, &me, NULL, true); for (b = jcl->buddies, buds = 0; b && buds < sizeof(sortlist)/sizeof(sortlist[0]); b = b->next) { @@ -4868,10 +5341,7 @@ static void JCL_RegenerateBuddyList(qboolean force) Q_snprintf(convolink, sizeof(convolink), "^[%s\\xmppacc\\%i\\xmpp\\%s^]", b->name, jcl->accountnum, b->accountdomain); - if (!b->image) - Con_SubPrintf(console, "^[\\img\\gfx/menudot1.lmp\\w\\32\\h\\32^]"); - else - Con_SubPrintf(console, "^[\\img\\xmpp/%s.png\\w\\32\\h\\32^]", b->accountdomain); + Con_SubPrintf(console, "^[\\img\\xmpp/%s.png\\fbimg\\"IMG_FB_THEM"\\w\\32\\h\\32^] ", b->accountdomain); Con_SubPrintf(console, "%s", convolink); if (*chatres->fstatus) @@ -5141,31 +5611,31 @@ void JCL_WriteConfig(void) Q_snprintf(foo, sizeof(foo), "%i", jcl->forcetls); XML_CreateNode(n, "forcetls", "", foo); XML_CreateNode(n, "savepassword", "", jcl->savepassword?"1":"0"); - XML_CreateNode(n, "allowauth_plain_nontls", "", jcl->allowauth_plainnontls?"1":"0"); - XML_CreateNode(n, "allowauth_plain_tls", "", jcl->allowauth_plaintls?"1":"0"); - XML_CreateNode(n, "allowauth_digest_md5", "", jcl->allowauth_digestmd5?"1":"0"); - XML_CreateNode(n, "allowauth_scram_sha_1", "", jcl->allowauth_scramsha1?"1":"0"); + XML_CreateNode(n, "allowauth_plain_nontls", "", jcl->sasl.allowauth_plainnontls?"1":"0"); + XML_CreateNode(n, "allowauth_plain_tls", "", jcl->sasl.allowauth_plaintls?"1":"0"); + XML_CreateNode(n, "allowauth_digest_md5", "", jcl->sasl.allowauth_digestmd5?"1":"0"); + XML_CreateNode(n, "allowauth_scram_sha_1", "", jcl->sasl.allowauth_scramsha1?"1":"0"); - if (*jcl->oauth2.saslmethod) + if (*jcl->sasl.oauth2.saslmethod) { - XML_CreateNode(n, "allowauth_oauth2", "", jcl->allowauth_oauth2?"1":"0"); + XML_CreateNode(n, "allowauth_oauth2", "", jcl->sasl.allowauth_oauth2?"1":"0"); oauth2 = XML_CreateNode(n, "oauth2", "", ""); - XML_AddParameter(oauth2, "method", jcl->oauth2.saslmethod); - XML_CreateNode(oauth2, "obtain-url", "", jcl->oauth2.obtainurl); - XML_CreateNode(oauth2, "refresh-url", "", jcl->oauth2.refreshurl); - XML_CreateNode(oauth2, "client-id", "", jcl->oauth2.clientid); - XML_CreateNode(oauth2, "client-secret", "", jcl->oauth2.clientsecret); - XML_CreateNode(oauth2, "scope", "", jcl->oauth2.scope); - XML_CreateNode(oauth2, "auth-token", "", jcl->oauth2.authtoken); - XML_CreateNode(oauth2, "refresh-token", "", jcl->oauth2.refreshtoken); - XML_CreateNode(oauth2, "access-token", "", jcl->oauth2.accesstoken); + XML_AddParameter(oauth2, "method", jcl->sasl.oauth2.saslmethod); + XML_CreateNode(oauth2, "obtain-url", "", jcl->sasl.oauth2.obtainurl); + XML_CreateNode(oauth2, "refresh-url", "", jcl->sasl.oauth2.refreshurl); + XML_CreateNode(oauth2, "client-id", "", jcl->sasl.oauth2.clientid); + XML_CreateNode(oauth2, "client-secret", "", jcl->sasl.oauth2.clientsecret); + XML_CreateNode(oauth2, "scope", "", jcl->sasl.oauth2.scope); + XML_CreateNode(oauth2, "auth-token", "", jcl->sasl.oauth2.authtoken); + XML_CreateNode(oauth2, "refresh-token", "", jcl->sasl.oauth2.refreshtoken); + XML_CreateNode(oauth2, "access-token", "", jcl->sasl.oauth2.accesstoken); } XML_CreateNode(n, "username", "", jcl->username); XML_CreateNode(n, "domain", "", jcl->domain); XML_CreateNode(n, "resource", "", jcl->resource); - if (!*jcl->oauth2.saslmethod || !jcl->allowauth_oauth2 || *jcl->password) //avoid writing password lest we encourage someone to supply it when its not useful - XML_CreateNode(n, "password", "", jcl->password); //FIXME: should we base64 this just to obscure it? + if (jcl->savepassword) + XML_CreateNode(n, "password", "", jcl->sasl.password_plain); //FIXME: should we base64 this just to obscure it? probably not. trivial obscurity does few favours. XML_CreateNode(n, "serveraddr", "", jcl->serveraddr); Q_snprintf(foo, sizeof(foo), "%i", jcl->serverport); XML_CreateNode(n, "serverport", "", foo); @@ -5329,7 +5799,7 @@ void JCL_SendMessage(jclient_t *jcl, char *to, char *msg) if (!strncmp(msg, "/me ", 4)) XMPP_ConversationPrintf(con, title, "* ^5%s^7"COLOURYELLOW"%s\n", ((!strcmp(jcl->localalias, ">>"))?"me":jcl->localalias), msg+3); else - XMPP_ConversationPrintf(con, title, "^5%s^7: "COLOURYELLOW"%s\n", jcl->localalias, msg); + XMPP_ConversationPrintf(con, title, "^[\\img\\xmpp/%s.png\\fbimg\\"IMG_FB_US"\\w\\32\\h\\32^]^5%s^7:\v"COLOURYELLOW"%s\n", jcl->barejid, jcl->localalias, msg); } void JCL_AttentionMessage(jclient_t *jcl, char *to, char *msg) { @@ -5405,7 +5875,7 @@ void XMPP_Menu_Password(jclient_t *acc) pCon_SetConsoleFloat(BUDDYLISTTITLE, "linebuffered", true); jclient_action_cl = acc; jclient_action_buddy = NULL; - jclient_action = ACT_PASSWORD; + jclient_action = ACT_SETAPASSWORD; } /* @@ -5566,8 +6036,8 @@ void JCL_Command(int accid, char *console) } else if (!strcmp(arg[0]+1, "oa2token")) { - free(jcl->oauth2.authtoken); - jcl->oauth2.authtoken = strdup(arg[1]); + free(jcl->sasl.oauth2.authtoken); + jcl->sasl.oauth2.authtoken = strdup(arg[1]); if (jcl->status == JCL_INACTIVE) jcl->status = JCL_DEAD; } @@ -5576,7 +6046,15 @@ void JCL_Command(int accid, char *console) if (!strcmp(arg[1], "savepassword")) jcl->savepassword = atoi(arg[2]); else if (!strcmp(arg[1], "avatars")) + { jcl->enabledcapabilities = (jcl->enabledcapabilities & ~CAP_AVATARS) | (atoi(arg[2])?CAP_AVATARS:0); + JCL_GeneratePresence(jcl, true); + } + else if (!strcmp(arg[1], "ft")) + { + jcl->enabledcapabilities = (jcl->enabledcapabilities & ~CAP_SIFT) | (atoi(arg[2])?CAP_SIFT:0); + JCL_GeneratePresence(jcl, false); + } else if (!strcmp(arg[1], "debug")) jcl->streamdebug = atoi(arg[2]); else if (!strcmp(arg[1], "resource")) @@ -5586,7 +6064,8 @@ void JCL_Command(int accid, char *console) } else if (!strcmp(arg[0]+1, "password")) { - Q_strncpyz(jcl->password, arg[1], sizeof(jcl->password)); + Q_strncpyz(jcl->sasl.password_plain, arg[1], sizeof(jcl->sasl.password_plain)); + jcl->sasl.password_hash_size = 0; if (jcl->status == JCL_INACTIVE) jcl->status = JCL_DEAD; } diff --git a/plugins/jabber/jingle.c b/plugins/jabber/jingle.c index 21b232b28..49104bac9 100644 --- a/plugins/jabber/jingle.c +++ b/plugins/jabber/jingle.c @@ -357,7 +357,7 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) if (!strcmp(action, "session-initiate")) { //these attributes are meant to only be present in initiate. for call forwarding etc. which we don't properly support. - XML_AddParameter(jingle, "initiator", jcl->jid); + XML_AddParameter(jingle, "initiator", jcl->fulljid); } if (!strcmp(action, "session-terminate")) @@ -392,7 +392,7 @@ static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action) if (!strcmp(action, "session-accept")) { if (c2c->content[c].method == transportmode) - XML_AddParameter(jingle, "responder", jcl->jid); + XML_AddParameter(jingle, "responder", jcl->fulljid); else action = "transport-replace"; } diff --git a/plugins/jabber/sift.c b/plugins/jabber/sift.c index c9a7959a3..993b59bc7 100644 --- a/plugins/jabber/sift.c +++ b/plugins/jabber/sift.c @@ -62,7 +62,7 @@ void XMPP_FT_Frame(jclient_t *jcl) { //server has accepted us, woo. //sid+requester(them)+target(us) - req = va("%s%s%s", ft->sid, ft->with, jcl->jid); + req = va("%s%s%s", ft->sid, ft->with, jcl->fulljid); SHA1(digest, sizeof(digest), req, strlen(req)); //in hex for (req = domain, j=0; j < 20; j++) diff --git a/plugins/jabber/xml.c b/plugins/jabber/xml.c index 574069ae2..5282dd60a 100644 --- a/plugins/jabber/xml.c +++ b/plugins/jabber/xml.c @@ -631,6 +631,8 @@ void XML_ConPrintTree(xmltree_t *t, char *subconsole, int indent) { int start, c, chunk; struct buf_ctx buf = {NULL, 0, 0}; + if (!t) + return; XML_DumpToBuf(&buf, t, indent); buf_cat(&buf, "", 1); diff --git a/plugins/jabber/xmpp.h b/plugins/jabber/xmpp.h index 1b2039a87..95cff9236 100644 --- a/plugins/jabber/xmpp.h +++ b/plugins/jabber/xmpp.h @@ -129,18 +129,13 @@ typedef struct jclient_s int serverport; char domain[256]; char username[256]; - char password[256]; char resource[256]; char certificatedomain[256]; int forcetls; //-1=off, 0=ifpossible, 1=fail if can't upgrade, 2=old-style tls qboolean savepassword; - qboolean allowauth_plainnontls; //allow plain plain - qboolean allowauth_plaintls; //allow tls plain - qboolean allowauth_digestmd5; //allow digest-md5 auth - qboolean allowauth_scramsha1; //allow scram-sha-1 auth - qboolean allowauth_oauth2; //use oauth2 where possible - - char jid[256]; //this is our full username@domain/resource string + + char fulljid[256]; //this is our full username@domain/resource string + char barejid[256]; //this is our bare username@domain string char localalias[256];//this is what's shown infront of outgoing messages. >> by default until we can get our name. char vcardphotohash[20]; //20-byte sha1 hash. enum @@ -151,9 +146,6 @@ typedef struct jclient_s } vcardphotohashstatus; qboolean vcardphotohashchanged; //forces a presence send. - char authnonce[256]; - int authmode; - int instreampos; qboolean connecting; //still waiting for intial stream tag @@ -166,19 +158,56 @@ typedef struct jclient_s char curquakeserver[2048]; char defaultnamespace[2048]; //should be 'jabber:client' or blank (and spammy with all the extra xmlns attribs) - struct + struct sasl_ctx_s { - char saslmethod[64]; - char obtainurl[256]; - char refreshurl[256]; - char clientid[256]; - char clientsecret[256]; - char *useraccount; - char *scope; - char *accesstoken; //one-shot access token - char *refreshtoken; //long-term token that we can use to get new access tokens - char *authtoken; //short-term authorisation token, usable to get an access token (and a refresh token if we're lucky) - } oauth2; + char *username; //might be different from the account name, but probably isn't. + char *domain; //might be different from the account domain, but probably isn't. + qboolean issecure; //tls enabled (either upgraded or initially) + int socket; + + //this stuff should be saved + char password_plain[256]; //plain password. scrubbed if we auth using a hashed auth. + qbyte password_hash[256]; //safer password, not encrypted, but stored hashed. + size_t password_hash_size; + char password_validity[256]; //internal string used to check that the salt was unchanged + + struct saslmethod_s *authmethod; //null name = oauth2->saslmethod + qboolean allowauth_plainnontls; //allow plain plain + qboolean allowauth_plaintls; //allow tls plain + qboolean allowauth_digestmd5; //allow digest-md5 auth + qboolean allowauth_scramsha1; //allow scram-sha-1 auth + qboolean allowauth_oauth2; //use oauth2 where possible + + struct + { + char authnonce[256]; + } digest; + + struct + { + qboolean plus; + char authnonce[256]; + char authvhash[20]; + char authcbindtype[20]; + char authcbinding[256]; + hashfunc_t *hashfunc; + size_t hashsize; + } scram; + + struct + { + char saslmethod[64]; + char obtainurl[256]; + char refreshurl[256]; + char clientid[256]; + char clientsecret[256]; + char *useraccount; + char *scope; + char *accesstoken; //one-shot access token + char *refreshtoken; //long-term token that we can use to get new access tokens + char *authtoken; //short-term authorisation token, usable to get an access token (and a refresh token if we're lucky) + } oauth2; + } sasl; struct iq_s { diff --git a/plugins/plugin.c b/plugins/plugin.c index 7fd8aa35f..bed27f0c2 100644 --- a/plugins/plugin.c +++ b/plugins/plugin.c @@ -315,6 +315,9 @@ BUILTIN(void, Net_Close, (qhandle_t socket)); #define ARGNAMES ,sock,certhostname BUILTINR(int, Net_SetTLSClient, (qhandle_t sock, const char *certhostname)); #undef ARGNAMES +#define ARGNAMES ,sock,outdata,datalen +BUILTINR(int, Net_GetTLSBinding, (qhandle_t sock, char *outdata, int *datalen)); +#undef ARGNAMES #define ARGNAMES ,inputbuffer,buffersize BUILTINR(int, ReadInputBuffer, (void *inputbuffer, int buffersize)); @@ -468,6 +471,7 @@ void Plug_InitStandardBuiltins(void) CHECKBUILTIN(Net_Send); CHECKBUILTIN(Net_Close); CHECKBUILTIN(Net_SetTLSClient); + CHECKBUILTIN(Net_GetTLSBinding); //random things CHECKBUILTIN(CL_GetStats); diff --git a/plugins/plugin.h b/plugins/plugin.h index e7f64be81..e68b74b1d 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -357,6 +357,7 @@ EBUILTIN(int, Net_Recv, (qhandle_t socket, void *buffer, int len)); EBUILTIN(int, Net_Send, (qhandle_t socket, void *buffer, int len)); EBUILTIN(void, Net_Close, (qhandle_t socket)); EBUILTIN(int, Net_SetTLSClient, (qhandle_t sock, const char *certhostname)); +EBUILTIN(int, Net_GetTLSBinding, (qhandle_t sock, char *outdata, int *datalen)); #define N_WOULDBLOCK 0 #define NET_CLIENTPORT -1 #define NET_SERVERPORT -2 diff --git a/specs/particles.txt b/specs/particles.txt index 1493131f3..544d500b3 100644 --- a/specs/particles.txt +++ b/specs/particles.txt @@ -37,16 +37,36 @@ scalefactor typically needs to be explicitly set to 1. this value affects how th texture specifies to use an image named TEXTURENAME for this effect. +shader [shadername] [\n{\nshaderbody\n}\n] + Specifies to use a named shader. + If shaderbody is included (as a nested block on the following line) then that shader text will be used to create the shader if it doesn't otherwise exist. + This overrides blendmodes, as no default shader will need to be created. + tcoords [tscale] [rsmax] [rsstep] specifies to use a subsection of the image. if tscale is set, all units are divided by this. it is the virtual size of your texture. So a value of 1 means that your texture coords must be between 0 and 1. But if it properly matches your texture's size, the coords are in pixels. if rsmax is present, each particle will use a random image. These images must be on a single row in your particle font. rsstep specifies the stride (gap from one to the next) in your particle font, and is only needed if rsmax is present and greater than 1. +atlas count_in_each_axis firstidx [last] + An alternative to tcoords. + The specified texture (or shader) is to be considered as a grid of sprites (x*x, where x is specified in that first arg). + firstidx specifies the first image to use (horizontal THEN vertical). + last specifies the last image to use (inclusive). The engine will pick one at random. They should not span multiple rows. + +rotation + the particle will start with a rotation rotated between startmin and startmax. + It will then rotate with some per-particle value randomly picked between the speedmin+speedmax values. + Should NOT be used on beam particles. + rotationstart [max] + obsolete, see rotation. the particle will start with a rotation rotated between min and max. if max is missing, the particle will always start with the min value. +rotationspeed [max] + obsolete, see rotation. + beamtexstep only valid if the effect is a beam. specifies the number of quake units per beam texture repitition. @@ -69,6 +89,10 @@ scalefactor 1 makes the particle scale the same as anything else 0 makes the particle not change size no matter how far it is +stretchfactor + controls how spark particles stretch according to their velocity. + negative values give fixed length sparks. + scaledelta controls how the particle scales over time specifies the change in the particle scale per second. @@ -84,6 +108,9 @@ count alpha specifies the initial alpha value of the effect +alpharand + specifies a randomized additonal value added to each particle's initial alpha. + alphadelta specifies how much the alpha value of the effect changes per second (subtracted) @@ -96,7 +123,7 @@ diesubrand basically the particle will live up to this much less time. the alpha value will also be aged by the amount chosen by this value randomvel [vert] - controls how fast the particle moves when it spawns. This works regardless of any requested velocities. + controls how fast the particle moves when it spawns (according to its spawn pattern). This works regardless of any requested velocities. if vert is not specified, horiz is used instead. veladd @@ -105,6 +132,20 @@ veladd orgadd biases how much to add to the starting origin relative to the requested velocity. +orgbias + biases the particle's origin by this absolute worldspace vector, regardless of spawn mode. + +velbias + biases the particle's velocity by this absolute worldspace vector, regardless of spawn mode. + +orgwrand + randomised offset for the particle's origin, in worldspace. + +velwrand + randomised offset for the particle's origin, in worldspace. + + + friction <| | > Proportion of the particle's speed that should be lost from friction. Negative values are accepted. @@ -135,7 +176,7 @@ inwater Specifies a replacement effect to use when this one is spawned underwater. assoc used is the replacement effect. the assoc value from the replaced effect is ignored (this includes +foo chains). -overwater [content names] +notunderwater [content names] Specifies that this particle should ONLY be spawned when out of water. The particle will not spawn under water (this does not affect assoc chains). Content names are a space-separated list of: water slime lava sky solid fluid. Default is fluid if not specified. @@ -163,19 +204,24 @@ green blue rgb rgb +rgbf Specifies the initial red, green, and/or blue values for each particle. Fully opaque is 255 or above. - Values above 255 are valid, but will remain opaque until the value drops below 255 from the colour deltas. + Values above 255 are valid, but will remain opaque until the value drops below 255 from the +colour deltas. + The rgbf form takes values scaled by 1 instead of 255. redrand greenrand bluerand rgbrand rgbrand +rgbrandf Specifies how much extra red, green, and/or blue there may be for particles. The initial colour will be multiplied by this amount before addition. Each componant is separately randomized. EG, red might add nothing, while the full green is added, and only half the blue. Fully opaque is 255 or above. + The rgbrandf form takes values scaled by 1 instead of 255. redrandsync greenrandsync @@ -192,6 +238,7 @@ greendelta bluedelta rgbdelta rgbdelta +rgbdeltaf Specifies how much the red, green, and/or blue values of each particle change over time. The value 255 is the value required to go from opaque to invisible in 1 second. @@ -201,7 +248,8 @@ rgbdeltatime rampmode mode may be one of: none: uses rgb+rand+sync+delta+scale+scaledelta values. - absolute: the ramp overrides all colour+scale values. The effect moves from one absolute ramp index to the next. + nearest(or absolute): the ramp overrides all colour+scale values. The effect moves from one absolute ramp index to the next. + lerp: smoothly interpolates from one value to the next. delta: uses rgb+rand+sync+scale, but not delta values. All delta values come from the colour ramp instead. if not none, the ramp index used is based upon the particle's age, its lifetime, and how many ramp elements there are. @@ -221,8 +269,20 @@ stains The stained colour is based upon the colour of the particle upon impact. blend - mode may be one of: add, subtract, blendcolour/blendcolor, blend - if the texture used is actually a shader, this is ignored. + If the texture used is actually a shader then that shader's blend mode will take precidence. + As a general rule you should try to use only the premul blend modes (as well as atlasing). + mode may be one of: + adda: + addc: + subtract: + invmoda: + invmodc: + blendcolour: + blendalpha: + premul_subtract: + premul_add: + premul_blend: + rtsmoke: spawnmode [arg1] [arg2] This affects how particles are positioned when they first spawn, and their initial velocities. @@ -254,6 +314,7 @@ spawnparam2 obsolete. see spawnmode. up + obsoleted by orgbias. the particle's starting origin is moved upwards by this amount (worldspace). type @@ -265,12 +326,17 @@ type texturedspark: textured particles are aligned along their direction of movement, their length depending upon their speed, width equal to their scale. cdecal/decal: particles are spawned only upon bsp geometry. They are clipped by it. udecal: unclipped decal. exact semantics are subject to change. - *default*: Particles are regular, rotating, 2d images. + normal/*default*: Particles are regular, rotating, 2d images. isbeam obsolete. please use 'type beam' instead. +clippeddecal [match] + implies 'type decal'. + The two extra args allow you to spawn these decals ONLY on surfaces with matching surfaceflags. + Separation of mask+match allows you to create many descrete surface types instead of being limited to 32 bits/types. + spawntime spawnchance @@ -316,20 +382,69 @@ sound [pitch] [delay] When the effect is first spawned, the named sound will play with the given volume and attenuation at the center point. This may not work with all spawn methods, as it will ignore any count scales. lightradius -lightradiusfade -lightrgb -lightrgbfade -lighttime Spawns a dlight when the effect is spawned. dlight is removed when radius drops to 0 or the age is exceeded. at this time it is not possible to override the corona/specular levels. +lightradiusfade + how fast the light radius shrinks per second + +lightrgb + dlight rgb colours. 1=white. higher values can over-saturate. + +lightrgbfade + how fast lightrgb changes over time. + +lighttime + Specifies the maximum lifetime of your dlight. + lightcubemap value 0 means no cubemap. otherwise with eg cubemap 5, uses image files cubemaps/5ft.tga, cubemaps/5bk.tga, etc. fixme: at the current time, the cubemap is world-aligned and cannot rotate. -model +lightscales + multipliers for the rtlight's various types of lighting + +lightshadows + 0 or 1, specifies whether the rtlight will cast shadows or not. Its faster if it doesn't. + +lightcorona + +model [options] + options are: + frame= + framestart= + framecount= + frameend= + frames= + framerate= + skin= + alpha= + scalemin= + scalemax= + trail= + orient + additive + transparent + fullbright + shadow + noshadow spawns sprites or models that fly away with random angles and run through some frame sequence. handy for simple gib effects. -spawnstain \ No newline at end of file +sound [options] + options are: + vol= + attn= + pitch= + delay= + weight= + Plays a sound when the effect is spawned. Only ONE sound will be used, picked randomly from the included sounds according to their weights. + +spawnstain + +rainfrequency + Specifies the interval between spawning new particle puffs on surfaces. + +flurry + These particles will periodically all change their direction, in a vauge attempt to approximate snow flurries.