diff --git a/source/src/Makefile b/source/src/Makefile index ead5cc7d..2e08712f 100644 --- a/source/src/Makefile +++ b/source/src/Makefile @@ -31,7 +31,6 @@ CLIENT_OBJS= \ client.o \ clientgame.o \ clients2c.o \ - cmodserver.o \ command.o \ console.o \ docs.o \ @@ -95,7 +94,6 @@ SERVER_LIBS= -L../enet -lenet -lz `sdl-config --libs` endif SERVER_OBJS= \ - cmodserver.o \ log-standalone.o \ protocol-standalone.o \ server-standalone.o \ @@ -103,6 +101,10 @@ SERVER_OBJS= \ stream-standalone.o \ tools-standalone.o \ wizard-standalone.o +MASTER_OBJS= \ + stream-standalone.o \ + command-standalone.o \ + master-standalone.o ifeq ($(PLATFORM),SunOS) CLIENT_LIBS+= -lsocket -lnsl -lX11 @@ -136,6 +138,7 @@ clean: $(CLIENT_OBJS): CXXFLAGS += $(CLIENT_INCLUDES) $(CLIENT_OBJS): $(CLIENT_PCH) $(SERVER_OBJS): CXXFLAGS += $(SERVER_INCLUDES) +$(filter-out $(SERVER_OBJS),$(MASTER_OBJS)): CXXFLAGS += $(SERVER_INCLUDES) ifneq (,$(findstring MINGW,$(PLATFORM))) ..\\vcpp\\%.o: @@ -148,6 +151,9 @@ client: $(CLIENT_OBJS) server: $(SERVER_OBJS) $(CXX) $(CXXFLAGS) -o ../../bin_win32/ac_server.exe $(SERVER_OBJS) $(SERVER_LIBS) +master: $(MASTER_OBJS) + $(CXX) $(CXXFLAGS) -o ../../bin_win32/ac_master.exe $(MASTER_OBJS) $(SERVER_LIBS) + client_install: client server_install: server @@ -157,6 +163,9 @@ client: libenet $(CLIENT_OBJS) server: libenet $(SERVER_OBJS) $(CXX) $(CXXFLAGS) -o ac_server $(SERVER_OBJS) $(SERVER_LIBS) + +master: libenet $(MASTER_OBJS) + $(CXX) $(CXXFLAGS) -o ac_master $(MASTER_OBJS) $(SERVER_LIBS) client_install: client cp ac_client ../../bin_unix/$(PLATFORM_PREFIX)_client diff --git a/source/src/command.cpp b/source/src/command.cpp index 287f8705..81ffd13e 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -362,7 +362,9 @@ char *executeret(const char *p) // all evaluation hap case ARG_6STR: ((void (__cdecl *)(char *, char *, char*, char*, char*, char*))id->fun)(w[1], w[2], w[3], w[4], w[5], w[6]); break; case ARG_7STR: ((void (__cdecl *)(char *, char *, char*, char*, char*, char*, char*))id->fun)(w[1], w[2], w[3], w[4], w[5], w[6], w[7]); break; case ARG_8STR: ((void (__cdecl *)(char *, char *, char*, char*, char*, char*, char*, char*))id->fun)(w[1], w[2], w[3], w[4], w[5], w[6], w[7], w[8]); break; +#ifndef STANDALONE case ARG_DOWN: ((void (__cdecl *)(bool))id->fun)(addreleaseaction(id->name)!=NULL); break; +#endif case ARG_1EXP: intret(((int (__cdecl *)(int))id->fun)(ATOI(w[1]))); break; case ARG_2EXP: intret(((int (__cdecl *)(int, int))id->fun)(ATOI(w[1]), ATOI(w[2]))); break; case ARG_1EXPF: floatret(((float (__cdecl *)(float))id->fun)(atof(w[1]))); break; @@ -462,6 +464,7 @@ int execute(const char *p) return i; } +#ifndef STANDALONE // tab-completion of all idents static int completesize = -1, completeidx = 0; @@ -672,6 +675,7 @@ void complete(char *s) } commandcomplete(s); } +#endif bool execfile(const char *cfgfile) { @@ -869,6 +873,7 @@ int strcmpa(char *a, char *b) { return strcmp(a,b)==0; } COMMANDN(strcmp, strcm int rndn(int a) { return a>0 ? rnd(a) : 0; } COMMANDN(rnd, rndn, ARG_1EXP); +#ifndef STANDALONE void writecfg() { stream *f = openfile(path("config/saved.cfg", true), "w"); @@ -925,6 +930,7 @@ void deletecfg() } } } +#endif void identnames(vector &names, bool builtinonly) { @@ -994,6 +1000,7 @@ COMMAND(scriptcontext, ARG_2STR); COMMAND(isolatecontext, ARG_1INT); COMMAND(sealcontexts, ARG_NONE); +#ifndef STANDALONE void _watchingdemo() { extern bool watchingdemo; @@ -1088,6 +1095,7 @@ void currentserver(int i) } COMMANDN(curserver, currentserver, ARG_1INT); +#endif void debugargs(char **args, int numargs) { diff --git a/source/src/cube.h b/source/src/cube.h index 0585f58c..e9ad3787 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -11,9 +11,9 @@ #include "entity.h" #include "world.h" #include "i18n.h" +#include "command.h" #ifndef STANDALONE - #include "command.h" #include "vote.h" #include "console.h" #endif @@ -48,7 +48,8 @@ extern int verbose; #include "protos.h" // external function decls #define AC_VERSION 1000 -#define AC_MASTER_URI "masterserver.cubers.net/cgi-bin/actioncube.pl/" // FIXME, change DNS on ac release +#define AC_MASTER_URI "masterserver.cubers.net" // FIXME, change DNS on ac release +#define AC_MASTER_PORT 28767 #endif diff --git a/source/src/main.cpp b/source/src/main.cpp index 5884d4fe..301c6e9a 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -562,6 +562,24 @@ void pushevent(const SDL_Event &e) events.add(e); } +bool interceptkey(int sym) +{ + static int lastintercept = SDLK_UNKNOWN; + int len = lastintercept == sym ? events.length() : 0; + SDL_Event event; + while(SDL_PollEvent(&event)) switch(event.type) + { + case SDL_MOUSEMOTION: break; + default: pushevent(event); break; + } + lastintercept = sym; + if(sym != SDLK_UNKNOWN) for(int i = len; i < events.length(); i++) + { + if(events[i].type == SDL_KEYDOWN && events[i].key.keysym.sym == sym) { events.remove(i); return true; } + } + return false; +} + static void resetmousemotion() { #ifndef WIN32 diff --git a/source/src/master.cpp b/source/src/master.cpp new file mode 100644 index 00000000..d77c3e70 --- /dev/null +++ b/source/src/master.cpp @@ -0,0 +1,606 @@ +#include "cube.h" +#include +#include + +#define INPUT_LIMIT 4096 +#define OUTPUT_LIMIT (64*1024) +#define CLIENT_TIME (3*60*1000) +#define AUTH_TIME (60*1000) +#define AUTH_LIMIT 100 +#define CLIENT_LIMIT 8192 +#define DUP_LIMIT 16 +#define PING_TIME 3000 +#define PING_RETRY 5 +#define KEEPALIVE_TIME (65*60*1000) +#define SERVER_LIMIT (10*1024) + +FILE *logfile = NULL; + +struct baninfo +{ + enet_uint32 ip, mask; +}; +vector bans, servbans, gbans; + +void clearbans() +{ + bans.shrink(0); + servbans.shrink(0); + gbans.shrink(0); +} +COMMAND(clearbans, ARG_NONE); + +void addban(vector &bans, const char *name) +{ + union { uchar b[sizeof(enet_uint32)]; enet_uint32 i; } ip, mask; + ip.i = 0; + mask.i = 0; + loopi(4) + { + char *end = NULL; + int n = strtol(name, &end, 10); + if(!end) break; + if(end > name) { ip.b[i] = n; mask.b[i] = 0xFF; } + name = end; + while(*name && *name++ != '.'); + } + baninfo &ban = bans.add(); + ban.ip = ip.i; + ban.mask = mask.i; +} +COMMANDF(ban, ARG_1STR, (char *name) { addban(bans, name); }); +COMMANDF(servban, ARG_1STR, (char *name) { addban(servbans, name); }); +COMMANDF(gban, ARG_1STR, (char *name) { addban(gbans, name); }); + +char *printban(const baninfo &ban, char *buf) +{ + union { uchar b[sizeof(enet_uint32)]; enet_uint32 i; } ip, mask; + ip.i = ban.ip; + mask.i = ban.mask; + int lastdigit = -1; + loopi(4) if(mask.b[i]) + { + if(lastdigit >= 0) *buf++ = '.'; + loopj(i - lastdigit - 1) { *buf++ = '*'; *buf++ = '.'; } + buf += sprintf(buf, "%d", ip.b[i]); + lastdigit = i; + } + return buf; +} + +bool checkban(vector &bans, enet_uint32 host) +{ + loopv(bans) if((host & bans[i].mask) == bans[i].ip) return true; + return false; +} + +struct gameserver +{ + ENetAddress address; + string ip; + int port, numpings; + enet_uint32 lastping, lastpong; +}; +vector gameservers; + +struct messagebuf +{ + vector &owner; + vector buf; + int refs; + + messagebuf(vector &owner) : owner(owner), refs(0) {} + + const char *getbuf() { return buf.getbuf(); } + int length() { return buf.length(); } + void purge(); + + bool equals(const messagebuf &m) const + { + return buf.length() == m.buf.length() && !memcmp(buf.getbuf(), m.buf.getbuf(), buf.length()); + } + + bool endswith(const messagebuf &m) const + { + return buf.length() >= m.buf.length() && !memcmp(&buf[buf.length() - m.buf.length()], m.buf.getbuf(), m.buf.length()); + } + + void concat(const messagebuf &m) + { + if(buf.length() && buf.last() == '\0') buf.pop(); + buf.put(m.buf.getbuf(), m.buf.length()); + } +}; +vector gameserverlists, gbanlists; +bool updateserverlist = true; + +struct client +{ + ENetAddress address; + ENetSocket socket; + char input[INPUT_LIMIT]; + messagebuf *message; + vector output; + int inputpos, outputpos; + enet_uint32 connecttime, lastinput; + int servport; + bool shouldpurge; + + client() : message(NULL), inputpos(0), outputpos(0), servport(-1), shouldpurge(false) {} +}; +vector clients; + +ENetSocket serversocket = ENET_SOCKET_NULL; + +time_t starttime; +enet_uint32 servtime = 0; + +void fatal(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(logfile, fmt, args); + fputc('\n', logfile); + va_end(args); + exit(EXIT_FAILURE); +} + +void conoutfv(const char *fmt, va_list args) +{ + vfprintf(logfile, fmt, args); + fputc('\n', logfile); +} + +void conoutf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + conoutfv(fmt, args); + va_end(args); +} + +void purgeclient(int n) +{ + client &c = *clients[n]; + if(c.message) c.message->purge(); + enet_socket_destroy(c.socket); + delete clients[n]; + clients.remove(n); +} + +void output(client &c, const char *msg, int len = 0) +{ + if(!len) len = strlen(msg); + c.output.put(msg, len); +} + +void outputf(client &c, const char *fmt, ...) +{ + string msg; + va_list args; + va_start(args, fmt); + vformatstring(msg, fmt, args); + va_end(args); + + output(c, msg); +} + +ENetSocket pingsocket = ENET_SOCKET_NULL; + +bool setuppingsocket() +{ + if(pingsocket != ENET_SOCKET_NULL) return true; + pingsocket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + if(pingsocket == ENET_SOCKET_NULL) return false; + enet_socket_set_option(pingsocket, ENET_SOCKOPT_NONBLOCK, 1); + return true; +} + +void setupserver(int port, const char *ip = NULL) +{ + ENetAddress address; + address.host = ENET_HOST_ANY; + address.port = port; + + if(ip) + { + if(enet_address_set_host(&address, ip)<0) + fatal("failed to resolve server address: %s", ip); + } + serversocket = enet_socket_create(ENET_SOCKET_TYPE_STREAM); + if(serversocket==ENET_SOCKET_NULL || + enet_socket_set_option(serversocket, ENET_SOCKOPT_REUSEADDR, 1) < 0 || + enet_socket_bind(serversocket, &address) < 0 || + enet_socket_listen(serversocket, -1) < 0) + fatal("failed to create server socket"); + if(enet_socket_set_option(serversocket, ENET_SOCKOPT_NONBLOCK, 1)<0) + fatal("failed to make server socket non-blocking"); + if(!setuppingsocket()) + fatal("failed to create ping socket"); + + enet_time_set(0); + + starttime = time(NULL); + char *ct = ctime(&starttime); + if(strchr(ct, '\n')) *strchr(ct, '\n') = '\0'; + conoutf("*** Starting master server on %s %d at %s ***", ip ? ip : "localhost", port, ct); +} + +void genserverlist() +{ + if(!updateserverlist) return; + while(gameserverlists.length() && gameserverlists.last()->refs<=0) + delete gameserverlists.pop(); + messagebuf *l = new messagebuf(gameserverlists); + loopv(gameservers) + { + gameserver &s = *gameservers[i]; + if(!s.lastpong) continue; + defformatstring(cmd)("addserver %s %d\n", s.ip, s.port); + l->buf.put(cmd, strlen(cmd)); + } + l->buf.add('\0'); + gameserverlists.add(l); + updateserverlist = false; +} + +void gengbanlist() +{ + messagebuf *l = new messagebuf(gbanlists); + const char *header = "cleargbans\n"; + l->buf.put(header, strlen(header)); + string cmd = "addgban "; + int cmdlen = strlen(cmd); + loopv(gbans) + { + baninfo &b = gbans[i]; + l->buf.put(cmd, printban(b, &cmd[cmdlen]) - cmd); + l->buf.add('\n'); + } + if(gbanlists.length() && gbanlists.last()->equals(*l)) + { + delete l; + return; + } + while(gbanlists.length() && gbanlists.last()->refs<=0) + delete gbanlists.pop(); + loopv(gbanlists) + { + messagebuf *m = gbanlists[i]; + if(m->refs > 0 && !m->endswith(*l)) m->concat(*l); + } + gbanlists.add(l); + loopv(clients) + { + client &c = *clients[i]; + if(c.servport >= 0 && !c.message) + { + c.message = l; + c.message->refs++; + } + } +} + +void addgameserver(client &c) +{ + if(gameservers.length() >= SERVER_LIMIT) return; + loopv(gameservers) + { + gameserver &s = *gameservers[i]; + if(s.address.host == c.address.host && s.port == c.servport) + { + s.lastping = 0; + s.numpings = 0; + return; + } + } + string hostname; + if(enet_address_get_host_ip(&c.address, hostname, sizeof(hostname)) < 0) + { + outputf(c, "failreg failed resolving ip\n"); + return; + } + gameserver &s = *gameservers.add(new gameserver); + s.address.host = c.address.host; + s.address.port = c.servport+1; + copystring(s.ip, hostname); + s.port = c.servport; + s.numpings = 0; + s.lastping = s.lastpong = 0; +} + +client *findclient(gameserver &s) +{ + loopv(clients) + { + client &c = *clients[i]; + if(s.address.host == c.address.host && s.port == c.servport) + return &c; + } + return NULL; +} + +void servermessage(gameserver &s, const char *msg) +{ + client *c = findclient(s); + if(c) outputf(*c, msg); +} + +void checkserverpongs() +{ + ENetBuffer buf; + ENetAddress addr; + static uchar pong[MAXTRANS]; + for(;;) + { + buf.data = pong; + buf.dataLength = sizeof(pong); + int len = enet_socket_receive(pingsocket, &addr, &buf, 1); + if(len <= 0) break; + loopv(gameservers) + { + gameserver &s = *gameservers[i]; + if(s.address.host == addr.host && s.address.port == addr.port) + { + if(s.lastping && (!s.lastpong || ENET_TIME_GREATER(s.lastping, s.lastpong))) + { + client *c = findclient(s); + if(c) + { + outputf(*c, "succreg\n"); + if(!c->message && gbanlists.length()) + { + c->message = gbanlists.last(); + c->message->refs++; + } + } + } + if(!s.lastpong) updateserverlist = true; + s.lastpong = servtime ? servtime : 1; + break; + } + } + } +} + +void bangameservers() +{ + loopvrev(gameservers) if(checkban(servbans, gameservers[i]->address.host)) + { + delete gameservers.remove(i); + updateserverlist = true; + } +} + +void checkgameservers() +{ + ENetBuffer buf; + loopv(gameservers) + { + gameserver &s = *gameservers[i]; + if(s.lastping && s.lastpong && ENET_TIME_LESS_EQUAL(s.lastping, s.lastpong)) + { + if(ENET_TIME_DIFFERENCE(servtime, s.lastpong) > KEEPALIVE_TIME) + { + delete gameservers.remove(i--); + updateserverlist = true; + } + } + else if(!s.lastping || ENET_TIME_DIFFERENCE(servtime, s.lastping) > PING_TIME) + { + if(s.numpings >= PING_RETRY) + { + servermessage(s, "failreg failed pinging server\n"); + delete gameservers.remove(i--); + updateserverlist = true; + } + else + { + static const uchar ping[] = { 1 }; + buf.data = (void *)ping; + buf.dataLength = sizeof(ping); + s.numpings++; + s.lastping = servtime ? servtime : 1; + enet_socket_send(pingsocket, &s.address, &buf, 1); + } + } + } +} + +void messagebuf::purge() +{ + refs = max(refs - 1, 0); + if(refs<=0 && owner.last()!=this) + { + owner.removeobj(this); + delete this; + } +} + +bool checkclientinput(client &c) +{ + if(c.inputpos<0) return true; + char *end = (char *)memchr(c.input, '\n', c.inputpos); + while(end) + { + *end++ = '\0'; + c.lastinput = servtime; + + int port; + if(!strncmp(c.input, "list", 4) && (!c.input[4] || isspace(c.input[4]))) + { + genserverlist(); + if(gameserverlists.empty() || c.message) return false; + c.message = gameserverlists.last(); + c.message->refs++; + c.output.setsize(0); + c.outputpos = 0; + c.shouldpurge = true; + return true; + } + else if(sscanf(c.input, "regserv %d", &port) == 1) + { + if(checkban(servbans, c.address.host)) return false; + if(port < 0 || port + 1 < 0 || (c.servport >= 0 && port != c.servport)) outputf(c, "failreg invalid port\n"); + else + { + c.servport = port; + addgameserver(c); + } + } + c.inputpos = &c.input[c.inputpos] - end; + memmove(c.input, end, c.inputpos); + + end = (char *)memchr(c.input, '\n', c.inputpos); + } + return c.inputpos<(int)sizeof(c.input); +} + +ENetSocketSet readset, writeset; + +void checkclients() +{ + ENetSocketSet readset, writeset; + ENetSocket maxsock = max(serversocket, pingsocket); + ENET_SOCKETSET_EMPTY(readset); + ENET_SOCKETSET_EMPTY(writeset); + ENET_SOCKETSET_ADD(readset, serversocket); + ENET_SOCKETSET_ADD(readset, pingsocket); + loopv(clients) + { + client &c = *clients[i]; + if(c.message || c.output.length()) ENET_SOCKETSET_ADD(writeset, c.socket); + else ENET_SOCKETSET_ADD(readset, c.socket); + maxsock = max(maxsock, c.socket); + } + if(enet_socketset_select(maxsock, &readset, &writeset, 1000)<=0) return; + + if(ENET_SOCKETSET_CHECK(readset, pingsocket)) checkserverpongs(); + if(ENET_SOCKETSET_CHECK(readset, serversocket)) + { + ENetAddress address; + ENetSocket clientsocket = enet_socket_accept(serversocket, &address); + if(clients.length()>=CLIENT_LIMIT || checkban(bans, address.host)) enet_socket_destroy(clientsocket); + else if(clientsocket!=ENET_SOCKET_NULL) + { + int dups = 0, oldest = -1; + loopv(clients) if(clients[i]->address.host == address.host) + { + dups++; + if(oldest<0 || clients[i]->connecttime < clients[oldest]->connecttime) oldest = i; + } + if(dups >= DUP_LIMIT) purgeclient(oldest); + + client *c = new client; + c->address = address; + c->socket = clientsocket; + c->connecttime = servtime; + c->lastinput = servtime; + clients.add(c); + } + } + + loopv(clients) + { + client &c = *clients[i]; + if((c.message || c.output.length()) && ENET_SOCKETSET_CHECK(writeset, c.socket)) + { + const char *data = c.output.length() ? c.output.getbuf() : c.message->getbuf(); + int len = c.output.length() ? c.output.length() : c.message->length(); + ENetBuffer buf; + buf.data = (void *)&data[c.outputpos]; + buf.dataLength = len-c.outputpos; + int res = enet_socket_send(c.socket, NULL, &buf, 1); + if(res>=0) + { + c.outputpos += res; + if(c.outputpos>=len) + { + if(c.output.length()) c.output.setsize(0); + else + { + c.message->purge(); + c.message = NULL; + } + c.outputpos = 0; + if(!c.message && c.output.empty() && c.shouldpurge) + { + purgeclient(i--); + continue; + } + } + } + else { purgeclient(i--); continue; } + } + if(ENET_SOCKETSET_CHECK(readset, c.socket)) + { + ENetBuffer buf; + buf.data = &c.input[c.inputpos]; + buf.dataLength = sizeof(c.input) - c.inputpos; + int res = enet_socket_receive(c.socket, NULL, &buf, 1); + if(res>0) + { + c.inputpos += res; + c.input[min(c.inputpos, (int)sizeof(c.input)-1)] = '\0'; + if(!checkclientinput(c)) { purgeclient(i--); continue; } + } + else { purgeclient(i--); continue; } + } + if(c.output.length() > OUTPUT_LIMIT) { purgeclient(i--); continue; } + if(ENET_TIME_DIFFERENCE(servtime, c.lastinput) >= CLIENT_TIME) { purgeclient(i--); continue; } + } +} + +void banclients() +{ + loopvrev(clients) if(checkban(bans, clients[i]->address.host)) purgeclient(i); +} + +volatile bool reloadcfg = true; + +void reloadsignal(int signum) +{ + reloadcfg = true; +} + +int main(int argc, char **argv) +{ + if(enet_initialize()<0) fatal("Unable to initialise network module"); + atexit(enet_deinitialize); + + const char *dir = "", *ip = AC_MASTER_URI; + int port = AC_MASTER_PORT; + if(argc>=2) dir = argv[1]; + if(argc>=3) port = atoi(argv[2]); + if(argc>=4) ip = argv[3]; + defformatstring(logname)("%smaster.log", dir); + defformatstring(cfgname)("%smaster.cfg", dir); + path(logname); + path(cfgname); + logfile = fopen(logname, "a"); + if(!logfile) logfile = stdout; + setvbuf(logfile, NULL, _IOLBF, 0); +#ifndef WIN32 + signal(SIGUSR1, reloadsignal); +#endif + setupserver(port, ip); + for(;;) + { + if(reloadcfg) + { + conoutf("reloading master.cfg"); + execfile(cfgname); + bangameservers(); + banclients(); + gengbanlist(); + reloadcfg = false; + } + + servtime = enet_time_get(); + checkclients(); + checkgameservers(); + } + + return EXIT_SUCCESS; +} + diff --git a/source/src/protos.h b/source/src/protos.h index f5adb049..4a7df934 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -32,44 +32,6 @@ struct color color(float r, float g, float b, float a) : r(r), g(g), b(b), alpha(a) {} }; -// command -extern bool persistidents; -extern int variable(const char *name, int min, int cur, int max, int *storage, void (*fun)(), bool persist); -extern float fvariable(const char *name, float min, float cur, float max, float *storage, void (*fun)(), bool persist); -extern char *svariable(const char *name, const char *cur, char **storage, void (*fun)(), bool persist); -extern void setvar(const char *name, int i, bool dofunc = false); -extern void setfvar(const char *name, float f, bool dofunc = false); -extern void setsvar(const char *name, const char *str, bool dofunc = false); -extern int getvar(const char *name); -extern bool identexists(const char *name); -extern bool addcommand(const char *name, void (*fun)(), int narg); -extern int execute(const char *p); -extern char *executeret(const char *p); -extern char *conc(char **w, int n, bool space); -extern void intret(int v); -extern const char *floatstr(float v); -extern void floatret(float v); -extern void result(const char *s); -extern void exec(const char *cfgfile); -extern bool execfile(const char *cfgfile); -extern void resetcomplete(); -extern void complete(char *s); -extern void push(const char *name, const char *action); -extern void pop(const char *name); -extern void alias(const char *name, const char *action); -extern const char *getalias(const char *name); -extern void writecfg(); -extern void deletecfg(); -extern void identnames(vector &names, bool builtinonly); -extern void changescriptcontext(int newcontext); -extern void explodelist(const char *s, vector &elems); -extern char *indexlist(const char *s, int pos); -extern char *parseword(const char *&p); -extern void pushscontext(int newcontext); -extern int popscontext(); -extern int curscontext(); -extern int execcontext; - // console extern void keypress(int code, bool isdown, int cooked, SDLMod mod = KMOD_NONE); extern int rendercommand(int x, int y, int w); @@ -478,6 +440,7 @@ extern SDL_Surface *screen; extern int colorbits, depthbits, stencilbits; extern void keyrepeat(bool on); +extern bool interceptkey(int sym); extern bool firstrun, inmainloop; enum @@ -696,6 +659,44 @@ extern void scrolldoc(int i); extern int stringsort(const char **a, const char **b); #endif +// command +extern bool persistidents; +extern int variable(const char *name, int min, int cur, int max, int *storage, void (*fun)(), bool persist); +extern float fvariable(const char *name, float min, float cur, float max, float *storage, void (*fun)(), bool persist); +extern char *svariable(const char *name, const char *cur, char **storage, void (*fun)(), bool persist); +extern void setvar(const char *name, int i, bool dofunc = false); +extern void setfvar(const char *name, float f, bool dofunc = false); +extern void setsvar(const char *name, const char *str, bool dofunc = false); +extern int getvar(const char *name); +extern bool identexists(const char *name); +extern bool addcommand(const char *name, void (*fun)(), int narg); +extern int execute(const char *p); +extern char *executeret(const char *p); +extern char *conc(char **w, int n, bool space); +extern void intret(int v); +extern const char *floatstr(float v); +extern void floatret(float v); +extern void result(const char *s); +extern void exec(const char *cfgfile); +extern bool execfile(const char *cfgfile); +extern void resetcomplete(); +extern void complete(char *s); +extern void push(const char *name, const char *action); +extern void pop(const char *name); +extern void alias(const char *name, const char *action); +extern const char *getalias(const char *name); +extern void writecfg(); +extern void deletecfg(); +extern void identnames(vector &names, bool builtinonly); +extern void changescriptcontext(int newcontext); +extern void explodelist(const char *s, vector &elems); +extern char *indexlist(const char *s, int pos); +extern char *parseword(const char *&p); +extern void pushscontext(int newcontext); +extern int popscontext(); +extern int curscontext(); +extern int execcontext; + // server extern int modeacronyms; extern void servertoclient(int chan, uchar *buf, int len); diff --git a/source/src/server.cpp b/source/src/server.cpp index e38b77ac..ccd5e596 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -3414,6 +3414,16 @@ void localconnect() } #endif +void processmasterinput(const char *cmd, int cmdlen, const char *args) +{ +#if 0 + if(!strncmp(cmd, "cleargbans", cmdlen)) + cleargbans(); + else if(sscanf(cmd, "addgban %s", val) == 1) + addgban(val); +#endif +} + void initserver(bool dedicated) { srand(time(NULL)); diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index c53230ed..3f0efe3d 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -1270,6 +1270,62 @@ void clearservers() servers.deletecontents(); } +#define RETRIEVELIMIT 20000 + +void retrieveservers(vector &data) +{ + ENetSocket sock = connectmaster(); + if(sock == ENET_SOCKET_NULL) return; + + extern char *mastername; + defformatstring(text)("retrieving servers from %s... (esc to abort)", mastername); + show_out_of_renderloop_progress(0, text); + + int starttime = SDL_GetTicks(), timeout = 0; + const char *req = "list\n"; + int reqlen = strlen(req); + ENetBuffer buf; + while(reqlen > 0) + { + enet_uint32 events = ENET_SOCKET_WAIT_SEND; + if(enet_socket_wait(sock, &events, 250) >= 0 && events) + { + buf.data = (void *)req; + buf.dataLength = reqlen; + int sent = enet_socket_send(sock, NULL, &buf, 1); + if(sent < 0) break; + req += sent; + reqlen -= sent; + if(reqlen <= 0) break; + } + timeout = SDL_GetTicks() - starttime; + show_out_of_renderloop_progress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text); + if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1; + if(timeout > RETRIEVELIMIT) break; + } + + if(reqlen <= 0) for(;;) + { + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + if(enet_socket_wait(sock, &events, 250) >= 0 && events) + { + if(data.length() >= data.capacity()) data.reserve(4096); + buf.data = data.getbuf() + data.length(); + buf.dataLength = data.capacity() - data.length(); + int recv = enet_socket_receive(sock, NULL, &buf, 1); + if(recv <= 0) break; + data.advance(recv); + } + timeout = SDL_GetTicks() - starttime; + show_out_of_renderloop_progress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text); + if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1; + if(timeout > RETRIEVELIMIT) break; + } + + if(data.length()) data.add('\0'); + enet_socket_destroy(sock); +} + VARP(masterupdatefrequency, 1, 60*60, 24*60*60); extern int updatecmod(int millis, int type); @@ -1279,12 +1335,10 @@ void updatefrommaster(int force) static int lastupdate = 0; if(!force && lastupdate && totalmillis-lastupdate data; + retrieveservers(data); - if ( !updatecmod(totalmillis, UT_CLIENT) ) return; - - uchar *reply = retrieveservers(buf, sizeof(buf)); - if(!*reply || strstr((char *)reply, "") || strstr((char *)reply, "")) conoutf("master server not replying"); + if(data.empty()) conoutf("master server not replying"); else { // preserve currently connected server from deletion @@ -1298,7 +1352,7 @@ void updatefrommaster(int force) } clearservers(); - execute((char *)reply); + execute(data.getbuf()); if(curserver) addserver(curname, curport, curweight); lastupdate = totalmillis; diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index 3da048d9..18c06e28 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -17,158 +17,172 @@ int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &remot } #endif -ENetSocket httpgetsend(ENetAddress &remoteaddress, const char *hostname, const char *req, const char *ref, const char *agent, ENetAddress *localaddress = NULL) +ENetSocket mastersock = ENET_SOCKET_NULL; +ENetAddress masteraddress = { ENET_HOST_ANY, ENET_PORT_ANY }, serveraddress = { ENET_HOST_ANY, ENET_PORT_ANY }; +string mastername = AC_MASTER_URI; +int lastupdatemaster = 0; +vector masterout, masterin; +int masteroutpos = 0, masterinpos = 0; + +void disconnectmaster() { - if(remoteaddress.host==ENET_HOST_ANY) + if(mastersock == ENET_SOCKET_NULL) return; + + enet_socket_destroy(mastersock); + mastersock = ENET_SOCKET_NULL; + + masterout.setsize(0); + masterin.setsize(0); + masteroutpos = masterinpos = 0; + + masteraddress.host = ENET_HOST_ANY; + masteraddress.port = ENET_PORT_ANY; + + lastupdatemaster = 0; +} + +ENetSocket connectmaster() +{ + if(!mastername[0]) return ENET_SOCKET_NULL; + + if(masteraddress.host == ENET_HOST_ANY) { - logline(ACLOG_INFO, "looking up %s...", hostname); - if(!resolverwait(hostname, &remoteaddress)) return ENET_SOCKET_NULL; + logline(ACLOG_INFO, "looking up %s...", mastername); + masteraddress.port = AC_MASTER_PORT; + if(!resolverwait(mastername, &masteraddress)) return ENET_SOCKET_NULL; } ENetSocket sock = enet_socket_create(ENET_SOCKET_TYPE_STREAM); - if(sock!=ENET_SOCKET_NULL && localaddress && enet_socket_bind(sock, localaddress) < 0) + if(sock != ENET_SOCKET_NULL && serveraddress.host != ENET_HOST_ANY && enet_socket_bind(sock, &serveraddress) < 0) { enet_socket_destroy(sock); sock = ENET_SOCKET_NULL; } - if(sock==ENET_SOCKET_NULL || connectwithtimeout(sock, hostname, remoteaddress)<0) + if(sock == ENET_SOCKET_NULL || connectwithtimeout(sock, mastername, masteraddress) < 0) { logline(ACLOG_WARNING, sock==ENET_SOCKET_NULL ? "could not open socket" : "could not connect"); return ENET_SOCKET_NULL; } - ENetBuffer buf; - defformatstring(httpget)("GET %s HTTP/1.0\nHost: %s\nReferer: %s\nUser-Agent: %s\n\n", req, hostname, ref, agent); - buf.data = httpget; - buf.dataLength = strlen((char *)buf.data); - logline(ACLOG_INFO, "sending request to %s...", hostname); - enet_socket_send(sock, NULL, &buf, 1); + + enet_socket_set_option(sock, ENET_SOCKOPT_NONBLOCK, 1); return sock; } -bool httpgetreceive(ENetSocket sock, ENetBuffer &buf, int timeout = 0) +bool requestmaster(const char *req) { - if(sock==ENET_SOCKET_NULL) return false; - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - if(enet_socket_wait(sock, &events, timeout) >= 0 && events) + if(mastersock == ENET_SOCKET_NULL) { - int len = enet_socket_receive(sock, NULL, &buf, 1); - if(len<=0) - { - enet_socket_destroy(sock); - return false; - } - buf.data = ((char *)buf.data)+len; - ((char*)buf.data)[0] = 0; - buf.dataLength -= len; + mastersock = connectmaster(); + if(mastersock == ENET_SOCKET_NULL) return false; } + + masterout.put(req, strlen(req)); return true; } -uchar *stripheader(uchar *b) +bool requestmasterf(const char *fmt, ...) { - char *s = strstr((char *)b, "\n\r\n"); - if(!s) s = strstr((char *)b, "\n\n"); - return s ? (uchar *)s : b; + defvformatstring(req, fmt, fmt); + return requestmaster(req); } -ENetSocket mssock = ENET_SOCKET_NULL; -ENetAddress msaddress = { ENET_HOST_ANY, ENET_PORT_ANY }; -ENetAddress masterserver = { ENET_HOST_ANY, 80 }; -//int lastupdatemaster = 0; -string masterbase; -string masterpath; -uchar masterrep[MAXTRANS]; -ENetBuffer masterb; +extern void processmasterinput(const char *cmd, int cmdlen, const char *args); -extern void update_cmod_and_ms(int millis, int type); -//extern int updatecmod(int millis, int type); +void processmasterinput() +{ + if(masterinpos >= masterin.length()) return; + + char *input = &masterin[masterinpos], *end = (char *)memchr(input, '\n', masterin.length() - masterinpos); + while(end) + { + *end++ = '\0'; + + const char *args = input; + while(args < end && !isspace(*args)) args++; + int cmdlen = args - input; + while(args < end && isspace(*args)) args++; + + if(!strncmp(input, "failreg", cmdlen)) + logline(ACLOG_WARNING, "master server registration failed: %s", args); + else if(!strncmp(input, "succreg", cmdlen)) + logline(ACLOG_INFO, "master server registration succeeded"); + else processmasterinput(input, cmdlen, args); + + masterinpos = end - masterin.getbuf(); + input = end; + end = (char *)memchr(input, '\n', masterin.length() - masterinpos); + } + + if(masterinpos >= masterin.length()) + { + masterin.setsize(0); + masterinpos = 0; + } +} + +void flushmasteroutput() +{ + if(masterout.empty()) return; + + ENetBuffer buf; + buf.data = &masterout[masteroutpos]; + buf.dataLength = masterout.length() - masteroutpos; + int sent = enet_socket_send(mastersock, NULL, &buf, 1); + if(sent >= 0) + { + masteroutpos += sent; + if(masteroutpos >= masterout.length()) + { + masterout.setsize(0); + masteroutpos = 0; + } + } + else disconnectmaster(); +} + +void flushmasterinput() +{ + if(masterin.length() >= masterin.capacity()) + masterin.reserve(4096); + + ENetBuffer buf; + buf.data = &masterin[masterin.length()]; + buf.dataLength = masterin.capacity() - masterin.length(); + int recv = enet_socket_receive(mastersock, NULL, &buf, 1); + if(recv > 0) + { + masterin.advance(recv); + processmasterinput(); + } + else disconnectmaster(); +} // send alive signal to masterserver every hour of uptime -void updatemasterserver(int millis, const ENetAddress &localaddr) +static inline void updatemasterserver(int millis, int port) { - update_cmod_and_ms(millis, UT_SERVER); - -/* if( !millis || millis/(60*60*1000)!=lastupdatemaster ) + if(!lastupdatemaster || millis-lastupdatemaster>60*60*1000) { - if ( updatecmod(millis, UT_SERVER) ){ - defformatstring(path)("%sregister.do?action=add&port=%d", masterpath, localaddr.port); - defformatstring(agent)("AssaultCube Server %d", AC_VERSION); - mssock = httpgetsend(masterserver, masterbase, path, "assaultcubeserver", agent, &msaddress); - masterrep[0] = 0; - masterb.data = masterrep; - masterb.dataLength = MAXTRANS-1; - } - lastupdatemaster = millis/(60*60*1000); - }*/ -} - -void checkmasterreply() -{ - if(mssock!=ENET_SOCKET_NULL && !httpgetreceive(mssock, masterb)) - { - mssock = ENET_SOCKET_NULL; - string text; - filtertext(text, (const char *) stripheader(masterrep)); - logline(ACLOG_INFO, "masterserver reply: %s", text); + if(mastername[0]) requestmasterf("regserv %d\n", port); + lastupdatemaster = millis ? millis : 1; } } -#ifndef STANDALONE - -#define RETRIEVELIMIT 20000 - -uchar *retrieveservers(uchar *buf, int buflen) -{ - buf[0] = '\0'; - - defformatstring(path)("%sretrieve.do?item=list", masterpath); - defformatstring(agent)("AssaultCube Client %d", AC_VERSION); - ENetAddress address = masterserver; - ENetSocket sock = httpgetsend(address, masterbase, path, "assaultcubeclient", agent); - if(sock==ENET_SOCKET_NULL) return buf; - /* only cache this if connection succeeds */ - masterserver = address; - - defformatstring(text)("retrieving servers from %s... (esc to abort)", masterbase); - show_out_of_renderloop_progress(0, text); - - ENetBuffer eb; - eb.data = buf; - eb.dataLength = buflen-1; - - int starttime = SDL_GetTicks(), timeout = 0; - while(httpgetreceive(sock, eb, 250)) - { - timeout = SDL_GetTicks() - starttime; - show_out_of_renderloop_progress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text); - SDL_Event event; - while(SDL_PollEvent(&event)) - { - if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) timeout = RETRIEVELIMIT + 1; - } - if(timeout > RETRIEVELIMIT) - { - buf[0] = '\0'; - enet_socket_destroy(sock); - return buf; - } - } - - return stripheader(buf); -} -#endif - ENetSocket pongsock = ENET_SOCKET_NULL, lansock = ENET_SOCKET_NULL; extern int getpongflags(enet_uint32 ip); void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version) { - checkmasterreply(); - if(protocol_version > 0 || strncmp(masterpath, AC_MASTER_URI, 23)) updatemasterserver(millis, localaddr); + flushmasteroutput(); + updatemasterserver(millis, localaddr.port); static ENetSocketSet sockset; ENET_SOCKETSET_EMPTY(sockset); ENetSocket maxsock = pongsock; ENET_SOCKETSET_ADD(sockset, pongsock); + if(mastersock != ENET_SOCKET_NULL) + { + maxsock = max(maxsock, mastersock); + ENET_SOCKETSET_ADD(sockset, mastersock); + } if(lansock != ENET_SOCKET_NULL) { maxsock = max(maxsock, lansock); @@ -177,9 +191,9 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli if(enet_socketset_select(maxsock, &sockset, NULL, 0) <= 0) return; // reply all server info requests + static uchar data[MAXTRANS]; ENetBuffer buf; ENetAddress addr; - uchar data[MAXTRANS]; buf.data = data; int len; @@ -300,15 +314,15 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli if(std) *msend += (int)buf.dataLength; else *csend += (int)buf.dataLength; } + + if(mastersock != ENET_SOCKET_NULL && ENET_SOCKETSET_CHECK(sockset, mastersock)) flushmasterinput(); } // this function should be made better, because it is used just ONCE (no need of so much parameters) void servermsinit(const char *master, const char *ip, int infoport, bool listen) { - const char *mid = strstr(master, "/"); - if(mid) copystring(masterbase, master, mid-master+1); - else copystring(masterbase, (mid = master)); - copystring(masterpath, mid); + copystring(mastername, master); + disconnectmaster(); if(listen) { @@ -316,7 +330,7 @@ void servermsinit(const char *master, const char *ip, int infoport, bool listen) if(*ip) { if(enet_address_set_host(&address, ip)<0) logline(ACLOG_WARNING, "server ip not resolved"); - else msaddress.host = address.host; + else serveraddress.host = address.host; } pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0) diff --git a/source/src/tools.h b/source/src/tools.h index a8c33862..63d66d5a 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -361,6 +361,7 @@ template struct vector void drop() { buf[--ulen].~T(); } bool empty() const { return ulen==0; } + int capacity() const { return alen; } int length() const { return ulen; } T &operator[](int i) { ASSERT(i>=0 && i= 0 && i