--- deliantra/server/socket/image.C 2006/08/13 17:16:05 1.1 +++ deliantra/server/socket/image.C 2007/07/06 03:52:51 1.46 @@ -1,31 +1,26 @@ /* - * static char *rcsid_init_c = - * "$Id: image.C,v 1.1 2006/08/13 17:16:05 elmex Exp $"; + * This file is part of Crossfire TRT, the Roguelike Realtime MORPG. + * + * Copyright (©) 2005,2006,2007 Marc Alexander Lehmann / Robin Redeker / the Crossfire TRT team + * Copyright (©) 2001,2007 Mark Wedel + * Copyright (©) 1992,2007 Frank Tore Johansen + * + * Crossfire TRT 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 3 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, see . + * + * The authors can be reached via e-mail to */ -/* - CrossFire, A Multiplayer game for X-windows - - Copyright (C) 2001 Mark Wedel - 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 author can be reached via e-mail to crossfire-devel@real-time.com -*/ - /** \file * Image related communication * @@ -40,28 +35,30 @@ #include #include -#include -#include -#include +#include "crc.h" #define MAX_FACE_SETS 20 /**< Maximum number of image sets the program will handle */ /** Information about one image */ -typedef struct FaceInfo { - uint8 *data; /**< image data */ - uint16 datalen; /**< length of the xpm data */ - uint32 checksum; /**< Checksum of face data */ +typedef struct FaceInfo +{ + uint8 *data; /**< image data */ + uint16 datalen; /**< length of the xpm data */ + uint32 checksum; /**< Checksum of face data */ } FaceInfo; /** Information about one face set */ -typedef struct { - char *prefix; /**< */ - char *fullname; - uint8 fallback; /**< faceset to use when an image is not found in this faceset */ - char *size; - char *extension; - char *comment; - FaceInfo *faces; /**< images in this faceset */ +typedef struct +{ + char *prefix; /**< */ + char *fullname; + + uint8 fallback; /**< faceset to use when an image is not found in this faceset */ + char *size; + char *extension; + char *comment; + + FaceInfo *faces; /**< images in this faceset */ } FaceSets; static FaceSets facesets[MAX_FACE_SETS]; /**< All facesets */ @@ -70,30 +67,37 @@ * Checks specified faceset is valid * \param fsn faceset number */ -int is_valid_faceset(int fsn) +int +is_valid_faceset (int fsn) { - if (fsn >=0 && fsn < MAX_FACE_SETS && facesets[fsn].prefix) return TRUE; - return FALSE; + if (fsn >= 0 && fsn < MAX_FACE_SETS && facesets[fsn].prefix) + return TRUE; + return FALSE; } /** * Frees all faceset information */ -void free_socket_images(void) +void +free_socket_images (void) { - int num,q; + int num, q; - for(num=0;num=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=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 = (uint8*) 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; ifacecache = 1; + else if (mode != CF_FACE_PNG) + ns->send_packet_printf ("drawinfo %d %s", NDI_RED, "Warning - send unsupported face mode. Will use Png"); -void SetFaceMode(char *buf, int len, NewSocket *ns) + if (mask) + ns->facecache = 1; +} + +/** + * client requested an image. send it rate-limited + * before flushing. + */ +void +AskFaceCmd (char *buf, int len, client *ns) { - char tmp[256]; + int idx = 0, pri = 0; - int mask =(atoi(buf) & CF_FACE_CACHE), mode=(atoi(buf) & ~CF_FACE_CACHE); + sscanf (buf, "%d %d", &idx, &pri); - if (mode==CF_FACE_NONE) { - ns->facecache=1; - } else if (mode!=CF_FACE_PNG) { - sprintf(tmp,"drawinfo %d %s", NDI_RED,"Warning - send unsupported face mode. Will use Png"); - Write_String_To_Socket(ns, tmp, strlen(tmp)); -#ifdef ESRV_DEBUG - LOG(llevDebug,"SetFaceMode: Invalid mode from client: %d\n", mode); -#endif + const facedata *d = face_data (idx, ns->faceset); + + if (!d) + return; // doh + + client::ixsend ix; + + ix.pri = pri; + ix.idx = idx; + ix.ofs = d->data.size (); + + auto (pos, ns->ixface.end ()); + + if (ns->fxix < 2) + { + // gcfclient does not support prioritising, older cfplus versions + // do not support interleaved transfers. + if (!ns->ixface.empty ()) + pos = ns->ixface.end () - 1; } - if (mask) { - ns->facecache=1; + else + { + // the by far most common case will be to insert + // near the end, so little looping. + while (pos != ns->ixface.begin ()) + { + --pos; + + // sort within 2k bins, to slightly prefer smaller images + if (pri > pos->pri || (pri == pos->pri && (ix.ofs >> 11) <= (pos->ofs >> 11))) + { + ++pos; + break; + } + } } + + ns->ixface.insert (pos, ix); + +#if 0 + for (auto (i, ns->ixface.begin ()); i != ns->ixface.end (); ++i) + fprintf (stderr, "<%d,%d> ", i->pri, i->ofs); + fprintf (stderr, "\n"); +#endif } /** - * Client has requested pixmap that it somehow missed getting. - * This will be called often if the client is - * caching images. + * Tells client the picture it has to use + * to smooth a picture number given as argument. */ +void +AskSmooth (char *buf, int len, client *ns) +{ + ns->send_face (atoi (buf)); + ns->flush_fx (); +} -void SendFaceCmd(char *buff, int len, NewSocket *ns) +// how lame +static void print_facename (packet &sl, const facedata &d) { - long tmpnum = atoi(buff); - short facenum=tmpnum & 0xffff; + for (int i = 0; i < CHKSUM_SIZE; ++i) + sl.printf ("%02x", d.chksum [i]); +} - if(facenum!=0) - esrv_send_face(ns, facenum,1); +// gcfclient uses the server-provided checksum for comparison, but always +// writes a broken checksum to its cache file, so we have to provide +// gcfclient with the same broken (and useless) checksum just to have it +// cache the image despite its bugs. +static uint32 gcfclient_checksum (const facedata *d) +{ + uint32 csum = 0; + + for (std::string::const_iterator i = d->data.begin (); + i != d->data.end (); + ++i) + { + csum = rotate_right (csum); + csum += *(uint8 *)&*i; + } + + return csum; } /** @@ -321,60 +293,200 @@ * face (and askface is the only place that should be setting it). Otherwise, * we look at the facecache, and if set, send the image name. */ +void +client::send_face (faceidx facenum) +{ + // never send face 0. ever. it does not exist. + if (!facenum) + return; + + const facedata *d = face_data (facenum, faceset); + + if (!d) + { + LOG (llevError, "client::send_face (%d) out of bounds??\n", facenum); + return; + } + + if (faces_sent[facenum]) + return; + + faces_sent[facenum] = true; + + // if for some reason we let a client without face caching connect, + // we better support that decision here and implement it. + if (!facecache) + return send_image (facenum); + + if (fxix) + { + fxface.push_back (facenum); + return; + } + + packet sl; + + if (force_face0) + sl << "face " << uint16 (facenum); + else if (image2) + sl << "face2 " << uint16 (facenum) << uint8 (0) << uint32 (force_bad_checksum ? gcfclient_checksum (d) : 0); + else + sl << "face1 " << uint16 (facenum) << uint32 (force_bad_checksum ? gcfclient_checksum (d) : 0); + + // how lame + print_facename (sl, *d); + send_packet (sl); -void esrv_send_face(NewSocket *ns,short face_num, int nocache) + if (EMI_smooth) + { + faceinfo *f = face_info (facenum); + + if (f->smooth) + { + send_face (f->smooth); + + packet sl ("smooth"); + + sl << uint16 (facenum) + << uint16 (f->smooth); + + send_packet (sl); + } + } +} + +void client::flush_fx () +{ + while (!fxface.empty ()) + { + packet fx ("fx"); + packet sx ("sx"); + + do + { + faceidx facenum = fxface.back (); fxface.pop_back (); + + const facedata *d = face_data (facenum, faceset); + + if (d) + { + fx << ber32 (facenum) + << data8 (d->chksum, CHKSUM_SIZE); + + if (smoothing) + { + faceinfo *f = face_info (facenum); + + if (f->smooth) + { + send_face (f->smooth); + sx << ber32 (facenum) + << ber32 (f->smooth) + << ber32 (f->smoothlevel); + } + } + } + } + while (!fxface.empty () + && fx.room () > ber32::size + CHKSUM_SIZE + 1 + && sx.room () > ber32::size * 3); + + send_packet (fx); + if (sx.length () > 3) send_packet (sx); + } +} + +void +client::send_image (faceidx facenum) +{ + // never send face 0. ever. it does not exist. + if (!facenum) + return; + + const facedata *d = face_data (facenum, faceset); + + faces_sent[facenum] = true; + + if (!d) + { + LOG (llevError, "client::send_image (%d) out of bounds??\n", facenum); + return; + } + + //TODO: check type here? + + if (force_image_newmap) + force_newmap = true; + + packet sl; + + sl << (image2 ? "image2 " : "image ") + << uint32 (facenum); + + if (image2) + sl << uint8 (0); + + sl << uint32 (d->data.size ()) + << data (d->data.data (), d->data.size ()); + + send_packet (sl); +} + +// send all faces of this object to the client +// this uses more bandwidth initially, but makes +// animations look much smoother, and every client +// is supposed to do client-side caching anyways. +void +client::send_faces (object *ob) +{ + send_face (ob->face); + + if (ob->animation_id) + { + animation &anim = animations [ob->animation_id]; + + for (int i = 0; i < anim.num_animations; i++) + send_face (anim.faces [i]); + } +} + +/** + * Need to send an animation sequence to the client. + * We will send appropriate face commands to the client if we haven't + * sent them the face yet (this can become quite costly in terms of + * how much we are sending - on the other hand, this should only happen + * when the player logs in and picks stuff up. + */ +void +client::send_animation (short anim_num) { - SockList sl; - char fallback; + /* Do some checking on the anim_num we got. Note that the animations + * are added in contigous order, so if the number is in the valid + * range, it must be a valid animation. + */ + if (anim_num < 0 || anim_num >= animations.size ()) + { + LOG (llevError, "esrv_send_anim (%d) out of bounds??\n", anim_num); + return; + } - if (face_num <= 0 || face_num >= nrofpixmaps) { - LOG(llevError,"esrv_send_face (%d) out of bounds??\n",face_num); - return; - } - - sl.buf = (unsigned char*) malloc(MAXSOCKBUF); - fallback = get_face_fallback(ns->faceset, face_num); - - if (facesets[fallback].faces[face_num].data == NULL) { - LOG(llevError,"esrv_send_face: faces[%d].data == NULL\n",face_num); - return; - } - - if (ns->facecache && !nocache) { - if (ns->image2) - strcpy((char*)sl.buf, "face2 "); - else if (ns->sc_version >= 1026) - strcpy((char*)sl.buf, "face1 "); - else - strcpy((char*)sl.buf, "face "); - - sl.len=strlen((const char*)sl.buf); - SockList_AddShort(&sl, face_num); - if (ns->image2) - SockList_AddChar(&sl, fallback); - if (ns->sc_version >= 1026) - SockList_AddInt(&sl, facesets[fallback].faces[face_num].checksum); - strcpy((char*)sl.buf + sl.len, new_faces[face_num].name); - sl.len += strlen(new_faces[face_num].name); - Send_With_Handling(ns, &sl); - } - else { - if (ns->image2) - strcpy((char*)sl.buf, "image2 "); - else - strcpy((char*)sl.buf, "image "); - sl.len=strlen((char*)sl.buf); - SockList_AddInt(&sl, face_num); - if (ns->image2) - SockList_AddChar(&sl, fallback); - SockList_AddInt(&sl, facesets[fallback].faces[face_num].datalen); - memcpy(sl.buf+sl.len, facesets[fallback].faces[face_num].data, - facesets[fallback].faces[face_num].datalen); - sl.len += facesets[fallback].faces[face_num].datalen; - Send_With_Handling(ns, &sl); + packet sl ("anim"); + + sl << uint16 (anim_num) + << uint16 (0); /* flags - not used right now */ + + /* Build up the list of faces. Also, send any information (ie, the + * the face itself) down to the client. + */ + for (int i = 0; i < animations[anim_num].num_animations; i++) + { + send_face (animations[anim_num].faces[i]); + sl << uint16 (animations[anim_num].faces[i]); /* flags - not used right now */ } - ns->faces_sent[face_num] |= NS_FACESENT_FACE; - free(sl.buf); + + send_packet (sl); + + anims_sent[anim_num] = 1; } /** @@ -382,25 +494,17 @@ * and the image_info file information. See the doc/Developers/protocol * if you want further detail. */ - -void send_image_info(NewSocket *ns, char *params) +void +send_image_info (client *ns, char *params) { - SockList sl; - int i; + packet sl; + + //TODO: second parameter is a checksum, but it makes no sense in this current framework + sl.printf ("replyinfo image_info\n%d\n%u\n", MAX_FACES, 0); - sl.buf = (unsigned char *) malloc(MAXSOCKBUF); + sl << "0:base:standard:0:32x32:none:The old 32x32 faceset.\n"; - sprintf((char*)sl.buf,"replyinfo image_info\n%d\n%d\n", nrofpixmaps-1, bmaps_checksum); - for (i=0; isend_packet (sl); } /** @@ -412,53 +516,58 @@ * - checksum * - name */ -void send_image_sums(NewSocket *ns, char *params) +void +send_image_sums (client *ns, char *params) { - int start, stop; - short i; - char qq; - char *cp, buf[MAX_BUF]; - SockList sl; - - sl.buf = (unsigned char *) malloc(MAXSOCKBUF); - - start = atoi(params); - for (cp = params; *cp != '\0'; cp++) - if (*cp == ' ') break; - - stop = atoi(cp); - if (stop < start || *cp == '\0' || (stop-start)>1000 || stop >= nrofpixmaps) { - sprintf(buf,"replyinfo image_sums %d %d", start, stop); - cs_write_string(ns, buf, strlen(buf)); - return; - } - sprintf((char*)sl.buf,"replyinfo image_sums %d %d ", start, stop); - - sl.len = strlen((const char*)sl.buf); - - for (i=start; i<=stop; i++) { - SockList_AddShort(&sl, i); - ns->faces_sent[i] |= NS_FACESENT_FACE; - - qq = get_face_fallback(ns->faceset, i); - SockList_AddInt(&sl, facesets[qq].faces[i].checksum); - SockList_AddChar(&sl, qq); - - qq = strlen(new_faces[i].name); - SockList_AddChar(&sl, ( char )( qq + 1 )); - strcpy((char*)sl.buf + sl.len, new_faces[i].name); - sl.len += qq; - SockList_AddChar(&sl, 0); - } - /* It would make more sense to catch this pre-emptively in the code above. - * however, if this really happens, we probably just want to cut down the - * size to less than 1000, since that is what we claim the protocol would - * support. - */ - if (sl.len >= MAXSOCKBUF) { - LOG(llevError,"send_image_send: buffer overrun, %d > %d\n", sl.len, MAXSOCKBUF); - abort(); + int start, stop; + char *cp; + + packet sl; + + start = atoi (params); + for (cp = params; *cp != '\0'; cp++) + if (*cp == ' ') + break; + + stop = atoi (cp); + if (stop < start || *cp == '\0' || (stop - start) > 1000 || stop >= MAX_FACES) + { + sl.printf ("replyinfo image_sums %d %d", start, stop); + ns->send_packet (sl); + sl.reset (); + return; } - Send_With_Handling(ns, &sl); - free(sl.buf); + + sl.printf ("replyinfo image_sums %d %d ", start, stop); + + for (int i = start; i <= stop && i < faces.size (); i++) + { + ns->faces_sent[i] = true; + + const facedata *d = face_data (i, ns->faceset); + + if (sl.room () < 2 + 4 + 1 + d->data.size () + 1) + break; + + sl << uint16 (i) + << uint32 (0) // checksum + << uint8 (ns->faceset); + + print_facename (sl, *d); sl << uint8 (0); + } + + /* It would make more sense to catch this pre-emptively in the code above. + * however, if this really happens, we probably just want to cut down the + * size to less than 1000, since that is what we claim the protocol would + * support. + */ + //TODO: taken care of above, should simply abort or make sure the above code is correct + if (sl.length () >= MAXSOCKBUF) + { + LOG (llevError, "send_image_send: buffer overrun, %d > %d\n", sl.length (), MAXSOCKBUF); + abort (); + } + + ns->send_packet (sl); } +