File: image.c

package info (click to toggle)
crossfire 1.75.0-9
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 24,168 kB
  • sloc: ansic: 83,169; sh: 4,659; perl: 1,736; lex: 1,443; makefile: 1,199; python: 43
file content (659 lines) | stat: -rw-r--r-- 19,897 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
/*
 * Crossfire -- cooperative multi-player graphical RPG and adventure game
 *
 * Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
 * Copyright (c) 1992 Frank Tore Johansen
 *
 * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
 * welcome to redistribute it under certain conditions. For details, please
 * see COPYING and LICENSE.
 *
 * The authors can be reached via e-mail at <crossfire@metalforge.org>.
 */

/**
 * @file
 * Handles face-related stuff, including the actual face data.
 */

#include "global.h"

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "image.h"

/**
 * Contains face information, with names, numbers, magicmap color and such.
 * It is sorted by alphabetical order.
 */
Face *new_faces;

/**
 * Following can just as easily be pointers, but
 * it is easier to keep them like this.
 */
const Face *blank_face, *empty_face, *smooth_face;


/**
 * Number of bitmaps loaded from the "bmaps" file.
 */
static unsigned int nrofpixmaps = 0;

face_sets facesets[MAX_FACE_SETS];    /**< All facesets */

/**
 * The only thing this table is used for now is to
 * translate the colorname in the magicmap field of the
 * face into a numeric index that is then sent to the
 * client for magic map commands.  The order of this table
 * must match that of the NDI colors in include/newclient.h.
 */
static const char *const colorname[] = {
    "black",        /* 0  */
    "white",        /* 1  */
    "blue",         /* 2  */
    "red",          /* 3  */
    "orange",       /* 4  */
    "light_blue",   /* 5  */
    "dark_orange",  /* 6  */
    "green",        /* 7  */
    "light_green",  /* 8  */
    "grey",         /* 9  */
    "brown",        /* 10 */
    "yellow",       /* 11 */
    "khaki"         /* 12 */
};

/**
 * Used for bsearch searching for faces by name.
 * The face "bug.111" is always put at first.
 * @todo "bug.111" should be a regular face, alas many places consider face 0
 * to be that face. This should be fixed at some point.
 * @param a first item to compare.
 * @param b second item to compare.
 * @retval -1 if a < b
 * @retval 0 if a == b
 * @retval 1 if a > b
 */
static int compare_face(const Face *a, const Face *b) {
    if (strcmp(a->name, "bug.111") == 0) {
        if (strcmp(b->name, "bug.111") == 0)
            return 0;
        return -1;
    } else if (strcmp(b->name, "bug.111") == 0)
        return 1;
    return strcmp(a->name, b->name);
}

/**
 * This returns the face with 'name'.
 *
 * @param name
 * face to search for.
 * @return matching face, NULL if not found.
 */
static Face *internal_find_face(const char *name) {
    Face *bp, tmp;

    tmp.name = name;
    bp = (Face *)bsearch(&tmp, new_faces, nrofpixmaps, sizeof(Face), (int (*)(const void *, const void *))compare_face);

    return bp;
}

/**
 * Finds a color by name.
 *
 * @param name
 * color name, case-sensitive.
 * @return
 * the matching color in the coloralias if found,
 * 0 otherwise.
 *
 * @note
 * 0 will actually be black, so there is no
 * way the calling function can tell if an error occurred or not
 */
static uint8_t find_color(const char *name) {
    uint8_t i;

    for (i = 0; i < sizeof(colorname)/sizeof(*colorname); i++)
        if (!strcmp(name, colorname[i]))
            return i;

    LOG(llevError, "Unknown color: %s\n", name);
    return 0;
}

/**
 * This reads the lib/faces file, getting color and visibility information.
 * It is called by read_bmap_names().
 *
 * @note
 * will call exit() if file doesn't exist.
 */
static void read_face_data(void) {
    char buf[MAX_BUF], *cp;
    Face *on_face = NULL;
    FILE *fp;

    snprintf(buf, sizeof(buf), "%s/faces", settings.datadir);
    if ((fp = fopen(buf, "r")) == NULL) {
        LOG(llevError, "faces: couldn't open file: %s\n", strerror(errno));
        exit(-1);
    }

    while (fgets(buf, MAX_BUF, fp) != NULL) {
        if (*buf == '#')
            continue;
        if (!strncmp(buf, "end", 3)) {
            on_face = NULL;
        } else if (!strncmp(buf, "face", 4)) {
            Face *tmp;

            cp = buf+5;
            cp[strlen(cp)-1] = '\0'; /* remove newline */

            if ((tmp = internal_find_face(cp)) == NULL) {
                LOG(llevError, "faces: couldn't find '%s'\n", cp);
                on_face = NULL;
                continue;
            }
            on_face = tmp;
            on_face->visibility = 0;
        } else if (on_face == NULL) {
            LOG(llevError, "faces: got line with no face set: %s\n", buf);
        } else if (!strncmp(buf, "visibility", 10)) {
            on_face->visibility = atoi(buf+11);
        } else if (!strncmp(buf, "magicmap", 8)) {
            cp = buf+9;
            cp[strlen(cp)-1] = '\0';
            on_face->magicmap = find_color(cp);
        } else if (!strncmp(buf, "is_floor", 8)) {
            int value = atoi(buf+9);
            if (value)
                on_face->magicmap |= FACE_FLOOR;
        } else
            LOG(llevDebug, "faces: unknown line in %s\n", buf);
    }
    fclose(fp);
}

/**
 * This reads the bmaps file to get all the bitmap names and
 * stuff.  It only needs to be done once, because it is player
 * independent (ie, what display the person is on will not make a
 * difference.)
 *
 * @note
 * will call exit() if file doesn't exist, and abort() in case of memory error.
 */
void read_bmap_names(void) {
    char buf[MAX_BUF], *p;
    FILE *fp;
    unsigned int i;
    size_t l;

    bmaps_checksum = 0;
    snprintf(buf, sizeof(buf), "%s/bmaps.paths", settings.datadir);
    if ((fp = fopen(buf, "r")) == NULL) {
        LOG(llevError, "bmaps: couldn't open: %s\n", strerror(errno));
        exit(-1);
    }

    nrofpixmaps = 0;

    /* First count how many bitmaps we have, so we can allocate correctly */
    while (fgets(buf, MAX_BUF, fp) != NULL) {
        if (buf[0] != '#' && buf[0] != '\n') {
            nrofpixmaps++;
        }
    }

    rewind(fp);
    assert(nrofpixmaps > 0);
    new_faces = (Face *)malloc(sizeof(Face)*nrofpixmaps);
    if (new_faces == NULL) {
        fatal(OUT_OF_MEMORY);
    }

    for (i = 0; i < nrofpixmaps; i++) {
        new_faces[i].name = NULL;
        new_faces[i].visibility = 0;
        new_faces[i].magicmap = 255;
        new_faces[i].smoothface = NULL;
    }

    i = 0;
    while (i < nrofpixmaps && fgets(buf, MAX_BUF, fp) != NULL) {
        if (*buf == '#')
            continue;

        p = strrchr(buf, '/');
        if ((p == NULL) || (strtok(p, " \t\n") == NULL)) {
            LOG(llevError, "bmaps: syntax error in %s\n", buf);
            fatal(SEE_LAST_ERROR);
        }
        /* strtok converted the final newline or tab to NULL so all is ok */
        new_faces[i].name = strdup_local(p + 1);

        /* We need to calculate the checksum of the bmaps file
         * name->number mapping to send to the client.  This does not
         * need to match what sum or other utility may come up with -
         * as long as we get the same results on the same real file
         * data, it does the job as it lets the client know if
         * the file has the same data or not.
         */
        ROTATE_RIGHT(bmaps_checksum);
        bmaps_checksum += i&0xff;
        bmaps_checksum &= 0xffffffff;

        ROTATE_RIGHT(bmaps_checksum);
        bmaps_checksum += (i>>8)&0xff;
        bmaps_checksum &= 0xffffffff;
        for (l = 0; l < strlen(p); l++) {
            ROTATE_RIGHT(bmaps_checksum);
            bmaps_checksum += p[l];
            bmaps_checksum &= 0xffffffff;
        }

        i++;
    }
    fclose(fp);

    if (i != nrofpixmaps) {
        LOG(llevError, "read_bmap_names: first read gave %d faces but only loaded %d??\n", nrofpixmaps, i);
        fatal(SEE_LAST_ERROR);
    }

    LOG(llevDebug, "bmaps: loaded %d faces\n", nrofpixmaps);

    qsort(new_faces, nrofpixmaps, sizeof(Face), (int (*)(const void *, const void *))compare_face);

    for (i = 0; i < nrofpixmaps; i++) {
        new_faces[i].number = i;
    }

    read_face_data();

    for (i = 0; i < nrofpixmaps; i++) {
        if (new_faces[i].magicmap == 255) {
            new_faces[i].magicmap = 0;
        }
    }
    /* Actually forcefully setting the colors here probably should not
     * be done - it could easily create confusion.
     */
    blank_face = internal_find_face(BLANK_FACE_NAME);
    ((Face*)blank_face)->magicmap = find_color("khaki")|FACE_FLOOR; // TODO: doh :(

    empty_face = internal_find_face(EMPTY_FACE_NAME);

    smooth_face = internal_find_face(SMOOTH_FACE_NAME);
}

/**
 * This returns the face with 'name'.
 *
 * @param name
 * face to search for.
 * @param error
 * value to return if face was not found.
 * @return found face, or error.
 *
 * @note
 * If a face is not found, then error is returned.  This can be useful if
 * you want some default face used, or can be set to NULL
 * so that it will be known that the face could not be found
 * (needed in client, so that it will know to request that image
 * from the server)
 */
const Face *find_face(const char *name, const Face *error) {
    const Face *bp = internal_find_face(name);
    return bp ? bp : error;
}

/**
 * Reads the smooth file to know how to smooth datas.
 * the smooth file if made of 2 elements lines.
 * lines starting with # are comment
 * the first element of line is face to smooth
 * the next element is the 16x2 faces picture
 * used for smoothing
 *
 * @note
 * will call exit() if file can't be opened.
 */
int read_smooth(void) {
    char buf[MAX_BUF], *p, *q;
    FILE *fp;
    Face *regular, *smoothed;
    int nrofsmooth = 0;

    snprintf(buf, sizeof(buf), "%s/smooth", settings.datadir);
    if ((fp = fopen(buf, "r")) == NULL) {
        LOG(llevError, "Cannot open smooth file: %s\n", strerror(errno));
        exit(-1);
    }

    while (fgets(buf, MAX_BUF, fp) != NULL) {
        if (*buf == '#')
            continue;

        if ((p = strchr(buf, '\n')))
            *p = '\0';

        p = strchr(buf, ' ');
        if (!p)
            continue;

        *p = '\0';
        q = buf;
        regular = internal_find_face(q);
        if (regular == NULL) {
            LOG(llevError, "invalid regular face: %s\n", q);
            continue;
        }
        q = p+1;
        smoothed = internal_find_face(q);
        if (smoothed == NULL) {
            LOG(llevError, "invalid smoothed face: %s\n", q);
            continue;
        }

        regular->smoothface = smoothed;

        nrofsmooth++;
    }
    fclose(fp);

    LOG(llevDebug, "smooth: loaded %d entries\n", nrofsmooth);
    return nrofsmooth;
}

/**
 * Find the smooth face for a given face.
 *
 * @param face the face to find the smoothing face for
 *
 * @param smoothed return value: set to smooth face
 *
 * @return 1=smooth face found, 0=no smooth face found
 */
int find_smooth(const Face *face, const Face **smoothed) {
    (*smoothed) = NULL;

    if (face && face->smoothface) {
        (*smoothed) = face->smoothface;
        return 1;
    }

    return 0;
}

/**
 * Deallocates memory allocated by read_bmap_names() and read_smooth().
 */
void free_all_images(void) {
    unsigned int i;

    for (i = 0; i < nrofpixmaps; i++)
        free((char*)(new_faces[i].name));
    free(new_faces);
}

/**
 * Checks fallback are correctly defined.
 * This is a simple recursive function that makes sure the fallbacks
 * are all proper (eg, the fall back to defined sets, and also
 * eventually fall back to 0).  At the top level, togo is set to
 * MAX_FACE_SETS.  If togo gets to zero, it means we have a loop.
 * This is only run when we first load the facesets.
 */
static void check_faceset_fallback(int faceset, int togo) {
    int fallback = facesets[faceset].fallback;

    /* proper case - falls back to base set */
    if (fallback == 0)
        return;

    if (!facesets[fallback].prefix) {
        LOG(llevError, "Face set %d falls to non set faceset %d\n", faceset, fallback);
        abort();
    }
    togo--;
    if (togo == 0) {
        LOG(llevError, "Infinite loop found in facesets. aborting.\n");
        abort();
    }
    check_faceset_fallback(fallback, togo);
}

/**
 * Loads all the image types into memory.
 *
 * This  way, we can easily send them to the client.  We should really
 * do something better than abort on any errors - on the other hand,
 * these are all fatal to the server (can't work around them), but the
 * abort just seems a bit messy (exit would probably be better.)
 *
 * Couple of notes:  We assume that the faces are in a continous block.
 * This works fine for now, but this could perhaps change in the future
 *
 * Function largely rewritten May 2000 to be more general purpose.
 * The server itself does not care what the image data is - to the server,
 * it is just data it needs to allocate.  As such, the code is written
 * to do such.
 */

void read_client_images(void) {
    char filename[400];
    char buf[HUGE_BUF];
    char *cp, *cps[7+1], *slash;
    FILE *infile;
    int len, fileno, i;
    unsigned int num;
    const Face *face;

    memset(facesets, 0, sizeof(facesets));
    snprintf(filename, sizeof(filename), "%s/image_info", settings.datadir);
    if ((infile = fopen(filename, "r")) == NULL) {
        LOG(llevError, "Unable to open %s\n", filename);
        abort();
    }
    while (fgets(buf, HUGE_BUF-1, infile) != NULL) {
        if (buf[0] == '#')
            continue;
        if (split_string(buf, cps, sizeof(cps)/sizeof(*cps), ':') != 7)
            LOG(llevError, "Bad line in image_info file, ignoring line:\n  %s", buf);
        else {
            len = atoi(cps[0]);
            if (len >= MAX_FACE_SETS) {
                LOG(llevError, "To high a setnum in image_info file: %d > %d\n", len, MAX_FACE_SETS);
                abort();
            }
            facesets[len].prefix = strdup_local(cps[1]);
            facesets[len].fullname = strdup_local(cps[2]);
            facesets[len].fallback = atoi(cps[3]);
            facesets[len].size = strdup_local(cps[4]);
            facesets[len].extension = strdup_local(cps[5]);
            facesets[len].comment = strdup_local(cps[6]);
        }
    }
    fclose(infile);
    for (i = 0; i < MAX_FACE_SETS; i++) {
        if (facesets[i].prefix)
            check_faceset_fallback(i, MAX_FACE_SETS);
    }
    /* Loaded the faceset information - now need to load up the
     * actual faces.
     */

    for (fileno = 0; fileno < MAX_FACE_SETS; fileno++) {
        /* if prefix is not set, this is not used */
        if (!facesets[fileno].prefix)
            continue;
        facesets[fileno].faces = calloc(nrofpixmaps, sizeof(face_info));

        snprintf(filename, sizeof(filename), "%s/crossfire.%d", settings.datadir, fileno);
        LOG(llevDebug, "images: loading from %s\n", filename);

        if ((infile = fopen(filename, "rb")) == NULL) {
            LOG(llevError, "Unable to open %s\n", filename);
            abort();
        }
        while (fgets(buf, HUGE_BUF-1, infile) != NULL) {
            if (strncmp(buf, "IMAGE ", 6) != 0) {
                LOG(llevError, "read_client_images:Bad image line - not IMAGE, instead\n%s", buf);
                abort();
            }
            cp = buf + 6;
            len = atoi(cp);
            if (len == 0 || len > MAX_IMAGE_SIZE) {
                LOG(llevError, "read_client_images: length not valid: %d > %d \n%s", len, MAX_IMAGE_SIZE, buf);
                abort();
            }

            for ( ; *cp != ' ' && *cp != '\n' && *cp != '\0'; cp++) {
                /* Increment pointer until next token. */
            }

            if (*cp != ' ') {
                LOG(llevError, "read_client_images: couldn't read name\n");
                abort();
            }
            cp++;
            /* cp points to the start of the full name */
            slash = strrchr(cp, '/');
            if (slash != NULL)
                cp = slash + 1;
            if (cp[strlen(cp) - 1] == '\n')
                cp[strlen(cp) - 1] = '\0';

            /* cp points to the start of the picture name itself */
            face = find_face(cp, NULL);
            if (face == NULL) {
                LOG(llevError, "read_client_images: couldn't find picture %s\n", cp);
                abort();
            }
            num = face->number;
            if (num >= nrofpixmaps) {
                LOG(llevError, "read_client_images: invalid picture number %d for %s\n", num, cp);
                abort();
            }

            facesets[fileno].faces[num].datalen = len;
            facesets[fileno].faces[num].data = malloc(len);
            if ((i = fread(facesets[fileno].faces[num].data, len, 1, infile)) != 1) {
                LOG(llevError, "read_client_images: Did not read desired amount of data, wanted %d, got %d\n%s", len, i, buf);
                abort();
            }
            facesets[fileno].faces[num].checksum = 0;
            for (i = 0; i < len; i++) {
                ROTATE_RIGHT(facesets[fileno].faces[num].checksum);
                facesets[fileno].faces[num].checksum += facesets[fileno].faces[num].data[i];
                facesets[fileno].faces[num].checksum &= 0xffffffff;
            }
        }
        fclose(infile);
    } /* For fileno < MAX_FACE_SETS */
}

/**
 * Checks specified faceset is valid
 * \param fsn faceset number
 */
int is_valid_faceset(int fsn) {
    if (fsn >= 0 && fsn < MAX_FACE_SETS && facesets[fsn].prefix)
        return TRUE;
    return FALSE;
}

/**
 * Frees all faceset information
 */
void free_socket_images(void) {
    int num;
    unsigned int q;

    for (num = 0; num < MAX_FACE_SETS; num++) {
        if (facesets[num].prefix) {
            for (q = 0; q < nrofpixmaps; q++)
                free(facesets[num].faces[q].data);
            free(facesets[num].prefix);
            free(facesets[num].fullname);
            free(facesets[num].size);
            free(facesets[num].extension);
            free(facesets[num].comment);
            free(facesets[num].faces);
        }
    }
}

/**
 * This returns the set we will actually use when sending
 * a face.  This is used because the image files may be sparse.
 * This function is recursive.  imageno is the face number we are
 * trying to send
 *
 * If face is not found in specified faceset, tries with 'fallback' faceset.
 *
 * \param faceset faceset to check
 * \param imageno image number
 *
 */
int get_face_fallback(int faceset, int imageno) {
    /* faceset 0 is supposed to have every image, so just return.  Doing
     * so also prevents infinite loops in the case if it not having
     * the face, but in that case, we are likely to crash when we try
     * to access the data, but that is probably preferable to an infinite
     * loop.
     */
    if (faceset == 0)
        return 0;

    if (!facesets[faceset].prefix) {
        LOG(llevError, "get_face_fallback called with unused set (%d)?\n", faceset);
        return 0;   /* use default set */
    }
    if (facesets[faceset].faces[imageno].data)
        return faceset;
    return get_face_fallback(facesets[faceset].fallback, imageno);
}

/**
 * Return the number of faces, including the "bug" one.
 * @return number of faces.
 */
unsigned int get_faces_count() {
    return nrofpixmaps;
}

/**
 * Return the face at the specified index.
 * @param index index, between 0 and get_faces_count() excluded.
 * @return face, NULL if the index is invalid.
 */
const Face *get_face_by_index(int index) {
    if (index < 0 || index >= nrofpixmaps)
        return NULL;
    return &new_faces[index];
}

/**
 * Get a face from its unique identifier.
 * @param id face identifier.
 * @return matching face, NULL if no face with this identifier.
 */
const Face *get_face_by_id(uint16_t id) {
    for (int f = 0; f < nrofpixmaps; f++) {
        if (new_faces[f].number == id)
            return &new_faces[f];
    }
    return NULL;
}