1 // client.cpp, mostly network related client game code
2 
3 #include "cube.h"
4 #include "bot/bot.h"
5 
6 VAR(connected, 1, 0, 0);
7 
8 ENetHost *clienthost = NULL;
9 ENetPeer *curpeer = NULL, *connpeer = NULL;
10 int connmillis = 0, connattempts = 0, discmillis = 0;
11 SVAR(curdemofile, "n/a");
12 extern bool clfail, cllock;
13 extern int searchlan;
14 
getclientnum()15 int getclientnum() { return player1 ? player1->clientnum : -1; }
16 
multiplayer(bool msg)17 bool multiplayer(bool msg)
18 {
19     // check not correct on listen server?
20     if(curpeer && msg) conoutf(_("operation not available in multiplayer"));
21     return curpeer!=NULL;
22 }
23 
allowedittoggle()24 bool allowedittoggle()
25 {
26     bool allow = !curpeer || gamemode==1;
27     if(!allow) conoutf(_("editing in multiplayer requires coopedit mode (1)"));
28     return allow;
29 }
30 
31 void throttle();
32 
33 VARF(throttle_interval, 0, 5, 30, throttle());
34 VARF(throttle_accel,    0, 2, 32, throttle());
35 VARF(throttle_decel,    0, 2, 32, throttle());
36 
throttle()37 void throttle()
38 {
39     if(!curpeer) return;
40     ASSERT(ENET_PEER_PACKET_THROTTLE_SCALE==32);
41     enet_peer_throttle_configure(curpeer, throttle_interval*1000, throttle_accel, throttle_decel);
42 }
43 
44 string clientpassword = "";
45 int connectrole = CR_DEFAULT;
46 bool modprotocol = false;
47 
abortconnect()48 void abortconnect()
49 {
50     if(!connpeer) return;
51     clientpassword[0] = '\0';
52     connectrole = CR_DEFAULT;
53     if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer);
54     connpeer = NULL;
55 #if 0
56     if(!curpeer)
57     {
58         enet_host_destroy(clienthost);
59         clienthost = NULL;
60     }
61 #endif
62 }
63 
connectserv_(const char * servername,int serverport=0,const char * password=NULL,int role=CR_DEFAULT)64 void connectserv_(const char *servername, int serverport = 0, const char *password = NULL, int role = CR_DEFAULT)
65 {
66     if(serverport <= 0) serverport = CUBE_DEFAULT_SERVER_PORT;
67     if(watchingdemo) enddemoplayback();
68     if(!clfail && cllock && searchlan<2) return;
69 
70     if(connpeer)
71     {
72         conoutf(_("aborting connection attempt"));
73         abortconnect();
74     }
75     connectrole = role;
76     copystring(clientpassword, password ? password : "");
77     ENetAddress address;
78     address.port = serverport;
79 
80     if(servername)
81     {
82         addserver(servername, serverport, 0);
83         conoutf(_("%c2attempting to %sconnect to %c5%s%c4:%d%c2"), CC, role==CR_DEFAULT?"":"\f8admin\f2", CC, servername, CC, serverport, CC);
84         if(!resolverwait(servername, &address))
85         {
86             conoutf(_("%c2could %c3not resolve%c2 server %c5%s%c2"), CC, CC, CC, CC, servername, CC);
87             clientpassword[0] = '\0';
88             connectrole = CR_DEFAULT;
89             return;
90         }
91     }
92     else
93     {
94         conoutf(_("%c2attempting to connect over %c1LAN%c2"), CC, CC, CC);
95         address.host = ENET_HOST_BROADCAST;
96     }
97 
98     if(!clienthost)
99         clienthost = enet_host_create(NULL, 2, 3, 0, 0);
100 
101     if(clienthost)
102     {
103         connpeer = enet_host_connect(clienthost, &address, 3, 0);
104         enet_host_flush(clienthost);
105         connmillis = totalmillis;
106         connattempts = 0;
107         if(!m_mp(gamemode)) gamemode = GMODE_TEAMDEATHMATCH;
108     }
109     else
110     {
111         conoutf(_("%c2could %c3not connect%c2 to server"),CC,CC,CC);
112         clientpassword[0] = '\0';
113         connectrole = CR_DEFAULT;
114     }
115 }
116 
connectserv(char * servername,int * serverport,char * password)117 void connectserv(char *servername, int *serverport, char *password)
118 {
119     modprotocol = false;
120     connectserv_(servername, *serverport, password);
121 }
122 
connectadmin(char * servername,int * serverport,char * password)123 void connectadmin(char *servername, int *serverport, char *password)
124 {
125     modprotocol = false;
126     if(!password[0]) return;
127     connectserv_(servername, *serverport, password, CR_ADMIN);
128 }
129 
lanconnect()130 void lanconnect()
131 {
132     modprotocol = false;
133     connectserv_(NULL);
134 }
135 
modconnectserv(char * servername,int * serverport,char * password)136 void modconnectserv(char *servername, int *serverport, char *password)
137 {
138     modprotocol = true;
139     connectserv_(servername, *serverport, password);
140 }
141 
modconnectadmin(char * servername,int * serverport,char * password)142 void modconnectadmin(char *servername, int *serverport, char *password)
143 {
144     modprotocol = true;
145     if(!password[0]) return;
146     connectserv_(servername, *serverport, password, CR_ADMIN);
147 }
148 
modlanconnect()149 void modlanconnect()
150 {
151     modprotocol = true;
152     connectserv_(NULL);
153 }
154 
whereami()155 void whereami()
156 {
157     conoutf("you are at (%.2f,%.2f)", player1->o.x, player1->o.y);
158 }
159 
go_to(float * x,float * y,char * showmsg)160 void go_to(float *x, float *y, char *showmsg)
161 {
162     if(player1->state != CS_EDITING) return;
163     player1->newpos.x = *x;
164     player1->newpos.y = *y;
165     if(!showmsg || !*showmsg || strcmp(showmsg, "0")!=0)
166         conoutf("you are going to (%.2f; %.2f)", *x, *y);
167 }
168 
disconnect(int onlyclean,int async)169 void disconnect(int onlyclean, int async)
170 {
171     bool cleanup = onlyclean!=0;
172     if(curpeer)
173     {
174         if(!discmillis)
175         {
176             enet_peer_disconnect(curpeer, DISC_NONE);
177             enet_host_flush(clienthost);
178             discmillis = totalmillis;
179         }
180         if(curpeer->state!=ENET_PEER_STATE_DISCONNECTED)
181         {
182             if(async) return;
183             enet_peer_reset(curpeer);
184         }
185         curpeer = NULL;
186         discmillis = 0;
187         connected = 0;
188         conoutf(_("disconnected"));
189         cleanup = true;
190     }
191 
192     if(cleanup)
193     {
194         player1->clientnum = -1;
195         player1->lifesequence = 0;
196         player1->clientrole = CR_DEFAULT;
197         kickallbots();
198         loopv(players) zapplayer(players[i]);
199         clearvote();
200         audiomgr.clearworldsounds(false);
201         localdisconnect();
202     }
203 #if 0
204     if(!connpeer && clienthost)
205     {
206         enet_host_destroy(clienthost);
207         clienthost = NULL;
208     }
209 #endif
210     if(!onlyclean) localconnect();
211     if(identexists("onDisconnect"))
212     {
213         defformatstring(ondisconnect)("onDisconnect %d", -1);
214         execute(ondisconnect);
215     }}
216 
trydisconnect()217 void trydisconnect()
218 {
219     if(connpeer)
220     {
221         conoutf(_("aborting connection attempt"));
222         abortconnect();
223         return;
224     }
225     if(!curpeer)
226     {
227         conoutf(_("not connected"));
228         return;
229     }
230     conoutf(_("attempting to disconnect..."));
231     disconnect(0, !discmillis);
232 }
233 
_toserver(char * text,int msg,int msgt)234 void _toserver(char *text, int msg, int msgt)
235 {
236     bool toteam = text && text[0] == '%' && (m_teammode || team_isspect(player1->team));
237     if(!toteam && text[0] == '%' && strlen(text) > 1) text++; // convert team-text to normal-text if no team-mode is active
238     if(toteam) text++;
239     filtertext(text, text);
240     trimtrailingwhitespace(text);
241     if(servstate.mastermode == MM_MATCH && servstate.matchteamsize && !team_isactive(player1->team) && !(player1->team == TEAM_SPECT && player1->clientrole == CR_ADMIN)) toteam = true; // spect chat
242     if(*text)
243     {
244         if(msg == SV_TEXTME) conoutf("\f%d%s %s", toteam ? 1 : 0, colorname(player1), highlight(text));
245         else conoutf("%s:\f%d %s", colorname(player1), toteam ? 1 : 0, highlight(text));
246         addmsg(toteam ? msgt : msg, "rs", text);
247     }
248 }
249 
toserver(char * text)250 void toserver(char *text)
251 {
252     _toserver(text, SV_TEXT, SV_TEAMTEXT);
253 }
254 
toserverme(char * text)255 void toserverme(char *text)
256 {
257     _toserver(text, SV_TEXTME, SV_TEAMTEXTME);
258 }
259 
echo(char * text)260 void echo(char *text)
261 {
262     const char *s = strtok(text, "\n");
263     do
264     {
265         conoutf("%s", s ? s : "");
266         s = strtok(NULL, "\n");
267     }
268     while(s);
269 }
270 
271 VARP(allowhudechos, 0, 1, 1);
hudecho(char * text)272 void hudecho(char *text)
273 {
274     const char *s = strtok(text, "\n");
275     void (*outf)(const char *s, ...) = allowhudechos ? hudoutf : conoutf;
276     do
277     {
278         outf("%s", s ? s : "");
279         s = strtok(NULL, "\n");
280     }
281     while(s);
282 }
283 
pm(char * text)284 void pm(char *text)
285 {
286     if(!text || !text[0]) return;
287     int cn = -1;
288     char digit;
289     while ((digit = *text++) != '\0')
290     {
291         if (digit < '0' || digit > '9') break;
292         if(cn < 0) cn = 0;
293         else cn *= 10;
294         cn += digit - '0';
295     }
296     playerent *to = getclient(cn);
297     if(!to)
298     {
299         conoutf("invalid client number specified");
300         return;
301     }
302 
303     if(!isspace(digit)) { --text; }
304 
305     // FIXME:
306     /*if(!text || !text[0] || !isdigit(text[0])) return;
307     int cn = -1;
308     char *numend = strpbrk(text, " \t");
309     if(!numend) return;
310     string cnbuf;
311     copystring(cnbuf, text, min(numend-text+1, MAXSTRLEN));
312     cn = atoi(cnbuf);
313     playerent *to = getclient(cn);
314     if(!to)
315     {
316         conoutf("invalid client number specified");
317         return;
318     }
319 
320     if(*numend) numend++;*/
321     // :FIXME
322 
323     filtertext(text, text);
324     trimtrailingwhitespace(text);
325 
326     addmsg(SV_TEXTPRIVATE, "ris", cn, text);
327     conoutf("to %s:\f9 %s", colorname(to), highlight(text));
328 }
329 COMMAND(pm, "c");
330 
331 COMMAND(echo, "c");
332 COMMAND(hudecho, "c");
333 COMMANDN(say, toserver, "c");
334 COMMANDN(me, toserverme, "c");
335 COMMANDN(connect, connectserv, "sis");
336 COMMAND(connectadmin, "sis");
337 COMMAND(lanconnect, "");
338 COMMANDN(modconnect, modconnectserv, "sis");
339 COMMAND(modconnectadmin, "sis");
340 COMMAND(modlanconnect, "");
341 COMMANDN(disconnect, trydisconnect, "");
342 COMMAND(whereami, "");
343 COMMAND(go_to, "ffs");
344 
current_version(char * text)345 void current_version(char *text)
346 {
347     int version = atoi(text);
348     if (version && AC_VERSION<version) conoutf("YOUR VERSION OF ASSAULTCUBE IS OUTDATED!\nYOU MUST UPDATE ASSAULTCUBE\nplease visit %s for more information",AC_MASTER_URI);
349 }
350 COMMAND(current_version, "s");
351 
cleanupclient()352 void cleanupclient()
353 {
354     abortconnect();
355     disconnect(1);
356     if(clienthost)
357     {
358         enet_host_destroy(clienthost);
359         clienthost = NULL;
360     }
361 }
362 
363 // collect c2s messages conveniently
364 
365 vector<uchar> messages;
366 
addmsg(int type,const char * fmt,...)367 void addmsg(int type, const char *fmt, ...)
368 {
369     static uchar buf[MAXTRANS];
370     ucharbuf p(buf, MAXTRANS);
371     putint(p, type);
372     int numi = 1, nums = 0;
373     bool reliable = false;
374     if(fmt)
375     {
376         va_list args;
377         va_start(args, fmt);
378         while(*fmt) switch(*fmt++)
379         {
380             case 'r': reliable = true; break;
381             case 'v':
382             {
383                 int n = va_arg(args, int);
384                 int *v = va_arg(args, int *);
385                 loopi(n) putint(p, v[i]);
386                 numi += n;
387                 break;
388             }
389 
390             case 'i':
391             {
392                 int n = isdigit(*fmt) ? *fmt++-'0' : 1;
393                 loopi(n) putint(p, va_arg(args, int));
394                 numi += n;
395                 break;
396             }
397             case 's':
398             {
399                 const char *t = va_arg(args, const char *);
400                 if(t) sendstring(t, p); nums++; break;
401             }
402         }
403         va_end(args);
404     }
405     int num = nums?0:numi, msgsize = msgsizelookup(type);
406     if(msgsize && num!=msgsize) { fatal("inconsistent msg size for %d (%d != %d)", type, num, msgsize); }
407     int len = p.length();
408     messages.add(len&0xFF);
409     messages.add((len>>8)|(reliable ? 0x80 : 0));
410     loopi(len) messages.add(buf[i]);
411 }
412 
413 static int lastupdate = -1000, lastping = 0;
414 bool sendmapidenttoserver = false;
415 
sendpackettoserv(int chan,ENetPacket * packet)416 void sendpackettoserv(int chan, ENetPacket *packet)
417 {
418     if(curpeer) enet_peer_send(curpeer, chan, packet);
419     else localclienttoserver(chan, packet);
420 }
421 
c2skeepalive()422 void c2skeepalive()
423 {
424     if(clienthost && (curpeer || connpeer)) enet_host_service(clienthost, NULL, 0);
425 }
426 
427 extern string masterpwd;
428 bool sv_pos = true;
429 
c2sinfo(playerent * d)430 void c2sinfo(playerent *d)                  // send update to the server
431 {
432     if(d->clientnum<0) return;              // we haven't had a welcome message from the server yet
433     if(totalmillis-lastupdate<40) return;    // don't update faster than 25fps
434 
435     if(d->state==CS_ALIVE || d->state==CS_EDITING)
436     {
437         packetbuf q(100);
438         int cn = d->clientnum,
439             x = (int)(d->o.x*DMF),          // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes
440             y = (int)(d->o.y*DMF),
441             z = (int)((d->o.z - d->eyeheight)*DMF),
442             ya = (int)((512 * d->yaw) / 360.0f),
443             pi = (int)((127 * d->pitch) / 90.0f),
444             r = (int)(31*d->roll/20),
445             dxt = (int)(d->vel.x*DVELF),
446             dyt = (int)(d->vel.y*DVELF),
447             dzt = (int)(d->vel.z*DVELF),
448             dx = dxt - d->vel_t.i[0],
449             dy = dyt - d->vel_t.i[1],
450             dz = dzt - d->vel_t.i[2],
451             // pack rest in 1 int: strafe:2, move:2, onfloor:1, onladder: 1
452             f = (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | (((int)d->onladder)<<5) | ((d->lifesequence&1)<<6) | (((int)d->crouching)<<7),
453             g = (dx?1:0) | ((dy?1:0)<<1) | ((dz?1:0)<<2) | ((r?1:0)<<3) | (((int)d->scoping)<<4) | (((int)d->shoot)<<5);
454             d->vel_t.i[0] = dxt;
455             d->vel_t.i[1] = dyt;
456             d->vel_t.i[2] = dzt;
457         int usefactor = sfactor < 7 ? 7 : sfactor, sizexy = 1 << (usefactor + 4);
458         if(cn >= 0 && cn < 32 &&
459             usefactor <= 7 + 3 &&       // map size 7..10
460             x >= 0 && x < sizexy &&
461             y >= 0 && y < sizexy &&
462             z >= -2047 && z <= 2047 &&
463             ya >= 0 && ya < 512 &&
464             pi >= -128 && pi <= 127 &&
465             r >= -32 && r <= 31 &&
466             dx >= -8 && dx <= 7 && // FIXME
467             dy >= -8 && dy <= 7 &&
468             dz >= -8 && dz <= 7)
469         { // compact POS packet
470             bool noroll = !r, novel = !dx && !dy && !dz;
471             bitbuf<packetbuf> b(q);
472             putint(q, SV_POSC);
473             b.putbits(5, cn);
474             b.putbits(2, usefactor - 7);
475             b.putbits(usefactor + 4, x);
476             b.putbits(usefactor + 4, y);
477             b.putbits(9, ya);
478             b.putbits(8, pi + 128);
479             b.putbits(1, noroll ? 1 : 0);
480             if(!noroll) b.putbits(6, r + 32);
481             b.putbits(1, novel ? 1 : 0);
482             if(!novel)
483             {
484                 b.putbits(4, dx + 8);
485                 b.putbits(4, dy + 8);
486                 b.putbits(4, dz + 8);
487             }
488             b.putbits(8, f);
489             b.putbits(1, z < 0 ? 1 : 0);
490             if(z < 0) z = -z;                 // z is encoded with 3..10 bits minimum (fitted to byte boundaries), or full 11 bits if necessary
491             int s = (b.rembits() - 1 + 8) % 8;
492             if(s < 3) s += 8;
493             if(z >= (1 << s)) s = 11;
494             b.putbits(1, s == 11 ? 1 : 0);
495             b.putbits(s, z);
496             b.putbits(1, d->scoping ? 1 : 0);
497             b.putbits(1, d->shoot ? 1 : 0);
498         }
499         else
500         { // classic POS packet
501             putint(q, SV_POS);
502             putint(q, d->clientnum);
503             putuint(q, x);
504             putuint(q, y);
505             putuint(q, z);
506             putuint(q, (int)d->yaw);
507             putint(q, (int)d->pitch);
508             putuint(q, g);
509             if (r) putint(q, (int)(125*d->roll/20));
510             if (dx) putint(q, dx);
511             if (dy) putint(q, dy);
512             if (dz) putint(q, dz);
513             putuint(q, f);
514         }
515         sendpackettoserv(0, q.finalize());
516         d->shoot = false;
517     }
518 
519     if(sendmapidenttoserver || messages.length() || totalmillis-lastping>250)
520     {
521         packetbuf p(MAXTRANS);
522 
523         if(sendmapidenttoserver) // new map
524         {
525             p.reliable();
526             putint(p, SV_MAPIDENT);
527             putint(p, maploaded);
528             putint(p, hdr.maprevision);
529             sendmapidenttoserver = false;
530         }
531         int i = 0;
532         while(i < messages.length()) // send messages collected during the previous frames
533         {
534             int len = messages[i] | ((messages[i+1]&0x7F)<<8);
535             if(p.remaining() < len) break;
536             if(messages[i+1]&0x80) p.reliable();
537             p.put(&messages[i+2], len);
538             i += 2 + len;
539         }
540         messages.remove(0, i);
541         if(totalmillis-lastping>250)
542         {
543             putint(p, SV_PING);
544             putint(p, totalmillis);
545             lastping = totalmillis;
546         }
547         if(p.length()) sendpackettoserv(1, p.finalize());
548     }
549 
550     if(clienthost) enet_host_flush(clienthost);
551     lastupdate = totalmillis;
552 }
553 
getbuildtype()554 int getbuildtype()
555 {
556     return (isbigendian() ? 0x80 : 0 )|(adler((unsigned char *)guns, sizeof(guns)) % 31 << 8)|
557         #ifdef WIN32
558             0x40 |
559         #endif
560         #ifdef __APPLE__
561             0x20 |
562         #endif
563         #ifdef _DEBUG
564             0x08 |
565         #endif
566         #ifdef __GNUC__
567             0x04 |
568         #endif
569             0;
570 }
571 
sendintro()572 void sendintro()
573 {
574     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
575     putint(p, SV_CONNECT);
576     putint(p, AC_VERSION);
577     putint(p, getbuildtype());
578     sendstring(player1->name, p);
579     sendstring(genpwdhash(player1->name, clientpassword, sessionid), p);
580     sendstring(!lang || strlen(lang) != 2 ? "" : lang, p);
581     putint(p, connectrole);
582     clientpassword[0] = '\0';
583     connectrole = CR_DEFAULT;
584     putint(p, player1->nextprimweap->type);
585     loopi(2) putint(p, player1->skin(i));
586     sendpackettoserv(1, p.finalize());
587 }
588 
gets2c()589 void gets2c()           // get updates from the server
590 {
591     ENetEvent event;
592     if(!clienthost || (!curpeer && !connpeer)) return;
593     if(connpeer && totalmillis/3000 > connmillis/3000)
594     {
595         conoutf(_("attempting to connect..."));
596         connmillis = totalmillis;
597         ++connattempts;
598         if(connattempts > 3)
599         {
600             conoutf(_("%c3could not connect to server"), CC);
601             abortconnect();
602             return;
603         }
604     }
605     while(clienthost!=NULL && enet_host_service(clienthost, &event, 0)>0)
606     switch(event.type)
607     {
608         case ENET_EVENT_TYPE_CONNECT:
609             disconnect(1);
610             curpeer = connpeer;
611             connpeer = NULL;
612             connected = 1;
613             conoutf(_("connected to server"));
614             if(identexists("onConnect"))
615             {
616                 defformatstring(onconnect)("onConnect %d", -1);
617                 execute(onconnect);
618             }
619             throttle();
620             if(editmode) toggleedit(true);
621             break;
622 
623         case ENET_EVENT_TYPE_RECEIVE:
624         {
625             extern packetqueue pktlogger;
626             pktlogger.queue(event.packet);
627 
628             if(discmillis) conoutf(_("attempting to disconnect..."));
629             else servertoclient(event.channelID, event.packet->data, (int)event.packet->dataLength);
630             // destroyed in logger
631             //enet_packet_destroy(event.packet);
632             break;
633         }
634 
635         case ENET_EVENT_TYPE_DISCONNECT:
636         {
637             extern const char *disc_reason(int reason);
638             if(event.data>=DISC_NUM) event.data = DISC_NONE;
639             if(event.peer==connpeer)
640             {
641                 conoutf(_("%c3could not connect to server"), CC);
642                 abortconnect();
643             }
644             else
645             {
646                 if(!discmillis || event.data) conoutf(_("%c3server network error, disconnecting (%s) ..."), CC, disc_reason(event.data));
647                 disconnect();
648             }
649             return;
650         }
651 
652         default:
653             break;
654     }
655 }
656 
657 // for AUTH:
658 
659 vector<authkey *> authkeys;
660 
661 VARP(autoauth, 0, 1, 1);
662 
findauthkey(const char * desc)663 authkey *findauthkey(const char *desc)
664 {
665     loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, player1->name)) return authkeys[i];
666     loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc)) return authkeys[i];
667     return NULL;
668 }
669 
addauthkey(const char * name,const char * key,const char * desc)670 void addauthkey(const char *name, const char *key, const char *desc)
671 {
672     loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) delete authkeys.remove(i);
673     if(name[0] && key[0]) authkeys.add(new authkey(name, key, desc));
674 }
675 
_hasauthkey(const char * name,const char * desc)676 bool _hasauthkey(const char *name, const char *desc)
677 {
678     if(!name[0] && !desc[0]) return authkeys.length() > 0;
679     loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) return true;
680     return false;
681 }
682 
genauthkey(const char * secret)683 void genauthkey(const char *secret)
684 {
685     if(!secret[0]) { conoutf("you must specify a secret password"); return; }
686     vector<char> privkey, pubkey;
687     genprivkey(secret, privkey, pubkey);
688     conoutf("private key: %s", privkey.getbuf());
689     conoutf("public key: %s", pubkey.getbuf());
690 }
691 
saveauthkeys()692 void saveauthkeys()
693 {
694     if(authkeys.length())
695     {
696         stream *f = openfile("config/auth.cfg", "w");
697         if(!f) { conoutf("failed to open config/auth.cfg for writing"); return; }
698         loopv(authkeys)
699         {
700             authkey *a = authkeys[i];
701             f->printf("authkey \"%s\" \"%s\" \"%s\"\n", a->name, a->key, a->desc);
702         }
703         conoutf("saved authkeys to config/auth.cfg");
704         delete f;
705     }
706     else conoutf("you need to use 'addauthkey USER KEY DESC' first; one DESC 'public', one DESC empty(=private)");
707 }
708 
tryauth(const char * desc)709 bool tryauth(const char *desc)
710 {
711     authkey *a = findauthkey(desc);
712     if(!a) return false;
713     a->lastauth = lastmillis;
714     addmsg(SV_AUTHTRY, "rss", a->desc, a->name);
715     return true;
716 }
717 
718 COMMANDN(authkey, addauthkey, "sss");
719 COMMANDF(hasauthkey, "ss", (char *name, char *desc) { intret(_hasauthkey(name, desc) ? 1 : 0); });
720 COMMAND(genauthkey, "s");
721 COMMAND(saveauthkeys, "");
722 COMMANDF(auth, "s", (char *desc) { intret(tryauth(desc)); });
723 
724 // :for AUTH
725 
726 // sendmap/getmap commands, should be replaced by more intuitive map downloading
727 
728 vector<char *> securemaps;
729 
resetsecuremaps()730 void resetsecuremaps() { securemaps.deletearrays(); }
securemap(char * map)731 void securemap(char *map) { if(map) securemaps.add(newstring(map)); }
securemapcheck(const char * map,bool msg)732 bool securemapcheck(const char *map, bool msg)
733 {
734     if(strstr(map, "maps/")==map || strstr(map, "maps\\")==map) map += strlen("maps/");
735     loopv(securemaps) if(!strcmp(securemaps[i], map))
736     {
737         if(msg)
738         {
739             conoutf(_("%c3map %c4%s%c3 is secured, this means you CAN'T send, receive or overwrite it"), CC, CC, map, CC);
740             if(connected)
741             {
742                 conoutf(_("%c3if you get this error alot you or the server may be running an outdated game"), CC);
743                 conoutf(_("%c3you may check for updates at %c1http://assault.cubers.net/download.html"), CC, CC);
744             }
745             return true;
746         }
747     }
748     return false;
749 }
750 
sendmap(char * mapname)751 void sendmap(char *mapname)
752 {
753     if(!*mapname) mapname = getclientmap();
754     if(securemapcheck(mapname)) return;
755     if(gamemode == GMODE_COOPEDIT && !strcmp(getclientmap(), mapname)) save_world(mapname);
756 
757     int mapsize, cfgsize, cfgsizegz, revision;
758     uchar *mapdata = readmap(path(mapname), &mapsize, &revision);
759     if(!mapdata) return;
760     uchar *cfgdata = readmcfggz(path(mapname), &cfgsize, &cfgsizegz);
761     if(!cfgdata) { cfgsize = 0; cfgsizegz = 0; }
762 
763     packetbuf p(MAXTRANS + mapsize + cfgsizegz, ENET_PACKET_FLAG_RELIABLE);
764 
765     putint(p, SV_SENDMAP);
766     sendstring(mapname, p);
767     putint(p, mapsize);
768     putint(p, cfgsize);
769     putint(p, cfgsizegz);
770     putint(p, revision);
771     if(MAXMAPSENDSIZE - p.length() < mapsize + cfgsizegz || cfgsize > MAXCFGFILESIZE)
772     {
773         conoutf(_("map %s is too large to send"), mapname);
774         delete[] mapdata;
775         if(cfgsize) delete[] cfgdata;
776         return;
777     }
778     p.put(mapdata, mapsize);
779     delete[] mapdata;
780     if(cfgsizegz)
781     {
782         p.put(cfgdata, cfgsizegz);
783         delete[] cfgdata;
784     }
785 
786     sendpackettoserv(2, p.finalize());
787     conoutf(_("sending map %s to server..."), mapname);
788 }
789 
getmap(char * name,char * callback)790 void getmap(char *name, char *callback)
791 {
792     if((!name || !*name)
793         || (connected && !strcmp(name, getclientmap())) )
794     {
795         conoutf(_("requesting map from server..."));
796         packetbuf p(10, ENET_PACKET_FLAG_RELIABLE);
797         putint(p, SV_RECVMAP);
798         sendpackettoserv(2, p.finalize());
799     }
800     else
801     {
802         defformatstring(package)("packages/maps/%s.cgz", name);
803         requirepackage(PCK_MAP, package);
804         if(downloadpackages())
805         {
806             if(callback && *callback) execute(callback);
807             conoutf("map %s installed successfully", name);
808         }
809         else conoutf("\f3map download failed.");
810     }
811 }
812 
deleteservermap(char * mapname)813 void deleteservermap(char *mapname)
814 {
815     const char *name = behindpath(mapname);
816     if(!*name || securemapcheck(name)) return;
817     addmsg(SV_REMOVEMAP, "rs", name);
818 }
819 
820 string demosubpath;
getdemo(int * idx,char * dsp)821 void getdemo(int *idx, char *dsp)
822 {
823     if(dsp && dsp[0]) formatstring(demosubpath)("%s/", dsp);
824     else copystring(demosubpath, "");
825     if(*idx<=0) conoutf(_("getting demo..."));
826     else conoutf(_("getting demo %d..."), *idx);
827     addmsg(SV_GETDEMO, "ri", *idx);
828 }
829 
listdemos()830 void listdemos()
831 {
832     conoutf(_("listing demos..."));
833     addmsg(SV_LISTDEMOS, "r");
834 }
835 
shiftgametime(int newmillis)836 void shiftgametime(int newmillis)
837 {
838     newmillis = max(0, newmillis);
839     int gamemillis = gametimecurrent + (lastmillis - lastgametimeupdate);
840 
841     if(!watchingdemo) { conoutf("You have to be watching a demo to change game time"); return; }
842     if(newmillis > gametimemaximum) { conoutf("Invalid time specified"); return; }
843 
844     if(newmillis < gamemillis)
845     {
846         // if rewinding
847         if(!curdemofile || !curdemofile[0]) return;
848         watchingdemo = false;
849         callvote(SA_MAP, curdemofile, "-1");
850         nextmillis = newmillis;
851     }
852     else
853     {
854         nextmillis = newmillis - gamemillis;
855     }
856 }
857 
setminutesremaining(int * minutes)858 void setminutesremaining(int *minutes)
859 {
860     shiftgametime(gametimemaximum - (*minutes)*60000);
861 }
862 
rewinddemo(int * seconds)863 void rewinddemo(int *seconds)
864 {
865     int gamemillis = gametimecurrent+(lastmillis-lastgametimeupdate);
866     shiftgametime(gamemillis - (*seconds)*1000);
867 }
868 
869 COMMAND(sendmap, "s");
870 COMMAND(getmap, "ss");
871 COMMAND(deleteservermap, "s");
872 COMMAND(resetsecuremaps, "");
873 COMMAND(securemap, "s");
874 COMMAND(getdemo, "is");
875 COMMAND(listdemos, "");
876 COMMANDN(setmr, setminutesremaining, "i");
877 COMMANDN(rewind, rewinddemo, "i");
878 
879 // packages auto - downloader
880 
881 // arrays
882 vector<pckserver *> pckservers;
883 hashtable<const char *, package *> pendingpackages;
884 
885 // cubescript
886 VARP(autodownload, 0, 1, 1);
887 
resetpckservers()888 void resetpckservers()
889 {
890     pckservers.deletecontents();
891 }
892 
addpckserver(char * addr)893 void addpckserver(char *addr)
894 {
895     pckserver *srcserver = new pckserver();
896     srcserver->addr = newstring(addr);
897     srcserver->pending = true;
898     pckservers.add(srcserver);
899 }
900 
901 COMMAND(resetpckservers, "");
902 COMMAND(addpckserver, "s");
903 
904 // cURL / Network
905 
906 bool havecurl = false, canceldownloads = false;
907 
setupcurl()908 void setupcurl()
909 {
910     if(curl_global_init(CURL_GLOBAL_NOTHING)) conoutf(_("\f3could not init cURL, content downloads not available"));
911     else
912     {
913         havecurl = true;
914         execfile("config/pcksources.cfg");
915     }
916 }
917 
918 // detect the "nearest" server
pckserversort(pckserver ** a,pckserver ** b)919 int pckserversort(pckserver **a, pckserver **b)
920 {
921     if((*a)->ping < 0) return ((*b)->ping < 0) ? 0 : 1;
922     if((*b)->ping < 0) return -1;
923 
924     return (*a)->ping == (*b)->ping ? 0 : ((*a)->ping < (*b)->ping ? -1 : 1);
925 }
926 
927 SDL_Thread* pingpcksrvthread = NULL;
928 SDL_mutex *pingpcksrvlock = NULL;
pingpckservers(void * data)929 int pingpckservers(void *data)
930 {
931     SDL_mutexP(pingpcksrvlock);
932     vector<pckserver> serverstoping;
933     loopv(pckservers) serverstoping.add(*pckservers[i]);
934     SDL_mutexV(pingpcksrvlock);
935     // measure the time it took to receive each's server response
936     // not very accurate but it should do the trick
937     loopv(serverstoping)
938     {
939         double ping = 0, namelookup = 0;
940         pckserver *serv = &serverstoping[i];
941         CURL *cu = curl_easy_init();
942         curl_easy_setopt(cu, CURLOPT_URL, serv->addr);
943         curl_easy_setopt(cu, CURLOPT_NOSIGNAL, 1);	    // Fixes crashbug for some buggy libcurl versions (Linux)
944         curl_easy_setopt(cu, CURLOPT_NOPROGRESS, 1);
945         curl_easy_setopt(cu, CURLOPT_NOBODY, 1);            // don't download response body (as its size may vary a lot from one server to another)
946         curl_easy_setopt(cu, CURLOPT_CONNECTTIMEOUT, 10);   // the timeout should be large here
947         int result = curl_easy_perform(cu);
948         curl_easy_getinfo(cu, CURLINFO_TOTAL_TIME, &ping);
949         curl_easy_getinfo(cu, CURLINFO_NAMELOOKUP_TIME, &namelookup);
950         ping -= namelookup; // ignore DNS lookup time as it should be performed only once
951         curl_easy_cleanup(cu);
952         cu = NULL;
953         if(result == CURLE_OPERATION_TIMEDOUT || result == CURLE_COULDNT_RESOLVE_HOST)
954             serv->responsive = false;
955         else
956             serv->ping = (int)(ping*1000);
957     }
958 
959     SDL_mutexP(pingpcksrvlock);
960     loopv(serverstoping) loopvj(pckservers) if(!strcmp(serverstoping[i].addr, pckservers[j]->addr)) *pckservers[j] = serverstoping[i];
961     pckservers.sort(pckserversort);
962     // print the results. we should make it silent once tested enough
963     loopv(pckservers)
964     {
965         pckserver *serv = pckservers[i];
966         if(serv->ping > 0) conoutf("%d. %s (%d ms)", i+1, serv->addr, serv->ping);
967         else conoutf("%d. %s (did not reply)", i+1, serv->addr);
968     }
969     SDL_mutexV(pingpcksrvlock);
970 
971     SDL_DestroyMutex(pingpcksrvlock);
972     pingpcksrvthread = NULL;
973     pingpcksrvlock = NULL;
974     return 0;
975 }
976 
sortpckservers()977 void sortpckservers()
978 {
979     if(pingpcksrvthread || !havecurl) return;
980     pingpcksrvlock = SDL_CreateMutex();
981     pingpcksrvthread = SDL_CreateThread(pingpckservers, NULL);
982 }
983 COMMAND(sortpckservers, "");
984 
write_callback(void * ptr,size_t size,size_t nmemb,FILE * stream)985 static size_t write_callback(void *ptr, size_t size, size_t nmemb, FILE *stream)
986 {
987     return fwrite(ptr, size, nmemb, stream);
988 }
989 
progress_callback(void * clientp,double dltotal,double dlnow,double ultotal,double ulnow)990 static int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
991 {
992     package *pck = (package *)clientp;
993     loadingscreen(_("downloading package %d out of %d...\n%s %.0f/%.0f KB (%.1f%%)\n(ESC to cancel)"), pck->number + 1, pck->number + pendingpackages.numelems,
994         pck->name, dlnow/double(1000.0), dltotal/double(1000.0), dltotal == 0 ? 0 : (dlnow/dltotal * double(100.0)));
995     if(interceptkey(SDLK_ESCAPE))
996     {
997         canceldownloads = true;
998         loadingscreen();
999         return 1;
1000     }
1001     return 0;
1002 }
1003 
processdownload(package * pck)1004 int processdownload(package *pck)
1005 {
1006     string tmpname = "";
1007     copystring(tmpname, findfile(path("tmp", true), "rb"));
1008     if(!pck->pending)
1009     {
1010         switch(pck->type)
1011         {
1012             case PCK_TEXTURE: case PCK_AUDIO:
1013             {
1014                 const char *pckname = findfile(path(pck->name, true), "w+");
1015                 preparedir(pckname);
1016                 // with textures/sounds, the image/audio file itself is sent. Just need to copy it from the temporary file
1017                 if(!copyfile(tmpname, pckname)) conoutf(_("\f3failed to install"), pckname);
1018                 break;
1019             }
1020 
1021             case PCK_MAP: case PCK_MAPMODEL:
1022             {
1023                 addzip(tmpname, pck->name, NULL, true, pck->type);
1024                 break;
1025             }
1026 
1027             case PCK_SKYBOX:
1028             {
1029                 char *fname = newstring(pck->name), *ls = strrchr(fname, '/');
1030                 if(ls) *ls = '\0';
1031                 addzip(tmpname, fname, NULL, true, pck->type);
1032                 break;
1033             }
1034 
1035             default:
1036                 conoutf(_("could not install package %s"), pck->name);
1037                 break;
1038         }
1039         delfile(tmpname);
1040     }
1041     return 0;
1042 }
1043 
1044 // download a package
dlpackage(package * pck)1045 double dlpackage(package *pck)
1046 {
1047     if(!pck || !pck->source) return false;
1048     const char *tmpname = findfile(path("tmp", true), "wb");
1049     FILE *outfile = fopen(tmpname, "wb");
1050     string req, pckname = "";
1051     sprintf(req, "%s/%s%s", pck->source->addr, strreplace(pckname, pck->name, " ", "%20"), (pck->type==PCK_MAP || pck->type==PCK_MAPMODEL || pck->type==PCK_SKYBOX) ? ".zip" : "");
1052     conoutf(_("downloading %s from %s ..."), pck->name, pck->source->addr);
1053     if(!outfile)
1054     {
1055         pck->pending = false;
1056         conoutf("\f3failed to write temporary file \"%s\"", tmpname);
1057         return 0;
1058     }
1059 
1060     int result, httpresult = 0;
1061     double dlsize;
1062     pck->curl = curl_easy_init();
1063     curl_easy_setopt(pck->curl, CURLOPT_URL, req);
1064     curl_easy_setopt(pck->curl, CURLOPT_NOSIGNAL, 1);		 // Fixes crashbug for some buggy libcurl versions (Linux)
1065     curl_easy_setopt(pck->curl, CURLOPT_WRITEFUNCTION, write_callback);
1066     curl_easy_setopt(pck->curl, CURLOPT_WRITEDATA, outfile);
1067     curl_easy_setopt(pck->curl, CURLOPT_NOPROGRESS, 0);
1068     curl_easy_setopt(pck->curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
1069     curl_easy_setopt(pck->curl, CURLOPT_PROGRESSDATA, pck);
1070     curl_easy_setopt(pck->curl, CURLOPT_CONNECTTIMEOUT, 10);     // generous timeout for Bukz ;)
1071     result = curl_easy_perform(pck->curl);
1072     curl_easy_getinfo(pck->curl, CURLINFO_RESPONSE_CODE, &httpresult);
1073     curl_easy_getinfo(pck->curl, CURLINFO_SIZE_DOWNLOAD, &dlsize);
1074     curl_easy_cleanup(pck->curl);
1075     pck->curl = NULL;
1076     fclose(outfile);
1077 
1078     pck->pending = false;
1079     if(result == CURLE_OPERATION_TIMEDOUT || result == CURLE_COULDNT_RESOLVE_HOST)
1080     {
1081         // mark source unresponsive
1082         pck->source->responsive = false;
1083         conoutf(_("\f3could not connect to %s"), pck->source->addr);
1084 
1085         // try to find another source
1086         pckserver *source = NULL;
1087         loopv(pckservers) if(pckservers[i]->responsive) { source = pckservers[i]; break; }
1088         if(!source)
1089         {
1090             conoutf(_("\f3no more servers to connect to"));
1091             canceldownloads = true;
1092             return 0;
1093         }
1094 
1095         // update all packages
1096         enumerate(pendingpackages, package *, pack,
1097         {
1098             pack->source = source;
1099         });
1100 
1101         pck->pending = true;
1102 
1103         return dlpackage(pck); // retry current
1104     }
1105     if(!result && httpresult == 200) processdownload(pck);
1106     else if(result == CURLE_ABORTED_BY_CALLBACK) conoutf(_("\f3download cancelled"));
1107     else conoutf(_("\f2request for %s failed (cURL %d, HTTP %d)"), req, result, httpresult);
1108     return (!result && httpresult == 200) ? dlsize : 0;
1109 }
1110 
1111 int lastpackagesdownload = 0;
downloadpackages()1112 int downloadpackages()
1113 {
1114     if(!pendingpackages.numelems) return 0;
1115     else if(totalmillis - lastpackagesdownload < 2000)
1116     {
1117         conoutf("\f3quick download attempt aborted");
1118         return 0;
1119     }
1120 
1121     double total = 0;
1122     int downloaded = 0;
1123     enumerate(pendingpackages, package *, pck,
1124     {
1125         if(!canceldownloads)
1126         {
1127             if(connected) c2skeepalive(); // try to avoid time out
1128             pck->number = downloaded++;
1129             total += dlpackage(pck);
1130         }
1131         pendingpackages.remove(pck->name);
1132         delete pck;
1133     });
1134     canceldownloads = false;
1135     lastpackagesdownload = lastmillis;
1136     return (int)total;
1137 }
1138 
requirepackage(int type,const char * path)1139 bool requirepackage(int type, const char *path)
1140 {
1141     if(!havecurl || canceldownloads || type < 0 || type >= PCK_NUM || pendingpackages.access(path)) return false;
1142 
1143     package *pck = new package;
1144     pck->name = unixpath(newstring(path));
1145     pck->type = type;
1146     loopv(pckservers) if(pckservers[i]->responsive) { pck->source = pckservers[i]; break; }
1147     if(!pck->source) { conoutf(_("\f3no responsive source server found, can't download")); return false; }
1148     pck->pending = true;
1149 
1150     pendingpackages.access(pck->name, pck);
1151 
1152     return true;
1153 }
1154 
1155 
writepcksourcecfg()1156 void writepcksourcecfg()
1157 {
1158     stream *f = openfile(path("config/pcksources.cfg", true), "w");
1159     if(!f) return;
1160     f->printf("// list of package source servers (only add servers you trust!)\n");
1161     loopv(pckservers) f->printf("\naddpckserver %s", pckservers[i]->addr);
1162     f->printf("\n");
1163     delete f;
1164 }
1165