1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20 // cl_parse.c -- parse a message received from the server
21
22 #include "client.h"
23
24 int serverPacketCount;
25 int noFrameFromServerPacket;
26
27 void CL_Reconnect_f (void);
28
29 //=============================================================================
30
CL_DownloadFileName(char * dest,int destlen,char * fn)31 void CL_DownloadFileName(char *dest, int destlen, char *fn)
32 {
33 //if (strncmp(fn, "players", 7) == 0)
34 // Com_sprintf (dest, destlen, "%s/%s", BASEDIRNAME, fn);
35 //else
36 Com_sprintf (dest, destlen, "%s/%s", FS_Gamedir(), fn);
37 }
38
CL_FinishDownload(void)39 void CL_FinishDownload (void)
40 {
41 #ifdef _DEBUG
42 clientinfo_t *ci;
43 #endif
44
45 int r;
46 char oldn[MAX_OSPATH];
47 char newn[MAX_OSPATH];
48
49 fclose (cls.download);
50
51 FS_FlushCache();
52
53 // rename the temp file to it's final name
54 CL_DownloadFileName(oldn, sizeof(oldn), cls.downloadtempname);
55 CL_DownloadFileName(newn, sizeof(newn), cls.downloadname);
56
57 r = rename (oldn, newn);
58 if (r)
59 Com_Printf ("failed to rename.\n", LOG_CLIENT);
60
61 #ifdef _DEBUG
62 if (cls.serverProtocol == PROTOCOL_R1Q2 && (strstr(newn, "players"))) {
63 for (r = 0; r < cl.maxclients; r++) {
64 ci = &cl.clientinfo[r];
65 if (ci->deferred)
66 CL_ParseClientinfo (r);
67 }
68 }
69 #endif
70
71 cls.failed_download = false;
72 cls.downloadpending = false;
73 cls.downloadname[0] = 0;
74 cls.downloadposition = 0;
75 cls.download = NULL;
76 cls.downloadpercent = 0;
77 }
78
79 /*
80 ===============
81 CL_CheckOrDownloadFile
82
83 Returns true if the file exists, otherwise it attempts
84 to start a download from the server.
85 ===============
86 */
CL_CheckOrDownloadFile(const char * filename)87 qboolean CL_CheckOrDownloadFile (const char *filename)
88 {
89 FILE *fp;
90 int length;
91 char *p;
92 char name[MAX_OSPATH];
93 static char lastfilename[MAX_OSPATH] = {0};
94
95 //r1: don't attempt same file many times
96 if (!strcmp (filename, lastfilename))
97 return true;
98
99 strcpy (lastfilename, filename);
100
101 if (strstr (filename, ".."))
102 {
103 Com_Printf ("Refusing to check a path with .. (%s)\n", LOG_CLIENT, filename);
104 return true;
105 }
106
107 if (strchr (filename, ' '))
108 {
109 Com_Printf ("Refusing to check a path containing spaces (%s)\n", LOG_CLIENT, filename);
110 return true;
111 }
112
113 if (strchr (filename, ':'))
114 {
115 Com_Printf ("Refusing to check a path containing a colon (%s)\n", LOG_CLIENT, filename);
116 return true;
117 }
118
119 if (filename[0] == '/')
120 {
121 Com_Printf ("Refusing to check a path starting with / (%s)\n", LOG_CLIENT, filename);
122 return true;
123 }
124
125 if (FS_LoadFile (filename, NULL) != -1)
126 {
127 // it exists, no need to download
128 return true;
129 }
130
131 #ifdef USE_CURL
132 if (CL_QueueHTTPDownload (filename))
133 {
134 //we return true so that the precache check keeps feeding us more files.
135 //since we have multiple HTTP connections we want to minimize latency
136 //and be constantly sending requests, not one at a time.
137 return true;
138 }
139 else
140 #endif
141 {
142 strcpy (cls.downloadname, filename);
143
144 //r1: fix \ to /
145 p = cls.downloadname;
146 while ((p = strchr(p, '\\')))
147 p[0] = '/';
148
149 length = (int)strlen(cls.downloadname);
150
151 //normalize path
152 p = cls.downloadname;
153 while ((p = strstr (p, "./")))
154 {
155 memmove (p, p+2, length - (p - cls.downloadname) - 1);
156 length -= 2;
157 }
158
159 //r1: verify we are giving the server a legal path
160 if (cls.downloadname[length-1] == '/')
161 {
162 Com_Printf ("Refusing to download bad path (%s)\n", LOG_CLIENT, filename);
163 return true;
164 }
165
166 // download to a temp name, and only rename
167 // to the real name when done, so if interrupted
168 // a runt file wont be left
169 COM_StripExtension (cls.downloadname, cls.downloadtempname);
170 strcat (cls.downloadtempname, ".tmp");
171
172 //ZOID
173 // check to see if we already have a tmp for this file, if so, try to resume
174 // open the file if not opened yet
175 CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
176
177 // FS_CreatePath (name);
178
179 fp = fopen (name, "r+b");
180 if (fp)
181 {
182 // it exists
183 int len;
184
185 fseek(fp, 0, SEEK_END);
186 len = ftell(fp);
187
188 cls.download = fp;
189
190 // give the server an offset to start the download
191 Com_Printf ("Resuming %s\n", LOG_CLIENT, cls.downloadname);
192
193 MSG_WriteByte (clc_stringcmd);
194 if (cls.serverProtocol == PROTOCOL_R1Q2)
195 MSG_WriteString (va("download \"%s\" %i udp-zlib", cls.downloadname, len));
196 else
197 MSG_WriteString (va("download \"%s\" %i", cls.downloadname, len));
198 }
199 else
200 {
201 Com_Printf ("Downloading %s\n", LOG_CLIENT, cls.downloadname);
202
203 MSG_WriteByte (clc_stringcmd);
204 if (cls.serverProtocol == PROTOCOL_R1Q2)
205 MSG_WriteString (va("download \"%s\" 0 udp-zlib", cls.downloadname));
206 else
207 MSG_WriteString (va("download \"%s\"", cls.downloadname));
208 }
209
210 MSG_EndWriting (&cls.netchan.message);
211
212 send_packet_now = true;
213 cls.downloadpending = true;
214
215 return false;
216 }
217 }
218
219 /*
220 ===============
221 CL_Download_f
222
223 Request a download from the server
224 ===============
225 */
CL_Download_f(void)226 void CL_Download_f (void)
227 {
228 //char name[MAX_OSPATH];
229 //FILE *fp;
230 // char *p;
231 char *filename;
232
233 if (Cmd_Argc() != 2) {
234 Com_Printf("Usage: download <filename>\n", LOG_CLIENT);
235 return;
236 }
237
238 if (cls.state < ca_connected)
239 {
240 Com_Printf ("Not connected.\n", LOG_CLIENT);
241 return;
242 }
243
244 //Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1));
245 filename = Cmd_Argv(1);
246
247 if (FS_LoadFile (filename, NULL) != -1)
248 {
249 // it exists, no need to download
250 Com_Printf("File already exists.\n", LOG_CLIENT);
251 return;
252 }
253
254 CL_CheckOrDownloadFile (filename);
255
256 /*if (strstr (filename, ".."))
257 {
258 Com_Printf ("Refusing to download a path with .. (%s)\n", LOG_CLIENT, filename);
259 return;
260 }
261
262 if (FS_LoadFile (filename, NULL) != -1)
263 { // it exists, no need to download
264 Com_Printf("File already exists.\n", LOG_CLIENT);
265 return;
266 }
267
268 strncpy (cls.downloadname, filename, sizeof(cls.downloadname)-1);
269
270
271 // download to a temp name, and only rename
272 // to the real name when done, so if interrupted
273 // a runt file wont be left
274 COM_StripExtension (cls.downloadname, cls.downloadtempname);
275 strcat (cls.downloadtempname, ".tmp");
276
277 //ZOID
278 // check to see if we already have a tmp for this file, if so, try to resume
279 // open the file if not opened yet
280 CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
281
282 fp = fopen (name, "r+b");
283 if (fp) { // it exists
284 int len;
285
286 fseek(fp, 0, SEEK_END);
287 len = ftell(fp);
288
289 cls.download = fp;
290
291 // give the server an offset to start the download
292 Com_Printf ("Resuming %s\n", LOG_CLIENT, cls.downloadname);
293 MSG_WriteByte (clc_stringcmd);
294 if (cls.serverProtocol == PROTOCOL_R1Q2) {
295 MSG_WriteString (va("download \"%s\" %i udp-zlib", cls.downloadname, len));
296 } else {
297 MSG_WriteString (va("download \"%s\" %i", cls.downloadname, len));
298 }
299 } else {
300 Com_Printf ("Downloading %s\n", LOG_CLIENT, cls.downloadname);
301
302 MSG_WriteByte (clc_stringcmd);
303 if (cls.serverProtocol == PROTOCOL_R1Q2) {
304 MSG_WriteString (va("download \"%s\" 0 udp-zlib", cls.downloadname));
305 } else {
306 MSG_WriteString (va("download \"%s\" 0", cls.downloadname));
307 }
308 }
309 MSG_EndWriting (&cls.netchan.message);
310
311 send_packet_now = true;*/
312 }
313
CL_Passive_f(void)314 void CL_Passive_f (void)
315 {
316 if (cls.state != ca_disconnected) {
317 Com_Printf ("Passive mode can only be modified when you are disconnected.\n", LOG_CLIENT);
318 } else {
319 cls.passivemode = !cls.passivemode;
320
321 if (cls.passivemode) {
322 NET_Config (NET_CLIENT);
323 Com_Printf ("Listening for passive connections on port %d\n", LOG_CLIENT, Cvar_IntValue ("ip_clientport"));
324 } else {
325 Com_Printf ("No longer listening for passive connections.\n", LOG_CLIENT);
326 }
327 }
328 }
329
330 /*
331 ======================
332 CL_RegisterSounds
333 ======================
334 */
CL_RegisterSounds(void)335 void CL_RegisterSounds (void)
336 {
337 int i;
338
339 S_BeginRegistration ();
340 CL_RegisterTEntSounds ();
341 for (i=1 ; i<MAX_SOUNDS ; i++)
342 {
343 if (!cl.configstrings[CS_SOUNDS+i][0])
344 break;
345 cl.sound_precache[i] = S_RegisterSound (cl.configstrings[CS_SOUNDS+i]);
346 Sys_SendKeyEvents (); // pump message loop
347 }
348 S_EndRegistration ();
349 }
350
351 /*
352 =====================
353 CL_ParseDownload
354
355 A download message has been received from the server
356 =====================
357 */
358
CL_ParseDownload(qboolean dataIsCompressed)359 void CL_ParseDownload (qboolean dataIsCompressed)
360 {
361 int size, percent;
362 char name[MAX_OSPATH];
363
364 // read the data
365 size = MSG_ReadShort (&net_message);
366 percent = MSG_ReadByte (&net_message);
367
368 if (size < 0)
369 {
370 if (size == -1)
371 Com_Printf ("Server does not have this file.\n", LOG_CLIENT);
372 else
373 Com_Printf ("Bad download data from server.\n", LOG_CLIENT);
374
375 //r1: nuke the temp filename
376 cls.downloadtempname[0] = 0;
377 cls.downloadname[0] = 0;
378 cls.failed_download = true;
379
380 if (cls.download)
381 {
382 // if here, we tried to resume a file but the server said no
383 fclose (cls.download);
384 cls.download = NULL;
385 }
386
387 cls.downloadpending = false;
388 CL_RequestNextDownload ();
389 return;
390 }
391
392 // open the file if not opened yet
393 if (!cls.download)
394 {
395 if (!cls.downloadtempname[0])
396 {
397 Com_Printf ("Received download packet without request. Ignored.\n", LOG_CLIENT);
398 net_message.readcount += size;
399 return;
400 }
401 CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
402
403 FS_CreatePath (name);
404
405 cls.download = fopen (name, "wb");
406 if (!cls.download)
407 {
408 net_message.readcount += size;
409 Com_Printf ("Failed to open %s\n", LOG_CLIENT, cls.downloadtempname);
410 cls.downloadpending = false;
411 CL_RequestNextDownload ();
412 return;
413 }
414 }
415
416 //r1: downloading something, drop to console to show status bar
417 SCR_EndLoadingPlaque();
418
419 //r1: if we're stuck with udp, may as well make best use of the bandwidth...
420 if (dataIsCompressed)
421 {
422 #ifndef NO_ZLIB
423 uint16 uncompressedLen;
424 byte uncompressed[0xFFFF];
425
426 uncompressedLen = MSG_ReadShort (&net_message);
427
428 if (!uncompressedLen)
429 Com_Error (ERR_DROP, "uncompressedLen == 0");
430
431 ZLibDecompress (net_message_buffer + net_message.readcount, size, uncompressed, uncompressedLen, -15);
432 fwrite (uncompressed, 1, uncompressedLen, cls.download);
433 Com_DPrintf ("svc_zdownload(%s): %d -> %d\n", cls.downloadname, size, uncompressedLen);
434 #else
435 Com_Error (ERR_DROP, "Received a unrequested compressed download");
436 #endif
437 }
438 else
439 {
440 fwrite (net_message_buffer + net_message.readcount, 1, size, cls.download);
441 }
442
443 net_message.readcount += size;
444
445 if (percent != 100)
446 {
447 cls.downloadpercent = percent;
448
449 MSG_WriteByte (clc_stringcmd);
450 MSG_Print ("nextdl");
451 MSG_EndWriting (&cls.netchan.message);
452 send_packet_now = true;
453 }
454 else
455 {
456 CL_FinishDownload ();
457
458 // get another file if needed
459 CL_RequestNextDownload ();
460 }
461 }
462
463
464 /*
465 =====================================================================
466
467 SERVER CONNECTING MESSAGES
468
469 =====================================================================
470 */
471
472 /*
473 ==================
474 CL_ParseServerData
475 ==================
476 */
CL_ParseServerData(void)477 qboolean CL_ParseServerData (void)
478 {
479 char *str;
480 int i;
481 int newVersion;
482 cvar_t *gameDirHack;
483 //
484 // wipe the client_state_t struct
485 //
486 CL_ClearState ();
487 cls.state = ca_connected;
488
489 // parse protocol version number
490 i = MSG_ReadLong (&net_message);
491 cls.serverProtocol = i;
492
493 cl.servercount = MSG_ReadLong (&net_message);
494 cl.attractloop = MSG_ReadByte (&net_message);
495
496 if (i != PROTOCOL_ORIGINAL && i != PROTOCOL_R1Q2 && !cl.attractloop)
497 Com_Error (ERR_HARD, "Server is using unknown protocol %d.", i);
498
499 // game directory
500 str = MSG_ReadString (&net_message);
501 strncpy (cl.gamedir, str, sizeof(cl.gamedir)-1);
502
503 // set gamedir, fucking christ this is messy!
504 if ((str[0] && (!fs_gamedirvar->string || !fs_gamedirvar->string[0] || strcmp(fs_gamedirvar->string, str))) ||
505 (!str[0] && (fs_gamedirvar->string || fs_gamedirvar->string[0])))
506 {
507 if (strcmp(fs_gamedirvar->string, str))
508 {
509 if (cl.attractloop)
510 {
511 Cvar_ForceSet ("game", str);
512 FS_SetGamedir (str);
513 }
514 else
515 {
516 Cvar_Set("game", str);
517 }
518 }
519 }
520
521 Cvar_ForceSet ("$game", str);
522
523 gameDirHack = Cvar_FindVar ("game");
524 gameDirHack->flags |= CVAR_NOSET;
525
526 // parse player entity number
527 cl.playernum = MSG_ReadShort (&net_message);
528
529 // get the full level name
530 str = MSG_ReadString (&net_message);
531
532 if (cls.serverProtocol == PROTOCOL_R1Q2)
533 {
534 cl.enhancedServer = MSG_ReadByte (&net_message);
535
536 newVersion = MSG_ReadShort (&net_message);
537 if (newVersion != MINOR_VERSION_R1Q2)
538 {
539 if (cl.attractloop)
540 {
541 if (newVersion < MINOR_VERSION_R1Q2)
542 Com_Printf ("This demo was recorded with an earlier version of the R1Q2 protocol. It may not play back properly.\n", LOG_CLIENT);
543 else
544 Com_Printf ("This demo was recorded with a later version of the R1Q2 protocol. It may not play back properly. Please update your R1Q2 client.\n", LOG_CLIENT);
545 }
546 else
547 {
548 if (newVersion > MINOR_VERSION_R1Q2)
549 Com_Printf ("Server reports a higher R1Q2 protocol number than your client supports. Some features will be unavailable until you update your R1Q2 client.\n", LOG_CLIENT);
550 else
551 Com_Printf ("Server reports a lower R1Q2 protocol number. The server admin needs to update their server!\n", LOG_CLIENT);
552 }
553
554 //cap if server is above us just to be safe
555 if (newVersion > MINOR_VERSION_R1Q2)
556 newVersion = MINOR_VERSION_R1Q2;
557 }
558
559 if (newVersion >= 1903)
560 {
561 MSG_ReadByte (&net_message); //was ad
562 cl.strafeHack = MSG_ReadByte (&net_message);
563 }
564 else
565 {
566 cl.strafeHack = false;
567 }
568
569 cls.protocolVersion = newVersion;
570 }
571 else
572 {
573 cl.enhancedServer = false;
574 cl.strafeHack = false;
575 cls.protocolVersion = 0;
576 }
577
578 Com_DPrintf ("Serverdata packet received. protocol=%d, servercount=%d, attractloop=%d, clnum=%d, game=%s, map=%s, enhanced=%d\n", cls.serverProtocol, cl.servercount, cl.attractloop, cl.playernum, cl.gamedir, str, cl.enhancedServer);
579
580 if (cl.playernum == -1)
581 { // playing a cinematic or showing a pic, not a level
582 //SCR_PlayCinematic (str);
583 // tell the server to advance to the next map / cinematic
584 MSG_WriteByte (clc_stringcmd);
585 MSG_Print (va("nextserver %i\n", cl.servercount));
586 MSG_EndWriting (&cls.netchan.message);
587 }
588 else
589 {
590 // seperate the printfs so the server message can have a color
591 Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n", LOG_CLIENT);
592 Com_Printf ("\2%s\n", LOG_CLIENT, str);
593
594 // need to prep refresh at next oportunity
595 cl.refresh_prepped = false;
596 }
597
598 //CL_FixCvarCheats();
599 return true;
600 }
601 /*
602 ==================
603 CL_ParseBaseline
604 ==================
605 */
CL_ParseBaseline(void)606 void CL_ParseBaseline (void)
607 {
608 entity_state_t *es;
609 uint32 bits;
610 int newnum;
611
612
613 newnum = CL_ParseEntityBits (&bits);
614 es = &cl_entities[newnum].baseline;
615 CL_ParseDelta (&null_entity_state, es, newnum, bits);
616 }
617
CL_ParseZPacket(void)618 void CL_ParseZPacket (void)
619 {
620 #ifndef NO_ZLIB
621 byte buff_in[MAX_MSGLEN];
622 byte buff_out[0xFFFF];
623
624 sizebuf_t sb, old;
625
626 int16 compressed_len = MSG_ReadShort (&net_message);
627 int16 uncompressed_len = MSG_ReadShort (&net_message);
628
629 if (uncompressed_len <= 0)
630 Com_Error (ERR_DROP, "CL_ParseZPacket: uncompressed_len <= 0");
631
632 if (compressed_len <= 0)
633 Com_Error (ERR_DROP, "CL_ParseZPacket: compressed_len <= 0");
634
635 MSG_ReadData (&net_message, buff_in, compressed_len);
636
637 SZ_Init (&sb, buff_out, uncompressed_len);
638 sb.cursize = ZLibDecompress (buff_in, compressed_len, buff_out, uncompressed_len, -15);
639
640 old = net_message;
641 net_message = sb;
642 CL_ParseServerMessage ();
643 net_message = old;
644
645 Com_DPrintf ("Got a ZPacket, %d->%d\n", uncompressed_len + 4, compressed_len);
646 #else
647 Com_Error (ERR_DROP, "Receied a zPacket but no zlib in this binary");
648 #endif
649 }
650
651
652 /*
653 ================
654 CL_LoadClientinfo
655
656 ================
657 */
CL_LoadClientinfo(clientinfo_t * ci,char * s)658 void CL_LoadClientinfo (clientinfo_t *ci, char *s)
659 {
660 int i;
661 char *t;
662 //char original_model_name[MAX_QPATH];
663 //char original_skin_name[MAX_QPATH];
664
665 char model_name[MAX_QPATH];
666 char skin_name[MAX_QPATH];
667 char model_filename[MAX_QPATH];
668 char skin_filename[MAX_QPATH];
669 char weapon_filename[MAX_QPATH];
670
671 Q_strncpy(ci->cinfo, s, sizeof(ci->cinfo)-1);
672
673 ci->deferred = false;
674
675 // isolate the player's name
676 Q_strncpy(ci->name, s, sizeof(ci->name)-1);
677
678 i = 0;
679
680 t = strchr (s, '\\');
681 if (t)
682 {
683 if (t - s >= sizeof(ci->name)-1)
684 {
685 i = -1;
686 }
687 else
688 {
689 ci->name[t-s] = 0;
690 s = t+1;
691 }
692 }
693
694 //r1ch: check sanity of paths: only allow printable data
695 t = s;
696 while (*t)
697 {
698 //if (!isprint (*t))
699 if (*t <= 32)
700 {
701 i = -1;
702 break;
703 }
704 t++;
705 }
706
707 if (cl_noskins->intvalue || s[0] == 0 || i == -1)
708 {
709 badskin:
710 //strcpy (model_filename, "players/male/tris.md2");
711 //strcpy (weapon_filename, "players/male/weapon.md2");
712 //strcpy (skin_filename, "players/male/grunt.pcx");
713 strcpy (ci->iconname, "/players/male/grunt_i.pcx");
714 strcpy (model_name, "male");
715 ci->model = re.RegisterModel ("players/male/tris.md2");
716 //memset(ci->weaponmodel, 0, sizeof(ci->weaponmodel));
717 //ci->weaponmodel[0] = re.RegisterModel (weapon_filename);
718 ci->skin = re.RegisterSkin ("players/male/grunt.pcx");
719 ci->icon = re.RegisterPic (ci->iconname);
720 }
721 else
722 {
723 int length;
724 int j;
725
726 Q_strncpy (model_name, s, sizeof(model_name)-1);
727
728 t = strchr(model_name, '/');
729 if (!t)
730 t = strchr(model_name, '\\');
731
732 if (!t)
733 {
734 memcpy (model_name, "male\0grunt\0\0\0\0\0\0", 16);
735 s = "male\0grunt";
736 }
737 else
738 {
739 t[0] = 0;
740 }
741
742 //strcpy (original_model_name, model_name);
743
744 // isolate the skin name
745 Q_strncpy (skin_name, s + strlen(model_name) + 1, sizeof(skin_name)-1);
746 //strcpy (original_skin_name, s + strlen(model_name) + 1);
747
748 length = (int)strlen (model_name);
749 for (j = 0; j < length; j++)
750 {
751 if (!isvalidchar(model_name[j]))
752 {
753 Com_DPrintf ("Bad character '%c' in playermodel '%s'\n", model_name[j], model_name);
754 goto badskin;
755 }
756 }
757
758 length = (int)strlen (skin_name);
759 for (j = 0; j < length; j++)
760 {
761 if (!isvalidchar(skin_name[j]))
762 {
763 Com_DPrintf ("Bad character '%c' in playerskin '%s'\n", skin_name[j], skin_name);
764 goto badskin;
765 }
766 }
767
768 // model file
769 Com_sprintf (model_filename, sizeof(model_filename), "players/%s/tris.md2", model_name);
770 ci->model = re.RegisterModel (model_filename);
771 if (!ci->model)
772 {
773 ci->deferred = true;
774 //if (!CL_CheckOrDownloadFile (model_filename))
775 // return;
776
777 strcpy(model_name, "male");
778 //Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
779 strcpy (model_filename, "players/male/tris.md2");
780 ci->model = re.RegisterModel (model_filename);
781 }
782
783 // skin file
784 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name);
785 ci->skin = re.RegisterSkin (skin_filename);
786
787 if (!ci->skin)
788 {
789 //Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", original_model_name, original_skin_name);
790 ci->deferred = true;
791 //CL_CheckOrDownloadFile (skin_filename);
792 }
793
794 // if we don't have the skin and the model wasn't male,
795 // see if the male has it (this is for CTF's skins)
796 if (!ci->skin && Q_stricmp(model_name, "male"))
797 {
798 // change model to male
799 strcpy(model_name, "male");
800 strcpy (model_filename, "players/male/tris.md2");
801 ci->model = re.RegisterModel (model_filename);
802
803 // see if the skin exists for the male model
804 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name);
805 ci->skin = re.RegisterSkin (skin_filename);
806 }
807
808 // if we still don't have a skin, it means that the male model didn't have
809 // it, so default to grunt
810 if (!ci->skin) {
811 // see if the skin exists for the male model
812 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/grunt.pcx", model_name);
813 ci->skin = re.RegisterSkin (skin_filename);
814 }
815
816 // icon file
817 Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/%s/%s_i.pcx", model_name, skin_name);
818 ci->icon = re.RegisterPic (ci->iconname);
819
820 if (!ci->icon) {
821 //Com_sprintf (ci->iconname, sizeof(ci->iconname), "players/%s/%s_i.pcx", original_model_name, original_skin_name);
822 ci->deferred = true;
823 //ci->icon = re.RegisterPic ("/players/male/grunt_i.pcx");
824 }
825 }
826
827 // weapon file
828 for (i = 0; i < num_cl_weaponmodels; i++)
829 {
830 Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/%s/%s", model_name, cl_weaponmodels[i]);
831 ci->weaponmodel[i] = re.RegisterModel(weapon_filename);
832
833 if (!ci->weaponmodel[i])
834 {
835 //Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", original_model_name, cl_weaponmodels[i]);
836 ci->deferred = true;
837 }
838
839 if (!ci->weaponmodel[i] && strcmp(model_name, "cyborg") == 0)
840 {
841 // try male
842 Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/%s", cl_weaponmodels[i]);
843 ci->weaponmodel[i] = re.RegisterModel(weapon_filename);
844 }
845
846 if (!cl_vwep->intvalue)
847 break; // only one when vwep is off
848 }
849
850 // must have loaded all data types to be valud
851 if (!ci->skin || !ci->icon || !ci->model || !ci->weaponmodel[0])
852 {
853 ci->skin = NULL;
854 ci->icon = NULL;
855 ci->model = NULL;
856 ci->weaponmodel[0] = NULL;
857 return;
858 }
859 }
860
861 /*
862 ================
863 CL_ParseClientinfo
864
865 Load the skin, icon, and model for a client
866 ================
867 */
CL_ParseClientinfo(int player)868 void CL_ParseClientinfo (int player)
869 {
870 char *s;
871 clientinfo_t *ci;
872
873 s = cl.configstrings[player+CS_PLAYERSKINS];
874
875 ci = &cl.clientinfo[player];
876
877 CL_LoadClientinfo (ci, s);
878 }
879
880
881 /*
882 ================
883 CL_ParseConfigString
884 ================
885 */
CL_ParseConfigString(void)886 void CL_ParseConfigString (void)
887 {
888 size_t length;
889 int i;
890 char *s;
891 char olds[MAX_QPATH];
892
893 i = MSG_ReadShort (&net_message);
894 if (i < 0 || i >= MAX_CONFIGSTRINGS)
895 Com_Error (ERR_DROP, "CL_ParseConfigString: configstring %d >= MAX_CONFIGSTRINGS", i);
896 s = MSG_ReadString(&net_message);
897
898 Q_strncpy (olds, cl.configstrings[i], sizeof(olds)-1);
899
900 //Com_Printf ("cs: %i=%s\n", LOG_GENERAL, i, MakePrintable (s));
901
902 //r1ch: only allow statusbar to overflow
903 /*if (i >= CS_STATUSBAR && i < CS_AIRACCEL)
904 strncpy (cl.configstrings[i], s, (sizeof(cl.configstrings[i]) * (CS_AIRACCEL - i))-1);
905 else
906 Q_strncpy (cl.configstrings[i], s, sizeof(cl.configstrings[i])-1);*/
907
908 //r1: overflow may be desired by some mods in stats programs for example. who knows.
909 length = strlen(s);
910
911 if (length >= (sizeof(cl.configstrings[0]) * (MAX_CONFIGSTRINGS-i)) - 1)
912 Com_Error (ERR_DROP, "CL_ParseConfigString: configstring %d exceeds available space", i);
913
914 //r1: don't allow basic things to overflow
915 if (i != CS_NAME && i < CS_GENERAL)
916 {
917 if (i >= CS_STATUSBAR && i < CS_AIRACCEL)
918 {
919 strncpy (cl.configstrings[i], s, (sizeof(cl.configstrings[i]) * (CS_AIRACCEL - i))-1);
920 }
921 else
922 {
923 if (length >= MAX_QPATH)
924 Com_Printf ("WARNING: Configstring %d of length %d exceeds MAX_QPATH.\n", LOG_CLIENT|LOG_WARNING, i, (int)length);
925 Q_strncpy (cl.configstrings[i], s, sizeof(cl.configstrings[i])-1);
926 }
927 }
928 else
929 {
930 strcpy (cl.configstrings[i], s);
931 }
932
933 // do something apropriate
934 if (i == CS_AIRACCEL)
935 {
936 pm_airaccelerate = (qboolean)atoi(cl.configstrings[CS_AIRACCEL]);
937 }
938 else if (i >= CS_LIGHTS && i < CS_LIGHTS+MAX_LIGHTSTYLES)
939 {
940 CL_SetLightstyle (i - CS_LIGHTS);
941 }
942 #ifdef CD_AUDIO
943 else if (i == CS_CDTRACK)
944 {
945 if (cl.refresh_prepped)
946 CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), true);
947 }
948 #endif
949 else if (i >= CS_MODELS && i < CS_MODELS+MAX_MODELS)
950 {
951 if (cl.refresh_prepped)
952 {
953 cl.model_draw[i-CS_MODELS] = re.RegisterModel (cl.configstrings[i]);
954 if (cl.configstrings[i][0] == '*')
955 cl.model_clip[i-CS_MODELS] = CM_InlineModel (cl.configstrings[i]);
956 else
957 cl.model_clip[i-CS_MODELS] = NULL;
958 }
959
960 //r1: load map whilst connecting to save a bit of time
961 /*if (i == CS_MODELS + 1)
962 {
963 CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &i);
964 if (i && i != atoi(cl.configstrings[CS_MAPCHECKSUM]))
965 Com_Error (ERR_DROP, "Local map version differs from server: 0x%.8x != 0x%.8x\n",
966 i, atoi(cl.configstrings[CS_MAPCHECKSUM]));
967 }*/
968 }
969 else if (i >= CS_SOUNDS && i < CS_SOUNDS+MAX_MODELS)
970 {
971 if (cl.refresh_prepped)
972 cl.sound_precache[i-CS_SOUNDS] = S_RegisterSound (cl.configstrings[i]);
973 }
974 else if (i >= CS_IMAGES && i < CS_IMAGES+MAX_MODELS)
975 {
976 if (cl.refresh_prepped)
977 re.RegisterPic (cl.configstrings[i]);
978 }
979 else if (i == CS_MAXCLIENTS)
980 {
981 if (!cl.attractloop)
982 cl.maxclients = atoi(cl.configstrings[CS_MAXCLIENTS]);
983 }
984 else if (i >= CS_PLAYERSKINS && i < CS_PLAYERSKINS+MAX_CLIENTS)
985 {
986 //r1: hack to avoid parsing non-skins from mods that overload CS_PLAYERSKINS
987 //FIXME: how reliable is CS_MAXCLIENTS?
988 i -= CS_PLAYERSKINS;
989 if (i < cl.maxclients)
990 {
991 if (cl.refresh_prepped && strcmp(olds, s))
992 CL_ParseClientinfo (i);
993 }
994 else
995 {
996 Com_DPrintf ("CL_ParseConfigString: Ignoring out-of-range playerskin %d (%s)\n", i, MakePrintable(s, 0));
997 }
998 }
999 }
1000
1001 /*
1002 =====================================================================
1003
1004 ACTION MESSAGES
1005
1006 =====================================================================
1007 */
1008
1009 /*
1010 ==================
1011 CL_ParseStartSoundPacket
1012 ==================
1013 */
CL_ParseStartSoundPacket(void)1014 void CL_ParseStartSoundPacket(void)
1015 {
1016 vec3_t pos_v;
1017 float *pos;
1018 int channel, ent;
1019 int sound_num;
1020 float volume;
1021 float attenuation;
1022 int flags;
1023 float ofs;
1024
1025 flags = MSG_ReadByte (&net_message);
1026 if (flags == -1)
1027 Com_Error (ERR_DROP, "CL_ParseStartSoundPacket: End of message while reading flags");
1028
1029 sound_num = MSG_ReadByte (&net_message);
1030 if (sound_num == -1)
1031 Com_Error (ERR_DROP, "CL_ParseStartSoundPacket: End of message while reading sound_num");
1032
1033 if (flags & SND_VOLUME)
1034 volume = MSG_ReadByte (&net_message) / 255.0f;
1035 else
1036 volume = DEFAULT_SOUND_PACKET_VOLUME;
1037
1038 if (flags & SND_ATTENUATION)
1039 {
1040 int attn;
1041 attn = MSG_ReadByte (&net_message);
1042 if (attn == -1)
1043 Com_Error (ERR_DROP, "CL_ParseStartSoundPacket: End of message while reading attenuation");
1044 attenuation = attn / 64.0f;
1045 }
1046 else
1047 attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;
1048
1049 if (flags & SND_OFFSET)
1050 {
1051 int offset;
1052 offset = MSG_ReadByte (&net_message);
1053 if (offset == -1)
1054 Com_Error (ERR_DROP, "CL_ParseStartSoundPacket: End of message while reading offset");
1055
1056 ofs = offset / 1000.0f;
1057 }
1058 else
1059 ofs = 0;
1060
1061 if (flags & SND_ENT)
1062 { // entity reletive
1063 channel = MSG_ReadShort(&net_message);
1064 if (channel == -1)
1065 Com_Error (ERR_DROP, "CL_ParseStartSoundPacket: End of message while reading channel");
1066
1067 ent = channel>>3;
1068
1069 if (ent < 0 || ent > MAX_EDICTS)
1070 Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent);
1071
1072 channel &= 7;
1073 }
1074 else
1075 {
1076 ent = 0;
1077 channel = 0;
1078 }
1079
1080 if (flags & SND_POS)
1081 { // positioned in space
1082 MSG_ReadPos (&net_message, pos_v);
1083
1084 pos = pos_v;
1085 }
1086 else // use entity number
1087 pos = NULL;
1088
1089 if (!cl.sound_precache[sound_num])
1090 return;
1091
1092 S_StartSound (pos, ent, channel, cl.sound_precache[sound_num], volume, attenuation, ofs);
1093 }
1094
CL_ServerFPSChanged(void)1095 static void CL_ServerFPSChanged (void)
1096 {
1097 centity_t *cent;
1098 int i;
1099
1100 cl.gunlerp_start = cl.gunlerp_end = 0;
1101
1102 for (i = 0; i < MAX_ENTITIES; i++)
1103 {
1104 cent = &cl_entities[i];
1105 cent->lerp_time = 0;
1106 }
1107 }
1108
CL_ParseSetting(void)1109 static void CL_ParseSetting (void)
1110 {
1111 uint32 setting, value;
1112
1113 setting = MSG_ReadLong (&net_message);
1114 value = MSG_ReadLong (&net_message);
1115
1116 if (setting >= SVSET_MAX)
1117 return;
1118
1119 cl.settings[setting] = value;
1120
1121 //if FPS changed, reset some internal lerp variables
1122 if (setting == SVSET_FPS)
1123 CL_ServerFPSChanged ();
1124 }
1125
CL_CheckForIP(const char * s)1126 static void CL_CheckForIP (const char *s)
1127 {
1128 unsigned int ip1, ip2, ip3, ip4;
1129 unsigned int port;
1130
1131 port = 0;
1132
1133 while (s[0])
1134 {
1135 if (sscanf (s, "%u.%u.%u.%u", &ip1, &ip2, &ip3, &ip4) == 4)
1136 {
1137 if (ip1 < 256 && ip2 < 256 && ip3 < 256 && ip4 < 256)
1138 {
1139 const char *p;
1140 p = strrchr (s, ':');
1141
1142 if (p)
1143 {
1144 p++;
1145 port = strtoul (p, NULL, 10);
1146 if (port <= 1024 || port > 65535)
1147 break;
1148 }
1149
1150 if (port == 0)
1151 port = PORT_SERVER;
1152
1153 Com_sprintf (cls.followHost, sizeof(cls.followHost), "%u.%u.%u.%u:%u", ip1, ip2, ip3, ip4, port);
1154 break;
1155 }
1156 }
1157 s++;
1158 }
1159 }
1160
CL_CheckForURL(const char * s)1161 static void CL_CheckForURL (const char *s)
1162 {
1163 char followURL[1024];
1164 char *p;
1165
1166 p = strstr (s, "http://");
1167 if (p)
1168 {
1169 Q_strncpy (followURL, p, sizeof(followURL)-1);
1170 StripHighBits (followURL, 1);
1171 p = strchr (followURL, ' ');
1172 if (p)
1173 p[0] = '\0';
1174
1175 Sys_UpdateURLMenu (followURL);
1176 }
1177 }
1178
SHOWNET(const char * s)1179 void SHOWNET(const char *s)
1180 {
1181 if (cl_shownet->intvalue>=2)
1182 Com_Printf ("%3i:%s\n", LOG_CLIENT, net_message.readcount-1, s);
1183 }
1184
CL_ParsePrint(void)1185 void CL_ParsePrint (void)
1186 {
1187 int i;
1188 char *s;
1189
1190 i = MSG_ReadByte (&net_message);
1191 s = MSG_ReadString (&net_message);
1192
1193 if (i == PRINT_CHAT)
1194 {
1195 if (CL_IgnoreMatch (s))
1196 return;
1197
1198 S_StartLocalSound ("misc/talk.wav");
1199 if (cl_filterchat->intvalue)
1200 {
1201 StripHighBits(s, (int)cl_filterchat->intvalue == 2);
1202 strcat (s, "\n");
1203 }
1204 con.ormask = 128;
1205
1206 CL_CheckForIP (s);
1207 CL_CheckForURL (s);
1208 SCR_AddChatMessage (s);
1209
1210 //r1: change !p_version to !version since p is for proxies
1211 if ((strstr (s, "!r1q2_version") || strstr (s, "!version")) &&
1212 (cls.lastSpamTime == 0 || cls.realtime > cls.lastSpamTime + 300000))
1213 cls.spamTime = cls.realtime + (int)(random() * 1500);
1214
1215 Com_Printf ("%s", LOG_CLIENT|LOG_CHAT, s);
1216 }
1217 else
1218 {
1219 int len;
1220
1221 Com_Printf ("%s", LOG_CLIENT, s);
1222
1223 //strip newline for trigger match
1224 len = strlen(s);
1225 if (s[len-1] == '\n')
1226 s[len-1] = '\0';
1227
1228 Cmd_ExecTrigger (s); //Triggers
1229 }
1230
1231 con.ormask = 0;
1232 }
1233
1234 /*
1235 =====================
1236 CL_ParseServerMessage
1237 =====================
1238 */
CL_ParseServerMessage(void)1239 qboolean CL_ParseServerMessage (void)
1240 {
1241 int cmd, extrabits;
1242 char *s;
1243 int oldReadCount;
1244 qboolean gotFrame, ret;
1245
1246 //
1247 // if recording demos, copy the message out
1248 //
1249 if (cl_shownet->intvalue == 1)
1250 Com_Printf ("%i ", LOG_CLIENT, net_message.cursize);
1251 else if (cl_shownet->intvalue >= 2)
1252 Com_Printf ("------------------\n", LOG_CLIENT);
1253
1254 serverPacketCount++;
1255 gotFrame = false;
1256 ret = true;
1257
1258 //
1259 // parse the message
1260 //
1261 for (;;)
1262 {
1263 if (net_message.readcount > net_message.cursize)
1264 {
1265 Com_Error (ERR_DROP,"CL_ParseServerMessage: Bad server message (%d>%d)", net_message.readcount, net_message.cursize);
1266 break;
1267 }
1268
1269 oldReadCount = net_message.readcount;
1270
1271 cmd = MSG_ReadByte (&net_message);
1272
1273 if (cmd == -1)
1274 {
1275 SHOWNET("END OF MESSAGE");
1276 break;
1277 }
1278
1279 #ifdef _DEBUG
1280 if (cmd == 31)
1281 Sys_DebugBreak ();
1282 #endif
1283
1284 //r1: more hacky bit stealing in the name of bandwidth
1285 extrabits = cmd & 0xE0;
1286 cmd &= 0x1F;
1287
1288 if (cl_shownet->intvalue>=2)
1289 {
1290 if (cmd >= svc_max_enttypes)
1291 Com_Printf ("%3i:BAD CMD %i\n", LOG_CLIENT, net_message.readcount-1,cmd);
1292 else
1293 SHOWNET(svc_strings[cmd]);
1294 }
1295
1296 // other commands
1297 switch (cmd)
1298 {
1299 case svc_muzzleflash:
1300 CL_ParseMuzzleFlash ();
1301 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1302 break;
1303
1304 case svc_muzzleflash2:
1305 CL_ParseMuzzleFlash2 ();
1306 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1307 break;
1308
1309 case svc_temp_entity:
1310 CL_ParseTEnt ();
1311 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1312 break;
1313
1314 case svc_layout:
1315 s = MSG_ReadString (&net_message);
1316 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1317 strncpy (cl.layout, s, sizeof(cl.layout)-1);
1318 break;
1319
1320 case svc_inventory:
1321 CL_ParseInventory ();
1322 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1323 break;
1324
1325 case svc_nop:
1326 break;
1327
1328 case svc_disconnect:
1329 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1330 Com_Error (ERR_DISCONNECT, "Server disconnected\n");
1331 break;
1332
1333 case svc_reconnect:
1334 Com_Printf ("Server disconnected, reconnecting\n", LOG_CLIENT);
1335 if (cls.download) {
1336 //ZOID, close download
1337 fclose (cls.download);
1338 cls.download = NULL;
1339 }
1340 cls.downloadname[0] = 0;
1341 cls.state = ca_connecting;
1342 cls.connect_time = -99999; // CL_CheckForResend() will fire immediately
1343 break;
1344
1345 case svc_sound:
1346 CL_ParseStartSoundPacket();
1347 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1348 break;
1349
1350 case svc_print:
1351 CL_ParsePrint ();
1352 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1353
1354 break;
1355
1356 case svc_stufftext:
1357 s = MSG_ReadString (&net_message);
1358 Com_DPrintf ("stufftext: %s\n", s);
1359
1360 //ugly, but necessary :(
1361 if (!cl.attractloop || !strcmp(s, "precache\n"))
1362 Cbuf_AddText (s);
1363 else
1364 Com_DPrintf ("WARNING: Demo tried to execute command '%s', ignored.\n", MakePrintable(s, 0));
1365 break;
1366
1367 case svc_serverdata:
1368 Cbuf_Execute (); // make sure any stuffed commands are done
1369 if (!CL_ParseServerData ())
1370 return true;
1371 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1372 break;
1373
1374 case svc_configstring:
1375 CL_ParseConfigString ();
1376 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1377 break;
1378
1379 case svc_spawnbaseline:
1380 CL_ParseBaseline ();
1381 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1382 break;
1383
1384 case svc_centerprint:
1385 SCR_CenterPrint (MSG_ReadString (&net_message));
1386 CL_WriteDemoMessage (net_message.data + oldReadCount, net_message.readcount - oldReadCount, false);
1387 break;
1388
1389 case svc_download:
1390 CL_ParseDownload (false);
1391 break;
1392
1393 case svc_playerinfo:
1394 case svc_packetentities:
1395 case svc_deltapacketentities:
1396 #ifdef _DEBUG
1397 Sys_DebugBreak ();
1398 #endif
1399 Com_Error (ERR_DROP, "Out of place frame data");
1400 break;
1401
1402 case svc_frame:
1403 //note, frame is written to demo stream in a special way (see cl_ents.c)
1404 CL_ParseFrame (extrabits);
1405 gotFrame = true;
1406 break;
1407
1408 // ************** r1q2 specific BEGIN ****************
1409 case svc_zpacket:
1410 //contents of zpackets are written to demo implicity on decompress
1411 CL_ParseZPacket();
1412 break;
1413
1414 case svc_zdownload:
1415 CL_ParseDownload(true);
1416 break;
1417
1418 case svc_playerupdate:
1419 gotFrame = true;
1420 ret = false;
1421 CL_ParsePlayerUpdate ();
1422 break;
1423
1424 case svc_setting:
1425 CL_ParseSetting ();
1426 break;
1427 // ************** r1q2 specific END ******************
1428
1429 default:
1430 #ifdef _DEBUG
1431 //Sys_DebugBreak ();
1432 #endif
1433 if (developer->intvalue)
1434 {
1435 Com_Printf ("Unknown command char %d, ignoring!!\n", LOG_CLIENT, cmd);
1436 }
1437 else
1438 {
1439 /*if (cls.serverProtocol != PROTOCOL_ORIGINAL && cls.realtime - cls.connect_time < 30000)
1440 {
1441 Com_Printf ("Unknown command byte %d, assuming protocol mismatch. Reconnecting with protocol 34.\nPlease be sure that you and the server are using the latest build of R1Q2.\n", LOG_CLIENT, cmd);
1442 CL_Disconnect(false);
1443 cls.serverProtocol = PROTOCOL_ORIGINAL;
1444 CL_Reconnect_f ();
1445 return;
1446 }*/
1447 Com_Error (ERR_DROP,"CL_ParseServerMessage: Unknown command byte %d (0x%.2x)", cmd, cmd);
1448 }
1449 break;
1450
1451 }
1452 }
1453
1454 if (!gotFrame)
1455 noFrameFromServerPacket++;
1456 else
1457 noFrameFromServerPacket = 0;
1458
1459 //flush this frame
1460 CL_WriteDemoMessage (NULL, 0, true);
1461
1462 return ret;
1463 }
1464