1 /*
2 * Crossfire -- cooperative multi-player graphical RPG and adventure game
3 *
4 * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5 * Copyright (c) 1992 Frank Tore Johansen
6 *
7 * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8 * welcome to redistribute it under certain conditions. For details, please
9 * see COPYING and LICENSE.
10 *
11 * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12 */
13
14 /**
15 * @file
16 * Contains image related functions at a high level. It mostly deals with the
17 * caching of the images, processing the image commands from the server, etc.
18 */
19
20 #include "client.h"
21
22 #include <ctype.h>
23 #include <glib/gstdio.h>
24
25 #ifdef WIN32
26 #include <io.h>
27 #include <direct.h>
28 #endif
29
30 #include "external.h"
31
32 /* Rotate right from bsd sum. */
33 #define ROTATE_RIGHT(c) if ((c) & 01) (c) = ((c) >>1) + 0x80000000; else (c) >>= 1;
34
35 /*#define CHECKSUM_DEBUG*/
36
37 struct FD_Cache {
38 char name[MAX_BUF];
39 int fd;
40 } fd_cache[MAX_FACE_SETS];
41
42 /**
43 * Given a filename, this tries to load the data. It returns 0 success, -1 on
44 * failure. It returns the data and len, the passed options. This function
45 * is called only if the client caching feature is enabled.
46 *
47 * @param filename File name of an image to try to load.
48 * @param data Caller-allocated pointer to a buffer to load image into.
49 * @param len Amount of buffer used by the loaded image.
50 * @param csum Returns 0/unset (caller already knows if checksum matches?).
51 * Changes have made such that the caller knows whether or not
52 * the checksum matches, so there is little point to re-do it.
53 * @return 0 on success, -1 on failure.
54 */
load_image(char * filename,guint8 * data,int * len,guint32 * csum)55 static int load_image(char *filename, guint8 *data, int *len, guint32 *csum) {
56 int fd, i;
57 char *cp;
58
59 /* If the name includes an @, then that is a combined image file, so we
60 * need to load the image a bit specially. By using these combined image
61 * files, it reduces number of opens needed. In fact, we keep track of
62 * which ones we have opened to improve performance. Note that while not
63 * currently done, this combined image scheme could be done when storing
64 * images in the player's image cache. */
65 if ((cp = strchr(filename, '@')) != NULL) {
66 char *lp;
67 int offset, last = -1;
68
69 #ifdef WIN32
70 int length;
71 #endif
72
73 offset = atoi(cp + 1);
74 lp = strchr(cp, ':');
75 if (!lp) {
76 LOG(LOG_ERROR, "common::load_image",
77 "Corrupt filename - has '@' but no ':' ?(%s)", filename);
78 return -1;
79 }
80 #ifdef WIN32
81 length = atoi(lp + 1);
82 #endif
83 *cp = 0;
84 for (i = 0; i < MAX_FACE_SETS; i++) {
85 if (!strcmp(fd_cache[i].name, filename)) {
86 break;
87 }
88 if (last == -1 && fd_cache[i].fd == -1) {
89 last = i;
90 }
91 }
92 /* Didn't find a matching entry yet, so make one */
93 if (i == MAX_FACE_SETS) {
94 if (last == -1) {
95 LOG(LOG_WARNING, "common::load_image",
96 "fd_cache filled up? unable to load matching cache entry");
97 *cp = '@'; /* put @ back in string */
98 return -1;
99 }
100 #ifdef WIN32
101 if ((fd_cache[last].fd = open(filename, O_RDONLY | O_BINARY)) == -1)
102 #else
103 if ((fd_cache[last].fd = open(filename, O_RDONLY)) == -1)
104 #endif
105 {
106 LOG(LOG_WARNING, "common::load_image", "unable to load listed cache file %s",
107 filename);
108 *cp = '@'; /* put @ back in string */
109 return -1;
110 }
111 strcpy(fd_cache[last].name, filename);
112 i = last;
113 }
114 lseek(fd_cache[i].fd, offset, SEEK_SET);
115 #ifdef WIN32
116 *len = read(fd_cache[i].fd, data, length);
117 #else
118 *len = read(fd_cache[i].fd, data, 65535);
119 #endif
120 *cp = '@';
121 } else {
122 #ifdef WIN32
123 int length = 0;
124 if ((fd = open(filename, O_RDONLY | O_BINARY)) == -1) {
125 return -1;
126 }
127 length = lseek(fd, 0, SEEK_END);
128 lseek(fd, 0, SEEK_SET);
129 *len = read(fd, data, length);
130 #else
131 if ((fd = open(filename, O_RDONLY)) == -1) {
132 return -1;
133 }
134 *len = read(fd, data, 65535);
135 #endif
136 close(fd);
137 }
138
139 face_info.cache_hits++;
140 *csum = 0;
141 return 0;
142
143 #if 0
144 /* Shouldn't be needed anymore */
145 *csum = 0;
146 for (i = 0; i < *len; i++) {
147 ROTATE_RIGHT(*csum);
148 *csum += data[i];
149 *csum &= 0xffffffff;
150 }
151 #endif
152
153 }
154
155 /****************************************************************************
156 * This is our image caching logic. We use a hash to make the name lookups
157 * happen quickly - this is done for speed, but also because we don't really
158 * have a good idea on how many images may used. It also means that as the
159 * cache gets filled up with images in a random order, the lookup is still
160 * pretty quick.
161 *
162 * If a bucket is filled with an entry that is not of the right name,
163 * we store/look for the correct one in the next bucket.
164 */
165
166 /* This should be a power of 2 */
167 #define IMAGE_HASH 8192
168
169 Face_Information face_info;
170
171 /** This holds the name we recieve with the 'face' command so we know what
172 * to save it as when we actually get the face.
173 */
174 static char *facetoname[MAXPIXMAPNUM];
175
176
177 struct Image_Cache {
178 char *image_name;
179 struct Cache_Entry *cache_entry;
180 } image_cache[IMAGE_HASH];
181
182 /**
183 * This function is basically hasharch from the server, common/arch.c a few
184 * changes - first, we stop processing when we reach the first . - this is
185 * because I'm not sure if hashing .111 at the end of all the image names will
186 * be very useful.
187 */
image_hash_name(char * str,int tablesize)188 static guint32 image_hash_name(char *str, int tablesize) {
189 guint32 hash = 0;
190 char *p;
191
192 /* use the same one-at-a-time hash function the server now uses */
193 for (p = str; *p != '\0' && *p != '.'; p++) {
194 hash += *p;
195 hash += hash << 10;
196 hash ^= hash >> 6;
197 }
198 hash += hash << 3;
199 hash ^= hash >> 11;
200 hash += hash << 15;
201 return hash % tablesize;
202 }
203
204 /**
205 * This function returns an index into the image_cache for a matching entry,
206 * -1 if no match is found.
207 */
image_find_hash(char * str)208 static gint32 image_find_hash(char *str) {
209 guint32 hash = image_hash_name(str, IMAGE_HASH), newhash;
210
211 newhash = hash;
212 do {
213 /* No entry - return immediately */
214 if (image_cache[newhash].image_name == NULL) {
215 return -1;
216 }
217 if (!strcmp(image_cache[newhash].image_name, str)) {
218 return newhash;
219 }
220 newhash ++;
221 if (newhash == IMAGE_HASH) {
222 newhash = 0;
223 }
224 } while (newhash != hash);
225
226 /* If the hash table is full, this is bad because we won't be able to
227 * add any new entries.
228 */
229 LOG(LOG_WARNING, "common::image_find_hash",
230 "Hash table is full, increase IMAGE_CACHE size");
231 return -1;
232 }
233
234 /**
235 *
236 */
image_remove_hash(char * imagename,Cache_Entry * ce)237 static void image_remove_hash(char *imagename, Cache_Entry *ce) {
238 int hash_entry;
239 Cache_Entry *last;
240
241 hash_entry = image_find_hash(imagename);
242 if (hash_entry == -1) {
243 LOG(LOG_ERROR, "common::image_remove_hash",
244 "Unable to find cache entry for %s, %s", imagename, ce->filename);
245 return;
246 }
247 if (image_cache[hash_entry].cache_entry == ce) {
248 image_cache[hash_entry].cache_entry = ce->next;
249 free(ce->filename);
250 free(ce);
251 return;
252 }
253 last = image_cache[hash_entry].cache_entry;
254 while (last->next && last->next != ce) {
255 last = last->next;
256 }
257 if (!last->next) {
258 LOG(LOG_ERROR, "common::image_rmove_hash",
259 "Unable to find cache entry for %s, %s", imagename, ce->filename);
260 return;
261 }
262 last->next = ce->next;
263 free(ce->filename);
264 free(ce);
265 }
266
267 /**
268 * This finds and returns the Cache_Entry of the image that matches name
269 * and checksum if has_sum is set. If has_sum is not set, we can't
270 * do a checksum comparison.
271 */
image_find_cache_entry(char * imagename,guint32 checksum,int has_sum)272 static Cache_Entry *image_find_cache_entry(char *imagename, guint32 checksum,
273 int has_sum) {
274 int hash_entry;
275 Cache_Entry *entry;
276
277 hash_entry = image_find_hash(imagename);
278 if (hash_entry == -1) {
279 return NULL;
280 }
281 entry = image_cache[hash_entry].cache_entry;
282 if (has_sum) {
283 while (entry) {
284 if (entry->checksum == checksum) {
285 break;
286 }
287 entry = entry->next;
288 }
289 }
290 return entry; /* This could be NULL */
291 }
292
293 /**
294 * Add a hash entry. Returns the entry we added, NULL on failure.
295 */
image_add_hash(char * imagename,char * filename,guint32 checksum,guint32 ispublic)296 static Cache_Entry *image_add_hash(char *imagename, char *filename,
297 guint32 checksum, guint32 ispublic) {
298 Cache_Entry *new_entry;
299 guint32 hash = image_hash_name(imagename, IMAGE_HASH), newhash;
300
301 newhash = hash;
302 while (image_cache[newhash].image_name != NULL &&
303 strcmp(image_cache[newhash].image_name, imagename)) {
304 newhash ++;
305 if (newhash == IMAGE_HASH) {
306 newhash = 0;
307 }
308 /* If the hash table is full, can't do anything */
309 if (newhash == hash) {
310 LOG(LOG_WARNING, "common::image_find_hash",
311 "Hash table is full, increase IMAGE_CACHE size");
312 return NULL;
313 }
314 }
315 if (!image_cache[newhash].image_name) {
316 image_cache[newhash].image_name = g_strdup(imagename);
317 }
318
319 /* We insert the new entry at the start of the list of the buckets
320 * for this entry. In the case of the players entries, this probably
321 * improves performance, presuming ones later in the file are more likely
322 * to be used compared to those at the start of the file.
323 */
324 new_entry = g_malloc(sizeof(struct Cache_Entry));
325 new_entry->filename = g_strdup(filename);
326 new_entry->checksum = checksum;
327 new_entry->ispublic = ispublic;
328 new_entry->image_data = NULL;
329 new_entry->next = image_cache[newhash].cache_entry;
330 image_cache[newhash].cache_entry = new_entry;
331 return new_entry;
332 }
333
334 /**
335 * Process a line from the bmaps.client file. In theory, the format should be
336 * quite strict, as it is computer generated, but we try to be lenient/follow
337 * some conventions. Note that this is destructive to the data passed in
338 * line.
339 */
image_process_line(char * line,guint32 ispublic)340 static void image_process_line(char *line, guint32 ispublic) {
341 char imagename[MAX_BUF], filename[MAX_BUF];
342 guint32 checksum;
343
344 if (line[0] == '#') {
345 return; /* Ignore comments */
346 }
347
348 if (sscanf(line, "%s %u %s", imagename, &checksum, filename) == 3) {
349 image_add_hash(imagename, filename, checksum, ispublic);
350 } else {
351 LOG(LOG_WARNING, "common::image_process_line",
352 "Did not parse line %s properly?", line);
353 }
354 }
355
356 /**
357 *
358 */
init_common_cache_data(void)359 void init_common_cache_data(void) {
360 FILE *fp;
361 char bmaps[MAX_BUF], inbuf[MAX_BUF];
362 int i;
363
364 if (!want_config[CONFIG_CACHE]) {
365 return;
366 }
367
368 for (i = 0; i < MAXPIXMAPNUM; i++) {
369 facetoname[i] = NULL;
370 }
371
372 /* First, make sure that image_cache is nulled out */
373 memset(image_cache, 0, IMAGE_HASH * sizeof(struct Image_Cache));
374
375 snprintf(bmaps, sizeof(bmaps), "%s/bmaps.client", CF_DATADIR);
376 if ((fp = fopen(bmaps, "r")) != NULL) {
377 while (fgets(inbuf, MAX_BUF - 1, fp) != NULL) {
378 image_process_line(inbuf, 1);
379 }
380 fclose(fp);
381 } else {
382 snprintf(inbuf, sizeof(inbuf),
383 "Unable to open %s. You may wish to download and install the image file to improve performance.\n",
384 bmaps);
385 draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, inbuf);
386 }
387
388 snprintf(bmaps, sizeof(bmaps), "%s/image-cache/bmaps.client", cache_dir);
389 if ((fp = fopen(bmaps, "r")) != NULL) {
390 while (fgets(inbuf, MAX_BUF - 1, fp) != NULL) {
391 image_process_line(inbuf, 0);
392 }
393 fclose(fp);
394 } /* User may not have a cache, so no error if not found */
395 for (i = 0; i < MAX_FACE_SETS; i++) {
396 fd_cache[i].fd = -1;
397 fd_cache[i].name[0] = '\0';
398 }
399 }
400
401 /******************************************************************************
402 *
403 * Code related to face caching.
404 *
405 *****************************************************************************/
406
407 char facecachedir[MAX_BUF];
408
409 /**
410 *
411 */
requestface(int pnum,char * facename)412 void requestface(int pnum, char *facename) {
413 face_info.cache_misses++;
414 facetoname[pnum] = g_strdup(facename);
415 cs_print_string(csocket.fd, "askface %d", pnum);
416 }
417
418 /**
419 * This is common for all the face commands (face2, face1, face).
420 * For face1 and face commands, faceset should always be zero.
421 * for face commands, has_sum and checksum will be zero.
422 * pnum is the face number, while face is the name.
423 * We actually don't care what the set it - it could be useful right now,
424 * but in the current caching scheme, we look through all the facesets for
425 * the image and if the checksum matches, we assume we have match.
426 * This approach makes sure that we don't have to store the same image multiple
427 * times simply because the set number may be different.
428 */
finish_face_cmd(int pnum,guint32 checksum,int has_sum,char * face,int faceset)429 void finish_face_cmd(int pnum, guint32 checksum, int has_sum, char *face,
430 int faceset) {
431 int len;
432 guint32 nx, ny;
433 guint8 data[65536], *png_tmp;
434 char filename[1024];
435 guint32 newsum = 0;
436 Cache_Entry *ce = NULL;
437
438 #if 0
439 fprintf(stderr, "finish_face_cmd, pnum=%d, checksum=%d, face=%s\n",
440 pnum, checksum, face);
441 #endif
442
443 /* In the case of gfx, we don't care about checksum. For public and
444 * private areas, if we care about checksum, and the image doesn't match,
445 * we go onto the next step. If nothing found, we request it
446 * from the server.
447 */
448 snprintf(filename, sizeof(filename), "%s/gfx/%s.png", cache_dir, face);
449 if (load_image(filename, data, &len, &newsum) == -1) {
450 ce = image_find_cache_entry(face, checksum, has_sum);
451 if (!ce) {
452 /* Not in our cache, so request it from the server */
453 requestface(pnum, face);
454 return;
455 } else if (ce->image_data) {
456 /* If this has image_data, then it has already been rendered */
457 if (!associate_cache_entry(ce, pnum)) {
458 return;
459 }
460 }
461 if (ce->ispublic)
462 snprintf(filename, sizeof(filename), "%s/%s",
463 CF_DATADIR, ce->filename);
464 else
465 snprintf(filename, sizeof(filename), "%s/image-cache/%s",
466 cache_dir, ce->filename);
467 if (load_image(filename, data, &len, &newsum) == -1) {
468 LOG(LOG_WARNING, "common::finish_face_cmd",
469 "file %s listed in cache file, but unable to load", filename);
470 requestface(pnum, face);
471 return;
472 }
473 }
474
475 /* If we got here, we found an image and the checksum is OK. */
476
477 if (!(png_tmp = png_to_data(data, len, &nx, &ny))) {
478 /* If the data is bad, remove it if it is in the players private cache */
479 LOG(LOG_WARNING, "common::finish_face_cmd",
480 "Got error on png_to_data, image=%s", face);
481 if (ce) {
482 if (!ce->ispublic) {
483 unlink(filename);
484 }
485 image_remove_hash(face, ce);
486 }
487
488 requestface(pnum, face);
489 }
490
491 /* create_and_rescale_image_from data is an external reference to a piece in
492 * the gui section of the code.
493 */
494 if (create_and_rescale_image_from_data(ce, pnum, png_tmp, nx, ny)) {
495 LOG(LOG_WARNING, "common::finish_face_cmd",
496 "Got error on create_and_rescale_image_from_data, file=%s", filename);
497 requestface(pnum, face);
498 }
499 free(png_tmp);
500 }
501
502
503 /**
504 * We can now connect to different servers, so we need to clear out any old
505 * images. We try to free the data also to prevent memory leaks.
506 * Note that we don't touch our hashed entries - so that when we connect to a
507 * new server, we still have all that information.
508 */
reset_image_cache_data(void)509 void reset_image_cache_data(void) {
510 int i;
511
512 if (want_config[CONFIG_CACHE]) {
513 for (i = 1; i < MAXPIXMAPNUM; i++) {
514 free(facetoname[i]);
515 facetoname[i] = NULL;
516 }
517 }
518 }
519
520 /**
521 * We only get here if the server believes we are caching images. We rely on
522 * the fact that the server will only send a face command for a particular
523 * number once - at current time, we have no way of knowing if we have already
524 * received a face for a particular number.
525 */
Face2Cmd(guint8 * data,int len)526 void Face2Cmd(guint8 *data, int len) {
527 int pnum;
528 guint8 setnum;
529 guint32 checksum;
530 char *face;
531
532 /* A quick sanity check, since if client isn't caching, all the data
533 * structures may not be initialized.
534 */
535 if (!use_config[CONFIG_CACHE]) {
536 LOG(LOG_WARNING, "common::Face2Cmd",
537 "Received a 'face' command when we are not caching");
538 return;
539 }
540 pnum = GetShort_String(data);
541 setnum = data[2];
542 checksum = GetInt_String(data + 3);
543 face = (char *)data + 7;
544 data[len] = '\0';
545
546 finish_face_cmd(pnum, checksum, 1, face, setnum);
547 }
548
549 /**
550 *
551 */
Image2Cmd(guint8 * data,int len)552 void Image2Cmd(guint8 *data, int len) {
553 int pnum, plen;
554 guint8 setnum;
555
556 pnum = GetInt_String(data);
557 setnum = data[4];
558 plen = GetInt_String(data + 5);
559 if (len < 9 || (len - 9) != plen) {
560 LOG(LOG_WARNING, "common::Image2Cmd", "Lengths don't compare (%d,%d)",
561 (len - 9), plen);
562 return;
563 }
564 display_newpng(pnum, data + 9, plen, setnum);
565 }
566
567 /**
568 * Helper for display_newpng, implements the caching of the image to disk.
569 */
cache_newpng(int face,guint8 * buf,int buflen,int setnum,Cache_Entry ** ce)570 static void cache_newpng(int face, guint8 *buf, int buflen, int setnum,
571 Cache_Entry **ce) {
572 char filename[MAX_BUF], basename[MAX_BUF];
573 FILE *tmpfile;
574 guint32 i, csum;
575
576 if (facetoname[face] == NULL) {
577 LOG(LOG_WARNING, "common::display_newpng",
578 "Caching images, but name for %ld not set", face);
579 /* Return to avoid null dereference. */
580 return;
581 }
582 /* Make necessary leading directories */
583 snprintf(filename, sizeof(filename), "%s/image-cache", cache_dir);
584 if (g_access(filename, R_OK | W_OK | X_OK) == -1) {
585 g_mkdir(filename, 0755);
586 }
587
588 snprintf(filename, sizeof(filename), "%s/image-cache/%c%c",
589 cache_dir, facetoname[face][0], facetoname[face][1]);
590 if (access(filename, R_OK | W_OK | X_OK) == -1) {
591 g_mkdir(filename, 0755);
592 }
593
594 /* If setnum is valid, and we have faceset information for it,
595 * put that prefix in. This will make it easier later on to
596 * allow the client to switch image sets on the fly, as it can
597 * determine what set the image belongs to.
598 * We also append the number to it - there could be several versions
599 * of 'face.base.111.x' if different servers have different image
600 * values.
601 */
602 if (setnum >= 0 && setnum < MAX_FACE_SETS &&
603 face_info.facesets[setnum].prefix) {
604 snprintf(basename, sizeof(basename), "%s.%s", facetoname[face],
605 face_info.facesets[setnum].prefix);
606 } else {
607 strcpy(basename, facetoname[face]);
608 }
609
610 /* Decrease it by one since it will immediately get increased
611 * in the loop below.
612 */
613 setnum--;
614 do {
615 setnum++;
616 snprintf(filename, sizeof(filename), "%s/image-cache/%c%c/%s.%d",
617 cache_dir, facetoname[face][0], facetoname[face][1], basename, setnum);
618 } while (g_access(filename, F_OK) == -0);
619
620 #ifdef WIN32
621 if ((tmpfile = fopen(filename, "wb")) == NULL)
622 #else
623 if ((tmpfile = fopen(filename, "w")) == NULL)
624 #endif
625 {
626 LOG(LOG_WARNING, "common::display_newpng", "Can not open %s for writing",
627 filename);
628 } else {
629 /* found a file we can write to */
630
631 fwrite(buf, buflen, 1, tmpfile);
632 fclose(tmpfile);
633 csum = 0;
634 for (i = 0; (int)i < buflen; i++) {
635 ROTATE_RIGHT(csum);
636 csum += buf[i];
637 csum &= 0xffffffff;
638 }
639 snprintf(filename, sizeof(filename), "%c%c/%s.%d", facetoname[face][0],
640 facetoname[face][1],
641 basename, setnum);
642 *ce = image_add_hash(facetoname[face], filename, csum, 0);
643
644 /* It may very well be more efficient to try to store these up
645 * and then write them as a bunch instead of constantly opening the
646 * file for appending. OTOH, hopefully people will be using the
647 * built image archives, so only a few faces actually need to get
648 * downloaded.
649 */
650 snprintf(filename, sizeof(filename), "%s/image-cache/bmaps.client", cache_dir);
651 if ((tmpfile = fopen(filename, "a")) == NULL) {
652 LOG(LOG_WARNING, "common::display_newpng", "Can not open %s for appending",
653 filename);
654 } else {
655 fprintf(tmpfile, "%s %u %c%c/%s.%d\n",
656 facetoname[face], csum, facetoname[face][0],
657 facetoname[face][1], basename, setnum);
658 fclose(tmpfile);
659 }
660 }
661 }
662
663
664 /**
665 * This function is called when the server has sent us the actual png data for
666 * an image. If caching, we need to write this data to disk (this is handled
667 * in the function cache_newpng).
668 */
display_newpng(int face,guint8 * buf,int buflen,int setnum)669 void display_newpng(int face, guint8 *buf, int buflen, int setnum) {
670 guint8 *pngtmp;
671 guint32 width, height;
672 Cache_Entry *ce = NULL;
673
674 if (use_config[CONFIG_CACHE]) {
675 cache_newpng(face, buf, buflen, setnum, &ce);
676 }
677
678 pngtmp = png_to_data(buf, buflen, &width, &height);
679 if (!pngtmp) {
680 LOG(LOG_ERROR, "display_newpng", "error in PNG data; discarding");
681 return;
682 }
683
684 if (create_and_rescale_image_from_data(ce, face, pngtmp, width, height)) {
685 LOG(LOG_WARNING, "common::display_newpng",
686 "create_and_rescale_image_from_data failed for face %ld", face);
687 }
688
689 if (use_config[CONFIG_CACHE]) {
690 free(facetoname[face]);
691 facetoname[face] = NULL;
692 }
693 free(pngtmp);
694 }
695
696 /**
697 * Takes the data from a replyinfo image_info and breaks it down. The info
698 * contained is the checkums, number of images, and faceset information. It
699 * stores this data into the face_info structure.
700 * Since we know data is null terminated, we can use the strchr operations
701 * with safety.
702 * In each block, we find the newline - if we find one, we presume the data is
703 * good, and update the face_info accordingly. if we don't find a newline, we
704 * return.
705 */
get_image_info(guint8 * data,int len)706 void get_image_info(guint8 *data, int len) {
707 char *cp, *lp, *cps[7], buf[MAX_BUF];
708 int onset = 0, badline = 0, i;
709
710 replyinfo_status |= RI_IMAGE_INFO;
711
712 lp = (char *)data;
713 cp = strchr(lp, '\n');
714 if (!cp || (cp - lp) > len) {
715 return;
716 }
717 face_info.num_images = atoi(lp);
718
719 lp = cp + 1;
720 cp = strchr(lp, '\n');
721 if (!cp || (cp - lp) > len) {
722 return;
723 }
724 face_info.bmaps_checksum = strtoul(lp, NULL,
725 10); /* need unsigned, so no atoi */
726
727 lp = cp + 1;
728 cp = strchr(lp, '\n');
729 while (cp && (cp - lp) <= len) {
730 *cp++ = '\0';
731
732 /* The code below is pretty much the same as the code from the server
733 * which loads the original faceset file.
734 */
735 if (!(cps[0] = strtok(lp, ":"))) {
736 badline = 1;
737 }
738 for (i = 1; i < 7; i++) {
739 if (!(cps[i] = strtok(NULL, ":"))) {
740 badline = 1;
741 }
742 }
743 if (badline) {
744 LOG(LOG_WARNING, "common::get_image_info", "bad data, ignoring line:/%s/", lp);
745 } else {
746 onset = atoi(cps[0]);
747 if (onset >= MAX_FACE_SETS) {
748 LOG(LOG_WARNING, "common::get_image_info", "setnum is too high: %d > %d",
749 onset, MAX_FACE_SETS);
750 }
751 face_info.facesets[onset].prefix = g_strdup(cps[1]);
752 face_info.facesets[onset].fullname = g_strdup(cps[2]);
753 face_info.facesets[onset].fallback = atoi(cps[3]);
754 face_info.facesets[onset].size = g_strdup(cps[4]);
755 face_info.facesets[onset].extension = g_strdup(cps[5]);
756 face_info.facesets[onset].comment = g_strdup(cps[6]);
757 }
758 lp = cp;
759 cp = strchr(lp, '\n');
760 }
761 face_info.have_faceset_info = 1;
762 /* if the user has requested a specific face set and that set
763 * is not numeric, try to find a matching set and send the
764 * relevent setup command.
765 */
766 if (face_info.want_faceset && atoi(face_info.want_faceset) == 0) {
767 for (onset = 0; onset < MAX_FACE_SETS; onset++) {
768 if (face_info.facesets[onset].prefix &&
769 !g_ascii_strcasecmp(face_info.facesets[onset].prefix, face_info.want_faceset)) {
770 break;
771 }
772 if (face_info.facesets[onset].fullname &&
773 !g_ascii_strcasecmp(face_info.facesets[onset].fullname, face_info.want_faceset)) {
774 break;
775 }
776 }
777 if (onset < MAX_FACE_SETS) { /* We found a match */
778 face_info.faceset = onset;
779 cs_print_string(csocket.fd, "setup faceset %d", onset);
780 } else {
781 snprintf(buf, sizeof(buf), "Unable to find match for faceset %s on the server",
782 face_info.want_faceset);
783 draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_CONFIG, buf);
784 }
785 }
786
787 }
788
789 /**
790 * This gets a block of checksums from the server. This lets it prebuild the
791 * images or what not. It would probably be nice to add a gui callback
792 * someplace that gives a little status display (18% done or whatever) - that
793 * probably needs to be done further up.
794 *
795 * The start and stop values are not meaningful - they are here because the
796 * semantics of the requestinfo/replyinfo is that replyinfo includes the same
797 * request data as the requestinfo (thus, if the request failed for some
798 * reason, the client would know which one failed and then try again).
799 * Currently, we don't have any logic in the function below to deal with
800 * failures.
801 */
get_image_sums(char * data,int len)802 void get_image_sums(char *data, int len) {
803 int stop, imagenum, slen, faceset;
804 guint32 checksum;
805 char *cp, *lp;
806
807 cp = strchr((char *)data, ' ');
808 if (!cp || (cp - data) > len) {
809 return;
810 }
811
812 while (isspace(*cp)) {
813 cp++;
814 }
815 lp = cp;
816 cp = strchr(lp, ' ');
817 if (!cp || (cp - data) > len) {
818 return;
819 }
820 stop = atoi(lp);
821
822 replyinfo_last_face = stop;
823
824 /* Can't use isspace here, because it matches with tab, ascii code
825 * 9 - this results in advancing too many spaces because
826 * starting at image 2304, the MSB of the image number will be
827 * 9. Using a check against space will work until we get up to
828 * 8192 images.
829 */
830 while (*cp == ' ') {
831 cp++;
832 }
833 while ((cp - data) < len) {
834 imagenum = GetShort_String((guint8 *)cp);
835 cp += 2;
836 checksum = GetInt_String((guint8 *)cp);
837 cp += 4;
838 faceset = *cp;
839 cp++;
840 slen = *cp;
841 cp++;
842 /* Note that as is, this can break horribly if the client is missing a large number
843 * of images - that is because it will request a whole bunch which will overflow
844 * the servers output buffer, causing it to close the connection.
845 * What probably should be done is for the client to just request this checksum
846 * information in small batches so that even if the client has no local
847 * images, requesting the entire batch won't overflow the sockets buffer - this
848 * probably amounts to about 100 images at a time
849 */
850 finish_face_cmd(imagenum, checksum, 1, (char *)cp, faceset);
851 if (imagenum > stop) {
852 LOG(LOG_WARNING, "common::get_image_sums",
853 "Received an image beyond our range? %d > %d", imagenum, stop);
854 }
855 cp += slen;
856 }
857 }
858
859