/*
* 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
*/
/** \file
* Image related communication
*
* \date 2003-12-02
*
* This file deals with the image related communication to the
* client. I've located all the functions in this file - this
* localizes it more, and means that we don't need to declare
* things like all the structures as globals.
*/
#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 */
} 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 */
} FaceSets;
static FaceSets facesets[MAX_FACE_SETS]; /**< All facesets */
/**
* 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 < faces.size (); 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
*
*/
static 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);
}
/**
* 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);
}
#define MAX_IMAGE_SIZE 10000
/**
* Client tells us what type of faces it wants. Also sets
* the caching attribute.
*
*/
void
SetFaceMode (char *buf, int len, client *ns)
{
int mask = (atoi (buf) & CF_FACE_CACHE), mode = (atoi (buf) & ~CF_FACE_CACHE);
if (mode == CF_FACE_NONE)
ns->facecache = 1;
else if (mode != CF_FACE_PNG)
ns->send_packet_printf ("drawinfo %d %s", NDI_RED, "Warning - send unsupported face mode. Will use Png");
if (mask)
ns->facecache = 1;
}
/**
* client requested an image. send it rate-limited
* before flushing.
*/
void
AskFaceCmd (char *buf, int len, client *ns)
{
int idx = 0, pri = 0;
sscanf (buf, "%d %d", &idx, &pri);
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;
}
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
}
/**
* 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 ();
}
// how lame
static void print_facename (packet &sl, const facedata &d)
{
for (int i = 0; i < CHKSUM_SIZE; ++i)
sl.printf ("%02x", d.chksum [i]);
}
// 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;
}
/**
* Sends a face to a client if they are in pixmap mode
* nothing gets sent in bitmap mode.
* If nocache is true (nonzero), ignore the cache setting from the client -
* this is needed for the askface, in which we really do want to send the
* 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;
}
// refuse tos end non-image faces
if (d->type)
return;
if (!must_send_face (facenum))
return;
// 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);
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)
{
/* 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;
}
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 */
}
send_packet (sl);
anims_sent[anim_num] = 1;
}
/**
* Sends the number of images, checksum of the face file,
* and the image_info file information. See the doc/Developers/protocol
* if you want further detail.
*/
void
send_image_info (client *ns, char *params)
{
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 << "0:base:standard:0:32x32:none:The old 32x32 faceset.\n";
ns->send_packet (sl);
}
/**
* Sends requested face information.
* \param ns socket to send to
* \param params contains first and last index of face
*
* For each image in [start..stop] sends
* - checksum
* - name
*/
void
send_image_sums (client *ns, char *params)
{
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;
}
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);
}