diff --git a/CMakeLists.txt b/CMakeLists.txt index a991a50b2..1390aca7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,10 @@ SET(FTE_BUILD_CONFIG ${CMAKE_HOME_DIRECTORY}/engine/common/config_fteqw.h CACHE SET(FTE_LIB_DEFINES ${FTE_LIB_DEFINES};CONFIG_FILE_NAME=${FTE_BUILD_CONFIG}) SET(FTE_USE_SDL false CACHE BOOL "Force the use of SDL instead of using native builds.") +INCLUDE(GNUInstallDirs) +SET(FTE_INSTALL_BINDIR games CACHE STRING "Binary dir to install to.") +SET(FTE_INSTALL_LIBDIR fteqw CACHE STRING "Binary dir to install to.") + IF(NOT WIN32) SET(SYS_LIBS ${SYS_LIBS} m) ELSE() @@ -131,8 +135,12 @@ ELSE() SET(FTE_LIB_DEFINES ${FTE_LIB_DEFINES};NO_OPENGL) ENDIF() -FIND_PACKAGE(JPEG) +SET(FTE_DEP_JPEG true CACHE BOOL "Link against libjpeg.") +IF(FTE_DEP_JPEG) + FIND_PACKAGE(JPEG) +ENDIF() IF(JPEG_FOUND) + INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIRS} ) SET(FTE_LIB_DEFINES ${FTE_LIB_DEFINES};LIBJPEG_STATIC) SET(FTE_LIBS ${FTE_LIBS} ${JPEG_LIBRARIES}) ELSE() @@ -142,6 +150,7 @@ ENDIF() FIND_PACKAGE(PNG) IF(PNG_FOUND) + INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIRS} ) SET(FTE_LIB_DEFINES ${FTE_LIB_DEFINES};LIBPNG_STATIC) SET(FTE_LIBS ${FTE_LIBS} ${PNG_LIBRARIES}) ELSE() @@ -169,6 +178,7 @@ ENDIF() FIND_LIBRARY(VORBISFILE_LIBRARY NAMES vorbisfile) IF(NOT VORBISFILE_LIBRARY) + INCLUDE_DIRECTORIES( ${VORBISFILE_INCLUDE_DIRS} ) MESSAGE(WARNING "libvorbisfile library NOT available. Who listens to the bgm anyway?") SET(FTE_LIB_DEFINES ${FTE_LIB_DEFINES};NO_OGG) ENDIF() @@ -215,11 +225,13 @@ IF(CMAKE_BUILD_TYPE MATCHES "Debug") ENDIF() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FILE_OFFSET_BITS=64") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DFTE_LIBRARY_PATH=${CMAKE_INSTALL_FULL_LIBDIR}/${FTE_INSTALL_LIBDIR}") + FUNCTION(EMBED_PLUGIN_META PLUGNAME PLUGTITLE PLUGDESC) SET_TARGET_PROPERTIES(plug_${PLUGNAME} PROPERTIES OUTPUT_NAME "${PLUGNAME}") SET_TARGET_PROPERTIES(plug_${PLUGNAME} PROPERTIES PREFIX "fteplug_") SET_TARGET_PROPERTIES(plug_${PLUGNAME} PROPERTIES LINK_FLAGS "-Wl,--no-undefined") - SET(INSTALLTARGS ${INSTALLTARGS} "plug_${PLUGNAME}") + SET(INSTALLTARGS ${INSTALLTARGS} "plug_${PLUGNAME}" PARENT_SCOPE) #sadly we need to use a temp zip file, because otherwise zip insists on using zip64 extensions which breaks zip -A (as well as any attempts to read any files). ADD_CUSTOM_COMMAND( TARGET plug_${PLUGNAME} POST_BUILD @@ -1020,6 +1032,7 @@ ELSE() engine/common/cvar.c engine/common/cmd.c engine/common/sha1.c #for websockets + engine/common/sha2.c #for fingerprints engine/http/httpclient.c #for the pipe stuff engine/common/log.c engine/common/fs.c @@ -1157,8 +1170,7 @@ SET(FTE_PLUG_QI true CACHE BOOL "Compile Quake-Injnector plugin.") IF(FTE_PLUG_QI) ADD_LIBRARY(plug_qi MODULE plugins/plugin.c - plugins/qi/qi.c - plugins/emailnot/md5.c + plugins/qi/qi.c plugins/jabber/xml.c ) SET_TARGET_PROPERTIES(plug_qi PROPERTIES COMPILE_DEFINITIONS "FTEPLUGIN;${FTE_LIB_DEFINES}") @@ -1241,11 +1253,7 @@ IF(FTE_PLUG_NAMEMAKER) plugins/namemaker/namemaker.c ) SET_TARGET_PROPERTIES(plug_namemaker PROPERTIES COMPILE_DEFINITIONS "${FTE_LIB_DEFINES}") - SET_TARGET_PROPERTIES(plug_namemaker PROPERTIES OUTPUT_NAME "namemaker") - SET_TARGET_PROPERTIES(plug_namemaker PROPERTIES PREFIX "fteplug_") - SET_TARGET_PROPERTIES(plug_namemaker PROPERTIES LINK_FLAGS "-Wl,--no-undefined") TARGET_LINK_LIBRARIES(plug_namemaker ${SYS_LIBS}) - SET(INSTALLTARGS ${INSTALLTARGS} plug_namemaker) EMBED_PLUGIN_META(namemaker "Name Maker Plugin" "Provides a lame UI for selecting arbitrary non-ascii glyphs as part of your nickname.") ENDIF() @@ -1525,13 +1533,16 @@ IF(FTE_PLUG_XMPP) ENDIF() ENDIF() #android -INCLUDE(GNUInstallDirs) -SET(FTE_INSTALL_BINDIR games CACHE STRING "Binary dir to install to.") INSTALL(TARGETS ${INSTALLTARGS} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/${FTE_INSTALL_BINDIR}" - LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/${FTE_INSTALL_LIBDIR}" ) +INSTALL(FILES + fteqw.desktop + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications/") + + SET(FTE_MENU_SYS true CACHE BOOL "Compile System Menu.") IF(FTE_MENU_SYS) ADD_CUSTOM_TARGET(menusys ALL diff --git a/engine/Makefile b/engine/Makefile index 8b0c09d78..ab94569d8 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -84,10 +84,10 @@ MAKE:=$(MAKE) --no-print-directory SVNREVISION="$(SVNREVISION)" SVN_VERSION="$(S #update these to download+build a different version. this assumes that the url+subdirs etc contain a consistant version everywhere. JPEGVER=9c ZLIBVER=1.2.13 -PNGVER=1.6.37 +PNGVER=1.6.39 OGGVER=1.3.4 VORBISVER=1.3.6 -SDL2VER=2.0.10 +SDL2VER=2.26.4 SCINTILLAVER=373 OPUSVER=1.3.1 SPEEXVER=1.2.0 @@ -1044,6 +1044,14 @@ endif ifeq (1,$(LINK_ZLIB)) CLIENTLIBFLAGS+=-DZLIB_STATIC CLIENTLDDEPS+=-lz + + #and deflate64, because why not. + ifneq ("$(wildcard $(ARCHLIBS)/infback9.h)","") + CLIENTLIBFLAGS+=-DZLIB_DEFLATE64 + CLIENTLDDEPS+=-lz9 + QCC_CFLAGS+=-DZLIB_DEFLATE64 + QCC_LDFLAGS+=-lz9 + endif endif ifeq (1,$(LINK_ODE)) ALL_CFLAGS+=$(shell $(PKGCONFIG) ode --cflags --silence-errors) -DODE_STATIC @@ -2124,7 +2132,7 @@ m-profile: _qcc-tmp: $(REQDIR) - @$(MAKE) $(TYPE) EXE_NAME="$(EXE_NAME)$(EXEPOSTFIX)" PRECOMPHEADERS="" OUT_DIR="$(OUT_DIR)" WCFLAGS="$(CLIENT_ONLY_CFLAGS) $(WCFLAGS)" LDFLAGS="$(LDFLAGS) $(QCC_LDFLAGS)" OBJS="QCC_OBJS SOBJS" + @$(MAKE) $(TYPE) EXE_NAME="$(EXE_NAME)$(EXEPOSTFIX)" PRECOMPHEADERS="" OUT_DIR="$(OUT_DIR)" WCFLAGS="$(QCC_CFLAGS) $(WCFLAGS)" LDFLAGS="$(LDFLAGS) $(QCC_LDFLAGS)" OBJS="QCC_OBJS SOBJS" qcc-rel: @$(MAKE) _qcc-tmp TYPE=_out-rel REQDIR=reldir EXE_NAME="../fteqcc$(BITS)" OUT_DIR="$(RELEASE_DIR)/$(NCDIRPREFIX)$(QCC_DIR)" SOBJS="qcctui.o packager.o $(if $(findstring win,$(FTE_TARGET)),fteqcc.o)" qccgui-rel: @@ -2429,6 +2437,13 @@ libs-$(ARCH)/libz.a libs-$(ARCH)/libz.pc: test -f zlib-$(ZLIBVER).tar.gz || wget http://zlib.net/zlib-$(ZLIBVER).tar.gz -test -f libs-$(ARCH)/libz.a || (mkdir -p libs-$(ARCH) && cd libs-$(ARCH) && tar -xvzf ../zlib-$(ZLIBVER).tar.gz && cd zlib-$(ZLIBVER) && $(TOOLOVERRIDES) ./configure --static && $(TOOLOVERRIDES) $(MAKE) libz.a CC="$(CC) $(W32_CFLAGS) -fPIC" && cp libz.a ../ && $(TOOLOVERRIDES) $(AR) -s ../libz.a && cp zlib.h zconf.h zutil.h zlib.pc ../ ) endif +libs-$(ARCH)/libz9.a: libs-$(ARCH)/libz.a + (cd libs-$(ARCH)/zlib-$(ZLIBVER) && \ + $(CC) -o contrib/infback9/infback9.o -c contrib/infback9/infback9.c -I. && \ + $(CC) -o contrib/infback9/inftree9.o -c contrib/infback9/inftree9.c -I. && \ + cp contrib/infback9/infback9.h .. && \ + $(AR) rcs ../libz9.a contrib/infback9/infback9.o contrib/infback9/inftree9.o) + libs-$(ARCH)/libpng.a libs-$(ARCH)/libpng.pc: libs-$(ARCH)/libz.a libs-$(ARCH)/libz.pc test -f libpng-$(PNGVER).tar.gz || wget http://prdownloads.sourceforge.net/libpng/libpng-$(PNGVER).tar.gz?download -O libpng-$(PNGVER).tar.gz @@ -2465,7 +2480,7 @@ libs-$(ARCH)/libBulletDynamics.a: ifeq ($(FTE_TARGET),web) makelibs: libs-$(ARCH)/libz.a $(MAKELIBS) else -makelibs: libs-$(ARCH)/libjpeg.a libs-$(ARCH)/libz.a libs-$(ARCH)/libpng.a libs-$(ARCH)/libogg.a libs-$(ARCH)/libvorbis.a libs-$(ARCH)/libopus.a libs-$(ARCH)/libspeex.a libs-$(ARCH)/libspeexdsp.a libs-$(ARCH)/libfreetype.a $(MAKELIBS) +makelibs: libs-$(ARCH)/libjpeg.a libs-$(ARCH)/libz9.a libs-$(ARCH)/libz.a libs-$(ARCH)/libpng.a libs-$(ARCH)/libogg.a libs-$(ARCH)/libvorbis.a libs-$(ARCH)/libopus.a libs-$(ARCH)/libspeex.a libs-$(ARCH)/libspeexdsp.a libs-$(ARCH)/libfreetype.a $(MAKELIBS) endif HTTP_OBJECTS=http/httpserver.c http/iwebiface.c common/fs_stdio.c http/ftpserver.c diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 470026916..6f8887151 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -3009,9 +3009,9 @@ fail: else if (!strcmp(auth, "SHA1")) hashfunc = &hash_sha1; else if (!strcmp(auth, "SHA2_256")) - hashfunc = &hash_sha256; + hashfunc = &hash_sha2_256; else if (!strcmp(auth, "SHA2_512")) - hashfunc = &hash_sha512; + hashfunc = &hash_sha2_512; else if (*auth) Con_Printf("Server requires unsupported auth method: %s\n", auth); diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 6844a5db9..92bc63631 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -3231,7 +3231,7 @@ void CLQ1_AddShadow(entity_t *ent) scenetris_t *t; cl_adddecal_ctx_t ctx; - if (!r_blobshadows || !ent->model || (ent->model->type != mod_alias && ent->model->type != mod_halflife)) + if (!r_blobshadows || !ent->model || (ent->model->type != mod_alias && ent->model->type != mod_halflife) || (ent->flags & RF_NOSHADOW)) return; s = R_RegisterShader("shadowshader", SUF_NONE, @@ -3295,7 +3295,7 @@ void CLQ1_AddShadow(entity_t *ent) } ctx.t = t; - Vector4Set(ctx.rgbavalue, 0, 0, 0, r_blobshadows); + Vector4Set(ctx.rgbavalue, 0, 0, 0, r_blobshadows*((ent->flags & RF_TRANSLUCENT)?ent->shaderRGBAf[3]:1)); Mod_ClipDecal(cl.worldmodel, shadoworg, ctx.axis[0], ctx.axis[1], ctx.axis[2], radius, 0,0, CL_AddDecal_Callback, &ctx); if (!t->numidx) cl_numstris--; diff --git a/engine/client/cl_ignore.c b/engine/client/cl_ignore.c index 46166c805..91d5600a8 100644 --- a/engine/client/cl_ignore.c +++ b/engine/client/cl_ignore.c @@ -460,10 +460,13 @@ static void Ignoreteam_f(void) } if (j == MAX_TEAMIGNORELIST) Con_Printf("You cannot ignore more than %d teams\n", MAX_TEAMIGNORELIST); - Q_strncpyz(ignoreteamlist[j], arg, sizeof(ignoreteamlist[j])); - if (j + 1 < MAX_TEAMIGNORELIST) - ignoreteamlist[j + 1][0] = 0; - Con_Printf("Added team %s to ignore list\n", arg); + else + { + Q_strncpyz(ignoreteamlist[j], arg, sizeof(ignoreteamlist[j])); + if (j + 1 < MAX_TEAMIGNORELIST) + ignoreteamlist[j + 1][0] = 0; + Con_Printf("Added team %s to ignore list\n", arg); + } return; } } diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index 8a9715a3a..e007b7a4e 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -37,7 +37,7 @@ cvar_t cl_c2sdupe = CVARD("cl_c2sdupe", "0", "Send duplicate copies of packets t cvar_t cl_c2spps = CVARD("cl_c2spps", "0", "Reduces outgoing packet rates by dropping up to a third of outgoing packets."); cvar_t cl_c2sImpulseBackup = CVARD("cl_c2sImpulseBackup","3", "Prevents the cl_c2spps setting from dropping redundant packets that contain impulses, in an attempt to keep impulses more reliable."); static cvar_t cl_c2sMaxRedundancy = CVARD("cl_c2sMaxRedundancy","5", "This is the maximum number of input frames to send in each input packet. Values greater than 1 provide redundancy and avoid prediction misses, though you might find cl_c2sdupe provides equivelent result and at lower latency. It is locked at 3 for vanilla quakeworld, and locked at 1 for vanilla netquake."); -cvar_t cl_netfps = CVARD("cl_netfps", "150", "Send up to this many packets to the server per second. The rate used is also limited by the server which usually forces a cap to this setting of 77. Low packet rates can result in extra extrapolation to try to hide the resulting latencies."); +cvar_t cl_netfps = CVARFD("cl_netfps", "150", CVAR_ARCHIVE, "Send up to this many packets to the server per second. The rate used is also limited by the server which usually forces a cap to this setting of 77. Low packet rates can result in extra extrapolation to try to hide the resulting latencies."); cvar_t cl_queueimpulses = CVARD("cl_queueimpulses", "0", "Queues unsent impulses instead of replacing them. This avoids the need for extra wait commands (and the timing issues of such commands), but potentially increases latency and can cause scripts to be desynced with regard to buttons and impulses."); cvar_t cl_smartjump = CVARD("cl_smartjump", "1", "Makes the jump button act as +moveup when in water. This is typically quieter and faster."); cvar_t cl_iDrive = CVARFD("cl_iDrive", "1", CVAR_SEMICHEAT, "Effectively releases movement keys when the opposing key is pressed. This avoids dead-time when both keys are pressed. This can be emulated with various scripts, but that's messy."); @@ -1393,6 +1393,9 @@ void CL_ClampPitch (int pnum, float frametime) VectorAngles(view[0], view[2], pv->viewangles, false); VectorClear(pv->viewanglechange); + //fixme: in_vraim stuff + VectorCopy(pv->viewangles, pv->aimangles); + return; } #if 1 @@ -1450,7 +1453,10 @@ void CL_ClampPitch (int pnum, float frametime) if (!vang[ROLL]) { if (!pv->viewanglechange[PITCH] && !pv->viewanglechange[YAW] && !pv->viewanglechange[ROLL]) + { + VectorCopy(pv->viewangles, pv->aimangles); return; + } } else { @@ -1490,6 +1496,9 @@ void CL_ClampPitch (int pnum, float frametime) pv->viewangles[ROLL] += 360; if (pv->viewangles[PITCH] < -180) pv->viewangles[PITCH] += 360; + + //fixme: in_vraim stuff + VectorCopy(pv->viewangles, pv->aimangles); return; } #endif diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index ad9982214..6fa9dd3dd 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -187,23 +187,20 @@ cvar_t cl_gunanglex = CVAR("cl_gunanglex", "0"); cvar_t cl_gunangley = CVAR("cl_gunangley", "0"); cvar_t cl_gunanglez = CVAR("cl_gunanglez", "0"); -#ifdef HAVE_DTLS -extern cvar_t net_enable_dtls; -#endif cvar_t cl_proxyaddr = CVAR("cl_proxyaddr", ""); 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 = CVARAFD("cl_downloads", "1", /*q3*/"cl_allowDownload", 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."); -cvar_t cl_download_mapsrc = CVARFD("cl_download_mapsrc", "", CVAR_ARCHIVE, "Specifies an http location prefix for map downloads. EG: \"http://example.com/path/quakemaps/\""); +cvar_t cl_downloads = CVARAFD("cl_downloads", "1", /*q3*/"cl_allowDownload", CVAR_NOTFROMSERVER|CVAR_ARCHIVE, "Allows you to block all automatic downloads."); +cvar_t cl_download_csprogs = CVARFD("cl_download_csprogs", "1", CVAR_NOTFROMSERVER|CVAR_ARCHIVE, "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|CVAR_ARCHIVE, "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."); +cvar_t cl_download_mapsrc = CVARFD("cl_download_mapsrc", "", CVAR_ARCHIVE, "Specifies an http location prefix for map downloads. EG: \"http://example.com/path/gamemaps/\""); cvar_t cl_download_packages = CVARFD("cl_download_packages", "1", CVAR_NOTFROMSERVER, "0=Do not download packages simply because the server is using them. 1=Download and load packages as needed (does not affect games which do not use this package). 2=Do download and install permanently (use with caution!)"); -cvar_t requiredownloads = CVARFD("requiredownloads","1", CVAR_ARCHIVE, "0=join the game before downloads have even finished (might be laggy). 1=wait for all downloads to complete before joining."); +cvar_t requiredownloads = CVARAFD("cl_download_wait", "1", /*old*/"requiredownloads", CVAR_ARCHIVE, "0=join the game before downloads have even finished (might be laggy). 1=wait for all downloads to complete before joining."); cvar_t mod_precache = CVARD("mod_precache","1", "Controls when models are loaded.\n0: Load them only when they're actually needed.\n1: Load them upfront.\n2: Lazily load them to shorten load times at the risk of brief stuttering during only the start of the map."); cvar_t cl_muzzleflash = CVAR("cl_muzzleflash", "1"); -cvar_t gl_simpleitems = CVARF("gl_simpleitems", "0", CVAR_ARCHIVE); -cvar_t cl_item_bobbing = CVARF("cl_model_bobbing", "0", CVAR_ARCHIVE); +cvar_t gl_simpleitems = CVARFD("gl_simpleitems", "0", CVAR_ARCHIVE, "Replace models with simpler sprites."); +cvar_t cl_item_bobbing = CVARFD("cl_model_bobbing", "0", CVAR_ARCHIVE, "Makes rotating pickup items bob too."); cvar_t cl_countpendingpl = CVARD("cl_countpendingpl", "0", "If set to 1, packet loss percentages will show packets still in transit as lost, even if they might still be received."); cvar_t cl_standardchat = CVARFD("cl_standardchat", "0", CVAR_ARCHIVE, "Disables auto colour coding in chat messages."); @@ -213,7 +210,7 @@ cvar_t msg_filter_pickups = CVARD("msg_filter_pickups", "0", "Prevents pickup m cvar_t cl_standardmsg = CVARFD("cl_standardmsg", "0", CVAR_ARCHIVE, "Disables auto colour coding in console prints."); cvar_t cl_parsewhitetext = CVARD("cl_parsewhitetext", "1", "When parsing chat messages, enable support for messages like: red{white}red"); -cvar_t cl_dlemptyterminate = CVAR("cl_dlemptyterminate", "1"); +cvar_t cl_dlemptyterminate = CVARD("cl_dlemptyterminate", "1", "Terminate downloads when reciving an empty download packet. This should help work around buggy mvdsv servers."); static void QDECL Cvar_CheckServerInfo(struct cvar_s *var, char *oldvalue) { //values depend upon the serverinfo, so reparse for overrides. @@ -289,15 +286,6 @@ static struct int numadr; int nextadr; netadr_t adr[8]; //addresses that we're trying to transfer to, one entry per dns result, eg both ::1 AND 127.0.0.1 -#ifdef HAVE_DTLS - enum - { //not relevant when given a direct dtls address. - DTLS_DISABLE, - DTLS_TRY, - DTLS_REQUIRE, - DTLS_ACTIVE, - } dtlsupgrade; -#endif int protocol; //nq/qw/q2/q3. guessed based upon server replies int subprotocol; //the monkeys are trying to eat me. struct @@ -320,13 +308,20 @@ static struct enum coninfomode_e { CIM_DEFAULT, //sends both a qw getchallenge and nq connect (also with postfixed getchallenge so modified servers can force getchallenge) - CIM_NQONLY, //disables getchallenge (so fte servers treat us as an nq server). should not be used for dpp7 servers. + CIM_NQONLY, //disables getchallenge (so fte servers treat us as an nq client). should not be used for dpp7 servers. CIM_QEONLY, //forces dtls and uses a different nq netchan version } mode; + enum coninfospec_e + { + CIS_DEFAULT, //default + CIS_JOIN, //force join + CIS_OBSERVE, //force observe + } spec; int defaultport; int tries; //increased each try, every fourth trys nq connect packets. unsigned char guid[64]; //client->server guid (so doesn't change with transfers) -// qbyte fingerprint[5*4]; //sha1 hash of accepted dtls certs + + struct dtlspeercred_s peercred; } connectinfo; qboolean nomaster; @@ -536,10 +531,9 @@ char *CL_GUIDString(netadr_t *adr) { static qbyte buf[2048]; static int buflen; - unsigned int digest[4]; + qbyte digest[DIGEST_MAXSIZE]; char serveraddr[256]; - void *blocks[2]; - int lens[2]; + void *ctx; if (!*cl_sendguid.string && *connectinfo.ext.guidsalt) { @@ -587,14 +581,50 @@ char *CL_GUIDString(netadr_t *adr) } } - blocks[0] = buf;lens[0] = buflen; - blocks[1] = serveraddr;lens[1] = strlen(serveraddr); - Com_BlocksChecksum(2, blocks, lens, (void*)digest); - - Q_snprintfz(connectinfo.guid, sizeof(connectinfo.guid), "%08x%08x%08x%08x", digest[0], digest[1], digest[2], digest[3]); + ctx = alloca(hash_md4.contextsize); + hash_md4.init(ctx); + hash_md4.process(ctx, buf, buflen); + hash_md4.process(ctx, serveraddr, strlen(serveraddr)); + hash_md4.terminate(digest, ctx); + Base16_EncodeBlock(digest, hash_md4.digestsize, connectinfo.guid, sizeof(connectinfo.guid)); return connectinfo.guid; } +static void CL_ConnectAbort(const char *format, ...) +{ //stops trying to connect, doesn't affect the _current_ connection, so usable for transfers. + va_list argptr; + char reason[1024]; + + if (format) + { + va_start (argptr, format); + Q_vsnprintfz (reason, sizeof(reason), format,argptr); + va_end (argptr); + + Cvar_Set(&cl_disconnectreason, reason); + Con_Printf (CON_ERROR"%s\n", reason); + } +#ifdef HAVE_DTLS + while (connectinfo.numadr) + NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[--connectinfo.numadr]); +#endif + connectinfo.numadr = 0; + SCR_EndLoadingPlaque(); + connectinfo.trying = false; + + if (format) + { + //try and force the menu to show again. this should force the disconnectreason to show. + if (!Key_Dest_Has(kdm_console)) + { +#ifdef MENU_DAT + if (!MP_Toggle(1)) +#endif + Menu_Prompt(NULL, NULL, reason, NULL, NULL, "Okay", true); + } + } +} + /* ======================= CL_SendConnectPacket @@ -658,13 +688,29 @@ static void CL_SendConnectPacket (netadr_t *to) t1 = Sys_DoubleTime (); +#ifdef HAVE_DTLS + if (connectinfo.peercred.hash && net_enable_dtls.ival>0) + { + char cert[8192]; + char digest[DIGEST_MAXSIZE]; + int sz = NET_GetConnectionCertificate(cls.sockets, to, QCERT_PEERCERTIFICATE, cert, sizeof(cert)); + if (sz <= 0 || memcmp(connectinfo.peercred.digest, digest, CalcHash(connectinfo.peercred.hash, digest, sizeof(digest), cert, sz))) + { //FIXME: we may have already pinned the bad cert, which may cause issues when reconnecting without FP info later. + if (NET_GetConnectionCertificate(cls.sockets, to, QCERT_ISENCRYPTED, NULL, 0)<0) + CL_ConnectAbort ("Fingerprint specified, but server did not report any certificate\n"); + else + CL_ConnectAbort ("Server certificate does not match specified fingerprint\n"); + return; + } + } +#endif + if (!to) { to = &addr; if (!NET_StringToAdr (cls.servername, PORT_DEFAULTSERVER, to)) { - Con_TPrintf ("CL_SendConnectPacket: Bad server address \"%s\"\n", cls.servername); - connectinfo.trying = false; + CL_ConnectAbort ("CL_SendConnectPacket: Bad server address \"%s\"\n", cls.servername); return; } } @@ -675,8 +721,7 @@ static void CL_SendConnectPacket (netadr_t *to) if (!NET_IsClientLegal(to)) { - Con_TPrintf ("Illegal server address\n"); - connectinfo.trying = false; + CL_ConnectAbort("Illegal server address\n"); return; } @@ -715,10 +760,12 @@ static void CL_SendConnectPacket (netadr_t *to) Q_strncatz(data, va("\\prx\\%s", cls.servername), sizeof(data)); *a = '@'; } + if (connectinfo.spec==CIS_OBSERVE) + Q_strncatz(data, "\\spectator\\1", sizeof(data)); //the info itself { static const char *prioritykeys[] = {"name", "password", "spectator", "lang", "rate", "team", "topcolor", "bottomcolor", "skin", "_", "*", NULL}; - static const char *ignorekeys[] = {"prx", "*z_ext", NULL}; + const char *ignorekeys[] = {"prx", "*z_ext", (connectinfo.spec!=CIS_DEFAULT)?"spectator":NULL, NULL}; InfoBuf_ToString(&cls.userinfo[0], data+strlen(data), sizeof(data)-strlen(data), prioritykeys, ignorekeys, NULL, &cls.userinfosync, &cls.userinfo[0]); } if (connectinfo.protocol == CP_QUAKEWORLD) //zquake extension info. @@ -808,32 +855,18 @@ static void CL_ResolvedServer(void *vctx, void *data, size_t a, size_t b) if (!ctx->found) { - Cvar_Set(&cl_disconnectreason, va("Bad server address \"%s\"", ctx->servername)); - Con_TPrintf ("Bad server address \"%s\"\n", ctx->servername); - connectinfo.trying = false; - SCR_EndLoadingPlaque(); + CL_ConnectAbort("Unable to resolve server address \"%s\"\n", ctx->servername); return; } #ifdef HAVE_DTLS for (i = 0; i < ctx->found; i++) { - if (connectinfo.dtlsupgrade == DTLS_ACTIVE || connectinfo.mode==CIM_QEONLY) + if (net_enable_dtls.ival>=4 || connectinfo.mode==CIM_QEONLY)// || (connectinfo.peercred.hash && net_enable_dtls.ival >= 1)) { //if we've already established a dtls connection, stick with it if (ctx->adr[i].prot == NP_DGRAM) ctx->adr[i].prot = NP_DTLS; } - else if (connectinfo.adr[i].prot == NP_DTLS) - { //dtls connections start out with regular udp, and upgrade to dtls once its established that the server supports it. - //FIXME: remove this block once our new netcode is better established. - connectinfo.dtlsupgrade = DTLS_REQUIRE; - ctx->adr[i].prot = NP_DGRAM; - } - else - { - //hostname didn't specify dtls. upgrade if we're allowed, but don't mandate it. - //connectinfo.dtlsupgrade = DTLS_TRY; - } } #endif @@ -846,11 +879,11 @@ static void CL_ResolvedServer(void *vctx, void *data, size_t a, size_t b) static void CL_ResolveServer(void *vctx, void *data, size_t a, size_t b) { struct resolvectx_s *ctx = vctx; - const char *host = strrchr(cls.servername+1, '@'); + const char *host = strrchr(ctx->servername+1, '@'); if (host) host++; else - host = cls.servername; + host = ctx->servername; ctx->found = NET_StringToAdr2 (host, connectinfo.defaultport, ctx->adr, countof(ctx->adr), NULL); @@ -1109,9 +1142,7 @@ void CL_CheckForResend (void) connectinfo.nextadr = 0; if (!connectinfo.numadr) { - Con_TPrintf ("CL_CheckForResend: Bad server address \"%s\"\n", cls.servername); - connectinfo.trying = false; - SCR_EndLoadingPlaque(); + CL_ConnectAbort("CL_CheckForResend: Bad server address \"%s\"\n", cls.servername); return; } NET_AdrToString(data, sizeof(data), &connectinfo.adr[connectinfo.nextadr]); @@ -1200,6 +1231,9 @@ void CL_CheckForResend (void) else connectinfo.clogged = false; //do the prints and everything. + if (!cls.sockets) //only if its needed... we don't want to keep using a new port unless we have to + NET_InitClient(false); + #ifdef HAVE_DTLS if (connectinfo.numadr>0 && connectinfo.adr[0].prot == NP_DTLS) { //get through the handshake first, instead of waiting for a 5-sec timeout between polls. @@ -1208,17 +1242,16 @@ void CL_CheckForResend (void) case NETERR_CLOGGED: //temporary failure connectinfo.clogged = true; return; + case NETERR_DISCONNECTED: + CL_ConnectAbort("DTLS Certificate Verification Failure\n"); + break; + case NETERR_NOROUTE: //not an error here, just means we need to send a new handshake. + break; default: break; } } - - if (connectinfo.dtlsupgrade != DTLS_ACTIVE) #endif - { - if (!cls.sockets) //only if its needed... we don't want to keep using a new port unless we have to - NET_InitClient(false); - } t1 = Sys_DoubleTime (); if (!connectinfo.istransfer) @@ -1246,10 +1279,7 @@ void CL_CheckForResend (void) to = &connectinfo.adr[connectinfo.nextadr%connectinfo.numadr]; if (!NET_IsClientLegal(to)) { - Cvar_Set(&cl_disconnectreason, va("Illegal server address")); - Con_TPrintf ("Illegal server address\n"); - SCR_EndLoadingPlaque(); - connectinfo.trying = false; + CL_ConnectAbort ("Illegal server address\n"); return; } @@ -1274,12 +1304,9 @@ void CL_CheckForResend (void) connectinfo.clogged = false; if (connectinfo.tries == 0 && connectinfo.nextadr < connectinfo.numadr) - if (!NET_EnsureRoute(cls.sockets, "conn", cls.servername, to)) + if (!NET_EnsureRoute(cls.sockets, "conn", &connectinfo.peercred, to, true)) { - Cvar_Set(&cl_disconnectreason, va("Unable to establish connection to %s\n", cls.servername)); - Con_Printf ("Unable to establish connection to %s\n", cls.servername); - connectinfo.trying = false; - SCR_EndLoadingPlaque(); + CL_ConnectAbort ("Unable to establish connection to %s\n", cls.servername); return; } @@ -1377,32 +1404,92 @@ void CL_CheckForResend (void) } else { - Cvar_Set(&cl_disconnectreason, va("No route to \"%s\", giving up\n", cls.servername)); - Con_TPrintf ("No route to host, giving up\n"); - connectinfo.trying = false; - SCR_EndLoadingPlaque(); + CL_ConnectAbort ("Unable to connect to %s, giving up\n", cls.servername); NET_CloseClient(); } } } -static void CL_BeginServerConnect(const char *host, int port, qboolean noproxy, enum coninfomode_e mode) +static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum coninfomode_e mode, enum coninfospec_e spec) { - if (!strncmp(host, "localhost", 9)) - noproxy = true; //FIXME: resolve the address here or something so that we don't end up using a proxy for lan addresses. + const char *schemeend = strstr(host, "://"); + char *arglist; - if (strstr(host, "://") || !*cl_proxyaddr.string || noproxy) - Q_strncpyz (cls.servername, host, sizeof(cls.servername)); + Q_strncpyz(cls.serverurl, host, sizeof(cls.serverurl)); + + if (schemeend) + { + const char *schemestart = strchr(host, ':'); + int schemelen; + //if its one of our explicit protocols then use the url as-is + const char *netschemes[] = {"udp", "udp4", "udp6", "ipx", "tcp", "tcp4", "tcp6", /*ipx*/"spx", "ws", "wss", "tls", "dtls", "ice", "rtc", "ices", "rtcs", "irc", "udg", "unix"}; + int i; + size_t slen; + + if (!schemestart || schemestart==schemeend) + schemestart = host; + else + schemestart++; + schemelen = schemeend-schemestart; + + Q_strncpyz (cls.servername, "", sizeof(cls.servername)); + for (i = 0; i < countof(netschemes); i++) + { + slen = strlen(netschemes[i]); + if (schemelen == slen && !strncmp(schemestart, netschemes[i], slen)) + { + Q_strncpyz (cls.servername, host, sizeof(cls.servername)); //oh. will probably be okay then + break; + } + } + if (!*cls.servername) + { //not some '/foo' name, not rtc:// either... + char *sl = strchr(schemeend+3, '/'); + if (sl) + { + if (!strncmp(sl, "/observe", 8)) + { + if (spec == CIS_DEFAULT) + spec = CIS_OBSERVE; + else if (spec != CIS_OBSERVE) + Con_Printf("Ignoring 'observe'\n"); + memmove(sl, sl+8, strlen(sl+8)+1); + } + else if (!strncmp(sl, "/join", 5)) + { + if (spec == CIS_DEFAULT) + spec = CIS_JOIN; + else if (spec != CIS_OBSERVE) + Con_Printf("Ignoring 'join'\n"); + memmove(sl, sl+5, strlen(sl+5)+1); + } + else if (!strncmp(sl, "/", 1) && (sl[1] == 0 || sl[1]=='?')) + { + //current spectator mode + memmove(sl, sl+1, strlen(sl+1)+1); + } + } + Q_strncpyz (cls.servername, schemeend+3, sizeof(cls.servername)); //probably some game-specific mess that we don't know + } + } else - Q_snprintfz(cls.servername, sizeof(cls.servername), "%s@%s", host, cl_proxyaddr.string); + { + if (!strncmp(host, "localhost", 9)) + noproxy = true; //FIXME: resolve the address here or something so that we don't end up using a proxy for lan addresses. + + if (strstr(host, "://") || !*cl_proxyaddr.string || noproxy) + Q_strncpyz (cls.servername, host, sizeof(cls.servername)); + else + Q_snprintfz(cls.servername, sizeof(cls.servername), "%s@%s", host, cl_proxyaddr.string); + } + + arglist = strchr(cls.servername, '?'); if (!port) port = cl_defaultport.value; -#ifdef HAVE_DTLS - while (connectinfo.numadr) - NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[--connectinfo.numadr]); -#endif + + CL_ConnectAbort(NULL); memset(&connectinfo, 0, sizeof(connectinfo)); if (*cl_disconnectreason.string) Cvar_Set(&cl_disconnectreason, ""); @@ -1410,15 +1497,38 @@ static void CL_BeginServerConnect(const char *host, int port, qboolean noproxy, connectinfo.defaultport = port; connectinfo.protocol = CP_UNKNOWN; connectinfo.mode = mode; + connectinfo.spec = spec; -#ifdef HAVE_DTLS - if (net_enable_dtls.ival >= 3) - connectinfo.dtlsupgrade = DTLS_REQUIRE; - else if (net_enable_dtls.ival >= 2) - connectinfo.dtlsupgrade = DTLS_TRY; - else - connectinfo.dtlsupgrade = DTLS_DISABLE; -#endif + connectinfo.peercred.name = cls.servername; + if (arglist) + { + *arglist++ = 0; + while (*arglist) + { + char *e = strchr(arglist, '&'); + if (e) + *e=0; + if (!strncasecmp(arglist, "fp=", 3)) + { + size_t l = 8*Base64_DecodeBlock(arglist+3, arglist+strlen(arglist), connectinfo.peercred.digest, sizeof(connectinfo.peercred.digest)); + if (l <= 160) + connectinfo.peercred.hash = &hash_sha1; + else if (l <= 256) + connectinfo.peercred.hash = &hash_sha2_256; + else if (l <= 512) + connectinfo.peercred.hash = &hash_sha2_512; + else + connectinfo.peercred.hash = NULL; + } + else + Con_Printf(CON_WARNING"uri arg not known: \"%s\"\n", arglist); + + if (e) + arglist=e+1; + else + break; + } + } SCR_SetLoadingStage(LS_CONNECTION); CL_CheckForResend(); @@ -1434,9 +1544,11 @@ void CL_BeginServerReconnect(void) } #endif #ifdef HAVE_DTLS - if (connectinfo.numadr>0) - NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[0]); - connectinfo.dtlsupgrade = 0; + { + int i; + for (i = 0; i < connectinfo.numadr; i++) + NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[i]); + } #endif #ifdef SUPPORT_ICE while (connectinfo.numadr) //remove any ICE addresses. probably we'll end up with no addresses left leaving us free to re-resolve giving us the original(ish) rtc connection. @@ -1474,11 +1586,11 @@ void CL_Transfer_f(void) return; } + CL_ConnectAbort(NULL); server = Cmd_Argv (1); if (!*server) { //if they didn't specify a server, abort any active transfer/connection. - connectinfo.trying = false; return; } @@ -1529,7 +1641,7 @@ void CL_Connect_f (void) #endif CL_Disconnect_f (); - CL_BeginServerConnect(server, 0, false, CIM_DEFAULT); + CL_BeginServerConnect(server, 0, false, CIM_DEFAULT, CIS_DEFAULT); } #if defined(CL_MASTER) && defined(HAVE_PACKET) static void CL_ConnectBestRoute_f (void) @@ -1564,7 +1676,7 @@ static void CL_ConnectBestRoute_f (void) else #endif CL_Disconnect_f (); - CL_BeginServerConnect(server, 0, true, CIM_DEFAULT); + CL_BeginServerConnect(server, 0, true, CIM_DEFAULT, CIS_DEFAULT); } #endif @@ -1591,9 +1703,7 @@ static void CL_Join_f (void) CL_Disconnect_f (); - Cvar_Set(&spectator, "0"); - - CL_BeginServerConnect(server, 0, false, CIM_DEFAULT); + CL_BeginServerConnect(server, 0, false, CIM_DEFAULT, CIS_JOIN); } void CL_Observe_f (void) @@ -1621,7 +1731,7 @@ void CL_Observe_f (void) Cvar_Set(&spectator, "1"); - CL_BeginServerConnect(server, 0, false, CIM_DEFAULT); + CL_BeginServerConnect(server, 0, false, CIM_DEFAULT, CIS_OBSERVE); } #ifdef NQPROT @@ -1646,7 +1756,7 @@ void CLNQ_Connect_f (void) CL_Disconnect_f (); - CL_BeginServerConnect(server, 26000, true, mode); + CL_BeginServerConnect(server, 26000, true, mode, CIS_DEFAULT/*doesn't really do spec/join stuff, but if the server asks for our info later...*/); } #endif @@ -2242,9 +2352,7 @@ void CL_Disconnect_f (void) #endif CL_Disconnect (NULL); - - connectinfo.trying = false; - + CL_ConnectAbort(NULL); NET_CloseClient(); (void)CSQC_UnconnectedInit(); @@ -2570,6 +2678,7 @@ void CL_CheckServerInfo(void) cl.bunnyspeedcap = Q_atof(InfoBuf_ValueForKey(&cl.serverinfo, "pm_bunnyspeedcap")); movevars.slidefix = (Q_atof(InfoBuf_ValueForKey(&cl.serverinfo, "pm_slidefix")) != 0); movevars.slidyslopes = (Q_atof(InfoBuf_ValueForKey(&cl.serverinfo, "pm_slidyslopes")) != 0); + movevars.bunnyfriction = (Q_atof(InfoBuf_ValueForKey(&cl.serverinfo, "pm_bunnyfriction")) != 0); movevars.airstep = (Q_atof(InfoBuf_ValueForKey(&cl.serverinfo, "pm_airstep")) != 0); movevars.pground = (Q_atof(InfoBuf_ValueForKey(&cl.serverinfo, "pm_pground")) != 0); movevars.stepdown = (Q_atof(InfoBuf_ValueForKey(&cl.serverinfo, "pm_stepdown")) != 0); @@ -2929,6 +3038,7 @@ void CL_Packet_f (void) int i, l; char *in, *out; netadr_t adr; + struct dtlspeercred_s cred = {Cmd_Argv(1)}; if (Cmd_Argc() != 3) { @@ -3018,7 +3128,7 @@ void CL_Packet_f (void) if (!cls.sockets) NET_InitClient(false); - if (!NET_EnsureRoute(cls.sockets, "packet", Cmd_Argv(1), &adr)) + if (!NET_EnsureRoute(cls.sockets, "packet", &cred, &adr, true)) return; NET_SendPacket (cls.sockets, out-send, send, &adr); @@ -3339,7 +3449,8 @@ void CL_ConnectionlessPacket (void) { if (CL_IsPendingServerAddress(&net_from)) { - if (!NET_EnsureRoute(cls.sockets, "redir", cls.servername, &adr)) + struct dtlspeercred_s cred = {cls.servername}; //FIXME + if (!NET_EnsureRoute(cls.sockets, "redir", &cred, &adr, true)) Con_Printf (CON_ERROR"Unable to redirect to %s\n", data); else { @@ -3357,29 +3468,20 @@ void CL_ConnectionlessPacket (void) else if (!strcmp(s, "reject")) { //generic rejection. stop trying. char *data = MSG_ReadStringLine(); - Con_Printf ("reject\n%s\n", data); + Con_Printf ("reject\n"); if (CL_IsPendingServerAddress(&net_from)) - { - Cvar_Set(&cl_disconnectreason, va("%s\n", data)); - connectinfo.trying = false; - } + CL_ConnectAbort("%s\n", data); return; } else if (!strcmp(s, "badname")) { //rejected purely because of player name if (CL_IsPendingServerAddress(&net_from)) - { - Cvar_Set(&cl_disconnectreason, va("bad player name\n")); - connectinfo.trying = false; - } + CL_ConnectAbort("bad player name\n"); } else if (!strcmp(s, "badaccount")) { //rejected because username or password is wrong if (CL_IsPendingServerAddress(&net_from)) - { - Cvar_Set(&cl_disconnectreason, va("invalid username or password\n")); - connectinfo.trying = false; - } + CL_ConnectAbort("invalid username or password\n"); } Con_Printf ("f%s\n", s); @@ -3605,40 +3707,50 @@ void CL_ConnectionlessPacket (void) } #ifdef HAVE_DTLS - if (candtls && net_from.prot == NP_DGRAM && (connectinfo.dtlsupgrade || candtls > 1) && !NET_IsEncrypted(&net_from)) + if ((candtls && net_enable_dtls.ival) && net_from.prot == NP_DGRAM && (net_enable_dtls.ival>1 || candtls > 1) && !NET_IsEncrypted(&net_from)) { - //c2s getchallenge - //s2c c%u\0DTLS=$candtls + //c2s getchallenge //<> - //c2s dtlsconnect %u - //s2c dtlsopened + //c2s dtlsconnect %u [REALTARGET] + //s2c dtlsopened //c2s DTLS(getchallenge) //DTLS(etc) - //NOTE: the dtlsconnect/dtlsopened parts are redundant and the non-dtls parts are entirely optional (and should be skipped the client requries/knows the server supports dtls) + //NOTE: the dtlsconnect/dtlsopened parts are redundant and the non-dtls parts are now entirely optional (and should be skipped if the client requries/knows the server supports dtls) //the challenge response includes server capabilities, so we still need the getchallenge/response part of the handshake despite dtls making the actual challenge part redundant. //getchallenge has to be done twice, with the outer one only reporting whether dtls can/should be used. //this means the actual connect packet is already over dtls, which protects the user's userinfo. //FIXME: do rcon via dtls too, but requires tracking pending rcon packets until the handshake completes. - //server says it can do dtls, but will still need to ask it to allocate extra resources for us. + //server says it can do dtls, but will still need to ask it to allocate extra resources for us (I hadn't gotten dtls cookies working properly at that point). - char *pkt; - //qwfwd proxy routing - char *at; - if ((at = strrchr(cls.servername, '@'))) + if (net_enable_dtls.ival>0) { - *at = 0; - pkt = va("%c%c%c%c""dtlsconnect %i %s", 255, 255, 255, 255, connectinfo.challenge, cls.servername); - *at = '@'; + char *pkt; + //qwfwd proxy routing. it doesn't support it yet, but hey, if its willing to forward the dtls packets its all good. + char *at; + if ((at = strrchr(cls.servername, '@'))) + { + *at = 0; + pkt = va("%c%c%c%c""dtlsconnect %i %s", 255, 255, 255, 255, connectinfo.challenge, cls.servername); + *at = '@'; + } + else + pkt = va("%c%c%c%c""dtlsconnect %i", 255, 255, 255, 255, connectinfo.challenge); + NET_SendPacket (cls.sockets, strlen(pkt), pkt, &net_from); + return; + } + else if (candtls >= 3) + { + Cvar_Set(&cl_disconnectreason, va("DTLS is disabled, but server requires it. not connecting\n")); + connectinfo.trying = false; + Con_Printf("DTLS is disabled, but server requires it. Set ^[/net_enable_dtls 1^] before connecting again.\n"); + return; } - else - pkt = va("%c%c%c%c""dtlsconnect %i", 255, 255, 255, 255, connectinfo.challenge); - NET_SendPacket (cls.sockets, strlen(pkt), pkt, &net_from); - return; } - if (connectinfo.dtlsupgrade == DTLS_REQUIRE && !NET_IsEncrypted(&net_from)) + if (net_enable_dtls.ival>=3 && !NET_IsEncrypted(&net_from)) { Cvar_Set(&cl_disconnectreason, va("Server does not support/allow dtls. not connecting\n")); connectinfo.trying = false; @@ -3801,10 +3913,9 @@ void CL_ConnectionlessPacket (void) return; memset(&cred, 0, sizeof(cred)); - cred.peer.name = cls.servername; - if (NET_DTLS_Create(cls.sockets, &net_from, &cred)) + cred.peer = connectinfo.peercred; + if (NET_DTLS_Create(cls.sockets, &net_from, &cred, true)) { - connectinfo.dtlsupgrade = DTLS_ACTIVE; connectinfo.numadr = 1; //fixate on this resolved address. connectinfo.adr[0] = net_from; connectinfo.adr[0].prot = NP_DTLS; @@ -3812,11 +3923,7 @@ void CL_ConnectionlessPacket (void) connectinfo.time = 0; //send a new challenge NOW. } else - { - if (connectinfo.dtlsupgrade == DTLS_TRY) - connectinfo.dtlsupgrade = DTLS_DISABLE; - Con_Printf ("unable to establish dtls route\n"); - } + CL_ConnectAbort("Unable to initialise dtls driver. You may need to adjust tls_provider or disable dtls with ^[/net_enable_dtls 0^]\n"); //this is a local issue, and not a result on remote packets. #else Con_Printf ("dtlsopened (unsupported)\n"); #endif @@ -3853,15 +3960,6 @@ client_connect: //fixme: make function Con_TPrintf ("ignoring connection\n"); return; } - if (net_from.type != NA_LOOPBACK) - { - Con_TPrintf (S_COLOR_GRAY"connection\n"); - -#ifdef HAVE_SERVER - if (sv.state && sv.state != ss_clustermode) - SV_UnspawnServer(); -#endif - } if (cls.state >= ca_connected) { @@ -3879,6 +3977,15 @@ client_connect: //fixme: make function return; } } + if (net_from.type != NA_LOOPBACK) + { +// Con_TPrintf (S_COLOR_GRAY"connection\n"); + +#ifdef HAVE_SERVER + if (sv.state && sv.state != ss_clustermode) + SV_UnspawnServer(); +#endif + } connectinfo.trying = false; cl.splitclients = 0; cls.protocol = connectinfo.protocol; @@ -4111,7 +4218,7 @@ void CLNQ_ConnectionlessPacket(void) else { //send a dummy packet. - //this makes our local nat think we initialised the conversation, so that we can receive the. + //this makes our local firewall think we initialised the conversation, so that we can receive their packets. however this only works if our nat uses the same public port for private ports. Netchan_Transmit(&cls.netchan, 1, "\x01", 2500); } return; @@ -4294,10 +4401,6 @@ void CL_ReadPackets (void) else NET_ReadPackets(cls.sockets); -#ifdef HAVE_DTLS - NET_DTLS_Timeouts(cls.sockets); -#endif - // // check timeout // @@ -5421,9 +5524,9 @@ NORETURN void VARGS Host_EndGame (const char *message, ...) SCR_EndLoadingPlaque(); CL_Disconnect (string); + CL_ConnectAbort(NULL); SV_UnspawnServer(); - connectinfo.trying = false; Cvar_Set(&cl_shownet, "0"); @@ -6214,7 +6317,7 @@ qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file) // "quake2:rtc://broker:port/game" // "qw://[stream@]host[:port]/COMMAND" join, spectate, qtvplay //we'll chop off any non-auth prefix, its just so we can handle multiple protocols via a single uri scheme. - char *t, *cmd; + char *t, *cmd, *args; const char *url; char buffer[8192]; const char *schemestart = strchr(fname, ':'); @@ -6260,6 +6363,15 @@ qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file) t[urilen] = 0; url = t+schemelen; + *buffer = 0; + for (args = t+schemelen; *args; args++) + { + if (*args == '?') + { + *args++ = 0; + break; + } + } for (cmd = t+schemelen; *cmd; cmd++) { if (*cmd == '/') diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index ef2fad995..2a6176db6 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -35,6 +35,7 @@ static char *CLNQ_ParseProQuakeMessage (char *s); #endif static void DLC_Poll(qdownload_t *dl); static void CL_ProcessUserInfo (int slot, player_info_t *player); +static void Con_HexDump(qbyte *packet, size_t len, size_t badoffset); #ifdef NQPROT char *cl_dp_packagenames; @@ -978,10 +979,9 @@ qboolean CL_CheckOrEnqueDownloadFile (const char *filename, const char *localnam if (flags & DLLF_ALLOWWEB) { - extern cvar_t sv_dlURL; const char *dlURL = InfoBuf_ValueForKey(&cl.serverinfo, "sv_dlURL"); if (!*dlURL) - dlURL = sv_dlURL.string; + dlURL = fs_dlURL.string; flags &= ~(DLLF_TRYWEB|DLLF_ALLOWWEB); if (*dlURL && (flags & DLLF_NONGAME) && !strncmp(filename, "package/", 8)) { //filename is something like: package/GAMEDIR/foo.pk3 @@ -1000,10 +1000,10 @@ qboolean CL_CheckOrEnqueDownloadFile (const char *filename, const char *localnam { char base[MAX_QPATH]; COM_FileBase(filename, base, sizeof(base)); -#ifndef FTE_TARGET_WEB - if (strncmp(cl_download_mapsrc.string, "http://", 7) && !strncmp(cl_download_mapsrc.string, "https://", 8)) +#ifndef FTE_TARGET_WEB //don't care about prefixes in the web build, for site-relative uris. + if (strncmp(cl_download_mapsrc.string, "http://", 7) && strncmp(cl_download_mapsrc.string, "https://", 8)) { - Con_Printf("%s: Scheme not specified.\n", cl_download_mapsrc.name); + Con_Printf("%s: Scheme not specified, assuming https.\n", cl_download_mapsrc.name); filename = va("https://%s/%s", cl_download_mapsrc.string, filename+5); } else @@ -1487,6 +1487,7 @@ static int CL_LoadModels(int stage, qboolean dontactuallyload) SCR_SetLoadingFile("external textures"); if (cl.worldmodel && cl.worldmodel->loadstate == MLS_LOADING) COM_WorkerPartialSync(cl.worldmodel, &cl.worldmodel->loadstate, MLS_LOADING); + CL_CheckServerInfo(); //some serverinfo rules can change with map type, so make sure they're updated now we're sure we know it properly. if (cl.worldmodel && cl.worldmodel->loadstate == MLS_LOADED) Mod_NowLoadExternal(cl.worldmodel); @@ -1498,6 +1499,7 @@ static int CL_LoadModels(int stage, qboolean dontactuallyload) if (atstage()) { SCR_SetLoadingFile("newmap"); + // if (!cl.worldmodel || cl.worldmodel->type == mod_dummy) // Host_EndGame("No worldmodel was loaded\n"); Surf_NewMap (cl.worldmodel); @@ -2238,6 +2240,7 @@ static void DL_Completed(qdownload_t *dl, qofs_t start, qofs_t end) static float chunkrate; +static int CL_CountQueuedDownloads(void); static void CL_ParseChunkedDownload(qdownload_t *dl) { qbyte *svname; @@ -2261,7 +2264,41 @@ static void CL_ParseChunkedDownload(qdownload_t *dl) svname = MSG_ReadString(); if (cls.demoplayback) - return; + { //downloading in demos is allowed ONLY for csprogs.dat + extern cvar_t cl_downloads, cl_download_csprogs; + if (!cls.download && !dl && + !strcmp(svname, "csprogs.dat") && filesize && filesize == strtoul(InfoBuf_ValueForKey(&cl.serverinfo, "*csprogssize"), NULL, 0) && + cl_downloads.ival && cl_download_csprogs.ival) + { + //FIXME: should probably save this to memory instead of bloating it on disk. + dl = Z_Malloc(sizeof(*dl)); + + Q_strncpyz(dl->remotename, svname, sizeof(dl->remotename)); + Q_strncpyz(dl->localname, va("csprogsvers/%x.dat", (unsigned int)strtoul(InfoBuf_ValueForKey(&cl.serverinfo, "*csprogs"), NULL, 0)), sizeof(dl->localname)); + + // download to a temp name, and only rename + // to the real name when done, so if interrupted + // a runt file wont be left + COM_StripExtension (dl->localname, dl->tempname, sizeof(dl->tempname)-5); + Q_strncatz (dl->tempname, ".tmp", sizeof(dl->tempname)); + + dl->method = DL_QWPENDING; + dl->percent = 0; + dl->sizeunknown = true; + dl->flags = DLLF_OVERWRITE; + + if (COM_FCheckExists(dl->localname)) + { + Con_DPrintf("Demo embeds redundant %s\n", dl->localname); + Z_Free(dl); + return; + } + cls.download = dl; + Con_Printf("Saving recorded file %s (%lu bytes)\n", dl->localname, (unsigned long)filesize); + } + else + return; + } if (!*svname) { @@ -2365,6 +2402,7 @@ static void CL_ParseChunkedDownload(qdownload_t *dl) dl->method = DL_QWCHUNKS; dl->percent = 0; dl->size = filesize; + dl->sizeunknown = false; dl->starttime = Sys_DoubleTime(); @@ -2389,7 +2427,8 @@ static void CL_ParseChunkedDownload(qdownload_t *dl) if (!dl) { - Con_Printf("ignoring download data packet\n"); + if (!cls.demoplayback) //mute it in demos. + Con_Printf("ignoring download data packet\n"); return; } @@ -2399,11 +2438,6 @@ static void CL_ParseChunkedDownload(qdownload_t *dl) if (!dl->file) return; - if (cls.demoplayback) - { //err, yeah, when playing demos we don't actually pay any attention to this. - return; - } - VFS_SEEK(dl->file, chunknum*DLBLOCKSIZE); if (dl->size - chunknum*DLBLOCKSIZE < DLBLOCKSIZE) //final block is actually meant to be smaller than we recieve. VFS_WRITE(dl->file, data, dl->size - chunknum*DLBLOCKSIZE); @@ -2415,6 +2449,9 @@ static void CL_ParseChunkedDownload(qdownload_t *dl) dl->percent = dl->completedbytes/(float)dl->size*100; chunkrate += 1; + + if (dl->completedbytes == dl->size) + CL_DownloadFinished(dl); } static int CL_CountQueuedDownloads(void) @@ -3706,6 +3743,7 @@ static void CLQ2_ParseServerData (void) Cvar_ForceCallback(Cvar_FindVar("r_particlesdesc")); Surf_PreNewMap(); + CL_CheckServerInfo(); } #endif @@ -3738,7 +3776,7 @@ void CL_ParseEstablished(void) else security = "^["S_COLOR_RED"plain-text\\tip\\"CON_WARNING"Do not type passwords as they can potentially be seen by network sniffers^]"; - Con_TPrintf ("Connected to ^["S_COLOR_BLUE"%s\\type\\connect %s^] (%s).\n", cls.servername, cls.servername, security); + Con_TPrintf ("\rConnected to ^["S_COLOR_BLUE"%s\\type\\connect %s^] (%s).\n", cls.servername, cls.servername, security); } } @@ -5549,6 +5587,8 @@ static void CL_ProcessUserInfo (int slot, player_info_t *player) player->rbottomcolor = 13; */ + player->chatstate = atoi(InfoBuf_ValueForKey (&player->userinfo, "chat")); + #ifdef HEXEN2 /*if we're running hexen2, they have to be some class...*/ player->h2playerclass = atoi(InfoBuf_ValueForKey (&player->userinfo, "cl_playerclass")); @@ -5691,7 +5731,10 @@ static void CL_ParseSetInfo (void) { player = &cl.players[slot]; - Con_DLPrintf(strcmp(key, "chat")?1:2,"SETINFO %s: %s=%s\n", player->name, key, val); + if (cl_shownet.value == 3) + Con_Printf("\t%i(%s): %s=\"%s\"\n", slot, player->name, key, val); + else + Con_DLPrintf(strcmp(key, "chat")?1:2,"SETINFO %s: %s=%s\n", player->name, key, val); InfoBuf_SetStarKey(&player->userinfo, key, val); player->userinfovalid = true; @@ -5715,7 +5758,10 @@ static void CL_ServerInfo (void) Q_strncpyz (key, MSG_ReadString(), sizeof(key)); Q_strncpyz (value, MSG_ReadString(), sizeof(value)); - Con_DPrintf("SERVERINFO: %s=%s\n", key, value); + if (cl_shownet.value == 3) + Con_Printf("\t%s=%s\n", key, value); + else + Con_DPrintf("SERVERINFO: %s=%s\n", key, value); InfoBuf_SetStarKey(&cl.serverinfo, key, value); @@ -5874,6 +5920,9 @@ static void CL_SetStatNumeric (int pnum, int stat, int ivalue, float fvalue) cl.players[cls_lastto].stats[stat]=ivalue; cl.players[cls_lastto].statsf[stat]=fvalue; + if (cl_shownet.value == 3) + Con_Printf("\t%i: %i=%g\n", cls_lastto, stat, fvalue); + for (pnum = 0; pnum < cl.splitclients; pnum++) if (cl.playerview[pnum].cam_spec_track == cls_lastto && cl.playerview[pnum].cam_state != CAM_FREECAM) CL_SetStat_Internal(pnum, stat, ivalue, fvalue); @@ -5887,6 +5936,9 @@ static void CL_SetStatNumeric (int pnum, int stat, int ivalue, float fvalue) cl.players[pl].statsf[stat]=fvalue; } + if (cl_shownet.value == 3) + Con_Printf("\t%i(%i): %i=%g\n", pnum, pl, stat, fvalue); + CL_SetStat_Internal(pnum, stat, ivalue, fvalue); } @@ -6693,9 +6745,13 @@ static void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds { int cbuflevel; #ifdef NQPROT - if (!*stufftext && *msg == 1 && !cls.allow_csqc) + if (!*stufftext && *msg == 1) { - Con_DPrintf("Proquake: %s\n", msg); + if (developer.ival) + { + Con_DPrintf("Proquake Message:\n"); + Con_HexDump(msg, strlen(msg), 1); + } msg = CLNQ_ParseProQuakeMessage(msg); } #endif @@ -7396,7 +7452,10 @@ void CLQW_ParseServerMessage (void) i = MSG_ReadByte (); if (i >= MAX_NET_LIGHTSTYLES) Host_EndGame ("svc_lightstyle > MAX_LIGHTSTYLES"); - R_UpdateLightStyle(i, MSG_ReadString(), 1, 1, 1); + s = MSG_ReadString(); + if (cl_shownet.value == 3) + Con_Printf("\t%i=\"%s\"\n", i, s); + R_UpdateLightStyle(i, s, 1, 1, 1); break; #ifdef PEXT_LIGHTSTYLECOL case svcfte_lightstylecol: @@ -7847,7 +7906,7 @@ void CLQ2_ParseServerMessage (void) // if (cl_shownet.value == 1) Con_Printf ("%i ",net_message.cursize); - else if (cl_shownet.value == 2) + else if (cl_shownet.value >= 2) Con_Printf ("------------------\n"); @@ -8237,7 +8296,7 @@ void CLNQ_ParseServerMessage (void) // if (cl_shownet.value == 1) Con_Printf ("%i ",net_message.cursize); - else if (cl_shownet.value == 2) + else if (cl_shownet.value >= 2) Con_Printf ("------------------\n"); // diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index 4e57aef62..05a029e0f 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -615,7 +615,8 @@ void CL_CalcClientTime(void) { if (!cls.state) { - cl.servertime += host_frametime; + if (!cl.implicitpause) + cl.servertime += host_frametime; cl.time = cl.servertime; return; } @@ -958,6 +959,8 @@ float CL_GetPredictionRealtime(playerview_t *pv) return simtime; } + +qboolean CSQC_GetSSQCEntityOrigin(unsigned int ssqcent, float *out); /* ============== CL_PredictMove @@ -1059,7 +1062,8 @@ void CL_PredictMovePNum (int seat) if (pv->cam_state == CAM_PENDING && pv->cam_spec_track >= 0 && pv->cam_spec_track < cl.allocated_client_slots && pv->viewentity != pv->cam_spec_track+1) { if ((cl.inframes[cl.validsequence & UPDATE_MASK].playerstate[pv->cam_spec_track].messagenum == cl.validsequence) || - (pv->cam_spec_track+1 < cl.maxlerpents && cl.lerpents[pv->cam_spec_track+1].sequence == cl.lerpentssequence)) + (pv->cam_spec_track+1 < cl.maxlerpents && cl.lerpents[pv->cam_spec_track+1].sequence == cl.lerpentssequence) || + CSQC_GetSSQCEntityOrigin(pv->cam_spec_track+1, NULL)) { pv->cam_state = CAM_EYECAM; pv->viewentity = pv->cam_spec_track+1; diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index 0edaf1003..15d968925 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -78,6 +78,7 @@ void RSpeedShow(void) RSpNames[RSPEED_PROTOCOL] = "Client Protocol"; RSpNames[RSPEED_SERVER] = "Server"; + RSpNames[RSPEED_AUDIO] = "Audio"; memset(RQntNames, 0, sizeof(RQntNames)); RQntNames[RQUANT_MSECS] = "Microseconds"; @@ -1399,7 +1400,7 @@ const char *SCR_ShowPics_ClickCommand(float cx, float cy, qboolean istouch) float x, y, w, h; showpic_t *sp; mpic_t *p; - qboolean tryload = !showpics_touchtime; + qboolean tryload = istouch && !showpics_touchtime; float bestdist = istouch?16:1; const char *best = NULL; showpics_touchtime = realtime; @@ -1876,9 +1877,9 @@ void R_GetGPUUtilisation(float *gpu, float *mem) typedef void *nvmlDevice_t; struct nvmlUtilization_s { - unsigned int cpu; + unsigned int gpu; unsigned int mem; - } util = {-1,-1}; + } util = {~0u,~0u}; static int (*nvmlDeviceGetUtilizationRates) (nvmlDevice_t device, struct nvmlUtilization_s *utilization); static nvmlDevice_t dev; if (!tried) @@ -1906,8 +1907,8 @@ void R_GetGPUUtilisation(float *gpu, float *mem) if (dev) nvmlDeviceGetUtilizationRates(dev, &util); - *gpu = util.cpu/100.0; - *mem = util.mem/100.0; + *gpu = (util.gpu == ~0u)?-1:(util.gpu/100.0); + *mem = (util.mem == ~0u)?-1:(util.mem/100.0); #else *gpu = *mem = -1; #endif @@ -1954,6 +1955,11 @@ void SCR_DrawFPS (void) sprintf(str, "%.0f%% GPU", gpu*100); SCR_StringXY(str, show_fps_x.value, (show_fps_y.value>=0)?(show_fps_y.value+8):(show_fps_y.value-1)); } +/* if (gpumem>=0) + { + sprintf(str, "%.0f%% VRAM Bus", gpumem*100); + SCR_StringXY(str, show_fps_x.value, (show_fps_y.value>=0)?(show_fps_y.value+16):(show_fps_y.value-2)); + }*/ } void SCR_DrawClock(void) diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 08b9c2e63..5681f6748 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -2870,7 +2870,7 @@ entity_t *CL_NewTempEntity (void) return ent; } -void CSQC_GetEntityOrigin(unsigned int csqcent, float *out); +qboolean CSQC_GetEntityOrigin(unsigned int csqcent, float *out); /* ================= diff --git a/engine/client/client.h b/engine/client/client.h index f90fc0832..6c90e2b45 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -187,6 +187,7 @@ typedef struct player_info_s qboolean ignored; qboolean vignored; + unsigned int chatstate; // skin information unsigned int rtopcolor; //real, according to their userinfo @@ -491,7 +492,8 @@ typedef struct infobuf_t userinfo[MAX_SPLITS]; infosync_t userinfosync; - char servername[MAX_OSPATH]; // name of server from original connect + char serverurl[MAX_OSPATH*4]; // eg qw://foo:27500/join?fp=blah + char servername[MAX_OSPATH]; // internal parsing, eg dtls://foo:27500 struct ftenet_connections_s *sockets; @@ -1450,7 +1452,7 @@ qboolean CL_MayLerp(void); // #ifdef CSQC_DAT qboolean CSQC_Inited(void); -void CSQC_RendererRestarted(void); +void CSQC_RendererRestarted(qboolean initing); qboolean CSQC_UnconnectedOkay(qboolean inprinciple); qboolean CSQC_UnconnectedInit(void); qboolean CSQC_CheckDownload(const char *name, unsigned int checksum, size_t checksize); //reports whether we already have a usable csprogs.dat diff --git a/engine/client/console.c b/engine/client/console.c index f857d9c8f..e87147bb3 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -3294,7 +3294,7 @@ void Con_DrawConsole (int lines, qboolean noback) } selactive = Key_GetConsoleSelectionBox(con_current, &selsx, &selsy, &selex, &seley); - if ((con_current->flags & CONF_KEEPSELECTION) && con_current->selstartline && con_current->selendline && con_current->buttonsdown != CB_SELECTED) + if ((con_current->flags & CONF_KEEPSELECTION) && con_current->selstartline && con_current->selendline && con_current->buttonsdown != CB_SELECTED && con_current->buttonsdown != CB_TAPPED) selactive = -1; Font_BeginString(font_console, x, y, &x, &y); @@ -3329,14 +3329,18 @@ void Con_DrawConsole (int lines, qboolean noback) mouseconsole = con_mouseover?con_mouseover:con_current; - if (con_current->buttonsdown == CB_SELECTED) + if (con_current->buttonsdown == CB_SELECTED || con_current->buttonsdown == CB_TAPPED) { //select was released... console_t *con = con_current; char *buffer; + qboolean tapped = con->buttonsdown==CB_TAPPED; con->buttonsdown = CB_NONE; if (con->selstartline) { - con->flags |= CONF_KEEPSELECTION; + if (tapped) + con->flags &= ~CONF_KEEPSELECTION; + else + con->flags |= CONF_KEEPSELECTION; if (con->userline) { if (con->flags & CONF_BACKSELECTION) @@ -3427,6 +3431,57 @@ void Con_DrawOneConsole(console_t *con, qboolean focused, struct font_s *font, f con->display = con->current; Con_DrawConsoleLines(con, con->display, con->displayscroll, x, sx, sy, y, selactive, selsx, selex, selsy, seley, lineagelimit); + + if (con->buttonsdown == CB_SELECTED || con->buttonsdown == CB_TAPPED) + { //select was released... + char *buffer; + qboolean tapped = con->buttonsdown==CB_TAPPED; + con->buttonsdown = CB_NONE; + if (con->selstartline) + { + if (tapped) + con->flags &= ~CONF_KEEPSELECTION; + else + con->flags |= CONF_KEEPSELECTION; + if (con->userline) + { + if (con->flags & CONF_BACKSELECTION) + { + con->userline = con->selendline; + con->useroffset = con->selendoffset; + } + else + { + con->userline = con->selstartline; + con->useroffset = con->selstartoffset; + } + } + if (con->selstartline == con->selendline && con->selendoffset <= con->selstartoffset+1) + { + if (keydown[K_LSHIFT] || keydown[K_RSHIFT]) + ; + else + { + buffer = Con_CopyConsole(con, false, true, false); + if (buffer) + { + Key_HandleConsoleLink(con, buffer); + Z_Free(buffer); + } + } + } + else + { + buffer = Con_CopyConsole(con, true, false, true); //don't keep markup if we're copying to the clipboard + if (buffer) + { + Sys_SaveClipboard(CBT_SELECTION, buffer); + Z_Free(buffer); + } + } + } + } + Font_EndString(font); } diff --git a/engine/client/image.c b/engine/client/image.c index 14e9a3a81..45ec9dd9d 100644 --- a/engine/client/image.c +++ b/engine/client/image.c @@ -4633,8 +4633,10 @@ static void *ReadEXRFile(qbyte *buf, size_t len, const char *fname, int *outwidt fd = mkstemp(tname); //bsd4.3/posix1-2001 if (fd >= 0) { - write(fd, buf, len); - ctx = exr.OpenInputFile(tname); + if (write(fd, buf, len) == len) + ctx = exr.OpenInputFile(tname); + else + ctx = NULL; close(fd); //we don't need the input file now. unlink(tname); #endif @@ -14162,18 +14164,18 @@ image_t *Image_FindTexture(const char *identifier, const char *subdir, unsigned image_t *tex; if (!subdir) subdir = ""; - tex = Hash_Get(&imagetable, identifier); + tex = Hash_GetInsensitive(&imagetable, identifier); while(tex) { if (!((tex->flags ^ flags) & (IF_CLAMP|IF_PALETTIZE|IF_PREMULTIPLYALPHA))) { - if (r_ignoremapprefixes.ival || !strcmp(subdir, tex->subpath?tex->subpath:"") || ((flags|tex->flags) & IF_INEXACT)) + if (r_ignoremapprefixes.ival || !Q_strcasecmp(subdir, tex->subpath?tex->subpath:"") || ((flags|tex->flags) & IF_INEXACT)) { tex->regsequence = r_regsequence; return tex; } } - tex = Hash_GetNext(&imagetable, identifier, tex); + tex = Hash_GetNextInsensitive(&imagetable, identifier, tex); } return NULL; } @@ -14212,7 +14214,7 @@ static image_t *Image_CreateTexture_Internal (const char *identifier, const char tex->fallbackheight = 0; tex->fallbackfmt = TF_INVALID; if (*tex->ident) - Hash_Add(&imagetable, tex->ident, tex, buck); + Hash_AddInsensitive(&imagetable, tex->ident, tex, buck); return tex; } @@ -14549,7 +14551,7 @@ void Image_DestroyTexture(image_t *tex) Sys_UnlockMutex(com_resourcemutex); #endif if (*tex->ident) - Hash_RemoveData(&imagetable, tex->ident, tex); + Hash_RemoveDataInsensitive(&imagetable, tex->ident, tex); Z_Free(tex); } @@ -14787,7 +14789,7 @@ void Image_Shutdown(void) { tex = imagelist; if (*tex->ident) - Hash_RemoveData(&imagetable, tex->ident, tex); + Hash_RemoveDataInsensitive(&imagetable, tex->ident, tex); imagelist = tex->next; if (tex->status == TEX_LOADED) j++; diff --git a/engine/client/in_generic.c b/engine/client/in_generic.c index a9ce96240..6058c9748 100644 --- a/engine/client/in_generic.c +++ b/engine/client/in_generic.c @@ -751,8 +751,10 @@ void IN_MoveMouse(struct mouse_s *mouse, float *movements, int pnum, float frame mx *= 1.75; my *= 1.75; +#ifdef QUAKESTATS if (IN_WeaponWheelAccumulate(pnum, mx, my, 0)) - mx = my = 0; + mx = my = 0; +#endif } } else @@ -1023,10 +1025,12 @@ void IN_MoveJoystick(struct joy_s *joy, float *movements, int pnum, float framet } } +#ifdef QUAKESTATS if (IN_WeaponWheelAccumulate(joy->qdeviceid, jstrafe[1]*50, -jstrafe[0]*50, 20)) jstrafe[0] = jstrafe[1] = 0; if (IN_WeaponWheelAccumulate(joy->qdeviceid, jlook[1]*50, jlook[0]*50, 20)) jlook[0] = jlook[1] = 0; +#endif if (Key_Dest_Has(~kdm_game)) { diff --git a/engine/client/in_sdl.c b/engine/client/in_sdl.c index 482ea4eaa..34ceffb0e 100644 --- a/engine/client/in_sdl.c +++ b/engine/client/in_sdl.c @@ -506,7 +506,7 @@ static void J_ControllerSensor(SDL_JoystickID jid, SDL_SensorType sensor, float if (joy->qdevid == DEVID_UNSET) return; - safeswitch(sensor) + switch(sensor) { case SDL_SENSOR_ACCEL: IN_Accelerometer(joy->qdevid, data[0], data[1], data[2]); @@ -520,7 +520,7 @@ static void J_ControllerSensor(SDL_JoystickID jid, SDL_SensorType sensor, float case SDL_SENSOR_GYRO_R: case SDL_SENSOR_INVALID: case SDL_SENSOR_UNKNOWN: - safedefault: + default: break; } } diff --git a/engine/client/keys.c b/engine/client/keys.c index 8b514176c..2eb180148 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -761,7 +761,7 @@ qboolean Key_GetConsoleSelectionBox(console_t *con, int *sx, int *sy, int *ex, i *ey = con->mousecursor[1]; return true; } - else if (con->buttonsdown == CB_SELECT || con->buttonsdown == CB_SELECTED) + else if (con->buttonsdown == CB_SELECT || con->buttonsdown == CB_SELECTED || con->buttonsdown == CB_TAPPED) { //right-mouse //select. copy-to-clipboard on release. @@ -1219,7 +1219,10 @@ void Key_ConsoleRelease(console_t *con, int key, unsigned int unicode) if ((key == K_TOUCHTAP || key == K_MOUSE1) && con->buttonsdown == CB_SELECT) { - con->buttonsdown = CB_SELECTED; + if (fabs(con->mousedown[0] - con->mousecursor[0]) < 5 && fabs(con->mousedown[1] - con->mousecursor[1]) < 5 && realtime < con->mousedowntime + 0.4) + con->buttonsdown = CB_TAPPED; //don't leave it selected. + else + con->buttonsdown = CB_SELECTED; return; } if ((key == K_TOUCHSLIDE || key == K_MOUSE1) && con->buttonsdown == CB_SCROLL)// || (key == K_MOUSE2 && con->buttonsdown == CB_SCROLL_R)) @@ -1276,7 +1279,7 @@ void Key_ConsoleRelease(console_t *con, int key, unsigned int unicode) return; } } - if (con->buttonsdown == CB_SELECTED) + if (con->buttonsdown == CB_SELECTED || con->buttonsdown == CB_TAPPED) ; //will time out in the drawing code. else // if (con->buttonsdown == CB_MOVE) //window title(move) @@ -1408,6 +1411,8 @@ void Key_EmojiCompletion_c(int argn, const char *partial, struct xcommandargcomp char guess[256]; char repl[256]; size_t ofs, len; + if (*partial != ':') + return; //don't show annoying completion crap. if (!emojidata) Key_LoadEmojiList(); len = strlen(partial); diff --git a/engine/client/keys.h b/engine/client/keys.h index 2cf736dcb..8600dddb4 100644 --- a/engine/client/keys.h +++ b/engine/client/keys.h @@ -176,9 +176,9 @@ typedef enum { #define K_GP_DIAMOND_CONFIRM K_GP_DIAMOND_DOWN //roughly equivelent to k_return for menu behaviours #define K_GP_DIAMOND_CANCEL K_GP_DIAMOND_RIGHT //roughly like escape, at least in menus #define K_GP_DIAMOND_ALTCONFIRM K_GP_DIAMOND_UP //for more negative confirmations. - K_GP_VIEW, + K_GP_VIEW, //aka back (near left stick) K_GP_GUIDE, - K_GP_MENU, + K_GP_MENU, //aka options/start (near right stick) K_GP_LEFT_STICK, K_GP_RIGHT_STICK, K_GP_LEFT_SHOULDER, diff --git a/engine/client/m_download.c b/engine/client/m_download.c index 8e8235d3a..ac76c1d3d 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -267,6 +267,8 @@ static void PM_FreePackage(package_t *p) int i; #endif + COM_AssertMainThread("PM_FreePackage"); + if (p->link) { if (p->alternative) @@ -963,6 +965,7 @@ static qboolean PM_CheckFile(const char *filename, enum fs_relative base) return false; } +static void PM_Plugin_Source_CacheFinished(void *ctx, vfsfile_t *f); static void PM_AddSubListModule(void *module, plugupdatesourcefuncs_t *funcs, const char *url, const char *prefix, unsigned int flags) { size_t i; @@ -1015,6 +1018,9 @@ static void PM_AddSubListModule(void *module, plugupdatesourcefuncs_t *funcs, co downloadablessequence++; } + + if (pm_source[i].funcs && (pm_source[i].status == SRCSTAT_UNTRIED) && (pm_source[i].flags&SRCFL_ENABLED)) //cache only! + pm_source[i].funcs->Update(pm_source[i].url, VFS_OpenPipeCallback(PM_Plugin_Source_CacheFinished, &pm_source[i]), true); } static void PM_AddSubList(const char *url, const char *prefix, unsigned int flags) { @@ -1498,7 +1504,7 @@ static qboolean PM_ParsePackageList(const char *f, unsigned int parseflags, cons size_t signsize; enum hashvalidation_e r; int i; - hashfunc_t *hf = &hash_sha512; + hashfunc_t *hf = &hash_sha2_512; void *hashdata = Z_Malloc(hf->digestsize); void *hashctx = Z_Malloc(hf->contextsize); tokstart = COM_StringParse (tokstart, authority, sizeof(authority), false, false); @@ -1709,7 +1715,7 @@ void PM_EnumerateMaps(const char *partial, struct xcommandargcompletioncb_s *ctx { Q_snprintfz(mname, sizeof(mname), "%s:%s", p->name, d->name); if (!Q_strncasecmp(mname, partial, partiallen)) - ctx->cb(mname, NULL, NULL, ctx); + ctx->cb(mname, p->description, NULL, ctx); } } } @@ -2069,9 +2075,16 @@ static void PM_PreparePackageList(void) #ifdef PLUGINS { char nat[MAX_OSPATH]; - FS_NativePath("", FS_BINARYPATH, nat, sizeof(nat)); - Con_DPrintf("Loading plugins from \"%s\"\n", nat); - Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_DL_POSTFIX, PM_EnumeratedPlugin, &pluginsadded, NULL); + if (FS_NativePath("", FS_BINARYPATH, nat, sizeof(nat))) + { + Con_DPrintf("Loading plugins from \"%s\"\n", nat); + Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_DL_POSTFIX, PM_EnumeratedPlugin, &pluginsadded, NULL); + } + if (FS_NativePath("", FS_LIBRARYPATH, nat, sizeof(nat))) + { + Con_DPrintf("Loading plugins from \"%s\"\n", nat); + Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_DL_POSTFIX, PM_EnumeratedPlugin, &pluginsadded, NULL); + } } #endif } @@ -2093,26 +2106,31 @@ void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const cha pri = maxpri; for (p = availablepackages; p; p = p->next) { - if ((p->flags & DPF_ENABLED) && p->qhash && p->priority>=minpri&&p->prioritygamedir)) + if ((p->flags & (DPF_ENABLED|DPF_MANIMARKED)) && p->priority>=minpri&&p->prioritygamedir)) pri = p->priority; } minpri = pri+1; for (p = availablepackages; p; p = p->next) { - if ((p->flags & DPF_ENABLED) && p->qhash && p->priority==pri && !Q_strcasecmp(parent_pure, p->gamedir)) + if ((p->flags & (DPF_ENABLED|DPF_MANIMARKED)) && p->priority==pri && !Q_strcasecmp(parent_pure, p->gamedir)) { + char *qhash = (p->qhash&&*p->qhash)?p->qhash:NULL; + unsigned int fsfl = SPF_COPYPROTECTED; + if (!qhash || !(p->flags & DPF_SIGNATUREACCEPTED)) + fsfl |= SPF_UNTRUSTED; //never trust it if we can't provide it + for (d = p->deps; d; d = d->next) { if (d->dtype == DEP_FILE) { Q_snprintfz(temp, sizeof(temp), "%s/%s", p->gamedir, d->name); - FS_AddHashedPackage(oldpaths, parent_pure, parent_logical, search, loadstuff, temp, *p->qhash?p->qhash:NULL, p->packprefix, SPF_COPYPROTECTED|SPF_UNTRUSTED); + FS_AddHashedPackage(oldpaths, parent_pure, parent_logical, search, loadstuff, temp, qhash, p->packprefix, fsfl); } else if (d->dtype == DEP_CACHEFILE) { Q_snprintfz(temp, sizeof(temp), "downloads/%s", d->name); - FS_AddHashedPackage(oldpaths, parent_pure, parent_logical, NULL, loadstuff, temp, *p->qhash?p->qhash:NULL, p->packprefix, SPF_COPYPROTECTED|SPF_UNTRUSTED); + FS_AddHashedPackage(oldpaths, parent_pure, parent_logical, NULL, loadstuff, temp, qhash, p->packprefix, fsfl); } } } @@ -2291,6 +2309,8 @@ static void PM_UnmarkPackage(package_t *package, unsigned int markflag) package_t *o; struct packagedep_s *dep; + COM_AssertMainThread("PM_UnmarkPackage"); + if (pkg_updating) return; @@ -2686,7 +2706,8 @@ static void PM_ListDownloaded(struct dl_download *dl) if (f) { - pm_source[listidx].status = SRCSTAT_OBTAINED; + if (dl->replycode != 100) + pm_source[listidx].status = SRCSTAT_OBTAINED; downloadablessequence++; if (pm_source[listidx].flags & SRCFL_UNSAFE) PM_ParsePackageList(f, DPF_SIGNATUREACCEPTED, dl->url, pm_source[listidx].prefix); @@ -2772,7 +2793,7 @@ static void PM_ListDownloaded(struct dl_download *dl) } } static void PM_Plugin_Source_Finished(void *ctx, vfsfile_t *f) -{ +{ //plugin closed its write end. struct pm_source_s *src = ctx; size_t idx = src-pm_source; if (idx < pm_numsources && ctx == &pm_source[idx]) @@ -2785,13 +2806,31 @@ static void PM_Plugin_Source_Finished(void *ctx, vfsfile_t *f) dl.status = DL_FINISHED; dl.user_num = src-pm_source; dl.url = src->url; + dl.replycode = 200; //okay src->curdl = &dl; PM_ListDownloaded(&dl); } } - else + VFS_CLOSE(f); +} +static void PM_Plugin_Source_CacheFinished(void *ctx, vfsfile_t *f) +{ //plugin closed its write end. + struct pm_source_s *src = ctx; + size_t idx = src-pm_source; + if (idx < pm_numsources && ctx == &pm_source[idx]) { - Con_Printf("PM_Plugin_Source_Finished: stale\n"); + COM_AssertMainThread("PM_Plugin_Source_CacheFinished"); + if (!src->curdl) + { + struct dl_download dl; + dl.file = f; + dl.status = DL_FINISHED; + dl.user_num = src-pm_source; + dl.url = src->url; + dl.replycode = 100; + src->curdl = &dl; + PM_ListDownloaded(&dl); + } } VFS_CLOSE(f); } @@ -2871,7 +2910,7 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) if (pm_source[i].funcs) { - pm_source[i].funcs->Update(pm_source[i].url, VFS_OpenPipeCallback(PM_Plugin_Source_Finished, &pm_source[i])); + pm_source[i].funcs->Update(pm_source[i].url, VFS_OpenPipeCallback(PM_Plugin_Source_Finished, &pm_source[i]), false); } else { @@ -2925,7 +2964,7 @@ qboolean PM_RegisterUpdateSource(void *module, plugupdatesourcefuncs_t *funcs) } } else - PM_AddSubListModule(module, funcs, va("plug:%s", funcs->description), NULL, SRCFL_PLUGIN); + PM_AddSubListModule(module, funcs, va("plug:%s", funcs->description), NULL, SRCFL_PLUGIN|SRCFL_ENABLED); //plugin sources default to enabled as you can always just disable the plugin that provides it. return true; } @@ -2973,13 +3012,243 @@ static void COM_QuotedKeyVal(const char *key, const char *val, char *buf, size_t Q_strncatz(buf, "\"\n", bufsize); } +static void PM_WriteInstalledPackage_v3(package_t *p, char *buf, size_t bufsize) +{ + struct packagedep_s *dep; + + buf[0] = '{'; + buf[1] = '\n'; + buf[2] = 0; + COM_QuotedKeyVal("package", p->name, buf, bufsize); + COM_QuotedKeyVal("category", p->category, buf, bufsize); + if (p->flags & DPF_ENABLED) + COM_QuotedKeyVal("enabled", "1", buf, bufsize); + if (p->flags & DPF_GUESSED) + COM_QuotedKeyVal("guessed", "1", buf, bufsize); + if (*p->title && strcmp(p->title, p->name)) + COM_QuotedKeyVal("title", p->title, buf, bufsize); + if (*p->version) + COM_QuotedKeyVal("ver", p->version, buf, bufsize); + COM_QuotedKeyVal("gamedir", p->gamedir, buf, bufsize); + if (p->qhash) + COM_QuotedKeyVal("qhash", p->qhash, buf, bufsize); + if (p->priority!=PM_DEFAULTPRIORITY) + COM_QuotedKeyVal("priority", va("%i", p->priority), buf, bufsize); + if (p->arch) + COM_QuotedKeyVal("arch", p->arch, buf, bufsize); + + if (p->license) + COM_QuotedKeyVal("license", p->license, buf, bufsize); + if (p->website) + COM_QuotedKeyVal("website", p->website, buf, bufsize); + if (p->author) + COM_QuotedKeyVal("author", p->author, buf, bufsize); + if (p->description) + COM_QuotedKeyVal("desc", p->description, buf, bufsize); + if (p->previewimage) + COM_QuotedKeyVal("preview", p->previewimage, buf, bufsize); + if (p->filesize) + COM_QuotedKeyVal("filesize", va("%"PRIu64, p->filesize), buf, bufsize); + + if (p->fsroot == FS_BINARYPATH) + COM_QuotedKeyVal("root", "bin", buf, bufsize); + if (p->packprefix) + COM_QuotedKeyVal("packprefix", p->packprefix, buf, bufsize); + + for (dep = p->deps; dep; dep = dep->next) + { + safeswitch(dep->dtype) + { + case DEP_FILE: COM_QuotedKeyVal("file", dep->name, buf, bufsize); break; + case DEP_CACHEFILE: COM_QuotedKeyVal("cachefile", dep->name, buf, bufsize); break; + case DEP_MAP: COM_QuotedKeyVal("map", dep->name, buf, bufsize); break; + case DEP_REQUIRE: COM_QuotedKeyVal("depend", dep->name, buf, bufsize); break; + case DEP_CONFLICT: COM_QuotedKeyVal("conflict", dep->name, buf, bufsize); break; + case DEP_REPLACE: COM_QuotedKeyVal("replace", dep->name, buf, bufsize); break; + case DEP_FILECONFLICT: COM_QuotedKeyVal("fileconflict",dep->name, buf, bufsize); break; + case DEP_RECOMMEND: COM_QuotedKeyVal("recommend", dep->name, buf, bufsize); break; + case DEP_NEEDFEATURE: COM_QuotedKeyVal("need", dep->name, buf, bufsize); break; + case DEP_SUGGEST: COM_QuotedKeyVal("suggest", dep->name, buf, bufsize); break; + case DEP_SOURCE: COM_QuotedKeyVal("source", dep->name, buf, bufsize); break; + case DEP_EXTRACTNAME: COM_QuotedKeyVal("unzipfile", dep->name, buf, bufsize); break; + safedefault: + break; + } + } + + if (p->flags & DPF_TESTING) + COM_QuotedKeyVal("test", "1", buf, bufsize); + + if ((p->flags & DPF_AUTOMARKED) && !(p->flags & DPF_USERMARKED)) + COM_QuotedKeyVal("auto", "1", buf, bufsize); + + Q_strncatz(buf, "}", bufsize); + Q_strncatz(buf, "\n", bufsize); +} +static void PM_WriteInstalledPackage_v2(package_t *p, char *buf, size_t bufsize) +{ + struct packagedep_s *dep; + + buf[0] = 0; + COM_QuotedString(va("%s%s", p->category, p->name), buf, bufsize, false); + if (p->flags & DPF_ENABLED) + { //v3+ + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("enabled=1"), buf, bufsize); + } + else + { //v2 + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("stale=1"), buf, bufsize); + } + if (p->flags & DPF_GUESSED) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("guessed=1"), buf, bufsize); + } + if (*p->title && strcmp(p->title, p->name)) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("title=%s", p->title), buf, bufsize); + } + if (*p->version) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("ver=%s", p->version), buf, bufsize); + } + //if (*p->gamedir) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("gamedir=%s", p->gamedir), buf, bufsize); + } + if (p->qhash) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("qhash=%s", p->qhash), buf, bufsize); + } + if (p->priority!=PM_DEFAULTPRIORITY) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("priority=%i", p->priority), buf, bufsize); + } + if (p->arch) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("arch=%s", p->arch), buf, bufsize); + } + + if (p->license) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("license=%s", p->license), buf, bufsize); + } + if (p->website) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("website=%s", p->website), buf, bufsize); + } + if (p->author) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("author=%s", p->author), buf, bufsize); + } + if (p->description) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("desc=%s", p->description), buf, bufsize); + } + if (p->previewimage) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("preview=%s", p->previewimage), buf, bufsize); + } + if (p->filesize) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("filesize=%"PRIu64, p->filesize), buf, bufsize); + } + + if (p->fsroot == FS_BINARYPATH) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat("root=bin", buf, bufsize); + } + if (p->packprefix) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("packprefix=%s", p->packprefix), buf, bufsize); + } + + for (dep = p->deps; dep; dep = dep->next) + { + if (dep->dtype == DEP_FILE) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("file=%s", dep->name), buf, bufsize); + } + else if (dep->dtype == DEP_CACHEFILE) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("cachefile=%s", dep->name), buf, bufsize); + } + else if (dep->dtype == DEP_MAP) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("map=%s", dep->name), buf, bufsize); + } + else if (dep->dtype == DEP_REQUIRE) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("depend=%s", dep->name), buf, bufsize); + } + else if (dep->dtype == DEP_CONFLICT) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("conflict=%s", dep->name), buf, bufsize); + } + else if (dep->dtype == DEP_REPLACE) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("replace=%s", dep->name), buf, bufsize); + } + else if (dep->dtype == DEP_FILECONFLICT) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("fileconflict=%s", dep->name), buf, bufsize); + } + else if (dep->dtype == DEP_RECOMMEND) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("recommend=%s", dep->name), buf, bufsize); + } + else if (dep->dtype == DEP_NEEDFEATURE) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("need=%s", dep->name), buf, bufsize); + } + } + + if (p->flags & DPF_TESTING) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat("test=1", buf, bufsize); + } + + if ((p->flags & DPF_AUTOMARKED) && !(p->flags & DPF_USERMARKED)) + { + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat("auto", buf, bufsize); + } + + buf[bufsize-2] = 0; //just in case. + Q_strncatz(buf, "\n", bufsize); +} static void PM_WriteInstalledPackages(void) { char buf[65536]; int i; char *s; package_t *p; - struct packagedep_s *dep; vfsfile_t *f = FS_OpenVFS(INSTALLEDFILES, "wb", FS_ROOT); qboolean v3 = false; if (!f) @@ -3021,233 +3290,11 @@ static void PM_WriteInstalledPackages(void) { if (p->flags & (DPF_PRESENT|DPF_ENABLED)) { - buf[0] = 0; if (v3) - { - buf[0] = '{'; - buf[1] = '\n'; - buf[2] = 0; - COM_QuotedKeyVal("package", p->name, buf, sizeof(buf)); - COM_QuotedKeyVal("category", p->category, buf, sizeof(buf)); - if (p->flags & DPF_ENABLED) - COM_QuotedKeyVal("enabled", "1", buf, sizeof(buf)); - if (p->flags & DPF_GUESSED) - COM_QuotedKeyVal("guessed", "1", buf, sizeof(buf)); - if (*p->title && strcmp(p->title, p->name)) - COM_QuotedKeyVal("title", p->title, buf, sizeof(buf)); - if (*p->version) - COM_QuotedKeyVal("ver", p->version, buf, sizeof(buf)); - COM_QuotedKeyVal("gamedir", p->gamedir, buf, sizeof(buf)); - if (p->qhash) - COM_QuotedKeyVal("qhash", p->qhash, buf, sizeof(buf)); - if (p->priority!=PM_DEFAULTPRIORITY) - COM_QuotedKeyVal("priority", va("%i", p->priority), buf, sizeof(buf)); - if (p->arch) - COM_QuotedKeyVal("arch", p->arch, buf, sizeof(buf)); - - if (p->license) - COM_QuotedKeyVal("license", p->license, buf, sizeof(buf)); - if (p->website) - COM_QuotedKeyVal("website", p->website, buf, sizeof(buf)); - if (p->author) - COM_QuotedKeyVal("author", p->author, buf, sizeof(buf)); - if (p->description) - COM_QuotedKeyVal("desc", p->description, buf, sizeof(buf)); - if (p->previewimage) - COM_QuotedKeyVal("preview", p->previewimage, buf, sizeof(buf)); - if (p->filesize) - COM_QuotedKeyVal("filesize", va("%"PRIu64, p->filesize), buf, sizeof(buf)); - - if (p->fsroot == FS_BINARYPATH) - COM_QuotedKeyVal("root", "bin", buf, sizeof(buf)); - if (p->packprefix) - COM_QuotedKeyVal("packprefix", p->packprefix, buf, sizeof(buf)); - - for (dep = p->deps; dep; dep = dep->next) - { - if (dep->dtype == DEP_FILE) - COM_QuotedKeyVal("file", dep->name, buf, sizeof(buf)); - else if (dep->dtype == DEP_CACHEFILE) - COM_QuotedKeyVal("cachefile", dep->name, buf, sizeof(buf)); - else if (dep->dtype == DEP_MAP) - COM_QuotedKeyVal("map", dep->name, buf, sizeof(buf)); - else if (dep->dtype == DEP_REQUIRE) - COM_QuotedKeyVal("depend", dep->name, buf, sizeof(buf)); - else if (dep->dtype == DEP_CONFLICT) - COM_QuotedKeyVal("conflict", dep->name, buf, sizeof(buf)); - else if (dep->dtype == DEP_REPLACE) - COM_QuotedKeyVal("replace", dep->name, buf, sizeof(buf)); - else if (dep->dtype == DEP_FILECONFLICT) - COM_QuotedKeyVal("fileconflict", dep->name, buf, sizeof(buf)); - else if (dep->dtype == DEP_RECOMMEND) - COM_QuotedKeyVal("recommend", dep->name, buf, sizeof(buf)); - else if (dep->dtype == DEP_NEEDFEATURE) - COM_QuotedKeyVal("need", dep->name, buf, sizeof(buf)); - } - - if (p->flags & DPF_TESTING) - COM_QuotedKeyVal("test", "1", buf, sizeof(buf)); - - if ((p->flags & DPF_AUTOMARKED) && !(p->flags & DPF_USERMARKED)) - COM_QuotedKeyVal("auto", "1", buf, sizeof(buf)); - - Q_strncatz(buf, "}", sizeof(buf)); - } + PM_WriteInstalledPackage_v3(p, buf, sizeof(buf)); else - { - COM_QuotedString(va("%s%s", p->category, p->name), buf, sizeof(buf), false); - if (p->flags & DPF_ENABLED) - { //v3+ - // Q_strncatz(buf, " ", sizeof(buf)); - // COM_QuotedConcat(va("enabled=1"), buf, sizeof(buf)); - } - else - { //v2 - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("stale=1"), buf, sizeof(buf)); - } - if (p->flags & DPF_GUESSED) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("guessed=1"), buf, sizeof(buf)); - } - if (*p->title && strcmp(p->title, p->name)) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("title=%s", p->title), buf, sizeof(buf)); - } - if (*p->version) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("ver=%s", p->version), buf, sizeof(buf)); - } - //if (*p->gamedir) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("gamedir=%s", p->gamedir), buf, sizeof(buf)); - } - if (p->qhash) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("qhash=%s", p->qhash), buf, sizeof(buf)); - } - if (p->priority!=PM_DEFAULTPRIORITY) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("priority=%i", p->priority), buf, sizeof(buf)); - } - if (p->arch) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("arch=%s", p->arch), buf, sizeof(buf)); - } + PM_WriteInstalledPackage_v2(p, buf, sizeof(buf)); - if (p->license) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("license=%s", p->license), buf, sizeof(buf)); - } - if (p->website) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("website=%s", p->website), buf, sizeof(buf)); - } - if (p->author) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("author=%s", p->author), buf, sizeof(buf)); - } - if (p->description) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("desc=%s", p->description), buf, sizeof(buf)); - } - if (p->previewimage) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("preview=%s", p->previewimage), buf, sizeof(buf)); - } - if (p->filesize) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("filesize=%"PRIu64, p->filesize), buf, sizeof(buf)); - } - - if (p->fsroot == FS_BINARYPATH) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat("root=bin", buf, sizeof(buf)); - } - if (p->packprefix) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("packprefix=%s", p->packprefix), buf, sizeof(buf)); - } - - for (dep = p->deps; dep; dep = dep->next) - { - if (dep->dtype == DEP_FILE) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("file=%s", dep->name), buf, sizeof(buf)); - } - else if (dep->dtype == DEP_CACHEFILE) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("cachefile=%s", dep->name), buf, sizeof(buf)); - } - else if (dep->dtype == DEP_MAP) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("map=%s", dep->name), buf, sizeof(buf)); - } - else if (dep->dtype == DEP_REQUIRE) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("depend=%s", dep->name), buf, sizeof(buf)); - } - else if (dep->dtype == DEP_CONFLICT) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("conflict=%s", dep->name), buf, sizeof(buf)); - } - else if (dep->dtype == DEP_REPLACE) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("replace=%s", dep->name), buf, sizeof(buf)); - } - else if (dep->dtype == DEP_FILECONFLICT) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("fileconflict=%s", dep->name), buf, sizeof(buf)); - } - else if (dep->dtype == DEP_RECOMMEND) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("recommend=%s", dep->name), buf, sizeof(buf)); - } - else if (dep->dtype == DEP_NEEDFEATURE) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat(va("need=%s", dep->name), buf, sizeof(buf)); - } - } - - if (p->flags & DPF_TESTING) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat("test=1", buf, sizeof(buf)); - } - - if ((p->flags & DPF_AUTOMARKED) && !(p->flags & DPF_USERMARKED)) - { - Q_strncatz(buf, " ", sizeof(buf)); - COM_QuotedConcat("auto", buf, sizeof(buf)); - } - } - - buf[sizeof(buf)-2] = 0; //just in case. - Q_strncatz(buf, "\n", sizeof(buf)); VFS_WRITE(f, buf, strlen(buf)); } } @@ -3270,6 +3317,7 @@ static void PM_PackageEnabled(package_t *p) if (dep->dtype != DEP_FILE && dep->dtype != DEP_CACHEFILE) continue; COM_FileExtension(dep->name, ext, sizeof(ext)); + if (!pm_packagesinstalled) if (!stricmp(ext, "pak") || !stricmp(ext, "pk3") || !stricmp(ext, "zip")) { if (pm_packagesinstalled) @@ -3420,6 +3468,10 @@ static qboolean PM_Download_Got_Extract(package_t *p, searchpathfuncs_t *archive archive->ReadFile(archive, &loc, f); if (FS_WriteFile(destname, f, loc.len, p->fsroot)) { + if (!FS_NativePath(destname, p->fsroot, native, sizeof(native))) + Q_strncpyz(native, destname, sizeof(native)); + Con_Printf("Extracted %s (to %s)\n", p->name, native); + p->flags = nfl; success = true; continue; @@ -3639,7 +3691,10 @@ static void PM_Download_PreliminaryGot(struct dl_download *dl) for (info.p = availablepackages; info.p ; info.p=info.p->next) { if (info.p->download == dl) + { + info.p->download = NULL; break; + } } info.successful = (dl->status == DL_FINISHED); @@ -3945,7 +4000,7 @@ static void PM_DownloadsCompleted(int iarg, void *data) Z_Free(pm_onload.package); pm_onload.package = NULL; COM_Gamedir(p->gamedir, packs); - Cbuf_InsertText(va("map %s\n", map), RESTRICT_LOCAL, false); + Cbuf_InsertText(map, RESTRICT_LOCAL, false); Z_Free(map); } else @@ -4071,7 +4126,7 @@ static void PM_StartADownload(void) } if (p->filesha512 && tmpfile) - tmpfile = FS_Hash_ValidateWrites(tmpfile, p->name, p->filesize, &hash_sha512, p->filesha512); + tmpfile = FS_Hash_ValidateWrites(tmpfile, p->name, p->filesize, &hash_sha2_512, p->filesha512); else if (p->filesha1 && tmpfile) tmpfile = FS_Hash_ValidateWrites(tmpfile, p->name, p->filesize, &hash_sha1, p->filesha1); @@ -4672,11 +4727,34 @@ void PM_Command_f(void) Con_Printf(" ^[[Add]\\type\\pkg add %s;pkg apply^]", COM_QuotedString(p->name, quoted, sizeof(quoted), false)); if ((p->flags&DPF_MARKED) && p == PM_MarkedPackage(p->name, DPF_MARKED)) Con_Printf(" ^[[Remove]\\type\\pkg rem %s;pkg apply^]", COM_QuotedString(p->name, quoted, sizeof(quoted), false)); + + + if (p->flags & DPF_SIGNATUREACCEPTED) + Con_Printf(" ^&02Trusted"); + else if (p->flags & DPF_SIGNATUREREJECTED) + Con_Printf(" ^&04Untrusted"); + else if (p->flags & DPF_SIGNATUREUNKNOWN) + Con_Printf(" ^&0EUnverified"); + else + Con_Printf(" ^&0EUnsigned"); + Con_Printf("\n"); } Z_Free(sorted); Con_Printf("\n"); } + else if (!strcmp(act, "internal")) + { + char buf[65536]; + key = Cmd_Argv(2); + for (p = availablepackages; p; p=p->next) + { + if (Q_strcasecmp(p->name, key)) + continue; + PM_WriteInstalledPackage_v3(p, buf, sizeof(buf)); + Con_Printf("%s", buf); + } + } else if (!strcmp(act, "show")) { struct packagedep_s *dep; @@ -5012,6 +5090,7 @@ void PM_AddManifestPackages(ftemanifest_t *man) int idx; struct manpack_s *pack; const char *baseurl = man->updateurl; //this is a url for updated versions of the fmf itself. + int dtype; for (p = availablepackages; p; p = p->next) p->flags &= ~DPF_MANIMARKED; @@ -5031,6 +5110,20 @@ void PM_AddManifestPackages(ftemanifest_t *man) continue; //ignore it } + if (pack->mirrors[0]) + { + for (p = availablepackages; p; p = p->next) + { + if (p->mirror[0] && !strcmp(p->mirror[0], pack->mirrors[0])) + break; + } + if (p) + { + PM_MarkPackage(p, DPF_MANIMARKED); + continue; + } + } + p = Z_Malloc(sizeof(*p)); p->name = Z_StrDup(pack->path); p->title = Z_StrDup(pack->path); @@ -5040,6 +5133,16 @@ void PM_AddManifestPackages(ftemanifest_t *man) strcpy(p->version, ""); p->flags = DPF_FORGETONUNINSTALL|DPF_MANIFEST|DPF_GUESSED; p->qhash = pack->crcknown?Z_StrDupf("%#x", pack->crc):NULL; + dtype = DEP_FILE; + + //note that this signs the hash(validated with size) with an separately trusted authority and is thus not dependant upon trusting the manifest itself... + //that said, we can't necessarily trust any overrides the manifest might include - those parts do not form part of the signature. + if (!pack->prefix && pack->crcknown && strchr(p->name, '/')) + { + p->signature = pack->signature?Z_StrDup(pack->signature):NULL; + p->filesha512 = pack->sha512?Z_StrDup(pack->sha512):NULL; + p->filesize = pack->filesize; + } { char *c = p->name; @@ -5104,7 +5207,9 @@ void PM_AddManifestPackages(ftemanifest_t *man) if (url && *url) p->mirror[i] = Z_StrDup(url); } - PM_AddDep(p, DEP_FILE, path); + PM_AddDep(p, dtype, path); + + PM_ValidateAuthenticity(p, VH_UNSUPPORTED); m = PM_InsertPackage(p); if (!m) @@ -5262,6 +5367,8 @@ typedef struct { int downloadablessequence; char titletext[128]; char applymessage[128]; //so we can change its text to give it focus + char filtertext[128]; + qboolean filtering; const void *expandedpackage; //which package we're currently viewing maps for. qboolean populated; } dlmenu_t; @@ -5567,12 +5674,14 @@ static qboolean MD_Key (struct menucustom_s *c, struct emenu_s *m, int key, unsi static void MD_MapDraw (int x, int y, struct menucustom_s *c, struct emenu_s *m) { - const package_t *p = c->dptr; struct packagedep_s *map = c->dptr2; +#if defined(HAVE_SERVER) + const package_t *p = c->dptr; struct packagedep_s *dep; float besttime, fulltime, bestkills, bestsecrets; char *package = NULL; const char *ext; +#endif if (y + 8 < 0 || y >= vid.height) //small optimisation. return; @@ -5580,7 +5689,7 @@ static void MD_MapDraw (int x, int y, struct menucustom_s *c, struct emenu_s *m) if (c->dint != downloadablessequence) return; //probably stale - +#if defined(HAVE_SERVER) for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype == DEP_CACHEFILE) @@ -5600,6 +5709,7 @@ static void MD_MapDraw (int x, int y, struct menucustom_s *c, struct emenu_s *m) Draw_FunStringU8(CON_WHITEMASK, x+48+8*4, y, va("^m%s^m (%g %g in %.1f secs)", map->name, bestkills,bestsecrets,fulltime)); } else +#endif Draw_FunStringU8(CON_WHITEMASK, x+48+8*4, y, map->name); } static qboolean MD_MapKey (struct menucustom_s *c, struct emenu_s *m, int key, unsigned int unicode) @@ -5798,7 +5908,7 @@ static qboolean MD_RevertUpdates (union menuoption_s *mo,struct emenu_s *m,int k return false; } -static int MD_AddMapItems(emenu_t *m, package_t *p, int y) +static int MD_AddMapItems(emenu_t *m, package_t *p, int y, const char *filter) { struct packagedep_s *dep; menucustom_t *c; @@ -5806,6 +5916,9 @@ static int MD_AddMapItems(emenu_t *m, package_t *p, int y) { if (dep->dtype != DEP_MAP) continue; + if (filter) + if (!strstr(dep->name, filter)) + continue; c = MC_AddCustom(m, 0, y, p, downloadablessequence, NULL); c->dptr2 = dep; c->draw = MD_MapDraw; @@ -5816,7 +5929,37 @@ static int MD_AddMapItems(emenu_t *m, package_t *p, int y) } return y; } -static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix, void *selpackage) +static int MD_DownloadMenuFiltered(package_t *p, const char *filter) +{ + struct packagedep_s *dep; + if ((p->flags & DPF_HIDDEN) && (p->arch || !(p->flags & DPF_ENABLED))) + return true; + if ((p->flags & DPF_HIDEUNLESSPRESENT) && !(p->flags & DPF_PRESENT)) + return true; + + if (filter && *filter) + { + if (strstr(p->title, filter) || + strstr(p->name, filter) || + (p->description && strstr(p->description, filter)) || + (p->author && strstr(p->author, filter))) + ; + else + { + for (dep = p->deps; dep; dep = dep->next) + { + if (dep->dtype == DEP_MAP) + if (strstr(dep->name, filter)) + return -1; + } + if (!dep) + return true; + } + } + return false; +} + +static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix, const char *filter, void *selpackage) { char path[MAX_QPATH]; package_t *p; @@ -5826,15 +5969,15 @@ static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix, int prefixlen = strlen(pathprefix); struct packagedep_s *dep; dlmenu_t *info = m->data; + int filtered; //add all packages in this dir for (p = availablepackages; p; p = p->next) { if (strncmp(p->category, pathprefix, prefixlen)) continue; - if ((p->flags & DPF_HIDDEN) && (p->arch || !(p->flags & DPF_ENABLED))) - continue; - if ((p->flags & DPF_HIDEUNLESSPRESENT) && !(p->flags & DPF_PRESENT)) + filtered = MD_DownloadMenuFiltered(p, filter); + if (filtered==true) continue; slash = strchr(p->category+prefixlen, '/'); if (!slash) @@ -5899,11 +6042,11 @@ static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix, c->common.height = 8; y += 8; - if (info->expandedpackage == p) + if (info->expandedpackage == p || filtered<0) { m->selecteditem = (menuoption_t*)c; - y = MD_AddMapItems(m, p, y); + y = MD_AddMapItems(m, p, y, (filtered==-1)?filter:NULL); } if (!m->selecteditem || p == selpackage) m->selecteditem = (menuoption_t*)c; @@ -5915,9 +6058,7 @@ static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix, { if (strncmp(p->category, pathprefix, prefixlen)) continue; - if ((p->flags & DPF_HIDDEN) && (p->arch || !(p->flags & DPF_ENABLED))) - continue; - if ((p->flags & DPF_HIDEUNLESSPRESENT) && !(p->flags & DPF_PRESENT)) + if (MD_DownloadMenuFiltered(p, filter)==true) continue; slash = strchr(p->category+prefixlen, '/'); @@ -5938,7 +6079,7 @@ static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix, MC_AddBufferedText(m, 48, 320-16, y, path+prefixlen, false, true); y += 8; Q_strncatz(path, "/", sizeof(path)); - y = MD_AddItemsToDownloadMenu(m, y, path, selpackage); + y = MD_AddItemsToDownloadMenu(m, y, path, filter, selpackage); } } } @@ -5981,6 +6122,8 @@ static void MD_Download_UpdateStatus(struct emenu_s *m) info->populated = false; MC_AddWhiteText(m, 24, 320, 8, "Downloads", false)->text = info->titletext; + if (info->filtering || *info->filtertext) + MC_AddWhiteText(m, 24, 320, 16, va("%sFilter: %s", info->filtering?"^m":"", info->filtertext), false); MC_AddWhiteText(m, 16, 320, 24, "^Ue01d^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01f", false); //FIXME: should probably reselect the previous selected item. lets just assume everyone uses a mouse... @@ -6053,64 +6196,71 @@ static void MD_Download_UpdateStatus(struct emenu_s *m) info->populated = true; MC_AddFrameStart(m, 48); -#ifdef WEBCLIENT - for (i = 0, sources=false; i < pm_numsources; i++) + if (!info->filtering) { - if (pm_source[i].flags & SRCFL_HISTORIC) - continue; //historic... ignore it. - if (!sources) - MC_AddBufferedText(m, 48, 320-16, y, "Sources", false, true), y += 8; - sources=true; - c = MC_AddCustom(m, 0, y, p, i, NULL); - c->draw = MD_Source_Draw; - c->key = MD_Source_Key; - c->common.width = 320-48-16; - c->common.height = 8; +#ifdef WEBCLIENT + for (i = 0, sources=false; i < pm_numsources; i++) + { + if (pm_source[i].flags & SRCFL_HISTORIC) + continue; //historic... ignore it. + if (!sources) + MC_AddBufferedText(m, 48, 320-16, y, "Sources", false, true), y += 8; + sources=true; + c = MC_AddCustom(m, 0, y, p, i, NULL); + c->draw = MD_Source_Draw; + c->key = MD_Source_Key; + c->common.width = 320-48-16; + c->common.height = 8; - if (!m->selecteditem) - m->selecteditem = (menuoption_t*)c; - y += 8; - } - y+=4; //small gap + if (!m->selecteditem) + m->selecteditem = (menuoption_t*)c; + y += 8; + } + y+=4; //small gap #endif - MC_AddBufferedText(m, 48, 320-16, y, "Options", false, true), y += 8; - b = MC_AddCommand(m, 48, 320-16, y, info->applymessage, MD_ApplyDownloads); - b->rightalign = false; - b->common.tooltip = "Enable/Disable/Download/Delete packages to match any changes made (you will be prompted with a list of the changes that will be made)."; - y+=8; - d = b = MC_AddCommand(m, 48, 320-16, y, "Back", MD_PopMenu); - b->rightalign = false; - y+=8; -#ifdef WEBCLIENT - if (pm_numsources) - { - b = MC_AddCommand(m, 48, 320-16, y, "Mark Updates", MD_MarkUpdatesButton); + MC_AddBufferedText(m, 48, 320-16, y, "Options", false, true), y += 8; + b = MC_AddCommand(m, 48, 320-16, y, info->applymessage, MD_ApplyDownloads); + b->rightalign = false; + b->common.tooltip = "Enable/Disable/Download/Delete packages to match any changes made (you will be prompted with a list of the changes that will be made)."; + y+=8; + d = b = MC_AddCommand(m, 48, 320-16, y, "Back", MD_PopMenu); b->rightalign = false; - b->common.tooltip = "Select any updated versions of packages that are already installed."; y+=8; - } -#endif - b = MC_AddCommand(m, 48, 320-16, y, "Undo Changes", MD_RevertUpdates); - b->rightalign = false; - b->common.tooltip = "Reset selection to only those packages that are currently installed."; - y+=8; #ifdef WEBCLIENT - if (pm_numsources) - { - c = MC_AddCustom(m, 48, y, p, 0, NULL); - c->draw = MD_AutoUpdate_Draw; - c->key = MD_AutoUpdate_Key; - c->common.width = 320-48-16; - c->common.height = 8; - y += 8; - } + if (pm_numsources) + { + b = MC_AddCommand(m, 48, 320-16, y, "Mark Updates", MD_MarkUpdatesButton); + b->rightalign = false; + b->common.tooltip = "Select any updated versions of packages that are already installed."; + y+=8; + } #endif - y+=4; //small gap + b = MC_AddCommand(m, 48, 320-16, y, "Undo Changes", MD_RevertUpdates); + b->rightalign = false; + b->common.tooltip = "Reset selection to only those packages that are currently installed."; + y+=8; +#ifdef WEBCLIENT + if (pm_numsources) + { + c = MC_AddCustom(m, 48, y, p, 0, NULL); + c->draw = MD_AutoUpdate_Draw; + c->key = MD_AutoUpdate_Key; + c->common.width = 320-48-16; + c->common.height = 8; + y += 8; + } +#endif + y+=4; //small gap + } + else d = NULL; MC_AddBufferedText(m, 48, 320-16, y, "Packages", false, true), y += 8; - MD_AddItemsToDownloadMenu(m, y, info->pathprefix, oldpackage); + MD_AddItemsToDownloadMenu(m, y, info->pathprefix, info->filtertext, oldpackage); if (!m->selecteditem) m->selecteditem = (menuoption_t*)d; - m->cursoritem = (menuoption_t*)MC_AddWhiteText(m, 40, 0, m->selecteditem->common.posy, NULL, false); + if (info->filtering) + m->cursoritem = NULL; + else + m->cursoritem = (menuoption_t*)MC_AddWhiteText(m, 40, 0, m->selecteditem->common.posy, NULL, false); MC_AddFrameEnd(m, 48)->frac = framefrac; } @@ -6129,6 +6279,57 @@ static void MD_Download_UpdateStatus(struct emenu_s *m) } } +static qboolean MD_Download_Key(struct emenu_s *m, int key, unsigned int unicode) +{ + dlmenu_t *info = m->data; + + if (info->filtering) + { + if (key == K_BACKSPACE) + { + unsigned int l = strlen(info->filtertext); + if (l) + { + while (l > 0 && (info->filtertext[--l]&0xc0) == 0x80) + ; + info->filtertext[l] = 0; + } + else + info->filtering = false; + } + else if (key == K_ENTER || key == K_ESCAPE) + info->filtering = false; //done... + else if (key>=K_F1 && key!=K_RALT && key!=K_RCTRL && key!=K_RSHIFT) + { //some other action... don't swallow clicks. + info->filtering = false; + info->populated = false; + return false; + } + else if (unicode) + { + unsigned int l = strlen(info->filtertext); + l+=utf8_encode(info->filtertext+l, unicode, sizeof(info->filtertext)-1-l); + info->filtertext[l] = 0; + } + + info->populated = false; + return true; + } + else if (key == '/') + { + info->filtering = true; + info->filtertext[0] = 0; + + info->populated = false; + return true; + } + else + { + info->filtering = false; + return false; + } +} + void Menu_DownloadStuff_f (void) { emenu_t *menu; @@ -6140,6 +6341,7 @@ void Menu_DownloadStuff_f (void) menu->menu.persist = true; menu->predraw = MD_Download_UpdateStatus; + menu->key = MD_Download_Key; info->downloadablessequence = downloadablessequence; diff --git a/engine/client/m_items.c b/engine/client/m_items.c index 3ca167cf5..bcdeb8e4d 100644 --- a/engine/client/m_items.c +++ b/engine/client/m_items.c @@ -110,7 +110,8 @@ menuoption_t *M_NextSelectableItem(emenu_t *m, menuoption_t *old, qboolean wrap) //but we're lazy so we don't consider the next char. italic fonts are annoying like that. feel free to refudge it. void Draw_Hexen2BigFontString(int x, int y, const char *text) { - int c; + conchar_t *w, buffer[256]; + unsigned int codeflags, oldflags=CON_WHITEMASK, c; int sx, sy; mpic_t *p; p = R_RegisterShader ("gfx/menu/bigfont.lmp", SUF_2D, @@ -144,9 +145,45 @@ void Draw_Hexen2BigFontString(int x, int y, const char *text) p->defaulttextures->base = R_LoadHiResTexture("gfx/menu/bigfont.lmp", NULL, IF_PREMULTIPLYALPHA|IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP|IF_CLAMP); } - while(*text) + + COM_ParseFunString(oldflags, text, buffer, sizeof(buffer), false); + + for (w = buffer; *w; ) { - c = *text++; + w = Font_Decode(w, &codeflags, &c); + if (codeflags & CON_HIDDEN) + continue; + if (c >= 0xe020 && c <= 0xe07f) + c &= 0x00ff; //convert to quake glyph to unicode/ascii... + + if (codeflags != oldflags) + { + vec4_t rgba; + unsigned int col; + oldflags = codeflags; + + col = (codeflags&CON_FGMASK)>>CON_FGSHIFT; + rgba[0] = consolecolours[col].fr; + rgba[1] = consolecolours[col].fg; + rgba[2] = consolecolours[col].fb; + if(codeflags & CON_HALFALPHA) + rgba[3] = 0.5; + else + rgba[3] = 1; + if (vid.flags&VID_SRGBAWARE) + { + rgba[0] = M_SRGBToLinear(rgba[0], 1); + rgba[1] = M_SRGBToLinear(rgba[1], 1); + rgba[2] = M_SRGBToLinear(rgba[2], 1); + } + if (codeflags & CON_BLINKTEXT) + { + float a = (sin(realtime*3)+1)*0.3 + 0.4; + VectorScale(rgba, a, rgba); + } + R2D_ImageColours(rgba[0], rgba[1], rgba[2], rgba[3]); + } + if (c >= 'a' && c <= 'z') { sx = ((c-'a')%8)*20; @@ -197,6 +234,8 @@ void Draw_Hexen2BigFontString(int x, int y, const char *text) default: x+=20; break; } } + + R2D_ImageColours(1, 1, 1, 1); } #endif @@ -248,6 +287,8 @@ void Draw_BigFontString(int x, int y, const char *text) for (w = buffer; *w; ) { w = Font_Decode(w, &codeflags, &codepoint); + if (codeflags & CON_HIDDEN) + continue; if (codepoint >= 0xe020 && codepoint <= 0xe07f) codepoint &= 0x00ff; //convert to quake glyph to unicode/ascii... @@ -323,7 +364,7 @@ int dotofs; static void MenuTooltipChange(emenu_t *menu, const char *text) { - unsigned int MAX_CHARS=1024; + unsigned int MAX_CHARS=2048; menutooltip_t *mtt; if (menu->tooltip) { @@ -412,7 +453,7 @@ static qboolean M_MouseMoved(emenu_t *menu) { menu->selecteditem = option; if (menu->cursoritem) - menu->cursoritem->common.posy = menu->selecteditem->common.posy; + menu->cursoritem->common.posy = menu->selecteditem->common.posy + (menu->selecteditem->common.height-menu->cursoritem->common.height)/2; } menu->tooltiptime = realtime + 1; MenuTooltipChange(menu, menu->mouseitem->common.tooltip); @@ -765,7 +806,7 @@ static void MenuDrawItems(int xpos, int ypos, menuoption_t *option, emenu_t *men int y = ypos+option->common.posy; if (!option->edit.slim) - y += (16-8)/2; //fat ones are twice the height on account of the text box's borders. + y += (option->common.height-8)/2; //fat ones are twice the height on account of the text box's borders. Draw_FunStringWidth(x, y, option->edit.caption, option->edit.captionwidth, true, !menu->cursoritem && menu->selecteditem == option); x += option->edit.captionwidth + 3*8; @@ -923,6 +964,7 @@ menutext_t *MC_AddWhiteText(emenu_t *menu, int lhs, int rhs, int y, const char * n->common.posx = lhs; n->common.posy = y; n->common.width = (rhs)?rhs-lhs:0; + n->common.height = 8; n->rightalign = rightalign; if (text) { @@ -1068,7 +1110,7 @@ menupicture_t *MC_AddCenterPicture(emenu_t *menu, int y, int height, char *picna return MC_AddPicture(menu, x, y, width, height, picname); } -menuoption_t *MC_AddCursorSmall(emenu_t *menu, menuresel_t *reselection, int x, int y) +menuoption_t *MC_AddCursorSmall(emenu_t *menu, menuresel_t *reselection, int x) { menuoption_t *n = Z_Malloc(sizeof(menucommon_t)); if (reselection) @@ -1076,7 +1118,11 @@ menuoption_t *MC_AddCursorSmall(emenu_t *menu, menuresel_t *reselection, int x, n->common.type = mt_menucursor; n->common.iszone = true; n->common.posx = x; - n->common.posy = y; + n->common.height = 8; + if (!menu->selecteditem) + n->common.posy = -8; + else + n->common.posy = menu->selecteditem->common.posy + (menu->selecteditem->common.height-n->common.height)/2; n->common.next = menu->options; menu->options = (menuoption_t *)n; @@ -1090,7 +1136,7 @@ menuoption_t *MC_AddCursorSmall(emenu_t *menu, menuresel_t *reselection, int x, if (sel->common.posx == menu->reselection->x && sel->common.posy == menu->reselection->y) { menu->selecteditem = sel; - n->common.posy = sel->common.posy; + n->common.posy = sel->common.posy + (sel->common.height-n->common.height)/2; break; } sel = M_NextSelectableItem(menu, sel, false); @@ -1182,7 +1228,7 @@ menuedit_t *MC_AddEdit(emenu_t *menu, int cx, int ex, int y, char *text, char *d n->common.posx = cx; n->common.posy = y; n->common.width = ex-cx+(17)*8; - n->common.height = n->slim?8:16; + n->common.height = 8 + (n->slim?0:(8*2)); //the 8bit artwork has 8*8 borders - only 4 pixels of that contains any actual data, but replacement images don't stick to that. so just treat them as the full +/- 8 extents here. n->modified = true; n->captionwidth = ex-cx; n->caption = (char *)(n+1); @@ -2035,13 +2081,14 @@ menuoption_t *M_PrevSelectableItem(emenu_t *m, menuoption_t *old, qboolean wrap) void M_Complex_Key(emenu_t *currentmenu, int key, int unicode) { + menuoption_t *mi; if (!currentmenu) return; //erm... M_CheckMouseMove(currentmenu); if (currentmenu->key) - if (currentmenu->key(key, currentmenu)) + if (currentmenu->key(currentmenu, key, unicode)) return; if (currentmenu->selecteditem && currentmenu->selecteditem->common.type == mt_custom && (key == K_DOWNARROW || key == K_KP_DOWNARROW || key == K_GP_DPAD_DOWN || key == K_GP_LEFT_THUMB_DOWN || key == K_GP_DIAMOND_CONFIRM || key == K_GP_DIAMOND_ALTCONFIRM || key == K_UPARROW || key == K_KP_UPARROW || key == K_GP_DPAD_UP || key == K_GP_LEFT_THUMB_UP || key == K_TAB || key == K_MWHEELUP || key == K_MWHEELDOWN || key == K_PGUP || key == K_PGDN)) @@ -2154,34 +2201,37 @@ void M_Complex_Key(emenu_t *currentmenu, int key, int unicode) S_LocalSound ("misc/menu1.wav"); if (currentmenu->cursoritem) - currentmenu->cursoritem->common.posy = currentmenu->selecteditem->common.posy; + currentmenu->cursoritem->common.posy = currentmenu->selecteditem->common.posy + (currentmenu->selecteditem->common.height-currentmenu->cursoritem->common.height)/2; } break; case K_MWHEELUP: case K_MWHEELDOWN: - if (currentmenu->mouseitem) + mi = currentmenu->mouseitem; + if (!mi) + mi = currentmenu->selecteditem; + if (mi) { qboolean handled = false; - switch(currentmenu->mouseitem->common.type) + switch(mi->common.type) { case mt_combo: - if (mousecursor_x >= currentmenu->xpos + currentmenu->mouseitem->common.posx + currentmenu->mouseitem->combo.captionwidth + 3*8) + if (mousecursor_x >= currentmenu->xpos + mi->common.posx + mi->combo.captionwidth + 3*8) { - MC_Combo_Key(¤tmenu->mouseitem->combo, key); + MC_Combo_Key(&mi->combo, key); handled = true; } break; case mt_checkbox: - if (mousecursor_x >= currentmenu->xpos + currentmenu->mouseitem->common.posx + currentmenu->mouseitem->check.textwidth + 3*8) + if (mousecursor_x >= currentmenu->xpos + mi->common.posx + mi->check.textwidth + 3*8) { - MC_CheckBox_Key(¤tmenu->mouseitem->check, currentmenu, key); + MC_CheckBox_Key(&mi->check, currentmenu, key); handled = true; } break; case mt_custom: - if (currentmenu->mouseitem->custom.key) - handled = currentmenu->mouseitem->custom.key(¤tmenu->mouseitem->custom, currentmenu, key, unicode); + if (mi->custom.key) + handled = mi->custom.key(&mi->custom, currentmenu, key, unicode); break; default: break; @@ -2189,9 +2239,9 @@ void M_Complex_Key(emenu_t *currentmenu, int key, int unicode) if (handled) { - currentmenu->selecteditem = currentmenu->mouseitem; + currentmenu->selecteditem = mi; if (currentmenu->cursoritem) - currentmenu->cursoritem->common.posy = currentmenu->selecteditem->common.posy; + currentmenu->cursoritem->common.posy = currentmenu->selecteditem->common.posy + (currentmenu->selecteditem->common.height-currentmenu->cursoritem->common.height)/2; break; } else if (key == K_MWHEELUP) @@ -2220,7 +2270,7 @@ void M_Complex_Key(emenu_t *currentmenu, int key, int unicode) S_LocalSound ("misc/menu1.wav"); if (currentmenu->cursoritem) - currentmenu->cursoritem->common.posy = currentmenu->selecteditem->common.posy; + currentmenu->cursoritem->common.posy = currentmenu->selecteditem->common.posy + (currentmenu->selecteditem->common.height-currentmenu->cursoritem->common.height)/2; break; //require a double-click when selecting... } //fall through @@ -2280,7 +2330,7 @@ void M_Complex_Key(emenu_t *currentmenu, int key, int unicode) -qboolean MC_Main_Key (int key, emenu_t *menu) //here purly to restart demos. +qboolean MC_Main_Key (emenu_t *menu, int key, unsigned int unicode) //here purly to restart demos. { if (key == K_ESCAPE || key == K_GP_BACK || key == K_GP_DIAMOND_CANCEL || key == K_MOUSE2) { @@ -2621,13 +2671,13 @@ void M_Menu_Main_f (void) if (b) { mainm->selecteditem = (menuoption_t*)b; - mainm->cursoritem->common.posy = mainm->selecteditem->common.posy; + mainm->cursoritem->common.posy = mainm->selecteditem->common.posy + (mainm->selecteditem->common.height-mainm->cursoritem->common.height)/2; } } int MC_AddBulk(struct emenu_s *menu, menuresel_t *resel, menubulk_t *bulk, int xstart, int xtextend, int y) { - int selectedy = y, last_y = y; + int last_y = y; menuoption_t *selected = NULL; while (bulk) @@ -2755,9 +2805,7 @@ int MC_AddBulk(struct emenu_s *menu, menuresel_t *resel, menubulk_t *bulk, int x } menu->selecteditem = selected; - if (selected) - selectedy = selected->common.posy; - menu->cursoritem = (menuoption_t*)MC_AddCursorSmall(menu, resel, xtextend + 8, selectedy); + menu->cursoritem = (menuoption_t*)MC_AddCursorSmall(menu, resel, xtextend + 8); return y; } #endif diff --git a/engine/client/m_master.c b/engine/client/m_master.c index 4b35486d1..da2f5fca8 100644 --- a/engine/client/m_master.c +++ b/engine/client/m_master.c @@ -765,7 +765,7 @@ static void SL_PostDraw (emenu_t *menu) } } } -static qboolean SL_Key (int key, emenu_t *menu) +static qboolean SL_Key (emenu_t *menu, int key, unsigned int unicode) { serverlist_t *info = (serverlist_t*)(menu + 1); @@ -1370,7 +1370,7 @@ static void M_QuickConnect_PreDraw(emenu_t *menu) } } -static qboolean M_QuickConnect_Key (int key, emenu_t *menu) +static qboolean M_QuickConnect_Key (emenu_t *menu, int key, unsigned int unicode) { return false; } diff --git a/engine/client/m_mp3.c b/engine/client/m_mp3.c index 75a2d4fe8..382ec8ba8 100644 --- a/engine/client/m_mp3.c +++ b/engine/client/m_mp3.c @@ -1183,7 +1183,7 @@ void Com_CompleateOSFileName(char *name) strcpy(name, compleatenamename); } -qboolean M_Media_Key (int key, emenu_t *menu) +qboolean M_Media_Key (emenu_t *menu, int key, unsigned int unicode) { int dir; if (key == K_ESCAPE || key == K_GP_BACK || key == K_MOUSE2) diff --git a/engine/client/m_multi.c b/engine/client/m_multi.c index cf99a56d1..0bbb3fecc 100644 --- a/engine/client/m_multi.c +++ b/engine/client/m_multi.c @@ -439,6 +439,7 @@ void M_Menu_Setup_f (void) menucustom_t *ci; menubutton_t *b; static menuresel_t resel; + int y; #ifdef Q2CLIENT if (M_GameType() == MGT_QUAKE2) //quake2 main menu. @@ -469,7 +470,7 @@ void M_Menu_Setup_f (void) cu->draw = MSetupQ2_TransDraw; cu->key = MSetupQ2_ChangeSkin; - menu->cursoritem = (menuoption_t*)MC_AddCursorSmall(menu, &resel, 54, 32); + menu->cursoritem = (menuoption_t*)MC_AddCursorSmall(menu, &resel, 54); return; } #endif @@ -480,9 +481,10 @@ void M_Menu_Setup_f (void) // MC_AddPicture(menu, 72, 32, Draw_CachePic ("gfx/mp_menu.lmp") ); + y = 40; menu->selecteditem = (menuoption_t*) - (info->nameedit = MC_AddEdit(menu, 64, 160, 40, "Your name", name.string)); - (info->teamedit = MC_AddEdit(menu, 64, 160, 56, "Your team", team.string)); + (info->nameedit = MC_AddEdit(menu, 64, 160, y, "Your name", name.string)); y+= info->nameedit->common.height; + (info->teamedit = MC_AddEdit(menu, 64, 160, y, "Your team", team.string)); y+= info->teamedit->common.height; #ifdef HEXEN2 info->ticlass = -1; if (M_GameType() == MGT_HEXEN2) @@ -497,7 +499,11 @@ void M_Menu_Setup_f (void) NULL }; cvar_t *pc = Cvar_Get("cl_playerclass", "1", CVAR_USERINFO|CVAR_ARCHIVE, "Hexen2"); - (info->classedit = MC_AddCombo(menu, 64, 160, 72, "Your class", (const char **)classnames, pc->ival-1)); + (info->classedit = MC_AddCombo(menu, 64, 160, y, "Your class", (const char **)classnames, pc->ival-1)); y+= info->classedit->common.height; + + //trim options if the artwork is missing. + while (info->classedit->numoptions && !COM_FCheckExists(va("gfx/menu/netp%i.lmp", info->classedit->numoptions))) + info->classedit->numoptions--; } else #endif @@ -505,21 +511,25 @@ void M_Menu_Setup_f (void) MC_AddPicture(menu, 16, 4, 32, 144, "gfx/qplaque.lmp"); MC_AddCenterPicture(menu, 4, 24, "gfx/p_multi.lmp"); - (info->skinedit = MC_AddEdit(menu, 64, 160, 72, "Your skin", skin.string)); + (info->skinedit = MC_AddEdit(menu, 64, 160, y, "Your skin", skin.string)); y+= info->skinedit->common.height; } - ci = MC_AddCustom(menu, 172+32, 88, NULL, 0, NULL); + ci = MC_AddCustom(menu, 172+32, y, NULL, 0, NULL); ci->draw = MSetup_TransDraw; ci->key = NULL; - MC_AddCommand(menu, 64, 160, 96, "Top colour", SetupMenuColour); - MC_AddCommand(menu, 64, 160, 120, "Lower colour", SetupMenuColour); + MC_AddCommand(menu, 64, 160, y+8, "Top colour", SetupMenuColour); + MC_AddCommand(menu, 64, 160, y+32, "Lower colour", SetupMenuColour); + y+= 16; + y+=4; b = MC_AddConsoleCommand(menu, 64, 204, 168, "Network Settings", "menu_network\n"); b->common.tooltip = "Change network and client prediction settings."; + y += b->common.height; b = MC_AddConsoleCommand(menu, 64, 204, 176, "Teamplay Settings", "menu_teamplay\n"); b->common.tooltip = "Change teamplay macro settings."; - menu->cursoritem = (menuoption_t*)MC_AddCursorSmall(menu, &resel, 54, 32); + y += b->common.height; + menu->cursoritem = (menuoption_t*)MC_AddCursorSmall(menu, &resel, 54); info->lowercolour = bottomcolor.value; @@ -637,14 +647,19 @@ static int QDECL M_Menu_GameOptions_AddMap(const char *fname, qofs_t fsize, time struct mapopts_s *ctx = parm; size_t i; char *ext; + char trimmedfname[MAX_QPATH]; if (Q_strncasecmp(fname, "maps/", 5)) return true; //o.O fname += 5; - if (*fname == 'b' && *fname == '_') + if (fname[0] == 'b' && fname[1] == '_') return true; //stoopid ammo boxes. ext = strrchr(fname, '.'); - if (ext && !strcmp(ext, ".bsp")) - *ext = 0; + if (ext && !strcmp(ext, ".bsp") && ext-fnamecount; i++) if (!Q_strcasecmp(ctx->maps[i], fname)) @@ -761,8 +776,21 @@ void M_Menu_GameOptions_f (void) MC_AddCommand (menu, 64, 160, y, "Start game", MultiBeginGame);y+=16; y+=4; - info->hostnameedit = MC_AddEdit (menu, 64, 160, y, "Hostname", name.string);y+=16; + info->hostnameedit = MC_AddEdit (menu, 64, 160, y, "Hostname", name.string);y+=info->hostnameedit->common.height; info->publicgame = MC_AddCombo (menu, 64, 160, y, "Public", publicoptions, bound(0, sv_public.ival+1, 4));y+=8; +#if !defined(FTE_TARGET_WEB) && defined(HAVE_DTLS) + { + static const char *encoptions[] = + { + "Disabled", + "Accept", + "Request", + "Require", + NULL + }; + MC_AddCvarCombo (menu, 64, 160, y, "DTLS Encryption", &net_enable_dtls, encoptions, NULL);y+=8; + } +#endif y+=4; for (players = 0; players < sizeof(numplayeroptions)/ sizeof(numplayeroptions[0]); players++) @@ -782,7 +810,21 @@ void M_Menu_GameOptions_f (void) info->fraglimit = MC_AddCombo (menu, 64, 160, y, "Frag Limit", (const char **)fraglimitoptions, fraglimit.value/10);y+=8; y+=8; - M_Menu_GameOptions_AddMap((mgt == MGT_QUAKE2)?"maps/base1":"maps/start", 0, 0, &mapopts, NULL); + //populate it with an appropriate default. its a shame it won't change with the deathmatch/coop options + switch(mgt) + { + case MGT_QUAKE2: + M_Menu_GameOptions_AddMap("maps/base1.bsp", 0, 0, &mapopts, NULL); + break; + case MGT_HEXEN2: + M_Menu_GameOptions_AddMap("maps/demo1.bsp", 0, 0, &mapopts, NULL); + break; + case MGT_QUAKE1: + M_Menu_GameOptions_AddMap("maps/start.bsp", 0, 0, &mapopts, NULL); + break; + default: + break; + } COM_EnumerateFiles("maps/*.bsp", M_Menu_GameOptions_AddMap, &mapopts); COM_EnumerateFiles("maps/*.bsp.gz", M_Menu_GameOptions_AddMap, &mapopts); COM_EnumerateFiles("maps/*.bsp.xz", M_Menu_GameOptions_AddMap, &mapopts); @@ -1099,6 +1141,15 @@ void M_Menu_Network_f (void) "Smooth Demos Only", NULL }; +#ifdef HAVE_DTLS + static const char *dtlsopts[] = { + "Disabled", + "Accept", + "Request", + "Require", + NULL + }; +#endif static const char *smoothingvalues[] = {"0", "1", "2", NULL}; extern cvar_t cl_download_csprogs, cl_download_redirection, requiredownloads, cl_solid_players; extern cvar_t cl_predict_players, cl_lerp_smooth, cl_predict_extrapolate; @@ -1111,8 +1162,11 @@ void M_Menu_Network_f (void) MB_EDITCVARSLIM("Network FPS", "cl_netfps", "Sets ammount of FPS used to communicate with server (sent and received)"), MB_EDITCVARSLIM("Rate", "rate", "Maximum bytes per second that the server should send to the client"), MB_EDITCVARSLIM("Download Rate", "drate", "Maximum bytes per second that the server should send maps and demos to the client"), +#ifdef HAVE_DTLS + MB_COMBOCVAR("DTLS Encryption", net_enable_dtls, dtlsopts, NULL, "Use this to avoid snooping. Certificates will be pinned."), +#endif MB_SPACING(4), - MB_CHECKBOXCVARTIP("Require Download", requiredownloads, 0, "Ignore downloaded content sent to the client and connect immediately"), + MB_CHECKBOXCVARTIP("Wait for Downloads", requiredownloads, 0, "Ignore downloaded content sent to the client and connect immediately"), MB_CHECKBOXCVARTIP("Redirect Download", cl_download_redirection, 0, "Whether the client will ignore download redirection from servers"), MB_CHECKBOXCVARTIP("Download CSQC", cl_download_csprogs, 0, "Whether to allow the client to download CSQC (client-side QuakeC) progs from servers"), MB_SPACING(4), diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 69d268f38..2aff2df5e 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -421,17 +421,20 @@ void M_Menu_Options_f (void) MC_AddFrameEnd(menu, framey); menu->predraw = M_Options_Predraw; - o = NULL; - if (!o && !m_preset_chosen.ival) - o = M_FindButton(menu, "fps_preset\n"); -#ifdef PACKAGEMANAGER - if (!o && PM_AreSourcesNew(false)) - o = M_FindButton(menu, "menu_download\n"); -#endif - if (o) + if (!resel.x) { - menu->selecteditem = (menuoption_t*)o; - menu->cursoritem->common.posy = o->common.posy; + o = NULL; + if (!o && !m_preset_chosen.ival) + o = M_FindButton(menu, "fps_preset\n"); +#ifdef PACKAGEMANAGER + if (!o && PM_AreSourcesNew(false)) + o = M_FindButton(menu, "menu_download\n"); +#endif + if (o) + { + menu->selecteditem = (menuoption_t*)o; + menu->cursoritem->common.posy = o->common.posy + (o->common.height-menu->cursoritem->common.height)/2; + } } } @@ -446,7 +449,7 @@ typedef struct { soundcardinfo_t *card; } audiomenuinfo_t; -qboolean M_Audio_Key (int key, struct emenu_s *menu) +qboolean M_Audio_Key (struct emenu_s *menu, int key, unsigned int unicode) { int i; audiomenuinfo_t *info = menu->data; @@ -1078,17 +1081,18 @@ const char *presetexec[] = struct { const char *name; + qboolean dorestart; const char *desc; const char *settings; } builtinpresets[] = { - { "hdr", + { "hdr", true, "Don't let colour depth stop you!", "set vid_srgb 2\n" "set r_hdr_irisadaptation 1\n" }, - { "shib", + { "shib", true, "Performance optimisations for large/detailed maps.", "set r_temporalscenecache 1\n" //the main speedup. @@ -1096,7 +1100,7 @@ struct "set sv_autooffload 1\n" //Needs polish still. "set gl_pbolightmaps 1\n" //FIXME: this needs to be the default eventually. }, - { "dm", + { "dm", false, "Various settings to make you more competitive." "set cl_yieldcpu 0\n" @@ -1110,20 +1114,42 @@ struct "set sys_clockprecision 1\n" //windows kinda sucks otherwise #endif }, - { "qw", - "Enable QuakeWorld-isms, for better gameplay.", + { "qw", false, + "Enable QuakeWorld physics, for better gameplay.", "set sv_nqplayerphysics 0\n" "set sv_gameplayfix_multiplethinks 1\n" + "cvarreset pm_bunnyfriction\n" + "cvarreset pm_edgefriction\n" + "cvarreset pm_slidefix\n" + "cvarreset pm_slidyslopes\n" }, - { "nq" - "Disable QuakeWorld-isms, for nq mod compat.", + { "hybridphysics", false, + "Tweak QuakeWorld player physics to feel like nq physics, while still supporting prediction.", + + "set sv_nqplayerphysics 0\n" + "set sv_gameplayfix_multiplethinks 1\n" + "set pm_bunnyfriction 1\n" //don't need bunnyspeedcap with this. + "set pm_edgefriction 2\n" //forces traceline instead of tracebox, to match nq (applies earlier, making it more aggressive) + "set pm_slidefix 1\n" //smoother running down slopes + "set pm_slidyslopes 1\n" //*sigh* + "set pm_noround 1\n" //lame + "set sv_maxtic 0\n" //fixed tick rates. + }, + { "nq", false, + "Disable QuakeWorld physics, for nq mod compat.", "set sv_nqplayerphysics 1\n" "set sv_gameplayfix_multiplethinks 0\n" + //*also* set these, in case they use nqplayerphysics 2 after, which should give better hints. + "set pm_bunnyfriction 1\n" //don't need bunnyspeedcap with this. + "set pm_edgefriction 2\n" //forces traceline instead of tracebox, to match nq (applies earlier, making it more aggressive) + "set pm_slidefix 1\n" //smoother running down slopes + "set pm_slidyslopes 1\n" //*sigh* + "set pm_noround 1\n" //lame }, - { "dp", + { "dp", false, "Reconfigures FTE to mimic DP for compat reasons.", "if $server then echo Be sure to restart your server\n" @@ -1153,7 +1179,7 @@ struct // "sv_listen_dp 1\nsv_listen_nq 0\nsv_listen_qw 0\ncl_loopbackprotocol dpp7\ndpcompat_nopreparse 1\n" }, - { "tenebrae", + { "tenebrae", true, "Reconfigures FTE to mimic Tenebrae for compat/style reasons.", //for the luls. combine with the tenebrae mod for maximum effect. "fps_preset nq\n" @@ -1169,7 +1195,7 @@ struct "set r_nolerp 1\n" //well, that matches tenebrae. for the luls, right? }, - { "timedemo", + { "timedemo", false, "Reconfigure some stuff to get through timedemos really fast. Some people might consider this cheating.", //some extra things to pwn timedemos. "fps_preset fast\n" @@ -1321,7 +1347,7 @@ static void M_Menu_Preset_Predraw(emenu_t *menu) } } M_Menu_ApplyGravity(menu->options); - menu->cursoritem->common.posy = menu->selecteditem->common.posy; //make sure it shows the right place still + menu->cursoritem->common.posy = menu->selecteditem->common.posy + (menu->selecteditem->common.height-menu->cursoritem->common.height)/2; if (forcereload) Cbuf_InsertText("\nfs_restart\nvid_reload\n", RESTRICT_LOCAL, true); @@ -1404,7 +1430,7 @@ void M_Menu_Preset_f (void) if (presetoption[item]) { menu->selecteditem = presetoption[item]; - menu->cursoritem->common.posy = menu->selecteditem->common.posy; + menu->cursoritem->common.posy = menu->selecteditem->common.posy + (menu->selecteditem->common.height-menu->cursoritem->common.height)/2; } //so they can actually see the preset they're picking. @@ -1457,7 +1483,7 @@ void FPS_Preset_f (void) { if (!stricmp(builtinpresets[i].name, arg)) { - if (doreload) + if (doreload && builtinpresets[i].dorestart) Cbuf_InsertText("\nfs_restart\nvid_reload\n", RESTRICT_LOCAL, false); Cbuf_InsertText(builtinpresets[i].settings, RESTRICT_LOCAL, false); return; @@ -4419,7 +4445,8 @@ void M_Menu_Mods_f (void) MC_AddFrameStart(menu, 32); for (i = 0; i<1 || Mods_GetMod(i); i++) { - c = MC_AddCustom(menu, 64, 32+i*8, menu->data, i, NULL); + struct modlist_s *mod = Mods_GetMod(i); + c = MC_AddCustom(menu, 64, 32+i*8, menu->data, i, (mod&&mod->manifest)?mod->manifest->basedir:NULL); // if (!menu->selecteditem) // menu->selecteditem = (menuoption_t*)c; c->common.height = 8; diff --git a/engine/client/m_script.c b/engine/client/m_script.c index 897ef714c..6356e7649 100644 --- a/engine/client/m_script.c +++ b/engine/client/m_script.c @@ -43,7 +43,7 @@ void M_Script_Option (emenu_t *menu, char *optionvalue, qboolean isexplicit) } } Cmd_TokenizeString(buf, false, false); - Cmd_ExpandString(scriptname, buf, sizeof(buf), &expandlevel, true, true); + Cmd_ExpandString(scriptname, buf, sizeof(buf), &expandlevel, true, false, false); //and execute it as-is Cbuf_AddText(buf, execlevel); @@ -57,7 +57,7 @@ void M_Script_Remove (emenu_t *menu) M_Script_Option(menu, "cancel", false); } -qboolean M_Script_Key (int key, emenu_t *menu) +qboolean M_Script_Key (struct emenu_s *menu, int key, unsigned int unicode) { if (menu->selecteditem && menu->selecteditem->common.type == mt_edit) return false; diff --git a/engine/client/menu.c b/engine/client/menu.c index 1015cbc17..864590465 100644 --- a/engine/client/menu.c +++ b/engine/client/menu.c @@ -1081,7 +1081,7 @@ void M_Help_Draw (emenu_t *m) R2D_ScalePic ((vid.width-width)/2, (vid.height-height)/2, width, height, pic); } } -qboolean M_Help_Key (int key, emenu_t *m) +qboolean M_Help_Key (struct emenu_s *m, int key, unsigned int unicode) { switch (key) { diff --git a/engine/client/menu.h b/engine/client/menu.h index 3fd4a8abb..1a95e537b 100644 --- a/engine/client/menu.h +++ b/engine/client/menu.h @@ -329,7 +329,7 @@ struct emenu_s { void (*reset) (struct emenu_s *); //called after a video mode switch / shader reload. void (*remove) (struct emenu_s *); - qboolean (*key) (int key, struct emenu_s *); //true if key was handled + qboolean (*key) (struct emenu_s *, int key, unsigned int unicode); //true if key was handled void (*predraw) (struct emenu_s *); void (*postdraw) (struct emenu_s *); menuoption_t *options; @@ -353,7 +353,7 @@ menupicture_t *MC_AddPicture(emenu_t *menu, int x, int y, int width, int height, menupicture_t *MC_AddSelectablePicture(emenu_t *menu, int x, int y, int height, char *picname); menupicture_t *MC_AddCenterPicture(emenu_t *menu, int y, int height, char *picname); menupicture_t *MC_AddCursor(emenu_t *menu, menuresel_t *resel, int x, int y); -menuoption_t *MC_AddCursorSmall(emenu_t *menu, menuresel_t *reselection, int x, int y); +menuoption_t *MC_AddCursorSmall(emenu_t *menu, menuresel_t *reselection, int x); menuslider_t *MC_AddSlider(emenu_t *menu, int tx, int sx, int y, const char *text, cvar_t *var, float min, float max, float delta); menucheck_t *MC_AddCheckBox(emenu_t *menu, int tx, int cx, int y, const char *text, cvar_t *var, int cvarbitmask); menucheck_t *MC_AddCheckBoxFunc(emenu_t *menu, int tx, int cx, int y, const char *text, qboolean (*func) (menucheck_t *option, emenu_t *menu, chk_set_t set), int bits); diff --git a/engine/client/merged.h b/engine/client/merged.h index b62c0c147..257c8f766 100644 --- a/engine/client/merged.h +++ b/engine/client/merged.h @@ -184,6 +184,8 @@ void Mod_ParseEntities(struct model_s *mod); void Mod_LoadMapArchive(struct model_s *mod, void *archivedata, size_t archivesize); extern void Mod_ClearAll (void); extern void Mod_Purge (enum mod_purge_e type); +extern void Mod_SetModifier (const char *modifier); +extern char mod_modifier[]; extern qboolean Mod_PurgeModel (struct model_s *mod, enum mod_purge_e ptype); extern struct model_s *Mod_FindName (const char *name); //find without loading. needload should be set. extern struct model_s *Mod_ForName (const char *name, enum mlverbosity_e verbosity); //finds+loads diff --git a/engine/client/net_master.c b/engine/client/net_master.c index ca2a7f8c9..8409a1ba2 100644 --- a/engine/client/net_master.c +++ b/engine/client/net_master.c @@ -5,6 +5,7 @@ clientside master queries and server ping/polls #include "quakedef.h" #include "cl_master.h" +#include "netinc.h" #define FAVOURITESFILE "favourites.txt" @@ -351,7 +352,7 @@ static void SV_Master_SingleHeartbeat(net_masterlist_t *master) //Note that Darkplaces clients are supposed to be able to use the qw protocol, so it should be okay to heartbeat as Darkplaces-Quake here even when not doing any nq protocols. //either way, custom protocols tend to require ftemaster/dpmaster so we want to heartbeat regardless. #if defined(NQPROT) && !defined(QUAKETC) - if (sv_listen_dp.value || sv_listen_nq.value || strcasecmp(com_protocolname.string, "FTE-Quake")) +// if (sv_listen_dp.value || sv_listen_nq.value || strcasecmp(com_protocolname.string, "FTE-Quake")) #endif { //darkplaces here refers to the master server protocol, rather than the game protocol @@ -374,7 +375,7 @@ static void SV_Master_SingleHeartbeat(net_masterlist_t *master) if (sv_reportheartbeats.ival != 2 || !master->announced) { COM_Parse(master->cv.string); - Con_TPrintf ("Sending heartbeat to %s (%s)\n", NET_AdrToString (adr, sizeof(adr), na), com_token); + Con_TPrintf (S_COLOR_GRAY"Sending heartbeat to %s (%s)\n", NET_AdrToString (adr, sizeof(adr), na), com_token); } master->announced = true; } @@ -413,7 +414,7 @@ static void SV_Master_SingleHeartbeat(net_masterlist_t *master) struct thr_res { qboolean success; - netadr_t na[8]; + netadr_t na[MAX_MASTER_ADDRESSES]; char str[1]; //trailing }; static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) @@ -496,7 +497,10 @@ static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) { //tcp masters require a route if (NET_AddrIsReliable(na)) - NET_EnsureRoute(svs.sockets, master->cv.name, master->cv.string, na); + { + struct dtlspeercred_s cred = {master->cv.string}; + NET_EnsureRoute(svs.sockets, master->cv.name, &cred, na, true); + } //q2+qw masters are given a ping to verify that they're still up switch (master->protocol) @@ -523,6 +527,39 @@ static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) } Z_Free(work); } + +#if defined(SUPPORT_ICE) +struct stunheader_s +{ + unsigned short msgtype; + unsigned short msglen; + unsigned int magiccookie; + unsigned int transactid[3]; +}; +static void SV_Master_Worker_Resolved_Broker(void *ctx, void *data, size_t a, size_t b) +{ + struct thr_res *work = data; + if (svs.sockets && work->na[0].type != NA_INVALID) //something resolved... + { + struct stunheader_s msg = {htons(1), htons(sizeof(msg)-20), BigLong(0x2112a442), {42,42,42}}; + + //randomize the transaction id to avoid poisoning. + if (!Sys_RandomBytes((qbyte*)msg.transactid, sizeof(msg.transactid))) + { //FIXME: not really random enough to avoid hacks. oh well. + msg.transactid[0] = rand(); + msg.transactid[1] = rand(); + msg.transactid[2] = rand(); + } + + svs.sockets->srflx_tid[0] = msg.transactid[0]; + svs.sockets->srflx_tid[1] = msg.transactid[1]; + svs.sockets->srflx_tid[2] = msg.transactid[2]; + + NET_SendPacket(svs.sockets, sizeof(msg), &msg, &work->na[0]); + } + Z_Free(work); +} +#endif //worker thread static void SV_Master_Worker_Resolve(void *ctx, void *data, size_t a, size_t b) { @@ -536,13 +573,19 @@ static void SV_Master_Worker_Resolve(void *ctx, void *data, size_t a, size_t b) { str = COM_ParseOut(str, token, sizeof(token)); if (*token) - found += NET_StringToAdr2(token, 0, &work->na[found], MAX_MASTER_ADDRESSES-found, NULL); + found += NET_StringToAdr2(token, 0, &work->na[found], countof(work->na)-found, NULL); if (first && found) break; //if we found one by name, don't try any fallback ip addresses. first = false; } work->success = !!found; - COM_AddWork(WG_MAIN, SV_Master_Worker_Resolved, NULL, work, a, b); + +#if defined(SUPPORT_ICE) + if (a==~(size_t)0) + COM_AddWork(WG_MAIN, SV_Master_Worker_Resolved_Broker, NULL, work, a, b); + else +#endif + COM_AddWork(WG_MAIN, SV_Master_Worker_Resolved, NULL, work, a, b); } /* @@ -556,7 +599,7 @@ let it know we are alive, and log information void SV_Master_Heartbeat (void) { int i; - int interval = bound(90, sv_heartbeat_interval.ival, 600); + int interval = bound(85, sv_heartbeat_interval.ival, 600); if (sv_public.ival<=0 || SSV_IsSubServer()) return; @@ -601,6 +644,18 @@ void SV_Master_Heartbeat (void) else SV_Master_SingleHeartbeat(&net_masterlist[i]); } + +#if defined(SUPPORT_ICE) + if (*net_ice_broker.string) + { + const char *s = net_ice_broker.string; + struct thr_res *work = Z_Malloc(sizeof(*work) + strlen(s)); + if (!strncmp(s, "tls://", 6) || !strncmp(s, "tcp://", 6)) + s+=6; //ignore weird prefixes here + strcpy(work->str, s); + COM_AddWork(WG_MAIN, SV_Master_Worker_Resolve, NULL, work, ~(size_t)0, 0); + } +#endif } #ifdef HAVE_LEGACY @@ -2517,26 +2572,88 @@ void SListOptionChanged(serverinfo_t *newserver) } } +static qboolean MasterInfo_ReadProtocol(serverinfo_t *info, const char *infostring) +{ + char *token = Info_ValueForKey(infostring, "protocol"); + if (*token) + { + //read the protocol number + info->protocol = strtoul(token, &token, 0); + + //and try to figure out which filter it should be under. + info->special &= ~SS_PROTOCOLMASK; + if (*token) + { + while (*token) + { + if (*token == 'w') + info->special |= SS_QUAKEWORLD; + else if (*token == 'n' || *token == 'd') + info->special |= SS_NETQUAKE; + else if (*token == 'x') + info->special |= SS_QEPROT; + else + continue; + break; + } + } + else switch(info->protocol) + { + case PROTOCOL_VERSION_QW: info->special |= SS_QUAKEWORLD; break; +#ifdef NQPROT + case PROTOCOL_VERSION_NQ: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_H2: info->special |= SS_NETQUAKE; break; //erk + case PROTOCOL_VERSION_NEHD: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_FITZ: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_RMQ: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_DP5: info->special |= SS_NETQUAKE; break; //dp actually says 3... but hey, that's dp being WEIRD. + case PROTOCOL_VERSION_DP6: info->special |= SS_NETQUAKE; break; + case PROTOCOL_VERSION_DP7: info->special |= SS_NETQUAKE; break; + case NQ_NETCHAN_VERSION_QEX:info->special |= SS_QEPROT; break; + case NQ_NETCHAN_VERSION: +#endif + default: + if ((info->special&SS_PROTOCOLMASK) == SS_UNKNOWN) + { //guesses... + if (PROTOCOL_VERSION_Q2 >= info->protocol && info->protocol >= PROTOCOL_VERSION_Q2_MIN) + info->special |= SS_QUAKE2; //q2 has a range! + else if (info->protocol > 60) + info->special |= SS_QUAKE3; + else if (!strcmp(Info_ValueForKey(infostring, "gamename"), "DarkPlaces-Quake") || *Info_ValueForKey(infostring, "nqprotocol")) + info->special |= SS_NETQUAKE; + else + info->special |= SS_QUAKEWORLD; + } + break; + } + return true; + } + info->protocol = 0; + return false; +} + #ifdef WEBCLIENT static void MasterInfo_ProcessHTTPInfo(serverinfo_t *srv, const char *info) { char adrbuf[MAX_ADR_SIZE]; if (info && (!(srv->status & SRVSTATUS_ALIVE) || srv->ping == PING_UNKNOWN)) { - if (srv->adr.prot == NP_RTC_TLS || srv->adr.prot == NP_RTC_TCP) + if (srv->adr.prot != NP_DGRAM) { srv->sends = 0; //no point pinging it, it won't work. srv->ping = PING_UNKNOWN; srv->status |= SRVSTATUS_ALIVE; //or at least wouldn't have been reported this time around. } - else - srv->sends = 1; //no point pinging it, it won't work. + Q_strncpyz(srv->name, Info_ValueForKey(info, "hostname"), sizeof(srv->name)); Q_strncpyz(srv->gamedir, Info_ValueForKey(info, "modname"), sizeof(srv->gamedir)); Q_strncpyz(srv->map, Info_ValueForKey(info, "mapname"), sizeof(srv->map)); srv->players = atoi(Info_ValueForKey(info, "clients")); srv->maxplayers = atoi(Info_ValueForKey(info, "maxclients")); + if (!MasterInfo_ReadProtocol(srv, info)) + srv->special = (srv->special&~SS_PROTOCOLMASK)|SS_QUAKEWORLD; //assume its an older fteqw server. + srv->numbots = 0; srv->numhumans = srv->players - srv->numbots; srv->freeslots = srv->maxplayers - srv->players; @@ -2560,7 +2677,7 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl) char *el; serverinfo_t *info; char linebuffer[2048]; - char *brokerid; + const char *brokerid; char *infostring; netadr_t brokeradr; @@ -2607,12 +2724,19 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl) if (*s == '#') //hash is a comment, apparently. continue; - for (infostring = s; *infostring && *infostring != ' '; ) + for (infostring = s; *infostring && *infostring != ' ' && *infostring != '\t'; ) infostring++; - if (*infostring == ' ') - *infostring++ = 0; + if (*infostring == ' ' || *infostring == '\t') + { + *infostring++ = 0; //null terminate the address + while(*infostring == ' ' || *infostring == '\t') + infostring++; //skip over any whitespace... + if (*infostring != '\\') + infostring = NULL; //err... no. not an info string. probably a comment. + } else infostring = NULL; + if (!strncmp(s, "ice:///", 7) || !strncmp(s, "ices:///", 8) || !strncmp(s, "rtc:///", 7) || !strncmp(s, "rtcs:///", 8)) { brokerid = s+((s[4]==':')?7:6); @@ -2620,10 +2744,14 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl) if (!*brokerid) continue; //invalid... } + else if (*s == '/') + { + brokerid = s; + adr = brokeradr; + } else { - brokerid = ""; - if (!NET_StringToAdr(s, 80, &adr)) + if (!NET_StringToAdr2(s, 80, &adr, 1, &brokerid)) continue; } @@ -2644,8 +2772,8 @@ static void MasterInfo_ProcessHTTP(struct dl_download *dl) info->special = 0; if (protocoltype == MP_QUAKEWORLD) info->special |= SS_QUAKEWORLD; - else if (protocoltype == MP_DPMASTER) - info->special |= SS_GETINFO; + else if (protocoltype == MP_DPMASTER) //actually ftemaster... so assume fteqw servers not ftenq ones unless otherwise indicated. + info->special |= SS_QUAKEWORLD|SS_GETINFO; #if defined(Q2CLIENT) || defined(Q2SERVER) else if (protocoltype == MP_QUAKE2) info->special |= SS_QUAKE2; @@ -2885,18 +3013,18 @@ void MasterInfo_Refresh(qboolean doreset) Master_AddMaster("255.255.255.255:"STRINGIFY(PORT_Q3SERVER), MT_BCAST, MP_QUAKE3, "Nearby Quake3 UDP servers."); #endif - if (!fs_manifest->rtcbroker || !*fs_manifest->rtcbroker) + if (!*net_ice_broker.string) ; //nope, sorry, not configured. else { char *url; COM_Parse(com_protocolname.string); - if (!strncmp(fs_manifest->rtcbroker, "tls://", 6)) - url = va("https://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); - else if (!strncmp(fs_manifest->rtcbroker, "tcp://", 6)) - url = va("http://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); + if (!strncmp(net_ice_broker.string, "tls://", 6)) + url = va("https://%s/raw/%s", net_ice_broker.string+6, com_token); + else if (!strncmp(net_ice_broker.string, "tcp://", 6)) + url = va("http://%s/raw/%s", net_ice_broker.string+6, com_token); else - url = va("http://%s/raw/%s", fs_manifest->rtcbroker, com_token); + url = va("http://%s/raw/%s", net_ice_broker.string, com_token); Master_AddMasterHTTP(url, MT_MASTERHTTP, MP_DPMASTER, "Public Servers Potentially Behind A NAT."); } @@ -3245,65 +3373,23 @@ static int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolea else if (!strncmp(DISTRIBUTION, Info_ValueForKey(msg, "*version"), strlen(DISTRIBUTION))) info->special |= SS_FTESERVER; - info->protocol = strtoul(Info_ValueForKey(msg, "protocol"), &token, 0); - if (info->protocol) - { - switch(info->protocol) - { - case PROTOCOL_VERSION_QW: info->special |= SS_QUAKEWORLD; break; -#ifdef NQPROT - case PROTOCOL_VERSION_NQ: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_H2: info->special |= SS_NETQUAKE; break; //erk - case PROTOCOL_VERSION_NEHD: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_FITZ: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_RMQ: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_DP5: info->special |= SS_NETQUAKE; break; //dp actually says 3... but hey, that's dp being WEIRD. - case PROTOCOL_VERSION_DP6: info->special |= SS_NETQUAKE; break; - case PROTOCOL_VERSION_DP7: info->special |= SS_NETQUAKE; break; - case NQ_NETCHAN_VERSION_QEX:info->special |= SS_QEPROT; break; - case NQ_NETCHAN_VERSION: -#endif - default: - while (*token) - { - if (*token == 'w') - info->special |= SS_QUAKEWORLD; - else if (*token == 'n' || *token == 'd') - info->special |= SS_NETQUAKE; - else if (*token == 'x') - info->special |= SS_QEPROT; - else - continue; - break; - } - if ((info->special&SS_PROTOCOLMASK) == SS_UNKNOWN) - { //guesses... - if (PROTOCOL_VERSION_Q2 >= info->protocol && info->protocol >= PROTOCOL_VERSION_Q2_MIN) - info->special |= SS_QUAKE2; //q2 has a range! - else if (info->protocol > 60) - info->special |= SS_QUAKE3; - else if (!strcmp(Info_ValueForKey(msg, "gamename"), "DarkPlaces-Quake") || *Info_ValueForKey(msg, "nqprotocol")) - info->special |= SS_NETQUAKE; - else - info->special |= SS_QUAKEWORLD; - } - break; - } - } + if (!MasterInfo_ReadProtocol(info, msg)) + { //try and guess. #ifdef Q2CLIENT - else if (prototype == MP_QUAKE2) - info->special |= SS_QUAKE2; + if (prototype == MP_QUAKE2) + info->special |= SS_QUAKE2; #endif #ifdef Q3CLIENT - else if (prototype == MP_QUAKE3 || prototype == MP_DPMASTER/*if no protocol, assume q3 behaviours*/) - info->special |= SS_QUAKE3; + else if (prototype == MP_QUAKE3 || prototype == MP_DPMASTER/*if no protocol, assume q3 behaviours*/) + info->special |= SS_QUAKE3; #endif #ifdef NQPROT - else if (prototype == MP_NETQUAKE) - info->special |= SS_NETQUAKE; + else if (prototype == MP_NETQUAKE) + info->special |= SS_NETQUAKE; #endif - else - info->special |= SS_QUAKEWORLD; + else + info->special |= SS_QUAKEWORLD; + } if (favorite) //was specifically named, not retrieved from a master. info->special |= SS_FAVORITE; @@ -3782,16 +3868,19 @@ static void NetQ3_GlobalServers_Request(size_t masternum, int protocol, const ch const char *url; struct dl_download *dl; COM_Parse(com_protocolname.string); - if (!strncmp(fs_manifest->rtcbroker, "tls://", 6)) - url = va("https://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); - else if (!strncmp(fs_manifest->rtcbroker, "tcp://", 6)) - url = va("http://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); - else - url = va("http://%s/raw/%s", fs_manifest->rtcbroker, com_token); + if (*net_ice_broker.string) + { + if (!strncmp(net_ice_broker.string, "tls://", 6)) + url = va("https://%s/raw/%s", net_ice_broker.string+6, com_token); + else if (!strncmp(net_ice_broker.string, "tcp://", 6)) + url = va("http://%s/raw/%s", net_ice_broker.string+6, com_token); + else + url = va("http://%s/raw/%s", net_ice_broker.string, com_token); - dl = HTTP_CL_Get(url, NULL, MasterInfo_ProcessHTTP); - if (dl) - dl->isquery = true; + dl = HTTP_CL_Get(url, NULL, MasterInfo_ProcessHTTP); + if (dl) + dl->isquery = true; + } } #endif #if POLLTOTALSOCKETS>0 diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index a6b48299a..75cdb3ec5 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -793,7 +793,7 @@ static model_t *CSQC_GetModelForIndex(int index) else if (index < 0 && index > -MAX_CSMODELS) { if (!cl.model_csqcprecache[-index]) - cl.model_csqcprecache[-index] = Mod_ForName(Mod_FixName(cl.model_csqcname[-index], csqc_world.worldmodel->publicname), MLV_WARN); + cl.model_csqcprecache[-index] = Mod_ForName(Mod_FixName(cl.model_csqcname[-index], csqc_world.worldmodel?csqc_world.worldmodel->publicname:NULL), MLV_WARN); return cl.model_csqcprecache[-index]; } else @@ -2770,7 +2770,8 @@ static void QCBUILTIN PF_R_RenderScene(pubprogfuncs_t *prinst, struct globalvars { csqc_worldchanged = false; cl.worldmodel = r_worldentity.model = csqc_world.worldmodel; - FS_LoadMapPackFile(cl.worldmodel->name, cl.worldmodel->archive); + if (cl.worldmodel) + FS_LoadMapPackFile(cl.worldmodel->name, cl.worldmodel->archive); Surf_NewMap(csqc_world.worldmodel); CL_UpdateWindowTitle(); @@ -7208,7 +7209,7 @@ static struct { {"patch_getcp", PF_patch_getcp, 0}, {"patch_getmesh", PF_patch_getmesh, 0}, {"patch_create", PF_patch_create, 0}, -// {"patch_calculate", PF_patch_calculate, 0}, + {"patch_evaluate", PF_patch_evaluate, 0}, #endif #ifdef ENGINE_ROUTING @@ -7997,7 +7998,7 @@ static qboolean CSQC_ValidateMainCSProgs(void *file, size_t filesize, unsigned i } else { //FTE uses folded-md4. yeah, its broken but at least its still more awkward - if (LittleLong(Com_BlockChecksum(file, filesize)) != checksum) + if (LittleLong(CalcHashInt(&hash_md4, file, filesize)) != checksum) return false; } return true; @@ -8309,7 +8310,11 @@ qboolean CSQC_Init (qboolean anycsqc, const char *csprogsname, unsigned int chec } if (cl_nocsqc.value) + { + if (checksum || progssize) + Con_Printf(CON_WARNING"Server is using csqc, but its disabled via %s\n", cl_nocsqc.name); return false; + } if (cls.state == ca_disconnected) { @@ -8324,6 +8329,7 @@ qboolean CSQC_Init (qboolean anycsqc, const char *csprogsname, unsigned int chec movevars.stepdown = true; movevars.walljump = false;//(pm_walljump.value); movevars.slidyslopes = false;//(pm_slidyslopes.value!=0); + movevars.bunnyfriction = false; movevars.autobunny = false; //pm_autobunny.value!=0 movevars.watersinkspeed = 60;//*pm_watersinkspeed.string?pm_watersinkspeed.value:60; movevars.flyfriction = 4;//*pm_flyfriction.string?pm_flyfriction.value:4; @@ -8428,9 +8434,13 @@ qboolean CSQC_Init (qboolean anycsqc, const char *csprogsname, unsigned int chec csprogsnum = PR_LoadProgs(csqcprogs, csprogs_checkname); if (csprogsnum >= 0) Con_DPrintf("Loaded csprogs.dat\n"); + else if (csprogs_checksum || csprogs_checksize) + Con_Printf(CON_WARNING"Unable to load \"csprogsvers/%x.dat\"\n", csprogs_checksum); } - if (csqc_singlecheats || anycsqc) + if (csprogsnum >= 0 && !Q_strcasecmp(csprogs_checkname, "csaddon.dat")) + ; //using csaddon directly... map editor mode? + else if (csqc_singlecheats || anycsqc) { csaddonnum = PR_LoadProgs(csqcprogs, "csaddon.dat"); if (csaddonnum >= 0) @@ -8551,7 +8561,7 @@ qboolean CSQC_Init (qboolean anycsqc, const char *csprogsname, unsigned int chec csqc_world.physicstime = 0.1; - CSQC_RendererRestarted(); + CSQC_RendererRestarted(true); if (cls.state == ca_disconnected) CSQC_WorldLoaded(); @@ -8560,17 +8570,36 @@ qboolean CSQC_Init (qboolean anycsqc, const char *csprogsname, unsigned int chec return true; //success! } -void CSQC_RendererRestarted(void) +void CSQC_RendererRestarted(qboolean initing) { int i; if (!csqcprogs) return; - csqc_world.worldmodel = cl.worldmodel; - - for (i = 0; i < MAX_CSMODELS; i++) + if (initing) { - cl.model_csqcprecache[i] = NULL; + //called at startup + if (csqc_worldchanged) + { + csqc_worldchanged = false; + cl.worldmodel = r_worldentity.model = csqc_world.worldmodel; + if (cl.worldmodel) + FS_LoadMapPackFile(cl.worldmodel->name, cl.worldmodel->archive); + Surf_NewMap(csqc_world.worldmodel); + CL_UpdateWindowTitle(); + + World_RBE_Shutdown(&csqc_world); + World_RBE_Start(&csqc_world); + } + } + else + { //FIXME: this might be awkward in the purecsqc case. + csqc_world.worldmodel = cl.worldmodel; + + for (i = 0; i < MAX_CSMODELS; i++) + { + cl.model_csqcprecache[i] = NULL; + } } //FIXME: registered shaders @@ -9704,13 +9733,29 @@ int CSQC_StartSound(int entnum, int channel, char *soundname, vec3_t pos, float return false; } -void CSQC_GetEntityOrigin(unsigned int csqcent, float *out) +qboolean CSQC_GetEntityOrigin(unsigned int csqcent, float *out) { wedict_t *ent; if (!csqcprogs) - return; + return false; ent = WEDICT_NUM_UB(csqcprogs, csqcent); VectorCopy(ent->v->origin, out); + return true; +} +qboolean CSQC_GetSSQCEntityOrigin(unsigned int ssqcent, float *out) +{ + csqcedict_t *ent; + if (csqcprogs && ssqcent < maxcsqcentities) + { + ent = csqcent[ssqcent]; + if (ent) + { + if (out) + VectorCopy(ent->v->origin, out); + return true; + } + } + return false; } void CSQC_ParseEntities(qboolean sized) @@ -9723,7 +9768,23 @@ void CSQC_ParseEntities(qboolean sized) qboolean removeflag; if (!csqcprogs) - Host_EndGame("CSQC needs to be initialized for this server.\n"); + { + const char *fname = va("csprogsvers/%x.dat", (unsigned int)strtoul(InfoBuf_ValueForKey(&cl.serverinfo, "*csprogs"), NULL, 0)); + const char *msg; + if (cl_nocsqc.value) + msg = "blocked by cl_nocsqc.\n"; + else if (!COM_FCheckExists(fname)) + { + extern cvar_t cl_downloads, cl_download_csprogs; + if (!cl_downloads.ival || !cl_download_csprogs.ival) + msg = "downloading blocked by cl_downloads or cl_download_csprogs.\n"; + else + msg = "unable to download.\n"; + } + else + msg = "not initialised.\n"; + Host_EndGame("%s required, but %s", fname, msg); + } if (!csqcg.CSQC_Ent_Update || !csqcg.self) Host_EndGame("CSQC has no CSQC_Ent_Update function\n"); @@ -9782,7 +9843,9 @@ void CSQC_ParseEntities(qboolean sized) CSQC_EntityCheck(entnum); - if (cl_csqcdebug.ival) + if (cl_shownet.ival == 3) + Con_Printf("%3i: Remove %i\n", MSG_GetReadCount(), entnum); + else if (cl_csqcdebug.ival) Con_Printf("Remove %i\n", entnum); ent = csqcent[entnum]; @@ -9828,15 +9891,26 @@ void CSQC_ParseEntities(qboolean sized) G_FLOAT(OFS_PARM0) = true; - if (cl_csqcdebug.ival) + if (cl_shownet.ival == 3) + Con_Printf("%3i: Added %i (%i)\n", MSG_GetReadCount(), entnum, packetsize); + else if (cl_csqcdebug.ival) Con_Printf("Add %i\n", entnum); } else { G_FLOAT(OFS_PARM0) = false; - if (cl_csqcdebug.ival) + if (cl_shownet.ival == 3) + Con_Printf("%3i: Update %i (%i)\n", MSG_GetReadCount(), entnum, packetsize); + else if (cl_csqcdebug.ival) Con_Printf("Update %i\n", entnum); } +#ifdef QUAKESTATS + if (entnum-1 < cl.allocated_client_slots && cls.findtrack && cl.players[entnum-1].stats[STAT_HEALTH] > 0) + { //FIXME: is this still needed with the autotrack stuff? + Cam_Lock(&cl.playerview[0], entnum-1); + cls.findtrack = false; + } +#endif *csqcg.self = EDICT_TO_PROG(csqcprogs, (void*)ent); csqc_mayread = true; diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index e89109309..1e58159cb 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -105,6 +105,7 @@ struct { char facename[MAX_OSPATH]; float scale; //poop int outline; //argh + unsigned int fontflags; //erk int sizes; int size[FONT_SIZES]; struct font_s *font[FONT_SIZES]; @@ -233,7 +234,7 @@ void PR_ReloadFonts(qboolean reload) { //otherwise load it. for (j = 0; j < fontslot[i].sizes; j++) { - fontslot[i].font[j] = Font_LoadFont(fontslot[i].facename, fontslot[i].size[j], fontslot[i].scale, fontslot[i].outline); + fontslot[i].font[j] = Font_LoadFont(fontslot[i].facename, fontslot[i].size[j], fontslot[i].scale, fontslot[i].outline, fontslot[i].fontflags); } } } @@ -333,7 +334,7 @@ void QCBUILTIN PF_CL_loadfont (pubprogfuncs_t *prinst, struct globalvars_s *pr_g if (qrenderer > QR_NONE) { for (i = 0; i < fontslot[slotnum].sizes; i++) - fontslot[slotnum].font[i] = Font_LoadFont(facename, fontslot[slotnum].size[i], fontslot[slotnum].scale, fontslot[slotnum].outline); + fontslot[slotnum].font[i] = Font_LoadFont(facename, fontslot[slotnum].size[i], fontslot[slotnum].scale, fontslot[slotnum].outline, fontslot[slotnum].fontflags); } G_FLOAT(OFS_RETURN) = slotnum; @@ -342,7 +343,7 @@ void QCBUILTIN PF_CL_loadfont (pubprogfuncs_t *prinst, struct globalvars_s *pr_g #ifdef HAVE_LEGACY void CL_LoadFont_f(void) { - extern cvar_t r_font_postprocess_outline; + extern cvar_t r_font_postprocess_outline, r_font_postprocess_mono; //console command for compat with dp/debug. if (Cmd_Argc() == 1) { @@ -418,6 +419,8 @@ void CL_LoadFont_f(void) fontslot[slotnum].scale = 1; fontslot[slotnum].sizes = 0; fontslot[slotnum].outline = r_font_postprocess_outline.ival; //locked in at definition, so different fonts can have different settings even with vid_reload going on. + fontslot[slotnum].fontflags = 0 | + (r_font_postprocess_mono.ival?FONT_MONO:0); } if (!*facename) return; @@ -437,6 +440,14 @@ void CL_LoadFont_f(void) fontslot[slotnum].outline = atoi(Cmd_Argv(sizenum++)); continue; } + if (!strcmp(a, "mono")) + { + if (atoi(Cmd_Argv(sizenum++))) + fontslot[slotnum].fontflags |= FONT_MONO; + else + fontslot[slotnum].fontflags &= ~FONT_MONO; + continue; + } if (!strcmp(a, "blur")) { //fontslot[slotnum].blur = atoi(Cmd_Argv(sizenum++)); @@ -471,7 +482,7 @@ void CL_LoadFont_f(void) if (qrenderer > QR_NONE) { for (i = 0; i < fontslot[slotnum].sizes; i++) - fontslot[slotnum].font[i] = Font_LoadFont(facename, fontslot[slotnum].size[i], fontslot[slotnum].scale, fontslot[slotnum].outline); + fontslot[slotnum].font[i] = Font_LoadFont(facename, fontslot[slotnum].size[i], fontslot[slotnum].scale, fontslot[slotnum].outline, fontslot[slotnum].fontflags); } //FIXME: slotnum0==default is problematic. diff --git a/engine/client/r_2d.c b/engine/client/r_2d.c index 5564f4134..8348da40b 100644 --- a/engine/client/r_2d.c +++ b/engine/client/r_2d.c @@ -56,7 +56,7 @@ struct extern cvar_t scr_conalpha; extern cvar_t gl_conback; extern cvar_t gl_font, con_textfont; -extern cvar_t r_font_postprocess_outline; +extern cvar_t r_font_postprocess_outline, r_font_postprocess_mono; extern cvar_t gl_screenangle; extern cvar_t vid_minsize; extern cvar_t vid_conautoscale; @@ -167,6 +167,7 @@ void R2D_Shutdown(void) Cvar_Unhook(&con_textfont); Cvar_Unhook(&gl_font); Cvar_Unhook(&r_font_postprocess_outline); + Cvar_Unhook(&r_font_postprocess_mono); Cvar_Unhook(&vid_conautoscale); Cvar_Unhook(&gl_screenangle); Cvar_Unhook(&vid_conheight); @@ -444,6 +445,7 @@ void R2D_Init(void) Cvar_Hook(&con_textfont, R2D_Font_Callback); Cvar_Hook(&gl_font, R2D_Font_Callback); Cvar_Hook(&r_font_postprocess_outline, R2D_Font_Callback); + Cvar_Hook(&r_font_postprocess_mono, R2D_Font_Callback); Cvar_Hook(&vid_conautoscale, R2D_Conautoscale_Callback); Cvar_Hook(&gl_screenangle, R2D_ScreenAngle_Callback); Cvar_Hook(&vid_conheight, R2D_Conheight_Callback); @@ -1137,6 +1139,7 @@ void R2D_Font_Changed(void) { float tsize; const char *con_font_name = con_textfont.string; + unsigned int flags; if (!con_textsize.modified) return; if (!*con_font_name) @@ -1170,6 +1173,10 @@ void R2D_Font_Changed(void) if (qrenderer == QR_NONE) return; + flags = 0; + if (r_font_postprocess_mono.ival) + flags |= FONT_MONO; + if (!strcmp(gl_font.string, "?")) { #ifndef AVAIL_FREETYPE @@ -1184,9 +1191,9 @@ void R2D_Font_Changed(void) LOGFONTW lf = {0}; CHOOSEFONTW cf = {sizeof(cf)}; extern HWND mainwindow; - font_default = Font_LoadFont("", 8, 1, r_font_postprocess_outline.ival); + font_default = Font_LoadFont("", 8, 1, r_font_postprocess_outline.ival, flags); if (tsize != 8) - font_console = Font_LoadFont("", tsize, 1, r_font_postprocess_outline.ival); + font_console = Font_LoadFont("", tsize, 1, r_font_postprocess_outline.ival, flags); if (!font_console) font_console = font_default; @@ -1230,19 +1237,19 @@ void R2D_Font_Changed(void) } if (COM_FCheckExists("fonts/qfont.kfont")) - font_menu = Font_LoadFont("qfont", 20, 1, r_font_postprocess_outline.ival); + font_menu = Font_LoadFont("qfont", 20, 1, r_font_postprocess_outline.ival, flags); else font_menu = NULL; - font_default = Font_LoadFont(gl_font.string, 8, 1, r_font_postprocess_outline.ival); + font_default = Font_LoadFont(gl_font.string, 8, 1, r_font_postprocess_outline.ival, flags); if (!font_default && *gl_font.string) - font_default = Font_LoadFont("", 8, 1, r_font_postprocess_outline.ival); + font_default = Font_LoadFont("", 8, 1, r_font_postprocess_outline.ival, flags); if (tsize != 8 || strcmp(gl_font.string, con_font_name)) { - font_console = Font_LoadFont(con_font_name, tsize, 1, r_font_postprocess_outline.ival); + font_console = Font_LoadFont(con_font_name, tsize, 1, r_font_postprocess_outline.ival, flags); if (!font_console) - font_console = Font_LoadFont("", tsize, 1, r_font_postprocess_outline.ival); + font_console = Font_LoadFont("", tsize, 1, r_font_postprocess_outline.ival, flags); } if (!font_console) font_console = font_default; diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c index dbc8426a6..0ad032fcd 100644 --- a/engine/client/r_surf.c +++ b/engine/client/r_surf.c @@ -4098,7 +4098,10 @@ TRACE(("dbg: Surf_NewMap: tp\n")); void Surf_PreNewMap(void) { + extern cvar_t gl_specular; + r_loadbumpmapping = r_deluxemapping || r_glsl_offsetmapping.ival; + r_loadbumpmapping |= gl_specular.value>0; #ifdef RTLIGHTS r_loadbumpmapping |= r_shadow_realtime_world.ival || r_shadow_realtime_dlight.ival; #endif diff --git a/engine/client/render.h b/engine/client/render.h index 2b75767c6..64c58320f 100644 --- a/engine/client/render.h +++ b/engine/client/render.h @@ -753,6 +753,7 @@ enum { RSPEED_PALETTEFLASHES, RSPEED_2D, RSPEED_SERVER, + RSPEED_AUDIO, RSPEED_SETUP, RSPEED_SUBMIT, RSPEED_PRESENT, diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 0f65ad489..7417374c5 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -117,6 +117,7 @@ cvar_t gl_shadeq1_name = CVARD ("gl_shadeq1_name", "*", "Rename all surfac extern cvar_t r_vertexlight; extern cvar_t r_forceprogramify; extern cvar_t r_glsl_precache; +extern cvar_t r_halfrate; extern cvar_t dpcompat_nopremulpics; #ifdef PSKMODELS cvar_t dpcompat_psa_ungroup = CVAR ("dpcompat_psa_ungroup", "0"); @@ -375,7 +376,7 @@ cvar_t r_tessellation = CVARAFD ("r_tessellation", "0", "gl_ati_truform", cvar_t gl_ati_truform_type = CVAR ("gl_ati_truform_type", "1"); cvar_t r_tessellation_level = CVAR ("r_tessellation_level", "5"); cvar_t gl_blend2d = CVAR ("gl_blend2d", "1"); -cvar_t gl_blendsprites = CVARD ("gl_blendsprites", "0", "Specifies how sprites are blended.\n0: Alpha tested.\n1: Premultiplied blend.\n2: Additive blend."); +cvar_t gl_blendsprites = CVARFD ("gl_blendsprites", "0", CVAR_SHADERSYSTEM, "Specifies how sprites are blended.\n0: Alpha tested.\n1: Premultiplied blend.\n2: Additive blend."); cvar_t r_deluxemapping_cvar = CVARAFD ("r_deluxemapping", "1", "r_glsl_deluxemapping", CVAR_ARCHIVE|CVAR_RENDERERLATCH, "Enables bumpmapping based upon precomputed light directions.\n0=off\n1=use if available\n2=auto-generate (if possible)"); cvar_t mod_loadsurfenvmaps = CVARD ("r_loadsurfenvmaps", "1", "Load local reflection environment-maps, where available. These are normally defined via env_cubemap entities dotted around the place."); @@ -457,6 +458,7 @@ cvar_t gl_texturemode2d = CVARFCD("gl_texturemode2d", "GL_LINEAR", "Specifies how 2d images are sampled. format is a 3-tupple "); cvar_t r_font_linear = CVARF("r_font_linear", "1", CVAR_ARCHIVE); cvar_t r_font_postprocess_outline = CVARFD("r_font_postprocess_outline", "0", 0, "Controls the number of pixels of dark borders to use around fonts."); +cvar_t r_font_postprocess_mono = CVARFD("r_font_postprocess_mono", "0", 0, "Disables anti-aliasing on fonts."); #if defined(HAVE_LEGACY) && defined(AVAIL_FREETYPE) cvar_t dpcompat_smallerfonts = CVARFD("dpcompat_smallerfonts", "0", 0, "Mimics DP's behaviour of using a smaller font size than was actually requested."); @@ -983,6 +985,7 @@ void Renderer_Init(void) Cvar_Register (&gl_texturemode2d, GLRENDEREROPTIONS); Cvar_Register (&r_font_linear, GLRENDEREROPTIONS); Cvar_Register (&r_font_postprocess_outline, GLRENDEREROPTIONS); + Cvar_Register (&r_font_postprocess_mono, GLRENDEREROPTIONS); #if defined(HAVE_LEGACY) && defined(AVAIL_FREETYPE) Cvar_Register (&dpcompat_smallerfonts, GLRENDEREROPTIONS); #endif @@ -1020,6 +1023,7 @@ void Renderer_Init(void) Cvar_Register (&r_forceprogramify, GLRENDEREROPTIONS); Cvar_Register (&r_glsl_precache, GLRENDEREROPTIONS); + Cvar_Register (&r_halfrate, GRAPHICALNICETIES); #ifdef HAVE_LEGACY Cvar_Register (&dpcompat_nopremulpics, GLRENDEREROPTIONS); #endif @@ -1807,6 +1811,8 @@ TRACE(("dbg: R_ApplyRenderer: starting on client state\n")); #endif if (cl.worldmodel) { + int wmidx = 0; + model_t *oldwm = cl.worldmodel; cl.worldmodel = NULL; CL_ClearEntityLists(); //shouldn't really be needed, but we're paranoid @@ -1819,6 +1825,9 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n")); TRACE(("dbg: R_ApplyRenderer: reloading model %s\n", cl.model_name[i])); + if (oldwm == cl.model_precache[i]) + wmidx = i; + #ifdef Q2CLIENT //skip vweps if (cls.protocol == CP_QUAKE2 && *cl.model_name[i] == '#') cl.model_precache[i] = NULL; @@ -1846,14 +1855,19 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n")); if (!cl.model_csqcname[i][0]) break; + if (oldwm == cl.model_csqcprecache[i]) + wmidx = -i; + cl.model_csqcprecache[i] = NULL; TRACE(("dbg: R_ApplyRenderer: reloading csqc model %s\n", cl.model_csqcname[i])); cl.model_csqcprecache[i] = Mod_ForName (Mod_FixName(cl.model_csqcname[i], cl.model_name[1]), MLV_SILENT); } -#endif - //fixme: worldmodel could be ssqc or csqc. - cl.worldmodel = cl.model_precache[1]; + if (wmidx < 0) + cl.worldmodel = cl.model_csqcprecache[-wmidx]; + else +#endif + cl.worldmodel = cl.model_precache[wmidx]; if (cl.worldmodel && cl.worldmodel->loadstate == MLS_LOADING) COM_WorkerPartialSync(cl.worldmodel, &cl.worldmodel->loadstate, MLS_LOADING); @@ -1861,14 +1875,14 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n")); TRACE(("dbg: R_ApplyRenderer: done the models\n")); if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED) { -// Con_Printf ("\nThe required model file '%s' could not be found.\n\n", cl.model_name[i]); -// Con_Printf ("You may need to download or purchase a client pack in order to play on this server.\n\n"); +// Con_Printf ("\nThe required model file '%s' could not be found.\n\n", cl.model_name[i]); +// Con_Printf ("You may need to download or purchase a client pack in order to play on this server.\n\n"); - CL_Disconnect ("Worldmodel missing after video reload"); + CL_Disconnect ("Worldmodel missing after video reload"); - if (newr) - memcpy(¤trendererstate, newr, sizeof(currentrendererstate)); - return true; + if (newr) + memcpy(¤trendererstate, newr, sizeof(currentrendererstate)); + return true; } TRACE(("dbg: R_ApplyRenderer: checking any wad textures\n")); @@ -1891,7 +1905,7 @@ TRACE(("dbg: R_ApplyRenderer: efrags\n")); #endif #ifdef CSQC_DAT Shader_DoReload(); - CSQC_RendererRestarted(); + CSQC_RendererRestarted(false); #endif #ifdef MENU_DAT MP_RendererRestarted(); diff --git a/engine/client/sbar.c b/engine/client/sbar.c index 88e57134f..d4e5c9fdb 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -29,20 +29,22 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. extern cvar_t *hud_tracking_show; extern cvar_t *hud_miniscores_show; -cvar_t scr_scoreboard_drawtitle = CVARD("scr_scoreboard_drawtitle", "1", "Wastes screen space when looking at the scoreboard."); -cvar_t scr_scoreboard_forcecolors = CVARD("scr_scoreboard_forcecolors", "0", "Makes the scoreboard colours obey enemycolor/teamcolor rules."); //damn americans -cvar_t scr_scoreboard_newstyle = CVARD("scr_scoreboard_newstyle", "1", "Display team colours and stuff in a style popularised by Electro. Looks more modern, but might not quite fit classic huds."); // New scoreboard style ported from Electro, by Molgrum -cvar_t scr_scoreboard_showfrags = CVARD("scr_scoreboard_showfrags", "0", "Display kills+deaths+teamkills, as determined by fragfile.dat-based conprint parsing. These may be inaccurate if you join mid-game."); -cvar_t scr_scoreboard_showflags = CVARD("scr_scoreboard_showflags", "2", "Display flag caps+touches on the scoreboard, where our fragfile.dat supports them.\n0: off\n1: on\n2: on only if someone appears to have interacted with a flag."); -cvar_t scr_scoreboard_fillalpha = CVARD("scr_scoreboard_fillalpha", "0.7", "Transparency amount for newstyle scoreboard."); -cvar_t scr_scoreboard_backgroundalpha = CVARD("scr_scoreboard_backgroundalpha", "0.5", "Further multiplier for the background alphas."); -cvar_t scr_scoreboard_teamscores = CVARD("scr_scoreboard_teamscores", "1", "Makes +showscores act as +showteamscores. Because reasons."); -cvar_t scr_scoreboard_teamsort = CVARD("scr_scoreboard_teamsort", "0", "On the scoreboard, sort players by their team BEFORE their personal score."); -cvar_t scr_scoreboard_titleseperator = CVAR("scr_scoreboard_titleseperator", "1"); -cvar_t scr_scoreboard_showruleset = CVAR("scr_scoreboard_showruleset", "1"); -cvar_t sbar_teamstatus = CVARD("sbar_teamstatus", "1", "Display the last team say from each of your team members just above the sbar area."); +static cvar_t scr_scoreboard_drawtitle = CVARD("scr_scoreboard_drawtitle", "1", "Wastes screen space when looking at the scoreboard."); +static cvar_t scr_scoreboard_forcecolors = CVARD("scr_scoreboard_forcecolors", "0", "Makes the scoreboard colours obey enemycolor/teamcolor rules."); //damn americans +static cvar_t scr_scoreboard_newstyle = CVARD("scr_scoreboard_newstyle", "1", "Display team colours and stuff in a style popularised by Electro. Looks more modern, but might not quite fit classic huds."); // New scoreboard style ported from Electro, by Molgrum +static cvar_t scr_scoreboard_showfrags = CVARD("scr_scoreboard_showfrags", "0", "Display kills+deaths+teamkills, as determined by fragfile.dat-based conprint parsing. These may be inaccurate if you join mid-game."); +static cvar_t scr_scoreboard_showflags = CVARD("scr_scoreboard_showflags", "2", "Display flag caps+touches on the scoreboard, where our fragfile.dat supports them.\n0: off\n1: on\n2: on only if someone appears to have interacted with a flag."); +static cvar_t scr_scoreboard_fillalpha = CVARD("scr_scoreboard_fillalpha", "0.7", "Transparency amount for newstyle scoreboard."); +static cvar_t scr_scoreboard_backgroundalpha = CVARD("scr_scoreboard_backgroundalpha", "0.5", "Further multiplier for the background alphas."); +static cvar_t scr_scoreboard_teamscores = CVARD("scr_scoreboard_teamscores", "1", "Makes +showscores act as +showteamscores. Because reasons."); +static cvar_t scr_scoreboard_teamsort = CVARD("scr_scoreboard_teamsort", "0", "On the scoreboard, sort players by their team BEFORE their personal score."); +static cvar_t scr_scoreboard_titleseperator = CVAR("scr_scoreboard_titleseperator", "1"); +static cvar_t scr_scoreboard_showruleset = CVAR("scr_scoreboard_showruleset", "1"); +static cvar_t scr_scoreboard_afk = CVARD("scr_scoreboard_afk", "1", "Show 'afk' in the packetloss column when they're afk."); +static cvar_t scr_scoreboard_ping_status = CVARD("scr_scoreboard_ping_status", "25 50 100 150", "Threshholds required to switch ping display from green to white, yellow, megenta and red."); +static cvar_t sbar_teamstatus = CVARD("sbar_teamstatus", "1", "Display the last team say from each of your team members just above the sbar area."); -cvar_t cl_sbaralpha = CVARAFD("cl_sbaralpha", "0.75", "scr_sbaralpha", CVAR_ARCHIVE, "Specifies the transparency of the status bar. Only Takes effect when cl_sbar is set to 2."); //with premultiplied alpha, this needs to affect the RGB values too. +static cvar_t cl_sbaralpha = CVARAFD("cl_sbaralpha", "0.75", "scr_sbaralpha", CVAR_ARCHIVE, "Specifies the transparency of the status bar. Only Takes effect when cl_sbar is set to 2."); //with premultiplied alpha, this needs to affect the RGB values too. //=========================================== //rogue changed and added defines @@ -1165,6 +1167,8 @@ void Sbar_Init (void) Cvar_Register(&scr_scoreboard_showfrags, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_showflags, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_showruleset, "Scoreboard settings"); + Cvar_Register(&scr_scoreboard_afk, "Scoreboard settings"); + Cvar_Register(&scr_scoreboard_ping_status, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_fillalpha, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_backgroundalpha, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_teamscores, "Scoreboard settings"); @@ -1279,7 +1283,7 @@ void Draw_TinyString (float x, float y, const qbyte *str) if (!font_tiny) { - font_tiny = Font_LoadFont("gfx/tinyfont", 8, 1, 0); + font_tiny = Font_LoadFont("gfx/tinyfont", 8, 1, 0, 0); if (!font_tiny) return; } @@ -3385,7 +3389,16 @@ ping time frags name { \ int p = s->ping; \ if (p < 0 || p > 999) p = 999; \ - sprintf(num, "%4i", p); \ + if (p >= scr_scoreboard_ping_status.vec4[3] && scr_scoreboard_ping_status.vec4[3] && *scr_scoreboard_ping_status.string) \ + sprintf(num, S_COLOR_RED"%4i", p); \ + else if (p >= scr_scoreboard_ping_status.vec4[2] && scr_scoreboard_ping_status.vec4[2]) \ + sprintf(num, S_COLOR_MAGENTA"%4i", p); \ + else if (p >= scr_scoreboard_ping_status.vec4[1] && scr_scoreboard_ping_status.vec4[1]) \ + sprintf(num, S_COLOR_YELLOW"%4i", p); \ + else if (p >= scr_scoreboard_ping_status.vec4[0] || !scr_scoreboard_ping_status.vec4[0]) \ + sprintf(num, S_COLOR_WHITE"%4i", p); \ + else \ + sprintf(num, S_COLOR_GREEN"%4i", p); \ Draw_FunStringWidth(x, y, num, 4*8, false, false); \ },NOFILL) @@ -3397,9 +3410,14 @@ ping time frags name },NOFILL) #define COLUMN_TIME COLUMN(time, 4*8, \ { \ - total = realtime - s->realentertime; \ - minutes = (int)total/60; \ - sprintf (num, "%4i", minutes); \ + if (scr_scoreboard_afk.ival && s->chatstate&2) \ + sprintf (num, S_COLOR_RED"afk"); \ + else \ + { \ + total = realtime - s->realentertime; \ + minutes = (int)total/60; \ + sprintf (num, "%4i", minutes); \ + } \ Draw_FunStringWidth(x, y, num, 4*8, false, false); \ },NOFILL) #define COLUMN_FRAGS COLUMN(frags, 5*8, \ diff --git a/engine/client/screen.h b/engine/client/screen.h index d558ac3af..b4ff687a3 100644 --- a/engine/client/screen.h +++ b/engine/client/screen.h @@ -321,7 +321,8 @@ void Font_Init(void); void Font_Shutdown(void); int Font_RegisterTrackerImage(const char *image); //returns a unicode char value that can be used to embed the char within a line of text. qboolean Font_TrackerValid(unsigned int imid); -struct font_s *Font_LoadFont(const char *fontfilename, float height, float scale, int outline); +struct font_s *Font_LoadFont(const char *fontfilename, float height, float scale, int outline, unsigned int flags); +#define FONT_MONO 1 void Font_Free(struct font_s *f); void Font_BeginString(struct font_s *font, float vx, float vy, int *px, int *py); void Font_BeginScaledString(struct font_s *font, float vx, float vy, float szx, float szy, float *px, float *py); /*avoid using*/ diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index 85c186a0e..00817a4d0 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -3889,7 +3889,7 @@ static void S_UpdateCard(soundcardinfo_t *sc) if (ch->sfx && (ch->vol[0] || ch->vol[1]) ) { if (snd_show.ival > 1) - Con_Printf ("%i, %i %i %i %i %i %i %s\n", i, ch->vol[0], ch->vol[1], ch->vol[2], ch->vol[3], ch->vol[4], ch->vol[5], ch->sfx->name); + Con_Printf ("%i, %i/%i/%i/%i/%i/%i %s\n", i, ch->vol[0], ch->vol[1], ch->vol[2], ch->vol[3], ch->vol[4], ch->vol[5], ch->sfx->name); active++; } else if (ch->sfx) @@ -3961,11 +3961,12 @@ int S_GetMixerTime(soundcardinfo_t *sc) void S_Update (void) { soundcardinfo_t *sc; - + RSpeedMark(); S_LockMixer(); for (sc = sndcardinfo; sc; sc = sc->next) S_UpdateCard(sc); S_UnlockMixer(); + RSpeedEnd(RSPEED_AUDIO); } void S_ExtraUpdate (void) diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index cb0eaa496..1f4d6ca52 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -213,7 +213,7 @@ void Sys_Printf (char *fmt, ...) if (w >= 0xe000 && w < 0xe100) { /*not all quake chars are ascii compatible, so map those control chars to safe ones so we don't mess up anyone's xterm*/ - if ((w & 0x7f) > 0x20) + if ((w & 0x7f) >= 0x20) putc(w&0x7f, out); else if (w & 0x80) { @@ -993,10 +993,10 @@ dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) lib = NULL; if (!lib) lib = dlopen (name, RTLD_LOCAL|RTLD_LAZY); - if (!lib && !strstr(name, ".so")) - lib = dlopen (va("%s.so", name), RTLD_LOCAL|RTLD_LAZY); - if (!lib && !strstr(name, ".so") && !strncmp(name, "./", 2) && host_parms.binarydir) - lib = dlopen (va("%s%s.so", host_parms.binarydir, name+2), RTLD_LOCAL|RTLD_LAZY); + if (!lib && !strstr(name, ARCH_DL_POSTFIX)) + lib = dlopen (va("%s"ARCH_DL_POSTFIX, name), RTLD_LOCAL|RTLD_LAZY); + if (!lib && !strstr(name, ARCH_DL_POSTFIX) && !strncmp(name, "./", 2) && host_parms.binarydir) + lib = dlopen (va("%s%s"ARCH_DL_POSTFIX, host_parms.binarydir, name+2), RTLD_LOCAL|RTLD_LAZY); if (!lib) { Con_DLPrintf(2,"%s\n", dlerror()); @@ -1219,7 +1219,7 @@ static void DoSign(const char *fname, int signtype) } else if (f) { - hashfunc_t *h = (signtype==1)?&hash_sha256:&hash_sha512; + hashfunc_t *h = (signtype==1)?&hash_sha2_256:&hash_sha2_512; size_t l, ts = 0; void *ctx = alloca(h->contextsize); qbyte data[65536*16]; diff --git a/engine/client/sys_sdl.c b/engine/client/sys_sdl.c index 74e0a8736..a5c8a3be3 100644 --- a/engine/client/sys_sdl.c +++ b/engine/client/sys_sdl.c @@ -11,7 +11,7 @@ #ifndef WIN32 #include #include -#ifdef __unix__ +#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) //apple make everything painful. #include #endif #else @@ -729,7 +729,7 @@ static int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char * { Q_snprintfz(file, sizeof(file), "%s/%s", truepath, ent->d_name); - if (stat(file, &st) == 0) + if (stat(file, &st) == 0 || lstat(file, &st) == 0) { Q_snprintfz(file, sizeof(file), "%s%s%s", apath, ent->d_name, S_ISDIR(st.st_mode)?"/":""); @@ -740,8 +740,8 @@ static int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char * return false; } } - else - printf("Stat failed for \"%s\"\n", file); +// else +// printf("Stat failed for \"%s\"\n", file); } } } while(1); diff --git a/engine/client/view.c b/engine/client/view.c index 2db9d3aba..009d93a43 100644 --- a/engine/client/view.c +++ b/engine/client/view.c @@ -1512,20 +1512,32 @@ static int QDECL V_DepthSortTwoEntities(const void *p1,const void *p2) } void V_DepthSortEntities(float *vieworg) { - int i; + int i, j; vec3_t disp; for (i = 0; i < cl_numvisedicts; i++) { if (cl_visedicts[i].flags & RF_WEAPONMODEL) { //weapon models have their own extra matrix thing going on. don't mess up because of it. - cl_visedicts[i].angles[0] = 0; + //however, qsort is not stable so hide ordering in here so they still come out with the same ordering, at least with respect to each other. + cl_visedicts[i].angles[0] = -1-i; continue; } if (cl_visedicts[i].rtype == RT_MODEL && cl_visedicts[i].model && cl_visedicts[i].model->type == mod_brush) { - VectorAdd(cl_visedicts[i].model->maxs, cl_visedicts[i].model->mins, disp); - VectorMA(cl_visedicts[i].origin, 0.5, disp, disp); - VectorSubtract(disp, vieworg, disp); + if (1) + { //by nearest point. + for (j=0 ; j<3 ; j++) + { + disp[j] = vieworg[j] - cl_visedicts[i].origin[j]; + disp[j] -= bound(cl_visedicts[i].model->mins[j], disp[j], cl_visedicts[i].model->maxs[j]); + } + } + else + { //by midpoint... + VectorAdd(cl_visedicts[i].model->maxs, cl_visedicts[i].model->mins, disp); + VectorMA(cl_visedicts[i].origin, 0.5, disp, disp); + VectorSubtract(disp, vieworg, disp); + } } else { @@ -2181,7 +2193,7 @@ void R_DrawNameTags(void) } else #endif - if (w && w->progs && svs.gametype == GT_PROGS) + if (w && w->progs && w->progs->saveent) { int best = 0; float bestscore = 0, score = 0; diff --git a/engine/client/zqtp.c b/engine/client/zqtp.c index 1b898ba88..12c71119b 100644 --- a/engine/client/zqtp.c +++ b/engine/client/zqtp.c @@ -1830,7 +1830,7 @@ char *TP_LocationName (const vec3_t location) recursive = true; level = Cmd_ExecLevel; - Cmd_ExpandString (locdata[minnum].name, buf, sizeof(buf), &level, true, false); + Cmd_ExpandString (locdata[minnum].name, buf, sizeof(buf), &level, false, true, false); recursive = false; return buf; @@ -3427,7 +3427,7 @@ void TP_UpdateAutoStatus(void) vars.autoteamstatus_time = realtime + 3; level = tp_autostatus.restriction; - newstatus = Cmd_ExpandString(tp_autostatus.string, newstatusbuf, sizeof(newstatusbuf), &level, true, true); + newstatus = Cmd_ExpandString(tp_autostatus.string, newstatusbuf, sizeof(newstatusbuf), &level, false, true, true); newstatus = TP_ParseMacroString(newstatus); if (!strcmp(newstatus, vars.autoteamstatus)) @@ -3789,7 +3789,7 @@ void CL_Say (qboolean team, char *extra) { char buf[1024]; int level = Cmd_ExecLevel; - Cmd_ExpandString (cl_fakename.string, buf, sizeof(buf), &level, true, true); + Cmd_ExpandString (cl_fakename.string, buf, sizeof(buf), &level, false, true, true); strcpy (buf, TP_ParseMacroString (buf)); Q_snprintfz (sendtext, sizeof(sendtext), "\x0d%s: ", TP_ParseFunChars(buf)); } diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 21564c3a1..a26128c35 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -429,9 +429,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //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. //FIXME: we don't cache server certs - #ifndef MASTERONLY - #define HAVE_DTLS - #endif + #define HAVE_DTLS #endif #if defined(USE_SQLITE) || defined(USE_MYSQL) @@ -690,7 +688,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define ARCH_CPU_POSTFIX "x86" #elif defined(__powerpc__) || defined(__ppc__) #define ARCH_CPU_POSTFIX "ppc" - #elif defined(__aarch64__) + #elif defined(__aarch64__) || defined(__arm64__) #define ARCH_CPU_POSTFIX "arm64" #elif defined(__arm__) #ifdef __SOFTFP__ diff --git a/engine/common/cmd.c b/engine/common/cmd.c index c4cd01f94..34e8b1acb 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -150,7 +150,23 @@ static void Cmd_MacroList_f (void) } +static void Cmd_MacroCompletion_c(int argn, const char *partial, struct xcommandargcompletioncb_s *ctx) +{ + size_t i, len; + const char *end = partial; + if (*end++ != '$') + return; + if (*end == '{') + end++; + len = strlen(end); + for (i = 0; i < macro_count; i++) + { + if (len <= strlen(macro_commands[i].name)) + if (!strncmp(end, macro_commands[i].name, len)) + ctx->cb(va("${%s}", macro_commands[i].name), NULL, NULL, ctx); + } +} @@ -900,7 +916,7 @@ static void Cmd_Exec_f (void) Cbuf_InsertText (fs_manifest->defaultoverrides, level, false); #if defined(HAVE_LEGACY) && defined(HAVE_CLIENT) - if (l == 1914 && Com_BlockChecksum(f, l) == 0x2d7b72b9) + if (l == 1914 && CalcHashInt(&hash_md4, f, l) == 0x2d7b72b9) s = (char*)replacementq1binds; #endif } @@ -1026,7 +1042,7 @@ static void Cmd_Echo_f (void) Q_strncatz(text, "\n", sizeof(text)); //echo text is often quoted, so expand the text again now that we're no longer in quotes. - t = Cmd_ExpandString(text, extext, sizeof(extext), &level, !Cmd_IsInsecure()?true:false, true); + t = Cmd_ExpandString(text, extext, sizeof(extext), &level, false, !Cmd_IsInsecure()?true:false, true); #ifndef HAVE_CLIENT Con_Printf ("%s", t); @@ -1174,9 +1190,8 @@ static void Cmd_Alias_f (void) // check for overlap with a command if (Cmd_Exists (s)) { //commands always take precedence over aliases (so mods can't clobber 'quit' etc), so creating an alias with one of these names is stupid. always try to rename them. - if (Cmd_IsInsecure()) + if (Cmd_IsInsecure() && snprintf(cmd, sizeof(cmd), "%s_a", s) < sizeof(cmd)) { - snprintf(cmd, sizeof(cmd), "%s_a", s); if (Cmd_Exists (cmd)) { Con_Printf (S_COLOR_RED"Can't register alias, %s is a command\n", s); @@ -1195,9 +1210,8 @@ static void Cmd_Alias_f (void) { //aliases take precedence over cvars (while cvars can be set via 'set'), so user's choice. if (Cvar_FindVar (s)) { - if (Cmd_IsInsecure()) + if (Cmd_IsInsecure() && snprintf(cmd, sizeof(cmd), "%s_a", s) < sizeof(cmd)) { - snprintf(cmd, sizeof(cmd), "%s_a", s); Con_Printf (S_COLOR_RED"alias %s: renamed to %s due to cvar conflict\n", s, cmd); s = cmd; } @@ -1652,7 +1666,7 @@ static const char *Cmd_ExpandCvar(char *cvarterm, int maxaccesslevel, int *newac quotetype = 2; } else if (fixup-cvarterm > 2 && !strncmp(fixup-2, " !", 2)) - { //abort is not defined + { //abort if not defined pl = 2; quotetype = 3; } @@ -1684,8 +1698,9 @@ static const char *Cmd_ExpandCvar(char *cvarterm, int maxaccesslevel, int *newac else cvarname = cvarterm; - result = strtoul(cvarname, &t, 10); - if ((dpcompat_console.ival||fixval) && (*t == 0 || (*t == '-' && t[1] == 0))) //only expand $0 if its actually ${0} - this avoids conflicting with the $0 macro + if (!cvarname) + ; + else if ((result = strtoul(cvarname, &t, 10)), (dpcompat_console.ival||fixval) && (*t == 0 || (*t == '-' && t[1] == 0))) //only expand $0 if its actually ${0} - this avoids conflicting with the $0 macro { if (*t == '-') //pure number with a trailing minus means { //args starting after that. @@ -1755,7 +1770,7 @@ If not SERVERONLY, also expands $macro expressions Note: dest must point to a 1024 byte buffer ================ */ -char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accesslevel, qboolean expandcvars, qboolean expandmacros) +char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accesslevel, qboolean expandargs, qboolean expandcvars, qboolean expandmacros) { unsigned int c; char buf[255]; @@ -1763,7 +1778,7 @@ char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accessle int quotes = 0; const char *str; const char *bestvar; - int name_length, var_length; + int name_length, var_length, best_length; qboolean striptrailing; int maxaccesslevel = *accesslevel; @@ -1774,7 +1789,7 @@ char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accessle if (c == '"') quotes++; - if (c == '%' && !(quotes&1) && !dpcompat_console.ival) + if (c == '%' && !(quotes&1) && !dpcompat_console.ival && expandargs) { //QW262/ezquake does this. kinda annoying. char *end; if (data[1] == '%') @@ -1792,7 +1807,7 @@ char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accessle str = Cmd_Args(); data+=2; } - else if ((i=strtol(data+1, &end, 10))) + else if ((i=strtol(data+1, &end, 10)) || (end!=data+1&&(!*end||*end==' '||*end=='\t'))) { data = end; str = Cmd_Argv(i); @@ -1857,7 +1872,7 @@ char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accessle buf[0] = 0; buf[1] = 0; bestvar = NULL; - var_length = 0; + var_length = best_length = 0; while((c = *data)) { if (c < ' ' || c == '$') @@ -1868,15 +1883,15 @@ char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accessle buf[i++] = c; buf[i] = 0; if ((str = Cmd_ExpandCvar(buf+striptrailing, expandcvars?maxaccesslevel:-999, accesslevel, false, &var_length))) - bestvar = str; + bestvar = str, best_length=var_length; if (expandmacros && (str = TP_MacroString (buf+striptrailing, accesslevel, &var_length))) - bestvar = str; + bestvar = str, best_length=var_length; } if (bestvar) { str = bestvar; - name_length = var_length; + name_length = best_length; } else { @@ -1919,7 +1934,7 @@ char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accessle if (len >= destlen-1) break; } - }; + } dest[len] = 0; @@ -2498,8 +2513,9 @@ cmd_completion_t *Cmd_Complete(const char *partial, qboolean caseinsens) cvar_group_t *grp; cvar_t *cvar; - const char *sp; + const char *sp, *e; qboolean quoted = false; + int arg = 0; static cmd_completion_t c; @@ -2515,30 +2531,37 @@ cmd_completion_t *Cmd_Complete(const char *partial, qboolean caseinsens) c.partial = Z_StrDup(partial); c.caseinsens = caseinsens; - for (sp = partial; *sp; sp++) + len = 0; + for(e = partial;;) { - if (*sp == ' ' || *sp == '\t') + sp = e; //the start of where we're trying to complete... + while (*sp == ' ' || *sp == '\t') + sp++; //leading spaces are annoying... + e = COM_Parse(sp); + if (!arg && e) + len = e - partial; + if (e && (*e == ' ' || *e == '\t')) + { //there seems to be whitespace after it. + arg++; + while (*sp == ' ' || *sp == '\t') + sp++; + //try to handle quotes + if (*sp == '\\' && sp[1] == '\"') + { + sp+=2; + quoted = true; + } + else if (*sp == '\"') + { + sp++; + quoted = true; + } + else + quoted = false; + } + else break; } - len = sp - partial; - if (*sp) - { - while (*sp == ' ' || *sp == '\t') - sp++; - //try to handle quotes - if (*sp == '\\' && sp[1] == '\"') - { - sp+=2; - quoted = true; - } - else if (*sp == '\"') - { - sp++; - quoted = true; - } - } - else - sp = NULL; // if (len) { @@ -2547,7 +2570,7 @@ cmd_completion_t *Cmd_Complete(const char *partial, qboolean caseinsens) for (cmd=cmd_functions ; cmd ; cmd=cmd->next) if (!Q_strncasecmp (partial,cmd->name, len) && (!partial[len] || strlen(cmd->name) == len)) { - if (sp && cmd->argcompletion) + if (arg) { struct cmdargcompletion_ctx_s ctx; ctx.cb.cb = Cmd_Complete_CheckArg; @@ -2557,7 +2580,10 @@ cmd_completion_t *Cmd_Complete(const char *partial, qboolean caseinsens) ctx.res = &c; ctx.desc = cmd->description; ctx.quoted = quoted; - cmd->argcompletion(1, sp, &ctx.cb); + + Cmd_MacroCompletion_c(arg, sp, &ctx.cb); + if (cmd->argcompletion) + cmd->argcompletion(arg, sp, &ctx.cb); } else Cmd_Complete_Check(cmd->name, &c, cmd->description); @@ -2931,7 +2957,7 @@ void Cmd_ExecuteString (const char *text, int level) if (dpcompat_console.ival && !strncmp(text, "alias", 5) && (text[5] == ' ' || text[5] == '\t')) ; //certain commands don't get pre-expanded in dp. evil hack. quote them to pre-expand anyway. double evil. else - text = Cmd_ExpandString(text, dest, sizeof(dest), &level, true/*!Cmd_IsInsecure()?true:false*/, true); + text = Cmd_ExpandString(text, dest, sizeof(dest), &level, false, true/*!Cmd_IsInsecure()?true:false*/, true); Cmd_TokenizeString (text, (level == RESTRICT_LOCAL&&!dpcompat_console.ival)?true:false, false); // execute the command line @@ -2976,14 +3002,14 @@ void Cmd_ExecuteString (const char *text, int level) execlevel = level; } - Cbuf_InsertText ("\n", execlevel, false); - // if the alias value is a command or cvar and // the alias is called with parameters, add them //unless we're mimicing dp, or the alias has explicit expansions (or macros) in which case it can do its own damn args - { + if (dpcompat_console.ival) + { //defective double escaping. the following line should sum it up nicely... + //set foo 3; alias test "set foo 2; echo $foo==1"; set foo 1; test char *ignoringquoteswasstupid; - Cmd_ExpandString(a->value, dest, sizeof(dest), &execlevel, !Cmd_IsInsecure()?true:false, true); + Cmd_ExpandString(a->value, dest, sizeof(dest), &execlevel, true, !Cmd_IsInsecure()?true:false, true); for (ignoringquoteswasstupid = dest; *ignoringquoteswasstupid; ) { //double up dollars, to prevent expansion when its actually execed. if (*ignoringquoteswasstupid == '$') @@ -2993,9 +3019,17 @@ void Cmd_ExecuteString (const char *text, int level) } ignoringquoteswasstupid++; } - if ((a->restriction?a->restriction:rcon_level.ival) > execlevel) - return; } + else + { //more sane (and ezquake-like) + //set foo 3; alias test "set foo 2; echo $foo==2"; set foo 1; test + //alias test "echo Args were $qt${* q}$qt"; set foo 1; test Test Args Here + Cmd_ExpandString(a->value, dest, sizeof(dest), &execlevel, true, false, false); //expand args, but not other stuff. + } + if ((a->restriction?a->restriction:rcon_level.ival) > execlevel) + return; //we expanded something it wasn't meant to see. + + Cbuf_InsertText ("\n", execlevel, false); if (!dpcompat_console.ival) { if (Cmd_Argc() > 1 && (!strncmp(a->value, "cmd ", 4) || (!strchr(a->value, ' ') && !strchr(a->value, '\t') && @@ -4416,7 +4450,7 @@ void Cmd_Init (void) Cmd_AddCommandAD ("seta_calc", Cmd_set_f, Cmd_Set_c, "Sets the named cvar to the result of a (complex) expression. Also forces the archive flag so that the cvar will always be written into any saved configs."); Cmd_AddCommandD ("vstr", Cmd_Vstr_f, "Executes the string value of the cvar, much like if it were an alias. For compatibility with q3."); Cmd_AddCommandAD ("inc", Cvar_Inc_f, Cmd_Set_c, "Adds a value to the named cvar. Use a negative value if you wish to decrease the cvar's value."); - Cmd_AddCommand ("if", Cmd_if_f); + Cmd_AddCommandD ("if", Cmd_if_f, "For conditionally executing console commands."); Cmd_AddCommand ("cmdlist", Cmd_List_f); Cmd_AddCommand ("aliaslist", Cmd_AliasList_f); diff --git a/engine/common/cmd.h b/engine/common/cmd.h index 2c7e4d6a6..3b5c69039 100644 --- a/engine/common/cmd.h +++ b/engine/common/cmd.h @@ -251,7 +251,7 @@ void Cmd_MessageTrigger (char *message, int type); void Cmd_ShiftArgs (int ammount, qboolean expandstring); -char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accesslevel, qboolean expandcvars, qboolean expandmacros); +char *Cmd_ExpandString (const char *data, char *dest, int destlen, int *accesslevel, qboolean expandargs, qboolean expandcvars, qboolean expandmacros); qboolean If_EvaluateBoolean(const char *text, int restriction); extern cvar_t rcon_level; diff --git a/engine/common/com_mesh.c b/engine/common/com_mesh.c index 4a9c38607..275f9b4cf 100644 --- a/engine/common/com_mesh.c +++ b/engine/common/com_mesh.c @@ -1718,6 +1718,7 @@ qboolean Alias_GAliasBuildMesh(mesh_t *mesh, vbo_t **vbop, galiasinfo_t *inf, in bytecolours = !!inf->ofs_rgbaub; #endif mesh->st_array = inf->ofs_st_array; + mesh->lmst_array[0] = inf->ofs_lmst_array; //some formats allow for two. #endif mesh->trneighbors = inf->ofs_trineighbours; @@ -1831,6 +1832,7 @@ qboolean Alias_GAliasBuildMesh(mesh_t *mesh, vbo_t **vbop, galiasinfo_t *inf, in meshcache.vbo.indexcount = inf->numindexes; meshcache.vbo.vertcount = inf->numverts; meshcache.vbo.texcoord = inf->vbotexcoords; + meshcache.vbo.lmcoord[0] = inf->vbolmtexcoords; meshcache.vbo.coord = inf->vbo_skel_verts; memset(&meshcache.vbo.coord2, 0, sizeof(meshcache.vbo.coord2)); meshcache.vbo.normals = inf->vbo_skel_normals; @@ -1897,6 +1899,7 @@ qboolean Alias_GAliasBuildMesh(mesh_t *mesh, vbo_t **vbop, galiasinfo_t *inf, in meshcache.vbo.indexcount = inf->numindexes; meshcache.vbo.vertcount = inf->numverts; meshcache.vbo.texcoord = inf->vbotexcoords; + meshcache.vbo.lmcoord[0] = inf->vbolmtexcoords; meshcache.vbo.coord = inf->vbo_skel_verts; memset(&meshcache.vbo.coord2, 0, sizeof(meshcache.vbo.coord2)); meshcache.vbo.normals = inf->vbo_skel_normals; @@ -2030,6 +2033,7 @@ qboolean Alias_GAliasBuildMesh(mesh_t *mesh, vbo_t **vbop, galiasinfo_t *inf, in meshcache.vbo.indexcount = inf->numindexes; meshcache.vbo.vertcount = inf->numverts; meshcache.vbo.texcoord = inf->vbotexcoords; + meshcache.vbo.lmcoord[0] = inf->vbolmtexcoords; #ifdef SKELETALMODELS memset(&meshcache.vbo.bonenums, 0, sizeof(meshcache.vbo.bonenums)); @@ -3083,6 +3087,8 @@ static void Mod_GenerateMeshVBO(model_t *mod, galiasinfo_t *galias) //determine the amount of space we need for our vbos. if (galias->ofs_st_array) vbospace += sizeof(*galias->ofs_st_array) * galias->numverts; + if (galias->ofs_lmst_array) + vbospace += sizeof(*galias->ofs_lmst_array) * galias->numverts; if (galias->ofs_rgbaf) vbospace += sizeof(*galias->ofs_rgbaf) * galias->numverts; else if (galias->ofs_rgbaub) @@ -3112,6 +3118,8 @@ static void Mod_GenerateMeshVBO(model_t *mod, galiasinfo_t *galias) BE_VBO_Begin(&vboctx, vbospace); if (galias->ofs_st_array) BE_VBO_Data(&vboctx, galias->ofs_st_array, sizeof(*galias->ofs_st_array) * galias->numverts, &galias->vbotexcoords); + if (galias->ofs_lmst_array) + BE_VBO_Data(&vboctx, galias->ofs_lmst_array, sizeof(*galias->ofs_lmst_array) * galias->numverts, &galias->vbolmtexcoords); if (galias->ofs_rgbaf) BE_VBO_Data(&vboctx, galias->ofs_rgbaf, sizeof(*galias->ofs_rgbaf) * galias->numverts, &galias->vborgba); else if (galias->ofs_rgbaub) diff --git a/engine/common/com_mesh.h b/engine/common/com_mesh.h index 0bd02f274..3a6220934 100644 --- a/engine/common/com_mesh.h +++ b/engine/common/com_mesh.h @@ -205,6 +205,7 @@ typedef struct galiasinfo_s #endif vboarray_t vboindicies; vboarray_t vbotexcoords; + vboarray_t vbolmtexcoords; vboarray_t vborgba; //yeah, just you try reading THAT as an actual word. void *vbomem; void *ebomem; diff --git a/engine/common/common.c b/engine/common/common.c index 80c9d14d9..bbf3f8625 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -6891,9 +6891,9 @@ static int Base64_Decode(char inp) return (inp-'a') + 26; if (inp >= '0' && inp <= '9') return (inp-'0') + 52; - if (inp == '+') + if (inp == '+' || inp == '-') return 62; - if (inp == '/') + if (inp == '/' || inp == '_') return 63; //if (inp == '=') //padding char return 0; //invalid @@ -6929,6 +6929,23 @@ size_t Base64_EncodeBlock(const qbyte *in, size_t length, char *out, size_t outs *out = 0; return out-start; } +size_t Base64_EncodeBlockURI(const qbyte *in, size_t length, char *out, size_t outsize) +{ //special uri-safe version (also trims) + outsize = Base64_EncodeBlock(in, length, out, outsize); + for (length = 0; length < outsize; length++) + { + if (out[length] == '+') + out[length] = '-'; + else if (out[length] == '/') + out[length] = '_'; + else if (out[length] == '=') + { //truncate it here. + out[length] = 0; + return length; + } + } + return outsize; +} size_t Base64_DecodeBlock(const char *in, const char *in_end, qbyte *out, size_t outsize) { qbyte *start = out; diff --git a/engine/common/common.h b/engine/common/common.h index a744998b9..08fdac30f 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -692,7 +692,7 @@ vfsfile_t *FS_OpenTCP(const char *name, int defaultport, qboolean assumetls); vfsfile_t *FS_OpenWithFriends(const char *fname, char *sysname, size_t sysnamesize, int numfriends, ...); -#define countof(array) (sizeof(array)/sizeof(array[0])) +#define countof(array) (sizeof(array)/sizeof((array)[0])) #ifdef _WIN32 //windows doesn't support utf-8. Which is a shame really, because that's the charset we expect from everything. char *narrowen(char *out, size_t outlen, wchar_t *wide); @@ -767,7 +767,6 @@ typedef struct char *defaultexec; //execed after cvars are reset, to give game-specific engine-defaults. char *defaultoverrides; //execed after default.cfg, to give usable defaults even when the mod the user is running is shit. char *eula; //when running as an installer, the user will be presented with this as a prompt - char *rtcbroker; //the broker to use for webrtc connections. char *basedir; //this is where we expect to find the data. char *iconname; //path we can find the icon (relative to the fmf's location) @@ -790,13 +789,16 @@ typedef struct } gamepath[8]; struct manpack_s //FIXME: this struct should be replaced with packagemanager info instead. { - int type; + enum manifestdeptype_e type; char *path; //the 'pure' name char *prefix; qboolean crcknown; //if the crc was specified unsigned int crc; //the public crc char *mirrors[8]; //a randomized (prioritized-on-load) list of mirrors to use. (may be 'prompt:game,package', 'unzip:file,url', 'xz:url', 'gz:url' char *condition; //only downloaded if this cvar is set | delimited allows multiple cvars. + char *sha512; //package must match this hash, if specified + char *signature; //signs the hash + qofs_t filesize; int mirrornum; //the index we last tried to download from, so we still work even if mirrors are down. } package[64]; } ftemanifest_t; @@ -930,14 +932,12 @@ void InfoBuf_WriteToFile(vfsfile_t *f, infobuf_t *info, const char *commandname, void InfoBuf_Enumerate (infobuf_t *info, void *ctx, void(*cb)(void *ctx, const char *key, const char *value)); -void Com_BlocksChecksum (int blocks, void **buffer, int *len, unsigned char *outbuf); -unsigned int Com_BlockChecksum (const void *buffer, int length); -void Com_BlockFullChecksum (const void *buffer, int len, unsigned char *outbuf); qbyte COM_BlockSequenceCheckByte (qbyte *base, int length, int sequence, unsigned mapchecksum); qbyte COM_BlockSequenceCRCByte (qbyte *base, int length, int sequence); qbyte Q2COM_BlockSequenceCRCByte (qbyte *base, int length, int sequence); size_t Base64_EncodeBlock(const qbyte *in, size_t length, char *out, size_t outsize); //tries to null terminate, but returns length without termination. +size_t Base64_EncodeBlockURI(const qbyte *in, size_t length, char *out, size_t outsize); //slightly different chars for uri safety. also trims. size_t Base64_DecodeBlock(const char *in, const char *in_end, qbyte *out, size_t outsize); // +/ and = size_t Base16_EncodeBlock(const char *in, size_t length, qbyte *out, size_t outsize); size_t Base16_DecodeBlock(const char *in, qbyte *out, size_t outsize); @@ -951,15 +951,16 @@ typedef struct void (*process) (void *context, const void *data, size_t datasize); void (*terminate) (unsigned char *digest, void *context); } hashfunc_t; -extern hashfunc_t hash_sha1; -extern hashfunc_t hash_sha224; -extern hashfunc_t hash_sha256; -extern hashfunc_t hash_sha384; -extern hashfunc_t hash_sha512; -extern hashfunc_t hash_crc16; +extern hashfunc_t hash_md4; //required for vanilla qw mapchecks +extern hashfunc_t hash_sha1; //required for websockets, and ezquake's crypted rcon +extern hashfunc_t hash_sha2_224; +extern hashfunc_t hash_sha2_256; //required for webrtc +extern hashfunc_t hash_sha2_384; +extern hashfunc_t hash_sha2_512; +extern hashfunc_t hash_crc16; //aka ccitt, required for qw's clc_move and various bits of dp compat extern hashfunc_t hash_crc16_lower; unsigned int hashfunc_terminate_uint(const hashfunc_t *hash, void *context); //terminate, except returning the digest as a uint instead of a blob. folds the digest if longer than 4 bytes. -unsigned int CalcHashInt(const hashfunc_t *hash, const unsigned char *data, size_t datasize); +unsigned int CalcHashInt(const hashfunc_t *hash, const void *data, size_t datasize); size_t CalcHash(const hashfunc_t *hash, unsigned char *digest, size_t maxdigestsize, const unsigned char *data, size_t datasize); size_t CalcHMAC(const hashfunc_t *hashfunc, unsigned char *digest, size_t maxdigestsize, const unsigned char *data, size_t datalen, const unsigned char *key, size_t keylen); diff --git a/engine/common/console.h b/engine/common/console.h index 898c20a36..002a3c1dc 100644 --- a/engine/common/console.h +++ b/engine/common/console.h @@ -124,7 +124,8 @@ enum CB_MOVE = 4, CB_ACTIONBAR = 5, CB_SELECT = 6, - CB_SELECTED = 7, + CB_SELECTED = 7, //selection ended (deferred until drawing to ensure selections happen properly) + CB_TAPPED = 8, //quick-tap ended (deferred until drawing to ensure selections happen properly) //the flags part CB_STALE = (1u<<28), //WAS held last frame - to make sure we still do stuff when released on the same frame. diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 028c576d5..6928203b1 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -897,6 +897,7 @@ Cvar_Set static cvar_t *Cvar_SetCore (cvar_t *var, const char *value, qboolean force) { //fixme: force should probably be a latch bitmask char *latch=NULL; + qboolean changed; COM_AssertMainThread("Cvar_SetCore"); @@ -912,6 +913,9 @@ static cvar_t *Cvar_SetCore (cvar_t *var, const char *value, qboolean force) return NULL; } + if (!value) + value = var->defaultstr; + if (force) ; else if (0)//var->flags & CVAR_SERVEROVERRIDE && !force) @@ -999,33 +1003,33 @@ static cvar_t *Cvar_SetCore (cvar_t *var, const char *value, qboolean force) latch = var->string;//save off the old value (so cvar_set(var, var->string) works) - var->string = (char*)Z_Malloc (Q_strlen(value)+1); - Q_strcpy (var->string, value); - var->value = Q_atof (var->string); - var->ival = Q_atoi (var->string); + changed = (!latch) || strcmp(latch, value); var->flags &= ~CVAR_TEAMPLAYTAINT; - + if (changed) { - char *str = COM_Parse(var->string); - var->vec4[0] = atof(com_token); - str = COM_Parse(str); - var->vec4[1] = atof(com_token); - str = COM_Parse(str); - var->vec4[2] = atof(com_token); - if (!str || !*str) - var->vec4[3] = 1; - else + var->string = Z_StrDup (value); + var->value = Q_atof (var->string); + var->ival = Q_atoi (var->string); + { + char *str = COM_Parse(var->string); + var->vec4[0] = atof(com_token); str = COM_Parse(str); - var->vec4[3] = atof(com_token); + var->vec4[1] = atof(com_token); + str = COM_Parse(str); + var->vec4[2] = atof(com_token); + if (!str || !*str) + var->vec4[3] = 1; + else + { + str = COM_Parse(str); + var->vec4[3] = atof(com_token); + } } - } - if (latch) - { - if (strcmp(latch, value)) - { + if (latch) + { //don't do this on registration. var->modified=true; //only modified if it changed. var->modifiedcount++; if (var->callback) @@ -1046,6 +1050,7 @@ static cvar_t *Cvar_SetCore (cvar_t *var, const char *value, qboolean force) #endif } } + if ((var->flags & CVAR_ARCHIVE) && !(var->flags & CVAR_SERVEROVERRIDE) && cl_warncmd.ival) { if (var->latched_string) @@ -1055,12 +1060,11 @@ static cvar_t *Cvar_SetCore (cvar_t *var, const char *value, qboolean force) } else { - if (strcmp(latch, value)) + if (!latch || strcmp(latch, value)) Cvar_ConfigChanged(); } } - Z_Free (latch); // free the old value string } diff --git a/engine/common/fs.c b/engine/common/fs.c index d31459b0b..0f89e75c7 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -33,6 +33,11 @@ static cvar_t fs_gamepath = CVARAFD ("fs_gamepath"/*q3ish*/, "", "fs_gamedir"/ static cvar_t fs_basepath = CVARAFD ("fs_basepath"/*q3*/, "", "fs_basedir"/*q2*/, CVAR_NOUNSAFEEXPAND|CVAR_NOSET|CVAR_NOSAVE, "Provided for Q2/Q3 compat. System path of the base directory."); static cvar_t fs_homepath = CVARAFD ("fs_homepath"/*q3ish*/, "", "fs_homedir"/*q2ish*/, CVAR_NOUNSAFEEXPAND|CVAR_NOSET|CVAR_NOSAVE, "Provided for Q2/Q3 compat. System path of the base directory."); static cvar_t dpcompat_ignoremodificationtimes = CVARAFD("fs_packageprioritisation", "1", "dpcompat_ignoremodificationtimes", CVAR_NOUNSAFEEXPAND|CVAR_NOSAVE, "Favours the package that is:\n0: Most recently modified\n1: Is alphabetically last (favour z over a, 9 over 0)."); +#ifdef FTE_TARGET_WEB +cvar_t fs_dlURL = CVARAFD(/*ioq3*/"sv_dlURL", "", /*dp*/"sv_curl_defaulturl", CVAR_SERVERINFO|CVAR_NOSAVE, "Provides clients with an external url from which they can obtain pk3s/packages from an external http server instead of having to download over udp."); +#else +cvar_t fs_dlURL = CVARAFD(/*ioq3*/"sv_dlURL", "", /*dp*/"sv_curl_defaulturl", CVAR_SERVERINFO|CVAR_ARCHIVE, "Provides clients with an external url from which they can obtain pk3s/packages from an external http server instead of having to download over udp."); +#endif int active_fs_cachetype; static int fs_referencetype; int fs_finds; @@ -189,7 +194,7 @@ qboolean Sys_ResolveFileURL(const char *inurl, int inlen, char *out, int outlen) { //has an authority field... i+=2; //except we don't support authorities other than ourself... - if (i < inend || *i != '/') + if (i >= inend || *i != '/') return false; //must be an absolute path... #ifdef _WIN32 i++; //on windows, (full)absolute paths start with a drive name... @@ -308,7 +313,6 @@ void FS_Manifest_Free(ftemanifest_t *man) Z_Free(man->eula); Z_Free(man->defaultexec); Z_Free(man->defaultoverrides); - Z_Free(man->rtcbroker); Z_Free(man->basedir); Z_Free(man->iconname); for (i = 0; i < sizeof(man->gamepath) / sizeof(man->gamepath[0]); i++) @@ -320,6 +324,8 @@ void FS_Manifest_Free(ftemanifest_t *man) Z_Free(man->package[i].path); Z_Free(man->package[i].prefix); Z_Free(man->package[i].condition); + Z_Free(man->package[i].sha512); + Z_Free(man->package[i].signature); for (j = 0; j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++) Z_Free(man->package[i].mirrors[j]); } @@ -354,8 +360,6 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm) newm->defaultexec = Z_StrDup(oldm->defaultexec); if (oldm->defaultoverrides) newm->defaultoverrides = Z_StrDup(oldm->defaultoverrides); - if (oldm->rtcbroker) - newm->rtcbroker = Z_StrDup(oldm->rtcbroker); if (oldm->iconname) newm->iconname = Z_StrDup(oldm->iconname); if (oldm->basedir) @@ -372,12 +376,20 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm) } for (i = 0; i < sizeof(newm->package) / sizeof(newm->package[0]); i++) { + newm->package[i].type = oldm->package[i].type; + newm->package[i].crc = oldm->package[i].crc; + newm->package[i].crcknown = oldm->package[i].crcknown; if (oldm->package[i].path) newm->package[i].path = Z_StrDup(oldm->package[i].path); if (oldm->package[i].prefix) newm->package[i].prefix = Z_StrDup(oldm->package[i].prefix); if (oldm->package[i].condition) newm->package[i].condition = Z_StrDup(oldm->package[i].condition); + if (oldm->package[i].sha512) + newm->package[i].sha512 = Z_StrDup(oldm->package[i].sha512); + if (oldm->package[i].signature) + newm->package[i].signature = Z_StrDup(oldm->package[i].signature); + newm->package[i].filesize = oldm->package[i].filesize; for (j = 0; j < sizeof(newm->package[i].mirrors) / sizeof(newm->package[i].mirrors[0]); j++) if (oldm->package[i].mirrors[j]) newm->package[i].mirrors[j] = Z_StrDup(oldm->package[i].mirrors[j]); @@ -450,8 +462,6 @@ static void FS_Manifest_Print(ftemanifest_t *man) } //Con_Printf("%s", man->defaultoverrides); } - if (man->rtcbroker) - Con_Printf("rtcbroker %s\n", COM_QuotedString(man->rtcbroker, buffer, sizeof(buffer), false)); if (man->iconname) Con_Printf("icon %s\n", COM_QuotedString(man->iconname, buffer, sizeof(buffer), false)); if (man->basedir) @@ -461,11 +471,10 @@ static void FS_Manifest_Print(ftemanifest_t *man) { if (man->gamepath[i].path) { - size_t bufsize = strlen(man->gamepath[i].path) + 16; - char *str = Z_Malloc(bufsize); - if (man->gamepath[i].flags & GAMEDIR_PRIVATE) - Q_strncatz(str, "*", bufsize); - Q_strncatz(str, man->gamepath[i].path, bufsize); + char *str = va("%s%s%s", + (man->gamepath[i].flags & GAMEDIR_QSHACK)?"/":"", + (man->gamepath[i].flags & GAMEDIR_PRIVATE)?"*":"", + man->gamepath[i].path); if (man->gamepath[i].flags & GAMEDIR_BASEGAME) Con_Printf("basegame %s\n", COM_QuotedString(str, buffer, sizeof(buffer), false)); @@ -483,10 +492,16 @@ static void FS_Manifest_Print(ftemanifest_t *man) else Con_Printf("package "); Con_Printf("%s", COM_QuotedString(man->package[i].path, buffer, sizeof(buffer), false)); - if (man->package[i].condition) - Con_Printf(" prefix %s", COM_QuotedString(man->package[i].condition, buffer, sizeof(buffer), false)); + if (man->package[i].prefix) + Con_Printf(" prefix %s", COM_QuotedString(man->package[i].prefix, buffer, sizeof(buffer), false)); if (man->package[i].condition) Con_Printf(" condition %s", COM_QuotedString(man->package[i].condition, buffer, sizeof(buffer), false)); + if (man->package[i].filesize) + Con_Printf(" filesize %"PRIuQOFS, man->package[i].filesize); + if (man->package[i].sha512) + Con_Printf(" sha512 %s", COM_QuotedString(man->package[i].sha512, buffer, sizeof(buffer), false)); + if (man->package[i].signature) + Con_Printf(" signature %s", COM_QuotedString(man->package[i].signature, buffer, sizeof(buffer), false)); if (man->package[i].crcknown) Con_Printf(" crc 0x%x", man->package[i].crc); for (j = 0; j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++) @@ -538,8 +553,6 @@ static ftemanifest_t *FS_Manifest_Create(const char *syspath, const char *basedi #else man->mainconfig = Z_StrDup("fte.cfg"); #endif - - man->rtcbroker = Z_StrDup("tls://master.frag-net.com:27950"); //This is eukara's server. fixme: this really ought to be a cvar instead. return man; } @@ -552,6 +565,9 @@ static qboolean FS_Manifest_ParsePackage(ftemanifest_t *man, int packagetype) char *condition = NULL; char *prefix = NULL; char *arch = NULL; + char *signature = NULL; + char *sha512 = NULL; + qofs_t filesize = 0; unsigned int arg = 1; unsigned int mirrors = 0; char *mirror[countof(man->package[0].mirrors)]; @@ -598,6 +614,12 @@ static qboolean FS_Manifest_ParsePackage(ftemanifest_t *man, int packagetype) prefix = Cmd_Argv(arg++); else if (!strcmp(a, "arch")) arch = Cmd_Argv(arg++); + else if (!strcmp(a, "signature")) + signature = Cmd_Argv(arg++); + else if (!strcmp(a, "sha512")) + sha512 = Cmd_Argv(arg++); + else if (!strcmp(a, "filesize")||!strcmp(a, "size")) + filesize = strtoull(Cmd_Argv(arg++), NULL, 0); else if (!strcmp(a, "mirror")) { a = Cmd_Argv(arg++); @@ -642,6 +664,9 @@ mirror: man->package[i].path = Z_StrDup(path); man->package[i].prefix = prefix?Z_StrDup(prefix):NULL; man->package[i].condition = condition?Z_StrDup(condition):NULL; + man->package[i].sha512 = sha512?Z_StrDup(sha512):NULL; + man->package[i].signature = signature?Z_StrDup(signature):NULL; + man->package[i].filesize = filesize; man->package[i].crcknown = crcknown; man->package[i].crc = crc; for (j = 0; j < mirrors; j++) @@ -784,11 +809,12 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) { Z_StrCat(&man->defaultoverrides, va("%s %s\n", Cmd_Argv(0), Cmd_Args())); } +#ifdef HAVE_LEGACY else if (!Q_strcasecmp(cmd, "rtcbroker")) { - Z_Free(man->rtcbroker); - man->rtcbroker = Z_StrDup(Cmd_Argv(1)); + Z_StrCat(&man->defaultexec, va("set %s %s\n", net_ice_broker.name, Cmd_Args())); } +#endif else if (!Q_strcasecmp(cmd, "updateurl")) { Z_Free(man->updateurl); @@ -1330,10 +1356,10 @@ static void COM_CalcHash_Thread(void *ctx, void *fname, size_t a, size_t b) // {"crc16", &hash_crc16}, {"sha1", &hash_sha1}, #if defined(HAVE_SERVER) || defined(HAVE_CLIENT) -// {"sha224", &hash_sha224}, - {"sha256", &hash_sha256}, -// {"sha384", &hash_sha384}, -// {"sha512", &hash_sha512}, +// {"sha224", &hash_sha2_224}, + {"sha256", &hash_sha2_256}, +// {"sha384", &hash_sha2_384}, +// {"sha512", &hash_sha2_512}, #endif }; qbyte digest[DIGEST_MAXSIZE]; @@ -2277,6 +2303,7 @@ qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out char cleanname[MAX_QPATH]; char *last; qboolean wasbase; //to handle out-of-order base/game dirs. + int nlen; if (relativeto == FS_SYSTEM) { @@ -2295,7 +2322,7 @@ qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out { //this is sometimes used to query the actual path. //don't alow it for other stuff though. - if (relativeto != FS_ROOT && relativeto != FS_BINARYPATH && relativeto != FS_GAMEONLY) + if (relativeto != FS_ROOT && relativeto != FS_BINARYPATH && relativeto != FS_LIBRARYPATH && relativeto != FS_GAMEONLY) return false; } else @@ -2310,29 +2337,36 @@ qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out case FS_GAME: //this is really for diagnostic type stuff... if (FS_FLocateFile(fname, FSLF_IFFOUND, &loc)) { - snprintf(out, outlen, "%s/%s", loc.search->logicalpath, fname); + nlen = snprintf(out, outlen, "%s/%s", loc.search->logicalpath, fname); break; } //fallthrough case FS_GAMEONLY: if (com_homepathenabled) - snprintf(out, outlen, "%s%s/%s", com_homepath, gamedirfile, fname); + nlen = snprintf(out, outlen, "%s%s/%s", com_homepath, gamedirfile, fname); else - snprintf(out, outlen, "%s%s/%s", com_gamepath, gamedirfile, fname); + nlen = snprintf(out, outlen, "%s%s/%s", com_gamepath, gamedirfile, fname); break; + case FS_LIBRARYPATH: +#ifdef FTE_LIBRARY_PATH + nlen = snprintf(out, outlen, STRINGIFY(FTE_LIBRARY_PATH)"/%s", fname); + break; +#else + return false; +#endif case FS_BINARYPATH: if (host_parms.binarydir && *host_parms.binarydir) - snprintf(out, outlen, "%s%s", host_parms.binarydir, fname); + nlen = snprintf(out, outlen, "%s%s", host_parms.binarydir, fname); else - snprintf(out, outlen, "%s%s", host_parms.basedir, fname); + nlen = snprintf(out, outlen, "%s%s", host_parms.basedir, fname); break; case FS_ROOT: if (com_installer) return false; if (com_homepathenabled) - snprintf(out, outlen, "%s%s", com_homepath, fname); + nlen = snprintf(out, outlen, "%s%s", com_homepath, fname); else - snprintf(out, outlen, "%s%s", com_gamepath, fname); + nlen = snprintf(out, outlen, "%s%s", com_gamepath, fname); break; case FS_BASEGAMEONLY: // fte/ @@ -2349,9 +2383,9 @@ qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out if (!last) return false; //eep? if (com_homepathenabled) - snprintf(out, outlen, "%s%s/%s", com_homepath, last, fname); + nlen = snprintf(out, outlen, "%s%s/%s", com_homepath, last, fname); else - snprintf(out, outlen, "%s%s/%s", com_gamepath, last, fname); + nlen = snprintf(out, outlen, "%s%s/%s", com_gamepath, last, fname); break; case FS_PUBGAMEONLY: // $gamedir/ or qw/ but not fte/ last = NULL; @@ -2372,9 +2406,9 @@ qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out if (!last) return false; //eep? if (com_homepathenabled) - snprintf(out, outlen, "%s%s/%s", com_homepath, last, fname); + nlen = snprintf(out, outlen, "%s%s/%s", com_homepath, last, fname); else - snprintf(out, outlen, "%s%s/%s", com_gamepath, last, fname); + nlen = snprintf(out, outlen, "%s%s/%s", com_gamepath, last, fname); break; case FS_PUBBASEGAMEONLY: // qw/ (fixme: should be the last non-private basedir) last = NULL; @@ -2390,14 +2424,14 @@ qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out if (!last) return false; //eep? if (com_homepathenabled) - snprintf(out, outlen, "%s%s/%s", com_homepath, last, fname); + nlen = snprintf(out, outlen, "%s%s/%s", com_homepath, last, fname); else - snprintf(out, outlen, "%s%s/%s", com_gamepath, last, fname); + nlen = snprintf(out, outlen, "%s%s/%s", com_gamepath, last, fname); break; default: Sys_Error("FS_NativePath case not handled\n"); } - return true; + return nlen < outlen; } //returns false to stop the enumeration. check the return value of the fs enumerator to see if it was canceled by this return value. @@ -3529,6 +3563,7 @@ void FS_AddHashedPackage(searchpath_t **oldpaths, const char *parentpath, const static void FS_AddManifestPackages(searchpath_t **oldpaths, const char *purepath, const char *logicalpaths, searchpath_t *search, unsigned int loadstuff) { +#ifndef PACKAGEMANAGER int i; int ptlen, palen; @@ -3552,6 +3587,7 @@ static void FS_AddManifestPackages(searchpath_t **oldpaths, const char *purepath ); } } +#endif } static void FS_AddDownloadManifestPackages(searchpath_t **oldpaths, unsigned int loadstuff)//, const char *purepath, searchpath_t *search, const char *extension, searchpathfuncs_t *(QDECL *OpenNew)(vfsfile_t *file, const char *desc)) @@ -3803,6 +3839,68 @@ qboolean FS_Restarted(unsigned int *since) return false; } +#ifdef __WIN32 //already assumed to be case insensitive. let the OS keep fixing up the paths itself. +static void FS_FixupFileCase(char *out, size_t outsize, const char *basedir, const char *entry) +{ + Q_snprintfz(out, outsize, "%s%s", basedir, entry); +} +#else +struct fixupcase_s +{ + char *out; + size_t outsize; + const char *match; + size_t matchlen; + qboolean isdir; //directory results have a trailing / +}; +static int FS_FixupFileCaseResult(const char *name, qofs_t sz, time_t modtime, void *vparm, searchpathfuncs_t *spath) +{ + struct fixupcase_s *parm = vparm; + if (strlen(name) != parm->matchlen+parm->isdir) + return true; + if (parm->isdir && name[parm->matchlen] != '/') + return true; + if (Q_strncasecmp(name, parm->match, parm->matchlen)) + return true; + memcpy(parm->out, name, parm->matchlen); + return !!Q_strncmp(name, parm->match, parm->matchlen); //stop if we find the exact path case. otherwise keep looking +} +//like snprintf("%s%s") but fixes up 'gamedir' case to a real file +static qboolean FS_FixupFileCase(char *out, size_t outsize, const char *basedir, const char *entry, qboolean isdir) +{ + char *s; + struct fixupcase_s parm = {out+strlen(basedir), outsize-strlen(basedir), entry, strlen(entry), isdir}; + if (Q_snprintfz(out, outsize, "%s%s", basedir, entry) >= outsize || outsize < strlen(basedir)+1 || parm.outsize < parm.matchlen+1) + return false; //over sized?... + if (strchr(entry, '/')) for(;;) + { + parm.match = entry; + s = strchr(entry, '/'); + if (s) + { + parm.isdir = true; + parm.matchlen = s-entry; + Sys_EnumerateFiles(basedir, "*", FS_FixupFileCaseResult, &parm, NULL); + parm.out += parm.matchlen+1; + parm.outsize -= parm.matchlen+1; + entry += (s-entry)+1; + } + else + { + parm.isdir = isdir; + parm.matchlen = strlen(entry); + parm.out[-1] = 0; + Sys_EnumerateFiles(out, "*", FS_FixupFileCaseResult, &parm, NULL); + parm.out[-1] = '/'; + break; + } + } + else + Sys_EnumerateFiles(basedir, "*", FS_FixupFileCaseResult, &parm, NULL); + return true; +} +#endif + /* ================ FS_AddGameDirectory @@ -3854,13 +3952,12 @@ static searchpath_t *FS_AddSingleGameDirectory (searchpath_t **oldpaths, const c static void FS_AddGameDirectory (searchpath_t **oldpaths, const char *puredir, unsigned int loadstuff, unsigned int flags) { char syspath[MAX_OSPATH]; - Q_snprintfz(syspath, sizeof(syspath), "%s%s", com_gamepath, puredir); - gameonly_gamedir = FS_AddSingleGameDirectory(oldpaths, puredir, syspath, loadstuff, flags&~(com_homepathenabled?SPF_WRITABLE:0u)); - if (com_homepathenabled) - { - Q_snprintfz(syspath, sizeof(syspath), "%s%s", com_homepath, puredir); + if (FS_FixupFileCase(syspath, sizeof(syspath), com_gamepath, puredir, true)) + gameonly_gamedir = FS_AddSingleGameDirectory(oldpaths, puredir, syspath, loadstuff, flags&~(com_homepathenabled?SPF_WRITABLE:0u)); + else + gameonly_gamedir = NULL; + if (com_homepathenabled && FS_FixupFileCase(syspath, sizeof(syspath), com_homepath, puredir, true)) gameonly_homedir = FS_AddSingleGameDirectory(oldpaths, puredir, syspath, loadstuff, flags); - } else gameonly_homedir = NULL; } @@ -4136,7 +4233,7 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths) #define QCFG "//schemes quake qw\n" QUAKEOVERRIDES "set com_parseutf8 0\n" QRPCOMPAT #define KEXCFG "//schemes quake_r2\n" QUAKEOVERRIDES "set com_parseutf8 1\nset campaign 0\nset net_enable_dtls 1\nset sv_mintic 0.016666667\nset sv_maxtic $sv_mintic\nset cl_netfps 60\n" /*NetQuake reconfiguration, to make certain people feel more at home...*/ -#define NQCFG "//disablehomedir 1\n//mainconfig ftenq\n" QCFG "cfg_save_auto 1\nset sv_nqplayerphysics 1\nset cl_loopbackprotocol auto\ncl_sbar 1\nset plug_sbar 0\nset sv_port "STRINGIFY(PORT_NQSERVER)"\ncl_defaultport "STRINGIFY(PORT_NQSERVER)"\nset m_preset_chosen 1\nset vid_wait 1\nset cl_demoreel 1\n" +#define NQCFG "//disablehomedir 1\n//mainconfig ftenq\n" QCFG "cfg_save_auto 1\nset pm_bunnyfriction 1\nset sv_nqplayerphysics 1\nset cl_loopbackprotocol auto\ncl_sbar 1\nset plug_sbar 0\nset sv_port "STRINGIFY(PORT_NQSERVER)"\ncl_defaultport "STRINGIFY(PORT_NQSERVER)"\nset m_preset_chosen 1\nset vid_wait 1\nset cl_demoreel 1\n" #define SPASMCFG NQCFG "fps_preset builtin_spasm\nset cl_demoreel 0\ncl_sbar 2\nset gl_load24bit 1\n" #define FITZCFG NQCFG "fps_preset builtin_spasm\ncl_sbar 2\nset gl_load24bit 1\n" #define TENEBRAECFG NQCFG "fps_preset builtin_tenebrae\n" @@ -4152,11 +4249,13 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths) /*set some stuff so our regular qw client appears more like hexen2. sv_mintic is required to 'fix' the ravenstaff so that its projectiles don't impact upon each other*/ #define HEX2CFG "//schemes hexen2\n" "set v_gammainverted 1\nset com_parseutf8 -1\nset gl_font gfx/hexen2\nset in_builtinkeymap 0\nset_calc cl_playerclass int (random * 5) + 1\nset cl_forwardspeed 200\nset cl_backspeed 200\ncl_sidespeed 225\nset sv_maxspeed 640\ncl_run 0\nset watervis 1\nset r_lavaalpha 1\nset r_lavastyle -2\nset r_wateralpha 0.5\nset sv_pupglow 1\ngl_shaftlight 0.5\nsv_mintic 0.015\nset r_meshpitch -1\nset r_meshroll -1\nr_sprite_backfacing 1\nset mod_warnmodels 0\nset cl_model_bobbing 1\nsv_sound_watersplash \"misc/hith2o.wav\"\nsv_sound_land \"fx/thngland.wav\"\nset sv_walkpitch 0\n" /*yay q2!*/ -#define Q2CFG "//schemes quake2\n" "set allow_skybox 1\nset v_gammainverted 1\nset com_parseutf8 0\ncom_gamedirnativecode 1\nset sv_bigcoords 0\nsv_port "STRINGIFY(PORT_Q2SERVER)"\ncl_defaultport "STRINGIFY(PORT_Q2SERVER)"\n" +#define Q2CFG "//schemes quake2\n" "set v_gammainverted 1\nset com_parseutf8 0\ncom_gamedirnativecode 1\nset sv_bigcoords 0\nsv_port "STRINGIFY(PORT_Q2SERVER)"\ncl_defaultport "STRINGIFY(PORT_Q2SERVER)"\n" /*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/ #define Q3CFG "//schemes quake3\n" "set v_gammainverted 0\nset snd_ignorecueloops 1\nsetfl g_gametype 0 s\nset gl_clear 1\nset r_clearcolour 0 0 0\nset com_parseutf8 0\ngl_overbright "FORWEB("0","2")"\nseta model sarge\nseta headmodel sarge\nseta handicap 100\ncom_gamedirnativecode 1\nsv_port "STRINGIFY(PORT_Q3SERVER)"\ncl_defaultport "STRINGIFY(PORT_Q3SERVER)"\ncom_protocolversion 68\n" //#define RMQCFG "sv_bigcoords 1\n" +#define HLCFG "plug_load ffmpeg\n" + #ifndef UPDATEURL #ifdef HAVE_SSL #define UPDATEURL(g) "/downloadables.php?game=" #g @@ -4225,7 +4324,7 @@ static const gamemode_info_t gamemode_info[] = { //because we can. 'fps_preset spasm' is hopefully close enough... {"-fitz", "nq", QUAKEPROT, {"id1/pak0.pak","id1/quake.rc"},FITZCFG,{"id1"}, "FauxFitz", UPDATEURL(Q1)}, //because we can - {"-tenebrae", NULL, QUAKEPROT, {"id1/pak0.pak","id1/quake.rc"},TENEBRAECFG,{"id1", "tenebrae"}, "FauxTenebrae", UPDATEURL(Q1)}, + {"-tenebrae", NULL, QUAKEPROT, {"tenebrae/Pak0.pak","id1/quake.rc"},TENEBRAECFG,{"id1", "tenebrae"}, "FauxTenebrae", UPDATEURL(Q1)}, //quake's mission packs should not be favoured over the base game nor autodetected //third part mods also tend to depend upon the mission packs for their huds, even if they don't use any other content. @@ -4281,10 +4380,8 @@ static const gamemode_info_t gamemode_info[] = { //for the luls // {"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"}, #endif -#if defined(HLSERVER) || defined(HLCLIENT) - //can run in windows, needs hl gamecode enabled. maps can always be viewed, but meh. - {"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, NULL, {"valve", "*ftehl"}, "Half-Life"}, -#endif + /* maintained by FreeHL ~eukara */ + {"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, HLCFG, {"logos", "valve"}, "Half-Life"}, #endif {NULL} @@ -4872,6 +4969,8 @@ static void FS_ReloadPackFilesFlags(unsigned int reloadflags) } else { + if (!FS_GamedirIsOkay(dir)) + continue; FS_AddGameDirectory(&oldpaths, dir, reloadflags, fl); } } @@ -4888,15 +4987,6 @@ static void FS_ReloadPackFilesFlags(unsigned int reloadflags) char *dir = fs_manifest->gamepath[i].path; if (dir && !(fs_manifest->gamepath[i].flags&GAMEDIR_BASEGAME)) { - //don't allow leading dots, hidden files are evil. - //don't allow complex paths. those are evil too. - if (!*dir || *dir == '.' || !strcmp(dir, ".") || strstr(dir, "..") || strstr(dir, "/") - || strstr(dir, "\\") || strstr(dir, ":") ) - { - Con_Printf ("Gamedir should be a single filename, not a path\n"); - continue; - } - for (j = 0; j < countof(fs_manifest->gamepath); j++) { char *dir2 = fs_manifest->gamepath[j].path; @@ -4906,12 +4996,28 @@ static void FS_ReloadPackFilesFlags(unsigned int reloadflags) if (j < countof(fs_manifest->gamepath)) continue; //already loaded above. don't mess up gameonly_gamedir. + fl = SPF_EXPLICIT; + if (!(fs_manifest->gamepath[i].flags&GAMEDIR_READONLY)) + fl |= SPF_WRITABLE; + if (fs_manifest->gamepath[i].flags&GAMEDIR_PRIVATE) + fl |= SPF_PRIVATE; + if (fs_manifest->gamepath[i].flags&GAMEDIR_QSHACK) + fl |= SPF_QSHACK; + if (*dir == '*') - { + { //just in case... shouldn't be needed. + dir++; + fl |= GAMEDIR_PRIVATE; } + + if (fs_manifest->gamepath[i].flags & GAMEDIR_SPECIAL) + ; //don't. else { - FS_AddGameDirectory(&oldpaths, dir, reloadflags, SPF_EXPLICIT|SPF_WRITABLE); + //don't use evil gamedir names. + if (!FS_GamedirIsOkay(dir)) + continue; + FS_AddGameDirectory(&oldpaths, dir, reloadflags, fl); } } } @@ -5535,6 +5641,8 @@ qboolean Sys_FindGameData(const char *poshname, const char *gamename, char *base return true; if (!strcmp(gamename, "quake") || !strcmp(gamename, "afterquake") || !strcmp(gamename, "netquake") || !strcmp(gamename, "spasm") || !strcmp(gamename, "fitz") || !strcmp(gamename, "tenebrae")) { + if (Sys_SteamHasFile(basepath, basepathlen, "Quake", "Id1/PAK0.PAK")) //dos legacies need to die. + return true; if (Sys_SteamHasFile(basepath, basepathlen, "Quake", "id1/PAK0.PAK")) //dos legacies need to die. return true; if (Sys_SteamHasFile(basepath, basepathlen, "Quake", "id1/pak0.pak")) //people may have tried to sanitise it already. @@ -5671,16 +5779,19 @@ static qboolean FS_DirHasAPackage(char *basedir, ftemanifest_t *man) return defaultret; } +#ifdef _WIN32 //false stops the search (and returns that value to FS_DirHasGame) static int QDECL FS_DirDoesHaveGame(const char *fname, qofs_t fsize, time_t modtime, void *ctx, searchpathfuncs_t *subdir) { return false; } +#endif //just check each possible file, see if one is there. static qboolean FS_DirHasGame(const char *basedir, int gameidx) { int j; + char realpath[MAX_OSPATH]; //none listed, just assume its correct. if (!gamemode_info[gameidx].auniquefile[0]) @@ -5690,8 +5801,13 @@ static qboolean FS_DirHasGame(const char *basedir, int gameidx) { if (!gamemode_info[gameidx].auniquefile[j]) continue; //no more +#ifdef _WIN32 if (!Sys_EnumerateFiles(basedir, gamemode_info[gameidx].auniquefile[j], FS_DirDoesHaveGame, NULL, NULL)) return true; //search was cancelled by the callback, so it actually got called. +#else + if (FS_FixupFileCase(realpath, sizeof(realpath), basedir, gamemode_info[gameidx].auniquefile[j], false) && access(realpath, R_OK) == 0) + return true; //something readable. +#endif } return false; } @@ -5746,7 +5862,7 @@ static int FS_IdentifyDefaultGame(char *newbase, int sizeof_newbase, qboolean fi if (gamenum != -1) Q_strncpyz(newbase, host_parms.binarydir, sizeof_newbase); } - if (gamenum == -1 && *com_homepath && !fixedbase) + if (gamenum == -1 && *com_homepath && com_homepathusable && !fixedbase) { gamenum = FS_IdentifyDefaultGameFromDir(com_homepath); if (gamenum != -1) @@ -6141,6 +6257,8 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean char *vidfile[] = {"gfx.wad", "gfx/conback.lmp", //misc stuff "gfx/palette.lmp", "pics/colormap.pcx", "gfx/conchars.png"}; //palettes searchpathfuncs_t *vidpath[countof(vidfile)]; + char *menufile[] = {"menu.dat"/*mods*/, "gfx/ttl_main.lmp"/*q1*/, "pics/m_main_quit.pcx"/*q2*/, "gfx/menu/title0.lmp"/*h2*/}; + searchpathfuncs_t *menupath[countof(menufile)]; #endif //if any of these files change location, the configs will be re-execed. @@ -6159,6 +6277,16 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean else vidpath[i] = NULL; } + for (i = 0; i < countof(menufile); i++) + { + if (allowreloadconfigs) + { + FS_FLocateFile(menufile[i], FSLF_IFFOUND|FSLF_SECUREONLY, &loc); + menupath[i] = loc.search?loc.search->handle:NULL; + } + else + menupath[i] = NULL; + } #endif if (allowreloadconfigs && fs_noreexec.ival) @@ -6439,6 +6567,12 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean } #endif + //our basic filesystem should be okay, but no packages loaded yet. +#ifdef MANIFESTDOWNLOADS + //make sure the package manager knows what its meant to know... + PM_AddManifestPackages(man); +#endif + if (Sys_LockMutex(fs_thread_mutex)) { #ifdef HAVE_CLIENT @@ -6537,6 +6671,20 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean Cbuf_AddText ("vid_reload\n", RESTRICT_LOCAL); vidrestart = false; } + + + if (qrenderer != QR_NONE && allowreloadconfigs) + { + for (i = 0; i < countof(menufile); i++) + { + FS_FLocateFile(menufile[i], FSLF_IFFOUND, &loc); + if (menupath[i] != (loc.search?loc.search->handle:NULL)) + { + Cbuf_AddText ("menu_restart\n", RESTRICT_LOCAL); + break; + } + } + } #endif //rebuild the cache now, should be safe to waste some cycles on it @@ -6701,7 +6849,7 @@ int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), Q_strncpyz(basedir, com_gamepath, sizeof(basedir)); if (gamemode_info[i].manifestfile || ((gamemode_info[i].exename || (i>0&&gamemode_info[i].customexec&&gamemode_info[i-1].customexec&&strcmp(gamemode_info[i].customexec,gamemode_info[i-1].customexec))) && FS_DirHasGame(com_gamepath, i)) || - (e.anygamedir&&Sys_FindGameData(NULL, gamemode_info[i].argname+1, basedir, sizeof(basedir), true))) + (e.anygamedir&&Sys_FindGameData(NULL, gamemode_info[i].argname+1, basedir, sizeof(basedir), false))) { man = FS_GenerateLegacyManifest(i, basedir); if (e.callback(e.usr, man)) @@ -7203,6 +7351,7 @@ static void FS_ChangeMod_f(void) int packages = 0; const char *arg = "?"; qboolean okay = false; + char *dir = NULL; if (Cmd_IsInsecure()) return; @@ -7246,6 +7395,11 @@ static void FS_ChangeMod_f(void) arg = Cmd_Argv(i++); packagespaths[packages-1].subpath = Z_StrDup(arg); } + else if (!strcmp(arg, "dir")) + { + arg = Cmd_Argv(i++); + Z_StrDupPtr(&dir, arg); + } else if (!strcmp(arg, "map")) { Z_Free(fs_loadedcommand); @@ -7268,13 +7422,14 @@ static void FS_ChangeMod_f(void) } if (okay) - COM_Gamedir("", packagespaths); + COM_Gamedir(dir?dir:"", packagespaths); else { Con_Printf("unsupported args: %s\n", arg); Z_Free(fs_loadedcommand); fs_loadedcommand = NULL; } + Z_Free(dir); for (i = 0; i < packages; i++) { @@ -7645,6 +7800,7 @@ void COM_InitFilesystem (void) Cvar_Register(&fs_gamepath, "Filesystem"); Cvar_Register(&fs_basepath, "Filesystem"); Cvar_Register(&fs_homepath, "Filesystem"); + Cvar_Register(&fs_dlURL, "Filesystem"); COM_InitHomedir(NULL); diff --git a/engine/common/fs.h b/engine/common/fs.h index cbf3a4e6b..065332457 100644 --- a/engine/common/fs.h +++ b/engine/common/fs.h @@ -27,6 +27,7 @@ extern int fs_hash_files; //for tracking efficiency. no functional use. extern qboolean fs_readonly; //if true, fopen(, "w") should always fail. extern void *fs_thread_mutex; extern float fs_accessed_time; +extern cvar_t fs_dlURL; struct searchpath_s; struct searchpathfuncs_s @@ -90,7 +91,7 @@ void Menu_Download_Update(void); typedef struct { char *description; - void (*Update) (const char *url, vfsfile_t *out); + void (*Update) (const char *url, vfsfile_t *out, qboolean favourcache); #define plugupdatesourcefuncs_name "UpdateSource" } plugupdatesourcefuncs_t; qboolean PM_RegisterUpdateSource(void *module, plugupdatesourcefuncs_t *funcs); diff --git a/engine/common/fs_dzip.c b/engine/common/fs_dzip.c index f2dd74f67..1f9a43fd6 100644 --- a/engine/common/fs_dzip.c +++ b/engine/common/fs_dzip.c @@ -1235,9 +1235,9 @@ static int QDECL FSDZ_GeneratePureCRC(searchpathfuncs_t *handle, int seed, int c } if (crctype) - result = Com_BlockChecksum(filecrcs, numcrcs*sizeof(int)); + result = CalcHashInt(&hash_md4, filecrcs, numcrcs*sizeof(int)); else - result = Com_BlockChecksum(filecrcs+1, (numcrcs-1)*sizeof(int)); + result = CalcHashInt(&hash_md4, filecrcs+1, (numcrcs-1)*sizeof(int)); BZ_Free(filecrcs); return result; diff --git a/engine/common/fs_pak.c b/engine/common/fs_pak.c index 6817c2adf..de306ca0d 100644 --- a/engine/common/fs_pak.c +++ b/engine/common/fs_pak.c @@ -176,9 +176,9 @@ static int QDECL FSPAK_GeneratePureCRC(searchpathfuncs_t *handle, int seed, int } if (crctype) - result = Com_BlockChecksum(filecrcs, numcrcs*sizeof(int)); + result = CalcHashInt(&hash_md4, filecrcs, numcrcs*sizeof(int)); else - result = Com_BlockChecksum(filecrcs+1, (numcrcs-1)*sizeof(int)); + result = CalcHashInt(&hash_md4, filecrcs+1, (numcrcs-1)*sizeof(int)); BZ_Free(filecrcs); return result; diff --git a/engine/common/fs_zip.c b/engine/common/fs_zip.c index 8fcd51712..760bb101a 100644 --- a/engine/common/fs_zip.c +++ b/engine/common/fs_zip.c @@ -607,6 +607,9 @@ typedef struct #define ZFL_SYMLINK (1u<<3) //file is a symlink #define ZFL_CORRUPT (1u<<4) //file is corrupt or otherwise unreadable (usually just means we don't support reading it rather than actually corrupt, but hey). #define ZFL_WEAKENCRYPT (1u<<5) //traditional zip encryption +#define ZFL_DEFLATE64D (1u<<6) //need to use zlib's 'inflateBack9' stuff. + +#define ZFL_COMPRESSIONTYPE (ZFL_STORED|ZFL_CORRUPT|ZFL_DEFLATED|ZFL_DEFLATE64D|ZFL_BZIP2) typedef struct zipfile_s @@ -788,9 +791,9 @@ static int QDECL FSZIP_GeneratePureCRC(searchpathfuncs_t *handle, int seed, int } if (crctype || numcrcs < 1) - result = Com_BlockChecksum(filecrcs, numcrcs*sizeof(int)); + result = CalcHashInt(&hash_md4, filecrcs, numcrcs*sizeof(int)); else - result = Com_BlockChecksum(filecrcs+1, (numcrcs-1)*sizeof(int)); + result = CalcHashInt(&hash_md4, filecrcs+1, (numcrcs-1)*sizeof(int)); BZ_Free(filecrcs); return result; @@ -1030,6 +1033,95 @@ static struct decompressstate *FSZIP_Deflate_Init(zipfile_t *source, qofs_t star } #endif +#if defined(ZLIB_DEFLATE64) && !defined(AVAIL_ZLIB) + #undef ZLIB_DEFLATE64 //don't be silly. +#endif +#if defined(ZLIB_DEFLATE64) +#include "infback9.h" //an obscure compile-your-own part of zlib. +struct def64ctx +{ + vfsfile_t *src; + vfsfile_t *dst; + qofs_t csize; + qofs_t usize; + unsigned int crc; + + qbyte inbuf[0x8000]; +}; +static unsigned int FSZIP_Deflate64_Grab(void *vctx, unsigned char **bufptr) +{ + struct def64ctx *ctx = vctx; + int avail; + avail = sizeof(ctx->inbuf); + if (avail > ctx->csize) + avail = ctx->csize; //don't over-read. + if (avail <= 0) + { + *bufptr = NULL; + return 0; + } + + avail = VFS_READ(ctx->src, ctx->inbuf, avail); + *bufptr = ctx->inbuf; + + if (avail < 0) + avail = 0; //treated as eof... + ctx->csize -= avail; + return avail; +} +static int FSZIP_Deflate64_Spew(void *vctx, unsigned char *buf, unsigned int buflen) +{ + struct def64ctx *ctx = vctx; + + //update the crc + ctx->crc = crc32(ctx->crc, buf, buflen); + ctx->usize += buflen; + + if (VFS_WRITE(ctx->dst, buf, buflen) != buflen) + return 1; //failure returns non-zero. + return 0; +} +//inflateBack stuff is apparently not restartable, and must read the entire file +static vfsfile_t *FSZIP_Deflate64(vfsfile_t *src, qofs_t csize, qofs_t usize, unsigned int crc32) +{ + z_stream strm = {NULL}; + qbyte window[65536]; + struct def64ctx ctx; + ctx.src = src; + ctx.dst = VFSPIPE_Open(1, true); + ctx.csize = csize; + ctx.usize = 0; + ctx.crc = 0; + + strm.data_type = Z_UNKNOWN; + inflateBack9Init(&strm, window); + //getting inflateBack9 to + if (Z_STREAM_END != inflateBack9(&strm, FSZIP_Deflate64_Grab, &ctx, FSZIP_Deflate64_Spew, &ctx)) + { //some stream error? + Con_Printf("Decompression error\n"); + VFS_CLOSE(ctx.dst); + ctx.dst = NULL; + } + else if (ctx.csize != 0 || ctx.usize != usize) + { //corrupt file table? + Con_Printf("Decompression size error\n"); + Con_Printf("read %i of %i bytes\n", (unsigned)ctx.csize, (unsigned)csize); + Con_Printf("wrote %i of %i bytes\n", (unsigned)ctx.usize, (unsigned)usize); + VFS_CLOSE(ctx.dst); + ctx.dst = NULL; + } + else if (ctx.crc != crc32) + { //corrupt file table? + Con_Printf("CRC32 error\n"); + VFS_CLOSE(ctx.dst); + ctx.dst = NULL; + } + inflateBack9End(&strm); + + return ctx.dst; +} +#endif + #ifdef AVAIL_BZLIB //if the offset is still within our decompressed block then we can just rewind a smidge static qboolean FSZIP_BZip2_Seek(struct decompressstate *st, qofs_t *offset, qofs_t newoffset) @@ -1403,13 +1495,40 @@ static vfsfile_t *QDECL FSZIP_OpenVFS(searchpathfuncs_t *handle, flocation_t *lo vfsz->funcs.WriteBytes = NULL; vfsz->funcs.seekstyle = SS_SLOW; + //NOTE: pf->name is the quakeified name, and may have an extra prefix/stripped prefix for certain zips - different from what you'd see if you opened the zip yourself. this is only relevant for debugging, whuch might be misleading but is not fatal. if (!FSZIP_ValidateLocalHeader(zip, pf, &vfsz->startpos, &datasize)) { - Con_Printf("file %s:%s is incompatible or inconsistent with zip central directory\n", zip->filename, pf->name); + Con_Printf(CON_WARNING"file %s:%s is incompatible or inconsistent with zip central directory\n", zip->filename, pf->name); Z_Free(vfsz); return NULL; } + if (flags & ZFL_DEFLATE64D) + { //crap +#if defined(ZLIB_DEFLATE64) + vfsfile_t *tmp = NULL; + qofs_t startpos = vfsz->startpos; + qofs_t usize = vfsz->length; + qofs_t csize = datasize; + Z_Free(vfsz); + + Con_Printf(CON_WARNING"file %s:%s was compressed with deflate64\n", zip->filename, pf->name); + + if (Sys_LockMutex(zip->mutex)) + { + VFS_SEEK(vfsz->parent->raw, startpos); + tmp = FSZIP_Deflate64(zip->raw, csize, usize, pf->crc); + Sys_UnlockMutex(zip->mutex); + } + + return tmp; +#else + Con_Printf(CON_WARNING"%s:%s: deflate64 not supported\n", COM_SkipPath(zip->filename), pf->name); + Z_Free(vfsz); + return NULL; +#endif + } + if (flags & ZFL_DEFLATED) { #ifdef AVAIL_ZLIB @@ -1670,11 +1789,19 @@ static qboolean FSZIP_ValidateLocalHeader(zipfile_t *zip, zpackfile_t *zfile, qo return false; //FIXME: proper spanned zips fragment compressed data over multiple spans, but we don't support that if (local.cmethod == 0) - return (zfile->flags & (ZFL_STORED|ZFL_CORRUPT|ZFL_DEFLATED|ZFL_BZIP2)) == ZFL_STORED; + return (zfile->flags & ZFL_COMPRESSIONTYPE) == ZFL_STORED; +#if defined(AVAIL_ZLIB) if (local.cmethod == 8) - return (zfile->flags & (ZFL_STORED|ZFL_CORRUPT|ZFL_DEFLATED|ZFL_BZIP2)) == ZFL_DEFLATED; + return (zfile->flags & ZFL_COMPRESSIONTYPE) == ZFL_DEFLATED; +#endif +#if defined(ZLIB_DEFLATE64) + if (local.cmethod == 9) + return (zfile->flags & ZFL_COMPRESSIONTYPE) == ZFL_DEFLATE64D; +#endif +#ifdef AVAIL_BZLIB if (local.cmethod == 12) - return (zfile->flags & (ZFL_STORED|ZFL_CORRUPT|ZFL_DEFLATED|ZFL_BZIP2)) == ZFL_BZIP2; + return (zfile->flags & ZFL_COMPRESSIONTYPE) == ZFL_BZIP2; +#endif return false; //some other method that we don't know. } @@ -1850,7 +1977,8 @@ static qboolean FSZIP_ReadCentralEntry(zipfile_t *zip, qbyte *data, struct zipce //7: tokenize else if (entry->cmethod == 8) //8: deflate entry->flags |= ZFL_DEFLATED; - //9: deflate64 - patented. sometimes written by microsoft's crap, so this might be problematic. only minor improvements. + else if (entry->cmethod == 9) //9: deflate64 - patented. sometimes written by microsoft's crap, so this might be problematic. only minor improvements. + entry->flags |= ZFL_DEFLATE64D; //10: implode else if (entry->cmethod == 12) //12: bzip2 entry->flags |= ZFL_BZIP2; diff --git a/engine/common/gl_q2bsp.c b/engine/common/gl_q2bsp.c index ce5a05748..20f4718a6 100644 --- a/engine/common/gl_q2bsp.c +++ b/engine/common/gl_q2bsp.c @@ -69,7 +69,6 @@ struct cminfo_s; void CM_Init(void); -static qboolean CM_HeadnodeVisible (struct model_s *mod, int nodenum, const qbyte *visbits); static qboolean VARGS CM_AreasConnected (struct model_s *mod, unsigned int area1, unsigned int area2); static size_t CM_WriteAreaBits (struct model_s *mod, qbyte *buffer, size_t buffersize, int area, qboolean merge); static qbyte *CM_ClusterPVS (struct model_s *mod, int cluster, pvsbuffer_t *buffer, pvsmerge_t merge); @@ -82,9 +81,12 @@ static void CM_SetAreaPortalState (model_t *mod, unsigned int portalnum, unsigne static size_t CM_SaveAreaPortalBlob (model_t *mod, void **data); static size_t CM_LoadAreaPortalBlob (model_t *mod, void *ptr, size_t ptrsize); +#ifdef HAVE_SERVER static unsigned int Q23BSP_FatPVS(model_t *mod, const vec3_t org, pvsbuffer_t *buffer, qboolean merge); static qboolean Q23BSP_EdictInFatPVS(model_t *mod, const struct pvscache_s *ent, const qbyte *pvs, const int *areas); static void Q23BSP_FindTouchedLeafs(model_t *mod, struct pvscache_s *ent, const float *mins, const float *maxs); +static qboolean CM_HeadnodeVisible (struct model_s *mod, int nodenum, const qbyte *visbits); +#endif #ifdef HAVE_CLIENT static void CM_PrepareFrame(model_t *mod, refdef_t *refdef, int area, int viewclusters[2], pvsbuffer_t *vis, qbyte **entvis_out, qbyte **surfvis_out); @@ -557,7 +559,8 @@ static void Patch_Evaluate_QuadricBezier( float t, const vec_t *point0, const ve Patch_Evaluate =============== */ -static void Patch_Evaluate( const vec_t *p, const unsigned short *numcp, const int *tess, vec_t *dest, int comp ) +#define Patch_Evaluate(p,numcp,tess,dest, comp) Patch_EvaluateStride(p,comp,numcp,tess,dest,comp,comp) +static void Patch_EvaluateStride(const vec_t *p, int pstride, const unsigned short *numcp, const int *tess, vec_t *dest, int deststride, int comp) { int num_patches[2], num_tess[2]; int index[3], dstpitch, i, u, v, x, y; @@ -568,14 +571,15 @@ static void Patch_Evaluate( const vec_t *p, const unsigned short *numcp, const i if (!tess[0] || !tess[1]) { //not really a patch - for( i = 0; i < comp*numcp[1]*numcp[0]; i++ ) - dest[i] = p[i]; + for( u = 0; u < numcp[1]*numcp[0]; u++, dest += deststride, p += pstride) + for( i = 0; i < comp; i++ ) + dest[i] = p[i]; return; } num_patches[0] = numcp[0] / 2; num_patches[1] = numcp[1] / 2; - dstpitch = ( num_patches[0] * tess[0] + 1 ) * comp; + dstpitch = ( num_patches[0] * tess[0] + 1 ) * deststride; step[0] = 1.0f / (float)tess[0]; step[1] = 1.0f / (float)tess[1]; @@ -603,24 +607,113 @@ static void Patch_Evaluate( const vec_t *p, const unsigned short *numcp, const i // current 3x3 patch control points for( i = 0; i < 3; i++ ) { - pv[i][0] = &p[( index[0]+i ) * comp]; - pv[i][1] = &p[( index[1]+i ) * comp]; - pv[i][2] = &p[( index[2]+i ) * comp]; + pv[i][0] = &p[( index[0]+i ) * pstride]; + pv[i][1] = &p[( index[1]+i ) * pstride]; + pv[i][2] = &p[( index[2]+i ) * pstride]; } - tvec = dest + v * tess[1] * dstpitch + u * tess[0] * comp; + tvec = dest + v * tess[1] * dstpitch + u * tess[0] * deststride; for( y = 0, t = 0.0f; y < num_tess[1]; y++, t += step[1], tvec += dstpitch ) { Patch_Evaluate_QuadricBezier( t, pv[0][0], pv[0][1], pv[0][2], v1, comp ); Patch_Evaluate_QuadricBezier( t, pv[1][0], pv[1][1], pv[1][2], v2, comp ); Patch_Evaluate_QuadricBezier( t, pv[2][0], pv[2][1], pv[2][2], v3, comp ); - for( x = 0, tvec2 = tvec, s = 0.0f; x < num_tess[0]; x++, s += step[0], tvec2 += comp ) + for( x = 0, tvec2 = tvec, s = 0.0f; x < num_tess[0]; x++, s += step[0], tvec2 += deststride ) Patch_Evaluate_QuadricBezier( s, v1, v2, v3, tvec2, comp ); } } } } +#ifdef TERRAIN +#include "gl_terrain.h" +patchtessvert_t *PatchInfo_Evaluate(const qcpatchvert_t *cp, const unsigned short patch_cp[2], const short subdiv[2], unsigned short *size) +{ + int step[2], flat[2]; + float subdivlevel; + unsigned int numverts; + patchtessvert_t *out; + int i; + + if (subdiv[0]>=0 && subdiv[1]>=0) + { //fixed + step[0] = subdiv[0]; + step[1] = subdiv[1]; + } + else + { + // find the degree of subdivision in the u and v directions + subdivlevel = bound(1, r_subdivisions.ival, 15); + Patch_GetFlatness ( subdivlevel, cp->v, sizeof(*cp)/sizeof(vec_t), patch_cp, flat ); + + step[0] = 1 << flat[0]; + step[1] = 1 << flat[1]; + } + if (!step[0] || !step[1]) + { + size[0] = patch_cp[0]; + size[1] = patch_cp[1]; + } + else + { + size[0] = ( patch_cp[0] >> 1 ) * step[0] + 1; + size[1] = ( patch_cp[1] >> 1 ) * step[1] + 1; + } + if( size[0] <= 0 || size[1] <= 0 ) + return NULL; + + numverts = (unsigned int)size[0] * size[1]; + +// fill in + + out = BZ_Malloc(sizeof(*out) * numverts); + for (i = 0; i < numverts*sizeof(*out)/sizeof(vec_t); i++) + ((vec_t *)out)[i] = -1; + Patch_EvaluateStride ( cp->v, sizeof(*cp)/sizeof(vec_t), patch_cp, step, out->v, sizeof(*out)/sizeof(vec_t), countof(cp->v)); + Patch_EvaluateStride ( cp->rgba, sizeof(*cp)/sizeof(vec_t), patch_cp, step, out->rgba, sizeof(*out)/sizeof(vec_t), countof(cp->rgba)); + Patch_EvaluateStride ( cp->tc, sizeof(*cp)/sizeof(vec_t), patch_cp, step, out->tc, sizeof(*out)/sizeof(vec_t), countof(cp->tc)); + + return out; +} +unsigned int PatchInfo_EvaluateIndexes(const unsigned short *size, index_t *out_indexes) +{ + int i, u, v, p; +// compute new indexes avoiding adding invalid triangles + unsigned int numindexes = 0; + index_t *indexes = out_indexes; + for (v = 0, i = 0; v < size[1]-1; v++) + { + for (u = 0; u < size[0]-1; u++, i += 6) + { + indexes[0] = p = v * size[0] + u; + indexes[1] = p + size[0]; + indexes[2] = p + 1; + +// if ( !VectorEquals(mesh->xyz_array[indexes[0]], mesh->xyz_array[indexes[1]]) && +// !VectorEquals(mesh->xyz_array[indexes[0]], mesh->xyz_array[indexes[2]]) && +// !VectorEquals(mesh->xyz_array[indexes[1]], mesh->xyz_array[indexes[2]]) ) + { + indexes += 3; + numindexes += 3; + } + + indexes[0] = p + 1; + indexes[1] = p + size[0]; + indexes[2] = p + size[0] + 1; + +// if ( !VectorEquals(mesh->xyz_array[indexes[0]], mesh->xyz_array[indexes[1]]) && +// !VectorEquals(mesh->xyz_array[indexes[0]], mesh->xyz_array[indexes[2]]) && +// !VectorEquals(mesh->xyz_array[indexes[1]], mesh->xyz_array[indexes[2]]) ) + { + indexes += 3; + numindexes += 3; + } + } + } + + return numindexes; +} +#endif #define PLANE_NORMAL_EPSILON 0.00001 @@ -4123,7 +4216,7 @@ static void CM_OpenAllPortals(model_t *mod, char *ents) //this is a compleate ha #endif -#if defined(HAVE_SERVER) && defined(Q3BSPS) +#if defined(Q3BSPS) static void CalcClusterPHS(cminfo_t *prv, int cluster) { int j, k, l, index; @@ -4167,6 +4260,8 @@ static void CalcClusterPHS(cminfo_t *prv, int cluster) } prv->phscalced[cluster>>3] |= 1<<(cluster&7); } +#endif +#if defined(HAVE_SERVER) && defined(Q3BSPS) static void CMQ3_CalcPHS (model_t *mod) { cminfo_t *prv = (cminfo_t*)mod->meshinfo; @@ -4517,7 +4612,7 @@ static cmodel_t *CM_LoadMap (model_t *mod, qbyte *filein, size_t filelen, qboole return NULL; } - checksum = LittleLong (Com_BlockChecksum (buf, length)); + checksum = LittleLong (CalcHashInt(&hash_md4, buf, length)); header = *(q2dheader_t *)(buf); header.ident = LittleLong(header.ident); @@ -5197,12 +5292,14 @@ static int CM_PointLeafnum_r (model_t *mod, const vec3_t p, int num) return -1 - num; } +#ifdef HAVE_SERVER static int CM_PointLeafnum (model_t *mod, const vec3_t p) { if (!mod || mod->loadstate != MLS_LOADED) return 0; // sound may call this without map loaded return CM_PointLeafnum_r (mod, p, 0); } +#endif static int CM_PointCluster (model_t *mod, const vec3_t p, int *area) { @@ -6884,6 +6981,7 @@ static qbyte *CM_ClusterPHS (model_t *mod, int cluster, pvsbuffer_t *buffer) return buffer->buffer; } +#ifdef HAVE_SERVER static unsigned int SV_Q2BSP_FatPVS (model_t *mod, const vec3_t org, pvsbuffer_t *result, qboolean merge) { int leafs[64]; @@ -7053,6 +7151,7 @@ static void Q23BSP_FindTouchedLeafs(model_t *model, struct pvscache_s *ent, cons } } } +#endif /* =============================================================================== @@ -7314,6 +7413,7 @@ static size_t CM_LoadAreaPortalBlob (model_t *mod, void *ptr, size_t ptrsize) return 0; } +#ifdef HAVE_SERVER /* ============= CM_HeadnodeVisible @@ -7344,6 +7444,7 @@ static qboolean CM_HeadnodeVisible (model_t *mod, int nodenum, const qbyte *visb return true; return CM_HeadnodeVisible(mod, node->childnum[1], visbits); } +#endif static unsigned int Q2BSP_PointContents(model_t *mod, const vec3_t axis[3], const vec3_t p) { diff --git a/engine/common/log.c b/engine/common/log.c index e58004cd4..c0a0da5d2 100644 --- a/engine/common/log.c +++ b/engine/common/log.c @@ -695,7 +695,10 @@ static void CertLog_Write(void) VFS_PUTS(f, certhex); VFS_PRINTF(f, "\" %i\n", l->trusted?true:false); } + VFS_CLOSE(f); } + else + Con_Printf(CON_ERROR"Unable to write %s\n", CERTLOG_FILENAME); } static void CertLog_Purge(void) { @@ -753,6 +756,7 @@ static void CertLog_Import(const char *filename) } CertLog_Update(addressstring, certdata, certsize, atoi(trusted)); } + VFS_CLOSE(f); } static void CertLog_UntrustAll_f(void) { @@ -792,9 +796,10 @@ static void CertLog_Add_Prompted(void *vctx, promptbutton_t button) } qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems) { //this is specifically for dtls certs. - extern cvar_t net_enable_dtls; struct certlog_s *l; qboolean trusted = (net_enable_dtls.ival >= 2); + char digest[DIGEST_MAXSIZE]; + char fp[DIGEST_MAXSIZE*2+1]; if (certlog_curprompt) return false; @@ -808,11 +813,20 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, { //cert is new, but we don't care about full trust. don't bother to prompt when the user doesn't much care. //(but do pin so we at least know when its MITMed after the fact) Con_Printf(CON_WARNING"Auto-Pinning certificate for %s."CON_DEFAULT" ^[/seta %s 2^]+ for actual security.\n", hostname, net_enable_dtls.name); + if (certsize) + Base64_EncodeBlockURI(digest, CalcHash(&hash_sha1, digest, sizeof(digest), cert, certsize), fp, sizeof(fp)); + else + strcpy(fp, ""); + Con_Printf(S_COLOR_GRAY" fp: %s\n", fp); CertLog_Update(hostname, cert, certsize, false); CertLog_Write(); } else if (!l || l->certsize != certsize || memcmp(l->cert, cert, certsize) || (trusted && !l->trusted)) { //new or different + if (certsize) + Base64_EncodeBlockURI(digest, CalcHash(&hash_sha1, digest, sizeof(digest), cert, certsize), fp, sizeof(fp)); + else + strcpy(fp, ""); if (qrenderer) { unsigned int i; @@ -820,7 +834,7 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, char *text; const char *accepttext; const char *lines[] = { - va(localtext("Certificate for %s\n"), hostname), + va(localtext("Certificate for %s\n(fp:"S_COLOR_GRAY"%s"S_COLOR_WHITE")\n"), hostname, fp), (certlogproblems&CERTLOG_WRONGHOST)?localtext("^1Certificate does not match host\n"):"", ((certlogproblems&(CERTLOG_MISSINGCA|CERTLOG_WRONGHOST))==CERTLOG_MISSINGCA)?localtext("^1Certificate authority is untrusted.\n"):"", (certlogproblems&CERTLOG_EXPIRED)?localtext("^1Expired Certificate\n"):"", diff --git a/engine/common/md4.c b/engine/common/md4.c index 0e08ff9cd..4edb246f0 100644 --- a/engine/common/md4.c +++ b/engine/common/md4.c @@ -51,7 +51,7 @@ typedef struct { } MD4_CTX; void MD4Init (MD4_CTX *); -void MD4Update (MD4_CTX *, unsigned char *, unsigned int); +void MD4Update (MD4_CTX *, const unsigned char *, size_t); void MD4Final (unsigned char [16], MD4_CTX *); @@ -84,9 +84,9 @@ These notices must be retained in any copies of any part of this documentation a #define S33 11 #define S34 15 -static void MD4Transform (UINT4 [4], unsigned char [64]); +static void MD4Transform (UINT4 [4], const unsigned char [64]); static void Encode (unsigned char *, UINT4 *, unsigned int); -static void Decode (UINT4 *, unsigned char *, unsigned int); +static void Decode (UINT4 *, const unsigned char *, unsigned int); static unsigned char PADDING[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -122,7 +122,7 @@ context->state[3] = 0x10325476; } /* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ -void MD4Update (MD4_CTX *context, unsigned char *input, unsigned int inputLen) +void MD4Update (MD4_CTX *context, const unsigned char *input, size_t inputLen) { unsigned int i, index, partLen; @@ -182,7 +182,7 @@ void MD4Final (unsigned char digest[16], MD4_CTX *context) /* MD4 basic transformation. Transforms state based on block. */ -static void MD4Transform (UINT4 state[4], unsigned char block[64]) +static void MD4Transform (UINT4 state[4], const unsigned char block[64]) { UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; @@ -267,7 +267,7 @@ static void Encode (unsigned char *output, UINT4 *input, unsigned int len) /* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ -static void Decode (UINT4 *output, unsigned char *input, unsigned int len) +static void Decode (UINT4 *output, const unsigned char *input, unsigned int len) { unsigned int i, j; @@ -276,40 +276,13 @@ for (i = 0, j = 0; j < len; i++, j += 4) } //=================================================================== - -unsigned int Com_BlockChecksum (void *buffer, int length) +#include "quakedef.h" +hashfunc_t hash_md4 = { - unsigned int digest[4]; - unsigned int val; - MD4_CTX ctx; + 16, //digest size + sizeof(MD4_CTX), + (void(*)(void*ctx))MD4Init, + (void(*)(void*ctx,const void*in,size_t))MD4Update, + (void(*)(qbyte*out,void*ctx))MD4Final, +}; - MD4Init (&ctx); - MD4Update (&ctx, (unsigned char *)buffer, length); - MD4Final ( (unsigned char *)digest, &ctx); - - val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; - - return val; -} - -void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf) -{ - MD4_CTX ctx; - - MD4Init (&ctx); - MD4Update (&ctx, (unsigned char *)buffer, len); - MD4Final ( outbuf, &ctx); -} - - -void Com_BlocksChecksum (int blocks, void **buffer, int *len, unsigned char *outbuf) -{ - MD4_CTX ctx; - - MD4Init (&ctx); - while(blocks --> 0) - { - MD4Update (&ctx, (unsigned char *)*buffer++, *len++); - } - MD4Final (outbuf, &ctx); -} diff --git a/engine/common/net.h b/engine/common/net.h index 3994fc337..79a815134 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -151,17 +151,18 @@ neterr_t NET_SendPacket (struct ftenet_connections_s *col, int length, const voi int NET_LocalAddressForRemote(struct ftenet_connections_s *collection, netadr_t *remote, netadr_t *local, int idx); void NET_PrintAddresses(struct ftenet_connections_s *collection); qboolean NET_AddressSmellsFunny(netadr_t *a); -qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, char *host, netadr_t *adr); +struct dtlspeercred_s; +qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr, qboolean outgoing); void NET_TerminateRoute(struct ftenet_connections_s *collection, netadr_t *adr); void NET_PrintConnectionsStatus(struct ftenet_connections_s *collection); enum addressscope_e { - ASCOPE_PROCESS=0, - ASCOPE_HOST=1, - ASCOPE_LINK=2, - ASCOPE_LAN=3, - ASCOPE_NET=4 + ASCOPE_PROCESS=0, //unusable + ASCOPE_HOST=1, //unroutable + ASCOPE_LINK=2, //unpredictable + ASCOPE_LAN=3, //private + ASCOPE_NET=4 //aka hopefully globally routable }; enum addressscope_e NET_ClassifyAddress(netadr_t *adr, const char **outdesc); @@ -174,6 +175,7 @@ char *NET_AdrToString (char *s, int len, netadr_t *a); char *NET_SockadrToString (char *s, int len, struct sockaddr_qstorage *a, size_t sizeofa); char *NET_BaseAdrToString (char *s, int len, netadr_t *a); size_t NET_StringToSockaddr2 (const char *s, int defaultport, netadrtype_t afhint, struct sockaddr_qstorage *sadr, int *addrfamily, int *addrsize, size_t addrcount); +qboolean NET_StringToAdr_NoDNS(const char *address, int port, netadr_t *out); #define NET_StringToSockaddr(s,p,a,f,z) (NET_StringToSockaddr2(s,p,NA_INVALID,a,f,z,1)>0) size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t addrcount, const char **pathstart); #define NET_StringToAdr(s,p,a) NET_StringToAdr2(s,p,a,1,NULL) @@ -191,23 +193,27 @@ qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *na enum certprops_e { - QCERT_PEERFINGERPRINT + QCERT_ISENCRYPTED, //0 or error + QCERT_PEERSUBJECT, //null terminated. should be a hash of the primary cert, ignoring chain. + QCERT_PEERCERTIFICATE, //should be the primary cert, ignoring chain. no fixed maximum size required, mostly 2k but probably best to allow at leasy 5k.. or 8k. + + QCERT_LOCALCERTIFICATE, //the cert we're using/advertising. may have no context. to tell people what fp to expect. }; -size_t NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize); +int NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize); #ifdef HAVE_DTLS struct dtlscred_s; struct dtlsfuncs_s; -qboolean NET_DTLS_Create(struct ftenet_connections_s *col, netadr_t *to, const struct dtlscred_s *cred); +qboolean NET_DTLS_Create(struct ftenet_connections_s *col, netadr_t *to, const struct dtlscred_s *cred, qboolean outgoing); qboolean NET_DTLS_Decode(struct ftenet_connections_s *col); qboolean NET_DTLS_Disconnect(struct ftenet_connections_s *col, netadr_t *to); -void NET_DTLS_Timeouts(struct ftenet_connections_s *col); extern cvar_t dtls_psk_hint, dtls_psk_user, dtls_psk_key; +extern cvar_t net_enable_dtls; #endif #ifdef SUPPORT_ICE neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to); void ICE_Terminate(netadr_t *to); //if we kicked the client/etc, kill their ICE too. -qboolean ICE_IsEncrypted(netadr_t *to); +int ICE_GetPeerCertificate(netadr_t *to, enum certprops_e prop, char *out, size_t outsize); void ICE_Init(void); #endif extern cvar_t timeout; diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index b68224700..f66c98d3e 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -203,6 +203,7 @@ unsigned int Net_PextMask(unsigned int protover, qboolean fornq) #ifdef PEXT_Q3BSP PEXT_Q3BSP | #endif + PEXT_TE_BULLET | //qw's gunshot+explosions etc. PEXT_FLOATCOORDS | PEXT_HLBSP; //these all depend fully upon the player/entity deltas, and don't make sense for NQ. Implement PEXT2_REPLACEMENTDELTAS instead. diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c index aae1f0027..0a1576aec 100644 --- a/engine/common/net_ice.c +++ b/engine/common/net_ice.c @@ -1,3 +1,43 @@ +/* +Interactive Connectivity Establishment (rfc 5245) +find out your peer's potential ports. +spam your peer with stun packets. +see what sticks. +the 'controller' assigns some final candidate pair to ensure that both peers send+receive from a single connection. +if no candidates are available, try using stun to find public nat addresses. + +in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. + this does limit which interfaces we can send packets from, which may cause issues with local TURN relays(does not result in extra prflx candidates) and VPNs(may need to be set up as the default route), and prevents us from being able to report reladdr in candidate offers (again mostly only of use with TURN) + lan connections should resolve to a host interface anyway + +stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. +peers don't like it when those are missing. + +host candidates - addresses that are directly known (but are probably unroutable private things) +server reflexive candidates - addresses that we found from some public stun server (useful for NATs that use a single public port for each unique private port) +peer reflexive candidates - addresses that our peer finds out about as we spam them +relayed candidates - some sort of socks5 or something proxy. + + +Note: Even after the ICE connection becomes active, you should continue to collect local candidates and transmit them to the peer out of band. +this allows the connection to pick a new route if some router dies (like a relay kicking us). +FIXME: the client currently disconnects from the broker. the server tracks players via ip rather than ICE. + +tcp rtp framing should generally be done with a 16-bit network-endian length prefix followed by the data. + +NOTE: we do NOT distinguish between media-level and session-level attributes, as such we can only handle ONE media stream per session. we also don't support rtcp. +*/ +/* +webrtc +basically just sctp-over-dtls-over-ice or srtp(negotiated via dtls)-over-ice. +the sctp part is pure bloat+pain for us, but as its required for browser compat we have to support it anyway - but we only use it where we must. +we don't do any srtp stuff at all. +*/ +/* +broker +ftemaster provides a broker service +*/ + #include "quakedef.h" #include "netinc.h" @@ -62,7 +102,7 @@ typedef struct #include "zlib.h" #endif #ifdef SUPPORT_ICE -static cvar_t net_ice_exchangeprivateips = CVARFD("net_ice_exchangeprivateips", "", CVAR_NOTFROMSERVER, "Boolean. When set to 0, hides private IP addresses from your peers. Only addresses determined from the other side of your router will be shared. Setting it to 0 may be desirable but it can cause connections to fail when your router does not support hairpinning, whereas 1 fixes that at the cost of exposing private IP addresses."); +static cvar_t net_ice_exchangeprivateips = CVARFD("net_ice_exchangeprivateips", "0", CVAR_NOTFROMSERVER, "Boolean. When set to 0, hides private IP addresses from your peers - only addresses determined from the other side of your router will be shared. You should only need to set this to 1 if mdns is unavailable."); static cvar_t net_ice_allowstun = CVARFD("net_ice_allowstun", "1", CVAR_NOTFROMSERVER, "Boolean. When set to 0, prevents the use of stun to determine our public address (does not prevent connecting to our peer's server-reflexive candidates)."); static cvar_t net_ice_allowturn = CVARFD("net_ice_allowturn", "1", CVAR_NOTFROMSERVER, "Boolean. When set to 0, prevents registration of turn connections (does not prevent connecting to our peer's relay candidates)."); static cvar_t net_ice_allowmdns = CVARFD("net_ice_allowmdns", "1", CVAR_NOTFROMSERVER, "Boolean. When set to 0, prevents the use of multicast-dns to obtain candidates using random numbers instead of revealing private network info."); @@ -77,36 +117,6 @@ static cvar_t net_ice_debug = CVARFD("net_ice_debug", "0", CVAR_NOTFROMSERVER, " #define ASCOPE_TURN_REQUIRESCOPE ASCOPE_LAN //don't report loopback/link-local addresses to turn relays. #endif -/* -Interactive Connectivity Establishment (rfc 5245) -find out your peer's potential ports. -spam your peer with stun packets. -see what sticks. -the 'controller' assigns some final candidate pair to ensure that both peers send+receive from a single connection. -if no candidates are available, try using stun to find public nat addresses. - -in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. - this does limit which interfaces we can send packets from, which may cause issues with local TURN relays(does not result in extra prflx candidates) and VPNs(may need to be set up as the default route), and prevents us from being able to report reladdr in candidate offers (again mostly only of use with TURN) - lan connections should resolve to a host interface anyway - -stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. -peers don't like it when those are missing. - -host candidates - addresses that are directly known (but are probably unroutable private things) -server reflexive candidates - addresses that we found from some public stun server (useful for NATs that use a single public port for each unique private port) -peer reflexive candidates - addresses that our peer finds out about as we spam them -relayed candidates - some sort of socks5 or something proxy. - - -Note: Even after the ICE connection becomes active, you should continue to collect local candidates and transmit them to the peer out of band. -this allows the connection to pick a new route if some router dies (like a relay kicking us). -FIXME: the client currently disconnects from the broker. the server tracks players via ip rather than ICE. - -tcp rtp framing should generally be done with a 16-bit network-endian length prefix followed by the data. - -NOTE: we do NOT distinguish between media-level and session-level attributes, as such we can only handle ONE media stream per session. we also don't support rtcp. -*/ - struct icecandidate_s { struct icecandinfo_s info; @@ -214,10 +224,22 @@ struct icestate_s int id; char *name; } codecslot[34]; //96-127. don't really need to care about other ones. + + struct + { //this block is for our inbound udp broker reliability, ensuring we get candidate info to where its needed... + char *text; + unsigned int inseq; + unsigned int outseq; + } u; }; typedef struct sctp_s { + char *friendlyname; //for printing/debugging. + struct icestate_s *icestate; //for forwarding over ice connections... + const struct dtlsfuncs_s *dtlsfuncs; //for forwarding over dtls connects (ice-lite) + void *dtlsstate; + quint16_t myport, peerport; qboolean peerhasfwdtsn; double nextreinit; @@ -256,12 +278,26 @@ typedef struct sctp_s unsigned short qstreamid; //in network endian. } sctp_t; #ifdef HAVE_DTLS -extern cvar_t net_enable_dtls; -static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void *data, size_t length); + +static const struct +{ + const char *name; + hashfunc_t *hash; +} webrtc_hashes[] = +{ //RFC8211 specifies this list of hashes +// {"md2", &hash_md2}, //deprecated, hopefully we won't see it +// {"md5", &hash_md5}, //deprecated, hopefully we won't see it + {"sha-1", &hash_sha1}, + {"sha-224", &hash_sha2_224}, + {"sha-256", &hash_sha2_256}, + {"sha-384", &hash_sha2_384}, + {"sha-512", &hash_sha2_512}, +}; #endif static neterr_t ICE_Transmit(void *cbctx, const qbyte *data, size_t datasize); static neterr_t TURN_Encapsulate(struct icestate_s *ice, netadr_t *to, const qbyte *data, size_t datasize); static void TURN_AuthorisePeer(struct icestate_s *con, struct iceserver_s *srv, int peer); +static neterr_t SCTP_Transmit(sctp_t *sctp, const void *data, size_t length); static struct icestate_s *icelist; @@ -669,7 +705,8 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co con = Z_Malloc(sizeof(*con)); con->conname = Z_StrDup(conname); - con->friendlyname = peername?Z_StrDup(peername):Z_StrDupf("%i", icenum++); + icenum++; + con->friendlyname = peername?Z_StrDup(peername):Z_StrDupf("%i", icenum); con->proto = proto; con->rpwd = Z_StrDup(""); con->rufrag = Z_StrDup(""); @@ -724,6 +761,7 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co con->qadr.type = NA_ICE; con->qadr.prot = NP_DGRAM; Q_strncpyz(con->qadr.address.icename, con->friendlyname, sizeof(con->qadr.address.icename)); + con->qadr.port = icenum; con->mode = mode; @@ -1583,7 +1621,7 @@ static void MDNS_ProcessPacket(qbyte *inmsg, size_t inmsgsize, netadr_t *source) static void MDNS_ReadPackets(void) { qbyte inmsg[9000]; - ssize_t inmsgsize; + int inmsgsize; netadr_t adr; struct sockaddr_qstorage source; @@ -1912,26 +1950,24 @@ static void ICE_ParseSDPLine(struct icestate_s *con, const char *value) } else if (!strncmp(value, "a=fingerprint:", 14)) { + hashfunc_t *hash = NULL; + int i; char name[64]; value = COM_ParseOut(value+14, name, sizeof(name)); - if (!strcasecmp(name, "sha-1")) - con->cred.peer.hash = &hash_sha1; - else if (!strcasecmp(name, "sha-224")) - con->cred.peer.hash = &hash_sha224; - else if (!strcasecmp(name, "sha-256")) - con->cred.peer.hash = &hash_sha256; - else if (!strcasecmp(name, "sha-384")) - con->cred.peer.hash = &hash_sha384; - else if (!strcasecmp(name, "sha-512")) - con->cred.peer.hash = &hash_sha512; - else - con->cred.peer.hash = NULL; //hash not recognised - if (con->cred.peer.hash) + for (i = 0; i < countof(webrtc_hashes); i++) + { + if (!strcasecmp(name, webrtc_hashes[i].name)) + { + hash = webrtc_hashes[i].hash; + break; + } + } + if (hash && (!con->cred.peer.hash || hash->digestsize>con->cred.peer.hash->digestsize)) //FIXME: digest size is not a good indicator of whether its exploitable or not, but should work for sha1/sha2 options. the sender here is expected to be trustworthy anyway. { int b, o, v; while (*value == ' ') value++; - for (b = 0; b < con->cred.peer.hash->digestsize; ) + for (b = 0; b < hash->digestsize; ) { v = *value; if (v >= '0' && v <= '9') @@ -1958,8 +1994,10 @@ static void ICE_ParseSDPLine(struct icestate_s *con, const char *value) break; value++; } - if (b != con->cred.peer.hash->digestsize) - con->cred.peer.hash = NULL; //bad! + if (b == hash->digestsize) + con->cred.peer.hash = hash; //it was the right size, woo. + else + con->cred.peer.hash = NULL; //bad! (should we 0-pad?) } } else if (!strncmp(value, "a=sctp-port:", 12)) @@ -2088,9 +2126,12 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch } else if (!strcmp(value, STRINGIFY(ICE_FAILED))) { - con->state = ICE_FAILED; - if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: ice state failed\n", con->friendlyname); + if (con->state != ICE_FAILED) + { + con->state = ICE_FAILED; + if (net_ice_debug.ival >= 1) + Con_Printf(S_COLOR_GRAY"[%s]: ice state failed\n", con->friendlyname); + } } else if (!strcmp(value, STRINGIFY(ICE_CONNECTED))) { @@ -2129,7 +2170,7 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch else if (net_enable_dtls.ival >= 3) { //peer doesn't seem to support dtls. con->state = ICE_FAILED; - Con_Printf(CON_WARNING"WARNING: [%s]: peer does not support dtls. Set net_enable_dtls to 0 to make optional.\n", con->friendlyname); + Con_Printf(CON_WARNING"WARNING: [%s]: peer does not support dtls. Set net_enable_dtls to 1 to make optional.\n", con->friendlyname); } else if (con->state == ICE_CONNECTING && net_enable_dtls.ival>=2) Con_Printf(CON_WARNING"WARNING: [%s]: peer does not support dtls.\n", con->friendlyname); @@ -2137,6 +2178,8 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch if (!con->sctp && (!con->sctpoptional || !con->peersctpoptional) && con->mysctpport && con->peersctpport) { con->sctp = Z_Malloc(sizeof(*con->sctp)); + con->sctp->icestate = con; + con->sctp->friendlyname = con->friendlyname; con->sctp->myport = htons(con->mysctpport); con->sctp->peerport = htons(con->peersctpport); con->sctp->o.tsn = rand() ^ (rand()<<16); @@ -2147,7 +2190,10 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch else if (!con->dtlsstate && con->cred.peer.hash) { if (!con->peersctpoptional) - Con_Printf(CON_WARNING"WARNING: [%s]: peer is trying to use dtls.\n", con->friendlyname); + { + con->state = ICE_FAILED; + Con_Printf(CON_WARNING"WARNING: [%s]: peer is trying to use dtls.%s\n", con->friendlyname, net_enable_dtls.ival?"":" Set ^[/net_enable_dtls 1^]."); + } } #endif } @@ -2468,14 +2514,12 @@ static qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *va { int b; Q_strncatz(value, "a=fingerprint:", valuelen); - if (con->cred.peer.hash == &hash_sha1) - Q_strncatz(value, "sha-1", valuelen); - else if (con->cred.peer.hash == &hash_sha256) - Q_strncatz(value, "sha-256", valuelen); - else if (con->cred.peer.hash == &hash_sha512) - Q_strncatz(value, "sha-512", valuelen); - else - Q_strncatz(value, "UNKNOWN", valuelen); + for (b = 0; b < countof(webrtc_hashes); b++) + { + if (con->cred.peer.hash == webrtc_hashes[b].hash) + break; + } + Q_strncatz(value, (b==countof(webrtc_hashes))?"UNKNOWN":webrtc_hashes[b].name, valuelen); for (b = 0; b < con->cred.peer.hash->digestsize; b++) Q_strncatz(value, va(b?":%02X":" %02X", con->cred.peer.digest[b]), valuelen); Q_strncatz(value, "\n", valuelen); @@ -2530,16 +2574,17 @@ static qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *va { //this is a preliminary check to avoid wasting time if (!con->cred.local.certsize) return false; //fail if we cannot do dtls when its required. - if (!strcmp(prop, "sdpanswer") || !con->cred.peer.hash) + if (!strcmp(prop, "sdpanswer") && !con->cred.peer.hash) return false; //don't answer if they failed to provide a cert } if (con->cred.local.certsize) { qbyte fingerprint[DIGEST_MAXSIZE]; int b; - CalcHash(&hash_sha256, fingerprint, sizeof(fingerprint), con->cred.local.cert, con->cred.local.certsize); + hashfunc_t *hash = &hash_sha2_256; //browsers use sha-256, lets match them. + CalcHash(hash, fingerprint, sizeof(fingerprint), con->cred.local.cert, con->cred.local.certsize); Q_strncatz(value, "a=fingerprint:sha-256", valuelen); - for (b = 0; b < hash_sha256.digestsize; b++) + for (b = 0; b < hash->digestsize; b++) Q_strncatz(value, va(b?":%02X":" %02X", fingerprint[b]), valuelen); Q_strncatz(value, "\n", valuelen); @@ -2560,6 +2605,16 @@ static qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *va } // Q_strncatz(value, va("c=IN %s %s\n", sender.type==NA_IPV6?"IP6":"IP4", NET_BaseAdrToString(tmpstr, sizeof(tmpstr), &sender)), valuelen); Q_strncatz(value, "c=IN IP4 0.0.0.0\n", valuelen); + + for (can = con->lc; can; can = can->next) + { + char canline[256]; + can->dirty = false; //doesn't matter now. + ICE_CandidateToSDP(can, canline, sizeof(canline)); + Q_strncatz(value, canline, valuelen); + Q_strncatz(value, "\n", valuelen); + } + Q_strncatz(value, va("a=ice-pwd:%s\n", con->lpwd), valuelen); Q_strncatz(value, va("a=ice-ufrag:%s\n", con->lufrag), valuelen); @@ -3045,7 +3100,7 @@ void ICE_Tick(void) { #ifdef HAVE_DTLS if (con->sctp) - SCTP_Transmit(con->sctp, con, NULL,0); //try to keep it ticking... + SCTP_Transmit(con->sctp, NULL,0); //try to keep it ticking... if (con->dtlsstate) con->dtlsfuncs->Timeouts(con->dtlsstate); #endif @@ -3105,7 +3160,8 @@ icefuncs_t iceapi = ICE_AddRCandidateInfo, ICE_Close, ICE_CloseModule, - ICE_GetLCandidateSDP + ICE_GetLCandidateSDP, + ICE_Find }; #endif @@ -3188,9 +3244,12 @@ struct sctp_chunk_fwdtsn_s } streams[];*/ }; -static neterr_t SCTP_PeerSendPacket(struct icestate_s *peer, int length, const void *data) +static neterr_t SCTP_PeerSendPacket(sctp_t *sctp, int length, const void *data) { //sends to the dtls layer (which will send to the generic ice dispatcher that'll send to the dgram stuff... layers on layers. - if (peer) + struct icestate_s *peer = sctp->icestate; + if (sctp->dtlsstate) + return sctp->dtlsfuncs->Transmit(sctp->dtlsstate, data, length); + else if (peer) { if (peer->dtlsstate) return peer->dtlsfuncs->Transmit(peer->dtlsstate, data, length); @@ -3224,7 +3283,7 @@ static quint32_t SCTP_Checksum(const struct sctp_header_s *h, size_t size) return ~crc; } -static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void *data, size_t length) +neterr_t SCTP_Transmit(sctp_t *sctp, const void *data, size_t length) { qbyte pkt[65536]; size_t pktlen = 0; @@ -3278,7 +3337,7 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void pktlen += sizeof(*init) + sizeof(*ftsn); h->crc = SCTP_Checksum(h, pktlen); - return SCTP_PeerSendPacket(peer, pktlen, h); + return SCTP_PeerSendPacket(sctp, pktlen, h); } else { @@ -3293,7 +3352,7 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void pktlen += sizeof(*cookie) + sctp->cookiesize; h->crc = SCTP_Checksum(h, pktlen); - return SCTP_PeerSendPacket(peer, pktlen, h); + return SCTP_PeerSendPacket(sctp, pktlen, h); } } @@ -3365,7 +3424,7 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void if (pktlen + sizeof(*d) + length >= 500 && length && pktlen != sizeof(*h)) { //probably going to result in fragmentation issues. send separate packets. h->crc = SCTP_Checksum(h, pktlen); - SCTP_PeerSendPacket(peer, pktlen, h); + SCTP_PeerSendPacket(sctp, pktlen, h); //reset to the header pktlen = sizeof(*h); @@ -3392,10 +3451,10 @@ static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void return NETERR_SENT; //nothing to send... h->crc = SCTP_Checksum(h, pktlen); - return SCTP_PeerSendPacket(peer, pktlen, h); + return SCTP_PeerSendPacket(sctp, pktlen, h); } -static void SCTP_DecodeDCEP(sctp_t *sctp, struct icestate_s *peer, qbyte *resp) +static void SCTP_DecodeDCEP(sctp_t *sctp, qbyte *resp) { //send an ack... size_t pktlen = 0; struct sctp_header_s *h = (void*)resp; @@ -3420,7 +3479,7 @@ static void SCTP_DecodeDCEP(sctp_t *sctp, struct icestate_s *peer, qbyte *resp) sctp->qstreamid = sctp->i.r.sid; if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: New SCTP Channel: \"%s\" (%s)\n", peer->friendlyname, label, prot); + Con_Printf(S_COLOR_GRAY"[%s]: New SCTP Channel: \"%s\" (%s)\n", sctp->friendlyname, label, prot); h->dstport = sctp->peerport; h->srcport = sctp->myport; @@ -3440,7 +3499,7 @@ static void SCTP_DecodeDCEP(sctp_t *sctp, struct icestate_s *peer, qbyte *resp) pktlen += sizeof(*d) + length; h->crc = SCTP_Checksum(h, pktlen); - SCTP_PeerSendPacket(peer, pktlen, h); + SCTP_PeerSendPacket(sctp, pktlen, h); } } @@ -3449,7 +3508,7 @@ struct sctp_errorcause_s quint16_t cause; quint16_t length; }; -static void SCTP_ErrorChunk(struct icestate_s *peer, const char *errortype, struct sctp_errorcause_s *s, size_t totallen) +static void SCTP_ErrorChunk(sctp_t *sctp, const char *errortype, struct sctp_errorcause_s *s, size_t totallen) { quint16_t cc, cl; while(totallen > 0) @@ -3463,20 +3522,20 @@ static void SCTP_ErrorChunk(struct icestate_s *peer, const char *errortype, stru if (net_ice_debug.ival >= 1) switch(cc) { - case 1: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Invalid Stream Identifier\n", peer->friendlyname, errortype); break; - case 2: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Missing Mandatory Parameter\n", peer->friendlyname, errortype); break; - case 3: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Stale Cookie Error\n", peer->friendlyname, errortype); break; - case 4: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Out of Resource\n", peer->friendlyname, errortype); break; - case 5: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unresolvable Address\n", peer->friendlyname, errortype); break; - case 6: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unrecognized Chunk Type\n", peer->friendlyname, errortype); break; - case 7: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Invalid Mandatory Parameter\n", peer->friendlyname, errortype); break; - case 8: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unrecognized Parameters\n", peer->friendlyname, errortype); break; - case 9: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: No User Data\n", peer->friendlyname, errortype); break; - case 10: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Cookie Received While Shutting Down\n", peer->friendlyname, errortype); break; - case 11: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Restart of an Association with New Addresses\n", peer->friendlyname, errortype); break; - case 12: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: User Initiated Abort\n", peer->friendlyname, errortype); break; - case 13: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Protocol Violation [%s]\n", peer->friendlyname, errortype, (char*)(s+1)); break; - default: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unknown Reason\n", peer->friendlyname, errortype); break; + case 1: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Invalid Stream Identifier\n", sctp->friendlyname, errortype); break; + case 2: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Missing Mandatory Parameter\n", sctp->friendlyname, errortype); break; + case 3: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Stale Cookie Error\n", sctp->friendlyname, errortype); break; + case 4: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Out of Resource\n", sctp->friendlyname, errortype); break; + case 5: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unresolvable Address\n", sctp->friendlyname, errortype); break; + case 6: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unrecognized Chunk Type\n", sctp->friendlyname, errortype); break; + case 7: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Invalid Mandatory Parameter\n", sctp->friendlyname, errortype); break; + case 8: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unrecognized Parameters\n", sctp->friendlyname, errortype); break; + case 9: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: No User Data\n", sctp->friendlyname, errortype); break; + case 10: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Cookie Received While Shutting Down\n", sctp->friendlyname, errortype); break; + case 11: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Restart of an Association with New Addresses\n", sctp->friendlyname, errortype); break; + case 12: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: User Initiated Abort\n", sctp->friendlyname, errortype); break; + case 13: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Protocol Violation [%s]\n", sctp->friendlyname, errortype, (char*)(s+1)); break; + default: Con_Printf(S_COLOR_GRAY"[%s]: SCTP %s: Unknown Reason\n", sctp->friendlyname, errortype); break; } totallen -= cl; @@ -3485,12 +3544,12 @@ static void SCTP_ErrorChunk(struct icestate_s *peer, const char *errortype, stru } } -static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connections_t *col) +void SCTP_Decode(sctp_t *sctp, ftenet_connections_t *col) { qbyte resp[4096]; qbyte *msg = net_message.data; - qbyte *msgend = net_message.data+net_message.cursize; + qbyte *msgend = msg+net_message.cursize; struct sctp_header_s *h = (struct sctp_header_s*)msg; struct sctp_chunk_s *c = (struct sctp_chunk_s*)(h+1); quint16_t clen; @@ -3507,15 +3566,25 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection if (net_message.cursize&3) { if (net_ice_debug.ival >= 2) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: packet not padded\n", peer->friendlyname); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: packet not padded\n", sctp->friendlyname); return; //mimic chrome, despite it being pointless. } + //passed the simple header checks, spend a memcpy... + msg = alloca(net_message.cursize); + memcpy(msg, net_message.data, net_message.cursize); + msgend = msg+net_message.cursize; + h = (struct sctp_header_s*)msg; + c = (struct sctp_chunk_s*)(h+1); + while ((qbyte*)(c+1) <= msgend) { clen = BigShort(c->length); - if ((qbyte*)c + clen > msgend) - break; //corrupt + if ((qbyte*)c + clen > msgend || clen < sizeof(*c)) + { + Con_Printf(CON_ERROR"Corrupt SCTP message\n"); + break; + } safeswitch(c->type) { case SCTP_TYPE_DATA: @@ -3528,17 +3597,17 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection if (adv >= SCTP_RCVSIZE) { if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Future Packet\n", peer->friendlyname);/*too far in the future. we can't track such things*/ + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Future Packet\n", sctp->friendlyname);/*too far in the future. we can't track such things*/ } else if (adv <= 0) { if (net_ice_debug.ival >= 2) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: PreCumulative\n", peer->friendlyname);/*already acked this*/ + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: PreCumulative\n", sctp->friendlyname);/*already acked this*/ } else if (sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] & 1<<(tsn&7)) { if (net_ice_debug.ival >= 2) - Con_DPrintf(S_COLOR_GRAY"[%s]: SCTP: Dupe\n", peer->friendlyname);/*already processed it. FIXME: Make a list for the next SACK*/ + Con_DPrintf(S_COLOR_GRAY"[%s]: SCTP: Dupe\n", sctp->friendlyname);/*already processed it. FIXME: Make a list for the next SACK*/ } else { @@ -3567,10 +3636,10 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection if (adv > sctp->i.htsn) //weird maths in case it wraps. sctp->i.htsn = adv; sctp->i.r.tsn++; - if (sctp->i.r.size + clen-sizeof(*dc) > sizeof(sctp->i.r.buf)) + if (sctp->i.r.size + dlen > sizeof(sctp->i.r.buf)) { if (net_ice_debug.ival >= 2) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Oversized\n", peer->friendlyname); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Oversized\n", sctp->friendlyname); sctp->i.r.toobig = true; //reassembled packet was too large, just corrupt it. } else @@ -3599,7 +3668,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection } } else if (sctp->i.r.ppid == BigLong(SCTP_PPID_DCEP)) - SCTP_DecodeDCEP(sctp, peer, resp); + SCTP_DecodeDCEP(sctp, resp); } } @@ -3651,7 +3720,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection break; default: if (net_ice_debug.ival >= 2) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Found unknown init parameter %i||%#x\n", peer->friendlyname, ptype, ptype); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Found unknown init parameter %i||%#x\n", sctp->friendlyname, ptype, ptype); break; } p = (void*)((qbyte*)p + ((plen+3)&~3)); @@ -3661,7 +3730,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection { sctp->nextreinit = 0; if (sctp->cookie) - SCTP_Transmit(sctp, peer, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. + SCTP_Transmit(sctp, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. } else { @@ -3701,7 +3770,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection //complete. calc the proper crc and send it off. rh->crc = SCTP_Checksum(rh, end-resp); - SCTP_PeerSendPacket(peer, end-resp, rh); + SCTP_PeerSendPacket(sctp, end-resp, rh); } } break; @@ -3735,24 +3804,26 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection //complete. calc the proper crc and send it off. pongh->crc = SCTP_Checksum(pongh, sizeof(*pongh) + clen); - SCTP_PeerSendPacket(peer, sizeof(*pongh) + clen, pongh); + SCTP_PeerSendPacket(sctp, sizeof(*pongh) + clen, pongh); Z_Free(pongh); } break; // case SCTP_TYPE_PONG: //we don't send pings case SCTP_TYPE_ABORT: - SCTP_ErrorChunk(peer, "Abort", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); - ICE_Set(peer, "state", STRINGIFY(ICE_FAILED)); + SCTP_ErrorChunk(sctp, "Abort", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); + if (sctp->icestate) + ICE_Set(sctp->icestate, "state", STRINGIFY(ICE_FAILED)); break; case SCTP_TYPE_SHUTDOWN: //FIXME. we should send an ack... - ICE_Set(peer, "state", STRINGIFY(ICE_FAILED)); + if (sctp->icestate) + ICE_Set(sctp->icestate, "state", STRINGIFY(ICE_FAILED)); if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Shutdown\n", peer->friendlyname); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Shutdown\n", sctp->friendlyname); break; // case SCTP_TYPE_SHUTDOWNACK: //we don't send shutdowns, cos we're lame like that... case SCTP_TYPE_ERROR: //not fatal... - SCTP_ErrorChunk(peer, "Error", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); + SCTP_ErrorChunk(sctp, "Error", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); break; case SCTP_TYPE_COOKIEECHO: if (clen >= sizeof(struct sctp_chunk_s)) @@ -3771,7 +3842,7 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection //complete. calc the proper crc and send it off. rh->crc = SCTP_Checksum(rh, end-resp); - SCTP_PeerSendPacket(peer, end-resp, rh); + SCTP_PeerSendPacket(sctp, end-resp, rh); sctp->o.writable = true; //channel SHOULD now be open for data. } @@ -3803,19 +3874,183 @@ static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer, ftenet_connection safedefault: //no idea what this chunk is, just ignore it... if (net_ice_debug.ival >= 1) - Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Unsupported chunk %i\n", peer->friendlyname, c->type); + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Unsupported chunk %i\n", sctp->friendlyname, c->type); break; } c = (struct sctp_chunk_s*)((qbyte*)c + ((clen+3)&~3)); //next chunk is 4-byte aligned. } if (sctp->i.ackneeded >= 5) - SCTP_Transmit(sctp, peer, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. + SCTP_Transmit(sctp, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. //we already made sense of it all. net_message.cursize = 0; } +#if 0 +qboolean SCTP_Handshake(const dtlsfuncs_t *dtlsfuncs, void *dtlsstate, sctp_t **out) +{ + const int myport = ICELITE_SCTP_PORT; + struct cookiedata_s + { + qbyte peerhasfwdtsn; + int overifycode; + int iverifycode; + int ictsn; + int otsn; +// int checkcode; + }; + //if this is an sctp init packet, send a cookie. + //if its an initack then create the new state. + qbyte resp[4096]; + + qbyte *msg = net_message.data; + qbyte *msgend = msg+net_message.cursize; + struct sctp_header_s *h = (struct sctp_header_s*)msg; + struct sctp_chunk_s *c = (struct sctp_chunk_s*)(h+1); + quint16_t clen; + if (net_message.cursize&3) + return false; + if ((qbyte*)c+1 > msgend) + return false; //runt + if (h->dstport != htons(myport)) + return false; //not for us... + clen = BigShort(c->length); + if ((qbyte*)c + ((clen+3)&~3) == msgend) //don't allow multiple chucks + switch(c->type) + { + default: + return false; //not the right kind of packet. + case SCTP_TYPE_COOKIEECHO: + if (clen == sizeof(struct sctp_chunk_s)+sizeof(struct cookiedata_s)) + { + struct cookiedata_s *cookie = (struct cookiedata_s *)(c+1); + sctp_t *sctp; + struct sctp_header_s *rh = (void*)resp; + struct sctp_chunk_s *rack = (void*)(rh+1); + qbyte *end = (void*)(rack+1); + if (h->verifycode == cookie->iverifycode) + if (h->crc == SCTP_Checksum(h, net_message.cursize)) //make sure the crc matches. + { //looks okay. + NET_AdrToString(resp, sizeof(resp), &net_from); + + *out = sctp = Z_Malloc(sizeof(*sctp) + strlen(resp)); + sctp->friendlyname = strcpy((char*)(sctp+1), resp); + sctp->icestate = NULL; + sctp->dtlsfuncs = dtlsfuncs; + sctp->dtlsstate = dtlsstate; + + sctp->myport = h->dstport; + sctp->peerport = h->srcport; + sctp->o.tsn = cookie->otsn; + sctp->i.ctsn = cookie->ictsn; + sctp->o.verifycode = cookie->overifycode; + sctp->i.verifycode = cookie->iverifycode; + sctp->peerhasfwdtsn = cookie->peerhasfwdtsn; + sctp->o.writable = true; //channel SHOULD now be open for data (once it gets the ack anyway). + + //let our peer know too. + rh->srcport = sctp->myport; + rh->dstport = sctp->peerport; + rh->verifycode = sctp->o.verifycode; + rh->crc = 0; + rack->type = SCTP_TYPE_COOKIEACK; + rack->flags = 0; + rack->length = BigShort(sizeof(*rack)); + + //complete. calc the proper crc and send it off. + rh->crc = SCTP_Checksum(rh, end-resp); + SCTP_PeerSendPacket(sctp, end-resp, rh); + return true; + } + } + break; + case SCTP_TYPE_INIT: + if (h->verifycode == 0) //this must be 0 for inits. + if (clen >= sizeof(struct sctp_chunk_init_s)) + if (h->crc == SCTP_Checksum(h, net_message.cursize)) //make sure the crc matches. + { + struct sctp_chunk_init_s *init = (void*)c; + struct { + quint16_t ptype; + quint16_t plen; + } *p = (void*)(init+1); + + struct cookiedata_s cookie = {0}; + + cookie.otsn = rand() ^ (rand()<<16); + Sys_RandomBytes((void*)&cookie.iverifycode, sizeof(cookie.iverifycode)); + cookie.ictsn = BigLong(init->tsn)-1; + cookie.overifycode = init->verifycode; + (void)BigLong(init->arwc); + (void)BigShort(init->numoutstreams); + (void)BigShort(init->numinstreams); + + while ((qbyte*)p+sizeof(*p) <= (qbyte*)c+clen) + { + unsigned short ptype = BigShort(p->ptype); + unsigned short plen = BigShort(p->plen); + switch(ptype) + { + case 32776: //ASCONF + break; + case 49152: + cookie.peerhasfwdtsn = true; + break; + default: + if (net_ice_debug.ival >= 2) + Con_Printf(S_COLOR_GRAY"[%s]: SCTP: Found unknown init parameter %i||%#x\n", NET_AdrToString(resp,sizeof(resp), &net_from), ptype, ptype); + break; + } + p = (void*)((qbyte*)p + ((plen+3)&~3)); + } + + { + struct sctp_header_s *rh = (void*)resp; + struct sctp_chunk_init_s *rinit = (void*)(rh+1); + struct { + quint16_t ptype; + quint16_t plen; + struct cookiedata_s cookie; + } *rinitcookie = (void*)(rinit+1); + struct { + quint16_t ptype; + quint16_t plen; + } *rftsn = (void*)(rinitcookie+1); + qbyte *end = cookie.peerhasfwdtsn?(void*)(rftsn+1):(void*)(rinitcookie+1); + + rh->srcport = h->dstport; + rh->dstport = h->srcport; + rh->verifycode = init->verifycode; + rh->crc = 0; + *rinit = *init; + rinit->chunk.type = SCTP_TYPE_INITACK; + rinit->chunk.flags = 0; + rinit->chunk.length = BigShort(end-(qbyte*)rinit); + rinit->verifycode = cookie.iverifycode; + rinit->arwc = BigLong(65536); + rinit->numoutstreams = init->numoutstreams; + rinit->numinstreams = init->numinstreams; + rinit->tsn = BigLong(cookie.otsn); + rinitcookie->ptype = BigShort(7); + rinitcookie->plen = BigShort(sizeof(*rinitcookie)); + memcpy(&rinitcookie->cookie, &cookie, sizeof(rinitcookie->cookie)); //frankly the contents of the cookie are irrelevant to anything. we've already verified the peer's ice pwd/ufrag stuff as well as their dtls certs etc. + rftsn->ptype = BigShort(49152); + rftsn->plen = BigShort(sizeof(*rftsn)); + + //complete. calc the proper crc and send it off. + rh->crc = SCTP_Checksum(rh, end-resp); + dtlsfuncs->Transmit(dtlsstate, resp, end-resp); + } + return true; + } + break; + } + + return false; +} +#endif + //======================================== #endif @@ -4001,9 +4236,24 @@ qboolean ICE_WasStun(ftenet_connections_t *col) if (con->state == ICE_CONNECTING) ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } + return true; } } } + + //only accept actual responses, not spoofed stuff. + if (stun->magiccookie == BigLong(0x2112a442) + && stun->transactid[0]==col->srflx_tid[0] + && stun->transactid[1]==col->srflx_tid[1] + && stun->transactid[2]==col->srflx_tid[2] + && !NET_CompareAdr(&col->srflx[adr.type!=NA_IP], &adr)) + { + if (col->srflx[adr.type!=NA_IP].type==NA_INVALID) + Con_Printf(S_COLOR_GRAY"Public address reported as %s\n", NET_AdrToString(errmsg, sizeof(errmsg), &adr)); + else + Con_Printf(CON_ERROR"Server reflexive address changed to %s\n", NET_AdrToString(errmsg, sizeof(errmsg), &adr)); + col->srflx[adr.type!=NA_IP] = adr; + } } return true; } @@ -4435,6 +4685,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) unsigned int tielow = 0; qboolean usecandidate = false; unsigned int priority = 0; + char *lpwd = NULL; #endif char *integritypos = NULL; int error = 0; @@ -4615,6 +4866,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) if (con->state == ICE_CONNECTING) ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } + lpwd = con->lpwd; } }//otherwise its just an ip check else @@ -4706,13 +4958,13 @@ qboolean ICE_WasStun(ftenet_connections_t *col) MSG_WriteChar(&buf, 0); #ifdef SUPPORT_ICE - if (con) + if (lpwd) { //message integrity is a bit annoying data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; //but the hash is to the start of the attribute's header - CalcHMAC(&hash_sha1, integrity, sizeof(integrity), data, buf.cursize, con->lpwd, strlen(con->lpwd)); + CalcHMAC(&hash_sha1, integrity, sizeof(integrity), data, buf.cursize, lpwd, strlen(lpwd)); MSG_WriteShort(&buf, BigShort(STUNATTR_MESSAGEINTEGRITIY)); MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data @@ -4730,10 +4982,11 @@ qboolean ICE_WasStun(ftenet_connections_t *col) data[3] = ((buf.cursize-20)>>0)&0xff; #ifdef SUPPORT_ICE - TURN_Encapsulate(con, &net_from, data, buf.cursize); -#else - NET_SendPacket(col, buf.cursize, data, &net_from); + if (con) + TURN_Encapsulate(con, &net_from, data, buf.cursize); + else #endif + NET_SendPacket(col, buf.cursize, data, &net_from); return true; } } @@ -4773,7 +5026,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) net_from = con->qadr; #ifdef HAVE_DTLS if (con->sctp) - SCTP_Decode(con->sctp, con, col); + SCTP_Decode(con->sctp, col); else #endif if (net_message.cursize) @@ -4788,7 +5041,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) return false; } #ifdef SUPPORT_ICE -qboolean ICE_IsEncrypted(netadr_t *to) +int ICE_GetPeerCertificate(netadr_t *to, enum certprops_e prop, char *out, size_t outsize) { #ifdef HAVE_DTLS struct icestate_s *con; @@ -4796,12 +5049,14 @@ qboolean ICE_IsEncrypted(netadr_t *to) { if (NET_CompareAdr(to, &con->qadr)) { - if (con->dtlsstate) - return true; + if (con->dtlsstate && con->dtlsfuncs->GetPeerCertificate) + return con->dtlsfuncs->GetPeerCertificate(con->dtlsstate, prop, out, outsize); + else if (prop==QCERT_ISENCRYPTED && con->dtlsstate) + return 0; } } #endif - return false; + return -1; } void ICE_Terminate(netadr_t *to) { @@ -4830,9 +5085,9 @@ neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to) return NETERR_DISCONNECTED; #ifdef HAVE_DTLS if (con->sctp) - return SCTP_Transmit(con->sctp, con, data, length); + return SCTP_Transmit(con->sctp, data, length); if (con->dtlsstate) - return SCTP_PeerSendPacket(con, length, data); + return con->dtlsfuncs->Transmit(con->dtlsstate, data, length); #endif if (con->chosenpeer.type != NA_INVALID) return ICE_Transmit(con, data, length); @@ -5015,7 +5270,7 @@ static void FTENET_ICE_SendOffer(ftenet_ice_connection_t *b, int cl, struct ices ice->blockcandidates = false; } } -static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, int cl, struct icestate_s **ret) +static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, const char *peeraddr, int cl, struct icestate_s **ret) { //sends offer struct icestate_s *ice; qboolean usewebrtc; @@ -5030,7 +5285,7 @@ static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, int cl, struct ices else usewebrtc = net_ice_usewebrtc.ival; #endif - ice = *ret = iceapi.Create(b, NULL, b->generic.islisten?NULL:va("/%s", b->gamename), usewebrtc?ICEM_WEBRTC:ICEM_ICE, b->generic.islisten?ICEP_QWSERVER:ICEP_QWCLIENT, !b->generic.islisten); + ice = *ret = iceapi.Create(b, NULL, b->generic.islisten?((peeraddr&&*peeraddr)?va("%s:%i", peeraddr,cl):NULL):va("/%s", b->gamename), usewebrtc?ICEM_WEBRTC:ICEM_ICE, b->generic.islisten?ICEP_QWSERVER:ICEP_QWCLIENT, !b->generic.islisten); if (!*ret) return; //some kind of error?!? @@ -5226,14 +5481,16 @@ handleerror: if (cl == -1) { b->error = true; -// Con_Printf("Broker closed connection: %s\n", data); + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Broker host lost connection: %s\n", b->ice?b->ice->friendlyname:"?", *data?data:""); } else if (cl >= 0 && cl < b->numclients) { + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Broker client lost connection: %s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", *data?data:""); if (b->clients[cl].ice) iceapi.Close(b->clients[cl].ice, false); b->clients[cl].ice = NULL; -// Con_Printf("Broker closing connection: %s\n", data); } break; case ICEMSG_NAMEINUSE: @@ -5256,13 +5513,23 @@ handleerror: Z_ReallocElements((void**)&b->clients, &b->numclients, cl+1, sizeof(b->clients[0])); } if (cl >= 0 && cl < b->numclients) - FTENET_ICE_Establish(b, cl, &b->clients[cl].ice); + { + FTENET_ICE_Establish(b, (len>3)?data:NULL, cl, &b->clients[cl].ice); + + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: New client spotted...\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?"); + } + else if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: New client spotted, but index is unusable\n", "?"); } else { // Con_DPrintf("Server found: %s\n", data); - FTENET_ICE_Establish(b, cl, &b->ice); + FTENET_ICE_Establish(b, (len>3)?data:NULL, cl, &b->ice); b->serverid = cl; + + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Relay to server now open\n", b->ice?b->ice->friendlyname:"?"); } break; case ICEMSG_OFFER: //we received an offer from a client @@ -5282,19 +5549,31 @@ handleerror: { if (cl >= 0 && cl < b->numclients && b->clients[cl].ice) { + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Got offer:\n%s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", data); iceapi.Set(b->clients[cl].ice, "sdpoffer", data); iceapi.Set(b->clients[cl].ice, "state", STRINGIFY(ICE_CONNECTING)); FTENET_ICE_SendOffer(b, cl, b->clients[cl].ice, "sdpanswer"); + break; } + + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Got bad offer/answer:\n%s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", data); } else { if (b->ice) { + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Got answer:\n%s\n", b->ice?b->ice->friendlyname:"?", data); iceapi.Set(b->ice, "sdpanswer", data); iceapi.Set(b->ice, "state", STRINGIFY(ICE_CONNECTING)); + break; } + + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Got bad offer/answer:\n%s\n", b->ice?b->ice->friendlyname:"?", data); } break; case ICEMSG_CANDIDATE: @@ -5310,12 +5589,20 @@ handleerror: if (b->generic.islisten) { if (cl >= 0 && cl < b->numclients && b->clients[cl].ice) + { + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Got candidate:\n%s\n", b->clients[cl].ice->friendlyname, data); iceapi.Set(b->clients[cl].ice, "sdp", data); + } } else { if (b->ice) + { + if (net_ice_debug.ival) + Con_Printf(S_COLOR_GRAY"[%s]: Got candidate:\n%s\n", b->ice->friendlyname, data); iceapi.Set(b->ice, "sdp", data); + } } break; } @@ -5405,12 +5692,12 @@ ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(ftenet_connections_t address+=6; else if (!strncmp(address, "ices://", 7)||!strncmp(address, "rtcs://", 7)) address+=7; - if (address == path && *path=='/' && fs_manifest->rtcbroker) + if (address == path && *path=='/') { - if (!strncmp(fs_manifest->rtcbroker, "tls://", 6) || !strncmp(fs_manifest->rtcbroker, "tcp://", 6)) - Q_strncpyz(newcon->brokername, fs_manifest->rtcbroker+6, sizeof(newcon->brokername)); //name is for prints only. + if (!strncmp(net_ice_broker.string, "tls://", 6) || !strncmp(net_ice_broker.string, "tcp://", 6)) + Q_strncpyz(newcon->brokername, net_ice_broker.string+6, sizeof(newcon->brokername)); //name is for prints only. else - Q_strncpyz(newcon->brokername, fs_manifest->rtcbroker, sizeof(newcon->brokername)); //name is for prints only. + Q_strncpyz(newcon->brokername, net_ice_broker.string, sizeof(newcon->brokername)); //name is for prints only. Q_strncpyz(newcon->gamename, path+1, sizeof(newcon->gamename)); //so we know what to tell the broker. } else @@ -5462,4 +5749,134 @@ void ICE_Init(void) Cvar_Register(&net_ice_debug, "networking"); Cmd_AddCommand("net_ice_show", ICE_Show_f); } -#endif \ No newline at end of file + + +#ifdef HAVE_SERVER +void SVC_ICE_Offer(void) +{ //handles an 'ice_offer' udp message from a broker + extern cvar_t net_ice_servers; + struct icestate_s *ice; + static float throttletimer; + char *sdp, *s; + char buf[1400]; + int sz; + char *clientaddr = Cmd_Argv(1); //so we can do ip bans on the client's srflx address + char *brokerid = Cmd_Argv(2); //specific id to identify the pairing on the broker. + netadr_t adr; + if (!sv.state) + return; //err..? + if (net_from.prot != NP_DTLS && net_from.prot != NP_WSS && net_from.prot != NP_TLS) + { //a) dtls provides a challenge. + //b) this contains the caller's ips. We'll be pinging them anyway, but hey. also it'll be too late at this point but it keeps the other side honest. + Con_ThrottlePrintf(&throttletimer, 0, CON_WARNING"%s: ice handshake via %s was unencrypted\n", NET_AdrToString (buf, sizeof(buf), &net_from)); + return; + } + + if (!NET_StringToAdr_NoDNS(clientaddr, 0, &adr)) //no dns-resolution denial-of-service attacks please. + { + Con_ThrottlePrintf(&throttletimer, 0, CON_WARNING"%s: ice handshake specifies bad client address: %s\n", NET_AdrToString (buf, sizeof(buf), &net_from), clientaddr); + return; + } + if (SV_BannedReason(&adr)!=NULL) + { + Con_ThrottlePrintf(&throttletimer, 0, CON_WARNING"%s: ice handshake for %s - banned\n", NET_AdrToString (buf, sizeof(buf), &net_from), clientaddr); + return; + } + + ice = iceapi.Create(NULL, brokerid, clientaddr, ICEM_WEBRTC, ICEP_QWSERVER, false); + if (!ice) + return; //some kind of error?!? + //use the sender as a stun server. FIXME: put server's address in the request instead. + iceapi.Set(ice, "server", va("stun:%s", NET_AdrToString (buf, sizeof(buf), &net_from))); //the sender should be able to act as a stun server for use. should probably just pass who its sending to and call it a srflx anyway, tbh. + + s = net_ice_servers.string; + while((s=COM_Parse(s))) + iceapi.Set(ice, "server", com_token); + + sdp = MSG_ReadString(); + if (!strncmp(sdp, "{\"type\":\"offer\",\"sdp\":\"", 23)) + { //browsers are poo + sdp += 22; + COM_ParseCString(sdp, buf, sizeof(buf), NULL); + sdp = buf; + } + + if (iceapi.Set(ice, "sdpoffer", sdp)) + { + iceapi.Set(ice, "state", STRINGIFY(ICE_CONNECTING)); //skip gathering, just trickle. + + Q_snprintfz(buf, sizeof(buf), "\xff\xff\xff\xff""ice_answer %s", brokerid); + sz = strlen(buf)+1; + if (iceapi.Get(ice, "sdpanswer", buf+sz, sizeof(buf)-sz)) + { + sz += strlen(buf+sz); + + NET_SendPacket(svs.sockets, sz, buf, &net_from); + } + } + + //and because we won't have access to its broker, disconnect it from any persistent state to let it time out. + iceapi.Close(ice, false); +} +void SVC_ICE_Candidate(void) +{ + struct icestate_s *ice; + char *sdp; + char buf[1400]; + char *brokerid = Cmd_Argv(1); //specific id to identify the pairing on the broker. + unsigned int seq = atoi(Cmd_Argv(2)); //their seq, to ack and prevent dupes + unsigned int ack = atoi(Cmd_Argv(3)); //so we don't resend endlessly... *cough* + if (net_from.prot != NP_DTLS && net_from.prot != NP_WSS && net_from.prot != NP_TLS) + { + return; + } + ice = iceapi.Find(NULL, brokerid); + if (!ice) + return; //bad state. lost packet? + + //parse the inbound candidates + for(;;) + { + sdp = MSG_ReadStringLine(); + if (msg_badread || !*sdp) + break; + if (seq++ < ice->u.inseq) + continue; + ice->u.inseq++; + if (!strncmp(sdp, "{\"candidate\":\"", 14)) + { //dewebify + sdp += 13; + buf[0]='a'; + buf[1]='='; + COM_ParseCString(sdp, buf+2, sizeof(buf)-2, NULL); + sdp = buf; + } + iceapi.Set(ice, "sdp", sdp); + } + + while (ack > ice->u.outseq) + { //drop an outgoing candidate line + char *nl = strchr(ice->u.text, '\n'); + if (nl) + { + nl++; + memmove(ice->u.text, nl, strlen(nl)+1); //chop it away. + ice->u.outseq++; + continue; + } + //wut? + if (ack > ice->u.outseq) + ice->u.outseq = ack; //a gap? oh noes! + break; + } + + //check for new candidates to include + while (iceapi.GetLCandidateSDP(ice, buf, sizeof(buf))) + Z_StrCat(&ice->u.text, buf); + + Q_snprintfz(buf, sizeof(buf), "\xff\xff\xff\xff""ice_scand %s %u %u\n%s", brokerid, ice->u.outseq, ice->u.inseq, ice->u.text?ice->u.text:""); + NET_SendPacket(svs.sockets, strlen(buf), buf, &net_from); +} +#endif + +#endif diff --git a/engine/common/net_ssl_gnutls.c b/engine/common/net_ssl_gnutls.c index 32e117ac5..3382c7fe0 100644 --- a/engine/common/net_ssl_gnutls.c +++ b/engine/common/net_ssl_gnutls.c @@ -99,6 +99,7 @@ #define GNUTLS_X509_STUFF \ + GNUTLS_FUNC(gnutls_certificate_server_set_request,void,(gnutls_session_t session, gnutls_certificate_request_t req)) \ GNUTLS_FUNC(gnutls_sec_param_to_pk_bits,unsigned int,(gnutls_pk_algorithm_t algo, gnutls_sec_param_t param)) \ GNUTLS_FUNC(gnutls_x509_crt_init,int,(gnutls_x509_crt_t * cert)) \ GNUTLS_FUNC(gnutls_x509_crt_deinit,void,(gnutls_x509_crt_t cert)) \ @@ -108,6 +109,7 @@ GNUTLS_FUNC(gnutls_x509_crt_set_expiration_time,int,(gnutls_x509_crt_t cert, time_t exp_time)) \ GNUTLS_FUNC(gnutls_x509_crt_set_serial,int,(gnutls_x509_crt_t cert, const void *serial, size_t serial_size)) \ GNUTLS_FUNC(gnutls_x509_crt_set_dn,int,(gnutls_x509_crt_t crt, const char *dn, const char **err)) \ + GNUTLS_FUNC(gnutls_x509_crt_get_dn3,int,(gnutls_x509_crt_t crt, gnutls_datum_t * dn, unsigned flags)) \ GNUTLS_FUNC(gnutls_x509_crt_set_issuer_dn,int,(gnutls_x509_crt_t crt,const char *dn, const char **err)) \ GNUTLS_FUNC(gnutls_x509_crt_set_key,int,(gnutls_x509_crt_t crt, gnutls_x509_privkey_t key)) \ GNUTLS_FUNC(gnutls_x509_crt_export2,int,(gnutls_x509_crt_t cert, gnutls_x509_crt_fmt_t format, gnutls_datum_t * out)) \ @@ -123,7 +125,9 @@ GNUTLS_FUNC(gnutls_pubkey_init,int,(gnutls_pubkey_t * key)) \ GNUTLS_FUNC(gnutls_pubkey_deinit,void,(gnutls_pubkey_t key)) \ GNUTLS_FUNC(gnutls_pubkey_import_x509,int,(gnutls_pubkey_t key, gnutls_x509_crt_t crt, unsigned int flags)) \ - GNUTLS_FUNC(gnutls_pubkey_verify_hash2,int,(gnutls_pubkey_t key, gnutls_sign_algorithm_t algo, unsigned int flags, const gnutls_datum_t * hash, const gnutls_datum_t * signature)) + GNUTLS_FUNC(gnutls_pubkey_verify_hash2,int,(gnutls_pubkey_t key, gnutls_sign_algorithm_t algo, unsigned int flags, const gnutls_datum_t * hash, const gnutls_datum_t * signature)) \ + GNUTLS_FUNC(gnutls_certificate_get_ours,const gnutls_datum_t*,(gnutls_session_t session)) \ + GNUTLS_FUNC(gnutls_certificate_get_crt_raw,int,(gnutls_certificate_credentials_t sc, unsigned idx1, unsigned idx2, gnutls_datum_t * cert)) #define GNUTLS_FUNCS \ @@ -548,7 +552,7 @@ static int QDECL SSL_CheckFingerprint(gnutls_session_t session) if (!memcmp(digest, file->peerdigest, file->peerhashfunc->digestsize)) return 0; } - Con_DPrintf(CON_ERROR "%s: rejecting certificate\n", file->certname); + Con_Printf(CON_ERROR "%s: rejecting certificate\n", file->certname); return GNUTLS_E_CERTIFICATE_ERROR; } #endif @@ -1084,7 +1088,10 @@ qboolean SSL_InitGlobal(qboolean isserver) #endif } else + { qgnutls_certificate_set_verify_function (xcred[isserver], SSL_CheckCert); +// qgnutls_certificate_set_retrieve_function (xcred[isserver], SSL_FindClientCert); + } #endif } else @@ -1151,7 +1158,7 @@ static int GetPSKForServer(gnutls_session_t sess, char **username, gnutls_datum_ if (strcmp(svhint, dtls_psk_user.string) || CalcHashInt(&hash_sha1, dtls_psk_key.string, strlen(dtls_psk_key.string)) != 0x3dd348e4) { Con_Printf(CON_WARNING "Possible QEx Server, please set your ^[%s\\type\\%s^] and ^[%s\\type\\%s^] cvars correctly, their current values are likely to crash the server.\n", dtls_psk_user.name,dtls_psk_user.name, dtls_psk_key.name,dtls_psk_key.name); - return 0; //don't report anything. + return -1; //don't report anything. } } } @@ -1174,7 +1181,10 @@ static int GetPSKForServer(gnutls_session_t sess, char **username, gnutls_datum_ static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboolean datagram) { // Initialize TLS session - qgnutls_init (&newf->session, GNUTLS_NONBLOCK|(isserver?GNUTLS_SERVER:GNUTLS_CLIENT)|(datagram?GNUTLS_DATAGRAM:0)); + qgnutls_init (&newf->session, ((newf->certcred)?GNUTLS_FORCE_CLIENT_CERT:0) + |GNUTLS_NONBLOCK + |(isserver?GNUTLS_SERVER:GNUTLS_CLIENT) + |(datagram?GNUTLS_DATAGRAM:0)); if (!isserver) qgnutls_server_name_set(newf->session, GNUTLS_NAME_DNS, newf->certname, strlen(newf->certname)); @@ -1182,6 +1192,7 @@ static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboole if (newf->certcred) { + qgnutls_certificate_server_set_request(newf->session, GNUTLS_CERT_REQUIRE); //we will need to validate their fingerprint. qgnutls_credentials_set (newf->session, GNUTLS_CRD_CERTIFICATE, newf->certcred); qgnutls_set_default_priority (newf->session); } @@ -1192,6 +1203,8 @@ static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboole qgnutls_credentials_set (newf->session, GNUTLS_CRD_ANON, anoncred[isserver]); #else #ifdef HAVE_DTLS + qgnutls_certificate_server_set_request(newf->session, GNUTLS_CERT_REQUEST); //request a cert, we'll use it for fingerprints. + if (datagram && !isserver) { //do psk as needed. we can still do the cert stuff if the server isn't doing psk. gnutls_psk_client_credentials_t pskcred; @@ -1402,16 +1415,22 @@ static void *GNUDTLS_CreateContext(const dtlscred_t *credinfo, void *cbctx, nete // Sys_Printf("DTLS_CreateContext: server=%i\n", isserver); - if (credinfo && credinfo->local.cert && credinfo->local.key && credinfo->peer.hash) + if (credinfo && ((credinfo->local.cert && credinfo->local.key) || credinfo->peer.hash)) { - gnutls_datum_t pub = {credinfo->local.cert, credinfo->local.certsize}, - priv = {credinfo->local.key, credinfo->local.keysize}; qgnutls_certificate_allocate_credentials (&newf->certcred); - qgnutls_certificate_set_x509_key_mem(newf->certcred, &pub, &priv, GNUTLS_X509_FMT_DER); + if (credinfo->local.cert && credinfo->local.key) + { + gnutls_datum_t pub = {credinfo->local.cert, credinfo->local.certsize}, + priv = {credinfo->local.key, credinfo->local.keysize}; + qgnutls_certificate_set_x509_key_mem(newf->certcred, &pub, &priv, GNUTLS_X509_FMT_DER); + } - newf->peerhashfunc = credinfo->peer.hash; - memcpy(newf->peerdigest, credinfo->peer.digest, newf->peerhashfunc->digestsize); - qgnutls_certificate_set_verify_function (newf->certcred, SSL_CheckFingerprint); + if (credinfo->peer.hash) + { + newf->peerhashfunc = credinfo->peer.hash; + memcpy(newf->peerdigest, credinfo->peer.digest, newf->peerhashfunc->digestsize); + qgnutls_certificate_set_verify_function (newf->certcred, SSL_CheckFingerprint); + } } SSL_SetCertificateName(newf, credinfo?credinfo->peer.name:NULL); @@ -1442,6 +1461,9 @@ static neterr_t GNUDTLS_Transmit(void *ctx, const qbyte *data, size_t datasize) return NETERR_DISCONNECTED; } + if (!datasize) + return NETERR_SENT; + ret = qgnutls_record_send(f->session, data, datasize); if (ret < 0) { @@ -1462,17 +1484,39 @@ static neterr_t GNUDTLS_Received(void *ctx, sizebuf_t *message) if (f->challenging) { - int cli_addr = 0xdeadbeef; //FIXME: replace with client's IP:port. + size_t asize; + safeswitch (net_from.type) + { + case NA_LOOPBACK: asize = 0; break; + case NA_IP: asize = sizeof(net_from.address.ip); break; + case NA_IPV6: asize = sizeof(net_from.address.ip6); break; + case NA_IPX: asize = sizeof(net_from.address.ipx); break; +#ifdef UNIXSOCKETS + case NA_UNIX: asize = (qbyte*)&net_from.address.un.path[net_from.address.un.len]-(qbyte*)&net_from.address; break; //unlikely to be spoofed... +#endif +#ifdef IRCCONNECT + //case NA_IRC: +#endif +#ifdef HAVE_WEBSOCKCL + //case NA_WEBSOCKET: //basically web browser. +#endif +#ifdef SUPPORT_ICE + case NA_ICE: asize = strlen(net_from.address.icename); break; +#endif + case NA_INVALID: + safedefault: return NETERR_NOROUTE; + } + memset(&f->prestate, 0, sizeof(f->prestate)); ret = qgnutls_dtls_cookie_verify(&cookie_key, - &cli_addr, sizeof(cli_addr), + &net_from.address, asize, message->data, message->cursize, &f->prestate); if (ret == GNUTLS_E_BAD_COOKIE) { qgnutls_dtls_cookie_send(&cookie_key, - &cli_addr, sizeof(cli_addr), + &net_from.address, asize, &f->prestate, (gnutls_transport_ptr_t)f, DTLS_Push); return NETERR_CLOGGED; @@ -1599,6 +1643,74 @@ static neterr_t GNUDTLS_Timeouts(void *ctx) return NETERR_SENT; } +static int GNUDTLS_GetPeerCertificate(void *ctx, enum certprops_e prop, char *out, size_t outsize) +{ + gnutlsfile_t *f = (gnutlsfile_t *)ctx; + if (f && (f->challenging || f->handshaking)) + return -1; //no cert locked down yet... + safeswitch(prop) + { + case QCERT_ISENCRYPTED: + return 0; //well, should be... + case QCERT_PEERSUBJECT: + { + unsigned int certcount; + const gnutls_datum_t *const certlist = qgnutls_certificate_get_peers(f->session, &certcount); + if (certlist) + { + gnutls_x509_crt_t cert = NULL; + gnutls_datum_t dn={NULL}; + qgnutls_x509_crt_init(&cert); + qgnutls_x509_crt_import(cert, certlist, GNUTLS_X509_FMT_DER); + qgnutls_x509_crt_get_dn3(cert, &dn, 0); + if (dn.size >= outsize) + dn.size = -1; //too big... + else + { + memcpy(out, dn.data, dn.size); + out[dn.size] = 0; + } + (*qgnutls_free)(dn.data); + qgnutls_x509_crt_deinit(cert); + return (int)dn.size; + } + } + return -1; + case QCERT_PEERCERTIFICATE: + { + unsigned int certcount; + const gnutls_datum_t *const certlist = qgnutls_certificate_get_peers(f->session, &certcount); + if (certlist && certlist->size <= outsize) + { + memcpy(out, certlist->data, certlist->size); + return certlist->size; + } + } + return -1; + case QCERT_LOCALCERTIFICATE: + { + const gnutls_datum_t *cert; + gnutls_datum_t d; + + if (f) + cert = qgnutls_certificate_get_ours(f->session); + else //no actual context? get our default dtls server cert. + { + qgnutls_certificate_get_crt_raw (xcred[true], 0/*first chain*/, 0/*primary one*/, &d); + cert = &d; + } + if (cert->size <= outsize) + { + memcpy(out, cert->data, cert->size); + return cert->size; + } + } + return -1; + safedefault: + return -1; //dunno what you want from me. + } +} + static qboolean GNUDTLS_GenTempCertificate(const char *subject, struct dtlslocalcred_s *qcred) { gnutls_datum_t priv = {NULL}, pub = {NULL}; @@ -1681,7 +1793,7 @@ static const dtlsfuncs_t dtlsfuncs_gnutls = GNUDTLS_Transmit, GNUDTLS_Received, GNUDTLS_Timeouts, - NULL, + GNUDTLS_GetPeerCertificate, GNUDTLS_GenTempCertificate }; static const dtlsfuncs_t *GNUDTLS_InitServer(void) diff --git a/engine/common/net_ssl_winsspi.c b/engine/common/net_ssl_winsspi.c index a2495c7e8..267b29742 100644 --- a/engine/common/net_ssl_winsspi.c +++ b/engine/common/net_ssl_winsspi.c @@ -2,9 +2,21 @@ #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. +DTLS1.0 is supported from win8 onwards, or patched-win7 +DTLS1.2 is supported from win10-1607 onwards. + +unlike the other tls providers we use pfx files (to work around microsoft making it so fucking hard to actually import/export the damn private key any other way) +pfx files are encrypted with your username as a password +convert from pem to pfx: + openssl pkcs12 -inkey private.pem -in fullchain.pem -export -nodes -out identity.pfx -passout pass:$USER + (in the above formula, swap out $USER for %USERNAME% if you're using a windows cmd prompt) +you can start fte with an arg like the following for a fully CA-signed cert: + fteqwsv -pfx c:/foo/bar.pfx + (otherwise it'll use identity.pfx from your basedir, autogenerated with some dodgy info) + +TODO: RTC connections do not validate the client peer. +TODO: get someone else to figure out the PSK stuff. no way can I test/debug/write that without documentation nor working drivers. +Note: testing this stuff is a pain when eg browsers do NOT support DTLS1.0 any more. */ #include "winquake.h" @@ -12,6 +24,7 @@ I've given up for now. #define SECURITY_WIN32 #include #include +#include #include #define SP_PROT_TLS1_1_SERVER 0x00000100 @@ -22,14 +35,21 @@ I've given up for now. #define SP_PROT_DTLS_SERVER 0x00010000 #define SP_PROT_DTLS_CLIENT 0x00020000 +#define SP_PROT_DTLS1_0_SERVER SP_PROT_DTLS_SERVER +#define SP_PROT_DTLS1_0_CLIENT SP_PROT_DTLS_CLIENT +#define SP_PROT_DTLS1_2_SERVER 0x00040000 +#define SP_PROT_DTLS1_2_CLIENT 0x00080000 +#define SP_PROT_DTLS1_X_SERVER (SP_PROT_DTLS1_0_SERVER | SP_PROT_DTLS1_2_SERVER) +#define SP_PROT_DTLS1_X_CLIENT (SP_PROT_DTLS1_0_CLIENT | SP_PROT_DTLS1_2_CLIENT) + //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) +#define USE_PROT_DGRAM_SERVER (SP_PROT_DTLS1_X_SERVER) +#define USE_PROT_DGRAM_CLIENT (SP_PROT_DTLS1_X_CLIENT) #ifndef szOID_RSA_SHA512RSA #define szOID_RSA_SHA512RSA "1.2.840.113549.1.1.13" @@ -45,6 +65,17 @@ I've given up for now. #define SEC_E_INVALID_PARAMETER 0x8009035DL #endif +#ifndef SECBUFFER_ALERT +#define SECBUFFER_ALERT 17 +#endif +#ifndef SECPKG_ATTR_DTLS_MTU +#define SECPKG_ATTR_DTLS_MTU 34 +#endif + +#ifndef CRYPT_ARCHIVABLE +#define CRYPT_ARCHIVABLE 0x00004000 +#endif + //hungarian ensures we hit no macros. static struct @@ -57,6 +88,7 @@ static struct 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 *pSetContextAttributesA) (PCtxtHandle,unsigned long,void*,unsigned long); SECURITY_STATUS (WINAPI *pQueryContextAttributesA) (PCtxtHandle,ULONG,PVOID); SECURITY_STATUS (WINAPI *pFreeCredentialsHandle) (PCredHandle); SECURITY_STATUS (WINAPI *pDeleteSecurityContext) (PCtxtHandle); @@ -67,11 +99,26 @@ static struct 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); + PCCERT_CONTEXT (WINAPI *pCertCreateCertificateContext) (DWORD dwCertEncodingType, const BYTE *pbCertEncoded, DWORD cbCertEncoded); DWORD (WINAPI *pCertNameToStrA) (DWORD dwCertEncodingType, PCERT_NAME_BLOB pName, DWORD dwStrType, LPCSTR psz, DWORD csz); - + BOOL (WINAPI *pCertFreeCertificateContext) (PCCERT_CONTEXT pCertContext); 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 *); + HCERTSTORE (WINAPI *pCertOpenStore) (LPCSTR lpszStoreProvider, DWORD dwEncodingType, HCRYPTPROV hCryptProv, DWORD dwFlags, const void *pvPara); + BOOL (WINAPI *pCertAddCertificateContextToStore) (HCERTSTORE hCertStore, PCCERT_CONTEXT pCertContext, DWORD dwAddDisposition, PCCERT_CONTEXT *ppStoreContext); + BOOL (WINAPI *pPFXExportCertStoreEx) (HCERTSTORE hStore, CRYPT_DATA_BLOB *pPFX, LPCWSTR szPassword, void *pvPara, DWORD dwFlags); + BOOL (WINAPI *pCertCloseStore) (HCERTSTORE hCertStore, DWORD dwFlags); + HCERTSTORE (WINAPI *pPFXImportCertStore) (CRYPT_DATA_BLOB *pPFX, LPCWSTR szPassword, DWORD dwFlags); + PCCERT_CONTEXT (WINAPI *pCertFindCertificateInStore) (HCERTSTORE hCertStore, DWORD dwCertEncodingType, DWORD dwFindFlags, DWORD dwFindType, const void *pvFindPara, PCCERT_CONTEXT pPrevCertContext); + BOOL (WINAPI *pCryptAcquireCertificatePrivateKey) (PCCERT_CONTEXT pCert, DWORD dwFlags, void *pvParameters, HCRYPTPROV *phCryptProvOrNCryptKey, DWORD *pdwKeySpec, BOOL *pfCallerFreeProvOrNCryptKey); + BOOL (WINAPI *pCertSetCertificateContextProperty) (PCCERT_CONTEXT pCertContext, DWORD dwPropId, DWORD dwFlags, const void *pvData); } crypt; +static struct +{ + dllhandle_t *lib; + BOOL (WINAPI *pCryptAcquireContextW) (HCRYPTPROV *phProv, LPCWSTR szContainer, LPCWSTR szProvider, DWORD dwProvType, DWORD dwFlags); + BOOL (WINAPI *pCryptGenKey) (HCRYPTPROV hProv, ALG_ID Algid, DWORD dwFlags, HCRYPTKEY *phKey); +} advapi; void SSL_Init(void) { dllfunction_t secur_functable[] = @@ -83,6 +130,7 @@ void SSL_Init(void) {(void**)&secur.pInitializeSecurityContextW, "InitializeSecurityContextW"}, {(void**)&secur.pAcceptSecurityContext, "AcceptSecurityContext"}, {(void**)&secur.pCompleteAuthToken, "CompleteAuthToken"}, + {(void**)&secur.pSetContextAttributesA, "SetContextAttributesA"}, {(void**)&secur.pQueryContextAttributesA, "QueryContextAttributesA"}, {(void**)&secur.pFreeCredentialsHandle, "FreeCredentialsHandle"}, {(void**)&secur.pDeleteSecurityContext, "DeleteSecurityContext"}, @@ -94,9 +142,26 @@ void SSL_Init(void) {(void**)&crypt.pCertGetCertificateChain, "CertGetCertificateChain"}, {(void**)&crypt.pCertVerifyCertificateChainPolicy, "CertVerifyCertificateChainPolicy"}, {(void**)&crypt.pCertFreeCertificateChain, "CertFreeCertificateChain"}, + {(void**)&crypt.pCertCreateCertificateContext, "CertCreateCertificateContext"}, {(void**)&crypt.pCertNameToStrA, "CertNameToStrA"}, + {(void**)&crypt.pCertFreeCertificateContext, "CertFreeCertificateContext"}, {(void**)&crypt.pCertCreateSelfSignCertificate, "CertCreateSelfSignCertificate"}, {(void**)&crypt.pCertStrToNameA, "CertStrToNameA"}, + {(void**)&crypt.pCertOpenStore, "CertOpenStore"}, + {(void**)&crypt.pCertAddCertificateContextToStore, "CertAddCertificateContextToStore"}, + {(void**)&crypt.pPFXExportCertStoreEx, "PFXExportCertStoreEx"}, + {(void**)&crypt.pCertCloseStore, "CertCloseStore"}, + {(void**)&crypt.pPFXImportCertStore, "PFXImportCertStore"}, + {(void**)&crypt.pCertFindCertificateInStore, "CertFindCertificateInStore"}, + {(void**)&crypt.pCryptAcquireCertificatePrivateKey, "CryptAcquireCertificatePrivateKey"}, + {(void**)&crypt.pCertSetCertificateContextProperty, "CertSetCertificateContextProperty"}, + {NULL, NULL} + }; + + dllfunction_t advapi_functable[] = + { + {(void**)&advapi.pCryptAcquireContextW, "CryptAcquireContextW"}, + {(void**)&advapi.pCryptGenKey, "CryptGenKey"}, {NULL, NULL} }; @@ -104,13 +169,15 @@ void SSL_Init(void) secur.lib = Sys_LoadLibrary("secur32.dll", secur_functable); if (!crypt.lib) crypt.lib = Sys_LoadLibrary("crypt32.dll", crypt_functable); + if (!advapi.lib) + advapi.lib = Sys_LoadLibrary("advapi32.dll", advapi_functable); } qboolean SSL_Inited(void) { - return !!secur.lib && !!crypt.lib; + return !!secur.lib && !!crypt.lib && !!advapi.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) +#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 | ISC_REQ_USE_SUPPLIED_CREDS) struct sslbuf { @@ -146,9 +213,11 @@ typedef struct { CredHandle cred; SecHandle sechnd; - int headersize, footersize; + int headersize, mtu, footersize; //schannel is strict about its mtus. char headerdata[1024], footerdata[1024]; + double resendtimer; //for the client so it can keep retrying the handshake. + #ifdef HAVE_DTLS void *cbctx; neterr_t (*transmit)(void *cbctx, const qbyte *data, size_t datasize); @@ -200,7 +269,7 @@ static void SSPI_Error(sslfile_t *f, const char *error, ...) f->stream = NULL; } -static void SSPI_TryFlushCryptOut(sslfile_t *f) +static neterr_t SSPI_TryFlushCryptOut(sslfile_t *f) { int sent; if (f->outcrypt.avail) @@ -208,21 +277,24 @@ static void SSPI_TryFlushCryptOut(sslfile_t *f) #ifdef HAVE_DTLS if (f->transmit) { - f->transmit(f->cbctx, f->outcrypt.data, f->outcrypt.avail); + neterr_t e = f->transmit(f->cbctx, f->outcrypt.data, f->outcrypt.avail); f->outcrypt.avail = 0; - return; + return e; } #endif sent = VFS_WRITE(f->stream, f->outcrypt.data, f->outcrypt.avail); } else - return; + return NETERR_SENT; - if (sent > 0) + if (f->datagram) + f->outcrypt.avail = 0; //anything unsent is a dropped packet... + else if (sent > 0) { memmove(f->outcrypt.data, f->outcrypt.data + sent, f->outcrypt.avail - sent); f->outcrypt.avail -= sent; } + return NETERR_SENT; } static int SSPI_CheckNewInCrypt(sslfile_t *f) @@ -265,9 +337,10 @@ static void SSPI_Decode(sslfile_t *f) SecBuff[3].BufferType = SECBUFFER_EMPTY; //space for extra marker ss = secur.pDecryptMessage(&f->sechnd, &BuffDesc, 0, &ulQop); - - if (ss < 0) + if (FAILED(ss)) { + if (f->datagram) + return; //some sort of corruption? ignore that packet. if (ss == SEC_E_INCOMPLETE_MESSAGE) { if (f->incrypt.avail == f->incrypt.datasize) @@ -278,6 +351,7 @@ static void SSPI_Decode(sslfile_t *f) { 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; + case SEC_E_UNFINISHED_CONTEXT_DELETED: SSPI_Error(f, "DecryptMessage failed: SEC_E_UNFINISHED_CONTEXT_DELETED\n"); break; //peer aborted? default: SSPI_Error(f, "DecryptMessage failed: %0#lx\n", ss); break; } return; @@ -318,7 +392,7 @@ static void SSPI_Decode(sslfile_t *f) } //convert outgoing data->crypt -static void SSPI_Encode(sslfile_t *f) +static neterr_t SSPI_Encode(sslfile_t *f) { SECURITY_STATUS ss; SecBufferDesc BuffDesc; @@ -329,16 +403,16 @@ static void SSPI_Encode(sslfile_t *f) { SSPI_TryFlushCryptOut(f); if (f->outcrypt.avail) - return; //don't flood too much + return NETERR_CLOGGED; //don't flood too much } //don't corrupt the handshake data. if (f->handshaking) - return; + return NETERR_CLOGGED; if (!f->outraw.avail) - return; + return NETERR_SENT; BuffDesc.ulVersion = SECBUFFER_VERSION; BuffDesc.cBuffers = 4; @@ -364,8 +438,12 @@ static void SSPI_Encode(sslfile_t *f) if (ss < 0) { - SSPI_Error(f, "EncryptMessage failed\n"); - return; + switch (ss) + { + case SEC_E_ENCRYPT_FAILURE: SSPI_Error(f, "EncryptMessage failed SEC_E_ENCRYPT_FAILURE (in: %i, max out %i)\n", f->outraw.avail, f->outcrypt.avail); + default: SSPI_Error(f, "EncryptMessage failed %x\n", ss); + } + return NETERR_DISCONNECTED; } f->outraw.avail = 0; @@ -374,20 +452,20 @@ static void SSPI_Encode(sslfile_t *f) if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[0].pvBuffer, SecBuff[0].cbBuffer, true) < SecBuff[0].cbBuffer) { SSPI_Error(f, "crypt buffer overflowed\n"); - return; + return NETERR_DISCONNECTED; } if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[1].pvBuffer, SecBuff[1].cbBuffer, true) < SecBuff[1].cbBuffer) { SSPI_Error(f, "crypt buffer overflowed\n"); - return; + return NETERR_DISCONNECTED; } if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[2].pvBuffer, SecBuff[2].cbBuffer, true) < SecBuff[2].cbBuffer) { SSPI_Error(f, "crypt buffer overflowed\n"); - return; + return NETERR_DISCONNECTED; } - SSPI_TryFlushCryptOut(f); + return SSPI_TryFlushCryptOut(f); } char *narrowen(char *out, size_t outlen, wchar_t *wide); @@ -412,7 +490,8 @@ static DWORD VerifyKnownCertificates(DWORD status, wchar_t *domain, qbyte *data, status = SEC_E_OK; else #endif - status = TRUST_E_FAIL; + if (SUCCEEDED(status)) + status = TRUST_E_EXPLICIT_DISTRUST; } return status; } @@ -431,7 +510,7 @@ static DWORD VerifyKnownCertificates(DWORD status, wchar_t *domain, qbyte *data, 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; + status = TRUST_E_EXPLICIT_DISTRUST; } BZ_Free(knowncert); } @@ -518,27 +597,28 @@ static DWORD VerifyServerCertificate(PCCERT_CONTEXT pServerCert, PWSTR pwszServe 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_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 TRUST_E_EXPLICIT_DISTRUST: err = "blocked"; 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; + case CERT_E_WRONG_USAGE: err = "CERT_E_WRONG_USAGE"; break; + default: err = va("%#x", (int)Status); break; } Con_Printf(CON_ERROR "Error verifying certificate for '%ls': %s\n", pwszServerName, err); @@ -558,21 +638,84 @@ static DWORD VerifyServerCertificate(PCCERT_CONTEXT pServerCert, PWSTR pwszServe } static PCCERT_CONTEXT SSPI_GetServerCertificate(void) { + //frankly this is all kinda fucked static PCCERT_CONTEXT ret; - char *issuertext = "CN=127.0.0.1, O=\"FTE QuakeWorld\", OU=Testing, C=TR"; + static const char *issuertext = "CN=127.0.0.1, O=\"FTE QuakeWorld\", OU=Fallback, C=QW"; + wchar_t *const container = L"diediedie"; + static const int provtype = PROV_RSA_SCHANNEL; + static wchar_t *const provname = MS_DEF_RSA_SCHANNEL_PROV_W; + CRYPT_KEY_PROV_INFO kpi; CERT_NAME_BLOB issuerblob; + HCRYPTPROV prov = 0; + HCRYPTKEY hkey = 0; //nope, not registry related. CRYPT_ALGORITHM_IDENTIFIER sigalg; SYSTEMTIME expiredate; + int i; + const char *pfxname = NULL; - if (ret) + qofs_t fsz = 0; + CRYPT_DATA_BLOB pfxblob = {0, NULL}; + HCERTSTORE store; + wchar_t password[512]; + DWORD fucksake = countof(password); + static qboolean tried; + + if (ret||tried) return ret; + tried = true; + + i = COM_CheckParm("-pfx"); + if (i && i < com_argc-1) + pfxname = com_argv[i+1]; + + if (pfxname) + pfxblob.pbData = FS_MallocFile(pfxname, FS_SYSTEM, &fsz); + if (!pfxblob.pbData) + pfxblob.pbData = FS_MallocFile("identity.pfx", FS_ROOT, &fsz); + if (pfxblob.pbData) + { + pfxblob.cbData = fsz; + if (!GetUserNameW(password, &fucksake)) //use their username as a password. at least it'll block most accidental redistriction. should prolly mix in their computer name, w/e + *password = 0; + store = crypt.pPFXImportCertStore(&pfxblob, password, 0); + if (!store) //try a couple of other passwords, for people creating their own manually. + store = crypt.pPFXImportCertStore(&pfxblob, L"", 0); + if (!store) + store = crypt.pPFXImportCertStore(&pfxblob, NULL, 0); + + if (store) + { + HCRYPTPROV hprov = 0; + DWORD keyspec = 0; + BOOL willbefalse = false; + + ret = crypt.pCertFindCertificateInStore(store, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL); + if (ret && crypt.pCryptAcquireCertificatePrivateKey(ret, 0, NULL, &hprov, &keyspec, &willbefalse)) + { + char fdn[1024]; + char digest[DIGEST_MAXSIZE]; + char b64[DIGEST_MAXSIZE*2+1]; + size_t dgsz = CalcHash(&hash_sha1, digest, sizeof(digest), ret->pbCertEncoded, ret->cbCertEncoded); + Base64_EncodeBlock(digest, dgsz, b64, sizeof(b64)); + Con_Printf("Loaded Certificate fingerprint is %s\n", b64); + crypt.pCertNameToStrA(ret->dwCertEncodingType, &ret->pCertInfo->Subject, CERT_X500_NAME_STR, fdn, sizeof(fdn)); + Con_Printf("Loaded Certificate DN: %s\n", fdn); + return ret; + } + } + Con_Printf(CON_ERROR"pfx certificate failed to load.\n"); + pfxname = NULL; //don't overwrite an overridden name. it + } + else if (pfxname) + Con_Printf(CON_WARNING"Generating new pfx file: %s\n", pfxname); memset(&sigalg, 0, sizeof(sigalg)); sigalg.pszObjId = szOID_RSA_SHA512RSA; GetSystemTime(&expiredate); - expiredate.wYear += 2; //2 years hence. woo + expiredate.wYear += 5; //5 years hence. woo + expiredate.wDay = 1; //work around feb nightmares... not to be confused with Week-Day... memset(&issuerblob, 0, sizeof(issuerblob)); @@ -580,11 +723,28 @@ static PCCERT_CONTEXT SSPI_GetServerCertificate(void) issuerblob.pbData = Z_Malloc(issuerblob.cbData); crypt.pCertStrToNameA(X509_ASN_ENCODING, issuertext, CERT_X500_NAME_STR, NULL, issuerblob.pbData, &issuerblob.cbData, NULL); + advapi.pCryptAcquireContextW(&prov, container, provname, provtype, CRYPT_NEWKEYSET|CRYPT_MACHINE_KEYSET); + if (!prov) + { //try again. fucking retarded api. + if (!advapi.pCryptAcquireContextW(&prov, container, provname, provtype, CRYPT_MACHINE_KEYSET)) + { + Con_Printf(CON_ERROR"CryptAcquireContext failed.\n"); + return NULL; + } + } + advapi.pCryptGenKey(prov, AT_KEYEXCHANGE, CRYPT_EXPORTABLE|CRYPT_ARCHIVABLE, &hkey); + kpi.pwszContainerName = container; + kpi.pwszProvName = provname; + kpi.dwProvType = provtype; + kpi.dwFlags = CERT_SET_KEY_CONTEXT_PROP_ID; + kpi.cProvParam = 0; + kpi.dwKeySpec = AT_KEYEXCHANGE; + ret = crypt.pCertCreateSelfSignCertificate( - 0, + prov, &issuerblob, 0, - NULL, + &kpi, &sigalg, NULL, &expiredate, @@ -594,18 +754,92 @@ static PCCERT_CONTEXT SSPI_GetServerCertificate(void) { //try and downgrade the signature algo if it failed. sigalg.pszObjId = szOID_RSA_SHA1RSA; ret = crypt.pCertCreateSelfSignCertificate( - 0, + prov, &issuerblob, 0, - NULL, + &kpi, &sigalg, NULL, &expiredate, NULL ); } + if (!ret) + Con_Printf(CON_ERROR"Certificate generation failed...\n"); + else + { + //this is stupid and redundant, yet apparently still needed. + kpi.pwszContainerName = container; + kpi.pwszProvName = provname; + kpi.dwProvType = provtype; + kpi.dwFlags = CRYPT_MACHINE_KEYSET; + kpi.dwKeySpec = AT_KEYEXCHANGE; + crypt.pCertSetCertificateContextProperty(ret, CERT_KEY_PROV_INFO_PROP_ID, 0, &kpi); + { + HCRYPTPROV hprov = 0; + DWORD keyspec = 0; + BOOL willbefalse = false; + if (!crypt.pCryptAcquireCertificatePrivateKey(ret, 0, NULL, &hprov, &keyspec, &willbefalse)) + { + Con_Printf(CON_ERROR"Private key is defective.\n"); + return NULL; + } + } + + //write it to disk + { + wchar_t password[512]; + CRYPT_DATA_BLOB blob = {0}; + HCERTSTORE store = crypt.pCertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, CERT_STORE_CREATE_NEW_FLAG, NULL); + if (store) + { + if (crypt.pCertAddCertificateContextToStore(store, ret, CERT_STORE_ADD_ALWAYS, NULL)) + { + DWORD fucksake = countof(password); + if (!GetUserNameW(password, &fucksake)) //use their username as a password. at least it'll block most accidental redistriction. should prolly mix in their computer name, w/e + *password = 0; + + if (crypt.pPFXExportCertStoreEx(store, &blob, password, NULL, EXPORT_PRIVATE_KEYS|REPORT_NO_PRIVATE_KEY|REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)) + { + blob.pbData = alloca(blob.cbData); + if (crypt.pPFXExportCertStoreEx(store, &blob, password, NULL, EXPORT_PRIVATE_KEYS|REPORT_NO_PRIVATE_KEY|REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)) + { + if (blob.cbData) + { + if (!FS_WriteFile(pfxname?pfxname:"identity.pfx", blob.pbData, blob.cbData, pfxname?FS_SYSTEM:FS_ROOT)) + Con_Printf(CON_ERROR"FS_WriteFile(%s) failed\n", pfxname?pfxname:"identity.pfx"); + } + else + Con_Printf(CON_ERROR"PFXExportCertStoreEx no data\n"); + } + else + Con_Printf(CON_ERROR"PFXExportCertStoreEx failed\n"); + } + else + Con_Printf(CON_ERROR"PFXExportCertStoreEx failed\n"); + } + else + Con_Printf(CON_ERROR"CertAddCertificateContextToStore failed\n"); + crypt.pCertCloseStore(store, 0); + } + else + Con_Printf(CON_ERROR"CertOpenStore failed\n"); + } + + { + char fdn[1024]; + char digest[DIGEST_MAXSIZE]; + char b64[DIGEST_MAXSIZE*2+1]; + size_t dgsz = CalcHash(&hash_sha1, digest, sizeof(digest), ret->pbCertEncoded, ret->cbCertEncoded); + Base64_EncodeBlock(digest, dgsz, b64, sizeof(b64)); + Con_Printf("Generated Certificate fingerprint is %s\n", b64); + crypt.pCertNameToStrA(ret->dwCertEncodingType, &ret->pCertInfo->Subject, CERT_X500_NAME_STR, fdn, sizeof(fdn)); + Con_Printf("Generated Certificate DN: %s\n", fdn); + } + } Z_Free(issuerblob.pbData); + return ret; } @@ -632,9 +866,14 @@ static void SSPI_GenServerCredentials(sslfile_t *f) } ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_INBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime); - if (ss < 0) + if (FAILED(ss) && f->datagram) + { //try again with just dtls1, for cred, &Lifetime); + } + if (FAILED(ss)) { - SSPI_Error(f, localtext("WinSSPI: AcquireCredentialsHandle failed\n")); + SSPI_Error(f, localtext("WinSSPI: AcquireCredentialsHandle failed %#x\n"), ss); return; } } @@ -652,9 +891,6 @@ static void SSPI_Handshake (sslfile_t *f) int i; qboolean retries = 5; -// char buf1[128]; -// char buf2[128]; - retry: if (f->outcrypt.avail) @@ -686,6 +922,8 @@ retry: return; //gave up. else if (f->handshaking == HS_STARTCLIENT) { + PCCERT_CONTEXT cert; + //no input data yet. f->handshaking = HS_CLIENT; @@ -694,13 +932,25 @@ retry: 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) + //just always use the same credentials, cos we're lazy and lame, and windows users don't give a shit about privacy anyway. + cert = SSPI_GetServerCertificate(); + if (cert) { - SSPI_Error(f, localtext("WINSSPI: AcquireCredentialsHandle failed\n")); - return; + SchannelCred.cCreds = 1; + SchannelCred.paCred = &cert; } + ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime); + if (FAILED(ss) && f->datagram) + { + SchannelCred.grbitEnabledProtocols = SP_PROT_DTLS1_0_CLIENT; + ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime); + } + if (ss < 0) + { + SSPI_Error(f, localtext("WINSSPI: AcquireCredentialsHandle failed (%x)\n"), (int)ss); + 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) @@ -730,11 +980,11 @@ retry: 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); + ss = secur.pInitializeSecurityContextW (&f->cred, &f->sechnd, NULL, MessageAttribute|(f->datagram?ISC_REQ_DATAGRAM:ISC_REQ_STREAM), 0, SECURITY_NETWORK_DREP, &InBuffDesc, 0, NULL, &OutBuffDesc, &ContextAttributes, &Lifetime); if (ss == SEC_E_INCOMPLETE_MESSAGE) - { -// Con_Printf("SEC_E_INCOMPLETE_MESSAGE\n"); + { //TLS splits the data randomly, so this should not be considered fatal +// Con_Printf("SEC_E_INCOMPLETE_MESSAGE (available %i)\n", (int)f->incrypt.avail); if (!f->datagram && f->incrypt.avail == f->incrypt.datasize) SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024); return; @@ -780,6 +1030,14 @@ retry: i++; } + if (f->datagram) + { //for dtls's cookie + InSecBuff[i].BufferType = SECBUFFER_EXTRA; + InSecBuff[i].cbBuffer = 11; + InSecBuff[i].pvBuffer = "Hello World"; + i++; + } + for (; i < InBuffDesc.cBuffers; i++) { InSecBuff[i].BufferType = SECBUFFER_EMPTY; @@ -789,15 +1047,26 @@ retry: i = 1; OutSecBuff[i++].BufferType = SECBUFFER_EXTRA; - OutSecBuff[i++].BufferType = 17/*SECBUFFER_ALERT*/; + OutSecBuff[i++].BufferType = SECBUFFER_ALERT; -#define ServerMessageAttribute (ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY /*| ASC_REQ_EXTENDED_ERROR*/ | ASC_REQ_ALLOCATE_MEMORY) +#define ServerMessageAttribute (ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY | /*ASC_REQ_EXTENDED_ERROR |*/ ASC_REQ_ALLOCATE_MEMORY) + ContextAttributes = ServerMessageAttribute|(f->datagram?ASC_REQ_DATAGRAM:ASC_REQ_STREAM); +// if (f->peerhash) //for ICE +// ContextAttributes |= ASC_REQ_MUTUAL_AUTH; 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, + ContextAttributes, SECURITY_NETWORK_DREP, &f->sechnd, &OutBuffDesc, &ContextAttributes, NULL); + if (f->datagram && ss == SEC_I_CONTINUE_NEEDED && f->handshaking != HS_SERVER) + { //this seems wrong, but schannel complains if we don't continue to pass null above. + secur.pDeleteSecurityContext(&f->sechnd); //avoid leaks + memset(&f->sechnd, 0, sizeof(f->sechnd)); + } + else + f->handshaking = HS_SERVER; + if (ss == SEC_E_INVALID_TOKEN) - { + { //sender sent us something that wasn't (d)tls. // Con_Printf("SEC_E_INVALID_TOKEN\n"); if (f->datagram) return; @@ -809,12 +1078,11 @@ retry: SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024); return; } -// else -// Con_Printf("InitializeSecurityContextA %x\n", ss); - f->handshaking = HS_SERVER; +// else if (FAILED(ss)) +// Con_Printf("AcceptSecurityContext %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) + if (InSecBuff[1].BufferType == SECBUFFER_EXTRA && !f->datagram) { memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - InSecBuff[1].cbBuffer), InSecBuff[1].cbBuffer); f->incrypt.avail = InSecBuff[1].cbBuffer; @@ -827,20 +1095,23 @@ retry: if (ss == SEC_I_INCOMPLETE_CREDENTIALS) { - SSPI_Error(f, localtext("server requires credentials\n")); - return; + //FIXME: load/generate our identity, redo the pAcquireCredentialsHandleA + Con_TPrintf(CON_WARNING"server requires credentials, attempting to ignore\n"); + goto retry; } - if (ss < 0) + if (FAILED(ss)) { + const char *fname = (f->handshaking>=HS_STARTSERVER)?"AcceptSecurityContext":"InitializeSecurityContext"; 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; + case SEC_E_ALGORITHM_MISMATCH: SSPI_Error(f, "%s failed: SEC_E_ALGORITHM_MISMATCH\n", fname); break; + case SEC_E_INVALID_HANDLE: SSPI_Error(f, "%s failed: SEC_E_INVALID_HANDLE\n", fname); break; + case SEC_E_ILLEGAL_MESSAGE: SSPI_Error(f, "%s failed: SEC_E_ILLEGAL_MESSAGE\n", fname); break; + case SEC_E_INVALID_TOKEN: SSPI_Error(f, "%s failed: SEC_E_INVALID_TOKEN\n", fname); break; + case SEC_E_INVALID_PARAMETER: SSPI_Error(f, "%s failed: SEC_E_INVALID_PARAMETER\n", fname); break; + case SEC_E_INTERNAL_ERROR: SSPI_Error(f, "%s failed: SEC_E_INTERNAL_ERROR\n", fname); break; + default: SSPI_Error(f, "%s failed: %lx\n", fname, (long)ss); break; } return; } @@ -861,9 +1132,20 @@ retry: SecPkgContext_StreamSizes strsizes; CERT_CONTEXT *remotecert; +// Con_Printf("ContextAttributes: %x (%s, %s)\n", ContextAttributes, (f->handshaking == HS_SERVER)?"sv":"cl", f->datagram?"dtls":"tls"); + + if (f->datagram) + { //ask for a bit bigger. we use sendto for mtu guessing, schannel spitting out errors for arbitrary lower values is just annoying - it knows nothing about the actual connection. + ULONG mtu = 8192; + secur.pSetContextAttributesA(&f->sechnd, SECPKG_ATTR_DTLS_MTU, &mtu, sizeof(mtu)); + } + secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_STREAM_SIZES, &strsizes); f->headersize = strsizes.cbHeader; f->footersize = strsizes.cbTrailer; + + f->mtu = strsizes.cbMaximumMessage - (f->headersize+f->footersize); //compute the maximum payload we can actually get with that. + if (f->handshaking != HS_SERVER) { //server takes an annonymous client. client expects a proper certificate. if (*f->wpeername) @@ -876,7 +1158,7 @@ retry: } if (VerifyServerCertificate(remotecert, f->wpeername, 0, f->datagram)) { - SSPI_Error(f, localtext("Error validating certificante\n")); + SSPI_Error(f, localtext("Error validating certificate\n")); return; } } @@ -893,7 +1175,10 @@ retry: { for (i = 0; i < OutBuffDesc.cBuffers; i++) if (OutSecBuff[i].BufferType == SECBUFFER_TOKEN && OutSecBuff[i].cbBuffer) + { + f->resendtimer = realtime+0.2; f->transmit(f->cbctx, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer); + } } else #endif @@ -950,7 +1235,7 @@ static int QDECL SSPI_WriteBytes (struct vfsfile_s *file, const void *buffer, in //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; + return 0; bytestowrite = SSPI_CopyIntoBuffer(&f->outraw, buffer, bytestowrite, false); @@ -996,27 +1281,54 @@ static qboolean QDECL SSPI_Close (struct vfsfile_s *file) } #include +static qboolean CleanUpHostname(const char *hostname, wchar_t *out, size_t outcount) +{ //strip any fte-specific junk, like dtls:// or [] or : + const char *hostnameend; + const char *host = strstr(hostname, "://"); + int i = 0; + if (host) + hostname = host+3; + hostnameend = hostname+strlen(hostname); + //any dtls:// prefix will have been stripped now. + if (*hostname == '[') + { //eg: [::1]:foo - skip the lead [ and strip the ] and any trailing data (hopefully just a :port or nothing) + hostname++; + host = strchr(hostname, ']'); + if (host) + hostnameend = host; + } + else + { //eg: 127.0.0.1:port - strip the port number if specified. + host = strchr(hostname, ':'); + if (host) + hostnameend = host; + } + + while(hostname < hostnameend) + { + int err; + int c = utf8_decode(&err, hostname, (void*)&hostname); + if (c > WCHAR_MAX) + err = true; //no 16bit surrogates. they're evil. + else if (i == outcount - 1) + err = true; //no space to store it + else + out[i++] = c; + if (err) + { + out[i] = 0; + return false; + } + } + out[i] = 0; + return true; +} + static vfsfile_t *SSPI_OpenVFS(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()) return NULL; - if (server) - { -// localname = servername; - peername = ""; - } - else - { -// localname = ""; - peername = servername; - } /* if (server) //unsupported @@ -1024,22 +1336,11 @@ static vfsfile_t *SSPI_OpenVFS(const char *servername, vfsfile_t *source, qboole */ newf = Z_Malloc(sizeof(*newf)); - while(*peername) + if (!CleanUpHostname(server?"":servername, newf->wpeername, countof(newf->wpeername))) { - 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); - return NULL; - } + Z_Free(newf); + return NULL; } - newf->wpeername[i] = 0; newf->handshaking = server?HS_STARTSERVER:HS_STARTCLIENT; newf->stream = source; @@ -1085,7 +1386,7 @@ static int SSPI_GetChannelBinding(vfsfile_t *vf, qbyte *binddata, size_t *bindsi switch(secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_UNIQUE_BINDINGS, &bindings)) { case SEC_E_OK: - if (bindings.Bindings->cbApplicationDataLength <= *bindsize) + if (bindings.Bindings->cbApplicationDataLength <= *bindsize && !Q_strncasecmp(((char*)bindings.Bindings)+bindings.Bindings->dwApplicationDataOffset, "tls-unique:", 11)) { //will contain 'tls-unique:BINARYDATA' *bindsize = bindings.Bindings->cbApplicationDataLength-11; @@ -1108,8 +1409,6 @@ static int SSPI_GetChannelBinding(vfsfile_t *vf, qbyte *binddata, size_t *bindsi #if defined(HAVE_DTLS) static void *SSPI_DTLS_CreateContext(const dtlscred_t *credinfo, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) { - int i = 0; - const char *remotehost = credinfo->peer.name; sslfile_t *ctx; if (!SSL_Inited()) return NULL; @@ -1120,23 +1419,11 @@ static void *SSPI_DTLS_CreateContext(const dtlscred_t *credinfo, void *cbctx, ne ctx->cbctx = cbctx; ctx->transmit = push; - while(*remotehost) + if (!CleanUpHostname((credinfo&&credinfo->peer.name)?credinfo->peer.name:"", ctx->wpeername, countof(ctx->wpeername))) { - 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; - } + Z_Free(ctx); + return NULL; } - ctx->wpeername[i] = 0; SSPI_ExpandBuffer(&ctx->outraw, 8192); SSPI_ExpandBuffer(&ctx->outcrypt, 65536); @@ -1158,31 +1445,33 @@ static void SSPI_DTLS_DestroyContext(void *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); + if (!datasize) + { //we use these as a way to probe whether its sendable or not yet. + if (f->handshaking) + { + if (f->resendtimer < realtime) + SSPI_Handshake(f); //keep trying... + return NETERR_CLOGGED; + } + return NETERR_SENT; + } + + if (f->handshaking) + SSPI_Handshake(f); //keep trying... + if (f->handshaking == HS_ERROR) + return NETERR_DISCONNECTED; + if (f->handshaking) + return NETERR_CLOGGED; //not ready yet + + if (datasize >= f->mtu) + return NETERR_MTU; //we're not allowed. //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; - else - ret = NETERR_CLOGGED; //not ready yet - } - else - { - SSPI_Encode(f); - ret = NETERR_SENT; - } - - return ret; + return SSPI_Encode(f); } static neterr_t SSPI_DTLS_Received(void *ctx, sizebuf_t *msg) @@ -1190,8 +1479,6 @@ static neterr_t SSPI_DTLS_Received(void *ctx, sizebuf_t *msg) int ret; sslfile_t *f = (sslfile_t *)ctx; -//Con_Printf("DTLS_Received: %i\n", datasize); - f->incrypt.data = msg->data; f->incrypt.avail = f->incrypt.datasize = msg->cursize; @@ -1208,7 +1495,7 @@ static neterr_t SSPI_DTLS_Received(void *ctx, sizebuf_t *msg) SSPI_Decode(f); ret = NETERR_SENT; - if (f->inraw.avail > msg->maxsize) + if (f->inraw.avail < msg->maxsize) msg->cursize = f->inraw.avail; else msg->cursize = msg->maxsize; @@ -1223,38 +1510,179 @@ static neterr_t SSPI_DTLS_Timeouts(void *ctx) sslfile_t *f = (sslfile_t *)ctx; if (f->handshaking) { -// SSPI_Handshake(f); + SSPI_DTLS_Transmit(ctx, NULL, 0); return NETERR_CLOGGED; } return NETERR_SENT; } +static qboolean SSPI_DTLS_CheckConnection(void *cbctx, void *peeraddr, size_t peeraddrsize, void *indata, size_t insize, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), void (*EstablishTrueContext)(void **cbctx, void *state)) +{ //we got a packet that might be a 'dtls hello' packet. try figuring out what's going on. + + SECURITY_STATUS ss; + SecBufferDesc OutBuffDesc; + SecBuffer OutSecBuff[8]; + SecBufferDesc InBuffDesc; + SecBuffer InSecBuff[8]; + ULONG ContextAttributes; + int i; + char replymessage[4094]; + + static sslfile_t *pending; + if (!pending) + { + pending = SSPI_DTLS_CreateContext(NULL, NULL, NULL, true); + if (!pending) + return false; + } + + InBuffDesc.ulVersion = SECBUFFER_VERSION; + InBuffDesc.cBuffers = countof(InSecBuff); + InBuffDesc.pBuffers = InSecBuff; + i = 0; + + InSecBuff[i].BufferType = SECBUFFER_TOKEN; + InSecBuff[i].cbBuffer = insize; + InSecBuff[i].pvBuffer = indata; + i++; + + //for dtls's cookie + InSecBuff[i].BufferType = SECBUFFER_EXTRA; + InSecBuff[i].cbBuffer = peeraddrsize; + InSecBuff[i].pvBuffer = peeraddr; + i++; + + for (; i < InBuffDesc.cBuffers; i++) + { + InSecBuff[i].BufferType = SECBUFFER_EMPTY; + InSecBuff[i].pvBuffer = NULL; + InSecBuff[i].cbBuffer = 0; + } + + OutBuffDesc.ulVersion = SECBUFFER_VERSION; + OutBuffDesc.cBuffers = countof(OutSecBuff); + OutBuffDesc.pBuffers = OutSecBuff; + + OutSecBuff[0].BufferType = SECBUFFER_TOKEN; + OutSecBuff[0].cbBuffer = sizeof(replymessage); + OutSecBuff[0].pvBuffer = replymessage; + + for (i = 1; i < OutBuffDesc.cBuffers; i++) + { + OutSecBuff[i].BufferType = SECBUFFER_EMPTY; + OutSecBuff[i].pvBuffer = NULL; + OutSecBuff[i].cbBuffer = 0; + } + + i = 1; + OutSecBuff[i++].BufferType = SECBUFFER_EXTRA; + OutSecBuff[i++].BufferType = SECBUFFER_ALERT; + + ContextAttributes = ServerMessageAttribute|ASC_REQ_DATAGRAM; + ss = secur.pAcceptSecurityContext(&pending->cred, NULL, &InBuffDesc, + ContextAttributes, SECURITY_NETWORK_DREP, &pending->sechnd, + &OutBuffDesc, &ContextAttributes, NULL); + + //expect SEC_I_CONTINUE_NEEDED for a outgoing challenge (cookie request) + //expect SEC_I_MESSAGE_FRAGMENT for anything more. + + for (i = 0; i < OutBuffDesc.cBuffers; i++) + if (OutSecBuff[i].BufferType == SECBUFFER_TOKEN && OutSecBuff[i].cbBuffer) + push(cbctx, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer); + if (ss == SEC_I_MESSAGE_FRAGMENT) + { //looks like we got a reply to the dtls handshake. lock down its ip. + pending->cbctx = cbctx; + pending->transmit = push; + pending->handshaking = HS_SERVER; + EstablishTrueContext(&pending->cbctx, pending); + pending = NULL; + return true; + } + //try to avoid memory leaks. this stuff isn't documented nearly well enough. + secur.pDeleteSecurityContext(&pending->sechnd); + memset(&pending->sechnd, 0, sizeof(pending->sechnd)); + + return false; +} + +qboolean SSPI_DTLS_GenTempCertificate(const char *subject, struct dtlslocalcred_s *cred) +{ //exporting+importing certs here is just too damn painful. use a single cert and a dummy value for the key. for webrtc this SHOULD be okay, even if its expired... + PCCERT_CONTEXT cert = SSPI_GetServerCertificate(); + if (!cert) + return false; + + cred->cert = memcpy(BZ_Malloc(cert->cbCertEncoded), cert->pbCertEncoded, cert->cbCertEncoded); + cred->certsize = cert->cbCertEncoded; + cred->key = NULL; + cred->keysize = 0; + + return true; +} + + +static int SSPI_DTLS_GetPeerCertificate(void *ctx, enum certprops_e prop, char *out, size_t outsize) +{ + sslfile_t *f = (sslfile_t *)ctx; + CERT_CONTEXT *cert = NULL; + + safeswitch(prop) + { + case QCERT_ISENCRYPTED: + if (f->handshaking == HS_ESTABLISHED) + return 0; //handshake is done, its all encrypted. + return -1; //still pending, doesn't count as encrypted yet. + + case QCERT_PEERCERTIFICATE: + secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &cert); + if (cert && cert->cbCertEncoded <= outsize) + { + memcpy(out, cert->pbCertEncoded, cert->cbCertEncoded); + return cert->cbCertEncoded; + } + return -1; + + case QCERT_PEERSUBJECT: + secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &cert); + if (cert && cert->cbCertEncoded <= outsize) + return crypt.pCertNameToStrA(cert->dwCertEncodingType, &cert->pCertInfo->Subject, CERT_X500_NAME_STR, out, outsize); + return -1; + case QCERT_LOCALCERTIFICATE: + safedefault: + return -1; + } +} + static const dtlsfuncs_t dtlsfuncs_schannel = { SSPI_DTLS_CreateContext, - NULL, + SSPI_DTLS_CheckConnection, SSPI_DTLS_DestroyContext, SSPI_DTLS_Transmit, SSPI_DTLS_Received, SSPI_DTLS_Timeouts, + SSPI_DTLS_GetPeerCertificate, + SSPI_DTLS_GenTempCertificate }; -/*static const dtlsfuncs_t *SSPI_DTLS_InitServer(void) +static 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. + //make sure we can load a cert... + if (!SSL_Inited() || !SSPI_GetServerCertificate()) + return NULL; //otherwise refuse to run as a server instead of causing cert issues with every restart for users. + return &dtlsfuncs_schannel; -}*/ +} static const dtlsfuncs_t *SSPI_DTLS_InitClient(void) { return &dtlsfuncs_schannel; } #endif - -//#include //windows sucks too much to actually include this. oh well. +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define SSPI_VerifyHash NULL //old versions of msvc are crippled... +#else #define STATUS_SUCCESS ((NTSTATUS)0x00000000) #define STATUS_INVALID_SIGNATURE ((NTSTATUS)0xC000A000) +#include static enum hashvalidation_e SSPI_VerifyHash(const qbyte *hashdata, size_t hashsize, const qbyte *pemcert, size_t pemcertsize, const qbyte *signdata, size_t signsize) { NTSTATUS status; @@ -1325,14 +1753,15 @@ static enum hashvalidation_e SSPI_VerifyHash(const qbyte *hashdata, size_t hashs return VH_INCORRECT; //its bad return VH_UNSUPPORTED; //some weird transient error...? } +#endif ftecrypto_t crypto_sspi = { - "WinSSPI", + "SChannel", SSPI_OpenVFS, SSPI_GetChannelBinding, SSPI_DTLS_InitClient, - NULL,//SSPI_DTLS_InitServer, + SSPI_DTLS_InitServer, SSPI_VerifyHash, NULL,//SSPI_GenerateHash, }; diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index ff1bd3da0..61eff720c 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -118,6 +118,7 @@ int UDP6_OpenSocket (int port); #ifdef HAVE_IPX void IPX_CloseSocket (int socket); #endif +cvar_t net_ice_broker = CVARFD("net_ice_broker", "tls://master.frag-net.com:27950", CVAR_NOTFROMSERVER, "This is the default broker we attempt to connect through when using 'sv_public /foo' or 'connect /foo'."); cvar_t timeout = CVARD("timeout","65", "Connections will time out if no packets are received for this duration of time."); // seconds without any message cvar_t net_hybriddualstack = CVARD("net_hybriddualstack", "1", "Uses hybrid ipv4+ipv6 sockets where possible. Not supported on xp or below."); cvar_t net_fakeloss = CVARFD("net_fakeloss", "0", CVAR_CHEAT, "Simulates packetloss in both receiving and sending, on a scale from 0 to 1."); @@ -141,6 +142,7 @@ cvar_t net_enable_tls = CVARD("net_enable_tls", "0", "If enabled, binary dat #endif #ifdef HAVE_HTTPSV #ifdef SV_MASTER +extern ftenet_connections_t *svm_sockets; cvar_t net_enable_http = CVARD("net_enable_http", "1", "If enabled, tcp ports will accept inbound http clients, potentially serving large files which could distrupt gameplay (This does not affect outgoing http(s) requests)."); cvar_t net_enable_rtcbroker = CVARD("net_enable_rtcbroker", "1", "If 1, tcp ports will accept websocket connections from clients trying to broker direct webrtc connections. This should be low traffic, but might involve a lot of mostly-idle connections."); cvar_t net_enable_websockets = CVARD("net_enable_websockets", "0", "If enabled, tcp ports will accept websocket game clients."); @@ -152,28 +154,34 @@ cvar_t net_enable_websockets = CVARD("net_enable_websockets", "1", "If enabled, #endif #endif #if defined(HAVE_DTLS) -#if defined(HAVE_SERVER) static void QDECL NET_Enable_DTLS_Changed(struct cvar_s *var, char *oldvalue) { var->ival = var->value; //set up the default value if (!*var->string) - var->ival = 0; //FIXME: change to 1 then 2 when better tested. + var->ival = 1; //FIXME: change to 1 then 2 when better tested. - if (var->ival && svs.sockets) +#if defined(HAVE_SERVER) + if (svs.sockets) { - if (!svs.sockets->dtlsfuncs) - svs.sockets->dtlsfuncs = DTLS_InitServer(); - if (!svs.sockets->dtlsfuncs) - { - if (var->ival >= 2) - Con_Printf("%sUnable to set %s to \"%s\", no DTLS provider available.\n", (var->ival >= 2)?CON_ERROR:CON_WARNING, var->name, var->string); - var->ival = 0; //disable the cvar (internally) if we don't have a usable certificate. this allows us to default the cvar to enabled without it breaking otherwise. - } + svs.sockets->dtlsfuncs = (var->ival)?DTLS_InitServer():NULL; + if (!svs.sockets->dtlsfuncs && var->ival >= 2) + Con_Printf("%sUnable to set %s to \"%s\", no DTLS provider available.\n", (var->ival >= 2)?CON_ERROR:CON_WARNING, var->name, var->string); } + + { + char cert[8192]; + int certsize; + char digest[DIGEST_MAXSIZE]; + certsize = (svs.sockets&&svs.sockets->dtlsfuncs)?svs.sockets->dtlsfuncs->GetPeerCertificate(NULL, QCERT_LOCALCERTIFICATE, cert, sizeof(cert)):-1; + if (certsize > 0) + InfoBuf_SetStarBlobKey(&svs.info, "*fp", cert, Base64_EncodeBlockURI(digest, CalcHash(&hash_sha2_256, digest, sizeof(digest), cert, certsize), cert, sizeof(cert))); + else + InfoBuf_SetStarKey(&svs.info, "*fp", ""); + } +#endif } cvar_t net_enable_dtls = CVARAFCD("net_enable_dtls", "", "sv_listen_dtls", 0, NET_Enable_DTLS_Changed, "Controls serverside dtls support.\n0: dtls blocked, not advertised.\n1: clientside choice.\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 dtls_psk_hint = CVARFD("dtls_psk_hint", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the public server identity."); cvar_t dtls_psk_user = CVARFD("dtls_psk_user", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the username to use when the client+server's hints match."); cvar_t dtls_psk_key = CVARFD("dtls_psk_key", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the hexadecimal key which must match between client+server. Will only be used when client+server's hint settings match."); @@ -253,7 +261,8 @@ static void NET_TLS_Provider_Changed(struct cvar_s *var, char *oldvalue) } #if defined(HAVE_DTLS) && defined(HAVE_SERVER) - Cvar_ForceCallback(&net_enable_dtls); + if (net_enable_dtls.string) //might not be registered yet... + Cvar_ForceCallback(&net_enable_dtls); #endif } #endif @@ -1569,6 +1578,7 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num netproto_t prot; netadrtype_t afhint; char *path; + char *args; struct { @@ -1648,8 +1658,7 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num //`connect /GAMENAME` is equivelent to `connect rtc://broker/GAMENAME` if (*s == '/') { - char *broker = fs_manifest->rtcbroker; - if (!broker || !*broker) + if (!*net_ice_broker.string) { //FIXME: use referrer? or the website's host? Con_DPrintf("No default rtc broker\n"); return 0; //can't accept it @@ -1675,7 +1684,8 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num a->prot = NP_RTC_TCP; s += 6; } - path = strchr(path, '/'); + + path = strchr(s, '/'); if (path) { if (s == path) //no hostname specified = use default broker (resolving it later) @@ -1686,12 +1696,18 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num a->address.websocketurl[path-s] = 0; } else + { + Con_DPrintf("Path too long\n"); return 0; //too long + } if (pathstart) *pathstart = path; } else + { + Con_DPrintf("No path\n"); return 0; //reject it when there's no path + } return 1; } else if (!strncmp (s, "ws://", 5) || !strncmp (s, "wss://", 6)) @@ -1704,6 +1720,20 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num Q_strncpyz(a->address.websocketurl, s, sizeof(a->address.websocketurl)); return 1; } + else if (*net_ice_broker.string) + { + a->type = NA_INVALID; //not quite right, but w/e. + a->prot = NP_RTC_TLS; + Q_snprintfz(a->address.websocketurl, sizeof(a->address.websocketurl), "/udp/%s", s); +// if (numaddresses < 2) + return 1; + + a++; + a->type = NA_WEBSOCKET; + a->prot = NP_WSS; + Q_snprintfz(a->address.websocketurl, sizeof(a->address.websocketurl), "wss://%s", s); + return 2; + } else { /*code for convienience - no other protocols work anyway*/ @@ -1797,11 +1827,17 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num } } + args = strchr(s, '?'); + if (args) + *args=0; + path = strchr(s, '/'); #if !defined(HAVE_WEBSOCKCL) && defined(SUPPORT_ICE) - if (path == s && fs_manifest->rtcbroker && *fs_manifest->rtcbroker) + if (path == s) { - s = fs_manifest->rtcbroker; + if (!*net_ice_broker.string) + return result; + s = net_ice_broker.string; if (!strncmp(s, "tls://", 6) || !strncmp(s, "wss://", 6)) s+=6, prot=NP_RTC_TLS; else if (!strncmp(s, "tcp://", 6)) @@ -1833,6 +1869,9 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num a[i].prot = prot; } + if (args) + *args='?'; + return result; } @@ -2026,7 +2065,7 @@ int ParsePartialIP(const char *s, netadr_t *a) { if (*s == ':') { - port = strtoul(s+1, &address, 10); + port = htons(strtoul(s+1, &address, 10)); if (*address) //if there was something other than a number there, give up now return 0; break; //end-of-string @@ -2131,10 +2170,59 @@ qboolean NET_StringToAdrMasked (const char *s, qboolean allowdns, netadr_t *a, n return true; } +qboolean NET_StringToAdr_NoDNS(const char *address, int port, netadr_t *out) +{ + int peerbits; + if (*address == '[') + { + char *close = strchr(address+1, ']'); + if (close) + *close = 0; + peerbits = NET_StringToAdr_NoDNS(address+1, 0, out); + if (close) + { + *close = ']'; + if (close[1] == ':') + out->port = htons(atoi(close+2)); + } + return peerbits; + } + else + { + peerbits = ParsePartialIP(address, out); + if (out->type == NA_IP && peerbits == 32) + { + //ignore invalid addresses + if (!out->address.ip[0] && !out->address.ip[1] && !out->address.ip[2] && !out->address.ip[3]) + out->type = NA_INVALID; + } + else if (out->type == NA_IPV6 && peerbits == 128) + { + //ignore invalid addresses + int i; + for (i = 0; i < countof(out->address.ip6); i++) + if (out->address.ip6[i]) + break; + if (i == countof(out->address.ip6)) + out->type = NA_INVALID; + } + else + out->type = NA_INVALID; + + return out->type != NA_INVALID; + } +} + qboolean NET_IsEncrypted(netadr_t *adr) { + if (adr->type == NA_LOOPBACK) + return true; //might as well claim it, others can't snoop on it so... #ifdef SUPPORT_ICE - if (adr->type == NA_ICE && ICE_IsEncrypted(adr)) + if (adr->type == NA_ICE && ICE_GetPeerCertificate(adr, QCERT_ISENCRYPTED, NULL, 0)==0) + return true; +#endif +#if defined(FTE_TARGET_WEB) + if (adr->prot == NP_RTC_TLS) //web port works a bit differently... webrtc is ALWAYS encrypted, but only report it as secure when the broker connection is encrypted too. return true; #endif if (adr->prot == NP_DTLS || adr->prot == NP_TLS || adr->prot == NP_WSS) @@ -2454,6 +2542,7 @@ void *TLS_GetKnownCertificate(const char *certname, size_t *size) //the xor helps break that shitty recursive loop of mistrust from defects in other people's code. //at least until there's a sandbox that checks the dns resolutions for our update requests anyway. //I should probably just copy the downloadables file to sourceforge. + //FIXME: we should be signing the content, not the sender. this SHOULD become redundant. static struct { qbyte *data; @@ -3022,7 +3111,7 @@ struct dtlspeer_s struct dtlspeer_s **link; }; -void NET_DTLS_Timeouts(ftenet_connections_t *col) +static void NET_DTLS_Timeouts(ftenet_connections_t *col) { struct dtlspeer_s *peer, **link; if (!col) @@ -3085,7 +3174,7 @@ static neterr_t FTENET_DTLS_DoSendPacket(void *cbctx, const qbyte *data, size_t struct dtlspeer_s *peer = cbctx; return NET_SendPacketCol(peer->col, length, data, &peer->addr); } -qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred_t *cred) +qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred_t *cred, qboolean outgoing) { extern cvar_t timeout; struct dtlspeer_s *peer; @@ -3102,12 +3191,12 @@ qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred peer->addr = *to; peer->col = col; - if (col->islisten) - peer->funcs = DTLS_InitServer(); - else + if (outgoing) peer->funcs = DTLS_InitClient(); + else + peer->funcs = DTLS_InitServer(); if (peer->funcs) - peer->dtlsstate = peer->funcs->CreateContext(cred, peer, FTENET_DTLS_DoSendPacket, col->islisten); + peer->dtlsstate = peer->funcs->CreateContext(cred, peer, FTENET_DTLS_DoSendPacket, !outgoing); peer->timeout = realtime+timeout.value; if (peer->dtlsstate) @@ -3124,6 +3213,8 @@ qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred peer = NULL; } } + else + peer->timeout = realtime+timeout.value; return peer!=NULL; } #ifdef HAVE_SERVER @@ -3146,7 +3237,7 @@ static void FTENET_DTLS_Established(void **ctx, void *state) } qboolean NET_DTLS_CheckInbound(ftenet_connections_t *col) { - extern cvar_t timeout, net_enable_dtls; + extern cvar_t timeout; struct dtlspeer_s *peer; netadr_t *from = &net_from; if (from->prot != NP_DGRAM || !net_enable_dtls.ival || !col->dtlsfuncs) @@ -3183,6 +3274,16 @@ static void NET_DTLS_DisconnectPeer(ftenet_connections_t *col, struct dtlspeer_s peer->funcs->DestroyContext(peer->dtlsstate); Z_Free(peer); } +static struct dtlspeer_s *FTENET_DTLS_FindPeer(ftenet_connections_t *col, netadr_t *to) +{ + struct dtlspeer_s *peer; + for (peer = col->dtls; peer; peer = peer->next) + { + if (NET_CompareAdr(&peer->addr, to)) + break; + } + return peer; +} qboolean NET_DTLS_Disconnect(ftenet_connections_t *col, netadr_t *to) { struct dtlspeer_s *peer; @@ -3190,25 +3291,17 @@ qboolean NET_DTLS_Disconnect(ftenet_connections_t *col, netadr_t *to) if (!col || (to->prot != NP_DGRAM && to->prot != NP_DTLS)) return false; n.prot = NP_DGRAM; - for (peer = col->dtls; peer; peer = peer->next) - { - if (NET_CompareAdr(&peer->addr, &n)) - { - NET_DTLS_DisconnectPeer(col, peer); - break; - } - } - return peer?true:false; + peer = FTENET_DTLS_FindPeer(col, &n); + if (!peer) + return false; + NET_DTLS_DisconnectPeer(col, peer); + return true; } static neterr_t FTENET_DTLS_SendPacket(ftenet_connections_t *col, int length, const void *data, netadr_t *to) { struct dtlspeer_s *peer; to->prot = NP_DGRAM; - for (peer = col->dtls; peer; peer = peer->next) - { - if (NET_CompareAdr(&peer->addr, to)) - break; - } + peer = FTENET_DTLS_FindPeer(col, to); to->prot = NP_DTLS; if (peer) return peer->funcs->Transmit(peer->dtlsstate, data, length); @@ -3219,69 +3312,64 @@ static neterr_t FTENET_DTLS_SendPacket(ftenet_connections_t *col, int length, co qboolean NET_DTLS_Decode(ftenet_connections_t *col) { extern cvar_t timeout; - struct dtlspeer_s *peer; - for (peer = col->dtls; peer; peer = peer->next) + struct dtlspeer_s *peer = FTENET_DTLS_FindPeer(col, &net_from); + if (peer) { - if (NET_CompareAdr(&peer->addr, &net_from)) + peer->timeout = realtime+timeout.value; //refresh the timeout if our peer is still alive. + switch(peer->funcs->Received(peer->dtlsstate, &net_message)) { - peer->timeout = realtime+timeout.value; //refresh the timeout if our peer is still alive. - switch(peer->funcs->Received(peer->dtlsstate, &net_message)) - { - case NETERR_DISCONNECTED: + case NETERR_DISCONNECTED: + if (col->islisten) NET_DTLS_DisconnectPeer(col, peer); - net_message.cursize = 0; - break; - case NETERR_NOROUTE: - return false; //not a valid dtls packet. - default: - case NETERR_CLOGGED: - //ate it - net_message.cursize = 0; - break; - case NETERR_SENT: - //we decoded it properly - net_from.prot = NP_DTLS; - break; - } + net_message.cursize = 0; + break; + case NETERR_NOROUTE: + return false; //not a valid dtls packet. + default: + case NETERR_CLOGGED: + //ate it + net_message.cursize = 0; + break; + case NETERR_SENT: + //we decoded it properly net_from.prot = NP_DTLS; - return true; + break; } + net_from.prot = NP_DTLS; + return true; } return false; } #endif -size_t NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize) +int NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize) { if (!col) - return 0; + return -1; - switch(prop) - { - default: - break; - case QCERT_PEERFINGERPRINT: -#if 0//def HAVE_DTLS - if (a->prot == NP_DTLS) - { - struct dtlspeer_s *peer; - { - a->prot = NP_DGRAM; - for (peer = col->dtls; peer; peer = peer->next) - { - if (NET_CompareAdr(&peer->addr, a)) - break; - } - a->prot = NP_DTLS; - } - if (peer) - return peer->funcs->GetPeerCertificate(peer->dtlsstate, data, length); - } +#ifdef SUPPORT_ICE + if (a->type == NA_ICE) + return ICE_GetPeerCertificate(a, prop, out, outsize); #endif - return 0; +#ifdef HAVE_DTLS + if (a->prot == NP_DTLS) + { + struct dtlspeer_s *peer; + { + a->prot = NP_DGRAM; + for (peer = col->dtls; peer; peer = peer->next) + { + if (NET_CompareAdr(&peer->addr, a)) + break; + } + a->prot = NP_DTLS; + } + if (peer && peer->funcs->GetPeerCertificate) + return peer->funcs->GetPeerCertificate(peer->dtlsstate, prop, out, outsize); } - return 0; +#endif + return -1; } @@ -3867,17 +3955,17 @@ neterr_t FTENET_Datagram_SendPacket(ftenet_generic_connection_t *con, int length { char adr[256]; if (ecode==NET_ENETUNREACH&&to->type==NA_IPV6) //ipv6 support STILL sucks too much. don't spam non-developers, its just annoying. - Con_DPrintf("NET_SendPacket(%s) Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); + Con_DPrintf(S_COLOR_GRAY"%s - Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); #ifdef HAVE_CLIENT else if (ecode == NET_EADDRNOTAVAIL || (ecode==NET_ENETUNREACH&&to->type==NA_IPV6)) - Con_DPrintf("NET_SendPacket(%s) Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); + Con_DPrintf(S_COLOR_GRAY"%s - Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); else #endif { #ifdef _WIN32 - Con_Printf ("NET_SendPacket(%s) ERROR: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); + Con_Printf (S_COLOR_GRAY"%s - ERROR: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); #else - Con_Printf ("NET_SendPacket(%s) ERROR: %s\n", NET_AdrToString (adr, sizeof(adr), to), strerror(ecode)); + Con_Printf (S_COLOR_GRAY"%s - ERROR: %s\n", NET_AdrToString (adr, sizeof(adr), to), strerror(ecode)); #endif } } @@ -4146,7 +4234,7 @@ ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(ftenet_connecti } if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) - Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(neterrno())); + Sys_Error ("FTENET_Datagram_EstablishConnection: ioctl FIONBIO: %s", strerror(neterrno())); //ipv6 sockets need to add themselves to a multicast group, so that we can receive broadcasts on a lan #if defined(HAVE_IPV6) @@ -4262,10 +4350,19 @@ typedef struct ftenet_tcp_stream_s { struct { char resource[64]; - int clientnum; + int clientnum; //low number slots + unsigned int clientseq; //something less reuse-y #ifdef SUPPORT_RTC_ICE struct icestate_s *ice; #endif + //for brokering with a udp-based server. + netadr_t target; + int sends; + char *offer; + char *candidates; + float resendtime; + int candack; //number of received candidates (to avoid dupes) + int outcand; //number of sent candidates (to avoid redundant resends) } webrtc; #endif } ftenet_tcp_stream_t; @@ -4559,9 +4656,19 @@ qboolean FTENET_TCP_HTTPResponse(ftenet_tcp_stream_t *st, httparg_t arg[WCATTR_C body = NULL; } } +#else + else if (!strcmp(name, "favicon.ico")) + { +// Con_Printf("Redirect %s to %s (copyrighted)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + resp = va( "HTTP/1.1 302 Found\r\n" + "Location: https://fteqw.org/%s\r\n" + "Content-Type: text/html\r\n" + , "favicon.png"); + body = NULL; + } #endif -#if defined(SV_MASTER) && !defined(HAVE_SERVER) - else if ((st->dlfile=SVM_GenerateIndex(arg[WCATTR_HOST], name, &filetype, query))) +#if defined(SV_MASTER) + else if (svm_sockets && st->con->generic.owner==svm_sockets && (st->dlfile=SVM_GenerateIndex(arg[WCATTR_HOST], name, &filetype, query))) ; #endif #ifdef HAVE_SERVER @@ -4850,7 +4957,7 @@ qboolean FTENET_TCP_HTTPResponse(ftenet_tcp_stream_t *st, httparg_t arg[WCATTR_C void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_stream_t *client, ftenet_tcp_stream_t *server) { - qbyte buffer[3]; + qbyte buffer[256]; int trynext = 0; ftenet_tcp_stream_t *o; if (client->webrtc.clientnum < 0) @@ -4869,12 +4976,16 @@ void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_strea if (server) { //and tell them both, if the server is actually up + int o = client->remoteaddr.prot; buffer[0] = ICEMSG_NEWPEER; buffer[1] = (client->webrtc.clientnum>>0)&0xff; buffer[2] = (client->webrtc.clientnum>>8)&0xff; // buffer[3] = (client->webrtc.clientnum>>16)&0xff; // buffer[4] = (client->webrtc.clientnum>>24)&0xff; - FTENET_TCP_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, 3); + client->remoteaddr.prot = 0; + NET_BaseAdrToString(buffer+3, sizeof(buffer)-3, &client->remoteaddr); //let the server know who's trying to connect to them. for ip bans. + client->remoteaddr.prot = o; + FTENET_TCP_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, 3+strlen(buffer+3)); buffer[0] = ICEMSG_NEWPEER; buffer[1] = 0xff; @@ -4885,7 +4996,7 @@ void FTENET_TCP_WebRTCServerAssigned(ftenet_tcp_stream_t *list, ftenet_tcp_strea } } -qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) +static const char *FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) { char *resp; char adr[256]; @@ -4907,7 +5018,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - return false; + return "websockets disabled"; } for (i = 0; i < WCATTR_COUNT; i++) @@ -4922,7 +5033,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - return false; //overflow... + return "overflow"; //overflow... } if (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t' || st->inbuffer[i] == '\r') { @@ -4945,7 +5056,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st if (st->inbuffer[i] < ' ' && st->inbuffer[i] != '\t') { Con_Printf("http request contained control codes\n"); - return false; + return "bad char"; } arg[attr][alen++] = st->inbuffer[i]; } @@ -5074,7 +5185,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st else { /*just a word on the line on its own. that would be invalid in http*/ - return false; + return "bad header"; } } } @@ -5082,7 +5193,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st if (!headerscomplete) { Con_DPrintf("http header parsing failed\n"); - return false; //the caller said it was complete! something's fucked if we're here + return "bad header"; //the caller said it was complete! something's fucked if we're here } //okay, the above code parsed all the headers that we care about. @@ -5095,10 +5206,10 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - Con_Printf("http oversize request\n"); - return false; //can never be completed. + Con_DPrintf("http oversize request\n"); + return "bad header"; //can never be completed. } - return true; + return NULL; } //clients uploading chunked stuff is bad/unsupported. @@ -5108,8 +5219,8 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - Con_Printf("http encoded request\n"); - return false; //can't handle the request, so discard the connection + Con_DPrintf("unsupported http encoded request\n"); + return "unsupported"; //can't handle the request, so discard the connection } memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); @@ -5139,12 +5250,12 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st if (cltype == TCPC_WEBRTC_CLIENT||cltype==TCPC_WEBRTC_HOST) { if (!net_enable_rtcbroker.ival) - return false; + return "broker disabled"; } else //TCPC_WEBSOCKETNQ, TCPC_WEBSOCKETB, TCPC_WEBSOCKETU { if (!net_enable_websockets.ival) - return false; + return "websocket clients disabled"; } if (websocketver != 13) @@ -5156,7 +5267,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored "\r\n"); VFS_WRITE(st->clientstream, resp, strlen(resp)); - return false; + return "bad header"; } else { @@ -5165,6 +5276,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st unsigned char sha1digest[20]; char *blurgh; char *protoname = ""; + static int clientseq; blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]); tobase64(acceptkey, sizeof(acceptkey), sha1digest, CalcHash(&hash_sha1, sha1digest, sizeof(sha1digest), blurgh, strlen(blurgh))); @@ -5200,6 +5312,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st else Q_strncpyz(st->webrtc.resource, arg[WCATTR_URL], sizeof(st->webrtc.resource)); st->webrtc.clientnum = -1; + st->webrtc.clientseq = clientseq++; #ifndef SUPPORT_RTC_ICE if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) failreason = "client did not specify resource type"; @@ -5208,7 +5321,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st if (failreason) { Con_DPrintf("Websocket(%s) request for %s from %s - %s\n", arg[WCATTR_WSPROTO], arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), failreason); - return false; + return failreason; } Con_DPrintf("Websocket request for %s from %s (%s)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), arg[WCATTR_WSPROTO]); @@ -5222,23 +5335,19 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st //send the websocket handshake response. VFS_WRITE(st->clientstream, resp, strlen(resp)); - if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) - { //client should be connected to us rather than any impostors. tell it to start its ICE handshake. - net_message_buffer[0] = ICEMSG_NEWPEER; - net_message_buffer[1] = 0xff; - net_message_buffer[2] = 0xff; - FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); - } - else if (st->clienttype == TCPC_WEBRTC_HOST || st->clienttype == TCPC_WEBRTC_CLIENT) + if (st->clienttype == TCPC_WEBRTC_HOST || st->clienttype == TCPC_WEBRTC_CLIENT) { ftenet_tcp_stream_t *o; + + //split the requested resource by protocol/room + char *idstart = strchr(st->webrtc.resource, '/'); + if (!idstart++) + { //MUST have a protocol name + return "no game protocol specified"; + } + if (st->clienttype == TCPC_WEBRTC_HOST) - { //if its a server, then let it know its final resource name - char *idstart = strchr(st->webrtc.resource, '/'); - if (!idstart++) - { //MUST have a protocol name - return false; - } + { if (!*idstart) { //webrtc servers need some unique resource address. lets use their ip+port for now. we should probably be randomising this static unsigned int g; @@ -5255,7 +5364,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); *st->webrtc.resource = 0; //don't trigger shutdown broadcasts to valid clients. - return false; //conflict! can't have two servers listening on the same url + return "room conflict"; //conflict! can't have two servers listening on the same url } } @@ -5277,14 +5386,65 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st #endif } else - { //find its server, if we can - for (o = con->tcpstreams; o; o = o->next) - { - if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(st->webrtc.resource, o->webrtc.resource)) - break; + { //a client looking for a server... + if (!*idstart) + { //that's trying to connect to us... +#ifdef HAVE_SERVER + if (sv.state != ss_dead) + { + net_message_buffer[0] = ICEMSG_NEWPEER; + net_message_buffer[1] = 0xff; + net_message_buffer[2] = 0xff; + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); + } + else +#endif + return "no local server"; //not running a server. can't honour it. + } + else if (!strncmp(idstart, "udp/", 4)) + { //we don't use StringToAdr to avoid dns lookup stalls (denial of service attacks). should at least work for server browsers. +#if defined(HAVE_DTLS) && defined(SV_MASTER) + struct dtlspeercred_s cred={NULL}; + if (net_enable_dtls.ival) + { + if (!NET_StringToAdr_NoDNS(idstart+4, PORT_QWCLIENT, &st->webrtc.target)) + return "bad target address (names are disallowed)"; +// if (NET_ClassifyAddress(&st->webrtc.target, NULL) <= ASCOPE_LAN) +// return false; //block addresses on the broker's lan. we use non-standard protocols so this should not cause problems while being useful for custom deployments. + +#ifdef SV_MASTER + if (con->generic.owner == svm_sockets) + if (!SVM_FixupServerAddress(&st->webrtc.target, &cred)) + return "target not registered"; //we don't know about this server... + //if the specified credentials, reject the connection if different. +#endif + + //use dtls to contact the server. + if (st->webrtc.target.prot == NP_DGRAM) + st->webrtc.target.prot = NP_DTLS; + if (st->webrtc.target.prot == NP_DTLS) //don't make expensive tcp connections! + NET_EnsureRoute(con->generic.owner, NULL, &cred, &st->webrtc.target, true); + + //we'll sythesise some rdp when we get an offer. + net_message_buffer[0] = ICEMSG_NEWPEER; + net_message_buffer[1] = 0xff; + net_message_buffer[2] = 0xff; + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); + } + else +#endif + return "rdp-via-udp is not enabled on this broker"; + } + else + { //find its server, if we can + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(st->webrtc.resource, o->webrtc.resource)) + break; + } + //and assign it to this client + FTENET_TCP_WebRTCServerAssigned(con->tcpstreams, st, o); } - //and assign it to this client - FTENET_TCP_WebRTCServerAssigned(con->tcpstreams, st, o); } } @@ -5305,14 +5465,17 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st MSG_WriteByte(&net_message, NQ_NETCHAN_VERSION); con->generic.owner->ReadGamePacket(); } - return true; + return NULL; } } else { if (!net_enable_http.ival) - return false; - return FTENET_TCP_HTTPResponse(st, arg, acceptsgzip); + return "http disabled"; + if (FTENET_TCP_HTTPResponse(st, arg, acceptsgzip)) + return NULL; + else + return "http error"; } } #endif @@ -5365,8 +5528,22 @@ void FTENET_TCP_PrintStatus(ftenet_generic_connection_t *gcon) } } -static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) +static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st, const char *reason) { //some sort of error. kill the connection info (will be cleaned up later) + +#ifdef HAVE_HTTPSV + if (st->clienttype == TCPC_WEBRTC_CLIENT && st->clientstream && !strcmp(st->webrtc.resource, st->webrtc.resource)) + { + qbyte msg[256]; + msg[0] = ICEMSG_PEERLOST; + msg[1] = 0xff; + msg[2] = 0xff; + Q_strncpyz(msg+3, reason, sizeof(msg)-3); + + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, msg, 3+strlen(msg+3)); + } +#endif + #ifdef HAVE_EPOLL if (st->socketnum != INVALID_SOCKET) epoll_ctl(epoll_fd, EPOLL_CTL_DEL, st->socketnum, NULL); @@ -5374,6 +5551,7 @@ static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_s if (st->clientstream) VFS_CLOSE(st->clientstream); st->clientstream = NULL; + st->socketnum = INVALID_SOCKET; if (st->dlfile) VFS_CLOSE(st->dlfile); @@ -5386,12 +5564,13 @@ static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_s { if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(o->webrtc.resource, st->webrtc.resource)) { - qbyte msg[3]; + qbyte msg[256]; msg[0] = ICEMSG_PEERLOST; msg[1] = (st->webrtc.clientnum>>0)&0xff; msg[2] = (st->webrtc.clientnum>>8)&0xff; + Q_strncpyz(msg+3, reason, sizeof(msg)-3); - FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3); + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3+strlen(msg+3)); break; //should only be one. } } @@ -5403,12 +5582,13 @@ static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_s { if (o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(o->webrtc.resource, st->webrtc.resource)) { - qbyte msg[3]; + qbyte msg[256]; msg[0] = ICEMSG_PEERLOST; msg[1] = (st->webrtc.clientnum>>0)&0xff; msg[2] = (st->webrtc.clientnum>>8)&0xff; + Q_strncpyz(msg+3, reason, sizeof(msg)-3); - FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3); + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3+strlen(msg+3)); } } #ifdef SV_MASTER @@ -5439,12 +5619,13 @@ static void FTENET_TCP_Flush(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t * }*/ } } -//returns true if we read a game packet (should re-call in this case. -static enum{ - FTETCP_DONE, FTETCP_KILL, FTETCP_RETRY -} FTENET_TCP_ReadStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) +//returns a string for why it was killed. or NULL for nothing more to do. +static const char *FTENET_TCP_ReadStream(ftenet_tcp_connection_t *con, ftenet_tcp_stream_t *st) { char adr[MAX_ADR_SIZE]; +restart: //gotos are evil. I am evil. live with it. + if (!st->clientstream) + return NULL; if (st->inlen < sizeof(st->inbuffer)-1) { int ret = VFS_READ(st->clientstream, st->inbuffer+st->inlen, sizeof(st->inbuffer)-1-st->inlen); @@ -5452,7 +5633,7 @@ static enum{ { st->outlen = 0; //don't flush, no point. Con_DPrintf ("tcp peer %s closed connection\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "connection lost"; } st->inlen += ret; } @@ -5461,7 +5642,7 @@ static enum{ { case TCPC_UNKNOWN: if (st->inlen < 6) - return FTETCP_DONE; + return NULL; //so TLS apparently uses a first byte that is always < 64. which is handy to know. if (con->generic.islisten && st->remoteaddr.prot == NP_STREAM && st->clientstream && !((st->inbuffer[0] >= 'a' && st->inbuffer[0] <= 'z') || (st->inbuffer[0] >= 'A' && st->inbuffer[0] <= 'Z'))) @@ -5474,7 +5655,7 @@ static enum{ vfsfile_t *stream = st->clientstream; int (QDECL *realread) (struct vfsfile_s *file, void *buffer, int bytestoread); if (st->inlen > sizeof(net_message_buffer)) - return FTETCP_KILL; //would cause data loss... + return "oversize"; //would cause data loss... realread = stream->ReadBytes; stream->ReadBytes = TLSPromoteRead; memcpy(net_message_buffer, st->inbuffer, st->inlen); @@ -5489,7 +5670,7 @@ static enum{ if (st->inlen < 0) { //okay, something failed... st->inlen = 0; - return FTETCP_KILL; + return "error"; } else { @@ -5498,13 +5679,13 @@ static enum{ } } if (!st->clientstream || net_message.cursize) - return FTETCP_KILL; //failure, or it didn't read all the data that we buffered for it (error instead of forgetting it). + return "tls error"; //failure, or it didn't read all the data that we buffered for it (error instead of forgetting it). if (developer.ival) Con_Printf("promoted peer to tls: %s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); - return FTETCP_RETRY; //might be a usable packet in there that we now need to make sense of. + goto restart; //might be a usable packet in there that we now need to make sense of. } #endif - return FTETCP_KILL; + return "no tls"; } //check if its a qizmo connection (or rather a general qw-over-tcp connection) @@ -5523,11 +5704,11 @@ static enum{ { //send the qizmo handshake response. if (VFS_WRITE(st->clientstream, "qizmo\n", 6) != 6) - return FTETCP_KILL; //unable to write for some reason. + return "write error"; //unable to write for some reason. } - return FTETCP_DONE; + return NULL; } - return FTETCP_KILL; //not enabled. + return "net_enable_qizmo"; //not enabled. } //check if we have some http-like protocol with a header that ends with two trailing new lines (carrage returns optional, at least here) @@ -5573,7 +5754,7 @@ static enum{ //now try to pass it over MSV_NewNetworkedNode(st->clientstream, st->inbuffer, st->inbuffer+i, st->inlen-i, NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); st->clientstream = NULL; //qtv code took it. - return FTETCP_KILL; + return "node linked"; } else #endif @@ -5594,11 +5775,11 @@ static enum{ { case -2: VFS_PUTS(st->clientstream, "QTVSV 1\n" "PERROR: net_enable_qtv is disabled on this server\n\n"); - return FTETCP_KILL; + return "net_enable_qtv disabled"; case -1: //error - return FTETCP_KILL; + return "error"; case 0: //retry - return FTETCP_DONE; + return NULL; case 1: //accepted #ifdef HAVE_EPOLL //the tcp connection will now be handled by the dedicated qtv code rather than us. @@ -5608,19 +5789,21 @@ static enum{ st->epoll.Polled = NULL; #endif st->clientstream = NULL; //qtv code took it. - return FTETCP_KILL; + return "qtv client"; } } else #endif { #ifdef HAVE_HTTPSV - if (FTENET_TCP_ParseHTTPRequest(con, st)) - return FTETCP_RETRY; + const char *err = FTENET_TCP_ParseHTTPRequest(con, st); + if (!err) + goto restart; + return err; #else Con_DPrintf ("Unknown TCP handshake from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + return "unsupported handshake"; #endif - return FTETCP_KILL; } } else @@ -5628,11 +5811,11 @@ static enum{ //they splurged too much data and we don't even know what they were //either way we're expecting a request header in our buffer that can never be completed if (st->inlen >= sizeof(st->inbuffer)-1) - return FTETCP_KILL; + return "unknown"; } } - return FTETCP_DONE; + return NULL; #ifdef HAVE_HTTPSV case TCPC_HTTPCLIENT: /*try and keep it flushed*/ @@ -5652,25 +5835,25 @@ static enum{ st->clienttype = TCPC_UNKNOWN; //wait for the next request (could potentially be a websocket connection) Con_DPrintf ("Outgoing file transfer complete\n"); if (st->httpstate.connection_close) - return FTETCP_KILL; + return "complete"; } FTENET_TCP_Flush(con, st); } - return FTETCP_DONE; + return NULL; #endif case TCPC_QIZMO: if (st->inlen < 2) - return FTETCP_DONE; + return NULL; net_message.cursize = BigShort(*(short*)st->inbuffer); if (net_message.cursize >= sizeof(net_message_buffer) ) { Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "oversize"; } if (net_message.cursize+2 > st->inlen) { //not enough buffered to read a packet out of it. - return FTETCP_DONE; + return NULL; } memcpy(net_message_buffer, st->inbuffer+2, net_message.cursize); @@ -5684,7 +5867,7 @@ static enum{ net_from_connection = &con->generic; con->generic.owner->ReadGamePacket(); - return FTETCP_RETRY; + goto restart; #ifdef HAVE_HTTPSV case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: @@ -5701,7 +5884,7 @@ static enum{ if (ctrl & 0x7000) { Con_Printf ("%s: reserved bits set\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "reserved"; } if ((ctrl & 0x7f) == 127) { @@ -5710,7 +5893,7 @@ static enum{ if (sizeof(ullpaylen) < 8) { Con_Printf ("%s: payload frame too large\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "oversize"; } else { @@ -5728,12 +5911,12 @@ static enum{ if (ullpaylen < 0x10000) { Con_Printf ("%s: payload size (%"PRIu64") encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); - return FTETCP_KILL; + return "corrupt"; } if (ullpaylen > 0x40000) { Con_Printf ("%s: payload size (%"PRIu64") is abusive\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); - return FTETCP_KILL; + return "oversize"; } paylen = ullpaylen; payoffs += 8; @@ -5749,7 +5932,7 @@ static enum{ if (paylen < 126) { Con_Printf ("%s: payload size encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "corrupt"; } payoffs += 2; } @@ -5774,7 +5957,7 @@ static enum{ if (payoffs + paylen >= sizeof(st->inbuffer)-1) { Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "oversize"; } break; } @@ -5794,7 +5977,7 @@ static enum{ { case WS_PACKETTYPE_CONTINUATION: /*continuation*/ Con_Printf ("websocket continuation frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; //can't handle these. + return "unsupported"; //can't handle these. case WS_PACKETTYPE_TEXTFRAME: /*text frame*/ // Con_Printf ("websocket text frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); { @@ -5835,7 +6018,7 @@ static enum{ if (net_message.cursize+8 >= sizeof(net_message_buffer) ) { Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "oversize"; } #ifdef SUPPORT_RTC_ICE if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) @@ -5868,21 +6051,42 @@ static enum{ } else if ((st->clienttype == TCPC_WEBRTC_CLIENT || st->clienttype == TCPC_WEBRTC_HOST) && paylen >= 3) { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. - ftenet_tcp_stream_t *o; - short clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); - int type = (st->clienttype != TCPC_WEBRTC_CLIENT)?TCPC_WEBRTC_CLIENT:TCPC_WEBRTC_HOST; - for (o = con->tcpstreams; o; o = o->next) - { - if (o->clienttype == type && clnum == o->webrtc.clientnum && !strcmp(o->webrtc.resource, st->webrtc.resource)) + if (st->webrtc.target.type != NA_INVALID && st->clienttype==TCPC_WEBRTC_CLIENT) + { //if the server is a udp one, we need to buffer some stuff to handle resends over a dtls connection that still has to be established. + if (st->inbuffer[payoffs] == ICEMSG_OFFER) { - st->inbuffer[payoffs+1] = (st->webrtc.clientnum>>0)&0xff; - st->inbuffer[payoffs+2] = (st->webrtc.clientnum>>8)&0xff; - FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, st->inbuffer+payoffs, paylen); - break; + BZ_Free(st->webrtc.offer); + st->webrtc.offer = BZF_Malloc(paylen-3+1); + memcpy(st->webrtc.offer, st->inbuffer+payoffs+3, paylen-3); + st->webrtc.offer[paylen-3] = 0; + st->webrtc.resendtime = FLT_MIN; + } + else if (st->inbuffer[payoffs] == ICEMSG_CANDIDATE && paylen > 3) + { + Z_StrCatLen(&st->webrtc.candidates, st->inbuffer+payoffs+3, paylen-3); + if ((st->inbuffer+payoffs+3)[paylen-3-1]!= '\n') + Z_StrCat(&st->webrtc.candidates, "\n"); + st->webrtc.resendtime = FLT_MIN; } } - if (!o) - Con_DPrintf("Unable to relay\n"); + else + { //forward it to the other side. much easier with tcp. + ftenet_tcp_stream_t *o; + short clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); + int type = (st->clienttype != TCPC_WEBRTC_CLIENT)?TCPC_WEBRTC_CLIENT:TCPC_WEBRTC_HOST; + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == type && clnum == o->webrtc.clientnum && !strcmp(o->webrtc.resource, st->webrtc.resource)) + { + st->inbuffer[payoffs+1] = (st->webrtc.clientnum>>0)&0xff; + st->inbuffer[payoffs+2] = (st->webrtc.clientnum>>8)&0xff; + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, st->inbuffer+payoffs, paylen); + break; + } + } + if (!o) + Con_DPrintf("Unable to relay\n"); + } net_message.cursize = 0; } else @@ -5902,11 +6106,11 @@ static enum{ break; case WS_PACKETTYPE_CLOSE: /*connection close*/ Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "drop"; //they're about to drop anyway. case WS_PACKETTYPE_PING: /*ping*/ // Con_Printf ("websocket ping from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); if (FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_PONG, st->inbuffer+payoffs, paylen) != NETERR_SENT) - return FTETCP_KILL; + return "write error"; break; case WS_PACKETTYPE_PONG: /*pong*/ st->timeouttime = Sys_DoubleTime() + 30; @@ -5915,7 +6119,7 @@ static enum{ break; default: Con_Printf ("Unsupported websocket opcode (%i) from %s\n", (ctrl>>8) & 0xf, NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - return FTETCP_KILL; + return "unsupported"; } memmove(st->inbuffer, st->inbuffer+payoffs + paylen, st->inlen - (payoffs + paylen)); @@ -5929,41 +6133,124 @@ static enum{ net_from.connum = con->generic.connum; net_from_connection = &con->generic; con->generic.owner->ReadGamePacket(); - return FTETCP_RETRY; + goto restart; } } - return FTETCP_DONE; + return NULL; #endif } - return FTETCP_DONE; + return NULL; } +#ifdef SV_MASTER +static qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon); +void FTENET_TCP_ICEResponse(ftenet_connections_t *col, int type, const char *cid, const char *sdp) +{ + unsigned int id, connum; + ftenet_tcp_connection_t *con; + ftenet_tcp_stream_t *o; + + cid = COM_Parse(cid); + id = strtoul(com_token, NULL, 16); + connum = ((id>>16)&0xffff)-1; + id=(short)(id&0xffff); + if (connum >= countof(col->conn) || !col->conn[connum] || col->conn[connum]->GetPacket != FTENET_TCP_GetPacket) + return; + con = (ftenet_tcp_connection_t*)col->conn[connum]; + + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype != TCPC_WEBRTC_CLIENT || o->webrtc.target.type==NA_INVALID || o->webrtc.clientnum != id) + continue; + if (NET_CompareAdr(&net_from, &o->webrtc.target)) + { + char msg[1400]; + Z_Free(o->webrtc.offer); //we got the answer. can stop trying to spam it now. + o->webrtc.offer = NULL; + + if (type == ICEMSG_CANDIDATE) + { + const char *nc = o->webrtc.candidates; + char seq, ack; + cid = COM_Parse(cid); + seq = atoi(com_token); + cid = COM_Parse(cid); + ack = atoi(com_token); + + while (nc && o->webrtc.outcand < ack) + { //server saw one of our candidate lines. stop respamming that one. + o->webrtc.outcand++; + + for (; *nc && *nc != '\n'; nc++) + ; + if (*nc != '\n') + break; //getting exploited... + nc++; //skip the nl + } + if (!nc || !*nc) + { + Z_Free(o->webrtc.candidates); //all acked... + o->webrtc.candidates = NULL; + } + else if (nc != o->webrtc.candidates) + memmove(o->webrtc.candidates, nc, strlen(nc)+1); + o->webrtc.outcand = ack; //in case the server just wanted to drop some. + + while(*sdp) + { + for (nc = sdp; *nc && *nc != '\n'; nc++) + ; + if (*nc != '\n') + break; //getting exploited... + nc++; //skip the nl. + if (seq++ < o->webrtc.candack) + ; //already saw this line + else + { //new. yay reliables... + msg[0] = type; + msg[1] = 0xff; + msg[2] = 0xff; + memcpy(msg+3, sdp, nc-sdp); + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3+nc-sdp); + o->webrtc.candack=seq; + } + + sdp = nc; + } + } + else + { + msg[0] = type; + msg[1] = 0xff; + msg[2] = 0xff; + Q_strncpyz(msg+3, sdp, sizeof(msg)); + FTENET_TCP_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, msg, 3+strlen(msg+3)); + } + break; + } + } +} +#endif + #ifdef HAVE_EPOLL static void FTENET_TCP_Polled(epollctx_t *ctx, unsigned int events) { ftenet_tcp_stream_t *st = NULL; + const char *err; st = (ftenet_tcp_stream_t *)((qbyte*)ctx - ((qbyte*)&st->epoll-(qbyte*)st)); - for(;st->clientstream;) - { - switch(FTENET_TCP_ReadStream(st->con, st)) - { - case FTETCP_RETRY: - continue; - case FTETCP_KILL: - FTENET_TCP_KillStream(st->con, st); - return; - case FTETCP_DONE: - FTENET_TCP_Flush(st->con, st); - return; - } - } + + err = FTENET_TCP_ReadStream(st->con, st); + if (err) + FTENET_TCP_KillStream(st->con, st, err); + else + FTENET_TCP_Flush(st->con, st); } #endif -qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) +static qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) { ftenet_tcp_connection_t *con = (ftenet_tcp_connection_t*)gcon; - int ret; +// int ret; char adr[MAX_ADR_SIZE]; struct sockaddr_qstorage from; int fromlen; @@ -5999,14 +6286,13 @@ qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) } //due to the above checks about invalid sockets, the socket is always open for st below. - if (st->timeouttime < timeval) { #ifdef HAVE_HTTPSV if (!st->pinging && (st->clienttype==TCPC_WEBRTC_CLIENT||st->clienttype==TCPC_WEBRTC_HOST) && *st->webrtc.resource) { //ping broker clients. there usually shouldn't be any data flow to keep it active otherwise. st->timeouttime = timeval + 30; - st->pinging = true; + st->pinging = true; //cleared on ack. FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_PING, "ping", 4); } @@ -6014,21 +6300,95 @@ qboolean FTENET_TCP_GetPacket(ftenet_generic_connection_t *gcon) #endif { Con_DPrintf ("tcp peer %s timed out\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - FTENET_TCP_KillStream(con, st); + FTENET_TCP_KillStream(con, st, "timeout"); continue; } } for(;st->clientstream;) { - ret = FTENET_TCP_ReadStream(con, st); - if (ret == FTETCP_RETRY) - continue; - else if (ret == FTETCP_KILL) - FTENET_TCP_KillStream(con, st); + const char *err = FTENET_TCP_ReadStream(con, st); + if (err) + FTENET_TCP_KillStream(con, st, err); break; } FTENET_TCP_Flush(con, st); + +#ifdef HAVE_HTTPSV + if (st->clienttype==TCPC_WEBRTC_CLIENT && st->webrtc.target.type!=NA_INVALID && st->webrtc.resendtime < timeval && st->clientstream) + { + if (st->webrtc.offer) + { + static struct netprim_s prim={0}; + sizebuf_t msg; + char adr[256]; + netproto_t o = st->remoteaddr.prot; + + if (st->webrtc.sends > 5) + { + if (st->webrtc.sends > 6) + { + FTENET_TCP_KillStream(con, st, "Too many resends"); + continue; + } + st->webrtc.resendtime = timeval + 3; + st->webrtc.sends++; + continue; + } + + st->remoteaddr.prot = 0; //no prefixes! + NET_BaseAdrToString(adr, sizeof(adr), &st->remoteaddr); //let the server know who's trying to connect to them. for ip bans. + st->remoteaddr.prot = o; + + MSG_BeginWriting(&msg, prim, net_message_buffer, sizeof(net_message_buffer)); + MSG_WriteLong(&msg, ~0); + MSG_WriteString(&msg, va("ice_offer %s %x:%x", adr, (con->generic.connum<<16)|(quint16_t)st->webrtc.clientnum, st->webrtc.clientseq)); + MSG_WriteString(&msg, st->webrtc.offer); + safeswitch (NET_SendPacket(con->generic.owner, msg.cursize, msg.data, &st->webrtc.target)) + { + case NETERR_CLOGGED: //dtls still connecting, or just unable to send... + break; //don't update resend timer.. + case NETERR_SENT: + st->webrtc.sends++; + st->webrtc.resendtime = timeval + 1; + break; + case NETERR_NOROUTE: + case NETERR_DISCONNECTED: + case NETERR_MTU: + safedefault: + FTENET_TCP_KillStream(con, st, "target not reachable"); + break; + } + } + else if (st->webrtc.candidates) + { //hopefully the server will have a proper public address and so won't need this... but just in case... + static struct netprim_s prim={0}; + sizebuf_t msg; + + MSG_BeginWriting(&msg, prim, net_message_buffer, sizeof(net_message_buffer)); + MSG_WriteLong(&msg, ~0); + MSG_WriteString(&msg, va("ice_ccand %x:%x %i %i", (con->generic.connum<<16)|(quint16_t)st->webrtc.clientnum, st->webrtc.clientseq, st->webrtc.outcand, st->webrtc.candack)); + MSG_WriteString(&msg, st->webrtc.candidates); + safeswitch (NET_SendPacket(con->generic.owner, msg.cursize, msg.data, &st->webrtc.target)) + { + case NETERR_CLOGGED: //unable to send... + break; //don't update resend timer so we don't lose too much time + case NETERR_SENT: + st->webrtc.resendtime = timeval + 1; + st->webrtc.sends++; + break; + case NETERR_NOROUTE: + case NETERR_DISCONNECTED: + case NETERR_MTU: + safedefault: + FTENET_TCP_KillStream(con, st, "target not reachable"); + break; + } + } + else + st->webrtc.resendtime = timeval + 30; + } +#endif } if (con->generic.thesocket != INVALID_SOCKET && con->active < 256) @@ -7205,7 +7565,7 @@ static void FTENET_WebRTC_Heartbeat(ftenet_websocket_connection_t *b) //called from the javascript when there was some ice event. just forwards over the broker connection. static void FTENET_WebRTC_Callback(void *ctxp, int ctxi, int/*enum icemsgtype_s*/ evtype, const char *data) { - ftenet_websocket_connection_t *wcs = ctxp; + ftenet_websocket_connection_t *wsc = ctxp; size_t dl = strlen(data); qbyte *o = net_message_buffer; *o++ = evtype; @@ -7214,7 +7574,7 @@ static void FTENET_WebRTC_Callback(void *ctxp, int ctxi, int/*enum icemsgtype_s* memcpy(o, data, dl); o+=dl; //Con_Printf("To Broker: %i %i %s\n", evtype, ctxi, data); - emscriptenfte_ws_send(wcs->brokersock, net_message_buffer, o-net_message_buffer); + emscriptenfte_ws_send(wsc->brokersock, net_message_buffer, o-net_message_buffer); } static int FTENET_WebRTC_Create(qboolean initiator, ftenet_websocket_connection_t *wsc, int clid) { @@ -7248,7 +7608,7 @@ static int FTENET_WebRTC_Create(qboolean initiator, ftenet_websocket_connection_ } if (*brokeraddress == '/') { - brokeraddress = fs_manifest->rtcbroker; + brokeraddress = net_ice_broker.string; for (i = countof(pre); i --> 0; ) { if (!strncmp(brokeraddress, pre[i], strlen(pre[i]))) @@ -7342,7 +7702,17 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) } } - net_message.cursize = emscriptenfte_ws_recv(wsc->brokersock, net_message_buffer, sizeof(net_message_buffer)); + if (wsc->brokersock == INVALID_SOCKET) + net_message.cursize = 0; + else + { + net_message.cursize = emscriptenfte_ws_recv(wsc->brokersock, net_message_buffer, sizeof(net_message_buffer)); + if (net_message.cursize < 0) + { + emscriptenfte_ws_close(wsc->brokersock); + wsc->brokersock = INVALID_SOCKET; //error! + } + } if (net_message.cursize > 0) { int cmd; @@ -7362,7 +7732,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) if (cl == -1) { wsc->failed = true; -// Con_Printf("Broker closing connection: %s\n", MSG_ReadString()); + Con_Printf("Broker closing connection: %s\n", MSG_ReadString()); } else if (cl >= 0 && cl < wsc->numclients) { @@ -7470,7 +7840,11 @@ static neterr_t FTENET_WebRTC_SendPacket(ftenet_generic_connection_t *gcon, int if (NET_CompareAdr(to, &wsc->remoteadr)) { if (wsc->datasock == INVALID_SOCKET) + { + if (wsc->brokersock == INVALID_SOCKET) + return NETERR_DISCONNECTED; //no broker nor active data channel. its dead jim. return NETERR_CLOGGED; //we're still waiting for the broker to give us a server... or for a server to become available. + } else { if (emscriptenfte_ws_send(wsc->datasock, data, length) <= 0) @@ -7518,6 +7892,7 @@ static int FTENET_WebRTC_Establish(const char *address, const char *type) int i; char url[512]; char cleanaddress[512]; + char *udp=""; char *pre[] = { "wss://", "ices://", "rtcs://", "tls://", "ws://", "ice://", "rtc://", "tcp://"}; @@ -7537,8 +7912,26 @@ static int FTENET_WebRTC_Establish(const char *address, const char *type) if (*address == '/') { path = address+1; - - address = fs_manifest->rtcbroker; + address = NULL; + } + else + { + path = strchr(address, '/'); + if (!path) + { + if (i<0) + { + udp = "udp/"; + path = address; + address = NULL; + } + else + path = ""; + } + } + if (!address) + { + address = net_ice_broker.string; for (i = countof(pre); i --> 0; ) { if (!strncmp(address, pre[i], strlen(pre[i]))) @@ -7548,20 +7941,17 @@ static int FTENET_WebRTC_Establish(const char *address, const char *type) break; } } - } - else - { - path = strchr(address, '/'); - if (!path) - path = ""; + if (i<0) + i = 0; //default to the first one... wss... + if (!*address) + return INVALID_SOCKET; } Q_strncpyz(cleanaddress, address, sizeof(cleanaddress)); c = strchr(cleanaddress, '/'); if (c) *c = 0; COM_Parse(com_protocolname.string); - Q_snprintfz(url, sizeof(url), "%s%s/%s/%s", pre[i], cleanaddress, com_token, path); - + Q_snprintfz(url, sizeof(url), "%s%s/%s/%s%s", pre[i], cleanaddress, com_token, udp, path); return emscriptenfte_ws_connect(url, type); } @@ -7633,7 +8023,7 @@ static ftenet_generic_connection_t *FTENET_WebRTC_EstablishConnection(ftenet_con if (adr.type == NA_INVALID) { //if its using our broker, flip it over to a real address type, if we can. adr.type = NA_WEBSOCKET; - Q_strncpyz(adr.address.websocketurl, fs_manifest->rtcbroker, sizeof(adr.address.websocketurl)); + Q_strncpyz(adr.address.websocketurl, net_ice_broker.string, sizeof(adr.address.websocketurl)); } brokersocket = FTENET_WebRTC_Establish(address, isserver?"rtc_host":"rtc_client"); @@ -8092,6 +8482,10 @@ void NET_ReadPackets (ftenet_connections_t *collection) collection->bytesout = 0; collection->timemark = ctime; } + +#ifdef HAVE_DTLS + NET_DTLS_Timeouts(collection); +#endif } int NET_LocalAddressForRemote(ftenet_connections_t *collection, netadr_t *remote, netadr_t *local, int idx) @@ -8210,7 +8604,7 @@ neterr_t NET_SendPacket (ftenet_connections_t *collection, int length, const voi return NET_SendPacketCol (collection, length, data, to); } -qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char *host, netadr_t *adr) +qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr, qboolean outgoing) { switch(adr->prot) { @@ -8223,12 +8617,12 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char case NP_DTLS: #ifdef HAVE_DTLS adr->prot = NP_DGRAM; - if (NET_EnsureRoute(collection, routename, host, adr)) + if (NET_EnsureRoute(collection, routename, peerinfo, adr, outgoing)) { dtlscred_t cred; memset(&cred, 0, sizeof(cred)); - cred.peer.name = host; - if (NET_DTLS_Create(collection, adr, &cred)) + cred.peer = *peerinfo; + if (NET_DTLS_Create(collection, adr, &cred, outgoing)) { adr->prot = NP_DTLS; return true; @@ -8241,14 +8635,14 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char case NP_WSS: case NP_TLS: case NP_STREAM: - if (!FTENET_AddToCollection(collection, routename, host, adr->type, adr->prot)) + if (!FTENET_AddToCollection(collection, routename, peerinfo->name, adr->type, adr->prot)) return false; - Con_Printf("Establishing connection to %s\n", host); + Con_Printf("Establishing connection to %s\n", peerinfo->name); break; #if defined(SUPPORT_ICE) || defined(FTE_TARGET_WEB) case NP_RTC_TCP: case NP_RTC_TLS: - if (!FTENET_AddToCollection(collection, routename, host, adr->type, adr->prot)) + if (!FTENET_AddToCollection(collection, routename, peerinfo->name, adr->type, adr->prot)) return false; break; #endif @@ -8380,7 +8774,7 @@ enum addressscope_e NET_ClassifyAddress(netadr_t *adr, const char **outdesc) #define MAXADDRESSES 64 void NET_PrintAddresses(ftenet_connections_t *collection) { - int i; + int i, j; char adrbuf[MAX_ADR_SIZE]; int m; qboolean shown = false; @@ -8389,8 +8783,9 @@ void NET_PrintAddresses(ftenet_connections_t *collection) int flags[sizeof(addr)/sizeof(addr[0])]; const char *params[sizeof(addr)/sizeof(addr[0])]; qboolean warn = true; - static const char *scopes[] = {"process", "local", "link", "lan", "net"}; + static const char *scopes[] = {S_COLOR_GRAY"process", S_COLOR_GRAY"local", S_COLOR_GRAY"link", S_COLOR_GRAY"lan", "net"}; const char *desc; + char *fp, *scheme; if (!collection) return; @@ -8416,7 +8811,14 @@ void NET_PrintAddresses(ftenet_connections_t *collection) { shown = true; warn = false; - if ((addr[i].prot == NP_RTC_TCP || addr[i].prot == NP_RTC_TLS) && params[i]) + for (j = 0; j < countof(collection->srflx); j++) + { + if (collection->srflx[j].type!=NA_INVALID && NET_CompareAdr(&collection->srflx[j], &addr[i])) + break; + } + if (m < countof(collection->srflx)) + ; //gonna print it later anyway. + else if ((addr[i].prot == NP_RTC_TCP || addr[i].prot == NP_RTC_TLS) && params[i]) { if (addr[i].type == NA_INVALID) Con_TPrintf("%s address (%s): /%s\n", scopes[scope], con[i]->name, params[i]); @@ -8431,6 +8833,30 @@ void NET_PrintAddresses(ftenet_connections_t *collection) } } + fp = ""; + scheme = ""; +#if defined(HAVE_SERVER) && defined(HAVE_DTLS) + if (collection == svs.sockets) + { + fp = InfoBuf_ValueForKey(&svs.info, "*fp"); + if (*fp) + fp = va(S_COLOR_GRAY"?fp=%s", fp); + if (*COM_Parse(fs_manifest->schemes)) + scheme = va(S_COLOR_GRAY"%s://", com_token); + } +#endif + //show any master-reflexive addresses that were not above. + for (m = 0; m < countof(collection->srflx); m++) + { + if (collection->srflx[m].type == NA_INVALID) + continue; + if (collection->srflx[m].connum && collection->srflx[m].connum-1u < countof(collection->conn) && collection->conn[collection->srflx[m].connum-1]) + Con_TPrintf("reflexive address (%s): %s"S_COLOR_WHITE"%s%s\n", collection->conn[collection->srflx[m].connum-1]->name, scheme, NET_AdrToString(adrbuf, sizeof(adrbuf), &collection->srflx[m]), fp); + else + Con_TPrintf("reflexive address: %s"S_COLOR_WHITE"%s%s\n", scheme, NET_AdrToString(adrbuf, sizeof(adrbuf), &collection->srflx[m]), fp); + warn = false; + } + if (warn) Con_TPrintf("net address: no public addresses\n"); } @@ -8509,7 +8935,7 @@ int TCP_OpenStream (netadr_t *remoteaddr, const char *remotename) setsockopt(newsocket, SOL_SOCKET, SO_RCVBUF, (void*)&recvbufsize, sizeof(recvbufsize)); if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) - Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(neterrno())); + Sys_Error ("TCP_OpenStream: ioctl FIONBIO: %s", strerror(neterrno())); #ifdef UNIXSOCKETS if (remoteaddr->type == AF_UNIX) @@ -9042,6 +9468,7 @@ void NET_Init (void) #endif } + Cvar_Register(&net_ice_broker, "networking"); Cvar_Register(&timeout, "networking"); Cvar_Register(&net_hybriddualstack, "networking"); Cvar_Register(&net_fakeloss, "networking"); @@ -9066,6 +9493,9 @@ void NET_Init (void) Cvar_Register(&net_enable_rtcbroker, "networking"); #endif #endif +#ifdef HAVE_DTLS + Cvar_Register(&net_enable_dtls, "networking"); +#endif @@ -9283,9 +9713,6 @@ void SVNET_RegisterCvars(void) // Cvar_Register (&sv_port_unix, "networking"); #endif -#if defined(HAVE_DTLS) && defined(HAVE_SERVER) - Cvar_Register (&net_enable_dtls, "networking"); -#endif #ifdef HAVE_DTLS Cvar_Register (&dtls_psk_hint, "networking"); Cvar_Register (&dtls_psk_user, "networking"); @@ -9352,7 +9779,7 @@ void NET_InitServer(void) #ifdef HAVE_DTLS Cvar_ForceCallback(&net_enable_dtls); #endif -#ifdef SUPPORT_ICE +#if defined(SUPPORT_ICE) || defined(FTE_TARGET_WEB) Cvar_ForceCallback(&sv_public); #endif } @@ -9476,7 +9903,10 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea { trying = VFSTCP_IsStillConnecting(tf->sock); if (trying < 0) + { tf->readaborted = trying; + tf->writeaborted = true; + } else if (trying) return 0; tf->conpending = false; @@ -9507,18 +9937,22 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea case NET_ENOTCONN: Con_Printf("connection to \"%s\" failed\n", tf->peer); tf->readaborted = VFS_ERROR_NORESPONSE; + tf->writeaborted = true; break; case NET_ECONNABORTED: Con_DPrintf("connection to \"%s\" aborted\n", tf->peer); tf->readaborted = VFS_ERROR_NORESPONSE; + tf->writeaborted = true; break; case NET_ETIMEDOUT: Con_Printf("connection to \"%s\" timed out\n", tf->peer); tf->readaborted = VFS_ERROR_NORESPONSE; + tf->writeaborted = true; break; case NET_ECONNREFUSED: Con_DPrintf("connection to \"%s\" refused\n", tf->peer); tf->readaborted = VFS_ERROR_REFUSED; + tf->writeaborted = true; break; case NET_ECONNRESET: Con_DPrintf("connection to \"%s\" reset\n", tf->peer); @@ -9577,7 +10011,7 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt tf->conpending = false; } - len = send(tf->sock, buffer, bytestoread, 0); + len = send(tf->sock, buffer, bytestoread, MSG_NOSIGNAL); if (len == -1 || len == 0) { int reason = VFS_ERROR_UNSPECIFIED; @@ -9589,9 +10023,13 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt return 0; //nothing available yet. case NET_ETIMEDOUT: Con_Printf("connection to \"%s\" timed out\n", tf->peer); + tf->writeaborted = true; + tf->conpending = false; return VFS_ERROR_NORESPONSE; //don't bother trying to read if we never connected. case NET_ECONNREFUSED: //peer sent a reset instead of accepting a new connection Con_DPrintf("connection to \"%s\" refused\n", tf->peer); + tf->writeaborted = true; + tf->conpending = false; return VFS_ERROR_REFUSED; //don't bother trying to read if we never connected. case NET_ECONNABORTED: //peer closed its socket Con_Printf("connection to \"%s\" aborted\n", tf->peer); @@ -9606,6 +10044,8 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt case EPIPE: #endif Con_Printf("connection to \"%s\" failed\n", tf->peer); + tf->writeaborted = true; + tf->conpending = false; return VFS_ERROR_NORESPONSE; //don't bother trying to read if we never connected. default: Sys_Printf("tcp socket error %i (%s)\n", e, tf->peer); diff --git a/engine/common/netinc.h b/engine/common/netinc.h index 52935ab7e..f9fc11038 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -223,6 +223,9 @@ #ifndef INVALID_SOCKET #define INVALID_SOCKET -1 #endif +#ifndef MSG_NOSIGNAL + #define MSG_NOSIGNAL 0 //available on linux, no idea about other unixes. don't bug out too much... (d)tls needs this to not get constant SIGPIPE errors +#endif #ifndef INADDR_LOOPBACK #define INADDR_LOOPBACK 0x7f000001 @@ -289,10 +292,11 @@ typedef struct void (QDECL *AddRCandidateInfo)(struct icestate_s *con, struct icecandinfo_s *cand); //stuff that came from the peer. void (QDECL *Close)(struct icestate_s *con, qboolean force); //bye then. void (QDECL *CloseModule)(void *module); //closes all unclosed connections, with warning. -// struct icestate_s *(QDECL *Find)(void *module, const char *conname); qboolean (QDECL *GetLCandidateSDP)(struct icestate_s *con, char *out, size_t valuesize); //retrieves candidates that need reporting to the peer. + struct icestate_s *(QDECL *Find)(void *module, const char *conname); } icefuncs_t; extern icefuncs_t iceapi; +extern cvar_t net_ice_broker; #endif #ifdef HAVE_EPOLL @@ -372,7 +376,7 @@ typedef struct dtlsfuncs_s neterr_t (*Transmit)(void *ctx, const qbyte *data, size_t datasize); neterr_t (*Received)(void *ctx, sizebuf_t *message); //operates in-place... neterr_t (*Timeouts)(void *ctx); - void (*GetPeerCertificate)(void *ctx); + int (*GetPeerCertificate)(void *ctx, enum certprops_e prop, char *out, size_t outsize); qboolean (*GenTempCertificate)(const char *subject, struct dtlslocalcred_s *cred); } dtlsfuncs_t; #ifdef HAVE_DTLS @@ -431,6 +435,9 @@ typedef struct ftenet_connections_s size_t cursize; qbyte data[1]; } *delayed_packets; + + netadr_t srflx[2]; //ipv4, ipv6 + unsigned int srflx_tid[3]; //to verify the above. } ftenet_connections_t; void ICE_Tick(void); diff --git a/engine/common/plugin.c b/engine/common/plugin.c index beb592713..543c4c659 100644 --- a/engine/common/plugin.c +++ b/engine/common/plugin.c @@ -173,6 +173,7 @@ static plugin_t *Plug_Load(const char *file) static enum fs_relative prefixes[] = { FS_BINARYPATH, + FS_LIBRARYPATH, #ifndef ANDROID FS_ROOT, #endif @@ -514,7 +515,11 @@ static int QDECL Plug_Cmd_Argc(void) //void Cvar_SetString (char *name, char *value); static void QDECL Plug_Cvar_SetString(const char *name, const char *value) { - cvar_t *var = Cvar_Get(name, value, 0, "Plugin vars"); + cvar_t *var; + if (!value) + var = Cvar_FindVar(name); + else + var = Cvar_Get(name, value, 0, "Plugin vars"); if (var) Cvar_Set(var, value); } @@ -967,6 +972,7 @@ static qhandle_t QDECL Plug_FS_Open(const char *fname, qhandle_t *outhandle, int #ifndef WEBCLIENT f = NULL; #else + Con_Printf("Plugin %s requesting %s\n", currentplug->name, fname); handle = Plug_NewStreamHandle(STREAM_WEB); pluginstreamarray[handle].dl = HTTP_CL_Get(fname, NULL, Plug_DownloadComplete); pluginstreamarray[handle].dl->user_num = handle; @@ -1187,6 +1193,11 @@ void QDECL Plug_FS_EnumerateFiles(enum fs_relative fsroot, const char *match, in } } +unsigned int Plug_BlockChecksum(const void *data, size_t datasize) +{ //convienience function. we use md4 for legacy reasons (every qw engine must have an implementation) + return CalcHashInt(&hash_md4, data, datasize); +} + #if defined(HAVE_SERVER) && defined(HAVE_CLIENT) static qboolean QDECL Plug_MapLog_Query(const char *packagename, const char *mapname, float *vals) { @@ -1249,9 +1260,16 @@ void Plug_Initialise(qboolean fromgamedir) { if (!fromgamedir) { - FS_NativePath("", FS_BINARYPATH, nat, sizeof(nat)); - Con_DPrintf("Loading plugins from \"%s\"\n", nat); - Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, Plug_EnumeratedRoot, NULL, NULL); + if (FS_NativePath("", FS_BINARYPATH, nat, sizeof(nat))) + { + Con_DPrintf("Loading plugins from \"%s\"\n", nat); + Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, Plug_EnumeratedRoot, NULL, NULL); + } + if (FS_NativePath("", FS_LIBRARYPATH, nat, sizeof(nat))) + { + Con_DPrintf("Loading plugins from \"%s\"\n", nat); + Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, Plug_EnumeratedRoot, NULL, NULL); + } } } if (plug_loaddefault.ival & 1) @@ -1743,6 +1761,7 @@ int QDECL Plug_List_Print(const char *fname, qofs_t fsize, time_t modtime, void void Plug_List_f(void) { char binarypath[MAX_OSPATH]; + char librarypath[MAX_OSPATH]; char rootpath[MAX_OSPATH]; unsigned int u; plugin_t *plug; @@ -1765,9 +1784,22 @@ void Plug_List_f(void) while ((mssuck=strchr(binarypath, '\\'))) *mssuck = '/'; #endif - Con_DPrintf("Scanning for plugins at %s:\n", binarypath); + Con_Printf("Scanning for plugins at %s:\n", binarypath); Sys_EnumerateFiles(binarypath, PLUGINPREFIX"*" ARCH_DL_POSTFIX, Plug_List_Print, binarypath, NULL); } + if (FS_NativePath("", FS_LIBRARYPATH, librarypath, sizeof(librarypath))) + { +#ifdef _WIN32 + char *mssuck; + while ((mssuck=strchr(librarypath, '\\'))) + *mssuck = '/'; +#endif + if (strcmp(librarypath, rootpath)) + { + Con_Printf("Scanning for plugins at %s:\n", librarypath); + Sys_EnumerateFiles(librarypath, PLUGINPREFIX"*" ARCH_DL_POSTFIX, Plug_List_Print, librarypath, NULL); + } + } if (FS_NativePath("", FS_ROOT, rootpath, sizeof(rootpath))) { #ifdef _WIN32 @@ -1926,7 +1958,7 @@ static void *QDECL PlugBI_GetEngineInterface(const char *interfacename, size_t s COM_GetFileExtension, COM_FileBase, COM_CleanUpPath, - Com_BlockChecksum, + Plug_BlockChecksum, FS_LoadMallocFile, FS_GetPackHashes, @@ -2023,10 +2055,15 @@ static void *QDECL PlugBI_GetEngineInterface(const char *interfacename, size_t s InfoBuf_ValueForKey, Info_ValueForKey, Info_SetValueForKey, - +#ifdef HAVE_SERVER SV_DropClient, SV_ExtractFromUserinfo, SV_ChallengePasses, +#else + NULL, + NULL, + NULL, +#endif }; if (structsize == sizeof(funcs)) return &funcs; diff --git a/engine/common/pmove.c b/engine/common/pmove.c index be2ebbb6b..f3a1f6175 100644 --- a/engine/common/pmove.c +++ b/engine/common/pmove.c @@ -1473,8 +1473,9 @@ void PM_PlayerMove (float gamespeed) pmove.jump_msec = 0; } - PM_CheckJump (); + if (!movevars.bunnyfriction) + PM_CheckJump (); //qw-style bunny PM_Friction (); if (pmove.waterlevel >= 2) @@ -1486,6 +1487,9 @@ void PM_PlayerMove (float gamespeed) else PM_AirMove (); + if (movevars.bunnyfriction) + PM_CheckJump (); //nq-style bunny. note tick rate differences too. + /* //round to network precision for (i = 0; i < 3; i++) { diff --git a/engine/common/pmove.h b/engine/common/pmove.h index c307cf7cb..e3275c4aa 100644 --- a/engine/common/pmove.h +++ b/engine/common/pmove.h @@ -119,6 +119,7 @@ typedef struct { qboolean stepdown; qboolean slidyslopes; qboolean autobunny; + qboolean bunnyfriction; //force at least one frame of friction when bunnying. int stepheight; qbyte coordtype; //FIXME: EZPEXT1_FLOATENTCOORDS should mean 4, but the result does not match ezquake/mvdsv's round-towards-origin which would result in inconsistencies. so player coords are rounded inconsistently. diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index 91ee83671..f88c4d4c4 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -3942,7 +3942,7 @@ void QCBUILTIN PF_findradius (pubprogfuncs_t *prinst, struct globalvars_s *pr_gl for (j=0 ; j<3 ; j++) { eorg[j] = org[j] - ent->v->origin[j]; - eorg[j] -= bound(ent->v->mins[j], org[j], ent->v->maxs[j]); + eorg[j] -= bound(ent->v->mins[j], eorg[j], ent->v->maxs[j]); } } else @@ -3972,7 +3972,7 @@ void QCBUILTIN PF_findradius (pubprogfuncs_t *prinst, struct globalvars_s *pr_gl for (j=0 ; j<3 ; j++) { eorg[j] = org[j] - ent->v->origin[j]; - eorg[j] -= bound(ent->v->mins[j], org[j], ent->v->maxs[j]); + eorg[j] -= bound(ent->v->mins[j], eorg[j], ent->v->maxs[j]); } } else @@ -5665,21 +5665,18 @@ static void QCBUILTIN PF_digest_internal (pubprogfuncs_t *prinst, struct globalv unsigned char hexdig[sizeof(digest)*2+1]; if (!strcmp(hashtype, "MD4")) - { - digestsize = 16; - Com_BlockFullChecksum(str, len, digest); - } + digestsize = CalcHash(&hash_md4, digest, sizeof(digest), str, len); //md5? else if (!strcmp(hashtype, "SHA1")) digestsize = CalcHash(&hash_sha1, digest, sizeof(digest), str, len); - else if (!strcmp(hashtype, "SHA224")) - digestsize = CalcHash(&hash_sha224, digest, sizeof(digest), str, len); - else if (!strcmp(hashtype, "SHA256")) - digestsize = CalcHash(&hash_sha256, digest, sizeof(digest), str, len); - else if (!strcmp(hashtype, "SHA384")) - digestsize = CalcHash(&hash_sha384, digest, sizeof(digest), str, len); - else if (!strcmp(hashtype, "SHA512")) - digestsize = CalcHash(&hash_sha512, digest, sizeof(digest), str, len); + else if (!strcmp(hashtype, "SHA2-224") || !strcmp(hashtype, "SHA224")) + digestsize = CalcHash(&hash_sha2_224, digest, sizeof(digest), str, len); + else if (!strcmp(hashtype, "SHA2-256") || !strcmp(hashtype, "SHA256")) + digestsize = CalcHash(&hash_sha2_256, digest, sizeof(digest), str, len); + else if (!strcmp(hashtype, "SHA2-384") || !strcmp(hashtype, "SHA384")) + digestsize = CalcHash(&hash_sha2_384, digest, sizeof(digest), str, len); + else if (!strcmp(hashtype, "SHA2-512") || !strcmp(hashtype, "SHA512")) + digestsize = CalcHash(&hash_sha2_512, digest, sizeof(digest), str, len); else if (!strcmp(hashtype, "CRC16")) digestsize = CalcHash(&hash_crc16, digest, sizeof(digest), str, len); else diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index 7e4fed65a..0d4b1120a 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -356,6 +356,7 @@ void QCBUILTIN PF_brush_findinvolume(pubprogfuncs_t *prinst, struct globalvars_s void QCBUILTIN PF_patch_getcp(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_patch_getmesh(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_patch_create(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_patch_evaluate(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); #endif void QCBUILTIN PF_touchtriggers(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); diff --git a/engine/common/q1bsp.c b/engine/common/q1bsp.c index 016bd9347..6123c869a 100644 --- a/engine/common/q1bsp.c +++ b/engine/common/q1bsp.c @@ -2625,7 +2625,7 @@ bspx_header_t *BSPX_Setup(model_t *mod, char *filebase, size_t filelen, lump_t * } } - if (offs < filelen && mod && !mod->archive && mod_loadmappackages.ival) + if (offs < filelen && mod && !mod->archive && mod_loadmappackages.ival && filelen-offs > 22)//end-of-central-dir being 22 bytes sets a minimum zip size, which should slightly reduce false-positives. { //we have some sort of trailing junk... is it a zip?... Mod_LoadMapArchive(mod, filebase+offs, filelen-offs); } diff --git a/engine/common/q3api.h b/engine/common/q3api.h index 35406df18..57ca5deda 100644 --- a/engine/common/q3api.h +++ b/engine/common/q3api.h @@ -11,7 +11,7 @@ struct q3gamecode_s void (*SendAuthPacket)(struct ftenet_connections_s *socket, netadr_t *gameserver); void (*SendConnectPacket)(struct ftenet_connections_s *socket, netadr_t *to, int challenge, int qport, infobuf_t *userinfo); void (*Established)(void); - void (*VARGS SendClientCommand)(const char *fmt, ...) LIKEPRINTF(1); + void (VARGS *SendClientCommand)(const char *fmt, ...) LIKEPRINTF(1); void (*SendCmd)(struct ftenet_connections_s *socket, struct usercmd_s *cmd, unsigned int movesequence, double gametime); int (*ParseServerMessage) (sizebuf_t *msg); void (*Disconnect) (struct ftenet_connections_s *socket); //disconnects from the server, killing all connection+cgame state. diff --git a/engine/common/sha1.c b/engine/common/sha1.c index 93000df2d..88ae464c4 100644 --- a/engine/common/sha1.c +++ b/engine/common/sha1.c @@ -194,7 +194,7 @@ unsigned int hashfunc_terminate_uint(const hashfunc_t *func, void *context) r ^= digest[l]<<((l%sizeof(r))*8); return r; } -unsigned int CalcHashInt(const hashfunc_t *func, const unsigned char *data, size_t datasize) +unsigned int CalcHashInt(const hashfunc_t *func, const void *data, size_t datasize) { void *ctx = alloca(func->contextsize); func->init(ctx); diff --git a/engine/common/sha2.c b/engine/common/sha2.c index f6a1e65de..a5069d38c 100644 --- a/engine/common/sha2.c +++ b/engine/common/sha2.c @@ -515,7 +515,7 @@ static void sha256_finish (qbyte *digest, void *context) memcpy(digest, hd->buf, 256/8); } -hashfunc_t hash_sha224 = +hashfunc_t hash_sha2_224 = { 224/8, sizeof(SHA2_CONTEXT), @@ -523,7 +523,7 @@ hashfunc_t hash_sha224 = sha2_write, sha224_finish }; -hashfunc_t hash_sha256 = +hashfunc_t hash_sha2_256 = { 256/8, sizeof(SHA2_CONTEXT), @@ -547,7 +547,7 @@ static void sha512_finish (qbyte *digest, void *context) memcpy(digest, hd->buf, 512/8); } -hashfunc_t hash_sha384 = +hashfunc_t hash_sha2_384 = { 384/8, sizeof(SHA2_CONTEXT), @@ -555,7 +555,7 @@ hashfunc_t hash_sha384 = sha2_write, sha384_finish }; -hashfunc_t hash_sha512 = +hashfunc_t hash_sha2_512 = { 512/8, sizeof(SHA2_CONTEXT), diff --git a/engine/common/zone.c b/engine/common/zone.c index af974267b..51e5471a8 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -254,6 +254,16 @@ char *Z_StrDupf(const char *format, ...) return string; } +void Z_StrCatLen(char **ptr, const char *append, size_t newlen) +{ + size_t oldlen = *ptr?strlen(*ptr):0; + char *newptr = BZ_Malloc(oldlen+newlen+1); + memcpy(newptr, *ptr, oldlen); + memcpy(newptr+oldlen, append, newlen); + newptr[oldlen+newlen] = 0; + BZ_Free(*ptr); + *ptr = newptr; +} void Z_StrCat(char **ptr, const char *append) { size_t oldlen = *ptr?strlen(*ptr):0; diff --git a/engine/common/zone.h b/engine/common/zone.h index 25decbd1c..ea8df49b6 100644 --- a/engine/common/zone.h +++ b/engine/common/zone.h @@ -151,6 +151,7 @@ void QDECL ZG_FreeGroup(zonegroup_t *ctx); char *Z_StrDupf(const char *format, ...); void Z_StrCat(char **ptr, const char *append); +void Z_StrCatLen(char **ptr, const char *append, size_t newlen); //still doesn't allow nulls, but src doesn't need null termination. /* void *Hunk_Alloc (int size); // returns 0 filled memory diff --git a/engine/gl/gl_font.c b/engine/gl/gl_font.c index 4e9a2fa42..7453625ee 100644 --- a/engine/gl/gl_font.c +++ b/engine/gl/gl_font.c @@ -15,7 +15,7 @@ void Font_Init(void); void Font_Shutdown(void); -struct font_s *Font_LoadFont(const char *fontfilename, float height, float scale, int outline); +struct font_s *Font_LoadFont(const char *fontfilename, float height, float scale, int outline, unsigned int flags); void Font_Free(struct font_s *f); void Font_BeginString(struct font_s *font, float vx, float vy, int *px, int *py); void Font_BeginScaledString(struct font_s *font, float vx, float vy, float szx, float szy, float *px, float *py); /*avoid using*/ @@ -80,6 +80,9 @@ const char *(VARGS *pFT_Error_String) (FT_Error error_code); typedef unsigned int FT_Pixel_Mode; //for consistency even without freetype support. #endif +#ifndef FT_PIXEL_MODE_MONO +#define FT_PIXEL_MODE_MONO 1 +#endif #ifndef FT_PIXEL_MODE_GRAY #define FT_PIXEL_MODE_GRAY 2 #endif @@ -322,6 +325,7 @@ typedef struct font_s unsigned short truecharheight; //what you actually got, for compat with dp's lets-use-the-wrong-size-for-double-padding-between-lines thing. float scale; //some sort of poop short outline; + unsigned int flags; unsigned short faces; fontface_t *face[MAX_FACES]; @@ -473,6 +477,7 @@ void Font_Init(void) fontplanes.shader = R_RegisterShader("ftefont", SUF_2D, "{\n" + "fullrate\n" //don't hurt readability of text. "if $nofixed\n" "program default2d\n" "endif\n" @@ -720,6 +725,32 @@ static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, FT out += PLANEWIDTH; } } + else if (pixelmode == FT_PIXEL_MODE_MONO) + { //1bit font ( + for (y = -pad; y < 0; y++) + { + for (x = -pad; x < (int)bmw+pad; x++) + out[x].c = BORDERCOLOUR; + out += PLANEWIDTH; + } + for (; y < bmh; y++) + { + for (x = -pad; x < 0; x++) + out[x].c = BORDERCOLOUR; + for (; x < bmw; x++) + out[x].c = (((unsigned char*)data)[x>>3]&(1<<(7-(x&7))))?0xffffffff:0; + for (; x < bmw+pad; x++) + out[x].c = BORDERCOLOUR; + data = (char*)data + pitch; + out += PLANEWIDTH; + } + for (; y < bmh+pad; y++) + { + for (x = -pad; x < (int)bmw+pad; x++) + out[x].c = BORDERCOLOUR; + out += PLANEWIDTH; + } + } else if ((unsigned int)pixelmode == FT_PIXEL_MODE_RGBA_SA) { //rgba source using standard alpha. //(we'll multiply out the alpha for the gpu) @@ -821,9 +852,6 @@ static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, FT if (outline) { - int bytes = (pixelmode == FT_PIXEL_MODE_GRAY)?1:4; - qbyte *alpha = (char*)data + bytes-1 - pitch*bmh; - static int filter_outline; static unsigned char filter_highest[MAXOUTLINE*2+1][MAXOUTLINE*2+1]; if (outline != filter_outline) @@ -836,23 +864,57 @@ static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, FT //expand it to out full(ish) size - alpha -= pitch*outline + bytes*outline; - out = &fontplanes.plane[c->bmx+((int)c->bmy-outline)*PLANEHEIGHT]; - for (y = -outline; y < (int)bmh+outline; y++, out += PLANEWIDTH) - for (x = -outline; x < (int)bmw+outline; x++) - { - int xn, x1 = max(outline-x, 0), x2 = min(2*outline, (int)bmw-1-x+outline); - int yn, y1 = max(outline-y, 0), y2 = min(2*outline, (int)bmh-1-y+outline); - int v, m = out[x].rgba[3]*255; - for (yn = y1; yn <= y2; yn++) - for (xn = x1; xn <= x2; xn++) - { - v = filter_highest[yn][xn] * alpha[(xn+x)*bytes+(yn+y)*pitch]; - m = max(m, v); - } - //out[x].c = 0; - out[x].rgba[3] = m/255; - } + if (pixelmode == FT_PIXEL_MODE_MONO) + { + qbyte *alpha = (char*)data - pitch*bmh; + qbyte a; + int bit; + + alpha -= pitch*outline; + out = &fontplanes.plane[c->bmx+((int)c->bmy-outline)*PLANEHEIGHT]; + for (y = -outline; y < (int)bmh+outline; y++, out += PLANEWIDTH) + for (x = -outline; x < (int)bmw+outline; x++) + { + int xn, x1 = max(outline-x, 0), x2 = min(2*outline, (int)bmw-1-x+outline); + int yn, y1 = max(outline-y, 0), y2 = min(2*outline, (int)bmh-1-y+outline); + int v, m = out[x].rgba[3]*255; + for (yn = y1; yn <= y2; yn++) + for (xn = x1; xn <= x2; xn++) + { + bit = (xn+x)-outline; + a = alpha[(bit>>3)+(yn+y)*pitch]; + a = (a&(1<<(7-(bit&7))))?0xff:0; + + v = filter_highest[yn][xn] * a; + m = max(m, v); + } + //out[x].c = 0; + out[x].rgba[3] = m/255; + } + } + else + { + int bytes = (pixelmode == FT_PIXEL_MODE_GRAY)?1:4; + qbyte *alpha = (char*)data + bytes-1 - pitch*bmh; + + alpha -= pitch*outline + bytes*outline; + out = &fontplanes.plane[c->bmx+((int)c->bmy-outline)*PLANEHEIGHT]; + for (y = -outline; y < (int)bmh+outline; y++, out += PLANEWIDTH) + for (x = -outline; x < (int)bmw+outline; x++) + { + int xn, x1 = max(outline-x, 0), x2 = min(2*outline, (int)bmw-1-x+outline); + int yn, y1 = max(outline-y, 0), y2 = min(2*outline, (int)bmh-1-y+outline); + int v, m = out[x].rgba[3]*255; + for (yn = y1; yn <= y2; yn++) + for (xn = x1; xn <= x2; xn++) + { + v = filter_highest[yn][xn] * alpha[(xn+x)*bytes+(yn+y)*pitch]; + m = max(m, v); + } + //out[x].c = 0; + out[x].rgba[3] = m/255; + } + } c->bmx -= outline; @@ -1316,7 +1378,7 @@ static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) return NULL; //some sort of error. } 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) + if (pFT_Load_Char(face, charidx, FT_LOAD_RENDER|(((f->flags&FONT_MONO)&&qface->ft.activeheight==qface->ft.actualsize/*FIXME*/)?FT_LOAD_TARGET_MONO:FT_LOAD_TARGET_NORMAL)|FT_LOAD_COLOR) == 0) { FT_GlyphSlot slot; FT_Bitmap *bm; @@ -1348,6 +1410,12 @@ static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) Image_ResampleTexture(PTI_L8, (void*)bm->buffer, bm->width, bm->rows, out, nw, nh); c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, out, nw, nh, nw*sizeof(*out)); } + /*else if (bm->pixel_mode == FT_PIXEL_MODE_MONO) + { + unsigned char *out = alloca(nw*nh*sizeof(*out)); + Image_ResampleTexture(PTI_L1, (void*)bm->buffer, bm->width, bm->rows, out, nw, nh); + c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, out, nw, nh, nw*sizeof(*out)); + }*/ else c = NULL; if (c) @@ -2208,7 +2276,7 @@ static qboolean Font_LoadFontLump(font_t *f, const char *facename) //creates a new font object from the given file, with each text row with the given height. //width is implicit and scales with height and choice of font. -struct font_s *Font_LoadFont(const char *fontfilename, float vheight, float scale, int outline) +struct font_s *Font_LoadFont(const char *fontfilename, float vheight, float scale, int outline, unsigned int flags) { struct font_s *f; int i = 0; @@ -2236,6 +2304,7 @@ struct font_s *Font_LoadFont(const char *fontfilename, float vheight, float scal f->scale = scale; f->charheight = height; f->truecharheight = height; + f->flags = flags; Q_strncpyz(f->name, fontfilename, sizeof(f->name)); switch(M_GameType()) @@ -2453,7 +2522,7 @@ struct font_s *Font_LoadFont(const char *fontfilename, float vheight, float scal } else { - f->alt = Font_LoadFont(aname, vheight, scale, outline); + f->alt = Font_LoadFont(aname, vheight, scale, outline, flags); if (f->alt) { VectorCopy(f->alt->tint, f->alttint); diff --git a/engine/gl/gl_heightmap.c b/engine/gl/gl_heightmap.c index d14f9af14..f1011075b 100644 --- a/engine/gl/gl_heightmap.c +++ b/engine/gl/gl_heightmap.c @@ -18,12 +18,6 @@ See gl_terrain.h for terminology, networking notes, etc. #include "gl_terrain.h" static plugterrainfuncs_t terrainfuncs; -typedef struct -{ - vec3_t v; - vec2_t tc; - vec4_t rgba; -} qcpatchvert_t; cvar_t mod_terrain_networked = CVARD("mod_terrain_networked", "0", "Terrain edits are networked. Clients will download sections on demand, and servers will notify clients of changes."); @@ -3321,6 +3315,8 @@ unsigned int Heightmap_PointContents(model_t *model, const vec3_t axis[3], const for (i = 0; i < hm->numbrushes; i++) { br = &hm->wbrushes[i]; + if (br->patch) + continue; //infinitely thin... for (j = 0; j < br->numplanes; j++) { @@ -3785,7 +3781,7 @@ static qboolean Heightmap_Trace_Patch(hmtrace_t *tr, brushes_t *brushinfo) if (!patch->tessvert) { - const struct patchcpvert_s *r1 = patch->cp, *r2; + const struct qcpatchvert_s *r1 = patch->cp, *r2; w = patch->numcp[0]; h = patch->numcp[1]; @@ -6255,6 +6251,8 @@ static brushes_t *Terr_Brush_Insert(model_t *model, heightmap_t *hm, brushes_t * out->planes = NULL; out->faces = NULL; out->numplanes = 0; + out->ispatch = !!brush->patch; + out->selected = false; ClearBounds(out->mins, out->maxs); if (brush->numplanes) { @@ -6426,6 +6424,9 @@ static brushes_t *Terr_Brush_Insert(model_t *model, heightmap_t *hm, brushes_t * AddPointToBounds(out->mins, model->mins, model->maxs); AddPointToBounds(out->maxs, model->mins, model->maxs); + if (out->patch && (out->patch->subdiv[0] || out->patch->subdiv[1])) + out->patch->tessvert = PatchInfo_Evaluate(out->patch->cp, out->patch->numcp, out->patch->subdiv, out->patch->tesssize); + return out; } @@ -6481,6 +6482,11 @@ static void Terr_Brush_DeleteIdx(heightmap_t *hm, size_t idx) } BZ_Free(br->planes); + if (br->patch) + { + BZ_Free(br->patch->tessvert); + BZ_Free(br->patch); + } hm->numbrushes--; hm->brushesedited = true; //plug the hole with some other brush. @@ -6506,6 +6512,104 @@ static qboolean Terr_Brush_DeleteId(heightmap_t *hm, unsigned int brushid) return false; } + +static void Patch_Serialise(sizebuf_t *sb, brushes_t *br) +{ + qbyte flags = 0; + unsigned int i, m = br->patch->numcp[0]*br->patch->numcp[1]; + + for (i = 0; i < m; i++) + { + if (br->patch->cp[i].rgba[0] != 1) + flags |= 1; + if (br->patch->cp[i].rgba[1] != 1) + flags |= 2; + if (br->patch->cp[i].rgba[2] != 1) + flags |= 4; + if (br->patch->cp[i].rgba[3] != 1) + flags |= 8; + } + + MSG_WriteLong(sb, br->id); + MSG_WriteLong(sb, br->contents); + MSG_WriteShort(sb, br->patch->numcp[0]); + MSG_WriteShort(sb, br->patch->numcp[1]); + + MSG_WriteByte(sb, flags); + + MSG_WriteString(sb, br->patch->tex->shadername); + MSG_WriteShort(sb, br->patch->subdiv[0]); + MSG_WriteShort(sb, br->patch->subdiv[1]); + + for (i = 0; i < m; i++) + { + MSG_WriteFloat(sb, br->patch->cp[i].v[0]); + MSG_WriteFloat(sb, br->patch->cp[i].v[1]); + MSG_WriteFloat(sb, br->patch->cp[i].v[2]); + MSG_WriteFloat(sb, br->patch->cp[i].tc[0]); + MSG_WriteFloat(sb, br->patch->cp[i].tc[1]); + + if (flags&1) + MSG_WriteFloat(sb, br->patch->cp[i].rgba[0]); + if (flags&2) + MSG_WriteFloat(sb, br->patch->cp[i].rgba[1]); + if (flags&4) + MSG_WriteFloat(sb, br->patch->cp[i].rgba[2]); + if (flags&8) + MSG_WriteFloat(sb, br->patch->cp[i].rgba[3]); + } +} +static size_t Patch_DeserialiseHeader(brushes_t *br) +{ + unsigned int numcp[2]; + br->id = MSG_ReadLong(); + br->contents = MSG_ReadLong(); + + br->numplanes = numcp[0] = (unsigned short)MSG_ReadShort(); + br->axialplanes = numcp[1] = (unsigned short)MSG_ReadShort(); + + if (numcp[0]*numcp[1] > 8192) + return 0; //too many. reject it as bad. + return sizeof(*br->patch) + sizeof(*br->patch->cp)*(numcp[0]*numcp[1]-countof(br->patch->cp)); +} +static qboolean Patch_Deserialise(heightmap_t *hm, brushes_t *br, void *mem) +{ + struct qcpatchvert_s vert; + qboolean flags; + unsigned int i, m; + flags = MSG_ReadByte(); + + br->patch = mem; + br->patch->numcp[0] = br->numplanes; + br->patch->numcp[1] = br->axialplanes; + br->numplanes = br->axialplanes = 0; + + m = br->patch->numcp[0]*br->patch->numcp[1]; + + //FIXME: as a server, we probably want to reject the brush if we exceed some texnum/memory limitation, so clients can't just spam new textures endlessly. + br->patch->tex = Terr_Brush_FindTexture(hm, MSG_ReadString()); + + br->patch->subdiv[0] = MSG_ReadShort(); + br->patch->subdiv[1] = MSG_ReadShort(); + + for (i = 0; i < m; i++) + { + vert.v[0] = MSG_ReadFloat(); + vert.v[1] = MSG_ReadFloat(); + vert.v[2] = MSG_ReadFloat(); + vert.tc[0] = MSG_ReadFloat(); + vert.tc[1] = MSG_ReadFloat(); + + vert.rgba[0] = (flags&1)?MSG_ReadFloat():1; + vert.rgba[1] = (flags&2)?MSG_ReadFloat():1; + vert.rgba[2] = (flags&4)?MSG_ReadFloat():1; + vert.rgba[3] = (flags&8)?MSG_ReadFloat():1; + + br->patch->cp[i] = vert; + } + return true; +} + static void Brush_Serialise(sizebuf_t *sb, brushes_t *br) { unsigned int i; @@ -6533,16 +6637,30 @@ static void Brush_Serialise(sizebuf_t *sb, brushes_t *br) MSG_WriteFloat(sb, br->faces[i].stdir[1][3]); } } -static qboolean Brush_Deserialise(heightmap_t *hm, brushes_t *br) +static size_t Brush_DeserialiseHeader(brushes_t *br, qboolean ispatch) { - unsigned int i; - unsigned int maxplanes = br->numplanes; + br->ispatch = ispatch; + if (br->ispatch) + return Patch_DeserialiseHeader(br); + br->id = MSG_ReadLong(); br->contents = MSG_ReadLong(); br->numplanes = MSG_ReadLong(); - if (br->numplanes > maxplanes) - return false; + if (br->numplanes > 8192) + return 0; //abusive + + return sizeof(*br->faces) * br->numplanes + + sizeof(*br->planes) * br->numplanes; +} +static qboolean Brush_Deserialise(heightmap_t *hm, brushes_t *br, void *mem) +{ + unsigned int i; + if (br->ispatch) + return Patch_Deserialise(hm, br, mem); + + br->faces = mem; + br->planes = (vec4_t*)(br->faces + br->numplanes); for (i = 0; i < br->numplanes; i++) { @@ -6568,88 +6686,6 @@ static qboolean Brush_Deserialise(heightmap_t *hm, brushes_t *br) return true; } -static void Patch_Serialise(sizebuf_t *sb, brushes_t *br) -{ - qbyte flags = 0; - unsigned int i, m = br->patch->numcp[0]*br->patch->numcp[1]; - - for (i = 0; i < m; i++) - { - if (br->patch->cp[i].rgba[0] != 1) - flags |= 1; - if (br->patch->cp[i].rgba[1] != 1) - flags |= 2; - if (br->patch->cp[i].rgba[2] != 1) - flags |= 4; - if (br->patch->cp[i].rgba[3] != 1) - flags |= 8; - } - - MSG_WriteLong(sb, br->id); - MSG_WriteByte(sb, flags); - - MSG_WriteLong(sb, br->contents); - MSG_WriteString(sb, br->patch->tex->shadername); - MSG_WriteShort(sb, br->patch->numcp[0]); - MSG_WriteShort(sb, br->patch->numcp[1]); - MSG_WriteShort(sb, br->patch->subdiv[0]); - MSG_WriteShort(sb, br->patch->subdiv[1]); - - for (i = 0; i < m; i++) - { - MSG_WriteFloat(sb, br->patch->cp[i].v[0]); - MSG_WriteFloat(sb, br->patch->cp[i].v[1]); - MSG_WriteFloat(sb, br->patch->cp[i].v[2]); - MSG_WriteFloat(sb, br->patch->cp[i].tc[0]); - MSG_WriteFloat(sb, br->patch->cp[i].tc[1]); - - if (flags&1) - MSG_WriteFloat(sb, br->patch->cp[i].rgba[0]); - if (flags&2) - MSG_WriteFloat(sb, br->patch->cp[i].rgba[1]); - if (flags&4) - MSG_WriteFloat(sb, br->patch->cp[i].rgba[2]); - if (flags&8) - MSG_WriteFloat(sb, br->patch->cp[i].rgba[3]); - } -} -static qboolean Patch_Deserialise(heightmap_t *hm, brushes_t *br) -{ - struct patchcpvert_s vert; - qboolean flags; - unsigned int i, maxverts = br->patch->numcp[0]*br->patch->numcp[1]; - br->id = MSG_ReadLong(); - flags = MSG_ReadByte(); - - br->contents = MSG_ReadLong(); - - //FIXME: as a server, we probably want to reject the brush if we exceed some texnum/memory limitation, so clients can't just spam new textures endlessly. - br->patch->tex = Terr_Brush_FindTexture(hm, MSG_ReadString()); - - br->patch->numcp[0] = MSG_ReadShort(); - br->patch->numcp[1] = MSG_ReadShort(); - br->patch->subdiv[0] = MSG_ReadShort(); - br->patch->subdiv[1] = MSG_ReadShort(); - - for (i = 0; i < br->patch->numcp[0]*br->patch->numcp[1]; i++) - { - vert.v[0] = MSG_ReadFloat(); - vert.v[1] = MSG_ReadFloat(); - vert.v[2] = MSG_ReadFloat(); - vert.tc[0] = MSG_ReadFloat(); - vert.tc[1] = MSG_ReadFloat(); - - vert.rgba[0] = (flags&1)?MSG_ReadFloat():1; - vert.rgba[1] = (flags&2)?MSG_ReadFloat():1; - vert.rgba[2] = (flags&4)?MSG_ReadFloat():1; - vert.rgba[3] = (flags&8)?MSG_ReadFloat():1; - - if (i < maxverts) - br->patch->cp[i] = vert; - } - return i <= maxverts; -} - #ifndef SERVERONLY heightmap_t *CL_BrushEdit_ForceContext(model_t *mod) @@ -6699,45 +6735,36 @@ void CL_Parse_BrushEdit(void) else if (cmd == hmcmd_brush_insert || cmd == hmcmd_patch_insert) //1=create/replace { brushes_t brush; + size_t tempmemsize; hm = CL_BrushEdit_ForceContext(mod); //do this early, to ensure that the textures are correct memset(&brush, 0, sizeof(brush)); - if (cmd == hmcmd_patch_insert) + tempmemsize = Brush_DeserialiseHeader(&brush, (cmd == hmcmd_patch_insert)); + if (!tempmemsize) + Host_EndGame("CL_Parse_BrushEdit: unparsable %s\n", brush.ispatch?"patch":"brush"); + if (!Brush_Deserialise(hm, &brush, alloca(tempmemsize))) + Host_EndGame("CL_Parse_BrushEdit: unparsable %s\n", brush.ispatch?"patch":"brush"); + + if (!ignore) //ignore if we're the server, we should already have it anyway (but might need it for demos, hence why its still sent). { - const unsigned int maxpoints = 64*64; - brush.patch = alloca(sizeof(*brush.patch) + sizeof(*brush.patch->cp)*(maxpoints-countof(brush.patch->cp))); - brush.patch->numcp[0] = 1; - brush.patch->numcp[1] = maxpoints; - if (!Patch_Deserialise(hm, &brush)) - Host_EndGame("CL_Parse_BrushEdit: unparsable patch\n"); - } - else - { - brush.numplanes = 128; - brush.planes = alloca(sizeof(*brush.planes) * brush.numplanes); - brush.faces = alloca(sizeof(*brush.faces) * brush.numplanes); - if (!Brush_Deserialise(hm, &brush)) - Host_EndGame("CL_Parse_BrushEdit: unparsable brush\n"); - } - if (ignore) - return; //ignore if we're the server, we should already have it anyway (but might need it for demos, hence why its still sent). - if (brush.id) - { - int i; - if (cls.demoplayback) - Terr_Brush_DeleteId(hm, brush.id); - else + if (brush.id) { - for (i = 0; i < hm->numbrushes; i++) + int i; + if (cls.demoplayback) + Terr_Brush_DeleteId(hm, brush.id); + else { - brushes_t *br = &hm->wbrushes[i]; - if (br->id == brush.id) - return; //we already have it. assume we just edited it. + for (i = 0; i < hm->numbrushes; i++) + { + brushes_t *br = &hm->wbrushes[i]; + if (br->id == brush.id) + return; //we already have it. assume we just edited it. + } } } + Terr_Brush_Insert(mod, hm, &brush); } - Terr_Brush_Insert(mod, hm, &brush); } else if (cmd == hmcmd_prespawning) { //delete all @@ -6899,31 +6926,22 @@ qboolean SV_Parse_BrushEdit(void) else if (cmd == hmcmd_brush_insert || cmd == hmcmd_patch_insert) { brushes_t brush; + size_t tempmemsize; memset(&brush, 0, sizeof(brush)); - if (cmd == hmcmd_patch_insert) + brush.ispatch = (cmd == hmcmd_patch_insert); + + tempmemsize = Brush_DeserialiseHeader(&brush, cmd == hmcmd_patch_insert); + if (!tempmemsize) { - const unsigned int maxpoints = 64*64; - brush.patch = alloca(sizeof(*brush.patch) + sizeof(*brush.patch->cp)*(maxpoints-countof(brush.patch->cp))); - memset(brush.patch, 0, sizeof(*brush.patch)); - brush.patch->numcp[0] = maxpoints; - brush.patch->numcp[1] = 1; - if (!Patch_Deserialise(hm, &brush)) - { - Con_Printf("SV_Parse_BrushEdit: %s sent an unparsable patch\n", host_client->name); - return false; - } + Con_Printf("SV_Parse_BrushEdit: %s sent an abusive %s\n", host_client->name, brush.ispatch?"patch":"brush"); + return false; } - else + if (!Brush_Deserialise(hm, &brush, alloca(tempmemsize))) { - brush.numplanes = 128; - brush.planes = alloca(sizeof(*brush.planes) * brush.numplanes); - brush.faces = alloca(sizeof(*brush.faces) * brush.numplanes); - if (!Brush_Deserialise(hm, &brush)) - { - Con_Printf("SV_Parse_BrushEdit: %s sent an unparsable brush\n", host_client->name); - return false; - } + Con_Printf("SV_Parse_BrushEdit: %s sent an unparsable brush\n", host_client->name); + return false; } + if (!authorise) { SV_PrintToClient(host_client, PRINT_MEDIUM, "Brush editing ignored: you are not a mapper\n"); @@ -6980,28 +6998,6 @@ qboolean SV_Parse_BrushEdit(void) } #endif -typedef struct -{ - string_t shadername; - vec3_t planenormal; - float planedist; - vec3_t sdir; - float sbias; - vec3_t tdir; - float tbias; -} qcbrushface_t; - -typedef struct -{ - string_t shadername; - unsigned int contents; - unsigned int cp_width; - unsigned int cp_height; - unsigned int subdiv_x; - unsigned int subdiv_y; - vec3_t texinfo; -} qcpatchinfo_t; - static void *validateqcpointer(pubprogfuncs_t *prinst, size_t qcptr, size_t elementsize, size_t elementcount, qboolean allownull) { //make sure that the sizes can't overflow @@ -7066,9 +7062,9 @@ void QCBUILTIN PF_patch_getcp(pubprogfuncs_t *prinst, struct globalvars_s *pr_gl G_INT(OFS_RETURN) = br->patch->numcp[0]*br->patch->numcp[1]; else { - maxverts = min(br->numplanes, maxverts); + maxverts = min(br->patch->numcp[0]*br->patch->numcp[1], maxverts); - for (j = 0; j < br->patch->numcp[0]*br->patch->numcp[1]; j++) + for (j = 0; j < maxverts; j++) { VectorCopy(br->patch->cp[j].v, out_verts->v); Vector2Copy(br->patch->cp[j].tc, out_verts->tc); @@ -7082,7 +7078,43 @@ void QCBUILTIN PF_patch_getcp(pubprogfuncs_t *prinst, struct globalvars_s *pr_gl } } } -// {"patch_getmesh", PF_patch_getmesh, 0, 0, 0, 0, D("int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, __out patchinfo_t out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")}, +// {"patch_evaluate", PF_patch_evaluate, 0, 0, 0, 0, D("int(patchvert_t *in_controlverts, patchvert_t *out_renderverts, int maxout, patchinfo_t *inout_info)", "Calculates the geometry of a hyperthetical patch.")}, +void QCBUILTIN PF_patch_evaluate(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcpatchinfo_t *inout_info = validateqcpointer(prinst, G_INT(OFS_PARM3), sizeof(*inout_info), 1, false); + unsigned int maxverts = G_INT(OFS_PARM2); + qcpatchvert_t *out_verts = validateqcpointer(prinst, G_INT(OFS_PARM1), sizeof(*out_verts), maxverts, true); + qcpatchvert_t *in_cp = validateqcpointer(prinst, G_INT(OFS_PARM0), sizeof(*in_cp), inout_info->cp_width*inout_info->cp_height, false); + + unsigned short numcp[] = {inout_info->cp_width, inout_info->cp_height}, size[2]; + short subdiv[] = {inout_info->subdiv_x, inout_info->subdiv_y}; + patchtessvert_t *working_verts = PatchInfo_Evaluate(in_cp, numcp, subdiv, size); + + unsigned int i; + if (working_verts) + { + if (out_verts) + { + maxverts = min(maxverts, size[0]*size[1]); + for (i = 0; i < maxverts; i++) + { + VectorCopy(working_verts[i].v, out_verts[i].v); + Vector4Copy(working_verts[i].rgba, out_verts[i].rgba); + Vector2Copy(working_verts[i].tc, out_verts[i].tc); + } + } + BZ_Free(working_verts); + + inout_info->cp_width = size[0]; //not really controlpoints, but the data works the same. + inout_info->cp_height = size[1]; + } + else + inout_info->cp_width = inout_info->cp_height = 0; //erk... + inout_info->subdiv_x = inout_info->subdiv_y = 0; //make it as explicit tessellation, so we can maybe skip this. + + G_INT(OFS_RETURN) = maxverts; +} +// {"patch_getmesh", PF_patch_getmesh, 0, 0, 0, 0, D("int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, patchinfo_t *out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")}, void QCBUILTIN PF_patch_getmesh(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { world_t *vmw = prinst->parms->user; @@ -7113,10 +7145,10 @@ void QCBUILTIN PF_patch_getmesh(pubprogfuncs_t *prinst, struct globalvars_s *pr_ if (out_info) { out_info->contents = br->contents; - out_info->cp_width = br->patch->numcp[0]; - out_info->cp_height = br->patch->numcp[1]; - out_info->subdiv_x = br->patch->subdiv[0]; - out_info->subdiv_y = br->patch->subdiv[1]; + out_info->cp_width = br->patch->tesssize[0]; + out_info->cp_height = br->patch->tesssize[1]; + out_info->subdiv_x = 0; + out_info->subdiv_y = 0; out_info->shadername = PR_TempString(prinst, br->patch->tex->shadername); } @@ -7124,9 +7156,9 @@ void QCBUILTIN PF_patch_getmesh(pubprogfuncs_t *prinst, struct globalvars_s *pr_ G_INT(OFS_RETURN) = br->patch->tesssize[0]*br->patch->tesssize[1]; else { - maxverts = min(br->numplanes, maxverts); + maxverts = min(br->patch->tesssize[0]*br->patch->tesssize[1], maxverts); - for (j = 0; j < br->patch->tesssize[0]*br->patch->tesssize[1]; j++) + for (j = 0; j < maxverts; j++) { VectorCopy(br->patch->tessvert[j].v, out_verts->v); Vector2Copy(br->patch->tessvert[j].tc, out_verts->tc); @@ -7408,8 +7440,8 @@ void QCBUILTIN PF_patch_create(pubprogfuncs_t *prinst, struct globalvars_s *pr_g { MSG_WriteByte(&sv.multicast, svcfte_brushedit); MSG_WriteShort(&sv.multicast, modelindex); - MSG_WriteByte(&sv.multicast, hmcmd_brush_insert); - Brush_Serialise(&sv.multicast, nb); + MSG_WriteByte(&sv.multicast, hmcmd_patch_insert); + Patch_Serialise(&sv.multicast, nb); SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0); return; } @@ -7419,8 +7451,8 @@ void QCBUILTIN PF_patch_create(pubprogfuncs_t *prinst, struct globalvars_s *pr_g { MSG_WriteByte(&cls.netchan.message, clcfte_brushedit); MSG_WriteShort(&cls.netchan.message, modelindex); - MSG_WriteByte(&cls.netchan.message, hmcmd_brush_insert); - Brush_Serialise(&cls.netchan.message, nb); + MSG_WriteByte(&cls.netchan.message, hmcmd_patch_insert); + Patch_Serialise(&cls.netchan.message, nb); return; } #endif @@ -7930,6 +7962,7 @@ void Mod_Terrain_Save_f(void) { //warning: brushes are not saved unless its a .map COM_StripExtension(mod->name, fname, sizeof(fname)); + Q_strncatz(fname, mod_modifier, sizeof(fname)); Q_strncatz(fname, ".ent", sizeof(fname)); FS_CreatePath(fname, FS_GAMEONLY); diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c index ff846e4b9..12dc24e1f 100644 --- a/engine/gl/gl_model.c +++ b/engine/gl/gl_model.c @@ -36,7 +36,7 @@ cvar_t mod_loadentfiles = CVAR("sv_loadentfiles", "1"); cvar_t mod_loadentfiles_dir = CVAR("sv_loadentfiles_dir", ""); cvar_t mod_external_vis = CVARD("mod_external_vis", "1", "Attempt to load .vis patches for quake maps, allowing transparent water to work properly."); cvar_t mod_warnmodels = CVARD("mod_warnmodels", "1", "Warn if any models failed to load. Set to 0 if your mod is likely to lack optional models (like its in development)."); //set to 0 for hexen2 and its otherwise-spammy-as-heck demo. -cvar_t mod_litsprites_force = CVARD("mod_litsprites_force", "0", "If set to 1, sprites will be lit according to world lighting (including rtlights), like Tenebrae. Ideally use EF_ADDITIVE or EF_FULLBRIGHT to make emissive sprites instead."); +cvar_t mod_litsprites_force = CVARFD("mod_litsprites_force", "0", CVAR_RENDERERLATCH, "If set to 1, sprites will be lit according to world lighting (including rtlights), like Tenebrae. Ideally use EF_ADDITIVE or EF_FULLBRIGHT to make emissive sprites instead."); cvar_t mod_loadmappackages = CVARD ("mod_loadmappackages", "1", "Load additional content embedded within bsp files."); cvar_t mod_lightscale_broken = CVARFD("mod_lightscale_broken", "0", CVAR_RENDERERLATCH, "When active, replicates a bug from vanilla - the radius of r_dynamic lights is scaled by per-surface texture scale rather than using actual distance."); cvar_t mod_lightpoint_distance = CVARD("mod_lightpoint_distance", "8192", "This is the maximum distance to trace when searching for a ground surface for lighting info on map formats without light more fancy lighting info. Use 2048 for full compat with Quake."); @@ -81,6 +81,7 @@ void Mod_LoadDoomSprite (model_t *mod); #define MAX_MOD_KNOWN 8192 model_t *mod_known; int mod_numknown; +char mod_modifier[MAX_QPATH]; //postfix for ent files extern cvar_t r_loadlits; #ifdef SPECULAR @@ -620,6 +621,17 @@ void Mod_Purge(enum mod_purge_e ptype) } } +void Mod_SetModifier(const char *modifier) +{ + if (!modifier || strlen(modifier) >= sizeof(mod_modifier)) modifier = ""; + if (strcmp(modifier, mod_modifier)) + { //if the modifier changed, force all models to reset. + COM_WorkerFullSync(); //sync all the workers, just in case. + strcpy(mod_modifier, modifier); + Mod_Purge(MP_RESET); //nuke it now + } +} + #ifndef SERVERONLY void Mod_FindCubemaps_f(void); void Mod_Realign_f(void); @@ -1449,7 +1461,7 @@ static const char *Mod_RemapBuggyTexture(const char *name, const qbyte *data, un { if (!strcmp(name, buggytextures[i].oldname)) { - unsigned int sum = Com_BlockChecksum(data, datalen); + unsigned int sum = CalcHashInt(&hash_md4, data, datalen); for (; i < sizeof(buggytextures)/sizeof(buggytextures[0]); i++) { if (strcmp(name, buggytextures[i].oldname)) @@ -2252,11 +2264,13 @@ static void Mod_SaveEntFile_f(void) { Q_snprintfz(fname, sizeof(fname), "maps/%s/%s", mod_loadentfiles_dir.string, mod->name+5); COM_StripExtension(fname, fname, sizeof(fname)); + Q_strncatz(fname, mod_modifier, sizeof(fname)); Q_strncatz(fname, ".ent", sizeof(fname)); } else { COM_StripExtension(mod->name, fname, sizeof(fname)); + Q_strncatz(fname, mod_modifier, sizeof(fname)); Q_strncatz(fname, ".ent", sizeof(fname)); } @@ -2293,6 +2307,7 @@ qboolean Mod_LoadEntitiesBlob(struct model_s *mod, const char *entdata, size_t e { Q_snprintfz(fname, sizeof(fname), "maps/%s/%s", mod_loadentfiles_dir.string, mod->name+5); COM_StripExtension(fname, fname, sizeof(fname)); + Q_strncatz(fname, mod_modifier, sizeof(fname)); Q_strncatz(fname, ".ent", sizeof(fname)); ents = FS_LoadMallocFile(fname, &sz); } @@ -2300,12 +2315,14 @@ qboolean Mod_LoadEntitiesBlob(struct model_s *mod, const char *entdata, size_t e if (mod_loadentfiles.value && !ents) { COM_StripExtension(mod->name, fname, sizeof(fname)); + Q_strncatz(fname, mod_modifier, sizeof(fname)); Q_strncatz(fname, ".ent", sizeof(fname)); ents = FS_LoadMallocFile(fname, &sz); } if (mod_loadentfiles.value && !ents) { //tenebrae compat COM_StripExtension(mod->name, fname, sizeof(fname)); + Q_strncatz(fname, mod_modifier, sizeof(fname)); Q_strncatz(fname, ".edo", sizeof(fname)); ents = FS_LoadMallocFile(fname, &sz); } @@ -3720,6 +3737,12 @@ TRACE(("dbg: Mod_LoadTextures: inittexturedescs\n")); m->nummiptex = LittleLong (m->nummiptex); + if ((1+m->nummiptex)*sizeof(int) > l->filelen) + { + Con_Printf(CON_WARNING "warning: %s contains corrupt texture lump\n", loadmodel->name); + return false; + } + loadmodel->numtextures = m->nummiptex; loadmodel->textures = ZG_Malloc(&loadmodel->memgroup, m->nummiptex * sizeof(*loadmodel->textures)); sizes = alloca(sizeof(*sizes)*m->nummiptex); @@ -5348,7 +5371,28 @@ static qboolean QDECL Mod_LoadBrushModel (model_t *mod, void *buffer, size_t fsi else if (!memcmp(&header.version, BSPVERSION_LONG2)) mod->engineflags |= MDLF_NEEDOVERBRIGHT, subbsp = sb_long2; else if (!memcmp(&header.version, BSPVERSIONHL)) + { + char tmp[64]; mod->fromgame = fg_halflife; + + //special hack to work around blueshit bugs - we need to swap LUMP_ENTITIES and LUMP_PLANES over + if (COM_ParseOut(mod_base + header.lumps[LUMP_PLANES].fileofs, tmp, sizeof(tmp)) && !strcmp(tmp, "{")) + { + COM_ParseOut(mod_base + header.lumps[LUMP_ENTITIES].fileofs, tmp, sizeof(tmp)); + if (strcmp(tmp, "{")) + { + int i; + for (i = 0; i < header.lumps[LUMP_ENTITIES].filelen && i < sizeof(dplane_t); i++) + if (mod_base[header.lumps[LUMP_ENTITIES].fileofs + i] == 0) + { //yeah, looks screwy in the way we expect. swap em over. + lump_t tmp = header.lumps[LUMP_ENTITIES]; + header.lumps[LUMP_ENTITIES] = header.lumps[LUMP_PLANES]; + header.lumps[LUMP_PLANES] = tmp; + break; + } + } + } + } else { Con_Printf (CON_ERROR "Mod_LoadBrushModel: %s has wrong version number (%i)\n", mod->name, i); @@ -5375,7 +5419,7 @@ static qboolean QDECL Mod_LoadBrushModel (model_t *mod, void *buffer, size_t fsi } if (i == LUMP_ENTITIES) continue; - chksum = Com_BlockChecksum(mod_base + header.lumps[i].fileofs, header.lumps[i].filelen); + chksum = CalcHashInt(&hash_md4, mod_base + header.lumps[i].fileofs, header.lumps[i].filelen); mod->checksum ^= chksum; if (i == LUMP_VISIBILITY || i == LUMP_LEAFS || i == LUMP_NODES) diff --git a/engine/gl/gl_shader.c b/engine/gl/gl_shader.c index 4a8b63539..276c5a835 100644 --- a/engine/gl/gl_shader.c +++ b/engine/gl/gl_shader.c @@ -46,6 +46,7 @@ cvar_t r_forceprogramify = CVARAFD("r_forceprogramify", "0", "dpcompat_makeshitu cvar_t dpcompat_nopremulpics = CVARFD("dpcompat_nopremulpics", "0", CVAR_SHADERSYSTEM, "By default FTE uses premultiplied alpha for hud/2d images, while DP does not (which results in halos with low-res content). Unfortunately DDS files would need to be recompressed, resulting in visible issues."); #endif cvar_t r_glsl_precache = CVARFD("r_glsl_precache", "0", CVAR_SHADERSYSTEM, "Force all relevant glsl permutations to load upfront."); +cvar_t r_halfrate = CVARFD("r_halfrate", "0", CVAR_SHADERSYSTEM, "Use half-rate shading (where supported by gpu)."); extern cvar_t r_glsl_offsetmapping_reliefmapping; extern cvar_t r_drawflat; @@ -964,9 +965,17 @@ static void Shader_NoMipMaps (parsestate_t *ps, const char **ptr) static void Shader_Affine (parsestate_t *ps, const char **ptr) { shader_t *shader = ps->s; - shader->flags |= SBITS_AFFINE; + int i; + for (i = 0; i < countof(shader->passes); i++) + shader->passes[i].shaderbits |= SBITS_AFFINE; +} +static void Shader_FullRate (parsestate_t *ps, const char **ptr) +{ + shader_t *shader = ps->s; + int i; + for (i = 0; i < countof(shader->passes); i++) + shader->passes[i].shaderbits |= SBITS_MISC_FULLRATE; } - static void Shader_NoPicMip (parsestate_t *ps, const char **ptr) { @@ -2003,6 +2012,7 @@ static qboolean Shader_LoadPermutations(char *name, program_t *prog, char *scrip for (end = *name?strchr(name+1, '#'):NULL; end && *end; ) { + size_t startoffset=offset; char *start = end+1; end = strchr(start, '#'); if (!end) @@ -2018,11 +2028,26 @@ static qboolean Shader_LoadPermutations(char *name, program_t *prog, char *scrip { if (*start == '=') { + if (offset == startoffset+8) + break; start++; prescript[offset++] = ' '; break; } - prescript[offset++] = toupper(*start++); + if ((*start >='a'&&*start<='z')||(*start >='A'&&*start<='Z')||*start=='_'||(*start >='0'&&*start<='9'&&offset>startoffset+8)) + prescript[offset++] = toupper(*start++); + else + { ///invalid symbol name... + offset = startoffset+8; + prescript[offset] = 0; + break; + } + } + if (offset == startoffset+8) + { ///invalid symbol name... + offset = startoffset; + prescript[offset] = 0; + break; } while (offset < sizeof(prescript) && start < end) prescript[offset++] = toupper(*start++); @@ -2872,6 +2897,7 @@ static shaderkey_t shaderkeys[] = {"deferredlight", Shader_Deferredlight, "fte"}, //(sort = prelight) // {"lpp_light", Shader_Deferredlight, "fte"}, //(sort = prelight) {"affine", Shader_Affine, "fte"}, //some hardware is horribly slow, and can benefit from certain hints. + {"fullrate", Shader_FullRate, "fte"}, //blocks half-rate shading on this surface. {"bemode", Shader_BEMode, "fte"}, @@ -5854,6 +5880,14 @@ done:; } } } + + if (!r_halfrate.ival) + { + for (i = 0; i < s->numpasses; i++) + { + s->passes[i].shaderbits |= SBITS_MISC_FULLRATE; + } + } } /* static void Shader_UpdateRegistration (void) diff --git a/engine/gl/gl_terrain.h b/engine/gl/gl_terrain.h index 5ee185993..279b4b0d3 100644 --- a/engine/gl/gl_terrain.h +++ b/engine/gl/gl_terrain.h @@ -259,13 +259,32 @@ typedef struct brushtex_s struct brushtex_s *next; } brushtex_t; +typedef struct patchtessvert_s +{ + vec3_t v; + vec4_t rgba; + vec2_t tc; +// vec3_t norm; +// vec3_t sdir; +// vec3_t tdir; +} patchtessvert_t; +typedef struct qcpatchvert_s +{ + vec3_t v; + vec4_t rgba; + vec2_t tc; +} qcpatchvert_t; +patchtessvert_t *PatchInfo_Evaluate(const qcpatchvert_t *cp, const unsigned short patch_cp[2], const short subdiv[2], unsigned short *size); +unsigned int PatchInfo_EvaluateIndexes(const unsigned short *size, index_t *out_indexes); + typedef struct { - unsigned int contents; + unsigned int contents; //bitmask unsigned int id; //networked/gamecode id. unsigned int axialplanes; //+x,+y,+z,-x,-y,-z. used for bevel stuff. unsigned int numplanes; unsigned char selected:1; //different shader stuff + unsigned char ispatch:1; //just for parsing really vec4_t *planes; vec3_t mins, maxs; //for optimisation and stuff struct patchdata_s @@ -275,23 +294,10 @@ typedef struct short subdiv[2]; //<0=regular q3 patch, 0=cp-only, >0=fixed-tessellation. unsigned short tesssize[2]; - struct patchtessvert_s - { - vec3_t v; - vec2_t tc; - vec4_t rgba; -// vec3_t norm; -// vec3_t sdir; -// vec3_t tdir; - } *tessvert; //x+(y*tesssize[0]) + patchtessvert_t *tessvert; //x+(y*tesssize[0]) //control points - struct patchcpvert_s - { - vec3_t v; - vec2_t tc; - vec4_t rgba; - } cp[1]; //x+(y*numcp[0]) extends past end of patchdata_s + qcpatchvert_t cp[1]; //x+(y*numcp[0]) extends past end of patchdata_s } *patch; //if this is NULL, then its a regular brush. otherwise its a patch. struct brushface_s { @@ -311,6 +317,28 @@ typedef struct qbyte *lightdata; } *faces; } brushes_t; + +typedef struct +{ + string_t shadername; + vec3_t planenormal; + float planedist; + vec3_t sdir; + float sbias; + vec3_t tdir; + float tbias; +} qcbrushface_t; +typedef struct +{ + string_t shadername; + unsigned int contents; + unsigned int cp_width; + unsigned int cp_height; + unsigned int subdiv_x; + unsigned int subdiv_y; + vec3_t texinfo; +} qcpatchinfo_t; + typedef struct heightmap_s { char path[MAX_QPATH]; diff --git a/engine/gl/gl_videgl.c b/engine/gl/gl_videgl.c index 88bbae836..fdf2fa141 100644 --- a/engine/gl/gl_videgl.c +++ b/engine/gl/gl_videgl.c @@ -182,12 +182,12 @@ qboolean EGL_LoadLibrary(char *driver) #ifndef _WIN32 if (!eslibrary) { - eslibrary = dlopen("libGL.so.1.2", RTLD_NOW|RTLD_GLOBAL); + eslibrary = dlopen("libGL"ARCH_DL_POSTFIX".1.2", RTLD_NOW|RTLD_GLOBAL); if (eslibrary) Sys_Printf("Loaded libGL.so.1.2\n"); } if (!eslibrary) { - eslibrary = dlopen("libGL.so.1", RTLD_NOW|RTLD_GLOBAL); + eslibrary = dlopen("libGL"ARCH_DL_POSTFIX".1", RTLD_NOW|RTLD_GLOBAL); if (eslibrary) Sys_Printf("Loaded libGL.so.1\n"); } if (!eslibrary) diff --git a/engine/gl/gl_vidnt.c b/engine/gl/gl_vidnt.c index 71bcea473..6cfb54ae4 100644 --- a/engine/gl/gl_vidnt.c +++ b/engine/gl/gl_vidnt.c @@ -1562,12 +1562,22 @@ static int GLVID_SetMode (rendererstate_t *info, unsigned char *palette) stat = CreateMainWindow(info, true); if (stat) { + EGLConfig cfg; maindc = GetDC(mainwindow); - stat = EGL_Init (info, palette, EGL_PLATFORM_WIN32, mainwindow, maindc, (EGLNativeWindowType)mainwindow, (EGLNativeDisplayType)maindc); - if (stat) - if (!GL_Init(info, &EGL_Proc)) - return false; + if (!EGL_InitDisplay(info, EGL_PLATFORM_WIN32, maindc, (EGLNativeDisplayType)maindc, &cfg)) + { + Con_Printf("couldn't find suitable EGL config\n"); + return false; + } + if (!EGL_InitWindow(info, EGL_PLATFORM_WIN32, mainwindow, (EGLNativeWindowType)mainwindow, cfg)) + { + Con_Printf("couldn't initialise EGL context\n"); + return false; + } + + if (!GL_Init(info, &EGL_Proc)) + return false; } break; #endif @@ -3369,7 +3379,7 @@ rendererinfo_t eglrendererinfo = GLBE_Init, GLBE_GenBrushModelVBO, GLBE_ClearVBO, - GLBE_UploadAllLightmaps, + GLBE_UpdateLightmaps, GLBE_SelectEntity, GLBE_SelectDLight, GLBE_Scissor, diff --git a/engine/gl/gl_vidsdl.c b/engine/gl/gl_vidsdl.c index 53148e817..9a6ce4004 100644 --- a/engine/gl/gl_vidsdl.c +++ b/engine/gl/gl_vidsdl.c @@ -806,10 +806,7 @@ static qboolean VKVID_Init (rendererstate_t *info, unsigned char *palette) 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 = diff --git a/engine/gl/gl_warp.c b/engine/gl/gl_warp.c index c30f96d0e..1e0a4b4db 100644 --- a/engine/gl/gl_warp.c +++ b/engine/gl/gl_warp.c @@ -74,7 +74,10 @@ void R_SetSky(const char *sky) int i; const char *shadername; extern cvar_t r_skyboxname; - Q_strncpyz(cl.skyname, sky, sizeof(cl.skyname)); + if (sky) + Q_strncpyz(cl.skyname, sky, sizeof(cl.skyname)); + else + sky = cl.skyname; if (qrenderer <= QR_NONE) return; //not ready yet... if (*r_skyboxname.string) //override it with the user's preference @@ -259,7 +262,7 @@ static void R_ForceSky_f(void) } void QDECL R_SkyBox_Changed (struct cvar_s *var, char *oldvalue) { - R_SetSky(var->string); + R_SetSky(NULL); // Shader_NeedReload(false); } diff --git a/engine/gl/glquake.h b/engine/gl/glquake.h index ae71db1e1..f3429a831 100644 --- a/engine/gl/glquake.h +++ b/engine/gl/glquake.h @@ -62,7 +62,7 @@ void ModBrush_LoadGLStuff(void *ctx, void *data, size_t a, size_t b); //data === #define GLdouble GLfloat #else #ifdef _WIN32 //windows might use the standard header filename, but it still requires that we manually include windows.h first. - #ifndef WIN32_BLOATED + #if !defined(WIN32_BLOATED) && !defined(WIN32_LEAN_AND_MEAN) #define WIN32_LEAN_AND_MEAN #endif #include diff --git a/engine/gl/shader.h b/engine/gl/shader.h index 131c9ef9f..902ff9ee7 100644 --- a/engine/gl/shader.h +++ b/engine/gl/shader.h @@ -169,6 +169,7 @@ enum SBITS_TESSELLATION = 0x00100000, SBITS_AFFINE = 0x00200000, + SBITS_MISC_FULLRATE = 0x00400000, //don't use half-rate shading (for text/ui) //provided for the backend to hack about with SBITS_LINES = 0x80000000 @@ -653,7 +654,6 @@ struct shader_s SHADER_HASLIGHTMAP = 1 << 16, SHADER_HASTOPBOTTOM = 1 << 17, SHADER_HASREFLECTCUBE = 1 << 18, //shader has a T_GEN_REFLECTCUBE pass (otherwise we can skip surf envmaps for better batching) -// SHADER_STATICDATA = 1 << 18, //set if true: no deforms, no tcgen, rgbgen=identitylighting, alphagen=identity, tmu0=st + tmu1=lm(if available) for every pass, no norms SHADER_HASREFLECT = 1 << 19, //says that we need to generate a reflection image first SHADER_HASREFRACT = 1 << 20, //says that we need to generate a refraction image first SHADER_HASREFRACTDEPTH = 1 << 21, //refraction generation needs to generate a depth texture too. diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c index 04cf9a053..528468e3a 100644 --- a/engine/http/httpclient.c +++ b/engine/http/httpclient.c @@ -1570,7 +1570,10 @@ void DL_Close(struct dl_download *dl) #ifdef MULTITHREAD dl->threadenable = false; if (dl->threadctx) + { Sys_WaitOnThread(dl->threadctx); + dl->threadctx = NULL; + } #endif if (dl->file && dl->file->seekstyle < SS_PIPE) VFS_SEEK(dl->file, 0); diff --git a/engine/http/httpserver.c b/engine/http/httpserver.c index 42e29ccd9..74c7df054 100644 --- a/engine/http/httpserver.c +++ b/engine/http/httpserver.c @@ -305,7 +305,7 @@ const char *HTTP_RunClient (HTTP_active_connections_t *cl) char *content; char *msg, *nl; char buf2[2560]; //short lived temp buffer. - char resource[2560]; + char resource[2560], *args; char host[256]; char mode[80]; qboolean hostspecified; @@ -389,6 +389,10 @@ const char *HTTP_RunClient (HTTP_active_connections_t *cl) } } + args = strchr(resource, '?'); + if (args) + *args++=0; + if (!strcmp(resource, "/")) strcpy(resource, "/index.html"); diff --git a/engine/http/iweb.h b/engine/http/iweb.h index 31e7bf5f1..00c900dbe 100644 --- a/engine/http/iweb.h +++ b/engine/http/iweb.h @@ -136,7 +136,7 @@ struct dl_download /*not used internally by the backend, but used by HTTP_CL_Get/thread wrapper*/ struct dl_download *next; qboolean (*notifystarted) (struct dl_download *dl, char *mimetype); //mime can be null for some protocols, read dl->totalsize for size. false if the mime just isn't acceptable. - void (*notifycomplete) (struct dl_download *dl); + void (*notifycomplete) (struct dl_download *dl); //lets the requester know that the download context is complete and the handle is no longer valid. }; vfsfile_t *VFSPIPE_Open(int refs, qboolean seekable); //refs should be 1 or 2, to say how many times it must be closed before its actually closed, so both ends can close separately diff --git a/engine/http/iwebiface.c b/engine/http/iwebiface.c index 8b0051fcd..681001be7 100644 --- a/engine/http/iwebiface.c +++ b/engine/http/iwebiface.c @@ -265,10 +265,7 @@ void PrepareStun(int epfd, int reportport) #if 0 char *stunserver = "localhost"; int stunport = 27500; -#elif 1 - char *stunserver = "stun.l.google.com"; - int stunport = 19302; -#else +#else //sorry about hardcoding a server, but probably few people are gonna care enough. char *stunserver = "master.frag-net.com"; int stunport = 27950; #endif diff --git a/engine/qclib/pr_comp.h b/engine/qclib/pr_comp.h index 47e93f6c3..9200d1587 100644 --- a/engine/qclib/pr_comp.h +++ b/engine/qclib/pr_comp.h @@ -558,6 +558,8 @@ enum qcop_e { OP_LSHIFT_DI, OP_RSHIFT_DI, + OP_WSTATE, //for the 'w' part of CWSTATE. will probably never be used, but hey, hexen2... + //special/fake opcodes used by the decompiler. OPD_GOTO_FORSTART, OPD_GOTO_WHILE1, diff --git a/engine/qclib/qcc.h b/engine/qclib/qcc.h index d914ec83d..78f907f68 100644 --- a/engine/qclib/qcc.h +++ b/engine/qclib/qcc.h @@ -944,6 +944,7 @@ enum { ERR_BADPLUSPLUSOPERATOR, ERR_BADNOTTYPE, ERR_BADTYPECAST, + ERR_BADMEMBER, ERR_MULTIPLEDEFAULTS, ERR_CASENOTIMMEDIATE, ERR_BADSWITCHTYPE, diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index 42e8ed6d8..234adc004 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -890,6 +890,8 @@ QCC_opcode_t pr_opcodes[] = {7, "<<", "LSHIFT_DI", PC_SHIFT, ASSOC_LEFT, &type_double, &type_integer, &type_double, OPF_STD}, {7, ">>", "RSHIFT_DI", PC_SHIFT, ASSOC_LEFT, &type_double, &type_integer, &type_double, OPF_STD}, +{7, "", "WSTATE", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_void}, + {0, NULL, "OPD_GOTO_FORSTART"}, {0, NULL, "OPD_GOTO_WHILE1"}, @@ -968,6 +970,7 @@ static int OpAssignsCount(unsigned int op) case OP_CALL8H: return 0; //also, eep. case OP_STATE: + case OP_WSTATE: case OP_CSTATE: case OP_CWSTATE: case OP_THINKTIME: @@ -2253,7 +2256,7 @@ void QCC_FreeTemp(QCC_sref_t t) if (t.sym && t.sym->symbolheader) { if (--t.sym->symbolheader->refcount < 0) - QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: over-freed refcount to %s", t.sym->name); + QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: over-freed refcount to %s", QCC_VarAtOffset(t)); } } @@ -2277,7 +2280,7 @@ static void QCC_UnFreeTemp(QCC_sref_t t) if (t.sym && t.sym->symbolheader) { if (!t.sym->symbolheader->refcount++) - QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: %s+%i@%i was already fully freed.", t.sym->name, t.ofs, t.sym->ofs); + QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: %s+%i@%i was already fully freed.", QCC_VarAtOffset(t), t.ofs, t.sym->ofs); } } @@ -3625,21 +3628,56 @@ QCC_sref_t QCC_PR_StatementFlags ( QCC_opcode_t *op, QCC_sref_t var_a, QCC_sref_ QCC_sref_t fldthink = QCC_PR_GetSRef(QCC_PR_FieldType(type_function), "think", NULL, true, 0, false); QCC_sref_t fldnextthink = QCC_PR_GetSRef(type_floatfield, "nextthink", NULL, true, 0, false); + QCC_UnFreeTemp(self); + QCC_UnFreeTemp(self); + //self.frame = var_a; QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldframe, fldframe.cast->aux_type, - true), var_a, false, false); + false), var_a, false, false); //self.think = var_b; QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldthink, fldthink.cast->aux_type, - true), var_b, false, false); + false), var_b, false, false); //self.frame = time + interval; time = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], time, QCC_MakeFloatConst(1/qcc_framerate), NULL); QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldnextthink, fldnextthink.cast->aux_type, - true), time, false, false); + false), time, false, false); + return nullsref; + } + break; + case OP_WSTATE: + { //there is no normal opcode. + QCC_ref_t tempref; + QCC_sref_t self = QCC_PR_GetSRef(type_entity, "self", NULL, true, 0, false); + QCC_sref_t time = QCC_PR_GetSRef(type_float, "time", NULL, true, 0, false); + QCC_sref_t fldframe = QCC_PR_GetSRef(type_floatfield, "weaponframe", NULL, true, 0, false); + QCC_sref_t fldthink = QCC_PR_GetSRef(QCC_PR_FieldType(type_function), "think", NULL, true, 0, false); + QCC_sref_t fldnextthink = QCC_PR_GetSRef(type_floatfield, "nextthink", NULL, true, 0, false); + + float framerate = (qcc_framerate>0)?qcc_framerate:(qcc_targetformat_ishexen2()?20:10); + + QCC_UnFreeTemp(self); + QCC_UnFreeTemp(self); + + //self.frame = var_a; + QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, + fldframe, fldframe.cast->aux_type, + false), var_a, false, false); + + //self.think = var_b; + QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, + fldthink, fldthink.cast->aux_type, + false), var_b, false, false); + + //self.frame = time + interval; + time = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], time, QCC_MakeFloatConst(1/framerate), NULL); + QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, + fldnextthink, fldnextthink.cast->aux_type, + false), time, false, false); return nullsref; } break; @@ -4562,6 +4600,12 @@ QCC_sref_t QCC_PR_StatementFlags ( QCC_opcode_t *op, QCC_sref_t var_a, QCC_sref_ return var_c; } break; + case OP_BITNOT_I64: + op = &pr_opcodes[OP_SUB_I64]; + var_b = var_a; + var_a = QCC_MakeInt64Const(~(longlong)0); + var_a.sym->referenced = true; + break; case OP_BITNOT_F: op = &pr_opcodes[OP_SUB_F]; var_b = var_a; @@ -5867,7 +5911,7 @@ static void QCC_PrecacheFile (const char *n, int ch) static void QCC_VerifyFormatString (const char *funcname, QCC_ref_t **arglist, unsigned int argcount) { - const char *s = "%s"; + const char *s = "%s", *reqtype; int firstarg = 1; const char *s0; char *err; @@ -6034,6 +6078,7 @@ noflags: //case 'll': //long long case 'l': isfloat = 0; break; //long case 'L': isfloat = 0; break; //long double + //case 'q': break; //long long in c case 'j': //[u]intmax_t case 'z': //size_t case 't': //ptrdiff_t @@ -6071,6 +6116,7 @@ nolength: memcpy(formatbuf, s0, s+1-s0); formatbuf[s+1-s0] = 0; + reqtype = NULL; switch(*s) { //fixme: should we validate char ranges? @@ -6085,9 +6131,7 @@ nolength: case ev_variant: break; default: - { - QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires float at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); - } + reqtype = "float"; break; } } @@ -6101,7 +6145,7 @@ nolength: case ev_variant: break; default: - QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires pointer at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); + reqtype = "pointer"; break; } } @@ -6118,6 +6162,7 @@ nolength: break; //fallthrough default: + reqtype = "int"; QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires int at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); break; } @@ -6133,12 +6178,12 @@ nolength: case ev_variant: break; default: - QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires vector at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); + reqtype = "vector"; break; } } else - QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires intvector at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); + reqtype = "intvector"; break; case 's': case 'S': @@ -6148,7 +6193,7 @@ nolength: case ev_variant: break; default: - QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires string at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); + reqtype = "string"; break; } break; @@ -6156,6 +6201,52 @@ nolength: QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: invalid format string: %s%s%s", funcname, col_name, s0, col_none); return; } + + if (reqtype) + { + QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires %s at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, reqtype, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); + switch(ARGCTYPE(thisarg)->type) + { + case ev_string: + QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %s or %S for strings"); + break; + case ev_float: + QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %g or %f or %hx for floats"); + break; + case ev_vector: + QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %v for vectors"); + break; + case ev_entity: + QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %i for entities"); + break; + case ev_pointer: + QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %p for pointer types"); + break; + case ev_integer: + QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %i or %lx for 32bit ints"); + break; + case ev_uint: + QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %lu or or %lx for 32bit ints"); + break; + + case ev_void: //coder's problem + case ev_field: //cast to int + case ev_function: //cast to int + case ev_variant: //should be accepted by anything... + case ev_int64: //specification problem + case ev_uint64: //specification problem + case ev_double: //specification problem + case ev_struct: //coder's problem + case ev_union: //coder's problem + case ev_accessor: //should be unreachable + case ev_enum: //should be unreachable + case ev_typedef: //should be unreachable + case ev_boolean: //should be unreachable + QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "cast to something else"); + break; + } + } + s++; break; default: @@ -8925,9 +9016,9 @@ static QCC_ref_t *QCC_PR_ParseField(QCC_ref_t *refbuf, QCC_ref_t *lhs) return refbuf; } if (t->parentclass) - QCC_PR_ParseError(ERR_INTERNAL, "%s is not a field of class %s", QCC_GetSRefName(QCC_RefToDef(field, false)), t->name); + QCC_PR_ParseError(ERR_BADMEMBER, "%s is not a field of class %s", QCC_GetSRefName(QCC_RefToDef(field, false)), t->name); else - QCC_PR_ParseError(ERR_INTERNAL, "%s is not a field", QCC_GetSRefName(QCC_RefToDef(field, false))); + QCC_PR_ParseError(ERR_BADMEMBER, "%s is not a field", QCC_GetSRefName(QCC_RefToDef(field, false))); } lhs = QCC_PR_ParseField(refbuf, lhs); @@ -8935,7 +9026,12 @@ static QCC_ref_t *QCC_PR_ParseField(QCC_ref_t *refbuf, QCC_ref_t *lhs) lhs = QCC_PR_ParseRefArrayPointer (refbuf, lhs, false, false); } else - QCC_PR_ParseError(ERR_INTERNAL, "%s is not a member of %s", QCC_PR_ParseName(), t->name); + { + QCC_PR_ParseWarning(ERR_BADMEMBER, "%s is not a member of %s", QCC_PR_ParseName(), t->name); + if (t->filen) + QCC_PR_Note(ERR_BADMEMBER, t->filen, t->line, "%s is defined here", t->name); + return QCC_PR_BuildRef(refbuf, REF_GLOBAL, QCC_MakeIntConst(0), nullsref, type_void, false); + } } else if (flag_qccx && t->type == ev_entity && QCC_PR_CheckToken("[")) { //p[%0] gives a regular array reference. except that p is probably a float, and we're expecting OP_LOAD_F @@ -9345,7 +9441,11 @@ fieldarrayindex: return QCC_PR_ParseRefArrayPointer(retbuf, r, allowarrayassign, makearraypointers); } } - QCC_PR_ParseError(0, "%s is not a member of %s", mname, tname); + + QCC_PR_ParseWarning(ERR_BADMEMBER, "%s is not a member of %s", mname, t->name); + if (t->filen) + QCC_PR_Note(ERR_BADMEMBER, t->filen, t->line, "%s is defined here", t->name); + QCC_PR_ParseError(ERR_BADMEMBER, NULL); } if (!ofs && idx.cast) ; @@ -14427,77 +14527,145 @@ void QCC_PR_ParseState (void) { QCC_sref_t s1, def; + pbool isinc; + //FIXME: this is ambiguous with pre-inc and post-inc logic. - if (QCC_PR_CheckToken("++") || QCC_PR_CheckToken("--")) + if ((isinc=QCC_PR_CheckToken("++")) || QCC_PR_CheckToken("--")) { - s1 = QCC_PR_ParseImmediate (); + const QCC_eval_t *first, *last; + int dir = 0; + int op = OP_CSTATE; + if (QCC_PR_CheckToken("(")) + { + op = OP_CWSTATE; + if (!QCC_PR_CheckToken("w")) + QCC_PR_Expect("W"); + QCC_PR_Expect(")"); + } + +// s1 = QCC_PR_ParseImmediate (); + + s1 = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); + s1 = QCC_SupplyConversion(s1, ev_float, true); + QCC_PR_Expect(".."); - def = QCC_PR_ParseImmediate (); +// def = QCC_PR_ParseImmediate (); + def = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); + def = QCC_SupplyConversion(def, ev_float, true); QCC_PR_Expect ("]"); if (s1.cast->type != ev_float || def.cast->type != ev_float) QCC_PR_ParseError(ERR_STATETYPEMISMATCH, "state type mismatch"); + first = QCC_SRef_EvalConst(s1); + last = QCC_SRef_EvalConst(def); + if (first&&last) + { //whether its a ++ or -- doesn't really matter, but hcc generates an error so we should at least generate a warning. + dir = (last->_float >= first->_float)?1:-1; + if (isinc) + { + if (first->_float > last->_float) + QCC_PR_ParseWarning(ERR_STATETYPEMISMATCH, "Forwards State Cycle with backwards range"); + } + else + { + if (first->_float < last->_float) + QCC_PR_ParseWarning(ERR_STATETYPEMISMATCH, "Forwards State Cycle with backwards range"); + } + } - if (QCC_OPCodeValid(&pr_opcodes[OP_CSTATE])) - QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_CSTATE], s1, def, NULL)); + + if (QCC_OPCodeValid(&pr_opcodes[op])) + QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[op], s1, def, NULL)); else { - QCC_statement_t *patch1, *entercyc, *fwd, *back; + QCC_statement_t *patch1, *entercycf, *entercycb, *fwd, *back; QCC_sref_t t1, t2; QCC_sref_t framef, frame; QCC_sref_t self; QCC_sref_t cycle_wrapped; self = QCC_PR_GetSRef(type_entity, "self", NULL, false, 0, false); - framef = QCC_PR_GetSRef(NULL, "frame", NULL, false, 0, false); + framef = QCC_PR_GetSRef(NULL, (op==OP_CWSTATE)?"weaponframe":"frame", NULL, false, 0, false); cycle_wrapped = QCC_PR_GetSRef(type_float, "cycle_wrapped", NULL, false, 0, false); - frame = QCC_PR_Statement(&pr_opcodes[OP_LOAD_F], self, framef, NULL); + frame = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_F], self, framef, NULL, 0); if (cycle_wrapped.cast) - QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(0), cycle_wrapped, NULL)); + QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(0), cycle_wrapped, NULL, STFL_PRESERVEB)); - //make sure the frame is within the bounds given. - t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], frame, s1, NULL, STFL_PRESERVEA); - t2 = QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], frame, def, NULL, STFL_PRESERVEA); - t1 = QCC_PR_Statement(&pr_opcodes[OP_OR_F], t1, t2, NULL); - patch1 = QCC_Generate_OP_IFNOT(t1, false); - QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEB)); - entercyc = QCC_Generate_OP_GOTO(); - patch1->b.ofs = &statements[numstatements] - patch1; - - t1 = QCC_PR_Statement(&pr_opcodes[OP_GE_F], def, s1, NULL); - fwd = QCC_Generate_OP_IFNOT(t1, false); //this block is the 'it's in a forwards direction' + if (dir) + fwd = NULL; //can skip the checks + else { + t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_GE_F], def, s1, NULL, STFL_PRESERVEA|STFL_PRESERVEB); + fwd = QCC_Generate_OP_IFNOT(t1, false); + } + if (dir >= 0) + { //this block is the 'it's in a forwards direction' + //make sure the frame is within the bounds given. + t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], frame, s1, NULL, STFL_PRESERVEA|STFL_PRESERVEB); + t2 = QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], frame, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB); + t1 = QCC_PR_Statement(&pr_opcodes[OP_OR_F], t1, t2, NULL); + patch1 = QCC_Generate_OP_IFNOT(t1, false); + { + QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); + entercycf = QCC_Generate_OP_GOTO(); + } + patch1->b.ofs = &statements[numstatements] - patch1; + QCC_PR_SimpleStatement(&pr_opcodes[OP_ADD_F], frame, QCC_MakeFloatConst(1), frame, false); - t1 = QCC_PR_Statement(&pr_opcodes[OP_GT_F], frame, def, NULL); + t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], frame, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB); + patch1 = QCC_Generate_OP_IFNOT(t1, false); + { + QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); + if (cycle_wrapped.cast) + QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(1), cycle_wrapped, NULL, STFL_PRESERVEB)); + } + patch1->b.ofs = &statements[numstatements] - patch1; + } + else entercycf = NULL; + if (fwd) + { + back = QCC_Generate_OP_GOTO(); + fwd->b.ofs = &statements[numstatements] - fwd; + } + else + back = NULL; + if (dir <= 0) + { //reverse animation. + + //make sure the frame is within the bounds given. + t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], frame, s1, NULL, STFL_PRESERVEA|STFL_PRESERVEB); + t2 = QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], frame, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB); + t1 = QCC_PR_Statement(&pr_opcodes[OP_OR_F], t1, t2, NULL); + patch1 = QCC_Generate_OP_IFNOT(t1, false); + { + QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); + entercycb = QCC_Generate_OP_GOTO(); + } + patch1->b.ofs = &statements[numstatements] - patch1; + + QCC_PR_SimpleStatement(&pr_opcodes[OP_SUB_F], frame, QCC_MakeFloatConst(1), frame, false); + t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], frame, def, NULL, STFL_PRESERVEA); patch1 = QCC_Generate_OP_IFNOT(t1, false); { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEB)); if (cycle_wrapped.cast) - QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(1), cycle_wrapped, NULL)); + QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(1), cycle_wrapped, NULL, 0)); } patch1->b.ofs = &statements[numstatements] - patch1; } - back = QCC_Generate_OP_GOTO(); - fwd->b.ofs = &statements[numstatements] - fwd; - { - //reverse animation. - QCC_PR_SimpleStatement(&pr_opcodes[OP_SUB_F], frame, QCC_MakeFloatConst(1), frame, false); - t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], frame, s1, NULL, STFL_PRESERVEA); - patch1 = QCC_Generate_OP_IFNOT(t1, false); - { - QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], def, frame, NULL, STFL_PRESERVEB)); - if (cycle_wrapped.cast) - QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(1), cycle_wrapped, NULL)); - } - patch1->b.ofs = &statements[numstatements] - patch1; - } - back->b.ofs = &statements[numstatements] - back; + else entercycb = NULL; - /*out of range*/entercyc->b.ofs = &statements[numstatements] - entercyc; + if (back) + back->a.ofs = &statements[numstatements] - back; + + if (entercycf) + /*out of range*/entercycf->a.ofs = &statements[numstatements] - entercycf; + if (entercycb) + /*out of range*/entercycb->a.ofs = &statements[numstatements] - entercycb; //self.frame = frame happens with the normal state opcode. - QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_STATE], frame, QCC_MakeSRef(pr_scope->def, 0, pr_scope->type), NULL)); + QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[(op==OP_CWSTATE)?OP_WSTATE:OP_STATE], frame, QCC_MakeSRef(pr_scope->def, 0, pr_scope->type), NULL)); } return; } diff --git a/engine/qclib/qcc_pr_lex.c b/engine/qclib/qcc_pr_lex.c index f51d00ab2..e5624f29e 100644 --- a/engine/qclib/qcc_pr_lex.c +++ b/engine/qclib/qcc_pr_lex.c @@ -1139,6 +1139,8 @@ static pbool QCC_PR_Precompiler(void) else if (!QC_strcasecmp(qcc_token, "framerate")) { qcc_framerate = atof(msg); + if (qcc_framerate < 0) + qcc_framerate = 0; } else if (!QC_strcasecmp(qcc_token, "once")) { @@ -4072,11 +4074,14 @@ NORETURN void VARGS QCC_PR_ParseError (int errortype, const char *error, ...) editbadfile(s_filen, pr_source_line); #endif - QCC_PR_PrintScope(); - if (flag_msvcstyle) - externs->Printf ("%s%s(%i) : %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); - else - externs->Printf ("%s%s:%i: %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); + if (error) + { + QCC_PR_PrintScope(); + if (flag_msvcstyle) + externs->Printf ("%s%s(%i) : %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); + else + externs->Printf ("%s%s:%i: %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); + } longjmp (pr_parse_abort, 1); } @@ -5826,11 +5831,13 @@ QCC_type_t *QCC_PR_ParseType (int newtype, pbool silentfail) //swap the class out for the appropriate function type... newparm = QCC_PR_GenFunctionType(type_void, basetype->params, basetype->num_parms); parmname = classname; + arraysize = 0; } else if (!flag_qcfuncs && basetype == newt && QCC_PR_CheckToken("(")) { newparm = QCC_PR_ParseFunctionType(false, type_void); parmname = classname; + arraysize = 0; } else { @@ -6095,6 +6102,50 @@ QCC_type_t *QCC_PR_ParseType (int newtype, pbool silentfail) } } } + + + /* iterate over every parent-class and see if our method already exists in conflicting nonvirtual type form */ + if (isvirt) + { + for(pc = newt; pc && !found; pc = pc->parentclass) + { + struct QCC_typeparam_s *pp; + int numpc; + int i; + found = false; + + if (pc == newt) + continue; + + pp = parms; + numpc = numparms; + + /* iterate over all of the virtual methods */ + for (i = 0; i < numpc; i++) + { + if (pp[i].type->type == newparm->type) + { + /* if we found it, abandon this loop - we still have to check the other classes however */ + if (!strcmp(pp[i].paramname, parmname)) + { + found = true; + break; + } + } + } + + /* we didn't find it as a field... so check if it exists as a nonvirtual method */ + if (found == false) + { + QC_snprintfz(membername, sizeof(membername), "%s::%s", pc->name, parmname); + + /* if we found it, game over */ + if (QCC_PR_GetDef(NULL, membername, NULL, false, 0, 0)) + QCC_PR_ParseError(0, "%s defined as virtual in %s, but nonvirtual in %s\n", parmname, newt->name, pc->name); + } + } + } + parms[numparms].ofs = basicindex; //ulp, its new numparms++; diff --git a/engine/qclib/qccmain.c b/engine/qclib/qccmain.c index 04144cc15..9bea53826 100644 --- a/engine/qclib/qccmain.c +++ b/engine/qclib/qccmain.c @@ -4782,6 +4782,10 @@ static void QCC_PR_CommandLinePrecompilerOptions (void) else QCC_PR_Warning(WARN_BADPARAMS, "cmdline", 0, "Unrecognised std parameter (%s)", myargv[i]); } + else if (!strnicmp(myargv[i], "-state-fps=", 11)) + { + qcc_framerate = atof(myargv[i]+11); + } else if ( !strnicmp(myargv[i], "-F", 2) || WINDOWSARG(!strnicmp(myargv[i], "/F", 2)) ) { pbool state; @@ -4816,6 +4820,13 @@ static void QCC_PR_CommandLinePrecompilerOptions (void) flag_ifstring = state; else if (!stricmp(arg, "true-empty-strings")) flag_brokenifstring = state; + else if (!stricmp(arg, "emulate-state")) + { + if (qcc_framerate>0 && state) + ;//already on, don't force if they already gave it an actual rate. + else + qcc_framerate = state?10:0; + } else if (!stricmp(arg, "arithmetic-exceptions")) qccwarningaction[WARN_DIVISIONBY0] = state?WA_ERROR:WA_IGNORE; else if (!stricmp(arg, "lno")) diff --git a/engine/qclib/qcctui.c b/engine/qclib/qcctui.c index c7879557c..fd15effed 100644 --- a/engine/qclib/qcctui.c +++ b/engine/qclib/qcctui.c @@ -120,7 +120,16 @@ static void QCC_FileList(const char *name, const void *compdata, size_t compsize { totalsize += plainsize; filecount += 1; - if (!method && compsize==plainsize) + if (method < 0) + { + if (method == -1-9) + externs->Printf("%s%8u DF64 %s%s\n", col_error, (unsigned)plainsize, name, col_none); + else if (method == -1) //general error + externs->Printf("%s%8u ERR %s%s\n", col_error, (unsigned)plainsize, name, col_none); + else + externs->Printf("%s%8u m%-3i %s%s\n", col_error, (unsigned)plainsize, -1-method, name, col_none); + } + else if (!method && compsize==plainsize) externs->Printf("%8u %s\n", (unsigned)plainsize, name); else externs->Printf("%8u %3u%% %s\n", (unsigned)plainsize, plainsize?(unsigned)((100*compsize)/plainsize):100u, name); @@ -191,10 +200,13 @@ int qcc_wildcmp(const char *wild, const char *string) -static const char *extractonly; -static pbool extractonlyfound; +static const char *extractonly; //the file we're looking for +static pbool extractonlyfound; //for errors. +static pbool extractecho; //print the file to stdout instead of writing it. static void QCC_FileExtract(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) { + if (method < 0) + return; //QC_decode will fail. provided for enumeration reasons. if (extractonly) { const char *sl = strrchr(extractonly, '/'); @@ -216,11 +228,19 @@ static void QCC_FileExtract(const char *name, const void *compdata, size_t comps void *buffer = malloc(plainsize); if (buffer && QC_decode(progfuncs, compsize, plainsize, method, compdata, buffer)) { - QCC_Mkdir(name); - if (!QCC_WriteFile(name, buffer, plainsize)) - externs->Printf(" write failure\n"); + if (extractecho) + { + externs->Printf("\n"); + fwrite(buffer, 1, plainsize, stdout); + } else - externs->Printf(" done\n"); + { + QCC_Mkdir(name); + if (!QCC_WriteFile(name, buffer, plainsize)) + externs->Printf(" write failure\n"); + else + externs->Printf(" done\n"); + } } else externs->Printf(" read failure\n"); @@ -253,6 +273,7 @@ int main (int argc, const char **argv) pbool writelog = false; //other systems are sane. #endif int colours = 2; //auto + int ziparg = -1; progexterns_t ext; progfuncs_t funcs; progfuncs = &funcs; @@ -265,52 +286,6 @@ int main (int argc, const char **argv) funcs.funcs.parms->Printf = logprintf; funcs.funcs.parms->Sys_Error = Sys_Error; - if ((argc == 3 && !strcmp(argv[1], "-l")) || (argc >= 3 && !strcmp(argv[1], "-x"))) - { - size_t blobsize; - void *blob = QCC_ReadFile(argv[2], NULL, NULL, &blobsize, false); - if (!blob) - { - logprintf("Unable to read %s\n", argv[2]); - return EXIT_FAILURE; - } - if (argc > 3) - { - for (i = 3; i < argc; i++) - { - extractonly = argv[i]; - extractonlyfound = false; - QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileExtract); - if (!extractonlyfound) - externs->Printf("Unable to find file %s\n", extractonly); - } - extractonly = NULL; - } - else if (argv[1][1] == 'x') - QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileExtract); - else - { - QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileList); - externs->Printf("Total size %u bytes, %u files\n", (unsigned)totalsize, (unsigned)filecount); - } - free(blob); - return EXIT_SUCCESS; - } - if (argc == 3 && (!strncmp(argv[1], "-z", 2) || !strcmp(argv[1], "-0") || !strcmp(argv[1], "-9"))) - { //exe -0 foo.pk3dir - enum pkgtype_e t; - if (argv[1][1] == '9') - t = PACKAGER_PK3; - else if (argv[1][1] == '0') - t = PACKAGER_PAK; //not really any difference but oh well - else - t = PACKAGER_PK3_SPANNED; - - if (Packager_CompressDir(argv[2], t, QCC_PR_PackagerMessage, NULL)) - return EXIT_SUCCESS; - else - return EXIT_FAILURE; - } for (i = 0; i < argc; i++) { if (!argv[i]) @@ -327,7 +302,20 @@ int main (int argc, const char **argv) colours = 0; else if (!strcmp(argv[i], "--color=auto")) colours = 2; + else if (!strcmp(argv[i], "-l") || + !strcmp(argv[i], "-x") || + !strcmp(argv[i], "-p") || + !strcmp(argv[i], "-z") || + !strcmp(argv[i], "-0") || + !strcmp(argv[i], "-9")) + { + ziparg = i; + break; //other args are all filenames. don't misinterpret stuff. + } } + + for (i = 0; i < COL_MAX; i++) + qcccol[i] = ""; #if defined(__linux__) || defined(__unix__) if (colours == 2) colours = isatty(STDOUT_FILENO); @@ -346,6 +334,86 @@ int main (int argc, const char **argv) (void)colours; #endif + if (ziparg >= 0) + { + if (ziparg+1 >= argc) + { + logprintf("archive name not specified\n"); + return EXIT_FAILURE; + } + switch(argv[ziparg][1]) + { + case 'l': //list all files. + { + size_t blobsize; + void *blob = QCC_ReadFile(argv[ziparg+1], NULL, NULL, &blobsize, false); + if (blob) + { + QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileList); + externs->Printf("Total size %lu bytes, %u files\n", (unsigned long)totalsize, (unsigned)filecount); + free(blob); + return EXIT_SUCCESS; + } + logprintf("Unable to read %s\n", argv[ziparg+1]); + } + break; + case 'p': //print (named) files to stdout. + extractecho = true; + //fall through + case 'x': //extract (named) files to working directory. + { //list/extract/view + size_t blobsize; + void *blob = QCC_ReadFile(argv[ziparg+1], NULL, NULL, &blobsize, false); + int ret = EXIT_FAILURE; + if (!blob) + logprintf("Unable to read %s\n", argv[ziparg+1]); + else if (ziparg+2 < argc) + { + for (i = ziparg+2; i < argc; i++) + { + extractonly = argv[i]; + extractonlyfound = false; + QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileExtract); + if (!extractonlyfound) + externs->Printf("Unable to find file %s\n", extractonly); + else + ret = EXIT_SUCCESS; + } + extractonly = NULL; + } + else + { + QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileExtract); + ret = EXIT_SUCCESS; + } + free(blob); + return ret; + } + case 'z': //fancy spanned stuff + case '0': //store-only (pak) + case '9': //best compression (pk3) + + { //exe -0 foo.pk3dir + enum pkgtype_e t; + if (argv[1][1] == '9') + t = PACKAGER_PK3; + else if (argv[1][1] == '0') + t = PACKAGER_PAK; //not really any difference but oh well + else + t = PACKAGER_PK3_SPANNED; + + if (Packager_CompressDir(argv[ziparg+1], t, QCC_PR_PackagerMessage, NULL)) + return EXIT_SUCCESS; + } + break; + default: + //should be unreachable. + break; + } + return EXIT_FAILURE; + } + + logfile = writelog?fopen("fteqcc.log", "wt"):false; if (logfile) diff --git a/engine/qclib/qcd_main.c b/engine/qclib/qcd_main.c index ea8ea0ff3..5b731de01 100644 --- a/engine/qclib/qcd_main.c +++ b/engine/qclib/qcd_main.c @@ -41,6 +41,44 @@ pbool QC_decodeMethodSupported(int method) return false; } + +#ifdef ZLIB_DEFLATE64 +#include "infback9.h" //an obscure compile-your-own part of zlib. +struct def64ctx +{ + const char *in; + char *out; + size_t csize; + size_t usize; + + char window[65536]; +}; +static unsigned int QC_Deflate64_Grab(void *vctx, unsigned char **bufptr) +{ + struct def64ctx *ctx = vctx; + + unsigned int avail = ctx->csize; + *bufptr = (unsigned char *)ctx->in; + ctx->csize = 0; + ctx->in += avail; + + return avail; +} +static int QC_Deflate64_Spew(void *vctx, unsigned char *buf, unsigned int buflen) +{ + struct def64ctx *ctx = vctx; + + if (buflen > ctx->usize) + return 1; //over the size of our buffer... + memcpy(ctx->out, buf, buflen); + ctx->out += buflen; + ctx->usize -= buflen; + return 0; +} +#endif + + + char *QC_decode(progfuncs_t *progfuncs, int complen, int len, int method, const void *info, char *buffer) { int i; @@ -87,6 +125,35 @@ char *QC_decode(progfuncs_t *progfuncs, int complen, int len, int method, const externs->Sys_Error("Failed block decompression\n"); inflateEnd(&strm); } +#endif +#ifdef ZLIB_DEFLATE64 + else if (method == 9) + { + z_stream strm = {NULL}; + struct def64ctx ctx; + ctx.in = info; + ctx.csize = complen; + ctx.out = buffer; + ctx.usize = len; + + strm.data_type = Z_UNKNOWN; + inflateBack9Init(&strm, ctx.window); + //getting inflateBack9 to + if (Z_STREAM_END != inflateBack9(&strm, QC_Deflate64_Grab, &ctx, QC_Deflate64_Spew, &ctx)) + { //some stream error? + externs->Printf("Decompression error\n"); + buffer = NULL; + } + else if (ctx.csize != 0 || ctx.usize != 0) + { //corrupt file table? + externs->Printf("Decompression size error\n"); + externs->Printf("read %i of %i bytes\n", (unsigned)ctx.csize, (unsigned)complen); + externs->Printf("wrote %i of %i bytes\n", (unsigned)ctx.usize, (unsigned)len); + buffer = NULL; + } + inflateBack9End(&strm); + return buffer; + } #endif //add your decryption/decompression routine here. else @@ -191,8 +258,17 @@ int QC_EnumerateFilesFromBlob(const void *blob, size_t blobsize, void (*cb)(cons unsigned int cdlen; const unsigned char *eocd; const unsigned char *cd; - int nl,el,cl; + unsigned int ofs_le; + unsigned int cd_nl,cd_el,cd_cl; int ret = 0; + + const unsigned char *le; + unsigned int csize, usize, method; + unsigned int le_nl,le_el; + char name[256]; + + const void *data; + if (blobsize < 22) return ret; if (!strncmp(blob, "PACK", 4)) @@ -221,13 +297,20 @@ int QC_EnumerateFilesFromBlob(const void *blob, size_t blobsize, void (*cb)(cons return ret; - for(; cdentries --> 0; cd += 46 + nl+el+cl) + for(; cdentries --> 0 && (QC_ReadRawInt(cd+0) == 0x02014b50); cd += 46 + cd_nl+cd_el+cd_cl) { - if (QC_ReadRawInt(cd+0) != 0x02014b50) - break; - nl = QC_ReadRawShort(cd+28); - el = QC_ReadRawShort(cd+30); - cl = QC_ReadRawShort(cd+32); + data = NULL, csize=usize=0, method=-1; + + cd_nl = QC_ReadRawShort(cd+28); //name length + cd_el = QC_ReadRawShort(cd+30); //extras length + cd_cl = QC_ReadRawShort(cd+32); //comment length + + ofs_le = QC_ReadRawInt(cd+42); + + if (cd_nl < sizeof(name)) //make can't be too long... + QC_strlcpy(name, cd+46, (cd_nl+1= sizeof(name)) - continue; //name is too long - QC_strlcpy(name, cd+46, (nl+1= 0) { - int nqcount = min(3,count); - do + //messy - TENQ_NQGUNSHOT loops until we reach our counter. should probably randomize positions a little + int nqcount = (nqtype[i] == TENQ_NQGUNSHOT)?min(3,count):1; + while(nqcount-->0) { MSG_WriteByte (&sv.nqmulticast, svc_temp_entity); MSG_WriteByte (&sv.nqmulticast, nqtype[i]); @@ -6148,19 +6153,12 @@ void SV_point_tempentity (vec3_t o, int type, int count) //count (usually 1) is MSG_WriteChar(&sv.nqmulticast, 0); MSG_WriteChar(&sv.nqmulticast, 0); } - else if (/*nqtype == TENQ_QWBLOOD ||*/ nqtype[i] == TENQ_QWGUNSHOT) + else if (nqtype[i] == TENQ_QWGUNSHOT) MSG_WriteByte (&sv.nqmulticast, count); MSG_WriteCoord (&sv.nqmulticast, o[0]); MSG_WriteCoord (&sv.nqmulticast, o[1]); MSG_WriteCoord (&sv.nqmulticast, o[2]); - - //messy - TENQ_NQGUNSHOT looks until we reach our counter. should probably randomize positions a little - if (nqcount > 1 && nqtype[i] == TENQ_NQGUNSHOT) - { - nqcount--; - continue; - } - } while(0); + } } #endif if (i) @@ -6461,6 +6459,31 @@ char *PF_infokey_Internal (int entnum, const char *key) sprintf(ov, "%d", SV_CalcPing (&svs.clients[entnum-1], true)); else if (!strcmp(key, "guid")) sprintf(ov, "%s", pl->guid); + else if (!strcmp(key, "*cert_dn")) + NET_GetConnectionCertificate(svs.sockets, &controller->netchan.remote_address, QCERT_PEERSUBJECT, ov, sizeof(ov)); + else if (!strncmp(key, "*cert_", 6)) + { + static struct + { + const char *name; + hashfunc_t *func; + } funcs[] = {{"sha1",&hash_sha1}, {"sha2_256", &hash_sha2_256}, {"sha2_512", &hash_sha2_512}}; + int i; + char buf[8192]; + char digest[DIGEST_MAXSIZE]; + int certsize; + *ov = 0; + for (i = 0; i < countof(funcs); i++) + { + if (!strcmp(key+6, funcs[i].name)) + { + certsize = NET_GetConnectionCertificate(svs.sockets, &controller->netchan.remote_address, QCERT_PEERCERTIFICATE, buf, sizeof(buf)); + if (certsize > 0) + Base64_EncodeBlockURI(digest,CalcHash(&hash_sha1, digest, sizeof(digest), buf, certsize), ov, sizeof(ov)); + break; + } + } + } else if (!strcmp(key, "challenge")) sprintf(ov, "%u", pl->challenge); else if (!strcmp(key, "*userid")) @@ -8109,6 +8132,8 @@ void PRH2_SetPlayerClass(client_t *cl, int classnum, qboolean fromqc) return; //reject it (it would crash the (standard hexen2) mod) if (classnum > 5) return; + while (classnum>1 && !COM_FCheckExists(va("gfx/menu/netp%i.lmp", classnum))) + classnum--; if (!fromqc) { @@ -8923,7 +8948,7 @@ void QCBUILTIN PF_sj_strhash (pubprogfuncs_t *prinst, struct globalvars_s *pr_gl { //not quite the same, but oh well const char *str = PF_VarString(prinst, 0, pr_globals); int len = strlen(str); - G_FLOAT(OFS_RETURN) = Com_BlockChecksum(str, len); + G_FLOAT(OFS_RETURN) = CalcHashInt(&hash_md4, str, len); } #endif static void QCBUILTIN PF_StopSound(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) @@ -11772,9 +11797,9 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs "} patchvert_t;\n" \ "#define patch_delete(modelidx,patchidx) brush_delete(modelidx,patchidx)\n" {"patch_getcp", PF_patch_getcp, 0, 0, 0, 0, D(qcpatchvert "int(float modelidx, int patchid, patchvert_t *out_controlverts, int maxcp, patchinfo_t *out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")}, - {"patch_getmesh", PF_patch_getmesh, 0, 0, 0, 0, D("int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, __out patchinfo_t out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")}, + {"patch_getmesh", PF_patch_getmesh, 0, 0, 0, 0, D("int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, patchinfo_t *out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")}, {"patch_create", PF_patch_create, 0, 0, 0, 0, D("int(float modelidx, int oldpatchid, patchvert_t *in_controlverts, patchinfo_t in_info)", "Inserts a new patch into the model. Return value is the new patch's id.")}, -// {"patch_calculate", PF_patch_calculate, 0, 0, 0, 0, D("int(patchvert_t *in_controlverts, patchvert_t *out_renderverts, int maxout, __inout patchinfo_t inout_info)", "Calculates the geometry of a hyperthetical patch.")}, + {"patch_evaluate", PF_patch_evaluate, 0, 0, 0, 0, D("int(patchvert_t *in_controlverts, patchvert_t *out_renderverts, int maxout, patchinfo_t *inout_info)", "Calculates the geometry of a hyperthetical patch.")}, #endif #ifdef ENGINE_ROUTING @@ -12201,7 +12226,9 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs // {"delayedparticle", PF_Fixme, 0, 0, 0, 528, D("float(vector org, vector vel, float delay, float collisiondelay, optional float theme)","Basically just extra args for 'particle'.")}, {"loadfromdata", PF_loadfromdata, 0, 0, 0, 529, D("void(string s)", "Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead.")}, {"loadfromfile", PF_loadfromfile, 0, 0, 0, 530, D("void(string s)", "Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead.")}, - {"setpause", PF_setpause, 0, 0, 0, 531, D("void(float pause)", "Sets whether the server should or should not be paused. This does not affect auto-paused things like when the console is down.")}, + {"setpause", PF_setpause, 0, 0, 0, 531, D("void(float pause)", "SSQC: Sets whether the server should or should not be paused.\n" + "CSQC: Only works in singleplayer, suitable for menu auto-pause. To pause in multiplayer use eg localcmd(\"cmd pause\n\") to ask the server side to pause.\n" + "Pause state between modules will be ORed, along with engine reasons for auto pausing.")}, //end dp extras //begin mvdsv extras #ifdef HAVE_LEGACY @@ -12564,7 +12591,7 @@ void PR_ResetBuiltins(progstype_t type) //fix all nulls to PF_FIXME and add any } } if (!BuiltinList[i].name) - Con_Printf("Failed to map builtin %s to %i specified in fte_bimap.dat\n", com_token, binum); + Con_Printf("Failed to map builtin %s to %i specified in fte_bimap.txt\n", com_token, binum); } } } @@ -13685,6 +13712,8 @@ void PR_DumpPlatform_f(void) {"INFOKEY_P_CSQCACTIVE","const string", QW|NQ, D("Client has csqc enabled. CSQC ents etc will be sent to this player."), 0, "\"csqcactive\""}, {"INFOKEY_P_SVPING", "const string", QW|NQ, NULL, 0, "\"svping\""}, {"INFOKEY_P_GUID", "const string", QW|NQ, D("Some hash string which should be reasonably unique to this player's quake installation."), 0, "\"guid\""}, + {"INFOKEY_P_CERT_SHA1", "const string", QW|NQ, D("Obtains the client's (d)tls certificate's fingerprint."), 0, "\"*cert_sha1\""}, + {"INFOKEY_P_CERT_DN", "const string", QW|NQ, D("Obtains the client's (d)tls certificate's Distinguished Name string."), 0, "\"*cert_dn\""}, {"INFOKEY_P_CHALLENGE", "const string", QW|NQ, NULL, 0, "\"challenge\""}, {"INFOKEY_P_USERID", "const string", QW|NQ, NULL, 0, "\"*userid\""}, {"INFOKEY_P_DOWNLOADPCT","const string",QW|NQ, D("The client's download percentage for the current file. Additional files are not known."), 0, "\"download\""}, diff --git a/engine/server/pr_q1qvm.c b/engine/server/pr_q1qvm.c index 70c6c46cf..8a608f3a0 100755 --- a/engine/server/pr_q1qvm.c +++ b/engine/server/pr_q1qvm.c @@ -1090,11 +1090,21 @@ static qintptr_t QVM_SetSpawnParams (void *offset, quintptr_t mask, const qintpt static qintptr_t QVM_ChangeLevel (void *offset, quintptr_t mask, const qintptr_t *arg) { const char *arg_mapname = VM_POINTER(arg[0]); -// const char *arg_entfilename = VM_POINTER(arg[1]); + const char *arg_entfilename = (qvm_api_version > 13)?(VM_POINTER(arg[1])):""; char newmap[MAX_QPATH]; if (sv.mapchangelocked) return 0; + + if (arg_entfilename && *arg_entfilename) + { + int nl = strlen(arg_mapname); + if (!strncmp(arg_mapname, arg_entfilename, nl) && arg_mapname[nl]=='#') + arg_mapname = arg_entfilename; + else + Con_Printf(CON_ERROR"%s: named ent file does not match map\n", "QVM_ChangeLevel"); + } + sv.mapchangelocked = true; COM_QuotedString(arg_mapname, newmap, sizeof(newmap), false); Cbuf_AddText (va("\nchangelevel %s\n", newmap), RESTRICT_LOCAL); @@ -1103,13 +1113,23 @@ static qintptr_t QVM_ChangeLevel (void *offset, quintptr_t mask, const qintptr_t static qintptr_t QVM_ChangeLevelHub (void *offset, quintptr_t mask, const qintptr_t *arg) { const char *arg_mapname = VM_POINTER(arg[0]); -// const char *arg_entfile = VM_POINTER(arg[1]); + const char *arg_entfilename = VM_POINTER(arg[1]); const char *arg_startspot = VM_POINTER(arg[2]); char newmap[MAX_QPATH]; char startspot[MAX_QPATH]; if (sv.mapchangelocked) return 0; + + if (arg_entfilename && *arg_entfilename) + { + int nl = strlen(arg_mapname); + if (!strncmp(arg_mapname, arg_entfilename, nl) && arg_mapname[nl]=='#') + arg_mapname = arg_entfilename; + else + Con_Printf(CON_ERROR"%s: named ent file does not match map\n", "QVM_ChangeLevelHub"); + } + sv.mapchangelocked = true; COM_QuotedString(arg_mapname, newmap, sizeof(newmap), false); COM_QuotedString(arg_startspot, startspot, sizeof(startspot), false); @@ -1668,6 +1688,7 @@ static qintptr_t QVM_Add_Bot (void *offset, quintptr_t mask, const qintptr_t *ar cl->userid = ++nextuserid; cl->protocol = SCP_BAD; //marker for bots cl->state = cs_spawned; + cl->connection_started = realtime; cl->spawned = true; sv.spawned_client_slots++; cl->netchan.message.allowoverflow = true; diff --git a/engine/server/server.h b/engine/server/server.h index a43045794..2cb1d0ade 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -697,6 +697,7 @@ typedef struct client_s qboolean qex; //qex sends strange clc inputs and needs workarounds for its prediction. it also favours fitzquake's protocol but violates parts of it. unsigned int lastruncmd; //for non-qw physics. timestamp they were last run, so switching between physics modes isn't a (significant) cheat + unsigned int hoverms; //purely for sv_showpredloss to avoid excessive spam //speed cheat testing #define NEWSPEEDCHEATPROT float msecs; @@ -1385,10 +1386,12 @@ void SV_SendClientPrespawnInfo(client_t *client); void SV_ClientProtocolExtensionsChanged(client_t *client); //sv_master.c -float SVM_Think(int port); +float SVM_Think(void); vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname, const char **mimetype, const char *query); void SVM_AddBrokerGame(const char *brokerid, const char *info); void SVM_RemoveBrokerGame(const char *brokerid); +qboolean SVM_FixupServerAddress(netadr_t *adr, struct dtlspeercred_s *cred); +void FTENET_TCP_ICEResponse(struct ftenet_connections_s *col, int type, const char *cid, const char *sdp); // @@ -1649,7 +1652,7 @@ typedef struct { qboolean hasauthed; qboolean isreverse; - char challenge[64]; + char challenge[64]; //aka nonce } qtvpendingstate_t; int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *headerend, qtvpendingstate_t *p); #endif diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 4fc732dab..c162558a9 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -471,6 +471,27 @@ static int QDECL CompleteMapList (const char *name, qofs_t flags, time_t mtime, ctx->cb(stripped, NULL, NULL, ctx); return true; } +static int QDECL CompleteMapListEnt (const char *name, qofs_t flags, time_t mtime, void *parm, searchpathfuncs_t *spath) +{ + struct xcommandargcompletioncb_s *ctx = parm; + char stripped[64]; + char *modifier = strchr(name, '#'); + if (!modifier) //skip non-modifiers. + return true; + if (modifier-name+4 > sizeof(stripped)) //too long... + return true; + + //make sure we have its .bsp + memcpy(stripped, name, modifier-name); + strcpy(stripped+(modifier-name), ".bsp"); + if (!COM_FCheckExists(stripped)) + return true; + + COM_StripExtension(name+5, stripped, sizeof(stripped)); + ctx->cb(stripped, NULL, NULL, ctx); + return true; +} + static int QDECL CompleteMapListExt (const char *name, qofs_t flags, time_t mtime, void *parm, searchpathfuncs_t *spath) { struct xcommandargcompletioncb_s *ctx = parm; @@ -493,6 +514,8 @@ static void SV_Map_c(int argn, const char *partial, struct xcommandargcompletion COM_EnumerateFiles(va("maps/%s*.cm", partial), CompleteMapList, ctx); COM_EnumerateFiles(va("maps/%s*.hmp", partial), CompleteMapList, ctx); + COM_EnumerateFiles(va("maps/%s*.ent", partial), CompleteMapListEnt, ctx); + COM_EnumerateFiles(va("maps/%s*/*.bsp", partial), CompleteMapList, ctx); COM_EnumerateFiles(va("maps/%s*/*.bsp.gz", partial), CompleteMapListExt, ctx); COM_EnumerateFiles(va("maps/%s*/*.bsp.xz", partial), CompleteMapListExt, ctx); @@ -609,7 +632,7 @@ fte: 'map package:mapname' should download the specified map package and load up its maps. mvdsv: -basemap#modifier.ent files +'map foo bar' should load 'maps/bar.ent' instead of the regular ent file. this 'bar' will usually be something like 'foo#modified' ====================== */ @@ -707,7 +730,7 @@ void SV_Map_f (void) sv.mapchangelocked = false; } else - PM_LoadMap(mangled, sep); + PM_LoadMap(mangled, va("%s %s\n", Cmd_Argv(0), COM_QuotedString(sep, expanded, sizeof(expanded), false))); return; } } @@ -851,10 +874,25 @@ void SV_Map_f (void) break; } if (!exts[i]) + { //try again. + char *mod = strchr(level, '#'); + if (mod) + { + *mod = 0; + for (i = 0; exts[i]; i++) + { + snprintf (expanded, sizeof(expanded), exts[i], level); + if (COM_FCheckExists (expanded)) + break; + } + *mod = '#'; + } + } + if (!exts[i]) { for (i = 0; exts[i]; i++) { - //doesn't exist, so try lowercase. Q3 does this. + //doesn't exist, so try lowercase. Q3 does this. really our fs_cache stuff should be handling this, but its possible its disabled. for (j = 0; j < sizeof(level) && level[j]; j++) { if (level[j] >= 'A' && level[j] <= 'Z') @@ -965,7 +1003,10 @@ void SV_Map_f (void) if (!isrestart) { if (q3singleplayer) + { Cvar_ForceSet(gametype, "2");//singleplayer + Cvar_ForceSet(&deathmatch, "0");//for non-q3 type stuff to not get confused.. + } else if (gametype->value == 2) Cvar_ForceSet(gametype, "");//force to ffa deathmatch } @@ -2101,7 +2142,7 @@ static void SV_Status_f (void) int i; client_t *cl; float cpu; - char *s, *p; + char *s, *p, *sec; char adr[MAX_ADR_SIZE]; float pi, po, bi, bo; @@ -2122,9 +2163,6 @@ static void SV_Status_f (void) #ifdef QWOVERQ3 extern cvar_t sv_listen_q3; #endif -#ifdef HAVE_DTLS - extern cvar_t net_enable_dtls; -#endif #ifndef SERVERONLY if (!sv.state && cls.state >= ca_connected && !cls.demoplayback && cls.protocol == CP_NETQUAKE) @@ -2171,6 +2209,7 @@ static void SV_Status_f (void) else s = "private"; Con_TPrintf("public : %s\n", s); + switch(svs.gametype) { #ifdef Q3SERVER @@ -2185,38 +2224,38 @@ static void SV_Status_f (void) #endif default: - Con_TPrintf("client types :%s", sv_listen_qw.ival?" QW":""); + Con_TPrintf("client types :%s", sv_listen_qw.ival?" ^[QW\\tip\\This is "FULLENGINENAME"'s standard protocol.^]":""); #ifdef NQPROT - Con_TPrintf("%s%s", (sv_listen_nq.ival==2)?" -NQ":(sv_listen_nq.ival?" NQ":""), sv_listen_dp.ival?" DP":""); + Con_TPrintf("%s%s", (sv_listen_nq.ival==2)?" ^[-NQ\\tip\\Allows 'Net'/'Normal' Quake clients to connect, with cookies and extensions that might confuse some old clients^]":(sv_listen_nq.ival?" ^[NQ\\tip\\Vanilla/Normal Quake protocol with maximum compatibility^]":""), sv_listen_dp.ival?" ^[DP\\tip\\Explicitly recognise connection requests from DP clients.^]":""); #endif #ifdef QWOVERQ3 if (sv_listen_q3.ival) Con_Printf(" Q3"); #endif #ifdef HAVE_DTLS if (net_enable_dtls.ival >= 3) - Con_Printf(" DTLS-only"); + Con_Printf(" ^[DTLS-only\\tip\\Insecure clients (those without support for DTLS) will be barred from connecting.^]"); else if (net_enable_dtls.ival) - Con_Printf(" DTLS"); + Con_Printf(" ^[DTLS\\tip\\Clients may optionally connect via DTLS for added security^]"); #endif Con_Printf("\n"); #if defined(TCPCONNECT) && !defined(CLIENTONLY) Con_TPrintf("tcp services :"); #if defined(HAVE_SSL) if (net_enable_tls.ival) - Con_Printf(" TLS"); + Con_Printf(" ^[TLS\\tip\\Clients are able to connect with Transport Layer Security for the other services, allowing for the use of tls://, wss:// or https:// schemes when their underlaying protocol is enabled.^]"); #endif #ifdef HAVE_HTTPSV if (net_enable_http.ival) - Con_Printf(" HTTP"); + Con_Printf(" ^[HTTP\\tip\\This server also acts as a web server. This might be useful to allow hosting demos or stats.^]"); if (net_enable_rtcbroker.ival) - Con_Printf(" RTC"); + Con_Printf(" ^[RTC\\tip\\This server is set up to act as a webrtc broker, allowing clients+servers to locate each other instead of playing on this server.^]"); if (net_enable_websockets.ival) - Con_Printf(" WebSocket"); + Con_Printf(" ^[WebSocket\\tip\\Clients can use the ws:// or possibly wss:// schemes to connect to this server, potentially from browser ports. This may be laggy.^]"); #endif if (net_enable_qizmo.ival) - Con_Printf(" Qizmo"); + Con_Printf(" ^[Qizmo\\tip\\Compatible with the tcp connection feature of qizmo, equivelent to 'connect tcp://ip:port' in FTE.^]"); if (net_enable_qtv.ival) - Con_Printf(" QTV"); + Con_Printf(" ^[QTV\\tip\\Allows receiving streamed mvd data from this server.^]"); Con_Printf("\n"); #endif break; @@ -2231,7 +2270,7 @@ static void SV_Status_f (void) Con_TPrintf("map uptime : %s\n", ShowTime(sv.world.physicstime)); //show the current map+name (but hide name if its too long or would be ugly) if (columns >= 80 && *sv.mapname && strlen(sv.mapname) < 45 && !strchr(sv.mapname, '\n')) - Con_TPrintf ("current map : %s (%s)\n", svs.name, sv.mapname); + Con_TPrintf ("current map : %s "S_COLOR_GRAY"(%s)\n", svs.name, sv.mapname); else Con_TPrintf ("current map : %s\n", svs.name); @@ -2329,7 +2368,7 @@ static void SV_Status_f (void) #define COLUMNS C_FRAGS C_USERID C_ADDRESS C_NAME C_RATE C_PING C_DROP C_DLP C_DLS C_PROT C_ADDRESS2 #define C_FRAGS COLUMN(0, "frags", if (cl->spectator==1)Con_Printf("%-5s ", "spec"); else Con_Printf("%5i ", (int)cl->old_frags)) #define C_USERID COLUMN(1, "userid", Con_Printf("%6i ", (int)cl->userid)) -#define C_ADDRESS COLUMN(2, "address ", Con_Printf("%-16.16s", s)) +#define C_ADDRESS COLUMN(2, "address ", Con_Printf("%s%-16.16s", sec, s)) #define C_NAME COLUMN(3, "name ", Con_Printf("%-16.16s", cl->name)) #define C_RATE COLUMN(4, " hz", Con_Printf("%4i ", (cl->frameunion.frames&&cl->netchan.frame_rate>0)?(int)(0.5f+1/cl->netchan.frame_rate):0)) #define C_PING COLUMN(5, "ping", Con_Printf("%4i ", (int)SV_CalcPing (cl, false))) @@ -2399,6 +2438,13 @@ static void SV_Status_f (void) else s = NET_BaseAdrToString (adr, sizeof(adr), &cl->netchan.remote_address); + if (NET_IsLoopBackAddress(&cl->netchan.remote_address)) + sec = ""; + else if (NET_IsEncrypted(&cl->netchan.remote_address)) + sec = S_COLOR_GREEN; + else + sec = S_COLOR_RED; + safeswitch(cl->protocol) { case SCP_BAD: p = "-----"; break; @@ -2690,7 +2736,9 @@ void SV_User_f (void) client_t *cl; int clnum=-1; unsigned int u; - char buf[256]; + char buf[8192]; + qbyte digest[DIGEST_MAXSIZE]; + int certsize; extern cvar_t sv_userinfo_bytelimit, sv_userinfo_keylimit; static const char *pext1names[32] = { "setview", "scale", "lightstylecol", "trans", "view2", "builletens", "accuratetimings", "sounddbl", "fatness", "hlbsp", "bullet", "hullsize", "modeldbl", "entitydbl", "entitydbl2", "floatcoords", @@ -2775,7 +2823,16 @@ void SV_User_f (void) Con_Printf("\n"); } - Con_Printf("ip: %s\n", NET_AdrToString(buf, sizeof(buf), &cl->netchan.remote_address)); + Con_Printf("ip: %s%s\n", NET_IsEncrypted(&cl->netchan.remote_address)?S_COLOR_GREEN:S_COLOR_RED, NET_AdrToString(buf, sizeof(buf), &cl->netchan.remote_address)); + certsize = NET_GetConnectionCertificate(svs.sockets, &cl->netchan.remote_address, QCERT_PEERCERTIFICATE, buf, sizeof(buf)); + if (certsize <= 0) + strcpy(buf, ""); + else + Base64_EncodeBlockURI(digest,CalcHash(&hash_sha1, digest, sizeof(digest), buf, certsize), buf, sizeof(buf)); + Con_Printf("fp: %s\n", buf); + if (NET_GetConnectionCertificate(svs.sockets, &cl->netchan.remote_address, QCERT_PEERSUBJECT, buf, sizeof(buf)) < 0) + strcpy(buf, ""); + Con_Printf("dn: %s\n", buf); switch(cl->realip_status) { case 1: @@ -3208,6 +3265,11 @@ void SV_PrecacheList_f(void) { unsigned int i; char *group = Cmd_Argv(1); + if (sv.state != ss_active) + { + Con_Printf("Server is not active.\n"); + return; + } #ifdef HAVE_LEGACY if (!*group || !strncmp(group, "vwep", 4)) { diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index 91acda181..8fd1018e1 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -3459,7 +3459,8 @@ void SV_Snapshot_BuildStateQ1(entity_state_t *state, edict_t *ent, client_t *cli if ((state->number-1) < (unsigned int)sv.allocated_client_slots && ent->v->movetype && client) { client_t *cl = &svs.clients[state->number-1]; - if (cl->isindependant) + extern cvar_t sv_nqplayerphysics; + if (cl->isindependant || sv_nqplayerphysics.ival==2) { state->u.q1.pmovetype = ent->v->movetype; if (state->u.q1.pmovetype && ((int)ent->v->flags & FL_ONGROUND) && (client->zquake_extensions&Z_EXT_PF_ONGROUND)) diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index b2bfd1aa6..bbeb5997f 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -1016,12 +1016,11 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, { //.map is commented out because quite frankly, they're a bit annoying when the engine loads the gpled start.map when really you wanted to just play the damn game intead of take it apart. //if you want to load a .map, just use 'map foo.map' instead. - char *exts[] = {"maps/%s", "maps/%s.bsp", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ "maps/%s.bsp.gz", "maps/%s.bsp.xz", NULL}, *e; - int depth, bestdepth; + char *exts[] = {"%s", "maps/%s", "maps/%s.bsp", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ "maps/%s.bsp.gz", "maps/%s.bsp.xz", NULL}, *e; + int depth, bestdepth = FDEPTH_MISSING; flocation_t loc; time_t filetime; - Q_snprintfz (sv.modelname, sizeof(sv.modelname), "%s", server); - bestdepth = COM_FDepthFile(sv.modelname, false); + char *mod = NULL; if (bestdepth == FDEPTH_MISSING) { //not an exact name, scan the maps subdir. for (i = 0; exts[i]; i++) @@ -1034,6 +1033,32 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, } } } + if (bestdepth == FDEPTH_MISSING) + { + mod = strchr(server, '#'); + if (mod) + { + *mod = 0; + bestdepth = COM_FDepthFile(server, false); + if (bestdepth != FDEPTH_MISSING) + Q_snprintfz (sv.modelname, sizeof(sv.modelname), "%s", server); + else + { //not an exact name, scan the maps subdir. + for (i = 0; exts[i]; i++) + { + depth = COM_FDepthFile(va(exts[i], server), false); + if (depth < bestdepth) + { + bestdepth = depth; + Q_snprintfz (sv.modelname, sizeof(sv.modelname), exts[i], server); + } + } + } + *mod = '#'; + if (bestdepth == FDEPTH_MISSING) + mod = NULL; + } + } if (!strncmp(sv.modelname, "maps/", 5)) Q_strncpyz (svs.name, sv.modelname+5, sizeof(svs.name)); @@ -1048,6 +1073,8 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, if (!strcmp(e, ".bsp")) *e = 0; + Mod_SetModifier(mod); + sv.world.worldmodel = Mod_ForName (sv.modelname, MLV_ERROR); if (FS_FLocateFile(sv.modelname,FSLF_IFFOUND, &loc) && FS_GetLocMTime(&loc, &filetime)) @@ -1127,7 +1154,7 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, if (file) { char text[64]; - sv.csqcchecksum = Com_BlockChecksum(file, fsz); + sv.csqcchecksum = CalcHashInt(&hash_md4, file, fsz); sprintf(text, "0x%x", sv.csqcchecksum); InfoBuf_SetValueForStarKey(&svs.info, "*csprogs", text); sprintf(text, "0x%x", (unsigned int)fsz); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 89a14f5ad..771b056a3 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -78,11 +78,6 @@ extern cvar_t password; #endif cvar_t spectator_password = CVARF("spectator_password", "", CVAR_NOUNSAFEEXPAND); // password for entering as a sepctator -#ifdef FTE_TARGET_WEB -cvar_t sv_dlURL = CVARAFD(/*ioq3*/"sv_dlURL", "", /*dp*/"sv_curl_defaulturl", CVAR_SERVERINFO|CVAR_NOSAVE, "Provides clients with an external url from which they can obtain pk3s/packages from an external http server instead of having to download over udp."); -#else -cvar_t sv_dlURL = CVARAFD(/*ioq3*/"sv_dlURL", "", /*dp*/"sv_curl_defaulturl", CVAR_SERVERINFO|CVAR_ARCHIVE, "Provides clients with an external url from which they can obtain pk3s/packages from an external http server instead of having to download over udp."); -#endif cvar_t allow_download = CVARAD("allow_download", "1", /*q3*/"sv_allowDownload", "If 1, permits downloading. Set to 0 to unconditionally block *ALL* downloads from this server. You may wish to set sv_dlURL if you wish clients to still be able to download content."); cvar_t allow_download_skins = CVARD("allow_download_skins", "1", "0 blocks downloading of any file in the skins/ directory"); cvar_t allow_download_models = CVARD("allow_download_models", "1", "0 blocks downloading of any file in the progs/ or models/ directory"); @@ -105,7 +100,7 @@ cvar_t allow_download_other = CVARD("allow_download_other", "0", "0 blocks down extern cvar_t sv_allow_splitscreen; -#ifdef SUPPORT_ICE +#if defined(SUPPORT_ICE) || defined(FTE_TARGET_WEB) static void QDECL SV_Public_Callback(struct cvar_s *var, char *oldvalue) { char name[64], *e; @@ -116,7 +111,11 @@ static void QDECL SV_Public_Callback(struct cvar_s *var, char *oldvalue) FTENET_AddToCollection(svs.sockets, var->name, va("/%s", (*name == '/')?name+1:name), NA_INVALID, NP_RTC_TLS); var->value = var->ival = 2; //so other stuff sees us as holepunched. } +#ifdef FTE_TARGET_WEB + else if (var->ival) //any kind of public is webrtc public, browsers don't allow more. +#else else if (var->ival == 2) +#endif FTENET_AddToCollection(svs.sockets, var->name, "/", NA_INVALID, NP_RTC_TLS); else FTENET_AddToCollection(svs.sockets, var->name, "", NA_INVALID, NP_INVALID); @@ -136,7 +135,6 @@ cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0"); #endif cvar_t sv_reconnectlimit = CVARD("sv_reconnectlimit", "0", "Blocks dupe connection within the specified length of time ."); cvar_t sv_use_dns = CVARD("sv_use_dns", "", "Performs a reverse-dns lookup in order to report more info about where clients are connecting from."); -extern cvar_t net_enable_dtls; cvar_t sv_reportheartbeats = CVARD("sv_reportheartbeats", "2", "Print a notice each time a heartbeat is sent to a master server. When set to 2, the message will be displayed once."); cvar_t sv_heartbeat_interval = CVARD("sv_heartbeat_interval", "110", "Interval between heartbeats. Low values are abusive, high values may cause NAT/ghost issues."); cvar_t sv_heartbeat_checks = CVARD("sv_heartbeat_checks", "1", "Report when sv_public 1 fails due to PROBABLE router/NAT issues."); @@ -158,7 +156,6 @@ cvar_t sv_pupglow = CVARFD("sv_pupglow", "", CVAR_SERVERINFO, "Instructs clie #ifdef SV_MASTER cvar_t sv_master = CVAR("sv_master", "0"); -cvar_t sv_masterport = CVAR("sv_masterport", "0"); #endif cvar_t sv_reliable_sound = CVARFD("sv_reliable_sound", "0", 0, "Causes all sounds to be sent reliably, so they will not be missed due to packetloss. However, this will cause them to be delayed somewhat, and slightly bursty. This can be overriden using the 'rsnd' userinfo setting (either forced on or forced off). Note: this does not affect sounds attached to particle effects."); @@ -1014,6 +1011,7 @@ void SV_FullClientUpdate (client_t *client, client_t *to) if (ISQWCLIENT(to)) { + float onservertime; unsigned int pext = to->fteprotocolextensions; int ping = SV_CalcPing (client, false); if (ping > 0xffff) @@ -1037,10 +1035,13 @@ void SV_FullClientUpdate (client_t *client, client_t *to) MSG_WriteByte(buf, client->lossage); ClientReliable_FinishWrite(to); + onservertime = realtime - client->connection_started; + if (onservertime > sv.time) + onservertime = sv.time; buf = ClientReliable_StartWrite(to, 6); MSG_WriteByte(buf, svc_updateentertime); MSG_WriteByte(buf, i); - MSG_WriteFloat(buf, realtime - client->connection_started); + MSG_WriteFloat(buf, onservertime); ClientReliable_FinishWrite(to); InfoBuf_ToString(&client->userinfo, info, (pext&PEXT_BIGUSERINFOS)?BASIC_INFO_STRING:sizeof(info), basicuserinfos, privateuserinfos, (pext&PEXT_BIGUSERINFOS)?NULL:basicuserinfos, &to->infosync, &client->userinfo); @@ -1119,19 +1120,68 @@ CONNECTIONLESS COMMANDS ============================================================================== */ +const char *SV_ProtocolNameForClient(client_t *cl) +{ + //okay, that failed... + safeswitch (cl->protocol) + { + case SCP_QUAKEWORLD: + if (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) + return "fteqw"; //changes enough to be significant. assumed to include csqc. + return "quakeworld"; + case SCP_BAD: + return "bot"; + case SCP_QUAKE2: + return "quake2"; + case SCP_QUAKE3: + return "quake3"; + case SCP_NETQUAKE: + if (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) + return "ftenq"; //changes enough to be significant. assumed to include csqc. + if (cl->qex) + return "qex"; + if (cl->proquake_angles_hack) + return "proquake"; + return "vanilla"; + case SCP_BJP3: + return "bjp3"; + case SCP_FITZ666: + //this gets messy... probably we should distinguish more + if (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) + return "ftenq"; //changes enough to be significant. assumed to include csqc. + if (cl->qex) + return "qex"; + if (cl->netchan.netprim.coordtype != COORDTYPE_FIXED_13_3 || cl->netchan.netprim.anglesize != 1) + return "rmq"; //while fte tends not to care, most people consider them separate. + return "fitz"; + case SCP_DARKPLACES6: + return "dp6"; + case SCP_DARKPLACES7: + return "dp7"; + safedefault: + return "unknown"; + } +} + char *SV_PlayerPublicAddress(client_t *cl) { //returns a string containing the client's IP address, as permitted for viewing by other clients. //if something useful is actually returned, it should be masked. - return "private"; + //we hide it entirely out of private info caution. most nq clients expect a #.#.#.INVALID type address. + //it should be fine to put other stuff here though, we put client version instead, if we know it. + const char *ver = InfoBuf_ValueForKey(&cl->userinfo, "*ver"); + const char *prot = SV_ProtocolNameForClient(cl); + + return va("prot %s %s", prot, ver); //something so they can't confuse ip parsing so easily nor pass them off as some other protocol. } -#define STATUS_OLDSTYLE 0 +#define STATUS_OLDSTYLE 0 //equivelent to STATUS_SERVERINFO|STATUS_PLAYERS #define STATUS_SERVERINFO 1 #define STATUS_PLAYERS 2 #define STATUS_SPECTATORS 4 #define STATUS_SPECTATORS_AS_PLAYERS 8 //for ASE - change only frags: show as "S" -#define STATUS_SHOWTEAMS 16 -#define STATUS_QTVLIST 32 //qtv destid "name" "streamid@host:port" numviewers +#define STATUS_SHOWTEAMS 16 +#define STATUS_QTVLIST 32 //qtv destid "name" "streamid@host:port" numviewers +#define STATUS_LOGININFO 64 /* ================ @@ -1151,6 +1201,8 @@ static void SVC_Status (void) int top, bottom; char frags[64]; char *skin, *team, *botpre, *specpre; + char junk[512]; + int jlen; int slots=0; @@ -1206,18 +1258,21 @@ static void SVC_Status (void) else sprintf(frags, "%i", cl->old_frags); - if (displayflags & STATUS_SHOWTEAMS) + junk[jlen = 0] = 0; + if ((displayflags & STATUS_SHOWTEAMS) && jlen+4userid, - frags, (int)(realtime - cl->connection_started)/60, - ping, specpre, botpre, name, skin, top, bottom, team); + junk[jlen++] = ' '; + jlen += strlen(COM_QuotedString(team, junk+jlen, sizeof(junk)-jlen, false)); } - else + if ((displayflags & STATUS_LOGININFO) && jlen+4userid, - frags, (int)(realtime - cl->connection_started)/60, - ping, specpre, botpre, name, skin, top, bottom); + junk[jlen++] = ' '; + jlen += strlen(COM_QuotedString("", junk+jlen, sizeof(junk)-jlen, false)); } + + Con_Printf ("%i %s %i %i \"%s%s%s\" \"%s\" %i %i%s\n", cl->userid, + frags, (int)(realtime - cl->connection_started)/60, + ping, specpre, botpre, name, skin, top, bottom, junk); } else slots++; @@ -1760,14 +1815,16 @@ qboolean SVC_GetChallenge (qboolean respond_dp) #endif #ifdef HAVE_DTLS - if (net_enable_dtls.ival/* || !*net_enable_dtls.string*/) + if (net_enable_dtls.ival>0/* || !*net_enable_dtls.string*/ && svs.sockets->dtlsfuncs) { lng = LittleLong(PROTOCOL_VERSION_DTLSUPGRADE); memcpy(over, &lng, sizeof(lng)); over+=sizeof(lng); - if (net_enable_dtls.ival >= 2) - lng = LittleLong(2); //required + if (net_enable_dtls.ival >= 3) + lng = LittleLong(3); //required + else if (net_enable_dtls.ival >= 2) + lng = LittleLong(2); //encouraged else lng = LittleLong(1); //supported memcpy(over, &lng, sizeof(lng)); @@ -2542,6 +2599,7 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id) if (cl->spectator) InfoBuf_SetValueForStarKey (&cl->userinfo, "*spectator", va("%i", cl->spectator)); cl->state = controller->state; + cl->connection_started = realtime; // host_client = NULL; // sv_player = NULL; @@ -2597,7 +2655,8 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) { if (spectator_password.string[0] && stricmp(spectator_password.string, "none") && - strcmp(spectator_password.string, s) ) + strcmp(spectator_password.string, s) && + !NET_IsLoopBackAddress(&info->adr)) { // failed Con_TPrintf ("%s:spectator password failed\n", NET_AdrToString (adrbuf, sizeof(adrbuf), &info->adr)); SV_RejectMessage (info->protocol, "requires a spectator password\n\n"); @@ -2612,7 +2671,8 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) s = Info_ValueForKey (info->userinfo, "password"); if (password.string[0] && stricmp(password.string, "none") && - strcmp(password.string, s) ) + strcmp(password.string, s) && + !NET_IsLoopBackAddress(&info->adr)) { Con_TPrintf ("%s:password failed\n", NET_AdrToString (adrbuf, sizeof(adrbuf), &info->adr)); SV_RejectMessage (info->protocol, "server requires a password\n\n"); @@ -3047,10 +3107,18 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) //this is the upper bound of the mtu, if its too high we'll get EMSGSIZE and we'll reduce it. //however, if it drops below newcl->netchan.message.maxsize then we'll start to see undeliverable reliables, which means dropped clients. newcl->netchan.mtu = MAX_DATAGRAM; //vanilla qw clients are assumed to have an mtu of this size. - if (info->mtu >= 64) + if (info->mtu >= 300) //anything smaller is someone being intentionally malicious. { //if we support application fragmenting, then we can send massive reliables without too much issue newcl->netchan.mtu = info->mtu; newcl->netchan.message.maxsize = sizeof(newcl->netchan.message_buf); + +#ifdef HAVE_ICE + if (info->adr.type == NA_ICE) + newcl->netchan.mtu -= 48+12; //dtls+sctp overhead + else +#endif + if (info->adr.prot == NP_DTLS || info->adr.prot == NP_TLS) + newcl->netchan.mtu -= 48; //dtls overhead } else { //otherwise we can't fragment the packets, and the only way to honour the mtu is to send less data. yay for more round-trips. @@ -3072,6 +3140,7 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) #endif newcl->state = cs_connected; + newcl->connection_started = realtime; #ifdef Q3SERVER newcl->gamestatesequence = -1; @@ -3655,9 +3724,6 @@ void SVC_DirectConnect(int expectedreliablesequence) } msg_badread=false; - if (!*info.guid) - NET_GetConnectionCertificate(svs.sockets, &net_from, QCERT_PEERFINGERPRINT, info.guid, sizeof(info.guid)); - info.adr = net_from; if (MSV_ClusterLogin(&info)) return; @@ -3978,9 +4044,14 @@ void SVC_ACK (void) } } } - Con_TPrintf ("A2A_ACK from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); + Con_TPrintf (S_COLOR_GRAY"A2A_ACK from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); } +#ifdef SUPPORT_ICE +void SVC_ICE_Offer(void); +void SVC_ICE_Candidate(void); +#endif + //returns false to block replies //this is to mitigate wasted bandwidth if we're used as a udp amplification qboolean SVC_ThrottleInfo (void) @@ -4019,7 +4090,7 @@ static struct attacker_s } *dosattacker; static size_t dosattacker_count; static size_t dosattacker_max; -#define dosattacker_limit 10 //if we get X packets +#define dosattacker_limit 15 //if we get X packets #define dosattacker_period 30 //within Y secs #define dosattacker_blocktime (60*60*24) //block them for Z secs (24 hours). static qboolean SV_DetectAmplificationDDOS (void) @@ -4222,8 +4293,10 @@ qboolean SV_ConnectionlessPacket (void) else { //NET_DTLS_Disconnect(svs.sockets, &net_from); - if (NET_DTLS_Create(svs.sockets, &net_from, NULL)) + if (NET_DTLS_Create(svs.sockets, &net_from, NULL, false)) Netchan_OutOfBandPrint(NS_SERVER, &net_from, "dtlsopened"); + else + SV_RejectMessage (SCP_QUAKEWORLD, "DTLS driver failure.\n"); } } else @@ -4268,6 +4341,13 @@ qboolean SV_ConnectionlessPacket (void) } else if (!strcmp(c, "realip") || !strcmp(c, "ip")) SVC_RealIP (); + +#ifdef SUPPORT_ICE + else if (!strcmp(c, "ice_offer")) + SVC_ICE_Offer(); + else if (!strcmp(c, "ice_ccand")) + SVC_ICE_Candidate(); +#endif /* else if (!strcmp(c,"lastscores")) { @@ -4490,16 +4570,18 @@ qboolean SVNQ_ConnectionlessPacket(void) /*dual-stack client, supporting either DP or QW protocols*/ SVC_GetChallenge (false); } +#ifdef HAVE_DTLS + else if (net_enable_dtls.ival > 2 && (net_from.prot == NP_DGRAM || net_from.prot == NP_STREAM || net_from.prot == NP_WS) && net_from.type != NA_LOOPBACK && !NET_IsEncrypted(&net_from)) + { + SV_RejectMessage (SCP_NETQUAKE, "This server requires the use of DTLS/TLS/WSS.\n"); + return true; + } +#endif else { //legacy pure-nq (though often DP). if (progstype == PROG_H2) { - SZ_Clear(&sb); - MSG_WriteLong(&sb, 0); - MSG_WriteByte(&sb, CCREP_REJECT); - MSG_WriteString(&sb, "NQ clients are not supported with hexen2 gamecode\n"); - *(int*)sb.data = BigLong(NETFLAG_CTL+sb.cursize); - NET_SendPacket(svs.sockets, sb.cursize, sb.data, &net_from); + SV_RejectMessage (SCP_NETQUAKE, "NQ clients are not supported with hexen2 gamecode\n"); return true; //not our version... } if (NET_WasSpecialPacket(svs.sockets)) @@ -5011,10 +5093,6 @@ qboolean SV_ReadPackets (float *delay) NET_ReadPackets(svs.sockets); -#ifdef HAVE_DTLS - NET_DTLS_Timeouts(svs.sockets); -#endif - if (inboundsequence == oldinboundsequence) return false; //nothing new. oldinboundsequence = inboundsequence; @@ -5269,6 +5347,7 @@ void SV_Impulse_f (void) pr_global_struct->time = sv.world.physicstime; svs.clients[i].state = cs_connected; + svs.clients[i].connection_started = realtime; SV_SetUpClientEdict(&svs.clients[i], svs.clients[i].edict); @@ -5380,7 +5459,11 @@ float SV_Frame (void) #endif #ifdef HAVE_CLIENT - isidle = !isDedicated && sv.allocated_client_slots == 1 && (Key_Dest_Has(~kdm_game) || IN_WeaponWheelIsShown()) && cls.state == ca_active && !cl.implicitpause; + isidle = !isDedicated && sv.allocated_client_slots == 1 && (Key_Dest_Has(~kdm_game) +#ifdef QUAKESTATS + || IN_WeaponWheelIsShown() +#endif + || cl.implicitpause) && cls.state == ca_active; /*server is effectively paused in SP/coop if there are no clients/spectators*/ if (sv.spawned_client_slots == 0 && sv.spawned_observer_slots == 0 && !deathmatch.ival) isidle = true; @@ -5442,12 +5525,7 @@ float SV_Frame (void) #ifdef SV_MASTER if (sv_master.ival) - { - if (sv_masterport.ival) - SVM_Think(sv_masterport.ival); - else - SVM_Think(PORT_QWMASTER); - } + SVM_Think(); #endif #ifdef PLUGINS @@ -5682,6 +5760,7 @@ void SV_InitLocal (void) extern cvar_t sv_aim; extern cvar_t pm_bunnyspeedcap; + extern cvar_t pm_bunnyfriction; extern cvar_t pm_ktjump; extern cvar_t pm_slidefix; extern cvar_t pm_airstep; @@ -5758,6 +5837,7 @@ void SV_InitLocal (void) Cvar_Register (&sv_bigcoords, cvargroup_serverphysics); Cvar_Register (&pm_bunnyspeedcap, cvargroup_serverphysics); + Cvar_Register (&pm_bunnyfriction, cvargroup_serverphysics); Cvar_Register (&pm_watersinkspeed, cvargroup_serverphysics); Cvar_Register (&pm_flyfriction, cvargroup_serverphysics); Cvar_Register (&pm_ktjump, cvargroup_serverphysics); @@ -5809,12 +5889,10 @@ void SV_InitLocal (void) Cvar_Register (&sv_banproxies, cvargroup_serverpermissions); #ifdef SV_MASTER Cvar_Register (&sv_master, cvargroup_servercontrol); - Cvar_Register (&sv_masterport, cvargroup_servercontrol); #endif Cvar_Register (&filterban, cvargroup_servercontrol); - Cvar_Register (&sv_dlURL, cvargroup_serverpermissions); Cvar_Register (&allow_download, cvargroup_serverpermissions); Cvar_Register (&allow_download_skins, cvargroup_serverpermissions); Cvar_Register (&allow_download_models, cvargroup_serverpermissions); diff --git a/engine/server/sv_master.c b/engine/server/sv_master.c index 770ac7416..0ca1318ea 100644 --- a/engine/server/sv_master.c +++ b/engine/server/sv_master.c @@ -45,6 +45,7 @@ typedef struct svm_server_s { unsigned int bots; //non-human players unsigned int clients; //human players unsigned int maxclients; //limit of bots+clients, but not necessarily spectators. + int secure:1; int needpass:1; int coop:1; char hostname[64]; //just for our own listings. @@ -189,6 +190,28 @@ static svm_server_t *SVM_GetServer(netadr_t *adr) } return NULL; } +qboolean SVM_FixupServerAddress(netadr_t *adr, struct dtlspeercred_s *cred) +{ //if we get a request to send an ice offer over udp, make sure we respond from the socket they heartbeated from, so their (possible) nat won't block us. + //also make sure the fingerprint stuff is okay. + svm_server_t *sv = SVM_GetServer(adr); + char *fp; + size_t b; + if (!sv) + return false; + *adr = sv->adr; //fix it up (mostly the connum so it follows the proper 'return' route) + fp = Info_ValueForKey(sv->rules, "*fp"); + b = Base64_DecodeBlock(fp, NULL, cred->digest, sizeof(cred->digest)); + if (b <= 20) + cred->hash = &hash_sha1; + else if (b <= 256/8) + cred->hash = &hash_sha2_256; + else if (b <= 512/8) + cred->hash = &hash_sha2_512; + else + return false; //just no. + memset(cred->digest+b, 0, cred->hash->digestsize-b); //make sure its -terminated, in case the provided size was wrong + return true; +} static svm_game_t *SVM_FindGame(const char *game, int create) { @@ -579,7 +602,7 @@ vfsfile_t *SVM_Generate_Gamelist(const char **mimetype, const char *query) if (game->numservers || !sv_hideinactivegames.ival) //only show active servers { QuakeCharsToHTML(tmpbuf, sizeof(tmpbuf), game->name, true); - VFS_PRINTF(f, "%s%u player%s", game->name, query?"?":"", query?query:"", tmpbuf, clients, clients==1?"":"s"); + VFS_PRINTF(f, "%s%u player%s", game->name, query?"?":"", query?query:"", tmpbuf, clients, clients==1?"":"s"); if (bots) VFS_PRINTF(f, ", %u bot%s", bots, bots==1?"":"s"); if (specs) @@ -634,12 +657,12 @@ static int QDECL SVM_SortServerRule(const void *r1, const void *r2) vfsfile_t *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr, const char *query) { vfsfile_t *f = VFSPIPE_Open(1, false); - char tmpbuf[256]; + char tmpbuf[512]; char hostname[1024]; svm_server_t *server; netadr_t adr[64]; size_t count, u; - const char *url; + const char *url, *fp; VFS_PRINTF(f, "%s", master_css); VFS_PRINTF(f, "

Single Server Info

\n"); @@ -656,10 +679,17 @@ vfsfile_t *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr); + fp = Info_ValueForKey(server->rules, "*fp"); + if (*fp) + fp = va("?fp=%s", Info_ValueForKey(server->rules, "*fp")); if (server->game->scheme && !server->brokerid) - url = va("%s", server->game->scheme, url, url); + url = va("%s", server->game->scheme, url,fp, url); - VFS_PRINTF(f, "%s%s%s%s%s%s%u/%u\n", server->game?server->game->name:"Unknown", url, (server->needpass&1)?"🔒":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients); + VFS_PRINTF(f, "%s%s%s%s%s%s%s%s%u/%u\n", + server->game?server->game->name:"Unknown", query?"?":"", query?query:"", server->game?server->game->name:"Unknown", //game column + url, //address column + server->secure?"🛡":"🚫", (server->needpass&1)?"🔒":"", (server->coop&1)?"🚸":"", hostname, //hostname column + server->gamedir, server->mapname, server->clients, server->maxclients); VFS_PRINTF(f, "\n"); VFS_PRINTF(f, "
\n"); @@ -749,7 +779,7 @@ vfsfile_t *SVM_Generate_Serverlist(const char **mimetype, const char *masteraddr infourl = url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr); } QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); - VFS_PRINTF(f, "%s%s%s%s%s%s%u", infourl, url, (server->needpass&1)?"🔒":"", (server->coop&1)?"🚸":"", hostname, server->gamedir, server->mapname, server->clients); + VFS_PRINTF(f, "%s%s%s%s%s%s%s%u", infourl, url, server->secure?"🛡":"🚫", (server->needpass&1)?"🔒":"", (server->coop&1)?"🚸":"", hostname, server->gamedir, server->mapname, server->clients); if (server->bots) VFS_PRINTF(f, "+%ub", server->bots); VFS_PRINTF(f, "/%u", server->maxclients); @@ -786,6 +816,10 @@ vfsfile_t *SVM_Generate_Rawlist(const char **mimetype, const char *masteraddr, c svm_game_t *game; svm_server_t *server; vfsfile_t *f = VFSPIPE_Open(1, false); + char *fp; + char *prot; + + masteraddr = ""; //client should work this out based on where it got the list from. COM_StripExtension(gamename, tmpbuf, sizeof(tmpbuf)); game = SVM_FindGame(tmpbuf, false); @@ -794,8 +828,13 @@ vfsfile_t *SVM_Generate_Rawlist(const char **mimetype, const char *masteraddr, c VFS_PRINTF(f, "#Server list for \"%s\"\n", tmpbuf); for (server = (game?game->firstserver:NULL); server; server = server->next) { + prot = Info_ValueForKey(server->rules, "protocol"); + if (!*prot) + prot = va("%i", server->protover); if (server->brokerid) - VFS_PRINTF(f, "rtc://%s/%s \\maxclients\\%u\\clients\\%u\\bots\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\\needpass\\%i\n", masteraddr, server->brokerid, server->maxclients, server->clients, server->bots, *server->hostname?server->hostname:"unnamed", *server->gamedir?server->gamedir:"-", *server->mapname?server->mapname:"-", server->needpass); + VFS_PRINTF(f, "rtc://%s/%s \\protocol\\%s\\maxclients\\%u\\clients\\%u\\bots\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\\needpass\\%i\n", masteraddr, server->brokerid, prot, server->maxclients, server->clients, server->bots, *server->hostname?server->hostname:"unnamed", *server->gamedir?server->gamedir:"-", *server->mapname?server->mapname:"-", server->needpass?1:0); + else if ((fp = Info_ValueForKey(server->rules, "*fp"))) + VFS_PRINTF(f, "rtc://%s/udp/%s \\protocol\\%s\\maxclients\\%u\\clients\\%u\\bots\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\\needpass\\%i\\*fp\\%s\n", masteraddr, NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr), prot, server->maxclients, server->clients, server->bots, *server->hostname?server->hostname:"unnamed", *server->gamedir?server->gamedir:"-", *server->mapname?server->mapname:"-", server->needpass?1:0, fp); else VFS_PRINTF(f, "%s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr)); } @@ -1112,6 +1151,13 @@ static void SVM_ProcessUDPPacket(void) *(int*)&net_from.address.ip6[12]=0; } +#ifdef HAVE_DTLS + if (*(int *)net_message.data != -1) + if (NET_DTLS_Decode(svm_sockets)) + if (!net_message.cursize) + return; +#endif + if (NET_WasSpecialPacket(svm_sockets)) { svm.total.stun++; @@ -1258,7 +1304,6 @@ static void SVM_ProcessUDPPacket(void) SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); if (!strcmp(chal, ourchallenge)) { - bots = atoi(Info_ValueForKey(s, "bots")); clients = atoi(Info_ValueForKey(s, "clients")); clients = max(0, clients-bots); @@ -1272,11 +1317,13 @@ static void SVM_ProcessUDPPacket(void) if (srv) { Q_strncpyz(srv->rules, s, sizeof(srv->rules)); + Info_RemoveKey(srv->rules, "challenge"); //prevent poisoning if (developer.ival) Info_Print(s, "\t"); if (game) srv->protover = atoi(Info_ValueForKey(s, "protocol")); srv->maxclients = atoi(Info_ValueForKey(s, "sv_maxclients")); + srv->secure = !!*Info_ValueForKey(s, "*fp"); srv->needpass = atoi(Info_ValueForKey(s, "needpass")); srv->coop = atoi(Info_ValueForKey(s, "coop")); if (!srv->coop) @@ -1453,6 +1500,7 @@ static void SVM_ProcessUDPPacket(void) Info_Print(s, "\t"); srv->protover = 3;//atoi(Info_ValueForKey(s, "protocol")); srv->maxclients = atoi(Info_ValueForKey(s, "maxclients")); + srv->secure = !!*Info_ValueForKey(s, "*fp"); srv->needpass = atoi(Info_ValueForKey(s, "needpass")); srv->coop = atoi(Info_ValueForKey(s, "coop")); if (!srv->coop) @@ -1500,6 +1548,14 @@ static void SVM_ProcessUDPPacket(void) //this isn't actually useful. we can't use it because we can't protect against spoofed denial-of-service attacks. //we could only use this by sending it a few pings to see if it is actually still responding. which is unreliable (especially if we're getting spammed by packet floods). } + else if (!strcmp(com_token, "ice_answer")) + { //one of our ws clients sent an ice offer over udp. this is the reply... hopefully. + FTENET_TCP_ICEResponse(svm_sockets, ICEMSG_OFFER, s, MSG_ReadString()); + } + else if (!strcmp(com_token, "ice_scand")) + { //a send or ack... + FTENET_TCP_ICEResponse(svm_sockets, ICEMSG_CANDIDATE, s, MSG_ReadString()); + } else svm.total.junk++; } @@ -1577,14 +1633,123 @@ float SVM_RequerySlaves(void) return 4; //nothing happening. } -float SVM_Think(int port) + +static void SVM_RegisterAlias(svm_game_t *game, char *aliasname) { + const char *a; + size_t l; + svm_game_t *aliasgame; + if (!game) + return; + + //make sure we never have dupes. they confuse EVERYTHING. + aliasgame = SVM_FindGame(aliasname, false); + if (aliasgame == game) + return; //already in there somehow. + if (aliasgame) + { + Con_Printf("game alias of %s is already registered\n", aliasname); + return; + } + game->persistent = true; //don't forget us! + + if (!*aliasname) + return; + + a = game->aliases; + if (a) for (; *a; a+=strlen(a)+1); + l = a-game->aliases; + game->aliases = BZ_Realloc(game->aliases, l+strlen(aliasname)+2); + memcpy(game->aliases+l, aliasname, strlen(aliasname)+1); + l += strlen(aliasname)+1; + game->aliases[l] = 0; +} +static void SVM_GameAlias_f(void) +{ + svm_game_t *game = SVM_FindGame(Cmd_Argv(1), 2); + if (!game) + { + Con_Printf("Unable to register game %s\n", Cmd_Argv(1)); + return; + } + SVM_RegisterAlias(game, Cmd_Argv(2)); +} +static void SVM_Register(void) +{ + size_t u; + + svm_sockets = FTENET_CreateCollection(true, SVM_ProcessUDPPacket); + Hash_InitTable(&svm.serverhash, 1024, Z_Malloc(Hash_BytesForBuckets(1024))); + + Cmd_AddCommand ("gamealias", SVM_GameAlias_f); + + Cvar_Register(&sv_masterport, "server control variables"); + Cvar_Register(&sv_masterport_tcp, "server control variables"); + Cvar_Register(&sv_heartbeattimeout, "server control variables"); + Cvar_Register(&sv_maxgames, "server control variables"); + Cvar_Register(&sv_maxservers, "server control variables"); + Cvar_Register(&sv_hideinactivegames, "server control variables"); + Cvar_Register(&sv_sortlist, "server control variables"); + Cvar_Register(&sv_hostname, "server control variables"); + Cvar_Register(&sv_slaverequery, "server control variables"); + for (u = 0; u < countof(sv_masterslave); u++) + Cvar_Register(&sv_masterslave[u].var, "server control variables"); +} +static qboolean SVM_FoundManifest(void *usr, ftemanifest_t *man) +{ + svm_game_t *game; + const char *g; + if (man->protocolname) + { //FIXME: we ought to do this for each manifest we could find. + g = man->protocolname; + +#if 1 + game = SVM_FindGame(man->formalname, 2); +#else + g = COM_Parse(g); + game = SVM_FindGame(com_token, 2); +#endif + if (!game) + return false; + if (man->schemes && !game->scheme) + { + COM_Parse(man->schemes); + game->scheme = Z_StrDup(com_token); + } + while (*g) + { + g = COM_Parse(g); + SVM_RegisterAlias(game, com_token); + } + } + + return false; +} +static void SVM_Begin(void) +{ //called once filesystem etc stuff is started. + SVM_FoundManifest(NULL, fs_manifest); + FS_EnumerateKnownGames(SVM_FoundManifest, NULL); + + Cvar_ForceCallback(&sv_masterport); + Cvar_ForceCallback(&sv_masterport_tcp); +} + +float SVM_Think(void) +{ +#ifndef MASTERONLY + if (!svm_sockets) + { + SVM_Register(); + SVM_Begin(); + } +#endif + NET_ReadPackets (svm_sockets); SVM_RemoveOldServers(); return SVM_RequerySlaves(); } #else -float SVM_Think(int port){return 4;} +float SVM_Think(void){return 4;} #endif @@ -1631,81 +1796,9 @@ static void SVM_Status_f(void) } -static void SVM_RegisterAlias(svm_game_t *game, char *aliasname) -{ - const char *a; - size_t l; - svm_game_t *aliasgame; - if (!game) - return; - - //make sure we never have dupes. they confuse EVERYTHING. - aliasgame = SVM_FindGame(aliasname, false); - if (aliasgame == game) - return; //already in there somehow. - if (aliasgame) - { - Con_Printf("game alias of %s is already registered\n", aliasname); - return; - } - game->persistent = true; //don't forget us! - - if (!*aliasname) - return; - - a = game->aliases; - if (a) for (; *a; a+=strlen(a)+1); - l = a-game->aliases; - game->aliases = BZ_Realloc(game->aliases, l+strlen(aliasname)+2); - memcpy(game->aliases+l, aliasname, strlen(aliasname)+1); - l += strlen(aliasname)+1; - game->aliases[l] = 0; -} -static qboolean SVM_FoundManifest(void *usr, ftemanifest_t *man) -{ - svm_game_t *game; - const char *g; - if (man->protocolname) - { //FIXME: we ought to do this for each manifest we could find. - g = man->protocolname; - -#if 1 - game = SVM_FindGame(man->formalname, 2); -#else - g = COM_Parse(g); - game = SVM_FindGame(com_token, 2); -#endif - if (!game) - return false; - if (man->schemes && !game->scheme) - { - COM_Parse(man->schemes); - game->scheme = Z_StrDup(com_token); - } - while (*g) - { - g = COM_Parse(g); - SVM_RegisterAlias(game, com_token); - } - } - - return false; -} - -static void SVM_GameAlias_f(void) -{ - svm_game_t *game = SVM_FindGame(Cmd_Argv(1), 2); - if (!game) - { - Con_Printf("Unable to register game %s\n", Cmd_Argv(1)); - return; - } - SVM_RegisterAlias(game, Cmd_Argv(2)); -} void SV_Init (struct quakeparms_s *parms) { int manarg; - size_t u; COM_InitArgv (parms->argc, parms->argv); @@ -1731,22 +1824,8 @@ void SV_Init (struct quakeparms_s *parms) Cmd_AddCommand ("quit", SV_Quit_f); Cmd_AddCommand ("status", SVM_Status_f); - Cmd_AddCommand ("gamealias", SVM_GameAlias_f); - svm_sockets = FTENET_CreateCollection(true, SVM_ProcessUDPPacket); - Hash_InitTable(&svm.serverhash, 1024, Z_Malloc(Hash_BytesForBuckets(1024))); - - Cvar_Register(&sv_masterport, "server control variables"); - Cvar_Register(&sv_masterport_tcp, "server control variables"); - Cvar_Register(&sv_heartbeattimeout, "server control variables"); - Cvar_Register(&sv_maxgames, "server control variables"); - Cvar_Register(&sv_maxservers, "server control variables"); - Cvar_Register(&sv_hideinactivegames, "server control variables"); - Cvar_Register(&sv_sortlist, "server control variables"); - Cvar_Register(&sv_hostname, "server control variables"); - Cvar_Register(&sv_slaverequery, "server control variables"); - for (u = 0; u < countof(sv_masterslave); u++) - Cvar_Register(&sv_masterslave[u].var, "server control variables"); + SVM_Register(); Cvar_ParseWatches(); host_initialized = true; @@ -1760,11 +1839,7 @@ void SV_Init (struct quakeparms_s *parms) Cmd_StuffCmds(); Cbuf_Execute (); - Cvar_ForceCallback(&sv_masterport); - Cvar_ForceCallback(&sv_masterport_tcp); - - SVM_FoundManifest(NULL, fs_manifest); - FS_EnumerateKnownGames(SVM_FoundManifest, NULL); + SVM_Begin(); Con_Printf ("Exe: %s\n", version_string()); @@ -1785,7 +1860,7 @@ float SV_Frame (void) } Cbuf_Execute (); - sleeptime = SVM_Think(sv_masterport.ival); + sleeptime = SVM_Think(); //record lots of info over multiple frames, for smoother stats info. svm.total.timestamp = realtime; diff --git a/engine/server/sv_mvd.c b/engine/server/sv_mvd.c index 52769cde9..76f02355f 100644 --- a/engine/server/sv_mvd.c +++ b/engine/server/sv_mvd.c @@ -266,19 +266,23 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade int raw = 0; char password[256] = ""; char userinfo[1024]; - enum { - //MUST BE ORDERED HIGHEST-PRIORITY-LAST - QTVAM_NONE, - QTVAM_PLAIN, -#ifdef TCPCONNECT -// QTVAM_CCITT, //16bit = ddos it - QTVAM_MD4, //fucked. required for eztv compat -// QTVAM_MD5, //fucked, no hash implemented - QTVAM_SHA1, //fucked too nowadays - QTVAM_SHA2_256, // - QTVAM_SHA2_512, // -#endif - } authmethod = QTVAM_NONE; + static struct + { + const char *name; //as seen in protocol + hashfunc_t *func; + int base; + } hashes[] = { + {"NONE", NULL, -1}, //for annonymous connections + {"PLAIN", NULL, 0}, +// {"CCITT", &hash_crc16, 16}, //'the CCITT standard CRC used by XMODEM'. 16bit anyway, don't allow, too easy to guess. +// {"MD4", &hash_md4, 15}, //md4 is available to all QW clients, but probably too weak to really use. +// {"MD5", &hash_md5, 16}, //blurgh + {"SHA1", &hash_sha1, 16}, + {"SHA2_256", &hash_sha2_256, 64}, + {"SHA2_512", &hash_sha2_512, 64}, +// {"SHA3_512", &hash_sha3_512, 16}, //eztv apparently allows this + }; + int authmethod = 0; //which of the above we're trying to use... start = headerstart; @@ -347,32 +351,20 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade { int thisauth; start = COM_ParseToken(start, NULL); - if (!strcmp(com_token, "NONE")) - thisauth = QTVAM_NONE; - else if (!strcmp(com_token, "PLAIN")) - thisauth = QTVAM_PLAIN; -#ifdef TCPCONNECT -// else if (!strcmp(com_token, "CCIT")) -// thisauth = QTVAM_CCITT; - else if (!strcmp(com_token, "MD4")) - thisauth = QTVAM_MD4; -// else if (!strcmp(com_token, "MD5")) -// thisauth = QTVAM_MD5; - else if (!strcmp(com_token, "SHA1")) - thisauth = QTVAM_SHA1; - else if (!strcmp(com_token, "SHA2_256")) - thisauth = QTVAM_SHA2_256; - else if (!strcmp(com_token, "SHA2_512")) - thisauth = QTVAM_SHA2_512; -#endif - else + for (thisauth = 1; ; thisauth++) { - thisauth = QTVAM_NONE; - Con_DPrintf("qtv: received unrecognised auth method (%s)\n", com_token); + if (thisauth == countof(hashes)) + { + Con_DPrintf("qtv: received unrecognised auth method (%s)\n", com_token); + break; + } + if (!strcmp(com_token, hashes[thisauth].name)) + { //we know this one. + if (authmethod < thisauth) + authmethod = thisauth; //and its better than the previous one we saw + break; + } } - - if (authmethod < thisauth) - authmethod = thisauth; } else if (!strcmp(com_token, "SOURCE")) { @@ -416,77 +408,38 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade p->hasauthed = true; //no password, no need to auth. else if (*password) { - hashfunc_t *func = NULL; - if (!*p->challenge && authmethod>QTVAM_PLAIN) + char hash[512]; + qbyte digest[DIGEST_MAXSIZE]; + if (!*p->challenge && hashes[authmethod].func) e = ("QTVSV 1\n" "PERROR: Challenge wasn't given...\n\n"); - else switch (authmethod) + switch(hashes[authmethod].base) { - case QTVAM_NONE: + default: + case -1: //no auth at all e = ("QTVSV 1\n" "PERROR: You need to provide a password.\n\n"); break; - case QTVAM_PLAIN: - p->hasauthed = !strcmp(qtv_password.string, password); + case 0: //plain text. challenge is not used. + Q_snprintfz(hash, sizeof(hash), "%s", qtv_password.string); break; -#ifdef TCPCONNECT - /*case QTVAM_CCITT: - { - unsigned short ushort_result; - QCRC_Init(&ushort_result); - QCRC_AddBlock(&ushort_result, p->challenge, strlen(p->challenge)); - QCRC_AddBlock(&ushort_result, qtv_password.string, strlen(qtv_password.string)); - p->hasauthed = (ushort_result == strtoul(password, NULL, 0)); - } - break;*/ - case QTVAM_MD4: - { - char hash[512]; - int md4sum[4]; - - Q_snprintfz(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); - Com_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum); - Q_snprintfz(hash, sizeof(hash), "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]); - p->hasauthed = !strcmp(password, hash); - } - break; -#ifdef HAVE_LEGACY //to be disabled at some point. - case QTVAM_SHA1: - { - char hash[512]; - int digest[5]; - - Q_snprintfz(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); - CalcHash(&hash_sha1, (char*)digest, sizeof(digest), hash, strlen(hash)); - Q_snprintfz(hash, sizeof(hash), "%08X%08X%08X%08X%08X", digest[0], digest[1], digest[2], digest[3], digest[4]); - p->hasauthed = !strcmp(password, hash); - - if (!p->hasauthed) - func = &hash_sha1; - } - break; -#else - case QTVAM_SHA1: func = &hash_sha1; break; -#endif - case QTVAM_SHA2_256: func = &hash_sha256; break; - case QTVAM_SHA2_512: func = &hash_sha512; break; -// case QTVAM_MD5: func = &hash_md5; break; -#endif - default: - e = ("QTVSV 1\n" - "PERROR: server bug detected.\n\n"); - break; - } - if (func) - { - char hash[DIGEST_MAXSIZE*2+1]; - qbyte digest[DIGEST_MAXSIZE]; - + case 15: //fucked encoding(missing some leading 0s) Q_snprintfz(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); - CalcHash(func, digest, sizeof(digest), hash, strlen(hash)); - Base64_EncodeBlock(digest, func->digestsize, hash, sizeof(hash)); - p->hasauthed = !strcmp(password, hash); + CalcHash(hashes[authmethod].func, digest, sizeof(digest), hash, strlen(hash)); + Q_snprintfz(hash, sizeof(hash), "%X%X%X%X", ((quint32_t*)digest)[0], ((quint32_t*)digest)[1], ((quint32_t*)digest)[2], ((quint32_t*)digest)[3]); + break; + case 16: + Q_snprintfz(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); + CalcHash(hashes[authmethod].func, digest, sizeof(digest), hash, strlen(hash)); + Base16_EncodeBlock(digest, hashes[authmethod].func->digestsize, hash, sizeof(hash)); + break; + case 64: + Q_snprintfz(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); + CalcHash(hashes[authmethod].func, digest, sizeof(digest), hash, strlen(hash)); + Base64_EncodeBlock(digest, hashes[authmethod].func->digestsize, hash, sizeof(hash)); + break; } + p->hasauthed = !strcmp(password, hash); if (!p->hasauthed && !e) { if (raw) @@ -499,66 +452,31 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade else { //no password, and not automagically authed - switch (authmethod) + switch (hashes[authmethod].base) { - case QTVAM_NONE: + case -1: if (raw) e = ""; else e = ("QTVSV 1\n" "PERROR: You need to provide a common auth method.\n\n"); break; - case QTVAM_PLAIN: + case 0: p->hasauthed = !strcmp(qtv_password.string, password); break; -#ifdef TCPCONNECT - /*case QTVAM_CCITT: - e = ("QTVSV 1\n" - "AUTH: CCITT\n" - "CHALLENGE: "); - goto hashedpassword;*/ - case QTVAM_MD4: - e = ("QTVSV 1\n" - "AUTH: MD4\n" - "CHALLENGE: "); - goto hashedpassword; - /*case QTVAM_MD5: - e = ("QTVSV 1\n" - "AUTH: MD5\n" - "CHALLENGE: "); - goto hashedpassword;*/ - case QTVAM_SHA1: - e = ("QTVSV 1\n" - "AUTH: SHA1\n" - "CHALLENGE: "); - goto hashedpassword; - case QTVAM_SHA2_256: - e = ("QTVSV 1\n" - "AUTH: SHA2_256\n" - "CHALLENGE: "); - goto hashedpassword; - case QTVAM_SHA2_512: - e = ("QTVSV 1\n" - "AUTH: SHA2_512\n" - "CHALLENGE: "); - goto hashedpassword; -hashedpassword: + default: { char tmp[32]; Sys_RandomBytes(tmp, sizeof(tmp)); - tobase64(p->challenge, sizeof(p->challenge), tmp, sizeof(tmp)); + Base64_EncodeBlock(tmp, sizeof(tmp), p->challenge, sizeof(p->challenge)); } - VFS_WRITE(clientstream, e, strlen(e)); - VFS_WRITE(clientstream, p->challenge, strlen(p->challenge)); - e = "\n\n"; + e = va("QTVSV 1\n" + "AUTH: %s\n" + "CHALLENGE: %s\n\n", + hashes[authmethod].name, p->challenge); VFS_WRITE(clientstream, e, strlen(e)); return QTV_RETRY; -#endif - default: - e = ("QTVSV 1\n" - "PERROR: server bug detected.\n\n"); - break; } } @@ -1715,7 +1633,7 @@ qboolean SV_MVD_Record (mvddest_t *dest) else if (sv_demoExtensions.ival) { /*everything*/ extern cvar_t pext_replacementdeltas; - demo.recorder.fteprotocolextensions = PEXT_CSQC | PEXT_COLOURMOD | PEXT_DPFLAGS | PEXT_CUSTOMTEMPEFFECTS | PEXT_ENTITYDBL | PEXT_ENTITYDBL2 | PEXT_FATNESS | PEXT_HEXEN2 | PEXT_HULLSIZE | PEXT_LIGHTSTYLECOL | PEXT_MODELDBL | PEXT_SCALE | PEXT_SETATTACHMENT | PEXT_SETVIEW | PEXT_SOUNDDBL | PEXT_SPAWNSTATIC2 | PEXT_TRANS; + demo.recorder.fteprotocolextensions = PEXT_CHUNKEDDOWNLOADS | PEXT_CSQC | PEXT_COLOURMOD | PEXT_DPFLAGS | PEXT_CUSTOMTEMPEFFECTS | PEXT_ENTITYDBL | PEXT_ENTITYDBL2 | PEXT_FATNESS | PEXT_HEXEN2 | PEXT_HULLSIZE | PEXT_LIGHTSTYLECOL | PEXT_MODELDBL | PEXT_SCALE | PEXT_SETATTACHMENT | PEXT_SETVIEW | PEXT_SOUNDDBL | PEXT_SPAWNSTATIC2 | PEXT_TRANS; #ifdef PEXT_VIEW2 demo.recorder.fteprotocolextensions |= PEXT_VIEW2; #endif diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 9c0903d07..dc0db1e36 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -67,19 +67,21 @@ cvar_t dpcompat_noretouchground = CVARD( "dpcompat_noretouchground", "0", "Preve cvar_t sv_sound_watersplash = CVAR( "sv_sound_watersplash", "misc/h2ohit1.wav"); cvar_t sv_sound_land = CVAR( "sv_sound_land", "demon/dland2.wav"); cvar_t sv_stepheight = CVARAFD("pm_stepheight", "", /*dp*/"sv_stepheight", CVAR_SERVERINFO, "If empty, the value "STRINGIFY(PM_DEFAULTSTEPHEIGHT)" will be used instead. This is the size of the step you can step up or down."); +extern cvar_t sv_nqplayerphysics; cvar_t pm_ktjump = CVARF("pm_ktjump", "", CVAR_SERVERINFO); cvar_t pm_bunnyspeedcap = CVARFD("pm_bunnyspeedcap", "", CVAR_SERVERINFO, "0 or 1, ish. If the player is traveling faster than this speed while turning, their velocity will be gracefully reduced to match their current maxspeed. You can still rocket-jump to gain high velocity, but turning will reduce your speed back to the max. This can be used to disable bunny hopping."); cvar_t pm_watersinkspeed = CVARFD("pm_watersinkspeed", "", CVAR_SERVERINFO, "This is the speed that players will sink at while inactive in water. Empty means 60."); cvar_t pm_flyfriction = CVARFD("pm_flyfriction", "", CVAR_SERVERINFO, "Amount of friction that applies in fly or 6dof mode. Empty means 4."); cvar_t pm_slidefix = CVARFD("pm_slidefix", "", CVAR_SERVERINFO, "Fixes an issue when walking down slopes (ie: so they act more like slopes and not a series of steps)"); -cvar_t pm_slidyslopes = CVARFD("pm_slidyslopes", "", CVAR_SERVERINFO, "Replicates NQ behaviour, where players will slowly slide down ramps"); +cvar_t pm_slidyslopes = CVARFD("pm_slidyslopes", "", CVAR_SERVERINFO, "Replicates NQ behaviour, where players will slowly slide down ramps. Generally requires 'pm_noround 1' too, otherwise the effect rounds to nothing."); +cvar_t pm_bunnyfriction = CVARFD("pm_bunnyfriction", "", CVAR_SERVERINFO, "Replicates NQ behaviour, ensuring that there's at least a frame of friction while jumping - friction is proportional to tick rate."); cvar_t pm_autobunny = CVARFD("pm_autobunny", "", CVAR_SERVERINFO, "Players will continue jumping without needing to release the jump button."); cvar_t pm_airstep = CVARAFD("pm_airstep", "", /*dp*/"sv_jumpstep", CVAR_SERVERINFO, "Allows players to step up while jumping. This makes stairs more graceful but also increases potential jump heights."); cvar_t pm_pground = CVARFD("pm_pground", "", CVAR_SERVERINFO, "Use persisten onground state instead of recalculating every frame."CON_WARNING"Do NOT use with nq mods, as most nq mods will interfere with onground state, resulting in glitches."); cvar_t pm_stepdown = CVARFD("pm_stepdown", "", CVAR_SERVERINFO, "Causes physics to stick to the ground, instead of constantly losing traction whiloe going down steps."); cvar_t pm_walljump = CVARFD("pm_walljump", "", CVAR_SERVERINFO, "Allows the player to bounce off walls while arborne."); -cvar_t pm_edgefriction = CVARAFD("pm_edgefriction", "", /*nq*/"edgefriction", CVAR_SERVERINFO, "Default value of 2"); +cvar_t pm_edgefriction = CVARAFD("pm_edgefriction", "", /*nq*/"edgefriction", CVAR_SERVERINFO, "Increases friction when about to walk over a cliff, so you're less likely to plummet by mistake. When empty defaults to 2, but uses a tracebox instead of a traceline to detect the drop."); #define cvargroup_serverphysics "server physics variables" void WPhys_Init(void) @@ -153,8 +155,6 @@ void WPhys_CheckVelocity (world_t *w, wedict_t *ent) { int i; #ifdef HAVE_SERVER - extern cvar_t sv_nqplayerphysics; - if (sv_nqplayerphysics.ival) { //bound axially (like vanilla) for (i=0 ; i<3 ; i++) @@ -689,9 +689,9 @@ static qboolean WPhys_PushAngles (world_t *w, wedict_t *pusher, vec3_t move, vec //float oldsolid; pushed_t *p; vec3_t org, org2, move2, forward, right, up; - short yawchange; - - yawchange = (amove[PITCH]||amove[ROLL])?0:ANGLE2SHORT(amove[YAW]); +#ifdef HAVE_SERVER + short yawchange = (amove[PITCH]||amove[ROLL])?0:ANGLE2SHORT(amove[YAW]); +#endif pushed_p = pushed; @@ -770,8 +770,10 @@ static qboolean WPhys_PushAngles (world_t *w, wedict_t *pusher, vec3_t move, vec // try moving the contacted entity VectorAdd (check->v->origin, move, check->v->origin); VectorAdd (check->v->angles, amove, check->v->angles); - if (check->entnum>0&&(check->entnum)<=sv.allocated_client_slots) +#ifdef HAVE_SERVER + if (w == &sv.world && check->entnum>0&&(check->entnum)<=sv.allocated_client_slots) svs.clients[check->entnum-1].baseangles[YAW] += yawchange; +#endif // figure movement due to the pusher's amove VectorSubtract (check->v->origin, pusher->v->origin, org); @@ -873,8 +875,10 @@ static qboolean WPhys_PushAngles (world_t *w, wedict_t *pusher, vec3_t move, vec VectorCopy (p->angles, p->ent->v->angles); World_LinkEdict (w, p->ent, false); - if (p->ent->entnum>0&&(p->ent->entnum)<=sv.allocated_client_slots) +#ifdef HAVE_SERVER + if (w==&sv.world && p->ent->entnum>0&&(p->ent->entnum)<=sv.allocated_client_slots) svs.clients[p->ent->entnum-1].baseangles[YAW] -= yawchange; +#endif } return false; } @@ -1221,8 +1225,10 @@ A moving object that doesn't obey physics static void WPhys_Physics_Noclip (world_t *w, wedict_t *ent) { vec3_t end; +#ifdef HAVE_SERVER trace_t trace; wedict_t *impact; +#endif // regular thinking if (!WPhys_RunThink (w, ent)) @@ -1231,7 +1237,7 @@ static void WPhys_Physics_Noclip (world_t *w, wedict_t *ent) VectorMA (ent->v->angles, host_frametime, ent->v->avelocity, ent->v->angles); VectorMA (ent->v->origin, host_frametime, ent->v->velocity, end); -#ifndef CLIENTONLY +#ifdef HAVE_SERVER //allow spectators to no-clip through portals without bogging down sock's mods. if (ent->entnum > 0 && ent->entnum <= sv.allocated_client_slots && w == &sv.world) { @@ -2419,7 +2425,6 @@ void World_Physics_Frame(world_t *w) int i; qboolean retouch; wedict_t *ent; - extern cvar_t sv_nqplayerphysics; w->framenum++; @@ -2459,7 +2464,7 @@ void World_Physics_Frame(world_t *w) if (retouch) World_LinkEdict (w, ent, true); // force retouch even for stationary -#ifndef CLIENTONLY +#ifdef HAVE_SERVER if (i > 0 && i <= sv.allocated_client_slots && w == &sv.world) { if (!svs.clients[i-1].isindependant) @@ -2508,7 +2513,7 @@ void World_Physics_Frame(world_t *w) *w->g.force_retouch-=1; } -#ifndef CLIENTONLY +#ifdef HAVE_SERVER /* ================ SV_Physics @@ -2523,7 +2528,6 @@ qboolean SV_Physics (void) double trueframetime = host_frametime; double maxtic = sv_maxtic.value; double mintic = sv_mintic.value; - extern cvar_t sv_nqplayerphysics; if (sv_nqplayerphysics.ival) if (mintic < 0.013) mintic = 0.013; //NQ physics can't cope with low rates and just generally bugs out. diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index 7d5897dd0..25e95934b 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -713,7 +713,7 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int for (split = client, seat = 0; split; split = split->controlled, seat++) { - if (split->protocol == SCP_QUAKEWORLD) + //if (split->protocol == SCP_QUAKEWORLD) { if (split->fteprotocolextensions & without) { @@ -3499,8 +3499,7 @@ void SV_SendClientMessages (void) stepmsec = 13; cmd.msec = stepmsec; - if (sv_showpredloss.ival) - Con_Printf("%s: forcing %g msecs (anti-hover)\n", c->name, cmd.msec); + c->hoverms += cmd.msec; VectorCopy(c->lastcmd.angles, cmd.angles); cmd.buttons = c->lastcmd.buttons; SV_RunCmd (&cmd, true); diff --git a/engine/server/sv_sys_unix.c b/engine/server/sv_sys_unix.c index d817f6dd7..3f4267946 100644 --- a/engine/server/sv_sys_unix.c +++ b/engine/server/sv_sys_unix.c @@ -1271,8 +1271,8 @@ dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) dllhandle_t *lib; lib = dlopen (name, RTLD_LAZY); - if (!lib && !strstr(name, ".so")) - lib = dlopen (va("%s.so", name), RTLD_LAZY); + if (!lib && !strstr(name, ARCH_DL_POSTFIX)) + lib = dlopen (va("%s"ARCH_DL_POSTFIX, name), RTLD_LAZY); if (!lib) { const char *err = dlerror(); diff --git a/engine/server/sv_sys_win.c b/engine/server/sv_sys_win.c index dbdf36a16..06a5ee1fc 100644 --- a/engine/server/sv_sys_win.c +++ b/engine/server/sv_sys_win.c @@ -753,6 +753,38 @@ int Sys_EnumerateFiles (const char *gpath, const char *match, int (QDECL *func)( return Sys_EnumerateFiles2(fullmatch, start, start, func, parm, spath); } +//wide only. we let the windows api sort out the mess of file urls. system-wide consistancy. +qboolean Sys_ResolveFileURL(const char *inurl, int inlen, char *out, int outlen) +{ + char *cp; + wchar_t wurl[MAX_PATH]; + wchar_t local[MAX_PATH]; + DWORD grr; + static HRESULT (WINAPI *pPathCreateFromUrlW)(PCWSTR pszUrl, PWSTR pszPath, DWORD *pcchPath, DWORD dwFlags); + if (!pPathCreateFromUrlW) + pPathCreateFromUrlW = Sys_GetAddressForName(Sys_LoadLibrary("Shlwapi.dll", NULL), "PathCreateFromUrlW"); + if (!pPathCreateFromUrlW) + return false; + + //need to make a copy, because we can't terminate the inurl easily. + cp = malloc(inlen+1); + memcpy(cp, inurl, inlen); + cp[inlen] = 0; + widen(wurl, sizeof(wurl), cp); + free(cp); + grr = sizeof(local)/sizeof(wchar_t); + if (FAILED(pPathCreateFromUrlW(wurl, local, &grr, 0))) + return false; + narrowen(out, outlen, local); + while(*out) + { + if (*out == '\\') + *out = '/'; + out++; + } + return true; +} + /* ================ Sys_Error diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 66881913b..3551ca871 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -84,7 +84,7 @@ cvar_t cmd_allowaccess = CVAR("cmd_allowaccess", "0"); //set to 1 to allow cmd t cvar_t cmd_gamecodelevel = CVARF("cmd_gamecodelevel", STRINGIFY(RESTRICT_LOCAL), CVAR_NOTFROMSERVER); //execution level which gamecode is told about (for unrecognised commands) cvar_t sv_pure = CVARFD("sv_pure", "", CVAR_SERVERINFO, "The most evil cvar in the world, many clients will ignore this.\n0=standard quake rules.\n1=clients should prefer files within packages present on the server.\n2=clients should use *only* files within packages present on the server.\nDue to quake 1.01/1.06 differences, a setting of 2 only works in total conversions."); -cvar_t sv_nqplayerphysics = CVARAFCD("sv_nqplayerphysics", "auto", "sv_nomsec", CVAR_ARCHIVE, SV_NQPhysicsUpdate, "Disable player prediction and run NQ-style player physics instead. This can be used for compatibility with mods that expect exact behaviour."); +cvar_t sv_nqplayerphysics = CVARAFCD("sv_nqplayerphysics", "auto", "sv_nomsec", CVAR_ARCHIVE, SV_NQPhysicsUpdate, "Disable player prediction and run NQ-style player physics instead. This can be used for compatibility with mods that expect exact behaviour. A value of 2 will not block prediction, and may be juddery/jerky/swimmy."); #ifdef HAVE_LEGACY static cvar_t sv_brokenmovetypes = CVARD("sv_brokenmovetypes", "0", "Emulate vanilla quakeworld by forcing MOVETYPE_WALK on all players. Shouldn't be used for any games other than QuakeWorld."); @@ -115,6 +115,7 @@ extern cvar_t pm_bunnyspeedcap; extern cvar_t pm_ktjump; extern cvar_t pm_slidefix; extern cvar_t pm_slidyslopes; +extern cvar_t pm_bunnyfriction; extern cvar_t pm_autobunny; extern cvar_t pm_airstep; extern cvar_t pm_pground; @@ -374,7 +375,6 @@ void SV_New_f (void) ClientReliableWrite_Byte (host_client, playernum); split->state = cs_connected; - split->connection_started = realtime; #ifdef SVRANKING split->stats_started = realtime; #endif @@ -411,7 +411,6 @@ void SV_New_f (void) playernum |= 128; split->state = cs_connected; - split->connection_started = realtime; #ifdef SVRANKING split->stats_started = realtime; #endif @@ -2087,8 +2086,12 @@ void SVQW_Spawn_f (void) //which really sucks. //so let multiplayer people know what's going on so that they don't think its an actual bug, and can harass the admin to get it fixed in mods that allow for it. if (!strcmp(sv_nqplayerphysics.string, "auto") || !strcmp(sv_nqplayerphysics.string, "")) - if (sv_nqplayerphysics.ival) + { + if (sv_nqplayerphysics.ival == 2) + SV_PrintToClient(host_client, PRINT_HIGH, CON_WARNING"Movement prediction may not match server due to non-quakeworld mod compatibilty\n"); + else SV_PrintToClient(host_client, PRINT_HIGH, CON_WARNING"Movement prediction is disabled in favour of non-quakeworld mod compatibilty\n"); + } } } @@ -3172,7 +3175,6 @@ qboolean SV_FindRemotePackage(const char *package, char *url, size_t urlsize) //or something. - extern cvar_t sv_dlURL; vfsfile_t *f; char line[512]; @@ -3206,9 +3208,9 @@ qboolean SV_FindRemotePackage(const char *package, char *url, size_t urlsize) VFS_CLOSE(f); } - if (*sv_dlURL.string) + if (*fs_dlURL.string) { //a fallback, though the above mechanism allows for a wildcard for all. - Q_strncatz(sv_dlURL.string, package, urlsize); + Q_strncatz(fs_dlURL.string, package, urlsize); Q_strncatz(url, package, urlsize); return true; } @@ -5458,7 +5460,6 @@ void SV_SetUpClientEdict (client_t *cl, edict_t *ent) ent->v->movetype = MOVETYPE_NOCLIP; ent->v->frags = 0; - cl->connection_started = realtime; } //dynamically add/remove a splitscreen client @@ -5948,18 +5949,7 @@ static void SVNQ_Spawn_f (void) host_client->maxspeed = ent->xv->maxspeed; } else - { - ED_Clear(svprogfuncs, ent); - ED_Spawned(ent, false); - - ent->v->colormap = NUM_FOR_EDICT(svprogfuncs, ent); - ent->v->team = 0; // FIXME - svprogfuncs->SetStringField(svprogfuncs, ent, &ent->v->netname, host_client->name, true); - - host_client->entgravity = ent->xv->gravity = 1.0; - host_client->entgravity*=sv_gravity.value; - host_client->maxspeed = ent->xv->maxspeed = sv_maxspeed.value; - } + SV_SetUpClientEdict(host_client, ent); // // force stats to be updated @@ -6220,6 +6210,8 @@ static void SVNQ_Status_f(void) int hours, mins, secs; if (!cl->state) continue; + if (i >= host_client->max_net_clients) + break; //don't send more than it expects. the ping parsers will give up and get spammy (sucks). secs = realtime - cl->connection_started; mins = secs/60; secs -= mins*60; @@ -6417,6 +6409,7 @@ ucmd_t ucmds[] = {"sayone", SV_SayOne_f}, {"say", SV_Say_f}, {"say_team", SV_Say_Team_f}, + {"status", SVNQ_Status_f}, #ifdef SVRANKING {"topten", Rank_ListTop10_f}, #endif @@ -7155,6 +7148,9 @@ int SV_PMTypeForClient (client_t *cl, edict_t *ent) } #endif + if (sv_nqplayerphysics.ival && sv_nqplayerphysics.ival != 2) + return PM_NONE; //let the client know that its prediction is fucked. should make it just lerp. + switch((int)ent->v->movetype) { case MOVETYPE_NOCLIP: @@ -7243,6 +7239,12 @@ void SV_RunCmd (usercmd_t *ucmd, qboolean recurse) #ifdef NEWSPEEDCHEATPROT if (ucmd->msec && host_client->msecs > 500) host_client->msecs = 500; + if (host_client->hoverms) + { + if (sv_showpredloss.ival) + Con_Printf("%s: forcing %g msecs (anti-hover)\n", host_client->name, cmd.msec); + host_client->hoverms = 0; + } if (ucmd->msec > host_client->msecs) { //they're over their timeslice allocation //if they're not taking the piss then be prepared to truncate the frame. this should hide clockskew without allowing full-on speedcheats. @@ -7608,6 +7610,7 @@ void SV_RunCmd (usercmd_t *ucmd, qboolean recurse) movevars.stepdown = (pm_stepdown.value != 0); movevars.walljump = (pm_walljump.value); movevars.slidyslopes = (pm_slidyslopes.value!=0); + movevars.bunnyfriction = (pm_bunnyfriction.value!=0); movevars.autobunny = (pm_autobunny.value!=0); movevars.watersinkspeed = *pm_watersinkspeed.string?pm_watersinkspeed.value:60; movevars.flyfriction = *pm_flyfriction.string?pm_flyfriction.value:4; diff --git a/engine/vk/vk_backend.c b/engine/vk/vk_backend.c index 29105f9fb..c49801b5f 100644 --- a/engine/vk/vk_backend.c +++ b/engine/vk/vk_backend.c @@ -2155,6 +2155,9 @@ static void BE_CreatePipeline(program_t *p, unsigned int shaderflags, unsigned i VkGraphicsPipelineCreateInfo pipeCreateInfo = {VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; VkPipelineShaderStageCreateInfo shaderStages[2] = {{0}}; VkPipelineRasterizationStateRasterizationOrderAMD ro = {VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_RASTERIZATION_ORDER_AMD}; //long enough names for you? +#ifdef VK_KHR_fragment_shading_rate + VkPipelineFragmentShadingRateStateCreateInfoKHR shadingrate = {VK_STRUCTURE_TYPE_PIPELINE_FRAGMENT_SHADING_RATE_STATE_CREATE_INFO_KHR}; +#endif struct specdata_s { int alphamode; @@ -2511,6 +2514,28 @@ static void BE_CreatePipeline(program_t *p, unsigned int shaderflags, unsigned i // pipeCreateInfo.flags = VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT; +#ifdef VK_KHR_fragment_shading_rate + if (vk.khr_fragment_shading_rate) + { + //three ways to specify rates... we need to set which one wins here. + shadingrate.combinerOps[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR;//pipeline vs primitive + shadingrate.combinerOps[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR;//previous vs attachment + if (blendflags & SBITS_MISC_FULLRATE) + { + shadingrate.fragmentSize.width = 1; + shadingrate.fragmentSize.height = 1; + } + else + { //actually this is more quater-rate. oh well. + shadingrate.fragmentSize.width = 2; + shadingrate.fragmentSize.height = 2; + } + + shadingrate.pNext = pipeCreateInfo.pNext; + pipeCreateInfo.pNext = &shadingrate; + } +#endif + err = vkCreateGraphicsPipelines(vk.device, vk.pipelinecache, 1, &pipeCreateInfo, vkallocationcb, &pipe->pipeline); DebugSetName(VK_OBJECT_TYPE_PIPELINE, (uint64_t)pipe->pipeline, p->name); @@ -2530,7 +2555,7 @@ static void BE_BindPipeline(program_t *p, unsigned int shaderflags, unsigned int blendflags &= 0 | SBITS_SRCBLEND_BITS | SBITS_DSTBLEND_BITS | SBITS_MASK_BITS | SBITS_ATEST_BITS | SBITS_MISC_DEPTHWRITE | SBITS_MISC_NODEPTHTEST | SBITS_DEPTHFUNC_BITS - | SBITS_LINES + | SBITS_LINES | SBITS_MISC_FULLRATE ; shaderflags &= 0 | SHADER_CULL_FRONT | SHADER_CULL_BACK diff --git a/engine/vk/vk_init.c b/engine/vk/vk_init.c index f2937bc05..f3f7af881 100644 --- a/engine/vk/vk_init.c +++ b/engine/vk/vk_init.c @@ -22,6 +22,9 @@ static cvar_t vk_khr_get_memory_requirements2 = CVARFD("vk_khr_get_memory_requir static cvar_t vk_khr_dedicated_allocation = CVARFD("vk_khr_dedicated_allocation", "", CVAR_VIDEOLATCH, "Flag vulkan memory allocations as dedicated, where applicable."); static cvar_t vk_khr_push_descriptor = CVARFD("vk_khr_push_descriptor", "", CVAR_VIDEOLATCH, "Enables better descriptor streaming."); static cvar_t vk_amd_rasterization_order = CVARFD("vk_amd_rasterization_order", "", CVAR_VIDEOLATCH, "Enables the use of relaxed rasterization ordering, for a small speedup at the minor risk of a little zfighting."); +#ifdef VK_KHR_fragment_shading_rate +static cvar_t vK_khr_fragment_shading_rate = CVARFD("vK_khr_fragment_shading_rate","", CVAR_VIDEOLATCH, "Enables the use of variable shading rates."); +#endif #ifdef VK_EXT_astc_decode_mode static cvar_t vk_ext_astc_decode_mode = CVARFD("vk_ext_astc_decode_mode", "", CVAR_VIDEOLATCH, "Enables reducing texture cache sizes for LDR ASTC-compressed textures."); #endif @@ -42,9 +45,12 @@ void VK_RegisterVulkanCvars(void) Cvar_Register (&vk_usememorypools, VKRENDEREROPTIONS); Cvar_Register (&vk_khr_get_memory_requirements2,VKRENDEREROPTIONS); - Cvar_Register (&vk_khr_dedicated_allocation,VKRENDEREROPTIONS); - Cvar_Register (&vk_khr_push_descriptor, VKRENDEREROPTIONS); - Cvar_Register (&vk_amd_rasterization_order, VKRENDEREROPTIONS); + Cvar_Register (&vk_khr_dedicated_allocation, VKRENDEREROPTIONS); + Cvar_Register (&vk_khr_push_descriptor, VKRENDEREROPTIONS); + Cvar_Register (&vk_amd_rasterization_order, VKRENDEREROPTIONS); +#ifdef VK_KHR_fragment_shading_rate + Cvar_Register (&vK_khr_fragment_shading_rate, VKRENDEREROPTIONS); +#endif #ifdef VK_EXT_astc_decode_mode Cvar_Register (&vk_ext_astc_decode_mode, VKRENDEREROPTIONS); #endif @@ -3497,7 +3503,7 @@ static void VK_PaintScreen(void) if (topmenu && topmenu->isopaque) nohud = true; #ifdef VM_CG - else if (q3->cg.Redraw(cl.time)) + else if (q3 && q3->cg.Redraw(cl.time)) nohud = true; #endif #ifdef CSQC_DAT @@ -3540,7 +3546,9 @@ static void VK_PaintScreen(void) nohud = true; } - SCR_DrawTwoDimensional(nohud); + r_refdef.playerview = &cl.playerview[0]; + if (!vrui.enabled) + SCR_DrawTwoDimensional(nohud); V_UpdatePalette (false); R2D_BrightenScreen(); @@ -4708,6 +4716,9 @@ qboolean VK_Init(rendererstate_t *info, const char **sysextnames, qboolean (*cre {&vk.khr_dedicated_allocation, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, &vk_khr_dedicated_allocation, true, NULL, NULL}, {&vk.khr_push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, &vk_khr_push_descriptor, true, NULL, NULL}, {&vk.amd_rasterization_order, VK_AMD_RASTERIZATION_ORDER_EXTENSION_NAME, &vk_amd_rasterization_order, false, NULL, NULL}, +#ifdef VK_KHR_fragment_shading_rate + {&vk.khr_fragment_shading_rate, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, &vK_khr_fragment_shading_rate, true, NULL, NULL}, +#endif #ifdef VK_EXT_astc_decode_mode {&vk.ext_astc_decode_mode, VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, &vk_ext_astc_decode_mode, true, NULL, NULL}, #endif @@ -4837,6 +4848,7 @@ qboolean VK_Init(rendererstate_t *info, const char **sysextnames, qboolean (*cre okay = vrsetup.createinstance(&vrsetup, NULL, NULL); if (!okay) { + Con_TPrintf(CON_ERROR"Unable to create vulkan instance\n"); if (info->vr) info->vr->Shutdown(); return false; diff --git a/engine/vk/vkrenderer.h b/engine/vk/vkrenderer.h index f8dba7c36..c455780cf 100644 --- a/engine/vk/vkrenderer.h +++ b/engine/vk/vkrenderer.h @@ -28,7 +28,7 @@ #endif #define VK_NO_PROTOTYPES -#include <../vulkan/vulkan.h> +#include #if defined(_MSC_VER) && !defined(UINT64_MAX) #define UINT64_MAX _UI64_MAX @@ -281,6 +281,9 @@ extern struct vulkaninfo_s qboolean khr_push_descriptor; //more efficient descriptor streaming qboolean amd_rasterization_order; //allows primitives to draw in any order qboolean ext_astc_decode_mode; //small perf boost +#ifdef VK_KHR_fragment_shading_rate + qboolean khr_fragment_shading_rate; //small perf boost. probably more useful for battery. +#endif VkInstance instance; VkDevice device; diff --git a/engine/web/ftejslib.js b/engine/web/ftejslib.js index 6e6c5def5..05d288812 100644 --- a/engine/web/ftejslib.js +++ b/engine/web/ftejslib.js @@ -260,7 +260,9 @@ mergeInto(LibraryManager.library, //we don't steal that because its impossible to leave it again once used. if (FTEC.evcb.key != 0 && event.keyCode != 122) { - if ({{{makeDynCall('iiiii','FTEC.evcb.key')}}}(0, event.type=='keydown', event.keyCode, 0)) + var codepoint = event.key.codePointAt(1)?0:event.key.codePointAt(0); // only if its a single codepoint - none of this 'Return' nonsense. + if (codepoint < ' ') codepoint = 0; //don't give a codepoint for c0 chars - like tab. + if ({{{makeDynCall('iiiii','FTEC.evcb.key')}}}(0, event.type=='keydown', event.keyCode, codepoint)) event.preventDefault(); } break; @@ -269,6 +271,7 @@ mergeInto(LibraryManager.library, case 'touchcancel': case 'touchleave': case 'touchmove': + event.preventDefault(); var touches = event.changedTouches; for (var i = 0; i < touches.length; i++) { @@ -278,12 +281,11 @@ mergeInto(LibraryManager.library, if (FTEC.evcb.button) { if (event.type == 'touchstart') - {{{makeDynCall('viii','FTEC.evcb.button')}}}(t.identifier+1, 1, 0); + {{{makeDynCall('viii','FTEC.evcb.button')}}}(t.identifier+1, 1, -1); else if (event.type != 'touchmove') - {{{makeDynCall('viii','FTEC.evcb.button')}}}(t.identifier+1, 0, 0); + {{{makeDynCall('viii','FTEC.evcb.button')}}}(t.identifier+1, 0, -1); } } - event.preventDefault(); break; case 'dragenter': case 'dragover': @@ -792,6 +794,8 @@ mergeInto(LibraryManager.library, return -1; if (s.con == 0) return 0; //not connected yet + if (s.err != 0) + return -1; if (len == 0) return 0; //... s.ws.send(HEAPU8.subarray(data, data+len)); @@ -886,6 +890,29 @@ mergeInto(LibraryManager.library, s.recvchan.binaryType = 'arraybuffer'; s.recvchan.onmessage = s.ws.onmessage; }; + s.pc.onconnectionstatechange = function(e) + { +//console.log(s.pc.connectionState); +//console.log(e); + switch (s.pc.connectionState) + { + //case "new": + //case "checking": + //case "connected": + case "disconnected": + s.err = 1; + break; + case "closed": + s.con = 0; + s.err = 1; + break; + case "failed": + s.err = 1; + break; + default: + break; + } + }; if (clientside) { @@ -921,10 +948,14 @@ mergeInto(LibraryManager.library, try { - if (1) + try + { desc = JSON.parse(offer); - else + } + catch(e) + { desc = {sdp:offer, type:offertype}; + } s.pc.setRemoteDescription(desc).then(() => { @@ -966,10 +997,14 @@ mergeInto(LibraryManager.library, try //don't screw up if the peer is trying to screw with us. { var desc; - if (1) + try + { desc = JSON.parse(offer); - else + } + catch(e) + { desc = {candidate:offer, sdpMid:null, sdpMLineIndex:0}; + } s.pc.addIceCandidate(desc); } catch(err) { console.log(err); } }, @@ -977,7 +1012,6 @@ mergeInto(LibraryManager.library, emscriptenfte_async_wget_data2 : function(url, ctx, onload, onerror, onprogress) { var _url = UTF8ToString(url); -// console.log("Attempting download of " + _url); var http = new XMLHttpRequest(); try { @@ -993,7 +1027,6 @@ mergeInto(LibraryManager.library, http.onload = function(e) { -//console.log("onload: " + _url + " status " + http.status); if (http.status == 200) { if (onload) @@ -1008,7 +1041,8 @@ mergeInto(LibraryManager.library, http.onerror = function(e) { -//console.log("onerror: " + _url); + //Note: Unfortunately it is not possible to distinguish between dns, network, certificate, or CORS errors (other than viewing the browser's log). + // This is apparently intentional to prevent sites probing lans - cors will make them all seem dead and thus uninteresting targets. if (onerror) {{{makeDynCall('vii','onerror')}}}(ctx, 0); }; diff --git a/engine/web/gl_vidweb.c b/engine/web/gl_vidweb.c index aff448fb1..b37851008 100644 --- a/engine/web/gl_vidweb.c +++ b/engine/web/gl_vidweb.c @@ -172,14 +172,14 @@ static int DOM_KeyEvent(unsigned int devid, int down, int scan, int uni) // Con_Printf("Key %s %i %i:%c\n", down?"down":"up", scan, uni, uni?(char)uni:' '); if (shift_down) { - uni = domkeytoshift(scan); +// uni = domkeytoshift(scan); scan = domkeytoquake(scan); - uni = (uni >= 32 && uni <= 127)?uni:0; +// uni = (uni >= 32 && uni <= 127)?uni:0; } else { scan = domkeytoquake(scan); - uni = (scan >= 32 && scan <= 127)?scan:0; +// uni = (scan >= 32 && scan <= 127)?scan:0; } IN_KeyEvent(keyboardid[devid], down, scan, uni); //Chars which don't map to some printable ascii value get preventDefaulted. @@ -192,8 +192,31 @@ static int DOM_KeyEvent(unsigned int devid, int down, int scan, int uni) return true; // return false; } +static int RemapTouchId(int id, qboolean final) +{ + static int touchids[8]; + int i; + if (!id) + return id; + for (i = 1; i < countof(touchids); i++) + if (touchids[i] == id) + { + if (final) + touchids[i] = 0; + return i; + } + for (i = 1; i < countof(touchids); i++) + if (touchids[i] == 0) + { + if (!final) + touchids[i] = id; + return i; + } + return id; +} static void DOM_ButtonEvent(unsigned int devid, int down, int button) { + devid = RemapTouchId(devid, !down); if (down == 2) { //fixme: the event is a float. we ignore that. @@ -216,11 +239,16 @@ static void DOM_ButtonEvent(unsigned int devid, int down, int button) else if (button == 1) button = 2; - IN_KeyEvent(mouseid[devid], down, K_MOUSE1+button, 0); + if (button < 0) + button = K_TOUCH; + else + button += K_MOUSE1; + IN_KeyEvent(mouseid[devid], down, button, 0); } } static void DOM_MouseMove(unsigned int devid, int abs, float x, float y, float z, float size) { + devid = RemapTouchId(devid, false); IN_MouseMove(mouseid[devid], abs, x, y, z, size); } diff --git a/engine/web/sys_web.c b/engine/web/sys_web.c index fdc2c9bd0..8273214ce 100644 --- a/engine/web/sys_web.c +++ b/engine/web/sys_web.c @@ -49,12 +49,50 @@ qboolean Sys_RandomBytes(qbyte *string, int len) void Sys_Printf (char *fmt, ...) { va_list argptr; - char buf[1024]; + char text[2048]; + conchar_t ctext[2048], *e, *c; + unsigned int len = 0; + unsigned int w, codeflags; va_start (argptr,fmt); - vsnprintf (buf, sizeof(buf), fmt, argptr); - emscriptenfte_print(buf); + vsnprintf (text, sizeof(text), fmt, argptr); va_end (argptr); + + //make sense of any markup + e = COM_ParseFunString(CON_WHITEMASK, text, ctext, sizeof(ctext), false); + + //convert to utf-8 for the js to make sense of + for (c = ctext; c < e; ) + { + c = Font_Decode(c, &codeflags, &w); + if (codeflags & CON_HIDDEN) + continue; + + //dequake it as required, so its only codepoints the browser will understand. should probably deal with linefeeds specially. + if (w >= 0xe000 && w < 0xe100) + { //quake-encoded mess + if ((w & 0x7f) >= 0x20) + w &= 0x7f; //regular (discoloured) ascii + else if (w & 0x80) + { //c1 glyphs + static char tab[32] = "---#@.@@@@ # >.." "[]0123456789.---"; + w = tab[w&31]; + } + else + { //c0 glyphs + static char tab[32] = ".####.#### # >.." "[]0123456789.---"; + w = tab[w&31]; + } + } + else if (w < ' ' && w != '\t' && w != '\r' && w != '\n') + w = '?'; //c0 chars are awkward + + len += utf8_encode(text+len, w, sizeof(text)-1-len); + } + text[len] = 0; + + //now throw it at the browser's console.log. + emscriptenfte_print(text); } #if 1 diff --git a/flatpak.json b/flatpak.json new file mode 100644 index 000000000..028cbc42e --- /dev/null +++ b/flatpak.json @@ -0,0 +1,79 @@ +{ + "//":"To build:", + "//":" flatpak-builder --user --install --from-git=~/quake/fteqw-code-git build-dir flatpak.json", + "//":" (swap the git url for a web-based one if you're not gonna make any local changes first - note that it'll only take committed changes)", + "//":"To then run:", + "//":" cd ~/quake && flatpak run info.triptohell.fteqw", + + "//":"To then create a bundle:", + "//":" flatpak build-bundle ~/.local/share/flatpak/repo fteqw.flatpak info.triptohell.fteqw", + "//":"Which can then be distributed and hopefully double-clicked etc, or just 'flatpak install --user fteqw.flatpak'.", + + "//":"Note: If you're making a TC, add your data/fmf as an extra module somehow, and remove the filesystem=host access.", + + "app-id": "info.triptohell.fteqw", + "runtime": "org.freedesktop.Platform", + "runtime-version":"22.08", + "sdk": "org.freedesktop.Sdk", + "command": "fteqw", + + "rename-desktop-file":"fteqw.desktop", + "strip":"true", + + "//":"dri needed for gl", + "//":"ipc supposedly recommended for x11's shm stuff", + "//":"flatpak doesn't seem to support alsa. anyone not using pipewire is thus fucked, nothing I can do about that", + "//":"filesystem /usr/share/quake seems b0rked, we can't find standard gamedata there. still using 'host' because steam might have your game library on some other partition/device other than home.", + "//":"--device=all fixes gamepad so we have usable inputs on steamdeck (apparently there's no proper way around that)", + "finish-args": [ + "--share=network", + + "--device=dri", + + "--share=ipc", + "--socket=x11", + + "--socket=wayland", + + "--filesystem=host", + "--filesystem=/run/udev:ro", + + "--device=all", + + "--device=snd", + "--socket=pulseaudio" + ], + "modules": [ + { + "name": "fteqw", + "buildsystem": "cmake", + + "//":"Using sdl to ensure game controller support eg for steamdeck etc. This may result in some clipboard issues as flatpak's sdl is a little too old (and sdl sucked in delaying proper support).", + "//":"Server stuff disabled, flatpak is not a good match. commandline tools also disabled for the most part, no .desktop files for those", + "//":"install to /app/bin instead of /app/games, flatpak just prefers it that way and the distinction isn't useful.", + "config-opts": ["-DCMAKE_BUILD_TYPE=Release", + "-DFTE_USE_SDL=true", + + "-DFTE_ENGINE_SERVER_ONLY=false", + "-DFTE_TOOL_QTV=false", + "-DFTE_TOOL_MASTER=false", + "-DFTE_TOOL_HTTPSV=false", + "-DFTE_TOOL_QCC=true", + "-DFTE_TOOL_IQM=false", + "-DFTE_TOOL_IMAGE=false", + + "-DCMAKE_INSTALL_PREFIX=/app", + "-DCMAKE_INSTALL_FULL_LIBDIR=/app/lib", + "-DCMAKE_INSTALL_DATAROOTDIR=/app/share", + "-DFTE_INSTALL_BINDIR=bin"], + "sources": [ + { + "type": "git", + "path": "/home/spike/quake/fteqw-code-git", + "//url": "https://github.com/fte-team/fteqw.git" + } + ] + } + ] +} + diff --git a/fteqtv/qtv.h b/fteqtv/qtv.h index 43f8763c8..85a9c6061 100644 --- a/fteqtv/qtv.h +++ b/fteqtv/qtv.h @@ -248,10 +248,10 @@ typedef struct void (*terminate) (unsigned char *digest, void *context); } hashfunc_t; extern hashfunc_t hash_sha1; -/*extern hashfunc_t hash_sha224; -extern hashfunc_t hash_sha256; -extern hashfunc_t hash_sha384; -extern hashfunc_t hash_sha512;*/ +/*extern hashfunc_t hash_sha2_224; +extern hashfunc_t hash_sha2_256; +extern hashfunc_t hash_sha2_384; +extern hashfunc_t hash_sha2_512;*/ #define HMAC HMAC_quake //stop conflicts... size_t CalcHash(hashfunc_t *hash, unsigned char *digest, size_t maxdigestsize, const unsigned char *string, size_t stringlen); size_t HMAC(hashfunc_t *hashfunc, unsigned char *digest, size_t maxdigestsize, const unsigned char *data, size_t datalen, const unsigned char *key, size_t keylen); diff --git a/fteqtv/source.c b/fteqtv/source.c index 23bf2d0db..37e4faedc 100644 --- a/fteqtv/source.c +++ b/fteqtv/source.c @@ -465,7 +465,7 @@ void Net_SendQTVConnectionRequest(sv_t *qtv, char *authmethod, char *challenge) snprintf(hash, sizeof(hash), "%s%s", challenge, qtv->connectpassword); Com_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum); - sprintf(hash, "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]); + sprintf(hash, "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]); //FIXME: bad formatting! str = hash; Net_QueueUpstream(qtv, strlen(str), str); str = "\"\n"; Net_QueueUpstream(qtv, strlen(str), str); diff --git a/fteqw.desktop b/fteqw.desktop new file mode 100644 index 000000000..25012a204 --- /dev/null +++ b/fteqw.desktop @@ -0,0 +1,40 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=FTEQW Game Engine +Comment=Awesome First Person Shooter +Exec=fteqw %u +Icon=info.triptohell.fteqw +Terminal=false +Categories=Game; +MimeType=application/x-quakeworlddemo;x-scheme-handler/quake;x-scheme-handler/qw; +Actions=quake;rerel;netquake;quake2;quake3;hexen2;hexen2mp + +[Desktop Action quake] +Name=Play Quake +Exec=fteqw %u -quake + +[Desktop Action rerel] +Name=Play Quake Rerelease +Exec=fteqw %u -quake_rerel + +[Desktop Action netquake] +Name=Play Classic Quake +Exec=fteqw %u -netquake + +[Desktop Action quake2] +Name=Play QuakeII +Exec=fteqw %u -quake2 + +[Desktop Action quake3] +Name=Play Quake III Arena +Exec=fteqw %u -quake + +[Desktop Action hexen2] +Name=Play Hexen2 +Exec=fteqw %u -hexen2 + +[Desktop Action hexen2mp] +Name=Play Hexen2 Mission Pack +Exec=fteqw %u -portals + diff --git a/imgtool.c b/imgtool.c index 0654a2455..741f1becc 100644 --- a/imgtool.c +++ b/imgtool.c @@ -434,13 +434,36 @@ qbyte GetPaletteIndexRange(int first, int stop, int red, int green, int blue) return best; } +const char *palette = NULL; + sh_config_t sh_config; viddef_t vid; + void ImgTool_SetupPalette(void) { int i; - //we ought to try to read gfx/palette.lmp, but its probably in a pak + FILE *fPAL; + qbyte cust_pal[768]; + host_basepal = default_quakepal; + + if (palette) + { + fPAL = fopen(palette, "rb"); + + if (fPAL != NULL) + { + Con_Printf("using user-specified palette\n"); + fread(cust_pal, 1, 768, fPAL); + fclose(fPAL); + host_basepal = cust_pal; + } + else + Con_Printf("cannot find palette file %s\n", palette); + } + else + Con_Printf("using built-in Quake palette\n"); + for (i = 0; i < 256; i++) { d_8to24rgbtable[i] = (host_basepal[i*3+0]<<0)|(host_basepal[i*3+1]<<8)|(host_basepal[i*3+2]<<16); @@ -2555,6 +2578,7 @@ int main(int argc, const char **argv) struct opts_s args; size_t files = 0; const char *outname = NULL; + for (u = 1; u < countof(sh_config.texfmt); u++) sh_config.texfmt[u] = true; @@ -2565,8 +2589,6 @@ int main(int argc, const char **argv) args.defaultext = NULL; args.width = args.height = 0; - ImgTool_SetupPalette(); - if (argc==1) goto showhelp; @@ -2669,6 +2691,16 @@ showhelp: return 1; } } + else if (!files && (!strcmp(argv[u], "-p") || !strcmp(argv[u], "--palette"))) + { + if (u+1 < argc) + palette = argv[++u]; + else + { + Con_Printf("--palette requires palette filename\n"); + return 1; + } + } else if (!strcmp(argv[u], "--resize")) { if (u+2 < argc) @@ -2766,6 +2798,8 @@ showhelp: argv[files++] = argv[u]; } + ImgTool_SetupPalette(); + if (mode == mode_unspecified && args.textype!=PTI_ANY) mode = mode_convert; diff --git a/iqm/iqm.cpp b/iqm/iqm.cpp index dc70c5fb5..d59d71844 100644 --- a/iqm/iqm.cpp +++ b/iqm/iqm.cpp @@ -5260,7 +5260,7 @@ static bool writemdl(const char *filename, bool md16) s -= 0.5; //glquake has some annoying half-texel offset thing. t -= 0.5; s = bound(0, s, skinwidth); - t = bound(0, t, skinwidth); + t = bound(0, t, skinheight); f->putlil((uint)(0?32:0)); //onseam. no verts are ever onseam for us, as we don't do that nonsense here. f->putlil((int)s); //mdl texcoords are ints, in texels. which sucks, but what can you do... f->putlil((int)t); diff --git a/plugins/avplug/avdecode.c b/plugins/avplug/avdecode.c index 25a7804b5..dc45fb980 100644 --- a/plugins/avplug/avdecode.c +++ b/plugins/avplug/avdecode.c @@ -4,7 +4,7 @@ static plugfsfuncs_t *filefuncs; static plugaudiofuncs_t *audiofuncs; -//#include "libavcodec/avcodec.h" +#include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libavutil/imgutils.h" diff --git a/plugins/avplug/avencode.c b/plugins/avplug/avencode.c index 3de872a6c..5eca9a074 100644 --- a/plugins/avplug/avencode.c +++ b/plugins/avplug/avencode.c @@ -3,7 +3,7 @@ #include "libavformat/avformat.h" //#include "libavformat/avio.h" -//#include "libavcodec/avcodec.h" +#include "libavcodec/avcodec.h" #include "libswscale/swscale.h" #include "libavutil/imgutils.h" #include "libavutil/opt.h" diff --git a/plugins/irc/ircclient.c b/plugins/irc/ircclient.c index a8a43ed83..4cfb52749 100644 --- a/plugins/irc/ircclient.c +++ b/plugins/irc/ircclient.c @@ -1308,6 +1308,7 @@ static struct ircice_s *IRC_ICE_Create(ircclient_t *irc, const char *sender, enu { struct icestate_s *ice; struct ircice_s *ircice; + char *s, token[MAX_OSPATH]; if (!piceapi) return NULL; @@ -1335,16 +1336,22 @@ static struct ircice_s *IRC_ICE_Create(ircclient_t *irc, const char *sender, enu //query dns to see if there's a stunserver hosted by the same domain //nslookup -querytype=SRV _stun._udp.example.com -// Q_snprintf(stunhost, sizeof(stunhost), "_stun._udp.%s", ice->server); -// if (NET_DNSLookup_SRV(stunhost, stunhost, sizeof(stunhost))) -// piceapi->Set(ice, "stunip", stunhost); -// else +// Q_snprintf(stunhost, sizeof(stunhost), "_stun._udp.%s", ice->server); +// if (NET_DNSLookup_SRV(stunhost, stunhost, sizeof(stunhost))) +// piceapi->Set(ice, "server", va("stun:%s" + stunhost)); +// else { - //irc services tend to not provide any stun info, so steal someone's... hopefully they won't mind too much. :( - piceapi->Set(ice, "stunport", "19302"); - piceapi->Set(ice, "stunip", "stun.l.google.com"); + char *stun = cvarfuncs->GetNVFDG("net_ice_broker", "", 0, NULL, NULL)->string; + s = strstr(stun, "://"); + if (s) stun = s+3; + piceapi->Set(ice, "server", va("stun:%s", stun)); } + //sadly we need to add the other ice servers ourselves despite there being a cvar to list them. + s = cvarfuncs->GetNVFDG("net_ice_servers", "", 0, NULL, NULL)->string; + while((s=cmdfuncs->ParseToken(s, token, sizeof(token), NULL))) + piceapi->Set(ice, "server", token); + ircice = malloc(sizeof(*ircice)); memset(ircice, 0, sizeof(*ircice)); ircice->next = irc->ice; diff --git a/plugins/jabber/jabberclient.c b/plugins/jabber/jabberclient.c index 8aa9f0068..6ad123701 100644 --- a/plugins/jabber/jabberclient.c +++ b/plugins/jabber/jabberclient.c @@ -1238,11 +1238,11 @@ static int sasl_scramsha1minus_initial(struct sasl_ctx_s *ctx, char *buf, int bu } static int sasl_scramsha256minus_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { - return sasl_scram_initial(ctx, buf, bufsize, &hash_sha256, false); + return sasl_scram_initial(ctx, buf, bufsize, &hash_sha2_256, false); } static int sasl_scramsha512minus_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { - return sasl_scram_initial(ctx, buf, bufsize, &hash_sha512, false); + return sasl_scram_initial(ctx, buf, bufsize, &hash_sha2_512, false); } static int sasl_scramsha1plus_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { @@ -1250,11 +1250,11 @@ static int sasl_scramsha1plus_initial(struct sasl_ctx_s *ctx, char *buf, int buf } static int sasl_scramsha256plus_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { - return sasl_scram_initial(ctx, buf, bufsize, &hash_sha256, true); + return sasl_scram_initial(ctx, buf, bufsize, &hash_sha2_256, true); } static int sasl_scramsha512plus_initial(struct sasl_ctx_s *ctx, char *buf, int bufsize) { - return sasl_scram_initial(ctx, buf, bufsize, &hash_sha512, true); + return sasl_scram_initial(ctx, buf, bufsize, &hash_sha2_512, true); } static void buf_cat(buf_t *buf, char *data, int len) diff --git a/plugins/jabber/jingle.c b/plugins/jabber/jingle.c index 836cefc26..04f049cc3 100644 --- a/plugins/jabber/jingle.c +++ b/plugins/jabber/jingle.c @@ -6,6 +6,8 @@ static struct c2c_s *JCL_JingleAddContentToSession(jclient_t *jcl, struct c2c_s char generatedname[64]; char stunhost[256]; int c; + char *s; + char token[MAX_OSPATH]; if (!bres) return NULL; @@ -82,10 +84,18 @@ static struct c2c_s *JCL_JingleAddContentToSession(jclient_t *jcl, struct c2c_s //google also don't provide stun srv records. //so we're basically screwed if we want to work with the googletalk xmpp service long term. //more methods are best, I suppose, but I'm lazy. - //yes, hardcoding means that other services might 'borrow' googles' stun servers. - piceapi->Set(ice, "stunport", "19302"); - piceapi->Set(ice, "stunip", "stun.l.google.com"); + + //try to use our default rtcbroker setting as a stun server, too. + char *stun = cvarfuncs->GetNVFDG("net_ice_broker", "", 0, NULL, NULL)->string; + s = strstr(stun, "://"); + if (s) stun = s+3; + piceapi->Set(ice, "server", va("stun:%s", stun)); } + + //if the user has manually set up some other stun servers, use them. + s = cvarfuncs->GetNVFDG("net_ice_servers", "", 0, NULL, NULL)->string; + while((s=cmdfuncs->ParseToken(s, token, sizeof(token), NULL))) + piceapi->Set(ice, "server", token); return c2c; } static qboolean JCL_JingleAcceptAck(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq) diff --git a/plugins/net_ssl_openssl.c b/plugins/net_ssl_openssl.c index 0db6c6094..6c592c126 100644 --- a/plugins/net_ssl_openssl.c +++ b/plugins/net_ssl_openssl.c @@ -21,6 +21,7 @@ struct fte_certctx_s { const char *peername; qboolean dtls; + qboolean failure; hashfunc_t *hash; //if set peer's cert MUST match the specified digest (with this hash function) qbyte digest[DIGEST_MAXSIZE]; @@ -44,6 +45,8 @@ static int OSSL_Bio_FWrite(BIO *h, const char *buf, int size) BIO_set_retry_write(h); r = -1; //paranoia } +// else if (r < 0) +// Con_DPrintf("ossl Error: %i\n", r); return r; } static int OSSL_Bio_FRead(BIO *h, char *buf, int size) @@ -242,7 +245,12 @@ static int OSSL_Verify_Peer(int preverify_ok, X509_STORE_CTX *x509_ctx) uctx->hash->terminate(digest, hctx); //return 1 for success - return !memcmp(digest, uctx->digest, uctx->hash->digestsize); + if (memcmp(digest, uctx->digest, uctx->hash->digestsize)) + { + uctx->failure = true; + return 0; + } + return 1; } if(preverify_ok == 0) @@ -914,7 +922,22 @@ static void OSSL_DestroyContext(void *ctx) static neterr_t OSSL_Transmit(void *ctx, const qbyte *data, size_t datasize) { //we're sending data ossldtls_t *o = (ossldtls_t*)ctx; - int r = BIO_write(o->bio, data, datasize); + int r; + if (datasize == 0) //liveness test. return clogged while handshaking and sent when finished. openssl doesn't like 0-byte writes. + { + if (o->cert.failure) + return NETERR_DISCONNECTED; //actual security happens elsewhere. + else if (SSL_is_init_finished(o->ssl)) + return NETERR_SENT; //go on, send stuff. + else if (BIO_should_retry(o->bio)) + { + if (BIO_do_handshake(o->bio) == 1) + return NETERR_SENT; + } + return NETERR_CLOGGED; //can't send yet. + } + else + r = BIO_write(o->bio, data, datasize); if (r <= 0) { if (BIO_should_io_special(o->bio)) @@ -941,7 +964,7 @@ static neterr_t OSSL_Received(void *ctx, sizebuf_t *message) int r; if (!message) - r = 0; + r = BIO_read(o->bio, NULL, 0); else { o->pending = message->data; @@ -981,7 +1004,52 @@ static neterr_t OSSL_Timeouts(void *ctx) return OSSL_Received(ctx, NULL); } -qboolean OSSL_GenTempCertificate(const char *subject, struct dtlslocalcred_s *cred) +static int OSSL_GetPeerCertificate(void *ctx, enum certprops_e prop, char *out, size_t outsize) +{ + ossldtls_t *o = (ossldtls_t*)ctx; + X509 *cert; + + safeswitch(prop) + { + case QCERT_ISENCRYPTED: + return 0; //not an error. + case QCERT_PEERCERTIFICATE: + cert = SSL_get_peer_certificate(o->ssl); + goto returncert; + case QCERT_LOCALCERTIFICATE: + cert = vhost.servercert; + goto returncert; +returncert: + if (cert) + { + size_t blobsize = i2d_X509(cert, NULL); + qbyte *end = out; + if (blobsize <= outsize) + i2d_X509(cert, &end); + return end-(qbyte*)out; + } + return -1; + case QCERT_PEERSUBJECT: + { + int r; + X509 *cert = SSL_get_peer_certificate(o->ssl); + if (cert) + { + X509_NAME *iname = X509_get_subject_name(cert); + BIO *bio = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(bio, iname, 0, XN_FLAG_RFC2253); + r = BIO_read(bio, out, outsize-1); + out[(r<0)?0:r] = 0; + BIO_free(bio); + return r; + } + } + return -1; + safedefault: + return -1; + } +} +static qboolean OSSL_GenTempCertificate(const char *subject, struct dtlslocalcred_s *cred) { EVP_PKEY*pkey = EVP_PKEY_new(); RSA *rsa = RSA_new(); @@ -1033,7 +1101,7 @@ static dtlsfuncs_t ossl_dtlsfuncs = OSSL_Transmit, OSSL_Received, OSSL_Timeouts, - NULL, + OSSL_GetPeerCertificate, OSSL_GenTempCertificate, }; static const dtlsfuncs_t *OSSL_InitClient(void) @@ -1172,6 +1240,8 @@ static void OSSL_PluginShutdown(void) EVP_PKEY_free(vhost.privatekey); BIO_meth_free(biometh_vfs); BIO_meth_free(biometh_dtls); + + memset(&vhost, 0, sizeof(vhost)); } static qboolean OSSL_PluginMayShutdown(void) { diff --git a/plugins/plugin.h b/plugins/plugin.h index 6f46a4f9a..bfb7d71fb 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -499,8 +499,8 @@ typedef struct //for collision stuff F(void, SetInfoKey, (char *s, const char *key, const char *value, int maxsize)); //server things, shouldn't really be here but small. null in client-only builds - F(void, DropClient, (client_t *drop)); - F(void, ExtractFromUserinfo,(client_t *cl, qboolean verbose)); + F(void, DropClient, (struct client_s *drop)); + F(void, ExtractFromUserinfo,(struct client_s *cl, qboolean verbose)); F(qboolean, ChallengePasses, (int challenge)); #define plugworldfuncs_name "World" } plugworldfuncs_t; @@ -547,7 +547,7 @@ typedef struct //for plugins that need to read/write files... F(const char *,GetExtension,(const char *filename, const char *ignoreext)); F(void, FileBase, (const char *in, char *out, int outlen)); F(void, CleanUpPath, (char *str)); - F(unsigned int,BlockChecksum,(const void *buffer, int length)); //mostly for pack hashes. + F(unsigned int,BlockChecksum,(const void *buffer, size_t length)); //mostly for pack hashes. F(void*, LoadFile, (const char *fname, size_t *fsize)); //plugfuncs->Free //stuff that's useful for networking. diff --git a/plugins/qi/qi.c b/plugins/qi/qi.c index 0365c7c61..32b73149e 100644 --- a/plugins/qi/qi.c +++ b/plugins/qi/qi.c @@ -516,6 +516,8 @@ static void QI_AddPackages(xmltree_t *qifile) } static void QI_RunMap(xmltree_t *qifile, const char *map) { + char gamedir[65]; + char buf[256]; if (!qifile) { return; @@ -527,10 +529,35 @@ static void QI_RunMap(xmltree_t *qifile, const char *map) if (!*map || strchr(map, '\\') || strchr(map, '\"') || strchr(map, '\n') || strchr(map, ';')) map = ""; + strcpy(gamedir, "id1"); + { + char descbuf[1024]; + xmltree_t *tech = XML_ChildOfTree(qifile, "techinfo", 0); + const char *cmdline = XML_GetChildBody(tech, "commandline", ""); + while (cmdline) + { + cmdline = cmdfuncs->ParseToken(cmdline, descbuf, sizeof(descbuf), NULL); + if (!strcmp(descbuf, "-game")) + cmdline = cmdfuncs->ParseToken(cmdline, gamedir, sizeof(gamedir), NULL); + else if (!strcmp(descbuf, "-hipnotic") || !strcmp(descbuf, "-rogue") || !strcmp(descbuf, "-quoth")) + { + if (!*gamedir) + strcpy(gamedir, descbuf+1); + } + } + } - cmdfuncs->AddText("fs_changemod spmap \"", false); - cmdfuncs->AddText(map, false); - cmdfuncs->AddText("\"", false); + cmdfuncs->AddText("fs_changemod", false); + if (*gamedir) + { + cmdfuncs->AddText(" dir ", false); + cmdfuncs->AddText(cmdfuncs->QuotedString(gamedir, buf, sizeof(buf), false), false); + } + if (map && *map) + { + cmdfuncs->AddText(" spmap ", false); + cmdfuncs->AddText(cmdfuncs->QuotedString(map, buf, sizeof(buf), false), false); + } QI_AddPackages(qifile); // Con_Printf("Command: %s\n", cmd); cmdfuncs->AddText("\n", false); @@ -548,13 +575,13 @@ void VARGS VFS_PRINTF(vfsfile_t *vf, const char *format, ...) VFS_PUTS(vf, string); } -static void QI_WriteUpdateList(vfsfile_t *pminfo) +static void QI_WriteUpdateList(xmltree_t *database, vfsfile_t *pminfo) { xmltree_t *file; char descbuf[1024], *d, *nl; - if (thedatabase) - for (file = thedatabase->child; file; file = file->sibling) + if (database) + for (file = database->child; file; file = file->sibling) { const char *id = XML_GetParameter(file, "id", "unnamed"); const char *rating = XML_GetParameter(file, "rating", ""); @@ -937,7 +964,7 @@ static void QDECL QI_Tick(double realtime, double gametime) else { VFS_PRINTF(packagemanager, "version 3\n"); - QI_WriteUpdateList(packagemanager); + QI_WriteUpdateList(thedatabase, packagemanager); } VFS_CLOSE(packagemanager); packagemanager = NULL; @@ -981,8 +1008,48 @@ static void QI_ExecuteCommand_f(void) QI_RefreshMapList(true); } -void QI_GenPackages(const char *url, vfsfile_t *pipe) +void QI_GenPackages(const char *url, vfsfile_t *pipe, qboolean favourcache) { + if (thedatabase) + { //already read it?... fine. + VFS_PRINTF(pipe, "version 3\n"); + QI_WriteUpdateList(thedatabase, pipe); + VFS_CLOSE(pipe); + return; + } + else if (favourcache) + { //just read the cached copy from disk. use the menu to update it. + xmltree_t *t = NULL; + qhandle_t fd = -1; + int ofs = 0; + + int flen = filefuncs->Open("**plugconfig", &fd, 1); + if (flen >= 0) + { + qbyte *file = malloc(flen+1); + file[flen] = 0; + filefuncs->Read(fd, file, flen); + filefuncs->Close(fd); + + do + { + if (t) + XML_Destroy(t); + t = XML_Parse(file, &ofs, flen, false, ""); + } while(t && !t->child); + free(file); + + VFS_PRINTF(pipe, "version 3\n"); + QI_WriteUpdateList(t, pipe); + + if (t) + XML_Destroy(t); + VFS_CLOSE(pipe); + return; + } + } + + //fill it in once we've got it... if (packagemanager) VFS_CLOSE(packagemanager); packagemanager = pipe; diff --git a/plugins/quake3/clq3_ui.c b/plugins/quake3/clq3_ui.c index 4920eab65..a16c2bd54 100644 --- a/plugins/quake3/clq3_ui.c +++ b/plugins/quake3/clq3_ui.c @@ -1565,7 +1565,7 @@ static qintptr_t UI_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con break; */ -#if 1//def BOTLIB_STATIC +#ifdef HAVE_SERVER case UI_PC_ADD_GLOBAL_DEFINE: return botlib->PC_AddGlobalDefine(VM_POINTER(arg[0])); case UI_PC_LOAD_SOURCE: @@ -1837,7 +1837,9 @@ void UI_Start (void) uimenu.release = UI_Release; uimenu.lowpriority = true; +#ifdef HAVE_SERVER SV_InitBotLib(); +#endif uivm = vmfuncs->Create("ui", cvarfuncs->GetFloat("com_gamedirnativecode")?UI_SystemCallsNative:NULL, "vm/ui", UI_SystemCallsVM); if (uivm) { diff --git a/plugins/quake3/clq3defs.h b/plugins/quake3/clq3defs.h index f3fc10480..4337b466c 100644 --- a/plugins/quake3/clq3defs.h +++ b/plugins/quake3/clq3defs.h @@ -423,6 +423,7 @@ void UI_Stop (void); qboolean UI_OpenMenu(void); void UI_Reset(void); +#ifdef HAVE_SERVER void SVQ3_ShutdownGame(qboolean restarting); qboolean SVQ3_InitGame(server_static_t *server_state_static, server_t *server_state, qboolean restart); qboolean SVQ3_ConsoleCommand(void); @@ -436,3 +437,4 @@ void SVQ3_SendMessage(struct client_s *client); qboolean SVQ3_RestartGamecode(void); void SVQ3_ServerinfoChanged(const char *key); #endif +#endif diff --git a/plugins/quake3/q3common.c b/plugins/quake3/q3common.c index 2a6c83d46..ec3e6c0cf 100644 --- a/plugins/quake3/q3common.c +++ b/plugins/quake3/q3common.c @@ -16,8 +16,10 @@ double realtime; struct netprim_s msg_nullnetprim; #endif +#ifdef HAVE_SERVER //mostly for access to sv.state or svs.sockets q3serverstate_t sv3; +#endif //this file contains q3 netcode related things. //field info, netchan, and the WriteBits stuff (which should probably be moved to common.c with the others) diff --git a/plugins/quake3/q3common.h b/plugins/quake3/q3common.h index 3f8dbd0d3..afdbbc771 100644 --- a/plugins/quake3/q3common.h +++ b/plugins/quake3/q3common.h @@ -56,7 +56,7 @@ typedef struct //merge? - +#ifdef HAVE_SERVER typedef struct { world_t *world; @@ -77,3 +77,5 @@ typedef struct } q3serverstate_t; extern q3serverstate_t sv3; #undef CALCAREAGRIDBOUNDS +#endif + diff --git a/plugins/quake3/svq3_game.c b/plugins/quake3/svq3_game.c index 6ff3df493..4e4cf2f8c 100644 --- a/plugins/quake3/svq3_game.c +++ b/plugins/quake3/svq3_game.c @@ -3409,6 +3409,7 @@ void SVQ3_DirectConnect(netadr_t *from, sizebuf_t *msg) //Actually connect the c cl->protocol = SCP_QUAKE3; cl->state = cs_connected; + cl->connection_started = realtime; cl->name = cl->namebuf; cl->team = cl->teambuf; worldfuncs->ExtractFromUserinfo(cl, true); @@ -3434,6 +3435,7 @@ static int SVQ3_AddBot(void) return -1; //failure, no slots cl->protocol = SCP_BAD; + cl->connection_started = realtime; cl->state = cs_connected; cl->name = cl->namebuf; cl->team = cl->teambuf; diff --git a/quakec/csaddon/src/brush_draw.qc b/quakec/csaddon/src/brush_draw.qc index e8be5d021..2543e01af 100644 --- a/quakec/csaddon/src/brush_draw.qc +++ b/quakec/csaddon/src/brush_draw.qc @@ -3,7 +3,7 @@ void(int mod, int id) DrawEngineBrushWireframe = const vector col = '1 0 0'; for(int facenum = 0;;) { - int points = brush_getfacepoints(mod, id, ++facenum, &facepoints[0], MAX_FACEPOINTS); + int points = brush_getfacepoints(mod, id, ++facenum, &facepoints[0], facepoints.length); if (!points) break; //end of face list, I guess @@ -19,6 +19,77 @@ void(int mod, int id) DrawEngineBrushWireframe = R_EndPolygon(); } }; +void(patchvert_t *cp, patchinfo_t info, vector col, float alpha) DrawQCPatchTextured; +void(int mod, int id, float alpha) DrawEngineBrushFaded = +{ //draw one of the engine's brushes, but faded. + const vector col = '1 1 1'; + int contents; + brushface_t faces[MAX_BRUSHFACES]; + int numfaces = brush_get(mod, id, &faces[0], faces.length, &contents); + if (!numfaces) + { + numfaces = patch_getmesh(mod, id, __NULL__, 0, __NULL__); //ask how much space we need + if (numfaces) + { + patchvert_t *v = memalloc(sizeof(*v)*numfaces); + patchinfo_t patchinfo; + patch_getmesh(mod, id, v, numfaces, &patchinfo); //now we can actually get it + DrawQCPatchTextured(v, patchinfo, col, alpha); + memfree(v); + } + + return; + } + + for(int f = 0; f < numfaces; f++) + { + int points = brush_getfacepoints(mod, id, 1+f, &facepoints[0], facepoints.length); + if (!points) + continue; + + //this is unfortunate. the built in shaders expect to use lightmaps. we don't have those. + //because lightmaps are special things, we end up in a real mess. so lets just make sure there's a shader now, because we can. + //FIXME: we don't manage to pick up the size of the original wad image + + string materialname = strcat("textures/", faces[f].shadername); + shaderforname(materialname, + sprintf("{" + "{\n" + "map \"%s\"\n" + "rgbgen vertex\n" + "alphagen vertex\n" + "}\n" + "}", faces[f].shadername)); + + vector sz = drawgetimagesize(materialname); + R_BeginPolygon(materialname, 3); + for (int point = 0; point < points; point++) + R_PolygonVertex(facepoints[point], [(facepoints[point] * faces[f].sdir + faces[f].sbias)/sz_x, (facepoints[point] * faces[f].tdir + faces[f].tbias)/sz_y], col, alpha); + R_EndPolygon(); + } +}; + +void(patchvert_t *vert, patchinfo_t info, string shader, vector col, float alpha) DrawQCPatchWireframe = +{ + int x,y,cp; + R_BeginPolygon(shader); + for (y = 0, cp = 0; y < info.cpheight; y++, cp++) + for (x = 0; x < info.cpwidth-1; x++) + { + R_PolygonVertex(vert[cp].xyz, [vert[cp].s, vert[cp].t], col, alpha); + cp++; + R_PolygonVertex(vert[cp].xyz, [vert[cp].s, vert[cp].t], col, alpha); + R_EndPolygon(); + } + for (x = 0; x < info.cpwidth; x++) + for (y = 0, cp=x; y < info.cpheight-1; y++) + { + R_PolygonVertex(vert[cp].xyz, [vert[cp].s, vert[cp].t], col, alpha); + cp += info.cpwidth; + R_PolygonVertex(vert[cp].xyz, [vert[cp].s, vert[cp].t], col, alpha); + R_EndPolygon(); + } +}; void(brushface_t *faces, int numfaces, string shader, vector col, float alpha) DrawQCBrushWireframe = { @@ -69,7 +140,8 @@ void(brushface_t *faces, int numfaces, vector col, float alpha) DrawQCBrushTextu //this is unfortunate. the built in shaders expect to use lightmaps. we don't have those. //because lightmaps are special things, we end up in a real mess. so lets just make sure there's a shader now, because we can. //FIXME: we don't manage to pick up the size of the original wad image - shaderforname(faces[f].shadername, + string materialname = strcat("textures/", faces[f].shadername); + shaderforname(materialname, sprintf("{" "{\n" "map \"%s\"\n" @@ -78,14 +150,46 @@ void(brushface_t *faces, int numfaces, vector col, float alpha) DrawQCBrushTextu "}\n" "}", faces[f].shadername)); - vector sz = drawgetimagesize(faces[f].shadername); - R_BeginPolygon(faces[f].shadername); + vector sz = drawgetimagesize(materialname); + R_BeginPolygon(materialname, 3); for (point = 0; point < points; point++) R_PolygonVertex(facepoints[point], [(facepoints[point] * faces[f].sdir + faces[f].sbias)/sz_x, (facepoints[point] * faces[f].tdir + faces[f].tbias)/sz_y], col, alpha); R_EndPolygon(); } } }; +void(patchvert_t *cp, patchinfo_t info, vector col, float alpha) DrawQCPatchTextured = +{ + int x, y; + + string materialname = strcat("textures/", info.shadername); + shaderforname(materialname, + sprintf("{" + "{\n" + "map \"%s\"\n" + "rgbgen vertex\n" + "alphagen vertex\n" + "}\n" + "}", info.shadername)); + + R_BeginPolygon(materialname); + for (y = 0; y < info.cpheight-1; y++, cp++) + for (x = 0; x < info.cpwidth-1; x++, cp++) + { +#if 1 + R_PolygonVertex(cp[0].xyz, [cp[0].s,cp[0].t], col, alpha); + R_PolygonVertex(cp[1].xyz, [cp[1].s,cp[1].t], col, alpha); + R_PolygonVertex(cp[1+info.cpwidth].xyz, [cp[1+info.cpwidth].s,cp[1+info.cpwidth].t], col, alpha); + R_PolygonVertex(cp[0+info.cpwidth].xyz, [cp[0+info.cpwidth].s,cp[0+info.cpwidth].t], col, alpha); +#else + R_PolygonVertex(cp[0].xyz, [cp[0].s,cp[0].t], cp[0].rgb, cp[0].a); + R_PolygonVertex(cp[1].xyz, [cp[1].s,cp[1].t], cp[1].rgb, cp[1].a); + R_PolygonVertex(cp[1+info.cpwidth].xyz, [cp[1+info.cpwidth].s,cp[1+info.cpwidth].t], cp[1+info.cpwidth].rgb, cp[1+info.cpwidth].a); + R_PolygonVertex(cp[0+info.cpwidth].xyz, [cp[0+info.cpwidth].s,cp[0+info.cpwidth].t], cp[0+info.cpwidth].rgb, cp[0+info.cpwidth].a); +#endif + R_EndPolygon(); + } +}; void(vector *p, int points, string shader, vector col, float alpha) DrawAxisExtensions = { diff --git a/quakec/csaddon/src/brush_history.qc b/quakec/csaddon/src/brush_history.qc index e452c2868..c57440290 100644 --- a/quakec/csaddon/src/brush_history.qc +++ b/quakec/csaddon/src/brush_history.qc @@ -154,6 +154,26 @@ int(int mod, brushface_t *faces, int numfaces, int contents, float selectnow) br brush_select(mod, h->id); return h->id; }; +//create and journal. +int(int mod, patchvert_t *vert, patchinfo_t info, float selectnow) patch_history_create = +{ + int totalcp = info.cpwidth * info.cpheight; + history_t *h = history_allocate(); + + h->patchdata = memalloc(sizeof(patchvert_t) * totalcp); + memcpy(h->patchdata, vert, sizeof(patchvert_t) * totalcp); + h->patchinfo = info; + h->brushmodel = mod; + h->wasdelete = FALSE; + + h->id = patch_create(h->brushmodel, 0, vert, info); + + if (!h->id) + history_deallocate(h); + else if (selectnow) + brush_select(mod, h->id); + return h->id; +}; //delete and journal. void(int mod, int id) brush_history_delete = { @@ -196,4 +216,18 @@ int(int mod, int id, brushface_t *faces, int numfaces, int contents) brush_histo brush_selected(mod, id, -1, TRUE); } return id; +}; +int(int mod, int id, patchvert_t *vert, patchinfo_t info) patch_history_edit = +{ + int wasselected = brush_isselected(mod, id); + if (wasselected) + selectedbrushes[wasselected-1].id = -id; //hack so that brush_history_delete won't remove this entry. + brush_history_delete(mod, id); + id = patch_history_create(mod, vert, info, FALSE); + if (wasselected) + { + selectedbrushes[wasselected-1].id = id; + brush_selected(mod, id, -1, TRUE); + } + return id; }; \ No newline at end of file diff --git a/quakec/csaddon/src/brush_manip.qc b/quakec/csaddon/src/brush_manip.qc index 06cd1d39d..3067ee8cc 100644 --- a/quakec/csaddon/src/brush_manip.qc +++ b/quakec/csaddon/src/brush_manip.qc @@ -67,7 +67,7 @@ int(brushface_t *fa, int famax, vector *points, int numpoints, float height) Bru vector(vector guess) brush_snappoint = { - if (nogrid) + if (nogrid || autocvar_ca_grid <= 0) return guess; int facenum, points; float bestdist = autocvar_ca_grid*autocvar_ca_grid; //worst case value so we don't snap to grid when there's a vertex 0.001qu away from the grid. @@ -107,36 +107,60 @@ vector(vector guess) brush_snappoint = //move a brush so that its planes all move without any translations in positions or texcoords -void brushface_translate(brushface_t *fa, int numfaces, vector displacement) +void brushface_translate(vector displacement) { int i; - for (i = 0; i < numfaces; i++, fa++) + if (tmp.numcp) { - fa->planedist += fa->planenormal * displacement; - fa->sbias -= fa->sdir * displacement; - fa->tbias -= fa->tdir * displacement; + for (i = 0; i < tmp.numcp; i++) + tmp.cp[i].xyz += displacement; + } + else + { + for (i = 0; i < tmp.numfaces; i++) + { + tmp.faces[i].planedist += tmp.faces[i].planenormal * displacement; + tmp.faces[i].sbias -= tmp.faces[i].sdir * displacement; + tmp.faces[i].tbias -= tmp.faces[i].tdir * displacement; + } } }; //rotates a list of faces by the current v_* vectors, around the origin. //translate before+after first in order to deal with pivots. -void brushface_rotate(brushface_t *fa, int numfaces) +void brushface_rotate(void) { - for (int i = 0; i < numfaces; i++, fa++) + if (tmp.numcp) { - vector orig = fa->planenormal; - fa->planenormal[0] = orig * v_forward; - fa->planenormal[1] = orig * -v_right; //quake just isn't right... - fa->planenormal[2] = orig * v_up; + for (int i = 0; i < tmp.numcp; i++) + { + vector orig = tmp.cp[i].xyz; + tmp.cp[i].xyz = [orig * v_forward, + orig * -v_right, + orig * v_up]; + //don't need to touch tcs + } + } + else + { + brushface_t *fa = tmp.faces; + int numfaces = tmp.numfaces; + for (int i = 0; i < numfaces; i++, fa++) + { + vector orig = fa->planenormal; + fa->planenormal[0] = orig * v_forward; + fa->planenormal[1] = orig * -v_right; //quake just isn't right... + fa->planenormal[2] = orig * v_up; - orig = fa->sdir; - fa->sdir[0] = orig * v_forward; - fa->sdir[1] = orig * -v_right; //quake just isn't right... - fa->sdir[2] = orig * v_up; + orig = fa->sdir; + fa->sdir[0] = orig * v_forward; + fa->sdir[1] = orig * -v_right; //quake just isn't right... + fa->sdir[2] = orig * v_up; - orig = fa->tdir; - fa->tdir[0] = orig * v_forward; - fa->tdir[1] = orig * -v_right; //quake just isn't right... - fa->tdir[2] = orig * v_up; + orig = fa->tdir; + fa->tdir[0] = orig * v_forward; + fa->tdir[1] = orig * -v_right; //quake just isn't right... + fa->tdir[2] = orig * v_up; + } } }; @@ -415,15 +439,35 @@ void() brushedit_resettextures = { int model = selectedbrushes[sb].model; int brush = selectedbrushes[sb].id; - int face = selectedbrushes[sb].face; + __uint64 facemask = selectedbrushes[sb].facemask; int planecount = brush_get(model, brush, tmp.faces, tmp.faces.length, &tmp.contents); if (planecount) { for (int i = 0; i < planecount; i++) - if (!face || face == planecount+1) + if (facemask & (1lu << i)) reset_texturecoords(&tmp.faces[i]); + brush_history_edit(model, brush, tmp.faces, planecount, tmp.contents); + } + } +}; + +void(string newtexture) brushedit_settexture = +{ + for (int sb = 0; sb < selectedbrushcount; sb++) + { + int model = selectedbrushes[sb].model; + int brush = selectedbrushes[sb].id; + __uint64 facemask = selectedbrushes[sb].facemask; + + int planecount = brush_get(model, brush, tmp.faces, tmp.faces.length, &tmp.contents); + if (planecount) + { + for (int i = 0; i < planecount; i++) + if (facemask & (1lu << i)) + tmp.faces[i].shadername = newtexture; + brush_history_edit(model, brush, tmp.faces, planecount, tmp.contents); } } diff --git a/quakec/csaddon/src/brush_selection.qc b/quakec/csaddon/src/brush_selection.qc index 537a04929..a8265007e 100644 --- a/quakec/csaddon/src/brush_selection.qc +++ b/quakec/csaddon/src/brush_selection.qc @@ -3,6 +3,9 @@ var float autocvar_ca_brush_viewsize = 1024; //for different views. var string autocvar_ca_newbrushtexture = "metal4_2"; var float autocvar_ca_newbrushheight = 64; var float autocvar_ca_grid = 16; +var float autocvar_ca_explicitpatch = 0; +var float autocvar_ca_cpwidth = 2; +var float autocvar_ca_cpheight = 2; #define EPSILON (1.0 / 32) //inprecision sucks. @@ -36,7 +39,8 @@ enum : int BT_ROTATE, BT_MERGE, BT_PUSHFACE, - BT_CREATE, + BT_CREATEBRUSH, + BT_CREATEPATCH, BT_CREATEDRAG, BT_CLONEDISPLACE, BT_SLICE, @@ -58,15 +62,14 @@ typedef struct { int model; int id; - int face; - //fixme: do we need an array of faces here? + __uint64 facemask; //MAX_BRUSHFACES is 64... handy. } selbrush_t; selbrush_t *selectedbrushes; int selectedbrushcount; var int selectedbrushmodel = 1; //by default, the worldmodel. this is tracked to know which submodel to insert new brushes into. -int(int modelidx, int brushid) brush_isselected = +int(int modelidx, int brushid) brush_isselected = //0 for faceid means 'all' { for (int i = 0; i < selectedbrushcount; i++) if (selectedbrushes[i].id == brushid) @@ -74,6 +77,14 @@ int(int modelidx, int brushid) brush_isselected = return i+1; return 0; }; +int(int modelidx, int brushid, int faceid) brushface_isselected = //0 for faceid means 'all' +{ + for (int i = 0; i < selectedbrushcount; i++) + if (selectedbrushes[i].id == brushid) + if (selectedbrushes[i].model == modelidx) + return !!(selectedbrushes[i].facemask&(1ull<<(faceid-1i))); + return 0; +}; int(int modelidx, int brushid) brush_deselect = { int i = brush_isselected(modelidx, brushid); @@ -84,6 +95,25 @@ int(int modelidx, int brushid) brush_deselect = selectedbrushcount--; return TRUE; }; +int(int modelidx, int brushid, int face) brushface_deselect = +{ + //deselecting a single face + int i = brush_isselected(modelidx, brushid); //get the brush selection index + if (!i) + return FALSE; + i--; + face--; + if (!selectedbrushes[i].facemask) //no faces means all.. + selectedbrushes[i].facemask = ~0ul; + selectedbrushes[i].facemask &= ~(1ul<model = model; vertedit->brush = brush; vertedit->numidx = 0; - vertedit->numverts = 0; + vertedit->numverts = patch_getcp(vertedit->model, vertedit->brush, __NULL__, 0, __NULL__); + if (vertedit->numverts) + { //okay, this is a patch. one logical face. + memfree(vertedit->i); + memfree(vertedit->p); + + vertedit->p = memalloc(sizeof(*vertedit->p) * vertedit->numverts); + + if (!patchvert) + patchvert = memalloc(sizeof(*patchvert)*maxcp); + vertedit->numverts = patch_getcp(vertedit->model, vertedit->brush, patchvert, maxcp, &patchinfo); + for (i = 0; i < vertedit->numverts; i++) + vertedit->p[i] = patchvert[i].xyz; + + //some of the rest of the code assumes we have indexes. oh well. + k = (patchinfo.cpwidth-1)*(patchinfo.cpheight-1); + vertedit->numidx = k*6; + vertedit->i = ni = memalloc(sizeof(*ni) * vertedit->numidx); + for (i = 0; i < k; i++, ni+=6) + { + ni[0] = i; + ni[1] = i+1; + ni[2] = i+patchinfo.cpwidth; + + ni[3] = i+1; + ni[4] = i+patchinfo.cpwidth+1; + ni[5] = i+patchinfo.cpwidth; + } + return; + } + + //brush. for (i = 0; ; ) { points = brush_getfacepoints(vertedit->model, vertedit->brush, ++i, facepoints, facepoints.length); @@ -78,17 +112,36 @@ void(vertsoup_t *vertedit, int model, int brush) Debrushify = }; //determines only the various points of the brush, without duplicates. doesn't care about indexes. -void(brushface_t *faces, int numfaces) DebrushifyLite = +void(void) DebrushifyLite = { int points, k; vector p; vector *np; int fi[64]; vertedit.numidx = 0; - vertedit.numverts = 0; + + vertedit->numverts = patch_getcp(vertedit->model, vertedit->brush, __NULL__, 0, __NULL__); + if (vertedit->numverts) + { //okay, this is a patch. one logical face. + memfree(vertedit->i); + memfree(vertedit->p); + + vertedit->i = __NULL__; //don't bother. + vertedit->p = memalloc(sizeof(*vertedit->p) * vertedit->numverts); + + if (!patchvert) + patchvert = memalloc(sizeof(*patchvert)*maxcp); + + vertedit->numverts = patch_getcp(vertedit->model, vertedit->brush, patchvert, maxcp, &patchinfo); + + for (int i = 0; i < vertedit->numverts; i++) + vertedit->p[i] = patchvert[i].xyz; + return; + } + for (int i = 0; ; ) { - points = brush_calcfacepoints(++i, faces, numfaces, facepoints, facepoints.length); + points = brush_calcfacepoints(++i, tmp.faces, tmp.numfaces, facepoints, facepoints.length); if (!points) break; @@ -140,7 +193,7 @@ static float(vertsoup_t *soup, int *idx, __out vector norm, __out float dist) pl void(vertsoup_t *soup, int drawit) Rebrushify = { - brushface_t faces[64]; + brushface_t faces[MAX_BRUSHFACES]; int numfaces, f, point; string shader = 0; @@ -148,6 +201,36 @@ void(vertsoup_t *soup, int drawit) Rebrushify = float d; int o=0; + f = patch_getcp(soup->model, soup->brush, patchvert, maxcp, &patchinfo); + if (f) + { + for (o = 0; o < f; o++) + patchvert[o].xyz = soup.p[o]; + if (drawit) + { + const int maxtessverts=64*64; + patchinfo_t tessinfo = patchinfo; + patchvert_t *tessverts = memalloc(sizeof(*tessverts)*maxtessverts); + if (patch_evaluate(patchvert, tessverts, maxtessverts, &tessinfo) <= maxtessverts) + { + //draw textured preview (yay for fullbright lighting being overbright) + DrawQCPatchTextured(tessverts, tessinfo, '0.7 0.7 0.7', 0.7); //display the patch. in-place so you know what it'll actually look like. stoopid tessellation. + DrawQCPatchWireframe(tessverts, tessinfo, "chop", [0.3,0,0.3], 1); //show a somewhat faded indication of how many quads are actually being used. + } + memfree(tessverts); + + //draw it wireframe without depth testing + DrawQCPatchWireframe(patchvert, patchinfo, "chop", [0,0,1], 1); + } + else + { + patch_history_edit(soup->model, soup->brush, patchvert, patchinfo); + + soup->numidx = 0; + soup->numverts = 0; + } + return; + } tmp.numfaces = brush_get(soup->model, soup->brush, tmp.faces, tmp.faces.length, &tmp.contents); @@ -304,10 +387,7 @@ p3p1edge: return; //can't possibly be valid. if (drawit) - { - //draw it wireframe WITH depth testing - //DrawQCBrushWireframe(faces, numfaces, "terrainedit", '1 0 0', 1); - + { //draw textured preview (yay for block lighting) DrawQCBrushTextured(faces, numfaces, '0.7 0.7 0.7', 1); diff --git a/quakec/csaddon/src/csaddon.qc b/quakec/csaddon/src/csaddon.qc index e4437f8a1..fd8cba33c 100644 --- a/quakec/csaddon/src/csaddon.qc +++ b/quakec/csaddon/src/csaddon.qc @@ -183,7 +183,7 @@ void() wrap_renderscene = return; } - if (mousedown == 2) + if (mousedown & 2) { //RMB re-enables mlook if (releasedmouse) { @@ -269,8 +269,8 @@ void() wrap_renderscene = drawcharacter(curmousepos - '4 4', '+', '8 8', '1 1 1', 1); }; -var float(float,float,float) orig_input_event = __NULL__; -float (float event, float parama, float paramb) wrap_InputEvent = +var float(float,float,float,float) orig_input_event = __NULL__; +float (float event, float parama, float paramb, float devid) CSQC_InputEvent = { if (event == IE_KEYDOWN || event == IE_KEYUP) { @@ -341,23 +341,32 @@ float (float event, float parama, float paramb) wrap_InputEvent = } if (parama == K_MOUSE1) { - mousedown = 1; + mousedown |= 1; return TRUE; } if (parama == K_MOUSE2) { - mousedown = 2; + mousedown |= 2; + return TRUE; + } + if (parama == K_MOUSE3) + { + mousedown |= 4; return TRUE; } } else if (event == IE_KEYUP) { - if (parama == K_MOUSE1-1+mousedown) - mousedown = FALSE; + if (parama == K_MOUSE1) + mousedown &= ~1; + if (parama == K_MOUSE2) + mousedown &= ~2; + if (parama == K_MOUSE3) + mousedown &= ~4; } else if (event == IE_MOUSEDELTA) { - if (mousedown == 2) + if (mousedown & 2) return FALSE; originalmousepos = curmousepos; curmousepos_x += parama; @@ -377,7 +386,7 @@ float (float event, float parama, float paramb) wrap_InputEvent = { curmousepos_x = parama; curmousepos_y = paramb; - if (mousedown == 2) + if (mousedown & 2) return FALSE; return TRUE; } @@ -403,7 +412,7 @@ float (float event, float parama, float paramb) wrap_InputEvent = #endif if (orig_input_event) - return orig_input_event(event, parama, paramb); + return orig_input_event(event, parama, paramb, devid); return FALSE; }; @@ -520,7 +529,7 @@ void() CSQC_Input_Frame = if (pointedshadername != "") { - input_buttons = input_buttons - (input_buttons & 1); + input_buttons &= ~1; makevectors(input_angles); if (v_forward * pointedsurfacenormal >= 0) @@ -607,10 +616,10 @@ float(string cmd) CSQC_ConsoleCommand = }; /*this is a fallback function, in case the main progs does not have one*/ -float (float event, float parama, float paramb, float devid) CSQC_InputEvent = +/*float (float event, float parama, float paramb, float devid) CSQC_InputEvent = { return wrap_InputEvent(event, parama, paramb); -}; +};*/ void(float prevprogs) init = { @@ -626,9 +635,9 @@ void(float prevprogs) init = externset(0, wrap_renderscene, "renderscene"); externset(0, wrap_addentities, "addentities"); - /*wrap the parent's input event function*/ - orig_input_event = (float(float, float, float))externvalue(0, "CSQC_InputEvent"); - externset(0, wrap_InputEvent, "CSQC_InputEvent"); + /*wrap the parent's input event function in favour of ours*/ + orig_input_event = (float(float, float, float, float))externvalue(0, "CSQC_InputEvent"); + externset(0, CSQC_InputEvent, "CSQC_InputEvent"); } csfixups(); localcmd(sprintf("alias rtlight_editor \"set ca_show 1; set ca_editormode %g\"\n", MODE_LIGHTEDIT)); @@ -640,11 +649,16 @@ void(float prevprogs) init = //brush editor needs some commands for easier binds. editor_brushes_registercommands(); }; +void() initents = +{ + world_hitcontentsmaski = &world.hitcontentsmaski; +} void() CSQC_Shutdown = { #ifdef CAMQUAKE spline_shutdown(); #endif + brush_deselectall(); //so they don't stay hidden when restarted... }; diff --git a/quakec/csaddon/src/csaddon.src b/quakec/csaddon/src/csaddon.src index 78fd40e2a..11353f189 100644 --- a/quakec/csaddon/src/csaddon.src +++ b/quakec/csaddon/src/csaddon.src @@ -6,7 +6,7 @@ #define CSQC #define _ACCESSORS -#pragma target FTE +#pragma target FTE_5768 //#pragma optimise 2 //#pragma optimise no-filename diff --git a/quakec/csaddon/src/csfixups.qc b/quakec/csaddon/src/csfixups.qc index c68680e6d..630f17d97 100644 --- a/quakec/csaddon/src/csfixups.qc +++ b/quakec/csaddon/src/csfixups.qc @@ -27,6 +27,7 @@ void() csfixups = ptr_self = (entity*)externvalue(0, "&self"); }; +int *world_hitcontentsmaski; //evilness, so we can assign to world without errors. vector mousenear; vector mousefar; diff --git a/quakec/csaddon/src/csplat.qc b/quakec/csaddon/src/csplat.qc index 008be45d7..479af35fa 100644 --- a/quakec/csaddon/src/csplat.qc +++ b/quakec/csaddon/src/csplat.qc @@ -2414,11 +2414,12 @@ typedef struct int(float modelidx, int patchid, patchvert_t *out_controlverts, int maxcp, patchinfo_t *out_info) patch_getcp = #0:patch_getcp; /* Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error. */ -int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, __out patchinfo_t out_info) patch_getmesh = #0:patch_getmesh; /* +int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, patchinfo_t *out_info) patch_getmesh = #0:patch_getmesh; /* Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error. */ int(float modelidx, int oldpatchid, patchvert_t *in_controlverts, patchinfo_t in_info) patch_create = #0:patch_create; /* Inserts a new patch into the model. Return value is the new patch's id. */ +int(patchvert_t *in_controlverts, patchvert_t *out_renderverts, int maxout, patchinfo_t *inout_info) patch_evaluate = #0; typedef struct { diff --git a/quakec/csaddon/src/editor_brushes.qc b/quakec/csaddon/src/editor_brushes.qc index 9e9e6f234..9f709585c 100644 --- a/quakec/csaddon/src/editor_brushes.qc +++ b/quakec/csaddon/src/editor_brushes.qc @@ -40,20 +40,29 @@ static void() editor_brushes_drawselected = float intensity = (sin(gettime(5)*4)+1)*0.05; int facenum, point, points; vector col; + + for (int sb = 0; sb < selectedbrushcount; sb++) + { + int model = selectedbrushes[sb].model; + int brush = selectedbrushes[sb].id; + + //draw a faded version of all the brushes. + DrawEngineBrushFaded(model, brush, 0.5); + } //draw all selected brush faces. for (int sb = 0; sb < selectedbrushcount; sb++) { int model = selectedbrushes[sb].model; int brush = selectedbrushes[sb].id; - int face = selectedbrushes[sb].face; - for(facenum = 0;;) + __uint64 facemask = selectedbrushes[sb].facemask; + for(facenum = 0;; facenum++) { - points = brush_getfacepoints(model, brush, ++facenum, facepoints, MAX_FACEPOINTS); + points = brush_getfacepoints(model, brush, 1+facenum, facepoints, MAX_FACEPOINTS); if (!points) break; //end of face list, I guess - if (facenum == face) + if (facemask & (1ul< 1) brush_history_create(model, tmp.faces, tmp.numfaces, tmp.contents, TRUE); } + else if (tmp.numcp) + patch_history_edit(model, brush, tmp.cp, tmp.patchinfo); else brush_history_edit(model, brush, tmp.faces, tmp.numfaces, tmp.contents); bt_points = 0; @@ -478,7 +503,9 @@ void(vector mousepos) editor_brushes_addentities = vector o = mousenear; if (vlen(o - t) > 8192 && !autocvar_ca_brush_view) t = o + normalize(t)*8192; + *world_hitcontentsmaski = ~0; //I want to be able to select water... traceline(o, t, TRUE, world); + *world_hitcontentsmaski = 0; //don't break stuff #if 0 //find all the brushes within 32qu of the mouse cursor's impact point @@ -515,7 +542,7 @@ void(vector mousepos) editor_brushes_addentities = // bt_point[showpoints++] = brush_snappoint(trace_endpos); showpoints = 3; } - if (brushtool == BT_CREATE) + if (brushtool == BT_CREATEBRUSH || brushtool == BT_CREATEPATCH) bt_point[showpoints++] = brush_snappoint(trace_endpos); //FIXME: if slicing, draw the intersection @@ -607,7 +634,13 @@ void(vector mousepos) editor_brushes_overlay = case BT_CREATEDRAG: drawrawstring('0 32 0', "Drag+Create", '8 8 0', '1 1 1', 1); break; - case BT_CREATE: + case BT_CREATEPATCH: + if (autocvar_ca_explicitpatch) + drawrawstring('0 32 0', sprintf("Create Explicit %g*%g Patch", autocvar_ca_cpwidth, autocvar_ca_cpheight), '8 8 0', '1 1 1', 1); + else + drawrawstring('0 32 0', sprintf("Create Auto %g*%g Patch", autocvar_ca_cpwidth, autocvar_ca_cpheight), '8 8 0', '1 1 1', 1); + break; + case BT_CREATEBRUSH: drawrawstring('0 32 0', "Paint+Create", '8 8 0', '1 1 1', 1); break; case BT_SLICE: @@ -653,10 +686,12 @@ brusheditormodes registercommand("brushedit_redo"); registercommand("brushedit_delete"); registercommand("brushedit_create"); + registercommand("brushedit_createpatch"); registercommand("brushedit_slice"); registercommand("brushedit_matchface"); registercommand("brushedit_resettexcoords"); registercommand("brushedit_settexture"); + registercommand("brushedit_scrolltex"); registercommand("brushedit_subtract"); registercommand("brushedit_binds"); registercommand("brushedit_binds_default"); @@ -679,13 +714,21 @@ brusheditormodes brush_undo(); break; case "brushedit_create": - brushtool = BT_CREATE; + brushtool = BT_CREATEBRUSH; + bt_points = 0; + break; + case "brushedit_createpatch": + brushtool = BT_CREATEPATCH; bt_points = 0; break; case "brushedit_slice": brushtool = BT_SLICE; bt_points = 0; break; + case "brushedit_scrolltex": + brushtool = BT_MOVETEXTURE; + bt_points = 0; + break; case "+brushedit_nogrid": nogrid = TRUE; break; case "-brushedit_nogrid": nogrid = FALSE; break; @@ -719,9 +762,7 @@ brusheditormodes brushedit_resettextures(); break; case "brushedit_settexture": - //IMPLEMENTME - brushtool = BT_NONE; - bt_points = 0; + brushedit_settexture(argv(1)); break; case "brushedit_subtract": brushedit_subtract(); @@ -737,15 +778,16 @@ brusheditormodes localcmd("bind ctrl+z brushedit_undo\n"); localcmd("bind ctrl+y brushedit_redo\n"); localcmd("bind ctrl+i brushedit_create\n"); + localcmd("bind ctrl+p brushedit_createpatch\n"); localcmd("bind ctrl+s brushedit_slice\n"); localcmd("bind ctrl+m brushedit_matchface\n"); - localcmd("bind ctrl+delete brushedit_delete\n"); + localcmd("bind ctrl+del brushedit_delete\n"); localcmd("bind ctrl+backspace brushedit_subtract\n"); localcmd("bind ctrl+c +brushedit_clone\n"); localcmd("bind ctrl+d +brushedit_draw\n"); localcmd("bind ctrl+r +brushedit_rotate\n"); localcmd("bind ctrl+f +brushedit_pushface\n"); - localcmd("bind ctrl+t +brushedit_scrolltex\n"); + localcmd("bind ctrl+t brushedit_scrolltex\n"); localcmd("bind ctrl+v +brushedit_vertex\n"); break; case "brushedit_binds": @@ -806,7 +848,7 @@ float(float key, float unic, vector mousepos) editor_brushes_key = { if (vertedit.selectedvert1 != -1) { - mousedown = TRUE; + mousedown |= 1; mousetool = BT_VERTEXEDIT; if (vertedit.selectedvert2 != -1) { @@ -823,7 +865,9 @@ float(float key, float unic, vector mousepos) editor_brushes_key = return TRUE; } + *world_hitcontentsmaski = ~0; //I want to be able to select water... traceline(o, t, TRUE, world); + *world_hitcontentsmaski = 0; //don't break stuff. float tracemodel = trace_ent.modelindex; /*if (brushtool == BT_CREATEDRAG || (brushtool == BT_PUSHFACE && selectedbrushface)) @@ -835,10 +879,12 @@ float(float key, float unic, vector mousepos) editor_brushes_key = else*/ if (brushtool != BT_PUSHFACE && brushtool != BT_MOVETEXTURE) trace_brush_faceid = 0; - if (brushtool == BT_SLICE || brushtool == BT_CREATE) + if (brushtool == BT_SLICE || brushtool == BT_CREATEBRUSH || brushtool == BT_CREATEPATCH) { if (brushtool == BT_SLICE && bt_points > 2) bt_points = 2; + if (brushtool == BT_CREATEPATCH && bt_points > 3) + bt_points = 3; //FIXME: BT_CREATE is planar. so keep the points planar too. //FIXME: need a way to move points already placed @@ -883,10 +929,10 @@ float(float key, float unic, vector mousepos) editor_brushes_key = if (!tracemodel || !trace_brush_id) { } - else if (brush_isselected(tracemodel, trace_brush_id)) - brush_deselect(tracemodel, trace_brush_id); + else if (brushface_isselected(tracemodel, trace_brush_id, trace_brush_faceid)) + brushface_deselect(tracemodel, trace_brush_id, trace_brush_faceid); else - brush_select(tracemodel, trace_brush_id); + brushface_select(tracemodel, trace_brush_id, trace_brush_faceid); } else { //deselect everything but the targetted brush. @@ -895,11 +941,11 @@ float(float key, float unic, vector mousepos) editor_brushes_key = else if (!brush_isselected(tracemodel, trace_brush_id)) { brush_deselectall(); - brush_select(tracemodel, trace_brush_id); + brushface_select(tracemodel, trace_brush_id, trace_brush_faceid); } else { //its already selected, start doing whatever the current mouse action is meant to be - mousedown = TRUE; + mousedown |= 1; mousetool = brushtool; vertedit.selectedvert1 = -1; vertedit.selectedvert2 = -1; @@ -939,7 +985,7 @@ float(float key, float unic, vector mousepos) editor_brushes_key = if (selectedbrushface && (brushtool == BT_PUSHFACE || brushtool == BT_MOVETEXTURE)) { - mousedown = TRUE; + mousedown |= 1; mousetool = brushtool; vertedit.selectedvert1 = -1; vertedit.selectedvert2 = -1; @@ -975,7 +1021,51 @@ float(float key, float unic, vector mousepos) editor_brushes_key = mousedown = FALSE; return TRUE; } - else if (brushtool == BT_CREATE) + else if (brushtool == BT_CREATEPATCH) + { + if (bt_points == 4) + { + int x,y; + patchvert_t *vert; + patchvert_t *cp; + patchinfo_t patchinfo = {autocvar_ca_newbrushtexture, CONTENTBIT_SOLID, max(2,autocvar_ca_cpwidth), max(2,autocvar_ca_cpheight), 0, 0, '0 0 0'}; + tmp.numfaces = 0; + tmp.numcp = 0; + bt_points = 0; + + if (!autocvar_ca_explicitpatch) + { //must be odd sizes. cos grr. + patchinfo.cpwidth|=1; + patchinfo.cpheight|=1; + patchinfo.tesswidth = patchinfo.tessheight = -1; //proper q3 patch, engine decides according to smoothness (which means nothing quite fits). + } + if (patchinfo.cpwidth*patchinfo.cpheight>64*64) + { + cprint(sprintf("%i*%i patch dimensions too large", patchinfo.cpwidth, patchinfo.cpheight)); + return TRUE; + } + + //populate the CPs, averaging from the 4 specified coords. + cp = vert = memalloc(sizeof(*vert) * patchinfo.cpwidth*patchinfo.cpheight); + for (y = 0; y < patchinfo.cpheight; y++) + for (x = 0; x < patchinfo.cpwidth; x++) + { + cp->s = x/(patchinfo.cpwidth-1); + cp->t = y/(patchinfo.cpheight-1); + vector top = bt_point[0]+(bt_point[1]-bt_point[0])*cp->s; + vector bot = bt_point[3]+(bt_point[2]-bt_point[3])*cp->s; + cp->xyz = top+(bot-top)*cp->t; + cp->rgb = '1 1 1'; cp->a = 1; + cp++; + } + + brush_deselectall(); + patch_history_create(selectedbrushmodel, vert, patchinfo, TRUE); + memfree(vert); + } + return TRUE; + } + else if (brushtool == BT_CREATEBRUSH) { if (bt_points >= 3) { @@ -983,7 +1073,7 @@ float(float key, float unic, vector mousepos) editor_brushes_key = tmp.numcp = 0; bt_points = 0; brush_deselectall(); - brush_history_create(selectedbrushmodel, tmp.faces, tmp.numfaces, 1i, TRUE); + brush_history_create(selectedbrushmodel, tmp.faces, tmp.numfaces, CONTENTBIT_SOLID, TRUE); } return TRUE; } diff --git a/quakec/csaddon/src/editor_lights.qc b/quakec/csaddon/src/editor_lights.qc index 2f47cd2e2..717375296 100644 --- a/quakec/csaddon/src/editor_lights.qc +++ b/quakec/csaddon/src/editor_lights.qc @@ -31,7 +31,7 @@ void() editor_lights_addentities = tempent.effects |= 8192f; } else - tempent.effects &~= 8192f; + tempent.effects &= ~8192f; if (!(float)dynamiclight_get(l, LFIELD_RADIUS)) continue; setorigin(tempent, dynamiclight_get(l, LFIELD_ORIGIN)); diff --git a/quakec/csaddon/src/editor_particles.qc b/quakec/csaddon/src/editor_particles.qc index 3540c9ffc..730824318 100644 --- a/quakec/csaddon/src/editor_particles.qc +++ b/quakec/csaddon/src/editor_particles.qc @@ -326,7 +326,7 @@ float(float keyc, float unic, vector m) editor_particles_key = /*middle mouse*/ if (keyc == 514) { - mousedown = 3; + mousedown |= 4; applyparticles(); } @@ -364,7 +364,7 @@ void(vector mousepos) editor_particles_overlay = loadparticles(setname); } - if (mousedown == 3 && cureffect >= 0 && cureffect < numptypes) + if (mousedown & 4 && cureffect >= 0 && cureffect < numptypes) { vector t = unproject(mousepos + '0 0 8192'); vector o = unproject(mousepos); diff --git a/quakec/csqctest/src/ss/weapons.qc b/quakec/csqctest/src/ss/weapons.qc index b3eaa0c87..a849e27b2 100644 --- a/quakec/csqctest/src/ss/weapons.qc +++ b/quakec/csqctest/src/ss/weapons.qc @@ -235,14 +235,19 @@ void(float damage, vector dir) TraceAttack = } else { - WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte (MSG_BROADCAST, TE_GUNSHOT); + if (checkbuiltin(te_gunshot)) + te_gunshot(org); + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); #ifdef QWSSQC - WriteByte (MSG_BROADCAST, 1); //count + WriteByte (MSG_BROADCAST, 1); //count #endif - WriteCoord (MSG_BROADCAST, org_x); - WriteCoord (MSG_BROADCAST, org_y); - WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } } }; @@ -399,15 +404,16 @@ void() BecomeExplosion = self.dimension_seen = DIMENSION_NOCSQC; dimension_send = DIMENSION_NOCSQC; -#if 1 - WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte (MSG_BROADCAST, TE_EXPLOSION); - WriteCoord (MSG_BROADCAST, self.origin_x); - WriteCoord (MSG_BROADCAST, self.origin_y); - WriteCoord (MSG_BROADCAST, self.origin_z); -#else - te_explosion(self.origin); -#endif + if (checkbuiltin(te_explosion)) + te_explosion(self.origin); + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); self.dimension_seen = DIMENSION_DEFAULT;