Crossfire Client, Trunk  R20922
client.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
32 #include "client.h"
33 
34 #include <ctype.h>
35 #include <errno.h>
36 #include <gio/gio.h>
37 #ifdef HAVE_GIO_GNETWORKING_H
38 #include <gio/gnetworking.h>
39 #endif
40 
41 #include "external.h"
42 #include "mapdata.h"
43 #include "metaserver.h"
44 #include "script.h"
45 
46 /* actually declare the globals */
47 
49 
51 char *sound_server = BINDIR "/cfsndserv";
52 const char *config_dir;
53 const char *cache_dir;
54 
56 
60 
61 guint16 exp_table_max=0;
62 guint64 *exp_table=NULL;
63 
65 
68 static GInputStream *in;
69 
70 const char *const resists_name[NUM_RESISTS] = {
71  "armor", "magic", "fire", "elec",
72  "cold", "conf", "acid", "drain",
73  "ghit", "pois", "slow", "para",
74  "t undead", "fear", "depl","death",
75  "hword", "blind"
76 };
77 
78 typedef void (*CmdProc)(unsigned char *, int len);
79 
85 struct CmdMapping {
86  const char *cmdname;
87  void (*cmdproc)(unsigned char *, int );
89 };
90 
98 struct CmdMapping commands[] = {
99  /*
100  * The order of this table does not make much of a difference. Related
101  * commands are listed in groups.
102  */
103  { "map2", Map2Cmd, SHORT_ARRAY },
104  { "map_scroll", (CmdProc)map_scrollCmd, ASCII },
105  { "magicmap", MagicMapCmd, MIXED /* ASCII, then binary */},
106  { "newmap", NewmapCmd, NODATA },
107  { "mapextended", MapExtendedCmd, MIXED /* chars, then SHORT_ARRAY */ },
108 
109  { "item2", Item2Cmd, MIXED },
110  { "upditem", UpdateItemCmd, MIXED },
111  { "delitem", DeleteItem, INT_ARRAY },
112  { "delinv", DeleteInventory, ASCII },
113 
114  { "addspell", AddspellCmd, MIXED },
115  { "updspell", UpdspellCmd, MIXED },
116  { "delspell", DeleteSpell, INT_ARRAY },
117 
118  { "drawinfo", (CmdProc)DrawInfoCmd, ASCII },
119  { "drawextinfo", (CmdProc)DrawExtInfoCmd, ASCII},
120  {
121  "stats", StatsCmd, STATS /* Array of: int8, (int?s for
122  * that stat)
123  */
124  },
125  { "image2", Image2Cmd, MIXED /* int, int8, int, PNG */ },
126  {
127  "face2", Face2Cmd, MIXED /* int16, int8, int32, string
128  */
129  },
130  { "tick", TickCmd, INT_ARRAY /* uint32 */},
131 
132  { "music", (CmdProc)MusicCmd, ASCII },
133  {
134  "sound2", Sound2Cmd, MIXED /* int8, int8, int8, int8,
135  * int8, int8, chars, int8,
136  * chars
137  */
138  },
139  { "anim", AnimCmd, SHORT_ARRAY},
140  { "smooth", SmoothCmd, SHORT_ARRAY},
141 
142  { "player", PlayerCmd, MIXED /* 3 ints, int8, str */ },
143  { "comc", CompleteCmd, SHORT_INT },
144 
145  { "addme_failed", (CmdProc)AddMeFail, NODATA },
146  { "addme_success", (CmdProc)AddMeSuccess, NODATA },
147  { "version", (CmdProc)VersionCmd, ASCII },
148  { "goodbye", (CmdProc)GoodbyeCmd, NODATA },
149  { "setup", (CmdProc)SetupCmd, ASCII},
150  { "failure", (CmdProc)FailureCmd, ASCII},
151  { "accountplayers", (CmdProc)AccountPlayersCmd, ASCII},
152 
153  { "query", (CmdProc)handle_query, ASCII},
154  { "replyinfo", ReplyInfoCmd, ASCII},
155  { "ExtendedTextSet", (CmdProc)SinkCmd, NODATA},
156  { "ExtendedInfoSet", (CmdProc)SinkCmd, NODATA},
157 
158  { "pickup", PickupCmd, INT_ARRAY /* uint32 */},
159 };
160 
164 #define NCOMMANDS ((int)(sizeof(commands)/sizeof(struct CmdMapping)))
165 
166 GQuark client_error_quark();
167 
168 void client_mapsize(int width, int height) {
169  // Store desired size in use_config to check results from the server.
172 
173  // Set map size in case we receive 'map' before 'setup' commands.
174  mapdata_set_size(width, height);
175  cs_print_string(csocket.fd, "setup mapsize %dx%d", width, height);
176 }
177 
179  LOG(LOG_DEBUG, "close_server_connection", "Closing server connection");
180  g_io_stream_close(G_IO_STREAM(csocket.fd), NULL, NULL);
181  g_object_unref(csocket.fd);
182  csocket.fd = NULL;
183 }
184 
185 void client_run() {
186  GError* err = NULL;
187  SockList inbuf;
188  inbuf.buf = g_malloc(MAXSOCKBUF);
189  if (!SockList_ReadPacket(csocket.fd, &inbuf, MAXSOCKBUF - 1, &err)) {
190  /*
191  * If a socket error occurred while reading the packet, drop the
192  * server connection. Is there a better way to handle this?
193  */
194  LOG(LOG_ERROR, "client_run", "%s", err->message);
195  g_error_free(err);
197  return;
198  }
199  if (inbuf.len == 0) {
201  return;
202  }
203  /*
204  * Null-terminate the buffer, and set the data pointer so it points
205  * to the first character of the data (following the packet length).
206  */
207  inbuf.buf[inbuf.len] = '\0';
208  unsigned char* data = inbuf.buf + 2;
209  /*
210  * Commands that provide data are always followed by a space. Find
211  * the space and convert it to a null character. If no spaces are
212  * found, the packet contains a command with no associatd data.
213  */
214  while ((*data != ' ') && (*data != '\0')) {
215  ++data;
216  }
217  int len;
218  if (*data == ' ') {
219  *data = '\0';
220  data++;
221  len = inbuf.len - (data - inbuf.buf);
222  } else {
223  len = 0;
224  }
225  /*
226  * Search for the command in the list of supported server commands.
227  * If the server command is supported by the client, let the script
228  * watcher know what command was received, then process it and quit
229  * searching the command list.
230  */
231  int i;
232  for(i = 0; i < NCOMMANDS; i++) {
233  const char *cmdin = (char *)inbuf.buf + 2;
234  if (strcmp(cmdin, commands[i].cmdname) == 0) {
235  script_watch(cmdin, data, len, commands[i].cmdformat);
236  commands[i].cmdproc(data, len);
237  break;
238  }
239  }
240  /*
241  * After processing the command, mark the socket input buffer empty.
242  */
243  inbuf.len=0;
244  /*
245  * Complain about unsupported commands to facilitate troubleshooting.
246  * The client and server should negotiate a connection such that the
247  * server does not send commands the client does not support.
248  */
249  if (i == NCOMMANDS) {
250  LOG(LOG_ERROR, "client_run", "Unrecognized command from server (%s)\n",
251  inbuf.buf+2);
252  }
253  g_free(inbuf.buf);
254 }
255 
256 void client_connect(const char hostname[static 1]) {
257  GSocketClient *sclient = g_socket_client_new();
258 
259  // Store server hostname.
260  if (csocket.servername != NULL) {
261  g_free(csocket.servername);
262  }
263  csocket.servername = g_strdup(hostname);
264 
265  // Try to connect to server.
266  csocket.fd = g_socket_client_connect_to_host(
267  sclient, hostname, use_config[CONFIG_PORT], NULL, NULL);
268  g_object_unref(sclient);
269  if (csocket.fd == NULL) {
270  return;
271  }
272 
273  GSocket *socket = g_socket_connection_get_socket(csocket.fd);
274  int i = 1, fd = g_socket_get_fd(socket);
275 #ifndef WIN32
276 #ifdef HAVE_GIO_GNETWORKING_H
277  if (use_config[CONFIG_FASTTCP]) {
278  if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &i, sizeof(i)) == -1) {
279  perror("TCP_NODELAY");
280  }
281  }
282 #endif
283 #endif
284  in = g_io_stream_get_input_stream(G_IO_STREAM(csocket.fd));
285 }
286 
288  return csocket.fd != NULL && g_socket_connection_is_connected(csocket.fd);
289 }
290 
291 GSource *client_get_source() {
292  return g_pollable_input_stream_create_source(
293  G_POLLABLE_INPUT_STREAM(in), NULL);
294 }
295 
296 void client_negotiate(int sound) {
297  int tries;
298 
299  SendVersion(csocket);
300 
301  /* We need to get the version command fairly early on because we need to
302  * know if the server will support a request to use png images. This
303  * isn't done the best, because if the server never sends the version
304  * command, we can loop here forever. However, if it doesn't send the
305  * version command, we have no idea what we are dealing with.
306  */
307  tries=0;
308  while (csocket.cs_version==0) {
309  client_run();
310  if (csocket.fd == NULL) {
311  return;
312  }
313 
314  usleep(10*1000); /* 10 milliseconds */
315  tries++;
316  /* If we haven't got a response in 10 seconds, bail out */
317  if (tries > 1000) {
318  LOG (LOG_ERROR,"common::negotiate_connection", "Connection timed out");
320  return;
321  }
322  }
323 
324  if (csocket.sc_version<1023) {
325  LOG (LOG_WARNING,"common::negotiate_connection","Server does not support PNG images, yet that is all this client");
326  LOG (LOG_WARNING,"common::negotiate_connection","supports. Either the server needs to be upgraded, or you need to");
327  LOG (LOG_WARNING,"common::negotiate_connection","downgrade your client.");
328  exit(1);
329  }
330 
331  /* If the user has specified a numeric face id, use it. If it is a string
332  * like base, then that resolves to 0, so no real harm in that.
333  */
334  if (face_info.want_faceset) {
336  }
337 
338  /* For sound, a value following determines which sound features are
339  * wanted. The value is 1 for sound effects, and 2 for background music,
340  * or the sum of 1 + 2 (3) for both.
341  *
342  * For spellmon, try each acceptable level, but make sure the one the
343  * client prefers is last.
344  */
345  cs_print_string(csocket.fd, "setup "
346  "map2cmd 1 tick 1 sound2 %d darkness %d spellmon 1 spellmon 2 "
347  "faceset %d facecache %d want_pickup 1 loginmethod %d newmapcmd 1",
348  (sound >= 0) ? 3 : 0, want_config[CONFIG_LIGHTING] ? 1 : 0,
350 
351  /*
352  * We can do this right now also. There is not any reason to wait.
353  */
354  cs_print_string(csocket.fd, "requestinfo skill_info");
355  cs_print_string(csocket.fd,"requestinfo exp_table");
356  /*
357  * While these are only used for new login method, they should become
358  * standard fairly soon. All of these are pretty small, and do not add
359  * much to the cost. They make it more likely that the information is
360  * ready when the window that needs it is raised.
361  */
362  cs_print_string(csocket.fd,"requestinfo motd");
363  cs_print_string(csocket.fd,"requestinfo news");
364  cs_print_string(csocket.fd,"requestinfo rules");
365 
368 
369  /* If the server will answer the requestinfo for image_info and image_data,
370  * send it and wait for the response.
371  */
372  if (csocket.sc_version >= 1027) {
373  /* last_start is -99. This means the first face requested will be 1
374  * (not 0) - this is OK because 0 is defined as the blank face.
375  */
376  int last_end=0, last_start=-99;
377 
378  cs_print_string(csocket.fd,"requestinfo image_info");
380  replyinfo_status = 0;
382 
383  do {
384  client_run();
385  /*
386  * It is rare, but the connection can die while getting this info.
387  */
388  if (csocket.fd == NULL) {
389  return;
390  }
391 
393  /*
394  * We need to know how many faces to be able to make the
395  * request intelligently. So only do the following block if
396  * we have that info. By setting the sent flag, we will never
397  * exit this loop until that happens.
398  */
400  if (face_info.num_images != 0) {
401  /*
402  * Sort of fake things out - if we have sent the request
403  * for image sums but have not got them all answered yet,
404  * we then clear the bit from the status so we continue to
405  * loop.
406  */
407  if (last_end == face_info.num_images) {
408  /* Mark that we're all done */
409  if (replyinfo_last_face == last_end) {
412  }
413  } else {
414  /*
415  * If we are all caught up, request another 100 sums.
416  */
417  if (last_end <= (replyinfo_last_face+100)) {
418  last_start += 100;
419  last_end += 100;
420  if (last_end > face_info.num_images) {
421  last_end = face_info.num_images;
422  }
423  cs_print_string(csocket.fd,"requestinfo image_sums %d %d", last_start, last_end);
424  image_update_download_status(last_start, last_end, face_info.num_images);
425  }
426  }
427  } /* Still have image_sums request to send */
428  } /* endif download all faces */
429 
430  usleep(10*1000); /* 10 milliseconds */
431  /*
432  * Do not put in an upper time limit with tries like we did above.
433  * If the player is downloading all the images, the time this
434  * takes could be considerable.
435  */
436  } while (replyinfo_status != requestinfo_sent);
437  }
439  char buf[MAX_BUF];
440 
441  snprintf(buf, sizeof(buf), "Download of images complete. Found %d locally, downloaded %d from server\n",
444  }
445 
446  /* This needs to get changed around - we really don't want to send the
447  * SendAddMe until we do all of our negotiation, which may include things
448  * like downloading all the images and whatnot - this is more an issue if
449  * the user is not using the default face set, as in that case, we might
450  * end up building images from the wrong set.
451  * Only run this if not using new login method
452  */
453  if (!serverloginmethod) {
454  SendAddMe(csocket);
455  }
456 }
void PickupCmd(guint8 *data, int len)
Definition: commands.c:2401
void DeleteItem(unsigned char *data, int len)
Definition: commands.c:1810
int maxfd
Definition: client.c:58
void handle_query(char *data, int len)
Definition: commands.c:1542
#define CONFIG_FASTTCP
Definition: client.h:187
void client_negotiate(int sound)
Definition: client.c:296
size_t len
Definition: newclient.h:685
static int height
Definition: mapdata.c:80
void SetupCmd(char *buf, int len)
Definition: commands.c:901
GSocketConnection * fd
Definition: client.h:120
gint16 use_config[CONFIG_NUMS]
Definition: init.c:40
unsigned char buf[MAXSOCKBUF]
Definition: newclient.h:686
void(* CmdProc)(unsigned char *, int len)
Definition: client.c:78
void DrawInfoCmd(char *data, int len)
Definition: commands.c:1213
int replyinfo_last_face
Definition: client.c:58
void TickCmd(guint8 *data, int len)
Definition: commands.c:2387
void Sound2Cmd(unsigned char *data, int len)
Definition: sound.c:40
const char * cmdname
Definition: client.c:86
Definition: script.h:49
bool SockList_ReadPacket(GSocketConnection c[static 1], SockList sl[static 1], size_t len, GError **error)
Definition: newsocket.c:206
int sc_version
Definition: client.h:121
#define CONFIG_LIGHTING
Definition: client.h:201
#define MAX_SKILL
Definition: client.h:84
void AddMeFail(char *data, int len)
Definition: commands.c:1096
Warning that something might not work.
Definition: client.h:443
#define RI_IMAGE_SUMS
Definition: client.h:527
void ReplyInfoCmd(unsigned char *buf, int len)
Definition: commands.c:774
ClientSocket csocket
Definition: client.c:67
int wantloginmethod
Definition: client.c:59
Face_Information face_info
Definition: image.c:169
#define SOL_TCP
Definition: client.h:37
static GInputStream * in
Definition: client.c:68
#define MSG_TYPE_CLIENT
Definition: newclient.h:395
void MapExtendedCmd(unsigned char *data, int len)
Definition: commands.c:2261
int requestinfo_sent
Definition: client.c:57
const char * cache_dir
Definition: client.c:53
const char *const resists_name[NUM_RESISTS]
Definition: client.c:70
void MusicCmd(const char *data, int len)
Definition: sound.c:122
NameMapping resist_mapping[NUM_RESISTS]
Definition: client.c:64
int serverloginmethod
Definition: client.c:59
void DeleteInventory(unsigned char *data, int len)
Definition: commands.c:1836
char VERSION_INFO[MAX_BUF]
Definition: client.c:48
void script_watch(const char *cmd, const guint8 *data_initial, const int data_len, const enum CmdFormat format)
Definition: script.c:624
char * skill_names[MAX_SKILL]
Definition: client.c:50
void FailureCmd(char *buf, int len)
Definition: commands.c:2415
void Item2Cmd(unsigned char *data, int len)
Definition: commands.c:1715
guint64 * exp_table
Definition: client.c:62
void map_scrollCmd(char *data, int len)
Definition: commands.c:2193
void AccountPlayersCmd(char *buf, int len)
Definition: commands.c:2457
static int width
Definition: mapdata.c:79
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:109
void client_disconnect()
Definition: client.c:178
#define RI_IMAGE_INFO
Definition: client.h:526
void Map2Cmd(unsigned char *data, int len)
Definition: commands.c:2080
void UpdspellCmd(unsigned char *data, int len)
Definition: commands.c:1962
#define NUM_RESISTS
Definition: client.h:451
void SinkCmd(unsigned char *data, int len)
Definition: commands.c:2376
char * servername
Definition: client.h:131
void NewmapCmd(unsigned char *data, int len)
Definition: commands.c:2054
void SmoothCmd(unsigned char *data, int len)
Definition: commands.c:1193
NameMapping skill_mapping[MAX_SKILL]
Definition: client.c:64
void UpdateItemCmd(unsigned char *data, int len)
Definition: commands.c:1726
CmdFormat
Definition: script.h:43
Client_Player cpl
Definition: client.c:66
GQuark client_error_quark()
Definition: client.h:639
GSource * client_get_source()
Definition: client.c:291
#define CONFIG_MAPHEIGHT
Definition: client.h:204
void StatsCmd(unsigned char *data, int len)
Definition: commands.c:1354
#define CONFIG_PORT
Definition: client.h:207
#define MAX_BUF
Definition: client.h:40
void AddspellCmd(unsigned char *data, int len)
Definition: commands.c:1877
#define CONFIG_CACHE
Definition: client.h:189
const char * config_dir
Definition: client.c:52
gint16 want_config[CONFIG_NUMS]
Definition: init.c:40
#define CONFIG_DOWNLOAD
Definition: client.h:185
void client_run()
Definition: client.c:185
void Face2Cmd(guint8 *data, int len)
Definition: image.c:526
int last_used_skills[MAX_SKILL+1]
Definition: client.c:55
void GoodbyeCmd(char *data, int len)
Definition: commands.c:1128
#define NCOMMANDS
Definition: client.c:164
void SendAddMe(ClientSocket csock)
Definition: init.c:75
void VersionCmd(char *data, int len)
Definition: init.c:44
int replyinfo_status
Definition: client.c:57
void Image2Cmd(guint8 *data, int len)
Definition: image.c:552
void client_connect(const char hostname[static 1])
Definition: client.c:256
Warning that something definitely didn&#39;t work.
Definition: client.h:444
Definition: script.h:48
void(* cmdproc)(unsigned char *, int)
Definition: client.c:87
void AddMeSuccess(char *data, int len)
Definition: commands.c:1112
void AnimCmd(unsigned char *data, int len)
Definition: commands.c:1148
char * sound_server
Definition: client.c:51
Definition: script.h:44
void DrawExtInfoCmd(char *data, int len)
Definition: commands.c:1277
#define MAXSOCKBUF
Definition: newclient.h:25
void PlayerCmd(unsigned char *data, int len)
Definition: commands.c:1615
void draw_ext_info(int orig_color, int type, int subtype, const char *message)
Definition: info.c:932
#define CONFIG_SMOOTH
Definition: client.h:210
void DeleteSpell(unsigned char *data, int len)
Definition: commands.c:2000
int want_skill_exp
Definition: client.c:57
void CompleteCmd(unsigned char *data, int len)
Definition: player.c:218
int cs_print_string(GSocketConnection *fd, const char *str,...)
Definition: newsocket.c:245
Definition: script.h:50
void SendVersion(ClientSocket csock)
Definition: init.c:70
void MagicMapCmd(unsigned char *data, int len)
Definition: commands.c:2323
Useful debugging information.
Definition: client.h:441
bool client_is_connected()
Definition: client.c:287
#define NDI_GOLD
Definition: newclient.h:234
void mapdata_set_size(int viewx, int viewy)
Definition: mapdata.c:619
guint16 exp_table_max
Definition: client.c:61
void image_update_download_status(int start, int end, int total)
Definition: image.c:451
enum CmdFormat cmdformat
Definition: client.c:88
#define CONFIG_MAPWIDTH
Definition: client.h:203
void client_mapsize(int width, int height)
Definition: client.c:168
int cs_version
Definition: client.h:121
struct CmdMapping commands[]
Definition: client.c:98
#define MSG_TYPE_CLIENT_CONFIG
Definition: newclient.h:659