/* * static char *rcsid_image_c = * "$Id: image.c 11578 2009-02-23 22:02:27Z lalo $"; */ /* CrossFire, A Multiplayer game for X-windows Copyright (C) 2002 Mark Wedel & Crossfire Development Team Copyright (C) 1992 Frank Tore Johansen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The maintainer of this code can be reached at crossfire-devel@real-time.com */ /** * @file * Handles face-related stuff, including the actual face data. */ #include #include #include "image.h" New_Face *new_faces; /** * bmappair and ::xbm are used when looking for the image id numbers * of a face by name. xbm is sorted alphabetically so that bsearch * can be used to quickly find the entry for a name. the number is * then an index into the new_faces array. * This data is redundant with new_face information - the difference * is that this data gets sorted, and that doesn't necessarily happen * with the new_face data - when accessing new_face[some number], * that some number corresponds to the face at that number - for * xbm, it may not. At current time, these do in fact match because * the bmaps file is created in a sorted order. */ struct bmappair { char *name; unsigned int number; }; /** * The xbm array (which contains name and number information, and * is then sorted) contains nroffiles entries. */ static struct bmappair *xbm = NULL; /** * Following can just as easily be pointers, but * it is easier to keep them like this. */ New_Face *blank_face, *empty_face, *smooth_face; /** nroffiles is the actual number of bitmaps defined. */ static int nroffiles = 0; /** nrofpixmaps is the number of bitmaps loaded. With * the automatic generation of the bmaps file, this is now equal * to nroffiles. */ 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. */ static int compar(const struct bmappair *a, const struct bmappair *b) { return strcmp(a->name, b->name); } /** * 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 find_color(const char *name) { uint8 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; New_Face *on_face = NULL; FILE *fp; snprintf(buf, sizeof(buf), "%s/faces", settings.datadir); LOG(llevDebug, "Reading faces from %s...\n", buf); if ((fp = fopen(buf, "r")) == NULL) { LOG(llevError, "Cannot open faces file: %s\n", strerror_local(errno, buf, sizeof(buf))); 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)) { unsigned tmp; cp = buf+5; cp[strlen(cp)-1] = '\0'; /* remove newline */ if ((tmp = find_face(cp, (unsigned)-1)) == (unsigned)-1) { LOG(llevError, "Could not find face %s\n", cp); continue; } on_face = &new_faces[tmp]; on_face->visibility = 0; } else if (on_face == NULL) { LOG(llevError, "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, "Got unknown line in faces file: %s\n", buf); } LOG(llevDebug, "done\n"); 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, *q; FILE *fp; int value, nrofbmaps = 0, i; size_t l; bmaps_checksum = 0; snprintf(buf, sizeof(buf), "%s/bmaps", settings.datadir); LOG(llevDebug, "Reading bmaps from %s...\n", buf); if ((fp = fopen(buf, "r")) == NULL) { LOG(llevError, "Cannot open bmaps file: %s\n", strerror_local(errno, buf, sizeof(buf))); exit(-1); } /* 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') nrofbmaps++; rewind(fp); xbm = (struct bmappair *)malloc(sizeof(struct bmappair)*nrofbmaps); if (xbm == NULL) { LOG(llevError, "read_bmap_names: xbm memory allocation failure.\n"); abort(); } memset(xbm, 0, sizeof(struct bmappair)*nrofbmaps); nroffiles = 0; while (nroffiles < nrofbmaps && fgets(buf, MAX_BUF, fp) != NULL) { if (*buf == '#') continue; p = (*buf == '\\') ? (buf+1) : buf; if (!(p = strtok(p, " \t")) || !(q = strtok(NULL, " \t\n"))) { LOG(llevDebug, "Warning, syntax error: %s\n", buf); continue; } value = atoi(p); xbm[nroffiles].name = strdup_local(q); /* 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 += value&0xff; bmaps_checksum &= 0xffffffff; ROTATE_RIGHT(bmaps_checksum); bmaps_checksum += (value>>8)&0xff; bmaps_checksum &= 0xffffffff; for (l = 0; l < strlen(q); l++) { ROTATE_RIGHT(bmaps_checksum); bmaps_checksum += q[l]; bmaps_checksum &= 0xffffffff; } xbm[nroffiles].number = value; nroffiles++; if (value >= nrofpixmaps) nrofpixmaps = value+1; } fclose(fp); LOG(llevDebug, "done (got %d/%d/%d)\n", nrofpixmaps, nrofbmaps, nroffiles); new_faces = (New_Face *)malloc(sizeof(New_Face)*nrofpixmaps); if (new_faces == NULL) { LOG(llevError, "read_bmap_names: new_faces memory allocation failure.\n"); abort(); } for (i = 0; i < nrofpixmaps; i++) { new_faces[i].name = ""; new_faces[i].number = i; new_faces[i].visibility = 0; new_faces[i].magicmap = 255; new_faces[i].smoothface = (uint16)-1; } for (i = 0; i < nroffiles; i++) { new_faces[xbm[i].number].name = xbm[i].name; } qsort(xbm, nroffiles, sizeof(struct bmappair), (int (*)(const void *, const void *))compar); 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 = &new_faces[find_face(BLANK_FACE_NAME, 0)]; blank_face->magicmap = find_color("khaki")|FACE_FLOOR; empty_face = &new_faces[find_face(EMPTY_FACE_NAME, 0)]; smooth_face = &new_faces[find_face(SMOOTH_FACE_NAME, 0)]; } /** * This returns an the face number of face 'name'. Number is constant * during an invocation, but not necessarily between versions (this * is because the faces are arranged in alphabetical order, so * if a face is removed or added, all faces after that will now * have a different number. * * @param name * face to search for * @param error * value to return if face was not found. * * @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 negative * 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) */ unsigned find_face(const char *name, unsigned error) { struct bmappair *bp, tmp; char *p; if ((p = strchr(name, '\n'))) *p = '\0'; tmp.name = (char *)name; bp = (struct bmappair *)bsearch(&tmp, xbm, nroffiles, sizeof(struct bmappair), (int (*)(const void *, const void *))compar); return bp ? bp->number : 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; int regular, smoothed, nrofsmooth = 0; snprintf(buf, sizeof(buf), "%s/smooth", settings.datadir); LOG(llevDebug, "Reading smooth from %s...\n", buf); if ((fp = fopen(buf, "r")) == NULL) { LOG(llevError, "Cannot open smooth file %s: %s\n", strerror_local(errno, buf, sizeof(buf))); exit(-1); } while (fgets(buf, MAX_BUF, fp) != NULL) { if (*buf == '#') continue; p = strchr(buf, ' '); if (!p) continue; *p = '\0'; q = buf; regular = find_face(q, (unsigned)-1); if (regular == (unsigned)-1) { LOG(llevError, "invalid regular face: %s\n", q); continue; } q = p+1; smoothed = find_face(q, (unsigned)-1); if (smoothed == (unsigned)-1) { LOG(llevError, "invalid smoothed face: %s\n", q); continue; } new_faces[regular].smoothface = smoothed; nrofsmooth++; } fclose(fp); LOG(llevDebug, "done (got %d smooth 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(uint16 face, uint16 *smoothed) { (*smoothed) = 0; if (face < nrofpixmaps) { if (new_faces[face].smoothface == ((uint16)-1)) return 0; (*smoothed) = new_faces[face].smoothface; return 1; } return 0; } /** * Deallocates memory allocated by read_bmap_names() and read_smooth(). */ void free_all_images(void) { int i; for (i = 0; i < nroffiles; i++) free(xbm[i].name); free(xbm); 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]; FILE *infile; int num, len, compressed, fileno, i, badline; memset(facesets, 0, sizeof(facesets)); snprintf(filename, sizeof(filename), "%s/image_info", settings.datadir); if ((infile = open_and_uncompress(filename, 0, &compressed)) == NULL) { LOG(llevError, "Unable to open %s\n", filename); abort(); } while (fgets(buf, HUGE_BUF-1, infile) != NULL) { badline = 0; if (buf[0] == '#') continue; if (!(cps[0] = strtok(buf, ":"))) badline = 1; for (i = 1; i < 7; i++) { if (!(cps[i] = strtok(NULL, ":"))) badline = 1; } if (badline) { 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]); } } close_and_delete(infile, compressed); 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, "Loading image file %s\n", filename); if ((infile = open_and_uncompress(filename, 0, &compressed)) == 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(); } num = atoi(buf+6); if (num < 0 || num >= nrofpixmaps) { LOG(llevError, "read_client_images: Image num %d not in 0..%d\n%s", num, nrofpixmaps, buf); abort(); } /* Skip accross the number data */ for (cp = buf+6; *cp != ' '; cp++) ; 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(); } /* We don't actualy care about the name if the image that * is embedded in the image file, so just ignore it. */ 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; } } close_and_delete(infile, compressed); } /* 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, q; for (num = 0; num < MAX_FACE_SETS; num++) { if (facesets[num].prefix) { for (q = 0; q < nrofpixmaps; q++) if (facesets[num].faces[q].data) 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); }