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