From c68cbfec24f42765a1380f49046d26e518aa493b Mon Sep 17 00:00:00 2001 From: Spoike Date: Tue, 24 Jan 2017 10:27:39 +0000 Subject: [PATCH] implement status command for nq clients that expect something to be printed. IP addresses are withheld. fix stats issue. support menuqc-based loading screens. fixed 2d render-to-texture issues. (finally) throttle 'connection lost or aborted' messages. report the ip address too, because we can. begun work on nq-style player ip-logging. ip addresses are collected now, but there's still no actual database code yet. rewrote engine auto-update. now part of the updates menu instead of system-specific special-case code. Still requires a system function to actually invoke the updated engine however. added sdl audio capture support (requires sdl 2.0.5). treat q_version like f_version etc. respond to both. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5046 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_main.c | 35 ++- engine/client/cl_parse.c | 64 +++- engine/client/cl_screen.c | 6 +- engine/client/client.h | 13 + engine/client/keys.c | 3 + engine/client/m_download.c | 342 ++++++++++++++------- engine/client/m_items.c | 14 +- engine/client/m_options.c | 29 +- engine/client/menu.c | 3 + engine/client/menu.h | 2 + engine/client/pr_menu.c | 23 +- engine/client/quakedef.h | 3 +- engine/client/renderer.c | 2 +- engine/client/sbar.c | 9 + engine/client/snd_al.c | 1 + engine/client/snd_directx.c | 2 +- engine/client/snd_dma.c | 5 +- engine/client/snd_sdl.c | 133 +++++++- engine/client/sys_win.c | 486 +++++++----------------------- engine/client/valid.c | 27 +- engine/client/zqtp.c | 4 +- engine/common/common.c | 13 +- engine/common/common.h | 2 + engine/common/fs.c | 20 +- engine/common/fs.h | 1 + engine/common/log.c | 87 ++++++ engine/common/net_wins.c | 37 ++- engine/common/sys.h | 18 +- engine/dotnet2005/ftequake.vcproj | 2 +- engine/gl/gl_draw.c | 5 + engine/server/pr_cmds.c | 1 + engine/server/sv_ccmds.c | 8 + engine/server/sv_ents.c | 2 +- engine/server/sv_main.c | 9 +- engine/server/sv_send.c | 7 +- engine/server/sv_user.c | 68 ++++- 36 files changed, 855 insertions(+), 631 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index ba2c170a7..39305e1a2 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -41,6 +41,7 @@ static void CL_ForceStopDownload (qboolean finish); // references them even when on a unix system. qboolean noclip_anglehack; // remnant from old quake +int startuppending; void Host_FinishLoading(void); @@ -737,6 +738,7 @@ void CL_CheckForResend (void) int contype = 0; qboolean keeptrying = true; char *host; + extern int r_blockvidrestart; #ifndef CLIENTONLY if (!cls.state && (!connectinfo.trying || sv.state != ss_clustermode) && sv.state) @@ -970,6 +972,8 @@ void CL_CheckForResend (void) if (!connectinfo.trying) return; + if (startuppending || r_blockvidrestart) + return; //don't send connect requests until we've actually initialised fully. this isn't a huge issue, but makes the startup prints a little more sane. /* #ifdef NQPROT @@ -4952,7 +4956,6 @@ Runs all active servers extern cvar_t cl_netfps; extern cvar_t cl_sparemsec; -int startuppending; void CL_StartCinematicOrMenu(void); int nopacketcount; void SNDDMA_SetUnderWater(qboolean underwater); @@ -5363,7 +5366,7 @@ void CL_StartCinematicOrMenu(void) { COM_MainThreadWork(); - if (FS_DownloadingPackage()) + if (com_installer && FS_DownloadingPackage()) { startuppending = true; return; @@ -5681,6 +5684,7 @@ Host_Init */ void Host_Init (quakeparms_t *parms) { + char engineupdated[MAX_OSPATH]; int man; com_parseutf8.ival = 1; //enable utf8 parsing even before cvars are registered. @@ -5704,9 +5708,31 @@ void Host_Init (quakeparms_t *parms) COM_ParsePlusSets(false); Cbuf_Init (); Cmd_Init (); + COM_Init (); + + //we have enough of the filesystem inited now that we can read the package list and figure out which engine was last installed. + if (PM_FindUpdatedEngine(engineupdated, sizeof(engineupdated))) + { + PM_Shutdown(); //will restart later as needed, but we need to be sure that no files are open or anything. + if (Sys_EngineWasUpdated(engineupdated)) + { + COM_Shutdown(); + Cmd_Shutdown(); + Sys_Shutdown(); + Con_Shutdown(); + Memory_DeInit(); + Cvar_Shutdown(); + Sys_Quit(); + return; + } + } V_Init (); NET_Init (); - COM_Init (); + +#ifdef PLUGINS + Plug_Initialise(false); +#endif + #ifdef Q2BSPS CM_Init(); #endif @@ -5784,10 +5810,7 @@ to run quit through here before the final handoff to the sys code. void Host_Shutdown(void) { if (!host_initialized) - { - Sys_Printf ("recursive shutdown\n"); return; - } host_initialized = false; #ifdef WEBCLIENT diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index ec6e19457..b6c9d9a35 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -7371,18 +7371,19 @@ static char *CLNQ_ParseProQuakeMessage (char *s) return s; } -static enum { - CLNQPP_NONE, - CLNQPP_PINGS -} cl_nqparseprint; qboolean CLNQ_ParseNQPrints(char *s) { int i; char *start = s; - if (cl_nqparseprint == CLNQPP_PINGS) + if (!strcmp(s, "Client ping times:\n")) + { + cl.nqparseprint = CLNQPP_PINGS; + return true; + } + else if (cl.nqparseprint == CLNQPP_PINGS) { char *pingstart; - cl_nqparseprint = CLNQPP_NONE; + cl.nqparseprint = CLNQPP_NONE; while(*s == ' ') s++; pingstart = s; @@ -7414,7 +7415,7 @@ qboolean CLNQ_ParseNQPrints(char *s) { cl.players[i].ping = atoi(pingstart); } - cl_nqparseprint = CLNQPP_PINGS; + cl.nqparseprint = CLNQPP_PINGS; return true; } } @@ -7422,10 +7423,51 @@ qboolean CLNQ_ParseNQPrints(char *s) s = start; } - if (!strcmp(s, "Client ping times:\n")) + if (!strncmp(s, "host: ", 9)) { - cl_nqparseprint = CLNQPP_PINGS; - return true; + cl.nqparseprint = CLNQPP_STATUS; + return cls.nqexpectingstatusresponse; + } + else if (cl.nqparseprint == CLNQPP_STATUS) + { + if (!strncmp(s, "players: ", 9)) + { + cl.nqparseprint = CLNQPP_STATUSPLAYER; + return cls.nqexpectingstatusresponse; + } + else if (strchr(s, ':')) + return cls.nqexpectingstatusresponse; + + cl.nqparseprint = CLNQPP_NONE; //error of some kind... + cls.nqexpectingstatusresponse = false; + } + if (cl.nqparseprint == CLNQPP_STATUSPLAYER) + { + if (*s == '#') + { + cl.nqparseprint = CLNQPP_STATUSPLAYERIP; + cl.nqparseprintplayer = atoi(s+1)-1; + if (cl.nqparseprintplayer >= 0 && cl.nqparseprintplayer < cl.allocated_client_slots) + return cls.nqexpectingstatusresponse; + } + cl.nqparseprint = CLNQPP_NONE; //error of some kind... + cls.nqexpectingstatusresponse = false; + } + if (cl.nqparseprint == CLNQPP_STATUSPLAYERIP) + { + if (!strncmp(s, " ", 3)) + { + while(*s == ' ') + s++; + COM_ParseOut(s, cl.players[cl.nqparseprintplayer].ip, sizeof(cl.players[cl.nqparseprintplayer].ip)); + IPLog_Add(cl.players[cl.nqparseprintplayer].ip, cl.players[cl.nqparseprintplayer].name); + if (*cl.players[cl.nqparseprintplayer].ip != '[' && *cl.players[cl.nqparseprintplayer].ip < '0' && *cl.players[cl.nqparseprintplayer].ip > '9') + *cl.players[cl.nqparseprintplayer].ip = 0; //non-numeric addresses are not useful. + cl.nqparseprint = CLNQPP_STATUSPLAYER; + return cls.nqexpectingstatusresponse; + } + cl.nqparseprint = CLNQPP_NONE; //error of some kind... + cls.nqexpectingstatusresponse = false; } return false; @@ -7740,6 +7782,8 @@ void CLNQ_ParseServerMessage (void) if (*cl.players[i].name) cl.players[i].userid = i+1; Info_SetValueForKey(cl.players[i].userinfo, "name", cl.players[i].name, sizeof(cl.players[i].userinfo)); + if (!cl.nqplayernamechanged) + cl.nqplayernamechanged = realtime+2; } break; diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index 07ae36d59..8593cf542 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -1706,7 +1706,11 @@ void SCR_DrawLoading (qboolean opaque) int h2depth; if (CSQC_UseGamecodeLoadingScreen()) - return; + return; //will be drawn as part of the regular screen updates +#ifdef MENU_DAT + if (MP_UsingGamecodeLoadingScreen()) + return; //menuqc should have just drawn whatever overlays it wanted. +#endif //int mtype = M_GameType(); //unused variable y = vid.height/2; diff --git a/engine/client/client.h b/engine/client/client.h index 08a8993a2..bbc5ccdb9 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -169,6 +169,8 @@ typedef struct player_info_s int ping; qbyte pl; + char ip[128]; + struct { float time; //invalid if too old. @@ -531,6 +533,7 @@ typedef struct int language; colourised_t *colourised; + qboolean nqexpectingstatusresponse; } client_static_t; extern client_static_t cls; @@ -917,6 +920,16 @@ typedef struct MATCH_STANDBY, MATCH_INPROGRESS } matchstate; + + enum { + CLNQPP_NONE, + CLNQPP_PINGS, + CLNQPP_STATUS, //"host: *\n" ... "players: *\n\n" + CLNQPP_STATUSPLAYER, //#...\n + CLNQPP_STATUSPLAYERIP, // foobar\n + } nqparseprint; + int nqparseprintplayer; + float nqplayernamechanged; } client_state_t; extern unsigned int cl_teamtopcolor; diff --git a/engine/client/keys.c b/engine/client/keys.c index d58bf091a..4cb2d3c1f 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -746,6 +746,9 @@ void Key_DefaultLinkClicked(console_t *con, char *text, char *info) if (i == cl.splitclients) { extern cvar_t rcon_password; + if (*cl.players[player].ip) + Con_Footerf(con, true, "\n%s", cl.players[player].ip); + if (cl.spectator || cls.demoplayback) { //we're spectating, or an mvd diff --git a/engine/client/m_download.c b/engine/client/m_download.c index b7a1cb082..cd6f86cdb 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -36,9 +36,9 @@ vfsfile_t *FS_GZ_DecompressWriteFilter(vfsfile_t *outfile, qboolean autoclosefil #define PHPMIN #endif #ifdef NOLEGACY -#define PHPLEG "&leg=0" +#define PHPLEG "&leg=0&test=1" #else -#define PHPLEG "&leg=1" +#define PHPLEG "&leg=1&test=1" #endif #if defined(_DEBUG) || defined(DEBUG) #define PHPDBG "&dbg=1" @@ -48,11 +48,13 @@ vfsfile_t *FS_GZ_DecompressWriteFilter(vfsfile_t *outfile, qboolean autoclosefil #ifndef SVNREVISION #define SVNREVISION - #endif -#define DOWNLOADABLESARGS "ver=" STRINGIFY(SVNREVISION) PHPVK PHPGL PHPD3D PHPMIN PHPLEG PHPDBG "&arch="PLATFORM "_" ARCH_CPU_POSTFIX +#define SVNREVISIONSTR STRINGIFY(SVNREVISION) +#define DOWNLOADABLESARGS "ver=" SVNREVISIONSTR PHPVK PHPGL PHPD3D PHPMIN PHPLEG PHPDBG "&arch="PLATFORM "_" ARCH_CPU_POSTFIX -extern cvar_t fs_downloads_url; +extern cvar_t pm_autoupdate; +extern cvar_t pm_downloads_url; #define INSTALLEDFILES "installed.lst" //the file that resides in the quakedir (saying what's installed). //installed native okay [previously manually installed, or has no a qhash] @@ -70,7 +72,7 @@ extern cvar_t fs_downloads_url; //!installed * missing [simply not installed] -#define DPF_INSTALLED 0x01 +#define DPF_ENABLED 0x01 #define DPF_NATIVE 0x02 //appears to be installed properly #define DPF_CACHED 0x04 //appears to be installed in their dlcache dir (and has a qhash) #define DPF_CORRUPT 0x08 //will be deleted before it can be changed @@ -82,7 +84,9 @@ extern cvar_t fs_downloads_url; #define DPF_ENGINE 0x100 //engine update. replaces old autoupdate mechanism #define DPF_PURGE 0x200 //package should be completely removed (ie: the dlcache dir too). if its still marked then it should be reinstalled anew. available on cached or corrupt packages, implied by native. #define DPF_MANIFEST 0x400 //package was named by the manifest, and should only be uninstalled after a warning. +#define DPF_TESTING 0x800 //package is provided on a testing/trial basis, and will only be selected/listed if autoupdates are configured to allow it. +#define DPF_PRESENT (DPF_NATIVE|DPF_CACHED) //pak.lst //priories <0 //pakX @@ -127,7 +131,7 @@ typedef struct package_s { struct package_s *alternative; //alternative (hidden) forms of this package. unsigned int trymirrors; - char *mirror[8]; + char *mirror[8]; //FIXME: move to two types of dep... char gamedir[16]; enum fs_relative fsroot; char version[16]; @@ -156,6 +160,8 @@ typedef struct package_s { DEP_FILECONFLICT, //don't install if this file already exists. DEP_REQUIRE, DEP_RECOMMEND, //like depend, but uninstalling will not bubble. +// DEP_MIRROR, +// DEP_FAILEDMIRROR, DEP_FILE } dtype; @@ -237,6 +243,24 @@ static void PM_FreePackage(package_t *p) Z_Free(p); } +qboolean PM_PurgeOnDisable(package_t *p) +{ + //corrupt packages must be purged + if (p->flags & DPF_CORRUPT) + return true; + //engine updates can be present and not enabled + if (p->flags & DPF_ENGINE) + return false; + //hashed packages can also be present and not enabled, but only if they're in the cache and not native + if (*p->gamedir && p->qhash && (p->flags & DPF_CACHED)) + return false; + //FIXME: add basedir-plugins to the package manager so they can be enabled/disabled properly. + //if (p->arch) + // return false; + //all other packages must be deleted to disable them + return true; +} + //checks the status of each package void PM_ValidatePackage(package_t *p) { @@ -244,7 +268,7 @@ void PM_ValidatePackage(package_t *p) struct packagedep_s *dep; vfsfile_t *pf; p->flags &=~ (DPF_NATIVE|DPF_CACHED|DPF_CORRUPT); - if (p->flags & DPF_INSTALLED) + if (p->flags & DPF_ENABLED) { for (dep = p->deps; dep; dep = dep->next) { @@ -309,7 +333,7 @@ void PM_ValidatePackage(package_t *p) { if (o == p) continue; - if (o->flags & DPF_INSTALLED) + if (o->flags & DPF_ENABLED) { if (!strcmp(p->gamedir, o->gamedir) && p->fsroot == o->fsroot) if (strcmp(p->name, o->name) || strcmp(p->version, o->version)) @@ -328,7 +352,9 @@ void PM_ValidatePackage(package_t *p) p->flags |= DPF_CACHED; else if (!o) { - if (p->qhash) + if (!PM_PurgeOnDisable(p)) + p->flags |= fl; + else if (p->qhash) { char buf[8]; searchpathfuncs_t *archive; @@ -355,7 +381,7 @@ void PM_ValidatePackage(package_t *p) { p->flags |= fl; if (fl&DPF_NATIVE) - p->flags |= DPF_MARKED|DPF_INSTALLED; + p->flags |= DPF_MARKED|DPF_ENABLED; break; } else @@ -445,7 +471,8 @@ static qboolean PM_MergePackage(package_t *oldp, package_t *newp) newp->mirror[nm] = NULL; } } - oldp->flags &= ~DPF_FORGETONUNINSTALL | (newp->flags & DPF_FORGETONUNINSTALL); + //these flags should only remain set if set in both. + oldp->flags &= ~(DPF_FORGETONUNINSTALL|DPF_TESTING) | (newp->flags & (DPF_FORGETONUNINSTALL|DPF_TESTING)); PM_FreePackage(newp); return true; @@ -631,7 +658,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c subprefix = va("%s/%s", prefix, Cmd_Argv(2)); else subprefix = Cmd_Argv(2); - PM_AddSubList(Cmd_Argv(1), subprefix, (parseflags & DPF_INSTALLED)?true:false); + PM_AddSubList(Cmd_Argv(1), subprefix, (parseflags & DPF_ENABLED)?true:false); continue; } if (!strcmp(Cmd_Argv(0), "set")) @@ -654,6 +681,11 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c nummirrors++; } } + else if (!strcmp(Cmd_Argv(1), "updatemode")) + { + if (!(parseflags & DPF_ENABLED)) //don't use a downloaded file's version of this + Cvar_ForceSet(&pm_autoupdate, Cmd_Argv(2)); + } else { //erk @@ -682,7 +714,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c int i; if (version > 2) - flags &= ~DPF_INSTALLED; + flags &= ~DPF_ENABLED; p = Z_Malloc(sizeof(*p)); for (i = 1; i < argc; i++) @@ -739,10 +771,12 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c PM_AddDep(p, DEP_FILECONFLICT, arg+13); else if (!strncmp(arg, "recommend=", 10)) PM_AddDep(p, DEP_RECOMMEND, arg+10); + else if (!strncmp(arg, "test=", 5)) + flags |= DPF_TESTING; else if (!strncmp(arg, "stale=", 6) && version==2) - flags &= ~DPF_INSTALLED; + flags &= ~DPF_ENABLED; else if (!strncmp(arg, "installed=", 6) && version>2) - flags |= parseflags & DPF_INSTALLED; + flags |= parseflags & DPF_ENABLED; else { Con_DPrintf("Unknown package property\n"); @@ -868,7 +902,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c { if (!Q_strcasecmp(p->arch, THISENGINE)) { - if (Sys_GetAutoUpdateSetting() == UPD_UNSUPPORTED) + if (!Sys_EngineCanUpdate()) p->flags |= DPF_HIDDEN; else p->flags |= DPF_ENGINE; @@ -891,7 +925,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c p->flags |= DPF_HIDDEN; } } - if (p->flags & DPF_INSTALLED) + if (p->flags & DPF_ENABLED) p->flags |= DPF_MARKED; PM_InsertPackage(p); @@ -913,7 +947,7 @@ void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const cha loadedinstalled = true; if (f) { - PM_ParsePackageList(f, DPF_FORGETONUNINSTALL|DPF_INSTALLED, NULL, ""); + PM_ParsePackageList(f, DPF_FORGETONUNINSTALL|DPF_ENABLED, NULL, ""); VFS_CLOSE(f); } } @@ -924,14 +958,14 @@ 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_INSTALLED) && p->qhash && p->priority>=minpri&&p->prioritygamedir)) + if ((p->flags & DPF_ENABLED) && p->qhash && p->priority>=minpri&&p->prioritygamedir)) pri = p->priority; } minpri = pri+1; for (p = availablepackages; p; p = p->next) { - if ((p->flags & DPF_INSTALLED) && p->qhash && p->priority==pri && !Q_strcasecmp(parent_pure, p->gamedir)) + if ((p->flags & DPF_ENABLED) && p->qhash && p->priority==pri && !Q_strcasecmp(parent_pure, p->gamedir)) { for (d = p->deps; d; d = d->next) { @@ -950,7 +984,7 @@ void PM_Shutdown(void) { //free everything... loadedinstalled = false; - fs_downloads_url.modified = false; + pm_downloads_url.modified = false; downloadablessequence++; @@ -984,7 +1018,7 @@ static void PM_PreparePackageList(void) loadedinstalled = true; if (f) { - PM_ParsePackageList(f, DPF_FORGETONUNINSTALL|DPF_INSTALLED, NULL, ""); + PM_ParsePackageList(f, DPF_FORGETONUNINSTALL|DPF_ENABLED, NULL, ""); VFS_CLOSE(f); } } @@ -1030,7 +1064,7 @@ static void PM_RevertChanges(void) package_t *p; for (p = availablepackages; p; p = p->next) { - if (p->flags & DPF_INSTALLED) + if (p->flags & DPF_ENABLED) p->flags |= DPF_MARKED; else p->flags &= ~DPF_MARKED; @@ -1181,8 +1215,10 @@ static unsigned int PM_MarkUpdates (void) { if ((p->flags & DPF_ENGINE) && !(p->flags & DPF_HIDDEN)) { - if ((p->flags & DPF_MARKED) || !e || strcmp(e->version, p->version) < 0) - e = p; + if (!(p->flags & DPF_TESTING) || pm_autoupdate.ival >= UPD_TESTING) + if (!e || strcmp(e->version, p->version) < 0) //package must be more recent than the previously found engine + if (strcmp(SVNREVISIONSTR, "-") && strcmp(SVNREVISIONSTR, p->version) < 0) //package must be more recent than the current engine too, there's no point auto-updating to an older revision. + e = p; } if (p->flags & DPF_MARKED) { @@ -1191,11 +1227,12 @@ static unsigned int PM_MarkUpdates (void) { if (p == o || (o->flags & DPF_HIDDEN)) continue; - if (!strcmp(o->name, p->name) && !strcmp(o->arch?o->arch:"", p->arch?p->arch:"") && strcmp(o->version, p->version) > 0) - { - if (!b || strcmp(b->version, o->version) < 0) - b = o; - } + if (!(p->flags & DPF_TESTING) || pm_autoupdate.ival >= UPD_TESTING) + if (!strcmp(o->name, p->name) && !strcmp(o->arch?o->arch:"", p->arch?p->arch:"") && strcmp(o->version, p->version) > 0) + { + if (!b || strcmp(b->version, o->version) < 0) + b = o; + } } if (b) @@ -1208,7 +1245,7 @@ static unsigned int PM_MarkUpdates (void) } if (e && !(e->flags & DPF_MARKED)) { - if (Sys_GetAutoUpdateSetting() >= UPD_STABLE) + if (pm_autoupdate.ival >= UPD_STABLE) { changecount++; PM_MarkPackage(e); @@ -1224,7 +1261,7 @@ static void PM_PrintChanges(void) package_t *p; for (p = availablepackages; p; p=p->next) { - if (!(p->flags & DPF_MARKED) != !(p->flags & DPF_INSTALLED) || (p->flags & DPF_PURGE)) + if (!(p->flags & DPF_MARKED) != !(p->flags & DPF_ENABLED) || (p->flags & DPF_PURGE)) { changes++; if (p->flags & DPF_MARKED) @@ -1340,16 +1377,15 @@ static void PM_ListDownloaded(struct dl_download *dl) static void PM_UpdatePackageList(qboolean autoupdate, int retry) { unsigned int i; - int setting; - if (retry>1 || fs_downloads_url.modified) + if (retry>1 || pm_downloads_url.modified) PM_Shutdown(); PM_PreparePackageList(); //make sure our sources are okay. - if (*fs_downloads_url.string) - PM_AddSubList(fs_downloads_url.string, "", true); + if (*pm_downloads_url.string) + PM_AddSubList(pm_downloads_url.string, "", true); doautoupdate |= autoupdate; @@ -1362,11 +1398,7 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) if (downloadablelist[i].curdl) continue; - setting = Sys_GetAutoUpdateSetting(); -// if (setting == UPD_UNSUPPORTED) -// setting = autoupdatesetting+1; - - downloadablelist[i].curdl = HTTP_CL_Get(va("%s%s"DOWNLOADABLESARGS"%s", downloadablelist[i].url, strchr(downloadablelist[i].url,'?')?"&":"?", (setting>=UPD_TESTING)?"test=1":""), NULL, PM_ListDownloaded); + downloadablelist[i].curdl = HTTP_CL_Get(va("%s%s"DOWNLOADABLESARGS, downloadablelist[i].url, strchr(downloadablelist[i].url,'?')?"&":"?"), NULL, PM_ListDownloaded); if (downloadablelist[i].curdl) { downloadablelist[i].curdl->user_num = i; @@ -1437,6 +1469,9 @@ static void PM_WriteInstalledPackages(void) s = "version 2\n"; VFS_WRITE(f, s, strlen(s)); + s = va("set updatemode \"%s\"\n", pm_autoupdate.string); + VFS_WRITE(f, s, strlen(s)); + for (i = 0; i < numdownloadablelists; i++) { if (downloadablelist[i].save) @@ -1448,12 +1483,12 @@ static void PM_WriteInstalledPackages(void) for (p = availablepackages; p ; p=p->next) { - if (p->flags & (DPF_CACHED|DPF_INSTALLED)) + if (p->flags & (DPF_PRESENT|DPF_ENABLED)) { char buf[8192]; buf[0] = 0; COM_QuotedString(va("%s%s", p->category, p->name), buf, sizeof(buf), false); - if (p->flags & DPF_INSTALLED) + if (p->flags & DPF_ENABLED) { //v3+ // Q_strncatz(buf, " ", sizeof(buf)); // COM_QuotedConcat(va("installed=1"), buf, sizeof(buf)); @@ -1521,7 +1556,7 @@ static void PM_WriteInstalledPackages(void) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("file=%s", dep->name), buf, sizeof(buf)); - if ((p->flags & DPF_ENGINE) && (!e || strcmp(e->version, p->version) < 0)) + if ((p->flags & DPF_ENABLED) && (p->flags & DPF_ENGINE) && (!e || strcmp(e->version, p->version) < 0)) { e = p; ef = dep; @@ -1549,6 +1584,12 @@ static void PM_WriteInstalledPackages(void) } } + if (p->flags & DPF_TESTING) + { + Q_strncatz(buf, " ", sizeof(buf)); + COM_QuotedConcat("test=1", buf, sizeof(buf)); + } + buf[sizeof(buf)-2] = 0; //just in case. Q_strncatz(buf, "\n", sizeof(buf)); VFS_WRITE(f, buf, strlen(buf)); @@ -1586,7 +1627,7 @@ static int QDECL PM_ExtractFiles(const char *fname, qofs_t fsize, time_t mtime, else n = fname; if (FS_WriteFile(n, f, loc.len, p->fsroot)) - p->flags |= DPF_NATIVE|DPF_INSTALLED; + p->flags |= DPF_NATIVE|DPF_ENABLED; free(f); //keep track of the installed files, so we can delete them properly after. @@ -1600,6 +1641,7 @@ static void PM_StartADownload(void); //callback from PM_StartADownload static void PM_Download_Got(struct dl_download *dl) { + char native[MAX_OSPATH]; qboolean successful = dl->status == DL_FINISHED; package_t *p; char *tempname = dl->user_ctx; @@ -1640,7 +1682,7 @@ static void PM_Download_Got(struct dl_download *dl) searchpathfuncs_t *archive = FSZIP_LoadArchive(f, tempname, NULL); if (archive) { - p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_INSTALLED); + p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_ENABLED); archive->EnumerateFiles(archive, "*", PM_ExtractFiles, p); archive->EnumerateFiles(archive, "*/*", PM_ExtractFiles, p); archive->EnumerateFiles(archive, "*/*/*", PM_ExtractFiles, p); @@ -1694,19 +1736,23 @@ static void PM_Download_Got(struct dl_download *dl) } else destname = dep->name; - nfl |= DPF_INSTALLED | (p->flags & ~(DPF_CACHED|DPF_NATIVE|DPF_CORRUPT)); + nfl |= DPF_ENABLED | (p->flags & ~(DPF_CACHED|DPF_NATIVE|DPF_CORRUPT)); FS_CreatePath(destname, p->fsroot); if (FS_Remove(destname, p->fsroot)) ; if (!FS_Rename2(tempname, destname, p->fsroot, p->fsroot)) { //error! - Con_Printf("Couldn't rename %s to %s. Removed instead.\n", tempname, destname); + if (!FS_NativePath(destname, p->fsroot, native, sizeof(native))) + Q_strncpyz(native, destname, sizeof(native)); + Con_Printf("Couldn't rename %s to %s. Removed instead.\n", tempname, native); FS_Remove (tempname, p->fsroot); } else { //success! - Con_Printf("Downloaded %s (to %s)\n", p->name, destname); + if (!FS_NativePath(destname, p->fsroot, native, sizeof(native))) + Q_strncpyz(native, destname, sizeof(native)); + Con_Printf("Downloaded %s (to %s)\n", p->name, native); p->flags = nfl; PM_WriteInstalledPackages(); } @@ -1869,16 +1915,16 @@ static void PM_StartADownload(void) { //this appears to be a meta package with no download //just directly install it. p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT); - p->flags |= DPF_INSTALLED; + p->flags |= DPF_ENABLED; PM_WriteInstalledPackages(); } continue; } - if (p->qhash && (p->flags & DPF_CACHED)) + if (!PM_PurgeOnDisable(p)) { //its in our cache directory, so lets just use that p->trymirrors = 0; - p->flags |= DPF_INSTALLED; + p->flags |= DPF_ENABLED; PM_WriteInstalledPackages(); FS_ReloadPackFiles(); continue; @@ -1955,42 +2001,56 @@ static void PM_ApplyChanges(void) p = *link; if (p->download) ; //erk, dude, don't do two! - else if ((p->flags & DPF_PURGE) || (!(p->flags&DPF_MARKED) && (p->flags&DPF_INSTALLED))) - { //if we don't want it but we have it anyway: + else if ((p->flags & DPF_PURGE) || (!(p->flags&DPF_MARKED) && (p->flags&DPF_ENABLED))) + { //if we don't want it but we have it anyway. don't bother to follow this logic when reinstalling qboolean reloadpacks = false; struct packagedep_s *dep; - for (dep = p->deps; dep; dep = dep->next) + + if ((p->flags & DPF_PURGE) || PM_PurgeOnDisable(p)) { - if (dep->dtype == DEP_FILE) + for (dep = p->deps; dep; dep = dep->next) { - if (!reloadpacks) + if (dep->dtype == DEP_FILE) { - char ext[8]; - COM_FileExtension(dep->name, ext, sizeof(ext)); - if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) + if (!reloadpacks) { - reloadpacks = true; - FS_UnloadPackFiles(); + char ext[8]; + COM_FileExtension(dep->name, ext, sizeof(ext)); + if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) + { + reloadpacks = true; + FS_UnloadPackFiles(); + } } - } - if (*p->gamedir) - { - char *f = va("%s/%s", p->gamedir, dep->name); - char temp[MAX_OSPATH]; - if (p->qhash && FS_GenCachedPakName(f, p->qhash, temp, sizeof(temp)) && PM_CheckFile(temp, p->fsroot)) + if (*p->gamedir) { - if (p->flags & DPF_PURGE) - FS_Remove(temp, p->fsroot); + char *f = va("%s/%s", p->gamedir, dep->name); + char temp[MAX_OSPATH]; + if (p->qhash && FS_GenCachedPakName(f, p->qhash, temp, sizeof(temp)) && PM_CheckFile(temp, p->fsroot)) + { + if (!FS_Remove(temp, p->fsroot)) + p->flags |= DPF_CACHED; + } + else if (!FS_Remove(va("%s/%s", p->gamedir, dep->name), p->fsroot)) + p->flags |= DPF_NATIVE; } - else - FS_Remove(va("%s/%s", p->gamedir, dep->name), p->fsroot); + else if (!FS_Remove(dep->name, p->fsroot)) + p->flags |= DPF_NATIVE; } - else - FS_Remove(dep->name, p->fsroot); } } + p->flags &= ~(DPF_PURGE|DPF_ENABLED); + + /* FIXME: windows bug: + ** deleting an exe might 'succeed' but leave the file on disk for a while anyway. + ** the file will eventually disappear, but until then we'll still see it as present, + ** be unable to delete it again, and trying to open it to see if it still exists + ** will fail. + ** there's nothing we can do other than wait until whatever part of + ** windows that's fucking up releases its handles. + ** thankfully this only affects reinstalling exes/engines. + */ - p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_PURGE|DPF_INSTALLED); PM_ValidatePackage(p); PM_WriteInstalledPackages(); @@ -2028,7 +2088,7 @@ static void PM_ApplyChanges(void) //and flag any new/updated ones for a download for (p = availablepackages; p ; p=p->next) { - if ((p->flags&DPF_MARKED) && !(p->flags&DPF_INSTALLED) && !p->download) + if ((p->flags&DPF_MARKED) && !(p->flags&DPF_ENABLED) && !p->download) p->trymirrors = ~0u; } PM_StartADownload(); //and try to do those downloads. @@ -2071,7 +2131,7 @@ void PM_Command_f(void) { const char *status; char *markup; - if (p->flags & DPF_INSTALLED) + if (p->flags & DPF_ENABLED) markup = S_COLOR_GREEN; else if (p->flags & DPF_CORRUPT) markup = S_COLOR_RED; @@ -2080,7 +2140,7 @@ void PM_Command_f(void) else markup = S_COLOR_WHITE; - if (!(p->flags & DPF_MARKED) != !(p->flags & DPF_INSTALLED) || (p->flags & DPF_PURGE)) + if (!(p->flags & DPF_MARKED) != !(p->flags & DPF_ENABLED) || (p->flags & DPF_PURGE)) { if (p->flags & DPF_MARKED) { @@ -2094,7 +2154,7 @@ void PM_Command_f(void) else status = S_COLOR_CYAN""; } - else if ((p->flags & (DPF_INSTALLED|DPF_CACHED)) == DPF_CACHED) + else if ((p->flags & (DPF_ENABLED|DPF_CACHED)) == DPF_CACHED) status = S_COLOR_CYAN""; else status = ""; @@ -2124,7 +2184,7 @@ void PM_Command_f(void) if (p->flags & DPF_MARKED) { - if (p->flags & DPF_INSTALLED) + if (p->flags & DPF_ENABLED) { if (p->flags & DPF_PURGE) Con_Printf(" package is flagged to be re-installed\n"); @@ -2136,7 +2196,7 @@ void PM_Command_f(void) } else { - if (p->flags & DPF_INSTALLED) + if (p->flags & DPF_ENABLED) { if (p->flags & DPF_PURGE) Con_Printf(" package is flagged to be purged\n"); @@ -2160,6 +2220,8 @@ void PM_Command_f(void) Con_Printf(" package is hidden\n"); if (p->flags & DPF_ENGINE) Con_Printf(" package is an engine update\n"); + if (p->flags & DPF_TESTING) + Con_Printf(" package is untested\n"); return; } Con_Printf("\n"); @@ -2282,6 +2344,56 @@ void PM_Command_f(void) Con_Printf("%s: Unknown action %s\nShould be one of list, show, search, revert, add, rem, del, changes, apply\n", Cmd_Argv(0), act); } +qboolean PM_FindUpdatedEngine(char *syspath, size_t syspathsize) +{ + struct packagedep_s *dep; + package_t *e = NULL, *p; + char *pfname; + //figure out what we've previously installed. + if (!loadedinstalled) + { + vfsfile_t *f = FS_OpenVFS(INSTALLEDFILES, "rb", FS_ROOT); + loadedinstalled = true; + if (f) + { + PM_ParsePackageList(f, DPF_FORGETONUNINSTALL|DPF_ENABLED, NULL, ""); + VFS_CLOSE(f); + } + } + + for (p = availablepackages; p; p = p->next) + { + if ((p->flags & DPF_ENGINE) && !(p->flags & DPF_HIDDEN) && p->fsroot == FS_ROOT) + { + if ((p->flags & DPF_ENABLED) && (!e || strcmp(e->version, p->version) < 0)) + if (strcmp(SVNREVISIONSTR, "-") && strcmp(SVNREVISIONSTR, p->version) < 0) //package must be more recent than the current engine too, there's no point auto-updating to an older revision. + { + for (dep = p->deps, pfname = NULL; dep; dep = dep->next) + { + if (dep->dtype != DEP_FILE) + continue; + if (pfname) + { + pfname = NULL; + break; + } + pfname = dep->name; + } + + if (pfname && PM_CheckFile(pfname, p->fsroot)) + { + if (FS_NativePath(pfname, p->fsroot, syspath, syspathsize)) + e = p; + } + } + } + } + + if (e) + return true; + return false; +} + #else void PM_Command_f (void) { @@ -2307,7 +2419,6 @@ typedef struct { qboolean populated; } dlmenu_t; -static int autoupdatesetting = UPD_UNSUPPORTED; static void MD_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m) { package_t *p; @@ -2326,7 +2437,7 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m) Draw_FunString (x+4, y, "PND"); else { - switch((p->flags & (DPF_INSTALLED | DPF_MARKED))) + switch((p->flags & (DPF_ENABLED | DPF_MARKED))) { case 0: if (p->flags & DPF_PURGE) @@ -2339,10 +2450,12 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m) { Draw_FunString (x+4, y, "^Ue080^Ue082"); Draw_FunString (x+8, y, "^Ue081"); + if (p->flags & DPF_PRESENT) + Draw_FunString (x+8, y, "C"); } break; - case DPF_INSTALLED: - if (p->flags & DPF_PURGE || !(p->qhash && (p->flags & DPF_CACHED))) + case DPF_ENABLED: + if ((p->flags & DPF_PURGE) || PM_PurgeOnDisable(p)) Draw_FunString (x, y, "DEL"); else Draw_FunString (x, y, "REM"); @@ -2350,12 +2463,12 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m) case DPF_MARKED: if (p->flags & DPF_PURGE) Draw_FunString (x, y, "GET"); - else if (p->flags & (DPF_CACHED|DPF_NATIVE)) + else if (p->flags & (DPF_PRESENT)) Draw_FunString (x, y, "USE"); else Draw_FunString (x, y, "GET"); break; - case DPF_INSTALLED | DPF_MARKED: + case DPF_ENABLED | DPF_MARKED: if (p->flags & DPF_PURGE) Draw_FunString (x, y, "GET"); //purge and reinstall. else if (p->flags & DPF_CORRUPT) @@ -2373,6 +2486,11 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m) if (p->flags & DPF_DISPLAYVERSION) n = va("%s (%s)", n, *p->version?p->version:"unversioned"); + if (p->flags & DPF_TESTING) //hide testing updates + n = va("^h%s", n); +// if (!(p->flags & (DPF_ENABLED|DPF_MARKED|DPF_PRESENT)) +// continue; + if (&m->selecteditem->common == &c->common) Draw_AltFunString (x+48, y, n); else @@ -2392,7 +2510,7 @@ static qboolean MD_Key (struct menucustom_s *c, struct menu_s *m, int key, unsig if (p->alternative && (p->flags & DPF_HIDDEN)) p = p->alternative; - if (p->flags & DPF_INSTALLED) + if (p->flags & DPF_ENABLED) { switch (p->flags & (DPF_PURGE|DPF_MARKED)) { @@ -2401,7 +2519,7 @@ static qboolean MD_Key (struct menucustom_s *c, struct menu_s *m, int key, unsig break; case 0: p->flags |= DPF_PURGE; //purge - if (p->flags & (DPF_CACHED | DPF_CORRUPT)) + if (!PM_PurgeOnDisable(p)) break; //fall through case DPF_PURGE: @@ -2425,13 +2543,13 @@ static qboolean MD_Key (struct menucustom_s *c, struct menu_s *m, int key, unsig case DPF_MARKED: p->flags |= DPF_PURGE; //now: re-get despite already having it. - if (p->flags & (DPF_CACHED | DPF_CORRUPT)) + if ((p->flags & DPF_PRESENT) && !PM_PurgeOnDisable(p)) break; //only makes sense if we already have a cached copy that we're not going to use. //fallthrough case DPF_MARKED|DPF_PURGE: PM_UnmarkPackage(p); //now: delete - if (p->flags & (DPF_CACHED | DPF_CORRUPT)) + if ((p->flags & DPF_PRESENT) && !PM_PurgeOnDisable(p)) break; //only makes sense if we have a cached/corrupt copy of it already //fallthrough case DPF_PURGE: @@ -2477,20 +2595,13 @@ static void MD_AutoUpdate_Draw (int x, int y, struct menucustom_s *c, struct men { char *settings[] = { - "Unsupported", - "Revert Engine", "Off", "Stable Updates", "Test Updates" }; char *text; - int setting = Sys_GetAutoUpdateSetting(); - if (setting == UPD_UNSUPPORTED) - text = va("Auto Update: %s", settings[autoupdatesetting+1]); - else if (autoupdatesetting == UPD_UNSUPPORTED) - text = va("Auto Update: %s", settings[setting+1]); - else - text = va("Auto Update: %s (unsaved)", settings[autoupdatesetting+1]); + int setting = bound(0, pm_autoupdate.ival, 2); + text = va("Auto Update: %s", settings[setting]); if (&m->selecteditem->common == &c->common) Draw_AltFunString (x+4, y, text); else @@ -2500,12 +2611,13 @@ static qboolean MD_AutoUpdate_Key (struct menucustom_s *c, struct menu_s *m, int { if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { - if (autoupdatesetting == UPD_UNSUPPORTED) - autoupdatesetting = min(0, Sys_GetAutoUpdateSetting()); - autoupdatesetting+=1; - if (autoupdatesetting > UPD_TESTING) - autoupdatesetting = (Sys_GetAutoUpdateSetting() == UPD_UNSUPPORTED)?1:0; - PM_UpdatePackageList(true, 2); + char nv[8] = "0"; + if (pm_autoupdate.ival < UPD_TESTING && pm_autoupdate.ival >= 0) + Q_snprintfz(nv, sizeof(nv), "%i", pm_autoupdate.ival+1); + Cvar_ForceSet(&pm_autoupdate, nv); + PM_WriteInstalledPackages(); + + PM_UpdatePackageList(true, 0); } return false; } @@ -2524,14 +2636,6 @@ static qboolean MD_ApplyDownloads (union menuoption_s *mo,struct menu_s *m,int k { if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { -#ifdef HAVEAUTOUPDATE - if (autoupdatesetting != UPD_UNSUPPORTED) - { - Sys_SetAutoUpdateSetting(autoupdatesetting); - autoupdatesetting = UPD_UNSUPPORTED; - } -#endif - PM_ApplyChanges(); return true; } @@ -2599,8 +2703,11 @@ void M_AddItemsToDownloadMenu(menu_t *m) { if (strncmp(p->category, info->pathprefix, prefixlen)) continue; - if ((p->flags & DPF_HIDDEN) && (p->arch || !(p->flags & DPF_INSTALLED))) + if ((p->flags & DPF_HIDDEN) && (p->arch || !(p->flags & DPF_ENABLED))) continue; +// if (p->flags & DPF_TESTING) //hide testing updates +// if (!(p->flags & (DPF_ENABLED|DPF_MARKED|DPF_PRESENT)) +// continue; slash = strchr(p->category+prefixlen, '/'); if (slash) @@ -2622,7 +2729,7 @@ void M_AddItemsToDownloadMenu(menu_t *m) { if (!strncmp(s->category, info->pathprefix, slash-path) || s->category[slash-path] != '/') continue; - if (!(s->flags & DPF_INSTALLED) != !(s->flags & DPF_MARKED)) + if (!(s->flags & DPF_ENABLED) != !(s->flags & DPF_MARKED)) break; } @@ -2712,6 +2819,7 @@ void Menu_DownloadStuff_f (void) menu = M_CreateMenu(sizeof(dlmenu_t)); info = menu->data; + menu->persist = true; menu->predraw = M_Download_UpdateStatus; info->downloadablessequence = downloadablessequence; @@ -2727,7 +2835,7 @@ void Menu_DownloadStuff_f (void) //should only be called AFTER the filesystem etc is inited. void Menu_Download_Update(void) { - if (Sys_GetAutoUpdateSetting() == UPD_OFF || Sys_GetAutoUpdateSetting() == UPD_REVERT) + if (!pm_autoupdate.ival) return; PM_UpdatePackageList(true, 2); diff --git a/engine/client/m_items.c b/engine/client/m_items.c index 951ee0001..51c127587 100644 --- a/engine/client/m_items.c +++ b/engine/client/m_items.c @@ -1581,7 +1581,11 @@ void M_RemoveMenu (menu_t *menu) if (menu->remove) menu->remove(menu); if (menu == firstmenu) + { firstmenu = menu->parent; + if (firstmenu) + firstmenu->child = NULL; + } else { menu_t *prev; @@ -1636,14 +1640,8 @@ void M_RemoveAllMenus (qboolean leaveprompts) for (link = &firstmenu; *link; ) { m = *link; - if (!m->exclusive && leaveprompts) - { - //this is WEIRD. - if (m == firstmenu) - link = &m->parent; - else - link = &m->child; - } + if ((m->persist || !m->exclusive) && leaveprompts) + link = &m->parent; else M_RemoveMenu(m); } diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 26c22e51b..5834102f0 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -172,15 +172,6 @@ qboolean M_Options_InvertMouse (menucheck_t *option, struct menu_s *menu, chk_se } } -#ifdef HAVEAUTOUPDATE -static void M_Options_Remove(menu_t *m) -{ - menucombo_t *c = m->data; - if (c) - Sys_SetAutoUpdateSetting(c->selectedoption); -} -#endif - //options menu. void M_Menu_Options_f (void) { @@ -190,16 +181,19 @@ void M_Menu_Options_f (void) #endif int y; -#ifdef HAVEAUTOUPDATE -#define HAVEAUTOUPDATE - menuoption_t *updatecbo; +#ifdef WEBCLIENT static const char *autoupopts[] = { - "Revert", "Off", "Tested(Recommended)", "Untested(Latest)", NULL }; + static const char *autoupvals[] = { + "0", + "1", + "2", + NULL + }; #endif static const char *projections[] = { "Regular", @@ -256,8 +250,8 @@ void M_Menu_Options_f (void) MB_CHECKBOXCVAR("Lookspring", lookspring, 0), MB_CHECKBOXCVAR("Lookstrafe", lookstrafe, 0), MB_CHECKBOXCVAR("Windowed Mouse", _windowed_mouse, 0), -#ifdef HAVEAUTOUPDATE - MB_COMBORETURN("Auto Update", autoupopts, Sys_GetAutoUpdateSetting(), updatecbo, "This downloads engine updates from the internet, when a new build is available."), +#ifdef WEBCLIENT + MB_COMBOCVAR("Auto Update", pm_autoupdate, autoupopts, autoupvals, "This offers to download engine+package updates from the internet, when new versions are available."), #endif #ifndef CLIENTONLY MB_COMBOCVAR("Auto Save", sv_autosave, autosaveopts, autosavevals, NULL), @@ -311,11 +305,6 @@ void M_Menu_Options_f (void) MC_AddCvarCombo(menu, 16, 216, y, "Use Hud Plugin", &plug_sbar, hudplugopts, hudplugvalues); y += 8; } #endif - -#ifdef HAVEAUTOUPDATE - menu->data = updatecbo; - menu->remove = M_Options_Remove; -#endif } #ifndef __CYGWIN__ diff --git a/engine/client/menu.c b/engine/client/menu.c index bc4f3a533..15b5eadbe 100644 --- a/engine/client/menu.c +++ b/engine/client/menu.c @@ -1260,7 +1260,10 @@ void M_Shutdown(qboolean total) MP_Shutdown(); #endif if (total) + { + M_RemoveAllMenus(false); M_DeInit_Internal(); + } } void M_Reinit(void) diff --git a/engine/client/menu.h b/engine/client/menu.h index 56ec77f15..d2eacabc1 100644 --- a/engine/client/menu.h +++ b/engine/client/menu.h @@ -274,6 +274,7 @@ typedef struct menu_s { qboolean iszone; qboolean exclusive; + qboolean persist; //persists despite menuqc/engine changes etc void *data; //typecast @@ -467,6 +468,7 @@ qboolean MP_Init (void); void MP_Shutdown (void); qboolean MP_Toggle(int mode); void MP_Draw(void); +qboolean MP_UsingGamecodeLoadingScreen(void); void MP_RegisterCvarsAndCmds(void); qboolean MP_Keydown(int key, int unicode, unsigned int devid); void MP_Keyup(int key, int unicode, unsigned int devid); diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index 00b6d04c3..d346e9be9 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -2384,6 +2384,7 @@ world_t menu_world; func_t mp_init_function; func_t mp_shutdown_function; func_t mp_draw_function; +func_t mp_drawloading_function; func_t mp_keydown_function; func_t mp_keyup_function; func_t mp_inputevent_function; @@ -2604,6 +2605,7 @@ qboolean MP_Init (void) mp_init_function = PR_FindFunction(menu_world.progs, "m_init", PR_ANY); mp_shutdown_function = PR_FindFunction(menu_world.progs, "m_shutdown", PR_ANY); mp_draw_function = PR_FindFunction(menu_world.progs, "m_draw", PR_ANY); + mp_drawloading_function = PR_FindFunction(menu_world.progs, "m_drawloading", PR_ANY); mp_inputevent_function = PR_FindFunction(menu_world.progs, "Menu_InputEvent", PR_ANY); mp_keydown_function = PR_FindFunction(menu_world.progs, "m_keydown", PR_ANY); mp_keyup_function = PR_FindFunction(menu_world.progs, "m_keyup", PR_ANY); @@ -2734,8 +2736,15 @@ void MP_RegisterCvarsAndCmds(void) Cvar_Set(&forceqmenu, "1"); } +qboolean MP_UsingGamecodeLoadingScreen(void) +{ + return menu_world.progs && mp_drawloading_function; +} + void MP_Draw(void) { + extern qboolean scr_drawloading; + globalvars_t *pr_globals; if (!menu_world.progs) return; if (setjmp(mp_abort)) @@ -2748,14 +2757,14 @@ void MP_Draw(void) *menu_world.g.frametime = host_frametime; inmenuprogs++; - if (mp_draw_function) - { - globalvars_t *pr_globals = PR_globals(menu_world.progs, PR_CURRENT); - ((float *)pr_globals)[OFS_PARM0+0] = vid.width; - ((float *)pr_globals)[OFS_PARM0+1] = vid.height; - ((float *)pr_globals)[OFS_PARM0+2] = 0; + pr_globals = PR_globals(menu_world.progs, PR_CURRENT); + ((float *)pr_globals)[OFS_PARM0+0] = vid.width; + ((float *)pr_globals)[OFS_PARM0+1] = vid.height; + ((float *)pr_globals)[OFS_PARM0+2] = 0; + if (mp_drawloading_function && scr_drawloading) + PR_ExecuteProgram(menu_world.progs, mp_drawloading_function); + else if (mp_draw_function) PR_ExecuteProgram(menu_world.progs, mp_draw_function); - } inmenuprogs--; } diff --git a/engine/client/quakedef.h b/engine/client/quakedef.h index d56c2f2e8..7ce8002d1 100644 --- a/engine/client/quakedef.h +++ b/engine/client/quakedef.h @@ -290,7 +290,8 @@ extern qboolean noclip_anglehack; extern quakeparms_t host_parms; extern cvar_t fs_gamename; -extern cvar_t fs_downloads_url; +extern cvar_t pm_downloads_url; +extern cvar_t pm_autoupdate; extern cvar_t com_protocolname; extern cvar_t com_nogamedirnativecode; extern cvar_t com_parseutf8; diff --git a/engine/client/renderer.c b/engine/client/renderer.c index fe5678ad7..445c4d882 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -324,7 +324,7 @@ cvar_t gl_conback = CVARFDC ("gl_conback", "", // CVAR_ARCHIVE); //cvar_t gl_detailscale = CVAR ("gl_detailscale", "5"); cvar_t gl_font = CVARFD ("gl_font", "", - CVAR_RENDERERCALLBACK, ("Specifies the font file to use. a value such as FONT:ALTFONT specifies an alternative font to be used when ^^a is used.\n" + CVAR_RENDERERCALLBACK|CVAR_ARCHIVE, ("Specifies the font file to use. a value such as FONT:ALTFONT specifies an alternative font to be used when ^^a is used.\n" "When using TTF fonts, you will likely need to scale text to at least 150% - vid_conautoscale 1.5 will do this.\n" "TTF fonts may be loaded from your windows directory. \'gl_font cour?col=1,1,1:couri?col=0,1,0\' loads eg: c:\\windows\\fonts\\cour.ttf, and uses the italic version of courier for alternative text, with specific colour tints." )); diff --git a/engine/client/sbar.c b/engine/client/sbar.c index eb29a4ab2..c67aa7fb0 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -3324,6 +3324,15 @@ void Sbar_DeathmatchOverlay (int start) CL_SendClientCommand(true, "ping"); } } + if (cls.protocol == CP_NETQUAKE) + { + if (cl.nqplayernamechanged && cl.nqplayernamechanged < realtime) + { + cl.nqplayernamechanged = 0; + cls.nqexpectingstatusresponse = true; + CL_SendClientCommand(true, "status"); + } + } if (start) y = start; diff --git a/engine/client/snd_al.c b/engine/client/snd_al.c index d8682c151..5d7e696c9 100644 --- a/engine/client/snd_al.c +++ b/engine/client/snd_al.c @@ -1139,6 +1139,7 @@ static void OpenAL_Shutdown (soundcardinfo_t *sc) palcMakeContextCurrent(NULL); palcDestroyContext(oali->OpenAL_Context); palcCloseDevice(oali->OpenAL_Device); + Z_Free(oali->source); Z_Free(oali); } diff --git a/engine/client/snd_directx.c b/engine/client/snd_directx.c index f9bd76c5a..96bb93a1c 100644 --- a/engine/client/snd_directx.c +++ b/engine/client/snd_directx.c @@ -1134,7 +1134,7 @@ static BOOL CALLBACK dsound_capture_enumerate_ds(LPGUID lpGuid, LPCSTR lpcstrDes StringFromGUID2(lpGuid, mssuck, sizeof(mssuck)/sizeof(mssuck[0])); wcstombs(guidbuf, mssuck, sizeof(guidbuf)); - callback(SDRVNAME, guidbuf, lpcstrDescription); + callback(SDRVNAME, guidbuf, va("DS: %s", lpcstrDescription)); return TRUE; } diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index 82bda41a3..b235e6bde 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -437,10 +437,12 @@ extern snd_capture_driver_t OPENAL_Capture; #endif snd_capture_driver_t DSOUND_Capture; snd_capture_driver_t OSS_Capture; +snd_capture_driver_t SDL_Capture; snd_capture_driver_t *capturedrivers[] = { &DSOUND_Capture, + &SDL_Capture, &OSS_Capture, #ifdef AVAIL_OPENAL &OPENAL_Capture, @@ -1735,7 +1737,7 @@ static void QDECL S_Voip_EnumeratedCaptureDevice(const char *driver, const char fullintname = va("%s:%s", driver, devicecode); else fullintname = driver; - + Q_snprintfz(opts, sizeof(opts), "%s%s%s %s", snd_voip_capturedevice_opts.string, *snd_voip_capturedevice_opts.string?" ":"", COM_QuotedString(fullintname, nbuf, sizeof(nbuf), false), COM_QuotedString(readabledevice, dbuf, sizeof(dbuf), false)); Cvar_ForceSet(&snd_voip_capturedevice_opts, opts); } @@ -2072,6 +2074,7 @@ void S_ShutdownCard(soundcardinfo_t *sc) *link = sc->next; if (sc->Shutdown) sc->Shutdown(sc); + Z_Free(sc->channel); Z_Free(sc); break; } diff --git a/engine/client/snd_sdl.c b/engine/client/snd_sdl.c index c2edb5867..0438a8337 100644 --- a/engine/client/snd_sdl.c +++ b/engine/client/snd_sdl.c @@ -3,6 +3,13 @@ #ifdef DYNAMIC_SDL #define SDL_MAJOR_VERSION 2 +#define SDL_MINOR_VERSION 0 +#define SDL_PATCHLEVEL 5 +#define SDL_VERSIONNUM(X, Y, Z) ((X)*1000 + (Y)*100 + (Z)) +#define SDL_COMPILEDVERSION SDL_VERSIONNUM(SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL) +#define SDL_VERSION_ATLEAST(X, Y, Z) (SDL_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z)) + + //if we're not an sdl build, we probably want to link to sdl libraries dynamically or something. #include #define SDL_AudioDeviceID uint32_t @@ -18,9 +25,10 @@ #else #define AUDIO_S16SYS AUDIO_S16LSB #endif +#define SDLCALL QDECL typedef uint16_t SDL_AudioFormat; -typedef void VARGS (*SDL_AudioCallback)(void *userdata, uint8_t *stream, int len); +typedef void (SDLCALL *SDL_AudioCallback)(void *userdata, uint8_t *stream, int len); typedef struct SDL_AudioSpec { @@ -35,16 +43,20 @@ typedef struct SDL_AudioSpec void *userdata; } SDL_AudioSpec; -static int (*SDL_Init) (uint32_t flags); -static int (*SDL_InitSubSystem) (uint32_t flags); -static SDL_AudioDeviceID (*SDL_OpenAudioDevice) (const char *dev, int iscapture, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained, int allowed_changes); -static void (*SDL_PauseAudioDevice) (SDL_AudioDeviceID fd, int pausestate); -static void (*SDL_LockAudioDevice) (SDL_AudioDeviceID fd); -static void (*SDL_UnlockAudioDevice) (SDL_AudioDeviceID fd); -static int (*SDL_CloseAudioDevice) (SDL_AudioDeviceID fd); -static int (*SDL_GetNumAudioDevices) (int iscapture); -static const char *(*SDL_GetAudioDeviceName) (int index, int iscapture); -static const char *(*SDL_GetError) (void); +static int (SDLCALL *SDL_Init) (uint32_t flags); +static int (SDLCALL *SDL_InitSubSystem) (uint32_t flags); +static SDL_AudioDeviceID (SDLCALL *SDL_OpenAudioDevice) (const char *dev, int iscapture, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained, int allowed_changes); +static void (SDLCALL *SDL_PauseAudioDevice) (SDL_AudioDeviceID fd, int pausestate); +static void (SDLCALL *SDL_LockAudioDevice) (SDL_AudioDeviceID fd); +static void (SDLCALL *SDL_UnlockAudioDevice) (SDL_AudioDeviceID fd); +static int (SDLCALL *SDL_CloseAudioDevice) (SDL_AudioDeviceID fd); +static int (SDLCALL *SDL_GetNumAudioDevices) (int iscapture); +static const char *(SDLCALL *SDL_GetAudioDeviceName) (int index, int iscapture); +static const char *(SDLCALL *SDL_GetError) (void); +#if SDL_VERSION_ATLEAST(2,0,5) +static uint32_t (SDLCALL *SDL_GetQueuedAudioSize) (SDL_AudioDeviceID dev); +static uint32_t (SDLCALL *SDL_DequeueAudio) (SDL_AudioDeviceID dev, void *data, uint32_t len); +#endif #else #include #endif @@ -74,6 +86,10 @@ static qboolean SSDL_InitAudio(void) {(void*)&SDL_GetNumAudioDevices, "SDL_GetNumAudioDevices"}, {(void*)&SDL_GetAudioDeviceName, "SDL_GetAudioDeviceName"}, {(void*)&SDL_GetError, "SDL_GetError"}, +#if SDL_VERSION_ATLEAST(2,0,5) + {(void*)&SDL_GetQueuedAudioSize, "SDL_GetQueuedAudioSize"}, + {(void*)&SDL_DequeueAudio, "SDL_DequeueAudio"}, +#endif {NULL, NULL} }; static dllhandle_t *libsdl; @@ -82,6 +98,10 @@ static qboolean SSDL_InitAudio(void) libsdl = Sys_LoadLibrary("libSDL2-2.0.so.0", funcs); if (!libsdl) libsdl = Sys_LoadLibrary("libSDL2.so", funcs); //maybe they have a dev package installed that fixes this mess. +#ifdef _WIN32 + if (!libsdl) + libsdl = Sys_LoadLibrary("SDL2", funcs); +#endif if (libsdl) SDL_Init(SDL_INIT_NOPARACHUTE); else @@ -185,7 +205,7 @@ static void SSDL_Submit(soundcardinfo_t *sc, int start, int end) //SDL will call SSDL_Paint to paint when it's time, and the sound buffer is always there... } -static qboolean SDL_InitCard(soundcardinfo_t *sc, const char *devicename) +static qboolean QDECL SDL_InitCard(soundcardinfo_t *sc, const char *devicename) { SDL_AudioSpec desired, obtained; @@ -273,7 +293,7 @@ static qboolean QDECL SDL_Enumerate(void (QDECL *cb) (const char *drivername, co { const char *devname = SDL_GetAudioDeviceName(i, false); if (devname) - cb(SDRVNAME, devname, va("SDL (%s)", devname)); + cb(SDRVNAME, devname, va("SDL:%s", devname)); } } return true; @@ -289,4 +309,91 @@ sounddriver_t SDL_Output = SDL_Enumerate }; +#if SDL_VERSION_ATLEAST(2,0,5) && defined(VOICECHAT) +//Requires SDL 2.0.5+ supposedly. +//Bugging out for me on windows, with really low audio levels. looks like there's been some float->int conversion without a multiplier. asking for float audio gives stupidly low values too. +typedef struct +{ + SDL_AudioDeviceID dev; +} sdlcapture_t; +static void QDECL SDL_Capture_Start(void *ctx) +{ + sdlcapture_t *d = ctx; + SDL_PauseAudioDevice(d->dev, FALSE); +} + +static void QDECL SDL_Capture_Stop(void *ctx) +{ + sdlcapture_t *d = ctx; + SDL_PauseAudioDevice(d->dev, TRUE); +} + +static void QDECL SDL_Capture_Shutdown(void *ctx) +{ + sdlcapture_t *d = ctx; + SDL_CloseAudioDevice(d->dev); + Z_Free(d); +} + +static qboolean QDECL SDL_Capture_Enumerate(void (QDECL *callback) (const char *drivername, const char *devicecode, const char *readablename)) +{ + int i, count; + if (SSDL_InitAudio()) + { + count = SDL_GetNumAudioDevices(true); + for (i = 0; i < count; i++) + { + const char *name = SDL_GetAudioDeviceName(i, true); + if (name) + callback(SDRVNAME, name, va("SDL:%s", name)); + } + } + return true; +} +static void *QDECL SDL_Capture_Init (int rate, const char *devname) +{ + SDL_AudioSpec want, have; + sdlcapture_t c, *r; + + memset(&want, 0, sizeof(want)); + want.freq = rate; + want.format = AUDIO_S16SYS; + want.channels = 1; + want.samples = 256; //this seems to be chunk sizes rather than total buffer size, so lets keep it reasonably small for lower latencies + want.callback = NULL; + + c.dev = SDL_OpenAudioDevice(devname, true, &want, &have, 0); + if (!c.dev) //failed? + return NULL; + + r = Z_Malloc(sizeof(*r)); + *r = c; + return r; +} + +/*minbytes is a hint to not bother wasting time*/ +static unsigned int QDECL SDL_Capture_Update(void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes) +{ + sdlcapture_t *c = ctx; + unsigned int queuedsize = SDL_GetQueuedAudioSize(c->dev); + if (queuedsize < minbytes) + return 0; + if (queuedsize > maxbytes) + queuedsize = maxbytes; + + queuedsize = SDL_DequeueAudio(c->dev, buffer, queuedsize); + return queuedsize; +} +snd_capture_driver_t SDL_Capture = +{ + 1, + SDRVNAME, + SDL_Capture_Enumerate, + SDL_Capture_Init, + SDL_Capture_Start, + SDL_Capture_Update, + SDL_Capture_Stop, + SDL_Capture_Shutdown +}; +#endif diff --git a/engine/client/sys_win.c b/engine/client/sys_win.c index 9e96ef7a2..a2613f955 100644 --- a/engine/client/sys_win.c +++ b/engine/client/sys_win.c @@ -1031,10 +1031,12 @@ qboolean Sys_remove (char *path) if (WinNT) { wchar_t wide[MAX_OSPATH]; + DWORD err; widen(wide, sizeof(wide), path); if (DeleteFileW(wide)) return true; //success - if (GetLastError() == ERROR_FILE_NOT_FOUND) + err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) return true; //succeed when the file already didn't exist return false; //other errors? panic } @@ -2619,406 +2621,118 @@ void Win7_TaskListInit(void) } #endif -BOOL CopyFileU(const char *src, const char *dst, BOOL bFailIfExists) -{ - wchar_t wide1[2048]; - wchar_t wide2[2048]; - return CopyFileW(widen(wide1, sizeof(wide1), src), widen(wide2, sizeof(wide2), dst), bFailIfExists); -} - -//#define SVNREVISION 1 -#if defined(SVNREVISION) && !defined(MINIMAL) && !defined(NOLEGACY) - #define SVNREVISIONSTR STRINGIFY(SVNREVISION) - #if defined(OFFICIAL_RELEASE) - #define UPD_BUILDTYPE "rel" +#ifndef SVNREVISION + #if 0 //1 to debug engine update in msvc. + #define SVNREVISION 1 #else - #define UPD_BUILDTYPE "test" - //WARNING: Security comes from the fact that the triptohell.info certificate is hardcoded in the tls code. - //this will correctly detect insecure tls proxies also. - #define UPDATE_URL_ROOT "https://triptohell.info/moodles/" - #define UPDATE_URL_TESTED UPDATE_URL_ROOT "autoup/" - #define UPDATE_URL_NIGHTLY UPDATE_URL_ROOT - #define UPDATE_URL_VERSION "%sversion.txt" - #ifdef NOLEGACY - #ifdef _WIN64 - #define UPDATE_URL_BUILD "%snocompat64/fte" EXETYPE "64.exe" - #else - #define UPDATE_URL_BUILD "%snocompat/fte" EXETYPE ".exe" - #endif + #define SVNREVISION - + #endif +#endif +#define SVNREVISIONSTR STRINGIFY(SVNREVISION) + +#ifndef NOLEGACY + #if defined(SVNREVISION) && !defined(MINIMAL) + #if defined(OFFICIAL_RELEASE) + #define UPD_BUILDTYPE "rel" #else - #ifdef _WIN64 - #define UPDATE_URL_BUILD "%swin64/fte" EXETYPE "64.exe" + #define UPD_BUILDTYPE "test" + //WARNING: Security comes from the fact that the triptohell.info certificate is hardcoded in the tls code. + //this will correctly detect insecure tls proxies also. + #define UPDATE_URL_ROOT "https://triptohell.info/moodles/" + #define UPDATE_URL_TESTED UPDATE_URL_ROOT "autoup/" + #define UPDATE_URL_NIGHTLY UPDATE_URL_ROOT + #define UPDATE_URL_VERSION "%sversion.txt" + #ifdef NOLEGACY + #ifdef _WIN64 + #define UPDATE_URL_BUILD "%snocompat64/fte" EXETYPE "64.exe" + #else + #define UPDATE_URL_BUILD "%snocompat/fte" EXETYPE ".exe" + #endif #else - #define UPDATE_URL_BUILD "%swin32/fte" EXETYPE ".exe" + #ifdef _WIN64 + #define UPDATE_URL_BUILD "%swin64/fte" EXETYPE "64.exe" + #else + #define UPDATE_URL_BUILD "%swin32/fte" EXETYPE ".exe" + #endif #endif #endif + + #if defined(SERVERONLY) + #define EXETYPE "qwsv" //not gonna happen, but whatever. + #elif defined(GLQUAKE) && defined(D3DQUAKE) + #define EXETYPE "qw" + #elif defined(GLQUAKE) + #ifdef MINIMAL + #define EXETYPE "minglqw" + #else + #define EXETYPE "glqw" + #endif + #elif defined(D3DQUAKE) + #define EXETYPE "d3dqw" + #elif defined(SWQUAKE) + #define EXETYPE "swqw" + #else + //erm... + #define EXETYPE "qw" + #endif #endif #endif -#if defined(SERVERONLY) - #define EXETYPE "qwsv" //not gonna happen, but whatever. -#elif defined(GLQUAKE) && defined(D3DQUAKE) - #define EXETYPE "qw" -#elif defined(GLQUAKE) - #ifdef MINIMAL - #define EXETYPE "minglqw" - #else - #define EXETYPE "glqw" - #endif -#elif defined(D3DQUAKE) - #define EXETYPE "d3dqw" -#elif defined(SWQUAKE) - #define EXETYPE "swqw" -#else - //erm... - #define EXETYPE "qw" -#endif - -#ifdef UPDATE_URL_ROOT - -int sys_autoupdatesetting; - -qboolean Update_GetHomeDirectory(char *homedir, int homedirsize) -{ - HMODULE shfolder = LoadLibraryA("shfolder.dll"); - - if (shfolder) - { - HRESULT (WINAPI *dSHGetFolderPathW) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath); - dSHGetFolderPathW = (void *)GetProcAddress(shfolder, "SHGetFolderPathW"); - if (dSHGetFolderPathW) - { - wchar_t folderw[MAX_PATH]; - // 0x5 == CSIDL_PERSONAL - if (dSHGetFolderPathW(NULL, 0x5, NULL, 0, folderw) == S_OK) - { - narrowen(homedir, homedirsize, folderw); - Q_strncatz(homedir, "/My Games/"FULLENGINENAME"/", homedirsize); - return true; - } - } -// FreeLibrary(shfolder); - } - return false; -} - -static void Update_CreatePath (char *path) -{ - char *ofs; - - for (ofs = path+1 ; *ofs ; ofs++) - { - if (*ofs == '/') - { // create the directory - *ofs = 0; - Sys_mkdir (path); - *ofs = '/'; - } - } -} - -//ctx is a pointer to the original frontend process -void Update_PromptedDownloaded(void *ctx, int foo) -{ - if (foo == 0 && ctx) - { - PROCESS_INFORMATION childinfo; - STARTUPINFOW startinfo = {sizeof(startinfo)}; - wchar_t widearg[2048]; - wchar_t wideexe[2048]; - char cmdline[2048]; - -#ifndef SERVERONLY - SetHookState(false); - Host_Shutdown (); - CloseHandle (qwclsemaphore); - SetHookState(false); -#else - SV_Shutdown(); -#endif - TL_Shutdown(); - - narrowen(cmdline, sizeof(cmdline), GetCommandLineW()); - widen(wideexe, sizeof(wideexe), ctx); - widen(widearg, sizeof(widearg), va("\"%s\" %s", (char*)ctx, COM_Parse(cmdline))); - - CreateProcessW(wideexe, widearg, NULL, NULL, TRUE, 0, NULL, NULL, &startinfo, &childinfo); - Z_Free(ctx); - exit(1); - } - else - Z_Free(ctx); -} - -void Update_Version_Updated(struct dl_download *dl) -{ - //happens in a thread, avoid va - if (dl->file) - { - if (dl->status == DL_FINISHED) - { - char buf[8192]; - unsigned int size = 0, chunk; - char pendingname[MAX_OSPATH]; - vfsfile_t *pending; - Update_GetHomeDirectory(pendingname, sizeof(pendingname)); - Q_strncatz(pendingname, DISTRIBUTION UPD_BUILDTYPE EXETYPE".tmp", sizeof(pendingname)); - Update_CreatePath(pendingname); - pending = VFSOS_Open(pendingname, "wb"); - if (!pending) - Con_Printf("Unable to write to \"%s\"\n", pendingname); - else - { - while(1) - { - chunk = VFS_READ(dl->file, buf, sizeof(buf)); - if (!chunk) - break; - size += VFS_WRITE(pending, buf, chunk); - } - VFS_CLOSE(pending); - if (VFS_GETLEN(dl->file) != size) - Con_Printf("Download was the wrong size / corrupt\n"); - else - { - //figure out the original binary that was executed, so we can start from scratch. - //this is to attempt to avoid the new process appearing as 'foo.tmp'. which needlessly confuses firewall rules etc. - int ffe = COM_CheckParm("--fromfrontend"); - wchar_t wbinarypath[MAX_PATH]; - char ubinarypath[MAX_PATH]; - char *ffp; - GetModuleFileNameW(NULL, wbinarypath, countof(wbinarypath)-1); - narrowen(ubinarypath, sizeof(ubinarypath), wbinarypath); - ffp = Z_StrDup(ffe?com_argv[ffe+2]:ubinarypath); - - //make it pending - MyRegSetValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "pending" UPD_BUILDTYPE EXETYPE, REG_SZ, pendingname, strlen(pendingname)+1); - - Key_Dest_Remove(kdm_console); - M_Menu_Prompt(Update_PromptedDownloaded, ffp, "An update was downloaded", "Restart to activate.", "", ffp?"Restart":NULL, "", "Okay"); - } - } - } - else - Con_Printf("Update download failed\n"); - } -} -void Update_PromptedForUpdate(void *ctx, int foo) -{ - if (foo == 0) - { - struct dl_download *dl; - Con_Printf("Downloading update\n"); - dl = HTTP_CL_Get(va(UPDATE_URL_BUILD, (char*)ctx), NULL, Update_Version_Updated); - dl->file = FS_OpenTemp(); -#ifdef MULTITHREAD - DL_CreateThread(dl, NULL, NULL); -#endif - } - else - Con_Printf("Not downloading update\n"); -} -void Update_Versioninfo_Available(struct dl_download *dl) -{ - if (dl->file) - { - if (dl->status == DL_FINISHED) - { - char linebuf[1024]; - while(VFS_GETS(dl->file, linebuf, sizeof(linebuf))) - { - if (!strnicmp(linebuf, "Revision: ", 10)) - { - if (atoi(linebuf+10) > atoi(SVNREVISIONSTR)) - { - char *revision = va("Revision %i", atoi(linebuf+10)); - char *current = va("Current %i", atoi(SVNREVISIONSTR)); - - Con_Printf("An update is available, revision %i\n", atoi(linebuf+10)); - if (COM_CheckParm("-autoupdate") || COM_CheckParm("--autoupdate")) - Update_PromptedForUpdate(dl->user_ctx, 0); - else - { - Key_Dest_Remove(kdm_console); - M_Menu_Prompt(Update_PromptedForUpdate, dl->user_ctx, "An update is available.", revision, current, "Download", "", "Ignore"); - } - } - else - Con_Printf("autoupdate: already at latest version\n"); - return; - } - } - } - } -} - -void Update_Check(void) -{ - static qboolean doneupdatecheck; //once per run - struct dl_download *dl; - - if (sys_autoupdatesetting <= UPD_OFF) //not if disabled (do it once it does get enabled) - return; - - if (!doneupdatecheck) - { - char *updateroot = (sys_autoupdatesetting>=UPD_TESTING)?UPDATE_URL_NIGHTLY:UPDATE_URL_TESTED; - doneupdatecheck = true; - dl = HTTP_CL_Get(va(UPDATE_URL_VERSION, updateroot), NULL, Update_Versioninfo_Available); - dl->file = FS_OpenTemp(); - dl->user_ctx = updateroot; - dl->isquery = true; -#ifdef MULTITHREAD - DL_CreateThread(dl, NULL, NULL); -#endif - } -} - -int Sys_GetAutoUpdateSetting(void) -{ - return sys_autoupdatesetting; -} -void Sys_SetAutoUpdateSetting(int newval) -{ - if (sys_autoupdatesetting == newval) - return; - sys_autoupdatesetting = newval; - MyRegSetValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "AutoUpdateEnabled", REG_DWORD, &sys_autoupdatesetting, sizeof(sys_autoupdatesetting)); - - Update_Check(); -} - -BOOL DeleteFileU(const char *path) -{ - wchar_t wide[2048]; - return DeleteFileW(widen(wide, sizeof(wide), path)); -} -BOOL MoveFileU(const char *src, const char *dst) -{ - wchar_t wide1[2048]; - wchar_t wide2[2048]; - return MoveFileExW(widen(wide1, sizeof(wide1), src), widen(wide2, sizeof(wide2), dst), MOVEFILE_COPY_ALLOWED); -} - +#ifdef HAVEAUTOUPDATE +//this is for legacy reasons. old builds stored a 'pending' name in the registry for the 'frontend' to rename+use void Sys_SetUpdatedBinary(const char *binary) { +#ifdef UPD_BUILDTYPE //downloads menu has provided a new binary to use MyRegSetValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "pending" UPD_BUILDTYPE EXETYPE, REG_SZ, binary, strlen(binary)+1); +#endif } +qboolean Sys_EngineCanUpdate(void) +{ + char *e; -qboolean Sys_CheckUpdated(char *bindir, size_t bindirsize) + //no revision info in this build, meaning its custom built and thus cannot check against the available updated versions. + if (!strcmp(SVNREVISIONSTR, "-")) + return false; + + //svn revision didn't parse as an exact number. this implies it has an 'M' in it to mark it as modified. + //either way, its bad and autoupdates when we don't know what we're updating from is a bad idea. + strtoul(SVNREVISIONSTR, &e, 10); + if (!*SVNREVISIONSTR || *e) + return false; + + //update blocked via commandline + if (COM_CheckParm("-noupdate") || COM_CheckParm("--noupdate") || COM_CheckParm("-noautoupdate") || COM_CheckParm("--noautoupdate")) + return false; + + return true; +} +qboolean Sys_EngineWasUpdated(char *newbinary) { wchar_t wide1[2048]; - int ffe = COM_CheckParm("--fromfrontend"); + wchar_t widefe[MAX_OSPATH]; + wchar_t wargs[8192]; PROCESS_INFORMATION childinfo; STARTUPINFOW startinfo = {sizeof(startinfo)}; - char *e; - strtoul(SVNREVISIONSTR, &e, 10); - if (!*SVNREVISIONSTR || *e) //svn revision didn't parse as an exact number. this implies it has an 'M' in it to mark it as modified, or a - to mean unknown. either way, its bad and autoupdates when we don't know what we're updating from is a bad idea. - sys_autoupdatesetting = UPD_UNSUPPORTED; - else if (COM_CheckParm("-noupdate") || COM_CheckParm("--noupdate") || COM_CheckParm("-noautoupdate") || COM_CheckParm("--noautoupdate")) - sys_autoupdatesetting = UPD_REVERT; - else if (COM_CheckParm("-autoupdate") || COM_CheckParm("--autoupdate")) - sys_autoupdatesetting = UPD_TESTING; - else - { - //favour 'tested' - sys_autoupdatesetting = MyRegGetIntValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "AutoUpdateEnabled", 2); - } - - if (!strcmp(SVNREVISIONSTR, "-")) - return false; //no revision info in this build, meaning its custom built and thus cannot check against the available updated versions. - else if (sys_autoupdatesetting == UPD_REVERT || sys_autoupdatesetting == UPD_UNSUPPORTED) + //if we were called from a frontend, then don't chain to another, because that would be recursive, and that would be bad. + if (COM_CheckParm("--fromfrontend")) + return false; + //if we're not allowed for some other reason + if (!Sys_EngineCanUpdate()) return false; - else if (isPlugin == 1) - { - //download, but don't invoke. the caller is expected to start us up properly (once installed). - } - else if (!ffe) - { - //if we're not from the frontend (ie: we ARE the frontend), we should run the updated build instead - char pendingpath[MAX_OSPATH]; - char updatedpath[MAX_OSPATH]; - //FIXME: store versions instead of names - MyRegGetStringValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "pending" UPD_BUILDTYPE EXETYPE, pendingpath, sizeof(pendingpath)); - if (*pendingpath) - { - qboolean okay; - MyRegDeleteKeyValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "pending" UPD_BUILDTYPE EXETYPE); - Update_GetHomeDirectory(updatedpath, sizeof(updatedpath)); - Update_CreatePath(updatedpath); - Q_strncatz(updatedpath, "cur" UPD_BUILDTYPE EXETYPE".exe", sizeof(updatedpath)); - DeleteFileU(updatedpath); - okay = MoveFileU(pendingpath, updatedpath); - if (!okay) - { //if we just downloaded an update, we may need to wait for the existing process to close. - //sadly I'm too lazy to provide any sync mechanism (and wouldn't trust any auto-released handles or whatever), so lets just retry after a delay. - Sleep(2000); - okay = MoveFileU(pendingpath, updatedpath); - } - if (okay) - MyRegSetValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, UPD_BUILDTYPE EXETYPE, REG_SZ, updatedpath, strlen(updatedpath)+1); - else - { - MessageBox(NULL, va("Unable to rename %s to %s", pendingpath, updatedpath), FULLENGINENAME" autoupdate", 0); - DeleteFileU(pendingpath); - } - } + startinfo.dwFlags = STARTF_USESTDHANDLES; + startinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startinfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startinfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); - MyRegGetStringValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, UPD_BUILDTYPE EXETYPE, updatedpath, sizeof(updatedpath)); - - if (*updatedpath) - { - wchar_t widefe[MAX_OSPATH], wargs[2048]; - GetModuleFileNameW(NULL, widefe, countof(widefe)-1); - _snwprintf(wargs, countof(wargs), L"%s --fromfrontend \"%s\" \"%s\"", GetCommandLineW(), widen(wide1, sizeof(wide1), SVNREVISIONSTR), widefe); - if (CreateProcessW(widen(wide1, sizeof(wide1), updatedpath), wargs, NULL, NULL, TRUE, 0, NULL, NULL, &startinfo, &childinfo)) - return true; - } - } - else - { -// char frontendpath[MAX_OSPATH]; - //com_argv[ffe+1] is frontend revision - //com_argv[ffe+2] is frontend location - if (atoi(com_argv[ffe+1]) > atoi(SVNREVISIONSTR)) - { - //ping-pong it back, to make sure we're running the most recent version. -// GetModuleFileName(NULL, frontendpath, sizeof(frontendpath)-1); - if (CreateProcessW(widen(wide1, sizeof(wide1), com_argv[ffe+2]), GetCommandLineW(), NULL, NULL, TRUE, 0, NULL, NULL, &startinfo, &childinfo)) - return true; - } - if (com_argv[ffe+2]) - { - com_argv[0] = com_argv[ffe+2]; - Q_strncpyz(bindir, com_argv[0], bindirsize); - *COM_SkipPath(bindir) = 0; - } + GetModuleFileNameW(NULL, widefe, countof(widefe)-1); + _snwprintf(wargs, countof(wargs), L"%s --fromfrontend \"%s\" \"%s\"", GetCommandLineW(), widen(wide1, sizeof(wide1), SVNREVISIONSTR), widefe); + if (CreateProcessW(widen(wide1, sizeof(wide1), newbinary), wargs, NULL, NULL, TRUE, 0, NULL, NULL, &startinfo, &childinfo)) + return true; //it started up, we need to die now. - } - return false; -} -#else -#ifdef HAVEAUTOUPDATE -int Sys_GetAutoUpdateSetting(void) -{ - return -1; -} -void Sys_SetAutoUpdateSetting(int newval) -{ -} -void Sys_SetUpdatedBinary(const char *binary) -{ -} -#endif -qboolean Sys_CheckUpdated(char *bindir, size_t bindirsize) -{ - return false; -} -void Update_Check(void) -{ + return false; //failure! } #endif @@ -3475,6 +3189,13 @@ LRESULT CALLBACK NoCloseWindowProc(HWND w, UINT m, WPARAM wp, LPARAM lp) return DefWindowProc(w, m, wp, lp); } +BOOL CopyFileU(const char *src, const char *dst, BOOL bFailIfExists) +{ + wchar_t wide1[2048]; + wchar_t wide2[2048]; + return CopyFileW(widen(wide1, sizeof(wide1), src), widen(wide2, sizeof(wide2), dst), bFailIfExists); +} + void FS_CreateBasedir(const char *path); qboolean Sys_DoInstall(void) { @@ -4048,9 +3769,6 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin isPlugin = 0; } - if (Sys_CheckUpdated(bindir, sizeof(bindir))) - return true; - if (COM_CheckParm("-register_types")) { Sys_DoFileAssociations(1); @@ -4205,6 +3923,10 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin if (!Sys_Startup_CheckMem(&parms)) Sys_Error ("Not enough memory free; check disk space\n"); +// FS_ChangeGame(NULL, true, true); +// if (Sys_CheckUpdated(bindir, sizeof(bindir))) +// return true; + #ifndef CLIENTONLY if (isDedicated) //compleate denial to switch to anything else - many of the client structures are not initialized. @@ -4264,8 +3986,6 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin fflush(stdout); } - Update_Check(); - /* main window message loop */ while (1) { diff --git a/engine/client/valid.c b/engine/client/valid.c index 3e69abf17..ab5d6d379 100644 --- a/engine/client/valid.c +++ b/engine/client/valid.c @@ -613,49 +613,58 @@ void Validation_Auto_Response(int playernum, char *s) static float cmdlineresponsetime; static float scriptsresponsetime; - if (!strncmp(s, "f_version", 9) && versionresponsetime < Sys_DoubleTime()) //respond to it. + //quakeworld tends to use f_* + //netquake uses the slightly more guessable q_* form + if (!strncmp(s, "f_", 2)) + s+=2; + else if (!strncmp(s, "q_", 2)) + s+=2; + else + return; + + if (!strncmp(s, "version", 7) && versionresponsetime < Sys_DoubleTime()) //respond to it. { Validation_Version(); versionresponsetime = Sys_DoubleTime() + 5; } else if (cl.spectator) return; - else if (!strncmp(s, "f_server", 8) && serverresponsetime < Sys_DoubleTime()) //respond to it. + else if (!strncmp(s, "server", 6) && serverresponsetime < Sys_DoubleTime()) //respond to it. { Validation_Server(); serverresponsetime = Sys_DoubleTime() + 5; } - else if (!strncmp(s, "f_system", 8) && systemresponsetime < Sys_DoubleTime()) + else if (!strncmp(s, "system", 6) && systemresponsetime < Sys_DoubleTime()) { Validation_System(); systemresponsetime = Sys_DoubleTime() + 5; } - else if (!strncmp(s, "f_cmdline", 9) && cmdlineresponsetime < Sys_DoubleTime()) + else if (!strncmp(s, "cmdline", 7) && cmdlineresponsetime < Sys_DoubleTime()) { Validation_CmdLine(); cmdlineresponsetime = Sys_DoubleTime() + 5; } - else if (!strncmp(s, "f_fakeshaft", 11) && fakeshaftresponsetime < Sys_DoubleTime()) + else if (!strncmp(s, "fakeshaft", 9) && fakeshaftresponsetime < Sys_DoubleTime()) { Validation_FakeShaft(); fakeshaftresponsetime = Sys_DoubleTime() + 5; } - else if (!strncmp(s, "f_modified", 10) && modifiedresponsetime < Sys_DoubleTime()) //respond to it. + else if (!strncmp(s, "modified", 8) && modifiedresponsetime < Sys_DoubleTime()) //respond to it. { Validation_FilesModified(); modifiedresponsetime = Sys_DoubleTime() + 5; } - else if (!strncmp(s, "f_scripts", 9) && scriptsresponsetime < Sys_DoubleTime()) + else if (!strncmp(s, "scripts", 7) && scriptsresponsetime < Sys_DoubleTime()) { Validation_Scripts(); scriptsresponsetime = Sys_DoubleTime() + 5; } - else if (!strncmp(s, "f_skins", 7) && skinsresponsetime < Sys_DoubleTime()) //respond to it. + else if (!strncmp(s, "skins", 5) && skinsresponsetime < Sys_DoubleTime()) //respond to it. { Validation_Skins(); skinsresponsetime = Sys_DoubleTime() + 5; } - else if (!strncmp(s, "f_ruleset", 9) && rulesetresponsetime < Sys_DoubleTime()) + else if (!strncmp(s, "ruleset", 7) && rulesetresponsetime < Sys_DoubleTime()) { if (1) Validation_AllChecks(); diff --git a/engine/client/zqtp.c b/engine/client/zqtp.c index 12284c997..5ab01f418 100644 --- a/engine/client/zqtp.c +++ b/engine/client/zqtp.c @@ -1793,8 +1793,8 @@ void TP_SearchForMsgTriggers (char *s, int level) && t->string[0] && strstr(s, t->string)) { if (level == PRINT_CHAT && ( - strstr (s, "f_version") || strstr (s, "f_system") || - strstr (s, "f_speed") || strstr (s, "f_modified") || strstr (s, "f_ruleset"))) + strstr (s, "_version") || strstr (s, "_system") || + strstr (s, "_speed") || strstr (s, "_modified") || strstr (s, "_ruleset"))) continue; // don't let llamas fake proxy replies string = Cmd_AliasExist (t->name, RESTRICT_LOCAL); diff --git a/engine/common/common.c b/engine/common/common.c index ba29d8dac..fb42c869f 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -108,13 +108,14 @@ cvar_t gameversion = CVARFD("gameversion","", CVAR_SERVERINFO, "gamecode version cvar_t gameversion_min = CVARD("gameversion_min","", "gamecode version for server browsers"); cvar_t gameversion_max = CVARD("gameversion_max","", "gamecode version for server browsers"); cvar_t fs_gamename = CVARAFD("com_fullgamename", NULL, "fs_gamename", CVAR_NOSET, "The filesystem is trying to run this game"); -cvar_t fs_downloads_url = CVARFD("fs_downloads_url", NULL, CVAR_NOTFROMSERVER|CVAR_NOSAVE|CVAR_NOSET, "The URL of a package updates list."); cvar_t com_protocolname = CVARAD("com_protocolname", NULL, "com_gamename", "The protocol game name used for dpmaster queries. For compatibility with DP, you can set this to 'DarkPlaces-Quake' in order to be listed in DP's master server, and to list DP servers."); cvar_t com_parseutf8 = CVARD("com_parseutf8", "1", "Interpret console messages/playernames/etc as UTF-8. Requires special fonts. -1=iso 8859-1. 0=quakeascii(chat uses high chars). 1=utf8, revert to ascii on decode errors. 2=utf8 ignoring errors"); //1 parse. 2 parse, but stop parsing that string if a char was malformed. cvar_t com_parseezquake = CVARD("com_parseezquake", "0", "Treat chevron chars from configs as a per-character flag. You should use this only for compat with nquake's configs."); cvar_t com_highlightcolor = CVARD("com_highlightcolor", STRINGIFY(COLOR_RED), "ANSI colour to be used for highlighted text, used when com_parseutf8 is active."); cvar_t com_nogamedirnativecode = CVARFD("com_nogamedirnativecode", "1", CVAR_NOTFROMSERVER, FULLENGINENAME" blocks all downloads of files with a .dll or .so extension, however other engines (eg: ezquake and fodquake) do not - this omission can be used to trigger delayed eremote exploits in any engine (including "DISTRIBUTION") which is later run from the same gamedir.\nQuake2, Quake3(when debugging), and KTX typically run native gamecode from within gamedirs, so if you wish to run any of these games you will need to ensure this cvar is changed to 0, as well as ensure that you don't run unsafe clients.\n"); cvar_t sys_platform = CVAR("sys_platform", PLATFORM); +cvar_t pm_downloads_url = CVARFD("pm_downloads_url", NULL, CVAR_NOTFROMSERVER|CVAR_NOSAVE|CVAR_NOSET, "The URL of a package updates list."); //read from the default.fmf +cvar_t pm_autoupdate = CVARFD("pm_autoupdate", "1", CVAR_NOTFROMSERVER|CVAR_NOSAVE|CVAR_NOSET, "0: off.\n1: enabled (stable only).\n2: enabled (unstable).\nNote that autoupdate will still prompt the user to actually apply the changes."); //read from the package list only. qboolean com_modified; // set true if using non-id files @@ -5553,6 +5554,14 @@ void COM_Init (void) nullentitystate.solidsize = 0;//ES_SOLID_BSP; } +void COM_Shutdown (void) +{ +#ifdef LOADERTHREAD + COM_DestroyWorkerThread(); +#endif + COM_BiDi_Shutdown(); + FS_Shutdown(); +} /* ============ @@ -5596,7 +5605,7 @@ int memsearch (qbyte *start, int count, int search) } #ifdef NQPROT -//for compat with dpp7 protocols +//for compat with dpp7 protocols, or dp gamecode that neglects to properly precache particles. void COM_Effectinfo_Enumerate(int (*cb)(const char *pname)) { int i; diff --git a/engine/common/common.h b/engine/common/common.h index 4ec0a5b2d..54dd17cea 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -374,6 +374,7 @@ int COM_CheckParm (const char *parm); //WARNING: Legacy arguments should be list int COM_CheckNextParm (const char *parm, int last); void COM_AddParm (const char *parm); +void COM_Shutdown (void); void COM_Init (void); void COM_InitArgv (int argc, const char **argv); void COM_ParsePlusSets (qboolean docbuf); @@ -730,6 +731,7 @@ void Log_String (logtype_t lognum, char *s); void Con_Log (char *s); void Log_Logfile_f (void); void Log_Init(void); +void IPLog_Add(const char *ip, const char *name); //for associating player ip addresses with names. /*used by and for botlib and q3 gamecode*/ diff --git a/engine/common/fs.c b/engine/common/fs.c index f4a66c260..493671972 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -2955,7 +2955,6 @@ typedef struct { const char *manifestfile; } gamemode_info_t; const gamemode_info_t gamemode_info[] = { -#define MASTER_PREFIX "FTE-" //note that there is no basic 'fte' gamemode, this is because we aim for network compatability. Darkplaces-Quake is the closest we get. //this is to avoid having too many gamemodes anyway. @@ -2964,13 +2963,13 @@ const gamemode_info_t gamemode_info[] = { //for quake, we also allow extracting all files from paks. some people think it loads faster that way or something. //cmdline switch exename protocol name(dpmaster) identifying file exec dir1 dir2 dir3 dir(fte) full name - {"-quake", "q1", MASTER_PREFIX"Quake", {"id1/pak0.pak", "id1/quake.rc"},QCFG, {"id1", "qw", "*fte"}, "Quake", "https://fte.triptohell.info/downloadables.php" /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, + {"-quake", "q1", "FTE-Quake DarkPlaces-Quake", {"id1/pak0.pak", "id1/quake.rc"},QCFG, {"id1", "qw", "*fte"}, "Quake", "https://fte.triptohell.info/downloadables.php" /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, //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. //and q2 also has a rogue/pak0.pak file that we don't want to find and cause quake2 to look like dissolution of eternity //so just make these require the same files as good ol' quake. - {"-hipnotic", "hipnotic", MASTER_PREFIX"Hipnotic",{"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "hipnotic", "*fte"}, "Quake: Scourge of Armagon"}, - {"-rogue", "rogue", MASTER_PREFIX"Rogue", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "rogue", "*fte"}, "Quake: Dissolution of Eternity"}, + {"-hipnotic", "hipnotic", "FTE-Hipnotic",{"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "hipnotic", "*fte"}, "Quake: Scourge of Armagon"}, + {"-rogue", "rogue", "FTE-Rogue", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "rogue", "*fte"}, "Quake: Dissolution of Eternity"}, //various quake-based standalone mods. {"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"}, "Nexuiz"}, @@ -3968,7 +3967,7 @@ void FS_Shutdown(void) fs_thread_mutex = NULL; Cvar_SetEngineDefault(&fs_gamename, NULL); - Cvar_SetEngineDefault(&fs_downloads_url, NULL); + Cvar_SetEngineDefault(&pm_downloads_url, NULL); Cvar_SetEngineDefault(&com_protocolname, NULL); } @@ -5118,11 +5117,11 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean if (reloadconfigs) { Cvar_SetEngineDefault(&fs_gamename, man->formalname?man->formalname:"FTE"); - Cvar_SetEngineDefault(&fs_downloads_url, man->downloadsurl?man->downloadsurl:""); + Cvar_SetEngineDefault(&pm_downloads_url, man->downloadsurl?man->downloadsurl:""); Cvar_SetEngineDefault(&com_protocolname, man->protocolname?man->protocolname:"FTE"); //FIXME: flag this instead and do it after a delay? Cvar_ForceSet(&fs_gamename, fs_gamename.enginevalue); - Cvar_ForceSet(&fs_downloads_url, fs_downloads_url.enginevalue); + Cvar_ForceSet(&pm_downloads_url, pm_downloads_url.enginevalue); Cvar_ForceSet(&com_protocolname, com_protocolname.enginevalue); vidrestart = false; @@ -5567,7 +5566,8 @@ void COM_InitFilesystem (void) Cvar_Register(&cfg_reload_on_gamedir, "Filesystem"); Cvar_Register(&com_fs_cache, "Filesystem"); Cvar_Register(&fs_gamename, "Filesystem"); - Cvar_Register(&fs_downloads_url, "Filesystem"); + Cvar_Register(&pm_downloads_url, "Filesystem"); + Cvar_Register(&pm_autoupdate, "Filesystem"); Cvar_Register(&com_protocolname, "Server Info"); Cvar_Register(&fs_game, "Filesystem"); #ifdef Q2SERVER @@ -5697,10 +5697,6 @@ void COM_InitFilesystem (void) fs_readonly = COM_CheckParm("-readonly"); fs_thread_mutex = Sys_CreateMutex(); - -#ifdef PLUGINS - Plug_Initialise(false); -#endif } diff --git a/engine/common/fs.h b/engine/common/fs.h index 8430203a6..b6189ed60 100644 --- a/engine/common/fs.h +++ b/engine/common/fs.h @@ -70,6 +70,7 @@ void FS_AddHashedPackage(searchpath_t **oldpaths, const char *parent_pure, const void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const char *parent_logical, searchpath_t *search, unsigned int loadstuff, int minpri, int maxpri); int PM_IsApplying(void); void PM_ManifestPackage(const char *name, qboolean doinstall); +qboolean PM_FindUpdatedEngine(char *syspath, size_t syspathsize); //names the engine we should be running void Menu_Download_Update(void); int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), void *usr); diff --git a/engine/common/log.c b/engine/common/log.c index b5440e72a..659f013da 100644 --- a/engine/common/log.c +++ b/engine/common/log.c @@ -325,6 +325,89 @@ void SV_Fraglogfile_f (void) } */ +static qboolean IPLog_Merge_File(const char *fname) +{ + char ip[MAX_ADR_SIZE]; + char name[256]; + char line[1024]; + vfsfile_t *f; + if (!*fname) + fname = "iplog.txt"; + f = FS_OpenVFS(fname, "rb", FS_GAME); + if (!f) + return false; + if (!Q_strcasecmp(COM_FileExtension(fname, name, sizeof(name)), ".dat")) + { //we don't write this format because of it being limited to ipv4, as well as player name lengths + while (VFS_READ(f, line, 20) == 20) + { + Q_snprintfz(ip, sizeof(ip), "%i.%i.%i.%i", (qbyte)line[0], (qbyte)line[1], (qbyte)line[2], (qbyte)line[3]); + memcpy(name, line+4, 20-4); + name[20-4] = 0; + IPLog_Add(ip, name); + } + } + else + { + while (VFS_GETS(f, line, sizeof(line)-1)) + { + //whether the name contains quotes or what is an awkward one. + //we always write quotes (including string markup to avoid issues) + //dp doesn't, and our parser is lazy, so its possible we'll get gibberish that way + if (COM_ParseOut(COM_ParseOut(line, ip, sizeof(ip)), name, sizeof(name))) + IPLog_Add(ip, name); + } + } + VFS_CLOSE(f); + return true; +} +void IPLog_Add(const char *ipstr, const char *name) +{ + if (*ipstr != '[' && *ipstr < '0' && *ipstr > '9') + return; + //might be x.y.z.w:port + //might be x.y.z.FUCKED + //might be x.y.z.0/24 + //might be [::]:port + //might be [::]/bits + //or other ways to express an ip address + //note that ipv4 addresses should be converted to ipv6 ::ffff:x.y.z.w format for internal use or something, then this code only needs to deal with a single 128bit address format. + //ipv6 addresses generally only need 64bits to identify a user's home router, the other 64bits are generally 'just' to avoid nats. + //ignore ipx addresses, I doubt anyone will ever actually use it, and even if they do its just lans. +} +static void IPLog_Identify_f(void) +{ +// const char *nameorip = Cmd_Argv(1); + Con_Printf("Not yet implemented\n"); + //if *, use a mask that includes all ips + //try to parse as an ip + //if server is active, walk players to see if there's a name match to get their address and guess an address mask + //else if client is active, walk players to see if there's a name match, to get their address+mask if known via nq hacks + //look for matches +} +static void IPLog_Dump_f(void) +{ + const char *fname = Cmd_Argv(1); + if (!*fname) + fname = "iplog.txt"; +#if 1 + Con_Printf("Not yet implemented\n"); +#else + vfsfile_t *f = FS_OpenVFS(fname, "wb", FS_GAMEONLY); + VFS_PRINTF(f, "//generated by "FULLENGINENAME"\n", foo->ip, foo->name); + for (foo = first; foo; foo = foo->next) + { + char buf[1024]; + VFS_PRINTF(f, "%s %s\n", foo->ip, COM_QuotedString(foo->name, buf, sizeof(buf), false)); + } + VFS_CLOSE(f); +#endif +} +static void IPLog_Merge_f(void) +{ + const char *fname = Cmd_Argv(1); + if (!IPLog_Merge_File(fname)) + Con_Printf("unable to read %s\n", fname); +} void Log_Init(void) { @@ -346,6 +429,10 @@ void Log_Init(void) Cmd_AddCommand("logfile", Log_Logfile_f); + Cmd_AddCommand("identify", IPLog_Identify_f); + Cmd_AddCommand("ipmerge", IPLog_Merge_f); + Cmd_AddCommand("ipdump", IPLog_Dump_f); + // cmd line options, debug options #ifdef CRAZYDEBUGGING Cvar_ForceSet(&log_enable[LOG_CONSOLE], "1"); diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 1567bf854..4c9e7ae0c 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -2510,24 +2510,39 @@ qboolean FTENET_Generic_GetPacket(ftenet_generic_connection_t *con) return false; if (err == NET_EMSGSIZE) { - SockadrToNetadr (&from, &net_from); - Con_TPrintf ("Warning: Oversize packet from %s\n", + static unsigned int resettime; + unsigned int curtime = Sys_Milliseconds(); + if (curtime-resettime >= 5000) //throttle prints to once per 5 secs (even if they're about different clients, yay ddos) + { + SockadrToNetadr (&from, &net_from); + Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); + } return false; } if (err == NET_ECONNABORTED || err == NET_ECONNRESET) { - Con_TPrintf ("Connection lost or aborted\n"); //server died/connection lost. -#ifndef SERVERONLY - if (cls.state != ca_disconnected && !con->islisten) + static unsigned int resettime; + unsigned int curtime = Sys_Milliseconds(); + if (curtime-resettime >= 5000 || err == NET_ECONNRESET) //throttle prints to once per 5 secs (even if they're about different clients, yay ddos) { - if (cls.lastarbiatarypackettime+5 < Sys_DoubleTime()) //too many mvdsv - Cbuf_AddText("disconnect\nreconnect\n", RESTRICT_LOCAL); //retry connecting. + if (err == NET_ECONNABORTED) + Con_TPrintf ("Connection lost or aborted (%s)\n", NET_AdrToString (adr, sizeof(adr), &net_from)); //server died/connection lost. else - Con_Printf("Packet was not delivered - server might be badly configured\n"); - return false; - } + Con_TPrintf ("Connection lost or aborted\n"); //server died/connection lost. + resettime = curtime; +#ifndef SERVERONLY + //fixme: synthesise a reset packet for the caller to handle? "\xff\xff\xff\xffreset" ? + if (cls.state != ca_disconnected && !con->islisten) + { + if (cls.lastarbiatarypackettime+5 < Sys_DoubleTime()) //too many mvdsv + Cbuf_AddText("disconnect\nreconnect\n", RESTRICT_LOCAL); //retry connecting. + else + Con_Printf("Packet was not delivered - server might be badly configured\n"); + return false; + } #endif + } return false; } @@ -2535,7 +2550,7 @@ qboolean FTENET_Generic_GetPacket(ftenet_generic_connection_t *con) Con_Printf ("NET_GetPacket: Error (%i): %s\n", err, strerror(err)); return false; } - SockadrToNetadr (&from, &net_from); + SockadrToNetadr (&from, &net_from); net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; diff --git a/engine/common/sys.h b/engine/common/sys.h index 4e6e63bbe..59b0549a1 100644 --- a/engine/common/sys.h +++ b/engine/common/sys.h @@ -168,23 +168,21 @@ qboolean NPQTV_Sys_Startup(int argc, char *argv[]); void NPQTV_Sys_MainLoop(void); #endif -#define UPD_UNSUPPORTED -1 -#define UPD_REVERT 0 -#define UPD_OFF 1 -#define UPD_STABLE 2 -#define UPD_TESTING 3 +#define UPD_OFF 0 +#define UPD_STABLE 1 +#define UPD_TESTING 2 #if defined(WEBCLIENT) && defined(_WIN32) && !defined(SERVERONLY) int StartLocalServer(int close); #define HAVEAUTOUPDATE -int Sys_GetAutoUpdateSetting(void); -void Sys_SetAutoUpdateSetting(int newval); -void Sys_SetUpdatedBinary(const char *fname); +void Sys_SetUpdatedBinary(const char *fname); //legacy, so old build can still deal with updates properly +qboolean Sys_EngineCanUpdate(void); //says whether the system code is able to invoke new binaries properly +qboolean Sys_EngineWasUpdated(char *newbinary); //invoke the given system-path binary #else -#define Sys_GetAutoUpdateSetting() UPD_UNSUPPORTED -#define Sys_SetAutoUpdateSetting(n) +#define Sys_EngineCanUpdate() false #define Sys_SetUpdatedBinary(n) +#define Sys_EngineWasUpdated(n) false #endif void Sys_Init (void); diff --git a/engine/dotnet2005/ftequake.vcproj b/engine/dotnet2005/ftequake.vcproj index b171902fd..85db516f2 100644 --- a/engine/dotnet2005/ftequake.vcproj +++ b/engine/dotnet2005/ftequake.vcproj @@ -663,7 +663,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="../libs/speex,..\client,../libs/freetype2/include,../common,../server,../gl,../sw,../qclib,../libs,../libs/dxsdk7/include" - PreprocessorDefinitions="_DEBUG;GLQUAKE;VKQUAKE;WIN32;_WINDOWS;BOTLIB_STATIC;USE_MSVCRT_DEBUG" + PreprocessorDefinitions="_DEBUG;GLQUAKE;VKQUAKE;WIN32;_WINDOWS;BOTLIB_STATIC;USE_MSVCRT_DEBUG;DYNAMIC_SDL" BasicRuntimeChecks="3" SmallerTypeCheck="true" RuntimeLibrary="1" diff --git a/engine/gl/gl_draw.c b/engine/gl/gl_draw.c index 5d5791513..cd3066749 100644 --- a/engine/gl/gl_draw.c +++ b/engine/gl/gl_draw.c @@ -211,6 +211,11 @@ void GL_Set2D (qboolean flipped) qglLoadMatrixf(r_refdef.m_view); } + if (flipped) + r_refdef.flipcull = SHADER_CULL_FLIP; + else + r_refdef.flipcull = 0; + GL_SetShaderState2D(true); } diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index b31eccc1a..2eaffa3db 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -11508,6 +11508,7 @@ void PR_DumpPlatform_f(void) {"m_init", "void()", MENU}, {"m_shutdown", "void()", MENU}, {"m_draw", "void(vector screensize)", MENU, "Provides the menuqc with a chance to draw. Will be called even if the menu does not have focus, so be sure to avoid that. COMPAT: screensize is not provided in DP."}, + {"m_drawloading", "void(vector screensize)", MENU, "Additional drawing function to draw loading screen overlays."}, {"m_keydown", "void(float scan, float chr)", MENU}, {"m_keyup", "void(float scan, float chr)", MENU}, {"m_toggle", "void(float wantmode)", MENU}, diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 4c48bad38..dc9789c8e 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -1765,6 +1765,14 @@ static void SV_Status_f (void) int columns = 80; extern cvar_t sv_listen_qw, sv_listen_nq, sv_listen_dp, sv_listen_q3; +#ifndef SERVERONLY + if (!sv.state && cls.state >= ca_connected && !cls.demoplayback && cls.protocol == CP_NETQUAKE) + { //nq can normally forward the request to the server. + Cmd_ForwardToServer(); + return; + } +#endif + if (sv_redirected != RD_OBLIVION && (sv_redirected != RD_NONE #ifndef SERVERONLY || (vid.width < 68*8 && qrenderer != QR_NONE) diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index 97505d68d..1bd5101e0 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -1537,7 +1537,7 @@ qboolean SVFTE_EmitPacketEntities(client_t *client, packet_entities_t *to, sizeb MSG_WriteByte (msg, svcfte_updateentities); if (ISNQCLIENT(client) && (client->fteprotocolextensions2 & PEXT2_PREDINFO)) { - MSG_WriteShort(msg, client->last_sequence); + MSG_WriteShort(msg, client->last_sequence&0xffff); } // Con_Printf("Gen sequence %i\n", sequence); MSG_WriteFloat(msg, sv.world.physicstime); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 5fcd70448..ff08c9e95 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -2199,6 +2199,8 @@ client_t *SVC_DirectConnect(void) { {"FITZ", 1u<name, "unconnected", 11) && Q_strncasecmp(newcl->name, "connecting", 10)) + IPLog_Add(NET_AdrToString(adrbuf,sizeof(adrbuf), &newcl->netchan.remote_address), newcl->name); + return newcl; } diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index e4eb32fd4..d7429f1a2 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -2484,10 +2484,11 @@ qboolean SV_SendClientDatagram (client_t *client) qbyte buf[MAX_OVERALLMSGLEN]; sizebuf_t msg; unsigned int sentbytes; + unsigned int outframeseq = client->netchan.incoming_sequence; //this is so weird... but at least covers nq/qw sequence vs unreliables weirdness... if (ISQWCLIENT(client) || ISNQCLIENT(client)) { - client_frame_t *frame = &client->frameunion.frames[client->netchan.outgoing_sequence & UPDATE_MASK]; + client_frame_t *frame = &client->frameunion.frames[outframeseq & UPDATE_MASK]; frame->numresendstats = 0; } @@ -2522,11 +2523,11 @@ qboolean SV_SendClientDatagram (client_t *client) #endif { - if (!ISQ2CLIENT(client) && Netchan_CanReliable (&client->netchan, SV_RateForClient(client))) + if (!ISQ2CLIENT(client) && ((client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) || Netchan_CanReliable (&client->netchan, SV_RateForClient(client)))) { int pnum=1; client_t *c; - client_frame_t *frame = &client->frameunion.frames[client->netchan.outgoing_sequence & UPDATE_MASK]; + client_frame_t *frame = &client->frameunion.frames[outframeseq & UPDATE_MASK]; SV_UpdateClientStats (client, 0, &msg, frame); for (c = client->controlled; c; c = c->controlled,pnum++) diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 2371937b5..ef131e81d 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -635,6 +635,10 @@ void SVNQ_New_f (void) if (!gamedir[0]) { gamedir = FS_GetGamedir(true); +#ifndef NOLEGACY + if (!strcmp(gamedir, "qw")) //hack: hide the qw dir from nq clients. + gamedir = ""; +#endif } COM_FileBase(sv.modelname, mapname, sizeof(mapname)); @@ -5170,7 +5174,7 @@ void SV_CalcNetRates(client_t *cl, double *ftime, int *frames, double *minf, dou } } -void Cmd_FPSList_f(void) +static void Cmd_FPSList_f(void) { client_t *cl; int c; @@ -5233,7 +5237,7 @@ static void SV_STFU_f(void) } #ifdef NQPROT -void SVNQ_Spawn_f (void) +static void SVNQ_Spawn_f (void) { extern cvar_t sv_gravity; int i; @@ -5331,7 +5335,7 @@ void SVNQ_Spawn_f (void) host_client->send_message = true; } -void SVNQ_Begin_f (void) +static void SVNQ_Begin_f (void) { unsigned pmodel = 0, emodel = 0; int i; @@ -5460,7 +5464,7 @@ void SVNQ_Begin_f (void) SV_RunCmd (&host_client->lastcmd, false); SV_PostRunCmd(); } -void SVNQ_PreSpawn_f (void) +static void SVNQ_PreSpawn_f (void) { if (host_client->prespawn_stage < PRESPAWN_MAPCHECK) SV_StuffcmdToClient(host_client, va("cmd prespawn %s\n", Cmd_Args())); @@ -5499,13 +5503,13 @@ void SVNQ_PreSpawn_f (void) host_client->send_message = true; } -void SVNQ_NQInfo_f (void) +static void SVNQ_NQInfo_f (void) { Cmd_TokenizeString(va("setinfo \"%s\" \"%s\"\n", Cmd_Argv(0), Cmd_Argv(1)), false, false); SV_SetInfo_f(); } -void SVNQ_NQColour_f (void) +static void SVNQ_NQColour_f (void) { char *val; int top; @@ -5576,7 +5580,7 @@ void SVNQ_NQColour_f (void) SV_ExtractFromUserinfo (host_client, true); } -void SVNQ_Ping_f(void) +static void SVNQ_Ping_f(void) { int i; client_t *cl; @@ -5592,8 +5596,54 @@ void SVNQ_Ping_f(void) SV_PrintToClient(host_client, PRINT_HIGH, va("%3i %s\n", SV_CalcPing (cl, false), cl->name)); } } +static void SVNQ_Status_f(void) +{ //note: numerous NQ clients poll for this... + //so try to ensure that we adhere to various rules... + //we have a different function for server operators to use which contains more info. + int i; + client_t *cl; + int count; + extern cvar_t maxclients, maxspectators; -void SVNQ_Protocols_f(void) + /* + int nummodels, numsounds; + for (nummodels = 1; nummodels < MAX_PRECACHE_MODELS; nummodels++) + if (!sv.strings.model_precache[nummodels]) + break; + for (numsounds = 1; numsounds < MAX_PRECACHE_SOUNDS; numsounds++) + if (!sv.strings.sound_precache[numsounds]) + break;*/ + + SV_PrintToClient(host_client, PRINT_HIGH, va("host: %s\n", hostname.string)); //must be first, with same first 9 chars + SV_PrintToClient(host_client, PRINT_HIGH, va("version: %s\n", version_string())); +// SV_PrintToClient(host_client, PRINT_HIGH, va("IPv4: \n", )); +// SV_PrintToClient(host_client, PRINT_HIGH, va("IPv6: \n", )); + SV_PrintToClient(host_client, PRINT_HIGH, va("map: %s\n", svs.name)); +/* for (count = 1; count < MAX_PRECACHE_MODELS; count++) + if (!sv.strings.model_precache[count]) + break; + SV_PrintToClient(host_client, PRINT_HIGH, va("models: %i/%i\n", count-1, MAX_PRECACHE_MODELS-1));*/ +/* for (count = 1; count < MAX_PRECACHE_SOUNDS; count++) + if (!sv.strings.sound_precache[count]) + break; + SV_PrintToClient(host_client, PRINT_HIGH, va("sounds: %i/%i\n", count-1, MAX_PRECACHE_SOUNDS-1));*/ +// SV_PrintToClient(host_client, PRINT_HIGH, va("entities:%i/%i\n", sv.world.num_edicts, sv.world.max_edicts)); + for (count=0,i=0,cl=svs.clients ; istate) + count++; + } + SV_PrintToClient(host_client, PRINT_HIGH, va("players: %i active (%i max)\n\n", count, min(maxclients.ival+maxspectators.ival,sv.allocated_client_slots)));//must be last + for (i=0,cl=svs.clients ; istate) + continue; + SV_PrintToClient(host_client, PRINT_HIGH, va("#%i\n", i+1)); + SV_PrintToClient(host_client, PRINT_HIGH, va(" %s\n", "WITHHELD")); + } +} + +static void SVNQ_Protocols_f(void) { int i; host_client->supportedprotocols = 0; @@ -5841,7 +5891,7 @@ ucmd_t nqucmds[] = {"begin", SVNQ_Begin_f, true}, {"prespawn", SVNQ_PreSpawn_f, true}, - {"status", NULL}, + {"status", SVNQ_Status_f}, {"god", Cmd_God_f},