Crossfire Client, Branches  R11627
client.c
Go to the documentation of this file.
1 const char * const rcsid_common_client_c =
2  "$Id: client.c 9215 2008-06-02 18:31:04Z anmaster $";
3 /*
4  Crossfire client, a client program for the crossfire program.
5 
6  Copyright (C) 2001 Mark Wedel & Crossfire Development Team
7 
8  This program is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12 
13  This program is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with this program; if not, write to the Free Software
20  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 
22  The author can be reached via e-mail to crossfire-devel@real-time.com
23 */
24 
25  /* Client interface main routine.
26  * this file sets up a few global variables, connects to the server,
27  * tells it what kind of pictures it wants, adds the client and enters
28  * the main dispatch loop
29  *
30  * the main event loop (event_loop()) checks the tcp socket for input and
31  * then polls for x events. This should be fixed since you can just block
32  * on both filedescriptors.
33  *
34  * The DoClient function recieves a message (an ArgList), unpacks it, and
35  * in a slow for loop dispatches the command to the right function through
36  * the commands table. ArgLists are essentially like RPC things, only
37  * they don't require going through RPCgen, and it's easy to get variable
38  * length lists. They are just lists of longs, strings, characters, and
39  * byte arrays that can be converted to a machine independent format
40  */
41 
42 
43 #include <client.h>
44 #include <external.h>
45 #include <errno.h>
46 #include <script.h>
47 #include <ctype.h>
48 
49 #include "mapdata.h"
50 
51 /* actually declare the globals */
52 
53 #ifdef SERVER
54 char *server=SERVER;
55 #else
56 char *server=NULL;
57 #endif
58 
59 char VERSION_INFO[256];
60 
62 char *sound_server="cfsndserv";
64 
66 
71 
73 uint64 *exp_table=NULL;
74 
76 
79 
80 const char *const resists_name[NUM_RESISTS] = {
81 "armor", "magic", "fire", "elec",
82 "cold", "conf", "acid", "drain",
83 "ghit", "pois", "slow", "para",
84 "t undead", "fear", "depl","death",
85 "hword", "blind"};
86 
87 typedef void (*CmdProc)(unsigned char *, int len);
88 
89 struct CmdMapping {
90  const char *cmdname;
91  void (*cmdproc)(unsigned char *, int );
93 };
94 
95 
96 struct CmdMapping commands[] = {
97  /* Order of this table doesn't make a difference. I tried to sort
98  * of cluster the related stuff together.
99  */
100  { "map2", Map2Cmd, SHORT_ARRAY },
101  { "map_scroll", (CmdProc)map_scrollCmd, ASCII },
102  { "magicmap", MagicMapCmd, MIXED /* ASCII, then binary */},
103  { "newmap", NewmapCmd, NODATA },
104  { "mapextended", MapExtendedCmd, MIXED /* chars, then SHORT_ARRAY */ },
105 
106  { "item2", Item2Cmd, MIXED },
107  { "upditem", UpdateItemCmd, MIXED },
108  { "delitem", DeleteItem, INT_ARRAY },
109  { "delinv", DeleteInventory, ASCII },
110 
111  { "addspell", AddspellCmd, MIXED },
112  { "updspell", UpdspellCmd, MIXED },
113  { "delspell", DeleteSpell, INT_ARRAY },
114 
115  { "drawinfo", (CmdProc)DrawInfoCmd, ASCII },
116  { "drawextinfo", (CmdProc)DrawExtInfoCmd, ASCII},
117  { "stats", StatsCmd, STATS /* array of: int8, (int?s for that stat) */ },
118 
119  { "image2", Image2Cmd, MIXED /* int, int8, int, PNG */ },
120  { "face2", Face2Cmd, MIXED /* int16, int8, int32, string */},
121  { "tick", TickCmd, INT_ARRAY /* uint32 */},
122 
123 
124  { "sound", SoundCmd, MIXED /* int8, int8, int16, int8 */},
125  { "sound2", Sound2Cmd, MIXED /* int8, int8, int8, int8, int8, int8, chars, int8, chars */},
126  { "music", (CmdProc)MusicCmd, ASCII },
127  { "anim", AnimCmd, SHORT_ARRAY},
128  { "smooth", SmoothCmd, SHORT_ARRAY},
129 
130  { "player", PlayerCmd, MIXED /* 3 ints, int8, str */ },
131  { "comc", CompleteCmd, SHORT_INT },
132 
133  { "addme_failed", (CmdProc)AddMeFail, NODATA },
134  { "addme_success", (CmdProc)AddMeSuccess, NODATA },
135  { "version", (CmdProc)VersionCmd, ASCII },
136  { "goodbye", (CmdProc)GoodbyeCmd, NODATA },
137  { "setup", (CmdProc)SetupCmd, ASCII},
138  { "ExtendedInfoSet", (CmdProc)ExtendedInfoSetCmd, NODATA},
139 
140  { "query", (CmdProc)handle_query, ASCII},
141  { "replyinfo", ReplyInfoCmd, ASCII},
142  { "ExtendedTextSet", (CmdProc)SinkCmd, NODATA},
143 
144  { "pickup", PickupCmd, INT_ARRAY /* uint32 */},
145 };
146 
147 #define NCOMMANDS ((int)(sizeof(commands)/sizeof(struct CmdMapping)))
148 
149 void DoClient(ClientSocket *csocket)
150 {
151  int i,len;
152  unsigned char *data;
153 
154  while (1) {
155  i=SockList_ReadPacket(csocket->fd, &csocket->inbuf, MAXSOCKBUF-1);
156  if (i==-1) {
157  /* Need to add some better logic here */
158 #ifdef WIN32
159  closesocket(csocket->fd);
160 #else
161  close(csocket->fd);
162 #endif
163  csocket->fd=-1;
164  return;
165  }
166  if (i==0) return; /* Don't have a full packet */
167  /* Terminate the buffer */
168  csocket->inbuf.buf[csocket->inbuf.len]='\0';
169  data = csocket->inbuf.buf+2;
170  while ((*data != ' ') && (*data != '\0')) ++data;
171  if (*data == ' ') {
172  *data='\0';
173  data++;
174  len = csocket->inbuf.len - (data - csocket->inbuf.buf);
175  }
176  else len = 0;
177  for(i=0;i < NCOMMANDS;i++) {
178  if (strcmp((char*)csocket->inbuf.buf+2,commands[i].cmdname)==0) {
179  script_watch((char*)csocket->inbuf.buf+2,data,len,commands[i].cmdformat);
180  commands[i].cmdproc(data,len);
181  break;
182  }
183  }
184  csocket->inbuf.len=0;
185  if (i == NCOMMANDS) {
186  printf("Bad command from server (%s)\n",csocket->inbuf.buf+2);
187  }
188  }
189 }
190 
191 #ifdef WIN32
192 #define socklen_t int
193 #else
194 #include <netdb.h>
195 #include <sys/types.h>
196 #include <sys/socket.h>
197 #include <netinet/in.h>
198 #include <netinet/tcp.h>
199 #include <ctype.h>
200 #include <arpa/inet.h>
201 #endif
202 
203 /* returns the fd of the connected socket, -1 on failure. */
204 
205 int init_connection(char *host, int port)
206 {
207  int fd = -1, oldbufsize, newbufsize=65535;
208  socklen_t buflen=sizeof(int);
209 #if !HAVE_GETADDRINFO || WIN32
210  struct sockaddr_in insock;
211  struct protoent *protox;
212 
213  /* In my cases, an empty host will be saved as (null) in
214  * the defaults file. However, upon loading, that doesn't
215  * show up as a NULL, but rather this string. For whatever
216  * reasons, at least on my system, the lookup of this takes
217  * a long time, and it isn't a valid host name in any case,
218  * so just abort quickly
219  */
220  if (!strcmp(host,"(null)")) return -1;
221 
222  protox = getprotobyname("tcp");
223  if (protox == (struct protoent *) NULL)
224  {
225  LOG (LOG_ERROR,"common::init_connection", "Error getting protobyname (tcp)");
226  return -1;
227  }
228  fd = socket(PF_INET, SOCK_STREAM, protox->p_proto);
229  if (fd==-1) {
230  perror("init_connection: Error on socket command.\n");
231  LOG (LOG_ERROR,"common::init_connection", "Error on socket command");
232  return -1;
233  }
234  insock.sin_family = AF_INET;
235  insock.sin_port = htons((unsigned short)port);
236  if (isdigit(*host))
237  insock.sin_addr.s_addr = inet_addr(host);
238  else {
239  struct hostent *hostbn = gethostbyname(host);
240  if (hostbn == (struct hostent *) NULL)
241  {
242  LOG (LOG_ERROR,"common::init_connection","Unknown host: %s",host);
243  return -1;
244  }
245  memcpy(&insock.sin_addr, hostbn->h_addr, hostbn->h_length);
246  }
247  if (connect(fd,(struct sockaddr *)&insock,sizeof(insock)) == (-1))
248  {
249  LOG (LOG_ERROR,"common::init_connection","Can't connect to server");
250  perror("Can't connect to server");
251  return -1;
252  }
253 #else
254  struct addrinfo hints;
255  struct addrinfo *res = NULL, *ai;
256  char port_str[6];
257 
258  /* See note in section above about null hosts names */
259  if (!strcmp(host,"(null)")) return -1;
260 
261  snprintf(port_str, sizeof(port_str), "%d", port);
262 
263  memset(&hints, 0, sizeof(hints));
264  hints.ai_family = AF_UNSPEC;
265  hints.ai_socktype = SOCK_STREAM;
266  hints.ai_protocol = IPPROTO_TCP;
267 
268  if (getaddrinfo(host, port_str, &hints, &res) != 0)
269  return -1;
270 
271  for (ai = res; ai != NULL; ai = ai->ai_next) {
272  fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
273  if (fd == -1)
274  continue;
275 
276  if (connect(fd, ai->ai_addr, ai->ai_addrlen) != 0) {
277  close(fd);
278  fd = -1;
279  continue;
280  }
281 
282  break;
283  }
284 
285  freeaddrinfo(res);
286  if (fd == -1)
287  return -1;
288 #endif
289 
290  free(csocket.servername);
291  csocket.servername = malloc(sizeof(char)*(strlen(host)+1));
292  strcpy(csocket.servername, host);
293 
294 #ifndef WIN32
295  if (fcntl(fd, F_SETFL, O_NDELAY)==-1) {
296  LOG (LOG_ERROR,"common::init_connection","Error on fcntl.");
297  }
298 #else
299  {
300  unsigned long tmp = 1;
301  if (ioctlsocket(fd, FIONBIO, &tmp)<0) {
302  LOG (LOG_ERROR,"common::init_connection","Error on ioctlsocket.");
303  }
304  }
305 #endif
306 
307 #ifdef TCP_NODELAY
308  /* turn off nagle algorithm */
309  if (use_config[CONFIG_FASTTCP]) {
310  int i=1;
311 
312 #ifdef WIN32
313  if (setsockopt(fd, SOL_TCP, TCP_NODELAY, ( const char* )&i, sizeof(i)) == -1)
314  perror("TCP_NODELAY");
315 #else
316  if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &i, sizeof(i)) == -1)
317  perror("TCP_NODELAY");
318 #endif
319  }
320 #endif
321 
322  if (getsockopt(fd,SOL_SOCKET,SO_RCVBUF, (char*)&oldbufsize, &buflen)==-1)
323  oldbufsize=0;
324 
325  if (oldbufsize<newbufsize) {
326  if(setsockopt(fd,SOL_SOCKET,SO_RCVBUF, (char*)&newbufsize, sizeof(&newbufsize))) {
327  LOG(LOG_WARNING,"InitConnection: setsockopt"," unable to set output buf size to %d", newbufsize);
328  setsockopt(fd,SOL_SOCKET,SO_RCVBUF, (char*)&oldbufsize, sizeof(&oldbufsize));
329  }
330  }
331  return fd;
332 }
333 
334 /* This function negotiates/establishes the connection with the
335  * server.
336  */
337 
338 void negotiate_connection(int sound)
339 {
340  int tries;
341 
342  SendVersion(csocket);
343 
344  /* We need to get the version command fairly early on because
345  * we need to know if the server will support a request to use
346  * png images. This isn't done the best, because if the server
347  * never sends the version command, we can loop here forever.
348  * However, if it doesn't send the version command, we have no idea
349  * what we are dealing with.
350  */
351  tries=0;
352  while (csocket.cs_version==0) {
353  DoClient(&csocket);
354  if (csocket.fd == -1) return;
355 
356  usleep(10*1000); /* 10 milliseconds */
357  tries++;
358  /* If we have't got a response in 10 seconds, bail out */
359  if (tries > 1000) {
360 #ifdef WIN32
361  closesocket(csocket.fd);
362 #else
363  close(csocket.fd);
364 #endif
365  csocket.fd=-1;
366  return;
367  }
368  }
369 
370  if (csocket.sc_version<1023) {
371  LOG (LOG_WARNING,"common::negotiate_connection","Server does not support PNG images, yet that is all this client");
372  LOG (LOG_WARNING,"common::negotiate_connection","supports. Either the server needs to be upgraded, or you need to");
373  LOG (LOG_WARNING,"common::negotiate_connection","downgrade your client.");
374  exit(1);
375  }
376 
377  /* If the user has specified a numeric face id, use it. If it is a string
378  * like base, then that resolves to 0, so no real harm in that.
379  */
381  cs_print_string(csocket.fd,
382  "setup map2cmd 1 tick 1 sound %d%s sexp %d darkness %d newmapcmd 1 spellmon 1 faceset %d facecache %d exp64 1 itemcmd 2",
383  sound>=0, (sound>=0) ? " sound2 3" : "", want_skill_exp,
386 
387  /* We can do this right now also - isn't any reason to wait */
388  cs_print_string(csocket.fd,"requestinfo exp_table");
389 
393  if (use_config[CONFIG_MAPHEIGHT]!=11 || use_config[CONFIG_MAPWIDTH]!=11)
394  cs_print_string(csocket.fd,"setup mapsize %dx%d",use_config[CONFIG_MAPWIDTH], use_config[CONFIG_MAPHEIGHT]);
395 
397  if (use_config[CONFIG_SMOOTH]){ /*or other mapextended infos*/
398  cs_print_string(csocket.fd,"setup extendedMapInfos 1");
399  /*will handle all special infos requested when setup answer this command*/
400  }
401  cs_print_string(csocket.fd,"setup extendedTextInfos 1");
402  cs_print_string(csocket.fd,"setup want_pickup 1");
403  cs_print_string(csocket.fd,"setup inscribe 1");
404 
405  /* If the server will answer the requestinfo for image_info and image_data,
406  * send it and wait for the response.
407  */
408  if (csocket.sc_version >= 1027) {
409  /* last_start is -99. This means the first face requested will
410  * be 1 (not 0) - this is OK because 0 is defined as the blank
411  * face.
412  */
413  int last_end=0, last_start=-99;
414 
415  cs_print_string(csocket.fd,"requestinfo image_info");
417  replyinfo_status = 0;
419 
420  do {
421  DoClient(&csocket);
422 
423  /* it's rare, the connection can die while getting
424  * this info.
425  */
426  if (csocket.fd == -1) return;
427 
429  /* we need to know how many faces to
430  * be able to make the request intelligently.
431  * So only do the following block if we have that info.
432  * By setting the sent flag, we will never exit
433  * this loop until that happens.
434  */
436  if (face_info.num_images != 0) {
437  /* Sort of fake things out - if we have sent the
438  * request for image sums but have not got them all answered
439  * yet, we then clear the bit from the status
440  * so we continue to loop.
441  */
442  if (last_end == face_info.num_images) {
443  /* Mark that we're all done */
444  if (replyinfo_last_face == last_end) {
447  }
448  } else {
449  /* If we are all caught up, request another
450  * 100 sums.
451  */
452  if (last_end == replyinfo_last_face) {
453  last_start += 100;
454  last_end += 100;
455  if (last_end > face_info.num_images) last_end = face_info.num_images;
456  cs_print_string(csocket.fd,"requestinfo image_sums %d %d", last_start, last_end);
457  image_update_download_status(last_start, last_end, face_info.num_images);
458  }
459  }
460  } /* Still have image_sums request to send */
461  } /* endif download all faces */
462 
463  usleep(10*1000); /* 10 milliseconds */
464  /* Don't put in an upper time limit with tries like we did above - if the
465  * player is downloading all the images, the time this takes could be
466  * considerable.
467  */
468 
469  } while (replyinfo_status != requestinfo_sent);
470  }
472  char buf[MAX_BUF];
473 
474  snprintf(buf, sizeof(buf), "Download of images complete. Found %d locally, downloaded %d from server\n",
476  draw_info(buf, NDI_GOLD);
477  }
478 
479  /* This needs to get changed around - we really don't want to send
480  * the SendAddMe until we do all of our negotiation, which may include
481  * things like downloading all the images and whatnot - this is more an
482  * issue if the user is not using the default face set, as in that case,
483  * we might end up building images from the wrong set.
484  */
485  SendAddMe(csocket);
486 }
void negotiate_connection(int sound)
Definition: client.c:338
void(* cmdproc)(unsigned char *, int)
Definition: client.c:91
void DeleteItem(unsigned char *data, int len)
Definition: commands.c:941
int maxfd
Definition: client.c:68
#define SOL_TCP
Definition: client-types.h:124
void handle_query(char *data, int len)
Definition: commands.c:717
char * client_libdir
Definition: client.c:61
void SetupCmd(char *buf, int len)
Definition: commands.c:173
#define METASERVER2
Definition: config.h:151
uint16 exp_table_max
Definition: client.c:72
void DrawInfoCmd(char *data, int len)
Definition: commands.c:475
int replyinfo_last_face
Definition: client.c:68
void Sound2Cmd(unsigned char *data, int len)
Definition: sound.c:151
const char * cmdname
Definition: client.c:90
Definition: script.h:43
int sc_version
Definition: client.h:99
#define MAX_SKILL
Definition: client.h:61
void AddMeFail(char *data, int len)
Definition: commands.c:387
#define NDI_GOLD
Definition: newclient.h:213
void ExtendedInfoSetCmd(char *data, int len)
Definition: commands.c:370
ClientSocket csocket
Definition: client.c:78
Face_Information face_info
Definition: image.c:167
void DeleteSpell(unsigned char *data, int len)
Definition: commands.c:1051
void MagicMapCmd(unsigned char *data, int len)
Definition: commands.c:1336
int command_inscribe
Definition: client.c:75
sint16 want_config[CONFIG_NUMS]
Definition: init.c:50
int requestinfo_sent
Definition: client.c:68
int meta_port
Definition: client.c:67
const char *const resists_name[NUM_RESISTS]
Definition: client.c:80
#define META_SERVER
Definition: cconfig.h:73
void MusicCmd(const char *data, int len)
Definition: sound.c:199
void DeleteInventory(unsigned char *data, int len)
Definition: commands.c:960
void Image2Cmd(uint8 *data, int len)
Definition: image.c:514
char * server
Definition: client.c:56
char * skill_names[MAX_SKILL]
Definition: client.c:63
void Item2Cmd(unsigned char *data, int len)
Definition: commands.c:857
void Face2Cmd(uint8 *data, int len)
Definition: image.c:491
int metaserver2_on
Definition: client.c:69
int init_connection(char *host, int port)
Definition: client.c:205
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:178
void Map2Cmd(unsigned char *data, int len)
Definition: commands.c:1116
uint32 tick
Definition: client.c:70
#define NUM_RESISTS
Definition: client.h:432
void SinkCmd(unsigned char *data, int len)
Definition: commands.c:1376
char * servername
Definition: client.h:106
int SockList_ReadPacket(int fd, SockList *sl, int len)
Definition: newsocket.c:173
void SmoothCmd(unsigned char *data, int len)
Definition: commands.c:461
void TickCmd(uint8 *data, int len)
Definition: commands.c:1383
int cs_print_string(int fd, const char *str,...)
Definition: newsocket.c:259
sint16 use_config[CONFIG_NUMS]
Definition: init.c:50
const char *const rcsid_common_client_c
Definition: client.c:1
void UpdateItemCmd(unsigned char *data, int len)
Definition: commands.c:862
CmdFormat
Definition: script.h:37
Client_Player cpl
Definition: client.c:77
void PickupCmd(uint8 *data, int len)
Definition: commands.c:1399
void(* CmdProc)(unsigned char *, int len)
Definition: client.c:87
#define CONFIG_CACHE
Definition: client.h:156
void StatsCmd(unsigned char *data, int len)
Definition: commands.c:596
#define CONFIG_FASTTCP
Definition: client.h:154
int fd
Definition: client.h:97
void NewmapCmd(unsigned char *data, int len)
Definition: commands.c:1093
unsigned short uint16
Definition: client-types.h:79
#define META_PORT
Definition: cconfig.h:74
#define CONFIG_SMOOTH
Definition: client.h:177
int last_used_skills[MAX_SKILL+1]
Definition: client.c:65
void GoodbyeCmd(char *data, int len)
Definition: commands.c:406
#define NCOMMANDS
Definition: client.c:147
void SendAddMe(ClientSocket csock)
Definition: init.c:85
#define MAX_BUF
Definition: client-types.h:128
void VersionCmd(char *data, int len)
Definition: init.c:54
int replyinfo_status
Definition: client.c:68
Definition: script.h:42
void script_watch(const char *cmd, const uint8 *data, const int data_len, const enum CmdFormat format)
Definition: script.c:634
void AddMeSuccess(char *data, int len)
Definition: commands.c:398
void map_scrollCmd(char *data, int len)
Definition: commands.c:1221
unsigned int uint32
Definition: client-types.h:77
#define CONFIG_MAPWIDTH
Definition: client.h:170
unsigned char * buf
Definition: newclient.h:573
int len
Definition: newclient.h:572
void AnimCmd(unsigned char *data, int len)
Definition: commands.c:421
char * sound_server
Definition: client.c:62
Definition: script.h:38
void DrawExtInfoCmd(char *data, int len)
Definition: commands.c:526
void UpdspellCmd(unsigned char *data, int len)
Definition: commands.c:1019
#define MAXSOCKBUF
Definition: newclient.h:79
void PlayerCmd(unsigned char *data, int len)
Definition: commands.c:782
#define CONFIG_MAPHEIGHT
Definition: client.h:171
#define RI_IMAGE_SUMS
Definition: client.h:500
int metaserver_on
Definition: client.c:69
#define CONFIG_DOWNLOAD
Definition: client.h:152
void ReplyInfoCmd(uint8 *buf, int len)
Definition: commands.c:131
int want_skill_exp
Definition: client.c:67
SockList inbuf
Definition: client.h:98
void AddspellCmd(unsigned char *data, int len)
Definition: commands.c:979
void draw_info(const char *str, int color)
Definition: gx11.c:1773
void CompleteCmd(unsigned char *data, int len)
Definition: player.c:246
void DoClient(ClientSocket *csocket)
Definition: client.c:149
char VERSION_INFO[256]
Definition: client.c:59
Definition: script.h:44
void SoundCmd(unsigned char *data, int len)
Definition: sound.c:130
char * meta_server
Definition: client.c:61
#define CONFIG_LIGHTING
Definition: client.h:168
void SendVersion(ClientSocket csock)
Definition: init.c:78
#define RI_IMAGE_INFO
Definition: client.h:499
void mapdata_set_size(int viewx, int viewy)
Definition: mapdata.c:577
#define METASERVER
Definition: cconfig.h:75
uint64 * exp_table
Definition: client.c:73
void image_update_download_status(int start, int end, int total)
Definition: image.c:419
enum CmdFormat cmdformat
Definition: client.c:92
int cs_version
Definition: client.h:99
void MapExtendedCmd(unsigned char *data, int len)
Definition: commands.c:1278
struct CmdMapping commands[]
Definition: client.c:96