/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames 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 of the License, or
 * (at your option) any later version.
 * VgaGames 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.
 * You should have received a copy of the GNU General Public License
 * along with VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include "nw.h"
#ifdef SML3_HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif

void init_nw(void);
void dest_nw(void);

void vg4_nw_sendping(VG_BOOL);

static void nw_set_ports(int, int);
static VG_BOOL nw_check_server(VG_BOOL *);
static char * nw_get_ip(char *, size_t);
static void nw_close(void);
static int nw_connect(const char *, int, int, const char *, void (*)(const char * const *, int, void *), void *);
static VG_BOOL nw_update(VG_BOOL *, struct VG_NwZdata *);
static void nw_pause(void);
static VG_BOOL nw_key_pressed(int, int);
static VG_BOOL nw_key_newpressed(int, int);
static void nw_mouse_position(int, int *, int *);
static VG_BOOL nw_mouse_pressed(int, VG_BOOL *, VG_BOOL *, VG_BOOL *);
static VG_BOOL nw_mouse_newpressed(int, VG_BOOL *, VG_BOOL *, VG_BOOL *);
static int nw_numberofclients(int *);
static VG_BOOL nw_is_connected(int);
static int nw_local_clnr(void);
static const char * nw_get_clientname(int);
static void nw_connected_clients(void);
static void nw_put_zdata(const struct VG_NwZdata *);
static VG_BOOL nw_xdata_recv(char **, size_t *, int *);
static VG_BOOL nw_xdata_send(const char *, size_t);
static void nw_xdata_retag(void);

static VG_BOOL process_pause(int);
static VG_BOOL send2server(struct nwdata_packets *, unsigned char *);
static int set_hnamearray(char *, int, char **);
static void mouse_set_nw(unsigned char *);
static void mouse_get_nw(const unsigned char *, int *, int *, char *, char *, char *);
static void close_network(void);


/* set functions */
void
init_nw(void)
{
  vg4data.lists.nw.nw_seed = 0;
  vg4data.lists.nw.s = SML3_calloc(1, sizeof(*vg4data.lists.nw.s));
  vg4data.lists.nw.s->sockfd = -1;
  vg4data.lists.nw.s->svport = SERVER_PORT;
  vg4data.lists.nw.s->mbport = MBCAST_PORT;
  vg4->nw->set_ports = nw_set_ports;
  vg4->nw->check_server = nw_check_server;
  vg4->nw->get_ip = nw_get_ip;
  vg4->nw->close = nw_close;
  vg4->nw->connect = nw_connect;
  vg4->nw->update = nw_update;
  vg4->nw->pause = nw_pause;
  vg4->nw->key_pressed = nw_key_pressed;
  vg4->nw->key_newpressed = nw_key_newpressed;
  vg4->nw->mouse_position = nw_mouse_position;
  vg4->nw->mouse_pressed = nw_mouse_pressed;
  vg4->nw->mouse_newpressed = nw_mouse_newpressed;
  vg4->nw->numberofclients = nw_numberofclients;
  vg4->nw->is_connected = nw_is_connected;
  vg4->nw->local_clnr = nw_local_clnr;
  vg4->nw->get_clientname = nw_get_clientname;
  vg4->nw->connected_clients = nw_connected_clients;
  vg4->nw->put_zdata = nw_put_zdata;
  vg4->nw->xdata_recv = nw_xdata_recv;
  vg4->nw->xdata_send = nw_xdata_send;
  vg4->nw->xdata_retag = nw_xdata_retag;
  init_nw_server();
  init_nw_xdatalist();
} /* Ende init_nw */

void
dest_nw(void)
{
  vg4->nw->close();
  free(vg4data.lists.nw.s);
  vg4data.lists.nw.s = NULL;
  vg4data.lists.nw.nw_seed = 0;
} /* Ende dest_nw */


/* vg4_nw_sendping(): send ping to network-server */
void
vg4_nw_sendping(VG_BOOL ob_clnr)
{
  struct nwdata_packets nwpack;
  struct vgi_nw *sptr;

  sptr = vg4data.lists.nw.s;
  if (sptr->sockfd < 0 || vg4data.lists.nw.nw_seed == 0) { return; }

  memset(&nwpack, 0, sizeof(nwpack));
  nwpack.kenner = NWDATA_KENNER_RUNCONN;
  nwpack.action = NWDATA_ACTION_PING;
  nwpack.u.k_runconn.a_ping.c2s.clnr = (ob_clnr ? sptr->clnr : 0);
  send2server(&nwpack, sptr->dgram);
} /* Ende vg4_nw_sendping */


/* set to pause, deal with pause, set to continue */
static VG_BOOL
process_pause(int type)
{
  struct vgi_nw *sptr = vg4data.lists.nw.s;

  if (type == 1) {  /* pause game */
    if (sptr->is_paused) { return VG_TRUE; }

    VG_IMAGECOPY_ATTR_DEFAULT(&sptr->pause.iattr);
    sptr->pause.iattr.pixel.brightness = 30;
    sptr->pause.pause_ping = 0;

    sptr->pause.keyref_cont[0] = vg4->input->key_insert("CONTINUE", VG_FALSE, VG_FALSE);
    vg4->input->key_setkbd(sptr->pause.keyref_cont[0], VG_INPUT_KBDCODE_SPACE);
    sptr->pause.keyref_cont[1] = vg4->input->key_insert("CONTINUE", VG_FALSE, VG_FALSE);
    vg4->input->key_setkbd(sptr->pause.keyref_cont[1], VG_INPUT_KBDCODE_RETURN);
    sptr->pause.keyref_cont[2] = vg4->input->key_insert("CONTINUE", VG_FALSE, VG_FALSE);
    vg4->input->key_setkbd(sptr->pause.keyref_cont[2], VG_INPUT_KBDCODE_ESCAPE);

    sptr->pause.wattr_pixel = vg4->window->getattr();
    vg4->window->setattr(NULL);

    sptr->pause.wclone = vg4_window_clone_nolist(NULL, NULL);
    sptr->pause.au_susp = vg4->audio->suspend(VG_TRUE);

    { const char *prefix = vg4_get_prefix(NULL);
      char sprtfile[VGI_PATHSIZE];
      int wsize;
      vg4->window->getparameters(&wsize, NULL);
      if (wsize == VG_WINDOW_SIZE_LOW) {
        snprintf(sprtfile, sizeof(sprtfile), "%s/share/vgagames4/images/zzz-low.sprite", prefix);
      } else {
        snprintf(sprtfile, sizeof(sprtfile), "%s/share/vgagames4/images/zzz-high.sprite", prefix);
      }
      sptr->pause.sprt_zzz = vg4_sprite_load_nolist(sprtfile);
    }
    sptr->pause.imgp_cont = vg4_intern_ok_cancel_img(&sptr->pause.cont_posi, 1);
    sptr->pause.cont_ilauf = 0;

    sptr->is_paused = VG_TRUE;

  } else if (type == 2) {  /* game is paused */
    struct VG_Image *imgp_zzz;
    struct VG_ImagecopyAttr iattr_zzz;

    if (!vg4->input->update(VG_FALSE)) {  /* close window */
      vg4->window->clear();
      vg4->window->copy(sptr->pause.wclone, NULL, NULL);
      vg4->window->flush();
      vg4->image->destroy(sptr->pause.wclone);
      vg4->sprite->destroy(sptr->pause.sprt_zzz);
      vg4->image->destroy(sptr->pause.imgp_cont);
      vg4->input->key_remove(sptr->pause.keyref_cont[0]);
      vg4->input->key_remove(sptr->pause.keyref_cont[1]);
      vg4->input->key_remove(sptr->pause.keyref_cont[2]);
      vg4->audio->suspend(sptr->pause.au_susp);
      vg4->window->setattr(&sptr->pause.wattr_pixel);
      return VG_FALSE;
    }

    if (vg4->input->key_newpressed(sptr->pause.keyref_cont[0])
        || vg4->input->key_newpressed(sptr->pause.keyref_cont[1])
        || vg4->input->key_newpressed(sptr->pause.keyref_cont[2])) {
      /* continue */
      struct nwdata_packets nwpack;
      memset(&nwpack, 0, sizeof(nwpack));
      nwpack.kenner = NWDATA_KENNER_RUNCONN;
      nwpack.action = NWDATA_ACTION_CONTINUE;
      nwpack.u.k_runconn.a_continue.c2s.clnr = sptr->clnr;
      send2server(&nwpack, sptr->dgram);
    } else {  /* evtl. ping */
      if (++sptr->pause.pause_ping == 50) {
        vg4_nw_sendping(VG_TRUE);
        sptr->pause.pause_ping = 0;
      }
    }

    vg4->window->clear();
    vg4->window->copy(sptr->pause.wclone, NULL, &sptr->pause.iattr);

    if (vg4->sprite->next(sptr->pause.sprt_zzz, &imgp_zzz, &iattr_zzz) && imgp_zzz != NULL) {
      vg4->window->copy(imgp_zzz, NULL, &iattr_zzz);
    }

    sptr->pause.cont_ilauf++;
    if (sptr->pause.cont_ilauf >= 2000 / VGI_WAIT_TIME + 1) {
      sptr->pause.cont_ilauf = 0;
    } else if (sptr->pause.cont_ilauf >= 1000 / VGI_WAIT_TIME + 1) {
      vg4->window->copy(sptr->pause.imgp_cont, &sptr->pause.cont_posi, NULL);
    }

    vg4->window->flush();
    SML3_sleep_msek(VGI_WAIT_TIME);

  } else if (type == 3) {  /* continue game */
    if (!sptr->is_paused) { return VG_TRUE; }

    vg4->window->clear();
    vg4->window->copy(sptr->pause.wclone, NULL, NULL);
    vg4->window->flush();
    vg4->image->destroy(sptr->pause.wclone);
    sptr->pause.wclone = NULL;
    vg4->sprite->destroy(sptr->pause.sprt_zzz);
    sptr->pause.sprt_zzz = NULL;
    vg4->image->destroy(sptr->pause.imgp_cont);
    sptr->pause.imgp_cont = NULL;
    vg4->input->key_remove(sptr->pause.keyref_cont[0]);
    vg4->input->key_remove(sptr->pause.keyref_cont[1]);
    vg4->input->key_remove(sptr->pause.keyref_cont[2]);
    vg4->audio->suspend(sptr->pause.au_susp);
    vg4->window->setattr(&sptr->pause.wattr_pixel);
    sptr->is_paused = VG_FALSE;
  }

  return VG_TRUE;
} /* Ende process_pause */


/* send data in nwpack via dgram to nw-server */
static VG_BOOL
send2server(struct nwdata_packets *nwpack, unsigned char *dgram)
{
  size_t slen;
  VG_BOOL retw;
  struct vgi_nw *sptr = vg4data.lists.nw.s;

  if (nwpack == NULL || dgram == NULL) { return VG_TRUE; }

  retw = VG_TRUE;
  slen = cl_nwpack2dgram(nwpack, dgram);
  if (slen > 0) {
    fcntl(sptr->sockfd, F_SETFL, fcntl(sptr->sockfd, F_GETFL, 0) & ~O_NONBLOCK);
    if (sendto(sptr->sockfd, dgram, slen, 0, sptr->sad_send, sptr->salen) < 0) {
      outerr("sending to network-server failed: %s", strerror(errno));
      retw = VG_FALSE;
    }
    fcntl(sptr->sockfd, F_SETFL, fcntl(sptr->sockfd, F_GETFL, 0) | O_NONBLOCK);
  } else {
    retw = VG_FALSE;
  }

  return retw;
} /* Ende send2server */


/* set hostname-array from received hostnames */
static int
set_hnamearray(char *pstart, int hlen, char **hnames)
{
  char *pend;
  int cidx;

  cidx = 0;
  for (; hlen > 0 && cidx < MAX_CLIENTS;) {
    pend = memchr(pstart, '\n', hlen);
    if (pend == NULL) { break; }
    *pend = '\0';
    hnames[cidx++] = pstart;
    hlen -= (int)(size_t)(pend + 1 - pstart);
    pstart = pend + 1;
  }

  return cidx;
} /* Ende set_hnamearray */


/* set mouse-info into buffer */
static void
mouse_set_nw(unsigned char *cbuf)
{
  union {
    unsigned SML3_int64 iwert;
    unsigned char cwert[sizeof(SML3_int64)];
  } u;
  int bitanz, maxwert, xpos, ypos;
  VG_BOOL left, middle, right, isbig;

  if (cbuf == NULL) { return; }

  vg4->input->mouse_position(&xpos, &ypos);
  if (xpos < 0) { xpos = 0; }
  if (ypos < 0) { ypos = 0; }
  vg4->input->mouse_pressed(&left, &middle, &right);

  u.iwert = 0;

  bitanz = (MOUSE_BYTES * 8 - 4) / 2;
  maxwert = (1 << bitanz) - 1;

  if (xpos > maxwert || ypos > maxwert) { isbig = VG_TRUE; } else { isbig = VG_FALSE; }
  if (isbig) {
    xpos /= MOUSE_DIVVAL; if (xpos > maxwert) { xpos = maxwert; }
    ypos /= MOUSE_DIVVAL; if (ypos > maxwert) { ypos = maxwert; }
  }

  u.iwert = ((u.iwert << 1) + (!!isbig));
  u.iwert = ((u.iwert << 1) + (!!left));
  u.iwert = ((u.iwert << 1) + (!!middle));
  u.iwert = ((u.iwert << 1) + (!!right));
  u.iwert = ((u.iwert << bitanz) + xpos);
  u.iwert = ((u.iwert << bitanz) + ypos);
  u.iwert = SML3_hton64(u.iwert);

  memcpy(cbuf, u.cwert + sizeof(SML3_int64) - MOUSE_BYTES, MOUSE_BYTES);
} /* Ende mouse_set_nw */


/* get mouse-info from buffer */
static void
mouse_get_nw(const unsigned char *cbuf, int *xpos, int *ypos, char *left, char *middle, char *right)
{
  union {
    unsigned SML3_int64 iwert;
    unsigned char cwert[sizeof(SML3_int64)];
  } u;
  int bitanz, maxwert, izahl, isbig;

  if (xpos != NULL) { *xpos = 0; }
  if (ypos != NULL) { *ypos = 0; }
  if (cbuf == NULL) { return; }

  bitanz = (MOUSE_BYTES * 8 - 4) / 2;
  maxwert = (1 << bitanz) - 1;

  u.iwert = 0;
  memcpy(u.cwert + sizeof(SML3_int64) - MOUSE_BYTES, cbuf, MOUSE_BYTES);
  u.iwert = SML3_ntoh64(u.iwert);

  izahl = (u.iwert & ((1 << bitanz) - 1)); u.iwert >>= bitanz;
  if (ypos != NULL) { *ypos = izahl; }
  izahl = (u.iwert & ((1 << bitanz) - 1)); u.iwert >>= bitanz;
  if (xpos != NULL) { *xpos = izahl; }

  izahl = (u.iwert & 1); u.iwert >>= 1;
  if (right != NULL) {
    if (izahl) {
      if (*right == 0) { *right = 1; }
    } else {
      *right = 0;
    }
  }

  izahl = (u.iwert & 1); u.iwert >>= 1;
  if (middle != NULL) {
    if (izahl) {
      if (*middle == 0) { *middle = 1; }
    } else {
      *middle = 0;
    }
  }

  izahl = (u.iwert & 1); u.iwert >>= 1;
  if (left != NULL) {
    if (izahl) {
      if (*left == 0) { *left = 1; }
    } else {
      *left = 0;
    }
  }

  isbig = (u.iwert & 1); u.iwert >>= 1;
  if (isbig) {
    if (xpos != NULL) {
      (*xpos) *= MOUSE_DIVVAL;
      (*xpos) += (MOUSE_DIVVAL / 2);
      if ((*xpos) > maxwert) { (*xpos) = maxwert; }
    }
    if (ypos != NULL) {
      (*ypos) *= MOUSE_DIVVAL;
      (*ypos) += (MOUSE_DIVVAL / 2);
      if ((*ypos) > maxwert) { (*ypos) = maxwert; }
    }
  }
} /* Ende mouse_get_nw */


/* close network: send close-packet, get close-packet as acknowledgement */
static void
close_network(void)
{
  struct vgi_nw *sptr;
  struct nwdata_packets nwpack;
  size_t slen;
  socklen_t satmp;
  ssize_t dglen;
  struct timeval tv;
  fd_set fdread;
  int ilauf, erg;

  sptr = vg4data.lists.nw.s;

  if (sptr->clnr == 0) {  /* still connecting */
    for (ilauf = 3; ilauf > 0; ilauf--) {
      /* send close-packet */
      memset(&nwpack, 0, sizeof(nwpack));
      nwpack.kenner = NWDATA_KENNER_RUNCONN;
      nwpack.action = NWDATA_ACTION_CLOSE;
      nwpack.u.k_runconn.a_close.c2s.clnr = sptr->clnr;
      send2server(&nwpack, sptr->dgram);
      if (ilauf > 1) { SML3_sleep_msek(100 + ilauf * 50); }
    }
    return;
  }

  for (ilauf = 0; ilauf < 5; ilauf++) {
    /* send close-packet */
    memset(&nwpack, 0, sizeof(nwpack));
    nwpack.kenner = NWDATA_KENNER_RUNCONN;
    nwpack.action = NWDATA_ACTION_CLOSE;
    nwpack.u.k_runconn.a_close.c2s.clnr = sptr->clnr;
    send2server(&nwpack, sptr->dgram);

    /* receive close-packet as acknowledgement */

    /* wait for data */
    FD_ZERO(&fdread);
    FD_SET(sptr->sockfd, &fdread);
    tv.tv_sec = 0;
    tv.tv_usec = (100+ ilauf * 30) * 1000;

    erg = select(sptr->sockfd + 1, &fdread, NULL, NULL, &tv);
    if (erg < 0) { break; }
    if (erg == 0) { continue; }

    /* receive data packet */
    satmp = sptr->salen;
    dglen = recvfrom(sptr->sockfd, sptr->dgram, sizeof(sptr->dgram), 0, sptr->sad_recv, &satmp);
    if (dglen < 0) {
      if (!SML3_errno_is_eagain(errno)) { break; }
      dglen = 0;
    }
    slen = (size_t)dglen;

    /* check if from nw-server */
    if (slen > 0) {
      if (satmp != sptr->salen || !nw_addr_equal(sptr->ipv, sptr->sad_recv, sptr->sad_send, satmp)) { slen = 0; }
    }

    if (slen == 0) { SML3_sleep_msek(50); continue; }

    /* set nw-packet into struct */
    cl_dgram2nwpack(NULL, sptr->dgram, slen, &nwpack);
    if (nwpack.kenner != NWDATA_KENNER_RUNCONN || nwpack.action != NWDATA_ACTION_CLOSE) { SML3_sleep_msek(50); continue; }

    /* got acknowledgement */
    break;
  }
} /* Ende close_network */


/* nw_set_ports:
 * set connection-ports
 * @param svport  UDP-port for network-server, or 0 = default port
 * @param mbport  UDP-port for multicast-/broadcast-server, or 0 = default port
 */
static void
nw_set_ports(int svport, int mbport)
{
  struct vgi_nw *sptr;

  sptr = vg4data.lists.nw.s;

  if (svport <= 0) { svport = SERVER_PORT; }
  if (mbport <= 0) { mbport = MBCAST_PORT; }

  sptr->svport = svport;
  sptr->mbport = mbport;
} /* Ende nw_set_ports */


/* nw_check_server:
 * check for a running network-server in the LAN
 * @param islocal  returns whether server is running locally, if not NULL
 * @return  VG_TRUE: found, VG_FALSE: not found
 */
static VG_BOOL
nw_check_server(VG_BOOL *islocal)
{
  struct vgi_nw *sptr;
  int anz;
  struct SML3_nw_mbcast *ipliste;
  char mbportb[32];

  if (islocal != NULL) { *islocal = VG_FALSE; }

  sptr = vg4data.lists.nw.s;
  snprintf(mbportb, sizeof(mbportb), "%d", sptr->mbport);

  anz = SML3_nw_mbcast_client(NULL, mbportb, 0, 1, NULL, 0, &ipliste);
  if (anz <= 0) { return VG_FALSE; }
  if (anz > 0) { SML3_nw_mbcast_free(ipliste); }

  if (islocal != NULL && nw_sv_runlocal()) { *islocal = VG_TRUE; }

  return VG_TRUE;
} /* Ende nw_check_server */


/* nw_get_ip:
 * return ip for connection to network-server
 * @param remhost  for returning remote hostname, may be NULL
 * @param remsize  sizeof remhost
 * @return         ip of the network-server (allocated)
 *                 or NULL = error
 */
static char *
nw_get_ip(char *remhost, size_t remsize)
{
  struct vgi_nw *sptr;
  int anz, redo;
  struct SML3_nw_mbcast *ipliste;
  char *ipnr, mbportb[32];

  sptr = vg4data.lists.nw.s;
  snprintf(mbportb, sizeof(mbportb), "%d", sptr->mbport);

  for (redo = 0; redo < 3; redo++) {
    pinfo("Trying to get ip of network-server ...\n");
    anz = SML3_nw_mbcast_client(NULL, mbportb, 0, 0, NULL, 0, &ipliste);
    if (anz < 0) { outerr("get ip of network-server: %s", SML3_fehlermsg()); return NULL; }
    if (anz > 0) { break; }
  }

  if (anz == 0 || ipliste == NULL) {
    /* got no ip, try localhost */
    pinfo("No ip of network-server received, returning localhost\n");
    if (remhost != NULL && remsize > 0) { snprintf(remhost, remsize, "localhost"); }
    return SML3_strdup("127.0.0.1");
  }

  pinfo("Received ip of network-server: %s\n", ipliste->ipnr);

  ipnr = SML3_strdup(ipliste->ipnr);
  if (remhost != NULL && remsize > 0) {  /* remote hostname */
    snprintf(remhost, remsize, "%.*s", (int)ipliste->datlen, ipliste->daten);
  }
  SML3_nw_mbcast_free(ipliste);

  return ipnr;
} /* Ende nw_get_ip */


/* nw_close:
 * close connection to network-server
 */
static void
nw_close(void)
{
  struct vgi_nw *sptr;

  sptr = vg4data.lists.nw.s;

  if (sptr->sockfd >= 0) {  /* send close-packet and close connection */
    close_network();
    close(sptr->sockfd);
    pinfo("connection to network-server closed\n");
  }

  if (sptr->sad_send != NULL) { free(sptr->sad_send); }
  if (sptr->sad_recv != NULL) { free(sptr->sad_recv); }

  if (sptr->clients != NULL) { free(sptr->clients); }

  nw_free_nwdata_zdata(sptr->zdata.slist);
  nw_free_nwdata_zdata(sptr->zdata.rdata);

  memset(sptr, 0, sizeof(*sptr));
  sptr->sockfd = -1;
  sptr->svport = SERVER_PORT;
  sptr->mbport = MBCAST_PORT;

  vg4data.lists.nw.nw_seed = 0;
} /* Ende nw_close */


/* nw_connect:
 * connect to network-server
 * @param ip          internet address (ipv4 or ipv6) or name of the network-server
 * @param minclients  min. number of clients (1 to maxclients), or 0 = don't matter
 * @param maxclients  max. number of clients (1 to MAX_CLIENTS), or 0 = don't matter
 * @param name        client-name or NULL = hostname
 * @param cbkf        callback function for displaying connected clients, or NULL = no function
 *                     - parameters
 *                       1. param: array of client-names
 *                       2. param: number of clients
 *                       3. param: vdata (arbitrary data passed to cbkf())
 * @param vdata       3. param for cbkf()
 * @return  >0: client-number
 *           0: exit-request
 *          -1: error
 */
static int
nw_connect(const char *ip, int minclients, int maxclients, const char *name, void (*cbkf)(const char * const *, int, void *), void *vdata)
{
  const int sleep_time = 50;
  char hname[64], svportb[32];
  struct nwdata_packets nwpack;
  struct vgi_nw *sptr;
  size_t slen, tlen;
  socklen_t satmp;
  ssize_t dglen;
  int retw, iresend, irecount, keyref_cancel, keyref_endconn1, keyref_endconn2, concli;
  char *hnames[MAX_CLIENTS];
  struct VG_Image *imgp_start, *imgp_cancel;
  int startcancel_ilauf;
  struct VG_Image *wclone;

  sptr = vg4data.lists.nw.s;
  if (sptr->sockfd >= 0) { outerr("connection to network-server already open"); return -1; }

  snprintf(svportb, sizeof(svportb), "%d", sptr->svport);

  if (ip == NULL || *ip == '\0') { outerr("no ip given"); return -1; }
  if (maxclients < 0) { maxclients = 0; } else if (maxclients > MAX_CLIENTS) { maxclients = MAX_CLIENTS; }
  if (minclients <= 0 || minclients > maxclients) { minclients = 1; }
  if (name == NULL || *name == '\0') {
    if (gethostname(hname, sizeof(hname)) < 0) { snprintf(hname, sizeof(hname), "<nobody>"); }
  } else {
    snprintf(hname, sizeof(hname), "%s", name);
  }

  /* connect to network-server */
  sptr->ipv = 0;
  sptr->sockfd = SML3_nw_udp_client(ip, svportb, &sptr->ipv, &sptr->sad_send, &sptr->salen, NULL);
  if (sptr->sockfd < 0) {
    outerr("error connecting to network-server: %s", SML3_fehlermsg());
    return -1;
  }
  fcntl(sptr->sockfd, F_SETFL, fcntl(sptr->sockfd, F_GETFL, 0) | O_NONBLOCK);

  /* send new-connection request */
  memset(&nwpack, 0, sizeof(nwpack));
  nwpack.kenner = NWDATA_KENNER_GETCONN;
  nwpack.action = NWDATA_ACTION_NEWCONN;
  nwpack.u.k_getconn.a_newconn.c2s.maxcl = maxclients;
  slen = sizeof(nwpack.u.k_getconn.a_newconn.c2s.hname);
  tlen = strlen(hname);
  if (tlen >= slen) { tlen = slen - 1; hname[tlen] = '\0'; }
  memcpy(nwpack.u.k_getconn.a_newconn.c2s.hname, hname, tlen + 1);
  if (!send2server(&nwpack, sptr->dgram)) {
    outerr("connecting to network-server failed");
    vg4->nw->close();
    return -1;
  }

  /* set key-strokes for cancelling and for stop getting connections */
  keyref_cancel = vg4->input->key_insert("CANCEL", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyref_cancel, VG_INPUT_KBDCODE_ESCAPE);
  keyref_endconn1 = vg4->input->key_insert("END-CONN", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyref_endconn1, VG_INPUT_KBDCODE_RETURN);
  keyref_endconn2 = vg4->input->key_insert("END-CONN", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyref_endconn2, VG_INPUT_KBDCODE_SPACE);

  /* wait for final reply */

  sptr->sad_recv = SML3_malloc(sptr->salen);
  retw = 0;
  iresend = 4;
  irecount = 3 * 20;  /* 3 seconds (20 * sleep-time = 1 second) */
  concli = 0;

  imgp_cancel = vg4_intern_ok_cancel_img(NULL, 0);
  imgp_start = vg4_intern_ok_cancel_img(NULL, 2);
  startcancel_ilauf = 0;
  wclone = vg4->window->clone(NULL, NULL);

  for (;;) {
    if (!vg4->input->update(VG_FALSE)) { break; }  /* exit request */

    if (vg4->input->key_newpressed(keyref_cancel)) {  /* cancel */
      pinfo("connection to network-server cancelled by user\n");
      break;
    }

    if (concli >= minclients) {
      if (vg4->input->key_newpressed(keyref_endconn1) || vg4->input->key_newpressed(keyref_endconn2)) {  /* stop getting connections */
        memset(&nwpack, 0, sizeof(nwpack));
        nwpack.kenner = NWDATA_KENNER_GETCONN;
        nwpack.action = NWDATA_ACTION_ENDCONN;
        if (!send2server(&nwpack, sptr->dgram)) { retw = -1; break; }
      }
    }

    /* resend new-connection request, if no reply */
    if (iresend > 0 && --irecount == 0) {
      memset(&nwpack, 0, sizeof(nwpack));
      nwpack.kenner = NWDATA_KENNER_GETCONN;
      nwpack.action = NWDATA_ACTION_NEWCONN;
      nwpack.u.k_getconn.a_newconn.c2s.maxcl = maxclients;
      slen = sizeof(nwpack.u.k_getconn.a_newconn.c2s.hname);
      tlen = strlen(hname);
      if (tlen >= slen) { tlen = slen - 1; hname[tlen] = '\0'; }
      memcpy(nwpack.u.k_getconn.a_newconn.c2s.hname, hname, tlen + 1);
      if (!send2server(&nwpack, sptr->dgram)) { retw = -1; break; }
      iresend--; irecount = 3 * 20;
      if (iresend == 0) {  /* connection failed */
        pinfo("connection to network-server failed: no reply\n");
        break;
      } else {
        pinfo("resending connection-request to network-server\n");
      }
    }

    vg4->window->clear();
    vg4->window->copy(wclone, NULL, NULL);

    /* starting-/canceling-text */
    startcancel_ilauf++;
    if (startcancel_ilauf >= 4000 / sleep_time + 1) {
      startcancel_ilauf = 0;
    } else if (startcancel_ilauf >= 2000 / sleep_time + 1) {
      if (concli >= minclients) {
        vg4->window->copy(imgp_start, NULL, NULL);
      } else {
        vg4->window->copy(imgp_cancel, NULL, NULL);
      }
    }

    vg4->window->flush();
    SML3_sleep_msek(sleep_time);

    /* receive reply-nw-packet */
    satmp = sptr->salen;
    dglen = recvfrom(sptr->sockfd, sptr->dgram, sizeof(sptr->dgram), 0, sptr->sad_recv, &satmp);
    if (dglen == 0) { continue; }
    if (dglen < 0) {
      if (SML3_errno_is_eagain(errno)) { continue; }
      outerr("connecting to network-server: recvfrom failed: %s", strerror(errno));
      retw = -1;
      break;
    }
    slen = (size_t)dglen;

    /* check if from nw-server */
    if (satmp != sptr->salen || !nw_addr_equal(sptr->ipv, sptr->sad_recv, sptr->sad_send, satmp)) { continue; }

    /* set nw-packet into struct */
    cl_dgram2nwpack(NULL, sptr->dgram, slen, &nwpack);
    if (nwpack.kenner != NWDATA_KENNER_GETCONN) { continue; }

    /* interpret nw-reply */

    if (nwpack.action == NWDATA_ACTION_NEWCONN) {
      /* information about a new connection */
      int hanz;

      hanz = set_hnamearray(nwpack.u.k_getconn.a_newconn.s2c.hname, nwpack.u.k_getconn.a_newconn.s2c.hnamelen, hnames);
      if (hanz == 0) {
        outerr("connecting to network-server: info-reply has no data");
        retw = -1;
        break;
      }

      if (cbkf != NULL) {
        vg4->image->destroy(wclone);
        cbkf((const char * const *)hnames, hanz, vdata);
        wclone = vg4->window->clone(NULL, NULL);
      } else {
        int icl;
        pinfo("Connected clients: ");
        for (icl = 0; icl < hanz; icl++) { pinfo("\"%s\" ", hnames[icl]); }
        pinfo("\n");
      }

      concli = hanz;
      iresend = 0;

    } else if (nwpack.action == NWDATA_ACTION_ENDCONN) {
      /* final reply */
      int hanz, icl;

      hanz = set_hnamearray(nwpack.u.k_getconn.a_endconn.s2c.hname, nwpack.u.k_getconn.a_endconn.s2c.hnamelen, hnames);
      if (hanz == 0) {
        outerr("connecting to network-server: final-reply has no data");
        retw = -1;
        break;
      }

      if (cbkf != NULL) {
        cbkf((const char * const *)hnames, hanz, vdata);
      } else {
        int icl;
        pinfo("Finally connected clients: ");
        for (icl = 0; icl < hanz; icl++) { pinfo("\"%s\" ", hnames[icl]); }
        pinfo("\n");
      }

      sptr->anzcl = hanz;
      sptr->clients = SML3_calloc(sptr->anzcl, sizeof(*sptr->clients));
      for (icl = 0; icl < hanz; icl++) {
        nw_conncl_set(&sptr->conncl, NW_ICONN2CLNR(icl), 1);
        snprintf(sptr->clients[icl].name, sizeof(sptr->clients[icl].name), "%s", hnames[icl]);
      }
      sptr->zdata.slist = NULL;
      sptr->zdata.rdata = NULL;

      sptr->clnr = nwpack.u.k_getconn.a_endconn.s2c.clnr;
      vg4data.lists.nw.nw_seed = SML3_strtoui(nwpack.u.k_getconn.a_endconn.s2c.nw_seed, sizeof(nwpack.u.k_getconn.a_endconn.s2c.nw_seed), NULL, 36);
      sptr->seqnr = 0;
      sptr->seqnr_xdata = 1;
      sptr->xdata_tag = 0;

      retw = sptr->clnr;
      break;
    }
  }

  vg4->input->key_remove(keyref_endconn1);
  vg4->input->key_remove(keyref_endconn2);
  vg4->input->key_remove(keyref_cancel);

  vg4->image->destroy(imgp_cancel);
  vg4->image->destroy(imgp_start);
  vg4->image->destroy(wclone);

  /* exit-request or error */
  if (retw <= 0) { vg4->nw->close(); }

  return retw;
} /* Ende nw_connect */


/* nw_update:
 * update input-events and receive from and send to nw-server
 * @param dowait   for returning whether wait in the game-loop:
 *                  - VG_TRUE, if no subsequent package is available
 *                  - VG_FALSE, if subsequent packages are available
 * @param zdata    for returning received additional-data
 * @return  VG_TRUE  =  OK
 *          VG_FALSE =  exit-request (or network-error)
 */
static VG_BOOL
nw_update(VG_BOOL *dowait, struct VG_NwZdata *zdata)
{
  struct nwdata_packets nwpack;
  struct vgi_nw *sptr;
  size_t slen;
  socklen_t satmp;
  ssize_t dglen;
  int retry;
  VG_BOOL retw;

  if (dowait != NULL) { *dowait = VG_TRUE; }
  if (zdata != NULL) { zdata->size = 0; zdata->clnr = 0; }

  if (!vg4->input->update(VG_FALSE)) { return VG_FALSE; }

  if (vg4data.lists.nw.nw_seed == 0) { return VG_TRUE; }  /* no connection */

  sptr = vg4data.lists.nw.s;
  retry = 0;
  retw = VG_TRUE;

#ifdef NWPACK_DUMP
  fprintf(stderr, "NW-Update-Start: seqnr=%d, nwkeys.anz=%d\n", sptr->seqnr, sptr->nwkeys.anz);
#endif

  /* receive nw-packet and send keys */
  while (sptr->nwkeys.anz == 0) {

    /* receive nw-packet */
    satmp = sptr->salen;
    dglen = recvfrom(sptr->sockfd, sptr->dgram, sizeof(sptr->dgram), 0, sptr->sad_recv, &satmp);
    if (dglen < 0) {
      if (!SML3_errno_is_eagain(errno)) {
        outerr("receiving from network-server: recvfrom failed: %s", strerror(errno));
        retw = VG_FALSE;
        break;
      }
      dglen = 0;
    }
    slen = (size_t)dglen;

    /* check if from nw-server */
    if (slen > 0) {
      if (satmp != sptr->salen || !nw_addr_equal(sptr->ipv, sptr->sad_recv, sptr->sad_send, satmp)) { continue; }
    }

    /* process packet */

    if (slen > 0) {
      /* set nw-packet into struct */
      cl_dgram2nwpack(NULL, sptr->dgram, slen, &nwpack);
      if (nwpack.kenner != NWDATA_KENNER_RUNCONN) { continue; }

      if (nwpack.action == NWDATA_ACTION_CLOSE) {  /* close connection */
        pinfo("network-server closed connection\n");
        break;

      } else if (nwpack.action == NWDATA_ACTION_PAUSE) {  /* pause */
        process_pause(1);

      } else if (nwpack.action == NWDATA_ACTION_CONTINUE) {  /* continue */
        process_pause(3);

      } else if (nwpack.action == NWDATA_ACTION_KEYS) {  /* keys */
        int ianz;
        if (sptr->is_paused) { continue; }

        /* get keys-data packages */
        ianz = nwpack.u.k_runconn.a_keys.s2c.anz;
        if (ianz <= 0 || ianz > MAX_KEYSPACKETS) {
          outerr("receiving from network-server: invalid number of keys-data: %d", ianz);
          retw = VG_FALSE;
          break;
        }
        memcpy(sptr->nwkeys.data, nwpack.u.k_runconn.a_keys.s2c.data, sizeof(*sptr->nwkeys.data) * ianz);
        sptr->nwkeys.anz = ianz;
        sptr->nwkeys.pos = 0;

        /* check for correct sequence-number */
        while (sptr->nwkeys.anz > 0) {
          if (sptr->nwkeys.data[sptr->nwkeys.pos].seqnr == sptr->seqnr) { break; }
          sptr->nwkeys.anz--;
          sptr->nwkeys.pos++;
        }
        if (sptr->nwkeys.anz == 0) { continue; }

        /* update next expected sequence-number */
        sptr->seqnr += sptr->nwkeys.anz;

        /* check for additional-data to be sent, put it into data_chunk */
        sptr->zdata.chunk_len = 0;
        if (sptr->zdata.slist != NULL) {
          if (sptr->zdata.slist->pos == 0) {  /* new additional-data, set header */
            int zlen = nw_zheader_set(sptr->zdata.slist, sptr->zdata.data_chunk);
            sptr->zdata.chunk_len = zlen;
          }

          /* set data */
          ianz = (int)(sptr->zdata.slist->size - sptr->zdata.slist->pos);
          if (ianz > MAX_ZDATA - sptr->zdata.chunk_len) {
            ianz = MAX_ZDATA - sptr->zdata.chunk_len;
          }
          if (ianz > 0) {
            memcpy(&sptr->zdata.data_chunk[sptr->zdata.chunk_len],
                   &sptr->zdata.slist->data[sptr->zdata.slist->pos],
                   (size_t)ianz);
            sptr->zdata.slist->pos += ianz;
            sptr->zdata.chunk_len += ianz;
          } else {
            sptr->zdata.chunk_len = 0;
          }

          /* data complete? */
          if (sptr->zdata.slist->pos == sptr->zdata.slist->size) {  /* remove element */
            struct nwdata_zdata *znext = sptr->zdata.slist->next;
            if (sptr->zdata.slist->data != NULL) { free(sptr->zdata.slist->data); }
            free(sptr->zdata.slist);
            sptr->zdata.slist = znext;
          }
        }
      }

    } else if (retry == 0) {
      /* nothing received at the first call of recvfrom(), retry as packet could be delayed */
      struct timeval tv;
      fd_set fdread;
      FD_ZERO(&fdread);
      FD_SET(sptr->sockfd, &fdread);
      tv.tv_sec = 0;
      tv.tv_usec = 50000;
      if (select(sptr->sockfd + 1, &fdread, NULL, NULL, &tv) > 0) { continue; }
      retry++;
    }

    /* send data */

    if (sptr->is_paused) {
      if (!process_pause(2)) { retw = VG_FALSE; break; }
      continue;

    } else {  /* send keys */
      memset(&nwpack, 0, sizeof(nwpack));
      nwpack.kenner = NWDATA_KENNER_RUNCONN;
      nwpack.action = NWDATA_ACTION_KEYS;
      nwpack.u.k_runconn.a_keys.c2s.clnr = sptr->clnr;
      nwpack.u.k_runconn.a_keys.c2s.seqnr = sptr->seqnr;
      mouse_set_nw(nwpack.u.k_runconn.a_keys.c2s.keysdata);
#if 1
      vg4_input_nw_getkeys(nwpack.u.k_runconn.a_keys.c2s.keysdata + MOUSE_BYTES, MAX_KEYS);
#else  /* only for testing */
# define ZUFALL(P1, P2)  (P1 + (rand() % (P2 - P1 + 1)))
      { static char wrt[MAX_KEYS], wfirst=1;
        static const int imaske[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
        int maxbyt, ibyt, ibit, idx;
        unsigned char *keysdt = nwpack.u.k_runconn.a_keys.c2s.keysdata + MOUSE_BYTES;
        if (wfirst) { struct timeval tv; for (idx = 0; idx < MAX_KEYS; idx++) { wrt[idx] = 1; }; wfirst=0; gettimeofday(&tv, NULL); srand(tv.tv_usec); }
        maxbyt = MAX_KEYSBYT - MOUSE_BYTES;
        for (ibyt = 0; ibyt < maxbyt; ibyt++) {
          for (ibit = 0; ibit < 8; ibit++) {
            idx = ibyt * 8 + ibit;
            if (wrt[idx] > 0) {
              keysdt[ibyt] |= imaske[ibit];
              if (--wrt[idx] == 0) { wrt[idx] = ZUFALL(0, 200) - 100; }
            } else {
              keysdt[ibyt] &= ~imaske[ibit];
              if (++wrt[idx] == 0) { wrt[idx] = ZUFALL(0, 200) - 100; }
            }
            if (wrt[idx] == 0) { wrt[idx] = 1; }
          }
        }
      }
#endif
      /* check for additional-data in the data_chunk to be sent */
      if (sptr->zdata.chunk_len > 0) {
        memcpy(nwpack.u.k_runconn.a_keys.c2s.zdata,
               sptr->zdata.data_chunk,
               (size_t)sptr->zdata.chunk_len);
        nwpack.u.k_runconn.a_keys.c2s.zdatalen = sptr->zdata.chunk_len;
      }
      /* send */
      if (!send2server(&nwpack, sptr->dgram)) {
        outerr("sending to network-server failed");
        sptr->nwkeys.anz = 0;
        break;
      }
    }

    /* if no packet was received, redo */
    if (sptr->nwkeys.anz == 0) {
      struct timeval tv;
      fd_set fdread;
      if (++retry > 20) {
        pinfo("receiving network packet: timeout\n");
        break;
      }
      FD_ZERO(&fdread);
      FD_SET(sptr->sockfd, &fdread);
      tv.tv_sec = 0;
      tv.tv_usec = 25000 + (retry * 25000);
      if (select(sptr->sockfd + 1, &fdread, NULL, NULL, &tv) < 0) {
        outerr("receiving from network-server: select failed: %s", strerror(errno));
        retw = VG_FALSE;
        break;
      }
    }
  }

  if (retw == VG_FALSE) { vg4->nw->close(); return VG_FALSE; }
  if (sptr->nwkeys.anz == 0) { vg4->nw->close(); return VG_TRUE; }

  /* update pressed-keys from nw-data */
  { static const int imaske[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
    struct nwdata_run_keys_s2c *pdata;
    int icl, ibit, ibyt, maxbyt;
    unsigned char *keysdt;
    char *kpress;

    pdata = &sptr->nwkeys.data[sptr->nwkeys.pos];

#ifdef NWPACK_CKSUM
    { FILE *ffck;
      void *cksum;
      char ckbuf[40 + 1];
      snprintf(ckbuf, sizeof(ckbuf), "nwlog.%d", sptr->clnr);
      if ((ffck = fopen(ckbuf, "a")) != NULL) {
        cksum = SML3_cksum_init(SML3_CKSUM_DIGEST_SHA1);
        SML3_cksum_add(cksum, pdata, sizeof(*pdata));
        SML3_cksum_result(cksum, ckbuf, sizeof(ckbuf));
        fprintf(ffck, "%hu: %s\n", pdata->seqnr, ckbuf);
        fclose(ffck);
      }
    }
#endif

    sptr->conncl = pdata->conncl;

    for (icl = 0; icl < sptr->anzcl; icl++) {  /* for each client */
      if (!nw_conncl_get(&sptr->conncl, NW_ICONN2CLNR(icl))) { continue; }

      if (pdata->keyset & (1 << icl)) {  /* if client has set keys */
        /* update mouse-data */
        keysdt = &pdata->keysdata[icl * MAX_KEYSBYT];
        mouse_get_nw(keysdt,
                     &sptr->clients[icl].keys.mouse_x,
                     &sptr->clients[icl].keys.mouse_y,
                     &sptr->clients[icl].keys.mouse_left,
                     &sptr->clients[icl].keys.mouse_middle,
                     &sptr->clients[icl].keys.mouse_right);

        /* update keys-data */
        keysdt += MOUSE_BYTES;
        maxbyt = MAX_KEYSBYT - MOUSE_BYTES;
        for (ibyt = 0; ibyt < maxbyt; ibyt++) {
          for (ibit = 0; ibit < 8; ibit++) {
            kpress = &sptr->clients[icl].keys.key_pressed[ibyt * 8 + ibit];
            if (keysdt[ibyt] & imaske[ibit]) {  /* pressed */
              if (*kpress == 0) { *kpress = 1; }
            } else {  /* not pressed */
              *kpress = 0;
            }
          }
        }
      }
    }

    /* additional-data */
    if (pdata->zdatalen > 0) {
      char *pzdata = pdata->zdata;
      if (sptr->zdata.rdata == NULL) {  /* new additional-data, read header */
        int zlen;
        sptr->zdata.rdata = SML3_calloc(1, sizeof(*sptr->zdata.rdata));
        zlen = nw_zheader_get(sptr->zdata.rdata, pzdata);
        sptr->zdata.rdata->pos = 0;
        sptr->zdata.rdata->next = NULL;
        pdata->zdatalen -= zlen;
        pzdata += zlen;
      }

      /* set data */
      if (pdata->zdatalen > 0) {
        memcpy(&sptr->zdata.rdata->data[sptr->zdata.rdata->pos],
               pzdata,
               (size_t)pdata->zdatalen);
        sptr->zdata.rdata->pos += pdata->zdatalen;
      }

      /* data complete? */
      if (sptr->zdata.rdata->pos == sptr->zdata.rdata->size) {
        if (zdata != NULL) {
          zdata->size = sptr->zdata.rdata->size;
          if (zdata->size > 0) { memcpy(zdata->data, sptr->zdata.rdata->data, zdata->size); }
          zdata->clnr = sptr->zdata.rdata->clnr;
        }
        if (sptr->zdata.rdata->data != NULL) { free(sptr->zdata.rdata->data); }
        free(sptr->zdata.rdata);
        sptr->zdata.rdata = NULL;
      }
    }
  }

  sptr->nwkeys.pos++;
  sptr->nwkeys.anz--;
  if (dowait != NULL && sptr->nwkeys.anz > 0) { *dowait = VG_FALSE; }

#ifdef NWPACK_DUMP
  fprintf(stderr, "NW-Update-Ende: seqnr=%d, nwkeys.anz=%d\n", sptr->seqnr, sptr->nwkeys.anz);
#endif

  return retw;
} /* Ende nw_update */


/* nw_pause:
 * send pause-request to nw-server
 */
static void
nw_pause(void)
{
  struct nwdata_packets nwpack;
  struct vgi_nw *sptr;

  if (vg4data.lists.nw.nw_seed == 0) { return; }  /* no connection */

  sptr = vg4data.lists.nw.s;

  memset(&nwpack, 0, sizeof(nwpack));
  nwpack.kenner = NWDATA_KENNER_RUNCONN;
  nwpack.action = NWDATA_ACTION_PAUSE;
  nwpack.u.k_runconn.a_pause.c2s.clnr = sptr->clnr;
  send2server(&nwpack, sptr->dgram);
} /* Ende nw_pause */


/* nw_key_pressed:
 * return if a network-key for a client is pressed
 * @param clnr    client-number
 * @param keyref  key-entry reference-number
 * @return  VG_TRUE = pressed
 *          VG_FALSE = not pressed
 */
static VG_BOOL
nw_key_pressed(int clnr, int keyref)
{
  int ikey, iconn;
  struct vgi_nw *sptr;
  char pressed;

  if (vg4data.lists.nw.nw_seed == 0) { return VG_FALSE; }  /* no connection */

  sptr = vg4data.lists.nw.s;

  if (clnr < 1 || clnr > sptr->anzcl) { return VG_FALSE; }
  if (!nw_conncl_get(&sptr->conncl, clnr)) { return VG_FALSE; }

  iconn = NW_CLNR2ICONN(clnr);

  ikey = vg4_input_nw_keyref(keyref);
  if (ikey < 0) { return VG_FALSE; }

  pressed = sptr->clients[iconn].keys.key_pressed[ikey];
  if (pressed == 1 || pressed == 2) { return VG_TRUE; }

  return VG_FALSE;
} /* Ende nw_key_pressed */


/* nw_key_newpressed:
 * return if a network-key for a client is pressed which has not yet been queried
 * @param clnr    client-number
 * @param keyref  key-entry reference-number
 * @return  VG_TRUE = pressed
 *          VG_FALSE = not pressed
 */
static VG_BOOL
nw_key_newpressed(int clnr, int keyref)
{
  int ikey, iconn;
  struct vgi_nw *sptr;
  char *pressed;

  if (vg4data.lists.nw.nw_seed == 0) { return VG_FALSE; }  /* no connection */

  sptr = vg4data.lists.nw.s;

  if (clnr < 1 || clnr > sptr->anzcl) { return VG_FALSE; }
  if (!nw_conncl_get(&sptr->conncl, clnr)) { return VG_FALSE; }
  iconn = NW_CLNR2ICONN(clnr);

  ikey = vg4_input_nw_keyref(keyref);
  if (ikey < 0) { return VG_FALSE; }

  pressed = &sptr->clients[iconn].keys.key_pressed[ikey];
  if (*pressed == 1) { *pressed = 2; return VG_TRUE; }

  return VG_FALSE;
} /* Ende nw_key_newpressed */


/* nw_mouse_position:
 * return mouse-position of a client
 * @param clnr  client-number
 * @param xpos  for returing x-position
 * @param ypos  for returing y-position
 */
static void
nw_mouse_position(int clnr, int *xpos, int *ypos)
{
  int iconn;
  struct vgi_nw *sptr;

  if (xpos != NULL) { *xpos = 0; }
  if (ypos != NULL) { *ypos = 0; }

  if (vg4data.lists.nw.nw_seed == 0) { return; }  /* no connection */

  sptr = vg4data.lists.nw.s;

  if (clnr < 1 || clnr > sptr->anzcl) { return; }
  if (!nw_conncl_get(&sptr->conncl, clnr)) { return; }
  iconn = NW_CLNR2ICONN(clnr);

  if (xpos != NULL) { *xpos = sptr->clients[iconn].keys.mouse_x; }
  if (ypos != NULL) { *ypos = sptr->clients[iconn].keys.mouse_y; }
} /* Ende nw_mouse_position */


/* nw_mouse_pressed:
 * return pressed mouse-buttons of a client
 * @param clnr    client-number
 * @param left    for returing whether left button is pressed, (may be NULL)
 * @param middle  for returing whether middle button is pressed, (may be NULL)
 * @param right   for returing whether right button is pressed, (may be NULL)
 * @return  VG_TRUE = one or more buttons are pressed
 *          VG_FALSE = no button is pressed
 */
static VG_BOOL
nw_mouse_pressed(int clnr, VG_BOOL *left, VG_BOOL *middle, VG_BOOL *right)
{
  int iconn;
  struct vgi_nw *sptr;
  VG_BOOL retw = VG_FALSE;

  if (vg4data.lists.nw.nw_seed == 0) { return VG_FALSE; }  /* no connection */

  sptr = vg4data.lists.nw.s;

  if (clnr < 1 || clnr > sptr->anzcl) { return VG_FALSE; }
  if (!nw_conncl_get(&sptr->conncl, clnr)) { return VG_FALSE; }
  iconn = NW_CLNR2ICONN(clnr);

  if (sptr->clients[iconn].keys.mouse_left
      || sptr->clients[iconn].keys.mouse_middle
      || sptr->clients[iconn].keys.mouse_right) { retw = VG_TRUE; }

  if (left != NULL) {
    if (sptr->clients[iconn].keys.mouse_left) {
      *left = VG_TRUE;
    } else {
      *left = VG_FALSE;
    }
  }

  if (middle != NULL) {
    if (sptr->clients[iconn].keys.mouse_middle) {
      *middle = VG_TRUE;
    } else {
      *middle = VG_FALSE;
    }
  }

  if (right != NULL) {
    if (sptr->clients[iconn].keys.mouse_right) {
      *right = VG_TRUE;
    } else {
      *right = VG_FALSE;
    }
  }

  return retw;
} /* Ende nw_mouse_pressed */


/* nw_mouse_newpressed:
 * return pressed mouse-buttons of a client which has not yet been queried
 * @param clnr    client-number
 * @param left    for returing whether left button is new pressed, (may be NULL)
 * @param middle  for returing whether middle button is new pressed, (may be NULL)
 * @param right   for returing whether right button is new pressed, (may be NULL)
 * @return  VG_TRUE = one or more buttons are new pressed
 *          VG_FALSE = no button is new pressed
 */
static VG_BOOL
nw_mouse_newpressed(int clnr, VG_BOOL *left, VG_BOOL *middle, VG_BOOL *right)
{
  int iconn;
  struct vgi_nw *sptr;
  VG_BOOL retw = VG_FALSE;

  if (vg4data.lists.nw.nw_seed == 0) { return VG_FALSE; }  /* no connection */

  sptr = vg4data.lists.nw.s;

  if (clnr < 1 || clnr > sptr->anzcl) { return VG_FALSE; }
  if (!nw_conncl_get(&sptr->conncl, clnr)) { return VG_FALSE; }
  iconn = NW_CLNR2ICONN(clnr);

  if (sptr->clients[iconn].keys.mouse_left == 1
      || sptr->clients[iconn].keys.mouse_middle == 1
      || sptr->clients[iconn].keys.mouse_right == 1) { retw = VG_TRUE; }

  if (left != NULL) {
    if (sptr->clients[iconn].keys.mouse_left == 1) {
      sptr->clients[iconn].keys.mouse_left = 2;
      *left = VG_TRUE;
    } else {
      *left = VG_FALSE;
    }
  }

  if (middle != NULL) {
    if (sptr->clients[iconn].keys.mouse_middle == 1) {
      sptr->clients[iconn].keys.mouse_middle = 2;
      *middle = VG_TRUE;
    } else {
      *middle = VG_FALSE;
    }
  }

  if (right != NULL) {
    if (sptr->clients[iconn].keys.mouse_right == 1) {
      sptr->clients[iconn].keys.mouse_right = 2;
      *right = VG_TRUE;
    } else {
      *right = VG_FALSE;
    }
  }

  return retw;
} /* Ende nw_mouse_newpressed */


/* nw_numberofclients:
 * return number of clients (still connected or not)
 * @param conncl  for returning number of connected clients, if not NULL
 * @return  number of clients
 */
static int
nw_numberofclients(int *conncl)
{
  if (conncl != NULL) { *conncl = 0; }
  if (vg4data.lists.nw.nw_seed == 0) { return 0; }  /* no connection */

  if (conncl != NULL) {
    struct vgi_nw *sptr = vg4data.lists.nw.s;
    int icl;
    for (icl = 1; icl <= sptr->anzcl; icl++) {
      if (nw_conncl_get(&sptr->conncl, icl)) { (*conncl)++; }
    }
  }

  return vg4data.lists.nw.s->anzcl;
} /* Ende nw_numberofclients */


/* nw_is_connected:
 * return whether client is connected
 * @param clnr    client-number
 * @return  VG_TRUE = yes
 *          VG_FALSE = no
 */
static VG_BOOL
nw_is_connected(int clnr)
{
  struct vgi_nw *sptr;

  if (vg4data.lists.nw.nw_seed == 0) { return VG_FALSE; }  /* no connection */

  sptr = vg4data.lists.nw.s;

  if (clnr < 1 || clnr > sptr->anzcl) { return VG_FALSE; }
  if (!nw_conncl_get(&sptr->conncl, clnr)) { return VG_FALSE; }

  return VG_TRUE;
} /* Ende nw_is_connected */


/* nw_local_clnr:
 * return local client-number
 * @return  local client-number
 */
static int
nw_local_clnr(void)
{
  if (vg4data.lists.nw.nw_seed == 0) { return 0; }  /* no connection */
  return vg4data.lists.nw.s->clnr;
} /* Ende nw_local_clnr */


/* nw_get_clientname:
 * return name of a client
 * @param clnr    client-number
 * @return  pointer to name, or empty = not connected
 */
static const char *
nw_get_clientname(int clnr)
{
  int iconn;
  struct vgi_nw *sptr;

  if (vg4data.lists.nw.nw_seed == 0) { return ""; }  /* no connection */

  sptr = vg4data.lists.nw.s;

  if (clnr < 1 || clnr > sptr->anzcl) { return ""; }
  if (!nw_conncl_get(&sptr->conncl, clnr)) { return ""; }
  iconn = NW_CLNR2ICONN(clnr);

  return (const char *)sptr->clients[iconn].name;
} /* Ende nw_get_clientname */


/* nw_connected_clients:
 * update which clients are connected for using vg4->nw->is_connected(),
 * MUST NOT called within the game-loop where vg4->nw->update() is called.
 */
static void
nw_connected_clients(void)
{
  struct vgi_nw *sptr;
  struct nwdata_packets nwpack;
  size_t slen;
  socklen_t satmp;
  ssize_t dglen;
  struct timeval tv;
  fd_set fdread;
  int ilauf, erg;

  sptr = vg4data.lists.nw.s;

  if (sptr->clnr == 0) { return; }  /* still connecting */

  for (ilauf = 0; ilauf < 5; ilauf++) {
    /* send request */
    memset(&nwpack, 0, sizeof(nwpack));
    nwpack.kenner = NWDATA_KENNER_RUNCONN;
    nwpack.action = NWDATA_ACTION_CONNECTED;
    nwpack.u.k_runconn.a_connected.c2s.clnr = sptr->clnr;
    send2server(&nwpack, sptr->dgram);

    /* receive response */

    /* wait for data */
    FD_ZERO(&fdread);
    FD_SET(sptr->sockfd, &fdread);
    tv.tv_sec = 0;
    tv.tv_usec = (100+ ilauf * 30) * 1000;

    erg = select(sptr->sockfd + 1, &fdread, NULL, NULL, &tv);
    if (erg < 0) { break; }
    if (erg == 0) { continue; }

    /* receive data packet */
    satmp = sptr->salen;
    dglen = recvfrom(sptr->sockfd, sptr->dgram, sizeof(sptr->dgram), 0, sptr->sad_recv, &satmp);
    if (dglen < 0) {
      if (!SML3_errno_is_eagain(errno)) { break; }
      dglen = 0;
    }
    slen = (size_t)dglen;

    /* check if from nw-server */
    if (slen > 0) {
      if (satmp != sptr->salen || !nw_addr_equal(sptr->ipv, sptr->sad_recv, sptr->sad_send, satmp)) { slen = 0; }
    }

    if (slen == 0) { SML3_sleep_msek(50); continue; }

    /* set nw-packet into struct */
    cl_dgram2nwpack(NULL, sptr->dgram, slen, &nwpack);
    if (nwpack.kenner != NWDATA_KENNER_RUNCONN || nwpack.action != NWDATA_ACTION_CONNECTED) { SML3_sleep_msek(50); continue; }

    /* got response */
    sptr->conncl = nwpack.u.k_runconn.a_connected.s2c.conncl;
    break;
  }
} /* Ende nw_connected_clients */


/* nw_put_zdata:
 * put additional-data to be sent to the network-server
 * @param zdata  additional-data to be sent
 *               (element .clnr will be set automatically)
 */
static void
nw_put_zdata(const struct VG_NwZdata *zdata)
{
  struct vgi_nw *sptr;
  struct nwdata_zdata *nwzdata, **znextp;
  size_t dsize;

  if (vg4data.lists.nw.nw_seed == 0) { return; }  /* no connection */

  if (zdata == NULL || zdata->size == 0) { return; }
  dsize = zdata->size;

  sptr = vg4data.lists.nw.s;

  nwzdata = SML3_calloc(1, sizeof(*nwzdata));
  nwzdata->next = NULL;
  if (dsize > (size_t)VG_MAX_ZDATA) { dsize = (size_t)VG_MAX_ZDATA; }
  nwzdata->data = SML3_malloc(dsize);
  memcpy(nwzdata->data, zdata->data, dsize);
  nwzdata->size = dsize;
  nwzdata->clnr = sptr->clnr;

  for (znextp = &sptr->zdata.slist; *znextp != NULL; znextp = &(*znextp)->next) {;}
  *znextp = nwzdata;
} /* Ende nw_put_zdata */


/* nw_xdata_recv:
 * receive exchange-data from nw-server
 * @param ex_data  for returning received exchange-data,
 *                 will be allocated if ex_size returns greater 0
 * @param ex_size  for returning number of bytes in ex_data,
 *                 if returned value is 0, no data is available
 * @param ex_clnr  for returning client-number who sent this data
 * @return  VG_TRUE  =  OK
 *          VG_FALSE =  exit-request (or network-error)
 */
static VG_BOOL
nw_xdata_recv(char **ex_data, size_t *ex_size, int *ex_clnr)
{
  struct nwdata_packets nwpack;
  struct vgi_nw *sptr;
  size_t slen, xdata_pos;
  socklen_t satmp;
  ssize_t dglen;
  int retry, erg, ex_tag;
  VG_BOOL retw, do_close, skip_sel;
  struct timeval tv;
  fd_set fdread;

  if (ex_data == NULL || ex_size == NULL || ex_clnr == NULL) { return VG_TRUE; }
  *ex_data = NULL;
  *ex_size = 0;
  *ex_clnr = 0;
  ex_tag = -1;

  if (!vg4->input->update(VG_FALSE)) { vg4->nw->close(); return VG_FALSE; }

  if (vg4data.lists.nw.nw_seed == 0) { return VG_TRUE; }  /* no connection */

  sptr = vg4data.lists.nw.s;
  xdata_pos = 0;
  retry = 0;
  retw = VG_TRUE;
  do_close = VG_FALSE;
  skip_sel = VG_TRUE;

  /* receive exchange-data */

  for (;;) {
    slen = 0;

    if (!skip_sel) {
      /* wait for socket action */
      FD_ZERO(&fdread);
      FD_SET(sptr->sockfd, &fdread);
      tv.tv_sec = 0;
      tv.tv_usec = (50 + retry * 10) * 1000;

      erg = select(sptr->sockfd + 1, &fdread, NULL, NULL, &tv);
      if (erg < 0) {
        outerr("receiving exchange-data from network-server: select failed: %s", strerror(errno));
        retw = VG_FALSE;
        do_close = VG_TRUE;
        break;
      }

      if (erg == 0) {  /* time out */
        if (!vg4->input->update(VG_FALSE)) { retw = VG_FALSE; do_close = VG_TRUE; break; }
        vg4->window->flush();
        if (++retry == 30) {
          pinfo("receiving network packet (exchange-data): timeout\n");
          retw = VG_FALSE;
          do_close = VG_TRUE;
          break;
        }
      } else if (FD_ISSET(sptr->sockfd, &fdread)) { skip_sel = VG_TRUE; }  /* receive nw-packet */
    }

    if (skip_sel) {
      /* receive data packet */
      satmp = sptr->salen;
      dglen = recvfrom(sptr->sockfd, sptr->dgram, sizeof(sptr->dgram), 0, sptr->sad_recv, &satmp);
      if (dglen < 0) {
        if (!SML3_errno_is_eagain(errno)) {
          outerr("receiving from network-server: recvfrom failed: %s", strerror(errno));
          retw = VG_FALSE;
          do_close = VG_TRUE;
          break;
        }
        dglen = 0;
      }
      slen = (size_t)dglen;

      /* check if from nw-server */
      if (slen > 0) {
        if (satmp != sptr->salen || !nw_addr_equal(sptr->ipv, sptr->sad_recv, sptr->sad_send, satmp)) { slen = 0; }
      }
    }

    skip_sel = VG_TRUE;

    if (slen > 0) {  /* process packet */
      retry = 0;
      /* set nw-packet into struct */
      cl_dgram2nwpack(NULL, sptr->dgram, slen, &nwpack);
      if (nwpack.kenner != NWDATA_KENNER_RUNCONN) { continue; }

      if (nwpack.action == NWDATA_ACTION_CLOSE) {  /* close connection */
        pinfo("network-server closed connection\n");
        do_close = VG_TRUE;
        break;

      } else if (nwpack.action == NWDATA_ACTION_PAUSE) {  /* pause */
        process_pause(1);

      } else if (nwpack.action == NWDATA_ACTION_CONTINUE) {  /* continue */
        process_pause(3);

      } else if (nwpack.action == NWDATA_ACTION_XDATA_SEND) {  /* server sends exchange-data */
        if (sptr->is_paused) { continue; }
        if (nwpack.u.k_runconn.a_xdata_send.s2c.seqnr == sptr->seqnr_xdata) {  /* valid sequence-number */
          char *pxdata = nwpack.u.k_runconn.a_xdata_send.s2c.xdata;
          int xdatalen = nwpack.u.k_runconn.a_xdata_send.s2c.xdatalen;

          if (xdatalen > 0) {  /* exchange-data available */
            if (*ex_size == 0) {  /* new exchange-data, read header */
              struct nwdata_xdata nwdt;
              int xlen;
              xlen = nw_xheader_get(&nwdt, pxdata);
              *ex_size = nwdt.size;
              *ex_clnr = nwdt.clnr;
              ex_tag = nwdt.tag;
              sptr->conncl = nwdt.conncl;
              if (nwdt.size > 0) { free(nwdt.data); }
              xdatalen -= xlen;
              pxdata += xlen;
              if (*ex_size > 0) { *ex_data = SML3_calloc(*ex_size, sizeof(**ex_data)); }
            }

            /* set data */
            if (xdatalen > 0) {
              memcpy(*ex_data + xdata_pos, pxdata, (size_t)xdatalen);
              xdata_pos += xdatalen;
            }
          }

          /* increment sequence-number */
          sptr->seqnr_xdata++;

          /* data complete? */
          if (xdata_pos == *ex_size) {
            if (xdatalen == 0 || ex_tag == sptr->xdata_tag) { break; }
            /* wrong type of exchange-data, skip it */
            free(*ex_data);
            *ex_data = NULL;
            *ex_size = 0;
            *ex_clnr = 0;
            ex_tag = -1;
          }
        }
      }

    } else {  /* inform nw-server to send exchange-data */
      if (sptr->is_paused) {  /* pause */
        retry = 0;
        if (!process_pause(2)) { retw = VG_FALSE; do_close = VG_TRUE; break; }

      } else {  /* send request for next exchange-data */
        memset(&nwpack, 0, sizeof(nwpack));
        nwpack.kenner = NWDATA_KENNER_RUNCONN;
        nwpack.action = NWDATA_ACTION_XDATA_RECV;
        nwpack.u.k_runconn.a_xdata_recv.c2s.clnr = sptr->clnr;
        nwpack.u.k_runconn.a_xdata_recv.c2s.seqnr = sptr->seqnr_xdata;
        if (!send2server(&nwpack, sptr->dgram)) {
          outerr("sending to network-server failed");
          retw = VG_FALSE;
          do_close = VG_TRUE;
          break;
        }
        skip_sel = VG_FALSE;
      }
    }
  }

  if (do_close) { vg4->nw->close(); return retw; }

  return retw;
} /* Ende nw_xdata_recv */


/* nw_xdata_send:
 * send exchange-data to nw-server
 * @param ex_data  exchange-data
 * @param ex_size  number of bytes in ex_data
 *                 May not exceed 16777215
 * @return  VG_TRUE  =  OK
 *          VG_FALSE =  exit-request (or network-error)
 */
static VG_BOOL
nw_xdata_send(const char *ex_data, size_t ex_size)
{
  struct nwdata_packets nwpack;
  struct vgi_nw *sptr;
  size_t slen, xdata_posv, xdata_posb;
  socklen_t satmp;
  ssize_t dglen;
  int retry, erg;
  VG_BOOL retw, do_close, skip_sel, got_ack;
  struct timeval tv;
  fd_set fdread;

  if (ex_data == NULL || ex_size == 0) { return VG_TRUE; }

  if (!vg4->input->update(VG_FALSE)) { vg4->nw->close(); return VG_FALSE; }

  if (vg4data.lists.nw.nw_seed == 0) { return VG_TRUE; }  /* no connection */

  if (ex_size > 16777215) { ex_size = 16777215; }

  sptr = vg4data.lists.nw.s;
  xdata_posv = xdata_posb = 0;
  retry = 0;
  retw = VG_TRUE;
  do_close = VG_FALSE;
  skip_sel = VG_TRUE;
  got_ack = VG_TRUE;

  /* receive exchange-data */

  for (;;) {
    slen = 0;

    if (!skip_sel) {
      /* wait for socket action */
      FD_ZERO(&fdread);
      FD_SET(sptr->sockfd, &fdread);
      tv.tv_sec = 0;
      tv.tv_usec = (50 + retry * 10) * 1000;

      erg = select(sptr->sockfd + 1, &fdread, NULL, NULL, &tv);
      if (erg < 0) {
        outerr("receiving acknowledgement from network-server for exchange-data: select failed: %s", strerror(errno));
        retw = VG_FALSE;
        do_close = VG_TRUE;
        break;
      }

      if (erg == 0) {  /* time out */
        if (!vg4->input->update(VG_FALSE)) { retw = VG_FALSE; do_close = VG_TRUE; break; }
        vg4->window->flush();
        if (++retry == 30) {
          pinfo("receiving network packet (acknowledgement for exchange-data): timeout\n");
          retw = VG_FALSE;
          do_close = VG_TRUE;
          break;
        }
      } else if (FD_ISSET(sptr->sockfd, &fdread)) { skip_sel = VG_TRUE; }  /* receive nw-packet */
    }

    if (skip_sel) {
      /* receive data packet */
      satmp = sptr->salen;
      dglen = recvfrom(sptr->sockfd, sptr->dgram, sizeof(sptr->dgram), 0, sptr->sad_recv, &satmp);
      if (dglen < 0) {
        if (!SML3_errno_is_eagain(errno)) {
          outerr("receiving from network-server: recvfrom failed: %s", strerror(errno));
          retw = VG_FALSE;
          do_close = VG_TRUE;
          break;
        }
        dglen = 0;
      }
      slen = (size_t)dglen;

      /* check if from nw-server */
      if (slen > 0) {
        if (satmp != sptr->salen || !nw_addr_equal(sptr->ipv, sptr->sad_recv, sptr->sad_send, satmp)) { slen = 0; }
      }
    }

    skip_sel = VG_TRUE;

    if (slen > 0) {  /* process packet */
      retry = 0;
      /* set nw-packet into struct */
      cl_dgram2nwpack(NULL, sptr->dgram, slen, &nwpack);
      if (nwpack.kenner != NWDATA_KENNER_RUNCONN) { continue; }

      if (nwpack.action == NWDATA_ACTION_CLOSE) {  /* close connection */
        pinfo("network-server closed connection\n");
        do_close = VG_TRUE;
        break;

      } else if (nwpack.action == NWDATA_ACTION_PAUSE) {  /* pause */
        process_pause(1);

      } else if (nwpack.action == NWDATA_ACTION_CONTINUE) {  /* continue */
        process_pause(3);

      } else if (nwpack.action == NWDATA_ACTION_XDATA_RECV) {  /* server sends acknowledgement */
        if (sptr->is_paused) { continue; }
        if (nwpack.u.k_runconn.a_xdata_send.s2c.seqnr == sptr->seqnr_xdata) {  /* valid sequence-number */
          /* increment sequence-number */
          sptr->seqnr_xdata++;
          got_ack = VG_TRUE;
        }
      }

    } else {  /* send exchange-data to nw-server */
      if (sptr->is_paused) {  /* pause */
        retry = 0;
        if (!process_pause(2)) { retw = VG_FALSE; do_close = VG_TRUE; break; }

      } else {  /* send exchange-data */
        int ilen;

        memset(&nwpack, 0, sizeof(nwpack));
        nwpack.kenner = NWDATA_KENNER_RUNCONN;
        nwpack.action = NWDATA_ACTION_XDATA_SEND;
        nwpack.u.k_runconn.a_xdata_send.c2s.clnr = sptr->clnr;
        nwpack.u.k_runconn.a_xdata_send.c2s.seqnr = sptr->seqnr_xdata;
        nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen = 0;

        if (got_ack) {  /* next chunk */
          xdata_posv = xdata_posb;
          if (xdata_posb == ex_size) { break; }  /* sent complete */

          if (xdata_posv == 0) {  /* new exchange-data, set header */
            struct nwdata_xdata nwdt;
            int xlen;
            memset(&nwdt, 0, sizeof(nwdt));
            nwdt.size = ex_size;
            nwdt.clnr = sptr->clnr;
            nwdt.tag = sptr->xdata_tag;
            nwdt.conncl = 0;
            xlen = nw_xheader_set(&nwdt, nwpack.u.k_runconn.a_xdata_send.c2s.xdata);
            nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen = xlen;
          }      

          /* copy next chunk */
          ilen = ex_size - xdata_posb;
          if (ilen > MAX_XDATA - nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen) {
            ilen = MAX_XDATA - nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen;
          }
          xdata_posb += ilen;
          memcpy(nwpack.u.k_runconn.a_xdata_send.c2s.xdata + nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen,
                 ex_data + xdata_posv,
                 (size_t)ilen);
          nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen += ilen;

        } else {  /* previous chunk */
          if (xdata_posv == 0) {  /* new exchange-data, set header */
            struct nwdata_xdata nwdt;
            int xlen;
            memset(&nwdt, 0, sizeof(nwdt));
            nwdt.size = ex_size;
            nwdt.clnr = sptr->clnr;
            nwdt.tag = sptr->xdata_tag;
            nwdt.conncl = 0;
            xlen = nw_xheader_set(&nwdt, nwpack.u.k_runconn.a_xdata_send.c2s.xdata);
            nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen = xlen;
          }      

          /* copy previous chunk */
          ilen = xdata_posb - xdata_posv;
          memcpy(nwpack.u.k_runconn.a_xdata_send.c2s.xdata + nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen,
                 ex_data + xdata_posv,
                 (size_t)ilen);
          nwpack.u.k_runconn.a_xdata_send.c2s.xdatalen += ilen;
        }

        if (!send2server(&nwpack, sptr->dgram)) {
          outerr("sending to network-server failed");
          retw = VG_FALSE;
          do_close = VG_TRUE;
          break;
        }
        got_ack = VG_FALSE;
        skip_sel = VG_FALSE;
      }
    }
  }

  if (do_close) { vg4->nw->close(); return retw; }

  return retw;
} /* Ende nw_xdata_send */


/* nw_xdata_retag:
 * set a new tag of exchange-data
 */
static void
nw_xdata_retag(void)
{
  struct vgi_nw *sptr = vg4data.lists.nw.s;
  sptr->xdata_tag++;
} /* Ende nw_xdata_retag */
