// AmiVNC - Amiga experimental VNC server - Protocol ORL version 3.3
// (c) Stephane Guillard - stephane.guillard@steria.com

// Includes ***********************************************
// Needed to compile : SDK Cybergraphx 4.1 & SDK AmiTCP 4.3

#include <exec/types.h>
#include <exec/memory.h>

#include <intuition/IntuitionBase.h>
#include <intuition/intuition.h>

#include <graphics/gfxmacros.h>
#include <graphics/displayinfo.h>
#include <graphics/gfxbase.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <clib/utility_protos.h>
#include <clib/alib_protos.h>
#include <clib/commodities_protos.h>
#include <clib/icon_protos.h>

#include <dos/dostags.h>

#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <lineread.h>
#include <netdb.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <filedefs.h>

#include <signal.h>

#include <devices/input.h>
#include <devices/inputevent.h>
#include <libraries/commodities.h>

#include <clib/socket_protos.h>
#include <clib/netlib_protos.h>

#include <proto/socket.h>

#ifndef PLANAR
#include <cybergraphx/cybergraphics.h>
#include <proto/cybergraphics.h>
#endif

void	do_pointer(struct IOStdReq *inputReqBlk, int buttons, int x, int y, int fbw, int fbh, struct Screen *screen);
void	do_key(struct IOStdReq *inputReqBlk, int dn, int key);

// Specific Includes ***********************************************
#include "rfbproto.h"                   // Structures & constants for VNC protocol
#include "vncauth.h"                    // Prototypes for authentication

// Constants ***********************************************
#define FALSE           0               // What's that ?
#define TRUE            1               // And this ?
#define XDC_PORT        5900            // Port # for bind()
#define XDC_TILE        32              // Tile size
#define XDC_C_VBUF      (1L << 0)       // Bitmask for pBuffer allocation
#define XDC_C_MSOCK     (1L << 1)       // Bitmask for listen socket open
#define XDC_C_CSOCK     (1L << 2)       // Bitmask for client socket open
#define XDC_C_MAXDEPTH  4               // Max. raster depth : 4 bytes / pixel
#define XDC_LOGFILE     "T:AmiVNC.log"  // Log file name
#undef  PARANO

// Common messages
#ifndef __PPC__
char XDC_ID[] = "AmiVNC 68K 1.0.0 Aug 30th 2001 (StormC4/GCC)";
#else
char XDC_ID[] = "AmiVNC PPC 1.0.0 Aug 30th 2001 (StormC4/GCC)";
#endif

char XDC_SEND[] = "send() error";
char XDC_RECV[] = "recv() error";

// Global variables ***********************************************
LONG    iDuplicateSocketKey;            // Client socket key for transmitting to child process
ULONG   uOpened = NULL;                 // Opened resources, to be closed on exit
BOOL    bDie = FALSE, *pDie = &bDie;    // TRUE : processes are to exit (Argh, very trashy)
BOOL    bSession = FALSE;               // TRUE : clientsession opened
char    cPassword[MAXPWLEN + 1],        // Password
        *pPreStart = NULL,              // User command to execute when connection accepted
        *pPostStop = NULL;              // User command to execute when session closes
LONG    iMasterSocket,                  // Listening socket for incoming connections
        iClientSocket;                  // Client socket accept()ed
UBYTE   *pBuffer = NULL,                // Reference buffer for screen change detection
        *P2CBuffer = NULL;              // Temp buffer for ReadPixelArray8() in planar2chunky
char    *sPWFile = "S:AmiVNC.pwd";      // Password file name
FILE    *fLog;                          // Logfile
struct  sockaddr_in cliAddr;            // To find client IP address. Must be addressable for getpeername. 
struct  BitMap *pBM = NULL;             // Temp. BitMap for temp. RastPort for p2c
extern  struct IntuitionBase *IntuitionBase;
struct  Library *SocketBase;

// Free all resources allocated to a client session ***********************************************
void vCleanSession(char *cMsg, long lCode)
{
    // Tell the child process to exit (as we are the main process,
    // we can use bDie, which is in our address space)
    if (!bDie)
    {
        bDie = TRUE;
        Delay(125);
    }
    
    // Close everything opened
    if (uOpened & XDC_C_VBUF)   { free(pBuffer); pBuffer = NULL; uOpened ^= XDC_C_VBUF; }

    if (pBM)                    { FreeBitMap(pBM); pBM = NULL; }

    if (P2CBuffer)              { free(P2CBuffer); P2CBuffer = NULL; }

    if (uOpened & XDC_C_CSOCK)
    {
        CloseSocket(iClientSocket);
        uOpened ^= XDC_C_CSOCK;
        
        // Execute post-close user command (if any)
        if (bSession && pPostStop)
        {
             char cCommand[128];
             sprintf(cCommand, pPostStop, Inet_NtoA(cliAddr.sin_addr.s_addr));
             SystemTags(cCommand, SYS_Asynch, TRUE, TAG_DONE);
             bSession = FALSE;
        }
    }
    
    fprintf(fLog, "AmiVNC - %s (%ld)\nAmiVNC session closed\n", cMsg, lCode);
}

// Free all resources allocated to main process and exit ***********************************************
void vCleanExit(char *cMsg, long lCode)
{
    // Free tooltype ArgArray initialized by ArgArrayInit()
    // ArgArrayDone();
    
    vCleanSession(cMsg, lCode);
    
    if (uOpened & XDC_C_MSOCK) { CloseSocket(iMasterSocket); uOpened ^= XDC_C_MSOCK; }

    CloseLibrary(SocketBase);
    
    fprintf(fLog, "AmiVNC halted\n");
    
    if (fLog != stdout) fclose(fLog);
    
    exit(0L);
}

// Free all resources allocated to main process and exit ***********************************************
void vCleanSig(int iCode)
{
    vCleanExit("Break CTRL-C", (long) iCode);
}

// Authentication ***********************************************
BOOL bAuthentify(void)
{
    char cChallenge[16], cResponse[16];
    CARD32 c32Value;
    BOOL bAuthenticated = TRUE;
    int iCnt, iFile;

    // Read encoded password in s:AmiVNC.pwd
    if (!(iFile = open(sPWFile, O_RDONLY, 0)))
    return(FALSE);
    
    iCnt = read(iFile, cPassword, sizeof(cPassword));

    close(iFile);

    if (iCnt != sizeof(cPassword))
    return(FALSE);

    // Decode password
    vncDecryptPasswd(cPassword, cPassword);

    // Authenticate the connection, if required
    if (!strlen(cPassword))
    {
        // Send no-auth-required message
        c32Value = rfbNoAuth;
        if (!(-1 == (send(iClientSocket,(char *)&c32Value, sizeof(c32Value), 0))))
            return FALSE;

        return(TRUE);
    }
    else
    {
        // Send auth-required message
        c32Value = rfbVncAuth;
        if (-1 == (send(iClientSocket,(char *)&c32Value, sizeof(c32Value), 0)))
            return FALSE;

        // Now create a 16-byte challenge
        vncRandomBytes((UBYTE *)cChallenge);

        // Send the challenge to the client
        if (-1 == (send(iClientSocket, cChallenge, sizeof(cChallenge), 0)))
            return FALSE;

        // Read the response
        if (-1 == (recv(iClientSocket, (UBYTE *) cResponse, sizeof(cResponse), 0)))
            return FALSE;

        // Encrypt the challenge bytes
        vncEncryptBytes((UBYTE *)cChallenge, cPassword);

        // Compare them to the response
        for (iCnt = 0; iCnt < sizeof(cChallenge); iCnt++)
        {
            if (cChallenge[iCnt] != cResponse[iCnt])
            {
                bAuthenticated = FALSE;
                break;
            }
        }

        // Did the authentication work?
        if (!bAuthenticated)
        {
            c32Value = rfbVncAuthFailed;
            send(iClientSocket, (char *)&c32Value, sizeof(c32Value), 0);
            return FALSE;

        }
        else
        {
            // Tell the client we're ok
            c32Value = rfbVncAuthOK;
            if (-1 == (send(iClientSocket, (char *)&c32Value, sizeof(c32Value), 0)))
                return FALSE;

            return(TRUE);
        }
    }
}

// Incoming messages management thread ***********************************************
void __saveds vProcessIncomes(void)
{
    struct IOStdReq *inputReqBlk;
    struct MsgPort *inputPort;
    struct Screen *pCurrentScreen = -1, *pScreen;
    ULONG iLock;
    rfbClientToServerMsg sCMsg;
    struct Library *SocketBase;
    LONG iSocketChild;

    int fbw, fbh;

    // We have to reopen bsdsocket.library, because it can not be shared between processes.
    // OpenLibrary can not fail since it would have halted the father before creating us.
    if (!(SocketBase = OpenLibrary("bsdsocket.library", 4L)))
    {
        fprintf(fLog, "vProcessIncomes : cannot reopen bsdsocket.library\n");
        *pDie = TRUE;
        goto _Nolib;
    }

    // Obtain the client socket descriptor from the key saved for us by our father
    if (-1 == (iSocketChild = ObtainSocket(iDuplicateSocketKey, AF_INET, SOCK_STREAM, 0)))
    {
        fprintf(fLog, "vProcessIncomes : ObtainSocket() error %d\n", (int) Errno());
        *pDie = TRUE;
        goto _Nosock;
    }

    fprintf(fLog, "vProcessIncomes runs on socket %d\n", (int) iSocketChild);

    // Create input.device message structure
    inputPort = CreatePort(NULL, NULL);
    inputReqBlk = (struct IOStdReq *) CreateExtIO(inputPort, sizeof(struct IOStdReq));
    OpenDevice("input.device", NULL, (struct IORequest *) inputReqBlk, NULL);
    
    // Process incoming messages until main process tells to die.
    // Use pointer to bDie because it is not in our address space...
    while (!*pDie) // VERY VERY dirty way to know we have to exit...
    {
       if (-1 == recv(iSocketChild, (UBYTE *) &sCMsg, 1, 0))
       {
           fprintf(fLog, "vProcessIncomes : recv() error, errno = %d\n", (int) Errno());
           *pDie  = TRUE;
           continue;
       }
       
//       Forbid(); // Leave us handle properly our input event
       switch(sCMsg.type)
       {
           case rfbSetPixelFormat : // 0
           if (sz_rfbSetPixelFormatMsg - 1 == recv(iSocketChild, 1 + (UBYTE *) & sCMsg, sz_rfbSetPixelFormatMsg - 1, 0))
           {
#ifdef PARANO
               fprintf(fLog, "C -> S : rfbSetPixelFormat lanbpp %d depth %d bigE %d tc %d redM %d redS %d GreM %d GreS %d BluM %d BluS %d\n",
                  sCMsg.spf.format.bitsPerPixel,
                  sCMsg.spf.format.depth,
                  sCMsg.spf.format.bigEndian,
                  sCMsg.spf.format.trueColour,
                  sCMsg.spf.format.redMax,
                  sCMsg.spf.format.redShift,
                  sCMsg.spf.format.greenMax,
                  sCMsg.spf.format.greenShift,
                  sCMsg.spf.format.blueMax,
                  sCMsg.spf.format.blueShift
                  );
#endif // PARANO
               break;
           }
           
           case rfbFixColourMapEntries : // 1
           if (sz_rfbFixColourMapEntriesMsg - 1 == recv(iSocketChild, 1 + (UBYTE *) & sCMsg, sz_rfbFixColourMapEntriesMsg - 1, 0))
           {
#ifdef PARANO
               fprintf(fLog, "C -> S : rfbFixColourMapEntries\n");
#endif // PARANO
               break;
           }
           
           case rfbSetEncodings : // 2
           if (sz_rfbSetEncodingsMsg - 1 == recv(iSocketChild, 1 + (UBYTE *) & sCMsg, sz_rfbSetEncodingsMsg - 1, 0))
           {
#ifdef PARANO
               fprintf(fLog, "C -> S : rfbSetEncodings\n");
#endif // PARANO
               break;
           }
           
           case rfbFramebufferUpdateRequest : // 3
           if (sz_rfbFramebufferUpdateRequestMsg - 1 == recv(iSocketChild, 1 + (UBYTE *) & sCMsg, sz_rfbFramebufferUpdateRequestMsg - 1, 0))
           {
#ifdef PARANO            
               fprintf(fLog, "C -> S : rfbFramebufferUpdateRequest inc %d x %d y %d w %d h %d\n",
                  sCMsg.fur.incremental,
                  sCMsg.fur.x,
                  sCMsg.fur.y,
                  sCMsg.fur.w,
                  sCMsg.fur.h
                  );
#endif // PARANO            
               break;
           }
           
           case rfbKeyEvent : // 4
           if (sz_rfbKeyEventMsg - 1 == recv(iSocketChild, 1 + (UBYTE *) & sCMsg, sz_rfbKeyEventMsg - 1, 0))
           {
#ifdef PARANO
               fprintf(fLog, "C -> S : rfbKeyEvent down %d key %d ('%c')\n",
                  sCMsg.ke.down,
                  sCMsg.ke.key,
                  sCMsg.ke.key
                 );
#endif // PARANO

           do_key(inputReqBlk, sCMsg.ke.down, sCMsg.ke.key);

           break;
           }
           
           case rfbPointerEvent : // 5
           if (sz_rfbPointerEventMsg - 1 == recv(iSocketChild, 1 + (UBYTE *) & sCMsg, sz_rfbPointerEventMsg - 1, 0))
           {
#ifdef PARANO
               fprintf(fLog, "C -> S : rfbPointerEvent button %d x %d y %d\r",
                  sCMsg.pe.buttonMask,
                  sCMsg.pe.x,
                  sCMsg.pe.y
                 );
#endif // PARANO

               iLock = LockIBase(0);
               pScreen = IntuitionBase -> FirstScreen;
               UnlockIBase(iLock);

               if (pScreen != pCurrentScreen)
               {
                   pCurrentScreen = pScreen;
#ifdef PLANAR
                   fbw = GetBitMapAttr(pCurrentScreen -> RastPort.BitMap, BMA_WIDTH);
                   fbh = GetBitMapAttr(pCurrentScreen -> RastPort.BitMap, BMA_HEIGHT);
#else
                   if (GetCyberMapAttr(pCurrentScreen -> RastPort.BitMap, CYBRMATTR_ISCYBERGFX))
                   {
                       fbw = GetCyberMapAttr(pCurrentScreen -> RastPort.BitMap, CYBRMATTR_WIDTH);
                       fbh = GetCyberMapAttr(pCurrentScreen -> RastPort.BitMap, CYBRMATTR_HEIGHT);
                   }
                   else
                   {
                       fbw = GetBitMapAttr(pCurrentScreen -> RastPort.BitMap, BMA_WIDTH);
                       fbh = GetBitMapAttr(pCurrentScreen -> RastPort.BitMap, BMA_HEIGHT);
                   }
#endif
               }

               do_pointer(inputReqBlk, sCMsg.pe.buttonMask, sCMsg.pe.x, sCMsg.pe.y, fbw, fbh, pCurrentScreen);

               break;
           }
           
           case rfbClientCutText : // 6
           if (sz_rfbClientCutTextMsg - 1 == recv(iSocketChild, 1 + (UBYTE *) & sCMsg, sz_rfbClientCutTextMsg - 1, 0))
           {
               char *cText = (char *) malloc((unsigned) (sCMsg.cct.length + 1));
               recv(iSocketChild, (UBYTE *) cText, (int) (sCMsg.cct.length), 0);
               cText[sCMsg.cct.length] = 0;
#ifdef PARANO
               fprintf(fLog, "C -> S : rfbClientCutText '%s'\n", cText);
#endif // PARANO
               break;
           }
           
           default :
#ifdef PARANO
           fprintf(fLog, "vProcessIncomes : msg type %d unknown\n", sCMsg.type);
#endif // PARANO
           break;
       }
//       Permit();
    }
    
    CloseSocket(iSocketChild);

    if (inputReqBlk)
    {
        CloseDevice((struct IORequest *) inputReqBlk);
        DeleteExtIO((struct IORequest *) inputReqBlk);
    }
    
    if (inputPort) DeletePort(inputPort);

_Nosock:
    CloseLibrary(SocketBase);

_Nolib:
    fprintf(fLog, "vProcessIncomes halted\n");   
}

// Main entry point  ***********************************************
int main(int argc, char **argv)
{
    register int iCnt, jCnt;
    BPTR pFile;
    int iMajor, iMinor, iWidth, iHeight, iDepth, iSize, iMode, iPort, iUpdate = 0, iRShift = 8, iGShift = 16, iBShift = 24;
    unsigned long uLimit = 0xFFFFFFFF, uSize, uCnt, uTotal;
    struct sockaddr_in sAddr;                  // Local address for bind()
    struct Screen *pActiveScreen, *pNewScreen;
    rfbProtocolVersionMsg mProtVerMsg;
    rfbServerInitMsg mSerInitMsg;
    rfbFramebufferUpdateMsg mFBUpdMsg;
    rfbFramebufferUpdateRectHeader mFBRMsg;
    long iDelay = 50;
    CARD8 c8;
    BOOL bBig = FALSE, bFastMode, bPlanar, bVVA = FALSE;
    register UBYTE *pRaster;                                    // True WB screen raster
    static UBYTE uTile[XDC_TILE * XDC_TILE * XDC_C_MAXDEPTH];   // 3 Byte/pixel RGB Raster tile for client screen updates
    static UBYTE uCTile[XDC_TILE * XDC_TILE];                   // Chunky tile for planar comparisons 
    char cLog[80], *pC;
    ULONG uLock;
    UBYTE **ArgArray;
    UWORD  CLUT[256];                                           // 16 bit Planar2Chunky color lookup table (pen number -> RGB16PC values)
    UBYTE  CLUT8[256];                                          // 8 bit Planar2Chunky color lookup table (pen number -> BGR233 values)
    struct RastPort TempRP;                                     // Temp. RastPort for ReadPixelArray8

#ifndef PLANAR
    struct Library *CGXlib = OpenLibrary("cgxsystem.library", 40L);
#endif

    fLog = stdout;

    if (!(SocketBase = OpenLibrary("bsdsocket.library", 4L)))
    {
        fprintf(fLog, "main : cannot open bsdsocket.library\n");
        exit;
    }

    iPort = XDC_PORT;
    
    strcpy(cLog, XDC_LOGFILE);

    signal(SIGINT, vCleanSig);
    
    // Process AmiVNC icon tooltypes
#ifndef __PPC__
    ArgArray = ArgArrayInit((long) argc, (UBYTE **) argv);
    if (pC =  FindToolType(ArgArray, "PORT"))      iPort = atoi(pC);
    if (pC =  FindToolType(ArgArray, "BIGENDIAN")) bBig = (tolower(*pC) == 't') ? TRUE : FALSE;
    if ((pC = FindToolType(ArgArray, "LIMIT")) && (atoi(pC))) uLimit = atoi(pC);
    if (pC =  FindToolType(ArgArray, "RSHIFT"))    iRShift = atoi(pC);
    if (pC =  FindToolType(ArgArray, "GSHIFT"))    iGShift = atoi(pC);
    if (pC =  FindToolType(ArgArray, "BSHIFT"))    iBShift = atoi(pC);
    if (pC =  FindToolType(ArgArray, "LOGFILE"))   strcpy(cLog, pC);
    if (pC =  FindToolType(ArgArray, "PRESTART"))  pPreStart = pC;
    if (pC =  FindToolType(ArgArray, "POSTSTOP"))  pPostStop = pC;
    if (pC =  FindToolType(ArgArray, "VERBOSE"))   cLog[0] = 0;
    if (pC =  FindToolType(ArgArray, "VVA"))       bVVA = TRUE;
    if (pC =  FindToolType(ArgArray, "DELAY"))     iDelay = atoi(pC);
#endif

    // Process command line arguments (if any)
    if (argc > 1) while (*++argv)
    {
        switch(tolower((*argv)[1]))
        {
            // *** Force password change and exit
            case 'p' :
                strncpy(cPassword, *argv + 2, MAXPWLEN);
                cPassword[MAXPWLEN] = '\0';
                
                pC = strchr(cPassword, ' ');
                if (pC) *pC = '\0';
                
                for (iCnt = strlen(cPassword) ; iCnt < MAXPWLEN ; iCnt++)
                   cPassword[iCnt] = '\0';
                
                vncEncryptPasswd(cPassword, cPassword);
                
                if (!(pFile = Open(sPWFile, MODE_NEWFILE)))
                    vCleanExit("main : creat('s:AmiVNC.pwd') error", 0);

                Write(pFile, cPassword, sizeof(cPassword));
                Close(pFile);
                
                vCleanExit("main : Passwd stored", 0);
                break;
            
            // *** Force another port than the default 5900
            case 's' :
                iPort = atoi(*argv + 2);
                break;
            
            // *** Force BigEndian flag in mSerInitMsg
            case 'e' :
                bBig = TRUE;
                break;
            
            // *** Limit netblocksize for initial screen update
            case 'l' :
                uLimit = atoi(*argv + 2);
                break;
                
            // *** Force Red / Green / Blue bitshifts in mSerInitMsg
            // (play with this if you have color problems, legal values
            // for shifts are 0, 8, 16 and 24)
            case 'r' :
                iRShift = atoi(*argv + 2);
                break;
                
            case 'g' :
                iGShift = atoi(*argv + 2);
                break;
                
            case 'b' :
                iBShift = atoi(*argv + 2);
                break;
                
            // Force verbose mode to sdtout
            case 'v' :
                cLog[0] = 0;
                break;
                
            // Force VVA compatible (BGR233) pixel encoding
            case 'a' :
                bVVA = TRUE;
                break;

            // *** Force delay between screen scans
            case 'd' :
                iDelay = atoi(*argv + 2);
                break;

            default :
                printf("main : Arg [%s] ignored\n", *argv);
                break;
        }
    }
    
    // If log not forced to stdout, open log file
    if (cLog[0])
    {
        fLog = fopen(cLog, "w");
        if (!fLog)
        {
            fLog = stdout;
            vCleanExit("main : Log file open error", 0);
        }
    }

    // Welcome...
    fprintf(fLog, "%s\n(c) 1999-2001 stephane.guillard@steria.fr\n", XDC_ID);
#ifdef PLANAR
    fprintf(fLog, "Planar only version\n");
#endif
    
    // Print configurable parameter values
    fprintf(fLog, "main : parameter values :\n");
    fprintf(fLog, " - listen port set to %d\n", iPort);
    fprintf(fLog, " - endian set to %s\n", bBig ? "big" : "little");
    fprintf(fLog, " - initial xfer limit set to %08lX\n", uLimit);
    fprintf(fLog, " - color bitshifts : red %d, green %d, blue %d\n", iRShift, iGShift, iBShift);
    fprintf(fLog, " - log file set to %s\n", cLog[0] ? cLog : "stdout");
    fprintf(fLog, " - VVA BGR233 encoding : %s\n", bVVA ? "yes" : "no");
    fprintf(fLog, " - user command on client login : %s\n", pPreStart ? pPreStart : "none");
    fprintf(fLog, " - user command on client logout : %s\n", pPostStop ? pPostStop : "none");
    
    // Prepare listener socket
    fprintf(fLog, "main : Creating listener socket\n");
    if (-1 == (iMasterSocket = socket(AF_INET, SOCK_STREAM, 0)))
        vCleanExit("main : socket() error", Errno());

    uOpened |= XDC_C_MSOCK;
    
    sAddr.sin_family=AF_INET;
    sAddr.sin_addr.s_addr = 0;
    sAddr.sin_port = htons(iPort);
    
    fprintf(fLog, "main : Binding socket on port %d\n", sAddr.sin_port);
    if (-1 == (bind(iMasterSocket, (struct sockaddr *) &sAddr, sizeof(sAddr))))
        vCleanExit("main : bind() error", Errno());
    
    fprintf(fLog, "main : Listening set on port %d\n", sAddr.sin_port);
    if (-1 == (listen(iMasterSocket, 4)))
        vCleanExit("main : listen() error", Errno()); 

_NewSession:

    bDie = FALSE;
    
    // Wait for incoming connection and find client IP address (use iDuplicateSocketKey
    // as temp. var because at this point it is useless)
    fprintf(fLog, "main : Waiting for client connection\n");

    iDuplicateSocketKey = sizeof(cliAddr);
    if (-1 == (iClientSocket = accept(iMasterSocket, (struct sockaddr *) &cliAddr, &iDuplicateSocketKey)))
        vCleanExit("main : accept() error", Errno());
    
    uOpened |= XDC_C_CSOCK;
    
    // Now useless : getpeername(iClientSocket, (struct sockaddr *) &cliAddr, &iDuplicateSocketKey);

    fprintf(fLog, "main : accept()ed connection from %s on socket %d\n", Inet_NtoA(cliAddr.sin_addr.s_addr), (int) iClientSocket);
    
    // NegociateProtocolVersion
    fprintf(fLog, "main : Negociating protocol version\n");
    sprintf((char *) mProtVerMsg, rfbProtocolVersionFormat,
    rfbProtocolMajorVersion,
    rfbProtocolMinorVersion);
    if (-1 == (send(iClientSocket, (UBYTE *) mProtVerMsg, sz_rfbProtocolVersionMsg, 0)))
    { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }
    
    mProtVerMsg[12] = 0;

    if (-1 == (recv(iClientSocket, mProtVerMsg, sz_rfbProtocolVersionMsg, 0)))
    { vCleanSession(XDC_RECV, Errno()); goto _NewSession; }

    sscanf((char *) mProtVerMsg, rfbProtocolVersionFormat, &iMajor, &iMinor);
    fprintf(fLog, "main : Protocol supported by server : %d.%d, by client : %d.%d\n", rfbProtocolMajorVersion, rfbProtocolMinorVersion, iMajor, iMinor);

    // Check protocol version
    if (iMajor > rfbProtocolMajorVersion)
    { vCleanSession("main : unknown protocol", 0); goto _NewSession; }
    
    // Authenticate DES
    fprintf(fLog, "main : Authentication\n");
    if (FALSE == bAuthentify())
    { vCleanSession("main : Authenticate error", 0); goto _NewSession; }
    
    // ClientInit
    fprintf(fLog, "main : ClientInitialisation\n");
    if (-1 == (recv(iClientSocket, &c8, sizeof(c8), 0)))
    { vCleanSession(XDC_RECV, Errno()); goto _NewSession; }
    
    fprintf(fLog, "main : SharedFlag = %d (multiple clients : %s)\n", c8, c8 ? "Yes" : "No");

    // Grab active screen
    pActiveScreen = IntuitionBase -> FirstScreen;

    // Check if planar Amiga screen
    bPlanar = GetBitMapAttr(pActiveScreen -> RastPort.BitMap, BMA_FLAGS) & BMF_STANDARD ? TRUE : FALSE;

    fprintf(fLog, "main : Screen set to [%s] (%s)\n", pActiveScreen -> Title, bPlanar ? "planar" : "RTG");

    // Get screen info (mode, width, height, depth, linear address)
    if (!bPlanar) // RTG chunky linear modes
    {
#ifndef PLANAR
        if (GetCyberMapAttr(pActiveScreen -> RastPort.BitMap, CYBRMATTR_ISCYBERGFX)
         && GetCyberMapAttr(pActiveScreen -> RastPort.BitMap, CYBRMATTR_ISLINEARMEM))
        {
            // Get screenmode, width, height, depth
            iMode   = GetCyberMapAttr(pActiveScreen -> RastPort.BitMap, CYBRMATTR_PIXFMT);
            iWidth  = GetCyberMapAttr(pActiveScreen -> RastPort.BitMap, CYBRMATTR_WIDTH); // X size
            iHeight = GetCyberMapAttr(pActiveScreen -> RastPort.BitMap, CYBRMATTR_HEIGHT); // Y size
            iDepth  = GetCyberMapAttr(pActiveScreen -> RastPort.BitMap, CYBRMATTR_BPPIX); // Depth is in Bytes / pixel

            // Grab chunky raster pointer (dirty but EFFICIENT !)
            UnLockBitMap(LockBitMapTags(pActiveScreen -> RastPort.BitMap,
                     (ULONG) LBMI_BASEADDRESS, (ULONG) &uCnt,
                     TAG_DONE));
            pRaster = (UBYTE *) uCnt;

            // Calculate total byte size of screen raster (adjust to 2 byte / pixel if CLUT, will realloc later)
            iSize = iWidth * iHeight * (iDepth > 1 ? iDepth : 2);
            
            fprintf(fLog, "main : RTG mode %s/%ld ", CGXlib ? "CGFx" : "Pic96", (LONG) iMode);
        }
        else // mode unsupported (neither planar neither RTG chunky linear)
#endif
        { vCleanSession("main : screenmode not supported", 0); goto _NewSession; }
    }
    else // planar (non-RTG Amiga Classic chipset planar modes)
    {
        // Get width, height, depth
#ifndef PLANAR
        iMode = 0xFF; // To remember this is a non-RTG screen to set up frame buffer format for ServerInit
#endif
        iWidth  = GetBitMapAttr(pActiveScreen -> RastPort.BitMap, BMA_WIDTH); // X size
        iHeight = GetBitMapAttr(pActiveScreen -> RastPort.BitMap, BMA_HEIGHT); // Y size
        iDepth  = GetBitMapAttr(pActiveScreen -> RastPort.BitMap, BMA_DEPTH); // Depth is in Bits / pixel
        
        // Force size to 2 X Y (each planar pixel will be sent as a 2 byte RGB4 chunky pixel)
        iSize = iWidth * iHeight * (bVVA ? 1 : 2); 
        
        // Screen memory is not linear addressable (but we rather have a raster per bitplane)
        pRaster = NULL;
        
        fprintf(fLog, "main : planar mode ");
    }

    fprintf(fLog, "@ 0x%08lX, %dx%d, %d %cpp, size %d\n", (ULONG) pRaster, iWidth, iHeight, iDepth, bPlanar ? 'b' : 'B', bPlanar ? iWidth * iHeight * iDepth / 8 : iSize);

    // Fast mode : 2 byte pixel buffer :
    // - planar Amiga screens
    //   OR
    // - Picasso96 AND 2 byte pixels
    //   OR
    // - 1 byte RTG CLUT pixels
#ifndef PLANAR
    bFastMode = ((bPlanar) || ((!CGXlib) && (iDepth == 2)) || ((!bPlanar) && (iDepth == 1)));
#else
    bFastMode = TRUE;
#endif

    // Allocate pBuffer (reference buffer for screen compare)
    // - with the screen size if no VVA, and ((under P96 and Depth < 3) OR if planar)
    // - with 4 byte / pixel size if under CGFx or Depth >= 3 or VVA
    if (bVVA)
    {
        if (!(pBuffer = malloc((unsigned) ((bPlanar || (iDepth == 1)) ? iWidth * iHeight : iWidth * iHeight * XDC_C_MAXDEPTH))))
            { vCleanSession("main : malloc() error", 0); goto _NewSession; }
    }
    else
        if (!(pBuffer = malloc((unsigned) (bFastMode ? iSize : iWidth * iHeight * XDC_C_MAXDEPTH))))
            { vCleanSession("main : malloc() error", 0); goto _NewSession; }
   
    uOpened |= XDC_C_VBUF;
    
    // ServerInit : prepare RGB encodings
    // if client is VVA, force encoding as BGR233 in all cases, as VVA only knows this encoding
    if (bVVA)
    {
        mSerInitMsg.format.bitsPerPixel = 8;
        mSerInitMsg.format.depth = 8;
        mSerInitMsg.format.bigEndian = FALSE;
        mSerInitMsg.format.trueColour = TRUE;
        mSerInitMsg.format.redMax = 7;
        mSerInitMsg.format.greenMax = 7;
        mSerInitMsg.format.blueMax = 3;
        mSerInitMsg.format.redShift = 0;
        mSerInitMsg.format.greenShift = 3;
        mSerInitMsg.format.blueShift = 6;
    }
    else
#ifndef PLANAR
        if (bFastMode)
        {
            switch(iMode)
            {
                case PIXFMT_RGB16PC : // TESTED OK /* HiColor16 (5 bit R, 6 bit G, 5 bit B), format: gggbbbbbrrrrrggg */
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 16;
                    mSerInitMsg.format.bigEndian = TRUE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 31;
                    mSerInitMsg.format.greenMax = 63;
                    mSerInitMsg.format.blueMax = 31;
                    mSerInitMsg.format.redShift = 11;
                    mSerInitMsg.format.greenShift = 5;
                    mSerInitMsg.format.blueShift = 0;
                    break;
                case PIXFMT_RGB15PC :        /* HiColor15 (5 bit each), format: gggbbbbb0rrrrrgg */
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 15;
                    mSerInitMsg.format.bigEndian = TRUE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 31;
                    mSerInitMsg.format.greenMax = 31;
                    mSerInitMsg.format.blueMax = 31;
                    mSerInitMsg.format.redShift = 10;
                    mSerInitMsg.format.greenShift = 5;
                    mSerInitMsg.format.blueShift = 0;
                    break;
                case PIXFMT_RGB16 :          /* HiColor16 (5 bit R, 6 bit G, 5 bit B), format: rrrrrggggggbbbbb */
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 16;
                    mSerInitMsg.format.bigEndian = FALSE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 31;
                    mSerInitMsg.format.greenMax = 63;
                    mSerInitMsg.format.blueMax = 31;
                    mSerInitMsg.format.redShift = 11;
                    mSerInitMsg.format.greenShift = 5;
                    mSerInitMsg.format.blueShift = 0;
                    break;
                case PIXFMT_RGB15 :          /* HiColor15 (5 bit each), format: 0rrrrrgggggbbbbb */
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 15;
                    mSerInitMsg.format.bigEndian = FALSE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 31;
                    mSerInitMsg.format.greenMax = 31;
                    mSerInitMsg.format.blueMax = 31;
                    mSerInitMsg.format.redShift = 10;
                    mSerInitMsg.format.greenShift = 5;
                    mSerInitMsg.format.blueShift = 0;
                    break;
                case PIXFMT_BGR16PC :        /* HiColor16 (5 bit R, 6 bit G, 5 bit B), format: gggrrrrrbbbbbggg */
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 16;
                    mSerInitMsg.format.bigEndian = TRUE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 31;
                    mSerInitMsg.format.greenMax = 63;
                    mSerInitMsg.format.blueMax = 31;
                    mSerInitMsg.format.redShift = 0;
                    mSerInitMsg.format.greenShift = 5;
                    mSerInitMsg.format.blueShift = 11;
                    break;
                case PIXFMT_BGR15PC :        /* HiColor15 (5 bit each), format: gggrrrrr0bbbbbbgg */
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 15;
                    mSerInitMsg.format.bigEndian = TRUE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 31;
                    mSerInitMsg.format.greenMax = 31;
                    mSerInitMsg.format.blueMax = 31;
                    mSerInitMsg.format.redShift = 0;
                    mSerInitMsg.format.greenShift = 5;
                    mSerInitMsg.format.blueShift = 10;
                    break;
                case PIXFMT_BGR16 :        /* HiColor16 (5 bit R, 6 bit G, 5 bit B), format: bbbbbggggggrrrrr */
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 16;
                    mSerInitMsg.format.bigEndian = FALSE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 31;
                    mSerInitMsg.format.greenMax = 63;
                    mSerInitMsg.format.blueMax = 31;
                    mSerInitMsg.format.redShift = 0;
                    mSerInitMsg.format.greenShift = 5;
                    mSerInitMsg.format.blueShift = 11;
                    break;
                case PIXFMT_BGR15 :        /* HiColor15 (5 bit each), format: 0bbbbbbgggggrrrrr */
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 15;
                    mSerInitMsg.format.bigEndian = FALSE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 31;
                    mSerInitMsg.format.greenMax = 31;
                    mSerInitMsg.format.blueMax = 31;
                    mSerInitMsg.format.redShift = 0;
                    mSerInitMsg.format.greenShift = 5;
                    mSerInitMsg.format.blueShift = 10;
                    break;
            
                case PIXFMT_LUT8 :          /* Chunky CLUT screens, or ... */
                case 0xFF :                 /* planar screens : send 2 byte pixels,aligned as per GetRGB4 : 0000RRRR GGGGBBBB*/
#endif
                {
                    mSerInitMsg.format.bitsPerPixel = 16;
                    mSerInitMsg.format.depth = 12;
                    mSerInitMsg.format.bigEndian = FALSE;
                    mSerInitMsg.format.trueColour = TRUE;
                    mSerInitMsg.format.redMax = 15;
                    mSerInitMsg.format.greenMax = 15;
                    mSerInitMsg.format.blueMax = 15;
                    mSerInitMsg.format.redShift = 8;
                    mSerInitMsg.format.greenShift = 4;
                    mSerInitMsg.format.blueShift = 0;
                }
#ifndef PLANAR
                    break;
                
                default :
                    { vCleanSession("main : RGB mode not supported", 0); goto _NewSession; }
                    break;
             }
        }
        else // CGFx or Depth > 2 or screen not planar
             // then use dumb 24 bpp mode, sent as 32 bpp because VNC protocol forbids 3 byte pixels
        {
            mSerInitMsg.format.bitsPerPixel = 32;
            mSerInitMsg.format.depth = 24;
            mSerInitMsg.format.bigEndian = bBig;
            mSerInitMsg.format.trueColour = TRUE;
            mSerInitMsg.format.redMax =
            mSerInitMsg.format.greenMax =
            mSerInitMsg.format.blueMax = 255;
            mSerInitMsg.format.redShift = iRShift;
            mSerInitMsg.format.greenShift = iGShift;
            mSerInitMsg.format.blueShift = iBShift;
        }
#endif
    
    // Finish serverinit and send it
    mSerInitMsg.framebufferWidth = iWidth;
    mSerInitMsg.framebufferHeight = iHeight;
    mSerInitMsg.nameLength = sizeof(XDC_ID);
 
    if (-1 == (send(iClientSocket, (UBYTE *) &mSerInitMsg, sizeof(mSerInitMsg), 0)))
    { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }    

    if (-1 == (send(iClientSocket, (UBYTE *) XDC_ID, sizeof(XDC_ID), 0)))
    { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }

    // Initial Screen update : header...
    mFBUpdMsg.type = rfbFramebufferUpdate;
    mFBUpdMsg.nRects = 1;
    if (-1 == (send(iClientSocket, (UBYTE *) &mFBUpdMsg, sizeof(mFBUpdMsg), 0)))
    { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }
    
    // Initial Screen update : 1 rectangle for whole screen...
    mFBRMsg.r.x = 0;
    mFBRMsg.r.y = 0;
    mFBRMsg.r.w = iWidth;
    mFBRMsg.r.h = iHeight;
    mFBRMsg.encoding = rfbEncodingRaw;

    if (-1 == (send(iClientSocket, (UBYTE *) &mFBRMsg, sizeof(mFBRMsg), 0)))
    { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }

    // Copy raster into buffer for send() and for ulterior compares
    if ((bFastMode) && (!bVVA)) // Planar or Pic96/2 byte pixels, no VVA
    {
#ifndef PLANAR
        if (bPlanar)   // planar screen
        {
#endif
            // We have to :
            // - ReadPixelArray8 the whole screen,
            // - make a (256 entries max.) color lookup table (CLUT) out of screen colormap
            // - Then encode each pixel with GetRGB4 and the CLUT, as a 16 bit pixel array to send to the client

            P2CBuffer = malloc((unsigned) (iWidth * iHeight)); // 1 byte per pixel : pen number

            // Allocate bitmap for temp. rastport for ReadPixelArray8()
            pBM = AllocBitMap((unsigned long) iWidth, (unsigned long) 1, (unsigned long) iDepth,
                              BMF_CLEAR | BMF_DISPLAYABLE, pActiveScreen -> RastPort.BitMap);    // Temp. BitMap for temp. RastPort

            // Initialize temporary rastport necessary for ReadPixelArray8()
            InitRastPort(&TempRP);
            TempRP.Layer = NULL;
            TempRP.BitMap = pBM;
            
            // Fill color lookup table with ready-to-send 16 bit RGB values
            jCnt = 1 << iDepth;
            for (iCnt = 0 ; iCnt < jCnt ; iCnt++)
            {
                 ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iCnt);
                 ULONG wR = (lColor >> 8) & 0x0f;
                 ULONG wG = (lColor >> 4) & 0x0f;
                 ULONG wB = (lColor >> 0) & 0x0f;
                 CLUT[iCnt] = (wG << 4 | wB) << 8 | wR;
            }
                
            // Read our chunky clut-index array from planar raster
            ReadPixelArray8(&(pActiveScreen -> RastPort),
                            (unsigned long) 0, (unsigned long) 0, (unsigned long) iWidth - 1, (unsigned long) iHeight - 1,
                            P2CBuffer,
                            &TempRP
                           );

            // Make up the RBG chunky buffer
            for (jCnt = 0 ; jCnt < iHeight ; jCnt++)
               for (iCnt = 0 ; iCnt < iWidth ; iCnt++)
                   *((UWORD *) (pBuffer + (iCnt + jCnt * iWidth) * 2)) = CLUT[P2CBuffer[iCnt + jCnt * iWidth]];
#ifndef PLANAR
        }
        else
            if (iDepth == 1) // 8 bit pixels, CLUT, RTG chunky
            {
                // Fill color lookup table with 256 ready-to-send 16 bit RGB values
                for (iCnt = 0 ; iCnt < 256 ; iCnt++)
                {
                     ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iCnt);
                     ULONG wR = (lColor >> 8) & 0x0f;
                     ULONG wG = (lColor >> 4) & 0x0f;
                     ULONG wB = (lColor >> 0) & 0x0f;
                     CLUT[iCnt] = (wG << 4 | wB) << 8 | wR;
                }

                // Make up the RBG chunky buffer
                for (jCnt = 0 ; jCnt < iHeight ; jCnt++)
                   for (iCnt = 0 ; iCnt < iWidth ; iCnt++)
                       *((UWORD *) (pBuffer + (iCnt + jCnt * iWidth) * 2)) = CLUT[pRaster[iCnt + jCnt * iWidth]];
            }
            else // Picasso 96 2 byte pixel screen
            {
                // simply send raster memory (as 16 bit pixels)
                memcpy(pBuffer, pRaster, (unsigned) iSize);
            }
#endif        
    }
    else // neither planar nor CLUT nor Pic96/2byte pixels (thus 3 or more byte pixels or CGFx > 1 Bpp),
         // or VVA in all cases
    {
#ifndef PLANAR
        if (bPlanar)   // planar screen
        {
#endif
            // We have to :
            // - ReadPixelArray8 the whole screen,
            // - make a (256 entries max.) color lookup table (CLUT) out of screen colormap
            // - Then encode each pixel with GetRGB4 and the CLUT, as a 8 bit BGR233 pixel array to send to the client

            P2CBuffer = malloc((unsigned) (iWidth * iHeight)); // 1 byte per pixel : pen number

            // Allocate bitmap for temp. rastport for ReadPixelArray8()
            pBM = AllocBitMap((unsigned long) iWidth, (unsigned long) 1, (unsigned long) iDepth,
                              BMF_CLEAR | BMF_DISPLAYABLE, pActiveScreen -> RastPort.BitMap);    // Temp. BitMap for temp. RastPort

            // Initialize temporary rastport necessary for ReadPixelArray8()
            InitRastPort(&TempRP);
            TempRP.Layer = NULL;
            TempRP.BitMap = pBM;
            
            // Fill color lookup table with ready-to-send BGR233 values
            jCnt = 1 << iDepth;
            for (iCnt = 0 ; iCnt < jCnt ; iCnt++)
            {
                 ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iCnt);
                 UBYTE wR = (lColor >> 9) & 0x07;
                 UBYTE wG = (lColor >> 5) & 0x07;
                 UBYTE wB = (lColor >> 2) & 0x03;
                 CLUT8[iCnt] = (wB << 3 | wG) << 3 | wR;
            }
                
            // Read our chunky clut-index array from planar raster
            ReadPixelArray8(&(pActiveScreen -> RastPort),
                            (unsigned long) 0, (unsigned long) 0, (unsigned long) iWidth - 1, (unsigned long) iHeight - 1,
                            P2CBuffer,
                            &TempRP
                           );

            // Make up the BGR233 chunky buffer
            for (jCnt = 0 ; jCnt < iHeight ; jCnt++)
               for (iCnt = 0 ; iCnt < iWidth ; iCnt++)
                   pBuffer[iCnt + jCnt * iWidth] = CLUT8[P2CBuffer[iCnt + jCnt * iWidth]];
            
#ifndef PLANAR
        }
        else
            if (iDepth == 1) // 8 bit pixels, CLUT, RTG chunky
            {
                // Fill color lookup table with 256 ready-to-send BGR233 values
                for (iCnt = 0 ; iCnt < 256 ; iCnt++)
                {
                     ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iCnt);
                     UBYTE wR = (lColor >> 9) & 0x07;
                     UBYTE wG = (lColor >> 5) & 0x07;
                     UBYTE wB = (lColor >> 2) & 0x03;
                     CLUT8[iCnt] = (wB << 3 | wG) << 3 | wR;
                }

                // Make up the RBG chunky buffer
                for (jCnt = 0 ; jCnt < iHeight ; jCnt++)
                   for (iCnt = 0 ; iCnt < iWidth ; iCnt++)
                       pBuffer[iCnt + jCnt * iWidth] = CLUT8[pRaster[iCnt + jCnt * iWidth]];
            }
            else // All RTG modes with depth > 1 byte
            {
                // We have to make 4 byte pixels out of the raster
                ReadPixelArray(pBuffer,     // Buffer
                       (UWORD) 0, (UWORD) 0,        // Dest X/Y in buffer
                       (UWORD) (iWidth * XDC_C_MAXDEPTH),     // Byte width of buffer
                       & (pActiveScreen -> RastPort),
                       (UWORD) 0, (UWORD) 0,         // Source X/Y
                       (UWORD) iWidth, (UWORD) iHeight,  // Source w/h
                       (UBYTE) RECTFMT_ARGB
                       );

                if (bVVA) // Make up the BGR233 buffer
                    for (jCnt = 0 ; jCnt < iHeight ; jCnt++)
                       for (iCnt = 0 ; iCnt < iWidth ; iCnt++)
                            pBuffer[iCnt + jCnt * iWidth] =
                                (pBuffer[(iCnt  + jCnt * iWidth) * 4 + 1] & 0xE0) >> 5 |
                                (pBuffer[(iCnt  + jCnt * iWidth) * 4 + 2] & 0xE0) >> 2 |
                                (pBuffer[(iCnt  + jCnt * iWidth) * 4 + 3] & 0xC0) >> 0;
            }
#endif
    }

    // Send buffer (by uLimit packet size if set).
    uTotal = (bVVA ? iWidth * iHeight : (bFastMode ? iSize : iWidth * iHeight * XDC_C_MAXDEPTH));
    uSize = (uTotal > uLimit ? uLimit : uTotal);
    for (uCnt = 0 ; uCnt < uTotal ; uCnt += uSize)
        if (-1 == (send(iClientSocket, (UBYTE *) pBuffer + uCnt, (int) uSize, 0)))
        { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }

#ifndef PLANAR
    // if CGFx or depth > 2, reallocate the buffer to actual raster size (eg .3 byte / pixel instead of 4)
    if ((!bFastMode) && (iDepth != 4) && ((iDepth == 3) || (CGXlib)))
    {
        // Reallocate the buffer, resizing it to what's really necessary
        if (!(pBuffer = realloc(pBuffer, (unsigned) iSize)))
        { vCleanSession("main : realloc() error", 0); goto _NewSession; }
       
        // Byte copy the screen into the buffer for fast compare
        memcpy(pBuffer, pRaster, (unsigned) iSize);
    }

    if ((bFastMode) && (!bPlanar) && (bVVA) && (iDepth == 2))
    {
        // Reallocate the buffer, resizing it to what's really necessary
        if (!(pBuffer = realloc(pBuffer, (unsigned) iSize)))
        { vCleanSession("main : realloc() error", 0); goto _NewSession; }
       
        // Byte copy the screen into the buffer for fast compare
        memcpy(pBuffer, pRaster, (unsigned) iSize);
    }

    // if planar, reallocate the buffer to iWidth x iDepth for ReadPixelArray8() and store chunky raster
    if (bPlanar)
    {
#endif
        // Reallocate the buffer, resizing it to what's really necessary
        if ((!bVVA) && (!(pBuffer = realloc(pBuffer, (unsigned) (iWidth * iHeight)))))
        { vCleanSession("main : realloc() error", 0); goto _NewSession; }
       
        // Byte copy the screen into the buffer for fast compare
        memcpy(pBuffer, P2CBuffer, (unsigned) (iWidth * iHeight));
        
        // Free P2CBuffer, which from now on is useless
        free(P2CBuffer);
        P2CBuffer = NULL;
#ifndef PLANAR    
    }

    // if RTG chunky CLUT, reallocate the buffer to iWidth x iDepth and copy screen raster into it
    if ((!bPlanar) && (iDepth == 1))
    {
        // Reallocate the buffer, resizing it to what's really necessary
        if ((!bVVA) && (!(pBuffer = realloc(pBuffer, (unsigned) (iWidth * iHeight)))))
        { vCleanSession("main : realloc() error", 0); goto _NewSession; }
       
        // Byte copy the screen into the buffer for fast compare
        memcpy(pBuffer, pRaster, (unsigned) (iWidth * iHeight));
    }
#endif
    
    // Preset framebuffer update message X and Y size to XDC_TILE
    mFBRMsg.r.w =
    mFBRMsg.r.h = XDC_TILE;

    // Release a key to the client socket so that child process can grab it
    if (-1 == (iDuplicateSocketKey = ReleaseCopyOfSocket(iClientSocket, UNIQUE_ID)))
    { vCleanSession("main : ReleaseCopyOfSocket() error", Errno()); goto _NewSession; }

    // Create incoming messages handling child process
    if (!CreateNewProcTags(
          NP_Entry, vProcessIncomes,
          NP_StackSize, 32000,
          NP_Name, "AmiVNC handler",
          TAG_DONE
       ))
    { vCleanSession("main : CreateNewProcTags() error", 0); goto _NewSession; }
        
    // Execute user command on accepted connection (if any)
    if (pPreStart)
    {
        char cCommand[128];
        sprintf(cCommand, pPreStart, Inet_NtoA(cliAddr.sin_addr.s_addr));
        SystemTags(cCommand, SYS_Asynch, TRUE, TAG_DONE);
    }
    
    // Session is truly opened now
    bSession = TRUE;
    
    // While not end of session (by child or by ourself)
    while (!bDie)
    {
        // If a delay is required, then wait
        if (iDelay) Delay(iDelay);

        // Search for active screen change
        uLock = LockIBase(0);
        pNewScreen = IntuitionBase -> FirstScreen;
        UnlockIBase(uLock);

        if (pNewScreen != pActiveScreen)
        {
#ifndef PLANAR
            if (bPlanar) // Original Amiga screens
            {
#endif
                // Check if new screen is also planar and has same dimensions
                if ((GetBitMapAttr(pNewScreen -> RastPort.BitMap, BMA_FLAGS) & BMF_STANDARD)
                 && (iWidth == GetBitMapAttr(pNewScreen -> RastPort.BitMap, BMA_WIDTH)) // X size
                 && (iHeight == GetBitMapAttr(pNewScreen -> RastPort.BitMap, BMA_HEIGHT))) // Y size
                {
                    // Grab new screen
                    pActiveScreen = pNewScreen;
                    fprintf(fLog, "main : Screen set to [%s]\n", pActiveScreen -> Title);
                    
                    // Update depth
                    iDepth = GetBitMapAttr(pActiveScreen -> RastPort.BitMap, BMA_DEPTH); // Depth is in Bits / pixel
                    
                    // Update CLUT (or CLUT8 if bVVA)
                    jCnt = 1 << iDepth;
                    if (bVVA) // BGR233
                        for (iCnt = 0 ; iCnt < jCnt ; iCnt++)
                        {
                             ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iCnt);
                             UBYTE wR = (lColor >> 9) & 0x07;
                             UBYTE wG = (lColor >> 5) & 0x07;
                             UBYTE wB = (lColor >> 2) & 0x03;
                             CLUT8[iCnt] = (wB << 3 | wG) << 3 | wR;
                        }
                    else      // RGB16PC
                        for (iCnt = 0 ; iCnt < jCnt ; iCnt++)
                        {
                             ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iCnt);
                             ULONG wR = (lColor >> 8) & 0x0f;
                             ULONG wG = (lColor >> 4) & 0x0f;
                             ULONG wB = (lColor >> 0) & 0x0f;
                             CLUT[iCnt] = (wG << 4 | wB) << 8 | wR;
                        }
                
                    // Force client screen update (almost :-)
                    memset(pBuffer, iWidth * iHeight, 0xFF);
                }
                else
                { vCleanSession("main : planar screenmode switch not allowed", 0); goto _NewSession; }
#ifndef PLANAR
            } // bPlanar
            else // Chunky (RTG)
            {
                if (GetCyberMapAttr(pNewScreen -> RastPort.BitMap, CYBRMATTR_ISCYBERGFX)
                 && GetCyberMapAttr(pNewScreen -> RastPort.BitMap, CYBRMATTR_ISLINEARMEM)
                 && iWidth == GetCyberMapAttr(pNewScreen -> RastPort.BitMap, CYBRMATTR_WIDTH)
                 && iHeight == GetCyberMapAttr(pNewScreen -> RastPort.BitMap, CYBRMATTR_HEIGHT)
                 && iDepth == GetCyberMapAttr(pNewScreen -> RastPort.BitMap, CYBRMATTR_BPPIX))
                {
                    // Grab new screen
                    pActiveScreen = pNewScreen;
                    fprintf(fLog, "main : Screen set to [%s]\n", pActiveScreen -> Title);
                    UnLockBitMap(LockBitMapTags(pActiveScreen -> RastPort.BitMap,
                         LBMI_BASEADDRESS, (ULONG) &uCnt,
                         TAG_DONE));
                    pRaster = (UBYTE *) uCnt;
                
                    // If depth == 1, update CLUT
                    if (iDepth == 1)
                    {
                        if (bVVA) // BGR233
                            for (iCnt = 0 ; iCnt < 256 ; iCnt++)
                            {
                                 ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iCnt);
                                 UBYTE wR = (lColor >> 9) & 0x07;
                                 UBYTE wG = (lColor >> 5) & 0x07;
                                 UBYTE wB = (lColor >> 2) & 0x03;
                                 CLUT8[iCnt] = (wB << 3 | wG) << 3 | wR;
                            }
                        else      // RGB16PC
                            for (iCnt = 0 ; iCnt < 256 ; iCnt++)
                            {
                                 ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iCnt);
                                 ULONG wR = (lColor >> 8) & 0x0f;
                                 ULONG wG = (lColor >> 4) & 0x0f;
                                 ULONG wB = (lColor >> 0) & 0x0f;
                                 CLUT[iCnt] = (wG << 4 | wB) << 8 | wR;
                            }
                
                        // Force client screen update (almost :-)
                        memset(pBuffer, iWidth * iHeight, 0xFF);
                    }
                }
                else
                { vCleanSession("main : chunky screenmode switch not allowed", 0); goto _NewSession; }
            } // Chunky
#endif
        } // pNewScreen != pActiveScreen
                
        // Search for changes in screen...
        for (jCnt = 0 ; jCnt < iHeight; jCnt += XDC_TILE)
        {
            for (iCnt = 0 ; iCnt < iWidth ; iCnt += XDC_TILE)
            {
                register int iYtile, iXtile;
                BOOL bChange = FALSE;
#ifndef PLANAR    
                if (!bPlanar) // chunky linear-addressed rasters
                    for (iYtile = 0 ; iYtile < XDC_TILE ; iYtile ++)
                    {
                        if (memcmp((UBYTE *) (pBuffer) + iDepth * (iCnt + (jCnt + iYtile) * iWidth),
                           (UBYTE *) (pRaster) + iDepth * (iCnt + (jCnt + iYtile) * iWidth),
                           (unsigned) (XDC_TILE * iDepth)))
                        {
                            memcpy((UBYTE *) (pBuffer) + iDepth * (iCnt + (jCnt + iYtile) * iWidth),
                               (UBYTE *) (pRaster) + iDepth * (iCnt + (jCnt + iYtile) * iWidth),
                               (unsigned) (XDC_TILE * iDepth));

                            bChange = TRUE;
                        }
                    }
                else // planar rasters
                {
#endif
                    // 0 - Once every 10 scans, refresh palette (we have no event here to say palette has changed)
                    if (!(iUpdate++ % 10))
                    {
                        int nColor = 1 << iDepth, iColor;
                        if (bVVA)
                            for (iColor = 0 ; iColor < 256 ; iColor++)
                            {
                                 ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iColor);
                                 UBYTE wR = (lColor >> 9) & 0x07;
                                 UBYTE wG = (lColor >> 5) & 0x07;
                                 UBYTE wB = (lColor >> 2) & 0x03;
                                 CLUT8[iColor] = (wB << 3 | wG) << 3 | wR;
                            }
                        else
                            for (iColor = 0 ; iColor < nColor ; iColor++)
                            {
                                 ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iColor);
                                 ULONG wR = (lColor >> 8) & 0x0f;
                                 ULONG wG = (lColor >> 4) & 0x0f;
                                 ULONG wB = (lColor >> 0) & 0x0f;
                                 CLUT[iColor] = (wG << 4 | wB) << 8 | wR;
                            }
                    }
                
                    // 1 - get a chunky 32x32 1 byte clut entry buffer
                    ReadPixelArray8(&(pActiveScreen -> RastPort),
                                    (unsigned long) iCnt, (unsigned long) jCnt, (unsigned long) iCnt + XDC_TILE - 1, (unsigned long) jCnt + XDC_TILE - 1,
                                    uCTile,
                                    &TempRP
                                   );
                               
                    // 2 - then compare it to reference buffer. if !=, remember to update client and store new.
                    for (iYtile = 0 ; iYtile < XDC_TILE ; iYtile ++)
                    {
                        if (memcmp((UBYTE *) (pBuffer) + (iCnt + (jCnt + iYtile) * iWidth),
                                   (UBYTE *) (uCTile) + (iYtile * XDC_TILE),
                                   XDC_TILE))
                        {
                            memcpy((UBYTE *) (pBuffer) + (iCnt + (jCnt + iYtile) * iWidth),
                                   (UBYTE *) (uCTile) + (iYtile * XDC_TILE),
                                   XDC_TILE);
                               
                            bChange = TRUE;
                        }
                    }
#ifndef PLANAR
                }
#endif
        
                // If change found, send the tile
                if (bChange)
                {
                    if (-1 == (send(iClientSocket, (UBYTE *) &mFBUpdMsg, sizeof(mFBUpdMsg), 0)))
                    { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }

                    mFBRMsg.r.x = iCnt;
                    mFBRMsg.r.y = jCnt;
   
                    // Send framebuffer update header
                    if (-1 == (send(iClientSocket, (UBYTE *) &mFBRMsg, sizeof(mFBRMsg), 0)))
                    { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }

                    // Send update data
#ifndef PLANAR
                    if (bPlanar)
                    {
#endif
                        if (bVVA)
                        {
                            for (iYtile = 0 ; iYtile < XDC_TILE ; iYtile ++)
                               for (iXtile = 0 ; iXtile < XDC_TILE ; iXtile++)
                                   uTile[iXtile + iYtile * XDC_TILE] = CLUT8[uCTile[iXtile + iYtile * XDC_TILE]];
            
                            // Send framebuffer update pixel data
                            if (-1 == (send(iClientSocket, (UBYTE *) uTile, (LONG) XDC_TILE * XDC_TILE, 0)))
                            { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }
                        }
                        else
                        {
                            for (iYtile = 0 ; iYtile < XDC_TILE ; iYtile ++)
                               for (iXtile = 0 ; iXtile < XDC_TILE ; iXtile++)
                                   *((UWORD *) (uTile + (iXtile + iYtile * XDC_TILE) * 2)) = CLUT[uCTile[iXtile + iYtile * XDC_TILE]];
            
                            // Send framebuffer update pixel data
                            if (-1 == (send(iClientSocket, (UBYTE *) uTile, (LONG) XDC_TILE * XDC_TILE * 2, 0)))
                            { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }
                        }
#ifndef PLANAR
                    }
                    else
                        if ((!bVVA) && (bFastMode) && (iDepth == 2)) // 2 byte pixels under Pic96 : send directly from buffer (2 is idepth)
                        {
                            for (iYtile = 0 ; iYtile < XDC_TILE ; iYtile ++)
                               memcpy(uTile + XDC_TILE * iYtile * iDepth,
                                      pRaster + iDepth * (iCnt + (jCnt + iYtile) * iWidth),
                                      (unsigned) (XDC_TILE * iDepth));
        
                            // Send framebuffer update pixel data
                            if (-1 == (send(iClientSocket, (UBYTE *) uTile, (LONG) XDC_TILE * XDC_TILE * 2, 0)))
                            { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }
                        }
                        else
                            if ((!bVVA) && (iDepth == 1)) // RTG chunky 1 byte pixels
                            {
                                // Every 10 update, refresh color lookup table with 256 ready-to-send 16 bit RGB values
                                if (!(iUpdate++ % 10))
                                {
                                    int iColor;
                                    for (iColor = 0 ; iColor < 256 ; iColor++)
                                    {
                                         ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iColor);
                                         ULONG wR = (lColor >> 8) & 0x0f;
                                         ULONG wG = (lColor >> 4) & 0x0f;
                                         ULONG wB = (lColor >> 0) & 0x0f;
                                         CLUT[iColor] = (wG << 4 | wB) << 8 | wR;
                                    }
                                }

                                // Make up the RBG chunky buffer
                                for (iYtile = 0 ; iYtile < XDC_TILE ; iYtile ++)
                                   for (iXtile = 0 ; iXtile < XDC_TILE ; iXtile ++)
                                       *((UWORD *) (uTile + (iXtile + iYtile * XDC_TILE) * 2)) = CLUT[pBuffer[iCnt + iXtile + (jCnt + iYtile) * iWidth]];
        
                                // Send framebuffer update pixel data
                                if (-1 == (send(iClientSocket, (UBYTE *) uTile, (LONG) XDC_TILE * XDC_TILE * 2, 0)))
                                { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }

                            }
                            else // Dumb screenmode or VVA
                            {
                                if (iDepth > 1)
                                {
                                    ReadPixelArray(uTile,     // Buffer
                                               0, 0,        // Dest X/Y in buffer
                                               XDC_TILE * XDC_C_MAXDEPTH,
                                               &(pActiveScreen -> RastPort),
                                               iCnt, jCnt,         // Source X/Y
                                               XDC_TILE, XDC_TILE,  // Source w/h
                                               RECTFMT_ARGB
                                               );
                    
                                    if (bVVA)
                                    {
                                        // Make the BGR233 buffer into uTile
                                        for (iYtile = 0 ; iYtile < XDC_TILE ; iYtile ++)
                                           for (iXtile = 0 ; iXtile < XDC_TILE ; iXtile ++)
                                        {
                                            uTile[iXtile + iYtile * XDC_TILE] =
                                                (uTile[(iXtile  + iYtile * XDC_TILE) * 4 + 1] & 0xE0) >> 5 |
                                                (uTile[(iXtile  + iYtile * XDC_TILE) * 4 + 2] & 0xE0) >> 2 |
                                                (uTile[(iXtile  + iYtile * XDC_TILE) * 4 + 3] & 0xC0) >> 0;
                                        }
            
                                        // Send framebuffer update pixel data
                                        if (-1 == (send(iClientSocket, (UBYTE *) uTile, (LONG) XDC_TILE * XDC_TILE, 0)))
                                        { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }
                                    }
                                    else
                                    {
                                        // Send framebuffer update pixel data
                                        if (-1 == (send(iClientSocket, (UBYTE *) uTile, (LONG) XDC_TILE * XDC_TILE * XDC_C_MAXDEPTH, 0)))
                                        { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }
                                    }
                                }
                                else // iDepth == 1 and VVA (Depth == 1 and not VVA is trated above)
                                {
                                    // Once every 10 scans, refresh palette (we have no event here to say palette has changed)
                                    if (!(iUpdate++ % 10))
                                    {
                                        int iColor;
                                        for (iColor = 0 ; iColor < 256 ; iColor++)
                                        {
                                             ULONG lColor = GetRGB4(pActiveScreen -> ViewPort.ColorMap, (long) iColor);
                                             UBYTE wR = (lColor >> 9) & 0x07;
                                             UBYTE wG = (lColor >> 5) & 0x07;
                                             UBYTE wB = (lColor >> 2) & 0x03;
                                             CLUT8[iColor] = (wB << 3 | wG) << 3 | wR;
                                        }
                                    }
                
                                    // Make up the RBG chunky buffer
                                    for (iYtile = 0 ; iYtile < XDC_TILE ; iYtile ++)
                                       for (iXtile = 0 ; iXtile < XDC_TILE ; iXtile ++)
                                           uTile[iXtile + iYtile * XDC_TILE] = CLUT8[pBuffer[iCnt + iXtile + (jCnt + iYtile) * iWidth]];
        
                                    // Send framebuffer update pixel data
                                    if (-1 == (send(iClientSocket, (UBYTE *) uTile, (LONG) XDC_TILE * XDC_TILE, 0)))
                                    { vCleanSession(XDC_SEND, Errno()); goto _NewSession; }
                                }
                            }
#endif
                }
            }
        }
    }
    
    vCleanSession("main : Session stop (child stopped)", 0);
    goto _NewSession;
}
