juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

srv.c (38624B)


      1 /* srv.c - servidor de partidas: sockets, partidas persistentes, bots. El juego
      2  * en si lo pone un GameModule (ver srv.h). */
      3 #ifdef _WIN32
      4 #define _CRT_RAND_S                  /* rand_s */
      5 #endif
      6 #include <dirent.h>
      7 #include <errno.h>
      8 #include <fcntl.h>
      9 #include <signal.h>
     10 #include <stdarg.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #ifndef _WIN32
     15 #include <strings.h>
     16 #endif
     17 #include <sys/stat.h>
     18 #include <sys/time.h>
     19 #include <time.h>
     20 #include <unistd.h>
     21 #include "plat.h"
     22 #ifndef _WIN32
     23 #include <arpa/inet.h>
     24 #include <netinet/in.h>
     25 #endif
     26 #include "srv.h"
     27 #ifndef SRV_NO_WS
     28 #include "ws.h"      /* clientes WebSocket (la version web): mismo protocolo, envuelto en frames */
     29 #endif
     30 
     31 typedef struct {
     32     Server *srv;
     33     char    code[8];
     34     int     np;
     35     char    kind[SRV_MAX_SEATS];     /* H libre, P humano, B bot, N neutral, - no existe */
     36     char    token[SRV_MAX_SEATS][33];
     37     char    name[SRV_MAX_SEATS][20];
     38     uint64_t seed;
     39     char    opts[SRV_LINE];
     40     char  **hist;                    /* movidas resueltas, sin redactar */
     41     int     nhist, caphist;
     42     FILE   *log;
     43     char  **chat;                    /* "<de> <para|-1> <texto>" */
     44     int     nchat, capchat;
     45     FILE   *chatf;
     46     bool    loading;
     47     char    again[8];                /* la partida "otra como esta" ya creada (AGAIN), para no crear dos */
     48     void   *st;                      /* estado del juego (GameModule.state_size) */
     49 } SGame;
     50 
     51 /* Limites para que un cliente no pueda llenar la memoria o el disco del VPS. */
     52 #define SRV_MAX_GAMES      5000
     53 #define SRV_MAX_MOVES      20000     /* movidas por partida */
     54 #define SRV_MAX_CHAT       3000      /* lineas de chat por partida */
     55 #define SRV_CHAT_REPLAY    SRV_MAX_CHAT   /* al entrar se manda todo el chat que podes ver */
     56 #define SRV_CHAT_LEN       200
     57 #define SRV_CREATES_CONN   10        /* partidas nuevas por conexion */
     58 #define SRV_CREATES_MIN    60        /* partidas nuevas por minuto (en total) */
     59 #define SRV_HELLO_MS       10000     /* tiempo para saludar */
     60 #define SRV_IDLE_MS        600000    /* sin trafico: se corta (el cliente manda PING cada 30 s) */
     61 
     62 typedef struct {
     63     int  fd;
     64     char in[4096];
     65     int  inlen;
     66     bool hello;
     67     char name[20];
     68     char key[33];                /* AUTH: clave del asiento (hash de nombre + contrasena) */
     69     int  game, seat;
     70     int  creates;
     71     long long since, last, last_say;
     72 #ifndef SRV_NO_WS
     73     uint8_t ws;                  /* 0 TCP, 1 esperando el handshake, 2 WebSocket */
     74     uint8_t wsin[4096];          /* frames crudos; el payload pasa a in */
     75     int  wslen;
     76 #endif
     77 } Client;
     78 
     79 struct Server {
     80     const GameModule *gm;
     81     int     lfd, port;
     82     Client  cl[SRV_MAX_CLIENTS];
     83     SGame **games;
     84     int     ngames, cap;
     85     char    dir[512];
     86     char    admin[33];           /* secreto de <dir>/admin.key para el comando ADMIN */
     87     bool    quiet;
     88     long long create_window;
     89     int     creates_in_window;
     90 };
     91 
     92 long long srv_now_ms(void)
     93 {
     94 #ifdef _WIN32
     95     return (long long)GetTickCount64();
     96 #else
     97     struct timespec ts;
     98     clock_gettime(CLOCK_MONOTONIC, &ts);
     99     return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
    100 #endif
    101 }
    102 
    103 static void logf_(Server *s, const char *fmt, ...)
    104 {
    105     if (s->quiet) return;
    106     char t[32];
    107     time_t now = time(0);
    108     strftime(t, sizeof t, "%Y-%m-%d %H:%M:%S", localtime(&now));
    109     fprintf(stderr, "[%s] ", t);
    110     va_list ap;
    111     va_start(ap, fmt);
    112     vfprintf(stderr, fmt, ap);
    113     va_end(ap);
    114     fputc('\n', stderr);
    115 }
    116 
    117 static void random_bytes(void *buf, size_t n)
    118 {
    119 #ifdef _WIN32
    120     for (size_t i = 0; i < n; i++) {
    121         unsigned v;
    122         if (rand_s(&v)) v = (unsigned)rand();
    123         ((uint8_t *)buf)[i] = (uint8_t)v;
    124     }
    125 #else
    126     int fd = open("/dev/urandom", O_RDONLY);
    127     if (fd >= 0) {
    128         ssize_t r = read(fd, buf, n);
    129         close(fd);
    130         if (r == (ssize_t)n) return;
    131     }
    132     struct timeval tv;
    133     gettimeofday(&tv, 0);
    134     srand((unsigned)(tv.tv_usec ^ tv.tv_sec ^ getpid()));
    135     for (size_t i = 0; i < n; i++) ((uint8_t *)buf)[i] = (uint8_t)rand();
    136 #endif
    137 }
    138 
    139 static void new_token(char out[33])
    140 {
    141     uint8_t b[16];
    142     random_bytes(b, sizeof b);
    143     for (int i = 0; i < 16; i++) snprintf(out + 2 * i, 3, "%02x", b[i]);
    144 }
    145 
    146 /* ------------------------------------------------------------------ envio */
    147 
    148 static void drop(Server *s, Client *c);
    149 
    150 static void send_all(Server *s, Client *c, const void *data, int n)
    151 {
    152     const char *p = data;
    153     int off = 0;
    154     while (off < n) {
    155         ssize_t w = send(c->fd, p + off, n - off, MSG_NOSIGNAL);
    156         if (w <= 0) { drop(s, c); return; }
    157         off += (int)w;
    158     }
    159 }
    160 
    161 static void sendf(Server *s, Client *c, const char *fmt, ...)
    162 {
    163     if (c->fd < 0) return;
    164     char buf[SRV_LINE + 64];
    165     va_list ap;
    166     va_start(ap, fmt);
    167     int n = vsnprintf(buf, sizeof buf - 1, fmt, ap);
    168     va_end(ap);
    169     if (n < 0) return;
    170     if (n > (int)sizeof buf - 2) n = (int)sizeof buf - 2;
    171     buf[n++] = '\n';
    172 #ifndef SRV_NO_WS
    173     if (c->ws == 2) {
    174         uint8_t fr[sizeof buf + 10];
    175         send_all(s, c, fr, ws_wrap(WS_BINARY, buf, n, fr, sizeof fr));
    176         return;
    177     }
    178 #endif
    179     send_all(s, c, buf, n);
    180 }
    181 
    182 static bool seat_online(Server *s, SGame *g, int seat)
    183 {
    184     for (int i = 0; i < SRV_MAX_CLIENTS; i++)
    185         if (s->cl[i].fd >= 0 && s->cl[i].game >= 0 && s->games[s->cl[i].game] == g && s->cl[i].seat == seat) return true;
    186     return false;
    187 }
    188 
    189 static void send_seat(Server *s, Client *c, SGame *g, int i)
    190 {
    191     int on = g->kind[i] == 'B' || g->kind[i] == 'N' || seat_online(s, g, i);
    192     sendf(s, c, "SEAT %d %c %d %s", i, g->kind[i], on, g->name[i][0] ? g->name[i] : "-");
    193 }
    194 
    195 static void broadcast_seat(Server *s, SGame *g, int i)
    196 {
    197     for (int k = 0; k < SRV_MAX_CLIENTS; k++) {
    198         Client *c = &s->cl[k];
    199         if (c->fd >= 0 && c->game >= 0 && s->games[c->game] == g) send_seat(s, c, g, i);
    200     }
    201 }
    202 
    203 static void drop(Server *s, Client *c)
    204 {
    205     if (c->fd < 0) return;
    206     sock_close(c->fd);
    207     c->fd = -1;
    208     int gi = c->game;
    209     c->game = -1;
    210     c->inlen = 0;
    211     if (gi >= 0) broadcast_seat(s, s->games[gi], c->seat);
    212 }
    213 
    214 /* ------------------------------------------------------------ persistencia */
    215 
    216 static void game_dir(Server *s, SGame *g, char *out, size_t n) { snprintf(out, n, "%s/%s", s->dir, g->code); }
    217 
    218 static void save_meta(Server *s, SGame *g)
    219 {
    220     if (!s->dir[0]) return;
    221     char dir[600], tmp[700], path[700];
    222     game_dir(s, g, dir, sizeof dir);
    223     mkdir(dir, 0700);
    224     snprintf(tmp, sizeof tmp, "%s/meta.tmp", dir);
    225     snprintf(path, sizeof path, "%s/meta", dir);
    226     FILE *f = fopen(tmp, "w");
    227     if (!f) return;
    228     fchmod(fileno(f), 0600);
    229     fprintf(f, "%s\nSEED %llu\nOPTS %s\n", s->gm->magic, (unsigned long long)g->seed, g->opts);
    230     for (int i = 0; i < g->np; i++)
    231         fprintf(f, "SEAT %d %c %s %s\n", i, g->kind[i], g->token[i][0] ? g->token[i] : "-", g->name[i][0] ? g->name[i] : "-");
    232     fclose(f);
    233     rename(tmp, path);
    234 }
    235 
    236 static void open_log(Server *s, SGame *g)
    237 {
    238     if (!s->dir[0]) return;
    239     char dir[600], path[700];
    240     game_dir(s, g, dir, sizeof dir);
    241     mkdir(dir, 0700);
    242     snprintf(path, sizeof path, "%s/log", dir);
    243     g->log = fopen(path, "a");
    244     snprintf(path, sizeof path, "%s/chat", dir);
    245     g->chatf = fopen(path, "a");
    246 }
    247 
    248 /* ------------------------------------------------------------ chat */
    249 
    250 static bool chat_add(SGame *g, const char *line)
    251 {
    252     if (g->nchat >= SRV_MAX_CHAT) return false;
    253     if (g->nchat == g->capchat) {
    254         int nc = g->capchat ? g->capchat * 2 : 64;
    255         char **c = realloc(g->chat, sizeof(char *) * (size_t)nc);
    256         if (!c) return false;
    257         g->chat = c;
    258         g->capchat = nc;
    259     }
    260     char *copy = strdup(line);
    261     if (!copy) return false;
    262     g->chat[g->nchat++] = copy;
    263     return true;
    264 }
    265 
    266 /* el privado (para >= 0) lo ven solo quien lo manda y quien lo recibe */
    267 static bool chat_visible(const char *line, int seat)
    268 {
    269     int from, to;
    270     if (sscanf(line, "%d %d", &from, &to) != 2) return false;
    271     return to < 0 || to == seat || from == seat;
    272 }
    273 
    274 static void load_chat(Server *s, SGame *g)
    275 {
    276     char path[700], line[SRV_LINE];
    277     snprintf(path, sizeof path, "%s/%s/chat", s->dir, g->code);
    278     FILE *f = fopen(path, "r");
    279     if (!f) return;
    280     while (fgets(line, sizeof line, f)) {
    281         line[strcspn(line, "\r\n")] = 0;
    282         if (line[0]) chat_add(g, line);
    283     }
    284     fclose(f);
    285 }
    286 
    287 /* ------------------------------------------------------------ exportar */
    288 
    289 static void put_line(void *ctx, const char *line) { fputs(line, ctx); fputc('\n', ctx); }
    290 
    291 /* partida.txt: lo que exporte el juego, mas el chat (todo, tambien los privados: es el
    292  * archivo del servidor) */
    293 static void write_export(Server *s, SGame *g)
    294 {
    295     if (!s->dir[0] || !s->gm->export) return;
    296     char dir[600], tmp[700], path[700];
    297     game_dir(s, g, dir, sizeof dir);
    298     snprintf(tmp, sizeof tmp, "%s/partida.tmp", dir);
    299     snprintf(path, sizeof path, "%s/partida.txt", dir);
    300     FILE *f = fopen(tmp, "w");
    301     if (!f) return;
    302     s->gm->export(g->st, g->code, (const char *const *)g->hist, g->nhist, g->name, g->kind, put_line, f);
    303     if (g->nchat) {
    304         fprintf(f, "\n## Chat\n");
    305         for (int i = 0; i < g->nchat; i++) {
    306             int from, to, used = 0;
    307             if (sscanf(g->chat[i], "%d %d %n", &from, &to, &used) < 2) continue;
    308             const char *fn = from >= 0 && from < g->np && g->name[from][0] ? g->name[from] : "?";
    309             if (to >= 0 && to < g->np) fprintf(f, "%s -> %s: %s\n", fn, g->name[to][0] ? g->name[to] : "?", g->chat[i] + used);
    310             else fprintf(f, "%s: %s\n", fn, g->chat[i] + used);
    311         }
    312     }
    313     fclose(f);
    314     rename(tmp, path);
    315 }
    316 
    317 /* ------------------------------------------------------------ partidas */
    318 
    319 static void on_move(void *ctx, const char *move, bool is_auto)
    320 {
    321     SGame *g = ctx;
    322     Server *s = g->srv;
    323     if (g->nhist == g->caphist) {
    324         int nc = g->caphist ? g->caphist * 2 : 256;
    325         char **h = realloc(g->hist, sizeof(char *) * (size_t)nc);
    326         if (!h) return;
    327         g->hist = h;
    328         g->caphist = nc;
    329     }
    330     char *copy = strdup(move);
    331     if (!copy) return;
    332     g->hist[g->nhist++] = copy;
    333     if (g->loading) return;
    334     if (g->log && !is_auto) { fprintf(g->log, "%s\n", move); fflush(g->log); }
    335     write_export(s, g);
    336     char line[SRV_LINE];
    337     for (int k = 0; k < SRV_MAX_CLIENTS; k++) {
    338         Client *c = &s->cl[k];
    339         if (c->fd < 0 || c->game < 0 || s->games[c->game] != g) continue;
    340         s->gm->redact(move, c->seat, line, sizeof line);
    341         sendf(s, c, "EV %s", line);
    342     }
    343 }
    344 
    345 static SGame *add_game(Server *s)
    346 {
    347     if (s->ngames == s->cap) {
    348         int nc = s->cap ? s->cap * 2 : 16;
    349         SGame **n = realloc(s->games, sizeof(SGame *) * (size_t)nc);
    350         if (!n) return 0;
    351         s->games = n;
    352         s->cap = nc;
    353     }
    354     SGame *g = calloc(1, sizeof *g);
    355     if (!g) return 0;
    356     g->st = calloc(1, s->gm->state_size);
    357     if (!g->st) { free(g); return 0; }
    358     g->srv = s;
    359     s->games[s->ngames++] = g;
    360     return g;
    361 }
    362 
    363 static void free_game(SGame *g)
    364 {
    365     if (g->log) fclose(g->log);
    366     if (g->chatf) fclose(g->chatf);
    367     for (int i = 0; i < g->nchat; i++) free(g->chat[i]);
    368     free(g->chat);
    369     for (int i = 0; i < g->nhist; i++) free(g->hist[i]);
    370     free(g->hist);
    371     free(g->st);
    372     free(g);
    373 }
    374 
    375 static int find_game(Server *s, const char *code)
    376 {
    377     for (int i = 0; i < s->ngames; i++)
    378         if (!strcmp(s->games[i]->code, code)) return i;
    379     return -1;
    380 }
    381 
    382 static bool start_game(Server *s, SGame *g)
    383 {
    384     g->loading = true;
    385     if (!s->gm->start(g->st, g->opts, g->seed, g->kind, g->name, on_move, g)) return false;
    386     /* el juego puede sumar asientos propios (ej. los neutrales de Catan para 2) */
    387     for (int i = 0; i < SRV_MAX_SEATS; i++) if (g->kind[i] != '-') g->np = i + 1;
    388     return true;
    389 }
    390 
    391 static void load_game(Server *s, const char *code)
    392 {
    393     char path[700], line[SRV_LINE + 64];
    394     snprintf(path, sizeof path, "%s/%s/meta", s->dir, code);
    395     FILE *f = fopen(path, "r");
    396     if (!f) return;
    397     SGame tmp;
    398     memset(&tmp, 0, sizeof tmp);
    399     bool magic = false, opts = false;
    400     for (int i = 0; i < SRV_MAX_SEATS; i++) tmp.kind[i] = '-';
    401     while (fgets(line, sizeof line, f)) {
    402         line[strcspn(line, "\r\n")] = 0;
    403         unsigned long long seed;
    404         int i;
    405         char k, tok[40], name[64];
    406         if (!strcmp(line, s->gm->magic)) magic = true;
    407         else if (sscanf(line, "SEED %llu", &seed) == 1) tmp.seed = seed;
    408         else if (!strncmp(line, "OPTS ", 5)) { snprintf(tmp.opts, sizeof tmp.opts, "%.*s", (int)sizeof tmp.opts - 1, line + 5); opts = true; }
    409         else if (sscanf(line, "SEAT %d %c %39s %63[^\n]", &i, &k, tok, name) == 4 && i >= 0 && i < SRV_MAX_SEATS) {
    410             tmp.kind[i] = k;
    411             snprintf(tmp.token[i], 33, "%.32s", strcmp(tok, "-") ? tok : "");
    412             snprintf(tmp.name[i], 20, "%.19s", strcmp(name, "-") ? name : "");
    413             if (i + 1 > tmp.np) tmp.np = i + 1;
    414         }
    415     }
    416     fclose(f);
    417     if (!magic || !opts) return;
    418     SGame *g = add_game(s);
    419     if (!g) return;
    420     snprintf(g->code, sizeof g->code, "%s", code);
    421     g->seed = tmp.seed;
    422     g->np = tmp.np;
    423     memcpy(g->opts, tmp.opts, sizeof g->opts);
    424     memcpy(g->kind, tmp.kind, sizeof g->kind);
    425     memcpy(g->token, tmp.token, sizeof g->token);
    426     memcpy(g->name, tmp.name, sizeof g->name);
    427     if (!start_game(s, g)) {
    428         logf_(s, "partida %s: opciones invalidas", code);
    429         s->ngames--;
    430         free_game(g);
    431         return;
    432     }
    433     snprintf(path, sizeof path, "%s/%s/log", s->dir, code);
    434     f = fopen(path, "r");
    435     int n = 0;
    436     if (f) {
    437         while (fgets(line, sizeof line, f)) {
    438             line[strcspn(line, "\r\n")] = 0;
    439             if (!line[0]) continue;
    440             if (s->gm->replay(g->st, line) != 0) { logf_(s, "partida %s: movida %d no reproduce: %s", code, n, line); break; }
    441             n++;
    442         }
    443         fclose(f);
    444     }
    445     g->loading = false;
    446     load_chat(s, g);
    447     open_log(s, g);
    448     write_export(s, g);
    449 }
    450 
    451 static void load_all(Server *s)
    452 {
    453     DIR *d = opendir(s->dir);
    454     if (!d) return;
    455     struct dirent *e;
    456     while ((e = readdir(d)))
    457         if (e->d_name[0] != '.' && strlen(e->d_name) < 8) load_game(s, e->d_name);
    458     closedir(d);
    459     logf_(s, "%d partidas cargadas de %s", s->ngames, s->dir);
    460 }
    461 
    462 /* ------------------------------------------------------------ comandos */
    463 
    464 static void attach(Server *s, Client *c, int gi, int seat)
    465 {
    466     SGame *g = s->games[gi];
    467     c->game = gi;
    468     c->seat = seat;
    469     char line[SRV_LINE];
    470     sendf(s, c, "GAME %s %d %s", g->code, seat, g->token[seat]);
    471     s->gm->header(g->st, line, sizeof line);
    472     sendf(s, c, "HDR %s", line);
    473     for (int i = 0; i < g->np; i++) send_seat(s, c, g, i);
    474     for (int i = 0; i < g->nhist && c->fd >= 0; i++) {
    475         s->gm->redact(g->hist[i], seat, line, sizeof line);
    476         sendf(s, c, "EV %s", line);
    477     }
    478     int shown = 0, first = g->nchat;
    479     while (first > 0 && shown < SRV_CHAT_REPLAY) if (chat_visible(g->chat[--first], seat)) shown++;
    480     for (int i = first; i < g->nchat && c->fd >= 0; i++)
    481         if (chat_visible(g->chat[i], seat)) sendf(s, c, "CHAT %s", g->chat[i]);
    482     sendf(s, c, "SYNC");
    483     broadcast_seat(s, g, seat);
    484 }
    485 
    486 static void detach(Server *s, Client *c)
    487 {
    488     int gi = c->game;
    489     c->game = -1;
    490     if (gi >= 0) broadcast_seat(s, s->games[gi], c->seat);
    491 }
    492 
    493 static void clean_name(const char *in, char *out, int n)
    494 {
    495     int k = 0;
    496     while (*in == ' ') in++;
    497     for (; *in && k < n - 1; in++) {
    498         unsigned char ch = (unsigned char)*in;
    499         if (ch < 0x20 || ch == '|' || ch == 0x7F) continue;
    500         out[k++] = (char)ch;
    501     }
    502     while (k > 0 && ((unsigned char)out[k - 1] & 0xC0) == 0xC0) k--;   /* no cortar utf-8 */
    503     while (k > 0 && out[k - 1] == ' ') k--;
    504     out[k] = 0;
    505     if (!k) snprintf(out, (size_t)n, "?");
    506 }
    507 
    508 /* Arma la partida (opciones y tipo de asiento ya validados) y sienta al creador en el 0.
    509  * Con like (AGAIN): la gente de esa partida conserva su asiento y su llave (les aparece en
    510  * "mis partidas"), y el que pidio queda en el asiento myseat. */
    511 static void create_game(Server *s, Client *c, const char *opts, const char *seats, int np, const SGame *like, int myseat)
    512 {
    513     const GameModule *gm = s->gm;
    514     long long now = srv_now_ms();
    515     if (now - s->create_window > 60000) { s->create_window = now; s->creates_in_window = 0; }
    516     if (s->ngames >= SRV_MAX_GAMES || c->creates >= SRV_CREATES_CONN || s->creates_in_window >= SRV_CREATES_MIN) {
    517         sendf(s, c, "ERR %d server busy, try later", gm->err_args);
    518         return;
    519     }
    520     c->creates++;
    521     s->creates_in_window++;
    522     if (c->game >= 0) detach(s, c);
    523     SGame *g = add_game(s);
    524     if (like) like = s->games[find_game(s, like->code)];   /* add_game puede mover el arreglo */
    525     if (!g) { sendf(s, c, "ERR %d out of memory", gm->err_args); return; }
    526     static const char AL[] = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
    527     do {
    528         uint8_t b[6];
    529         random_bytes(b, sizeof b);
    530         for (int i = 0; i < 6; i++) g->code[i] = AL[b[i] % 32];
    531         g->code[6] = 0;
    532     } while (find_game(s, g->code) != s->ngames - 1);
    533     random_bytes(&g->seed, sizeof g->seed);
    534     snprintf(g->opts, sizeof g->opts, "%s", opts);
    535     g->np = np;
    536     for (int i = 0; i < SRV_MAX_SEATS; i++) g->kind[i] = '-';
    537     for (int i = 0; i < np; i++) {
    538         g->kind[i] = seats[i];
    539         if (seats[i] == 'B') snprintf(g->name[i], sizeof g->name[i], "Bot %d", i + 1);
    540     }
    541     int seat = 0;
    542     if (like) {
    543         for (int i = 0; i < np; i++)
    544             if (like->kind[i] == 'P') {
    545                 g->kind[i] = 'P';
    546                 memcpy(g->token[i], like->token[i], 33);
    547                 snprintf(g->name[i], sizeof g->name[i], "%s", like->name[i]);
    548             }
    549         seat = myseat >= 0 && myseat < np && g->kind[myseat] == 'P' ? myseat : 0;
    550     }
    551     if (g->kind[seat] != 'P' || !like) {
    552         g->kind[seat] = 'P';
    553         if (c->key[0]) memcpy(g->token[seat], c->key, 33);
    554         else new_token(g->token[seat]);
    555         snprintf(g->name[seat], sizeof g->name[seat], "%s", c->name);
    556     }
    557     if (!start_game(s, g)) {
    558         s->ngames--;
    559         free_game(g);
    560         sendf(s, c, "ERR %d bad create", gm->err_args);
    561         return;
    562     }
    563     g->loading = false;
    564     save_meta(s, g);
    565     open_log(s, g);         /* tambien abre el chat */
    566     write_export(s, g);
    567     char sum[128] = "";
    568     if (gm->summary) gm->summary(g->st, sum, sizeof sum);
    569     logf_(s, "partida %s creada por %s (%s)%s", g->code, c->name, sum, like ? " [otra como la anterior]" : "");
    570     attach(s, c, s->ngames - 1, seat);
    571 }
    572 
    573 static void cmd_create(Server *s, Client *c, const char *args)
    574 {
    575     const GameModule *gm = s->gm;
    576     char opts[SRV_LINE], seats[SRV_MAX_SEATS + 1] = "";
    577     int np = gm->create(args, opts, sizeof opts, seats);
    578     if (np < 1 || np > SRV_MAX_SEATS || seats[0] != 'H') { sendf(s, c, "ERR %d bad create", gm->err_args); return; }
    579     for (int i = 0; i < np; i++)
    580         if (seats[i] != 'H' && seats[i] != 'B') { sendf(s, c, "ERR %d bad seats", gm->err_args); return; }
    581     create_game(s, c, opts, seats, np, 0, 0);
    582 }
    583 
    584 /* AGAIN: otra partida como la que estoy jugando: mismas opciones, bots donde habia bots,
    585  * la misma gente en los mismos asientos (con su llave: les aparece en "mis partidas"),
    586  * los asientos que estaban libres siguen libres, y otro reparto. */
    587 static void cmd_again(Server *s, Client *c)
    588 {
    589     const GameModule *gm = s->gm;
    590     if (c->game < 0) { sendf(s, c, "ERR %d not in a game", gm->err_args); return; }
    591     SGame *old = s->games[c->game];
    592     /* si otro de la mesa ya pidio "otra", es esa: lo mando a su asiento en vez de crear una mas */
    593     if (old->again[0]) {
    594         int gi = find_game(s, old->again);
    595         if (gi >= 0 && !gm->is_over(s->games[gi]->st)) {
    596             SGame *g = s->games[gi];
    597             for (int k = 0; k < g->np; k++)
    598                 if (g->kind[k] == 'P' && !strcmp(g->token[k], old->token[c->seat])) { detach(s, c); attach(s, c, gi, k); return; }
    599         }
    600     }
    601     char opts[SRV_LINE], seats[SRV_MAX_SEATS + 1] = "";
    602     if (gm->again) gm->again(old->opts, opts, sizeof opts);
    603     else snprintf(opts, sizeof opts, "%s", old->opts);
    604     int np = old->np, myseat = c->seat;
    605     for (int i = 0; i < np; i++) seats[i] = old->kind[i] == 'B' ? 'B' : old->kind[i] == 'N' ? 'N' : 'H';
    606     seats[np] = 0;
    607     char oldcode[8];
    608     snprintf(oldcode, sizeof oldcode, "%s", old->code);
    609     create_game(s, c, opts, seats, np, old, myseat);
    610     int oi = find_game(s, oldcode);          /* create_game puede mover el arreglo de partidas */
    611     if (oi >= 0 && c->game >= 0) snprintf(s->games[oi]->again, sizeof s->games[oi]->again, "%s", s->games[c->game]->code);
    612 }
    613 
    614 static void cmd_join(Server *s, Client *c, const char *code)
    615 {
    616     int gi = -1;
    617     if (!strcmp(code, "-")) {
    618         for (int i = s->ngames - 1; i >= 0 && gi < 0; i--) {
    619             if (s->gm->is_over(s->games[i]->st)) continue;
    620             for (int k = 0; k < s->games[i]->np; k++) if (s->games[i]->kind[k] == 'H') { gi = i; break; }
    621         }
    622     } else gi = find_game(s, code);
    623     if (gi < 0) { sendf(s, c, "ERR %d no such game", s->gm->err_args); return; }
    624     SGame *g = s->games[gi];
    625     /* con AUTH, el mismo nombre vuelve a su asiento si la clave coincide */
    626     if (c->key[0])
    627         for (int k = 0; k < g->np; k++)
    628             if (g->kind[k] == 'P' && !strcasecmp(g->name[k], c->name)) {
    629                 if (strcmp(g->token[k], c->key)) { sendf(s, c, "ERR %d name taken or wrong password", s->gm->err_args); return; }
    630                 if (c->game >= 0) detach(s, c);
    631                 logf_(s, "%s volvio a %s (asiento %d)", c->name, g->code, k);
    632                 attach(s, c, gi, k);
    633                 return;
    634             }
    635     int seat = -1;
    636     for (int k = 0; k < g->np; k++) if (g->kind[k] == 'H') { seat = k; break; }
    637     if (seat < 0) { sendf(s, c, "ERR %d game is full", s->gm->err_args); return; }
    638     if (c->game >= 0) detach(s, c);
    639     g->kind[seat] = 'P';
    640     if (c->key[0]) memcpy(g->token[seat], c->key, 33);
    641     else new_token(g->token[seat]);
    642     snprintf(g->name[seat], sizeof g->name[seat], "%s", c->name);
    643     save_meta(s, g);
    644     write_export(s, g);     /* el nombre nuevo */
    645     logf_(s, "%s se unio a %s (asiento %d)", c->name, g->code, seat);
    646     attach(s, c, gi, seat);
    647     broadcast_seat(s, g, seat);
    648 }
    649 
    650 static int find_seat(Server *s, const char *code, const char *token, int *seat)
    651 {
    652     int gi = find_game(s, code);
    653     if (gi < 0 || strlen(token) != 32) return -1;
    654     for (int k = 0; k < s->games[gi]->np; k++)
    655         if (s->games[gi]->kind[k] == 'P' && !strcmp(s->games[gi]->token[k], token)) { *seat = k; return gi; }
    656     return -1;
    657 }
    658 
    659 static void cmd_open(Server *s, Client *c, const char *args)
    660 {
    661     char code[16], token[40];
    662     int seat;
    663     if (sscanf(args, "%15s %39s", code, token) != 2) { sendf(s, c, "ERR %d bad open", s->gm->err_args); return; }
    664     int gi = find_seat(s, code, token, &seat);
    665     if (gi < 0) { sendf(s, c, "ERR %d no such game or bad token", s->gm->err_args); return; }
    666     if (c->game >= 0) detach(s, c);
    667     attach(s, c, gi, seat);
    668 }
    669 
    670 static void send_stat(Server *s, Client *c, SGame *g, int seat)
    671 {
    672     char names[200] = "", st[128] = "";
    673     for (int i = 0; i < g->np; i++) {
    674         if (i) strncat(names, "|", sizeof names - strlen(names) - 1);
    675         strncat(names, g->name[i][0] ? g->name[i] : "-", sizeof names - strlen(names) - 1);
    676     }
    677     s->gm->status(g->st, seat, st, sizeof st);
    678     sendf(s, c, "STAT %s %d %s %d %s", g->code, seat, st, g->np, names);
    679 }
    680 
    681 static void cmd_status(Server *s, Client *c, const char *args)
    682 {
    683     char code[16], token[40];
    684     int used;
    685     while (sscanf(args, "%15s %39s%n", code, token, &used) == 2) {
    686         args += used;
    687         int seat;
    688         int gi = find_seat(s, code, token, &seat);
    689         if (gi >= 0) send_stat(s, c, s->games[gi], seat);
    690     }
    691     sendf(s, c, "STATEND");
    692 }
    693 
    694 /* MINE: todas las partidas donde tu clave (AUTH) tiene asiento, desde cualquier compu */
    695 static void cmd_mine(Server *s, Client *c)
    696 {
    697     int sent = 0;
    698     for (int i = s->ngames - 1; i >= 0 && c->key[0] && sent < 50; i--) {
    699         SGame *g = s->games[i];
    700         for (int k = 0; k < g->np; k++)
    701             if (g->kind[k] == 'P' && !strcmp(g->token[k], c->key)) { send_stat(s, c, g, k); sent++; break; }
    702     }
    703     sendf(s, c, "STATEND");
    704 }
    705 
    706 static void cmd_move(Server *s, Client *c, const char *args)
    707 {
    708     if (c->game < 0) { sendf(s, c, "ERR %d not in a game", s->gm->err_turn); return; }
    709     SGame *g = s->games[c->game];
    710     if (g->nhist >= SRV_MAX_MOVES) { sendf(s, c, "ERR %d game too long", s->gm->err_args); return; }
    711     int e = s->gm->submit(g->st, c->seat, args);
    712     if (e) sendf(s, c, "ERR %d rejected", e);
    713 }
    714 
    715 /* SAY <para|-1> <texto>: chat de la partida (para >= 0: privado a ese asiento) */
    716 static void cmd_say(Server *s, Client *c, const char *args)
    717 {
    718     if (c->game < 0) { sendf(s, c, "ERR %d not in a game", s->gm->err_turn); return; }
    719     SGame *g = s->games[c->game];
    720     int to, used = 0;
    721     if (sscanf(args, "%d %n", &to, &used) < 1 || to < -1 || to >= g->np || to == c->seat) {
    722         sendf(s, c, "ERR %d bad say", s->gm->err_args);
    723         return;
    724     }
    725     long long now = srv_now_ms();
    726     if (now - c->last_say < 300) return;                 /* nada de inundar */
    727     c->last_say = now;
    728     char text[SRV_CHAT_LEN + 1];
    729     clean_name(args + used, text, sizeof text);
    730     if (!strcmp(text, "?") && strcmp(args + used, "?")) return;   /* vacio */
    731     char line[SRV_LINE];
    732     snprintf(line, sizeof line, "%d %d %s", c->seat, to, text);
    733     if (!chat_add(g, line)) { sendf(s, c, "ERR %d chat full", s->gm->err_args); return; }
    734     if (g->chatf) { fprintf(g->chatf, "%s\n", line); fflush(g->chatf); }
    735     write_export(s, g);
    736     for (int k = 0; k < SRV_MAX_CLIENTS; k++) {
    737         Client *o = &s->cl[k];
    738         if (o->fd >= 0 && o->game >= 0 && s->games[o->game] == g && chat_visible(line, o->seat)) sendf(s, o, "CHAT %s", line);
    739     }
    740 }
    741 
    742 static bool is_key(const char *k)
    743 {
    744     size_t n = strlen(k);
    745     if (n != 32) return false;
    746     for (size_t i = 0; i < n; i++)
    747         if (!((k[i] >= '0' && k[i] <= '9') || (k[i] >= 'a' && k[i] <= 'f'))) return false;
    748     return true;
    749 }
    750 
    751 /* ADMIN <secreto> RESET <codigo> <clave> <nombre>: cambia la clave del asiento humano
    752  * con ese nombre (para quien se olvido la contrasena; el nombre va al final porque
    753  * puede tener espacios); lo corre el administrador del VPS con <juego>-admin
    754  * (comun/deploy/admin.sh). El secreto esta en <dir>/admin.key. */
    755 static void cmd_admin(Server *s, Client *c, const char *args)
    756 {
    757     int ea = s->gm->err_args;
    758     char secret[40], what[16], code[16], name[20], key[40];
    759     int used = 0;
    760     int n = sscanf(args, "%39s %15s %15s %39s %n", secret, what, code, key, &used);
    761     if (n < 1 || !s->admin[0] || strcmp(secret, s->admin)) { sendf(s, c, "ERR %d admin", ea); drop(s, c); return; }
    762     if (n != 4 || strcmp(what, "RESET") || !is_key(key) || !args[used]) { sendf(s, c, "ERR %d uso: ADMIN <secreto> RESET <codigo> <clave> <nombre>", ea); return; }
    763     clean_name(args + used, name, sizeof name);
    764     int gi = find_game(s, code);
    765     if (gi < 0) { sendf(s, c, "ERR %d no such game", ea); return; }
    766     SGame *g = s->games[gi];
    767     for (int k = 0; k < g->np; k++) {
    768         if (g->kind[k] != 'P' || strcasecmp(g->name[k], name)) continue;
    769         memcpy(g->token[k], key, 33);
    770         save_meta(s, g);
    771         for (int i = 0; i < SRV_MAX_CLIENTS; i++) {
    772             Client *o = &s->cl[i];
    773             if (o != c && o->fd >= 0 && o->game == gi && o->seat == k) drop(s, o);
    774         }
    775         logf_(s, "%s: clave del asiento %d (%s) cambiada por el administrador", g->code, k, g->name[k]);
    776         sendf(s, c, "OK %s %d %s", g->code, k, g->name[k]);
    777         return;
    778     }
    779     sendf(s, c, "ERR %d no human seat with that name", ea);
    780 }
    781 
    782 static void handle(Server *s, Client *c, char *line)
    783 {
    784     char *sp = strchr(line, ' ');
    785     const char *args = sp ? sp + 1 : "";
    786     if (sp) *sp = 0;
    787     int ea = s->gm->err_args;
    788     if (!c->hello) {
    789         if (strcmp(line, "HELLO")) { sendf(s, c, "ERR %d say HELLO first", ea); drop(s, c); return; }
    790         int ver = atoi(args);
    791         const char *nm = strchr(args, ' ');
    792         clean_name(nm ? nm + 1 : "?", c->name, sizeof c->name);
    793         if (ver != PROTO_VERSION) { sendf(s, c, "ERR %d protocol version %d required", ea, PROTO_VERSION); drop(s, c); return; }
    794         c->hello = true;
    795         sendf(s, c, "WELCOME %d", PROTO_VERSION);
    796         return;
    797     }
    798     if (!strcmp(line, "CREATE")) cmd_create(s, c, args);
    799     else if (!strcmp(line, "AGAIN")) cmd_again(s, c);
    800     else if (!strcmp(line, "JOIN")) cmd_join(s, c, args);
    801     else if (!strcmp(line, "OPEN")) cmd_open(s, c, args);
    802     else if (!strcmp(line, "STATUS")) cmd_status(s, c, args);
    803     else if (!strcmp(line, "MINE")) cmd_mine(s, c);
    804     else if (!strcmp(line, "SAY")) cmd_say(s, c, args);
    805     else if (!strcmp(line, "MOVE")) cmd_move(s, c, args);
    806     else if (!strcmp(line, "AUTH")) {
    807         if (!is_key(args)) { sendf(s, c, "ERR %d bad key", ea); return; }
    808         memcpy(c->key, args, 33);
    809     }
    810     else if (!strcmp(line, "ADMIN")) cmd_admin(s, c, args);
    811     else if (!strcmp(line, "LEAVE")) detach(s, c);
    812     else if (!strcmp(line, "PING")) sendf(s, c, "PONG");
    813     else sendf(s, c, "ERR %d unknown command", ea);
    814 }
    815 
    816 /* ------------------------------------------------------------ red */
    817 
    818 Server *srv_open(const GameModule *gm, int port, const char *datadir, bool quiet, char *err, int errlen)
    819 {
    820     Server *s = calloc(1, sizeof *s);
    821     if (!s) return 0;
    822     s->gm = gm;
    823     s->quiet = quiet;
    824     for (int i = 0; i < SRV_MAX_CLIENTS; i++) { s->cl[i].fd = -1; s->cl[i].game = -1; }
    825     sock_init();
    826     if (datadir && *datadir) {
    827         snprintf(s->dir, sizeof s->dir, "%s", datadir);
    828         mkdir(s->dir, 0700);
    829         /* secreto del comando ADMIN: se crea la primera vez, solo lo lee el usuario del servidor */
    830         char path[600];
    831         snprintf(path, sizeof path, "%s/admin.key", s->dir);
    832         FILE *f = fopen(path, "r");
    833         if (f) {
    834             if (fscanf(f, "%32s", s->admin) != 1 || !is_key(s->admin)) s->admin[0] = 0;
    835             fclose(f);
    836         }
    837         if (!s->admin[0]) {
    838             int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);   /* nace ya con 0600 */
    839             if (fd >= 0 && (f = fdopen(fd, "w"))) {
    840                 new_token(s->admin);
    841                 fprintf(f, "%s\n", s->admin);
    842                 fclose(f);
    843             } else if (fd >= 0) close(fd);
    844         }
    845     }
    846     s->lfd = (int)socket(AF_INET6, SOCK_STREAM, 0);
    847     bool v6 = s->lfd >= 0;
    848     if (!v6) s->lfd = (int)socket(AF_INET, SOCK_STREAM, 0);
    849     if (s->lfd < 0) { snprintf(err, (size_t)errlen, "socket: %s", sock_strerror(sock_err())); free(s); return 0; }
    850     int one = 1, zero = 0;
    851 #ifndef _WIN32   /* en Windows SO_REUSEADDR deja a otro robar el puerto */
    852     setsockopt(s->lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one);
    853 #endif
    854     (void)one;
    855     int r;
    856     if (v6) {
    857         setsockopt(s->lfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&zero, sizeof zero);
    858         struct sockaddr_in6 a;
    859         memset(&a, 0, sizeof a);
    860         a.sin6_family = AF_INET6;
    861         a.sin6_addr = in6addr_any;
    862         a.sin6_port = htons((uint16_t)port);
    863         r = bind(s->lfd, (struct sockaddr *)&a, sizeof a);
    864     } else {
    865         struct sockaddr_in a;
    866         memset(&a, 0, sizeof a);
    867         a.sin_family = AF_INET;
    868         a.sin_addr.s_addr = htonl(INADDR_ANY);
    869         a.sin_port = htons((uint16_t)port);
    870         r = bind(s->lfd, (struct sockaddr *)&a, sizeof a);
    871     }
    872     if (r < 0 || listen(s->lfd, 16) < 0) {
    873         snprintf(err, (size_t)errlen, "puerto %d: %s", port, sock_strerror(sock_err()));
    874         sock_close(s->lfd);
    875         free(s);
    876         return 0;
    877     }
    878     s->port = port;
    879     if (s->dir[0]) load_all(s);
    880     logf_(s, "escuchando en el puerto %d", port);
    881     return s;
    882 }
    883 
    884 int srv_port(Server *s) { return s->port; }
    885 
    886 int srv_fds(Server *s, int *fds, int max)
    887 {
    888     int n = 0;
    889     if (n < max) fds[n++] = s->lfd;
    890     for (int i = 0; i < SRV_MAX_CLIENTS && n < max; i++)
    891         if (s->cl[i].fd >= 0) fds[n++] = s->cl[i].fd;
    892     return n;
    893 }
    894 
    895 int srv_timeout(Server *s)
    896 {
    897     long long now = srv_now_ms(), best = 60000;
    898     for (int i = 0; i < s->ngames; i++) {
    899         long long t = s->gm->next_wake(s->games[i]->st);
    900         if (t < 0) continue;
    901         long long d = t - now;
    902         if (d < best) best = d < 0 ? 0 : d;
    903     }
    904     return (int)best;
    905 }
    906 
    907 static void accept_one(Server *s)
    908 {
    909     int fd = (int)accept(s->lfd, 0, 0);
    910     if (fd < 0) return;
    911     for (int i = 0; i < SRV_MAX_CLIENTS; i++)
    912         if (s->cl[i].fd < 0) {
    913             Client *c = &s->cl[i];
    914             memset(c, 0, sizeof *c);
    915             c->fd = fd;
    916             c->game = -1;
    917             c->since = c->last = srv_now_ms();
    918 #ifdef _WIN32
    919             DWORD tv = 3000;
    920 #else
    921             struct timeval tv = { 3, 0 };
    922 #endif
    923             setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (void *)&tv, sizeof tv);
    924             return;
    925         }
    926     sock_close(fd);
    927 }
    928 
    929 /* corta en lineas lo que haya en in y las despacha */
    930 static void feed_lines(Server *s, Client *c)
    931 {
    932     c->in[c->inlen] = 0;
    933     char *start = c->in, *nl;
    934     while (c->fd >= 0 && (nl = memchr(start, '\n', (size_t)(c->in + c->inlen - start)))) {
    935         *nl = 0;
    936         if (nl > start && nl[-1] == '\r') nl[-1] = 0;
    937         if (nl - start < SRV_LINE) handle(s, c, start);
    938         start = nl + 1;
    939     }
    940     if (c->fd < 0) return;
    941     c->inlen -= (int)(start - c->in);
    942     memmove(c->in, start, (size_t)c->inlen);
    943     if (c->inlen >= SRV_LINE) drop(s, c);
    944 }
    945 
    946 #ifndef SRV_NO_WS
    947 /* Procesa wsin: primero el handshake HTTP, despues frames cuyo payload va a in. */
    948 static void ws_input(Server *s, Client *c)
    949 {
    950     if (c->ws == 1) {
    951         char resp[512];
    952         int used = 0, r = ws_handshake((const char *)c->wsin, c->wslen, &used, resp, sizeof resp);
    953         if (r == 0) { if (c->wslen >= (int)sizeof c->wsin) drop(s, c); return; }
    954         if (r < 0) { send_all(s, c, resp, (int)strlen(resp)); drop(s, c); return; }
    955         send_all(s, c, resp, r);
    956         if (c->fd < 0) return;
    957         c->ws = 2;
    958         c->wslen -= used;
    959         memmove(c->wsin, c->wsin + used, (size_t)c->wslen);
    960     }
    961     int got = 0, ctl = 0, pinglen = 0;
    962     uint8_t ping[WS_CTL_MAX];
    963     int used = ws_unwrap(c->wsin, c->wslen, c->in + c->inlen, (int)(sizeof c->in - 1 - (size_t)c->inlen), &got, &ctl, ping, &pinglen);
    964     if (used < 0) { drop(s, c); return; }
    965     if (used == 0 && c->wslen >= (int)sizeof c->wsin) { drop(s, c); return; }   /* un frame mas grande que el buffer */
    966     c->inlen += got;
    967     c->wslen -= used;
    968     memmove(c->wsin, c->wsin + used, (size_t)c->wslen);
    969     if (ctl & (1 << WS_PING)) { uint8_t fr[WS_CTL_MAX + 2]; send_all(s, c, fr, ws_wrap(WS_PONG, ping, pinglen, fr, sizeof fr)); }
    970     if (c->fd >= 0) feed_lines(s, c);
    971     if ((ctl & (1 << WS_CLOSE)) && c->fd >= 0) { uint8_t fr[2]; send_all(s, c, fr, ws_wrap(WS_CLOSE, 0, 0, fr, sizeof fr)); drop(s, c); }
    972 }
    973 #endif
    974 
    975 static void read_client(Server *s, Client *c)
    976 {
    977 #ifndef SRV_NO_WS
    978     if (c->ws) {
    979         ssize_t r = recv(c->fd, c->wsin + c->wslen, (int)(sizeof c->wsin - (size_t)c->wslen), 0);
    980         if (r <= 0) { drop(s, c); return; }
    981         c->last = srv_now_ms();
    982         c->wslen += (int)r;
    983         ws_input(s, c);
    984         return;
    985     }
    986 #endif
    987     ssize_t r = recv(c->fd, c->in + c->inlen, (int)(sizeof c->in - (size_t)c->inlen - 1), 0);
    988     if (r <= 0) { drop(s, c); return; }
    989     c->last = srv_now_ms();
    990     c->inlen += (int)r;
    991 #ifndef SRV_NO_WS
    992     /* un navegador: lo primero que manda es un pedido HTTP de upgrade, no HELLO */
    993     if (!c->hello && c->inlen < 4 && !memcmp(c->in, "GET ", (size_t)c->inlen)) return;   /* puede ser "GE": esperar */
    994     if (!c->hello && c->inlen == (int)r && r >= 4 && !memcmp(c->in, "GET ", 4)) {
    995         memcpy(c->wsin, c->in, (size_t)r);
    996         c->wslen = (int)r;
    997         c->inlen = 0;
    998         c->ws = 1;
    999         ws_input(s, c);
   1000         return;
   1001     }
   1002 #endif
   1003     feed_lines(s, c);
   1004 }
   1005 
   1006 void srv_poll(Server *s, int timeout_ms)
   1007 {
   1008     struct pollfd pf[SRV_MAX_CLIENTS + 1];
   1009     int idx[SRV_MAX_CLIENTS + 1], n = 0;
   1010     pf[n].fd = s->lfd; pf[n].events = POLLIN; idx[n++] = -1;
   1011     for (int i = 0; i < SRV_MAX_CLIENTS; i++)
   1012         if (s->cl[i].fd >= 0) { pf[n].fd = s->cl[i].fd; pf[n].events = POLLIN; idx[n++] = i; }
   1013     int bt = srv_timeout(s);
   1014     if (timeout_ms < 0 || bt < timeout_ms) timeout_ms = bt;
   1015     if (poll(pf, (nfds_t)n, timeout_ms) > 0) {
   1016         for (int k = 0; k < n; k++) {
   1017             if (!(pf[k].revents & (POLLIN | POLLHUP | POLLERR))) continue;
   1018             if (idx[k] < 0) accept_one(s);
   1019             else if (s->cl[idx[k]].fd == (int)pf[k].fd) read_client(s, &s->cl[idx[k]]);
   1020         }
   1021     }
   1022     long long now = srv_now_ms();
   1023     for (int i = 0; i < SRV_MAX_CLIENTS; i++) {
   1024         Client *c = &s->cl[i];
   1025         if (c->fd < 0) continue;
   1026         if ((!c->hello && now - c->since > SRV_HELLO_MS) || now - c->last > SRV_IDLE_MS) drop(s, c);
   1027     }
   1028     for (int i = 0; i < s->ngames; i++) {
   1029         long long t = s->gm->next_wake(s->games[i]->st);
   1030         if (t >= 0 && now >= t) s->gm->wake(s->games[i]->st, now);
   1031     }
   1032 }
   1033 
   1034 void srv_close(Server *s)
   1035 {
   1036     if (!s) return;
   1037     for (int i = 0; i < SRV_MAX_CLIENTS; i++) if (s->cl[i].fd >= 0) sock_close(s->cl[i].fd);
   1038     sock_close(s->lfd);
   1039     for (int i = 0; i < s->ngames; i++) free_game(s->games[i]);
   1040     free(s->games);
   1041     free(s);
   1042 }
   1043 
   1044 /* ------------------------------------------------------------ daemon */
   1045 
   1046 static volatile sig_atomic_t stop;
   1047 static void on_signal(int sig) { (void)sig; stop = 1; }
   1048 
   1049 int srv_main(const GameModule *gm, const char *prog, int port, const char *data, int argc, char **argv)
   1050 {
   1051     bool quiet = false;
   1052     for (int i = 1; i < argc; i++) {
   1053         if (!strcmp(argv[i], "--port") && i + 1 < argc) port = atoi(argv[++i]);
   1054         else if (!strcmp(argv[i], "--data") && i + 1 < argc) data = argv[++i];
   1055         else if (!strcmp(argv[i], "--quiet")) quiet = true;
   1056         else {
   1057             fprintf(stderr, "uso: %s [--port %d] [--data %s] [--quiet]\n", prog, port, data);
   1058             return 1;
   1059         }
   1060     }
   1061 #ifdef SIGPIPE
   1062     signal(SIGPIPE, SIG_IGN);
   1063 #endif
   1064     signal(SIGINT, on_signal);
   1065     signal(SIGTERM, on_signal);
   1066     char err[256];
   1067     Server *s = srv_open(gm, port, data, quiet, err, sizeof err);
   1068     if (!s) { fprintf(stderr, "%s: %s\n", prog, err); return 1; }
   1069     while (!stop) srv_poll(s, 1000);
   1070     srv_close(s);
   1071     return 0;
   1072 }