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 
14 /**
15  * @file
16  * Client interface main routine.  Sets up a few global variables, connects to
17  * the server, tells it what kind of pictures it wants, adds the client and
18  * enters the main dispatch loop.
19  *
20  * The main event loop (event_loop()) checks the TCP socket for input and then
21  * polls for x events.  This should be fixed since you can just block on both
22  * filedescriptors.
23  *
24  * The DoClient function receives a message (an ArgList), unpacks it, and in a
25  * slow for loop dispatches the command to the right function through the
26  * commands table.   ArgLists are essentially like RPC things, only they don't
27  * require going through RPCgen, and it's easy to get variable length lists.
28  * They are just lists of longs, strings, characters, and byte arrays that can
29  * be converted to a machine independent format
30  */
31 
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 
48 char VERSION_INFO[MAX_BUF];
49 
50 char *skill_names[MAX_SKILL];
51 char *sound_server = BINDIR "/cfsndserv";
52 const char *config_dir;
53 const char *cache_dir;
54 
55 int last_used_skills[MAX_SKILL+1];
56 
57 int want_skill_exp = 0, replyinfo_status = 0, requestinfo_sent = 0,
58         replyinfo_last_face = 0, maxfd;
59 
60 /* Use the 'new' login method by default */
61 int wantloginmethod = 2;
62 int serverloginmethod = 0;
63 
64 guint16 exp_table_max=0;
65 guint64 *exp_table=NULL;
66 
67 NameMapping skill_mapping[MAX_SKILL], resist_mapping[NUM_RESISTS];
68 
69 Client_Player cpl;
70 ClientSocket csocket;
71 static GInputStream *in;
72 
73 const char *const resists_name[NUM_RESISTS] = {
74     "armor", "magic", "fire", "elec",
75     "cold", "conf", "acid", "drain",
76     "ghit", "pois", "slow", "para",
77     "t undead", "fear", "depl","death",
78     "hword", "blind"
79 };
80 
81 typedef void (*CmdProc)(unsigned char *, int len);
82 
83 /**
84  * Links server commands to client functions that implement them, and gives a
85  * rough indication of the type of data that the server supplies with the
86  * command.
87  */
88 struct CmdMapping {
89     const char *cmdname;
90     void (*cmdproc)(unsigned char *, int );
91     enum CmdFormat cmdformat;
92 };
93 
94 /**
95  * The list of server commands that this client supports along with pointers
96  * to the function that handles the command.  The table also gives a rough
97  * indication of the type of data that the server should send with each
98  * command.  If the client receives a command not listed in the table, a
99  * complaint is output on stdout.
100  */
101 struct CmdMapping commands[] = {
102     /*
103      * The order of this table does not make much of a difference.  Related
104      * commands are listed in groups.
105      */
106     { "map2",            Map2Cmd, SHORT_ARRAY },
107     { "map_scroll",      (CmdProc)map_scrollCmd, ASCII },
108     { "magicmap",        MagicMapCmd, MIXED    /* ASCII, then binary */},
109     { "newmap",          NewmapCmd, NODATA },
110     { "mapextended",     MapExtendedCmd, MIXED /* chars, then SHORT_ARRAY */ },
111 
112     { "item2",           Item2Cmd, MIXED },
113     { "upditem",         UpdateItemCmd, MIXED },
114     { "delitem",         DeleteItem, INT_ARRAY },
115     { "delinv",          DeleteInventory, ASCII },
116 
117     { "addspell",        AddspellCmd, MIXED },
118     { "updspell",        UpdspellCmd, MIXED },
119     { "delspell",        DeleteSpell, INT_ARRAY },
120 
121     { "drawinfo",        (CmdProc)DrawInfoCmd, ASCII },
122     { "drawextinfo",     (CmdProc)DrawExtInfoCmd, ASCII},
123     {
124         "stats",           StatsCmd, STATS       /* Array of: int8, (int?s for
125                                                 * that stat)
126                                                 */
127     },
128     { "image2",          Image2Cmd, MIXED      /* int, int8, int, PNG */ },
129     {
130         "face2",           Face2Cmd, MIXED       /* int16, int8, int32, string
131                                                 */
132     },
133     { "tick",            TickCmd, INT_ARRAY    /* uint32 */},
134 
135     { "music",           (CmdProc)MusicCmd, ASCII },
136     {
137         "sound2",          Sound2Cmd, MIXED      /* int8, int8, int8,  int8,
138                                                 * int8, int8, chars, int8,
139                                                 * chars
140                                                 */
141     },
142     { "anim",            AnimCmd, SHORT_ARRAY},
143     { "smooth",          SmoothCmd, SHORT_ARRAY},
144 
145     { "player",          PlayerCmd, MIXED      /* 3 ints, int8, str */ },
146     { "comc",            CompleteCmd, SHORT_INT },
147 
148     { "addme_failed",    (CmdProc)AddMeFail, NODATA },
149     { "addme_success",   (CmdProc)AddMeSuccess, NODATA },
150     { "version",         (CmdProc)VersionCmd, ASCII },
151     { "goodbye",         (CmdProc)GoodbyeCmd, NODATA },
152     { "setup",           (CmdProc)SetupCmd, ASCII},
153     { "failure",         (CmdProc)FailureCmd, ASCII},
154     { "accountplayers",  (CmdProc)AccountPlayersCmd, ASCII},
155 
156     { "query",           (CmdProc)handle_query, ASCII},
157     { "replyinfo",       ReplyInfoCmd, ASCII},
158     { "ExtendedTextSet", (CmdProc)SinkCmd, NODATA},
159     { "ExtendedInfoSet", (CmdProc)SinkCmd, NODATA},
160 
161     { "pickup",          PickupCmd, INT_ARRAY  /* uint32 */},
162 };
163 
164 /**
165  * The number of entries in #commands.
166  */
167 #define NCOMMANDS ((int)(sizeof(commands)/sizeof(struct CmdMapping)))
168 
169 GQuark client_error_quark();
170 
client_mapsize(int width,int height)171 void client_mapsize(int width, int height) {
172     // Store desired size in use_config to check results from the server.
173     use_config[CONFIG_MAPWIDTH] = width;
174     use_config[CONFIG_MAPHEIGHT] = height;
175 
176     // Set map size in case we receive 'map' before 'setup' commands.
177     mapdata_set_size(width, height);
178     cs_print_string(csocket.fd, "setup mapsize %dx%d", width, height);
179 }
180 
client_disconnect()181 void client_disconnect() {
182     LOG(LOG_DEBUG, "close_server_connection", "Closing server connection");
183     g_io_stream_close(G_IO_STREAM(csocket.fd), NULL, NULL);
184     g_object_unref(csocket.fd);
185     csocket.fd = NULL;
186 }
187 
client_run()188 void client_run() {
189     GError* err = NULL;
190     SockList inbuf;
191     inbuf.buf = g_malloc(MAXSOCKBUF);
192     if (!SockList_ReadPacket(csocket.fd, &inbuf, MAXSOCKBUF - 1, &err)) {
193     /*
194      * If a socket error occurred while reading the packet, drop the
195      * server connection.  Is there a better way to handle this?
196      */
197         if (!err) {
198             LOG(LOG_ERROR, "client_run", "%s", err->message);
199             g_error_free(err);
200         }
201         client_disconnect();
202         return;
203     }
204     if (inbuf.len == 0) {
205         client_disconnect();
206         return;
207     }
208     /*
209      * Null-terminate the buffer, and set the data pointer so it points
210      * to the first character of the data (following the packet length).
211      */
212     inbuf.buf[inbuf.len] = '\0';
213     unsigned char* data = inbuf.buf + 2;
214     /*
215      * Commands that provide data are always followed by a space.  Find
216      * the space and convert it to a null character.  If no spaces are
217      * found, the packet contains a command with no associatd data.
218      */
219     while ((*data != ' ') && (*data != '\0')) {
220         ++data;
221     }
222     int len;
223     if (*data == ' ') {
224         *data = '\0';
225         data++;
226         len = inbuf.len - (data - inbuf.buf);
227     } else {
228         len = 0;
229     }
230     /*
231      * Search for the command in the list of supported server commands.
232      * If the server command is supported by the client, let the script
233      * watcher know what command was received, then process it and quit
234      * searching the command list.
235      */
236     const char *cmdin = (char *)inbuf.buf + 2;
237     if (debug_protocol) {
238         // Extra spaces in front to make S->C easier to see
239         LOG(LOG_INFO, "    S->C", "len %d cmd %s", len, cmdin);
240     }
241     int i;
242     for(i = 0; i < NCOMMANDS; i++) {
243         if (strcmp(cmdin, commands[i].cmdname) == 0) {
244             script_watch(cmdin, data, len, commands[i].cmdformat);
245             commands[i].cmdproc(data, len);
246             break;
247         }
248     }
249     /*
250      * After processing the command, mark the socket input buffer empty.
251      */
252     inbuf.len=0;
253     /*
254      * Complain about unsupported commands to facilitate troubleshooting.
255      * The client and server should negotiate a connection such that the
256      * server does not send commands the client does not support.
257      */
258     if (i == NCOMMANDS) {
259         LOG(LOG_ERROR, "client_run", "Unrecognized command from server (%s)\n",
260             inbuf.buf + 2);
261         error_dialog("Server error",
262                      "The server sent an unrecognized command. "
263                      "Crossfire Client will now disconnect."
264                      "\n\nIf this problem persists with a particular "
265                      "character, try playing another character, and without "
266                      "disconnecting, playing the problematic character again.");
267         client_disconnect();
268     }
269     g_free(inbuf.buf);
270 }
271 
client_connect(const char hostname[static1])272 void client_connect(const char hostname[static 1]) {
273     GSocketClient *sclient = g_socket_client_new();
274 
275     // Store server hostname.
276     if (csocket.servername != NULL) {
277         g_free(csocket.servername);
278     }
279     csocket.servername = g_strdup(hostname);
280 
281     // Try to connect to server.
282     csocket.fd = g_socket_client_connect_to_host(
283             sclient, hostname, use_config[CONFIG_PORT], NULL, NULL);
284     g_object_unref(sclient);
285     if (csocket.fd == NULL) {
286         return;
287     }
288 
289     GSocket *socket = g_socket_connection_get_socket(csocket.fd);
290     int i = 1, fd = g_socket_get_fd(socket);
291 #ifndef WIN32
292 #ifdef HAVE_GIO_GNETWORKING_H
293     if (use_config[CONFIG_FASTTCP]) {
294         if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &i, sizeof(i)) == -1) {
295             perror("TCP_NODELAY");
296         }
297     }
298 #endif
299 #endif
300     in = g_io_stream_get_input_stream(G_IO_STREAM(csocket.fd));
301 }
302 
client_is_connected()303 bool client_is_connected() {
304     return csocket.fd != NULL && g_socket_connection_is_connected(csocket.fd);
305 }
306 
client_get_source()307 GSource *client_get_source() {
308     return g_pollable_input_stream_create_source(
309             G_POLLABLE_INPUT_STREAM(in), NULL);
310 }
311 
client_negotiate(int sound)312 void client_negotiate(int sound) {
313     int tries;
314 
315     SendVersion(csocket);
316 
317     /* We need to get the version command fairly early on because we need to
318      * know if the server will support a request to use png images.  This
319      * isn't done the best, because if the server never sends the version
320      * command, we can loop here forever.  However, if it doesn't send the
321      * version command, we have no idea what we are dealing with.
322      */
323     tries=0;
324     while (csocket.cs_version==0) {
325         client_run();
326         if (csocket.fd == NULL) {
327             return;
328         }
329 
330         usleep(10*1000);    /* 10 milliseconds */
331         tries++;
332         /* If we haven't got a response in 10 seconds, bail out */
333         if (tries > 1000) {
334             LOG (LOG_ERROR,"common::negotiate_connection", "Connection timed out");
335             client_disconnect();
336             return;
337         }
338     }
339 
340     if (csocket.sc_version<1023) {
341         LOG (LOG_WARNING,"common::negotiate_connection","Server does not support PNG images, yet that is all this client");
342         LOG (LOG_WARNING,"common::negotiate_connection","supports.  Either the server needs to be upgraded, or you need to");
343         LOG (LOG_WARNING,"common::negotiate_connection","downgrade your client.");
344         exit(1);
345     }
346 
347     /* If the user has specified a numeric face id, use it. If it is a string
348      * like base, then that resolves to 0, so no real harm in that.
349      */
350     if (face_info.want_faceset) {
351         face_info.faceset = atoi(face_info.want_faceset);
352     }
353 
354     /* For sound, a value following determines which sound features are
355      * wanted.  The value is 1 for sound effects, and 2 for background music,
356      * or the sum of 1 + 2 (3) for both.
357      *
358      * For spellmon, try each acceptable level, but make sure the one the
359      * client prefers is last.
360      */
361     cs_print_string(csocket.fd, "setup "
362             "map2cmd 1 tick 1 sound2 %d darkness %d spellmon 1 spellmon 2 "
363             "faceset %d facecache %d want_pickup 1 loginmethod %d newmapcmd 1 extendedTextInfos 1",
364             (sound >= 0) ? 3 : 0, want_config[CONFIG_LIGHTING] ? 1 : 0,
365             face_info.faceset, want_config[CONFIG_CACHE], wantloginmethod);
366 
367     /*
368      * We can do this right now also.  There is not any reason to wait.
369      */
370     cs_print_string(csocket.fd, "requestinfo skill_info");
371     cs_print_string(csocket.fd,"requestinfo exp_table");
372     /*
373      * While these are only used for new login method, they should become
374      * standard fairly soon.  All of these are pretty small, and do not add
375      * much to the cost.  They make it more likely that the information is
376      * ready when the window that needs it is raised.
377      */
378     cs_print_string(csocket.fd,"requestinfo motd");
379     cs_print_string(csocket.fd,"requestinfo news");
380     cs_print_string(csocket.fd,"requestinfo rules");
381 
382     client_mapsize(want_config[CONFIG_MAPWIDTH], want_config[CONFIG_MAPHEIGHT]);
383     use_config[CONFIG_SMOOTH]=want_config[CONFIG_SMOOTH];
384 
385     /* If the server will answer the requestinfo for image_info and image_data,
386      * send it and wait for the response.
387      */
388     if (csocket.sc_version >= 1027) {
389         /* last_start is -99.  This means the first face requested will be 1
390          * (not 0) - this is OK because 0 is defined as the blank face.
391          */
392         int last_end=0, last_start=-99;
393 
394         cs_print_string(csocket.fd,"requestinfo image_info");
395         requestinfo_sent = RI_IMAGE_INFO;
396         replyinfo_status = 0;
397         replyinfo_last_face = 0;
398 
399         do {
400             client_run();
401             /*
402              * It is rare, but the connection can die while getting this info.
403              */
404             if (csocket.fd == NULL) {
405                 return;
406             }
407 
408             if (use_config[CONFIG_DOWNLOAD]) {
409                 /*
410                  * We need to know how many faces to be able to make the
411                  * request intelligently.  So only do the following block if
412                  * we have that info.  By setting the sent flag, we will never
413                  * exit this loop until that happens.
414                  */
415                 requestinfo_sent |= RI_IMAGE_SUMS;
416                 if (face_info.num_images != 0) {
417                     /*
418                      * Sort of fake things out - if we have sent the request
419                      * for image sums but have not got them all answered yet,
420                      * we then clear the bit from the status so we continue to
421                      * loop.
422                      */
423                     if (last_end == face_info.num_images) {
424                         /* Mark that we're all done */
425                         if (replyinfo_last_face == last_end) {
426                             replyinfo_status |= RI_IMAGE_SUMS;
427                             image_update_download_status(face_info.num_images, face_info.num_images, face_info.num_images);
428                         }
429                     } else {
430                         /*
431                          * If we are all caught up, request another 100 sums.
432                          */
433                         if (last_end <= (replyinfo_last_face+100)) {
434                             last_start += 100;
435                             last_end += 100;
436                             if (last_end > face_info.num_images) {
437                                 last_end = face_info.num_images;
438                             }
439                             cs_print_string(csocket.fd,"requestinfo image_sums %d %d", last_start, last_end);
440                             image_update_download_status(last_start, last_end, face_info.num_images);
441                         }
442                     }
443                 } /* Still have image_sums request to send */
444             } /* endif download all faces */
445 
446             usleep(10*1000);    /* 10 milliseconds */
447             /*
448              * Do not put in an upper time limit with tries like we did above.
449              * If the player is downloading all the images, the time this
450              * takes could be considerable.
451              */
452         } while (replyinfo_status != requestinfo_sent);
453     }
454     if (use_config[CONFIG_DOWNLOAD]) {
455         char buf[MAX_BUF];
456 
457         snprintf(buf, sizeof(buf), "Download of images complete.  Found %d locally, downloaded %d from server\n",
458                  face_info.cache_hits, face_info.cache_misses);
459         draw_ext_info(NDI_GOLD, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_CONFIG, buf);
460     }
461 
462     /* This needs to get changed around - we really don't want to send the
463      * SendAddMe until we do all of our negotiation, which may include things
464      * like downloading all the images and whatnot - this is more an issue if
465      * the user is not using the default face set, as in that case, we might
466      * end up building images from the wrong set.
467      * Only run this if not using new login method
468      */
469     if (!serverloginmethod) {
470         SendAddMe(csocket);
471     }
472 }
473