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