Crossfire Client, Trunk  R20996
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  if (!err) {
195  LOG(LOG_ERROR, "client_run", "%s", err->message);
196  g_error_free(err);
197  }
199  return;
200  }
201  if (inbuf.len == 0) {
203  return;
204  }
205  /*
206  * Null-terminate the buffer, and set the data pointer so it points
207  * to the first character of the data (following the packet length).
208  */
209  inbuf.buf[inbuf.len] = '\0';
210  unsigned char* data = inbuf.buf + 2;
211  /*
212  * Commands that provide data are always followed by a space. Find
213  * the space and convert it to a null character. If no spaces are
214  * found, the packet contains a command with no associatd data.
215  */
216  while ((*data != ' ') && (*data != '\0')) {
217  ++data;
218  }
219  int len;
220  if (*data == ' ') {
221  *data = '\0';
222  data++;
223  len = inbuf.len - (data - inbuf.buf);
224  } else {
225  len = 0;
226  }
227  /*
228  * Search for the command in the list of supported server commands.
229  * If the server command is supported by the client, let the script
230  * watcher know what command was received, then process it and quit
231  * searching the command list.
232  */
233  const char *cmdin = (char *)inbuf.buf + 2;
234  if (debug_protocol) {
235  // Extra spaces in front to make S->C easier to see
236  LOG(LOG_INFO, " S->C", "len %d cmd %s", len, cmdin);
237  }
238  int i;
239  for(i = 0; i < NCOMMANDS; i++) {
240  if (strcmp(cmdin, commands[i].cmdname) == 0) {
241  script_watch(cmdin, data, len, commands[i].cmdformat);
242  commands[i].cmdproc(data, len);
243  break;
244  }
245  }
246  /*
247  * After processing the command, mark the socket input buffer empty.
248  */
249  inbuf.len=0;
250  /*
251  * Complain about unsupported commands to facilitate troubleshooting.
252  * The client and server should negotiate a connection such that the
253  * server does not send commands the client does not support.
254  */
255  if (i == NCOMMANDS) {
256  LOG(LOG_ERROR, "client_run", "Unrecognized command from server (%s)\n",
257  inbuf.buf + 2);
258  error_dialog("Server error",
259  "The server sent an unrecognized command. "
260  "Crossfire Client will now disconnect."
261  "\n\nIf this problem persists with a particular "
262  "character, try playing another character, and without "
263  "disconnecting, playing the problematic character again.");
265  }
266  g_free(inbuf.buf);
267 }
268 
269 void client_connect(const char hostname[static 1]) {
270  GSocketClient *sclient = g_socket_client_new();
271 
272  // Store server hostname.
273  if (csocket.servername != NULL) {
274  g_free(csocket.servername);
275  }
276  csocket.servername = g_strdup(hostname);
277 
278  // Try to connect to server.
279  csocket.fd = g_socket_client_connect_to_host(
280  sclient, hostname, use_config[CONFIG_PORT], NULL, NULL);
281  g_object_unref(sclient);
282  if (csocket.fd == NULL) {
283  return;
284  }
285 
286  GSocket *socket = g_socket_connection_get_socket(csocket.fd);
287  int i = 1, fd = g_socket_get_fd(socket);
288 #ifndef WIN32
289 #ifdef HAVE_GIO_GNETWORKING_H
290  if (use_config[CONFIG_FASTTCP]) {
291  if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &i, sizeof(i)) == -1) {
292  perror("TCP_NODELAY");
293  }
294  }
295 #endif
296 #endif
297  in = g_io_stream_get_input_stream(G_IO_STREAM(csocket.fd));
298 }
299 
301  return csocket.fd != NULL && g_socket_connection_is_connected(csocket.fd);
302 }
303 
304 GSource *client_get_source() {
305  return g_pollable_input_stream_create_source(
306  G_POLLABLE_INPUT_STREAM(in), NULL);
307 }
308 
309 void client_negotiate(int sound) {
310  int tries;
311 
312  SendVersion(csocket);
313 
314  /* We need to get the version command fairly early on because we need to
315  * know if the server will support a request to use png images. This
316  * isn't done the best, because if the server never sends the version
317  * command, we can loop here forever. However, if it doesn't send the
318  * version command, we have no idea what we are dealing with.
319  */
320  tries=0;
321  while (csocket.cs_version==0) {
322  client_run();
323  if (csocket.fd == NULL) {
324  return;
325  }
326 
327  usleep(10*1000); /* 10 milliseconds */
328  tries++;
329  /* If we haven't got a response in 10 seconds, bail out */
330  if (tries > 1000) {
331  LOG (LOG_ERROR,"common::negotiate_connection", "Connection timed out");
333  return;
334  }
335  }
336 
337  if (csocket.sc_version<1023) {
338  LOG (LOG_WARNING,"common::negotiate_connection","Server does not support PNG images, yet that is all this client");
339  LOG (LOG_WARNING,"common::negotiate_connection","supports. Either the server needs to be upgraded, or you need to");
340  LOG (LOG_WARNING,"common::negotiate_connection","downgrade your client.");
341  exit(1);
342  }
343 
344  /* If the user has specified a numeric face id, use it. If it is a string
345  * like base, then that resolves to 0, so no real harm in that.
346  */
347  if (face_info.want_faceset) {
349  }
350 
351  /* For sound, a value following determines which sound features are
352  * wanted. The value is 1 for sound effects, and 2 for background music,
353  * or the sum of 1 + 2 (3) for both.
354  *
355  * For spellmon, try each acceptable level, but make sure the one the
356  * client prefers is last.
357  */
358  cs_print_string(csocket.fd, "setup "
359  "map2cmd 1 tick 1 sound2 %d darkness %d spellmon 1 spellmon 2 "
360  "faceset %d facecache %d want_pickup 1 loginmethod %d newmapcmd 1",
361  (sound >= 0) ? 3 : 0, want_config[CONFIG_LIGHTING] ? 1 : 0,
363 
364  /*
365  * We can do this right now also. There is not any reason to wait.
366  */
367  cs_print_string(csocket.fd, "requestinfo skill_info");
368  cs_print_string(csocket.fd,"requestinfo exp_table");
369  /*
370  * While these are only used for new login method, they should become
371  * standard fairly soon. All of these are pretty small, and do not add
372  * much to the cost. They make it more likely that the information is
373  * ready when the window that needs it is raised.
374  */
375  cs_print_string(csocket.fd,"requestinfo motd");
376  cs_print_string(csocket.fd,"requestinfo news");
377  cs_print_string(csocket.fd,"requestinfo rules");
378 
381 
382  /* If the server will answer the requestinfo for image_info and image_data,
383  * send it and wait for the response.
384  */
385  if (csocket.sc_version >= 1027) {
386  /* last_start is -99. This means the first face requested will be 1
387  * (not 0) - this is OK because 0 is defined as the blank face.
388  */
389  int last_end=0, last_start=-99;
390 
391  cs_print_string(csocket.fd,"requestinfo image_info");
393  replyinfo_status = 0;
395 
396  do {
397  client_run();
398  /*
399  * It is rare, but the connection can die while getting this info.
400  */
401  if (csocket.fd == NULL) {
402  return;
403  }
404 
406  /*
407  * We need to know how many faces to be able to make the
408  * request intelligently. So only do the following block if
409  * we have that info. By setting the sent flag, we will never
410  * exit this loop until that happens.
411  */
413  if (face_info.num_images != 0) {
414  /*
415  * Sort of fake things out - if we have sent the request
416  * for image sums but have not got them all answered yet,
417  * we then clear the bit from the status so we continue to
418  * loop.
419  */
420  if (last_end == face_info.num_images) {
421  /* Mark that we're all done */
422  if (replyinfo_last_face == last_end) {
425  }
426  } else {
427  /*
428  * If we are all caught up, request another 100 sums.
429  */
430  if (last_end <= (replyinfo_last_face+100)) {
431  last_start += 100;
432  last_end += 100;
433  if (last_end > face_info.num_images) {
434  last_end = face_info.num_images;
435  }
436  cs_print_string(csocket.fd,"requestinfo image_sums %d %d", last_start, last_end);
437  image_update_download_status(last_start, last_end, face_info.num_images);
438  }
439  }
440  } /* Still have image_sums request to send */
441  } /* endif download all faces */
442 
443  usleep(10*1000); /* 10 milliseconds */
444  /*
445  * Do not put in an upper time limit with tries like we did above.
446  * If the player is downloading all the images, the time this
447  * takes could be considerable.
448  */
449  } while (replyinfo_status != requestinfo_sent);
450  }
452  char buf[MAX_BUF];
453 
454  snprintf(buf, sizeof(buf), "Download of images complete. Found %d locally, downloaded %d from server\n",
457  }
458 
459  /* This needs to get changed around - we really don't want to send the
460  * SendAddMe until we do all of our negotiation, which may include things
461  * like downloading all the images and whatnot - this is more an issue if
462  * the user is not using the default face set, as in that case, we might
463  * end up building images from the wrong set.
464  * Only run this if not using new login method
465  */
466  if (!serverloginmethod) {
467  SendAddMe(csocket);
468  }
469 }
void PickupCmd(guint8 *data, int len)
Definition: commands.c:2388
void DeleteItem(unsigned char *data, int len)
Definition: commands.c:1797
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:309
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
bool debug_protocol
Definition: main.c:61
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:2374
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:209
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:2248
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:1823
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:2402
void Item2Cmd(unsigned char *data, int len)
Definition: commands.c:1663
guint64 * exp_table
Definition: client.c:62
void map_scrollCmd(char *data, int len)
Definition: commands.c:2180
void AccountPlayersCmd(char *buf, int len)
Definition: commands.c:2444
static int width
Definition: mapdata.c:79
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:111
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:2067
void UpdspellCmd(unsigned char *data, int len)
Definition: commands.c:1949
#define NUM_RESISTS
Definition: client.h:451
void SinkCmd(unsigned char *data, int len)
Definition: commands.c:2363
char * servername
Definition: client.h:131
void NewmapCmd(unsigned char *data, int len)
Definition: commands.c:2041
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:1713
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:304
#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:1864
#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 error_dialog(char *error, char *message)
Definition: main.c:250
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:269
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:1987
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:248
Definition: script.h:50
void SendVersion(ClientSocket csock)
Definition: init.c:70
void MagicMapCmd(unsigned char *data, int len)
Definition: commands.c:2310
Useful debugging information.
Definition: client.h:441
bool client_is_connected()
Definition: client.c:300
#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
Minor, non-harmful issues.
Definition: client.h:442
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