Rulesave saves trade.type and trade.bonus correctly.
[freeciv.git] / client / connectdlg_common.c
blob2ec9b13861dddc3ce36053bbdc1a50f62270486b
1 /**********************************************************************
2 Freeciv - Copyright (C) 2004 - The Freeciv Project
3 This program is free software; you can redistribute it and / or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
13 #ifdef HAVE_CONFIG_H
14 #include <fc_config.h>
15 #endif
17 #include "fc_prehdrs.h"
19 #include <fcntl.h>
20 #include <stdio.h>
21 #include <signal.h> /* SIGTERM and kill */
22 #include <string.h>
23 #include <time.h>
25 #ifdef WIN32_NATIVE
26 #include <windows.h>
27 #endif
29 #ifdef FREECIV_HAVE_SYS_TYPES_H
30 #include <sys/types.h> /* fchmod */
31 #endif
33 #ifdef HAVE_SYS_STAT_H
34 #include <sys/stat.h> /* fchmod */
35 #endif
37 #ifdef HAVE_SYS_WAIT_H
38 #include <sys/wait.h>
39 #endif
41 /* utility */
42 #include "astring.h"
43 #include "capability.h"
44 #include "deprecations.h"
45 #include "fciconv.h"
46 #include "fcintl.h"
47 #include "ioz.h"
48 #include "log.h"
49 #include "mem.h"
50 #include "netintf.h"
51 #include "rand.h"
52 #include "registry.h"
53 #include "shared.h"
54 #include "support.h"
56 /* client */
57 #include "client_main.h"
58 #include "climisc.h"
59 #include "clinet.h" /* connect_to_server() */
60 #include "packhand_gen.h"
62 #include "chatline_common.h"
63 #include "connectdlg_g.h"
64 #include "connectdlg_common.h"
65 #include "tilespec.h"
67 #define WAIT_BETWEEN_TRIES 100000 /* usecs */
68 #define NUMBER_OF_TRIES 500
70 #if defined(HAVE_WORKING_FORK) && !defined(WIN32_NATIVE)
71 /* We are yet to see WIN32_NATIVE setup where even HAVE_WORKING_FORK would
72 * mean fork() that actually works for us. */
73 #define HAVE_USABLE_FORK
74 #endif
76 #ifdef HAVE_USABLE_FORK
77 static pid_t server_pid = -1;
78 #elif FREECIV_MSWINDOWS
79 HANDLE server_process = INVALID_HANDLE_VALUE;
80 HANDLE loghandle = INVALID_HANDLE_VALUE;
81 #endif /* HAVE_USABLE_FORK || FREECIV_MSWINDOWS */
82 bool server_quitting = FALSE;
84 static char challenge_fullname[MAX_LEN_PATH];
85 static bool client_has_hack = FALSE;
87 int internal_server_port;
89 /**************************************************************************
90 The general chain of events:
92 Two distinct paths are taken depending on the choice of mode:
94 if the user selects the multi- player mode, then a packet_req_join_game
95 packet is sent to the server. It is either successful or not. The end.
97 If the user selects a single- player mode (either a new game or a save game)
98 then:
99 1. the packet_req_join_game is sent.
100 2. on receipt, if we can join, then a challenge packet is sent to the
101 server, so we can get hack level control.
102 3. if we can't get hack, then we get dumped to multi- player mode. If
103 we can, then:
104 a. for a new game, we send a series of packet_generic_message packets
105 with commands to start the game.
106 b. for a saved game, we send the load command with a
107 packet_generic_message, then we send a PACKET_PLAYER_LIST_REQUEST.
108 the response to this request will tell us if the game was loaded or
109 not. if not, then we send another load command. if so, then we send
110 a series of packet_generic_message packets with commands to start
111 the game.
112 **************************************************************************/
114 /**************************************************************************
115 Tests if the client has started the server.
116 **************************************************************************/
117 bool is_server_running(void)
119 if (server_quitting) {
120 return FALSE;
122 #ifdef HAVE_USABLE_FORK
123 return (server_pid > 0);
124 #elif FREECIV_MSWINDOWS
125 return (server_process != INVALID_HANDLE_VALUE);
126 #else
127 return FALSE; /* We've been unable to start one! */
128 #endif
131 /**************************************************************************
132 Returns TRUE if the client has hack access.
133 **************************************************************************/
134 bool can_client_access_hack(void)
136 return client_has_hack;
139 /****************************************************************************
140 Kills the server if the client has started it.
142 If the 'force' parameter is unset, we just do a /quit. If it's set, then
143 we'll send a signal to the server to kill it (use this when the socket
144 is disconnected already).
145 ****************************************************************************/
146 void client_kill_server(bool force)
148 #ifdef HAVE_USABLE_FORK
149 if (server_quitting && server_pid > 0) {
150 /* Already asked to /quit.
151 * If it didn't do that, kill it. */
152 if (waitpid(server_pid, NULL, WUNTRACED) <= 0) {
153 kill(server_pid, SIGTERM);
154 waitpid(server_pid, NULL, WUNTRACED);
156 server_pid = -1;
157 server_quitting = FALSE;
159 #elif FREECIV_MSWINDOWS
160 if (server_quitting && server_process != INVALID_HANDLE_VALUE) {
161 /* Already asked to /quit.
162 * If it didn't do that, kill it. */
163 TerminateProcess(server_process, 0);
164 CloseHandle(server_process);
165 if (loghandle != INVALID_HANDLE_VALUE) {
166 CloseHandle(loghandle);
168 server_process = INVALID_HANDLE_VALUE;
169 loghandle = INVALID_HANDLE_VALUE;
170 server_quitting = FALSE;
172 #endif /* FREECIV_MSWINDOWS || HAVE_USABLE_FORK */
174 if (is_server_running()) {
175 if (client.conn.used && client_has_hack) {
176 /* This does a "soft" shutdown of the server by sending a /quit.
178 * This is useful when closing the client or disconnecting because it
179 * doesn't kill the server prematurely. In particular, killing the
180 * server in the middle of a save can have disastrous results. This
181 * method tells the server to quit on its own. This is safer from a
182 * game perspective, but more dangerous because if the kill fails the
183 * server will be left running.
185 * Another potential problem is because this function is called atexit
186 * it could potentially be called when we're connected to an unowned
187 * server. In this case we don't want to kill it. */
188 send_chat("/quit");
189 server_quitting = TRUE;
190 } else if (force) {
191 /* Either we already disconnected, or we didn't get control of the
192 * server. In either case, the only thing to do is a "hard" kill of
193 * the server. */
194 #ifdef HAVE_USABLE_FORK
195 kill(server_pid, SIGTERM);
196 waitpid(server_pid, NULL, WUNTRACED);
197 server_pid = -1;
198 #elif FREECIV_MSWINDOWS
199 TerminateProcess(server_process, 0);
200 CloseHandle(server_process);
201 if (loghandle != INVALID_HANDLE_VALUE) {
202 CloseHandle(loghandle);
204 server_process = INVALID_HANDLE_VALUE;
205 loghandle = INVALID_HANDLE_VALUE;
206 #endif /* FREECIV_MSWINDOWS || HAVE_USABLE_FORK */
207 server_quitting = FALSE;
210 client_has_hack = FALSE;
213 /****************************************************************
214 Forks a server if it can. Returns FALSE if we find we
215 couldn't start the server.
216 *****************************************************************/
217 bool client_start_server(void)
219 #if !defined(HAVE_USABLE_FORK) && !defined(WIN32_NATIVE)
220 /* Can't do much without fork */
221 return FALSE;
222 #else /* HAVE_USABLE_FORK || WIN32_NATIVE */
223 char buf[512];
224 int connect_tries = 0;
225 #if !defined(HAVE_USABLE_FORK)
226 /* Above also implies that this is WIN32_NATIVE ->
227 * Win32 that can't use fork() */
228 STARTUPINFO si;
229 PROCESS_INFORMATION pi;
231 char savesdir[MAX_LEN_PATH];
232 char scensdir[MAX_LEN_PATH];
233 char options[512];
234 char *depr;
235 #ifdef FREECIV_DEBUG
236 char cmdline1[512];
237 char cmdline2[512];
238 #endif /* FREECIV_DEBUG */
239 char cmdline3[512];
240 char cmdline4[512];
241 char logcmdline[512];
242 char scriptcmdline[512];
243 char savefilecmdline[512];
244 char savescmdline[512];
245 char scenscmdline[512];
246 #endif /* !HAVE_USABLE_FORK -> WIN32_NATIVE */
248 #ifdef FREECIV_IPV6_SUPPORT
249 enum fc_addr_family family = FC_ADDR_ANY;
250 #else
251 enum fc_addr_family family = FC_ADDR_IPV4;
252 #endif /* FREECIV_IPV6_SUPPORT */
254 /* only one server (forked from this client) shall be running at a time */
255 /* This also resets client_has_hack. */
256 client_kill_server(TRUE);
258 output_window_append(ftc_client, _("Starting local server..."));
260 /* find a free port */
261 /* Mitigate the risk of ending up with the port already
262 * used by standalone server on Windows where this is known to be buggy
263 * by not starting from DEFAULT_SOCK_PORT but from one higher. */
264 internal_server_port = find_next_free_port(DEFAULT_SOCK_PORT + 1,
265 DEFAULT_SOCK_PORT + 1 + 10000,
266 family, "localhost", TRUE);
268 if (internal_server_port < 0) {
269 output_window_append(ftc_client, _("Couldn't start the server."));
270 output_window_append(ftc_client,
271 _("You'll have to start one manually. Sorry..."));
272 return FALSE;
275 #ifdef HAVE_USABLE_FORK
277 int argc = 0;
278 const int max_nargs = 23;
279 char *argv[max_nargs + 1];
280 char port_buf[32];
281 char dbg_lvl_buf[32]; /* Do not move this inside the block where it gets filled,
282 * it's needed via the argv[x] pointer later on, so must
283 * remain in scope. */
285 /* Set up the command-line parameters. */
286 fc_snprintf(port_buf, sizeof(port_buf), "%d", internal_server_port);
287 argv[argc++] = "freeciv-server";
288 argv[argc++] = "-p";
289 argv[argc++] = port_buf;
290 argv[argc++] = "--bind";
291 argv[argc++] = "localhost";
292 argv[argc++] = "-q";
293 argv[argc++] = "1";
294 argv[argc++] = "-e";
295 argv[argc++] = "--saves";
296 argv[argc++] = "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR "saves";
297 argv[argc++] = "--scenarios";
298 argv[argc++] = "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR "scenarios";
299 argv[argc++] = "-A";
300 argv[argc++] = "none";
301 if (logfile) {
302 enum log_level llvl = log_get_level();
304 argv[argc++] = "--debug";
305 fc_snprintf(dbg_lvl_buf, sizeof(dbg_lvl_buf), "%d", llvl);
306 argv[argc++] = dbg_lvl_buf;
307 argv[argc++] = "--log";
308 argv[argc++] = logfile;
310 if (scriptfile) {
311 argv[argc++] = "--read";
312 argv[argc++] = scriptfile;
314 if (savefile) {
315 argv[argc++] = "--file";
316 argv[argc++] = savefile;
318 if (are_deprecation_warnings_enabled()) {
319 argv[argc++] = "--warnings";
321 argv[argc] = NULL;
322 fc_assert(argc <= max_nargs);
325 struct astring srv_cmdline_opts = ASTRING_INIT;
326 int i;
328 for (i = 1; i < argc; i++) {
329 astr_add(&srv_cmdline_opts, i == 1 ? "%s" : " %s", argv[i]);
331 log_verbose("Arguments to spawned server: %s",
332 astr_str(&srv_cmdline_opts));
333 astr_free(&srv_cmdline_opts);
336 server_pid = fork();
337 server_quitting = FALSE;
339 if (server_pid == 0) {
340 int fd;
342 /* inside the child */
344 /* avoid terminal spam, but still make server output available */
345 fclose(stdout);
346 fclose(stderr);
348 /* FIXME: include the port to avoid duplication? */
349 if (logfile) {
350 fd = open(logfile, O_WRONLY | O_CREAT | O_APPEND, 0644);
352 if (fd != 1) {
353 dup2(fd, 1);
355 if (fd != 2) {
356 dup2(fd, 2);
358 fchmod(1, 0644);
361 /* If it's still attatched to our terminal, things get messed up,
362 but freeciv-server needs *something* */
363 fclose(stdin);
364 fd = open("/dev/null", O_RDONLY);
365 if (fd != 0) {
366 dup2(fd, 0);
369 /* these won't return on success */
370 #ifdef DEBUG
371 /* Search under current directory (what ever that happens to be)
372 * only in debug builds. This allows running freeciv directly from build
373 * tree, but could be considered security risk in release builds. */
374 execvp("./fcser", argv);
375 execvp("./server/freeciv-server", argv);
376 #endif /* DEBUG */
377 execvp(BINDIR "/freeciv-server", argv);
378 execvp("freeciv-server", argv);
380 /* This line is only reached if freeciv-server cannot be started,
381 * so we kill the forked process.
382 * Calling exit here is dangerous due to X11 problems (async replies) */
383 _exit(1);
386 #else /* HAVE_USABLE_FORK */
387 #ifdef WIN32_NATIVE
388 if (logfile) {
389 loghandle = CreateFile(logfile, GENERIC_WRITE,
390 FILE_SHARE_READ | FILE_SHARE_WRITE,
391 NULL,
392 OPEN_ALWAYS, 0, NULL);
395 ZeroMemory(&si, sizeof(si));
396 si.cb = sizeof(si);
397 si.hStdOutput = loghandle;
398 si.hStdInput = INVALID_HANDLE_VALUE;
399 si.hStdError = loghandle;
400 si.dwFlags = STARTF_USESTDHANDLES;
402 /* Set up the command-line parameters. */
403 logcmdline[0] = 0;
404 scriptcmdline[0] = 0;
405 savefilecmdline[0] = 0;
407 /* the server expects command line arguments to be in local encoding */
408 if (logfile) {
409 char *logfile_in_local_encoding =
410 internal_to_local_string_malloc(logfile);
411 enum log_level llvl = log_get_level();
413 fc_snprintf(logcmdline, sizeof(logcmdline), " --debug %d --log %s",
414 llvl, logfile_in_local_encoding);
415 free(logfile_in_local_encoding);
417 if (scriptfile) {
418 char *scriptfile_in_local_encoding =
419 internal_to_local_string_malloc(scriptfile);
421 fc_snprintf(scriptcmdline, sizeof(scriptcmdline), " --read %s",
422 scriptfile_in_local_encoding);
423 free(scriptfile_in_local_encoding);
425 if (savefile) {
426 char *savefile_in_local_encoding =
427 internal_to_local_string_malloc(savefile);
429 fc_snprintf(savefilecmdline, sizeof(savefilecmdline), " --file %s",
430 savefile_in_local_encoding);
431 free(savefile_in_local_encoding);
434 interpret_tilde(savesdir, sizeof(savesdir),
435 "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR "saves");
436 internal_to_local_string_buffer(savesdir, savescmdline, sizeof(savescmdline));
438 interpret_tilde(scensdir, sizeof(scensdir),
439 "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR "scenarios");
440 internal_to_local_string_buffer(scensdir, scenscmdline, sizeof(scenscmdline));
442 if (are_deprecation_warnings_enabled()) {
443 depr = " --warnings";
444 } else {
445 depr = "";
448 fc_snprintf(options, sizeof(options),
449 "-p %d --bind localhost -q 1 -e%s%s%s --saves \"%s\" "
450 "--scenarios \"%s\" -A none %s",
451 internal_server_port, logcmdline, scriptcmdline, savefilecmdline,
452 savescmdline, scenscmdline, depr);
453 #ifdef DEBUG
454 fc_snprintf(cmdline1, sizeof(cmdline1), "./fcser %s", options);
455 fc_snprintf(cmdline2, sizeof(cmdline2),
456 "./server/freeciv-server %s", options);
457 #endif /* DEBUG */
458 fc_snprintf(cmdline3, sizeof(cmdline3),
459 BINDIR "/freeciv-server %s", options);
460 fc_snprintf(cmdline4, sizeof(cmdline4),
461 "freeciv-server %s", options);
463 if (
464 #ifdef DEBUG
465 !CreateProcess(NULL, cmdline1, NULL, NULL, TRUE,
466 DETACHED_PROCESS | NORMAL_PRIORITY_CLASS,
467 NULL, NULL, &si, &pi)
468 && !CreateProcess(NULL, cmdline2, NULL, NULL, TRUE,
469 DETACHED_PROCESS | NORMAL_PRIORITY_CLASS,
470 NULL, NULL, &si, &pi)
472 #endif /* DEBUG */
473 !CreateProcess(NULL, cmdline3, NULL, NULL, TRUE,
474 DETACHED_PROCESS | NORMAL_PRIORITY_CLASS,
475 NULL, NULL, &si, &pi)
476 && !CreateProcess(NULL, cmdline4, NULL, NULL, TRUE,
477 DETACHED_PROCESS | NORMAL_PRIORITY_CLASS,
478 NULL, NULL, &si, &pi)) {
479 log_error("Failed to start server process.");
480 #ifdef DEBUG
481 log_verbose("Tried with commandline: '%s'", cmdline1);
482 log_verbose("Tried with commandline: '%s'", cmdline2);
483 #endif /* DEBUG */
484 log_verbose("Tried with commandline: '%s'", cmdline3);
485 log_verbose("Tried with commandline: '%s'", cmdline4);
486 output_window_append(ftc_client, _("Couldn't start the server."));
487 output_window_append(ftc_client,
488 _("You'll have to start one manually. Sorry..."));
489 return FALSE;
492 log_verbose("Arguments to spawned server: %s", options);
494 server_process = pi.hProcess;
496 #endif /* WIN32_NATIVE */
497 #endif /* HAVE_USABLE_FORK */
499 /* a reasonable number of tries */
500 while (connect_to_server(user_name, "localhost", internal_server_port,
501 buf, sizeof(buf)) == -1) {
502 fc_usleep(WAIT_BETWEEN_TRIES);
503 #ifdef HAVE_USABLE_FORK
504 #ifndef WIN32_NATIVE
505 if (waitpid(server_pid, NULL, WNOHANG) != 0) {
506 break;
508 #endif /* WIN32_NATIVE */
509 #endif /* HAVE_USABLE_FORK */
510 if (connect_tries++ > NUMBER_OF_TRIES) {
511 log_error("Last error from connect attempts: '%s'", buf);
512 break;
516 /* weird, but could happen, if server doesn't support new startup stuff
517 * capabilities won't help us here... */
518 if (!client.conn.used) {
519 /* possible that server is still running. kill it */
520 client_kill_server(TRUE);
522 log_error("Failed to connect to spawned server!");
523 output_window_append(ftc_client, _("Couldn't connect to the server."));
524 output_window_append(ftc_client,
525 _("We probably couldn't start it from here."));
526 output_window_append(ftc_client,
527 _("You'll have to start one manually. Sorry..."));
529 return FALSE;
532 /* We set the topology to match the view.
534 * When a typical player launches a game, he wants the map orientation to
535 * match the tileset orientation. So if you use an isometric tileset you
536 * get an iso-map and for a classic tileset you get a classic map. In
537 * both cases the map wraps in the X direction by default.
539 * This works with hex maps too now. A hex map always has
540 * tileset_is_isometric(tileset) return TRUE. An iso-hex map has
541 * tileset_hex_height(tileset) != 0, while a non-iso hex map
542 * has tileset_hex_width(tileset) != 0.
544 * Setting the option here is a bit of a hack, but so long as the client
545 * has sufficient permissions to do so (it doesn't have HACK access yet) it
546 * is safe enough. Note that if you load a savegame the topology will be
547 * set but then overwritten during the load.
549 * Don't send it now, it will be sent to the server when receiving the
550 * server setting infos. */
552 char topobuf[16];
554 fc_strlcpy(topobuf, "WRAPX", sizeof(topobuf));
555 if (tileset_is_isometric(tileset) && 0 == tileset_hex_height(tileset)) {
556 fc_strlcat(topobuf, "|ISO", sizeof(topobuf));
558 if (0 < tileset_hex_width(tileset) || 0 < tileset_hex_height(tileset)) {
559 fc_strlcat(topobuf, "|HEX", sizeof(topobuf));
561 desired_settable_option_update("topology", topobuf, FALSE);
564 return TRUE;
565 #endif /* HAVE_USABLE_FORK || WIN32_NATIVE */
568 /*************************************************************************
569 generate a random string.
570 *************************************************************************/
571 static void randomize_string(char *str, size_t n)
573 const char chars[] =
574 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
575 int i;
577 for (i = 0; i < n - 1; i++) {
578 str[i] = chars[fc_rand(sizeof(chars) - 1)];
580 str[i] = '\0';
583 /****************************************************************
584 if the client is capable of 'wanting hack', then the server will
585 send the client a filename in the packet_join_game_reply packet.
587 this function creates the file with a suitably random string in it
588 and then sends the string to the server. If the server can open
589 and read the string, then the client is given hack access.
590 *****************************************************************/
591 void send_client_wants_hack(const char *filename)
593 if (filename[0] != '\0') {
594 struct packet_single_want_hack_req req;
595 struct section_file *file;
597 if (!is_safe_filename(filename)) {
598 return;
601 /* get the full filename path */
602 interpret_tilde(challenge_fullname, sizeof(challenge_fullname),
603 "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR);
604 make_dir(challenge_fullname);
606 sz_strlcat(challenge_fullname, filename);
608 /* generate an authentication token */
609 randomize_string(req.token, sizeof(req.token));
611 file = secfile_new(FALSE);
612 secfile_insert_str(file, req.token, "challenge.token");
613 if (!secfile_save(file, challenge_fullname, 0, FZ_PLAIN)) {
614 log_error("Couldn't write token to temporary file: %s",
615 challenge_fullname);
617 secfile_destroy(file);
619 /* tell the server what we put into the file */
620 send_packet_single_want_hack_req(&client.conn, &req);
624 /****************************************************************
625 handle response (by the server) if the client has got hack or not.
626 *****************************************************************/
627 void handle_single_want_hack_reply(bool you_have_hack)
629 /* remove challenge file */
630 if (challenge_fullname[0] != '\0') {
631 if (fc_remove(challenge_fullname) == -1) {
632 log_error("Couldn't remove temporary file: %s", challenge_fullname);
634 challenge_fullname[0] = '\0';
637 if (you_have_hack) {
638 output_window_append(ftc_client,
639 _("Established control over the server. "
640 "You have command access level 'hack'."));
641 client_has_hack = TRUE;
642 } else if (is_server_running()) {
643 /* only output this if we started the server and we NEED hack */
644 output_window_append(ftc_client,
645 _("Failed to obtain the required access "
646 "level to take control of the server. "
647 "Attempting to shut down server."));
648 client_kill_server(TRUE);
652 /****************************************************************
653 send server command to save game.
654 *****************************************************************/
655 void send_save_game(const char *filename)
657 if (filename) {
658 send_chat_printf("/save %s", filename);
659 } else {
660 send_chat("/save");
664 /**************************************************************************
665 Handle the list of rulesets sent by the server.
666 **************************************************************************/
667 void handle_ruleset_choices(const struct packet_ruleset_choices *packet)
669 char *rulesets[packet->ruleset_count];
670 int i;
671 size_t suf_len = strlen(RULESET_SUFFIX);
673 for (i = 0; i < packet->ruleset_count; i++) {
674 size_t len = strlen(packet->rulesets[i]);
676 rulesets[i] = fc_strdup(packet->rulesets[i]);
678 if (len > suf_len
679 && strcmp(rulesets[i] + len - suf_len, RULESET_SUFFIX) == 0) {
680 rulesets[i][len - suf_len] = '\0';
683 set_rulesets(packet->ruleset_count, rulesets);
685 for (i = 0; i < packet->ruleset_count; i++) {
686 free(rulesets[i]);
690 /**************************************************************************
691 Called by the GUI code when the user sets the ruleset. The ruleset
692 passed in here should match one of the strings given to set_rulesets().
693 **************************************************************************/
694 void set_ruleset(const char *ruleset)
696 char buf[4096];
698 fc_snprintf(buf, sizeof(buf), "/read %s%s", ruleset, RULESET_SUFFIX);
699 log_debug("Executing '%s'", buf);
700 send_chat(buf);