Updated copyright notice to 2011
[xboard.git] / backend.c
index b124be6..6eba7f6 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, 2010 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -126,22 +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) 
+#include "gettext.h"
+
+#ifdef ENABLE_NLS
+# define _(s) gettext (s)
+# define N_(s) gettext_noop (s)
 # define T_(s) gettext(s)
-#else 
+#else
 # ifdef WIN32
 #   define _(s) T_(s)
 #   define N_(s) s
 # else
-#   define _(s) (s) 
-#   define N_(s) s 
+#   define _(s) (s)
+#   define N_(s) s
 #   define T_(s) s
 # endif
-#endif 
+#endif
 
 
 /* A point in time */
@@ -158,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));
@@ -310,16 +309,23 @@ 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 )
-{
-    assert( dst != NULL );
-    assert( src != NULL );
-    assert( count > 0 );
+char*
+safeStrCpy( char *dst, const char *src, size_t count )
+{ // [HGM] made safe
+  int i;
+  assert( dst != NULL );
+  assert( src != NULL );
+  assert( count > 0 );
+
+  for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
+  if(  i == count && dst[count-1] != NULLCHAR)
+    {
+      dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
+      if(appData.debugMode)
+      fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
+    }
 
-    strncpy( dst, src, count );
-    dst[ count-1 ] = '\0';
-    return dst;
+  return dst;
 }
 
 /* Some compiler can't cast u64 to double
@@ -368,7 +374,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:
@@ -385,7 +391,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
@@ -439,11 +445,14 @@ int adjudicateLossPlies = 6;
 char white_holding[64], black_holding[64];
 TimeMark lastNodeCountTime;
 long lastNodeCount=0;
+int shiftKey; // [HGM] set by mouse handler
+
 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;
@@ -468,7 +477,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
 
@@ -507,11 +516,18 @@ ChessSquare  KnightmateArray[2][BOARD_FILES] = {
         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
 };
 
+ChessSquare SpartanArray[2][BOARD_FILES] = {
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
+        WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
+        BlackDragon, BlackKing, BlackAngel, BlackAlfil }
+};
+
 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
-       BlackKing, BlackMarshall, BlackAlfil, BlackLance }
+    { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
+       BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
 };
 
 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
@@ -545,31 +561,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
@@ -578,9 +594,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
@@ -666,7 +682,7 @@ InitBackEnd1()
     if (appData.icsActive) {
        appData.matchMode = FALSE;
        appData.matchGames = 0;
-#if ZIPPY      
+#if ZIPPY
        appData.noChessProgram = !appData.zippyPlay;
 #else
        appData.zippyPlay = FALSE;
@@ -720,7 +736,7 @@ InitBackEnd1()
 
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
-    
+
     first.which = _("first");
     second.which = _("second");
     first.maybeThinking = second.maybeThinking = FALSE;
@@ -768,8 +784,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;
@@ -813,25 +829,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 */
@@ -840,7 +872,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
@@ -850,7 +882,7 @@ InitBackEnd1()
       ZippyInit();
     }
 #endif
-    
+
     if (appData.noChessProgram) {
        programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
        sprintf(programVersion, "%s", PACKAGE_STRING);
@@ -862,6 +894,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.
@@ -870,8 +904,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;
 
@@ -886,7 +923,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;
 
@@ -895,7 +935,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 */
@@ -921,6 +961,8 @@ InitBackEnd1()
       case VariantJanus:      /* should work */
       case VariantSuper:      /* experimental */
       case VariantGreat:      /* experimental, requires legality testing to be off */
+      case VariantSChess:     /* S-Chess, should work */
+      case VariantSpartan:    /* should work */
        break;
       }
     }
@@ -971,46 +1013,62 @@ 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;
+            if(**str == '.') { // read fraction of increment
+                char *start = ++(*str);
+                if(result = NextIntegerFromString( str, &temp2)) return -1;
+                temp2 *= 1000;
+                while(start++ < *str) temp2 /= 10;
+                *inc += temp2;
+            }
         } 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       */
@@ -1021,53 +1079,59 @@ int GetTimeQuota(int movenr)
 int
 ParseTimeControl(tc, ti, mps)
      char *tc;
-     int ti;
+     float ti;
      int mps;
 {
   long tc1;
   long tc2;
-  char buf[MSG_SIZ];
-  
+  char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
+  int min, sec=0;
+
   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+%g", mps, mytc, ti);
+    else 
+      snprintf(buf, MSG_SIZ, ":%s+%g", 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;
@@ -1113,7 +1177,7 @@ InitBackEnd3 P((void))
 {
     GameMode initialMode;
     char buf[MSG_SIZ];
-    int err;
+    int err, len;
 
     InitChessProgram(&first, startedFromSetupPosition);
 
@@ -1126,17 +1190,21 @@ InitBackEnd3 P((void))
     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;
        }
@@ -1159,7 +1227,7 @@ InitBackEnd3 P((void))
        cmailISR =
          AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
     }
-    
+
     ThawUI();
     DisplayMessage("", "");
     if (StrCaseCmp(appData.initialMode, "") == 0) {
@@ -1167,7 +1235,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) {
@@ -1181,7 +1249,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;
     }
@@ -1342,7 +1413,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);
            }
@@ -1540,7 +1611,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)
@@ -1607,6 +1678,7 @@ StringToVariant(e)
     VariantClass v = VariantNormal;
     int i, found = FALSE;
     char buf[MSG_SIZ];
+    int len;
 
     if (!e) return v;
 
@@ -1630,7 +1702,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")) ||
@@ -1708,7 +1780,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;
@@ -1782,7 +1854,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;
@@ -1815,7 +1890,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;
@@ -1922,7 +1997,7 @@ TelnetRequest(ddww, option)
            break;
          default:
            ddwwStr = buf1;
-           sprintf(buf1, "%d", ddww);
+           snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
            break;
        }
        switch (option) {
@@ -1931,7 +2006,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);
@@ -2022,7 +2097,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));
@@ -2030,7 +2105,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;
@@ -2051,14 +2126,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];
@@ -2145,7 +2220,7 @@ AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, in
            if(v == VariantLoadable) type = "setup"; else
            type = VariantName(v);
        }
-       sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
+       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
@@ -2227,7 +2302,7 @@ DrawSeekGraph()
        DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
        if(i%500 == 0) {
            char buf[MSG_SIZ];
-           sprintf(buf, "%d", i);
+           snprintf(buf, MSG_SIZ, "%d", i);
            DrawSeekText(buf, hMargin+squareSize/8+7, yy);
        }
     }
@@ -2237,7 +2312,7 @@ DrawSeekGraph()
        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];
-           sprintf(buf, "%d", i);
+           snprintf(buf, MSG_SIZ, "%d", i);
            DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
        }
     }
@@ -2275,7 +2350,7 @@ int SeekGraphClick(ClickType click, int x, int y, int moving)
                return TRUE;
            } // on press 'hit', only show info
            if(moving == 2) return TRUE; // ignore right up-clicks on dot
-           sprintf(buf, "play %d\n", seekNrList[closest]);
+           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
@@ -2313,7 +2388,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;
@@ -2323,7 +2398,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;
@@ -2421,7 +2496,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
@@ -2533,23 +2608,23 @@ 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
@@ -2571,7 +2646,7 @@ 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
@@ -2600,7 +2675,7 @@ read_from_ics(isr, closure, data, count, error)
                            OutputKibitz(suppressKibitz, parse);
                        } 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
@@ -2656,7 +2731,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;
            }
 
@@ -2699,7 +2774,7 @@ read_from_ics(isr, closure, data, count, error)
                    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]), 
+                       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
@@ -2730,11 +2805,11 @@ read_from_ics(isr, closure, data, count, error)
            }
 
            // [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: ") || looking_at(buf, &i, "* whispers: ")) &&
-                  (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
+                  (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);
@@ -2750,7 +2825,7 @@ 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, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
@@ -2770,7 +2845,7 @@ read_from_ics(isr, closure, data, count, error)
 
            // [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:") ||
@@ -3042,14 +3117,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;
@@ -3132,7 +3207,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;
@@ -3143,7 +3218,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));
@@ -3186,8 +3261,8 @@ 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
@@ -3212,7 +3287,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);
                                }
@@ -3256,7 +3331,7 @@ read_from_ics(isr, closure, data, count, error)
                                  firstMove = TRUE;
                                }
                            }
-                       }                       
+                       }
                    }
 #endif
                    if (gameMode == IcsObserving && ics_gamenum == -1) {
@@ -3268,7 +3343,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;
@@ -3299,24 +3374,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
@@ -3338,18 +3413,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);
@@ -3379,8 +3454,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!
@@ -3443,24 +3518,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. *(*)) *}*"))){
@@ -3476,7 +3551,7 @@ read_from_ics(isr, closure, data, count, error)
                /*           [4] is " *" or empty (don't care). */
                int gamenum = atoi(star_match[0]);
                char *whitename, *blackname, *why, *endtoken;
-               ChessMove endtype = (ChessMove) 0;
+               ChessMove endtype = EndOfFile;
 
                if (tkind == 0) {
                  whitename = star_match[1];
@@ -3494,7 +3569,7 @@ 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) {
@@ -3600,14 +3675,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);
@@ -3616,8 +3691,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);
                      }
                    }
@@ -3658,7 +3733,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);
                          }
                        }
@@ -3682,10 +3757,10 @@ 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);
                        }
@@ -3719,7 +3794,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);
@@ -3727,11 +3802,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);
@@ -3742,13 +3817,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"
@@ -3764,13 +3839,13 @@ 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;
     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
     char to_play, board_chars[200];
-    char move_str[500], str[500], elapsed_time[500];
+    char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
     char black[32], white[32];
     Board board;
     int prevMove = currentMove;
@@ -3783,7 +3858,7 @@ ParseBoard12(string)
     Boolean weird = FALSE, reqFlag = FALSE;
 
     fromX = fromY = toX = toY = -1;
-    
+
     newGame = FALSE;
 
     if (appData.debugMode)
@@ -3811,7 +3886,7 @@ ParseBoard12(string)
               &ticking);
 
     if (n < 21) {
-        snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
+        snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
        DisplayError(str, 0);
        return;
     }
@@ -3824,7 +3899,7 @@ ParseBoard12(string)
                        0, 1);
       return;
     }
-    
+
     switch (relation) {
       case RELATION_OBSERVING_PLAYED:
       case RELATION_OBSERVING_STATIC:
@@ -3846,14 +3921,14 @@ 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 */
@@ -3879,7 +3954,7 @@ ParseBoard12(string)
       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
       if(partnerUp) DrawPosition(FALSE, partnerBoard);
       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
-      sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
+      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;
@@ -3919,8 +3994,8 @@ ParseBoard12(string)
        return;
     }
 
-   if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
-                                       weird && (int)gameInfo.variant <= (int)VariantShogi) {
+   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
       */
@@ -3935,16 +4010,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);
@@ -3958,17 +4033,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();
@@ -3976,7 +4051,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");
@@ -3999,7 +4074,7 @@ ParseBoard12(string)
   }
 
         gameInfo.outOfBook = NULL;
-       
+
        /* Do we have the ratings? */
        if (strcmp(player1Name, white) == 0 &&
            strcmp(player2Name, black) == 0) {
@@ -4025,7 +4100,7 @@ ParseBoard12(string)
            SendToICS("set shout 0\n");
        }
     }
-    
+
     /* Deal with midgame name changes */
     if (!newGame) {
        if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
@@ -4037,7 +4112,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;
@@ -4046,7 +4121,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;
@@ -4057,7 +4132,7 @@ ParseBoard12(string)
            return;
        }
     }
-    
+
   if (appData.debugMode) {
     fprintf(debugFP, "load %dx%d board\n", files, ranks);
   }
@@ -4132,14 +4207,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;
 
@@ -4150,7 +4225,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
@@ -4173,7 +4248,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;
@@ -4189,12 +4264,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 */
@@ -4205,7 +4280,7 @@ ParseBoard12(string)
       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
     }
-      
+
 
 #if ZIPPY
     if (appData.zippyPlay && newGame &&
@@ -4213,7 +4288,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) {
@@ -4231,7 +4306,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;
@@ -4245,20 +4320,22 @@ 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;
 
+         if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
+               strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
          // 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, MSG_SIZ);
           }
          valid = ParseOneMove(move_str, moveNum - 1, &moveType,
                                &fromX, &fromY, &toX, &toY, &promoChar)
@@ -4287,15 +4364,31 @@ 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);
+           if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
+           safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
            strcat(moveList[moveNum - 1], "\n");
+
+            if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
+              for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
+                ChessSquare old, new = boards[moveNum][k][j];
+                  if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
+                  old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
+                  if(old == new) continue;
+                  if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
+                  else if(new == WhiteWazir || new == BlackWazir) {
+                      if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
+                           boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
+                      else boards[moveNum][k][j] = old; // preserve type of Gold
+                  } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
+                      boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
+              }
          } 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;
@@ -4309,13 +4402,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 {
@@ -4337,7 +4430,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!
@@ -4358,7 +4451,7 @@ ParseBoard12(string)
            SetHighlights(fromX, fromY, toX, toY);
        }
     }
-    
+
     /* Start the clocks */
     whiteFlag = blackFlag = FALSE;
     appData.clockMode = !(basetime == 0 && increment == 0);
@@ -4375,26 +4468,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));
        }
@@ -4407,9 +4500,9 @@ 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();
@@ -4431,11 +4524,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);
     }
@@ -4448,7 +4541,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);
     }
 }
@@ -4495,7 +4588,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 {
@@ -4505,17 +4598,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);
@@ -4543,15 +4636,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;
@@ -4563,7 +4657,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:
@@ -4573,50 +4667,40 @@ 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 WhiteNonPromotion:
       case BlackNonPromotion:
-        sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
+        sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
         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 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",
-               ToUpper(PieceToChar((ChessSquare) fromX)),
-                AAA + toX, ONE + toY);
+      drop:
+       snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
+                ToUpper(PieceToChar((ChessSquare) fromX)),
+                AAA + toX, ONE + toY);
        break;
+      case IllegalMove:  /* could be a variant we don't quite understand */
+        if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
       case NormalMove:
       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;
     }
@@ -4638,43 +4722,43 @@ UploadGameEvent()
        char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
 
        if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
-           sprintf(command, "match %s", ics_handle);
+         snprintf(command,MSG_SIZ, "match %s", ics_handle);
        } else { // on FICS we must first go to general examine mode
-           strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
+         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++) {
-               sprintf(buf, "wild/%d", i);
+             snprintf(buf, MSG_SIZ, "wild/%d", i);
                if(StringToVariant(buf) == gameInfo.variant) break;
            }
-           if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
-           else if(i == 22) sprintf(buf, "%s fr\n", command);
-           else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
-       } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
+           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
-           sprintf(buf, "loadfen %s\n", fen);
+           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
-           sprintf(buf, "bsetup fen %s\n", fen);
+           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);
-           sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
+           snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
            SendToICS(buf);
            i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
-           sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
+           snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
            SendToICS(buf);
            i = boards[backwardMostMove][EP_STATUS];
            if(i >= 0) { // set e.p.
-               sprintf(buf, "bsetup eppos %c\n", i+AAA);
+             snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
                SendToICS(buf);
            }
            bsetup++;
@@ -4685,7 +4769,7 @@ UploadGameEvent()
     }
     for(i = backwardMostMove; i<last; i++) {
        char buf[20];
-       sprintf(buf, "%s\n", parseList[i]);
+       snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
        SendToICS(buf);
     }
     SendToICS(ics_prefix);
@@ -4699,11 +4783,11 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
      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",
@@ -4736,7 +4820,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] = '@';
@@ -4786,27 +4870,12 @@ 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, 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:
@@ -4843,7 +4912,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);
        }
 
@@ -4860,7 +4929,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
 
       case AmbiguousMove:
       case ImpossibleMove:
-      case (ChessMove) 0:      /* end of file */
+      case EndOfFile:
       case ElapsedTime:
       case Comment:
       case PGNTag:
@@ -4897,7 +4966,7 @@ if(appData.debugMode){
 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
@@ -4910,7 +4979,7 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
           moveList[endPV-1][2] = toX + AAA;
           moveList[endPV-1][3] = toY + ONE;
           parseList[endPV-1][0] = NULLCHAR;
-          strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
+          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
@@ -4930,6 +4999,9 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
     moveList[endPV-1][1] = fromY + ONE;
     moveList[endPV-1][2] = toX + AAA;
     moveList[endPV-1][3] = toY + ONE;
+    moveList[endPV-1][4] = promoChar;
+    moveList[endPV-1][5] = NULLCHAR;
+    strncat(moveList[endPV-1], "\n", MOVE_LEN);
     if(storeComments)
        CoordsToAlgebraic(boards[endPV - 1],
                             PosFlags(endPV - 1),
@@ -5041,7 +5113,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;
                }
        }
@@ -5169,7 +5241,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 */
 
@@ -5189,30 +5261,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]++;
@@ -5231,8 +5303,8 @@ InitPosition(redraw)
     int i, j, pawnRow, overrule,
     oldx = gameInfo.boardWidth,
     oldy = gameInfo.boardHeight,
-    oldh = gameInfo.holdingsWidth,
-    oldv = gameInfo.variant;
+    oldh = gameInfo.holdingsWidth;
+    static int oldv;
 
     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
 
@@ -5250,7 +5322,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   */
@@ -5270,23 +5342,23 @@ InitPosition(redraw)
     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
          SetCharTable(pieceNickName, appData.pieceNickNames);
     else SetCharTable(pieceNickName, "............");
+    pieces = FIDEArray;
 
     switch (gameInfo.variant) {
     case VariantFischeRandom:
       shuffleOpenings = TRUE;
     default:
-      pieces = FIDEArray;
       break;
     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;
@@ -5296,17 +5368,21 @@ 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 VariantSChess:
+      SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
+      gameInfo.holdingsSize = 7;
       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;
@@ -5318,14 +5394,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;
@@ -5333,21 +5409,25 @@ 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 VariantSpartan:
+      pieces = SpartanArray;
+      SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
       break;
     case VariantFairy:
       pieces = fairyArray;
-      SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
+      SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
       break;
     case VariantGreat:
       pieces = GreatArray;
@@ -5364,7 +5444,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:
@@ -5415,10 +5495,10 @@ InitPosition(redraw)
         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
         initialPosition[pawnRow][j] = WhitePawn;
-        initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
+        initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : 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;
@@ -5443,7 +5523,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;
@@ -5459,6 +5539,14 @@ InitPosition(redraw)
        initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
        initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
      }
+     if( gameInfo.variant == VariantSChess ) {
+      initialPosition[1][0] = BlackMarshall;
+      initialPosition[2][0] = BlackAngel;
+      initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
+      initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
+      initialPosition[1][1] = initialPosition[2][1] = 
+      initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
+     }
   if (appData.debugMode) {
     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
   }
@@ -5478,18 +5566,12 @@ InitPosition(redraw)
 
     if(oldx != gameInfo.boardWidth ||
        oldy != gameInfo.boardHeight ||
+       oldv != gameInfo.variant ||
        oldh != gameInfo.holdingsWidth
-#ifdef GOTHIC
-       || oldv == VariantGothic ||        // For licensing popups
-       gameInfo.variant == VariantGothic
-#endif
-#ifdef FALCON
-       || oldv == VariantFalcon ||
-       gameInfo.variant == VariantFalcon
-#endif
                                          )
             InitDrawingSizes(-2 ,0);
 
+    oldv = gameInfo.variant;
     if (redraw)
       DrawPosition(TRUE, boards[currentMove]);
 }
@@ -5500,10 +5582,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);
 
@@ -5513,7 +5595,8 @@ SendBoard(cps, moveNum)
       /* Kludge to set black to move, avoiding the troublesome and now
        * deprecated "black" command.
        */
-      if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
+      if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
+        SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
 
       SendToProgram("edit\n", cps);
       SendToProgram("#\n", cps);
@@ -5521,10 +5604,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);
             }
@@ -5536,17 +5619,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);
             }
@@ -5558,7 +5641,7 @@ SendBoard(cps, moveNum)
          }
        }
       }
-    
+
       SendToProgram(".\n", cps);
     }
     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
@@ -5585,12 +5668,18 @@ 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;
     }
 
+    // Treat Lance as Pawn when it is not representing Amazon
+    if(gameInfo.variant != VariantSuper) {
+        if(piece == WhiteLance) piece = WhitePawn; else
+        if(piece == BlackLance) piece = BlackPawn;
+    }
+
     // next weed out all moves that do not touch the promotion zone at all
     if((int)piece >= BlackPawn) {
         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
@@ -5636,6 +5725,11 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
        *promoChoice = PieceToChar(BlackFerz);  // no choice
        return FALSE;
     }
+    // no sense asking what we must promote to if it is going to explode...
+    if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
+       *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
+       return FALSE;
+    }
     if(autoQueen) { // predetermined
        if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
             *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
@@ -5648,9 +5742,8 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
              gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
     if(appData.testLegality && !premove) {
        moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
-                       fromY, fromX, toY, toX, NULLCHAR);
-       if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
-          moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
+                       fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
+       if(moveType != WhitePromotion && moveType  != BlackPromotion)
            return FALSE;
     }
 
@@ -5731,11 +5824,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 */
@@ -5752,16 +5845,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:
@@ -5791,10 +5884,12 @@ OnlyMove(int *x, int *y, Boolean captures) {
       case IcsPlayingBlack:
        if(WhiteOnMove(currentMove)) return FALSE;
        break;
+      case EditGame:
+        break;
       default:
        return FALSE;
     }
-    cl.pieceIn = EmptySquare; 
+    cl.pieceIn = EmptySquare;
     cl.rfIn = *y;
     cl.ffIn = *x;
     cl.rtIn = -1;
@@ -5803,8 +5898,7 @@ OnlyMove(int *x, int *y, Boolean captures) {
     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
     if( cl.kind == NormalMove ||
        cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
-       cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
-       cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+       cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
        cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
       fromX = cl.ff;
       fromY = cl.rf;
@@ -5822,8 +5916,7 @@ OnlyMove(int *x, int *y, Boolean captures) {
     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
     if( cl.kind == NormalMove ||
        cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
-       cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
-       cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+       cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
        cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
       fromX = cl.ff;
       fromY = cl.rf;
@@ -5839,13 +5932,12 @@ 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 lastLoadGameStart = EndOfFile;
 
-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;
@@ -5870,13 +5962,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;
 
@@ -5884,7 +5976,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;
 
@@ -5893,18 +5985,19 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
       case BeginningOfGame:
       case AnalyzeMode:
       case Training:
+       if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
        if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
            /* 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;
@@ -5921,12 +6014,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;
 
@@ -5942,12 +6035,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;
 
@@ -5959,44 +6052,44 @@ 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 */
-    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 */
+    /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
+    if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
+         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;
     }
 
     /* [HGM] always test for legality, to get promotion info */
@@ -6006,18 +6099,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 */
@@ -6029,7 +6115,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)) {
@@ -6042,19 +6128,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
@@ -6062,7 +6136,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;
@@ -6097,9 +6171,10 @@ 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
+    if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
+    else forwardMostMove = currentMove;
   }
 
   /* If we need the chess program but it's dead, restart it */
@@ -6124,10 +6199,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();
@@ -6142,10 +6217,10 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
       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);
+        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);
+      SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
       ics_user_moved = 1;
     }
   } else {
@@ -6168,6 +6243,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   switch (gameMode) {
   case EditGame:
+    if(appData.testLegality)
     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
     case MT_NONE:
     case MT_CHECK:
@@ -6185,7 +6261,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
       break;
     }
     break;
-    
+
   case MachinePlaysBlack:
   case MachinePlaysWhite:
     /* disable certain menu options while machine is thinking */
@@ -6197,15 +6273,15 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
   }
 
   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);
   }
@@ -6213,28 +6289,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;
@@ -6255,7 +6309,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;
@@ -6271,6 +6325,22 @@ MarkTargetSquares(int clear)
   DrawPosition(TRUE, NULL);
 }
 
+int
+Explode(Board board, int fromX, int fromY, int toX, int toY)
+{
+    if(gameInfo.variant == VariantAtomic &&
+       (board[toY][toX] != EmptySquare ||                     // capture?
+        toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
+                         board[fromY][fromX] == BlackPawn   )
+      )) {
+        AnimateAtomicCapture(board, fromX, fromY, toX, toY);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
+
 void LeftClick(ClickType clickType, int xPix, int yPix)
 {
     int x, y;
@@ -6300,8 +6370,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
@@ -6327,8 +6397,15 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     autoQueen = appData.alwaysPromoteToQueen;
 
     if (fromX == -1) {
+      gatingPiece = EmptySquare;
+      if (clickType != Press) {
+       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;
+      }
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
-       if (clickType == Press) {
            /* First square */
            if (OKToStartUserMove(x, y)) {
                fromX = x;
@@ -6340,12 +6417,8 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                    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;
        }
-       return;
-      }
     }
 
     /* fromX != -1 */
@@ -6360,12 +6433,12 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        /* Check if clicking again on the same color piece */
        fromP = boards[currentMove][fromY][fromX];
        toP = boards[currentMove][y][x];
-       frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
+       frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
        if ((WhitePawn <= fromP && fromP <= WhiteKing &&
             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))) {
@@ -6378,13 +6451,19 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                ClearHighlights();
            }
            if (OKToStartUserMove(x, y)) {
+               if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
+                 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
+               y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
+                 gatingPiece = boards[currentMove][fromY][fromX];
+               else gatingPiece = EmptySquare;
                fromX = x;
                fromY = y; dragging = 1;
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix);
            }
-           return;
           }
+          if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
+          second = FALSE; 
        }
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
@@ -6400,6 +6479,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            /* Second up/down in same square; just abort move */
            second = 0;
            fromX = fromY = -1;
+           gatingPiece = EmptySquare;
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
@@ -6440,7 +6520,7 @@ 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; }
@@ -6462,7 +6542,9 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     }
 
     // off-board moves should not be highlighted
-    if(x < 0 || x < 0) ClearHighlights();
+    if(x < 0 || y < 0) ClearHighlights();
+
+    if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
 
     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
        SetHighlights(fromX, fromY, toX, toY);
@@ -6481,9 +6563,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        }
        PromotionPopUp();
     } else {
+       int oldMove = currentMove;
        UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
        if (!appData.highlightLastMove || gotPremove) ClearHighlights();
        if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+       if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
+          Explode(boards[currentMove-1], fromX, fromY, toX, toY))
+           DrawPosition(TRUE, boards[currentMove]);
        fromX = fromY = -1;
     }
     appData.animate = saveAnimate;
@@ -6595,7 +6681,7 @@ 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 );
 }
@@ -6623,7 +6709,7 @@ 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
@@ -6634,7 +6720,7 @@ SufficientDefence(int pCnt[], int side, int nMine, int nHis)
            return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
        if(myPawns) return FALSE;
        if(pCnt[WhiteRook+side])
-           return pCnt[BlackRook-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;
@@ -6676,14 +6762,14 @@ MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisCol
 
        } 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] + 
+                       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)
@@ -6708,8 +6794,7 @@ 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 nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
@@ -6728,7 +6813,7 @@ Adjudicate(ChessProgramState *cps)
                         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;
                     }
@@ -6742,7 +6827,7 @@ Adjudicate(ChessProgramState *cps)
                         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;
                     }
@@ -6755,7 +6840,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 : nrB > 1 ? BlackWins : GameIsDrawn, 
+                           GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
                            return 1;
                        }
@@ -6810,7 +6895,7 @@ Adjudicate(ChessProgramState *cps)
                    case EP_WINS:
                        result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
                    default:
-                       result = (ChessMove) 0;
+                       result = EndOfFile;
                }
                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
                    if(engineOpponent)
@@ -6843,7 +6928,7 @@ Adjudicate(ChessProgramState *cps)
                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
                 if(gameInfo.variant == VariantXiangqi ?
                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
-                 : nrW + nrB == 4 && 
+                 : nrW + nrB == 4 &&
                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
@@ -6861,15 +6946,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
@@ -6894,7 +6977,7 @@ 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] )
@@ -6905,7 +6988,7 @@ Adjudicate(ChessProgramState *cps)
                              /* adjudicate after user-specified nr of repeats */
                             int result = GameIsDrawn;
                             char *details = "XBoard adjudication: repetition draw";
-                            if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
+                            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) {
@@ -6957,7 +7040,7 @@ 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;
@@ -7037,7 +7120,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];
-       sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), 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
@@ -7045,7 +7128,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
@@ -7114,7 +7197,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) {
@@ -7189,10 +7272,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,
@@ -7206,9 +7289,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);
@@ -7219,7 +7300,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);
@@ -7249,11 +7330,6 @@ 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--;
 
-       /* currentMoveString is set as a side-effect of ParseOneMove */
-       strcpy(machineMove, currentMoveString);
-       strcat(machineMove, "\n");
-       strcpy(moveList[forwardMostMove], machineMove);
-
         /* [AS] Save move info*/
         pvInfoList[ forwardMostMove ].score = programStats.score;
         pvInfoList[ forwardMostMove ].depth = programStats.depth;
@@ -7282,15 +7358,19 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             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;
             }
         }
 
-       if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
+       if(Adjudicate(cps)) {
+           DrawPosition(FALSE, boards[currentMove = forwardMostMove-1]);
+           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+           return; // [HGM] adjudicate: for all automatic game ends
+       }
 
 #if ZIPPY
        if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
@@ -7298,14 +7378,14 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
          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);
+               SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
          }
-         SendMoveToICS(moveType, fromX, fromY, toX, toY);
+         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];
 
-               sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
+               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.,
@@ -7346,12 +7426,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
         */
@@ -7364,11 +7444,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);
 
@@ -7398,6 +7478,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
     }
 
+    if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
+      int dummy, s=6; char buf[MSG_SIZ];
+      if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
+      if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+      ParseFEN(boards[0], &dummy, message+s);
+      DrawPosition(TRUE, boards[0]);
+      startedFromSetupPosition = TRUE;
+      return;
+    }
     /* [HGM] Allow engine to set up a position. Don't ask me why one would
      * want this, I was asked to put it in, and obliged.
      */
@@ -7414,7 +7503,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;
@@ -7424,13 +7513,15 @@ 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
+       if(message[9] == '\\' && message[10] == '\\')
+           EscapeExpand(message+9, message+11); // [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
+       if(message[14] == '\\' && message[15] == '\\')
+           EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
        DisplayError(message + 14, 0);
        return;
     }
@@ -7471,13 +7562,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) {
@@ -7511,7 +7602,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") ||
@@ -7546,7 +7637,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;
        }
@@ -7601,21 +7692,21 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            gameMode = EditGame;
            ModeHighlight();
        }
+        /* [HGM] illegal-move claim should forfeit game when Xboard */
+        /* only passes fully legal moves                            */
+        if( appData.testLegality && gameMode == TwoMachinesPlay ) {
+            GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
+                                "False illegal-move claim", GE_XBOARD );
+            return; // do not take back move we tested as valid
+        }
        currentMove = forwardMostMove-1;
        DisplayMove(currentMove-1); /* before DisplayMoveError */
        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]);
-
-        /* [HGM] illegal-move claim should forfeit game when Xboard */
-        /* only passes fully legal moves                            */
-        if( appData.testLegality && gameMode == TwoMachinesPlay ) {
-            GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
-                                "False illegal-move claim", GE_XBOARD );
-        }
        return;
     }
     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
@@ -7624,7 +7715,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.
@@ -7643,8 +7734,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) {
@@ -7665,7 +7756,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;
     }
@@ -7718,7 +7809,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                r = p + 1;
            }
        }
-            
+
         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
        return;
 
@@ -7843,7 +7934,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
     }
 
-    
+
     /*
      * Look for thinking output
      */
@@ -7894,7 +7985,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
@@ -7918,10 +8009,10 @@ 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;
                }
 
@@ -7934,7 +8025,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                                (unsigned) sizeof(tempStats.movelist) - 1);
                    }
 
-                    safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
+                    safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
                } else {
                    sprintf(tempStats.movelist, " no PV\n");
                }
@@ -7956,17 +8047,17 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
                 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;
@@ -7992,7 +8083,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;
@@ -8007,8 +8098,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);
                }
@@ -8029,7 +8120,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';
 
@@ -8078,7 +8169,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;
 
@@ -8099,7 +8190,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;
@@ -8116,7 +8207,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
@@ -8154,7 +8245,7 @@ ParseGameHistory(game)
     yynewstr(game);
     for (;;) {
        yyboardindex = boardIndex;
-       moveType = (ChessMove) yylex();
+       moveType = (ChessMove) Myylex();
        switch (moveType) {
          case IllegalMove:             /* maybe suicide chess, etc. */
   if (appData.debugMode) {
@@ -8162,20 +8253,8 @@ 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:
@@ -8213,7 +8292,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);
@@ -8223,7 +8302,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);
@@ -8231,7 +8310,7 @@ ParseGameHistory(game)
   }
            DisplayError(buf, 0);
            return;
-         case (ChessMove) 0:   /* end of file */
+         case EndOfFile:
            if (boardIndex < backwardMostMove) {
                /* Oops, gap.  How did that happen? */
                DisplayError(_("Gap in move list"), 0);
@@ -8277,6 +8356,7 @@ ParseGameHistory(game)
                if (q != NULL) *q = NULLCHAR;
                p++;
            }
+           while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
            gameInfo.resultDetails = StrSave(p);
            continue;
        }
@@ -8290,7 +8370,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]);
@@ -8324,15 +8404,24 @@ 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;
 
+  if (fromY == DROP_RANK) {
+       /* must be first */
+        piece = board[toY][toX] = (ChessSquare) fromX;
+  } else {
+      int i;
+
+      if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
+           if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
+               board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
+      } else
       if( board[fromY][fromX] == WhitePawn ) {
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
               board[EP_STATUS] = EP_PAWN_MOVE;
@@ -8341,62 +8430,52 @@ 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)
          king += (int) WhiteUnicorn - (int) WhiteKing;
 
     /* Code added by Tord: */
-    /* FRC castling assumed when king captures friendly rook. */
-    if (board[fromY][fromX] == WhiteKing &&
-            board[toY][toX] == WhiteRook) {
+    /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
+    if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
+        board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
-      if(toX > fromX) {
+      if((toX > fromX) != (piece == WhiteRook)) {
         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
       } else {
         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
       }
-    } else if (board[fromY][fromX] == BlackKing &&
-              board[toY][toX] == BlackRook) {
+    } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
+               board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
-      if(toX > fromX) {
+      if((toX > fromX) != (piece == BlackRook)) {
         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
       } else {
         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
@@ -8417,9 +8496,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
         board[toY][toX] = king;
         board[toY][toX+1] = board[fromY][BOARD_LEFT];
         board[fromY][BOARD_LEFT] = EmptySquare;
-    } else if (board[fromY][fromX] == WhitePawn
+    } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
+                board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
                && toY >= BOARD_HEIGHT-promoRank
-               && gameInfo.variant != VariantXiangqi
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
@@ -8481,9 +8560,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = BlackKing;
        board[fromY][0] = EmptySquare;
        board[toY][2] = BlackRook;
-    } else if (board[fromY][fromX] == BlackPawn
+    } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
+                board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
               && toY < promoRank
-               && gameInfo.variant != VariantXiangqi
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
@@ -8521,10 +8600,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) {
@@ -8533,10 +8608,11 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       /* [HGM] OK, so I have written it. Holdings are stored in the */
       /* penultimate board files, so they are automaticlly stored   */
       /* in the game history.                                       */
-      if (fromY == DROP_RANK) {
+      if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
+                                && promoChar && piece != WhitePawn && piece != BlackPawn) {
         /* Delete from holdings, by decreasing count */
         /* and erasing image if necessary            */
-        p = (int) fromX;
+        p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
         if(p < (int) BlackPawn) { /* white drop */
              p -= (int)WhitePawn;
                 p = PieceToNumber((ChessSquare)p);
@@ -8556,9 +8632,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
         }
       }
       if (captured != EmptySquare && gameInfo.holdingsSize > 0
-          && gameInfo.variant != VariantBughouse        ) {
+          && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
         /* [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;
        }
@@ -8600,11 +8676,15 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = EmptySquare;
       }
     }
-    if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
-        /* [HGM] Shogi promotions */
+    if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
+        board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
+    } else
+    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 && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
+        board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
     }
-
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
                && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
        // [HGM] superchess: take promotion piece out of holdings
@@ -8635,10 +8715,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;
@@ -8757,10 +8837,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 ||
@@ -8770,7 +8850,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
            }
@@ -8791,12 +8871,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);
     }
 
@@ -8809,7 +8889,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;
       }
@@ -8822,7 +8902,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 )
@@ -8831,16 +8911,18 @@ InitChessProgram(cps, setup)
            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
       if( gameInfo.variant == VariantGreat )
            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+      if( gameInfo.variant == VariantSChess )
+           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
 
       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;
@@ -8848,8 +8930,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;
@@ -8873,7 +8955,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()
                                ) {
@@ -8888,11 +8970,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
@@ -8919,18 +9001,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);
@@ -8967,7 +9049,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);
@@ -8976,7 +9058,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);
@@ -9048,8 +9130,8 @@ GameEnds(result, resultDetails, whosays)
 
     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) {
@@ -9078,17 +9160,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 ) {
@@ -9122,7 +9204,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;
                      }
@@ -9133,7 +9215,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;
                 }
@@ -9141,7 +9223,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);
@@ -9155,7 +9237,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;
                }
            }
@@ -9175,7 +9257,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
@@ -9205,7 +9287,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);
@@ -9256,8 +9338,8 @@ GameEnds(result, resultDetails, whosays)
                }
            }
        } else if (gameMode == EditGame ||
-                  gameMode == PlayFromGameFile || 
-                  gameMode == AnalyzeMode || 
+                  gameMode == PlayFromGameFile ||
+                  gameMode == AnalyzeMode ||
                   gameMode == AnalyzeFile) {
            nextGameMode = gameMode;
        } else {
@@ -9288,7 +9370,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);
            }
        }
@@ -9297,7 +9379,7 @@ GameEnds(result, resultDetails, whosays)
        if (first.isr != NULL)
          RemoveInputSource(first.isr);
        first.isr = NULL;
-    
+
        if (first.pr != NoProc) {
            ExitAnalyzeMode();
             DoSleep( appData.delayBeforeQuit );
@@ -9314,7 +9396,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);
            }
        }
@@ -9323,7 +9405,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);
@@ -9368,11 +9450,18 @@ GameEnds(result, resultDetails, whosays)
            return;
        } else {
            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));
+           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 (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
+               first.twoMachinesColor = "black\n";
+               second.twoMachinesColor = "white\n";
+           } else {
+               first.twoMachinesColor = "white\n";
+               second.twoMachinesColor = "black\n";
+           }
        }
     }
     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
@@ -9392,21 +9481,22 @@ GameEnds(result, resultDetails, whosays)
 /* 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;
     }
@@ -9430,7 +9520,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);
@@ -9484,7 +9574,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;
@@ -9492,7 +9582,7 @@ Reset(redraw, init)
     gotPremove = FALSE;
     alarmSounded = FALSE;
 
-    GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+    GameEnds(EndOfFile, NULL, GE_PLAYER);
     if(appData.serverMovesName != NULL) {
         /* [HGM] prepare to make moves file for broadcasting */
         clock_t t = clock();
@@ -9541,7 +9631,7 @@ AutoPlayGameLoop()
          return;
        if (matchMode || appData.timeDelay == 0)
          continue;
-       if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
+       if (appData.timeDelay < 0)
          return;
        StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
        break;
@@ -9558,10 +9648,18 @@ AutoPlayOneMove()
       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
     }
 
-    if (gameMode != PlayFromGameFile)
+    if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
       return FALSE;
 
+    if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
+      pvInfoList[currentMove].depth = programStats.depth;
+      pvInfoList[currentMove].score = programStats.score;
+      pvInfoList[currentMove].time  = 0;
+      if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
+    }
+
     if (currentMove >= forwardMostMove) {
+      if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
       gameMode = EditGame;
       ModeHighlight();
 
@@ -9570,7 +9668,7 @@ AutoPlayOneMove()
 
       return FALSE;
     }
-    
+
     toX = moveList[currentMove][2] - AAA;
     toY = moveList[currentMove][3] - ONE;
 
@@ -9609,26 +9707,26 @@ 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) {
+    if (readAhead != EndOfFile) {
       moveType = readAhead;
     } else {
       if (gameFileFP == NULL)
          return FALSE;
-      moveType = (ChessMove) yylex();
+      moveType = (ChessMove) Myylex();
     }
-    
+
     done = FALSE;
     switch (moveType) {
       case Comment:
-       if (appData.debugMode) 
+       if (appData.debugMode)
          fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
        p = yy_text;
 
@@ -9638,22 +9736,8 @@ 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:
@@ -9708,6 +9792,7 @@ LoadGameOneMove(readAhead)
            if (q != NULL) *q = NULLCHAR;
            p++;
        }
+       while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
        GameEnds(moveType, p, GE_FILE);
        done = TRUE;
        if (cmailMsgLoaded) {
@@ -9719,7 +9804,7 @@ LoadGameOneMove(readAhead)
        }
        break;
 
-      case (ChessMove) 0:      /* end of file */
+      case EndOfFile:
        if (appData.debugMode)
          fprintf(debugFP, "Parser hit end of file\n");
        switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
@@ -9747,7 +9832,7 @@ LoadGameOneMove(readAhead)
            if (appData.debugMode)
              fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
                      yy_text, (int) moveType);
-           return LoadGameOneMove((ChessMove)0); /* tail recursion */
+           return LoadGameOneMove(EndOfFile); /* tail recursion */
        }
        /* else fall thru */
 
@@ -9782,13 +9867,13 @@ LoadGameOneMove(readAhead)
        if (appData.debugMode)
          fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
                  yy_text, (int) moveType);
-       return LoadGameOneMove((ChessMove)0); /* tail recursion */
+       return LoadGameOneMove(EndOfFile); /* tail recursion */
 
       case IllegalMove:
        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);
@@ -9808,7 +9893,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);
@@ -9819,7 +9904,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);
@@ -9840,9 +9925,7 @@ LoadGameOneMove(readAhead)
        return FALSE;
     } else {
        /* currentMoveString is set as a side-effect of yylex */
-       strcat(currentMoveString, "\n");
-       strcpy(moveList[forwardMostMove], currentMoveString);
-       
+
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
        currentMove = forwardMostMove;
@@ -9905,9 +9988,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;
@@ -9915,12 +9998,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)) {
@@ -9929,14 +10012,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);
@@ -9944,11 +10027,11 @@ MakeRegisteredMove()
                GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
            }
            break;
-           
+
          case CMAIL_ACCEPT:
            GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
            break;
-             
+
          default:
            break;
        }
@@ -10044,7 +10127,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 )
@@ -10062,7 +10145,7 @@ LoadGame(f, gameNumber, title, useList)
 
     if (useList) {
        lg = (ListGame *) ListElem(&gameList, gameNumber-1);
-       
+
        if (lg) {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
@@ -10087,7 +10170,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);
@@ -10098,7 +10181,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);
@@ -10116,24 +10199,24 @@ 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.
      * 5-4-02: Let's try being more lenient and allowing a game to
      * start with an unnumbered move.  Does that break anything?
      */
-    cm = lastLoadGameStart = (ChessMove) 0;
+    cm = lastLoadGameStart = EndOfFile;
     while (gn > 0) {
        yyboardindex = forwardMostMove;
-       cm = (ChessMove) yylex();
+       cm = (ChessMove) Myylex();
        switch (cm) {
-         case (ChessMove) 0:
+         case EndOfFile:
            if (cmailMsgLoaded) {
                nCmailGames = CMAIL_MAX_GAMES - gn;
            } else {
@@ -10147,7 +10230,7 @@ LoadGame(f, gameNumber, title, useList)
            gn--;
            lastLoadGameStart = cm;
            break;
-           
+
          case MoveNumberOne:
            switch (lastLoadGameStart) {
              case GNUChessGame:
@@ -10155,7 +10238,7 @@ LoadGame(f, gameNumber, title, useList)
              case PGNTag:
                break;
              case MoveNumberOne:
-             case (ChessMove) 0:
+             case EndOfFile:
                gn--;           /* count this game */
                lastLoadGameStart = cm;
                break;
@@ -10170,7 +10253,7 @@ LoadGame(f, gameNumber, title, useList)
              case GNUChessGame:
              case PGNTag:
              case MoveNumberOne:
-             case (ChessMove) 0:
+             case EndOfFile:
                gn--;           /* count this game */
                lastLoadGameStart = cm;
                break;
@@ -10184,7 +10267,7 @@ LoadGame(f, gameNumber, title, useList)
            if (gn > 0) {
                do {
                    yyboardindex = forwardMostMove;
-                   cm = (ChessMove) yylex();
+                   cm = (ChessMove) Myylex();
                } while (cm == PGNTag || cm == Comment);
            }
            break;
@@ -10205,7 +10288,7 @@ LoadGame(f, gameNumber, title, useList)
          case NormalMove:
            /* Only a NormalMove can be at the start of a game
             * without a position diagram. */
-           if (lastLoadGameStart == (ChessMove) 0) {
+           if (lastLoadGameStart == EndOfFile ) {
              gn--;
              lastLoadGameStart = MoveNumberOne;
            }
@@ -10215,7 +10298,7 @@ LoadGame(f, gameNumber, title, useList)
            break;
        }
     }
-    
+
     if (appData.debugMode)
       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
 
@@ -10223,12 +10306,12 @@ LoadGame(f, gameNumber, title, useList)
        /* Skip any header junk before position diagram and/or move 1 */
        for (;;) {
            yyboardindex = forwardMostMove;
-           cm = (ChessMove) yylex();
+           cm = (ChessMove) Myylex();
 
-           if (cm == (ChessMove) 0 ||
+           if (cm == EndOfFile ||
                cm == GNUChessGame || cm == XBoardGame) {
                /* Empty game; pretend end-of-file and handle later */
-               cm = (ChessMove) 0;
+               cm = EndOfFile;
                break;
            }
 
@@ -10241,11 +10324,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++;
@@ -10256,7 +10339,7 @@ LoadGame(f, gameNumber, title, useList)
            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);
         }
 
@@ -10273,8 +10356,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) {
@@ -10296,17 +10379,17 @@ LoadGame(f, gameNumber, title, useList)
        }
 
        yyboardindex = forwardMostMove;
-       cm = (ChessMove) yylex();
+       cm = (ChessMove) Myylex();
 
        /* 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);
            yyboardindex = forwardMostMove;
-           cm = (ChessMove) yylex();
+           cm = (ChessMove) Myylex();
        }
     }
 
@@ -10359,19 +10442,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) {
@@ -10383,7 +10466,7 @@ LoadGame(f, gameNumber, title, useList)
            }
        }
        yyboardindex = forwardMostMove;
-       cm = (ChessMove) yylex();
+       cm = (ChessMove) Myylex();
     }
 
     if (first.pr == NoProc) {
@@ -10397,22 +10480,22 @@ 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);
        yyboardindex = forwardMostMove;
-       cm = (ChessMove) yylex();
+       cm = (ChessMove) Myylex();
     }
 
-    if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
+    if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
        cm == WhiteWins || cm == BlackWins ||
        cm == GameIsDrawn || cm == GameUnfinished) {
        DisplayMessage("", _("No moves in game"));
@@ -10435,7 +10518,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) {
@@ -10443,13 +10526,13 @@ 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);
 
     /* load the remaining moves from the file */
-    while (LoadGameOneMove((ChessMove)0)) {
+    while (LoadGameOneMove(EndOfFile)) {
       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     }
@@ -10472,7 +10555,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 */
@@ -10531,7 +10614,7 @@ LoadPosition(f, positionNumber, title)
     char *p, line[MSG_SIZ];
     Board initial_position;
     int i, j, fenMode, pn;
-    
+
     if (gameMode == Training )
        SetTrainingModeOff();
 
@@ -10544,11 +10627,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 */
@@ -10598,7 +10681,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++) {
@@ -10607,7 +10690,7 @@ LoadPosition(f, positionNumber, title)
                initial_position[i][j++] = CharToPiece(*p);
            }
        }
-    
+
        blackPlaysFirst = FALSE;
        if (!feof(f)) {
            (void) fgets(line, MSG_SIZ, f);
@@ -10616,13 +10699,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 {
@@ -10639,7 +10722,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);
@@ -10650,7 +10733,7 @@ int i, j;
     timeRemaining[0][1] = whiteTimeRemaining;
     timeRemaining[1][1] = blackTimeRemaining;
     DrawPosition(FALSE, boards[currentMove]);
-   
+
     return TRUE;
 }
 
@@ -10681,7 +10764,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;
     }
@@ -10717,7 +10800,7 @@ SavePart(str)
 {
     static char buf[MSG_SIZ];
     char *p;
-    
+
     p = strchr(str, ' ');
     if (p == NULL) return str;
     strncpy(buf, str, p - str);
@@ -10796,7 +10879,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 );
             }
@@ -10817,11 +10900,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);
@@ -10838,7 +10921,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 );
             }
         }
 
@@ -10859,15 +10942,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;
 
@@ -10886,7 +10968,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 */
@@ -10910,18 +10992,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 */
 
@@ -10942,7 +11031,7 @@ SaveGamePGN(f)
 
        i++;
     }
-    
+
     /* Start a new line */
     if (linelen > 0) fprintf(f, "\n");
 
@@ -10972,12 +11061,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);
@@ -11012,7 +11101,7 @@ SaveGameOldStyle(f)
            i++;
        }
     }
-    
+
     if (commentList[i] != NULL) {
        fprintf(f, "[%s]\n", commentList[i]);
     }
@@ -11077,11 +11166,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");
@@ -11106,7 +11195,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) {
@@ -11134,7 +11223,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;
@@ -11142,10 +11231,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
@@ -11155,7 +11244,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);
 
@@ -11181,7 +11270,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;
@@ -11220,30 +11309,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) {
@@ -11279,18 +11367,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) {
@@ -11320,10 +11408,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;
                }
@@ -11352,24 +11440,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);
            }
        }
@@ -11378,41 +11466,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);
            }
        }
     }
@@ -11471,7 +11554,7 @@ ExitEvent(status)
     /* Kill off chess programs */
     if (first.pr != NoProc) {
        ExitAnalyzeMode();
-        
+
         DoSleep( appData.delayBeforeQuit );
        SendToProgram("quit\n", &first);
         DoSleep( appData.delayAfterQuit );
@@ -11509,7 +11592,7 @@ PauseEvent()
            DisplayBothClocks();
        }
        if (gameMode == PlayFromGameFile) {
-           if (appData.timeDelay >= 0) 
+           if (appData.timeDelay >= 0)
                AutoPlayGameLoop();
        } else if (gameMode == IcsExamining && pauseExamInvalid) {
            Reset(FALSE, TRUE);
@@ -11565,11 +11648,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]);
@@ -11580,7 +11663,7 @@ void
 EditTagsEvent()
 {
     char *tags = PGNTags(&gameInfo);
-    EditTagsPopUp(tags);
+    EditTagsPopUp(tags, NULL);
     free(tags);
 }
 
@@ -11648,25 +11731,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();
 
@@ -11679,10 +11762,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) {
@@ -11709,11 +11792,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);
     }
@@ -11722,32 +11805,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();
 
@@ -11756,10 +11839,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) {
@@ -11784,11 +11867,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);
     }
@@ -11801,30 +11884,61 @@ 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);
 }
 
 void
+SettingsMenuIfReady()
+{
+  if (second.lastPing != second.lastPong) {
+    DisplayMessage("", _("Waiting for second chess program"));
+    ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
+    return;
+  }
+  ThawUI();
+  DisplayMessage("", "");
+  SettingsPopUp(&second);
+}
+
+int
+WaitForSecond(DelayedEventCallback retry)
+{
+    if (second.pr == NULL) {
+       StartChessProgram(&second);
+       if (second.protocolVersion == 1) {
+         retry();
+       } else {
+         /* kludge: allow timeout for initial "feature" command */
+         FreezeUI();
+         DisplayMessage("", _("Starting second chess program"));
+         ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
+       }
+       return 1;
+    }
+    return 0;
+}
+
+void
 TwoMachinesEvent P((void))
 {
     int i;
     char buf[MSG_SIZ];
     ChessProgramState *onmove;
     char *bookHit = NULL;
-    
+
     if (appData.noChessProgram) return;
 
     switch (gameMode) {
@@ -11859,21 +11973,14 @@ TwoMachinesEvent P((void))
     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
     ResurrectChessProgram();   /* in case first program isn't running */
 
-    if (second.pr == NULL) {
-       StartChessProgram(&second);
-       if (second.protocolVersion == 1) {
-         TwoMachinesEventIfReady();
-       } else {
-         /* kludge: allow timeout for initial "feature" command */
-         FreezeUI();
-         DisplayMessage("", _("Starting second chess program"));
-         ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
-       }
-       return;
-    }
+    if(WaitForSecond(TwoMachinesEventIfReady)) return;
     DisplayMessage("", "");
     InitChessProgram(&second, FALSE);
     SendToProgram("force\n", &second);
+    if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
+      ScheduleDelayedEvent(TwoMachinesEvent, 10);
+      return;
+    }
     if (startedFromSetupPosition) {
        SendBoard(&second, backwardMostMove);
     if (appData.debugMode) {
@@ -11898,12 +12005,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);
     }
 
@@ -11931,11 +12038,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;
@@ -11990,7 +12097,7 @@ IcsClientEvent()
       case AnalyzeFile:
        ExitAnalyzeMode();
        break;
-       
+
       default:
        EditGameEvent();
        break;
@@ -12032,7 +12139,7 @@ EditGameEvent()
        SendToProgram("force\n", &first);
        break;
       case TwoMachinesPlay:
-       GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+       GameEnds(EndOfFile, NULL, GE_PLAYER);
        ResurrectChessProgram();
        SetUserThinkingEnables();
        break;
@@ -12055,7 +12162,7 @@ EditGameEvent()
       default:
        return;
     }
-    
+
     pausing = FALSE;
     StopClocks();
     first.offeredDraw = second.offeredDraw = 0;
@@ -12082,8 +12189,8 @@ EditGameEvent()
            whiteFlag = blackFlag = 0;
        }
        DisplayTitle("");
-    }          
-    
+    }
+
     gameMode = EditGame;
     ModeHighlight();
     SetGameInfo();
@@ -12097,16 +12204,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;
@@ -12151,8 +12258,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 {
@@ -12196,7 +12303,7 @@ SendMultiLineToICS(buf)
     len = strlen(buf);
     if (len > MSG_SIZ)
       len = MSG_SIZ;
-  
+
     strncpy(temp, buf, len);
     temp[len] = 0;
 
@@ -12262,7 +12369,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);
                        }
@@ -12288,7 +12395,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) {
@@ -12319,7 +12426,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:
@@ -12341,8 +12448,8 @@ 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) {
@@ -12415,7 +12522,7 @@ void
 AcceptEvent()
 {
     /* Accept a pending offer of any kind from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("accept\n");
@@ -12440,7 +12547,7 @@ void
 DeclineEvent()
 {
     /* Decline a pending offer of any kind from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("decline\n");
@@ -12509,17 +12616,41 @@ CallFlagEvent()
 }
 
 void
+ClockClick(int which)
+{      // [HGM] code moved to back-end from winboard.c
+       if(which) { // black clock
+         if (gameMode == EditPosition || gameMode == IcsExamining) {
+           SetBlackToPlayEvent();
+         } else if (gameMode == EditGame || shiftKey) {
+           AdjustClock(which, -1);
+         } else if (gameMode == IcsPlayingWhite ||
+                    gameMode == MachinePlaysBlack) {
+           CallFlagEvent();
+         }
+       } else { // white clock
+         if (gameMode == EditPosition || gameMode == IcsExamining) {
+           SetWhiteToPlayEvent();
+         } else if (gameMode == EditGame || shiftKey) {
+           AdjustClock(which, -1);
+         } else if (gameMode == IcsPlayingBlack ||
+                  gameMode == MachinePlaysWhite) {
+           CallFlagEvent();
+         }
+       }
+}
+
+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
@@ -12554,7 +12685,7 @@ void
 AdjournEvent()
 {
     /* Offer Adjourn or accept pending Adjourn offer from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("adjourn\n");
@@ -12568,7 +12699,7 @@ void
 AbortEvent()
 {
     /* Offer Abort or accept pending Abort offer from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("abort\n");
@@ -12581,7 +12712,7 @@ void
 ResignEvent()
 {
     /* Resign.  You can do this even if it's not your turn. */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("resign\n");
@@ -12642,12 +12773,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]) {
@@ -12669,8 +12800,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);
@@ -12678,7 +12809,7 @@ ForwardInner(target)
     } else {
        currentMove = target;
     }
-    
+
     if (gameMode == EditGame || gameMode == EndOfGame) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
@@ -12711,13 +12842,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");
@@ -12752,7 +12883,7 @@ BackwardInner(target)
     }
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
-    
+
     if (moveList[target][0]) {
        int fromX, fromY, toX, toY;
         toX = moveList[target][2] - AAA;
@@ -12781,7 +12912,7 @@ BackwardInner(target)
     } else {
        currentMove = target;
     }
-    
+
     if (gameMode == EditGame || gameMode == EndOfGame) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
@@ -12811,7 +12942,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;
@@ -13036,7 +13167,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]);
@@ -13097,13 +13228,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);
 }
@@ -13117,8 +13248,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);
@@ -13151,7 +13282,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("-");
@@ -13212,7 +13343,13 @@ ReplaceComment(index, text)
      char *text;
 {
     int len;
+    char *p;
+    float score;
 
+    if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
+       pvInfoList[index-1].depth == len &&
+       fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
+       (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
     while (*text == '\n') text++;
     len = strlen(text);
     while (len > 0 && text[len - 1] == '\n') len--;
@@ -13231,12 +13368,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");
@@ -13283,27 +13420,27 @@ 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 + len + 6);
        free(old);
        // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
-       if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
-         if(addBraces) addBraces = FALSE; else { text++; len--; }
+       if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
+         if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
          while (*text == '\n') { text++; len--; }
          commentList[index][--oldlen] = NULLCHAR;
       }
-       if(addBraces) strcat(commentList[index], "\n{\n");
+       if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
        else          strcat(commentList[index], "\n");
        strcat(commentList[index], text);
-       if(addBraces) strcat(commentList[index], "\n}\n");
+       if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
        else          strcat(commentList[index], "\n");
     } else {
        commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
        if(addBraces)
-            strcpy(commentList[index], "{\n");
+         safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
        else commentList[index][0] = NULLCHAR;
        strcat(commentList[index], text);
-       strcat(commentList[index], "\n");
-       if(addBraces) strcat(commentList[index], "}\n");
+       strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
+       if(addBraces == TRUE) strcat(commentList[index], "}\n");
     }
 }
 
@@ -13322,7 +13459,7 @@ static char * FindStr( char * text, char * sub_text )
 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
 char *GetInfoFromComment( int index, char * text )
 {
-    char * sep = text;
+    char * sep = text, *p;
 
     if( text != NULL && index > 0 ) {
         int score = 0;
@@ -13360,11 +13497,20 @@ char *GetInfoFromComment( int index, char * text )
                 return text;
             }
 
+            p = text;
+            if(p[1] == '(') { // comment starts with PV
+               p = strchr(p, ')'); // locate end of PV
+               if(p == NULL || sep < p+5) return text;
+               // at this point we have something like "{(.*) +0.23/6 ..."
+               p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
+               *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
+               // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
+            }
             time = -1; sec = -1; deci = -1;
-            if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
-               sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
-                sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
-                sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
+            if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+               sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+                sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+                sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
                 return text;
             }
 
@@ -13380,7 +13526,7 @@ char *GetInfoFromComment( int index, char * text )
             /* [HGM] PV time: now locate end of PV info */
             while( *++sep >= '0' && *sep <= '9'); // strip depth
             if(time >= 0)
-            while( *++sep >= '0' && *sep <= '9'); // strip time
+            while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
             if(sec >= 0)
             while( *++sep >= '0' && *sep <= '9'); // strip seconds
             if(deci >= 0)
@@ -13400,6 +13546,7 @@ char *GetInfoFromComment( int index, char * text )
         pvInfoList[index-1].score = score;
         pvInfoList[index-1].time  = 10*time; // centi-sec
         if(*sep == '}') *sep = 0; else *--sep = '{';
+        if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
     }
     return sep;
 }
@@ -13414,24 +13561,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;
             }
@@ -13456,13 +13603,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;
                 }
@@ -13471,8 +13617,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);
 
@@ -13486,12 +13631,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;
@@ -13499,12 +13644,14 @@ 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, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
+                  sscanf(message, "hint: %c", &c)!=1 && 
                   sscanf(message, "pong %c", &c)!=1   && start != '#') {
                    quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
                    print = (appData.engineComments >= 2);
@@ -13513,8 +13660,8 @@ ReceiveFromProgram(isr, closure, message, count, error)
        }
        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);
        }
@@ -13523,7 +13670,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;
     }
 
@@ -13557,12 +13704,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 */
@@ -13570,10 +13717,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 %g\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 %g\n", mps, tc/60000,
+                seconds, inc/1000.);
       }
     }
     SendToProgram(buf, cps);
@@ -13582,18 +13729,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);
        }
     }
 }
@@ -13601,7 +13749,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;
@@ -13634,11 +13782,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);
 }
 
@@ -13652,12 +13800,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;
   }
@@ -13677,7 +13827,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;
   }
@@ -13699,14 +13849,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
@@ -13775,8 +13925,22 @@ 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;
+               if(q = strchr(buf, '=')) switch(opt->type) {
+                   case ComboBox:
+                       for(n=0; n<opt->max; n++)
+                           if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
+                       break;
+                   case TextBox:
+                       safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
+                       break;
+                   case Spin:
+                   case CheckBox:
+                       opt->value = atoi(q+1);
+                   default:
+                       break;
+               }
                strcat(buf, "\n");
                SendToProgram(buf, cps);
        }
@@ -13790,6 +13954,7 @@ FeatureDone(cps, val)
 {
   DelayedEventCallback cb = GetDelayedEvent();
   if ((cb == InitBackEnd3 && cps == &first) ||
+      (cb == SettingsMenuIfReady && cps == &second) ||
       (cb == TwoMachinesEventIfReady && cps == &second)) {
     CancelDelayedEvent();
     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
@@ -13801,7 +13966,7 @@ FeatureDone(cps, val)
 void
 ParseFeatures(args, cps)
      char* args;
-     ChessProgramState *cps;  
+     ChessProgramState *cps;
 {
   char *p = args;
   char *q;
@@ -13813,10 +13978,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;
@@ -13858,13 +14023,13 @@ 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;
@@ -13874,7 +14039,7 @@ ParseFeatures(args, cps)
     /* 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 == '=') {
@@ -13939,7 +14104,7 @@ NewSettingEvent(option, feature, command, value)
     char buf[MSG_SIZ];
 
     if (gameMode == EditPosition) EditPositionDone(TRUE);
-    sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
+    snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
     if(feature == NULL || *feature) SendToProgram(buf, &first);
     if (gameMode == TwoMachinesPlay) {
        if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
@@ -13954,7 +14119,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);
@@ -13991,11 +14156,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;
@@ -14021,9 +14186,9 @@ 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",
+         snprintf(res, MSG_SIZ, " {%s} %s",
                    T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
        }
     } else {
@@ -14033,7 +14198,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);
@@ -14048,19 +14213,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;
     }
@@ -14161,15 +14326,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
@@ -14204,7 +14374,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*/
@@ -14243,7 +14413,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;
@@ -14276,7 +14446,7 @@ AdjustClock(Boolean which, int dir)
 
 /* Stop clocks and reset to a fresh time control */
 void
-ResetClocks() 
+ResetClocks()
 {
     (void) StopClockTimer();
     if (appData.icsActive) {
@@ -14285,13 +14455,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();
 }
 
@@ -14307,7 +14479,7 @@ DecrementClocks()
 
     if (!appData.clockMode) return;
     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
-       
+
     GetTimeMark(&now);
 
     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
@@ -14319,17 +14491,30 @@ DecrementClocks()
     if (WhiteOnMove(forwardMostMove)) {
        if(whiteNPS >= 0) lastTickLength = 0;
        timeRemaining = whiteTimeRemaining -= lastTickLength;
+        if(timeRemaining < 0 && !appData.icsActive) {
+            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 && !appData.icsActive) { // [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);
@@ -14337,9 +14522,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 */
@@ -14349,7 +14534,7 @@ DecrementClocks()
 
        if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
            alarmSounded = FALSE;
-       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
+       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
            PlayAlarmSound();
            alarmSounded = TRUE;
        }
@@ -14380,15 +14565,15 @@ SwitchClocks(int newMoveNr)
            if(blackNPS >= 0) lastTickLength = 0;
            blackTimeRemaining -= lastTickLength;
            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
-//         if(pvInfoList[forwardMostMove-1].time == -1)
-                 pvInfoList[forwardMostMove-1].time =               // use GUI time
+//         if(pvInfoList[forwardMostMove].time == -1)
+                 pvInfoList[forwardMostMove].time =               // use GUI time
                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
        } else {
           if(whiteNPS >= 0) lastTickLength = 0;
           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 = 
+//         if(pvInfoList[forwardMostMove].time == -1)
+                 pvInfoList[forwardMostMove].time =
                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
        }
        flagged = CheckFlags();
@@ -14425,12 +14610,12 @@ SwitchClocks(int newMoveNr)
       whiteTimeRemaining : blackTimeRemaining);
     StartClockTimer(intendedTickLength);
 }
-       
+
 
 /* Stop both clocks */
 void
 StopClocks()
-{      
+{
     long lastTickLength;
     TimeMark now;
 
@@ -14451,7 +14636,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
@@ -14469,7 +14654,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;
@@ -14492,12 +14677,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;
     }
 
@@ -14510,22 +14695,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;
 }
 
@@ -14538,13 +14723,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;
 }
 
@@ -14553,9 +14738,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]))
@@ -14573,7 +14758,7 @@ StrCaseCmp(s1, s2)
      char *s1, *s2;
 {
     char c1, c2;
-    
+
     for (;;) {
        c1 = ToLower(*s1++);
        c2 = ToLower(*s2++);
@@ -14604,12 +14789,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 *
@@ -14620,7 +14806,7 @@ StrSavePtr(s, savePtr)
        free(*savePtr);
     }
     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
-       strcpy(*savePtr, s);
+      safeStrCpy(*savePtr, s, strlen(s)+1);
     }
     return(*savePtr);
 }
@@ -14634,7 +14820,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);
 }
@@ -14673,7 +14859,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) */
@@ -14756,7 +14942,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;
@@ -14805,7 +14991,7 @@ PositionToFEN(move, overrideCastling)
     }
     /* Fullmove number */
     sprintf(p, "%d", (move / 2) + 1);
-    
+
     return StrSave(buf);
 }
 
@@ -14882,12 +15068,12 @@ ParseFEN(board, blackPlaysFirst, fen)
     while(*p==' ') p++;
     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
         if(*p == '[') p++;
-        if(*p == '-' ) *p++; /* empty holdings */ else {
+        if(*p == '-' ) p++; /* empty holdings */ else {
             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
             /* if we would allow FEN reading to set board size, we would   */
             /* have to add holdings and shift the board read so far here   */
             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
-                *p++;
+                p++;
                 if((int) piece >= (int) BlackPawn ) {
                     i = (int)piece - (int)BlackPawn;
                    i = PieceToNumber((ChessSquare)i);
@@ -14903,7 +15089,7 @@ ParseFEN(board, blackPlaysFirst, fen)
                 }
             }
         }
-        if(*p == ']') *p++;
+        if(*p == ']') p++;
     }
 
     while(*p == ' ') p++;
@@ -14918,7 +15104,7 @@ ParseFEN(board, blackPlaysFirst, fen)
       case 'w':
         *blackPlaysFirst = FALSE;
        break;
-      case 'b': 
+      case 'b':
        *blackPlaysFirst = TRUE;
        break;
       default:
@@ -15031,14 +15217,14 @@ 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 {
          char c = *p++ - AAA;
 
          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
-         if(*p >= '0' && *p <='9') *p++;
+         if(*p >= '0' && *p <='9') p++;
          board[EP_STATUS] = c;
       }
     }
@@ -15051,7 +15237,7 @@ ParseFEN(board, blackPlaysFirst, fen)
 
     return TRUE;
 }
-      
+
 void
 EditPositionPasteFEN(char *fen)
 {
@@ -15088,9 +15274,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;
 }
 
@@ -15183,7 +15369,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;
@@ -15236,12 +15422,13 @@ PopTail(Boolean annotate)
        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; }
@@ -15273,7 +15460,7 @@ PopTail(Boolean annotate)
        return TRUE;
 }
 
-void 
+void
 CleanupTail()
 {      // remove all shelved variations
        int i;