diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 6e1b57ec..461fd467 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -91,11 +91,6 @@ // 4 ERROR: log only messages of level ERROR // 5 do not write to the log -// these switches control the naming of demos (see -W) -// --demofilenameformat="%Mmin_%G_%w_%H_%n" // default: "%w_%h_%n_%Mmin_%G" -// --demotimestampformat="%H%M_%Y%m%d" // default: "%Y%m%d_%H%M" -// --demotimelocal=1 // default: 0 - // don't use these switches, unless you really know what you're doing: // -u // uprate diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index ba437d71..8dd9f41c 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -55,6 +55,29 @@ modepenalty_weight:50 use_hx_modeinfo:weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold +// **** Demo recording settings **** + +// demo_filenameformat Demo file format string +// string [0..63 chars], default "%w_%h_%n_%Mmin_%G" +//demo_filenameformat:%w_%h_%n_%Mmin_%G + +// demo_max_number Maximum number of demo files in RAM +// integer [3..16], default 5 +//demo_max_number:5 + +// demo_path Demo path (and filename) prefix +// string [0..63 chars], default "" +//demo_path: + +// demo_save Write demos to file +// integer [0..1], default 1, 0:disabled, 1:enabled +//demo_save:1 + +// demo_timestampformat Demo timestamp format string +// string [0..23 chars], default "%Y%m%d_%H%M" +//demo_timestampformat:%Y%m%d_%H%M + + // **** Debug switches **** // dumpmaprot dump maprot parameters for all maps (once, to logs/debug/maprot_debug_verbose.txt) diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 3c7781f2..fb1673ae 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -1466,34 +1466,8 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) #endif } -void setDemoFilenameFormat(char *fmt) -{ - extern string demofilenameformat; - if(fmt && fmt[0]!='\0') - { - copystring(demofilenameformat, fmt); - } else copystring(demofilenameformat, DEFDEMOFILEFMT); // reset to default if passed empty string - or should we output the current value in this case? -} -COMMANDN(demonameformat, setDemoFilenameFormat, "s"); -void setDemoTimestampFormat(char *fmt) -{ - extern string demotimestampformat; - if(fmt && fmt[0]!='\0') - { - copystring(demotimestampformat, fmt); - } else copystring(demotimestampformat, DEFDEMOTIMEFMT); // reset to default if passed empty string - or should we output the current value in this case? -} -COMMANDN(demotimeformat, setDemoTimestampFormat, "s"); -void setDemoTimeLocal(int *truth) -{ - extern int demotimelocal; - demotimelocal = *truth == 0 ? 0 : 1; -} -COMMANDN(demotimelocal, setDemoTimeLocal, "i"); -void getdemonameformat() { extern string demofilenameformat; result(demofilenameformat); } COMMAND(getdemonameformat, ""); -void getdemotimeformat() { extern string demotimestampformat; result(demotimestampformat); } COMMAND(getdemotimeformat, ""); -void getdemotimelocal() { extern int demotimelocal; intret(demotimelocal); } COMMAND(getdemotimelocal, ""); - +SVARP(demonameformat, "%w_%h_%n_%Mmin_%G"); +SVARP(demotimestampformat, "%Y%m%d_%H%M"); const char *parseDemoFilename(char *srvfinfo) { @@ -1523,8 +1497,9 @@ const char *parseDemoFilename(char *srvfinfo) } copystring(srvmap, pch); } - extern const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap); - return getDemoFilename(gmode, mplay, mdrop, stamp, srvmap); + static string buf; + enet_uint32 ip = curpeer ? ntohl(curpeer->address.host) : 0; + return formatdemofilename(demonameformat, demotimestampformat, srvmap, gmode, stamp / 60, mplay, mdrop, ip, buf); } void receivefile(uchar *data, int len) diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 49e71ffa..9434627e 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -284,7 +284,7 @@ char *filtertext(char *dst, const char *src, int flags, int len) toupp = (flags & FTXT_TOUPPER) != 0, // translates to all-uppercase tolow = (flags & FTXT_TOLOWER) != 0, // translates to all-lowercase filename = (flags & FTXT_FILENAME) != 0, // strict a-z, 0-9 and "-_.()" (also translates "[]" and "{}" to "()"), removes everything between '<' and '>' - allowslash = (flags & FTXT_ALLOWSLASH) != 0, // only in combination with FTXT_FILENAME + allowslash = (flags & FTXT_ALLOWSLASH) != 0, // only in combination with FTXT_FILENAME or FTXT_MAPNAME mapname = (flags & FTXT_MAPNAME) != 0, // only allows lowercase chars, digits, '_', '-' and '.'; probably should be used in combination with TOLOWER cropwhite = (flags & FTXT_CROPWHITE) != 0, // removes leading and trailing whitespace pass = false; @@ -348,7 +348,7 @@ char *filtertext(char *dst, const char *src, int flags, int len) if(src[1]) ++src; continue; } - if(mapname && !isalnum(c) && !strchr("_-./\\", c)) continue; + if(mapname && !isalnum(c) && !strchr("_-.", c) && !(allowslash && strchr("/\\", c))) continue; if(isspace(c)) { if(leadingwhite) continue; diff --git a/source/src/protocol.h b/source/src/protocol.h index a1e7f344..752fa27c 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -212,9 +212,9 @@ enum #define FTXT__MAPMSG (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK) #define FTXT__MAPINFO_LICENSE (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_TOLOWER | FTXT_MAPNAME) #define FTXT__MAPINFO_COMMENT (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK) -#define FTXT__MAPNAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_TOLOWER) +#define FTXT__MAPNAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_ALLOWSLASH | FTXT_TOLOWER) #define FTXT__MDLATTR (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) -#define FTXT__DEMONAME (FTXT_NOCOLOR | FTXT_MAPNAME) +#define FTXT__DEMONAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_ALLOWSLASH) #define FTXT__PLAYERNAME (FTXT_NOWHITE | FTXT_NOCOLOR) #define FTXT__SERVERINFOLINE (FTXT_ALLOWTAB | FTXT_CROPWHITE) #define FTXT__SERVERINFO (FTXT_ALLOWTAB | FTXT_ALLOWNL) @@ -230,10 +230,11 @@ enum #define FTXT__MEDIAFILENAME (FTXT_FILENAME) #define FTXT__MEDIAFILEPATH (FTXT_FILENAME | FTXT_ALLOWSLASH) #define FTXT__ZIPDESC (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) -#define FTXT__CERTFILENAME (FTXT_MAPNAME | FTXT_TOLOWER) +#define FTXT__CERTFILENAME (FTXT_MAPNAME | FTXT_ALLOWSLASH | FTXT_TOLOWER) #define FTXT__CONSOLEPASTE (FTXT_TABTOBLANK) #define FTXT__VITACOMMENT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK) #define FTXT__VITACLAN (FTXT_NOWHITE | FTXT_NOCOLOR) #define FTXT__MAPROT (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_TOLOWER) #define FTXT__SERVPARNAME (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_SAFECS) +#define FTXT__FORMATSTRING (FTXT_NOWHITE | FTXT_NOCOLOR) diff --git a/source/src/protos.h b/source/src/protos.h index fef3287c..2d261500 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1114,8 +1114,6 @@ struct demoheader char desc[DHDR_DESCCHARS]; char plist[DHDR_PLISTCHARS]; }; -#define DEFDEMOFILEFMT "%w_%h_%n_%Mmin_%G" -#define DEFDEMOTIMEFMT "%Y%m%d_%H%M" extern bool watchingdemo; extern int demoprotocol; @@ -1159,23 +1157,21 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath, *ssk, *vitabasename; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *service, *logfilepath, *parfilepath, *ssk, *vitabasename; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; vector adminonlymaps; // FIXME: remove this - servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(5), + servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(-1), maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), - ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), + ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(NULL), maprotfile("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), vitabasename("config/servervita"), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) { motd[0] = servdesc_full[0] = servdesc_pre[0] = servdesc_suf[0] = voteperm[0] = mapperm[0] = '\0'; - demofilenameformat = DEFDEMOFILEFMT; - demotimestampformat = DEFDEMOTIMEFMT; } bool checkarg(const char *arg) @@ -1189,20 +1185,7 @@ struct servercommandline { // todo: gjlqEJQU case 'Y': ssk = a; break; // private server key case '-': - if(!strncmp(arg, "--demofilenameformat=", 21)) - { - demofilenameformat = arg+21; - } - else if(!strncmp(arg, "--demotimestampformat=", 22)) - { - demotimestampformat = arg+22; - } - else if(!strncmp(arg, "--demotimelocal=", 16)) - { - int ai = atoi(arg+16); - demotimelocal = ai == 0 ? 0 : 1; - } - else if(!strncmp(arg, "--masterport=", 13)) + if(!strncmp(arg, "--masterport=", 13)) { int ai = atoi(arg+13); masterport = ai == 0 ? AC_MASTER_PORT : ai; diff --git a/source/src/server.cpp b/source/src/server.cpp index 29f482e0..4f579423 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -741,6 +741,7 @@ void sendspawn(client *c) // demo stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL; bool recordpackets = false; +#define MAXDEMOS 16 int nextplayback = 0; void writedemo(int chan, void *data, int len) @@ -762,119 +763,14 @@ void recordpacket(int chan, ENetPacket *packet) if(recordpackets) writedemo(chan, packet->data, (int)packet->dataLength); } -#ifdef STANDALONE -const char *currentserver(int i) -{ - static string curSRVinfo; - string r; - r[0] = '\0'; - switch(i) - { - case 1: { copystring(r, scl.ip[0] ? scl.ip : "local"); break; } // IP - case 2: { copystring(r, scl.logident[0] ? scl.logident : "local"); break; } // HOST - case 3: { formatstring(r)("%d", scl.serverport); break; } // PORT - // the following are used by a client, a server will simply return empty strings for them - case 4: - case 5: - case 6: - case 7: - case 8: - { - break; - } - default: - { - formatstring(r)("%s %d", scl.ip[0] ? scl.ip : "local", scl.serverport); - break; - } - } - copystring(curSRVinfo, r); - return curSRVinfo; -} -#endif +SERVPAR(demo_max_number, 3, 5, MAXDEMOS, "DMaximum number of demo files in RAM"); +SERVPARLIST(demo_save, 0, 1, 0, endis, "DWrite demos to file"); +SERVSTR(demo_path, "", 0, 63, FTXT__DEMONAME, "DDemo path (and filename) prefix"); +SERVSTR(demo_filenameformat, "%w_%h_%n_%Mmin_%G", 0, 63, FTXT__FORMATSTRING, "DDemo file format string"); +SERVSTR(demo_timestampformat, "%Y%m%d_%H%M", 0, 23, FTXT__FORMATSTRING, "DDemo timestamp format string"); -// these are actually the values used by the client, the server ones are in "scl". -string demofilenameformat = DEFDEMOFILEFMT; -string demotimestampformat = DEFDEMOTIMEFMT; int demotimelocal = 0; -#ifdef STANDALONE -#define DEMOFORMAT scl.demofilenameformat -#define DEMOTSFORMAT scl.demotimestampformat -#else -#define DEMOFORMAT demofilenameformat -#define DEMOTSFORMAT demotimestampformat -#endif - -const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap) -{ - // we use the following internal mapping of formatchars: - // %g : gamemode (int) %G : gamemode (chr) %F : gamemode (full) - // %m : minutes remaining %M : minutes played - // %s : seconds remaining %S : seconds played - // %h : IP of server %H : hostname of server - // %n : mapName - // %w : timestamp "when" - static string dmofn; - copystring(dmofn, ""); - - int cc = 0; - int mc = strlen(DEMOFORMAT); - - while(ccsize(); demotmp->seek(0, SEEK_SET); - if(demofiles.length() >= scl.maxdemos) + if(demofiles.length() >= demo_max_number) { delete[] demofiles[0].data; demofiles.remove(0); @@ -914,7 +810,7 @@ void enddemorecord() int iTIME = time(NULL); const char *mTIME = numtime(); const char *sMAPN = behindpath(sg->smapname); - string iMAPN; + string iMAPN, buf; copystring(iMAPN, sMAPN); formatstring(d.file)( "%d:%d:%d:%s:%s", gamemode, mPLAY, mDROP, mTIME, iMAPN); @@ -923,9 +819,9 @@ void enddemorecord() demotmp->read(d.data, len); delete demotmp; demotmp = NULL; - if(scl.demopath[0]) + if(demo_save) { - formatstring(msg)("%s%s.dmo", scl.demopath, getDemoFilename(gamemode, mPLAY, mDROP, iTIME, iMAPN)); //d.file); + formatstring(msg)("%s%s.dmo", demo_path, formatdemofilename(demo_filenameformat, demo_timestampformat, iMAPN, gamemode, iTIME / 60, mPLAY, mDROP, servownip, buf)); path(msg); stream *demo = openfile(msg, "wb"); if(demo) @@ -4754,6 +4650,10 @@ void initserver(bool dedicated) sg->smapname[0] = '\0'; servparallfun(); + // init server parameters from commandline + if(scl.demopath) initserverparameter("demo_path", scl.demopath); + if(scl.maxdemos > 0) initserverparameter("demo_max_number", scl.maxdemos); + // start logging initserverparameter("logthreshold_console", scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO)); initserverparameter("logthreshold_syslog", scl.syslogthres); @@ -4796,8 +4696,8 @@ void initserver(bool dedicated) forbiddenlist.init(scl.forbidden); serverinfoinfo.init(scl.infopath, MAXTRANS / 2); serverinfomotd.init(scl.motdpath, MAXSTRLEN - 5); - mlog(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", scl.maxdemos); - if(scl.demopath[0]) mlog(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath); + mlog(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", demo_max_number); + if(demo_save) mlog(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", demo_path); if(scl.voteperm[0]) mlog(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm); if(scl.mapperm[0]) mlog(ACLOG_VERBOSE,"map permission string: \"%s\"", scl.mapperm); mlog(ACLOG_VERBOSE,"server description: \"%s\"", scl.servdesc_full); diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 923ce184..fe1731b6 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1909,8 +1909,9 @@ void servparallfun() // trigger all functions once, to make sure, unchanged defa #ifdef _DEBUG int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } -const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", "CCommandline switch overrides", "lServer load statistics", "" }; -const char *siddocchaptersorting = "sgvmdC", *siddocchaptercommentedout = "C"; +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", + "CCommandline switch overrides", "DDemo recording settings", "lServer load statistics", "" }; +const char *siddocchaptersorting = "sgDvmdC", *siddocchaptercommentedout = "CD"; void serverparameters_dumpdocu(char *fname) { diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 68ca9bc3..b9ab9fc3 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -2,6 +2,8 @@ #include "cube.h" +extern bool isdedicated; + #ifdef NO_POSIX_R char *strtok_r(char *s, const char *delim, char **b) { @@ -516,6 +518,61 @@ const char *iprtoa(const struct iprange &ipr, char *b) return b; } +char *formatdemofilename(const char *demoformat, const char *timestampformat, const char *map, int mode, int srvclock, int secondsplayed, int secondsremaining, enet_uint32 ip, char *buf) +{ + vector d; + // we use the following internal mapping of formatchars: + // %g : gamemode (int) %G : gamemode (chr) %F : gamemode (full) + // %m : minutes remaining %M : minutes played + // %s : seconds remaining %S : seconds played + // %h : IP of server %H : hostname of server (client only) + // %n : mapName + // %w : timestamp "when" + bool did_map = false, did_mode = false, did_ts = false; + for(const char *s = demoformat; *s; s++) + { + if(*s == '%') + { + switch(*++s) + { + case 'F': cvecconcat(d, fullmodestr(mode)); did_mode = true; break; + case 'g': cvecprintf(d, "%d", mode); did_mode = true; break; + case 'G': cvecconcat(d, acronymmodestr(mode)); did_mode = true; break; + case 'H': + if(!isdedicated) + { +#ifndef STANDALONE + ENetAddress a = { htonl(ip), 0 }; + cvecconcat(d, !enet_address_get_host(&a, buf, MAXSTRLEN) ? buf : "unknown"); + break; +#endif + } // fallthrough to "IP" on dedicated servers + case 'h': cvecconcat(d, ip ? iptoa(ip, buf) : "local"); break; + case 'm': cvecprintf(d, "%d", secondsremaining / 60); break; + case 'M': cvecprintf(d, "%d", secondsplayed / 60); break; + case 'n': cvecconcat(d, map); did_map = true; break; + case 's': cvecprintf(d, "%d", secondsremaining); break; + case 'S': cvecprintf(d, "%d", secondsplayed); break; + case 'w': + { + time_t t = ((time_t) srvclock) * 60; + bool utc = *timestampformat == 'U'; + cvecconcat(d, timestring(t, !utc, timestampformat + int(utc), buf)); + did_ts = true; + break; + } + default: + if(*s) d.add(*s); + break; + } + } + else d.add(*s); + } + d.add('\0'); + filtertext(buf, d.getbuf(), FTXT__DEMONAME); + return did_map && did_mode && did_ts ? buf : formatdemofilename("%w_%h_%n_%G", "%Y%m%d_%H%M", map, mode, srvclock, secondsplayed, secondsremaining, ip, buf); +} + int cmpiprange(const struct iprange *a, const struct iprange *b) { if(a->lr < b->lr) return -1; @@ -542,6 +599,13 @@ int cvecprintf(vector &v, const char *s, ...) return len; } +int cvecconcat(vector &v, const char *s) +{ + int len = strlen(s); + if(len) v.put(s, len); + return len; +} + const char *hiddenpwd(const char *pwd, int showchars) { static int sc = 3; @@ -563,7 +627,6 @@ int getlistindex(const char *key, const char *list[], bool acceptnumeric, int de } #if !defined(STANDALONE) && defined(_DEBUG) char *opts = conc(list, -1, true); - extern bool isdedicated; if(!isdedicated && *key) clientlogf("warning: unknown token \"%s\" (not in list [%s])", key, opts); delstring(opts); #endif diff --git a/source/src/tools.h b/source/src/tools.h index 83e6e2bd..376633a8 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -952,9 +952,11 @@ extern const char *atoip(const char *s, enet_uint32 *ip); extern const char *atoipr(const char *s, iprange *ir); extern const char *iptoa(const enet_uint32 ip, char *b); extern const char *iprtoa(const struct iprange &ipr, char *b); +extern char *formatdemofilename(const char *demoformat, const char *timestampformat, const char *map, int mode, int srvclock, int secondsplayed, int secondsremaining, enet_uint32 ip, char *buf); extern int cmpiprange(const struct iprange *a, const struct iprange *b); extern int cmpipmatch(const struct iprange *a, const struct iprange *b); extern int cvecprintf(vector &v, const char *s, ...) PRINTFARGS(2, 3); +extern int cvecconcat(vector &v, const char *s); extern const char *hiddenpwd(const char *pwd, int showchars = 0); extern int getlistindex(const char *key, const char *list[], bool acceptnumeric = true, int deflt = -1); extern void parseupdatelist(hashtable &ht, char *buf, const char *prefix = NULL, const char *suffix = NULL);