security fix: replaced sprintf with snprintf
[xboard.git] / backend.c
index 8b6d3c5..67a1c1a 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,7 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -126,15 +126,22 @@ extern int gettimeofday(struct timeval *, struct timezone *);
 # include "zippy.h"
 #endif
 #include "backendz.h"
-#include "gettext.h" 
-#ifdef ENABLE_NLS 
-# define _(s) gettext (s) 
-# define N_(s) gettext_noop (s) 
-#else 
-# define _(s) (s) 
-# define N_(s) s 
-#endif 
+#include "gettext.h"
+
+#ifdef ENABLE_NLS
+# define _(s) gettext (s)
+# define N_(s) gettext_noop (s)
+# define T_(s) gettext(s)
+#else
+# ifdef WIN32
+#   define _(s) T_(s)
+#   define N_(s) s
+# else
+#   define _(s) (s)
+#   define N_(s) s
+#   define T_(s) s
+# endif
+#endif
 
 
 /* A point in time */
@@ -151,8 +158,7 @@ void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
 void ics_printf P((char *format, ...));
 void SendToICS P((char *s));
 void SendToICSDelayed P((char *s, long msdelay));
-void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
-                     int toX, int toY));
+void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
 void HandleMachineMove P((char *message, ChessProgramState *cps));
 int AutoPlayOneMove P((void));
 int LoadGameOneMove P((ChessMove readAhead));
@@ -190,7 +196,7 @@ void ParseGameHistory P((char *game));
 void ParseBoard12 P((char *string));
 void KeepAlive P((void));
 void StartClocks P((void));
-void SwitchClocks P((void));
+void SwitchClocks P((int nr));
 void StopClocks P((void));
 void ResetClocks P((void));
 char *PGNDate P((void));
@@ -234,6 +240,7 @@ char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
 void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
+VariantClass startVariant; /* [HGM] nicks: initial variant */
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
@@ -242,6 +249,13 @@ int endPV = -1;
 static int exiting = 0; /* [HGM] moved to top */
 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
+Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
+int partnerHighlight[2];
+Boolean partnerBoardValid = 0;
+char partnerStatus[MSG_SIZ];
+Boolean partnerUp;
+Boolean originalFlip;
+Boolean twoBoards = 0;
 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
@@ -295,16 +309,27 @@ char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
 #define TN_SGA  0003
 #define TN_PORT 23
 
-/* [AS] */
-static char * safeStrCpy( char * dst, const char * src, size_t count )
+char*
+safeStrCpy( char *dst, const char *src, size_t count )
 {
-    assert( dst != NULL );
-    assert( src != NULL );
-    assert( count > 0 );
+  /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
+   *
+   * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
+   */
+
+  assert( dst != NULL );
+  assert( src != NULL );
+  assert( count > 0 );
+
+  strncpy( dst, src, count );
+  if(  dst[ count-1 ] != '\0' )
+    {
+      if(appData.debugMode)
+      printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
+    }
+  dst[ count-1 ] = '\0';
 
-    strncpy( dst, src, count );
-    dst[ count-1 ] = '\0';
-    return dst;
+  return dst;
 }
 
 /* Some compiler can't cast u64 to double
@@ -353,7 +378,7 @@ PosFlags(index)
   case VariantKriegspiel:
     flags |= F_KRIEGSPIEL_CAPTURE;
     break;
-  case VariantCapaRandom: 
+  case VariantCapaRandom:
   case VariantFischeRandom:
     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
   case VariantNoCastle:
@@ -370,7 +395,7 @@ PosFlags(index)
 
 FILE *gameFileFP, *debugFP;
 
-/* 
+/*
     [AS] Note: sometimes, the sscanf() function is used to parse the input
     into a fixed-size buffer. Because of this, we must be prepared to
     receive strings as long as the size of the input buffer, which is currently
@@ -426,9 +451,10 @@ TimeMark lastNodeCountTime;
 long lastNodeCount=0;
 int have_sent_ICS_logon = 0;
 int movesPerSession;
-long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
+int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
+long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
 long timeControl_2; /* [AS] Allow separate time controls */
-char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
+char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
 long timeRemaining[2][MAX_MOVES];
 int matchGame = 0;
 TimeMark programStartTime;
@@ -453,7 +479,7 @@ signed char  initialRights[BOARD_FILES];
 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
 int   initialRulePlies, FENrulePlies;
 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
-int loadFlag = 0; 
+int loadFlag = 0;
 int shuffleOpenings;
 int mute; // mute all sounds
 
@@ -530,31 +556,31 @@ ChessSquare XiangqiArray[2][BOARD_FILES] = {
 };
 
 ChessSquare CapablancaArray[2][BOARD_FILES] = {
-    { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
+    { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
-    { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
+    { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
 };
 
 ChessSquare GreatArray[2][BOARD_FILES] = {
-    { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
+    { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
-    { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
+    { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
 };
 
 ChessSquare JanusArray[2][BOARD_FILES] = {
-    { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
+    { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
-    { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
+    { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
 };
 
 #ifdef GOTHIC
 ChessSquare GothicArray[2][BOARD_FILES] = {
-    { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
+    { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
 };
 #else // !GOTHIC
@@ -563,9 +589,9 @@ ChessSquare GothicArray[2][BOARD_FILES] = {
 
 #ifdef FALCON
 ChessSquare FalconArray[2][BOARD_FILES] = {
-    { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
+    { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
 };
 #else // !FALCON
@@ -630,6 +656,7 @@ InitBackEnd1()
     int matched, min, sec;
 
     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
+    startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
 
     GetTimeMark(&programStartTime);
     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
@@ -650,7 +677,7 @@ InitBackEnd1()
     if (appData.icsActive) {
        appData.matchMode = FALSE;
        appData.matchGames = 0;
-#if ZIPPY      
+#if ZIPPY
        appData.noChessProgram = !appData.zippyPlay;
 #else
        appData.zippyPlay = FALSE;
@@ -704,9 +731,9 @@ InitBackEnd1()
 
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
-    
-    first.which = "first";
-    second.which = "second";
+
+    first.which = _("first");
+    second.which = _("second");
     first.maybeThinking = second.maybeThinking = FALSE;
     first.pr = second.pr = NoProc;
     first.isr = second.isr = NULL;
@@ -752,8 +779,8 @@ InitBackEnd1()
     TidyProgramName(first.program, first.host, first.tidy);
     TidyProgramName(second.program, second.host, second.tidy);
     first.matchWins = second.matchWins = 0;
-    strcpy(first.variants, appData.variant);
-    strcpy(second.variants, appData.variant);
+    safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
+    safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
     first.analysisSupport = second.analysisSupport = 2; /* detect */
     first.analyzing = second.analyzing = FALSE;
     first.initDone = second.initDone = FALSE;
@@ -797,25 +824,41 @@ InitBackEnd1()
     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
 
-    if (appData.firstProtocolVersion > PROTOVER ||
-       appData.firstProtocolVersion < 1) {
-      char buf[MSG_SIZ];
-      sprintf(buf, _("protocol version %d not supported"),
-             appData.firstProtocolVersion);
-      DisplayFatalError(buf, 0, 2);
-    } else {
-      first.protocolVersion = appData.firstProtocolVersion;
-    }
+    if (appData.firstProtocolVersion > PROTOVER
+       || appData.firstProtocolVersion < 1)
+      {
+       char buf[MSG_SIZ];
+       int len;
 
-    if (appData.secondProtocolVersion > PROTOVER ||
-       appData.secondProtocolVersion < 1) {
-      char buf[MSG_SIZ];
-      sprintf(buf, _("protocol version %d not supported"),
-             appData.secondProtocolVersion);
-      DisplayFatalError(buf, 0, 2);
-    } else {
-      second.protocolVersion = appData.secondProtocolVersion;
-    }
+       len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
+                      appData.firstProtocolVersion);
+       if( (len > MSG_SIZ) && appData.debugMode )
+         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
+       DisplayFatalError(buf, 0, 2);
+      }
+    else
+      {
+       first.protocolVersion = appData.firstProtocolVersion;
+      }
+
+    if (appData.secondProtocolVersion > PROTOVER
+       || appData.secondProtocolVersion < 1)
+      {
+       char buf[MSG_SIZ];
+       int len;
+
+       len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
+                      appData.secondProtocolVersion);
+       if( (len > MSG_SIZ) && appData.debugMode )
+         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
+       DisplayFatalError(buf, 0, 2);
+      }
+    else
+      {
+       second.protocolVersion = appData.secondProtocolVersion;
+      }
 
     if (appData.icsActive) {
         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
@@ -824,7 +867,7 @@ InitBackEnd1()
        appData.clockMode = FALSE;
        first.sendTime = second.sendTime = 0;
     }
-    
+
 #if ZIPPY
     /* Override some settings from environment variables, for backward
        compatibility.  Unfortunately it's not feasible to have the env
@@ -834,7 +877,7 @@ InitBackEnd1()
       ZippyInit();
     }
 #endif
-    
+
     if (appData.noChessProgram) {
        programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
        sprintf(programVersion, "%s", PACKAGE_STRING);
@@ -846,6 +889,8 @@ InitBackEnd1()
 
     if (!appData.icsActive) {
       char buf[MSG_SIZ];
+      int len;
+
       /* Check for variants that are supported only in ICS mode,
          or not at all.  Some that are accepted here nevertheless
          have bugs; see comments below.
@@ -854,8 +899,11 @@ InitBackEnd1()
       switch (variant) {
       case VariantBughouse:     /* need four players and two boards */
       case VariantKriegspiel:   /* need to hide pieces and move details */
-      /* case VariantFischeRandom: (Fabien: moved below) */
-       sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
+       /* case VariantFischeRandom: (Fabien: moved below) */
+       len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
+       if( (len > MSG_SIZ) && appData.debugMode )
+         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
        DisplayFatalError(buf, 0, 2);
        return;
 
@@ -870,7 +918,10 @@ InitBackEnd1()
       case Variant35:
       case Variant36:
       default:
-       sprintf(buf, _("Unknown variant name %s"), appData.variant);
+       len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
+       if( (len > MSG_SIZ) && appData.debugMode )
+         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
        DisplayFatalError(buf, 0, 2);
        return;
 
@@ -879,7 +930,7 @@ InitBackEnd1()
       case VariantGothic:     /* [HGM] should work */
       case VariantCapablanca: /* [HGM] should work */
       case VariantCourier:    /* [HGM] initial forced moves not implemented */
-      case VariantShogi:      /* [HGM] drops not tested for legality */
+      case VariantShogi:      /* [HGM] could still mate with pawn drop */
       case VariantKnightmate: /* [HGM] should work */
       case VariantCylinder:   /* [HGM] untested */
       case VariantFalcon:     /* [HGM] untested */
@@ -955,46 +1006,55 @@ int NextTimeControlFromString( char ** str, long * value )
     return result;
 }
 
-int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
-{   /* [HGM] routine added to read '+moves/time' for secondary time control */
-    int result = -1; long temp, temp2;
+int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
+{   /* [HGM] routine added to read '+moves/time' for secondary time control. */
+    int result = -1, type = 0; long temp, temp2;
 
-    if(**str != '+') return -1; // old params remain in force!
+    if(**str != ':') return -1; // old params remain in force!
     (*str)++;
-    if( NextTimeControlFromString( str, &temp ) ) return -1;
+    if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
+    if( NextIntegerFromString( str, &temp ) ) return -1;
+    if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
 
     if(**str != '/') {
         /* time only: incremental or sudden-death time control */
         if(**str == '+') { /* increment follows; read it */
             (*str)++;
+            if(**str == '!') type = *(*str)++; // Bronstein TC
             if(result = NextIntegerFromString( str, &temp2)) return -1;
             *inc = temp2 * 1000;
         } else *inc = 0;
-        *moves = 0; *tc = temp * 1000; 
+        *moves = 0; *tc = temp * 1000; *incType = type;
         return 0;
-    } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
+    }
 
     (*str)++; /* classical time control */
-    result = NextTimeControlFromString( str, &temp2);
+    result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
+
     if(result == 0) {
-        *moves = temp/60;
+        *moves = temp;
         *tc    = temp2 * 1000;
         *inc   = 0;
+        *incType = type;
     }
     return result;
 }
 
-int GetTimeQuota(int movenr)
+int GetTimeQuota(int movenr, int lastUsed, char *tcString)
 {   /* [HGM] get time to add from the multi-session time-control string */
-    int moves=1; /* kludge to force reading of first session */
+    int incType, moves=1; /* kludge to force reading of first session */
     long time, increment;
-    char *s = fullTimeControlString;
+    char *s = tcString;
 
-    if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
+    if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
+    if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
     do {
-        if(moves) NextSessionFromString(&s, &moves, &time, &increment);
+        if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
+        nextSession = s; suddenDeath = moves == 0 && increment == 0;
         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
         if(movenr == -1) return time;    /* last move before new session     */
+        if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
+        if(incType == '!' && lastUsed < increment) increment = lastUsed;
         if(!moves) return increment;     /* current session is incremental   */
         if(movenr >= 0) movenr -= moves; /* we already finished this session */
     } while(movenr >= -1);               /* try again for next session       */
@@ -1010,48 +1070,55 @@ ParseTimeControl(tc, ti, mps)
 {
   long tc1;
   long tc2;
-  char buf[MSG_SIZ];
-  
+  char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
+  int min, sec=0;
+  int len;
+
   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
+  if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
+      sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
   if(ti > 0) {
+
     if(mps)
-      sprintf(buf, "+%d/%s+%d", mps, tc, ti);
-    else sprintf(buf, "+%s+%d", tc, ti);
+      snprintf(buf, MSG_SIZ, ":%d/%s+%d", mps, mytc, ti);
+    else 
+      snprintf(buf, MSG_SIZ, ":%s+%d", mytc, ti);
   } else {
     if(mps)
-             sprintf(buf, "+%d/%s", mps, tc);
-    else sprintf(buf, "+%s", tc);
+      snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
+    else 
+      snprintf(buf, MSG_SIZ, ":%s", mytc);
   }
-  fullTimeControlString = StrSave(buf);
+  fullTimeControlString = StrSave(buf); // this should now be in PGN format
   
   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
     return FALSE;
   }
-  
+
   if( *tc == '/' ) {
     /* Parse second time control */
     tc++;
-    
+
     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
       return FALSE;
     }
-    
+
     if( tc2 == 0 ) {
       return FALSE;
     }
-    
+
     timeControl_2 = tc2 * 1000;
   }
   else {
     timeControl_2 = 0;
   }
-  
+
   if( tc1 == 0 ) {
     return FALSE;
   }
-  
+
   timeControl = tc1 * 1000;
-  
+
   if (ti >= 0) {
     timeIncrement = ti * 1000;  /* convert to ms */
     movesPerSession = 0;
@@ -1097,25 +1164,34 @@ InitBackEnd3 P((void))
 {
     GameMode initialMode;
     char buf[MSG_SIZ];
-    int err;
+    int err, len;
 
     InitChessProgram(&first, startedFromSetupPosition);
 
+    if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
+       free(programVersion);
+       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
+    }
 
     if (appData.icsActive) {
 #ifdef WIN32
         /* [DM] Make a console window if needed [HGM] merged ifs */
-        ConsoleCreate(); 
+        ConsoleCreate();
 #endif
        err = establish();
-       if (err != 0) {
-           if (*appData.icsCommPort != NULLCHAR) {
-               sprintf(buf, _("Could not open comm port %s"),  
-                       appData.icsCommPort);
-           } else {
-               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
+       if (err != 0)
+         {
+           if (*appData.icsCommPort != NULLCHAR)
+             len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
+                            appData.icsCommPort);
+           else
+             len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
                        appData.icsHost, appData.icsPort);
-           }
+
+           if( (len > MSG_SIZ) && appData.debugMode )
+             fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
+
            DisplayFatalError(buf, err, 1);
            return;
        }
@@ -1138,7 +1214,7 @@ InitBackEnd3 P((void))
        cmailISR =
          AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
     }
-    
+
     ThawUI();
     DisplayMessage("", "");
     if (StrCaseCmp(appData.initialMode, "") == 0) {
@@ -1146,7 +1222,7 @@ InitBackEnd3 P((void))
     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
       initialMode = TwoMachinesPlay;
     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
-      initialMode = AnalyzeFile; 
+      initialMode = AnalyzeFile;
     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
       initialMode = AnalyzeMode;
     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
@@ -1160,7 +1236,10 @@ InitBackEnd3 P((void))
     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
       initialMode = Training;
     } else {
-      sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
+      len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
+      if( (len > MSG_SIZ) && appData.debugMode )
+       fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
+
       DisplayFatalError(buf, 0, 2);
       return;
     }
@@ -1321,7 +1400,7 @@ establish()
                        appData.icsHost, appData.icsPort);
            } else {
                snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
-                       appData.remoteShell, appData.gateway, 
+                       appData.remoteShell, appData.gateway,
                        appData.remoteUser, appData.telnetProgram,
                        appData.icsHost, appData.icsPort);
            }
@@ -1339,6 +1418,19 @@ establish()
     }
 }
 
+void EscapeExpand(char *p, char *q)
+{      // [HGM] initstring: routine to shape up string arguments
+       while(*p++ = *q++) if(p[-1] == '\\')
+           switch(*q++) {
+               case 'n': p[-1] = '\n'; break;
+               case 'r': p[-1] = '\r'; break;
+               case 't': p[-1] = '\t'; break;
+               case '\\': p[-1] = '\\'; break;
+               case 0: *p = 0; return;
+               default: p[-1] = q[-1]; break;
+           }
+}
+
 void
 show_bytes(fp, buf, count)
      FILE *fp;
@@ -1506,7 +1598,7 @@ SendToICSDelayed(s,msdelay)
 
 
 /* Remove all highlighting escape sequences in s
-   Also deletes any suffix starting with '(' 
+   Also deletes any suffix starting with '('
    */
 char *
 StripHighlightAndTitle(s)
@@ -1573,6 +1665,7 @@ StringToVariant(e)
     VariantClass v = VariantNormal;
     int i, found = FALSE;
     char buf[MSG_SIZ];
+    int len;
 
     if (!e) return v;
 
@@ -1596,7 +1689,7 @@ StringToVariant(e)
 
     if (!found) {
       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
-         || StrCaseStr(e, "wild/fr") 
+         || StrCaseStr(e, "wild/fr")
          || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
         v = VariantFischeRandom;
       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
@@ -1674,7 +1767,7 @@ StringToVariant(e)
          v = VariantShatranj;
          break;
 
-       /* Temporary names for future ICC types.  The name *will* change in 
+       /* Temporary names for future ICC types.  The name *will* change in
           the next xboard/WinBoard release after ICC defines it. */
        case 29:
          v = Variant29;
@@ -1748,7 +1841,10 @@ StringToVariant(e)
          v = VariantNormal;
          break;
        default:
-         sprintf(buf, _("Unknown wild type %d"), wnum);
+         len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
+         if( (len > MSG_SIZ) && appData.debugMode )
+           fprintf(debugFP, "StringToVariant: buffer truncated.\n");
+
          DisplayError(buf, 0);
          v = VariantUnknown;
          break;
@@ -1781,7 +1877,7 @@ looking_at(buf, index, pattern)
     char *bufp = &buf[*index], *patternp = pattern;
     int star_count = 0;
     char *matchp = star_match[0];
-    
+
     for (;;) {
        if (*patternp == NULLCHAR) {
            *index = leftover_start = bufp - buf;
@@ -1888,7 +1984,7 @@ TelnetRequest(ddww, option)
            break;
          default:
            ddwwStr = buf1;
-           sprintf(buf1, "%d", ddww);
+           snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
            break;
        }
        switch (option) {
@@ -1897,7 +1993,7 @@ TelnetRequest(ddww, option)
            break;
          default:
            optionStr = buf2;
-           sprintf(buf2, "%d", option);
+           snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
            break;
        }
        fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
@@ -1971,7 +2067,7 @@ void
 VariantSwitch(Board board, VariantClass newVariant)
 {
    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
-   Board oldBoard;
+   static Board oldBoard;
 
    startedFromPositionFile = FALSE;
    if(gameInfo.variant == newVariant) return;
@@ -1988,7 +2084,7 @@ VariantSwitch(Board board, VariantClass newVariant)
     * case we want to add those holdings to the already received position.
     */
 
-   
+
    if (appData.debugMode) {
      fprintf(debugFP, "Switch board from %s to %s\n",
             VariantName(gameInfo.variant), VariantName(newVariant));
@@ -1996,7 +2092,7 @@ VariantSwitch(Board board, VariantClass newVariant)
    }
    shuffleOpenings = 0;       /* [HGM] shuffle */
    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
-   switch(newVariant) 
+   switch(newVariant)
      {
      case VariantShogi:
        newWidth = 9;  newHeight = 9;
@@ -2017,14 +2113,14 @@ VariantSwitch(Board board, VariantClass newVariant)
      default:
        newHoldingsWidth = gameInfo.holdingsSize = 0;
      };
-   
+
    if(newWidth  != gameInfo.boardWidth  ||
       newHeight != gameInfo.boardHeight ||
       newHoldingsWidth != gameInfo.holdingsWidth ) {
-     
+
      /* shift position to new playing area, if needed */
      if(newHoldingsWidth > gameInfo.holdingsWidth) {
-       for(i=0; i<BOARD_HEIGHT; i++) 
+       for(i=0; i<BOARD_HEIGHT; i++)
         for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
           board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
             board[i][j];
@@ -2064,6 +2160,204 @@ static int player2Rating = -1;
 ColorClass curColor = ColorNormal;
 int suppressKibitz = 0;
 
+// [HGM] seekgraph
+Boolean soughtPending = FALSE;
+Boolean seekGraphUp;
+#define MAX_SEEK_ADS 200
+#define SQUARE 0x80
+char *seekAdList[MAX_SEEK_ADS];
+int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
+float tcList[MAX_SEEK_ADS];
+char colorList[MAX_SEEK_ADS];
+int nrOfSeekAds = 0;
+int minRating = 1010, maxRating = 2800;
+int hMargin = 10, vMargin = 20, h, w;
+extern int squareSize, lineGap;
+
+void
+PlotSeekAd(int i)
+{
+       int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
+       xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
+       if(r < minRating+100 && r >=0 ) r = minRating+100;
+       if(r > maxRating) r = maxRating;
+       if(tc < 1.) tc = 1.;
+       if(tc > 95.) tc = 95.;
+       x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
+       y = ((double)r - minRating)/(maxRating - minRating)
+           * (h-vMargin-squareSize/8-1) + vMargin;
+       if(ratingList[i] < 0) y = vMargin + squareSize/4;
+       if(strstr(seekAdList[i], " u ")) color = 1;
+       if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
+          !strstr(seekAdList[i], "bullet") &&
+          !strstr(seekAdList[i], "blitz") &&
+          !strstr(seekAdList[i], "standard") ) color = 2;
+       if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
+       DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
+}
+
+void
+AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
+{
+       char buf[MSG_SIZ], *ext = "";
+       VariantClass v = StringToVariant(type);
+       if(strstr(type, "wild")) {
+           ext = type + 4; // append wild number
+           if(v == VariantFischeRandom) type = "chess960"; else
+           if(v == VariantLoadable) type = "setup"; else
+           type = VariantName(v);
+       }
+       snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
+       if(nrOfSeekAds < MAX_SEEK_ADS-1) {
+           if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
+           ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
+           sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
+           tcList[nrOfSeekAds] = base + (2./3.)*inc;
+           seekNrList[nrOfSeekAds] = nr;
+           zList[nrOfSeekAds] = 0;
+           seekAdList[nrOfSeekAds++] = StrSave(buf);
+           if(plot) PlotSeekAd(nrOfSeekAds-1);
+       }
+}
+
+void
+EraseSeekDot(int i)
+{
+    int x = xList[i], y = yList[i], d=squareSize/4, k;
+    DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
+    if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
+    // now replot every dot that overlapped
+    for(k=0; k<nrOfSeekAds; k++) if(k != i) {
+       int xx = xList[k], yy = yList[k];
+       if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
+           DrawSeekDot(xx, yy, colorList[k]);
+    }
+}
+
+void
+RemoveSeekAd(int nr)
+{
+       int i;
+       for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
+           EraseSeekDot(i);
+           if(seekAdList[i]) free(seekAdList[i]);
+           seekAdList[i] = seekAdList[--nrOfSeekAds];
+           seekNrList[i] = seekNrList[nrOfSeekAds];
+           ratingList[i] = ratingList[nrOfSeekAds];
+           colorList[i]  = colorList[nrOfSeekAds];
+           tcList[i] = tcList[nrOfSeekAds];
+           xList[i]  = xList[nrOfSeekAds];
+           yList[i]  = yList[nrOfSeekAds];
+           zList[i]  = zList[nrOfSeekAds];
+           seekAdList[nrOfSeekAds] = NULL;
+           break;
+       }
+}
+
+Boolean
+MatchSoughtLine(char *line)
+{
+    char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
+    int nr, base, inc, u=0; char dummy;
+
+    if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
+       sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
+       (u=1) &&
+       (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
+        sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
+       // match: compact and save the line
+       AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
+       return TRUE;
+    }
+    return FALSE;
+}
+
+int
+DrawSeekGraph()
+{
+    int i;
+    if(!seekGraphUp) return FALSE;
+    h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
+    w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
+
+    DrawSeekBackground(0, 0, w, h);
+    DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
+    DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
+    for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
+       int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
+       yy = h-1-yy;
+       DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
+       if(i%500 == 0) {
+           char buf[MSG_SIZ];
+           snprintf(buf, MSG_SIZ, "%d", i);
+           DrawSeekText(buf, hMargin+squareSize/8+7, yy);
+       }
+    }
+    DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
+    for(i=1; i<100; i+=(i<10?1:5)) {
+       int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
+       DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
+       if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
+           char buf[MSG_SIZ];
+           snprintf(buf, MSG_SIZ, "%d", i);
+           DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
+       }
+    }
+    for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
+    return TRUE;
+}
+
+int SeekGraphClick(ClickType click, int x, int y, int moving)
+{
+    static int lastDown = 0, displayed = 0, lastSecond;
+    if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
+       if(click == Release || moving) return FALSE;
+       nrOfSeekAds = 0;
+       soughtPending = TRUE;
+       SendToICS(ics_prefix);
+       SendToICS("sought\n"); // should this be "sought all"?
+    } else { // issue challenge based on clicked ad
+       int dist = 10000; int i, closest = 0, second = 0;
+       for(i=0; i<nrOfSeekAds; i++) {
+           int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
+           if(d < dist) { dist = d; closest = i; }
+           second += (d - zList[i] < 120); // count in-range ads
+           if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
+       }
+       if(dist < 120) {
+           char buf[MSG_SIZ];
+           second = (second > 1);
+           if(displayed != closest || second != lastSecond) {
+               DisplayMessage(second ? "!" : "", seekAdList[closest]);
+               lastSecond = second; displayed = closest;
+           }
+           if(click == Press) {
+               if(moving == 2) zList[closest] = 100; // right-click; push to back on press
+               lastDown = closest;
+               return TRUE;
+           } // on press 'hit', only show info
+           if(moving == 2) return TRUE; // ignore right up-clicks on dot
+           snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
+           SendToICS(ics_prefix);
+           SendToICS(buf);
+           return TRUE; // let incoming board of started game pop down the graph
+       } else if(click == Release) { // release 'miss' is ignored
+           zList[lastDown] = 100; // make future selection of the rejected ad more difficult
+           if(moving == 2) { // right up-click
+               nrOfSeekAds = 0; // refresh graph
+               soughtPending = TRUE;
+               SendToICS(ics_prefix);
+               SendToICS("sought\n"); // should this be "sought all"?
+           }
+           return TRUE;
+       } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
+       // press miss or release hit 'pop down' seek graph
+       seekGraphUp = FALSE;
+       DrawPosition(TRUE, NULL);
+    }
+    return TRUE;
+}
+
 void
 read_from_ics(isr, closure, data, count, error)
      InputSourceRef isr;
@@ -2081,7 +2375,7 @@ read_from_ics(isr, closure, data, count, error)
 #define STARTED_CHATTER 5
 #define STARTED_COMMENT 6
 #define STARTED_MOVES_NOHIDE 7
-    
+
     static int started = STARTED_NONE;
     static char parse[20000];
     static int parse_pos = 0;
@@ -2091,7 +2385,7 @@ read_from_ics(isr, closure, data, count, error)
     static int savingComment = FALSE;
     static int cmatch = 0; // continuation sequence match
     char *bp;
-    char str[500];
+    char str[MSG_SIZ];
     int i, oldi;
     int buf_len;
     int next_out;
@@ -2189,7 +2483,7 @@ read_from_ics(isr, closure, data, count, error)
 //     next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
        next_out = 0;
        leftover_start = 0;
-       
+
        i = 0;
        while (i < buf_len) {
            /* Deal with part of the TELNET option negotiation
@@ -2301,23 +2595,27 @@ read_from_ics(isr, closure, data, count, error)
                  next_out = i;
                continue;
            }
-               
+
            /* OK, this at least will *usually* work */
            if (!loggedOn && looking_at(buf, &i, "ics%")) {
                loggedOn = TRUE;
            }
-           
+
            if (loggedOn && !intfSet) {
                if (ics_type == ICS_ICC) {
-                 sprintf(str,
+                 snprintf(str, MSG_SIZ,
                          "/set-quietly interface %s\n/set-quietly style 12\n",
                          programVersion);
+                 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
+                     strcat(str, "/set-2 51 1\n/set seek 1\n");
                } else if (ics_type == ICS_CHESSNET) {
-                 sprintf(str, "/style 12\n");
+                 snprintf(str, MSG_SIZ, "/style 12\n");
                } else {
-                 strcpy(str, "alias $ @\n$set interface ");
+                 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
                  strcat(str, programVersion);
                  strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
+                 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
+                     strcat(str, "$iset seekremove 1\n$set seek 1\n");
 #ifdef WIN32
                  strcat(str, "$iset nohighlight 1\n");
 #endif
@@ -2335,9 +2633,10 @@ read_from_ics(isr, closure, data, count, error)
                    parse[parse_pos] = NULLCHAR;
                    if(chattingPartner>=0) {
                        char mess[MSG_SIZ];
-                       sprintf(mess, "%s%s", talker, parse);
+                       snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
                        OutputChatMessage(chattingPartner, mess);
                        chattingPartner = -1;
+                       next_out = i+1; // [HGM] suppress printing in ICS window
                    } else
                    if(!suppressKibitz) // [HGM] kibitz
                        AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
@@ -2361,12 +2660,12 @@ read_from_ics(isr, closure, data, count, error)
                                pvInfoList[forwardMostMove-1].score = 100*score;
                            }
                            OutputKibitz(suppressKibitz, parse);
-                           next_out = i+1; // [HGM] suppress printing in ICS window
                        } else {
                            char tmp[MSG_SIZ];
-                           sprintf(tmp, _("your opponent kibitzes: %s"), parse);
+                           snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
                            SendToPlayer(tmp, strlen(tmp));
                        }
+                       next_out = i+1; // [HGM] suppress printing in ICS window
                    }
                    started = STARTED_NONE;
                } else {
@@ -2382,6 +2681,7 @@ read_from_ics(isr, closure, data, count, error)
                    continue;
                }
                started = STARTED_NONE;
+               if(suppressKibitz) next_out = i+1;
            }
 
             /* Kludge to deal with rcmd protocol */
@@ -2418,7 +2718,7 @@ read_from_ics(isr, closure, data, count, error)
                (looking_at(buf, &i, "\"*\" is *a registered name") ||
                 looking_at(buf, &i, "Logging you in as \"*\"") ||
                 looking_at(buf, &i, "will be \"*\""))) {
-             strcpy(ics_handle, star_match[0]);
+             safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
              continue;
            }
 
@@ -2439,6 +2739,50 @@ read_from_ics(isr, closure, data, count, error)
              continue;
            }
 
+           oldi = i;
+           // [HGM] seekgraph: recognize sought lines and end-of-sought message
+           if(appData.seekGraph) {
+               if(soughtPending && MatchSoughtLine(buf+i)) {
+                   i = strstr(buf+i, "rated") - buf;
+                   if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                   next_out = leftover_start = i;
+                   started = STARTED_CHATTER;
+                   suppressKibitz = TRUE;
+                   continue;
+               }
+               if((gameMode == IcsIdle || gameMode == BeginningOfGame)
+                       && looking_at(buf, &i, "* ads displayed")) {
+                   soughtPending = FALSE;
+                   seekGraphUp = TRUE;
+                   DrawSeekGraph();
+                   continue;
+               }
+               if(appData.autoRefresh) {
+                   if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
+                       int s = (ics_type == ICS_ICC); // ICC format differs
+                       if(seekGraphUp)
+                       AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
+                             star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
+                       looking_at(buf, &i, "*% "); // eat prompt
+                       if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
+                       if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                       next_out = i; // suppress
+                       continue;
+                   }
+                   if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
+                       char *p = star_match[0];
+                       while(*p) {
+                           if(seekGraphUp) RemoveSeekAd(atoi(p));
+                           while(*p && *p++ != ' '); // next
+                       }
+                       looking_at(buf, &i, "*% "); // eat prompt
+                       if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                       next_out = i;
+                       continue;
+                   }
+               }
+           }
+
            /* skip formula vars */
            if (started == STARTED_NONE &&
                buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
@@ -2447,15 +2791,16 @@ read_from_ics(isr, closure, data, count, error)
              continue;
            }
 
-           oldi = i;
            // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
-           if (appData.autoKibitz && started == STARTED_NONE && 
+           if (appData.autoKibitz && started == STARTED_NONE &&
                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
                (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
-               if(looking_at(buf, &i, "* kibitzes: ") &&
-                  (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
+               if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
+                  (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
                    StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
                        suppressKibitz = TRUE;
+                       if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                       next_out = i;
                        if((StrStr(star_match[0], gameInfo.white) == star_match[0]
                                && (gameMode == IcsPlayingWhite)) ||
                           (StrStr(star_match[0], gameInfo.black) == star_match[0]
@@ -2467,25 +2812,36 @@ read_from_ics(isr, closure, data, count, error)
                            savingComment = TRUE;
                            suppressKibitz = gameMode != IcsObserving ? 2 :
                                (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
-                       } 
+                       }
                        continue;
                } else
-               if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
+               if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
+                   looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
+                        && atoi(star_match[0])) {
                    // suppress the acknowledgements of our own autoKibitz
+                   char *p;
+                   if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                   if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
                    SendToPlayer(star_match[0], strlen(star_match[0]));
-                   looking_at(buf, &i, "*% "); // eat prompt
+                   if(looking_at(buf, &i, "*% ")) // eat prompt
+                       suppressKibitz = FALSE;
                    next_out = i;
+                   continue;
                }
            } // [HGM] kibitz: end of patch
 
-//if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
-
            // [HGM] chat: intercept tells by users for which we have an open chat window
            channel = -1;
-           if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
+           if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
                                           looking_at(buf, &i, "* whispers:") ||
+                                          looking_at(buf, &i, "* kibitzes:") ||
+                                          looking_at(buf, &i, "* shouts:") ||
+                                          looking_at(buf, &i, "* c-shouts:") ||
+                                          looking_at(buf, &i, "--> * ") ||
                                           looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
-                                          looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
+                                          looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
+                                          looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
+                                          looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
                int p;
                sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
                chattingPartner = -1;
@@ -2493,27 +2849,57 @@ read_from_ics(isr, closure, data, count, error)
                if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
                for(p=0; p<MAX_CHAT; p++) {
                    if(channel == atoi(chatPartner[p])) {
-                   talker[0] = '['; strcat(talker, "]");
+                   talker[0] = '['; strcat(talker, "] ");
+                   Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
                    chattingPartner = p; break;
                    }
                } else
+               if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
+               for(p=0; p<MAX_CHAT; p++) {
+                   if(!strcmp("kibitzes", chatPartner[p])) {
+                       talker[0] = '['; strcat(talker, "] ");
+                       chattingPartner = p; break;
+                   }
+               } else
                if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
                for(p=0; p<MAX_CHAT; p++) {
-                   if(!strcmp("WHISPER", chatPartner[p])) {
-                       talker[0] = '['; strcat(talker, "]");
+                   if(!strcmp("whispers", chatPartner[p])) {
+                       talker[0] = '['; strcat(talker, "] ");
                        chattingPartner = p; break;
                    }
+               } else
+               if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
+                 if(buf[i-8] == '-' && buf[i-3] == 't')
+                 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
+                   if(!strcmp("c-shouts", chatPartner[p])) {
+                       talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
+                       chattingPartner = p; break;
+                   }
+                 }
+                 if(chattingPartner < 0)
+                 for(p=0; p<MAX_CHAT; p++) {
+                   if(!strcmp("shouts", chatPartner[p])) {
+                       if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
+                       else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
+                       else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
+                       chattingPartner = p; break;
+                   }
+                 }
                }
                if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
                for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
-                   talker[0] = 0;
+                   talker[0] = 0; Colorize(ColorTell, FALSE);
                    chattingPartner = p; break;
                }
                if(chattingPartner<0) i = oldi; else {
+                   Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
+                   if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
+                   if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
                    started = STARTED_COMMENT;
                    parse_pos = 0; parse[0] = NULLCHAR;
-                   savingComment = TRUE;
+                   savingComment = 3 + chattingPartner; // counts as TRUE
                    suppressKibitz = TRUE;
+                   continue;
                }
            } // [HGM] chat: end of patch
 
@@ -2521,18 +2907,9 @@ read_from_ics(isr, closure, data, count, error)
                 /* [DM] Backup address for color zippy lines */
                 backup = i;
 #if ZIPPY
-       #ifdef WIN32
                if (loggedOn == TRUE)
                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
-       #else
-                if (ZippyControl(buf, &i) ||
-                    ZippyConverse(buf, &i) ||
-                    (appData.zippyPlay && ZippyMatch(buf, &i))) {
-                     loggedOn = TRUE;
-                      if (!appData.colorize) continue;
-               }
-       #endif
 #endif
            } // [DM] 'else { ' deleted
                if (
@@ -2707,6 +3084,8 @@ read_from_ics(isr, closure, data, count, error)
                    memcpy(parse, &buf[oldi], parse_pos);
                    parse[parse_pos] = NULLCHAR;
                    started = STARTED_COMMENT;
+                   if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
+                       chattingPartner = savingComment - 3; // kludge to remember the box
                } else {
                    started = STARTED_CHATTER;
                }
@@ -2725,14 +3104,14 @@ read_from_ics(isr, closure, data, count, error)
                SendToICS("refresh\n");
                continue;
            }
-           
+
            if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
                ICSInitScript();
                have_sent_ICS_logon = 1;
                continue;
            }
-             
-           if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
+
+           if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
                (looking_at(buf, &i, "\n<12> ") ||
                 looking_at(buf, &i, "<12> "))) {
                loggedOn = TRUE;
@@ -2815,7 +3194,7 @@ read_from_ics(isr, closure, data, count, error)
                    gameInfo.whiteRating = string_to_rating(star_match[1]);
                    gameInfo.blackRating = string_to_rating(star_match[3]);
                    if (appData.debugMode)
-                     fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
+                     fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
                              gameInfo.whiteRating, gameInfo.blackRating);
                }
                continue;
@@ -2826,7 +3205,7 @@ read_from_ics(isr, closure, data, count, error)
                /* Header for a move list -- second line */
                /* Initial board will follow if this is a wild game */
                if (gameInfo.event != NULL) free(gameInfo.event);
-               sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
+               snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
                gameInfo.event = StrSave(str);
                 /* [HGM] we switched variant. Translate boards if needed. */
                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
@@ -2869,11 +3248,16 @@ read_from_ics(isr, closure, data, count, error)
                    break;
                }
                continue;
-           }                           
-           
+           }
+
            if (looking_at(buf, &i, "% ") ||
                ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
                 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
+               if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
+                   soughtPending = FALSE;
+                   seekGraphUp = TRUE;
+                   DrawSeekGraph();
+               }
                if(suppressKibitz) next_out = i;
                savingComment = FALSE;
                suppressKibitz = 0;
@@ -2890,7 +3274,7 @@ read_from_ics(isr, closure, data, count, error)
                            if (WhiteOnMove(forwardMostMove)) {
                                if (first.sendTime) {
                                  if (first.useColors) {
-                                   SendToProgram("black\n", &first); 
+                                   SendToProgram("black\n", &first);
                                  }
                                  SendTimeRemaining(&first, TRUE);
                                }
@@ -2934,7 +3318,7 @@ read_from_ics(isr, closure, data, count, error)
                                  firstMove = TRUE;
                                }
                            }
-                       }                       
+                       }
                    }
 #endif
                    if (gameMode == IcsObserving && ics_gamenum == -1) {
@@ -2946,7 +3330,7 @@ read_from_ics(isr, closure, data, count, error)
                        flipView = appData.flipView;
                        DrawPosition(TRUE, boards[currentMove]);
                        DisplayBothClocks();
-                       sprintf(str, "%s vs. %s",
+                       snprintf(str, MSG_SIZ, "%s vs. %s",
                                gameInfo.white, gameInfo.black);
                        DisplayTitle(str);
                        gameMode = IcsIdle;
@@ -2977,24 +3361,24 @@ read_from_ics(isr, closure, data, count, error)
                if(bookHit) { // [HGM] book: simulate book reply
                    static char bookMove[MSG_SIZ]; // a bit generous?
 
-                   programStats.nodes = programStats.depth = programStats.time = 
+                   programStats.nodes = programStats.depth = programStats.time =
                    programStats.score = programStats.got_only_move = 0;
                    sprintf(programStats.movelist, "%s (xbook)", bookHit);
 
-                   strcpy(bookMove, "move ");
+                   safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
                    strcat(bookMove, bookHit);
                    HandleMachineMove(bookMove, &first);
                }
                continue;
            }
-           
+
            if ((started == STARTED_MOVES || started == STARTED_BOARD ||
                 started == STARTED_HOLDINGS ||
                 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
                /* Accumulate characters in move list or board */
                parse[parse_pos++] = buf[i];
            }
-           
+
            /* Start of game messages.  Mostly we detect start of game
               when the first board image arrives.  On some versions
               of the ICS, though, we need to do a "refresh" after starting
@@ -3016,18 +3400,18 @@ read_from_ics(isr, closure, data, count, error)
                } else {
                    player = star_match[2];
                }
-               sprintf(str, "%sobserve %s\n",
+               snprintf(str, MSG_SIZ, "%sobserve %s\n",
                        ics_prefix, StripHighlightAndTitle(player));
                SendToICS(str);
 
                /* Save ratings from notify string */
-               strcpy(player1Name, star_match[0]);
+               safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
                player1Rating = string_to_rating(star_match[1]);
-               strcpy(player2Name, star_match[2]);
+               safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
                player2Rating = string_to_rating(star_match[3]);
 
                if (appData.debugMode)
-                 fprintf(debugFP, 
+                 fprintf(debugFP,
                          "Ratings from 'Game notification:' %s %d, %s %d\n",
                          player1Name, player1Rating,
                          player2Name, player2Rating);
@@ -3057,8 +3441,8 @@ read_from_ics(isr, closure, data, count, error)
                    SendToICS("refresh\n");
                }
                continue;
-           }    
-           
+           }
+
            /* Error messages */
 //         if (ics_user_moved) {
            if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
@@ -3069,10 +3453,10 @@ read_from_ics(isr, closure, data, count, error)
                    looking_at(buf, &i, "It is not your move")) {
                    /* Illegal move */
                    if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
-                       currentMove = --forwardMostMove;
+                       currentMove = forwardMostMove-1;
                        DisplayMove(currentMove - 1); /* before DMError */
                        DrawPosition(FALSE, boards[currentMove]);
-                       SwitchClocks();
+                       SwitchClocks(forwardMostMove-1); // [HGM] race
                        DisplayBothClocks();
                    }
                    DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
@@ -3121,24 +3505,24 @@ read_from_ics(isr, closure, data, count, error)
                   2    empty, white, or black (IGNORED)
                   3    player 2 name (not necessarily black)
                   4    player 2 rating
-                  
+
                   The names/ratings are sorted out when the game
                   actually starts (below).
                */
-               strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
-               player1Rating = string_to_rating(star_match[1]);
-               strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
-               player2Rating = string_to_rating(star_match[4]);
+               safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
+               player1Rating = string_to_rating(star_match[1]);
+               safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
+               player2Rating = string_to_rating(star_match[4]);
 
                if (appData.debugMode)
-                 fprintf(debugFP, 
+                 fprintf(debugFP,
                          "Ratings from 'Creating:' %s %d, %s %d\n",
                          player1Name, player1Rating,
                          player2Name, player2Rating);
 
                continue;
            }
-           
+
            /* Improved generic start/end-of-game messages */
            if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
                (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
@@ -3172,13 +3556,14 @@ read_from_ics(isr, closure, data, count, error)
                if (strncmp(why, "Creating ", 9) == 0 ||
                    strncmp(why, "Continuing ", 11) == 0) {
                    gs_gamenum = gamenum;
-                   strcpy(gs_kind, strchr(why, ' ') + 1);
+                   safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
                    VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
 #if ZIPPY
                    if (appData.zippyPlay) {
                        ZippyGameStart(whitename, blackname);
                    }
 #endif /*ZIPPY*/
+                   partnerBoardValid = FALSE; // [HGM] bughouse
                    continue;
                }
 
@@ -3219,6 +3604,7 @@ read_from_ics(isr, closure, data, count, error)
                    Reset(TRUE, TRUE);
                }
 #endif /*ZIPPY*/
+               if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
                continue;
            }
 
@@ -3276,14 +3662,14 @@ read_from_ics(isr, closure, data, count, error)
                      if (currentMove == 0 &&
                          gameMode == IcsPlayingWhite &&
                          appData.premoveWhite) {
-                       sprintf(str, "%s\n", appData.premoveWhiteText);
+                       snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
                        if (appData.debugMode)
                          fprintf(debugFP, "Sending premove:\n");
                        SendToICS(str);
                      } else if (currentMove == 1 &&
                                 gameMode == IcsPlayingBlack &&
                                 appData.premoveBlack) {
-                       sprintf(str, "%s\n", appData.premoveBlackText);
+                       snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
                        if (appData.debugMode)
                          fprintf(debugFP, "Sending premove:\n");
                        SendToICS(str);
@@ -3292,8 +3678,8 @@ read_from_ics(isr, closure, data, count, error)
                        ClearPremoveHighlights();
                        if (appData.debugMode)
                          fprintf(debugFP, "Sending premove:\n");
-                          UserMoveEvent(premoveFromX, premoveFromY, 
-                                       premoveToX, premoveToY, 
+                          UserMoveEvent(premoveFromX, premoveFromY,
+                                       premoveToX, premoveToY,
                                         premovePromoChar);
                      }
                    }
@@ -3315,8 +3701,8 @@ read_from_ics(isr, closure, data, count, error)
                    if (appData.debugMode)
                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
                                                         parse, currentMove);
-                   if (sscanf(parse, " game %d", &gamenum) == 1 &&
-                       gamenum == ics_gamenum) {
+                   if (sscanf(parse, " game %d", &gamenum) == 1) {
+                     if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
                        if (gameInfo.variant == VariantNormal) {
                           /* [HGM] We seem to switch variant during a game!
                            * Presumably no holdings were displayed, so we have
@@ -3334,7 +3720,7 @@ read_from_ics(isr, closure, data, count, error)
                             will tell us whether this is really bug or zh */
                          if (ics_getting_history == H_FALSE) {
                            ics_getting_history = H_REQUESTED;
-                           sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+                           snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
                            SendToICS(str);
                          }
                        }
@@ -3358,16 +3744,29 @@ read_from_ics(isr, closure, data, count, error)
                            char wh[16], bh[16];
                            PackHolding(wh, white_holding);
                            PackHolding(bh, black_holding);
-                           sprintf(str, "[%s-%s] %s-%s", wh, bh,
+                           snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
                                    gameInfo.white, gameInfo.black);
                        } else {
-                           sprintf(str, "%s [%s] vs. %s [%s]",
+                         snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
                                    gameInfo.white, white_holding,
                                    gameInfo.black, black_holding);
                        }
-
+                       if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
                         DrawPosition(FALSE, boards[currentMove]);
                        DisplayTitle(str);
+                     } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
+                       sscanf(parse, "game %d white [%s black [%s <- %s",
+                              &gamenum, white_holding, black_holding,
+                              new_piece);
+                        white_holding[strlen(white_holding)-1] = NULLCHAR;
+                        black_holding[strlen(black_holding)-1] = NULLCHAR;
+                        /* [HGM] copy holdings to partner-board holdings area */
+                        CopyHoldings(partnerBoard, white_holding, WhitePawn);
+                        CopyHoldings(partnerBoard, black_holding, BlackPawn);
+                        if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
+                        if(partnerUp) DrawPosition(FALSE, partnerBoard);
+                        if(twoBoards) { partnerUp = 0; flipView = !flipView; }
+                     }
                    }
                    /* Suppress following prompt */
                    if (looking_at(buf, &i, "*% ")) {
@@ -3382,7 +3781,7 @@ read_from_ics(isr, closure, data, count, error)
 
            i++;                /* skip unparsed character and loop back */
        }
-       
+
        if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
 //         started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
 //         SendToPlayer(&buf[next_out], i - next_out);
@@ -3390,11 +3789,11 @@ read_from_ics(isr, closure, data, count, error)
            SendToPlayer(&buf[next_out], leftover_start - next_out);
            next_out = i;
        }
-       
+
        leftover_len = buf_len - leftover_start;
        /* if buffer ends with something we couldn't parse,
           reparse it after appending the next read */
-       
+
     } else if (count == 0) {
        RemoveInputSource(isr);
         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
@@ -3405,13 +3804,13 @@ read_from_ics(isr, closure, data, count, error)
 
 
 /* Board style 12 looks like this:
-   
+
    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
-   
+
  * The "<12> " is stripped before it gets to this routine.  The two
  * trailing 0's (flip state and clock ticking) are later addition, and
  * some chess servers may not have them, or may have only the first.
- * Additional trailing fields may be added in the future.  
+ * Additional trailing fields may be added in the future.
  */
 
 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
@@ -3427,7 +3826,7 @@ read_from_ics(isr, closure, data, count, error)
 void
 ParseBoard12(string)
      char *string;
-{ 
+{
     GameMode newGameMode;
     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
@@ -3446,7 +3845,7 @@ ParseBoard12(string)
     Boolean weird = FALSE, reqFlag = FALSE;
 
     fromX = fromY = toX = toY = -1;
-    
+
     newGame = FALSE;
 
     if (appData.debugMode)
@@ -3487,7 +3886,7 @@ ParseBoard12(string)
                        0, 1);
       return;
     }
-    
+
     switch (relation) {
       case RELATION_OBSERVING_PLAYED:
       case RELATION_OBSERVING_STATIC:
@@ -3509,14 +3908,46 @@ ParseBoard12(string)
       case RELATION_ISOLATED_BOARD:
       default:
        /* Just display this board.  If user was doing something else,
-          we will forget about it until the next board comes. */ 
+          we will forget about it until the next board comes. */
        newGameMode = IcsIdle;
        break;
       case RELATION_STARTING_POSITION:
        newGameMode = gameMode;
        break;
     }
-    
+
+    if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
+        && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
+      // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
+      char *toSqr;
+      for (k = 0; k < ranks; k++) {
+        for (j = 0; j < files; j++)
+          board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
+        if(gameInfo.holdingsWidth > 1) {
+             board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
+             board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
+        }
+      }
+      CopyBoard(partnerBoard, board);
+      if(toSqr = strchr(str, '/')) { // extract highlights from long move
+        partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
+        partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
+      } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
+      if(toSqr = strchr(str, '-')) {
+        partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
+        partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
+      } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
+      if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
+      if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
+      if(partnerUp) DrawPosition(FALSE, partnerBoard);
+      if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
+      snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
+                (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
+      DisplayMessage(partnerStatus, "");
+       partnerBoardValid = TRUE;
+      return;
+    }
+
     /* Modify behavior for initial board display on move listing
        of wild games.
        */
@@ -3550,7 +3981,7 @@ ParseBoard12(string)
        return;
     }
 
-   if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
+   if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
                                        weird && (int)gameInfo.variant <= (int)VariantShogi) {
      /* [HGM] We seem to have switched variant unexpectedly
       * Try to guess new variant from board size
@@ -3566,16 +3997,16 @@ ParseBoard12(string)
             will tell us whether this is really bug or zh */
          if (ics_getting_history == H_FALSE) {
            ics_getting_history = H_REQUESTED; reqFlag = TRUE;
-           sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+           snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
            SendToICS(str);
          }
     }
-    
+
     /* Take action if this is the first board of a new game, or of a
        different game than is currently being displayed.  */
     if (gamenum != ics_gamenum || newGameMode != gameMode ||
        relation == RELATION_ISOLATED_BOARD) {
-       
+
        /* Forget the old game and get the history (if any) of the new one */
        if (gameMode != BeginningOfGame) {
          Reset(TRUE, TRUE);
@@ -3589,17 +4020,17 @@ ParseBoard12(string)
                   appData.getMoveList && !reqFlag) {
            /* Need to get game history */
            ics_getting_history = H_REQUESTED;
-           sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+           snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
            SendToICS(str);
        }
-       
+
        /* Initially flip the board to have black on the bottom if playing
           black or if the ICS flip flag is set, but let the user change
           it with the Flip View button. */
-       flipView = appData.autoFlipView ? 
+       flipView = appData.autoFlipView ?
          (newGameMode == IcsPlayingBlack) || ics_flip :
          appData.flipView;
-       
+
        /* Done with values from previous mode; copy in new ones */
        gameMode = newGameMode;
        ModeHighlight();
@@ -3607,7 +4038,7 @@ ParseBoard12(string)
        if (gamenum == gs_gamenum) {
            int klen = strlen(gs_kind);
            if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
-           sprintf(str, "ICS %s", gs_kind);
+           snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
            gameInfo.event = StrSave(str);
        } else {
            gameInfo.event = StrSave("ICS game");
@@ -3630,7 +4061,7 @@ ParseBoard12(string)
   }
 
         gameInfo.outOfBook = NULL;
-       
+
        /* Do we have the ratings? */
        if (strcmp(player1Name, white) == 0 &&
            strcmp(player2Name, black) == 0) {
@@ -3656,7 +4087,7 @@ ParseBoard12(string)
            SendToICS("set shout 0\n");
        }
     }
-    
+
     /* Deal with midgame name changes */
     if (!newGame) {
        if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
@@ -3668,7 +4099,7 @@ ParseBoard12(string)
            gameInfo.black = StrSave(black);
        }
     }
-    
+
     /* Throw away game result if anything actually changes in examine mode */
     if (gameMode == IcsExamining && !newGame) {
        gameInfo.result = GameUnfinished;
@@ -3677,7 +4108,7 @@ ParseBoard12(string)
            gameInfo.resultDetails = NULL;
        }
     }
-    
+
     /* In pausing && IcsExamining mode, we ignore boards coming
        in if they are in a different variation than we are. */
     if (pauseExamInvalid) return;
@@ -3688,7 +4119,7 @@ ParseBoard12(string)
            return;
        }
     }
-    
+
   if (appData.debugMode) {
     fprintf(debugFP, "load %dx%d board\n", files, ranks);
   }
@@ -3763,14 +4194,14 @@ ParseBoard12(string)
     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
 
-    
+
     if (ics_getting_history == H_GOT_REQ_HEADER ||
        ics_getting_history == H_GOT_UNREQ_HEADER) {
        /* This was an initial position from a move list, not
           the current position */
        return;
     }
-    
+
     /* Update currentMove and known move number limits */
     newMove = newGame || moveNum > forwardMostMove;
 
@@ -3781,7 +4212,7 @@ ParseBoard12(string)
             type when starting to examine a game.  But if we ask for
             the move list, the move list header will tell us */
            ics_getting_history = H_REQUESTED;
-           sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+           snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
            SendToICS(str);
        }
     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
@@ -3804,7 +4235,7 @@ ParseBoard12(string)
        if (!pausing || currentMove > forwardMostMove)
          currentMove = forwardMostMove;
     } else {
-       /* New part of history that is not contiguous with old part */ 
+       /* New part of history that is not contiguous with old part */
        if (pausing && gameMode == IcsExamining) {
            pauseExamInvalid = TRUE;
            forwardMostMove = pauseExamForwardMostMove;
@@ -3820,12 +4251,12 @@ ParseBoard12(string)
            }
 #endif
            ics_getting_history = H_REQUESTED;
-           sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+           snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
            SendToICS(str);
        }
        forwardMostMove = backwardMostMove = currentMove = moveNum;
     }
-    
+
     /* Update the clocks */
     if (strchr(elapsed_time, '.')) {
       /* Time is in ms */
@@ -3836,7 +4267,7 @@ ParseBoard12(string)
       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
     }
-      
+
 
 #if ZIPPY
     if (appData.zippyPlay && newGame &&
@@ -3844,7 +4275,7 @@ ParseBoard12(string)
        gameMode != IcsExamining)
       ZippyFirstBoard(moveNum, basetime, increment);
 #endif
-    
+
     /* Put the move on the move list, first converting
        to canonical algebraic form. */
     if (moveNum > 0) {
@@ -3862,7 +4293,7 @@ ParseBoard12(string)
        if (moveNum <= backwardMostMove) {
            /* We don't know what the board looked like before
               this move.  Punt. */
-           strcpy(parseList[moveNum - 1], move_str);
+         safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
            strcat(parseList[moveNum - 1], " ");
            strcat(parseList[moveNum - 1], elapsed_time);
            moveList[moveNum - 1][0] = NULLCHAR;
@@ -3876,20 +4307,20 @@ ParseBoard12(string)
            startedFromSetupPosition = TRUE;
            fromX = fromY = toX = toY = -1;
        } else {
-         // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
+         // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
          //                 So we parse the long-algebraic move string in stead of the SAN move
          int valid; char buf[MSG_SIZ], *prom;
 
          // str looks something like "Q/a1-a2"; kill the slash
-         if(str[1] == '/') 
-               sprintf(buf, "%c%s", str[0], str+2);
-         else  strcpy(buf, str); // might be castling
-         if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
+         if(str[1] == '/')
+           snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
+         else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
+         if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
                strcat(buf, prom); // long move lacks promo specification!
          if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
-               if(appData.debugMode) 
+               if(appData.debugMode)
                        fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
-               strcpy(move_str, buf);
+               safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
           }
          valid = ParseOneMove(move_str, moveNum - 1, &moveType,
                                &fromX, &fromY, &toX, &toY, &promoChar)
@@ -3918,15 +4349,15 @@ ParseBoard12(string)
            strcat(parseList[moveNum - 1], " ");
            strcat(parseList[moveNum - 1], elapsed_time);
            /* currentMoveString is set as a side-effect of ParseOneMove */
-           strcpy(moveList[moveNum - 1], currentMoveString);
+           safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
            strcat(moveList[moveNum - 1], "\n");
          } else {
            /* Move from ICS was illegal!?  Punt. */
-  if (appData.debugMode) {
-    fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
-    fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
-  }
-           strcpy(parseList[moveNum - 1], move_str);
+           if (appData.debugMode) {
+             fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
+             fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+           }
+           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
            strcat(parseList[moveNum - 1], " ");
            strcat(parseList[moveNum - 1], elapsed_time);
            moveList[moveNum - 1][0] = NULLCHAR;
@@ -3940,13 +4371,13 @@ ParseBoard12(string)
 
 #if ZIPPY
        /* Send move to chess program (BEFORE animating it). */
-       if (appData.zippyPlay && !newGame && newMove && 
+       if (appData.zippyPlay && !newGame && newMove &&
           (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
 
            if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
                (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
                if (moveList[moveNum - 1][0] == NULLCHAR) {
-                   sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
+                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
                            move_str);
                    DisplayError(str, 0);
                } else {
@@ -3968,7 +4399,7 @@ ParseBoard12(string)
                }
            } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
              if (moveList[moveNum - 1][0] == NULLCHAR) {
-               sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
+               snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
                DisplayError(str, 0);
              } else {
                if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
@@ -3989,7 +4420,7 @@ ParseBoard12(string)
            SetHighlights(fromX, fromY, toX, toY);
        }
     }
-    
+
     /* Start the clocks */
     whiteFlag = blackFlag = FALSE;
     appData.clockMode = !(basetime == 0 && increment == 0);
@@ -4006,26 +4437,26 @@ ParseBoard12(string)
       DisplayBothClocks();
     else
       StartClocks();
-    
+
     /* Display opponents and material strengths */
     if (gameInfo.variant != VariantBughouse &&
        gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
        if (tinyLayout || smallLayout) {
            if(gameInfo.variant == VariantNormal)
-               sprintf(str, "%s(%d) %s(%d) {%d %d}", 
+             snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
                    gameInfo.white, white_stren, gameInfo.black, black_stren,
                    basetime, increment);
            else
-               sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
+             snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
                    gameInfo.white, white_stren, gameInfo.black, black_stren,
                    basetime, increment, (int) gameInfo.variant);
        } else {
            if(gameInfo.variant == VariantNormal)
-               sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
+             snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
                    gameInfo.white, white_stren, gameInfo.black, black_stren,
                    basetime, increment);
            else
-               sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
+             snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
                    gameInfo.white, white_stren, gameInfo.black, black_stren,
                    basetime, increment, VariantName(gameInfo.variant));
        }
@@ -4035,17 +4466,20 @@ ParseBoard12(string)
   }
     }
 
-   
+
     /* Display the board */
     if (!pausing && !appData.noGUI) {
-      
+
       if (appData.premove)
-         if (!gotPremove || 
+         if (!gotPremove ||
             ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
             ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
              ClearPremoveHighlights();
 
-      DrawPosition(FALSE, boards[currentMove]);
+      j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
+       if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
+      DrawPosition(j, boards[currentMove]);
+
       DisplayMove(moveNum - 1);
       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
            !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
@@ -4059,11 +4493,11 @@ ParseBoard12(string)
     if(bookHit) { // [HGM] book: simulate book reply
        static char bookMove[MSG_SIZ]; // a bit generous?
 
-       programStats.nodes = programStats.depth = programStats.time = 
+       programStats.nodes = programStats.depth = programStats.time =
        programStats.score = programStats.got_only_move = 0;
        sprintf(programStats.movelist, "%s (xbook)", bookHit);
 
-       strcpy(bookMove, "move ");
+       safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
        strcat(bookMove, bookHit);
        HandleMachineMove(bookMove, &first);
     }
@@ -4076,7 +4510,7 @@ GetMoveListEvent()
     char buf[MSG_SIZ];
     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
        ics_getting_history = H_REQUESTED;
-       sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
+       snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
        SendToICS(buf);
     }
 }
@@ -4123,7 +4557,7 @@ SendMoveToProgram(moveNum, cps)
        buf[len++] = '\n';
        buf[len] = NULLCHAR;
       } else {
-       sprintf(buf, "%s\n", parseList[moveNum]);
+       snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
       }
       SendToProgram(buf, cps);
     } else {
@@ -4133,17 +4567,17 @@ SendMoveToProgram(moveNum, cps)
        AlphaRank(moveList[moveNum], 4); // and back
       } else
       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
-       * the engine. It would be nice to have a better way to identify castle 
+       * the engine. It would be nice to have a better way to identify castle
        * moves here. */
       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
                                                                         && cps->useOOCastle) {
-        int fromX = moveList[moveNum][0] - AAA; 
+        int fromX = moveList[moveNum][0] - AAA;
         int fromY = moveList[moveNum][1] - ONE;
-        int toX = moveList[moveNum][2] - AAA; 
+        int toX = moveList[moveNum][2] - AAA;
         int toY = moveList[moveNum][3] - ONE;
-        if((boards[moveNum][fromY][fromX] == WhiteKing 
+        if((boards[moveNum][fromY][fromX] == WhiteKing
             && boards[moveNum][toY][toX] == WhiteRook)
-           || (boards[moveNum][fromY][fromX] == BlackKing 
+           || (boards[moveNum][fromY][fromX] == BlackKing
                && boards[moveNum][toY][toX] == BlackRook)) {
          if(toX > fromX) SendToProgram("O-O\n", cps);
          else SendToProgram("O-O-O\n", cps);
@@ -4158,7 +4592,7 @@ SendMoveToProgram(moveNum, cps)
     /*       Send 'go' if we are in a mode where machine should play. */
     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
         (gameMode == TwoMachinesPlay   ||
-#ifdef ZIPPY
+#if ZIPPY
          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
 #endif
          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
@@ -4171,15 +4605,16 @@ SendMoveToProgram(moveNum, cps)
 }
 
 void
-SendMoveToICS(moveType, fromX, fromY, toX, toY)
+SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
      ChessMove moveType;
      int fromX, fromY, toX, toY;
+     char promoChar;
 {
     char user_move[MSG_SIZ];
 
     switch (moveType) {
       default:
-       sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
+       snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
                (int)moveType, fromX, fromY, toX, toY);
        DisplayError(user_move + strlen("say "), 0);
        break;
@@ -4191,7 +4626,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       case WhiteHSideCastleFR:
       case BlackHSideCastleFR:
       /* POP Fabien */
-       sprintf(user_move, "o-o\n");
+       snprintf(user_move, MSG_SIZ, "o-o\n");
        break;
       case WhiteQueenSideCastle:
       case BlackQueenSideCastle:
@@ -4201,38 +4636,30 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       case WhiteASideCastleFR:
       case BlackASideCastleFR:
       /* POP Fabien */
-       sprintf(user_move, "o-o-o\n");
+       snprintf(user_move, MSG_SIZ, "o-o-o\n");
        break;
-      case WhitePromotionQueen:
-      case BlackPromotionQueen:
-      case WhitePromotionRook:
-      case BlackPromotionRook:
-      case WhitePromotionBishop:
-      case BlackPromotionBishop:
-      case WhitePromotionKnight:
-      case BlackPromotionKnight:
-      case WhitePromotionKing:
-      case BlackPromotionKing:
-      case WhitePromotionChancellor:
-      case BlackPromotionChancellor:
-      case WhitePromotionArchbishop:
-      case BlackPromotionArchbishop:
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
+        sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
+        break;
+      case WhitePromotion:
+      case BlackPromotion:
         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
-            sprintf(user_move, "%c%c%c%c=%c\n",
+         snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
                PieceToChar(WhiteFerz));
         else if(gameInfo.variant == VariantGreat)
-            sprintf(user_move, "%c%c%c%c=%c\n",
+         snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
                PieceToChar(WhiteMan));
         else
-            sprintf(user_move, "%c%c%c%c=%c\n",
+         snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
-               PieceToChar(PromoPiece(moveType)));
+               promoChar);
        break;
       case WhiteDrop:
       case BlackDrop:
-       sprintf(user_move, "%c@%c%c\n",
+       snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
                ToUpper(PieceToChar((ChessSquare) fromX)),
                 AAA + toX, ONE + toY);
        break;
@@ -4240,7 +4667,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
       case IllegalMove:  /* could be a variant we don't quite understand */
-       sprintf(user_move, "%c%c%c%c\n",
+       snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
        break;
     }
@@ -4250,17 +4677,84 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
 }
 
 void
+UploadGameEvent()
+{   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
+    int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
+    static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
+    if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
+       DisplayError("You cannot do this while you are playing or observing", 0);
+       return;
+    }
+    if(gameMode != IcsExamining) { // is this ever not the case?
+       char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
+
+       if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
+         snprintf(command,MSG_SIZ, "match %s", ics_handle);
+       } else { // on FICS we must first go to general examine mode
+         safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
+       }
+       if(gameInfo.variant != VariantNormal) {
+           // try figure out wild number, as xboard names are not always valid on ICS
+           for(i=1; i<=36; i++) {
+             snprintf(buf, MSG_SIZ, "wild/%d", i);
+               if(StringToVariant(buf) == gameInfo.variant) break;
+           }
+           if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
+           else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
+           else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
+       } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
+       SendToICS(ics_prefix);
+       SendToICS(buf);
+       if(startedFromSetupPosition || backwardMostMove != 0) {
+         fen = PositionToFEN(backwardMostMove, NULL);
+         if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
+           snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
+           SendToICS(buf);
+         } else { // FICS: everything has to set by separate bsetup commands
+           p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
+           snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
+           SendToICS(buf);
+           if(!WhiteOnMove(backwardMostMove)) {
+               SendToICS("bsetup tomove black\n");
+           }
+           i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
+           snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
+           SendToICS(buf);
+           i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
+           snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
+           SendToICS(buf);
+           i = boards[backwardMostMove][EP_STATUS];
+           if(i >= 0) { // set e.p.
+             snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
+               SendToICS(buf);
+           }
+           bsetup++;
+         }
+       }
+      if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
+           SendToICS("bsetup done\n"); // switch to normal examining.
+    }
+    for(i = backwardMostMove; i<last; i++) {
+       char buf[20];
+       snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
+       SendToICS(buf);
+    }
+    SendToICS(ics_prefix);
+    SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
+}
+
+void
 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
      int rf, ff, rt, ft;
      char promoChar;
      char move[7];
 {
     if (rf == DROP_RANK) {
-       sprintf(move, "%c@%c%c\n",
+      sprintf(move, "%c@%c%c\n",
                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
     } else {
        if (promoChar == 'x' || promoChar == NULLCHAR) {
-           sprintf(move, "%c%c%c%c\n",
+         sprintf(move, "%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
        } else {
            sprintf(move, "%c%c%c%c%c\n",
@@ -4293,7 +4787,7 @@ AlphaRank(char *move, int n)
         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
     }
 
-    if(move[1]=='*' && 
+    if(move[1]=='*' &&
        move[2]>='0' && move[2]<='9' &&
        move[3]>='a' && move[3]<='x'    ) {
         move[1] = '@';
@@ -4333,6 +4827,8 @@ AlphaRank(char *move, int n)
     }
 }
 
+char yy_textstr[8000];
+
 /* Parser for moves from gnuchess, ICS, or user typein box */
 Boolean
 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
@@ -4341,27 +4837,17 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
      ChessMove *moveType;
      int *fromX, *fromY, *toX, *toY;
      char *promoChar;
-{       
+{
     if (appData.debugMode) {
         fprintf(debugFP, "move to parse: %s\n", move);
     }
-    *moveType = yylexstr(moveNum, move);
+    *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
 
     switch (*moveType) {
-      case WhitePromotionChancellor:
-      case BlackPromotionChancellor:
-      case WhitePromotionArchbishop:
-      case BlackPromotionArchbishop:
-      case WhitePromotionQueen:
-      case BlackPromotionQueen:
-      case WhitePromotionRook:
-      case BlackPromotionRook:
-      case WhitePromotionBishop:
-      case BlackPromotionBishop:
-      case WhitePromotionKnight:
-      case BlackPromotionKnight:
-      case WhitePromotionKing:
-      case BlackPromotionKing:
+      case WhitePromotion:
+      case BlackPromotion:
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
       case NormalMove:
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
@@ -4396,7 +4882,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
        if (appData.testLegality) {
          return (*moveType != IllegalMove);
        } else {
-         return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
+         return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
                        WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
        }
 
@@ -4434,7 +4920,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
 
 
 void
-ParsePV(char *pv)
+ParsePV(char *pv, Boolean storeComments)
 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
   int fromX, fromY, toX, toY; char promoChar;
   ChessMove moveType;
@@ -4443,18 +4929,36 @@ ParsePV(char *pv)
 
   endPV = forwardMostMove;
   do {
-    while(*pv == ' ') pv++;
-    if(*pv == '(') pv++; // first (ponder) move can be in parentheses
+    while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
+    if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
 if(appData.debugMode){
-fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
+fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
 }
     if(!valid && nr == 0 &&
-       ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
+       ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
+        // Hande case where played move is different from leading PV move
+        CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
+        CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
+        ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
+        if(!CompareBoards(boards[endPV], boards[endPV+2])) {
+          endPV += 2; // if position different, keep this
+          moveList[endPV-1][0] = fromX + AAA;
+          moveList[endPV-1][1] = fromY + ONE;
+          moveList[endPV-1][2] = toX + AAA;
+          moveList[endPV-1][3] = toY + ONE;
+          parseList[endPV-1][0] = NULLCHAR;
+          safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
+        }
+      }
+    pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
+    if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
+    if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
+    if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
+       valid++; // allow comments in PV
+       continue;
     }
-    while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
-    if(moveType == Comment) { valid++; continue; } // allow comments in PV
     nr++;
     if(endPV+1 > framePtr) break; // no space, truncate
     if(!valid) break;
@@ -4465,7 +4969,13 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+
     moveList[endPV-1][1] = fromY + ONE;
     moveList[endPV-1][2] = toX + AAA;
     moveList[endPV-1][3] = toY + ONE;
-    parseList[endPV-1][0] = NULLCHAR;
+    if(storeComments)
+       CoordsToAlgebraic(boards[endPV - 1],
+                            PosFlags(endPV - 1),
+                            fromY, fromX, toY, toX, promoChar,
+                            parseList[endPV - 1]);
+    else
+       parseList[endPV-1][0] = NULLCHAR;
   } while(valid);
   currentMove = endPV;
   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
@@ -4480,16 +4990,19 @@ Boolean
 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
 {
        int startPV;
+       char *p;
 
        if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
        lastX = x; lastY = y;
        while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
        startPV = index;
-      while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
-      index = startPV;
-       while(buf[index] && buf[index] != '\n') index++;
+       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
+       if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
+       index = startPV;
+       do{ while(buf[index] && buf[index] != '\n') index++;
+       } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
        buf[index] = 0;
-       ParsePV(buf+startPV);
+       ParsePV(buf+startPV, FALSE);
        *start = startPV; *end = index-1;
        return TRUE;
 }
@@ -4499,7 +5012,7 @@ LoadPV(int x, int y)
 { // called on right mouse click to load PV
   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
   lastX = x; lastY = y;
-  ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
+  ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
   return TRUE;
 }
 
@@ -4567,7 +5080,7 @@ int put(Board board, int pieceType, int rank, int n, int shade)
                        board[rank][i] = (ChessSquare) pieceType;
                        squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
                        squaresLeft[ANY]--;
-                       piecesLeft[pieceType]--; 
+                       piecesLeft[pieceType]--;
                        return i;
                }
        }
@@ -4695,7 +5208,7 @@ int SetCharTable( char *table, const char * map )
 {
     int result = FALSE; int NrPieces;
 
-    if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
+    if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
                     && NrPieces >= 12 && !(NrPieces&1)) {
         int i; /* [HGM] Accept even length from 12 to 34 */
 
@@ -4715,30 +5228,30 @@ int SetCharTable( char *table, const char * map )
 
 void Prelude(Board board)
 {      // [HGM] superchess: random selection of exo-pieces
-       int i, j, k; ChessSquare p; 
+       int i, j, k; ChessSquare p;
        static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
 
        GetPositionNumber(); // use FRC position number
 
        if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
            SetCharTable(pieceToChar, appData.pieceToCharTable);
-           for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
+           for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
                if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
        }
 
-       j = seed%4;                 seed /= 4; 
+       j = seed%4;                 seed /= 4;
        p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
        board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
        board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
-       j = seed%3 + (seed%3 >= j); seed /= 3; 
+       j = seed%3 + (seed%3 >= j); seed /= 3;
        p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
        board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
        board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
-       j = seed%3;                 seed /= 3; 
+       j = seed%3;                 seed /= 3;
        p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
        board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
        board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
-       j = seed%2 + (seed%2 >= j); seed /= 2; 
+       j = seed%2 + (seed%2 >= j); seed /= 2;
        p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
        board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
        board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
@@ -4776,7 +5289,7 @@ InitPosition(redraw)
         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
     }
 
-    
+
     /* [HGM] logic here is completely changed. In stead of full positions */
     /* the initialized data only consist of the two backranks. The switch */
     /* selects which one we will use, which is than copied to the Board   */
@@ -4792,7 +5305,10 @@ InitPosition(redraw)
     for(i=0; i<BOARD_FILES-2; i++)
       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
     initialPosition[EP_STATUS] = EP_NONE;
-    SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
+    SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+    if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
+         SetCharTable(pieceNickName, appData.pieceNickNames);
+    else SetCharTable(pieceNickName, "............");
 
     switch (gameInfo.variant) {
     case VariantFischeRandom:
@@ -4803,13 +5319,13 @@ InitPosition(redraw)
     case VariantShatranj:
       pieces = ShatranjArray;
       nrCastlingRights = 0;
-      SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
+      SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
       break;
     case VariantMakruk:
       pieces = makrukArray;
       nrCastlingRights = 0;
       startedFromSetupPosition = TRUE;
-      SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
+      SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
       break;
     case VariantTwoKings:
       pieces = twoKingsArray;
@@ -4819,17 +5335,17 @@ InitPosition(redraw)
     case VariantCapablanca:
       pieces = CapablancaArray;
       gameInfo.boardWidth = 10;
-      SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
+      SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
       break;
     case VariantGothic:
       pieces = GothicArray;
       gameInfo.boardWidth = 10;
-      SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
+      SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
       break;
     case VariantJanus:
       pieces = JanusArray;
       gameInfo.boardWidth = 10;
-      SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
+      SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
       nrCastlingRights = 6;
         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
@@ -4841,14 +5357,14 @@ InitPosition(redraw)
     case VariantFalcon:
       pieces = FalconArray;
       gameInfo.boardWidth = 10;
-      SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
+      SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
       break;
     case VariantXiangqi:
       pieces = XiangqiArray;
       gameInfo.boardWidth  = 9;
       gameInfo.boardHeight = 10;
       nrCastlingRights = 0;
-      SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
+      SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
       break;
     case VariantShogi:
       pieces = ShogiArray;
@@ -4856,21 +5372,21 @@ InitPosition(redraw)
       gameInfo.boardHeight = 9;
       gameInfo.holdingsSize = 7;
       nrCastlingRights = 0;
-      SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
+      SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
       break;
     case VariantCourier:
       pieces = CourierArray;
       gameInfo.boardWidth  = 12;
       nrCastlingRights = 0;
-      SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
+      SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
       break;
     case VariantKnightmate:
       pieces = KnightmateArray;
-      SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
+      SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
       break;
     case VariantFairy:
       pieces = fairyArray;
-      SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
+      SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
       break;
     case VariantGreat:
       pieces = GreatArray;
@@ -4887,7 +5403,7 @@ InitPosition(redraw)
     case VariantCrazyhouse:
     case VariantBughouse:
       pieces = FIDEArray;
-      SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
+      SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
       gameInfo.holdingsSize = 5;
       break;
     case VariantWildCastle:
@@ -4941,7 +5457,7 @@ InitPosition(redraw)
         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
         if(gameInfo.variant == VariantXiangqi) {
             if(j&1) {
-                initialPosition[pawnRow][j] = 
+                initialPosition[pawnRow][j] =
                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
                    initialPosition[2][j] = WhiteCannon;
@@ -4966,7 +5482,7 @@ InitPosition(redraw)
         /*       This sets default castling rights from none to normal corners   */
         /* Variants with other castling rights must set them themselves above    */
         nrCastlingRights = 6;
-       
+
         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
@@ -5023,10 +5539,10 @@ SendBoard(cps, moveNum)
      int moveNum;
 {
     char message[MSG_SIZ];
-    
+
     if (cps->useSetboard) {
       char* fen = PositionToFEN(moveNum, cps->fenOverride);
-      sprintf(message, "setboard %s\n", fen);
+      snprintf(message, MSG_SIZ,"setboard %s\n", fen);
       SendToProgram(message, cps);
       free(fen);
 
@@ -5044,10 +5560,10 @@ SendBoard(cps, moveNum)
        bp = &boards[moveNum][i][BOARD_LEFT];
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
          if ((int) *bp < (int) BlackPawn) {
-           sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
+           snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
                     AAA + j, ONE + i);
             if(message[0] == '+' || message[0] == '~') {
-                sprintf(message, "%c%c%c+\n",
+             snprintf(message, MSG_SIZ,"%c%c%c+\n",
                         PieceToChar((ChessSquare)(DEMOTED *bp)),
                         AAA + j, ONE + i);
             }
@@ -5059,17 +5575,17 @@ SendBoard(cps, moveNum)
          }
        }
       }
-    
+
       SendToProgram("c\n", cps);
       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
        bp = &boards[moveNum][i][BOARD_LEFT];
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
          if (((int) *bp != (int) EmptySquare)
              && ((int) *bp >= (int) BlackPawn)) {
-           sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
+           snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
                     AAA + j, ONE + i);
             if(message[0] == '+' || message[0] == '~') {
-                sprintf(message, "%c%c%c+\n",
+             snprintf(message, MSG_SIZ,"%c%c%c+\n",
                         PieceToChar((ChessSquare)(DEMOTED *bp)),
                         AAA + j, ONE + i);
             }
@@ -5081,12 +5597,14 @@ SendBoard(cps, moveNum)
          }
        }
       }
-    
+
       SendToProgram(".\n", cps);
     }
     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
 }
 
+static int autoQueen; // [HGM] oneclick
+
 int
 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
 {
@@ -5106,7 +5624,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
 
     piece = boards[currentMove][fromY][fromX];
     if(gameInfo.variant == VariantShogi) {
-        promotionZoneSize = 3;
+        promotionZoneSize = BOARD_HEIGHT/3;
         highestPromotingPiece = (int)WhiteFerz;
     } else if(gameInfo.variant == VariantMakruk) {
         promotionZoneSize = 3;
@@ -5157,7 +5675,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
        *promoChoice = PieceToChar(BlackFerz);  // no choice
        return FALSE;
     }
-    if(appData.alwaysPromoteToQueen) { // predetermined
+    if(autoQueen) { // predetermined
        if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
             *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
        else *promoChoice = PieceToChar(BlackQueen);
@@ -5170,8 +5688,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
     if(appData.testLegality && !premove) {
        moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                        fromY, fromX, toY, toX, NULLCHAR);
-       if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
-          moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
+       if(moveType != WhitePromotion && moveType  != BlackPromotion)
            return FALSE;
     }
 
@@ -5252,11 +5769,11 @@ OKToStartUserMove(x, y)
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
            return FALSE;
-       }           
+       }
        if (white_piece && !WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is Black's turn"));
            return FALSE;
-       }           
+       }
        if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
            /* Editing correspondence game history */
            /* Could disallow this or prompt for confirmation */
@@ -5273,16 +5790,16 @@ OKToStartUserMove(x, y)
            }
        }
        break;
-       
+
       case Training:
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
            return FALSE;
-       }           
+       }
        if (white_piece && !WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is Black's turn"));
            return FALSE;
-       }           
+       }
        break;
 
       default:
@@ -5298,17 +5815,72 @@ OKToStartUserMove(x, y)
     return TRUE;
 }
 
+Boolean
+OnlyMove(int *x, int *y, Boolean captures) {
+    DisambiguateClosure cl;
+    if (appData.zippyPlay) return FALSE;
+    switch(gameMode) {
+      case MachinePlaysBlack:
+      case IcsPlayingWhite:
+      case BeginningOfGame:
+       if(!WhiteOnMove(currentMove)) return FALSE;
+       break;
+      case MachinePlaysWhite:
+      case IcsPlayingBlack:
+       if(WhiteOnMove(currentMove)) return FALSE;
+       break;
+      default:
+       return FALSE;
+    }
+    cl.pieceIn = EmptySquare;
+    cl.rfIn = *y;
+    cl.ffIn = *x;
+    cl.rtIn = -1;
+    cl.ftIn = -1;
+    cl.promoCharIn = NULLCHAR;
+    Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
+    if( cl.kind == NormalMove ||
+       cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
+       cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
+       cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
+      fromX = cl.ff;
+      fromY = cl.rf;
+      *x = cl.ft;
+      *y = cl.rt;
+      return TRUE;
+    }
+    if(cl.kind != ImpossibleMove) return FALSE;
+    cl.pieceIn = EmptySquare;
+    cl.rfIn = -1;
+    cl.ffIn = -1;
+    cl.rtIn = *y;
+    cl.ftIn = *x;
+    cl.promoCharIn = NULLCHAR;
+    Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
+    if( cl.kind == NormalMove ||
+       cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
+       cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
+       cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
+      fromX = cl.ff;
+      fromY = cl.rf;
+      *x = cl.ft;
+      *y = cl.rt;
+      autoQueen = TRUE; // act as if autoQueen on when we click to-square
+      return TRUE;
+    }
+    return FALSE;
+}
+
 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
 int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = (ChessMove) 0;
 
-ChessMove
-UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
+void
+UserMoveEvent(fromX, fromY, toX, toY, promoChar)
      int fromX, fromY, toX, toY;
      int promoChar;
-     Boolean captureOwn;
 {
     ChessMove moveType;
     ChessSquare pdown, pup;
@@ -5333,13 +5905,13 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
       case IcsIdle:
        /* We switched into a game mode where moves are not accepted,
            perhaps while the mouse button was down. */
-        return ImpossibleMove;
+        return;
 
       case MachinePlaysWhite:
        /* User is moving for Black */
        if (WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
-            return ImpossibleMove;
+            return;
        }
        break;
 
@@ -5347,7 +5919,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
        /* User is moving for White */
        if (!WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is Black's turn"));
-            return ImpossibleMove;
+            return;
        }
        break;
 
@@ -5361,13 +5933,13 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
            /* User is moving for Black */
            if (WhiteOnMove(currentMove)) {
                DisplayMoveError(_("It is White's turn"));
-                return ImpossibleMove;
+                return;
            }
        } else {
            /* User is moving for White */
            if (!WhiteOnMove(currentMove)) {
                DisplayMoveError(_("It is Black's turn"));
-                return ImpossibleMove;
+                return;
            }
        }
        break;
@@ -5384,12 +5956,12 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
                premoveFromY = fromY;
                premovePromoChar = promoChar;
                gotPremove = 1;
-               if (appData.debugMode) 
+               if (appData.debugMode)
                    fprintf(debugFP, "Got premove: fromX %d,"
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
-            return ImpossibleMove;
+            return;
        }
        break;
 
@@ -5405,12 +5977,12 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
                premoveFromY = fromY;
                premovePromoChar = promoChar;
                gotPremove = 1;
-               if (appData.debugMode) 
+               if (appData.debugMode)
                    fprintf(debugFP, "Got premove: fromX %d,"
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
-            return ImpossibleMove;
+            return;
        }
        break;
 
@@ -5422,48 +5994,46 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
           click-click move is possible */
        if (toX == -2 || toY == -2) {
            boards[0][fromY][fromX] = EmptySquare;
-           return AmbiguousMove;
+           DrawPosition(FALSE, boards[currentMove]);
+           return;
        } else if (toX >= 0 && toY >= 0) {
            boards[0][toY][toX] = boards[0][fromY][fromX];
            if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
                if(boards[0][fromY][0] != EmptySquare) {
                    if(boards[0][fromY][1]) boards[0][fromY][1]--;
-                   if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
+                   if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
                }
            } else
            if(fromX == BOARD_RGHT+1) {
                if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
                    if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
-                   if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
+                   if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
                }
            } else
            boards[0][fromY][fromX] = EmptySquare;
-           return AmbiguousMove;
+           DrawPosition(FALSE, boards[currentMove]);
+           return;
        }
-        return ImpossibleMove;
+        return;
     }
 
-    if(toX < 0 || toY < 0) return ImpossibleMove;
+    if(toX < 0 || toY < 0) return;
     pdown = boards[currentMove][fromY][fromX];
     pup = boards[currentMove][toY][toX];
 
-    /* [HGM] If move started in holdings, it means a drop */
+    /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
-         if( pup != EmptySquare ) return ImpossibleMove;
-         if(appData.testLegality) {
-             /* it would be more logical if LegalityTest() also figured out
-              * which drops are legal. For now we forbid pawns on back rank.
-              * Shogi is on its own here...
-              */
-             if( (pdown == WhitePawn || pdown == BlackPawn) &&
-                 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
-                 return(ImpossibleMove); /* no pawn drops on 1st/8th */
-         }
-         return WhiteDrop; /* Not needed to specify white or black yet */
+         if( pup != EmptySquare ) return;
+         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
+          if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
+               moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
+          // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
+          if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
+          fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
+          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
+         fromY = DROP_RANK;
     }
 
-    userOfferedDraw = FALSE;
-       
     /* [HGM] always test for legality, to get promotion info */
     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                                          fromY, fromX, toY, toX, promoChar);
@@ -5471,18 +6041,11 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
     if (appData.testLegality) {
        if (moveType == IllegalMove || moveType == ImpossibleMove) {
            DisplayMoveError(_("Illegal move"));
-            return ImpossibleMove;
+            return;
        }
     }
 
-    return moveType;
-    /* [HGM] <popupFix> in stead of calling FinishMove directly, this
-       function is made into one that returns an OK move type if FinishMove
-       should be called. This to give the calling driver routine the
-       opportunity to finish the userMove input with a promotion popup,
-       without bothering the user with this for invalid or illegal moves */
-
-/*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
+    FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
 }
 
 /* Common tail of UserMoveEvent and DropMenuEvent */
@@ -5494,7 +6057,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 {
     char *bookHit = 0;
 
-    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
+    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
        // [HGM] superchess: suppress promotions to non-available piece
        int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
        if(WhiteOnMove(currentMove)) {
@@ -5507,19 +6070,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
        move type in caller when we know the move is a legal promotion */
     if(moveType == NormalMove && promoChar)
-        moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
-
-    /* [HGM] convert drag-and-drop piece drops to standard form */
-    if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
-         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
-          if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
-               moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
-          // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
-          if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
-          fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
-          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
-         fromY = DROP_RANK;
-    }
+        moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
 
     /* [HGM] <popupFix> The following if has been moved here from
        UserMoveEvent(). Because it seemed to belong here (why not allow
@@ -5527,7 +6078,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
        performed after it is known to what we promote. */
     if (gameMode == Training) {
       /* compare the move played on the board to the next move in the
-       * game. If they match, display the move and the opponent's response. 
+       * game. If they match, display the move and the opponent's response.
        * If they don't match, display an error message.
        */
       int saveAnimate;
@@ -5562,7 +6113,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   /* Ok, now we know that the move is good, so we can kill
      the previous line in Analysis Mode */
-  if ((gameMode == AnalyzeMode || gameMode == EditGame) 
+  if ((gameMode == AnalyzeMode || gameMode == EditGame)
                                && currentMove < forwardMostMove) {
     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
   }
@@ -5589,10 +6140,10 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
       gameMode = MachinePlaysBlack;
       StartClocks();
       SetGameInfo();
-      sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+      snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
       DisplayTitle(buf);
       if (first.sendName) {
-       sprintf(buf, "name %s\n", gameInfo.white);
+       snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
        SendToProgram(buf, &first);
       }
       StartClocks();
@@ -5604,7 +6155,13 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
   if (appData.icsActive) {
     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
        gameMode == IcsExamining) {
-      SendMoveToICS(moveType, fromX, fromY, toX, toY);
+      if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+        SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
+       SendToICS("draw ");
+        SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
+      }
+      // also send plain move, in case ICS does not understand atomic claims
+      SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
       ics_user_moved = 1;
     }
   } else {
@@ -5644,7 +6201,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
       break;
     }
     break;
-    
+
   case MachinePlaysBlack:
   case MachinePlaysWhite:
     /* disable certain menu options while machine is thinking */
@@ -5655,14 +6212,16 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
     break;
   }
 
+  userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
+
   if(bookHit) { // [HGM] book: simulate book reply
        static char bookMove[MSG_SIZ]; // a bit generous?
 
-       programStats.nodes = programStats.depth = programStats.time = 
+       programStats.nodes = programStats.depth = programStats.time =
        programStats.score = programStats.got_only_move = 0;
        sprintf(programStats.movelist, "%s (xbook)", bookHit);
 
-       strcpy(bookMove, "move ");
+       safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
        strcat(bookMove, bookHit);
        HandleMachineMove(bookMove, &first);
   }
@@ -5670,28 +6229,6 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 }
 
 void
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)
-     int fromX, fromY, toX, toY;
-     int promoChar;
-{
-    /* [HGM] This routine was added to allow calling of its two logical
-       parts from other modules in the old way. Before, UserMoveEvent()
-       automatically called FinishMove() if the move was OK, and returned
-       otherwise. I separated the two, in order to make it possible to
-       slip a promotion popup in between. But that it always needs two
-       calls, to the first part, (now called UserMoveTest() ), and to
-       FinishMove if the first part succeeded. Calls that do not need
-       to do anything in between, can call this routine the old way. 
-    */
-    ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
-if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
-    if(moveType == AmbiguousMove)
-       DrawPosition(FALSE, boards[currentMove]);
-    else if(moveType != ImpossibleMove && moveType != Comment)
-        FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
-}
-
-void
 Mark(board, flags, kind, rf, ff, rt, ft, closure)
      Board board;
      int flags;
@@ -5712,7 +6249,7 @@ void
 MarkTargetSquares(int clear)
 {
   int x, y;
-  if(!appData.markers || !appData.highlightDragging || 
+  if(!appData.markers || !appData.highlightDragging ||
      !appData.testLegality || gameMode == EditPosition) return;
   if(clear) {
     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
@@ -5732,9 +6269,15 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0;
+    static int second = 0, promotionChoice = 0, dragging = 0;
     char promoChoice = NULLCHAR;
 
+    if(appData.seekGraph && appData.icsActive && loggedOn &&
+       (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
+       SeekGraphClick(clickType, xPix, yPix, 0);
+       return;
+    }
+
     if (clickType == Press) ErrorPopDown();
     MarkTargetSquares(1);
 
@@ -5751,8 +6294,8 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        if(clickType == Release) return; // ignore upclick of click-click destination
        promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
        if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
-       if(gameInfo.holdingsWidth && 
-               (WhiteOnMove(currentMove) 
+       if(gameInfo.holdingsWidth &&
+               (WhiteOnMove(currentMove)
                        ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
                        : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
            // click in right holdings, for determining promotion piece
@@ -5775,7 +6318,10 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
        return;
 
+    autoQueen = appData.alwaysPromoteToQueen;
+
     if (fromX == -1) {
+      if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
        if (clickType == Press) {
            /* First square */
            if (OKToStartUserMove(x, y)) {
@@ -5783,13 +6329,17 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                fromY = y;
                second = 0;
                MarkTargetSquares(0);
-               DragPieceBegin(xPix, yPix);
+               DragPieceBegin(xPix, yPix); dragging = 1;
                if (appData.highlightDragging) {
                    SetHighlights(x, y, -1, -1);
                }
            }
+       } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
+           DragPieceEnd(xPix, yPix); dragging = 0;
+           DrawPosition(FALSE, NULL);
        }
        return;
+      }
     }
 
     /* fromX != -1 */
@@ -5809,12 +6359,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
             WhitePawn <= toP && toP <= WhiteKing &&
             !(fromP == WhiteKing && toP == WhiteRook && frc) &&
             !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
-           (BlackPawn <= fromP && fromP <= BlackKing && 
+           (BlackPawn <= fromP && fromP <= BlackKing &&
             BlackPawn <= toP && toP <= BlackKing &&
             !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
             !(fromP == BlackKing && toP == BlackRook && frc))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
+          if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
            } else {
@@ -5822,18 +6373,19 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            }
            if (OKToStartUserMove(x, y)) {
                fromX = x;
-               fromY = y;
+               fromY = y; dragging = 1;
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix);
            }
            return;
+          }
        }
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
     }
 
     if (clickType == Release && x == fromX && y == fromY) {
-       DragPieceEnd(xPix, yPix);
+       DragPieceEnd(xPix, yPix); dragging = 0;
        if (appData.animateDragging) {
            /* Undo animation damage if any */
            DrawPosition(FALSE, NULL);
@@ -5871,7 +6423,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        } else {
            ClearHighlights();
        }
-       DragPieceEnd(xPix, yPix);
+       DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
     }
@@ -5882,16 +6434,16 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        if(gameMode == EditPosition && piece != EmptySquare &&
           fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
            int n;
-            
+
            if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
                n = PieceToNumber(piece - (int)BlackPawn);
-               if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
+               if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
                boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
                boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
            } else
            if(x == BOARD_RGHT+1 && piece < BlackPawn) {
                n = PieceToNumber(piece);
-               if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
+               if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
                boards[currentMove][n][BOARD_WIDTH-1] = piece;
                boards[currentMove][n][BOARD_WIDTH-2]++;
            }
@@ -5935,6 +6487,87 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     }
 }
 
+int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
+{   // front-end-free part taken out of PieceMenuPopup
+    int whichMenu; int xSqr, ySqr;
+
+    if(seekGraphUp) { // [HGM] seekgraph
+       if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
+       if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
+       return -2;
+    }
+
+    if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
+        && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
+       if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
+       if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
+       if(action == Press)   {
+           originalFlip = flipView;
+           flipView = !flipView; // temporarily flip board to see game from partners perspective
+           DrawPosition(TRUE, partnerBoard);
+           DisplayMessage(partnerStatus, "");
+           partnerUp = TRUE;
+       } else if(action == Release) {
+           flipView = originalFlip;
+           DrawPosition(TRUE, boards[currentMove]);
+           partnerUp = FALSE;
+       }
+       return -2;
+    }
+
+    xSqr = EventToSquare(x, BOARD_WIDTH);
+    ySqr = EventToSquare(y, BOARD_HEIGHT);
+    if (action == Release) UnLoadPV(); // [HGM] pv
+    if (action != Press) return -2; // return code to be ignored
+    switch (gameMode) {
+      case IcsExamining:
+       if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
+      case EditPosition:
+       if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
+       if (xSqr < 0 || ySqr < 0) return -1;\r
+       whichMenu = 0; // edit-position menu
+       break;
+      case IcsObserving:
+       if(!appData.icsEngineAnalyze) return -1;
+      case IcsPlayingWhite:
+      case IcsPlayingBlack:
+       if(!appData.zippyPlay) goto noZip;
+      case AnalyzeMode:
+      case AnalyzeFile:
+      case MachinePlaysWhite:
+      case MachinePlaysBlack:
+      case TwoMachinesPlay: // [HGM] pv: use for showing PV
+       if (!appData.dropMenu) {
+         LoadPV(x, y);
+         return 2; // flag front-end to grab mouse events
+       }
+       if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
+           gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
+      case EditGame:
+      noZip:
+       if (xSqr < 0 || ySqr < 0) return -1;
+       if (!appData.dropMenu || appData.testLegality &&
+           gameInfo.variant != VariantBughouse &&
+           gameInfo.variant != VariantCrazyhouse) return -1;
+       whichMenu = 1; // drop menu
+       break;
+      default:
+       return -1;
+    }
+
+    if (((*fromX = xSqr) < 0) ||
+       ((*fromY = ySqr) < 0)) {
+       *fromX = *fromY = -1;
+       return -1;
+    }
+    if (flipView)
+      *fromX = BOARD_WIDTH - 1 - *fromX;
+    else
+      *fromY = BOARD_HEIGHT - 1 - *fromY;
+
+    return whichMenu;
+}
+
 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
 {
 //    char * hint = lastHint;
@@ -5956,11 +6589,108 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp
         stats.an_move_count = cpstats->nr_moves;
     }
 
-    if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
+    if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
 
     SetProgramStats( &stats );
 }
 
+void
+Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
+{      // count all piece types
+       int p, f, r;
+       *nB = *nW = *wStale = *bStale = *bishopColor = 0;
+       for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+               p = board[r][f];
+               pCnt[p]++;
+               if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
+               if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
+               if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
+               if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
+                  p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
+                       *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
+       }
+}
+
+int
+SufficientDefence(int pCnt[], int side, int nMine, int nHis)
+{
+       int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
+       int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
+
+       nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
+       if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
+       if(myPawns == 2 && nMine == 3) // KPP
+           return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
+       if(myPawns == 1 && nMine == 2) // KP
+           return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
+       if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
+           return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
+       if(myPawns) return FALSE;
+       if(pCnt[WhiteRook+side])
+           return pCnt[BlackRook-side] ||
+                  pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
+                  pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
+                  pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
+       if(pCnt[WhiteCannon+side]) {
+           if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
+           return majorDefense || pCnt[BlackAlfil-side] >= 2;
+       }
+       if(pCnt[WhiteKnight+side])
+           return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
+       return FALSE;
+}
+
+int
+MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
+{
+       VariantClass v = gameInfo.variant;
+
+       if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
+       if(v == VariantShatranj) return TRUE; // always winnable through baring
+       if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
+       if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
+
+       if(v == VariantXiangqi) {
+               int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
+
+               nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
+               if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
+               if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
+               if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
+               // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
+               if(stale) // we have at least one last-rank P plus perhaps C
+                   return majors // KPKX
+                       || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
+               else // KCA*E*
+                   return pCnt[WhiteFerz+side] // KCAK
+                       || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
+                       || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
+               // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
+
+       } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
+               int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
+
+               if(nMine == 1) return FALSE; // bare King
+               if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
+               nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
+               if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
+               // by now we have King + 1 piece (or multiple Bishops on the same color)
+               if(pCnt[WhiteKnight+side])
+                       return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
+                               pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
+                            || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
+               if(nBishops)
+                       return (pCnt[BlackKnight-side]); // KBKN, KFKN
+               if(pCnt[WhiteAlfil+side])
+                       return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
+               if(pCnt[WhiteWazir+side])
+                       return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
+       }
+
+       return TRUE;
+}
+
 int
 Adjudicate(ChessProgramState *cps)
 {      // [HGM] some adjudications useful with buggy engines
@@ -5972,86 +6702,40 @@ Adjudicate(ChessProgramState *cps)
        ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
        Boolean canAdjudicate = !appData.icsActive;
 
-       // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
-       if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+       // most tests only when we understand the game, i.e. legality-checking on
            if( appData.testLegality )
            {   /* [HGM] Some more adjudications for obstinate engines */
-               int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
-                    NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
-                    NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
+               int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
                static int moveCount = 6;
                ChessMove result;
                char *reason = NULL;
 
                 /* Count what is on board. */
-               for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
-               {   ChessSquare p = boards[forwardMostMove][i][j];
-                   int m=i;
-
-                   switch((int) p)
-                   {   /* count B,N,R and other of each side */
-                        case WhiteKing:
-                        case BlackKing:
-                            NrK++; break; // [HGM] atomic: count Kings
-                        case WhiteKnight:
-                             NrWN++; break;
-                        case WhiteBishop:
-                        case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
-                             bishopsColor |= 1 << ((i^j)&1);
-                             NrWB++; break;
-                        case BlackKnight:
-                             NrBN++; break;
-                        case BlackBishop:
-                        case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
-                             bishopsColor |= 1 << ((i^j)&1);
-                             NrBB++; break;
-                        case WhiteRook:
-                             NrWR++; break;
-                        case BlackRook:
-                             NrBR++; break;
-                        case WhiteQueen:
-                             NrWQ++; break;
-                        case BlackQueen:
-                             NrBQ++; break;
-                        case EmptySquare: 
-                             break;
-                        case BlackPawn:
-                             m = 7-i;
-                        case WhitePawn:
-                             PawnAdvance += m; NrPawns++;
-                    }
-                    NrPieces += (p != EmptySquare);
-                    NrW += ((int)p < (int)BlackPawn);
-                   if(gameInfo.variant == VariantXiangqi && 
-                     (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
-                       NrPieces--; // [HGM] XQ: do not count purely defensive pieces
-                        NrW -= ((int)p < (int)BlackPawn);
-                   }
-                }
+               Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
 
                /* Some material-based adjudications that have to be made before stalemate test */
-               if(gameInfo.variant == VariantAtomic && NrK < 2) {
+               if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
                    // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
                     boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
                     if(canAdjudicate && appData.checkMates) {
                         if(engineOpponent)
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-                         GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
+                         GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
                                                        "Xboard adjudication: King destroyed", GE_XBOARD );
                          return 1;
                     }
                }
 
                /* Bare King in Shatranj (loses) or Losers (wins) */
-                if( NrW == 1 || NrPieces - NrW == 1) {
+                if( nrW == 1 || nrB == 1) {
                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
                     boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
                     if(canAdjudicate && appData.checkMates) {
                         if(engineOpponent)
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
+                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
                          return 1;
                     }
@@ -6064,7 +6748,7 @@ Adjudicate(ChessProgramState *cps)
                            if(engineOpponent)
                              SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
                            ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-                           GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
+                           GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
                            return 1;
                        }
@@ -6098,8 +6782,8 @@ Adjudicate(ChessProgramState *cps)
                    if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
                        boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
                    else if(gameInfo.variant == VariantSuicide) // in suicide it depends
-                       boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
-                                                  ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
+                       boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
+                                                  ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
                                                                        EP_CHECKMATE : EP_WINS);
                    else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
                        boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
@@ -6130,11 +6814,9 @@ Adjudicate(ChessProgramState *cps)
                }
 
                 /* Next absolutely insufficient mating material. */
-                if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
-                                    gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
-                       (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
-                        NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
-                {    /* KBK, KNK, KK of KBKB with like Bishops */
+                if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
+                   !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
+                {    /* includes KBK, KNK, KK of KBKB with like Bishops */
 
                      /* always flag draws, for judging claims */
                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
@@ -6152,13 +6834,15 @@ Adjudicate(ChessProgramState *cps)
                 }
 
                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
-                if(NrPieces == 4 && 
-                   (   NrWR == 1 && NrBR == 1 /* KRKR */
-                   || NrWQ==1 && NrBQ==1     /* KQKQ */
-                   || NrWN==2 || NrBN==2     /* KNNK */
-                   || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
-                  ) ) {
-                     if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
+                if(gameInfo.variant == VariantXiangqi ?
+                       SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
+                 : nrW + nrB == 4 &&
+                   (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
+                   || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
+                   || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
+                   || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
+                   ) ) {
+                     if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
                      {    /* if the first 3 moves do not show a tactical win, declare draw */
                          if(engineOpponent) {
                            SendToProgram("force\n", engineOpponent); // suppress reply
@@ -6170,15 +6854,13 @@ Adjudicate(ChessProgramState *cps)
                      }
                 } else moveCount = 6;
            }
-       }
-         
        if (appData.debugMode) { int i;
            fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
                    forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
                    appData.drawRepeats);
            for( i=forwardMostMove; i>=backwardMostMove; i-- )
              fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
-           
+
        }
 
        // Repetition draws and 50-move rule can be applied independently of legality testing
@@ -6203,21 +6885,18 @@ Adjudicate(ChessProgramState *cps)
                         }
                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
-                                rights++; 
+                                rights++;
                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
                                    rights++;
                         }
-                        if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
+                        if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
                             && appData.drawRepeats > 1) {
                              /* adjudicate after user-specified nr of repeats */
-                            if(engineOpponent) {
-                              SendToProgram("force\n", engineOpponent); // suppress reply
-                              SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
-                            }
-                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-                            if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
+                            int result = GameIsDrawn;
+                            char *details = "XBoard adjudication: repetition draw";
+                            if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
                                // [HGM] xiangqi: check for forbidden perpetuals
                                int m, ourPerpetual = 1, hisPerpetual = 1;
                                for(m=forwardMostMove; m>k; m-=2) {
@@ -6229,27 +6908,31 @@ Adjudicate(ChessProgramState *cps)
                                if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
                                                                        ourPerpetual, hisPerpetual);
                                if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
-                                   GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
-                                          "Xboard adjudication: perpetual checking", GE_XBOARD );
-                                   return 1;
-                               }
-                               if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
+                                   result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
+                                   details = "Xboard adjudication: perpetual checking";
+                               } else
+                               if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
                                    break; // (or we would have caught him before). Abort repetition-checking loop.
+                               } else
                                // Now check for perpetual chases
                                if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
                                    hisPerpetual = PerpetualChase(k, forwardMostMove);
                                    ourPerpetual = PerpetualChase(k+1, forwardMostMove);
                                    if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
-                                       GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
-                                                     "Xboard adjudication: perpetual chasing", GE_XBOARD );
-                                       return 1;
-                                   }
+                                       result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
+                                       details = "Xboard adjudication: perpetual chasing";
+                                   } else
                                    if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
                                        break; // Abort repetition-checking loop.
                                }
                                // if neither of us is checking or chasing all the time, or both are, it is draw
                             }
-                             GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
+                            if(engineOpponent) {
+                              SendToProgram("force\n", engineOpponent); // suppress reply
+                              SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+                            }
+                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                             GameEnds( result, details, GE_XBOARD );
                              return 1;
                         }
                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
@@ -6265,7 +6948,18 @@ Adjudicate(ChessProgramState *cps)
                 /* if we hit starting position, add initial plies */
                 if( count == backwardMostMove )
                     count -= initialRulePlies;
-                count = forwardMostMove - count; 
+                count = forwardMostMove - count;
+               if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
+                       // adjust reversible move counter for checks in Xiangqi
+                       int i = forwardMostMove - count, inCheck = 0, lastCheck;
+                       if(i < backwardMostMove) i = backwardMostMove;
+                       while(i <= forwardMostMove) {
+                               lastCheck = inCheck; // check evasion does not count
+                               inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
+                               if(inCheck || lastCheck) count--; // check does not count
+                               i++;
+                       }
+               }
                 if( count >= 100)
                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
                          /* this is used to judge if draw claims are legal */
@@ -6284,8 +6978,7 @@ Adjudicate(ChessProgramState *cps)
                  * claim draws before making their move to avoid a race
                  * condition occurring after their move
                  */
-               if(gameMode == TwoMachinesPlay) // for now; figure out how to handle claims in human games
-                if( cps->other->offeredDraw || cps->offeredDraw ) {
+               if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
                          char *p = NULL;
                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
                              p = "Draw claim: 50-move rule";
@@ -6293,7 +6986,7 @@ Adjudicate(ChessProgramState *cps)
                              p = "Draw claim: 3-fold repetition";
                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
                              p = "Draw claim: insufficient mating material";
-                         if( p != NULL ) {
+                         if( p != NULL && canAdjudicate) {
                             if(engineOpponent) {
                               SendToProgram("force\n", engineOpponent); // suppress reply
                               SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
@@ -6335,8 +7028,7 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
        // after a book hit we never send 'go', and the code after the call to this routine
        // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
        char buf[MSG_SIZ];
-       if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
-       sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
+       snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
        SendToProgram(buf, cps);
        if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
@@ -6344,7 +7036,7 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
        cps->bookSuspend = FALSE; // after a 'go' we are never suspended
     } else { // 'go' might be sent based on 'firstMove' after this routine returns
        if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
-           SendToProgram("go\n", cps); 
+           SendToProgram("go\n", cps);
        cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
     }
     return bookHit; // notify caller of hit, so it can take action to send move to opponent
@@ -6413,7 +7105,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
      * Look for machine move.
      */
     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
-       (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
+       (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
     {
         /* This method is only useful on engines that support ping */
         if (cps->lastPing != cps->lastPong) {
@@ -6488,10 +7180,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
                               &fromX, &fromY, &toX, &toY, &promoChar)) {
            /* Machine move could not be parsed; ignore it. */
-            sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
+         snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
                    machineMove, cps->which);
            DisplayError(buf1, 0);
-            sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
+            snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
            if (gameMode == TwoMachinesPlay) {
              GameEnds(machineWhite ? BlackWins : WhiteWins,
@@ -6505,9 +7197,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         /* to make sure an illegal e.p. capture does not slip through,   */
         /* to cause a forfeit on a justified illegal-move complaint      */
         /* of the opponent.                                              */
-        if( gameMode==TwoMachinesPlay && appData.testLegality
-            && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
-                                                              ) {
+        if( gameMode==TwoMachinesPlay && appData.testLegality ) {
            ChessMove moveType;
            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
                              fromY, fromX, toY, toX, promoChar);
@@ -6518,7 +7208,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                 fprintf(debugFP, "castling rights\n");
            }
             if(moveType == IllegalMove) {
-                sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
+             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
                 GameEnds(machineWhite ? BlackWins : WhiteWins,
                            buf1, GE_XBOARD);
@@ -6548,38 +7238,15 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        if (cps->sendTime == 2) cps->sendTime = 1;
        if (cps->offeredDraw) cps->offeredDraw--;
 
-#if ZIPPY
-       if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
-           first.initDone) {
-         SendMoveToICS(moveType, fromX, fromY, toX, toY);
-         ics_user_moved = 1;
-         if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
-               char buf[3*MSG_SIZ];
-
-               sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
-                       programStats.score / 100.,
-                       programStats.depth,
-                       programStats.time / 100.,
-                       (unsigned int)programStats.nodes,
-                       (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
-                       programStats.movelist);
-               SendToICS(buf);
-if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
-         }
-       }
-#endif
        /* currentMoveString is set as a side-effect of ParseOneMove */
-       strcpy(machineMove, currentMoveString);
+       safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
        strcat(machineMove, "\n");
-       strcpy(moveList[forwardMostMove], machineMove);
+       safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
 
-        /* [AS] Save move info and clear stats for next move */
+        /* [AS] Save move info*/
         pvInfoList[ forwardMostMove ].score = programStats.score;
         pvInfoList[ forwardMostMove ].depth = programStats.depth;
         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
-        ClearProgramStats();
-        thinkOutput[0] = NULLCHAR;
-        hiddenThinkOutputState = 0;
 
        MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
 
@@ -6604,8 +7271,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
             if( count >= adjudicateLossPlies ) {
                ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
 
-                GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
-                    "Xboard adjudication", 
+                GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+                    "Xboard adjudication",
                     GE_XBOARD );
 
                 return;
@@ -6614,6 +7281,37 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
        if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
 
+#if ZIPPY
+       if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
+           first.initDone) {
+         if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+               SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
+               SendToICS("draw ");
+               SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
+         }
+         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
+         ics_user_moved = 1;
+         if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
+               char buf[3*MSG_SIZ];
+
+               snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
+                       programStats.score / 100.,
+                       programStats.depth,
+                       programStats.time / 100.,
+                       (unsigned int)programStats.nodes,
+                       (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
+                       programStats.movelist);
+               SendToICS(buf);
+if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
+         }
+       }
+#endif
+
+        /* [AS] Clear stats for next move */
+        ClearProgramStats();
+        thinkOutput[0] = NULLCHAR;
+        hiddenThinkOutputState = 0;
+
        bookHit = NULL;
        if (gameMode == TwoMachinesPlay) {
             /* [HGM] relaying draw offers moved to after reception of move */
@@ -6637,12 +7335,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
 
        ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-       
+
         if (!pausing && appData.ringBellAfterMoves) {
            RingBell();
        }
 
-       /* 
+       /*
         * Reenable menu items that were disabled while
         * machine was thinking
         */
@@ -6655,11 +7353,11 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        if(bookHit) {
                static char bookMove[MSG_SIZ]; // a bit generous?
 
-               strcpy(bookMove, "move ");
+               safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
                strcat(bookMove, bookHit);
                message = bookMove;
                cps = cps->other;
-               programStats.nodes = programStats.depth = programStats.time = 
+               programStats.nodes = programStats.depth = programStats.time =
                programStats.score = programStats.got_only_move = 0;
                sprintf(programStats.movelist, "%s (xbook)", bookHit);
 
@@ -6705,7 +7403,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            CopyBoard(boards[0], initial_position);
            initialRulePlies = FENrulePlies;
            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
-           else gameMode = MachinePlaysBlack;                 
+           else gameMode = MachinePlaysBlack;
            DrawPosition(FALSE, boards[currentMove]);
         }
        return;
@@ -6715,11 +7413,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
      * Look for communication commands
      */
     if (!strncmp(message, "telluser ", 9)) {
+       EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
        DisplayNote(message + 9);
        return;
     }
     if (!strncmp(message, "tellusererror ", 14)) {
        cps->userError = 1;
+       EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
        DisplayError(message + 14, 0);
        return;
     }
@@ -6760,13 +7460,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        return;
     }
     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
-       strcpy(realname, cps->tidy);
+        safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
        strcat(realname, " query");
        AskQuestion(realname, buf2, buf1, cps->pr);
        return;
     }
-    /* Commands from the engine directly to ICS.  We don't allow these to be 
-     *  sent until we are logged on. Crafty kibitzes have been known to 
+    /* Commands from the engine directly to ICS.  We don't allow these to be
+     *  sent until we are logged on. Crafty kibitzes have been known to
      *  interfere with the login process.
      */
     if (loggedOn) {
@@ -6800,7 +7500,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
      */
     if (strncmp(message + 1, "llegal move", 11) == 0 ||
        strncmp(message, "Error", 5) == 0) {
-       if (StrStr(message, "name") || 
+       if (StrStr(message, "name") ||
            StrStr(message, "rating") || StrStr(message, "?") ||
            StrStr(message, "result") || StrStr(message, "board") ||
            StrStr(message, "bk") || StrStr(message, "computer") ||
@@ -6835,7 +7535,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            cps->analysisSupport = FALSE;
            cps->analyzing = FALSE;
            Reset(FALSE, TRUE);
-           sprintf(buf2, _("%s does not support analysis"), cps->tidy);
+           snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
            DisplayError(buf2, 0);
            return;
        }
@@ -6890,11 +7590,11 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            gameMode = EditGame;
            ModeHighlight();
        }
-       currentMove = --forwardMostMove;
+       currentMove = forwardMostMove-1;
        DisplayMove(currentMove-1); /* before DisplayMoveError */
-       SwitchClocks();
+       SwitchClocks(forwardMostMove-1); // [HGM] race
        DisplayBothClocks();
-       sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
+       snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
                parseList[currentMove], cps->which);
        DisplayMoveError(buf1);
        DrawPosition(FALSE, boards[currentMove]);
@@ -6913,7 +7613,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
           Don't use it. */
        cps->sendTime = 0;
     }
-    
+
     /*
      * If chess program startup fails, exit with an error message.
      * Attempts to recover here are futile.
@@ -6932,8 +7632,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        DisplayFatalError(buf1, 0, 1);
        return;
     }
-    
-    /* 
+
+    /*
      * Look for hint output
      */
     if (sscanf(message, "Hint: %s", buf1) == 1) {
@@ -6954,7 +7654,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                DisplayError(buf2, 0);
            }
        } else {
-           strcpy(lastHint, buf1);
+         safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
        }
        return;
     }
@@ -7007,7 +7707,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                r = p + 1;
            }
        }
-            
+
         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
        return;
 
@@ -7132,7 +7832,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
     }
 
-    
+
     /*
      * Look for thinking output
      */
@@ -7173,6 +7873,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
 
        if (!ignore) {
+           ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
            buf1[0] = NULLCHAR;
            if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
@@ -7182,7 +7883,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                }
 
                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
-                if( cps->scoreIsAbsolute && 
+                if( cps->scoreIsAbsolute &&
                     ( gameMode == MachinePlaysBlack ||
                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
@@ -7194,11 +7895,11 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                 }
 
 
-               programStats.depth = plylev;
-               programStats.nodes = nodes;
-               programStats.time = time;
-               programStats.score = curscore;
-               programStats.got_only_move = 0;
+               tempStats.depth = plylev;
+               tempStats.nodes = nodes;
+               tempStats.time = time;
+               tempStats.score = curscore;
+               tempStats.got_only_move = 0;
 
                if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
                        int ticklen;
@@ -7206,52 +7907,55 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                        if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
                        else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
                        if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
-                                               gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
+                                               gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
                             whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
                        if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
-                                               gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
+                                               gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
                             blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
                }
 
                /* Buffer overflow protection */
                if (buf1[0] != NULLCHAR) {
-                   if (strlen(buf1) >= sizeof(programStats.movelist)
+                   if (strlen(buf1) >= sizeof(tempStats.movelist)
                        && appData.debugMode) {
                        fprintf(debugFP,
                                "PV is too long; using the first %u bytes.\n",
-                               (unsigned) sizeof(programStats.movelist) - 1);
+                               (unsigned) sizeof(tempStats.movelist) - 1);
                    }
 
-                    safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
+                    safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
                } else {
-                   sprintf(programStats.movelist, " no PV\n");
+                   sprintf(tempStats.movelist, " no PV\n");
                }
 
-               if (programStats.seen_stat) {
-                   programStats.ok_to_send = 1;
+               if (tempStats.seen_stat) {
+                   tempStats.ok_to_send = 1;
                }
 
-               if (strchr(programStats.movelist, '(') != NULL) {
-                   programStats.line_is_book = 1;
-                   programStats.nr_moves = 0;
-                   programStats.moves_left = 0;
+               if (strchr(tempStats.movelist, '(') != NULL) {
+                   tempStats.line_is_book = 1;
+                   tempStats.nr_moves = 0;
+                   tempStats.moves_left = 0;
                } else {
-                   programStats.line_is_book = 0;
+                   tempStats.line_is_book = 0;
                }
 
-                SendProgramStatsToFrontend( cps, &programStats );
+                   if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
+                       programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
 
-                /* 
+                SendProgramStatsToFrontend( cps, &tempStats );
+
+                /*
                     [AS] Protect the thinkOutput buffer from overflow... this
                     is only useful if buf1 hasn't overflowed first!
                 */
-               sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
-                       plylev, 
-                       (gameMode == TwoMachinesPlay ?
-                        ToUpper(cps->twoMachinesColor[0]) : ' '),
-                       ((double) curscore) / 100.0,
-                       prefixHint ? lastHint : "",
-                       prefixHint ? " " : "" );
+               snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
+                        plylev,
+                        (gameMode == TwoMachinesPlay ?
+                         ToUpper(cps->twoMachinesColor[0]) : ' '),
+                        ((double) curscore) / 100.0,
+                        prefixHint ? lastHint : "",
+                        prefixHint ? " " : "" );
 
                 if( buf1[0] != NULLCHAR ) {
                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
@@ -7277,7 +7981,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                 * if there is only 1 legal move
                  */
                sscanf(p, "(only move) %s", buf1);
-               sprintf(thinkOutput, "%s (only move)", buf1);
+               snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
                sprintf(programStats.movelist, "%s (only move)", buf1);
                programStats.depth = 1;
                programStats.nr_moves = 1;
@@ -7292,8 +7996,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                programStats.line_is_book = 1;
 
                 SendProgramStatsToFrontend( cps, &programStats );
-                
-               if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
+
+               if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
                }
@@ -7314,7 +8018,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                programStats.nodes = nodes;
                programStats.moves_left = mvleft;
                programStats.nr_moves = mvtot;
-               strcpy(programStats.move_name, mvname);
+               safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
                programStats.ok_to_send = 1;
                 programStats.movelist[0] = '\0';
 
@@ -7363,7 +8067,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            buf1[0] = NULLCHAR;
 
            if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
-                      &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
+                      &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
             {
                 ChessProgramStats cpstats;
 
@@ -7384,7 +8088,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                 cpstats.movelist[0] = '\0';
 
                if (buf1[0] != NULLCHAR) {
-                    safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
+                    safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
                }
 
                cpstats.ok_to_send = 0;
@@ -7401,7 +8105,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
 /* Parse a game score from the character string "game", and
    record it as the history of the current game.  The game
-   score is NOT assumed to start from the standard position. 
+   score is NOT assumed to start from the standard position.
    The display is not updated in any way.
    */
 void
@@ -7447,20 +8151,10 @@ ParseGameHistory(game)
     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
     setbuf(debugFP, NULL);
   }
-          case WhitePromotionChancellor:
-          case BlackPromotionChancellor:
-          case WhitePromotionArchbishop:
-          case BlackPromotionArchbishop:
-         case WhitePromotionQueen:
-         case BlackPromotionQueen:
-         case WhitePromotionRook:
-         case BlackPromotionRook:
-         case WhitePromotionBishop:
-         case BlackPromotionBishop:
-         case WhitePromotionKnight:
-         case BlackPromotionKnight:
-         case WhitePromotionKing:
-         case BlackPromotionKing:
+         case WhitePromotion:
+         case BlackPromotion:
+         case WhiteNonPromotion:
+         case BlackNonPromotion:
          case NormalMove:
          case WhiteCapturesEnPassant:
          case BlackCapturesEnPassant:
@@ -7496,7 +8190,7 @@ ParseGameHistory(game)
            break;
          case AmbiguousMove:
            /* bug? */
-           sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
+           snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
   if (appData.debugMode) {
     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
@@ -7506,7 +8200,7 @@ ParseGameHistory(game)
            return;
          case ImpossibleMove:
            /* bug? */
-           sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
+           snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
   if (appData.debugMode) {
     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
@@ -7573,7 +8267,7 @@ ParseGameHistory(game)
                                 parseList[boardIndex]);
        CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
        /* currentMoveString is set as a side-effect of yylex */
-       strcpy(moveList[boardIndex], currentMoveString);
+       safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
        strcat(moveList[boardIndex], "\n");
        boardIndex++;
        ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
@@ -7607,14 +8301,23 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
 
     /* [HGM] compute & store e.p. status and castling rights for new position */
     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
-    { int i;
 
       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
       oldEP = (signed char)board[EP_STATUS];
       board[EP_STATUS] = EP_NONE;
 
-      if( board[toY][toX] != EmptySquare ) 
-           board[EP_STATUS] = EP_CAPTURE;  
+      if( board[toY][toX] != EmptySquare )
+           board[EP_STATUS] = EP_CAPTURE;
+
+  /* [HGM] In Shatranj and Courier all promotions are to Ferz */
+  if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
+       && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
+         
+  if (fromY == DROP_RANK) {
+       /* must be first */
+        piece = board[toY][toX] = (ChessSquare) fromX;
+  } else {
+      int i;
 
       if( board[fromY][fromX] == WhitePawn ) {
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
@@ -7624,41 +8327,31 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
                        gameInfo.variant != VariantBerolina || toX < fromX)
                      board[EP_STATUS] = toX | berolina;
                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
-                       gameInfo.variant != VariantBerolina || toX > fromX) 
+                       gameInfo.variant != VariantBerolina || toX > fromX)
                      board[EP_STATUS] = toX;
           }
-      } else 
+      } else
       if( board[fromY][fromX] == BlackPawn ) {
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
-              board[EP_STATUS] = EP_PAWN_MOVE; 
+              board[EP_STATUS] = EP_PAWN_MOVE;
            if( toY-fromY== -2) {
                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
                      board[EP_STATUS] = toX | berolina;
                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
-                       gameInfo.variant != VariantBerolina || toX > fromX) 
+                       gameInfo.variant != VariantBerolina || toX > fromX)
                      board[EP_STATUS] = toX;
           }
        }
 
        for(i=0; i<nrCastlingRights; i++) {
            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
-              board[CASTLING][i] == toX   && castlingRank[i] == toY   
+              board[CASTLING][i] == toX   && castlingRank[i] == toY
              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
        }
 
-    }
-
-  /* [HGM] In Shatranj and Courier all promotions are to Ferz */
-  if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
-       && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
-         
-  if (fromX == toX && fromY == toY) return;
+     if (fromX == toX && fromY == toY) return;
 
-  if (fromY == DROP_RANK) {
-       /* must be first */
-        piece = board[toY][toX] = (ChessSquare) fromX;
-  } else {
      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
      if(gameInfo.variant == VariantKnightmate)
@@ -7804,10 +8497,6 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = board[fromY][fromX];
        board[fromY][fromX] = EmptySquare;
     }
-
-    /* [HGM] now we promote for Shogi, if needed */
-    if(gameInfo.variant == VariantShogi && promoChar == 'q')
-        board[toY][toX] = (ChessSquare) (PROMOTED piece);
   }
 
     if (gameInfo.holdingsWidth != 0) {
@@ -7841,7 +8530,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       if (captured != EmptySquare && gameInfo.holdingsSize > 0
           && gameInfo.variant != VariantBughouse        ) {
         /* [HGM] holdings: Add to holdings, if holdings exist */
-       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
+       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
                // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
                captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
        }
@@ -7883,11 +8572,12 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = EmptySquare;
       }
     }
-    if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
-        /* [HGM] Shogi promotions */
+    if(promoChar == '+') {
+        /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
         board[toY][toX] = (ChessSquare) (PROMOTED piece);
+    } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
+        board[toY][toX] = CharToPiece(promoChar);
     }
-
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
                && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
        // [HGM] superchess: take promotion piece out of holdings
@@ -7918,10 +8608,10 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
         if(gameInfo.variant == VariantKnightmate)
             king += (int) WhiteUnicorn - (int) WhiteKing;
         if(forwardMostMove == 0) {
-            if(blackPlaysFirst) 
+            if(blackPlaysFirst)
                 fprintf(serverMoves, "%s;", second.tidy);
             fprintf(serverMoves, "%s;", first.tidy);
-            if(!blackPlaysFirst) 
+            if(!blackPlaysFirst)
                 fprintf(serverMoves, "%s;", second.tidy);
         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
         lastLoadFlag = loadFlag;
@@ -7938,7 +8628,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
              boards[forwardMostMove][toY][toX] == EmptySquare
-             && fromX != toX )
+             && fromX != toX && fromY != toY)
                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
         // promotion suffix
         if(promoChar != NULLCHAR)
@@ -7965,8 +8655,8 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
     }
     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
-    forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
-    SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
+    // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
+    SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     gameInfo.result = GameUnfinished;
@@ -8040,10 +8730,10 @@ void SendEgtPath(ChessProgramState *cps)
            name[0] = ','; // extract next format name from feature and copy with prefixed ','
            while(*p && *p != ',') *q++ = *p++;
            *q++ = ':'; *q = 0;
-           if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
+           if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
                strcmp(name, ",nalimov:") == 0 ) {
                // take nalimov path from the menu-changeable option first, if it is defined
-               sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
+             snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
                SendToProgram(buf,cps);     // send egtbpath command for nalimov
            } else
            if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
@@ -8053,7 +8743,7 @@ void SendEgtPath(ChessProgramState *cps)
                while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
                c = *r; *r = 0;             // temporarily null-terminate path info
                    *--q = 0;               // strip of trailig ':' from name
-                   sprintf(buf, "egtpath %s %s\n", name+1, s);
+                   snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
                *r = c;
                SendToProgram(buf,cps);     // send egtbpath command for this format
            }
@@ -8074,12 +8764,12 @@ InitChessProgram(cps, setup)
     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
     if(cps->memSize) { /* [HGM] memory */
-       sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
+      snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
        SendToProgram(buf, cps);
     }
     SendEgtPath(cps); /* [HGM] EGT */
     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
-       sprintf(buf, "cores %d\n", appData.smpCores);
+      snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
        SendToProgram(buf, cps);
     }
 
@@ -8092,7 +8782,7 @@ InitChessProgram(cps, setup)
       char *v = VariantName(gameInfo.variant);
       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
         /* [HGM] in protocol 1 we have to assume all variants valid */
-       sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
+       snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
        DisplayFatalError(buf, 0, 1);
        return;
       }
@@ -8105,7 +8795,7 @@ InitChessProgram(cps, setup)
            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
-      if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
+      if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
       if( gameInfo.variant == VariantCourier )
@@ -8116,14 +8806,14 @@ InitChessProgram(cps, setup)
            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
 
       if(overruled) {
-           sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
-                               gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
+       snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
+                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
            /* [HGM] varsize: try first if this defiant size variant is specifically known */
-           if(StrStr(cps->variants, b) == NULL) { 
+           if(StrStr(cps->variants, b) == NULL) {
                // specific sized variant not known, check if general sizing allowed
                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
                    if(StrStr(cps->variants, "boardsize") == NULL) {
-                       sprintf(buf, "Board size %dx%d+%d not supported by %s",
+                    snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
                        DisplayFatalError(buf, 0, 1);
                        return;
@@ -8131,8 +8821,8 @@ InitChessProgram(cps, setup)
                    /* [HGM] here we really should compare with the maximum supported board size */
                }
            }
-      } else sprintf(b, "%s", VariantName(gameInfo.variant));
-      sprintf(buf, "variant %s\n", b);
+      } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
+      snprintf(buf, MSG_SIZ, "variant %s\n", b);
       SendToProgram(buf, cps);
     }
     currentlyInitializedVariant = gameInfo.variant;
@@ -8156,7 +8846,7 @@ InitChessProgram(cps, setup)
                        timeIncrement, appData.searchDepth,
                        searchTime);
     }
-    if (appData.showThinking 
+    if (appData.showThinking
        // [HGM] thinking: four options require thinking output to be sent
        || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
                                ) {
@@ -8171,11 +8861,11 @@ InitChessProgram(cps, setup)
        SendToProgram("easy\n", cps);
     }
     if (cps->usePing) {
-      sprintf(buf, "ping %d\n", ++cps->lastPing);
+      snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
-}   
+}
 
 
 void
@@ -8202,18 +8892,18 @@ StartChessProgram(cps)
        }
        err = StartChildProcess(buf, "", &cps->pr);
     }
-    
+
     if (err != 0) {
-       sprintf(buf, _("Startup failure on '%s'"), cps->program);
+      snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
        DisplayFatalError(buf, err, 1);
        cps->pr = NoProc;
        cps->isr = NULL;
        return;
     }
-    
+
     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
     if (cps->protocolVersion > 1) {
-      sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
+      snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
       cps->comboCnt = 0;  //                and values of combo boxes
       SendToProgram(buf, cps);
@@ -8250,7 +8940,7 @@ NextMatchGame P((void))
        if(index < 0) { // [HGM] autoinc
            lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
            if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
-       } 
+       }
        LoadGameFromFile(appData.loadGameFile,
                         index,
                         appData.loadGameFile, FALSE);
@@ -8259,7 +8949,7 @@ NextMatchGame P((void))
        if(index < 0) { // [HGM] autoinc
            lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
            if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
-       } 
+       }
        LoadPositionFromFile(appData.loadPositionFile,
                             index,
                             appData.loadPositionFile);
@@ -8314,20 +9004,25 @@ GameEnds(result, resultDetails, whosays)
 {
     GameMode nextGameMode;
     int isIcsGame;
-    char buf[MSG_SIZ];
+    char buf[MSG_SIZ], popupRequested = 0;
 
     if(endingGame) return; /* [HGM] crash: forbid recursion */
     endingGame = 1;
-
+    if(twoBoards) { // [HGM] dual: switch back to one board
+       twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
+       DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
+    }
     if (appData.debugMode) {
       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
              result, resultDetails ? resultDetails : "(null)", whosays);
     }
 
+    fromX = fromY = -1; // [HGM] abort any move the user is entering.
+
     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
        /* If we are playing on ICS, the server decides when the
-          game is over, but the engine can offer to draw, claim 
-          a draw, or resign. 
+          game is over, but the engine can offer to draw, claim
+          a draw, or resign.
         */
 #if ZIPPY
        if (appData.zippyPlay && first.initDone) {
@@ -8356,17 +9051,17 @@ GameEnds(result, resultDetails, whosays)
 
     /* If this is an ICS game, only ICS can really say it's done;
        if not, anyone can. */
-    isIcsGame = (gameMode == IcsPlayingWhite || 
-                gameMode == IcsPlayingBlack || 
-                gameMode == IcsObserving    || 
+    isIcsGame = (gameMode == IcsPlayingWhite ||
+                gameMode == IcsPlayingBlack ||
+                gameMode == IcsObserving    ||
                 gameMode == IcsExamining);
 
     if (!isIcsGame || whosays == GE_ICS) {
        /* OK -- not an ICS game, or ICS said it was done */
        StopClocks();
-       if (!isIcsGame && !appData.noChessProgram) 
+       if (!isIcsGame && !appData.noChessProgram)
          SetUserThinkingEnables();
-    
+
         /* [HGM] if a machine claims the game end we verify this claim */
         if(gameMode == TwoMachinesPlay && appData.testClaims) {
            if(appData.testLegality && whosays >= GE_ENGINE1 ) {
@@ -8400,7 +9095,7 @@ GameEnds(result, resultDetails, whosays)
                                result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
                      }
                      if(result != trueResult) {
-                             sprintf(buf, "False win claim: '%s'", resultDetails);
+                       snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
                              result = claimer == 'w' ? BlackWins : WhiteWins;
                              resultDetails = buf;
                      }
@@ -8411,7 +9106,7 @@ GameEnds(result, resultDetails, whosays)
                         (claimer=='b')==(forwardMostMove&1))
                                                                                   ) {
                       /* [HGM] verify: draws that were not flagged are false claims */
-                      sprintf(buf, "False draw claim: '%s'", resultDetails);
+                 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
                       result = claimer == 'w' ? BlackWins : WhiteWins;
                       resultDetails = buf;
                 }
@@ -8419,7 +9114,7 @@ GameEnds(result, resultDetails, whosays)
            }
            /* [HGM] bare: don't allow bare King to win */
            if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
-              && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
+              && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
               && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
               && result != GameIsDrawn)
            {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
@@ -8433,7 +9128,7 @@ GameEnds(result, resultDetails, whosays)
                }
                if(k <= 1) {
                        result = GameIsDrawn;
-                       sprintf(buf, "%s but bare king", resultDetails);
+                       snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
                        resultDetails = buf;
                }
            }
@@ -8453,7 +9148,7 @@ GameEnds(result, resultDetails, whosays)
            /* display last move only if game was not loaded from file */
            if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
                DisplayMove(currentMove - 1);
-    
+
            if (forwardMostMove != 0) {
                if (gameMode != PlayFromGameFile && gameMode != EditGame
                    && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
@@ -8483,7 +9178,7 @@ GameEnds(result, resultDetails, whosays)
                gameMode == IcsPlayingBlack ||
                gameMode == BeginningOfGame) {
                char buf[MSG_SIZ];
-               sprintf(buf, "result %s {%s}\n", PGNResult(result),
+               snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
                        resultDetails);
                if (first.pr != NoProc) {
                    SendToProgram(buf, &first);
@@ -8534,8 +9229,8 @@ GameEnds(result, resultDetails, whosays)
                }
            }
        } else if (gameMode == EditGame ||
-                  gameMode == PlayFromGameFile || 
-                  gameMode == AnalyzeMode || 
+                  gameMode == PlayFromGameFile ||
+                  gameMode == AnalyzeMode ||
                   gameMode == AnalyzeFile) {
            nextGameMode = gameMode;
        } else {
@@ -8566,7 +9261,7 @@ GameEnds(result, resultDetails, whosays)
            SendToProgram("force\n", &first);
            if (first.usePing) {
              char buf[MSG_SIZ];
-             sprintf(buf, "ping %d\n", ++first.lastPing);
+             snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
              SendToProgram(buf, &first);
            }
        }
@@ -8575,7 +9270,7 @@ GameEnds(result, resultDetails, whosays)
        if (first.isr != NULL)
          RemoveInputSource(first.isr);
        first.isr = NULL;
-    
+
        if (first.pr != NoProc) {
            ExitAnalyzeMode();
             DoSleep( appData.delayBeforeQuit );
@@ -8592,7 +9287,7 @@ GameEnds(result, resultDetails, whosays)
            SendToProgram("force\n", &second);
            if (second.usePing) {
              char buf[MSG_SIZ];
-             sprintf(buf, "ping %d\n", ++second.lastPing);
+             snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
              SendToProgram(buf, &second);
            }
        }
@@ -8601,7 +9296,7 @@ GameEnds(result, resultDetails, whosays)
        if (second.isr != NULL)
          RemoveInputSource(second.isr);
        second.isr = NULL;
-    
+
        if (second.pr != NoProc) {
             DoSleep( appData.delayBeforeQuit );
            SendToProgram("quit\n", &second);
@@ -8645,13 +9340,12 @@ GameEnds(result, resultDetails, whosays)
            endingGame = 0; /* [HGM] crash */
            return;
        } else {
-           char buf[MSG_SIZ];
            gameMode = nextGameMode;
-           sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
-                   first.tidy, second.tidy,
-                   first.matchWins, second.matchWins,
-                   appData.matchGames - (first.matchWins + second.matchWins));
-           DisplayFatalError(buf, 0, 0);
+           snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
+                    first.tidy, second.tidy,
+                    first.matchWins, second.matchWins,
+                    appData.matchGames - (first.matchWins + second.matchWins));
+           popupRequested++; // [HGM] crash: postpone to after resetting endingGame
        }
     }
     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
@@ -8660,26 +9354,33 @@ GameEnds(result, resultDetails, whosays)
     gameMode = nextGameMode;
     ModeHighlight();
     endingGame = 0;  /* [HGM] crash */
+    if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
+      if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
+       matchMode = FALSE; appData.matchGames = matchGame = 0;
+       DisplayNote(buf);
+      }
+    }
 }
 
 /* Assumes program was just initialized (initString sent).
    Leaves program in force mode. */
 void
-FeedMovesToProgram(cps, upto) 
+FeedMovesToProgram(cps, upto)
      ChessProgramState *cps;
      int upto;
 {
     int i;
-    
+
     if (appData.debugMode)
       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
              startedFromSetupPosition ? "position and " : "",
              backwardMostMove, upto, cps->which);
-    if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
+    if(currentlyInitializedVariant != gameInfo.variant) {
+      char buf[MSG_SIZ];
         // [HGM] variantswitch: make engine aware of new variant
        if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
                return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
-       sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
+       snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
        SendToProgram(buf, cps);
         currentlyInitializedVariant = gameInfo.variant;
     }
@@ -8703,7 +9404,7 @@ ResurrectChessProgram()
        If so, restart it and feed it all the moves made so far. */
 
     if (appData.noChessProgram || first.pr != NoProc) return;
-    
+
     StartChessProgram(&first);
     InitChessProgram(&first, FALSE);
     FeedMovesToProgram(&first, currentMove);
@@ -8757,7 +9458,7 @@ Reset(redraw, init)
     white_holding[0] = black_holding[0] = NULLCHAR;
     ClearProgramStats();
     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
-    
+
     ResetFrontEnd();
     ClearHighlights();
     flipView = appData.flipView;
@@ -8843,7 +9544,7 @@ AutoPlayOneMove()
 
       return FALSE;
     }
-    
+
     toX = moveList[currentMove][2] - AAA;
     toY = moveList[currentMove][3] - ONE;
 
@@ -8882,13 +9583,13 @@ LoadGameOneMove(readAhead)
     ChessMove moveType;
     char move[MSG_SIZ];
     char *p, *q;
-    
-    if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
+
+    if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
        gameMode != AnalyzeMode && gameMode != Training) {
        gameFileFP = NULL;
        return FALSE;
     }
-    
+
     yyboardindex = forwardMostMove;
     if (readAhead != (ChessMove)0) {
       moveType = readAhead;
@@ -8897,11 +9598,11 @@ LoadGameOneMove(readAhead)
          return FALSE;
       moveType = (ChessMove) yylex();
     }
-    
+
     done = FALSE;
     switch (moveType) {
       case Comment:
-       if (appData.debugMode) 
+       if (appData.debugMode)
          fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
        p = yy_text;
 
@@ -8911,22 +9612,10 @@ LoadGameOneMove(readAhead)
 
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
-      case WhitePromotionChancellor:
-      case BlackPromotionChancellor:
-      case WhitePromotionArchbishop:
-      case BlackPromotionArchbishop:
-      case WhitePromotionCentaur:
-      case BlackPromotionCentaur:
-      case WhitePromotionQueen:
-      case BlackPromotionQueen:
-      case WhitePromotionRook:
-      case BlackPromotionRook:
-      case WhitePromotionBishop:
-      case BlackPromotionBishop:
-      case WhitePromotionKnight:
-      case BlackPromotionKnight:
-      case WhitePromotionKing:
-      case BlackPromotionKing:
+      case WhitePromotion:
+      case BlackPromotion:
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
       case NormalMove:
       case WhiteKingSideCastle:
       case WhiteQueenSideCastle:
@@ -9059,7 +9748,7 @@ LoadGameOneMove(readAhead)
        if (appData.testLegality) {
            if (appData.debugMode)
              fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
-           sprintf(move, _("Illegal move: %d.%s%s"),
+           snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
                    (forwardMostMove / 2) + 1,
                    WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
            DisplayError(move, 0);
@@ -9079,7 +9768,7 @@ LoadGameOneMove(readAhead)
       case AmbiguousMove:
        if (appData.debugMode)
          fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
-       sprintf(move, _("Ambiguous move: %d.%s%s"),
+       snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
                (forwardMostMove / 2) + 1,
                WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
        DisplayError(move, 0);
@@ -9090,7 +9779,7 @@ LoadGameOneMove(readAhead)
       case ImpossibleMove:
        if (appData.debugMode)
          fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
-       sprintf(move, _("Illegal move: %d.%s%s"),
+       snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
                (forwardMostMove / 2) + 1,
                WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
        DisplayError(move, 0);
@@ -9112,8 +9801,8 @@ LoadGameOneMove(readAhead)
     } else {
        /* currentMoveString is set as a side-effect of yylex */
        strcat(currentMoveString, "\n");
-       strcpy(moveList[forwardMostMove], currentMoveString);
-       
+       safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
+
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
        currentMove = forwardMostMove;
@@ -9176,9 +9865,9 @@ MakeRegisteredMove()
            if (appData.debugMode)
              fprintf(debugFP, "Restoring %s for game %d\n",
                      cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
-    
+
            thinkOutput[0] = NULLCHAR;
-           strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
+           safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
@@ -9186,12 +9875,12 @@ MakeRegisteredMove()
            promoChar = cmailMove[lastLoadGameNumber - 1][4];
            MakeMove(fromX, fromY, toX, toY, promoChar);
            ShowMove(fromX, fromY, toX, toY);
-             
+
            switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
              case MT_NONE:
              case MT_CHECK:
                break;
-               
+
              case MT_CHECKMATE:
              case MT_STAINMATE:
                if (WhiteOnMove(currentMove)) {
@@ -9200,14 +9889,14 @@ MakeRegisteredMove()
                    GameEnds(WhiteWins, "White mates", GE_PLAYER);
                }
                break;
-               
+
              case MT_STALEMATE:
                GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
                break;
            }
 
            break;
-           
+
          case CMAIL_RESIGN:
            if (WhiteOnMove(currentMove)) {
                GameEnds(BlackWins, "White resigns", GE_PLAYER);
@@ -9215,11 +9904,11 @@ MakeRegisteredMove()
                GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
            }
            break;
-           
+
          case CMAIL_ACCEPT:
            GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
            break;
-             
+
          default:
            break;
        }
@@ -9315,7 +10004,7 @@ LoadGame(f, gameNumber, title, useList)
     GameMode oldGameMode;
     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
 
-    if (appData.debugMode) 
+    if (appData.debugMode)
        fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
 
     if (gameMode == Training )
@@ -9333,7 +10022,7 @@ LoadGame(f, gameNumber, title, useList)
 
     if (useList) {
        lg = (ListGame *) ListElem(&gameList, gameNumber-1);
-       
+
        if (lg) {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
@@ -9358,7 +10047,7 @@ LoadGame(f, gameNumber, title, useList)
     }
     lastLoadGameFP = f;
     lastLoadGameNumber = gameNumber;
-    strcpy(lastLoadGameTitle, title);
+    safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
     lastLoadGameUseList = useList;
 
     yynewfile(f);
@@ -9369,7 +10058,7 @@ LoadGame(f, gameNumber, title, useList)
            DisplayTitle(buf);
     } else if (*title != NULLCHAR) {
        if (gameNumber > 1) {
-           sprintf(buf, "%s %d", title, gameNumber);
+         snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
            DisplayTitle(buf);
        } else {
            DisplayTitle(title);
@@ -9387,12 +10076,12 @@ LoadGame(f, gameNumber, title, useList)
 
     /*
      * Skip the first gn-1 games in the file.
-     * Also skip over anything that precedes an identifiable 
-     * start of game marker, to avoid being confused by 
-     * garbage at the start of the file.  Currently 
+     * Also skip over anything that precedes an identifiable
+     * start of game marker, to avoid being confused by
+     * garbage at the start of the file.  Currently
      * recognized start of game markers are the move number "1",
      * the pattern "gnuchess .* game", the pattern
-     * "^[#;%] [^ ]* game file", and a PGN tag block.  
+     * "^[#;%] [^ ]* game file", and a PGN tag block.
      * A game that starts with one of the latter two patterns
      * will also have a move number 1, possibly
      * following a position diagram.
@@ -9418,7 +10107,7 @@ LoadGame(f, gameNumber, title, useList)
            gn--;
            lastLoadGameStart = cm;
            break;
-           
+
          case MoveNumberOne:
            switch (lastLoadGameStart) {
              case GNUChessGame:
@@ -9486,7 +10175,7 @@ LoadGame(f, gameNumber, title, useList)
            break;
        }
     }
-    
+
     if (appData.debugMode)
       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
 
@@ -9512,11 +10201,11 @@ LoadGame(f, gameNumber, title, useList)
            free(gameInfo.event);
        }
        gameInfo.event = StrSave(yy_text);
-    }  
+    }
 
     startedFromSetupPosition = FALSE;
     while (cm == PGNTag) {
-       if (appData.debugMode) 
+       if (appData.debugMode)
          fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
        err = ParsePGNTag(yy_text, &gameInfo);
        if (!err) numPGNTags++;
@@ -9524,9 +10213,10 @@ LoadGame(f, gameNumber, title, useList)
         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
         if(gameInfo.variant != oldVariant) {
             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
+           ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
            InitPosition(TRUE);
             oldVariant = gameInfo.variant;
-           if (appData.debugMode) 
+           if (appData.debugMode)
              fprintf(debugFP, "New variant %d\n", (int) oldVariant);
         }
 
@@ -9543,8 +10233,8 @@ LoadGame(f, gameNumber, title, useList)
          if (blackPlaysFirst) {
            currentMove = forwardMostMove = backwardMostMove = 1;
            CopyBoard(boards[1], initial_position);
-           strcpy(moveList[0], "");
-           strcpy(parseList[0], "");
+           safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
+           safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
            timeRemaining[0][1] = whiteTimeRemaining;
            timeRemaining[1][1] = blackTimeRemaining;
            if (commentList[0] != NULL) {
@@ -9571,7 +10261,7 @@ LoadGame(f, gameNumber, title, useList)
        /* Handle comments interspersed among the tags */
        while (cm == Comment) {
            char *p;
-           if (appData.debugMode) 
+           if (appData.debugMode)
              fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
            p = yy_text;
            AppendComment(currentMove, p, FALSE);
@@ -9586,7 +10276,9 @@ LoadGame(f, gameNumber, title, useList)
     if (numPGNTags > 0){
         char *tags;
        if (gameInfo.variant == VariantNormal) {
-         gameInfo.variant = StringToVariant(gameInfo.event);
+         VariantClass v = StringToVariant(gameInfo.event);
+         // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
+         if(v < VariantShogi) gameInfo.variant = v;
        }
        if (!matchMode) {
           if( appData.autoDisplayTags ) {
@@ -9627,19 +10319,19 @@ LoadGame(f, gameNumber, title, useList)
                }
            while (*p == ' ' || *p == '\t' ||
                   *p == '\n' || *p == '\r') p++;
-       
+
            if (strncmp(p, "black", strlen("black"))==0)
              blackPlaysFirst = TRUE;
            else
              blackPlaysFirst = FALSE;
            startedFromSetupPosition = TRUE;
-       
+
            CopyBoard(boards[0], initial_position);
            if (blackPlaysFirst) {
                currentMove = forwardMostMove = backwardMostMove = 1;
                CopyBoard(boards[1], initial_position);
-               strcpy(moveList[0], "");
-               strcpy(parseList[0], "");
+               safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
+               safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
                timeRemaining[0][1] = whiteTimeRemaining;
                timeRemaining[1][1] = blackTimeRemaining;
                if (commentList[0] != NULL) {
@@ -9665,14 +10357,14 @@ LoadGame(f, gameNumber, title, useList)
         fprintf(debugFP, "Load Game\n");
     }
        DisplayBothClocks();
-    }      
+    }
 
     /* [HGM] server: flag to write setup moves in broadcast file as one */
     loadFlag = appData.suppressLoadMoves;
 
     while (cm == Comment) {
        char *p;
-       if (appData.debugMode) 
+       if (appData.debugMode)
          fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
        p = yy_text;
        AppendComment(currentMove, p, FALSE);
@@ -9703,7 +10395,7 @@ LoadGame(f, gameNumber, title, useList)
     if (!matchMode && (pausing || appData.timeDelay != 0)) {
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
-    if (!matchMode && appData.timeDelay != 0) 
+    if (!matchMode && appData.timeDelay != 0)
       DrawPosition(FALSE, boards[currentMove]);
 
     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
@@ -9711,7 +10403,7 @@ LoadGame(f, gameNumber, title, useList)
     }
 
     /* if the first token after the PGN tags is a move
-     * and not move number 1, retrieve it from the parser 
+     * and not move number 1, retrieve it from the parser
      */
     if (cm != MoveNumberOne)
        LoadGameOneMove(cm);
@@ -9740,7 +10432,7 @@ LoadGame(f, gameNumber, title, useList)
       AutoPlayGameLoop();
     }
 
-    if (appData.debugMode) 
+    if (appData.debugMode)
        fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
 
     loadFlag = 0; /* [HGM] true game starts */
@@ -9799,7 +10491,7 @@ LoadPosition(f, positionNumber, title)
     char *p, line[MSG_SIZ];
     Board initial_position;
     int i, j, fenMode, pn;
-    
+
     if (gameMode == Training )
        SetTrainingModeOff();
 
@@ -9812,11 +10504,11 @@ LoadPosition(f, positionNumber, title)
     if (positionNumber == 0) positionNumber = 1;
     lastLoadPositionFP = f;
     lastLoadPositionNumber = positionNumber;
-    strcpy(lastLoadPositionTitle, title);
+    safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
     if (first.pr == NoProc) {
       StartChessProgram(&first);
       InitChessProgram(&first, FALSE);
-    }    
+    }
     pn = positionNumber;
     if (positionNumber < 0) {
        /* Negative position number means to seek to that byte offset */
@@ -9866,7 +10558,7 @@ LoadPosition(f, positionNumber, title)
     } else {
        (void) fgets(line, MSG_SIZ, f);
        (void) fgets(line, MSG_SIZ, f);
-    
+
         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
            (void) fgets(line, MSG_SIZ, f);
             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
@@ -9875,7 +10567,7 @@ LoadPosition(f, positionNumber, title)
                initial_position[i][j++] = CharToPiece(*p);
            }
        }
-    
+
        blackPlaysFirst = FALSE;
        if (!feof(f)) {
            (void) fgets(line, MSG_SIZ, f);
@@ -9884,13 +10576,13 @@ LoadPosition(f, positionNumber, title)
        }
     }
     startedFromSetupPosition = TRUE;
-    
+
     SendToProgram("force\n", &first);
     CopyBoard(boards[0], initial_position);
     if (blackPlaysFirst) {
        currentMove = forwardMostMove = backwardMostMove = 1;
-       strcpy(moveList[0], "");
-       strcpy(parseList[0], "");
+       safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
+       safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
        CopyBoard(boards[1], initial_position);
        DisplayMessage("", _("Black to play"));
     } else {
@@ -9907,7 +10599,7 @@ int i, j;
     }
 
     if (positionNumber > 1) {
-       sprintf(line, "%s %d", title, positionNumber);
+      snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
        DisplayTitle(line);
     } else {
        DisplayTitle(title);
@@ -9918,7 +10610,7 @@ int i, j;
     timeRemaining[0][1] = whiteTimeRemaining;
     timeRemaining[1][1] = blackTimeRemaining;
     DrawPosition(FALSE, boards[currentMove]);
-   
+
     return TRUE;
 }
 
@@ -9949,7 +10641,7 @@ char *DefaultFileName(ext)
        *p++ = '-';
        CopyPlayerNameIntoFileName(&p, gameInfo.black);
        *p++ = '.';
-       strcpy(p, ext);
+       safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
     } else {
        def[0] = NULLCHAR;
     }
@@ -9985,7 +10677,7 @@ SavePart(str)
 {
     static char buf[MSG_SIZ];
     char *p;
-    
+
     p = strchr(str, ' ');
     if (p == NULL) return str;
     strncpy(buf, str, p - str);
@@ -10064,7 +10756,7 @@ void GetOutOfBookInfo( char * buf )
                 }
 
                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
-                sprintf( buf+strlen(buf), "%s%.2f", 
+                sprintf( buf+strlen(buf), "%s%.2f",
                     pvInfoList[idx].score >= 0 ? "+" : "",
                     pvInfoList[idx].score / 100.0 );
             }
@@ -10085,11 +10777,11 @@ SaveGamePGN(f)
     char move_buffer[100]; /* [AS] Buffer for move+PV info */
 
     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
-    
+
     tm = time((time_t *) NULL);
-    
+
     PrintPGNTags(f, &gameInfo);
-    
+
     if (backwardMostMove > 0 || startedFromSetupPosition) {
         char *fen = PositionToFEN(backwardMostMove, NULL);
         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
@@ -10106,7 +10798,7 @@ SaveGamePGN(f)
             GetOutOfBookInfo( buf );
 
             if( buf[0] != '\0' ) {
-                fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
+                fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
             }
         }
 
@@ -10127,15 +10819,14 @@ SaveGamePGN(f)
        }
 
        /* Format move number */
-       if ((i % 2) == 0) {
-           sprintf(numtext, "%d.", (i - offset)/2 + 1);
-       } else {
-           if (newblock) {
-               sprintf(numtext, "%d...", (i - offset)/2 + 1);
-           } else {
-               numtext[0] = NULLCHAR;
-           }
-       }
+       if ((i % 2) == 0)
+         snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
+        else
+         if (newblock)
+           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
+         else
+           numtext[0] = NULLCHAR;
+
        numlen = strlen(numtext);
        newblock = FALSE;
 
@@ -10154,7 +10845,7 @@ SaveGamePGN(f)
        linelen += numlen;
 
        /* Get move */
-       strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
+       safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
        movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
 
        /* Print move */
@@ -10178,18 +10869,25 @@ SaveGamePGN(f)
 
             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
 
-            if( seconds <= 0) buf[0] = 0; else
-            if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
-               seconds = (seconds + 4)/10; // round to full seconds
-               if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
-                                  sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
-           }
+            if( seconds <= 0)
+             buf[0] = 0;
+           else
+             if( seconds < 30 )
+               snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
+             else
+               {
+                 seconds = (seconds + 4)/10; // round to full seconds
+                 if( seconds < 60 )
+                   snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
+                 else
+                   snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
+               }
 
-            sprintf( move_buffer, "{%s%.2f/%d%s}", 
-                pvInfoList[i].score >= 0 ? "+" : "",
-                pvInfoList[i].score / 100.0,
-                pvInfoList[i].depth,
-               buf );
+            snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
+                     pvInfoList[i].score >= 0 ? "+" : "",
+                     pvInfoList[i].score / 100.0,
+                     pvInfoList[i].depth,
+                     buf );
 
            movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
 
@@ -10210,7 +10908,7 @@ SaveGamePGN(f)
 
        i++;
     }
-    
+
     /* Start a new line */
     if (linelen > 0) fprintf(f, "\n");
 
@@ -10240,12 +10938,12 @@ SaveGameOldStyle(f)
 {
     int i, offset;
     time_t tm;
-    
+
     tm = time((time_t *) NULL);
-    
+
     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
     PrintOpponents(f);
-    
+
     if (backwardMostMove > 0 || startedFromSetupPosition) {
        fprintf(f, "\n[--------------\n");
        PrintPosition(f, backwardMostMove);
@@ -10280,7 +10978,7 @@ SaveGameOldStyle(f)
            i++;
        }
     }
-    
+
     if (commentList[i] != NULL) {
        fprintf(f, "[%s]\n", commentList[i]);
     }
@@ -10345,11 +11043,11 @@ SavePosition(f, dummy, dummy2)
 {
     time_t tm;
     char *fen;
-    
+
     if (gameMode == EditPosition) EditPositionDone(TRUE);
     if (appData.oldSaveStyle) {
        tm = time((time_t *) NULL);
-    
+
        fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
        PrintOpponents(f);
        fprintf(f, "[--------------\n");
@@ -10374,7 +11072,7 @@ ReloadCmailMsgEvent(unregister)
     int i;
     struct stat inbuf, outbuf;
     int status;
-    
+
     /* Any registered moves are unregistered if unregister is set, */
     /* i.e. invoked by the signal handler */
     if (unregister) {
@@ -10402,7 +11100,7 @@ ReloadCmailMsgEvent(unregister)
        outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
        sprintf(outFilename, "%s.out", appData.cmailGameName);
     }
-    
+
     status = stat(outFilename, &outbuf);
     if (status < 0) {
        cmailMailedMove = FALSE;
@@ -10410,10 +11108,10 @@ ReloadCmailMsgEvent(unregister)
        status = stat(inFilename, &inbuf);
        cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
     }
-    
+
     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
        counts the games, notes how each one terminated, etc.
-       
+
        It would be nice to remove this kludge and instead gather all
        the information while building the game list.  (And to keep it
        in the game list nodes instead of having a bunch of fixed-size
@@ -10423,7 +11121,7 @@ ReloadCmailMsgEvent(unregister)
        */
     cmailMsgLoaded = TRUE;
     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
-    
+
     /* Load first game in the file or popup game menu */
     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
 
@@ -10449,7 +11147,7 @@ RegisterMove()
        cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
        nCmailMovesRegistered --;
 
-       if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
+       if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
          {
              free(cmailCommentList[lastLoadGameNumber - 1]);
              cmailCommentList[lastLoadGameNumber - 1] = NULL;
@@ -10488,30 +11186,29 @@ RegisterMove()
            cmailCommentList[lastLoadGameNumber - 1]
              = StrSave(commentList[currentMove]);
        }
-       strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
+       safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
 
        if (appData.debugMode)
          fprintf(debugFP, "Saving %s for game %d\n",
                  cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
 
-       sprintf(string,
-               "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
-       
+       snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
+
        f = fopen(string, "w");
        if (appData.oldSaveStyle) {
            SaveGameOldStyle(f); /* also closes the file */
-           
-           sprintf(string, "%s.pos.out", appData.cmailGameName);
+
+           snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
            f = fopen(string, "w");
            SavePosition(f, 0, NULL); /* also closes the file */
        } else {
            fprintf(f, "{--------------\n");
            PrintPosition(f, currentMove);
            fprintf(f, "--------------}\n\n");
-           
+
            SaveGame(f, 0, NULL); /* also closes the file*/
        }
-       
+
        cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
        nCmailMovesRegistered ++;
     } else if (nCmailGames == 1) {
@@ -10547,18 +11244,18 @@ MailMoveEvent()
 
 #if CMAIL_PROHIBIT_REMAIL
     if (cmailMailedMove) {
-       sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
+      snprintf(msg, MSG_SIZ, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
        DisplayError(msg, 0);
        return;
     }
 #endif
 
     if (! (cmailMailedMove || RegisterMove())) return;
-    
+
     if (   cmailMailedMove
        || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
-       sprintf(string, partCommandString,
-               appData.debugMode ? " -v" : "", appData.cmailGameName);
+      snprintf(string, MSG_SIZ, partCommandString,
+              appData.debugMode ? " -v" : "", appData.cmailGameName);
        commandOutput = popen(string, "r");
 
        if (commandOutput == NULL) {
@@ -10588,10 +11285,10 @@ MailMoveEvent()
                if (   archived
                    && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
                        != NULL)) {
-                   sprintf(buffer, "%s/%s.%s.archive",
-                           arcDir,
-                           appData.cmailGameName,
-                           gameInfo.date);
+                 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
+                          arcDir,
+                          appData.cmailGameName,
+                          gameInfo.date);
                    LoadGameFromFile(buffer, 1, buffer, FALSE);
                    cmailMsgLoaded = FALSE;
                }
@@ -10620,24 +11317,24 @@ CmailMsg()
     char number[5];
     char string[MSG_SIZ];      /* Space for game-list */
     int  i;
-    
+
     if (!cmailMsgLoaded) return "";
 
     if (cmailMailedMove) {
-       sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
+      snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
     } else {
        /* Create a list of games left */
-       sprintf(string, "[");
+      snprintf(string, MSG_SIZ, "[");
        for (i = 0; i < nCmailGames; i ++) {
            if (! (   cmailMoveRegistered[i]
                   || (cmailResult[i] == CMAIL_OLD_RESULT))) {
                if (prependComma) {
-                   sprintf(number, ",%d", i + 1);
+                   snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
                } else {
-                   sprintf(number, "%d", i + 1);
+                   snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
                    prependComma = 1;
                }
-               
+
                strcat(string, number);
            }
        }
@@ -10646,41 +11343,36 @@ CmailMsg()
        if (nCmailMovesRegistered + nCmailResults == 0) {
            switch (nCmailGames) {
              case 1:
-               sprintf(cmailMsg,
-                       _("Still need to make move for game\n"));
+               snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
                break;
-               
+
              case 2:
-               sprintf(cmailMsg,
-                       _("Still need to make moves for both games\n"));
+               snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
                break;
-               
+
              default:
-               sprintf(cmailMsg,
-                       _("Still need to make moves for all %d games\n"),
-                       nCmailGames);
+               snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
+                        nCmailGames);
                break;
            }
        } else {
            switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
              case 1:
-               sprintf(cmailMsg,
-                       _("Still need to make a move for game %s\n"),
-                       string);
+               snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
+                        string);
                break;
-               
+
              case 0:
                if (nCmailResults == nCmailGames) {
-                   sprintf(cmailMsg, _("No unfinished games\n"));
+                 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
                } else {
-                   sprintf(cmailMsg, _("Ready to send mail\n"));
+                 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
                }
                break;
-               
+
              default:
-               sprintf(cmailMsg,
-                       _("Still need to make moves for games %s\n"),
-                       string);
+               snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
+                        string);
            }
        }
     }
@@ -10739,7 +11431,7 @@ ExitEvent(status)
     /* Kill off chess programs */
     if (first.pr != NoProc) {
        ExitAnalyzeMode();
-        
+
         DoSleep( appData.delayBeforeQuit );
        SendToProgram("quit\n", &first);
         DoSleep( appData.delayAfterQuit );
@@ -10777,7 +11469,7 @@ PauseEvent()
            DisplayBothClocks();
        }
        if (gameMode == PlayFromGameFile) {
-           if (appData.timeDelay >= 0) 
+           if (appData.timeDelay >= 0)
                AutoPlayGameLoop();
        } else if (gameMode == IcsExamining && pauseExamInvalid) {
            Reset(FALSE, TRUE);
@@ -10833,11 +11525,11 @@ EditCommentEvent()
     char title[MSG_SIZ];
 
     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
-       strcpy(title, _("Edit comment"));
+      safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
     } else {
-       sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
-               WhiteOnMove(currentMove - 1) ? " " : ".. ",
-               parseList[currentMove - 1]);
+      snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
+              WhiteOnMove(currentMove - 1) ? " " : ".. ",
+              parseList[currentMove - 1]);
     }
 
     EditCommentPopUp(currentMove, title, commentList[currentMove]);
@@ -10916,25 +11608,25 @@ MachineWhiteEvent()
       return;
 
 
-    if (gameMode == PlayFromGameFile || 
-       gameMode == TwoMachinesPlay  || 
-       gameMode == Training         || 
-       gameMode == AnalyzeMode      || 
+    if (gameMode == PlayFromGameFile ||
+       gameMode == TwoMachinesPlay  ||
+       gameMode == Training         ||
+       gameMode == AnalyzeMode      ||
        gameMode == EndOfGame)
        EditGameEvent();
 
-    if (gameMode == EditPosition) 
+    if (gameMode == EditPosition)
         EditPositionDone(TRUE);
 
     if (!WhiteOnMove(currentMove)) {
        DisplayError(_("It is not White's turn"), 0);
        return;
     }
-  
+
     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
       ExitAnalyzeMode();
 
-    if (gameMode == EditGame || gameMode == AnalyzeMode || 
+    if (gameMode == EditGame || gameMode == AnalyzeMode ||
        gameMode == AnalyzeFile)
        TruncateGame();
 
@@ -10947,10 +11639,10 @@ MachineWhiteEvent()
     pausing = FALSE;
     ModeHighlight();
     SetGameInfo();
-    sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+    snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
     DisplayTitle(buf);
     if (first.sendName) {
-      sprintf(buf, "name %s\n", gameInfo.black);
+      snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
       SendToProgram(buf, &first);
     }
     if (first.sendTime) {
@@ -10977,11 +11669,11 @@ MachineWhiteEvent()
     if(bookHit) { // [HGM] book: simulate book reply
        static char bookMove[MSG_SIZ]; // a bit generous?
 
-       programStats.nodes = programStats.depth = programStats.time = 
+       programStats.nodes = programStats.depth = programStats.time =
        programStats.score = programStats.got_only_move = 0;
        sprintf(programStats.movelist, "%s (xbook)", bookHit);
 
-       strcpy(bookMove, "move ");
+       safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
        strcat(bookMove, bookHit);
        HandleMachineMove(bookMove, &first);
     }
@@ -10990,32 +11682,32 @@ MachineWhiteEvent()
 void
 MachineBlackEvent()
 {
-    char buf[MSG_SIZ];
-   char *bookHit = NULL;
+  char buf[MSG_SIZ];
+  char *bookHit = NULL;
 
     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
        return;
 
 
-    if (gameMode == PlayFromGameFile || 
-       gameMode == TwoMachinesPlay  || 
-       gameMode == Training         || 
-       gameMode == AnalyzeMode      || 
+    if (gameMode == PlayFromGameFile ||
+       gameMode == TwoMachinesPlay  ||
+       gameMode == Training         ||
+       gameMode == AnalyzeMode      ||
        gameMode == EndOfGame)
         EditGameEvent();
 
-    if (gameMode == EditPosition) 
+    if (gameMode == EditPosition)
         EditPositionDone(TRUE);
 
     if (WhiteOnMove(currentMove)) {
        DisplayError(_("It is not Black's turn"), 0);
        return;
     }
-    
+
     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
       ExitAnalyzeMode();
 
-    if (gameMode == EditGame || gameMode == AnalyzeMode || 
+    if (gameMode == EditGame || gameMode == AnalyzeMode ||
        gameMode == AnalyzeFile)
        TruncateGame();
 
@@ -11024,10 +11716,10 @@ MachineBlackEvent()
     pausing = FALSE;
     ModeHighlight();
     SetGameInfo();
-    sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+    snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
     DisplayTitle(buf);
     if (first.sendName) {
-      sprintf(buf, "name %s\n", gameInfo.white);
+      snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
       SendToProgram(buf, &first);
     }
     if (first.sendTime) {
@@ -11052,11 +11744,11 @@ MachineBlackEvent()
     if(bookHit) { // [HGM] book: simulate book reply
        static char bookMove[MSG_SIZ]; // a bit generous?
 
-       programStats.nodes = programStats.depth = programStats.time = 
+       programStats.nodes = programStats.depth = programStats.time =
        programStats.score = programStats.got_only_move = 0;
        sprintf(programStats.movelist, "%s (xbook)", bookHit);
 
-       strcpy(bookMove, "move ");
+       safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
        strcat(bookMove, bookHit);
        HandleMachineMove(bookMove, &first);
     }
@@ -11069,18 +11761,18 @@ DisplayTwoMachinesTitle()
     char buf[MSG_SIZ];
     if (appData.matchGames > 0) {
         if (first.twoMachinesColor[0] == 'w') {
-           sprintf(buf, "%s vs. %s (%d-%d-%d)",
-                   gameInfo.white, gameInfo.black,
-                   first.matchWins, second.matchWins,
-                   matchGame - 1 - (first.matchWins + second.matchWins));
+         snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
+                  gameInfo.white, gameInfo.black,
+                  first.matchWins, second.matchWins,
+                  matchGame - 1 - (first.matchWins + second.matchWins));
        } else {
-           sprintf(buf, "%s vs. %s (%d-%d-%d)",
-                   gameInfo.white, gameInfo.black,
-                   second.matchWins, first.matchWins,
-                   matchGame - 1 - (first.matchWins + second.matchWins));
+         snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
+                  gameInfo.white, gameInfo.black,
+                  second.matchWins, first.matchWins,
+                  matchGame - 1 - (first.matchWins + second.matchWins));
        }
     } else {
-       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+      snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
     }
     DisplayTitle(buf);
 }
@@ -11092,7 +11784,7 @@ TwoMachinesEvent P((void))
     char buf[MSG_SIZ];
     ChessProgramState *onmove;
     char *bookHit = NULL;
-    
+
     if (appData.noChessProgram) return;
 
     switch (gameMode) {
@@ -11166,12 +11858,12 @@ TwoMachinesEvent P((void))
 
     SendToProgram(first.computerString, &first);
     if (first.sendName) {
-      sprintf(buf, "name %s\n", second.tidy);
+      snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
       SendToProgram(buf, &first);
     }
     SendToProgram(second.computerString, &second);
     if (second.sendName) {
-      sprintf(buf, "name %s\n", first.tidy);
+      snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
       SendToProgram(buf, &second);
     }
 
@@ -11199,11 +11891,11 @@ TwoMachinesEvent P((void))
     if(bookHit) { // [HGM] book: simulate book reply
        static char bookMove[MSG_SIZ]; // a bit generous?
 
-       programStats.nodes = programStats.depth = programStats.time = 
+       programStats.nodes = programStats.depth = programStats.time =
        programStats.score = programStats.got_only_move = 0;
        sprintf(programStats.movelist, "%s (xbook)", bookHit);
 
-       strcpy(bookMove, "move ");
+       safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
        strcat(bookMove, bookHit);
        savedMessage = bookMove; // args for deferred call
        savedState = onmove;
@@ -11258,7 +11950,7 @@ IcsClientEvent()
       case AnalyzeFile:
        ExitAnalyzeMode();
        break;
-       
+
       default:
        EditGameEvent();
        break;
@@ -11323,7 +12015,7 @@ EditGameEvent()
       default:
        return;
     }
-    
+
     pausing = FALSE;
     StopClocks();
     first.offeredDraw = second.offeredDraw = 0;
@@ -11350,8 +12042,8 @@ EditGameEvent()
            whiteFlag = blackFlag = 0;
        }
        DisplayTitle("");
-    }          
-    
+    }
+
     gameMode = EditGame;
     ModeHighlight();
     SetGameInfo();
@@ -11365,16 +12057,16 @@ EditPositionEvent()
        EditGameEvent();
        return;
     }
-    
+
     EditGameEvent();
     if (gameMode != EditGame) return;
-    
+
     gameMode = EditPosition;
     ModeHighlight();
     SetGameInfo();
     if (currentMove > 0)
       CopyBoard(boards[0], boards[currentMove]);
-    
+
     blackPlaysFirst = !WhiteOnMove(currentMove);
     ResetClocks();
     currentMove = forwardMostMove = backwardMostMove = 0;
@@ -11419,8 +12111,8 @@ EditPositionDone(Boolean fakeRights)
     }
     SendToProgram("force\n", &first);
     if (blackPlaysFirst) {
-       strcpy(moveList[0], "");
-       strcpy(parseList[0], "");
+        safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
+       safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
        currentMove = forwardMostMove = backwardMostMove = 1;
        CopyBoard(boards[1], boards[0]);
     } else {
@@ -11464,7 +12156,7 @@ SendMultiLineToICS(buf)
     len = strlen(buf);
     if (len > MSG_SIZ)
       len = MSG_SIZ;
-  
+
     strncpy(temp, buf, len);
     temp[len] = 0;
 
@@ -11530,7 +12222,7 @@ EditPositionMenuEvent(selection, x, y)
                 for (y = 0; y < BOARD_HEIGHT; y++) {
                    if (gameMode == IcsExamining) {
                        if (boards[currentMove][y][x] != EmptySquare) {
-                           sprintf(buf, "%sx@%c%c\n", ics_prefix,
+                         snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
                                     AAA + x, ONE + y);
                            SendToICS(buf);
                        }
@@ -11556,7 +12248,7 @@ EditPositionMenuEvent(selection, x, y)
       case EmptySquare:
        if (gameMode == IcsExamining) {
             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
-            sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
+            snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
            SendToICS(buf);
        } else {
             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
@@ -11587,7 +12279,7 @@ EditPositionMenuEvent(selection, x, y)
            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
             selection = (ChessSquare) (DEMOTED piece);
         } else if(piece == EmptySquare) selection = BlackSilver;
-        else selection = (ChessSquare)((int)piece + 1);       
+        else selection = (ChessSquare)((int)piece + 1);
         goto defaultlabel;
 
       case WhiteQueen:
@@ -11609,21 +12301,21 @@ EditPositionMenuEvent(selection, x, y)
         defaultlabel:
        if (gameMode == IcsExamining) {
             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
-           sprintf(buf, "%s%c@%c%c\n", ics_prefix,
-                    PieceToChar(selection), AAA + x, ONE + y);
+           snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
+                    PieceToChar(selection), AAA + x, ONE + y);
            SendToICS(buf);
        } else {
             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
                 int n;
                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
                     n = PieceToNumber(selection - BlackPawn);
-                    if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
+                    if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
                     boards[0][BOARD_HEIGHT-1-n][1]++;
                 } else
                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
                     n = PieceToNumber(selection);
-                    if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
+                    if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
                     boards[0][n][BOARD_WIDTH-1] = selection;
                     boards[0][n][BOARD_WIDTH-2]++;
                 }
@@ -11683,7 +12375,7 @@ void
 AcceptEvent()
 {
     /* Accept a pending offer of any kind from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("accept\n");
@@ -11708,7 +12400,7 @@ void
 DeclineEvent()
 {
     /* Decline a pending offer of any kind from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("decline\n");
@@ -11780,16 +12472,17 @@ void
 DrawEvent()
 {
     /* Offer draw or accept pending draw offer from opponent */
-    
+
     if (appData.icsActive) {
        /* Note: tournament rules require draw offers to be
           made after you make your move but before you punch
           your clock.  Currently ICS doesn't let you do that;
           instead, you immediately punch your clock after making
           a move, but you can offer a draw at any time. */
-       
+
         SendToICS(ics_prefix);
        SendToICS("draw\n");
+        userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
     } else if (cmailMsgLoaded) {
        if (currentMove == cmailOldMove &&
            commentList[cmailOldMove] != NULL &&
@@ -11821,7 +12514,7 @@ void
 AdjournEvent()
 {
     /* Offer Adjourn or accept pending Adjourn offer from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("adjourn\n");
@@ -11835,7 +12528,7 @@ void
 AbortEvent()
 {
     /* Offer Abort or accept pending Abort offer from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("abort\n");
@@ -11848,7 +12541,7 @@ void
 ResignEvent()
 {
     /* Resign.  You can do this even if it's not your turn. */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("resign\n");
@@ -11909,12 +12602,12 @@ ForwardInner(target)
 
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
-    
+
     if (gameMode == IcsExamining && pausing)
       limit = pauseExamForwardMostMove;
     else
       limit = forwardMostMove;
-    
+
     if (target > limit) target = limit;
 
     if (target > 0 && moveList[target - 1][0]) {
@@ -11936,8 +12629,8 @@ ForwardInner(target)
            }
        }
     }
-    if (gameMode == EditGame || gameMode == AnalyzeMode || 
-       gameMode == Training || gameMode == PlayFromGameFile || 
+    if (gameMode == EditGame || gameMode == AnalyzeMode ||
+       gameMode == Training || gameMode == PlayFromGameFile ||
        gameMode == AnalyzeFile) {
        while (currentMove < target) {
            SendMoveToProgram(currentMove++, &first);
@@ -11945,7 +12638,7 @@ ForwardInner(target)
     } else {
        currentMove = target;
     }
-    
+
     if (gameMode == EditGame || gameMode == EndOfGame) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
@@ -11978,13 +12671,13 @@ ToEndEvent()
        /* to optimze, we temporarily turn off analysis mode while we feed
         * the remaining moves to the engine. Otherwise we get analysis output
         * after each move.
-        */ 
+        */
         if (first.analysisSupport) {
          SendToProgram("exit\nforce\n", &first);
          first.analyzing = FALSE;
        }
     }
-       
+
     if (gameMode == IcsExamining && !pausing) {
         SendToICS(ics_prefix);
        SendToICS("forward 999999\n");
@@ -12019,7 +12712,7 @@ BackwardInner(target)
     }
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
-    
+
     if (moveList[target][0]) {
        int fromX, fromY, toX, toY;
         toX = moveList[target][2] - AAA;
@@ -12048,7 +12741,7 @@ BackwardInner(target)
     } else {
        currentMove = target;
     }
-    
+
     if (gameMode == EditGame || gameMode == EndOfGame) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
@@ -12078,7 +12771,7 @@ ToStartEvent()
     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
        /* to optimize, we temporarily turn off analysis mode while we undo
         * all the moves. Otherwise we get analysis output after each undo.
-        */ 
+        */
         if (first.analysisSupport) {
          SendToProgram("exit\nforce\n", &first);
          first.analyzing = FALSE;
@@ -12115,9 +12808,9 @@ ToNrEvent(int to)
 }
 
 void
-RevertEvent()
+RevertEvent(Boolean annotate)
 {
-    if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
+    if(PopTail(annotate)) { // [HGM] vari: restore old game tail
        return;
     }
     if (gameMode != IcsExamining) {
@@ -12303,7 +12996,7 @@ PrintPosition(fp, move)
      int move;
 {
     int i, j;
-    
+
     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
            char c = PieceToChar(boards[move][i][j]);
@@ -12364,13 +13057,13 @@ TimeControlTagValue()
 {
     char buf[MSG_SIZ];
     if (!appData.clockMode) {
-       strcpy(buf, "-");
+      safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
     } else if (movesPerSession > 0) {
-       sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
+      snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
     } else if (timeIncrement == 0) {
-       sprintf(buf, "%ld", timeControl/1000);
+      snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
     } else {
-       sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
+      snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
     }
     return StrSave(buf);
 }
@@ -12384,8 +13077,8 @@ SetGameInfo()
     char *p = NULL;
 
     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
-       r = gameInfo.result; 
-       p = gameInfo.resultDetails; 
+       r = gameInfo.result;
+       p = gameInfo.resultDetails;
        gameInfo.resultDetails = NULL;
     }
     ClearGameInfo(&gameInfo);
@@ -12418,7 +13111,7 @@ SetGameInfo()
        gameInfo.date = PGNDate();
        if (matchGame > 0) {
            char buf[MSG_SIZ];
-           sprintf(buf, "%d", matchGame);
+           snprintf(buf, MSG_SIZ, "%d", matchGame);
            gameInfo.round = StrSave(buf);
        } else {
            gameInfo.round = StrSave("-");
@@ -12498,12 +13191,12 @@ ReplaceComment(index, text)
     strncpy(commentList[index], text, len);
     commentList[index][len] = '\n';
     commentList[index][len + 1] = NULLCHAR;
-  } else { 
+  } else {
     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
     char *p;
-    commentList[index] = (char *) malloc(len + 6);
-    strcpy(commentList[index], "{\n");
-    strncpy(commentList[index]+2, text, len);
+    commentList[index] = (char *) malloc(len + 7);
+    safeStrCpy(commentList[index], "{\n", 3);
+    safeStrCpy(commentList[index]+2, text, len+1);
     commentList[index][len+2] = NULLCHAR;
     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
     strcat(commentList[index], "\n}\n");
@@ -12550,7 +13243,7 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
        while(commentList[index][oldlen-1] ==  '\n')
          commentList[index][--oldlen] = NULLCHAR;
        commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
-       strcpy(commentList[index], old);
+       safeStrCpy(commentList[index], old, oldlen);
        free(old);
        // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
        if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
@@ -12566,7 +13259,7 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
     } else {
        commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
        if(addBraces)
-            strcpy(commentList[index], "{\n");
+         safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
        else commentList[index][0] = NULLCHAR;
        strcat(commentList[index], text);
        strcat(commentList[index], "\n");
@@ -12681,24 +13374,24 @@ SendToProgram(message, cps)
 
     if (cps->pr == NULL) return;
     Attention(cps);
-    
+
     if (appData.debugMode) {
        TimeMark now;
        GetTimeMark(&now);
-       fprintf(debugFP, "%ld >%-6s: %s", 
+       fprintf(debugFP, "%ld >%-6s: %s",
                SubtractTimeMarks(&now, &programStartTime),
                cps->which, message);
     }
-    
+
     count = strlen(message);
     outCount = OutputToProcess(cps->pr, message, count, &error);
-    if (outCount < count && !exiting 
+    if (outCount < count && !exiting
                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
-       sprintf(buf, _("Error writing to %s chess program"), cps->which);
+      snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
-                sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
+                snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
             } else {
                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
             }
@@ -12723,13 +13416,12 @@ ReceiveFromProgram(isr, closure, message, count, error)
     if (isr != cps->isr) return; /* Killed intentionally */
     if (count <= 0) {
        if (count == 0) {
-           sprintf(buf,
-                   _("Error: %s chess program (%s) exited unexpectedly"),
+           snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
                    cps->which, cps->program);
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
-                    sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
+                    snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
                 } else {
                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
                 }
@@ -12738,8 +13430,7 @@ ReceiveFromProgram(isr, closure, message, count, error)
            RemoveInputSource(cps->isr);
            if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
        } else {
-           sprintf(buf,
-                   _("Error reading from %s chess program (%s)"),
+           snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
                    cps->which, cps->program);
            RemoveInputSource(cps->isr);
 
@@ -12753,12 +13444,12 @@ ReceiveFromProgram(isr, closure, message, count, error)
        }
        return;
     }
-    
+
     if ((end_str = strchr(message, '\r')) != NULL)
       *end_str = NULLCHAR;
     if ((end_str = strchr(message, '\n')) != NULL)
       *end_str = NULLCHAR;
-    
+
     if (appData.debugMode) {
        TimeMark now; int print = 1;
        char *quote = ""; char c; int i;
@@ -12766,20 +13457,22 @@ ReceiveFromProgram(isr, closure, message, count, error)
        if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
                char start = message[0];
                if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
-               if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
+               if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
                   sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
                   sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
                   sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
                   sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
                   sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
-                  sscanf(message, "pong %c", &c)!=1   && start != '#')
-                       { quote = "# "; print = (appData.engineComments == 2); }
+                  sscanf(message, "pong %c", &c)!=1   && start != '#') {
+                   quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
+                   print = (appData.engineComments >= 2);
+               }
                message[0] = start; // restore original message
        }
        if(print) {
                GetTimeMark(&now);
-               fprintf(debugFP, "%ld <%-6s: %s%s\n", 
-                       SubtractTimeMarks(&now, &programStartTime), cps->which, 
+               fprintf(debugFP, "%ld <%-6s: %s%s\n",
+                       SubtractTimeMarks(&now, &programStartTime), cps->which,
                        quote,
                        message);
        }
@@ -12788,7 +13481,7 @@ ReceiveFromProgram(isr, closure, message, count, error)
     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
     if (appData.icsEngineAnalyze) {
         if (strstr(message, "whisper") != NULL ||
-             strstr(message, "kibitz") != NULL || 
+             strstr(message, "kibitz") != NULL ||
             strstr(message, "tellics") != NULL) return;
     }
 
@@ -12822,12 +13515,12 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
        /* GNU Chess 4 has no st command; uses level in a nonstandard way */
        seconds = st % 60;
        if (seconds == 0) {
-         sprintf(buf, "level 1 %d\n", st/60);
+         snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
        } else {
-         sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
+         snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
        }
       } else {
-       sprintf(buf, "st %d\n", st);
+       snprintf(buf, MSG_SIZ, "st %d\n", st);
       }
     } else {
       /* Set conventional or incremental time control, using level command */
@@ -12835,10 +13528,10 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
        /* Note old gnuchess bug -- minutes:seconds used to not work.
           Fixed in later versions, but still avoid :seconds
           when seconds is 0. */
-       sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
+       snprintf(buf, MSG_SIZ, "level %d %ld %d\n", mps, tc/60000, inc/1000);
       } else {
-       sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
-               seconds, inc/1000);
+       snprintf(buf, MSG_SIZ, "level %d %ld:%02d %d\n", mps, tc/60000,
+                seconds, inc/1000);
       }
     }
     SendToProgram(buf, cps);
@@ -12847,18 +13540,19 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
     /* Orthogonally, limit search to given depth */
     if (sd > 0) {
       if (cps->sdKludge) {
-       sprintf(buf, "depth\n%d\n", sd);
+       snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
       } else {
-       sprintf(buf, "sd %d\n", sd);
+       snprintf(buf, MSG_SIZ, "sd %d\n", sd);
       }
       SendToProgram(buf, cps);
     }
 
     if(cps->nps > 0) { /* [HGM] nps */
-       if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
+       if(cps->supportsNPS == FALSE)
+         cps->nps = -1; // don't use if engine explicitly says not supported!
        else {
-               sprintf(buf, "nps %d\n", cps->nps);
-             SendToProgram(buf, cps);
+         snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
+         SendToProgram(buf, cps);
        }
     }
 }
@@ -12866,7 +13560,7 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
 ChessProgramState *WhitePlayer()
 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
 {
-    if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
+    if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
         return &second;
     return &first;
@@ -12899,11 +13593,11 @@ SendTimeRemaining(cps, machineWhite)
 
     if (time <= 0) time = 1;
     if (otime <= 0) otime = 1;
-    
-    sprintf(message, "time %ld\n", time);
+
+    snprintf(message, MSG_SIZ, "time %ld\n", time);
     SendToProgram(message, cps);
 
-    sprintf(message, "otim %ld\n", otime);
+    snprintf(message, MSG_SIZ, "otim %ld\n", otime);
     SendToProgram(message, cps);
 }
 
@@ -12917,12 +13611,14 @@ BoolFeature(p, name, loc, cps)
   char buf[MSG_SIZ];
   int len = strlen(name);
   int val;
+
   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
     (*p) += len + 1;
     sscanf(*p, "%d", &val);
     *loc = (val != 0);
-    while (**p && **p != ' ') (*p)++;
-    sprintf(buf, "accepted %s\n", name);
+    while (**p && **p != ' ')
+      (*p)++;
+    snprintf(buf, MSG_SIZ, "accepted %s\n", name);
     SendToProgram(buf, cps);
     return TRUE;
   }
@@ -12942,7 +13638,7 @@ IntFeature(p, name, loc, cps)
     (*p) += len + 1;
     sscanf(*p, "%d", loc);
     while (**p && **p != ' ') (*p)++;
-    sprintf(buf, "accepted %s\n", name);
+    snprintf(buf, MSG_SIZ, "accepted %s\n", name);
     SendToProgram(buf, cps);
     return TRUE;
   }
@@ -12964,14 +13660,14 @@ StringFeature(p, name, loc, cps)
     sscanf(*p, "%[^\"]", loc);
     while (**p && **p != '\"') (*p)++;
     if (**p == '\"') (*p)++;
-    sprintf(buf, "accepted %s\n", name);
+    snprintf(buf, MSG_SIZ, "accepted %s\n", name);
     SendToProgram(buf, cps);
     return TRUE;
   }
   return FALSE;
 }
 
-int 
+int
 ParseOption(Option *opt, ChessProgramState *cps)
 // [HGM] options: process the string that defines an engine option, and determine
 // name, type, default value, and allowed value range
@@ -13040,7 +13736,7 @@ ParseOption(Option *opt, ChessProgramState *cps)
        if(cps->optionSettings && cps->optionSettings[0])
            p = strstr(cps->optionSettings, opt->name); else p = NULL;
        if(p && (p == cps->optionSettings || p[-1] == ',')) {
-               sprintf(buf, "option %s", p);
+         snprintf(buf, MSG_SIZ, "option %s", p);
                if(p = strstr(buf, ",")) *p = 0;
                strcat(buf, "\n");
                SendToProgram(buf, cps);
@@ -13066,7 +13762,7 @@ FeatureDone(cps, val)
 void
 ParseFeatures(args, cps)
      char* args;
-     ChessProgramState *cps;  
+     ChessProgramState *cps;
 {
   char *p = args;
   char *q;
@@ -13078,10 +13774,10 @@ ParseFeatures(args, cps)
     if (*p == NULLCHAR) return;
 
     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
-    if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
-    if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
-    if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
-    if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
+    if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
+    if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
+    if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
+    if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
     if (BoolFeature(&p, "reuse", &val, cps)) {
       /* Engine can disable reuse, but can't enable it if user said no */
       if (!val) cps->reuse = FALSE;
@@ -13123,24 +13819,23 @@ ParseFeatures(args, cps)
     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
        if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
-           sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
+         snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
            SendToProgram(buf, cps);
            continue;
        }
        if(cps->nrOptions >= MAX_OPTIONS) {
            cps->nrOptions--;
-           sprintf(buf, "%s engine has too many options\n", cps->which);
+           snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
            DisplayError(buf, 0);
        }
        continue;
     }
-    if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
     /* End of additions by HGM */
 
     /* unknown feature: complain and skip */
     q = p;
     while (*q && *q != '=') q++;
-    sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
+    snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
     SendToProgram(buf, cps);
     p = q;
     if (*p == '=') {
@@ -13198,17 +13893,17 @@ PonderNextMoveEvent(newState)
 }
 
 void
-NewSettingEvent(option, command, value)
+NewSettingEvent(option, feature, command, value)
      char *command;
-     int option, value;
+     int option, value, *feature;
 {
     char buf[MSG_SIZ];
 
     if (gameMode == EditPosition) EditPositionDone(TRUE);
-    sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
-    SendToProgram(buf, &first);
+    snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
+    if(feature == NULL || *feature) SendToProgram(buf, &first);
     if (gameMode == TwoMachinesPlay) {
-       SendToProgram(buf, &second);
+       if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
     }
 }
 
@@ -13220,7 +13915,7 @@ ShowThinkingEvent()
     int newState = appData.showThinking
        // [HGM] thinking: other features now need thinking output as well
        || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
-    
+
     if (oldState == newState) return;
     oldState = newState;
     if (gameMode == EditPosition) EditPositionDone(TRUE);
@@ -13257,11 +13952,11 @@ DisplayMove(moveNumber)
     char cpThinkOutput[MSG_SIZ];
 
     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
-    
-    if (moveNumber == forwardMostMove - 1 || 
+
+    if (moveNumber == forwardMostMove - 1 ||
        gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
 
-       safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
+       safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
 
         if (strchr(cpThinkOutput, '\n')) {
            *strchr(cpThinkOutput, '\n') = NULLCHAR;
@@ -13287,10 +13982,10 @@ DisplayMove(moveNumber)
     if (moveNumber == forwardMostMove - 1 &&
        gameInfo.resultDetails != NULL) {
        if (gameInfo.resultDetails[0] == NULLCHAR) {
-           sprintf(res, " %s", PGNResult(gameInfo.result));
+         snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
        } else {
-           sprintf(res, " {%s} %s",
-                   gameInfo.resultDetails, PGNResult(gameInfo.result));
+         snprintf(res, MSG_SIZ, " {%s} %s",
+                   T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
        }
     } else {
        res[0] = NULLCHAR;
@@ -13299,7 +13994,7 @@ DisplayMove(moveNumber)
     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
        DisplayMessage(res, cpThinkOutput);
     } else {
-       sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
+      snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
                WhiteOnMove(moveNumber) ? " " : ".. ",
                parseList[moveNumber], res);
        DisplayMessage(message, cpThinkOutput);
@@ -13314,19 +14009,19 @@ DisplayComment(moveNumber, text)
     char title[MSG_SIZ];
     char buf[8000]; // comment can be long!
     int score, depth;
-    
+
     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
-      strcpy(title, "Comment");
+      safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
     } else {
-      sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
+      snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
              WhiteOnMove(moveNumber) ? " " : ".. ",
              parseList[moveNumber]);
     }
     // [HGM] PV info: display PV info together with (or as) comment
     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
-      if(text == NULL) text = "";                                           
+      if(text == NULL) text = "";
       score = pvInfoList[moveNumber].score;
-      sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
+      snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
              depth, (pvInfoList[moveNumber].time+50)/100, text);
       text = buf;
     }
@@ -13427,15 +14122,20 @@ CheckTimeControl()
     /*
      * add time to clocks when time control is achieved ([HGM] now also used for increment)
      */
-    if ( !WhiteOnMove(forwardMostMove) )
+    if ( !WhiteOnMove(forwardMostMove) ) {
        /* White made time control */
-        whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+        lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
+        whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
         /* [HGM] time odds: correct new time quota for time odds! */
                                             / WhitePlayer()->timeOdds;
-      else
+        lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
+    } else {
+        lastBlack -= blackTimeRemaining;
        /* Black made time control */
-        blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+        blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
                                             / WhitePlayer()->other->timeOdds;
+        lastWhite = whiteTimeRemaining;
+    }
 }
 
 void
@@ -13470,7 +14170,7 @@ GetTimeMark(tm)
     struct timezone timeZone;
 
     gettimeofday(&timeVal, &timeZone);
-    tm->sec = (long) timeVal.tv_sec; 
+    tm->sec = (long) timeVal.tv_sec;
     tm->ms = (int) (timeVal.tv_usec / 1000L);
 
 #else /*!HAVE_GETTIMEOFDAY*/
@@ -13509,7 +14209,7 @@ SubtractTimeMarks(tm2, tm1)
  * We give the human user a slight advantage if he is playing white---the
  * clocks don't run until he makes his first move, so it takes zero time.
  * Also, we don't account for network lag, so we could get out of sync
- * with GNU Chess's clock -- but then, referees are always right.  
+ * with GNU Chess's clock -- but then, referees are always right.
  */
 
 static TimeMark tickStartTM;
@@ -13542,7 +14242,7 @@ AdjustClock(Boolean which, int dir)
 
 /* Stop clocks and reset to a fresh time control */
 void
-ResetClocks() 
+ResetClocks()
 {
     (void) StopClockTimer();
     if (appData.icsActive) {
@@ -13551,13 +14251,15 @@ ResetClocks()
        whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
        blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
     } else { /* [HGM] correct new time quote for time odds */
-        whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
-        blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
+        whiteTC = blackTC = fullTimeControlString;
+        whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
+        blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
     }
     if (whiteFlag || blackFlag) {
        DisplayTitle("");
        whiteFlag = blackFlag = FALSE;
     }
+    lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
     DisplayBothClocks();
 }
 
@@ -13573,7 +14275,7 @@ DecrementClocks()
 
     if (!appData.clockMode) return;
     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
-       
+
     GetTimeMark(&now);
 
     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
@@ -13585,17 +14287,30 @@ DecrementClocks()
     if (WhiteOnMove(forwardMostMove)) {
        if(whiteNPS >= 0) lastTickLength = 0;
        timeRemaining = whiteTimeRemaining -= lastTickLength;
+        if(timeRemaining < 0) {
+            GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
+            if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
+                whiteStartMove = forwardMostMove; whiteTC = nextSession;
+                lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
+            }
+        }
        DisplayWhiteClock(whiteTimeRemaining - fudge,
                          WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
     } else {
        if(blackNPS >= 0) lastTickLength = 0;
        timeRemaining = blackTimeRemaining -= lastTickLength;
+        if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
+            GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
+            if(suddenDeath) {
+                blackStartMove = forwardMostMove;
+                lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
+            }
+        }
        DisplayBlackClock(blackTimeRemaining - fudge,
                          !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
     }
-
     if (CheckFlags()) return;
-       
+
     tickStartTM = now;
     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
     StartClockTimer(intendedTickLength);
@@ -13603,9 +14318,9 @@ DecrementClocks()
     /* if the time remaining has fallen below the alarm threshold, sound the
      * alarm. if the alarm has sounded and (due to a takeback or time control
      * with increment) the time remaining has increased to a level above the
-     * threshold, reset the alarm so it can sound again. 
+     * threshold, reset the alarm so it can sound again.
      */
-    
+
     if (appData.icsActive && appData.icsAlarm) {
 
        /* make sure we are dealing with the user's clock */
@@ -13615,7 +14330,7 @@ DecrementClocks()
 
        if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
            alarmSounded = FALSE;
-       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
+       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
            PlayAlarmSound();
            alarmSounded = TRUE;
        }
@@ -13632,7 +14347,7 @@ DecrementClocks()
    from the color that is *not* on move now.
 */
 void
-SwitchClocks()
+SwitchClocks(int newMoveNr)
 {
     long lastTickLength;
     TimeMark now;
@@ -13642,7 +14357,7 @@ SwitchClocks()
 
     if (StopClockTimer() && appData.clockMode) {
        lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
-       if (WhiteOnMove(forwardMostMove)) {
+       if (!WhiteOnMove(forwardMostMove)) {
            if(blackNPS >= 0) lastTickLength = 0;
            blackTimeRemaining -= lastTickLength;
            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
@@ -13654,11 +14369,12 @@ SwitchClocks()
           whiteTimeRemaining -= lastTickLength;
            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
 //         if(pvInfoList[forwardMostMove-1].time == -1)
-                 pvInfoList[forwardMostMove-1].time = 
+                 pvInfoList[forwardMostMove-1].time =
                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
        }
        flagged = CheckFlags();
     }
+    forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
     CheckTimeControl();
 
     if (flagged || !appData.clockMode) return;
@@ -13690,12 +14406,12 @@ SwitchClocks()
       whiteTimeRemaining : blackTimeRemaining);
     StartClockTimer(intendedTickLength);
 }
-       
+
 
 /* Stop both clocks */
 void
 StopClocks()
-{      
+{
     long lastTickLength;
     TimeMark now;
 
@@ -13716,7 +14432,7 @@ StopClocks()
     }
     CheckFlags();
 }
-       
+
 /* Start clock of player on move.  Time may have been reset, so
    if clock is already running, stop and restart it. */
 void
@@ -13734,7 +14450,7 @@ StartClocks()
       whiteTimeRemaining : blackTimeRemaining);
 
    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
-    whiteNPS = blackNPS = -1; 
+    whiteNPS = blackNPS = -1;
     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
        whiteNPS = first.nps;
@@ -13757,12 +14473,12 @@ TimeString(ms)
     long second, minute, hour, day;
     char *sign = "";
     static char buf[32];
-    
+
     if (ms > 0 && ms <= 9900) {
       /* convert milliseconds to tenths, rounding up */
       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
 
-      sprintf(buf, " %03.1f ", tenths/10.0);
+      snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
       return buf;
     }
 
@@ -13775,22 +14491,22 @@ TimeString(ms)
        sign = "-";
        second = -second;
     }
-    
+
     day = second / (60 * 60 * 24);
     second = second % (60 * 60 * 24);
     hour = second / (60 * 60);
     second = second % (60 * 60);
     minute = second / 60;
     second = second % 60;
-    
+
     if (day > 0)
-      sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
              sign, day, hour, minute, second);
     else if (hour > 0)
-      sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
     else
-      sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
-    
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
+
     return buf;
 }
 
@@ -13803,13 +14519,13 @@ StrStr(string, match)
      char *string, *match;
 {
     int i, length;
-    
+
     length = strlen(match);
-    
+
     for (i = strlen(string) - length; i >= 0; i--, string++)
       if (!strncmp(match, string, length))
        return string;
-    
+
     return NULL;
 }
 
@@ -13818,9 +14534,9 @@ StrCaseStr(string, match)
      char *string, *match;
 {
     int i, j, length;
-    
+
     length = strlen(match);
-    
+
     for (i = strlen(string) - length; i >= 0; i--, string++) {
        for (j = 0; j < length; j++) {
            if (ToLower(match[j]) != ToLower(string[j]))
@@ -13838,7 +14554,7 @@ StrCaseCmp(s1, s2)
      char *s1, *s2;
 {
     char c1, c2;
-    
+
     for (;;) {
        c1 = ToLower(*s1++);
        c2 = ToLower(*s2++);
@@ -13869,12 +14585,13 @@ char *
 StrSave(s)
      char *s;
 {
-    char *ret;
+  char *ret;
 
-    if ((ret = (char *) malloc(strlen(s) + 1))) {
-       strcpy(ret, s);
+  if ((ret = (char *) malloc(strlen(s) + 1)))
+    {
+      safeStrCpy(ret, s, strlen(s)+1);
     }
-    return ret;
+  return ret;
 }
 
 char *
@@ -13885,7 +14602,7 @@ StrSavePtr(s, savePtr)
        free(*savePtr);
     }
     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
-       strcpy(*savePtr, s);
+      safeStrCpy(*savePtr, s, strlen(s)+1);
     }
     return(*savePtr);
 }
@@ -13899,7 +14616,7 @@ PGNDate()
 
     clock = time((time_t *)NULL);
     tm = localtime(&clock);
-    sprintf(buf, "%04d.%02d.%02d",
+    snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
            tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
     return StrSave(buf);
 }
@@ -13938,7 +14655,7 @@ PositionToFEN(move, overrideCastling)
                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
                     *p++ = '+';
                     piece = (ChessSquare)(DEMOTED piece);
-                } 
+                }
                 *p++ = PieceToChar(piece);
                 if(p[-1] == '~') {
                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
@@ -14021,7 +14738,7 @@ PositionToFEN(move, overrideCastling)
   }
 
   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
-     gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
+     gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
     /* En passant target square */
     if (move > backwardMostMove) {
         fromX = moveList[move - 1][0] - AAA;
@@ -14070,7 +14787,7 @@ PositionToFEN(move, overrideCastling)
     }
     /* Fullmove number */
     sprintf(p, "%d", (move / 2) + 1);
-    
+
     return StrSave(buf);
 }
 
@@ -14081,7 +14798,7 @@ ParseFEN(board, blackPlaysFirst, fen)
      char *fen;
 {
     int i, j;
-    char *p;
+    char *p, c;
     int emptycount;
     ChessSquare piece;
 
@@ -14174,11 +14891,16 @@ ParseFEN(board, blackPlaysFirst, fen)
     while(*p == ' ') p++;
 
     /* Active color */
-    switch (*p++) {
+    c = *p++;
+    if(appData.colorNickNames) {
+      if( c == appData.colorNickNames[0] ) c = 'w'; else
+      if( c == appData.colorNickNames[1] ) c = 'b';
+    }
+    switch (c) {
       case 'w':
         *blackPlaysFirst = FALSE;
        break;
-      case 'b': 
+      case 'b':
        *blackPlaysFirst = TRUE;
        break;
       default:
@@ -14291,7 +15013,7 @@ ParseFEN(board, blackPlaysFirst, fen)
 
     /* read e.p. field in games that know e.p. capture */
     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
-       gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
+       gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
       if(*p=='-') {
         p++; board[EP_STATUS] = EP_NONE;
       } else {
@@ -14311,7 +15033,7 @@ ParseFEN(board, blackPlaysFirst, fen)
 
     return TRUE;
 }
-      
+
 void
 EditPositionPasteFEN(char *fen)
 {
@@ -14348,9 +15070,9 @@ Boolean set_cont_sequence(char *new_seq)
     len = strlen(new_seq);
     ret = (len > 0) && (len < sizeof(cseq));
     if (ret)
-        strcpy(cseq, new_seq);
+      safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
     else if (appData.debugMode)
-        fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
+      fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
     return ret;
 }
 
@@ -14443,7 +15165,7 @@ int wrap(char *dest, char *src, int count, int width, int *lp)
 
 // [HGM] vari: routines for shelving variations
 
-void 
+void
 PushTail(int firstMove, int lastMove)
 {
        int i, j, nrMoves = lastMove - firstMove;
@@ -14477,7 +15199,7 @@ PushTail(int firstMove, int lastMove)
        }
 
        storedGames++;
-       forwardMostMove = currentMove; // truncte game so we can start variation
+       forwardMostMove = firstMove; // truncate game so we can start variation
        if(storedGames == 1) GreyRevert(FALSE);
 }
 
@@ -14489,24 +15211,27 @@ PopTail(Boolean annotate)
 
        if(appData.icsActive) return FALSE; // only in local mode
        if(!storedGames) return FALSE; // sanity
+       CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
 
        storedGames--;
        ToNrEvent(savedFirst[storedGames]); // sets currentMove
        nrMoves = savedLast[storedGames] - currentMove;
        if(annotate) {
                int cnt = 10;
-               if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
-               else strcpy(buf, "(");
+               if(!WhiteOnMove(currentMove))
+                 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", currentMove+2>>1);
+               else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
                for(i=currentMove; i<forwardMostMove; i++) {
                        if(WhiteOnMove(i))
-                            sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
-                       else sprintf(moveBuf, " %s", SavePart(parseList[i]));
+                         snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", i+2>>1, SavePart(parseList[i]));
+                       else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
                        strcat(buf, moveBuf);
+                       if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
                        if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
                }
                strcat(buf, ")");
        }
-       for(i=1; i<nrMoves; i++) { // copy last variation back
+       for(i=1; i<=nrMoves; i++) { // copy last variation back
            CopyBoard(boards[currentMove+i], boards[framePtr+i]);
            for(j=0; j<MOVE_LEN; j++)
                moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
@@ -14531,7 +15256,7 @@ PopTail(Boolean annotate)
        return TRUE;
 }
 
-void 
+void
 CleanupTail()
 {      // remove all shelved variations
        int i;
@@ -14547,3 +15272,36 @@ CleanupTail()
        framePtr = MAX_MOVES-1;
        storedGames = 0;
 }
+
+void
+LoadVariation(int index, char *text)
+{       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
+       char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
+       int level = 0, move;
+
+       if(gameMode != EditGame && gameMode != AnalyzeMode) return;
+       // first find outermost bracketing variation
+       while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
+           if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
+               if(*p == '{') wait = '}'; else
+               if(*p == '[') wait = ']'; else
+               if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
+               if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
+           }
+           if(*p == wait) wait = NULLCHAR; // closing ]} found
+           p++;
+       }
+       if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
+       if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
+       end[1] = NULLCHAR; // clip off comment beyond variation
+       ToNrEvent(currentMove-1);
+       PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
+       // kludge: use ParsePV() to append variation to game
+       move = currentMove;
+       ParsePV(start, TRUE);
+       forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
+       ClearPremoveHighlights();
+       CommentPopDown();
+       ToNrEvent(currentMove+1);
+}
+