Complete.Org: Mailing Lists: Archives: freeciv-dev: April 2004:
[Freeciv-Dev] (PR#8491) Re: Freeciv commit: kauf: A new connect dialog f
Home

[Freeciv-Dev] (PR#8491) Re: Freeciv commit: kauf: A new connect dialog f

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: undisclosed-recipients: ;
Subject: [Freeciv-Dev] (PR#8491) Re: Freeciv commit: kauf: A new connect dialog for the client. This allo...
From: "Raimar Falke" <i-freeciv-lists@xxxxxxxxxxxxx>
Date: Mon, 12 Apr 2004 02:40:25 -0700
Reply-to: rt@xxxxxxxxxxx

<URL: http://rt.freeciv.org/Ticket/Display.html?id=8491 >

On Fri, Apr 09, 2004 at 09:00:42PM -0700, Raimar Falke wrote:

There are several problems in this commit. Some minor some more
serious. They should all be fixed.

> +/************************************************************************** 
> +The general chain of events:
> +
> +Two distinct paths are taken depending on the choice of mode: 
> +
> +if the user selects the multi- player mode, then a packet_req_join_game 
> +packet is sent to the server. It is either successful or not. The end.
> +
> +If the user selects a single- player mode (either a new game or a save game) 
> +then: 
> +   1. the packet_req_join_game is sent.
> +   2. on receipt, if we can join, then a challenge packet is sent to the
> +      server, so we can get hack level control.
> +   3. if we can't get hack, then we get dumped to multi- player mode. If
> +      we can, then:
> +      a. for a new game, we send a series of packet_generic_message packets
> +         with commands to start the game.
> +      b. for a saved game, we send the load command with a 
> +         packet_generic_message, then we send a PACKET_PLAYER_LIST_REQUEST.
> +         the response to this request will tell us if the game was loaded or
> +         not. if not, then we send another load command. if so, then we send
> +         a series of packet_generic_message packets with commands to start 
> +         the game.
> +**************************************************************************/ 

There is no packet_generic_message.

> +/**************************************************************** 
> +forks a server if it can. returns FALSE is we find we couldn't start
> +the server.
> +This is so system-intensive that it's *nix only.  VMS and Windows 
> +code will come later 
> +*****************************************************************/ 

AFAIK the comments are written in English. AFAIK every English
sentence starts with a capitalized word. This is the problem of the
whole commit.

> +  argv[i] = fc_malloc(10);
> +  my_snprintf(argv[i++], 10, "civserver");
> +  argv[i] = fc_malloc(3);
> +  my_snprintf(argv[i++], 3, "-p");
> +  argv[i] = fc_malloc(7);
> +  my_snprintf(argv[i++], 7, "%d", server_port);

I would have written this with a buffer:

my_snprintf(buffer, sizeof(buffer), "civserver");
argv[i] = mystrdup(buffer);
...
Less numbers which you can get wrong.

> +  server_pid = fork();
> +  
> +  if (server_pid == 0) {
> +    int fd;
> +
> +    /* inside the fork */  

"the child".

> +    /* avoid terminal spam, but still make server output available */ 

add "by redirecting the output to the logfile".

> +    /* these won't return on success */ 
> +    execvp("./ser", argv);
> +    execvp("./server/civserver", argv);
> +    execvp("civserver", argv);

> +    /* This line is only reached if civserver cannot be started, 
> +     * so we kill the forked process.

> +     * Calling exit here is dangerous due to X11 problems (async replies) */

We use exit in a lot of other places. Can someone provide further
informations?

> +    _exit(1);

> +  /* don't need these anymore */ 

Add "don't free the terminating NULL".

> +  for (i = 0; i < nargs - 1; i++) {
> +    free(argv[i]);
> +  }
> +  free(argv);
> +
> +  /* a reasonable number of tries */ 
> +  while(connect_to_server((char *)user_username(), "localhost", server_port, 
> +                          buf, sizeof(buf)) == -1) {
> +    myusleep(WAIT_BETWEEN_TRIES);
> +

> +    if (i > NUMBER_OF_TRIES)  break;

Add {}

> +    i++;
> +  }

> +  /* weird, but could happen, if server doesn't support new startup stuff
> +   * capabilities won't help us here... */ 
> +  if (!aconnection.used) {

> +    /* possible that server is still running. kill it */ 
> +    kill(server_pid, SIGTERM);
> +    server_pid = -1;

Use function client_kill_server.

> +    append_output_window(_("Couldn't connect to the server. We probably "
> +                           "couldn't start it from here. You'll have to "
> +                           "start one manually. Sorry..."));
> +    return FALSE;
> +  }
> +
> +  return TRUE;
> +#else /*VMS or Win32*/
> +  return FALSE;
> +#endif
> +}
> +
> +/************************************************************************** 
> +Finds the lowest port which can be used for the 
> +server (starting at the 5555C)
> +**************************************************************************/ 
> +int min_free_port(void)
> +{
> +  int port, n, s;
> +  struct sockaddr_in tmp;
> +  
> +  s = socket(AF_INET, SOCK_STREAM, 0);
> +  n = INADDR_ANY;
> +  port = 5554; /* make looping convenient */ 
> +  do {
> +    port++;
> +    memset(&tmp, 0, sizeof(struct sockaddr_in));
> +    tmp.sin_family = AF_INET;
> +    tmp.sin_port = htons(port);
> +    memcpy(&tmp. sin_addr, &n, sizeof(long));
> +  } while(bind(s, (struct sockaddr*) &tmp, sizeof(struct sockaddr_in)));
> +
> +  my_closesocket(s);
> +  
> +  return port;
> +}
> +
> +/**************************************************************** 
> +if the client is capable of 'wanting hack', then the server will 
> +send the client a filename in the packet_join_game_reply packet.
> +
> +this function creates the file with a suitably random number in it 
> +and then sends the filename and number to the server. If the server 
> +can open and read the number, then the client is given hack access.
> +*****************************************************************/ 

> +void send_client_wants_hack(char * filename)

Add "const".

> +{
> +  struct section_file file;
> +
> +  /* find some entropy */ 
> +  int entropy = myrand(MAX_UINT32) - time(NULL);

> +  /* we don't want zero */ 
> +  if (entropy == 0) {
> +    entropy++;
> +  }

Why?

> +  section_file_init(&file);
> +  secfile_insert_int(&file, entropy, "challenge.entropy");
> +  section_file_save(&file, filename, 0);      
> +  section_file_free(&file);
> +
> +  /* tell the server what we put into the file */ 
> +  dsend_packet_single_want_hack_req(&aconnection, entropy);
> +}
> +
> +/**************************************************************** 
> +handle response (by the server) if the client has got hack or not.
> +*****************************************************************/ 
> +void handle_single_want_hack_reply(bool you_have_hack)
> +{
> +  if (you_have_hack) {

> +    append_output_window("We have control of the server "
> +                         "(command access level hack)");

i18n

> +    client_has_hack = TRUE;
> +  } else if (is_server_running()) {
> +    /* only output this if we started the server and we NEED hack */

> +    append_output_window("We can't take control of server, "
> +                         "attempting to kill it.");

i18n

> +    client_kill_server();
> +  }
> +}
> +
> +/**************************************************************** 
> +send server commands to start a saved game.
> +*****************************************************************/ 
> +void send_start_saved_game(void)
> +{   
> +  char buf[MAX_LEN_MSG];
> +
> +  send_chat("/set timeout 0");
> +  send_chat("/set autotoggle 1");
> +  my_snprintf(buf, sizeof(buf), "/take %s %s", user_name, player_name);
> +  send_chat(buf);
> +  send_chat("/start");
> +}
> +
> +/**************************************************************** 
> +send server command to save game.
> +*****************************************************************/ 

> +void send_save_game(char *filename)

Add "const"

> +{   
> +  char message[MAX_LEN_MSG];
> +
> +  if (filename) {
> +    my_snprintf(message, MAX_LEN_MSG, "/save %s", filename);
> +  } else {
> +    my_snprintf(message, MAX_LEN_MSG, "/save");
> +  }
> +
> +  send_chat(message);
> +}
> Index: client/connectdlg_common.h
> ===================================================================
> RCS file: client/connectdlg_common.h
> diff -N client/connectdlg_common.h
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ client/connectdlg_common.h        10 Apr 2004 03:47:48 -0000      1.1
> @@ -0,0 +1,44 @@
> +/********************************************************************** 
> +Freeciv - Copyright (C) 2003 - The Freeciv Project
> +   This program is free software; you can redistribute it and / or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 2, or (at your option)
> +   any later version.
> +
> +   This program is distributed in the hope that it will be useful, 
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +***********************************************************************/ 
> +#ifndef FC__CONNECTDLG_COMMON_H
> +#define FC__CONNECTDLG_COMMON_H
> +
> +#include "shared.h"
> +
> +bool client_start_server(void);
> +void client_kill_server(void);
> +bool is_server_running(void);
> +
> +int min_free_port(void);
> +
> +void send_client_wants_hack(char *filename);
> +void send_start_saved_game(void);
> +void send_save_game(char *filename);
> +
> +extern char player_name[MAX_LEN_NAME];
> +extern char *current_filename;

> +enum skill_levels { 
> +  NOVICE, 
> +  EASY, 
> +  NORMAL, 
> +  HARD, 
> +  EXPERIMENTAL,
> +  NUM_SKILL_LEVELS
> +};
> +
> +extern const char *skill_level_names[NUM_SKILL_LEVELS];

Shouldn't/can't these be shared with the server.

> +extern bool client_has_hack;
> +
> +#endif  /* FC__CONNECTDLG_COMMON_H */ 


> Index: client/include/connectdlg_g.h
> ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/client/include/connectdlg_g.h,v
> retrieving revision 1.5
> retrieving revision 1.6
> diff -u -r1.5 -r1.6
> --- client/include/connectdlg_g.h     29 Nov 2003 09:52:45 -0000      1.5
> +++ client/include/connectdlg_g.h     10 Apr 2004 03:47:49 -0000      1.6
> @@ -14,6 +14,7 @@
>  #define FC__CONNECTDLG_G_H
>  
>  void close_connection_dialog(void);

> +void really_close_connection_dialog(void);

The naming is very poor. The documentation don't make it any clearer
what this function should do.

> +/******************************************************************
> + reinitialize the options_settable struct: allocate enough
> + space for all the options that the server is going to send us.
> +*******************************************************************/
> +void handle_options_settable_control(
> +                               struct packet_options_settable_control 
> *packet)
> +{
> +  int i; 
> +
> +  settable_options_free();
> +

> +  options_categories = fc_malloc(packet->ncategories * sizeof(char *));

Here and in a lot of other places: don't use sizeof of an explicit
type. Use the variable you assign to:

options_categories =
  fc_malloc(packet->ncategories * sizeof(options_categories[0]));
or
options_categories =
  fc_malloc(packet->ncategories * sizeof(*options_categories));

> +  num_options_categories = packet->ncategories;
> +  
> +  for (i = 0; i < num_options_categories; i++) {
> +    options_categories[i] = mystrdup(packet->category_names[i]);
> +  }

> +  /* avoid a malloc of size 0 warning */
> +  if (packet->nids == 0) {
> +    return;
> +  }

You don't set num_settable_options to 0 here.

> +  settable_options = fc_malloc(packet->nids * sizeof(struct 
> options_settable));
> +  num_settable_options = packet->nids;
> +
> +  for (i = 0; i < num_settable_options; i++) {
> +    settable_options[i].name = NULL;
> +    settable_options[i].short_help = NULL;
> +    settable_options[i].extra_help = NULL;
> +    settable_options[i].strval = NULL;
> +    settable_options[i].default_strval = NULL;
> +  }
> +}


> +/******************************************************************
> + Fill the settable_options array with an option.
> + If we've filled the last option, popup the dialog.
> +*******************************************************************/
> +void handle_options_settable(struct packet_options_settable *packet)
> +{
> +  int i = packet->id;

> +  assert(i >= 0);

Also check against the upper bound.

> +  settable_options[i].name = mystrdup(packet->name);
> +  settable_options[i].short_help = mystrdup(packet->short_help);
> +  settable_options[i].extra_help = mystrdup(packet->extra_help);
> +
> +  settable_options[i].type = packet->type;
> +  settable_options[i].category = packet->category;

> +  switch (packet->type) {
> +  case 0:

Use enums.

> +    settable_options[i].val = packet->val;
> +    settable_options[i].min = FALSE;
> +    settable_options[i].max = TRUE;
> +    settable_options[i].strval = NULL;
> +    settable_options[i].default_strval = NULL;
> +    break;
> +  case 1:
> +    settable_options[i].val = packet->val;
> +    settable_options[i].min = packet->min;
> +    settable_options[i].max = packet->max;
> +    settable_options[i].strval = NULL;
> +    settable_options[i].default_strval = NULL;
> +    break;
> +  case 2:
> +    settable_options[i].strval = mystrdup(packet->strval);
> +    settable_options[i].default_strval = mystrdup(packet->default_strval);
> +    break;
> +  default:
> +    assert(0);
> +  }
> +
> +  /* if we've received all the options, pop up the settings dialog */
> +  if (i == num_settable_options - 1) {
> +    popup_settable_options_dialog();
> +  }
>  }
> Index: common/connection.h
> ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/common/connection.h,v
> retrieving revision 1.33
> retrieving revision 1.34
> diff -u -r1.33 -r1.34
> --- common/connection.h       14 Jan 2004 11:58:12 -0000      1.33
> +++ common/connection.h       10 Apr 2004 03:47:49 -0000      1.34
> @@ -36,6 +36,7 @@
>  struct timer_list;
>  
>  #define MAX_LEN_PACKET   4096
> +
>  #define MAX_LEN_BUFFER   (MAX_LEN_PACKET * 128)
>  #define MAX_LEN_CAPSTR    512

Noise.

> Index: common/dataio.h
> ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/common/dataio.h,v
> retrieving revision 1.4
> retrieving revision 1.5
> diff -u -r1.4 -r1.5
> --- common/dataio.h   28 Nov 2003 17:37:21 -0000      1.4
> +++ common/dataio.h   10 Apr 2004 03:47:49 -0000      1.5
> @@ -59,7 +59,8 @@
>  
>  void dio_get_sint8(struct data_in *din, int *dest);
>  void dio_get_sint16(struct data_in *din, int *dest);
> -void dio_get_sint32(struct data_in *din, int *dest);
> +#define dio_get_sint32(d,v) dio_get_uint32(d,v)

Please explain. Please explain good. Really good.

> Index: common/game.c
> ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/common/game.c,v
> retrieving revision 1.176
> retrieving revision 1.177
> diff -u -r1.176 -r1.177
> --- common/game.c     19 Feb 2004 21:06:41 -0000      1.176
> +++ common/game.c     10 Apr 2004 03:47:49 -0000      1.177
> @@ -273,7 +273,8 @@
>    game.num_unit_types = 0;
>    game.num_impr_types = 0;
>    game.num_tech_types = 0;
> -
> + 
> +  game.nation_count = 0;

Was that a bug in the old code?

> +/*********************************************************
> + Below are the packets that control single-player mode.  
> +*********************************************************/
> +PACKET_SINGLE_WANT_HACK_REQ=108;cs,handle-per-conn,no-handle,dsend

> + UINT32 challenge;

Since an UINT32 don't provide _this_ much entropy we should have used
a more general approach: a string. This way if we add the entropy we
don't have to change the protocol.

> +PACKET_SINGLE_PLAYERLIST_REPLY=111;sc
> +  UINT8 nplayers;
> +  STRING load_filename[MAX_LEN_PACKET];
> +  STRING name[MAX_NUM_PLAYERS:nplayers][MAX_LEN_NAME]; 
> +  STRING username[MAX_NUM_PLAYERS:nplayers][MAX_LEN_NAME]; 

> +  STRING nation_name[MAX_NUM_PLAYERS:nplayers][MAX_LEN_NAME]; 

> +  STRING nation_flag[MAX_NUM_PLAYERS:nplayers][MAX_LEN_NAME]; 

Why can't the nation_flag not be extracet from the nation name?

> +  BOOL is_alive[MAX_NUM_PLAYERS:nplayers];
> +  BOOL is_ai[MAX_NUM_PLAYERS:nplayers];
> +end

> +PACKET_OPTIONS_SETTABLE_CONTROL=112;sc,handle-via-packet

> +  UINT16 nids;

Shouldn't this be named "noptions"?

> +  UINT8 ncategories;
> +  STRING category_names[256:ncategories][MAX_LEN_NAME];
> +end
> +
> +PACKET_OPTIONS_SETTABLE=113;sc
> +  UINT16 id;
> +  STRING name[MAX_LEN_NAME];
> +  STRING short_help[MAX_LEN_PACKET];
> +  STRING extra_help[MAX_LEN_PACKET];
> +  UINT8  type;                                       /* 0:bool, 1:int, 
> 2:string */
> +
> +  SINT32 val;                                        /* value for bool or 
> int */
> +  SINT32 default_val;                                /* default for bool or 
> int */
> +  SINT32 min;                                        /* min value for int */
> +  SINT32 max;                                        /* max value for int */
> +
> +  STRING strval[MAX_LEN_PACKET];             /* space for string */
> +  STRING default_strval[MAX_LEN_PACKET];     /* space for string */
> +
> +  UINT8 category;                            /* which category this is in */

IMHO this should look like:

  SINT32 int_value;
  SINT32 int_value_default;
  SINT32 int_value_min;
  SINT32 int_value_max;

  BOOL bool_value;
  BOOL bool_value_default;

  STRING string_value[MAX_LEN_PACKET];
  STRING string_value_default[MAX_LEN_PACKET];

> Index: common/shared.h
> ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/common/shared.h,v
> retrieving revision 1.123
> retrieving revision 1.124
> diff -u -r1.123 -r1.124
> --- common/shared.h   29 Mar 2004 18:52:54 -0000      1.123
> +++ common/shared.h   10 Apr 2004 03:47:49 -0000      1.124
> @@ -64,6 +64,7 @@
>  #define MAX_LEN_ADDR     256 /* see also MAXHOSTNAMELEN and RFC 1123 2.1 */
>  #define MAX_LEN_VET_SHORT_NAME 8
>  #define MAX_VET_LEVELS 10

> +#define MAX_LEN_PATH 4095

See C99 and FILENAME_MAX.

>  /* Use FC_INFINITY to denote that a certain event will never occur or
>     another unreachable condition. */
> Index: doc/HACKING
> ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/doc/HACKING,v
> retrieving revision 1.17
> retrieving revision 1.18
> diff -u -r1.17 -r1.18
> --- doc/HACKING       8 Jan 2004 11:34:45 -0000       1.17
> +++ doc/HACKING       10 Apr 2004 03:47:49 -0000      1.18
> @@ -920,6 +920,60 @@
>     "observer" or "controller".  (But observers not yet implemented.)
>     Exists in game.game_connections, and in pconn->player->connections.
>  
> +
> +===========================================================================
> +Starting a Server from Within a Client
> +===========================================================================
> +
> +After many years of complaints regarding the ease (or lack thereof) of
> +starting a game of Freeciv [start a server, input settings on a command line,
> +start a client, and connect, etc], a method has been developed for starting
> +and playing a complete game using only the client. This has been called the
> +"extended" or "new connect dialog". This is perhaps a misnomer, but there it 
> +is. This win32 client has had this feature in some form for some time. 
> +
> +It works by forking a server from within the client and then controlling that
> +server via chatline messages. The guts of the machinery to do this can be
> +found in these files:
> +
> +client/connectdlg_common.[ch]
> +client/gui-*/connectdlg.[ch]
> +common/packets.def
> +server/gamehand.[ch]
> +server/stdinhand.[ch]
> +
> +When a player starts a client, he is presents with several options: start
> +a new game, continue a saved game and connect to a networked game. For the 
> +latter option, connect_to_server() is called and login proceeeds as normal.

> +The the first two options, connectdlg_common.c:client_start_server() is 

Double "the".

> +called. Here, a server is spawned, standard input and outputs to that process
> +are closed, and then connect_to_server() is called so the client connects to 
> +that server.
> +
> +At this point everything regarding the client/server connection is as usual;
> +however, for the client to control the server, it must have access level 
> HACK,
> +so it must verify to the server that it is local (on the same machine or at
> +least has access to the same disk as the server). The procedure is:
> +
> +1. the server creates a file using gamehand.c:create_challenge_filename() and
> +   puts the name of this file in packet_server_join_reply that it sends back
> +   to the client. The name of the file is semi-random.
> +2. The client, upon receiving confirmation that it can join the server,
> +   creates a file using the name the server selected and places a random 
> number
> +   inside that file.
> +3. The client sends a packet [packet_single_want_hack_req] with that random 
> +   number back to the server.
> +4. The server upon receiving the packet [packet_single_want_hack_req], opens
> +   the file and compares the two numbers. If the file exists and the numbers
> +   are equal the server grants that client HACK access level and sends a
> +   packet [packet_single_want_hack_reply] informing the client of its 
> elevated
> +   access level.
> +

> +Only one other packet is used --- packet_single_playerlist_req --- which asks

Rephrase to "In addition to the changes to get the local client hack
access only another packet change is required --- ...."

> +the server to send a player list when a savegame is loaded. This list is used
> +for player selection.

> +/************************************************************************** 
> +find a suitably random file that we can write too, and return it's name.
> +**************************************************************************/
> +const char *create_challenge_filename(void)

This function shouldn't return the string. This is confusing.

> +/************************************************************************** 
> +opens a file specified by the packet and compares the packet values with
> +the file values. Sends an answer to the client once it's done.
> +**************************************************************************/
> +void handle_single_want_hack_req(struct connection *pc, int challenge)
> +{
> +  struct section_file file;
> +  int entropy = 0;
> +  bool could_load = TRUE;
> +  bool you_have_hack = FALSE;
> +
> +  if (section_file_load_nodup(&file, challenge_filename)) {

> +    entropy = secfile_lookup_int_default(&file, 0, "challenge.entropy");
...
> +  you_have_hack = (could_load && entropy && entropy == challenge);

Now I see why you don't like the value 0 here. But this could be changed easily.


> Index: server/srv_main.h
> ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/server/srv_main.h,v
> retrieving revision 1.17
> retrieving revision 1.18
> diff -u -r1.17 -r1.18
> --- server/srv_main.h 28 Nov 2003 17:37:22 -0000      1.17
> +++ server/srv_main.h 10 Apr 2004 03:47:50 -0000      1.18
> @@ -34,7 +34,7 @@
>    /* filenames */
>    char *log_filename;
>    char *gamelog_filename;

> -  char *load_filename;
> +  char load_filename[512]; /* FIXME: may not be long enough? use MAX_PATH? */

Use MAX_LEN_PATH.


-- 
 email: rf13@xxxxxxxxxxxxxxxxx
 "We've all heard that a million monkeys banging on a million typewriters
  will eventually reproduce the entire works of Shakespeare.
  Now, thanks to the Internet, we know this is not true."




[Prev in Thread] Current Thread [Next in Thread]
  • [Freeciv-Dev] (PR#8491) Re: Freeciv commit: kauf: A new connect dialog for the client. This allo..., Raimar Falke <=