Crossfire Server, Trunk  1.75.0
lowlevel.cpp
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2014 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 
26 #include "global.h"
27 
28 #include <assert.h>
29 #include <errno.h>
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 
35 #include "output_file.h"
36 #include "shared/newclient.h"
37 #include "sproto.h"
38 #include "stats.h"
39 
40 #ifdef WIN32
41 #include <winsock2.h>
42 #endif
43 
44 /***********************************************************************
45  *
46  * SockList functions/utilities
47  *
48  **********************************************************************/
49 
56  SockList_Reset(sl);
57 }
58 
66  (void)sl;
67 }
68 
75  sl->len = 2;
76 }
77 
84  sl->len = 0;
85 }
86 
95 static void SockList_Ensure(const SockList *sl, size_t size) {
96  if (sl->len+size > sizeof(sl->buf)) {
98  }
99 }
100 
106 void SockList_AddChar(SockList *sl, unsigned char data) {
107  SockList_Ensure(sl, 1);
108  sl->buf[sl->len++] = data;
109 }
110 
116 void SockList_AddShort(SockList *sl, uint16_t data) {
117  SockList_Ensure(sl, 2);
118  sl->buf[sl->len++] = (data>>8)&0xff;
119  sl->buf[sl->len++] = data&0xff;
120 }
121 
127 void SockList_AddInt(SockList *sl, uint32_t data) {
128  SockList_Ensure(sl, 4);
129  sl->buf[sl->len++] = (data>>24)&0xff;
130  sl->buf[sl->len++] = (data>>16)&0xff;
131  sl->buf[sl->len++] = (data>>8)&0xff;
132  sl->buf[sl->len++] = data&0xff;
133 }
134 
140 void SockList_AddInt64(SockList *sl, uint64_t data) {
141  SockList_Ensure(sl, 8);
142  sl->buf[sl->len++] = (char)((data>>56)&0xff);
143  sl->buf[sl->len++] = (char)((data>>48)&0xff);
144  sl->buf[sl->len++] = (char)((data>>40)&0xff);
145  sl->buf[sl->len++] = (char)((data>>32)&0xff);
146  sl->buf[sl->len++] = (char)((data>>24)&0xff);
147  sl->buf[sl->len++] = (char)((data>>16)&0xff);
148  sl->buf[sl->len++] = (char)((data>>8)&0xff);
149  sl->buf[sl->len++] = (char)(data&0xff);
150 }
151 
157 void SockList_AddString(SockList *sl, const char *data) {
158  SockList_AddData(sl, data, strlen(data));
159 }
160 
167 void SockList_AddData(SockList *sl, const void *data, size_t len) {
168  SockList_Ensure(sl, len);
169  memcpy(sl->buf+sl->len, data, len);
170  sl->len += len;
171 }
172 
179 void SockList_AddLen8Data(SockList *sl, const void *data, size_t len) {
180  assert(len <= 255);
181  SockList_AddChar(sl, len);
182  SockList_AddData(sl, data, len);
183 }
184 
191 void SockList_AddLen16Data(SockList *sl, const void *data, size_t len) {
192  assert(len <= 65535);
193  SockList_AddShort(sl, len);
194  SockList_AddData(sl, data, len);
195 }
196 
202 void SockList_AddPrintf(SockList *sl, const char *format, ...) {
203  size_t size;
204  int n;
205  va_list arg;
206 
207  size = sizeof(sl->buf)-sl->len;
208 
209  va_start(arg, format);
210  n = vsnprintf((char *)sl->buf+sl->len, size, format, arg);
211  va_end(arg);
212 
213  if (n <= -1 || (size_t)n >= size) {
214  LOG(llevError, "Truncating message exceeding MAXSOCKBUF. The message was:\n%s\n", sl->buf+sl->len);
215  }
216  sl->len += (size_t)n;
217 }
218 
226  char *p;
227 
228  p = stringbuffer_finish(sb);
229  SockList_AddString(sl, p);
230  free(p);
231 }
232 
238  SockList_Ensure(sl, 1);
239  sl->buf[sl->len] = '\0';
240 }
241 
246 size_t SockList_Avail(const SockList *sl) {
247  return sizeof(sl->buf)-sl->len;
248 }
249 
254 int GetInt_String(const unsigned char *data) {
255  return ((data[0]<<24)+(data[1]<<16)+(data[2]<<8)+data[3]);
256 }
257 
258 short GetShort_String(const unsigned char *data) {
259  return ((data[0]<<8)+data[1]);
260 }
261 
262 /******************************************************************************
263  *
264  * Start of read routines.
265  *
266  ******************************************************************************/
267 
275 int SockList_ReadPacket(int fd, SockList *sl, int len) {
276  int stat, toread;
277 
278  /* We already have a partial packet */
279  if (sl->len < 2) {
280 #ifdef WIN32 /* ***WIN32 SockList_ReadPacket: change read() to recv() */
281 
282  stat = recv(fd, reinterpret_cast<char *>(sl->buf+sl->len), 2-sl->len, 0);
283 
284 #else
285  do {
286  stat = read(fd, sl->buf+sl->len, 2-sl->len);
287  } while ((stat == -1) && (errno == EINTR));
288 #endif
289  if (stat < 0) {
290  /* In non blocking mode, EAGAIN is set when there is no
291  * data available.
292  */
293 #ifdef WIN32 /* ***WIN32 SockList_ReadPacket: error handling for win32 */
294  if ((stat == -1) && WSAGetLastError() != WSAEWOULDBLOCK) {
295  if (WSAGetLastError() == WSAECONNRESET)
296  LOG(llevDebug, "Connection closed by client\n");
297  else {
298  LOG(llevDebug, "ReadPacket got error %d, returning -1\n", WSAGetLastError());
299  }
300  return -1; /* kick this user! */
301  }
302 #else
303  if (errno == ECONNRESET) {
304  LOG(llevDebug, "ReadPacket got error %s, returning -1\n", strerror(errno));
305  return -1;
306  }
307  if (errno != EAGAIN && errno != EWOULDBLOCK) {
308  LOG(llevDebug, "ReadPacket got error %s, returning 0\n", strerror(errno));
309  }
310 #endif
311  return 0; /*Error */
312  }
313  if (stat == 0)
314  return -1;
315  sl->len += stat;
316 #ifdef CS_LOGSTATS
317  cst_tot.ibytes += stat;
318  cst_lst.ibytes += stat;
319 #endif
320  if (stat < 2)
321  return 0; /* Still don't have a full packet */
322  }
323  /* Figure out how much more data we need to read. Add 2 from the
324  * end of this - size header information is not included.
325  */
326  toread = 2+(sl->buf[0]<<8)+sl->buf[1]-sl->len;
327  if ((toread+(int)sl->len) >= len) {
328  LOG(llevError, "SockList_ReadPacket: Want to read more bytes than will fit in buffer (%lu>=%lu).\n", (unsigned long)toread+sl->len, (unsigned long)len);
329  /* Quick hack in case for 'oldsocketmode' input. If we are
330  * closing the socket anyways, then reading this extra 100 bytes
331  * shouldn't hurt.
332  */
333 #ifdef WIN32 /* ***win32 SockList_ReadPacket: change read() to recv() */
334  stat = recv(fd, reinterpret_cast<char *>(sl->buf+2), 100, 0);
335 #else
336  stat = read(fd, sl->buf+2, 100);
337 #endif /* end win32 */
338  (void) stat; // Don't care how much we read; avoid complier warnings
339 
340  /* return error so the socket is closed */
341  return -1;
342  }
343  do {
344 #ifdef WIN32 /* ***win32 SockList_ReadPacket: change read() to recv() */
345  stat = recv(fd, reinterpret_cast<char *>(sl->buf+sl->len), toread, 0);
346 #else
347  do {
348  stat = read(fd, sl->buf+sl->len, toread);
349  } while ((stat < 0) && (errno == EINTR));
350 #endif
351  if (stat < 0) {
352 #ifdef WIN32 /* ***win32 SockList_ReadPacket: change error handling for win32 */
353  if ((stat == -1) && WSAGetLastError() != WSAEWOULDBLOCK) {
354  if (WSAGetLastError() == WSAECONNRESET)
355  LOG(llevDebug, "Connection closed by client\n");
356  else {
357  LOG(llevDebug, "ReadPacket got error %d, returning -1\n", WSAGetLastError());
358  }
359  return -1; /* kick this user! */
360  }
361 #else
362  if (errno != EAGAIN && errno != EWOULDBLOCK) {
363  LOG(llevDebug, "ReadPacket got error %s, returning 0\n", strerror(errno));
364  }
365 #endif
366  return 0; /*Error */
367  }
368  if (stat == 0)
369  return -1;
370  sl->len += stat;
371 #ifdef CS_LOGSTATS
372  cst_tot.ibytes += stat;
373  cst_lst.ibytes += stat;
374 #endif
375  toread -= stat;
376  if (toread == 0)
377  return 1;
378  if (toread < 0) {
379  LOG(llevError, "SockList_ReadPacket: Read more bytes than desired.\n");
380  return 1;
381  }
382  } while (toread > 0);
383  return 0;
384 }
385 
386 /*******************************************************************************
387  *
388  * Start of write related routines.
389  *
390  ******************************************************************************/
391 
399 static void Write_To_Socket(socket_struct* ns, const unsigned char* buf, const int len) {
400  if (ns->status == Ns_Dead || !buf) {
401  LOG(llevDebug, "Write_To_Socket called with dead socket\n");
402  return;
403  }
404 
405 #ifdef WIN32
406  const int amt = send(ns->fd, reinterpret_cast<const char *>(buf), len, 0);
407 #else
408  const int amt = send(ns->fd, buf, len, 0);
409 #endif
410  if (amt < 0) { /* We got an error */
411 #ifdef WIN32 /* ***win32 Write_To_Socket: change error handling */
412  if (amt == -1 && WSAGetLastError() != WSAEWOULDBLOCK) {
413  LOG(llevInfo, "socket write failed: error code %d, disconnecting client\n",
414  WSAGetLastError());
415 #else
416  if (errno != EWOULDBLOCK) {
417  LOG(llevInfo, "socket write failed: %s, disconnecting client\n",
418  strerror(errno));
419 #endif
420  ns->status = Ns_Dead;
421  return;
422  } else { /* EWOULDBLOCK */
423  LOG(llevError,
424  "Write_To_Socket: write would block; disconnecting. Try "
425  "increasing SOCKETBUFSIZE.\n");
426  ns->status = Ns_Dead;
427  return;
428  }
429  } else if (amt != len) {
430  LOG(llevError, "Write_To_Socket: write wrote less than requested; "
431  "disconnecting. Try increasing SOCKETBUFSIZE.\n");
432  ns->status = Ns_Dead;
433  return;
434  }
435 #ifdef CS_LOGSTATS
436  cst_tot.obytes += amt;
437  cst_lst.obytes += amt;
438 #endif
439 }
440 
448  if (ns->status == Ns_Dead || sl == NULL)
449  return;
450 
451  sl->buf[0] = ((sl->len-2)>>8)&0xFF;
452  sl->buf[1] = (sl->len-2)&0xFF;
453  Write_To_Socket(ns, sl->buf, sl->len);
454 }
455 
456 /******************************************************************************
457  *
458  * statistics logging functions.
459  *
460  ******************************************************************************/
461 
462 #ifdef CS_LOGSTATS
463 
464 static int count_all_players() {
465  int players = 0;
466  player *pl;
467  for (pl = first_player, players = 0; pl != NULL; pl = pl->next, players++);
468  return players;
469 }
470 
471 /* cst_tot is for the life of the server, cst_last is for the last series of
472  * stats
473  */
475 
476 void reset_stats(struct CS_Stats *stats) {
477  memset(stats, 0, sizeof(CS_Stats));
478  stats->time_start = time(NULL);
479 }
480 
481 #define STAT(name, fmt, val) fprintf(f, "%s %" fmt "\n", name, val);
482 
486 void write_cs_stats(void) {
487  time_t now = time(NULL);
488  int players_active = count_players();
489  int players_total = count_all_players();
490 
491  // Old statistics format logged to the log file
492  LOG(llevInfo, "STAT: players: %d active, %d total\n", players_active, players_total);
493  LOG(llevInfo, "CSSTAT: %.16s tot %d %d %d %ld inc %d %d %d %ld\n",
494  ctime(&now),
497 
498  // Write OpenMetrics-formatted server stats to a file. This can be exported
499  // to tools like Prometheus to monitor the server.
500  if (settings.stat_file) {
501  OutputFile of;
502  FILE *f = of_open(&of, settings.stat_file);
503  if (f) {
504  // ticks can be zero due to idling, so prevent divide by zero
505  float ticks_over = cst_lst.ticks > 0 ? (float)cst_lst.ticks_overtime / cst_lst.ticks : 0;
506  float avg = cst_lst.ticks > 0 ? (float)cst_lst.total_ticktime / cst_lst.ticks : 0; // in us
507 
508  STAT("players_total", "d", players_total);
509  STAT("players_active", "d", players_active);
510  STAT("ticks_overtime_percent", "f", ticks_over * 100);
511  STAT("ticktime_max", "f", cst_lst.max_ticktime / 1e3); // in ms
512  STAT("ticktime_avg", "f", avg / 1e3); // in ms
513  STAT("bytes_in_total", "d", cst_tot.ibytes);
514  STAT("bytes_out_total", "d", cst_tot.obytes);
515 
516  int maps_in_memory = 0;
517  int maps_swapped = 0;
518  for (mapstruct *m = first_map; m != NULL; m = m->next) {
519  switch (m->in_memory) {
520  case MAP_IN_MEMORY:
521  maps_in_memory++;
522  break;
523  case MAP_SWAPPED:
524  maps_swapped++;
525  break;
526  }
527  }
528  STAT("maps_in_memory", "d", maps_in_memory);
529  STAT("maps_loaded_total", "d", maps_loaded_total);
530  STAT("maps_saved_total", "d", maps_saved_total);
531  STAT("maps_swapped", "d", maps_swapped);
532  STAT("maps_swapped_total", "d", maps_swapped_total);
533 
534  STAT("objects_alloc", "d", nrofallocobjects);
535  STAT("objects_free", "d", nroffreeobjects);
536  STAT("objects_active", "d", object_count_active());
537 
538  STAT("events_total", "d", events_total);
539  STAT("global_events_total", "d", global_events_total);
540 
541  STAT("log_total", "d", log_total);
542  of_close(&of);
543  } else {
544  LOG(llevError, "Unable to write to stat file: %s\n", settings.stat_file);
545  }
546  }
547 
550 }
551 #endif
player::next
player * next
Pointer to next player, NULL if this is last.
Definition: player.h:106
output_file.h
global.h
first_player
player * first_player
First player.
Definition: init.cpp:106
settings
struct Settings settings
Server settings.
Definition: init.cpp:139
SockList_AddShort
void SockList_AddShort(SockList *sl, uint16_t data)
Adds a 16 bit value.
Definition: lowlevel.cpp:116
CS_Stats::ibytes
int ibytes
ibytes, obytes are bytes in, out.
Definition: newclient.h:698
object_count_active
int object_count_active(void)
Objects statistics.
Definition: object.cpp:1783
CS_Stats::total_ticktime
unsigned long total_ticktime
Definition: newclient.h:709
Send_With_Handling
void Send_With_Handling(socket_struct *ns, SockList *sl)
Calls Write_To_Socket to send data to the client.
Definition: lowlevel.cpp:447
llevError
@ llevError
Error, serious thing.
Definition: logger.h:11
SockList_NullTerminate
void SockList_NullTerminate(SockList *sl)
Adds a NUL byte without changing the length.
Definition: lowlevel.cpp:237
LOG
void LOG(LogLevel logLevel, const char *format,...)
Logs a message to stderr, or to file.
Definition: logger.cpp:58
of_close
int of_close(OutputFile *of)
Closes an output file.
Definition: output_file.cpp:61
of_open
FILE * of_open(OutputFile *of, const char *fname)
Opens an output file.
Definition: output_file.cpp:30
player
One player.
Definition: player.h:105
socket_struct
Socket structure, represents a client-server connection.
Definition: newserver.h:89
Socket_Info::allocated_sockets
int allocated_sockets
Number of allocated items in init_sockets.
Definition: newserver.h:144
if
if(!(yy_init))
Definition: loader.cpp:36428
maps_saved_total
int maps_saved_total
Definition: logger.cpp:40
socket_info
Socket_Info socket_info
Socket information.
Definition: init.cpp:52
maps_swapped_total
int maps_swapped_total
Definition: logger.cpp:41
GetShort_String
short GetShort_String(const unsigned char *data)
Definition: lowlevel.cpp:258
SockList_AddData
void SockList_AddData(SockList *sl, const void *data, size_t len)
Adds a data block.
Definition: lowlevel.cpp:167
time
non standard information is not specified or uptime this means how long since the executable has been started A particular host may have been running a server for quite a long time
Definition: arch-handbook.txt:206
SockList_Reset
void SockList_Reset(SockList *sl)
Resets the length of the stored data for writing.
Definition: lowlevel.cpp:74
Settings::stat_file
char * stat_file
Definition: global.h:338
SockList_Ensure
static void SockList_Ensure(const SockList *sl, size_t size)
Checks that at least a given number of bytes is available in a SockList instance.
Definition: lowlevel.cpp:95
CS_Stats::ticks
unsigned long ticks
Definition: newclient.h:706
buf
StringBuffer * buf
Definition: readable.cpp:1565
Ns_Dead
@ Ns_Dead
Definition: newserver.h:67
SockList_Term
void SockList_Term(SockList *sl)
Frees all resources allocated by a SockList instance.
Definition: lowlevel.cpp:65
CS_Stats::time_start
time_t time_start
Definition: newclient.h:701
log_total
int log_total
Definition: logger.cpp:43
m
static event_registration m
Definition: citylife.cpp:422
MAP_IN_MEMORY
#define MAP_IN_MEMORY
Map is fully loaded.
Definition: map.h:126
stringbuffer_finish
char * stringbuffer_finish(StringBuffer *sb)
Deallocate the string buffer instance and return the string.
Definition: stringbuffer.cpp:76
events_total
int events_total
Definition: events.cpp:8
stats.h
first_map
mapstruct * first_map
First map.
Definition: init.cpp:107
SockList_Init
void SockList_Init(SockList *sl)
Initializes the SockList instance.
Definition: lowlevel.cpp:55
sproto.h
nrofallocobjects
int nrofallocobjects
How many OBs allocated (free + used)
Definition: object.cpp:291
SockList_AddLen8Data
void SockList_AddLen8Data(SockList *sl, const void *data, size_t len)
Adds a data block prepended with an 8 bit length field.
Definition: lowlevel.cpp:179
SockList_AddChar
void SockList_AddChar(SockList *sl, unsigned char data)
Adds an 8 bit value.
Definition: lowlevel.cpp:106
CS_Stats::obytes
int obytes
Definition: newclient.h:699
SockList::len
size_t len
Definition: newclient.h:689
global_events_total
int global_events_total
Definition: events.cpp:7
fatal
void fatal(enum fatal_error err)
fatal() is meant to be called whenever a fatal signal is intercepted.
Definition: utils.cpp:590
maps_loaded_total
int maps_loaded_total
Definition: logger.cpp:39
nroffreeobjects
int nroffreeobjects
How many OBs allocated and free (free)
Definition: object.cpp:290
StringBuffer
A buffer that will be expanded as content is added to it.
Definition: stringbuffer.cpp:25
cst_tot
CS_Stats cst_tot
llevInfo
@ llevInfo
Information.
Definition: logger.h:12
CS_Stats::max_ticktime
unsigned long max_ticktime
Definition: newclient.h:708
SockList_AddInt
void SockList_AddInt(SockList *sl, uint32_t data)
Adds a 32 bit value.
Definition: lowlevel.cpp:127
players
std::vector< archetype * > players
Definition: player.cpp:501
mapstruct
This is a game-map.
Definition: map.h:315
write_cs_stats
void write_cs_stats(void)
socket_struct::status
enum Sock_Status status
Definition: newserver.h:90
SockList_AddStringBuffer
void SockList_AddStringBuffer(SockList *sl, StringBuffer *sb)
Deallocates string buffer instance and appends its contents.
Definition: lowlevel.cpp:225
SockList_ReadPacket
int SockList_ReadPacket(int fd, SockList *sl, int len)
This reads from fd and puts the data in sl.
Definition: lowlevel.cpp:275
SockList_Avail
size_t SockList_Avail(const SockList *sl)
Returns the available bytes in a SockList instance.
Definition: lowlevel.cpp:246
Write_To_Socket
static void Write_To_Socket(socket_struct *ns, const unsigned char *buf, const int len)
This writes data to the socket.
Definition: lowlevel.cpp:399
reset_stats
void reset_stats(struct CS_Stats *stats)
MAP_SWAPPED
#define MAP_SWAPPED
Map spaces have been saved to disk.
Definition: map.h:127
SockList_AddInt64
void SockList_AddInt64(SockList *sl, uint64_t data)
Adds a 64 bit value.
Definition: lowlevel.cpp:140
stats
Player Stats effect how well a character can survie and interact inside the crossfire world This section discusses the various stats
Definition: stats.txt:2
data
====Textual A command containing textual data has data fields separated by one ASCII space character. word::A sequence of ASCII characters that does not contain the space or nul character. This is to distinguish it from the _string_, which may contain space characters. Not to be confused with a machine word. int::A _word_ containing the textual representation of an integer. Not to be confused with any of the binary integers in the following section. Otherwise known as the "string value of integer data". Must be parsed, e.g. using `atoi()` to get the actual integer value. string::A sequence of ASCII characters. This must only appear at the end of a command, since spaces are used to separate fields of a textual message.=====Binary All multi-byte integers are transmitted in network byte order(MSB first). int8::1-byte(8-bit) integer int16::2-byte(16-bit) integer int32::4-byte(32-bit) integer lstring::A length-prefixed string, which consists of an `int8` followed by that many bytes of the actual string. This is used to transmit a string(that may contain spaces) in the middle of binary data. l2string::Like _lstring_, but is prefixed with an `int16` to support longer strings Implementation Notes ~~~~~~~~~~~~~~~~~~~~ - Typical implementations read two bytes to determine the length of the subsequent read for the actual message, then read and parse the data from each message according to the commands described below. To send a message, the sender builds the message in a buffer, counts the length of the message, sends the length, and finally sends the actual message. TIP:Incorrectly transmitting or receiving the `length` field can lead to apparent "no response" issues as the client or server blocks to read the entire length of the message. - Since the protocol is highly interactive, it may be useful to set `TCP_NODELAY` on both the client and server. - If you are using a language with a buffered output stream, remember to flush the stream after a complete message. - If the connection is lost(which will also happen if the output buffer overflowing), the player is saved and the server cleans up. This does open up some abuses, but there is no perfect solution here. - The server only reads data from the socket if the player has an action. This isn 't really good, since many of the commands below might not be actual commands for the player. The alternative is to look at the data, and if it is a player command and there isn 't time, store it away to be processed later. But this increases complexity, in that the server must start buffering the commands. Fortunately, for now, there are few such client commands. Commands -------- In the documentation below, `S->C` represents a message to the client from the server, and `C->S` represents a message to the server from the client. Commands are documented in a brief format like:C->S:version< csval >[scval[vinfo]] Fields are enclosed like `< this >`. Optional fields are denoted like `[this]`. Spaces that appear in the command are literal, i.e. the<< _version > > command above uses spaces to separate its fields, but the command below does not:C->S:accountlogin< name >< password > As described in<< _messages > >, if a command contains data, then the command is separated from the data by a literal space. Many of the commands below refer to 'object tags'. Whenever the server creates an object, it creates a unique tag for that object(starting at 1 when the server is first run, and ever increasing.) Tags are unique, but are not consistent between runs. Thus, the client can not store tags when it exits and hope to re-use them when it joins the server at a later time - tags are only valid for the current connection. The protocol commands are broken into various sections which based somewhat on what the commands are for(ie, item related commands, map commands, image commands, etc.) In this way, all the commands related to similar functionality is in the same place. Initialization ~~~~~~~~~~~~~~ version ^^^^^^^ C->S:version< csval >[scval[vinfo]] S->C:version< csval >[scval[vinfo]] Used by the client and server to exchange which version of the Crossfire protocol they understand. Neither send this in response to the other - they should both send this shortly after a connection is established. csval::int, version level of C->S communications scval::int, version level of S->C communications vinfo::string, that is purely for informative that general client/server info(ie, javaclient, x11client, winclient, sinix server, etc). It is purely of interest of server admins who can see what type of clients people are using.=====Version ID If a new command is added to the protocol in the C->S direction, then the version number in csval will get increased. Likewise, the same is true for the scval. The version are currently integers, in the form ABCD. A=1, and will likely for quite a while. This will only really change if needed from rollover of B. B represents major protocol changes - if B mismatches, the clients will be totally unusable. Such an example would be change of map or item sending commands(either new commands or new format.) C represents more minor but still significant changes - clients might still work together, but some features that used to work may now fail due to the mismatch. An example may be a change in the meaning of some field in some command - providing the field is the same size, it still should be decoded properly, but the meaning won 't be processed properly. D represents very minor changes or new commands. Things should work no worse if D does not match, however if they do match, some new features might be included. An example of the would be the C->S mark command to mark items. Server not understanding this just means that the server can not process it, and will ignore it.=====Handling As far as the client is concerned, its _scval_ must be at least equal to the server, and its _csval_ should not be newer than the server. The server does not care about the version command it receives right now - all it currently does is log mismatches. In theory, the server should keep track of what the client has, and adjust the commands it sends respectively in the S->C direction. The server is resilant enough that it won 't crash with a version mismatch(however, client may end up sending commands that the server just ignores). It is really up to the client to enforce versioning and quit if the versions don 't match. NOTE:Since all packets have the length as the first 2 bytes, all that either the client or server needs to be able to do is look at the first string and see if it understands it. If not, it knows how many bytes it can skip. As such, exact version matches should not be necessary for proper operation - however, both the client and server needs to be coded to handle such cases.=====History _scval_ and _vinfo_ were added in version 1020. Before then, there was only one version sent in the version command. NOTE:For the most part, this has been obsoleted by the setup command which always return status and whether it understood the command or not. However there are still some cases where using this versioning is useful - an example it the addition of the requestinfo/replyinfo commands - the client wants to wait for acknowledge of all the replyinfo commands it has issued before sending the addme command. However, if the server doesn 't understand these options, the client will never get a response. With the versioning, the client can look at the version and know if it should wait for a response or if the server will never send back. setup ^^^^^ C->S, S->C:setup< option1 >< value1 >< option2 >< value2 > ... Sent by the client to request protocol option changes. This can be at any point during the life of a connection, but usually sent at least once right after the<< _version > > command. The server responds with a message in the same format confirming what configuration options were set. The server only sends a setup command in response to one from the client. The sc_version should be updated in the server if commands have been obsoleted such that old clients may not be able to play. option::word, name of configuration option value::word, value of configuration option. May need further parsing according to the setup options below=====Setup Options There are really 2 set of setup commands here:. Those that control preferences of the client(how big is the map, what faceset to use, etc). . Those that describe capabilities of the client(client supports this protocol command or that) .Setup Options[options="autowidth,header"]|===========================|Command|Description|beat|Ask the server to enable heartbeat support. When heartbeat is enabled, the client must send the server a command every three seconds. If no commands need to be sent, use the `beat` no-op command. Clients that do not contact the server within the interval are assumed to have a temporary connection failure.|bot(0/1 value)|If set to 1, the client will not be considered a player when updating information to the metaserver. This is to avoid having a server with many bots appear more crowded than others.|darkness(0/1 value)|If set to 1(default), the server will send darkness information in the map protocol commands. If 0, the server will not include darkness, thus saving a minor amount of bandwidth. Since the client is free to ignore the darkness information, this does not allow the client to cheat. In the case of the old 'map' protocol command, turning darkness off will result in the masking faces not getting sent to the client.|extended_stats(0/1 value)|If set to 1, the server will send the CS_STAT_RACE_xxx and CS_STAT_BASE_xxx values too, so the client can display various status related to statistics. Default is 0.|facecache(0/1)|Determines if the client is caching images(1) or wants the images sent to it without caching them(0). Default is 0. This replaces the setfacemode command.|faceset(8 bit)|Faceset the client wishes to use. If the faceset is not valid, the server returns the faceset the client will be using(default 0).|loginmethod(8 bit)|Client sends this to server to note login support. This is basically used as a subset of the csversion/scversion to find out what level of login support the server and client support. Current defined values:0:no advanced support - only legacy login method 1:account based login(described more below) 2:new character creation support This list may grow - for example, advanced character creation could become a feature.|map2cmd:(1)|This indicates client support for the map2 protocol command. See the map2 protocol details above for the main differences. Obsolete:This is the only supported mode now, but many clients use it as a sanity check for protocol versions, so the server still replies. It doesn 't do anything with the data|mapsize(int x) X(int y)|Sets the map size to x X y. Note the spaces here are only for clarity - there should be no spaces when actually sent(it should be 11x11 or 25x25). The default map size unless changed is 11x11. The minimum map size the server will allow is 9x9(no technical reason this could be smaller, but I don 't think the game would be smaller). The maximum map size supported in the current protocol is 63x63. However, each server can have its maximum map size sent to most any value. If the client sends an invalid mapsize command or a mapsize of 0x0, the server will respond with a mapsize that is the maximum size the server supports. Thus, if the client wants to know the maximum map size, it can just do a 'mapsize 0x0' or 'mapsize' and it will get the maximum size back. The server will constrain the provided mapsize x &y to the configured minumum and maximums. For example, if the maximum map size is 25x25, the minimum map size is 9x9, and the client sends a 31x7 mapsize request, the mapsize will be set to 25x9 and the server will send back a mapsize 25x9 setup command. When the values are valid, the server will send back a mapsize XxY setup command. Note that this is from its parsed values, so it may not match stringwise with what the client sent, but will match 0 wise. For example, the client may send a 'mapsize 025X025' command, in which case the server will respond with a 'mapsize 25x25' command - the data is functionally the same. The server will send an updated map view when this command is sent.|notifications(int value)|Value indicating what notifications the client accepts. It is incremental, a value means "all notifications till this level". The following levels are supported:1:quest-related notifications("addquest" and "updquest") 2:knowledge-related notifications("addknowledge") 3:character status flags(overloaded, blind,...)|num_look_objects(int value)|The maximum number of objects shown in the ground view. If more objects are present, fake objects are created for selecting the previous/next group of items. Defaults to 50 if not set. The server may adjust the given value to a suitable one data
Definition: protocol.txt:379
SockList_ResetRead
void SockList_ResetRead(SockList *sl)
Resets the length of the stored data for reading.
Definition: lowlevel.cpp:83
newclient.h
socket_struct::fd
int fd
Definition: newserver.h:91
count_players
int count_players(void)
Definition: metaserver.cpp:48
SockList_AddLen16Data
void SockList_AddLen16Data(SockList *sl, const void *data, size_t len)
Adds a data block prepended with an 16 bit length field.
Definition: lowlevel.cpp:191
CS_Stats::ticks_overtime
unsigned long ticks_overtime
Definition: newclient.h:707
cst_lst
CS_Stats cst_lst
Definition: newclient.h:712
SockList::buf
unsigned char buf[MAXSOCKBUF]
Definition: newclient.h:690
CS_Stats
Statistics for the last CS_LOGTIME seconds on the server.
Definition: newclient.h:697
OUT_OF_MEMORY
@ OUT_OF_MEMORY
Definition: define.h:48
SockList_AddPrintf
void SockList_AddPrintf(SockList *sl, const char *format,...)
Adds a printf like formatted string.
Definition: lowlevel.cpp:202
SockList
Contains the base information we use to make up a packet we want to send.
Definition: newclient.h:684
SockList_AddString
void SockList_AddString(SockList *sl, const char *data)
Adds a string without length.
Definition: lowlevel.cpp:157
llevDebug
@ llevDebug
Only for debugging purposes.
Definition: logger.h:13
GetInt_String
int GetInt_String(const unsigned char *data)
Basically does the reverse of SockList_AddInt, but on strings instead.
Definition: lowlevel.cpp:254
CS_Stats::max_conn
short max_conn
Maximum connections received.
Definition: newclient.h:700
OutputFile
Definition: output_file.h:41