// server.cpp: little more than enhanced multicaster // runs dedicated or as client coroutine #include "engine.hh" #define LOGSTRLEN 512 static FILE *logfile = NULL; void closelogfile() { if(logfile) { fclose(logfile); logfile = NULL; } } FILE *getlogfile() { #ifdef WIN32 return logfile; #else return logfile ? logfile : stdout; #endif } void setlogfile(const char *fname) { closelogfile(); if(fname && fname[0]) { fname = findfile(fname, "w"); if(fname) logfile = fopen(fname, "w"); } FILE *f = getlogfile(); if(f) setvbuf(f, NULL, _IOLBF, BUFSIZ); } void logoutf(const char *fmt, ...) { va_list args; va_start(args, fmt); logoutfv(fmt, args); va_end(args); } static void writelog(FILE *file, const char *buf) { static uchar ubuf[512]; size_t len = strlen(buf), carry = 0; while(carry < len) { size_t numu = encodeutf8(ubuf, sizeof(ubuf)-1, &((const uchar *)buf)[carry], len - carry, &carry); if(carry >= len) ubuf[numu++] = '\n'; fwrite(ubuf, 1, numu, file); } } static void writelogv(FILE *file, const char *fmt, va_list args) { static char buf[LOGSTRLEN]; vformatstring(buf, fmt, args, sizeof(buf)); writelog(file, buf); } #define DEFAULTCLIENTS 8 enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; struct client // server side version of "dynent" type { int type; int num; ENetPeer *peer; string hostname; void *info; }; vector clients; int laststatus = 0; int localclients = 0; bool hasnonlocalclients() { return false; } bool haslocalclients() { return localclients!=0; } client &addclient(int type) { client *c = NULL; loopv(clients) if(clients[i]->type==ST_EMPTY) { c = clients[i]; break; } if(!c) { c = new client; c->num = clients.length(); clients.add(c); } c->info = server::newclientinfo(); c->type = type; switch(type) { case ST_LOCAL: localclients++; break; } return *c; } void delclient(client *c) { if(!c) return; switch(c->type) { case ST_LOCAL: localclients--; break; case ST_EMPTY: return; } c->type = ST_EMPTY; c->peer = NULL; if(c->info) { server::deleteclientinfo(c->info); c->info = NULL; } } void cleanupserver() { } void process(ENetPacket *packet, int sender, int chan); //void disconnect_client(int n, int reason); int getservermtu() { return -1; } void *getclientinfo(int i) { return !clients.inrange(i) || clients[i]->type==ST_EMPTY ? NULL : clients[i]->info; } ENetPeer *getclientpeer(int i) { return NULL; } int getnumclients() { return clients.length(); } uint getclientip(int n) { return 0; } void sendpacket(int n, int chan, ENetPacket *packet, int exclude) { if(n<0) { loopv(clients) if(i!=exclude && server::allowbroadcast(i)) sendpacket(i, chan, packet); return; } switch(clients[n]->type) { case ST_LOCAL: localservertoclient(chan, packet); break; } } ENetPacket *sendf(int cn, int chan, const char *format, ...) { int exclude = -1; bool reliable = false; if(*format=='r') { reliable = true; ++format; } packetbuf p(MAXTRANS, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); va_list args; va_start(args, format); while(*format) switch(*format++) { case 'x': exclude = va_arg(args, int); break; case 'v': { int n = va_arg(args, int); int *v = va_arg(args, int *); loopi(n) putint(p, v[i]); break; } case 'i': { int n = isdigit(*format) ? *format++-'0' : 1; loopi(n) putint(p, va_arg(args, int)); break; } case 'f': { int n = isdigit(*format) ? *format++-'0' : 1; loopi(n) putfloat(p, (float)va_arg(args, double)); break; } case 's': sendstring(va_arg(args, const char *), p); break; case 'm': { int n = va_arg(args, int); p.put(va_arg(args, uchar *), n); break; } } va_end(args); ENetPacket *packet = p.finalize(); sendpacket(cn, chan, packet, exclude); return packet->referenceCount > 0 ? packet : NULL; } const char *disconnectreason(int reason) { switch(reason) { case DISC_EOP: return "end of packet"; case DISC_LOCAL: return "server is in local mode"; case DISC_KICK: return "kicked/banned"; case DISC_MSGERR: return "message error"; case DISC_IPBAN: return "ip is banned"; case DISC_PRIVATE: return "server is in private mode"; case DISC_MAXCLIENTS: return "server FULL"; case DISC_TIMEOUT: return "connection timed out"; case DISC_OVERFLOW: return "overflow"; case DISC_PASSWORD: return "invalid password"; default: return NULL; } } void disconnect_client(int n, int reason) { } void kicknonlocalclients(int reason) { } void process(ENetPacket *packet, int sender, int chan) // sender may be -1 { packetbuf p(packet); server::parsepacket(sender, chan, p); if(p.overread()) { disconnect_client(sender, DISC_EOP); return; } } void localclienttoserver(int chan, ENetPacket *packet) { client *c = NULL; loopv(clients) if(clients[i]->type==ST_LOCAL) { c = clients[i]; break; } if(c) process(packet, c->num, chan); } void updatetime() { } void localdisconnect(bool cleanup) { bool disconnected = false; loopv(clients) if(clients[i]->type==ST_LOCAL) { server::localdisconnect(i); delclient(clients[i]); disconnected = true; } if(!disconnected) return; game::gamedisconnect(cleanup); mainmenu = 1; } void localconnect() { if(initing) return; client &c = addclient(ST_LOCAL); copystring(c.hostname, "local"); game::gameconnect(false); server::localconnect(c.num); } void logoutfv(const char *fmt, va_list args) { FILE *f = getlogfile(); if(f) writelogv(f, fmt, args); } void initserver() { server::serverinit(); }