tWindows installer now upgrades properly; Windows system tray icon can be controlled via. MinToSysTray config variable; Unix server can now be configured while running via. "dopewars -A" - vaccinewars - be a doctor and try to vaccinate the world
 (HTM) git clone git://src.adamsgaard.dk/vaccinewars
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit c77672f5f19dc6838e0f010339fa43dbab051ff3
 (DIR) parent 8f75f1bd80b636b71b665bf2b93d10c5adcdd511
 (HTM) Author: Ben Webb <ben@salilab.org>
       Date:   Sun, 11 Nov 2001 19:20:30 +0000
       
       Windows installer now upgrades properly; Windows system tray icon can be
       controlled via. MinToSysTray config variable; Unix server can now be
       configured while running via. "dopewars -A"
       
       
       Diffstat:
         M ChangeLog                           |       5 ++++-
         M TODO                                |       7 ++++---
         M doc/configfile.html                 |       9 ++++++++-
         M po/POTFILES.in                      |       1 +
         M src/Makefile.am                     |       2 +-
         M src/Makefile.in                     |      21 ++++++++++++---------
         A src/admin.c                         |     104 +++++++++++++++++++++++++++++++
         A src/admin.h                         |      35 +++++++++++++++++++++++++++++++
         M src/dopewars.c                      |      48 +++++++++++++++++++++----------
         M src/dopewars.h                      |       6 +++++-
         M src/serverside.c                    |     113 +++++++++++++++++++------------
         M src/serverside.h                    |       1 -
         M win32/filelist                      |       5 +++++
         M win32/makeinstall.c                 |      14 +++++++++-----
         M win32/setup.c                       |     240 +++++++++++++++++++++++++++----
         M win32/uninstall.c                   |     249 ++-----------------------------
         M win32/util.c                        |     247 +++++++++++++++++++++++++++++++
         M win32/util.h                        |       7 +++++++
       
       18 files changed, 765 insertions(+), 349 deletions(-)
       ---
 (DIR) diff --git a/ChangeLog b/ChangeLog
       t@@ -1,4 +1,7 @@
        cvs
       +    - Fatal bug when visiting the bank (under Win2000/XP) fixed
       +    - Windows installer should now upgrade old versions properly
       +    - Windows server can now be run as an NT Service
            - Currency can now be configured with Currency.Symbol and Currency.Prefix
            - Windows client windows cannot now be made unreadably small
            - Bank/loan shark dialog now warns on entering negative prices
       t@@ -7,7 +10,7 @@ cvs
            - Documentation on the client-server protocol added
            - Windows graphical server can be minimized to the System Tray
            - Keyboard shortcuts for menu items in Windows client
       -    - Default buttons for Windows client, bank bug (hopefully) fixed
       +    - Default buttons (ENTER -> "OK") for Windows client
            - RPM build/make install can now be run as non-superuser
            - Code cleanups
        
 (DIR) diff --git a/TODO b/TODO
       t@@ -1,7 +1,8 @@
       -- Make Windows installer deal with old installed versions properly
       -- Make server run as an NT Service
       +- Admin of running servers
       +    DONE (- feedback when setting/querying variables)
       +    - mechanism for interacting with NT Service servers
       +DONE for Unix - test Windows (- Fix error reporting for config. file reading)
        - Configuration file editor thingy in the client?
       -- Make minimize-to-systray code a) more robust and b) configurable
        - GSS_API SOCKS support?
        - Fix problem with dialogs popping up while menus are open
        - Increase difficulty of escaping from another player - impose penalty on
 (DIR) diff --git a/doc/configfile.html b/doc/configfile.html
       t@@ -141,6 +141,13 @@ mode, not connected to a server) to use the file <i>/var/lib/dopewars.sco</i>
        to store high scores. This can be overridden with the -f
        <a href="commandline.html#hiscore">command line option</a>.</dd>
        
       +<dt><b>MinToSysTray=<i>TRUE</i></b></dt>
       +<dd>Rather than behaving as a normal window, the dopewars server window adds
       +an icon to the Windows System Tray, and, when the window is minimized, it
       +vanishes completely. Clicking on the System Tray icon will restore the
       +window to its normal state. If FALSE, the System Tray is not used. Only
       +supported on Windows systems.</dd>
       +
        <dt><b>Pager=<i>"more"</i></b></dt>
        <dd>Sets the pager used to display multi-page output in an interactive server
        to <i>more</i>. ("less" is a popular alternative)</dd>
       t@@ -597,6 +604,6 @@ any drugs, and clients will display this information if available.</dd>
        <ul>
        <li><a href="index.html">Main index</a></li>
        </ul>
       -<p>Last update: <b>28-10-2001</b></p>
       +<p>Last update: <b>20-11-2001</b></p>
        </body>
        </html>
 (DIR) diff --git a/po/POTFILES.in b/po/POTFILES.in
       t@@ -10,4 +10,5 @@ src/serverside.c
        src/error.c
        src/message.c
        src/network.c
       +src/admin.c
        src/AIPlayer.c
 (DIR) diff --git a/src/Makefile.am b/src/Makefile.am
       t@@ -1,5 +1,5 @@
        bin_PROGRAMS = dopewars
       -dopewars_SOURCES = AIPlayer.c curses_client.c dopeos.c dopewars.c \
       +dopewars_SOURCES = admin.c AIPlayer.c curses_client.c dopeos.c dopewars.c \
                           error.c gtk_client.c message.c network.c serverside.c \
                           tstring.c winmain.c @GTKPORT_C@
        dopewars_DEPENDENCIES = @INTLLIBS@ @GTKPORT_O@ @WNDRES@
 (DIR) diff --git a/src/Makefile.in b/src/Makefile.in
       t@@ -1,6 +1,6 @@
       -# Makefile.in generated automatically by automake 1.4 from Makefile.am
       +# Makefile.in generated automatically by automake 1.4-p5 from Makefile.am
        
       -# Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc.
       +# Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc.
        # This Makefile.in is free software; the Free Software Foundation
        # gives unlimited permission to copy and/or distribute it,
        # with or without modifications, as long as this notice is preserved.
       t@@ -96,7 +96,9 @@ WNDRES = @WNDRES@
        localedir = @localedir@
        
        bin_PROGRAMS = dopewars
       -dopewars_SOURCES = AIPlayer.c curses_client.c dopeos.c dopewars.c                    error.c gtk_client.c message.c network.c serverside.c                    tstring.c winmain.c @GTKPORT_C@
       +dopewars_SOURCES = admin.c AIPlayer.c curses_client.c dopeos.c dopewars.c \
       +                   error.c gtk_client.c message.c network.c serverside.c \
       +                   tstring.c winmain.c @GTKPORT_C@
        
        dopewars_DEPENDENCIES = @INTLLIBS@ @GTKPORT_O@ @WNDRES@
        INCLUDES = @GTK_CFLAGS@ -I.. -I.
       t@@ -114,9 +116,9 @@ PROGRAMS =  $(bin_PROGRAMS)
        CPPFLAGS = @CPPFLAGS@
        LDFLAGS = @LDFLAGS@
        LIBS = @LIBS@
       -dopewars_OBJECTS =  AIPlayer.o curses_client.o dopeos.o dopewars.o \
       -error.o gtk_client.o message.o network.o serverside.o tstring.o \
       -winmain.o
       +dopewars_OBJECTS =  admin.o AIPlayer.o curses_client.o dopeos.o \
       +dopewars.o error.o gtk_client.o message.o network.o serverside.o \
       +tstring.o winmain.o
        dopewars_LDADD = $(LDADD)
        dopewars_LDFLAGS = 
        CFLAGS = @CFLAGS@
       t@@ -130,9 +132,10 @@ DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
        
        TAR = gtar
        GZIP_ENV = --best
       -DEP_FILES =  .deps/AIPlayer.P .deps/curses_client.P .deps/dopeos.P \
       -.deps/dopewars.P .deps/error.P .deps/gtk_client.P .deps/message.P \
       -.deps/network.P .deps/serverside.P .deps/tstring.P .deps/winmain.P
       +DEP_FILES =  .deps/AIPlayer.P .deps/admin.P .deps/curses_client.P \
       +.deps/dopeos.P .deps/dopewars.P .deps/error.P .deps/gtk_client.P \
       +.deps/message.P .deps/network.P .deps/serverside.P .deps/tstring.P \
       +.deps/winmain.P
        SOURCES = $(dopewars_SOURCES)
        OBJECTS = $(dopewars_OBJECTS)
        
 (DIR) diff --git a/src/admin.c b/src/admin.c
       t@@ -0,0 +1,104 @@
       +/* admin.c      Dopewars server administration                          */
       +/* Copyright (C)  1998-2001  Ben Webb                                   */
       +/*                Email: ben@bellatrix.pcl.ox.ac.uk                     */
       +/*                WWW: http://dopewars.sourceforge.net/                 */
       +
       +/* This program is free software; you can redistribute it and/or        */
       +/* modify it under the terms of the GNU General Public License          */
       +/* as published by the Free Software Foundation; either version 2       */
       +/* of the License, or (at your option) any later version.               */
       +
       +/* This program is distributed in the hope that it will be useful,      */
       +/* but WITHOUT ANY WARRANTY; without even the implied warranty of       */
       +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        */
       +/* GNU General Public License for more details.                         */
       +
       +/* You should have received a copy of the GNU General Public License    */
       +/* along with this program; if not, write to the Free Software          */
       +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston,               */
       +/*                   MA  02111-1307, USA.                               */
       +
       +#ifdef HAVE_CONFIG_H
       +#include <config.h>
       +#endif
       +
       +#ifndef CYGWIN
       +#include <sys/types.h>
       +#include <sys/socket.h>
       +#include <sys/un.h>
       +#include <errno.h>
       +#include <stdio.h>
       +#include <glib.h>
       +
       +#include "network.h"
       +
       +static int OpenSocket(void) {
       +  struct sockaddr_un addr;
       +  int sock;
       +
       +  g_print("Attempting to connect to local dopewars server via. Unix domain\n"
       +          "socket /tmp/.dopewars/socket...\n");
       +  sock = socket(PF_UNIX,SOCK_STREAM,0);
       +  if (sock==-1) { perror("socket"); exit(1); }
       +
       +  addr.sun_family = AF_UNIX;
       +  strncpy(addr.sun_path,"/tmp/.dopewars/socket",sizeof(addr.sun_path));
       +  addr.sun_path[sizeof(addr.sun_path)-1]='\0';
       +
       +  if (connect(sock,(struct sockaddr *)&addr,sizeof(struct sockaddr_un))==-1) {
       +    perror("connect"); exit(1);
       +  }
       +
       +  g_print("Connection established.\n\n");
       +
       +  return sock;
       +}
       +
       +void AdminServer(void) {
       +  int sock,topsock;
       +  NetworkBuffer *netbuf;
       +  fd_set readfds,writefds,errorfds;
       +  gchar *msg,inbuf[200];
       +  gboolean doneOK;
       +
       +  sock=OpenSocket();
       +  netbuf = g_new(NetworkBuffer,1);
       +  InitNetworkBuffer(netbuf,'\n','\r',NULL);
       +  BindNetworkBufferToSocket(netbuf,sock);
       +
       +  while(1) {
       +    FD_ZERO(&readfds);
       +    FD_ZERO(&writefds);
       +    FD_ZERO(&errorfds);
       +
       +    FD_SET(0,&readfds);
       +    topsock=1;
       +    SetSelectForNetworkBuffer(netbuf,&readfds,&writefds,&errorfds,&topsock);
       +
       +    if (select(topsock,&readfds,&writefds,&errorfds,NULL)==-1) {
       +      if (errno==EINTR) continue;
       +      else perror("select"); break;
       +    }
       +
       +    if (FD_ISSET(0,&readfds)) {
       +      if (fgets(inbuf,sizeof(inbuf),stdin)) {
       +        inbuf[sizeof(inbuf)-1]='\0';
       +        if (strlen(inbuf)>0) {
       +          if (inbuf[strlen(inbuf)-1]=='\n') inbuf[strlen(inbuf)-1]='\0';
       +          QueueMessageForSend(netbuf,inbuf);
       +        }
       +      } else break;
       +    }
       +
       +    if (RespondToSelect(netbuf,&readfds,&writefds,&errorfds,&doneOK)) {
       +      while((msg=GetWaitingMessage(netbuf))!=NULL) {
       +        g_print("%s\n",msg); g_free(msg);
       +      }
       +    }
       +    if (!doneOK) break;
       +  }
       +  ShutdownNetworkBuffer(netbuf);
       +  g_free(netbuf);
       +  g_print("Connection closed\n");
       +}
       +#endif
 (DIR) diff --git a/src/admin.h b/src/admin.h
       t@@ -0,0 +1,35 @@
       +/* admin.h      Header file for dopewars server administration          */
       +/* Copyright (C)  1998-2001  Ben Webb                                   */
       +/*                Email: ben@bellatrix.pcl.ox.ac.uk                     */
       +/*                WWW: http://dopewars.sourceforge.net/                 */
       +
       +/* This program is free software; you can redistribute it and/or        */
       +/* modify it under the terms of the GNU General Public License          */
       +/* as published by the Free Software Foundation; either version 2       */
       +/* of the License, or (at your option) any later version.               */
       +
       +/* This program is distributed in the hope that it will be useful,      */
       +/* but WITHOUT ANY WARRANTY; without even the implied warranty of       */
       +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        */
       +/* GNU General Public License for more details.                         */
       +
       +/* You should have received a copy of the GNU General Public License    */
       +/* along with this program; if not, write to the Free Software          */
       +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston,               */
       +/*                   MA  02111-1307, USA.                               */
       +
       +
       +#ifndef __ADMIN_H__
       +#define __ADMIN_H__
       +
       +#ifdef HAVE_CONFIG_H
       +#include <config.h>
       +#endif
       +
       +#ifndef CYGWIN
       +
       +void AdminServer(void);
       +
       +#endif /* CYGWIN */
       +
       +#endif
 (DIR) diff --git a/src/dopewars.c b/src/dopewars.c
       t@@ -41,6 +41,7 @@
        #include <signal.h>
        #include <glib.h>
        #include <stdarg.h>
       +#include "admin.h"
        #include "curses_client.h"
        #include "dopeos.h"
        #include "gtk_client.h"
       t@@ -69,7 +70,13 @@ FILE *logfp;
        unsigned Port=7902;
        gboolean Sanitized,ConfigVerbose,DrugValue;
        gchar *HiScoreFile=NULL,*ServerName=NULL,*Pager=NULL,*ConvertFile=NULL;
       -gboolean WantHelp,WantVersion,WantAntique,WantColour,WantNetwork,WantConvert;
       +gboolean WantHelp,WantVersion,WantAntique,WantColour,WantNetwork,
       +         WantConvert,WantAdmin;
       +
       +#ifdef CYGWIN
       +gboolean MinToSysTray=TRUE;
       +#endif
       +
        ClientType WantedClient;
        int NumLocation=0,NumGun=0,NumCop=0,NumDrug=0,NumSubway=0,
            NumPlaying=0,NumStoppedTo=0;
       t@@ -259,6 +266,11 @@ struct GLOBALS Globals[] = {
             "MetaServer.Proxy.Password",
             N_("Password for HTTP Basic proxy authentication"),
             NULL,NULL,0,"",NULL,NULL },
       +#endif /* NETWORKING */
       +#ifdef CYGWIN
       +   { NULL,&MinToSysTray,NULL,NULL,NULL,"MinToSysTray",
       +     N_("If TRUE, the server minimizes to the System Tray"),
       +     NULL,NULL,0,"",NULL,NULL },
        #endif
           { NULL,NULL,NULL,&Pager,NULL,"Pager",
             N_("Program used to display multi-page output"),NULL,NULL,0,"",NULL,NULL },
       t@@ -1433,7 +1445,8 @@ void ReadConfigFile(char *FileName) {
              while (!g_scanner_eof(scanner)) if (!ParseNextConfig(scanner,FALSE)) {
                 errors++;
                 g_scanner_error(scanner,
       -                         _("Unable to process configuration file line"));
       +                         _("Unable to process configuration file line %d"),
       +                         g_scanner_cur_line(scanner));
              }
              g_scanner_destroy(scanner);
              fclose(fp);
       t@@ -1448,8 +1461,8 @@ _("Errors were encountered during the reading of the configuration file.\n"
        "file \"dopewars-log.txt\" for further details."));
        #else
                 g_warning(
       -_("Errors were encountered during the reading of the configuration file.\n"
       -"As a result, some settings may not work as expected. Please see the\n"
       +_("Errors were encountered during the reading of the configuration\n"
       +"file. As a result, some settings may not work as expected. Please see the\n"
        "messages on standard output for further details."));
        #endif
              }
       t@@ -1796,7 +1809,7 @@ void SetupParameters() {
           FirstClient=FirstServer=NULL;
           Noone.Name=g_strdup("Noone");
           WantColour=WantNetwork=TRUE;
       -   WantHelp=WantConvert=WantVersion=WantAntique=FALSE;
       +   WantHelp=WantConvert=WantVersion=WantAntique=WantAdmin=FALSE;
           WantedClient=CLIENT_AUTO;
           Server=AIPlayer=Client=Network=FALSE;
        
       t@@ -1891,16 +1904,16 @@ Drug dealing game based on \"Drug Wars\" by John E. Dell\n\
                                    default %s/dopewars.sco is used)\n\
          -o, --hostname=ADDR     specify a hostname where the server for multiplayer\n\
                                    dopewars can be found\n\
       -  -s, --public-server     run in server mode (note: for a \"non-interactive\"\n\
       -                            server, simply run as\n\
       -                            dopewars -s < /dev/null >> logfile & )\n\
       +  -s, --public-server     run in server mode (note: see the -A option for\n\
       +                            configuring a server once it\'s running)\n\
          -S, --private-server    run a \"private\" server (do not notify the metaserver)\n\
          -p, --port=PORT         specify the network port to use (default: 7902)\n\
       -  -g, --config-file=FILE  specify the pathname of a dopewars configuration file.\n\
       -                            This file is read immediately when the -g option\n\
       +  -g, --config-file=FILE  specify the pathname of a dopewars configuration file;\n\
       +                            this file is read immediately when the -g option\n\
                                    is encountered\n\
          -r, --pidfile=FILE      maintain pid file \"FILE\" while running the server\n\
          -l, --logfile=FILE      write log information to \"FILE\"\n\
       +  -A, --admin             connect to a locally-running server for administration\n\
          -c, --ai-player         create and run a computer player\n\
          -w, --windowed-client   force the use of a graphical (windowed)\n\
                                    client (GTK+ or Win32)\n\
       t@@ -1926,11 +1939,11 @@ Drug dealing game based on \"Drug Wars\" by John E. Dell\n\
                      (by default %s/dopewars.sco is used)\n\
          -o addr  specify a hostname where the server for multiplayer dopewars\n\
                      can be found\n\
       -  -s       run in server mode (note: for a \"non-interactive\" server, simply\n\
       -              run as dopewars -s < /dev/null >> logfile & )\n\
       +  -s       run in server mode (note: see the -A option for configuring a\n\
       +              server once it\'s running)\n\
          -S       run a \"private\" server (i.e. do not notify the metaserver)\n\
          -p port  specify the network port to use (default: 7902)\n\
       -  -g file  specify the pathname of a dopewars configuration file. This file\n\
       +  -g file  specify the pathname of a dopewars configuration file; this file\n\
                      is read immediately when the -g option is encountered\n\
          -r file  maintain pid file \"file\" while running the server\n\
          -l file  write log information to \"file\"\n\
       t@@ -1939,6 +1952,7 @@ Drug dealing game based on \"Drug Wars\" by John E. Dell\n\
          -t       force the use of a text-mode client (curses)\n\
                      (by default, a windowed client is used when possible)\n\
          -C file  convert an \"old format\" score file to the new format\n\
       +  -A       connect to a locally-running server for administration\n\
          -h       display this help information\n\
          -v       output version information and exit\n\n\
        dopewars is Copyright (C) Ben Webb 1998-2001, and released under the GNU GPL\n\
       t@@ -1948,7 +1962,7 @@ Report bugs to the author at ben@bellatrix.pcl.ox.ac.uk\n"),DATADIR);
        
        void HandleCmdLine(int argc,char *argv[]) {
           int c;
       -   static const gchar *options = "anbchvf:o:sSp:g:r:wtC:l:N";
       +   static const gchar *options = "anbchvf:o:sSp:g:r:wtC:l:NA";
        #ifdef HAVE_GETOPT_LONG
           static const struct option long_options[] = {
              { "no-color", no_argument, NULL, 'b' },
       t@@ -1967,6 +1981,7 @@ void HandleCmdLine(int argc,char *argv[]) {
              { "text-client", no_argument, NULL, 't' },
              { "convert", required_argument, NULL, 'C' },
              { "logfile", required_argument, NULL, 'l' },
       +      { "admin", no_argument, NULL, 'A' },
              { "help", no_argument, NULL, 'h' },
              { "version", no_argument, NULL, 'v' },
              { 0, 0, 0, 0 }
       t@@ -1999,6 +2014,7 @@ void HandleCmdLine(int argc,char *argv[]) {
                 case 'w': WantedClient=CLIENT_WINDOW; break;
                 case 't': WantedClient=CLIENT_CURSES; break;
                 case 'C': AssignName(&ConvertFile,optarg); WantConvert=TRUE; break;
       +         case 'A': WantAdmin=TRUE; break;
              }
           } while (c!=-1);
        }
       t@@ -2008,7 +2024,7 @@ int GeneralStartup(int argc,char *argv[]) {
        /* score init.) - Returns 0 if OK, -1 if something failed.           */
           SetupParameters();
           HandleCmdLine(argc,argv);
       -   if (!WantVersion && !WantHelp && !AIPlayer && !WantConvert) {
       +   if (!WantVersion && !WantHelp && !AIPlayer && !WantConvert && !WantAdmin) {
              return InitHighScoreFile();
           }
           return 0;
       t@@ -2063,6 +2079,8 @@ int main(int argc,char *argv[]) {
           if (GeneralStartup(argc,argv)==0) {
              if (WantVersion || WantHelp) {
                 HandleHelpTexts();
       +      } else if (WantAdmin) {
       +         AdminServer();
              } else if (WantConvert) {
                 ConvertHighScoreFile();
              } else {
 (DIR) diff --git a/src/dopewars.h b/src/dopewars.h
       t@@ -155,7 +155,10 @@ extern gboolean Sanitized,ConfigVerbose,DrugValue;
        extern int NumLocation,NumGun,NumCop,NumDrug,NumSubway,NumPlaying,NumStoppedTo;
        extern gchar *HiScoreFile,*ServerName,*Pager,*ConvertFile;
        extern gboolean WantHelp,WantVersion,WantAntique,WantColour,
       -                WantNetwork,WantConvert;
       +                WantNetwork,WantConvert,WantAdmin;
       +#ifdef CYGWIN
       +extern gboolean MinToSysTray;
       +#endif
        extern ClientType WantedClient;
        extern int LoanSharkLoc,BankLoc,GunShopLoc,RoughPubLoc;
        extern int DrugSortMethod,FightTimeout,IdleTimeout,ConnectTimeout;
       t@@ -393,4 +396,5 @@ void dopelog(int loglevel,const gchar *format,...);
        GLogLevelFlags LogMask(void);
        GString *GetLogString(GLogLevelFlags log_level,const gchar *message);
        void RestoreConfig(void);
       +void ScannerErrorHandler(GScanner *scanner,gchar *msg,gint error);
        #endif
 (DIR) diff --git a/src/serverside.c b/src/serverside.c
       t@@ -598,10 +598,26 @@ void PrintHelpTo(FILE *fp) {
           g_string_free(VarName,TRUE);
        }
        
       +static NetworkBuffer *reply_netbuf;
       +static void ServerReply(const gchar *msg) {
       +  int msglen;
       +  gchar *msgcp;
       +
       +  if (reply_netbuf) {
       +    msglen=strlen(msg);
       +    while (msglen>0 && msg[msglen-1]=='\n') msglen--;
       +    if (msglen>0) {
       +      msgcp = g_strndup(msg,msglen);
       +      QueueMessageForSend(reply_netbuf,msgcp);
       +      g_free(msgcp);
       +    }
       +  } else g_print(msg);
       +}
       +
        void ServerHelp(void) {
        /* Displays a simple help screen listing the server commands and options */
           int i;
       -#if CYGWIN || GUI_SERVER
       +//#if CYGWIN || GUI_SERVER
           int Lines;
           GString *VarName;
           VarName=g_string_new("");
       t@@ -616,14 +632,14 @@ void ServerHelp(void) {
              }
              g_print("%-26s\t%s\n",VarName->str,_(Globals[i].Help));
              Lines++;
       -#ifndef GUI_SERVER
       +/*#ifndef GUI_SERVER
              if (Lines%24==0) {
                 g_print(_("--More--")); bgetch(); g_print("\n");
              }
       -#endif
       +endif*/
           }
           g_string_free(VarName,TRUE);
       -#else
       +/*#else
           FILE *fp;
           fp=popen(Pager,"w");
           if (fp) {
       t@@ -634,7 +650,7 @@ void ServerHelp(void) {
                 PrintHelpTo(stdout);
              }
           }
       -#endif
       +endif*/
        }
        
        #if NETWORKING
       t@@ -706,6 +722,7 @@ static void StartServer(void) {
        #endif
        
           Scanner=g_scanner_new(&ScannerConfig);
       +   Scanner->msg_handler=ScannerErrorHandler;
           Scanner->input_name="(stdin)";
           CreatePidFile();
        
       t@@ -810,9 +827,23 @@ gboolean IsServerShutdown(void) {
           return (WantQuit && !FirstServer && !MetaConn);
        }
        
       -void HandleServerCommand(char *string) {
       +static GPrintFunc StartServerReply(NetworkBuffer *netbuf) {
       +  reply_netbuf = netbuf;
       +  if (netbuf) return g_set_print_handler(ServerReply);
       +  else return NULL;
       +}
       +
       +static void FinishServerReply(GPrintFunc oldprint) {
       +  if (oldprint) g_set_print_handler(oldprint);
       +}
       +
       +static void HandleServerCommand(char *string,NetworkBuffer *netbuf) {
           GSList *list;
           Player *tmp;
       +   GPrintFunc oldprint;
       +
       +   oldprint = StartServerReply(netbuf);
       +
           g_scanner_input_text(Scanner,string,strlen(string));
           if (!ParseNextConfig(Scanner,TRUE)) {
              if (g_strcasecmp(string,"help")==0 || g_strcasecmp(string,"h")==0 ||
       t@@ -827,27 +858,30 @@ void HandleServerCommand(char *string) {
                    g_print(_("Users currently logged on:-\n"));
                    for (list=FirstServer;list;list=g_slist_next(list)) {
                       tmp=(Player *)list->data;
       -               if (!IsCop(tmp)) g_print("%s\n",GetPlayerName(tmp));
       +               if (!IsCop(tmp)) {
       +                 g_print("%s\n",GetPlayerName(tmp));
       +               }
                    }
                 } else g_print(_("No users currently logged on!\n"));
              } else if (g_strncasecmp(string,"push ",5)==0) {
                 tmp=GetPlayerByName(string+5,FirstServer);
                 if (tmp) {
       -            dopelog(0,_("Pushing %s"),GetPlayerName(tmp));
       +            g_print(_("Pushing %s\n"),GetPlayerName(tmp));
                    SendServerMessage(NULL,C_NONE,C_PUSH,tmp,NULL);
       -         } else g_warning(_("No such user!"));
       +         } else g_print(_("No such user!\n"));
              } else if (g_strncasecmp(string,"kill ",5)==0) {
                 tmp=GetPlayerByName(string+5,FirstServer);
                 if (tmp) {
       -            dopelog(0,_("%s killed"),GetPlayerName(tmp));
       +            g_print(_("%s killed\n"),GetPlayerName(tmp));
                    BroadcastToClients(C_NONE,C_KILL,GetPlayerName(tmp),tmp,
                                       (Player *)FirstServer->data);
                    FirstServer=RemovePlayer(tmp,FirstServer);
       -         } else g_warning(_("No such user!"));
       +         } else g_print(_("No such user!\n"));
              } else {
       -         g_warning(_("Unknown command - try \"help\" for help..."));
       +         g_print(_("Unknown command - try \"help\" for help...\n"));
              }
           }
       +   FinishServerReply(oldprint);
        }
        
        Player *HandleNewConnection(void) {
       t@@ -890,27 +924,15 @@ void RemovePlayerFromServer(Player *Play) {
        }
        
        #ifndef CYGWIN
       -static gchar sockdir[] = "/tmp/.dopewars/";
       -
       -static gchar *GetLocalSocket(void) {
       -  return g_strdup_printf("%ssocket-%u",sockdir,Port);
       -}
       -
        static void CloseLocalSocket(int localsock) {
       -  gchar *sockname;
       -
          if (localsock>=0) close(localsock);
       -
       -  sockname=GetLocalSocket();
       -  unlink(sockname);
       -  rmdir(sockdir);
       -  g_free(sockname);
       +  unlink("/tmp/.dopewars/socket");
       +  rmdir("/tmp/.dopewars");
        }
        
        static int SetupLocalSocket(void) {
          int sock;
          struct sockaddr_un addr;
       -  gchar *sockname;
        
          CloseLocalSocket(-1);
        
       t@@ -919,20 +941,17 @@ static int SetupLocalSocket(void) {
        
          SetBlocking(sock,FALSE);
        
       -  sockname=GetLocalSocket();
       -  mkdir(sockdir,S_IRUSR|S_IWUSR|S_IXUSR);
       +  mkdir("/tmp/.dopewars",S_IRUSR|S_IWUSR|S_IXUSR);
        
          addr.sun_family = AF_UNIX;
       -  strncpy(addr.sun_path,sockname,sizeof(addr.sun_path));
       +  strncpy(addr.sun_path,"/tmp/.dopewars/socket",sizeof(addr.sun_path));
          addr.sun_path[sizeof(addr.sun_path)-1]='\0';
        
          bind(sock,(struct sockaddr *)&addr,sizeof(struct sockaddr_un));
        
       -  chmod(sockname,S_IRUSR|S_IWUSR);
       +  chmod("/tmp/.dopewars/socket",S_IRUSR|S_IWUSR);
        
          listen(sock,10);
       -
       -  g_free(sockname);
          
          return sock;
        }
       t@@ -945,11 +964,12 @@ void ServerLoop() {
           GSList *list,*nextlist,*localconn=NULL;
           fd_set readfs,writefs,errorfs;
           int topsock;
       +   GPrintFunc oldprint;
        // gboolean InputClosed=FALSE;
           struct timeval timeout;
           int MinTimeout;
           GString *LineBuf;
       -   gboolean EndOfLine,DoneOK;
       +   gboolean /*EndOfLine,*/DoneOK;
           gchar *buf;
        #ifndef CYGWIN
           int localsock;
       t@@ -1039,9 +1059,12 @@ void ServerLoop() {
                netbuf=g_new(NetworkBuffer,1);
                InitNetworkBuffer(netbuf,'\n','\r',NULL);
                BindNetworkBufferToSocket(netbuf,newlocal);
       -        SetBlocking(newlocal,FALSE);
                localconn = g_slist_append(localconn,netbuf);
       -g_print("New connection on Unix socket\n");
       +        oldprint = StartServerReply(netbuf);
       +        g_print(_("dopewars server version %s ready for admin commands; "
       +                  "try \"help\" for help"),VERSION);
       +        FinishServerReply(oldprint);
       +        dopelog(1,_("New admin connection"));
              }
              list=localconn;
              while (list) {
       t@@ -1051,13 +1074,13 @@ g_print("New connection on Unix socket\n");
                if (netbuf) {
                  if (RespondToSelect(netbuf,&readfs,&writefs,&errorfs,&DoneOK)) {
                    while((buf=GetWaitingMessage(netbuf))!=NULL) {
       -              g_print("Unix message received: %s\n",buf);
       -              HandleServerCommand(buf);
       +              dopelog(2,_("Admin command: %s"),buf);
       +              HandleServerCommand(buf,netbuf);
                      g_free(buf);
                    }
                  }
                  if (!DoneOK) {
       -g_print("Unix socket closed\n");
       +            dopelog(1,_("Admin connection closed"));
                    localconn = g_slist_remove(localconn,netbuf);
                    ShutdownNetworkBuffer(netbuf);
                    g_free(netbuf);
       t@@ -1198,7 +1221,7 @@ static void GuiDoCommand(GtkWidget *widget,gpointer data) {
           gchar *text;
           text=gtk_editable_get_chars(GTK_EDITABLE(widget),0,-1);
           gtk_editable_delete_text(GTK_EDITABLE(widget),0,-1);
       -   HandleServerCommand(text);
       +   HandleServerCommand(text,NULL);
           g_free(text);
           if (IsServerShutdown()) GuiQuitServer();
        }
       t@@ -1289,7 +1312,7 @@ static gint GuiRequestDelete(GtkWidget *widget,GdkEvent *event,gpointer data) {
              GuiQuitServer();
           } else {
              TriedPoliteShutdown=TRUE;
       -      HandleServerCommand("quit");
       +      HandleServerCommand("quit",NULL);
              if (IsServerShutdown()) GuiQuitServer();
           }
           return TRUE; /* Never allow automatic deletion - we handle it manually */
       t@@ -1297,6 +1320,7 @@ static gint GuiRequestDelete(GtkWidget *widget,GdkEvent *event,gpointer data) {
        
        #ifdef CYGWIN
        static HWND mainhwnd=NULL;
       +static BOOL systray=FALSE;
        
        static BOOL RegisterStatus(DWORD state) {
          SERVICE_STATUS status;
       t@@ -1367,7 +1391,7 @@ static LRESULT CALLBACK GuiServerWndProc(HWND hwnd,UINT msg,WPARAM wparam,
              if ((UINT)lparam==WM_LBUTTONDOWN) ShowWindow(mainhwnd,SW_SHOW);
              break;
            case WM_SYSCOMMAND:
       -      if (wparam==SC_MINIMIZE) {
       +      if (wparam==SC_MINIMIZE && systray) {
                ShowWindow(mainhwnd,SW_HIDE); return TRUE;
              }
              break;
       t@@ -1384,14 +1408,14 @@ static void SetupTaskBarIcon(GtkWidget *widget) {
          if (!widget && !mainhwnd) return;
        
          nid.hWnd = mainhwnd;
       -  if (widget) {
       +  if (widget && MinToSysTray) {
            nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
            nid.uCallbackMessage = MYWM_TASKBAR;
            nid.hIcon = mainIcon;
            strcpy(nid.szTip,"dopewars server - running");
       -    Shell_NotifyIcon(NIM_ADD,&nid);
       -    SetCustomWndProc(GuiServerWndProc);
       +    systray=Shell_NotifyIcon(NIM_ADD,&nid);
          } else {
       +    systray=FALSE;
            Shell_NotifyIcon(NIM_DELETE,&nid);
          }
        }
       t@@ -1445,6 +1469,7 @@ void GuiServerLoop(gboolean is_service) {
        #ifdef CYGWIN
           mainhwnd=window->hWnd;
           SetupTaskBarIcon(window);
       +   SetCustomWndProc(GuiServerWndProc);
           if (is_service && !RegisterStatus(SERVICE_RUNNING)) {
             dopelog(0,_("Failed to set NT Service status"));
             return;
 (DIR) diff --git a/src/serverside.h b/src/serverside.h
       t@@ -35,7 +35,6 @@ void CleanUpServer(void);
        void BreakHandle(int sig);
        void ClientLeftServer(Player *Play);
        void StopServer(void);
       -void HandleServerCommand(char *string);
        Player *HandleNewConnection(void);
        void ServerLoop(void);
        void HandleServerPlayer(Player *Play);
 (DIR) diff --git a/win32/filelist b/win32/filelist
       t@@ -41,6 +41,11 @@ uninstall.exe
        dopewars-log.txt
        dopewars.sco
        
       +[keepfiles]
       +dopewars-config.txt
       +dopewars-log.txt
       +dopewars.sco
       +
        [startmenudir]
        dopewars-1.5.3
        
 (DIR) diff --git a/win32/makeinstall.c b/win32/makeinstall.c
       t@@ -51,23 +51,23 @@ char *read_line(HANDLE hin) {
        InstData *ReadInstallData() {
          HANDLE fin;
          char *line,*line2,*line3,*line4;
       -  InstFiles *lastinst=NULL,*lastextra=NULL;
       +  InstFiles *lastinst=NULL,*lastextra=NULL,*lastkeep=NULL;
          InstLink *lastmenu=NULL,*lastdesktop=NULL;
          InstData *idata;
          int i;
          enum {
       -    S_PRODUCT=0,S_INSTDIR,S_INSTALL,S_EXTRA,S_STARTMENUDIR,
       +    S_PRODUCT=0,S_INSTDIR,S_INSTALL,S_EXTRA,S_KEEP,S_STARTMENUDIR,
            S_STARTMENU,S_DESKTOP,S_NTSERVICE,
            S_NONE
          } section=S_NONE;
          char *titles[S_NONE] = {
       -    "[product]","[instdir]", "[install]","[extrafiles]","[startmenudir]",
       -    "[startmenu]","[desktop]","[NT Service]"
       +    "[product]","[instdir]", "[install]","[extrafiles]","[keepfiles]",
       +    "[startmenudir]","[startmenu]","[desktop]","[NT Service]"
          };
        
          idata = bmalloc(sizeof(InstData));
          idata->installdir = idata->startmenudir = NULL;
       -  idata->instfiles = idata->extrafiles = NULL;
       +  idata->instfiles = idata->extrafiles = idata->keepfiles = NULL;
          idata->startmenu = idata->desktop = NULL;
          idata->service = NULL;
        
       t@@ -104,6 +104,9 @@ printf("start menu dir = %s\n",line);
                case S_EXTRA:
                  AddInstFiles(line,0,&lastextra,&idata->extrafiles);
                  break;
       +        case S_KEEP:
       +          AddInstFiles(line,0,&lastkeep,&idata->keepfiles);
       +          break;
                case S_STARTMENU:
                  line2=read_line(fin); line3=read_line(fin);
        printf("start menu entry = %s/%s/%s\n",line,line2,line3);
       t@@ -252,6 +255,7 @@ int main() {
          WriteLinkList(fout,idata->desktop);
        
          WriteServiceDetails(fout,idata->service);
       +  WriteFileList(fout,idata->keepfiles);
        
          CloseHandle(fout);
          bfree(inbuf);
 (DIR) diff --git a/win32/setup.c b/win32/setup.c
       t@@ -39,6 +39,7 @@ InstData *idata;
        HWND mainDlg[DL_NUM];
        DialogType CurrentDialog;
        HINSTANCE hInst=NULL;
       +char *oldversion=NULL;
        
        DWORD WINAPI DoInstall(LPVOID lpParam);
        static void GetWinText(char **text,HWND hWnd);
       t@@ -251,7 +252,7 @@ LPVOID GetResource(LPCTSTR resname,LPCTSTR restype) {
        }
        
        InstData *ReadInstData() {
       -  InstFiles *lastinst=NULL,*lastextra=NULL;
       +  InstFiles *lastinst=NULL,*lastextra=NULL,*lastkeep=NULL;
          InstLink *lastmenu=NULL,*lastdesktop=NULL;
          char *instdata,*pt,*filename,*line2,*line3,*line4;
          DWORD filesize;
       t@@ -319,6 +320,15 @@ InstData *ReadInstData() {
            line4=pt; pt += strlen(pt)+1;
            AddServiceDetails(filename,line2,line3,line4,&idata->service);
          }
       +  while (1) {
       +    filename=pt;
       +    pt += strlen(pt)+1;
       +    if (filename[0]) {
       +      filesize=atol(pt);
       +      pt += strlen(pt)+1;
       +      AddInstFiles(filename,filesize,&lastkeep,&idata->keepfiles);
       +    } else break;
       +  }
        
          return idata;
        }
       t@@ -359,12 +369,16 @@ char *GetFirstFile(InstFiles *filelist,DWORD totalsize) {
          return outbuf;
        }
        
       -BOOL OpenNextOutput(HANDLE *fout,InstFiles *filelist,InstFiles **listpt,
       -                    DWORD *fileleft,HANDLE logf) {
       +BOOL OpenNextOutput(HANDLE *fout,InstFiles *filelist,InstFiles *keepfiles,
       +                    InstFiles **listpt,DWORD *fileleft,HANDLE logf,
       +                    BOOL *skipfile) {
          char *filename,*sep;
          bstr *str;
       +  InstFiles *keeppt;
          DWORD bytes_written;
        
       +  *skipfile=FALSE;
       +
          if (*fout) CloseHandle(*fout);
          *fout = INVALID_HANDLE_VALUE;
        
       t@@ -392,20 +406,29 @@ BOOL OpenNextOutput(HANDLE *fout,InstFiles *filelist,InstFiles **listpt,
              CreateWholeDirectory(filename);
              *sep = '\\';
            }
       -    *fout = CreateFile(filename,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
       -    *fileleft = (*listpt)->filesize;
       -    bstr_assign(str,"Installing file: ");
       -    bstr_append(str,filename);
       -    bstr_append(str," (size ");
       -    bstr_append_long(str,(*listpt)->filesize);
       -    bstr_append(str,")");
       -    SendDlgItemMessage(mainDlg[DL_DOINSTALL],ST_FILELIST,
       -                       WM_SETTEXT,0,(LPARAM)str->text);
       -    if (*fout==INVALID_HANDLE_VALUE) {
       -      bstr_assign(str,"Cannot create file ");
       +    keeppt = keepfiles;
       +    while (keeppt && strcmp(keeppt->filename,filename)!=0) keeppt=keeppt->next;
       +
       +/* If the file is already installed (filesize!=0), then skip it */
       +    if (keeppt && keeppt->filesize!=0) {
       +      *fout = INVALID_HANDLE_VALUE+1; /* Make sure the handle is valid */
       +      *skipfile = TRUE;
       +    } else {
       +      *fout = CreateFile(filename,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
       +      bstr_assign(str,"Installing file: ");
              bstr_append(str,filename);
       -      DisplayError(str->text,TRUE,FALSE);
       +      bstr_append(str," (size ");
       +      bstr_append_long(str,(*listpt)->filesize);
       +      bstr_append(str,")");
       +      SendDlgItemMessage(mainDlg[DL_DOINSTALL],ST_FILELIST,
       +                         WM_SETTEXT,0,(LPARAM)str->text);
       +      if (*fout==INVALID_HANDLE_VALUE) {
       +        bstr_assign(str,"Cannot create file ");
       +        bstr_append(str,filename);
       +        DisplayError(str->text,TRUE,FALSE);
       +      }
            }
       +    *fileleft = (*listpt)->filesize;
          }
        
          bstr_free(str,TRUE);
       t@@ -569,13 +592,123 @@ void SetupUninstall() {
          bfree(startmenu);
        }
        
       +void StartRemoveOldVersion(char *oldversion,InstData *idata,
       +                           InstData **oldidata,HWND hwnd) {
       +  InstData *old;
       +  bstr *str;
       +  char *oldidir,*startmenu,*desktop;
       +  HANDLE fin;
       +
       +  *oldidata=NULL;
       +
       +  if (!oldversion) return;
       +
       +  oldidir = GetInstallDir(oldversion);
       +
       +  if (!SetCurrentDirectory(oldidir)) {
       +    str=bstr_new();
       +    bstr_assign(str,"Could not access old version's install directory ");
       +    bstr_append(str,oldidir);
       +    DisplayError(str->text,TRUE,TRUE);
       +  }
       +
       +  fin = CreateFile("install.log",GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
       +
       +  if (fin) {
       +    old = ReadOldInstData(fin,oldversion,oldidir);
       +    CloseHandle(fin);
       +    DeleteFile("install.log");
       +
       +    RemoveService(old->service);
       +    DeleteFileList(old->instfiles,hwnd,idata->keepfiles);
       +    DeleteFileList(old->extrafiles,hwnd,idata->keepfiles);
       +
       +    startmenu = GetStartMenuDir(old);
       +    desktop = GetDesktopDir();
       +    DeleteLinkList(startmenu,old->startmenu,hwnd);
       +    DeleteLinkList(desktop,old->desktop,hwnd);
       +
       +    RemoveUninstall(startmenu,oldversion,FALSE);
       +
       +    bfree(startmenu); bfree(desktop);
       +    *oldidata = old;
       +  }
       +}
       +
       +void FinishRemoveOldVersion(char *oldversion,InstData *idata,
       +                            InstData *oldidata) {
       +  InstFiles *keeppt;
       +  bstr *str;
       +  char *desktop,*startmenu;
       +  if (!oldidata) return;
       +
       +  desktop = GetDesktopDir();
       +
       +  str = bstr_new();
       +/* If we're installing into a different directory, move config. files etc.
       +   from the old directory to the new one */
       +  if (strcmp(oldidata->installdir,idata->installdir)!=0 &&
       +      SetCurrentDirectory(oldidata->installdir)) {
       +    for (keeppt = idata->keepfiles;keeppt;keeppt=keeppt->next) {
       +      if (keeppt->filesize!=0) {
       +        bstr_assign(str,idata->installdir);
       +        bstr_appendpath(str,keeppt->filename);
       +        if (CopyFile(keeppt->filename,str->text,FALSE)) {
       +          DeleteFile(keeppt->filename);
       +        }
       +      }
       +    }
       +    SetCurrentDirectory(desktop); /* Make sure we're not in the install dir */
       +    if (!RemoveWholeDirectory(oldidata->installdir)) {
       +      bstr_assign(str,"Could not remove old install directory:\n");
       +      bstr_append(str,oldidata->installdir);
       +      bstr_append(str,"\nYou may wish to manually remove it later.");
       +      DisplayError(str->text,FALSE,FALSE);
       +    }
       +  }
       +
       +  if (strcmp(idata->startmenudir,oldidata->startmenudir)!=0) {
       +    SetCurrentDirectory(desktop); /* Make sure we're not in the menu dir */
       +    startmenu = GetStartMenuDir(oldidata);
       +    if (!RemoveWholeDirectory(startmenu)) {
       +      bstr_assign(str,"Could not remove old Start Menu directory:\n");
       +      bstr_append(str,startmenu);
       +      bstr_append(str,"\nYou may wish to manually remove it later.");
       +      DisplayError(str->text,FALSE,FALSE);
       +    }
       +    bfree(startmenu);
       +  }
       +
       +/* Remove the old registry key */
       +  bstr_assign(str,UninstallKey);
       +  bstr_appendpath(str,oldversion);
       +  RegDeleteKey(HKEY_LOCAL_MACHINE,str->text);
       +
       +  bfree(desktop);
       +  bstr_free(str,TRUE);
       +  
       +  FreeInstData(oldidata,TRUE);
       +  oldversion=NULL; /* This is freed by FreeInstData */
       +}
       +
        DWORD WINAPI DoInstall(LPVOID lpParam) {
       -  HANDLE fout,logf;
       +  HANDLE fout,logf,fin;
          DWORD bytes_written,fileleft;
       +  BOOL skipfile;
          char *inbuf,*outbuf;
          int status,count;
          z_stream z;
          InstFiles *listpt;
       +  InstData *oldidata;
       +
       +/* Steal the filesize attribute to mark that these files are not
       +   already installed */
       +  for (listpt=idata->keepfiles;listpt;listpt=listpt->next) {
       +    listpt->filesize=0;
       +  }
       +
       +  StartRemoveOldVersion(oldversion,idata,&oldidata,
       +                        GetDlgItem(mainDlg[DL_DOINSTALL],ST_FILELIST));
        
          inbuf = GetResource(MAKEINTRESOURCE(1),"INSTFILE");
          if (!inbuf) return 0;
       t@@ -586,6 +719,16 @@ DWORD WINAPI DoInstall(LPVOID lpParam) {
            DisplayError("Cannot access install directory",TRUE,TRUE);
          }
        
       +/* Check for already-installed files */
       +  for (listpt=idata->keepfiles;listpt;listpt=listpt->next) {
       +    fin = CreateFile(listpt->filename,GENERIC_READ,0,NULL,OPEN_EXISTING,
       +                     0,NULL);
       +    if (fin != INVALID_HANDLE_VALUE) {
       +      CloseHandle(fin);
       +      listpt->filesize=1;
       +    }
       +  }
       +
          logf = CreateFile("install.log",GENERIC_WRITE,0,NULL,
                            CREATE_ALWAYS,0,NULL);
        
       t@@ -596,7 +739,8 @@ DWORD WINAPI DoInstall(LPVOID lpParam) {
        
          fout = INVALID_HANDLE_VALUE;
          listpt=NULL;
       -  OpenNextOutput(&fout,idata->instfiles,&listpt,&fileleft,logf);
       +  OpenNextOutput(&fout,idata->instfiles,idata->keepfiles,
       +                 &listpt,&fileleft,logf,&skipfile);
        
          outbuf = bmalloc(BUFFER_SIZE);
        
       t@@ -616,17 +760,18 @@ DWORD WINAPI DoInstall(LPVOID lpParam) {
              count = BUFFER_SIZE - z.avail_out;
              z.next_out = outbuf;
              while (count >= fileleft) {
       -        if (fileleft &&
       +        if (fileleft && !skipfile &&
                    !WriteFile(fout,z.next_out,fileleft,&bytes_written,NULL)) {
                  printf("Write error\n");
                }
                count-=fileleft;
                z.next_out+=fileleft;
       -        if (!OpenNextOutput(&fout,idata->instfiles,&listpt,
       -                            &fileleft,logf)) break;
       +        if (!OpenNextOutput(&fout,idata->instfiles,idata->keepfiles,
       +                            &listpt,&fileleft,logf,&skipfile)) break;
              }
              if (fout==INVALID_HANDLE_VALUE) break;
       -      if (count && !WriteFile(fout,z.next_out,count,&bytes_written,NULL)) {
       +      if (count && !skipfile &&
       +          !WriteFile(fout,z.next_out,count,&bytes_written,NULL)) {
                printf("Write error\n");
              }
              fileleft-=count;
       t@@ -637,7 +782,7 @@ DWORD WINAPI DoInstall(LPVOID lpParam) {
          }
        
          inflateEnd(&z);
       -  CloseHandle(fout);
       +  if (!skipfile) CloseHandle(fout);
        
          outbuf[0]='\0';
          if (!WriteFile(logf,outbuf,1,&bytes_written,NULL)) {
       t@@ -647,6 +792,8 @@ DWORD WINAPI DoInstall(LPVOID lpParam) {
        
          WriteFileList(logf,idata->extrafiles);
        
       +  FinishRemoveOldVersion(oldversion,idata,oldidata);
       +
          InstallService(idata);
        
          CoInitialize(NULL);
       t@@ -699,7 +846,11 @@ void FillFolderList(void) {
        }
        
        BOOL CheckExistingInstall(InstData *idata) {
       -  bstr *str,*subkey;
       +  bstr *str;
       +  char *sep,*prodname,*prodversion;
       +  char *subkey;
       +  int sublen;
       +  DWORD sublencp;
          HKEY key;
          DWORD ind;
          FILETIME ftime;
       t@@ -708,6 +859,9 @@ BOOL CheckExistingInstall(InstData *idata) {
          str=bstr_new();
          bstr_assign(str,UninstallKey);
          bstr_appendpath(str,idata->product);
       +
       +/* Split product into name and version */
       +  sep = strrchr(idata->product,'-');
          
          if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,str->text,0,KEY_READ,&key)
              ==ERROR_SUCCESS) {
       t@@ -715,15 +869,45 @@ BOOL CheckExistingInstall(InstData *idata) {
            if (MessageBox(NULL,"This program appears to already be installed.\n"
                           "Are you sure you want to go ahead and install it anyway?",
                           idata->product,MB_YESNO)==IDNO) retval=FALSE;
       -  } else {
       -// TODO: Check for old versions to upgrade
       +  } else if (sep) {
       +    *sep='\0';
       +    prodversion = sep+1;
       +    prodname = bstrdup(idata->product);
       +    *sep='-';
       +    sublencp=sublen=strlen(idata->product)+30;
       +    subkey = bmalloc(sublen);
            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,UninstallKey,0,KEY_READ,&key)
                ==ERROR_SUCCESS) {
       -/*    for (ind=0;RegEnumKeyEx(key,ind,subkey,subkey->len,
       -                              NULL,NULL,NULL,&ftime)==ERROR_SUCCESS;ind++) {
       -      }*/
       +      ind=0;
       +      while (RegEnumKeyEx(key,ind++,subkey,&sublencp,
       +                          NULL,NULL,NULL,&ftime)==ERROR_SUCCESS) {
       +        sublencp=sublen;
       +        sep=strrchr(subkey,'-');
       +        if (sep) {
       +          *sep='\0';
       +          if (strcmp(subkey,prodname)==0) {
       +            bstr_assign(str,"You are trying to install ");
       +            bstr_append(str,idata->product);
       +            bstr_append(str,".\nHowever, version ");
       +            bstr_append(str,sep+1);
       +            bstr_append(str," appears to already be installed.\n"
       +                        "Do you want to replace the existing version with "
       +                        "this one?\n(If you answer \"No\", and continue, "
       +                        "both versions will be installed.)");
       +            if (MessageBox(NULL,str->text,"Existing version",MB_YESNO)==IDYES) {
       +              *sep='-';
       +              oldversion=bstrdup(subkey);
       +            }
       +            break;
       +          }
       +        }
       +      }
       +      RegCloseKey(key);
            }
       +    bfree(prodname);
       +    bfree(subkey);
          }
       +  bstr_free(str,TRUE);
          return retval;
        }
        
 (DIR) diff --git a/win32/uninstall.c b/win32/uninstall.c
       t@@ -30,123 +30,6 @@ HINSTANCE hInst;
        HWND mainDlg;
        char *product;
        
       -void RemoveService(NTService *service) {
       -  SC_HANDLE scManager,scService;
       -  SERVICE_STATUS status;
       -
       -  if (!service) return;
       -
       -  scManager = OpenSCManager(NULL,NULL,GENERIC_READ);
       -
       -  if (!scManager) {
       -    DisplayError("Cannot connect to service manager",TRUE,FALSE);
       -    return;
       -  }
       -
       -  scService = OpenService(scManager,service->name,DELETE|SERVICE_STOP);
       -  if (!scService) {
       -    DisplayError("Cannot open service",TRUE,FALSE);
       -  } else {
       -    if (!ControlService(scService,SERVICE_CONTROL_STOP,&status) &&
       -        GetLastError()!=ERROR_SERVICE_NOT_ACTIVE) {
       -      DisplayError("Cannot stop service",TRUE,FALSE);
       -    }
       -    if (!DeleteService(scService)) {
       -      DisplayError("Cannot delete service",TRUE,FALSE);
       -    }
       -    CloseServiceHandle(scService);
       -  }
       -
       -  CloseServiceHandle(scManager);
       -}
       -
       -char *read_line0(HANDLE hin) {
       -  char *buf;
       -  int bufsize=32,strind=0;
       -  DWORD bytes_read;
       -  buf = bmalloc(bufsize);
       -
       -  while (1) {
       -    if (!ReadFile(hin,&buf[strind],1,&bytes_read,NULL)) {
       -      printf("Read error\n"); break;
       -    }
       -    if (bytes_read==0) { buf[strind]='\0'; break; } 
       -    else if (buf[strind]=='\0') break;
       -    else {
       -      strind++;
       -      if (strind>=bufsize) {
       -        bufsize*=2;
       -        buf = brealloc(buf,bufsize);
       -      }
       -    }
       -  }
       -  if (strind==0) { bfree(buf); return NULL; }
       -  else return buf;
       -}
       -
       -InstLink *ReadLinkList(HANDLE fin) {
       -  InstLink *first=NULL,*listpt=NULL,*newpt;
       -  char *linkfile,*origfile,*args;
       -
       -  while (1) {
       -    linkfile=read_line0(fin);
       -    if (!linkfile) break;
       -    origfile=read_line0(fin);
       -    args=read_line0(fin);
       -    if (!origfile) DisplayError("Corrupt install.log",FALSE,TRUE);
       -    newpt = bmalloc(sizeof(InstLink));
       -    if (listpt) listpt->next = newpt;
       -    else first = newpt;
       -    listpt = newpt;
       -    newpt->next=NULL;
       -    newpt->linkfile=linkfile;
       -    newpt->origfile=origfile;
       -    newpt->args=args;
       -  }
       -  return first;
       -}
       -
       -NTService *ReadServiceDetails(HANDLE fin) {
       -  NTService *service=NULL;
       -  char *name,*disp,*desc,*exe;
       -
       -  name = read_line0(fin);
       -  if (name) {
       -    disp = read_line0(fin);
       -    desc = read_line0(fin);
       -    exe = read_line0(fin);
       -    if (!disp || !desc || !exe) {
       -      DisplayError("Corrupt install.log",FALSE,TRUE);
       -    } else {
       -      AddServiceDetails(name,disp,desc,exe,&service);
       -    }
       -  }
       -
       -  return service;
       -}
       -
       -InstFiles *ReadFileList(HANDLE fin) {
       -  InstFiles *first=NULL,*listpt=NULL,*newpt;
       -  char *filename,*filesize;
       -
       -  while (1) {
       -    filename=read_line0(fin);
       -    if (!filename) break;
       -    filesize=read_line0(fin);
       -    if (!filesize) DisplayError("Corrupt install.log",FALSE,TRUE);
       -    newpt = bmalloc(sizeof(InstFiles));
       -    if (listpt) listpt->next = newpt;
       -    else first = newpt;
       -    listpt = newpt;
       -
       -    newpt->next=NULL;
       -    newpt->filename=filename;
       -    newpt->filesize=atol(filesize);
       -    bfree(filesize);
       -  }
       -  return first;
       -}
       -
        char *GetProduct(void) {
          char *product;
          product = strrchr(GetCommandLine(),' ');
       t@@ -157,125 +40,10 @@ char *GetProduct(void) {
          }
        }
        
       -char *GetInstallDir(char *product) {
       -  HKEY key;
       -  bstr *str;
       -  DWORD keytype,keylen;
       -  char *installdir;
       -
       -  str=bstr_new();
       -  bstr_assign(str,UninstallKey);
       -  bstr_appendpath(str,product);
       -  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,str->text,0,
       -                   KEY_ALL_ACCESS,&key)!=ERROR_SUCCESS) {
       -    DisplayError("Could not open registry",FALSE,TRUE);
       -  }
       -
       -  if (RegQueryValueEx(key,"InstallDirectory",NULL,
       -                      &keytype,NULL,&keylen)!=ERROR_SUCCESS ||
       -      keytype!=REG_SZ) {
       -    DisplayError("Could not query registry key",FALSE,TRUE);
       -  }
       -
       -  installdir = bmalloc(keylen);
       -  if (RegQueryValueEx(key,"InstallDirectory",NULL,
       -                      &keytype,installdir,&keylen)!=ERROR_SUCCESS) {
       -    DisplayError("Could not get registry key value",FALSE,TRUE);
       -  }
       -
       -  bstr_free(str,TRUE);
       -  return installdir;
       -}
       -
       -InstData *ReadInstData(HANDLE fin,char *product,char *installdir) {
       -  InstData *idata;
       -
       -  idata=bmalloc(sizeof(InstData));
       -
       -  idata->product=product;
       -  idata->installdir=installdir;
       -  idata->startmenudir=read_line0(fin);
       -
       -  idata->instfiles = ReadFileList(fin);
       -  idata->extrafiles = ReadFileList(fin);
       -
       -  idata->startmenu = ReadLinkList(fin);
       -  idata->desktop = ReadLinkList(fin);
       -
       -  idata->service = ReadServiceDetails(fin);
       -  return idata;
       -}
       -
       -void DeleteFileList(InstFiles *listpt) {
       -  bstr *str;
       -  char *sep;
       -
       -  str=bstr_new();
       -  for (;listpt;listpt=listpt->next) {
       -    bstr_assign(str,"Deleting file: ");
       -    bstr_append(str,listpt->filename);
       -    SendDlgItemMessage(mainDlg,ST_DELSTAT,WM_SETTEXT,0,(LPARAM)str->text);
       -    DeleteFile(listpt->filename);
       -    sep = strrchr(listpt->filename,'\\');
       -    if (sep) {
       -      *sep = '\0';
       -      RemoveWholeDirectory(listpt->filename);
       -      *sep = '\\';
       -    }
       -  }
       -  bstr_free(str,TRUE);
       -}
       -
       -void DeleteLinkList(char *dir,InstLink *listpt) {
       -  bstr *str;
       -  str=bstr_new();
       -  if (SetCurrentDirectory(dir)) {
       -    for (;listpt;listpt=listpt->next) {
       -      bstr_assign(str,"Deleting shortcut: ");
       -      bstr_append(str,listpt->linkfile);
       -      SendDlgItemMessage(mainDlg,ST_DELSTAT,WM_SETTEXT,0,(LPARAM)str->text);
       -      DeleteFile(listpt->linkfile);
       -    }
       -  } else {
       -    bstr_assign(str,"Could not find shortcut directory ");
       -    bstr_append(str,dir);
       -    DisplayError(str->text,TRUE,FALSE);
       -  }
       -  bstr_free(str,TRUE);
       -}
       -
       -void RemoveUninstall(char *startmenu,char *product) {
       -  bstr *inipath,*uninstpath,*uninstlink;
       -
       -  inipath=bstr_new();
       -  uninstpath=bstr_new();
       -  uninstlink=bstr_new();
       -
       -  bstr_assign(uninstlink,startmenu);
       -  bstr_appendpath(uninstlink,"Uninstall ");
       -  bstr_append(uninstlink,product);
       -  bstr_append(uninstlink,".LNK");
       -  DeleteFile(uninstlink->text);
       -
       -  bstr_assign_windir(inipath);
       -  bstr_assign(uninstpath,inipath->text);
       -
       -  bstr_appendpath(inipath,"wininit.ini");
       -  bstr_appendpath(uninstpath,UninstallEXE);
       -
       -  if (!WritePrivateProfileString("Renane","NUL",uninstpath->text,
       -                                 inipath->text)) {
       -    DisplayError("Cannot write to wininit.ini: ",TRUE,FALSE);
       -  }
       -
       -  bstr_free(uninstlink,TRUE);
       -  bstr_free(uninstpath,TRUE);
       -  bstr_free(inipath,TRUE);
       -}
       -
        DWORD WINAPI DoUninstall(LPVOID lpParam) {
          InstData *idata;
          HANDLE fin;
       +  HWND delstat;
          bstr *str;
          char *startmenu,*desktop,*installdir;
        
       t@@ -292,21 +60,22 @@ DWORD WINAPI DoUninstall(LPVOID lpParam) {
          fin = CreateFile("install.log",GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
        
          if (fin) {
       -    idata = ReadInstData(fin,product,installdir);
       +    idata = ReadOldInstData(fin,product,installdir);
            CloseHandle(fin);
       +    DeleteFile("install.log");
        
            RemoveService(idata->service);
        
       -    DeleteFile("install.log");
       -    DeleteFileList(idata->instfiles);
       -    DeleteFileList(idata->extrafiles);
       +    delstat = GetDlgItem(mainDlg,ST_DELSTAT);
       +    DeleteFileList(idata->instfiles,delstat,NULL);
       +    DeleteFileList(idata->extrafiles,delstat,NULL);
        
            startmenu = GetStartMenuDir(idata);
            desktop = GetDesktopDir();
       -    DeleteLinkList(startmenu,idata->startmenu);
       -    DeleteLinkList(desktop,idata->desktop);
       +    DeleteLinkList(startmenu,idata->startmenu,delstat);
       +    DeleteLinkList(desktop,idata->desktop,delstat);
        
       -    RemoveUninstall(startmenu,product);
       +    RemoveUninstall(startmenu,product,TRUE);
        
            SetCurrentDirectory(desktop); /* Just make sure we're not in the install
                                             directory any more */
 (DIR) diff --git a/win32/util.c b/win32/util.c
       t@@ -309,6 +309,7 @@ void FreeServiceDetails(NTService *service,BOOL freepts) {
        void FreeInstData(InstData *idata,BOOL freepts) {
          FreeFileList(idata->instfiles,freepts);
          FreeFileList(idata->extrafiles,freepts);
       +  FreeFileList(idata->keepfiles,freepts);
        
          FreeLinkList(idata->startmenu,freepts);
          FreeLinkList(idata->desktop,freepts);
       t@@ -485,3 +486,249 @@ BOOL RemoveWholeDirectory(char *path) {
          }
          return TRUE;
        }
       +
       +void RemoveService(NTService *service) {
       +  SC_HANDLE scManager,scService;
       +  SERVICE_STATUS status;
       +
       +  if (!service) return;
       +
       +  scManager = OpenSCManager(NULL,NULL,GENERIC_READ);
       +
       +  if (!scManager) {
       +    DisplayError("Cannot connect to service manager",TRUE,FALSE);
       +    return;
       +  }
       +
       +  scService = OpenService(scManager,service->name,DELETE|SERVICE_STOP);
       +  if (!scService) {
       +    DisplayError("Cannot open service",TRUE,FALSE);
       +  } else {
       +    if (!ControlService(scService,SERVICE_CONTROL_STOP,&status) &&
       +        GetLastError()!=ERROR_SERVICE_NOT_ACTIVE) {
       +      DisplayError("Cannot stop service",TRUE,FALSE);
       +    }
       +    if (!DeleteService(scService)) {
       +      DisplayError("Cannot delete service",TRUE,FALSE);
       +    }
       +    CloseServiceHandle(scService);
       +  }
       +
       +  CloseServiceHandle(scManager);
       +}
       +
       +char *read_line0(HANDLE hin) {
       +  char *buf;
       +  int bufsize=32,strind=0;
       +  DWORD bytes_read;
       +  buf = bmalloc(bufsize);
       +
       +  while (1) {
       +    if (!ReadFile(hin,&buf[strind],1,&bytes_read,NULL)) {
       +      printf("Read error\n"); break;
       +    }
       +    if (bytes_read==0) { buf[strind]='\0'; break; } 
       +    else if (buf[strind]=='\0') break;
       +    else {
       +      strind++;
       +      if (strind>=bufsize) {
       +        bufsize*=2;
       +        buf = brealloc(buf,bufsize);
       +      }
       +    }
       +  }
       +  if (strind==0) { bfree(buf); return NULL; }
       +  else return buf;
       +}
       +
       +InstLink *ReadLinkList(HANDLE fin) {
       +  InstLink *first=NULL,*listpt=NULL,*newpt;
       +  char *linkfile,*origfile,*args;
       +
       +  while (1) {
       +    linkfile=read_line0(fin);
       +    if (!linkfile) break;
       +    origfile=read_line0(fin);
       +    args=read_line0(fin);
       +    if (!origfile) DisplayError("Corrupt install.log",FALSE,TRUE);
       +    newpt = bmalloc(sizeof(InstLink));
       +    if (listpt) listpt->next = newpt;
       +    else first = newpt;
       +    listpt = newpt;
       +    newpt->next=NULL;
       +    newpt->linkfile=linkfile;
       +    newpt->origfile=origfile;
       +    newpt->args=args;
       +  }
       +  return first;
       +}
       +
       +NTService *ReadServiceDetails(HANDLE fin) {
       +  NTService *service=NULL;
       +  char *name,*disp,*desc,*exe;
       +
       +  name = read_line0(fin);
       +  if (name) {
       +    disp = read_line0(fin);
       +    desc = read_line0(fin);
       +    exe = read_line0(fin);
       +    if (!disp || !desc || !exe) {
       +      DisplayError("Corrupt install.log",FALSE,TRUE);
       +    } else {
       +      AddServiceDetails(name,disp,desc,exe,&service);
       +    }
       +  }
       +
       +  return service;
       +}
       +
       +InstFiles *ReadFileList(HANDLE fin) {
       +  InstFiles *first=NULL,*listpt=NULL,*newpt;
       +  char *filename,*filesize;
       +
       +  while (1) {
       +    filename=read_line0(fin);
       +    if (!filename) break;
       +    filesize=read_line0(fin);
       +    if (!filesize) DisplayError("Corrupt install.log",FALSE,TRUE);
       +    newpt = bmalloc(sizeof(InstFiles));
       +    if (listpt) listpt->next = newpt;
       +    else first = newpt;
       +    listpt = newpt;
       +
       +    newpt->next=NULL;
       +    newpt->filename=filename;
       +    newpt->filesize=atol(filesize);
       +    bfree(filesize);
       +  }
       +  return first;
       +}
       +
       +char *GetInstallDir(char *product) {
       +  HKEY key;
       +  bstr *str;
       +  DWORD keytype,keylen;
       +  char *installdir;
       +
       +  str=bstr_new();
       +  bstr_assign(str,UninstallKey);
       +  bstr_appendpath(str,product);
       +  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,str->text,0,
       +                   KEY_ALL_ACCESS,&key)!=ERROR_SUCCESS) {
       +    DisplayError("Could not open registry",FALSE,TRUE);
       +  }
       +
       +  if (RegQueryValueEx(key,"InstallDirectory",NULL,
       +                      &keytype,NULL,&keylen)!=ERROR_SUCCESS ||
       +      keytype!=REG_SZ) {
       +    DisplayError("Could not query registry key",FALSE,TRUE);
       +  }
       +
       +  installdir = bmalloc(keylen);
       +  if (RegQueryValueEx(key,"InstallDirectory",NULL,
       +                      &keytype,installdir,&keylen)!=ERROR_SUCCESS) {
       +    DisplayError("Could not get registry key value",FALSE,TRUE);
       +  }
       +
       +  bstr_free(str,TRUE);
       +  return installdir;
       +}
       +
       +InstData *ReadOldInstData(HANDLE fin,char *product,char *installdir) {
       +  InstData *idata;
       +
       +  idata=bmalloc(sizeof(InstData));
       +
       +  idata->product=product;
       +  idata->installdir=installdir;
       +  idata->startmenudir=read_line0(fin);
       +
       +  idata->instfiles = ReadFileList(fin);
       +  idata->extrafiles = ReadFileList(fin);
       +
       +  idata->startmenu = ReadLinkList(fin);
       +  idata->desktop = ReadLinkList(fin);
       +
       +  idata->service = ReadServiceDetails(fin);
       +  idata->keepfiles = ReadFileList(fin);
       +
       +  return idata;
       +}
       +
       +void DeleteFileList(InstFiles *listpt,HWND hwnd,InstFiles *keepfiles) {
       +  bstr *str;
       +  char *sep;
       +  InstFiles *keeppt;
       +
       +  str=bstr_new();
       +  for (;listpt;listpt=listpt->next) {
       +    keeppt = keepfiles;
       +    while (keeppt && strcmp(keeppt->filename,listpt->filename)!=0) {
       +      keeppt = keeppt->next;
       +    }
       +    if (keeppt) {
       +      keeppt->filesize=1; /* Mark that this file is already installed */
       +    } else {
       +      bstr_assign(str,"Deleting file: ");
       +      bstr_append(str,listpt->filename);
       +      SendMessage(hwnd,WM_SETTEXT,0,(LPARAM)str->text);
       +      DeleteFile(listpt->filename);
       +      sep = strrchr(listpt->filename,'\\');
       +      if (sep) {
       +        *sep = '\0';
       +        RemoveWholeDirectory(listpt->filename);
       +       *sep = '\\';
       +      }
       +    }
       +  }
       +  bstr_free(str,TRUE);
       +}
       +
       +void DeleteLinkList(char *dir,InstLink *listpt,HWND hwnd) {
       +  bstr *str;
       +  str=bstr_new();
       +  if (SetCurrentDirectory(dir)) {
       +    for (;listpt;listpt=listpt->next) {
       +      bstr_assign(str,"Deleting shortcut: ");
       +      bstr_append(str,listpt->linkfile);
       +      SendMessage(hwnd,WM_SETTEXT,0,(LPARAM)str->text);
       +      DeleteFile(listpt->linkfile);
       +    }
       +  } else {
       +    bstr_assign(str,"Could not find shortcut directory ");
       +    bstr_append(str,dir);
       +    DisplayError(str->text,TRUE,FALSE);
       +  }
       +  bstr_free(str,TRUE);
       +}
       +
       +void RemoveUninstall(char *startmenu,char *product,BOOL delexe) {
       +  bstr *inipath,*uninstpath,*uninstlink;
       +
       +  inipath=bstr_new();
       +  uninstpath=bstr_new();
       +  uninstlink=bstr_new();
       +
       +  bstr_assign(uninstlink,startmenu);
       +  bstr_appendpath(uninstlink,"Uninstall ");
       +  bstr_append(uninstlink,product);
       +  bstr_append(uninstlink,".LNK");
       +  DeleteFile(uninstlink->text);
       +
       +  if (delexe) {
       +    bstr_assign_windir(inipath);
       +    bstr_assign(uninstpath,inipath->text);
       +
       +    bstr_appendpath(inipath,"wininit.ini");
       +    bstr_appendpath(uninstpath,UninstallEXE);
       +
       +    if (!WritePrivateProfileString("Renane","NUL",uninstpath->text,
       +                                   inipath->text)) {
       +      DisplayError("Cannot write to wininit.ini: ",TRUE,FALSE);
       +    }
       +  }
       +
       +  bstr_free(uninstlink,TRUE);
       +  bstr_free(uninstpath,TRUE);
       +  bstr_free(inipath,TRUE);
       +}
 (DIR) diff --git a/win32/util.h b/win32/util.h
       t@@ -53,6 +53,7 @@ typedef struct _InstData {
          NTService *service;
          InstFiles *instfiles;
          InstFiles *extrafiles;
       +  InstFiles *keepfiles;
          InstLink *startmenu;
          InstLink *desktop;
        } InstData;
       t@@ -99,3 +100,9 @@ char *GetStartMenuDir(InstData *idata);
        char *GetDesktopDir(void);
        BOOL CreateWholeDirectory(char *path);
        BOOL RemoveWholeDirectory(char *path);
       +void DeleteLinkList(char *dir,InstLink *listpt,HWND hwnd);
       +void DeleteFileList(InstFiles *listpt,HWND hwnd,InstFiles *keepfiles);
       +InstData *ReadOldInstData(HANDLE fin,char *product,char *installdir);
       +char *GetInstallDir(char *product);
       +void RemoveService(NTService *service);
       +void RemoveUninstall(char *startmenu,char *product,BOOL delexe);