diff options
author | Markus Uhlin <markus@nifty-networks.net> | 2025-09-15 18:50:32 +0200 |
---|---|---|
committer | Markus Uhlin <markus@nifty-networks.net> | 2025-09-15 18:50:32 +0200 |
commit | c3eee8e333866d92e5fd94ae83cef618758c11bb (patch) | |
tree | 234a06fd90bd61a6668490a0cbf8870e6c674b81 /FICS/gamedb.c |
FICS RPBLC v1.4.61.4.6
Diffstat (limited to 'FICS/gamedb.c')
-rw-r--r-- | FICS/gamedb.c | 2256 |
1 files changed, 2256 insertions, 0 deletions
diff --git a/FICS/gamedb.c b/FICS/gamedb.c new file mode 100644 index 0000000..f3a351b --- /dev/null +++ b/FICS/gamedb.c @@ -0,0 +1,2256 @@ +/* gamedb.c + * + */ + +/* + fics - An internet chess server. + Copyright (C) 1993 Richard V. Nash + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +/* Revision history: + name email yy/mm/dd Change + Richard Nash 93/10/22 Created + Markus Uhlin 23/12/16 Fixed compiler warnings + Markus Uhlin 23/12/17 Reformatted functions + Markus Uhlin 23/12/23 Fixed clang warnings + Markus Uhlin 24/05/04 Refactored and reformatted all + functions plus more... + Markus Uhlin 24/07/17 Fixed out of bounds array access + in game_str() which resulted in + a crash. + Markus Uhlin 24/07/18 Return value checking + Markus Uhlin 24/08/03 See previous change + Markus Uhlin 24/11/23 Added width specifications to + multiple fscanf() calls. + Markus Uhlin 24/11/23 Fixed bugs in movesToString() + Markus Uhlin 24/11/25 Null checks + Markus Uhlin 24/12/02 Fixed bugs and ignored function + return values. + Markus Uhlin 25/03/18 Fixed unchecked return values + Markus Uhlin 25/03/25 ReadGameState: fixed truncated + stdio return value. + Markus Uhlin 25/04/01 Fixed call of risky function + Markus Uhlin 25/04/01 ReadV1GameFmt: guard num half + moves. + Markus Uhlin 25/04/06 Fixed Clang Tidy warnings. + Markus Uhlin 25/07/28 Fixed use of potentially + dangerous functions. + Markus Uhlin 25/07/29 Usage of 'int64_t'. + Markus Uhlin 25/08/16 Fixed uncontrolled data used in + path expressions. +*/ + +#include "stdinclude.h" +#include "common.h" + +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdint.h> + +#include "command.h" +#include "config.h" +#include "eco.h" +#include "ficsmain.h" +#include "gamedb.h" +#include "gameproc.h" +#include "maxxes-utils.h" +#include "network.h" +#include "playerdb.h" +#include "rmalloc.h" +#include "utils.h" + +#if __linux__ +#include <bsd/string.h> +#endif + +/* + * This should be enough to hold any game up to at least 250 moves. If + * we overwrite this, the server will crash. + */ +#define GAME_STRING_LEN 16000 + +PUBLIC game *garray = NULL; +PUBLIC int g_num = 0; + +PUBLIC const char *bstr[7] = { + [TYPE_UNTIMED] = "untimed", + [TYPE_BLITZ] = "blitz", + [TYPE_STAND] = "standard", + [TYPE_NONSTANDARD] = "non-standard", + [TYPE_WILD] = "wild", + [TYPE_LIGHT] = "lightning", + [TYPE_BUGHOUSE] = "bughouse" +}; +PUBLIC const char *rstr[2] = { + [TYPE_UNRATED] = "unrated", + [TYPE_RATED] = "rated" +}; + +PRIVATE char gameString[GAME_STRING_LEN]; + +/* + * This method is awful! How about allocation as we need it and + * freeing afterwards! + */ +PRIVATE int +get_empty_slot(void) +{ + if (garray != NULL) { + for (int i = 0; i < g_num; i++) { + if (garray[i].status == GAME_EMPTY) + return i; + } + } + + g_num++; + + if (!garray) { + garray = reallocarray(NULL, sizeof(game), g_num); + + if (garray == NULL) + err(1, "%s: reallocarray", __func__); + else + malloc_count++; + } else { + garray = reallocarray(garray, sizeof(game), g_num); + + if (garray == NULL) + err(1, "%s: reallocarray", __func__); + } // Yeah great, bet this causes lag! - DAV + + garray[g_num - 1].status = GAME_EMPTY; + return g_num - 1; +} + +PUBLIC int +game_new(void) +{ + int new = get_empty_slot(); + + game_zero(new); + + return new; +} + +PUBLIC int +game_zero(int g) +{ + garray[g].white = -1; + garray[g].black = -1; + + garray[g].link = -1; + garray[g].passes = 0; + garray[g].private = 0; + garray[g].rated = 0; + garray[g].result = END_NOTENDED; + garray[g].status = GAME_NEW; + garray[g].type = TYPE_UNTIMED; + + board_init(&garray[g].game_state, NULL, NULL); + + garray[g].bIncrement = 0; + garray[g].bInitTime = 300; // 5 minutes + garray[g].black_name[0] = '\0'; + garray[g].black_rating = 0; + + garray[g].wIncrement = 0; + garray[g].wInitTime = 300; // 5 minutes + garray[g].white_name[0] = '\0'; + garray[g].white_rating = 0; + + garray[g].examMoveList = NULL; + garray[g].examMoveListSize = 0; + +#ifdef TIMESEAL + garray[g].flag_check_time = 0L; + garray[g].flag_pending = FLAG_NONE; +#endif + + garray[g].game_state.gameNum = g; + garray[g].moveList = NULL; + garray[g].moveListSize = 0; + garray[g].numHalfMoves = 0; + garray[g].revertHalfMove = 0; + + return 0; +} + +PUBLIC int +game_free(int g) +{ + if (garray[g].moveListSize) + rfree(garray[g].moveList); + if (garray[g].examMoveListSize) + rfree(garray[g].examMoveList); + + garray[g].moveListSize = 0; + garray[g].examMoveListSize = 0; + + return 0; +} + +PUBLIC int +game_clear(int g) +{ + game_free(g); + game_zero(g); + return 0; +} + +PUBLIC int +game_remove(int g) +{ + // Should remove game from players observation list + game_clear(g); + garray[g].status = GAME_EMPTY; + return 0; +} + +// old moves not stored now - uses smoves +PUBLIC int +game_finish(int g) +{ + player_game_ended(g); // Alert playerdb that game ended + game_remove(g); + return 0; +} + +PUBLIC void +MakeFENpos(int g, char *FEN, size_t size) +{ + mstrlcpy(FEN, boardToFEN(g), size); +} + +PUBLIC char * +game_time_str(int wt, int winc, int bt, int binc) +{ + static char tstr[50] = { '\0' }; + + if ((!wt) && (!winc)) { // Untimed + (void) strlcpy(tstr, "", sizeof tstr); + return tstr; + } + + if ((wt == bt) && (winc == binc)) { + msnprintf(tstr, sizeof tstr, " %d %d", wt, winc); + } else { + msnprintf(tstr, sizeof tstr, " %d %d : %d %d", + wt, winc, + bt, binc); + } + + return tstr; +} + +PUBLIC char * +game_str(int rated, int wt, int winc, int bt, int binc, char *cat, char *board) +{ + static char tstr[200] = { '\0' }; + + if (rated != TYPE_UNRATED && + rated != TYPE_RATED) + rated = TYPE_UNRATED; + + if (cat && cat[0] && board && board[0] && (strcmp(cat, "standard") || + strcmp(board, "standard"))) { + msnprintf(tstr, sizeof(tstr), "%s %s%s Loaded from %s/%s", + rstr[rated], + bstr[game_isblitz(wt / 60, winc, bt / 60, binc, cat, board)], + game_time_str(wt / 60, winc, bt / 60, binc), + cat, + board); + } else { + msnprintf(tstr, sizeof(tstr), "%s %s%s", + rstr[rated], + bstr[game_isblitz(wt / 60, winc, bt / 60, binc, cat, board)], + game_time_str(wt / 60, winc, bt / 60, binc)); + } + + return tstr; +} + +PUBLIC int +game_isblitz(int wt, int winc, int bt, int binc, char *cat, char *board) +{ + int total; + + if (cat && cat[0] && board && board[0] && (!strcmp(cat, "wild"))) + return TYPE_WILD; + if (cat && cat[0] && board && board[0] && (strcmp(cat, "standard") || + strcmp(board, "standard"))) + return TYPE_NONSTANDARD; + + if (((wt == 0) && (winc == 0)) || ((bt == 0) && (binc == 0))) { + /* + * nonsense if one is timed and one is not + */ + return TYPE_UNTIMED; + } + + if ((wt != bt) || (winc != binc)) + return TYPE_NONSTANDARD; + + total = wt * 60 + winc * 40; + + if (total < 180) // 3 minutes + return TYPE_LIGHT; + if (total >= 900) // 15 minutes + return TYPE_STAND; + else + return TYPE_BLITZ; +} + +PUBLIC void +send_board_to(int g, int p) +{ + char *b; + int relation; + int side; + + /* + * Since we know 'g' and 'p', figure out our relationship to + * this game... + */ + + side = WHITE; + + if (garray[g].status == GAME_EXAMINE) { + if (parray[p].game == g) { + relation = 2; + } else { + relation = -2; + } + } else { + if (parray[p].game == g) { + side = parray[p].side; + relation = (side == garray[g].game_state.onMove ? 1 : + -1); + } else { + relation = 0; + } + } + + if (parray[p].flip) { // flip board? + if (side == WHITE) + side = BLACK; + else + side = WHITE; + } + + game_update_time(g); + b = board_to_string(garray[g].white_name, garray[g].black_name, + garray[g].wTime, + garray[g].bTime, + &garray[g].game_state, + (garray[g].status == GAME_EXAMINE) + ? garray[g].examMoveList + : garray[g].moveList, + parray[p].style, + side, + relation, + p); + +#ifdef TIMESEAL + if (con[parray[p].socket].timeseal) { + if (parray[p].bell) { + pprintf_noformat(p, "\007\n[G]\n%s", b); + } else { + pprintf_noformat(p, "\n[G]\n%s", b); + } + } else { + if (parray[p].bell) { + pprintf_noformat(p, "\007\n%s", b); + } else { + pprintf_noformat(p, "\n%s", b); + } + } +#else + if (parray[p].bell) { + pprintf_noformat(p, "\007\n%s", b); + } else { + pprintf_noformat(p, "\n%s", b); + } +#endif + + if (p != commanding_player) + pprintf(p, "%s", parray[p].prompt); +} + +PUBLIC void +send_boards(int g) +{ + simul_info_t *simInfo = &parray[garray[g].white].simul_info; + + if (simInfo->numBoards == 0 || simInfo->boards[simInfo->onBoard] == g) { + for (int p = 0; p < p_num; p++) { + if (parray[p].status == PLAYER_EMPTY) + continue; + if (player_is_observe(p, g) || (parray[p].game == g)) + send_board_to(g, p); + } + } +} + +PUBLIC void +game_update_time(int g) +{ + unsigned int now, timesince; + + if (garray[g].clockStopped) + return; + if (garray[g].type == TYPE_UNTIMED) + return; + + now = tenth_secs(); + timesince = now - garray[g].lastDecTime; + + if (garray[g].game_state.onMove == WHITE) { + garray[g].wTime -= timesince; + } else { + garray[g].bTime -= timesince; + } + + garray[g].lastDecTime = now; +} + +PUBLIC void +game_update_times(void) +{ + for (int g = 0; g < g_num; g++) { + if (garray[g].status != GAME_ACTIVE) + continue; + if (garray[g].clockStopped) + continue; + game_update_time(g); + } +} + +PUBLIC char * +EndString(int g, int personal) +{ + /* + * personal 0 == White checkmated + * personal 1 == loon checkmated + */ + char *blackguy, *whiteguy; + static char blackstr[] = "Black"; + static char whitestr[] = "White"; + static char endstr[200] = { '\0' }; + + blackguy = (personal ? garray[g].black_name : blackstr); + whiteguy = (personal ? garray[g].white_name : whitestr); + + switch (garray[g].result) { + case END_CHECKMATE: + msnprintf(endstr, sizeof endstr, "%s checkmated", + garray[g].winner == WHITE ? blackguy : whiteguy); + break; + case END_RESIGN: + msnprintf(endstr, sizeof endstr, "%s resigned", + garray[g].winner == WHITE ? blackguy : whiteguy); + break; + case END_FLAG: + msnprintf(endstr, sizeof endstr, "%s ran out of time", + garray[g].winner == WHITE ? blackguy : whiteguy); + break; + case END_AGREEDDRAW: + msnprintf(endstr, sizeof endstr, "Game drawn by mutual " + "agreement"); + break; + case END_BOTHFLAG: + msnprintf(endstr, sizeof endstr, "Game drawn because both " + "players ran out of time"); + break; + case END_REPETITION: + msnprintf(endstr, sizeof endstr, "Game drawn by repetition"); + break; + case END_50MOVERULE: + msnprintf(endstr, sizeof endstr, "Draw by the 50 move rule"); + break; + case END_ADJOURN: + msnprintf(endstr, sizeof endstr, "Game adjourned by mutual " + "agreement"); + break; + case END_LOSTCONNECTION: + msnprintf(endstr, sizeof endstr, "%s lost connection, " + "game adjourned", + garray[g].winner == WHITE ? whiteguy : blackguy); + break; + case END_ABORT: + msnprintf(endstr, sizeof endstr, "Game aborted by mutual " + "agreement"); + break; + case END_STALEMATE: + msnprintf(endstr, sizeof endstr, "Stalemate."); + break; + case END_NOTENDED: + msnprintf(endstr, sizeof endstr, "Still in progress"); + break; + case END_COURTESY: + msnprintf(endstr, sizeof endstr, "Game courtesyaborted by %s", + garray[g].winner == WHITE ? whiteguy : blackguy); + break; + case END_COURTESYADJOURN: + msnprintf(endstr, sizeof endstr, "Game courtesyadjourned by %s", + garray[g].winner == WHITE ? whiteguy : blackguy); + break; + case END_NOMATERIAL: + msnprintf(endstr, sizeof endstr, "Game drawn because neither " + "player has mating material"); + break; + case END_FLAGNOMATERIAL: + msnprintf(endstr, sizeof endstr, "%s ran out of time and %s " + "has no material to mate", + garray[g].winner == WHITE ? blackguy : whiteguy, + garray[g].winner == WHITE ? whiteguy : blackguy); + break; + case END_ADJDRAW: + msnprintf(endstr, sizeof endstr, "Game drawn by adjudication"); + break; + case END_ADJWIN: + msnprintf(endstr, sizeof endstr, "%s wins by adjudication", + garray[g].winner == WHITE ? whiteguy : blackguy); + break; + case END_ADJABORT: + msnprintf(endstr, sizeof endstr, "Game aborted by " + "adjudication"); + break; + default: + msnprintf(endstr, sizeof endstr, "???????"); + break; + } + + return endstr; +} + +PUBLIC char * +EndSym(int g) +{ + static char *symbols[] = { + "1-0", + "0-1", + "1/2-1/2", + "*" + }; + + switch (garray[g].result) { + case END_CHECKMATE: + case END_RESIGN: + case END_FLAG: + case END_ADJWIN: + return ((garray[g].winner == WHITE) ? symbols[0] : symbols[1]); + break; + case END_AGREEDDRAW: + case END_BOTHFLAG: + case END_REPETITION: + case END_50MOVERULE: + case END_STALEMATE: + case END_NOMATERIAL: + case END_FLAGNOMATERIAL: + case END_ADJDRAW: + return (symbols[2]); + break; + } + + return (symbols[3]); +} + +PUBLIC char * +movesToString(int g, int pgn) +{ + char tmp[160] = { '\0' }; + char *serv_loc = SERVER_LOCATION; + char *serv_name = SERVER_NAME; + int i, col; + int wr, br; + struct tm v_tm = {0}; + time_t curTime; + + wr = garray[g].white_rating; + br = garray[g].black_rating; + + curTime = untenths(garray[g].timeOfStart); + + if (pgn) { + msnprintf(gameString, sizeof gameString, + "\n[Event \"%s %s %s game\"]\n" + "[Site \"%s, %s\"]\n", + serv_name, + rstr[garray[g].rated], + bstr[garray[g].type], + serv_name, + serv_loc); + + errno = 0; + + if (localtime_r(&curTime, &v_tm) != NULL) { + strftime(tmp, sizeof(tmp), + "[Date \"%Y.%m.%d\"]\n" + "[Time \"%H:%M:%S\"]\n", + &v_tm); + mstrlcat(gameString, tmp, sizeof gameString); + } else + warn("%s: localtime_r()", __func__); + + msnprintf(tmp, sizeof tmp, + "[Round \"-\"]\n" + "[White \"%s\"]\n" + "[Black \"%s\"]\n" + "[WhiteElo \"%d\"]\n" + "[BlackElo \"%d\"]\n", + garray[g].white_name, + garray[g].black_name, + wr, br); + mstrlcat(gameString, tmp, sizeof gameString); + + msnprintf(tmp, sizeof tmp, + "[TimeControl \"%d+%d\"]\n" + "[Mode \"ICS\"]\n" + "[Result \"%s\"]\n\n", + garray[g].wInitTime / 10, + garray[g].wIncrement / 10, + EndSym(g)); + mstrlcat(gameString, tmp, sizeof gameString); + + col = 0; + + for (i = 0; i < garray[g].numHalfMoves; i++) { + if (!(i % 2)) { + if ((col += snprintf(tmp, sizeof tmp, "%d. ", + i / 2 + 1)) > 70) { + mstrlcat(gameString, "\n", + sizeof gameString); + col = 0; + } + + mstrlcat(gameString, tmp, sizeof gameString); + } + + if ((col += snprintf(tmp, sizeof tmp, "%s ", + (garray[g].status == GAME_EXAMINE) + ? garray[g].examMoveList[i].algString + : garray[g].moveList[i].algString)) > 70) { + mstrlcat(gameString, "\n", sizeof gameString); + col = 0; + } + + mstrlcat(gameString, tmp, sizeof gameString); + } + + mstrlcat(gameString, "\n", sizeof gameString); + } else { + /* + * !pgn + */ + + msnprintf(gameString, sizeof gameString, "\n%s ", + garray[g].white_name); + + if (wr > 0) { + msnprintf(tmp, sizeof tmp, "(%d) ", wr); + } else { + msnprintf(tmp, sizeof tmp, "(UNR) "); + } + + mstrlcat(gameString, tmp, sizeof gameString); + msnprintf(tmp, sizeof tmp, "vs. %s ", garray[g].black_name); + mstrlcat(gameString, tmp, sizeof gameString); + + if (br > 0) { + msnprintf(tmp, sizeof tmp, "(%d) ", br); + } else { + msnprintf(tmp, sizeof tmp, "(UNR) "); + } + + mstrlcat(gameString, tmp, sizeof gameString); + mstrlcat(gameString, "--- ", sizeof gameString); + + errno = 0; + + if (localtime_r(&curTime, &v_tm) != NULL) { + strftime(tmp, sizeof tmp, "%Y.%m.%d %H:%M:%S", &v_tm); + mstrlcat(gameString, tmp, sizeof gameString); + } else + warn("%s: localtime_r()", __func__); + + if (garray[g].rated) { + mstrlcat(gameString, "\nRated ", sizeof gameString); + } else { + mstrlcat(gameString, "\nUnrated ", sizeof gameString); + } + + if (garray[g].type == TYPE_BLITZ) { + mstrlcat(gameString, "Blitz ", sizeof(gameString)); + } else if (garray[g].type == TYPE_LIGHT) { + mstrlcat(gameString, "Lighting ", sizeof(gameString)); + } else if (garray[g].type == TYPE_BUGHOUSE) { + mstrlcat(gameString, "Bughouse ", sizeof(gameString)); + } else if (garray[g].type == TYPE_STAND) { + mstrlcat(gameString, "Standard ", sizeof(gameString)); + } else if (garray[g].type == TYPE_WILD) { + mstrlcat(gameString, "Wild ", sizeof(gameString)); + } else if (garray[g].type == TYPE_NONSTANDARD) { + mstrlcat(gameString, "Non-standard ", + sizeof(gameString)); + } else { + mstrlcat(gameString, "Untimed ", sizeof(gameString)); + } + + mstrlcat(gameString, "match, initial time: ", + sizeof gameString); + + if ((garray[g].bInitTime != garray[g].wInitTime) || + (garray[g].wIncrement != garray[g].bIncrement)) { + /* + * different starting times + */ + + msnprintf(tmp, sizeof tmp, "%d minutes, increment: %d " + "seconds AND %d minutes, increment: %d seconds." + "\n\n", + garray[g].wInitTime / 600, + garray[g].wIncrement / 10, + garray[g].bInitTime / 600, + garray[g].bIncrement / 10); + } else { + msnprintf(tmp, sizeof tmp, "%d minutes, increment: " + "%d seconds.\n\n", + garray[g].wInitTime / 600, + garray[g].wIncrement / 10); + } + + mstrlcat(gameString, tmp, sizeof gameString); + msnprintf(tmp, sizeof tmp, "Move %-19s%-19s\n", + garray[g].white_name, + garray[g].black_name); + mstrlcat(gameString, tmp, sizeof gameString); + mstrlcat(gameString, "---- ---------------- ----------------" + "\n", sizeof gameString); + + for (i = 0; i < garray[g].numHalfMoves; i += 2) { + if (i + 1 < garray[g].numHalfMoves) { + msnprintf(tmp, sizeof tmp, "%3d. %-16s ", + i / 2 + 1, + (garray[g].status == GAME_EXAMINE) + ? move_and_time(&garray[g].examMoveList[i]) + : move_and_time(&garray[g].moveList[i])); + + mstrlcat(gameString, tmp, sizeof gameString); + + msnprintf(tmp, sizeof tmp, "%-16s\n", + (garray[g].status == GAME_EXAMINE) + ? move_and_time(&garray[g].examMoveList[i + 1]) + : move_and_time(&garray[g].moveList[i + 1])); + } else { + msnprintf(tmp, sizeof tmp, "%3d. %-16s\n", + i / 2 + 1, + (garray[g].status == GAME_EXAMINE) + ? move_and_time(&garray[g].examMoveList[i]) + : move_and_time(&garray[g].moveList[i])); + } + + mstrlcat(gameString, tmp, sizeof gameString); + + if (strlen(gameString) > GAME_STRING_LEN - 100) + return gameString; // Bug out if getting + // close to filling this + // string + } + + mstrlcat(gameString, " ", sizeof gameString); + } + + msnprintf(tmp, sizeof tmp, "{%s} %s\n", EndString(g, 0), EndSym(g)); + mstrlcat(gameString, tmp, sizeof gameString); + + return gameString; +} + +PUBLIC void +game_disconnect(int g, int p) +{ + game_ended(g, (garray[g].white == p) ? WHITE : BLACK, + END_LOSTCONNECTION); +} + +PUBLIC int +CharToPiece(char c) +{ + switch (c) { + case 'P': + return W_PAWN; + case 'p': + return B_PAWN; + case 'N': + return W_KNIGHT; + case 'n': + return B_KNIGHT; + case 'B': + return W_BISHOP; + case 'b': + return B_BISHOP; + case 'R': + return W_ROOK; + case 'r': + return B_ROOK; + case 'Q': + return W_QUEEN; + case 'q': + return B_QUEEN; + case 'K': + return W_KING; + case 'k': + return B_KING; + default: + return NOPIECE; + } +} + +PUBLIC int +PieceToChar(int piece) +{ + switch (piece) { + case W_PAWN: + return 'P'; + case B_PAWN: + return 'p'; + case W_KNIGHT: + return 'N'; + case B_KNIGHT: + return 'n'; + case W_BISHOP: + return 'B'; + case B_BISHOP: + return 'b'; + case W_ROOK: + return 'R'; + case B_ROOK: + return 'r'; + case W_QUEEN: + return 'Q'; + case B_QUEEN: + return 'q'; + case W_KING: + return 'K'; + case B_KING: + return 'k'; + default: + return ' '; + } +} + +/* One line has everything on it */ +PRIVATE int +WriteMoves(FILE *fp, move_t *m) +{ + int i; + int piece, castle; + int useFile = 0, useRank = 0, check = 0; + unsigned long MoveInfo = (m->color == BLACK); + + castle = (m->moveString[0] == 'o'); + + if (castle) + piece = KING; + else + piece = piecetype(CharToPiece(m->moveString[0])); + +#define ORIGINAL_CODE 0 +#if ORIGINAL_CODE + MoveInfo = (MoveInfo <<= 3) | piece; + MoveInfo = (MoveInfo <<= 3) | m->fromFile; + MoveInfo = (MoveInfo <<= 3) | m->fromRank; + MoveInfo = (MoveInfo <<= 3) | m->toFile; + MoveInfo = (MoveInfo <<= 3) | m->toRank; + MoveInfo = (MoveInfo <<= 3) | (m->pieceCaptured & 7); + MoveInfo = (MoveInfo <<= 3) | (m->piecePromotionTo & 7); + MoveInfo = (MoveInfo <<= 1) | (m->enPassant != 0); +#else + MoveInfo <<= 3; + MoveInfo |= piece; + + MoveInfo <<= 3; + MoveInfo |= m->fromFile; + + MoveInfo <<= 3; + MoveInfo |= m->fromRank; + + MoveInfo <<= 3; + MoveInfo |= m->toFile; + + MoveInfo <<= 3; + MoveInfo |= m->toRank; + + MoveInfo <<= 3; + MoveInfo |= (m->pieceCaptured & 7); + + MoveInfo <<= 3; + MoveInfo |= (m->piecePromotionTo & 7); + + MoveInfo <<= 1; + MoveInfo |= (m->enPassant != 0); +#endif + + /* Are we using from-file or from-rank in 'algString'? */ + + if ((i = strlen(m->algString)) > 0) + i -= 1; + + if (m->algString[i] == '+') { + check = 1; + i--; + } + + if (piece != PAWN && !castle) { + i -= 2; + + if (i < 0) + return -1; + if (m->algString[i] == 'x') + i--; + if (i < 0) + return -1; + if (isdigit(m->algString[i])) { + useRank = 2; + i--; + } + if (i < 0) + return -1; + + useFile = (islower(m->algString[i]) ? 4 : 0); + } + + MoveInfo = ((MoveInfo << 3) | useFile | useRank | check); + + fprintf(fp, "%lx %x %x\n", MoveInfo, m->tookTime, m->atTime); + return 0; +} + +PRIVATE int +ReadMove(FILE *fp, move_t *m) +{ + char line[MAX_GLINE_SIZE] = { '\0' }; + + if (fgets(line, sizeof line, fp) == NULL) + return -1; + + _Static_assert(ARRAY_SIZE(m->moveString) > 7, "'moveString' too small"); + _Static_assert(ARRAY_SIZE(m->algString) > 7, "'algString' too small"); + + if (sscanf(line, "%d %d %d %d %d %d %d %d %d \"%7[^\"]\" \"%7[^\"]\" " + "%u %u\n", + &m->color, + &m->fromFile, &m->fromRank, + &m->toFile, &m->toRank, + &m->pieceCaptured, + &m->piecePromotionTo, + &m->enPassant, + &m->doublePawn, + m->moveString, + m->algString, + &m->atTime, + &m->tookTime) != 13) + return -1; + + return 0; +} + +PRIVATE void +WriteGameState(FILE *fp, game_state_t *gs) +{ + int i, j; + + for (i = 0; i < 8; i++) { + for (j = 0; j < 8; j++) + fprintf(fp, "%c", PieceToChar(gs->board[i][j])); + } + + fprintf(fp, "%d %d %d %d %d %d", + gs->wkmoved, gs->wqrmoved, gs->wkrmoved, + gs->bkmoved, gs->bqrmoved, gs->bkrmoved); + + for (i = 0; i < 8; i++) { + fprintf(fp, " %d %d", gs->ep_possible[0][i], + gs->ep_possible[1][i]); + } + + fprintf(fp, " %d %d %d\n", gs->lastIrreversable, gs->onMove, + gs->moveNum); +} + +PRIVATE int +ReadGameState(FILE *fp, game_state_t *gs, int version) +{ + int i, j; + int pieceChar; + int wkmoved, wqrmoved, wkrmoved, bkmoved, bqrmoved, bkrmoved; + + if (version == 0) { + for (i = 0; i < 8; i++) { + for (j = 0; j < 8; j++) { + if (fscanf(fp, "%d ", &gs->board[i][j]) != 1) + return -1; + } + } + } else { + (void) getc(fp); /* Skip past a newline. */ + + for (i = 0; i < 8; i++) { + for (j = 0; j < 8; j++) { + if ((pieceChar = getc(fp)) == EOF) + return -1; + gs->board[i][j] = CharToPiece(pieceChar); + } + } + } + + if (fscanf(fp, "%d %d %d %d %d %d", + &wkmoved, &wqrmoved, &wkrmoved, + &bkmoved, &bqrmoved, &bkrmoved) != 6) + return -1; + + gs->wkmoved = wkmoved; + gs->wqrmoved = wqrmoved; + gs->wkrmoved = wkrmoved; + + gs->bkmoved = bkmoved; + gs->bqrmoved = bqrmoved; + gs->bkrmoved = bkrmoved; + + for (i = 0; i < 8; i++) { + if (fscanf(fp, " %d %d", &gs->ep_possible[0][i], + &gs->ep_possible[1][i]) != 2) + return -1; + } + + if (fscanf(fp, " %d %d %d\n", &gs->lastIrreversable, &gs->onMove, + &gs->moveNum) != 3) + return -1; + return 0; +} + +PUBLIC int +got_attr_value(int g, char *attr, char *value, FILE *fp, char *file) +{ + if (!strcmp(attr, "w_init:")) { + garray[g].wInitTime = atoi(value); + } else if (!strcmp(attr, "w_inc:")) { + garray[g].wIncrement = atoi(value); + } else if (!strcmp(attr, "b_init:")) { + garray[g].bInitTime = atoi(value); + } else if (!strcmp(attr, "b_inc:")) { + garray[g].bIncrement = atoi(value); + } else if (!strcmp(attr, "white_name:")) { + mstrlcpy(garray[g].white_name, value, + sizeof(garray[g].white_name)); + } else if (!strcmp(attr, "black_name:")) { + mstrlcpy(garray[g].black_name, value, + sizeof(garray[g].black_name)); + } else if (!strcmp(attr, "white_rating:")) { + garray[g].white_rating = atoi(value); + } else if (!strcmp(attr, "black_rating:")) { + garray[g].black_rating = atoi(value); + } else if (!strcmp(attr, "result:")) { + garray[g].result = atoi(value); + } else if (!strcmp(attr, "timestart:")) { + garray[g].timeOfStart = atoi(value); + } else if (!strcmp(attr, "w_time:")) { + garray[g].wTime = atoi(value); + } else if (!strcmp(attr, "b_time:")) { + garray[g].bTime = atoi(value); + } else if (!strcmp(attr, "clockstopped:")) { + garray[g].clockStopped = atoi(value); + } else if (!strcmp(attr, "rated:")) { + garray[g].rated = atoi(value); + } else if (!strcmp(attr, "private:")) { + garray[g].private = atoi(value); + } else if (!strcmp(attr, "type:")) { + garray[g].type = atoi(value); + } else if (!strcmp(attr, "halfmoves:")) { + if ((garray[g].numHalfMoves = atoi(value)) == 0) + return 0; + else if (garray[g].numHalfMoves < 0 || + (size_t)garray[g].numHalfMoves > INT_MAX / sizeof(move_t)) { + warnx("%s: num half moves out-of-bounds (%d)", __func__, + garray[g].numHalfMoves); + return -1; + } else { + /* null */; + } + + garray[g].moveListSize = garray[g].numHalfMoves; + garray[g].moveList = reallocarray(NULL, sizeof(move_t), + garray[g].moveListSize); + + if (garray[g].moveList == NULL) + err(1, "%s: reallocarray", __func__); + else + malloc_count++; + + for (int i = 0; i < garray[g].numHalfMoves; i++) { + if (ReadMove(fp, &garray[g].moveList[i])) { + fprintf(stderr, "FICS: Trouble reading moves " + "from %s.\n", file); + return -1; + } + } + } else if (!strcmp(attr, "gamestate:")) { // Value meaningless + if (garray[g].status != GAME_EXAMINE && + ReadGameState(fp, &garray[g].game_state, 0)) { + fprintf(stderr, "FICS: Trouble reading game state " + "from %s.\n", file); + return -1; + } + } else { + fprintf(stderr, "FICS: Error bad attribute >%s< from file %s\n", + attr, file); + } + + return 0; +} + +PRIVATE int +ReadOneV1Move(FILE *fp, move_t *m) +{ + char PieceChar; + int i; + int useFile, useRank, check, piece; + unsigned long MoveInfo; + + if (fscanf(fp, "%lx %x %x", &MoveInfo, &m->tookTime, &m->atTime) != 3) + return -1; + + check = MoveInfo & 1; + useRank = MoveInfo & 2; + useFile = MoveInfo & 4; + + MoveInfo >>= 3; + m->enPassant = (MoveInfo & 1); // May have to negate later. + + MoveInfo >>= 1; + m->piecePromotionTo = (MoveInfo & 7); // May have to change color. + + MoveInfo >>= 3; + m->pieceCaptured = (MoveInfo & 7); // May have to change color. + + MoveInfo >>= 3; + m->toRank = (MoveInfo & 7); + + MoveInfo >>= 3; + m->toFile = (MoveInfo & 7); + + MoveInfo >>= 3; + m->fromRank = (MoveInfo & 7); + + MoveInfo >>= 3; + m->fromFile = (MoveInfo & 7); + + MoveInfo >>= 3; + piece = (MoveInfo & 7); + + m->color = ((MoveInfo & 8) ? BLACK : WHITE); + + if (m->pieceCaptured != NOPIECE) { + if (m->color == BLACK) + m->pieceCaptured |= WHITE; + else + m->pieceCaptured |= BLACK; + } + + if (piece == PAWN) { + PieceChar = 'P'; + + if ((m->toRank == 3 && m->fromRank == 1) || + (m->toRank == 4 && m->fromRank == 6)) + m->doublePawn = m->toFile; + else + m->doublePawn = -1; + + if (m->pieceCaptured) { + msnprintf(m->algString, sizeof m->algString, "%cx%c%d", + ('a' + m->fromFile), + ('a' + m->toFile), + (m->toRank + 1)); + } else { + msnprintf(m->algString, sizeof m->algString, "%c%d", + ('a' + m->toFile), + (m->toRank + 1)); + } + + if (m->piecePromotionTo != 0) { + if (m->piecePromotionTo == KNIGHT) { + mstrlcat(m->algString, "=N", + sizeof(m->algString)); + } else if (m->piecePromotionTo == BISHOP) { + mstrlcat(m->algString, "=B", + sizeof(m->algString)); + } else if (m->piecePromotionTo == ROOK) { + mstrlcat(m->algString, "=R", + sizeof(m->algString)); + } else if (m->piecePromotionTo == QUEEN) { + mstrlcat(m->algString, "=Q", + sizeof(m->algString)); + } + + m->piecePromotionTo |= m->color; + } + + if (m->enPassant) + m->enPassant = (m->toFile - m->fromFile); + } else { + m->doublePawn = -1; + PieceChar = PieceToChar(piecetype(piece) | WHITE); + + if (PieceChar == 'K' && m->fromFile == 4 && m->toFile == 6) { + mstrlcpy(m->algString, "O-O", sizeof(m->algString)); + mstrlcpy(m->moveString, "o-o", sizeof(m->moveString)); + } else if (PieceChar == 'K' && + m->fromFile == 4 && + m->toFile == 2) { + mstrlcpy(m->algString, "O-O-O", sizeof(m->algString)); + mstrlcpy(m->moveString, "o-o-o", sizeof(m->moveString)); + } else { + i = 0; + m->algString[i++] = PieceChar; + + if (useFile) + m->algString[i++] = 'a' + m->fromFile; + if (useRank) + m->algString[i++] = '1' + m->fromRank; + if (m->pieceCaptured != 0) + m->algString[i++] = 'x'; + + m->algString[i++] = ('a' + m->toFile); + m->algString[i++] = ('1' + m->toRank); + m->algString[i] = '\0'; + } + } + + if (m->algString[0] != 'O') { + int ret, too_long; + + ret = snprintf(m->moveString, sizeof m->moveString, + "%c/%c%d-%c%d", + PieceChar, + ('a' + m->fromFile), + (m->fromRank + 1), + ('a' + m->toFile), + (m->toRank + 1)); + + too_long = (ret < 0 || (size_t)ret >= sizeof m->moveString); + + if (too_long) { + fprintf(stderr, "FICS: %s: warning: " + "snprintf truncated\n", __func__); + } + } + if (check) + mstrlcat(m->algString, "+", sizeof m->algString); + return 0; +} + +PRIVATE int +ReadV1Moves(game *g, FILE *fp) +{ + g->moveListSize = g->numHalfMoves; + g->moveList = reallocarray(NULL, sizeof(move_t), g->moveListSize); + + if (g->moveList == NULL) + err(1, "%s: reallocarray", __func__); + else + malloc_count++; + + for (int i = 0; i < g->numHalfMoves; i++) { + if (ReadOneV1Move(fp, &g->moveList[i]) == -1) { + warnx("%s: failed to read move %d/%d", __func__, i, + g->numHalfMoves); + return -1; + } + } + + return 0; +} + +PRIVATE int +ReadV1GameFmt(game *g, FILE *fp, const char *file, int version) +{ + int ret[3]; + int64_t lval; + + _Static_assert(17 < ARRAY_SIZE(g->white_name), "Unexpected array size"); + _Static_assert(17 < ARRAY_SIZE(g->black_name), "Unexpected array size"); + + ret[0] = fscanf(fp, "%17s %17s", g->white_name, g->black_name); + ret[1] = fscanf(fp, "%d %d", // NOLINT + &g->white_rating, + &g->black_rating); + ret[2] = fscanf(fp, "%d %d %d %d", // NOLINT + &g->wInitTime, + &g->wIncrement, + &g->bInitTime, + &g->bIncrement); + if (ret[0] != 2 || + ret[1] != 2 || + ret[2] != 4) { + warnx("%s: fscanf error: %s", __func__, file); + return -1; + } + + if (version < 3 && !g->bInitTime) + g->bInitTime = g->wInitTime; + + if (fscanf(fp, "%" SCNd64, &lval) != 1) { + warnx("%s: %s: failed to get time of start", __func__, file); + return -1; + } else + g->timeOfStart = lval; + + if (fscanf(fp, "%d %d", &g->wTime, &g->bTime) != 2) { + warnx("%s: %s: failed to get 'wTime' and 'bTime'", __func__, + file); + return -1; + } + + if (version > 1) { + if (fscanf(fp, "%d %d", &g->result, &g->winner) != 2) { + warnx("%s: %s: failed to get 'result' nor 'winner'", + __func__, file); + return -1; + } + } else { + if (fscanf(fp, "%d", &g->result) != 1) { + warnx("%s: %s: failed to get 'result'", + __func__, file); + return -1; + } + } + + ret[0] = fscanf(fp, "%d %d %d %d", &g->private, &g->type, &g->rated, + &g->clockStopped); + ret[1] = fscanf(fp, "%d", &g->numHalfMoves); // NOLINT + if (ret[0] != 4 || ret[1] != 1) { + warnx("%s: fscanf error: %s", __func__, file); + return -1; + } else if (g->numHalfMoves < 0 || (size_t)g->numHalfMoves > + INT_MAX / sizeof(move_t)) { + warnx("%s: warning: num half moves out-of-bounds (%d)", + __func__, + g->numHalfMoves); + return -1; + } + + if (ReadV1Moves(g, fp) != 0) { + warnx("%s: failed to read moves: %s", __func__, file); + return -1; + } + + if (g->status != GAME_EXAMINE && + ReadGameState(fp, &g->game_state, version)) { + fprintf(stderr, "FICS: Trouble reading game state from %s.\n", + file); + return -1; + } + + return 0; +} + +PUBLIC int +ReadGameAttrs(FILE *fp, char *fname, int g) +{ + char *attr, *value; + char line[MAX_GLINE_SIZE] = { '\0' }; + int len = 0; + int version = 0; + + if (fgets(line, sizeof line, fp) == NULL) { + warnx("%s: fgets error", __func__); + return -1; + } + + if (line[0] == 'v') { + if (sscanf(line, "%*c %d", &version) != 1) + warn("%s: failed to get version", __func__); + } + + if (version > 0) { + if (ReadV1GameFmt(&garray[g], fp, fname, version) == -1) + return -1; + } else { + do { + if ((len = strlen(line)) <= 1) { + if (fgets(line, sizeof line, fp) == NULL) + break; + continue; + } + + line[len - 1] = '\0'; + attr = eatwhite(line); + + if (attr[0] == '#') + continue; // Comment + + value = eatword(attr); + + if (!*value) { + fprintf(stderr, "FICS: Error reading file %s\n", + fname); + if (fgets(line, sizeof line, fp) == NULL) + break; + continue; + } + + *value = '\0'; + value++; + value = eatwhite(value); + + if (!*value) { + fprintf(stderr, "FICS: Error reading file %s\n", + fname); + if (fgets(line, sizeof line, fp) == NULL) + break; + continue; + } + + stolower(attr); + + if (got_attr_value(g, attr, value, fp, fname)) + return -1; + + if (fgets(line, sizeof line, fp) == NULL) + break; + } while (!feof(fp)); + } + + if (!(garray[g].bInitTime)) + garray[g].bInitTime = garray[g].wInitTime; + return 0; +} + +PUBLIC int +game_read(int g, int wp, int bp) +{ + FILE *fp; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + + garray[g].white = wp; + garray[g].black = bp; + +#if 0 + garray[g].old_white = -1; + garray[g].old_black = -1; +#endif + + garray[g].moveListSize = 0; + garray[g].game_state.gameNum = g; + + mstrlcpy(garray[g].white_name, parray[wp].name, + sizeof(garray[g].white_name)); + mstrlcpy(garray[g].black_name, parray[bp].name, + sizeof(garray[g].black_name)); + + if (garray[g].type == TYPE_BLITZ) { + garray[g].white_rating = parray[wp].b_stats.rating; + garray[g].black_rating = parray[bp].b_stats.rating; + } else if (garray[g].type == TYPE_WILD) { + garray[g].white_rating = parray[wp].w_stats.rating; + garray[g].black_rating = parray[bp].w_stats.rating; + } else if (garray[g].type == TYPE_LIGHT) { + garray[g].white_rating = parray[wp].l_stats.rating; + garray[g].black_rating = parray[bp].l_stats.rating; + } else if (garray[g].type == TYPE_BUGHOUSE) { + garray[g].white_rating = parray[wp].bug_stats.rating; + garray[g].black_rating = parray[bp].bug_stats.rating; + } else { + garray[g].white_rating = parray[wp].s_stats.rating; + garray[g].black_rating = parray[bp].s_stats.rating; + } + + msnprintf(fname, sizeof fname, "%s/%c/%s-%s", adj_dir, + parray[wp].login[0], parray[wp].login, parray[bp].login); + fp = fopen(fname, "r"); + + if (!fp) { + return -1; + } + + if (ReadGameAttrs(fp, fname, g) < 0) { + fclose(fp); + return -1; + } + + fclose(fp); + + if (garray[g].result == END_ADJOURN || garray[g].result == + END_COURTESYADJOURN) + garray[g].result = END_NOTENDED; + + garray[g].status = GAME_ACTIVE; + garray[g].startTime = tenth_secs(); + garray[g].lastMoveTime = garray[g].startTime; + garray[g].lastDecTime = garray[g].startTime; + + // Need to do notification and pending cleanup + return 0; +} + +PUBLIC int +game_delete(int wp, int bp) +{ + char fname[MAX_FILENAME_SIZE]; + char lname[MAX_FILENAME_SIZE]; + + msnprintf(fname, sizeof fname, "%s/%c/%s-%s", adj_dir, + parray[wp].login[0], parray[wp].login, parray[bp].login); + msnprintf(lname, sizeof lname, "%s/%c/%s-%s", adj_dir, + parray[bp].login[0], parray[wp].login, parray[bp].login); + + unlink(fname); + unlink(lname); + return 0; +} + +PRIVATE void +WriteGameFile(FILE *fp, int g) +{ + game *gg = &garray[g]; + int64_t lval; + player *bp = &parray[gg->black]; + player *wp = &parray[gg->white]; + + fprintf(fp, "v %d\n", GAMEFILE_VERSION); + fprintf(fp, "%s %s\n", wp->name, bp->name); + fprintf(fp, "%d %d\n", gg->white_rating, gg->black_rating); + fprintf(fp, "%d %d %d %d\n", gg->wInitTime, gg->wIncrement, + gg->bInitTime, gg->bIncrement); + + lval = gg->timeOfStart; + fprintf(fp, "%" PRId64 "\n", lval); + +#ifdef TIMESEAL + fprintf(fp, "%d %d\n", + (con[wp->socket].timeseal ? (gg->wRealTime / 100) : gg->wTime), + (con[bp->socket].timeseal ? (gg->bRealTime / 100) : gg->bTime)); +#endif + + fprintf(fp, "%d %d\n", gg->result, gg->winner); + fprintf(fp, "%d %d %d %d\n", gg->private, gg->type, gg->rated, + gg->clockStopped); + fprintf(fp, "%d\n", gg->numHalfMoves); + + for (int i = 0; i < garray[g].numHalfMoves; i++) + WriteMoves(fp, &garray[g].moveList[i]); + + WriteGameState(fp, &garray[g].game_state); +} + +PUBLIC int +game_save(int g) +{ + FILE *fp; + char fname[MAX_FILENAME_SIZE]; + char lname[MAX_FILENAME_SIZE]; + game *gg = &garray[g]; + int fd; + player *wp, *bp; + + wp = &parray[gg->white]; + bp = &parray[gg->black]; + + msnprintf(fname, sizeof fname, "%s/%c/%s-%s", adj_dir, wp->login[0], + wp->login, bp->login); + msnprintf(lname, sizeof lname, "%s/%c/%s-%s", adj_dir, bp->login[0], + wp->login, bp->login); + + if ((fd = open(fname, g_open_flags[1], g_open_modes)) < 0) { + warn("%s: open: %s", __func__, fname); + return -1; + } else if ((fp = fdopen(fd, "w")) == NULL) { + fprintf(stderr, "FICS: Problem opening file %s for write\n", + fname); + close(fd); + return -1; + } + + WriteGameFile(fp, g); + +#if 0 + fprintf(fp, "W_Init: %d\n", garray[g].wInitTime); + fprintf(fp, "W_Inc: %d\n", garray[g].wIncrement); + fprintf(fp, "B_Init: %d\n", garray[g].bInitTime); + fprintf(fp, "B_Inc: %d\n", garray[g].bIncrement); + fprintf(fp, "white_name: %s\n", wp->name); + fprintf(fp, "black_name: %s\n", bp->name); + fprintf(fp, "white_rating: %d\n", garray[g].white_rating); + fprintf(fp, "black_rating: %d\n", garray[g].black_rating); + fprintf(fp, "result: %d\n", garray[g].result); + fprintf(fp, "TimeStart: %d\n", (int) garray[g].timeOfStart); + fprintf(fp, "W_Time: %d\n", garray[g].wTime); + fprintf(fp, "B_Time: %d\n", garray[g].bTime); + fprintf(fp, "ClockStopped: %d\n", garray[g].clockStopped); + fprintf(fp, "Rated: %d\n", garray[g].rated); + fprintf(fp, "Private: %d\n", garray[g].private); + fprintf(fp, "Type: %d\n", garray[g].type); + fprintf(fp, "HalfMoves: %d\n", garray[g].numHalfMoves); + + for (int i = 0; i < garray[g].numHalfMoves; i++) + WriteMoves(fp, &garray[g].moveList[i]); + + fprintf(fp, "GameState: IsNext\n"); + WriteGameState(fp, &garray[g].game_state); +#endif + + fclose(fp); + + /* + * Create link for easier stored game finding + */ + if (bp->login[0] != wp->login[0] && + link(fname, lname) != 0) + warn("%s: link() error", __func__); + return 0; +} + +PRIVATE long int +OldestHistGame(char *login) +{ + FILE *fp; + char pFile[MAX_FILENAME_SIZE] = { '\0' }; + long int when; + + /* Centralized validation of login */ + if (!is_valid_login_name(login)) { + warnx("%s: invalid login value: '%s'", __func__, login); + return 0L; + } + + msnprintf(pFile, sizeof pFile, "%s/player_data/%c/%s.%s", stats_dir, + login[0], login, STATS_GAMES); + + if ((fp = fopen(pFile, "r")) == NULL) { + msnprintf(pFile, sizeof pFile, "%s/player_data/%c/.rem.%s.%s", + stats_dir, login[0], login, STATS_GAMES); + fp = fopen(pFile, "r"); + } + + if (fp != NULL) { + if (fscanf(fp, "%*d %*c %*d %*c %*d %*s %*s %*d %*d %*d %*d " + "%*s %*s %ld", &when) != 1) { + warnx("%s: %s: failed to read 'when'", __func__, + &pFile[0]); + fclose(fp); + return 0L; + } + fclose(fp); + return when; + } else + return 0L; +} + +PRIVATE void +RemoveHistGame(char *file, int maxlines) +{ + FILE *fp; + char GameFile[MAX_FILENAME_SIZE] = { '\0' }; + char Opponent[MAX_LOGIN_NAME + 1] = { '\0' }; + char line[MAX_LINE_SIZE] = { '\0' }; + int count = 0; + long int When = 0, oppWhen = 0; + + _Static_assert(20 < ARRAY_SIZE(Opponent), "Not within bounds"); + + if ((fp = fopen(file, "r")) == NULL) { + return; + } else if (fgets(line, ARRAY_SIZE(line), fp) == NULL) { + warnx("%s: fgets error (file: %s)", __func__, file); + fclose(fp); + return; + } else if (sscanf(line, "%*d %*c %*d %*c %*d %20s %*s %*d %*d %*d " + "%*d %*s %*s %ld", Opponent, &When) != 2) { + warnx("%s: unexpected initial line (file: %s)", __func__, file); + fclose(fp); + return; + } + + count++; + + while (fgets(line, ARRAY_SIZE(line), fp) != NULL) + count++; + + fclose(fp); + stolower(Opponent); + + if (count > maxlines) { + truncate_file(file, maxlines); + oppWhen = OldestHistGame(Opponent); + + if (oppWhen > When || oppWhen <= 0L) { + msnprintf(GameFile, sizeof GameFile, "%s/%ld/%ld", + hist_dir, (When % 100), When); + unlink(GameFile); + } + } +} + +PUBLIC void +RemHist(char *who) +{ + FILE *fp; + char Opp[MAX_LOGIN_NAME] = { '\0' }; + char fName[MAX_FILENAME_SIZE] = { '\0' }; + long int When, oppWhen; + + msnprintf(fName, sizeof fName, "%s/player_data/%c/%s.%s", stats_dir, + who[0], who, STATS_GAMES); + + if ((fp = fopen(fName, "r")) != NULL) { + long int iter_no = 0; + + while (!feof(fp) && !ferror(fp)) { + const int ret = fscanf(fp, "%*d %*c %*d %*c %*d %19s " + "%*s %*d %*d %*d %*d %*s %*s %ld\n", Opp, &When); + if (ret != 2) { + warnx("%s: fscanf() error (%s:%ld)", __func__, + fName, iter_no); + break; + } + + stolower(Opp); + + // Centralized validation: only allow safe login names + if (!is_valid_login_name(Opp)) { + warnx("%s: invalid value: " + "Opp = '%s' (skipping)", __func__, Opp); + iter_no++; + continue; + } + + oppWhen = OldestHistGame(Opp); + + if (oppWhen > When || oppWhen <= 0L) { + char histfile[MAX_FILENAME_SIZE] = { '\0' }; + + msnprintf(histfile, sizeof histfile, + "%s/%ld/%ld", hist_dir, (When % 100), When); + if (unlink(histfile) != 0) { + warn("%s: unlink(%s)", __func__, + histfile); + } + } + + iter_no++; + } + + fclose(fp); + } +} + +PRIVATE void +write_g_out(int g, char *file, int maxlines, int isDraw, char *EndSymbol, + char *name, time_t *now) +{ + FILE *fp; + char cResult; + char tmp[2048] = { '\0' }; + char type[4]; + char *goteco; + char *ptmp = tmp; + int count = -1; + int fd; + int wp, bp; + int wr, br; + + wp = garray[g].white; + bp = garray[g].black; + + if (garray[g].private) { + type[0] = 'p'; + } else { + type[0] = ' '; + } + + if (garray[g].type == TYPE_BLITZ) { + wr = parray[wp].b_stats.rating; + br = parray[bp].b_stats.rating; + + type[1] = 'b'; + } else if (garray[g].type == TYPE_WILD) { + wr = parray[wp].w_stats.rating; + br = parray[bp].w_stats.rating; + + type[1] = 'w'; + } else if (garray[g].type == TYPE_STAND) { + wr = parray[wp].s_stats.rating; + br = parray[bp].s_stats.rating; + + type[1] = 's'; + } else if (garray[g].type == TYPE_LIGHT) { + wr = parray[wp].l_stats.rating; + br = parray[bp].l_stats.rating; + + type[1] = 'l'; + } else if (garray[g].type == TYPE_BUGHOUSE) { + wr = parray[wp].bug_stats.rating; + br = parray[bp].bug_stats.rating; + + type[1] = 'd'; + } else { + wr = 0; + br = 0; + + if (garray[g].type == TYPE_NONSTANDARD) + type[1] = 'n'; + else + type[1] = 'u'; + } + + if (garray[g].rated) { + type[2] = 'r'; + } else { + type[2] = 'u'; + } + + type[3] = '\0'; + + if ((fp = fopen(file, "r")) != NULL) { + while (fgets(tmp, sizeof tmp, fp) != NULL) { + /* null */; + } + if (sscanf(ptmp, "%d", &count) != 1) + warnx("%s: failed to read 'count'", __func__); + fclose(fp); + } + + count = (count + 1) % 100; + + if ((fd = open(file, g_open_flags[0], g_open_modes)) < 0) { + warn("%s: open: %s", __func__, file); + return; + } else if ((fp = fdopen(fd, "a")) == NULL) { + close(fd); + return; + } + + goteco = getECO(g); + + /* + * Counter + * Result + * MyRating + * MyColor + * OppRating + * OppName [pbr 2 12 2 12] + * ECO + * End + * Date + */ + if (name == parray[wp].name) { + if (isDraw) + cResult = '='; + else if (garray[g].winner == WHITE) + cResult = '+'; + else + cResult = '-'; + + fprintf(fp, "%d %c %d W %d %s %s %d %d %d %d %s %s %ld\n", + count, cResult, wr, br, parray[bp].name, type, + garray[g].wInitTime, garray[g].wIncrement, + garray[g].bInitTime, garray[g].bIncrement, + goteco, + EndSymbol, + (long int) *now); + } else { + if (isDraw) + cResult = '='; + else if (garray[g].winner == BLACK) + cResult = '+'; + else + cResult = '-'; + + fprintf(fp, "%d %c %d B %d %s %s %d %d %d %d %s %s %ld\n", + count, cResult, br, wr, parray[wp].name, type, + garray[g].wInitTime, garray[g].wIncrement, + garray[g].bInitTime, garray[g].bIncrement, + goteco, + EndSymbol, + (long int) *now); + } + + fclose(fp); + RemoveHistGame(file, maxlines); +} + +/* + * Find from_spot in journal list - return 0 if corrupted + */ +PUBLIC int +journal_get_info(struct JGI_context *ctx, const char *fname) +{ + FILE *fp; + char count; + + if ((fp = fopen(fname, "r")) == NULL) { + fprintf(stderr, "Corrupt journal file! %s\n", fname); + pprintf(ctx->p, "The journal file is corrupt! See an admin.\n"); + return 0; + } + + while (!feof(fp)) { + _Static_assert(ARRAY_SIZE(ctx->WhiteName) > 20, + "'WhiteName' too small"); + _Static_assert(ARRAY_SIZE(ctx->BlackName) > 20, + "'BlackName' too small"); + + _Static_assert(ARRAY_SIZE(ctx->type) > 99, "'type' too small"); + _Static_assert(ARRAY_SIZE(ctx->eco) > 99, "'eco' too small"); + _Static_assert(ARRAY_SIZE(ctx->ending) > 99, "'ending' too small"); + _Static_assert(ARRAY_SIZE(ctx->result) > 99, "'result' too small"); + + if (fscanf(fp, "%c %20s %d %20s %d %99s %d %d %99s %99s %99s\n", + &count, + ctx->WhiteName, &ctx->WhiteRating, + ctx->BlackName, &ctx->BlackRating, + ctx->type, + &ctx->t, &ctx->i, + ctx->eco, + ctx->ending, + ctx->result) != 11) { + fprintf(stderr, "FICS: Error in journal info format. " + "%s\n", fname); + pprintf(ctx->p, "The journal file is corrupt! Error in " + "internal format.\n"); + fclose(fp); + return 0; + } + + if (tolower(count) == ctx->from_spot) { + fclose(fp); + return 1; + } + } + + fclose(fp); + return 0; +} + +PUBLIC void +addjournalitem(int p, char count2, char *WhiteName2, int WhiteRating2, + char *BlackName2, int BlackRating2, char *type2, int t2, int i2, + char *eco2, char *ending2, char *result2, char *fname) +{ + FILE *fp; + FILE *fp2; + char BlackName[MAX_LOGIN_NAME + 1] = { '\0' }; + char WhiteName[MAX_LOGIN_NAME + 1] = { '\0' }; + char count; + char eco[100] = { '\0' }; + char ending[100] = { '\0' }; + char fname2[MAX_FILENAME_SIZE] = { '\0' }; + char result[100] = { '\0' }; + char type[100] = { '\0' }; + int WhiteRating, BlackRating; + int fd; + int have_output = 0; + int t, i; + + mstrlcpy(fname2, fname, sizeof fname2); + mstrlcat(fname2, ".w", sizeof fname2); + + if ((fd = open(fname2, g_open_flags[1], g_open_modes)) < 0) { + warn("%s: open", __func__); + return; + } else if ((fp2 = fdopen(fd, "w")) == NULL) { + fprintf(stderr, "FICS: Problem opening file %s for write\n", + fname); + pprintf(p, "Couldn't update journal! Report this to an admin." + "\n"); + close(fd); + return; + } + + if ((fp = fopen(fname, "r")) == NULL) { // Empty? + fprintf(fp2, "%c %s %d %s %d %s %d %d %s %s %s\n", + count2, + WhiteName2, WhiteRating2, + BlackName2, BlackRating2, + type2, + t2, i2, + eco2, + ending2, + result2); + fclose(fp2); + xrename(__func__, fname2, fname); + return; + } else { + _Static_assert(ARRAY_SIZE(WhiteName) > 19, + "'WhiteName' too small"); + _Static_assert(ARRAY_SIZE(BlackName) > 19, + "'BlackName' too small"); + + _Static_assert(ARRAY_SIZE(type) > 99, "'type' too small"); + _Static_assert(ARRAY_SIZE(eco) > 99, "'eco' too small"); + _Static_assert(ARRAY_SIZE(ending) > 99, "'ending' too small"); + _Static_assert(ARRAY_SIZE(result) > 99, "'result' too small"); + + while (!feof(fp)) { + if (fscanf(fp, "%c %19s %d %19s %d %99s %d %d %99s " + "%99s %99s\n", + &count, + WhiteName, &WhiteRating, + BlackName, &BlackRating, + type, + &t, &i, + eco, + ending, + result) != 11) { + fprintf(stderr, "FICS: Error in journal info " + "format - aborting. %s\n", fname); + fclose(fp); + fclose(fp2); + return; + } + + if ((count >= count2) && (!have_output)) { + fprintf(fp2, "%c %s %d %s %d %s %d %d %s %s %s\n", + count2, + WhiteName2, WhiteRating2, + BlackName2, BlackRating2, + type2, + t2, i2, + eco2, + ending2, + result2); + have_output = 1; + } + + if (count != count2) { + fprintf(fp2, "%c %s %d %s %d %s %d %d %s %s %s" + "\n", + count, + WhiteName, WhiteRating, + BlackName, BlackRating, + type, + t, i, + eco, + ending, + result); + } + } + + if (!have_output) { // Haven't written yet + fprintf(fp2, "%c %s %d %s %d %s %d %d %s %s %s\n", + count2, + WhiteName2, WhiteRating2, + BlackName2, BlackRating2, + type2, + t2, i2, + eco2, + ending2, + result2); + } + } + + fclose(fp); + fclose(fp2); + + xrename(__func__, fname2, fname); +} + +PUBLIC int +pjournal(int p, int p1, char *fname) +{ + FILE *fp; + char BlackName[MAX_LOGIN_NAME + 1] = { '\0' }; + char WhiteName[MAX_LOGIN_NAME + 1] = { '\0' }; + char count; + char eco[100] = { '\0' }; + char ending[100] = { '\0' }; + char result[100] = { '\0' }; + char type[100] = { '\0' }; + int WhiteRating, BlackRating; + int t, i; + + if ((fp = fopen(fname, "r")) == NULL) { + pprintf(p, "Sorry, no journal information available.\n"); + return COM_OK; + } + + pprintf(p, "Journal for %s:\n", parray[p1].name); + pprintf(p, " White Rating Black Rating " + "Type ECO End Result\n"); + + _Static_assert(ARRAY_SIZE(WhiteName) > 19, "'WhiteName' too small"); + _Static_assert(ARRAY_SIZE(BlackName) > 19, "'BlackName' too small"); + + _Static_assert(ARRAY_SIZE(type) > 99, "'type' too small"); + _Static_assert(ARRAY_SIZE(eco) > 99, "'eco' too small"); + _Static_assert(ARRAY_SIZE(ending) > 99, "'ending' too small"); + _Static_assert(ARRAY_SIZE(result) > 99, "'result' too small"); + + while (!feof(fp)) { + if (fscanf(fp, "%c %19s %d %19s %d %99s %d %d %99s %99s %99s\n", + &count, + WhiteName, &WhiteRating, + BlackName, &BlackRating, + type, + &t, &i, + eco, + ending, + result) != 11) { + fprintf(stderr, "FICS: Error in journal info format. " + "%s\n", fname); + fclose(fp); + return COM_OK; + } + + WhiteName[13] = '\0'; // only first 13 chars in name + BlackName[13] = '\0'; + + pprintf(p, "%c: %-13s %4d %-13s %4d [%3s%3d%4d] %s %3s " + "%-7s\n", + count, WhiteName, WhiteRating, + BlackName, BlackRating, + type, (t / 600), (i / 10), eco, ending, + result); + } + + fclose(fp); + return COM_OK; +} + +PUBLIC int +pgames(int p, int p1, char *fname) +{ + FILE *fp; + char MyColor[2] = { 0,0 }; + char OppName[MAX_LOGIN_NAME + 1] = { '\0' }; + char eco[100] = { '\0' }; + char ending[100] = { '\0' }; + char result[2] = { 0,0 }; // XXX: right size? + char type[100] = { '\0' }; + int MyRating, OppRating; + int count; + int wt, wi, bt, bi; + time_t t; + + if ((fp = fopen(fname, "r")) == NULL) { + pprintf(p, "Sorry, no game information available.\n"); + return COM_OK; + } + + pprintf(p, "History for %s:\n", parray[p1].name); + pprintf(p, " Opponent Type " + "ECO End Date\n"); + + _Static_assert(ARRAY_SIZE(result) > 1, "'result' too small"); + _Static_assert(ARRAY_SIZE(MyColor) > 1, "'MyColor' too small"); + _Static_assert(ARRAY_SIZE(OppName) > 19, "'OppName' too small"); + _Static_assert(ARRAY_SIZE(type) > 99, "'type' too small"); + _Static_assert(ARRAY_SIZE(eco) > 99, "'eco' too small"); + _Static_assert(ARRAY_SIZE(ending) > 99, "'ending' too small"); + + while (!feof(fp)) { + char tbuf[30] = { '\0' }; + + if (fscanf(fp, "%d %1s %d %1s %d %19s %99s %d %d %d %d %99s " + "%99s %ld\n", + &count, result, &MyRating, MyColor, + &OppRating, OppName, + type, + &wt, &wi, + &bt, &bi, + eco, + ending, + (long int *)&t) != 14) { + fprintf(stderr, "FICS: Error in games info format. " + "%s\n", fname); + fclose(fp); + return COM_OK; + } + + OppName[13] = '\0'; // only first 13 chars in name + + pprintf(p, "%2d: %s %4d %s %4d %-13s [%3s%3d%4d] %s %3s %s", + count, result, MyRating, MyColor, + OppRating, OppName, + type, (wt / 600), (wi / 10), eco, ending, + ctime_r(&t, tbuf) != NULL ? &tbuf[0] : ""); + } + + fclose(fp); + return COM_OK; +} + +PUBLIC void +game_write_complete(int g, int isDraw, char *EndSymbol) +{ + FILE *fp = NULL; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + int fd = -1; + int wp = garray[g].white, bp = garray[g].black; + time_t now = time(NULL); + + do { + msnprintf(fname, sizeof fname, "%s/%ld/%ld", + hist_dir, + (long int)(now % 100), + (long int)now); + errno = 0; + fd = open(fname, (O_WRONLY | O_CREAT | O_EXCL), 0644); + if (fd == -1 && errno == EEXIST) + now++; + } while (fd == -1 && errno == EEXIST); + + if (fd >= 0) { + if ((fp = fdopen(fd, "w")) != NULL) { + WriteGameFile(fp, g); + fclose(fp); + } else { + fprintf(stderr, "Trouble writing history file %s", + fname); + } + + close(fd); + } + + msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", + stats_dir, + parray[wp].login[0], + parray[wp].login, + STATS_GAMES); + write_g_out(g, fname, 10, isDraw, EndSymbol, parray[wp].name, &now); + + msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", + stats_dir, + parray[bp].login[0], + parray[bp].login, + STATS_GAMES); + write_g_out(g, fname, 10, isDraw, EndSymbol, parray[bp].name, &now); +} + +PUBLIC int +game_count(void) +{ + int g, count = 0; + + for (g = 0; g < g_num; g++) { + if (garray[g].status == GAME_ACTIVE || + garray[g].status == GAME_EXAMINE) + count++; + } + + if (count > game_high) + game_high = count; + return count; +} |