#include <wing.h> 
#include <ddraw.h>
#include <dsetup.h>
#include <dsound.h>


#define dd(x) \
  { \
    HRESULT h = (x); \
    if (h != DD_OK) { \
      DDError(h, #x, __FILE__, __LINE__); \
    } \
  }

global void DDError(HRESULT hError, char* pcFunction, char* pcFile, int iLine);


////////////////////////////////////////////////////////////////////////////////

class C64DisplayBase {
public:

  // destructor
  virtual ~C64DisplayBase() {}

  // draw the current frame
  virtual void OnFrame() = 0;

  // set mode and colors
  virtual byte* GetC64Screen(int iNewC64Width, int iNewC64Height, const BYTE* pbColor) = 0;

  // set window size and/or position
  virtual void Resize(const RECT& /*rcSrc*/, const RECT& /*rcDest*/) {
  }

  // realize palette
  virtual flag RealizePalette(flag /*fNewForeground*/) {
    return false;
  }

};

////////////////////////////////////////////////////////////////////////////////

class C64DisplayWinG : public C64DisplayBase {

  HMODULE hWinGLib;
  HDC hWinGDC;
  HBITMAP hBitmap;
  HBITMAP hOldBitmap;

  HWND hwnd;
  HDC hdc;
  HPALETTE hPalette;
  HPALETTE hOldPalette;

  BOOL (WINGAPI* pfnWinGBitBlt)(HDC, int, int, int, int, HDC, int, int);
  BOOL (WINGAPI* pfnWinGStretchBlt)(HDC, int, int, int, int, HDC, int, int, int, int);

  int iWidth;
  int iHeight;
  int iC64X;
  int iC64Y;
  int iC64Width;
  int iC64Height;
  flag fUpdate;
  flag fStretch;

public:

  // constructor
  C64DisplayWinG(HWND hwndNew) {

    hWinGLib = NULL;
    hWinGDC = NULL;
    hBitmap = NULL;
    hOldBitmap = NULL;

    hwnd = hwndNew;
    hdc = NULL;
    hPalette = NULL;
    hOldPalette = NULL;

    pfnWinGBitBlt = NULL;
    pfnWinGStretchBlt = NULL;

    iWidth = 0;
    iHeight = 0;
    iC64X = 0;
    iC64Y = 0;
    iC64Width = 0;
    iC64Height = 0;
    fUpdate = false;
    fStretch = false;

  };

  // destructor
  virtual ~C64DisplayWinG() {

    if (hdc != NULL) {
      if (hOldPalette != NULL) {
        win(SelectPalette(hdc, hOldPalette, false));
        hOldPalette = NULL;
      }
      win(ReleaseDC(hwnd, hdc));
      hdc = NULL;
    }

    if (hPalette != NULL) {
      win(DeleteObject(hPalette));
      hPalette = NULL;
    }

    if (hWinGDC != NULL) {
      if (hOldBitmap != NULL) {
        win(SelectObject(hWinGDC, hOldBitmap));
        hOldBitmap = NULL;
      }
      win(DeleteDC(hWinGDC));
      hWinGDC = NULL;
    }

    if (hBitmap != NULL) {
      win(DeleteObject(hBitmap));
      hBitmap = NULL;
    }

    if (hWinGLib != NULL) {
      win(FreeLibrary(hWinGLib));
      hWinGLib = NULL;
    }

  }

  // draw the current frame
  void OnFrame() {

    // blit the graphics
    if (fUpdate) {
      if (fStretch) {
        pfnWinGStretchBlt(hdc, 0, 0, iWidth, iHeight, hWinGDC, iC64X, iC64Y, iC64Width, iC64Height);
      } else {
        pfnWinGBitBlt(hdc, 0, 0, iWidth, iHeight, hWinGDC, iC64X, iC64Y);
      }
    }

  }

  // get pointer to C64 bitmap
  byte* GetC64Screen(int iNewC64Width, int iNewC64Height, const BYTE* pbColor) {

    // load WinG library
    win(hWinGLib = LoadLibrary("wing32.dll"));

    // set variables for OnFrame()
    win(pfnWinGBitBlt = (BOOL (WINGAPI*)(HDC, int, int, int, int, HDC, int, int))GetProcAddress(hWinGLib, "WinGBitBlt"));
    win(pfnWinGStretchBlt = (BOOL (WINGAPI*)(HDC, int, int, int, int, HDC, int, int, int, int))GetProcAddress(hWinGLib, "WinGStretchBlt"));

    // get recommended WinG bitmap format
    struct {
      BITMAPINFOHEADER InfoHeader;
      RGBQUAD aRGB[256];
    } Info;
    memset(&Info, 0, sizeof Info);
    BOOL (WINGAPI* pfnWinGRecommendDIBFormat)(BITMAPINFO FAR*);
    win(pfnWinGRecommendDIBFormat = (BOOL (WINGAPI*)(BITMAPINFO FAR*))GetProcAddress(hWinGLib, "WinGRecommendDIBFormat"));
    win(pfnWinGRecommendDIBFormat((BITMAPINFO*)&Info));
    if (Info.InfoHeader.biPlanes != 1 || Info.InfoHeader.biBitCount != 8) {
      error("WinG recommends unknown DIB format\nCan handle 8 bit only");
    }

    // set size and colors
    Info.InfoHeader.biWidth = iNewC64Width;
    Info.InfoHeader.biHeight = -iNewC64Height; // top-down DIB
    for (int i = 0; i < 256; i++) {
      int iColor = (i & 15) * 3;
      Info.aRGB[i].rgbRed = pbColor[iColor + 0];
      Info.aRGB[i].rgbGreen = pbColor[iColor + 1];
      Info.aRGB[i].rgbBlue = pbColor[iColor + 2];
    }

    // create WinG DC
    HDC (WINGAPI* pfnWinGCreateDC)();
    win(pfnWinGCreateDC = (HDC (WINGAPI*)())GetProcAddress(hWinGLib, "WinGCreateDC"));
    win(hWinGDC = pfnWinGCreateDC());
    UINT (WINGAPI* pfnWinGSetDIBColorTable)(HDC, UINT, UINT, RGBQUAD const FAR*);
    win(pfnWinGSetDIBColorTable = (UINT (WINGAPI*)(HDC, UINT, UINT, RGBQUAD const FAR*))GetProcAddress(hWinGLib, "WinGSetDIBColorTable"));
    pfnWinGSetDIBColorTable(hWinGDC, 0, 256, Info.aRGB);

    // create WinG bitmap
    byte* pbBitmap;
    HBITMAP (WINGAPI* pfnWinGCreateBitmap)(HDC, BITMAPINFO const FAR*, void FAR* FAR*);
    win(pfnWinGCreateBitmap = (HBITMAP (WINGAPI*)(HDC, BITMAPINFO const FAR*, void FAR* FAR*))GetProcAddress(hWinGLib, "WinGCreateBitmap"));
    win(hBitmap = pfnWinGCreateBitmap(hWinGDC, (BITMAPINFO*)&Info, (void**)&pbBitmap));
    assert(pbBitmap != NULL);

    // select WinG bitmap into WinG DC
    win(hOldBitmap = (HBITMAP)SelectObject(hWinGDC, hBitmap));

    // get window DC
    assert((GetClassLong(hwnd, GCL_STYLE) & CS_OWNDC) != 0);
    win(hdc = GetDC(hwnd));
    if ((GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) != 0) {

      // copy C64 colors
      struct {
        WORD Version;
        WORD NumEntries;
        PALETTEENTRY aEntry[256];
      } pal;
      memset(&pal, 0, sizeof pal);
      pal.Version = 0x0300;
      pal.NumEntries = 256;
      for (int i = 96; i < 128; i++) {
        int iColor = (i & 15) * 3;
        pal.aEntry[i].peRed = pbColor[iColor + 0];
        pal.aEntry[i].peGreen = pbColor[iColor + 1];
        pal.aEntry[i].peBlue = pbColor[iColor + 2];
        pal.aEntry[i].peFlags = PC_NOCOLLAPSE;
      }

      // fill in gaps to make background images look better
      i = 10;
      for (int iRed = 32; iRed < 6 * 32; iRed += 32) {
        for (int iGreen = 32; iGreen < 6 * 32; iGreen += 32) {
          for (int iBlue = 64; iBlue < 6 * 32; iBlue += 32) {
            while (pal.aEntry[i].peFlags != 0) {
              i++;
              assert(i < 256);
            }
            pal.aEntry[i].peRed = (byte)iRed;
            pal.aEntry[i].peGreen = (byte)iGreen;
            pal.aEntry[i].peBlue = (byte)iBlue;
            pal.aEntry[i].peFlags = PC_NOCOLLAPSE;
          }
        }
      }

      // create the palette
      win(hPalette = CreatePalette((LOGPALETTE*)&pal));
      win(hOldPalette = SelectPalette(hdc, hPalette, false));

    }

    // return pointer to 8 bit C64 screen
    return pbBitmap;

  }

  // set window size and/or position
  void Resize(const RECT& rcWindow, const RECT& rcC64) {

    // copy size and position
    iWidth = rcWindow.right - rcWindow.left;
    iHeight = rcWindow.bottom - rcWindow.top;
    iC64X = rcC64.left;
    iC64Y = rcC64.top;
    iC64Width = rcC64.right - rcC64.left;
    iC64Height = rcC64.bottom - rcC64.top;

    // check if screen is visible
    fUpdate = !IsIconic(hwnd) && iWidth > 0 && iHeight > 0;

    // use StretchBlt only if necessary
    fStretch = (iWidth != iC64Width) || (iHeight != iC64Height);

  }

  // realize palette
  flag RealizePalette(flag) {

    if (hPalette != NULL) {
      win(SelectPalette(hdc, hPalette, false));
      verify(::RealizePalette(hdc) != GDI_ERROR);
      InvalidateRect(hwnd, NULL, false);
      return true;
    }

    return false;
  }

};

////////////////////////////////////////////////////////////////////////////////

class C64DisplayDIBSection : public C64DisplayBase {

  HWND hwnd;
  HDC hdc;
  HPALETTE hPalette;
  HPALETTE hOldPalette;
  struct {
    BITMAPINFOHEADER bmih;
    RGBQUAD aRGB[256];
  } bmi;
  HBITMAP hDIB;
  byte* pbBitmap;
  int iMoveDown;

  int iScanLines;
  int iWidth;
  int iHeight;
  int iC64X;
  int iC64Y;
  int iC64Width;
  int iC64Height;
  flag fUpdate;
  flag fStretch;

public:

  // constructor
  C64DisplayDIBSection(HWND hwndNew) {

    hwnd = hwndNew;
    hdc = NULL;
    hPalette = NULL;
    hOldPalette = NULL;
    memset(&bmi, 0, sizeof bmi);
    hDIB = NULL;
    pbBitmap = NULL;
    iMoveDown = 0;

    iScanLines = 0;
    iWidth = 0;
    iHeight = 0;
    iC64X = 0;
    iC64Y = 0;
    iC64Width = 0;
    iC64Height = 0;
    fUpdate = false;
    fStretch = false;

  };

  // destructor
  virtual ~C64DisplayDIBSection() {

    if (hPalette != NULL) {
      win(SelectPalette(hdc, hOldPalette, false));
      win(DeleteObject(hPalette));
      hPalette = NULL;
    }

    if (hDIB != NULL) {
      win(DeleteObject(hDIB));
      hDIB = NULL;
    }

  }

  // draw the current frame
  void OnFrame() {

    // blit the graphics
    if (fUpdate) {
      if (fStretch) {
        StretchDIBits(hdc, 0, 0, iWidth, iHeight, iC64X, iC64Y, iC64Width, iC64Height, (void*)(pbBitmap), (BITMAPINFO*)&bmi, DIB_RGB_COLORS, SRCCOPY);
      } else {
        SetDIBitsToDevice(hdc, 0, 0, iWidth, iHeight, iC64X, iC64Y, 0, iScanLines, (void*)pbBitmap, (BITMAPINFO*)&bmi, DIB_RGB_COLORS);
      }
    }

  }

  // get pointer to C64 bitmap
  byte* GetC64Screen(int iNewC64Width, int iNewC64Height, const BYTE* pbColor) {

    // get DIB vertical displacement to correct bug in Windows 95 with top-down DIBs
    iMoveDown = gconf.GetInt("Display", "DIBSection Move Down (0 or 10 Pixels)", IsWindows95() ? 10 : 0);

    // get window DC
    assert((GetClassLong(hwnd, GCL_STYLE) & CS_OWNDC) != 0);
    win(hdc = GetDC(hwnd));

    // create and select the palette
    if ((GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) != 0) {

      // copy C64 colors
      struct {
        WORD Version;
        WORD NumEntries;
        PALETTEENTRY aEntry[256];
      } pal;
      memset(&pal, 0, sizeof pal);
      pal.Version = 0x0300;
      pal.NumEntries = 256;
      for (int i = 96; i < 128; i++) {
        int iColor = (i & 15) * 3;
        pal.aEntry[i].peRed = pbColor[iColor + 0];
        pal.aEntry[i].peGreen = pbColor[iColor + 1];
        pal.aEntry[i].peBlue = pbColor[iColor + 2];
        pal.aEntry[i].peFlags = PC_NOCOLLAPSE;
      }

      // fill in gaps to make background images look better
      i = 10;
      for (int iRed = 32; iRed < 6 * 32; iRed += 32) {
        for (int iGreen = 32; iGreen < 6 * 32; iGreen += 32) {
          for (int iBlue = 64; iBlue < 6 * 32; iBlue += 32) {
            while (pal.aEntry[i].peFlags != 0) {
              i++;
              assert(i < 256);
            }
            pal.aEntry[i].peRed = (byte)iRed;
            pal.aEntry[i].peGreen = (byte)iGreen;
            pal.aEntry[i].peBlue = (byte)iBlue;
            pal.aEntry[i].peFlags = PC_NOCOLLAPSE;
          }
        }
      }

      // create the palette
      win(hPalette = CreatePalette((LOGPALETTE*)&pal));
      win(hOldPalette = SelectPalette(hdc, hPalette, false));

    }

    // set bitmap size and colors
    iScanLines = iNewC64Height;
    bmi.bmih.biSize = sizeof BITMAPINFOHEADER;
    bmi.bmih.biWidth = iNewC64Width;
    bmi.bmih.biHeight = -iNewC64Height; // top-down DIB
    bmi.bmih.biPlanes = 1;
    bmi.bmih.biBitCount = 8;
    bmi.bmih.biCompression = BI_RGB;
    for (int i = 0; i < 256; i++) {
      int iColor = (i & 15) * 3;
      bmi.aRGB[i].rgbRed = pbColor[iColor + 0];
      bmi.aRGB[i].rgbGreen = pbColor[iColor + 1];
      bmi.aRGB[i].rgbBlue = pbColor[iColor + 2];
    }

    // create the bitmap
    win(hDIB = CreateDIBSection(hdc, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, (void**)&pbBitmap, NULL, 0));
    assert(pbBitmap != NULL);
    memset(pbBitmap, 0, iNewC64Width * iNewC64Height);

    // return pointer to 8 bit C64 screen
    return pbBitmap;

  }

  // set window size and/or position
  void Resize(const RECT& rcWindow, const RECT& rcC64) {

    // copy size and position
    iWidth = rcWindow.right - rcWindow.left;
    iHeight = rcWindow.bottom - rcWindow.top;
    iC64X = rcC64.left;
    iC64Y = rcC64.top + iMoveDown;
    iC64Width = rcC64.right - rcC64.left;
    iC64Height = rcC64.bottom - rcC64.top;

    // check if screen is visible
    fUpdate = !IsIconic(hwnd) && iWidth > 0 && iHeight > 0;

    // use StretchBlt only if necessary
    fStretch = (iWidth != iC64Width) || (iHeight != iC64Height);

  }

  // realize palette
  flag RealizePalette(flag) {

    if (hPalette != NULL) {
      win(SelectPalette(hdc, hPalette, false));
      verify(::RealizePalette(hdc) != GDI_ERROR);
      InvalidateRect(hwnd, NULL, false);
      return true;
    }

    return false;
  }

};

////////////////////////////////////////////////////////////////////////////////

class C64DisplayDirectDraw8 : public C64DisplayBase {

  HWND hwnd;
  IDirectDraw* pDirectDraw;
  IDirectDrawSurface* pDisplay;
  IDirectDrawClipper* pClipper;
  IDirectDrawPalette* pPalette;
  IDirectDrawSurface* pOffscreen;
  byte* pbC64Screen;

  flag fForeground;
  byte abColor[16 * 3];
  byte abColorTrans[256];

  RECT rcDisplay;
  RECT rcOffscreen;
  const byte* pbC64;
  int iC64Pitch;
  int iWidthBy8;
  int iHeight;

public:

  // constructor
  C64DisplayDirectDraw8(HWND hwndNew, IDirectDraw* pNewDirectDraw) {

    hwnd = hwndNew;
    pDirectDraw = pNewDirectDraw;
    pDisplay = NULL;
    pClipper = NULL;
    pPalette = NULL;
    pOffscreen = NULL;
    pbC64Screen = NULL;

    fForeground = false;
    memset(abColor, 0, sizeof abColor);
    memset(abColorTrans, 0, sizeof abColorTrans);

    memset(&rcDisplay, 0, sizeof rcDisplay);
    memset(&rcOffscreen, 0, sizeof rcOffscreen);
    pbC64 = NULL;
    iC64Pitch = 0;
    iWidthBy8 = 0;
    iHeight = 0;

  };

  // destructor
  virtual ~C64DisplayDirectDraw8() {

    if (pbC64Screen != NULL) {
      MemFree(pbC64Screen);
      pbC64Screen = NULL;
    }

    if (pOffscreen != NULL) {
      dd(pOffscreen->Release());
      pOffscreen = NULL;
    }

    if (pDisplay != NULL) {
      dd(pDisplay->Release());
      pDisplay = NULL;
    }

    if (pClipper != NULL) {
      dd(pClipper->Release());
      pClipper = NULL;
    }

    if (pPalette != NULL) {
      dd(pPalette->Release());
      pPalette = NULL;
    }

  }

#pragma optimize("g", off)

  // draw the current frame
  void OnFrame() {

    // check for visible area
    if (rcDisplay.left >= rcDisplay.right || rcDisplay.top >= rcDisplay.bottom) {
      return;
    }

    // restore surface after mode changes
    if (pOffscreen->IsLost()) {
      dd(pOffscreen->Restore());
    }

    // get offscreen surface pointer
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    assert(pOffscreen != NULL);
    if (pOffscreen->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR, NULL) != 0) {
      return;
    }

    // copy member variables to local variables (bug in Visual C++ 4.0)
    const byte* pbESI = pbC64;
    int iBlocksStart = iWidthBy8;
    const byte* pbColorTrans = abColorTrans;

    // copy C64 screen to offscreen surface, convert colors to 0x60-0x7F
    int iESIAdd = iC64Pitch - iWidthBy8 * 8;
    int iEDIAdd = ddsd.lPitch - iWidthBy8 * 8;
    int iLines = iHeight;
    if (fForeground) {

      __asm pushad

      __asm mov ESI,pbESI
      __asm mov EDI,ddsd.lpSurface
      __asm mov EDX,0x60606060

    NextLineFore:

      __asm mov ECX,iBlocksStart

    NextBlockFore:

      __asm mov EAX,[ESI+0]
      __asm mov EBX,[ESI+4]
 
      __asm or EAX,EDX
      __asm or EBX,EDX
 
      __asm mov [EDI+0],EAX
      __asm dec ECX
 
      __asm mov EAX,[ESI+8]
      __asm mov [EDI+4],EBX
 
      __asm je LastBlockFore
      __asm mov EBX,[ESI+12]
 
      __asm or EAX,EDX
      __asm or EBX,EDX
 
      __asm mov [EDI+8],EAX
      __asm add ESI,16
 
      __asm mov [EDI+12],EBX
      __asm add EDI,16

      __asm dec ECX
      __asm jne NextBlockFore

    LastBlockFore:

      __asm add ESI,iESIAdd
      __asm add EDI,iEDIAdd
      __asm dec iLines
      __asm jne NextLineFore

      __asm popad

    } else {

      __asm pushad

      __asm mov ESI,pbESI
      __asm mov EDI,ddsd.lpSurface
      __asm mov EDX,pbColorTrans
      __asm xor EAX,EAX
      __asm xor EBX,EBX

    NextLineBack:

      __asm mov ECX,iBlocksStart

    NextBlockBack:

      __asm mov AL,[ESI+0]
      __asm mov BL,[ESI+1]
      __asm mov AL,[EDX+EAX]
      __asm mov BL,[EDX+EBX]
      __asm mov [EDI+0],AL
      __asm mov [EDI+1],BL

      __asm mov AL,[ESI+2]
      __asm mov BL,[ESI+3]
      __asm mov AL,[EDX+EAX]
      __asm mov BL,[EDX+EBX]
      __asm mov [EDI+2],AL
      __asm mov [EDI+3],BL

      __asm mov AL,[ESI+4]
      __asm mov BL,[ESI+5]
      __asm mov AL,[EDX+EAX]
      __asm mov BL,[EDX+EBX]
      __asm mov [EDI+4],AL
      __asm mov [EDI+5],BL

      __asm mov AL,[ESI+6]
      __asm mov BL,[ESI+7]
      __asm mov AL,[EDX+EAX]
      __asm mov BL,[EDX+EBX]
      __asm mov [EDI+6],AL
      __asm mov [EDI+7],BL

      __asm add ESI,8
      __asm add EDI,8
      __asm dec ECX
      __asm jne NextBlockBack

      __asm add ESI,iESIAdd
      __asm add EDI,iEDIAdd
      __asm dec iLines
      __asm jne NextLineBack

      __asm popad

    }

    // release pointer to surface
    dd(pOffscreen->Unlock(ddsd.lpSurface));

    // blit offscreen image to visible window
    dd(pDisplay->Blt(&rcDisplay, pOffscreen, &rcOffscreen, 0, NULL));

  }

  // get pointer to C64 bitmap
  byte* GetC64Screen(int iNewC64Width, int iNewC64Height, const BYTE* pbColor) {

    // create display surface
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    dd(pDirectDraw->CreateSurface(&ddsd, &pDisplay, NULL));

    // create clipper
    dd(pDirectDraw->CreateClipper(0, &pClipper, NULL));
    dd(pClipper->SetHWnd(0, hwnd));
    dd(pDisplay->SetClipper(pClipper));

    // copy C64 colors (bit 4 = sprite, bit 5 = frame)
    PALETTEENTRY aPal[256];
    memset(aPal, 0, sizeof aPal);
    for (int i = 96; i < 128; i++) {
      int iColor = (i & 15) * 3;
      aPal[i].peRed = pbColor[iColor + 0];
      aPal[i].peGreen = pbColor[iColor + 1];
      aPal[i].peBlue = pbColor[iColor + 2];
      aPal[i].peFlags = PC_NOCOLLAPSE;
    }

    // fill in gaps to make background images look better
    i = 10;
    for (int iRed = 32; iRed < 6 * 32; iRed += 32) {
      for (int iGreen = 32; iGreen < 6 * 32; iGreen += 32) {
        for (int iBlue = 64; iBlue < 6 * 32; iBlue += 32) {
          while (aPal[i].peFlags != 0) {
            i++;
            assert(i < 256);
          }
          aPal[i].peRed = (byte)iRed;
          aPal[i].peGreen = (byte)iGreen;
          aPal[i].peBlue = (byte)iBlue;
          aPal[i].peFlags = PC_NOCOLLAPSE;
        }
      }
    }

    // create palette
    dd(pDirectDraw->CreatePalette(DDPCAPS_8BIT, aPal, &pPalette, NULL));
    dd(pDisplay->SetPalette(pPalette));

    // save colors for remapping in background
    memcpy(abColor, pbColor, sizeof abColor);

    // allocate C64 bitmap
    iC64Pitch = iNewC64Width;
    win(pbC64Screen = MemAlloc(iNewC64Width * iNewC64Height));
    return pbC64Screen;

  }

  // set window size and/or position
  void Resize(const RECT& rcWindow, const RECT& rcC64) {

    // release old offscreen surface
    if (pOffscreen != NULL) {
      dd(pOffscreen->Release());
      pOffscreen = NULL;
    }

    // calculate new parameters for OnFrame()
    rcDisplay = rcWindow;
    rcOffscreen.right = rcC64.right - rcC64.left;
    iWidthBy8 = (rcOffscreen.right + 7) / 8;
    iHeight = rcC64.bottom - rcC64.top;
    rcOffscreen.bottom = iHeight;
    pbC64 = pbC64Screen + rcC64.top * iC64Pitch + rcC64.left;

    // check whether to use video or system memory
    flag fStretch = (rcWindow.right - rcWindow.left != rcC64.right - rcC64.left);
    fStretch |= (rcWindow.bottom - rcWindow.top != rcC64.bottom - rcC64.top);
    flag fVideoRAM;
    if (fStretch) {
      fVideoRAM = gconf.GetInt("Display", "DirectDraw use Video RAM at n*n (1 or 0)", 1);
    } else {
      fVideoRAM = gconf.GetInt("Display", "DirectDraw use Video RAM at 1*1 (1 or 0)", 1);
    }

    // create offscreen surface
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
    ddsd.dwWidth = rcOffscreen.right;
    ddsd.dwHeight = rcOffscreen.bottom;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    if (fVideoRAM) {
      ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    } else {
      ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    }
    HRESULT hResult = pDirectDraw->CreateSurface(&ddsd, &pOffscreen, NULL);
    if (hResult != DD_OK) {
      if (fVideoRAM) {
        trace("Warning: Cannot create offscreen bitmap in Video RAM, trying System RAM");
        ddsd.ddsCaps.dwCaps ^= (DDSCAPS_VIDEOMEMORY ^ DDSCAPS_SYSTEMMEMORY);
      }
      dd(pDirectDraw->CreateSurface(&ddsd, &pOffscreen, NULL));
    }
  }

  // realize palette
  flag RealizePalette(flag fNewForeground) {

    // store display method for OnFrame()
    fForeground = fNewForeground;
    if (fForeground) {

      // realize our palette
      HRESULT hResult = pDisplay->SetPalette(pPalette);
      if (hResult == DDERR_SURFACELOST) {
        dd(pDisplay->Restore());
        hResult = pDisplay->SetPalette(pPalette);
      }
      dd(hResult);

    } else {

      // remap colors to foreign palette
      HDC hdc;
      win(hdc = GetDC(NULL));
      PALETTEENTRY aPal[256];
      verify(GetSystemPaletteEntries(hdc, 0, 256, aPal) == 256);
      win(ReleaseDC(NULL, hdc));
      for (int i = 0; i < 16; i++) {
        int iMin = 256 * 256 * 3;
        for (int iIndex = 0; iIndex < 256; iIndex++) {
          int iRed = aPal[iIndex].peRed - abColor[i * 3 + 0];
          int iGreen = aPal[iIndex].peGreen - abColor[i * 3 + 1];
          int iBlue = aPal[iIndex].peBlue - abColor[i * 3 + 2];
          int iDelta = iRed * iRed + iGreen * iGreen + iBlue * iBlue;
          if (iDelta < iMin) {
            iMin = iDelta;
            abColorTrans[i] = (byte)iIndex;
          }
        }
      }
      for (i = 16; i < 256; i <<= 1) {
        memcpy(abColorTrans + i, abColorTrans, i);
      }

    }

    // make the changes visible
    win(InvalidateRect(hwnd, NULL, false));
    return fForeground;
  }

};

////////////////////////////////////////////////////////////////////////////////

class C64DisplayDirectDraw16 : public C64DisplayBase {

  HWND hwnd;
  IDirectDraw* pDirectDraw;
  IDirectDrawSurface* pDisplay;
  IDirectDrawClipper* pClipper;
  IDirectDrawSurface* pOffscreen;
  byte* pbC64Screen;

  RECT rcDisplay;
  RECT rcOffscreen;
  const byte* pbC64;
  int iC64Pitch;
  int iWidthBy8;
  int iHeight;

  // colors 0-255 = 0000xxxx, 256-511 = xxxx0000
  dword adColors[512];

public:

  // constructor
  C64DisplayDirectDraw16(HWND hwndNew, IDirectDraw* pNewDirectDraw) {

    hwnd = hwndNew;
    pDirectDraw = pNewDirectDraw;
    pDisplay = NULL;
    pClipper = NULL;
    pOffscreen = NULL;
    pbC64Screen = NULL;

    memset(&rcDisplay, 0, sizeof rcDisplay);
    memset(&rcOffscreen, 0, sizeof rcOffscreen);
    pbC64 = NULL;
    iC64Pitch = 0;
    iWidthBy8 = 0;
    iHeight = 0;

    memset(adColors, 0, sizeof adColors);

  };

  // destructor
  virtual ~C64DisplayDirectDraw16() {

    if (pbC64Screen != NULL) {
      MemFree(pbC64Screen);
      pbC64Screen = NULL;
    }

    if (pOffscreen != NULL) {
      dd(pOffscreen->Release());
      pOffscreen = NULL;
    }

    if (pDisplay != NULL) {
      dd(pDisplay->Release());
      pDisplay = NULL;
    }

    if (pClipper != NULL) {
      dd(pClipper->Release());
      pClipper = NULL;
    }

  }

  // draw the current frame
  void OnFrame() {

    // check for visible area
    if (rcDisplay.left >= rcDisplay.right || rcDisplay.top >= rcDisplay.bottom) {
      return;
    }

    // restore surface after mode changes
    if (pOffscreen->IsLost()) {
      dd(pOffscreen->Restore());
    }

    // get offscreen surface pointer
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    assert(pOffscreen != NULL);
    if (pOffscreen->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR, NULL) != 0) {
      return;
    }

    // copy member variables to local variables (bug in Visual C++ 4.0)
    dword* pdColors = adColors;
    const byte* pbESI = pbC64;
    int iBlocksStart = iWidthBy8;

    // copy 8 bit C64 screen to 16 bit offscreen surface
    int iESIAdd = iC64Pitch - iWidthBy8 * 8;
    int iEDIAdd = ddsd.lPitch - iWidthBy8 * 8 * 2;
    int iLines = iHeight;

    __asm pushad

    __asm xor EAX,EAX
    __asm xor EBX,EBX
    __asm mov ESI,pbESI
    __asm mov EDI,ddsd.lpSurface

  NextLine:

    __asm mov ECX,iBlocksStart
    __asm push EBP
    __asm mov EBP,pdColors

  NextBlock:

    __asm mov AL,[ESI]
    __asm push ECX

    __asm mov BL,[ESI+1]
    __asm add ESI,8

    __asm mov ECX,[EBP+EAX*4]
    __asm add EDI,16

    __asm mov EDX,[EBP+1024+EBX*4]
    __asm mov AL,[ESI-6]

    __asm or EDX,ECX
    __asm mov BL,[ESI-5]

    __asm mov [EDI-16],EDX
    __asm mov ECX,[EBP+EAX*4]

    __asm mov EDX,[EBP+1024+EBX*4]
    __asm mov AL,[ESI-4]

    __asm or EDX,ECX
    __asm mov BL,[ESI-3]

    __asm mov [EDI-12],EDX
    __asm mov ECX,[EBP+EAX*4]

    __asm mov EDX,[EBP+1024+EBX*4]
    __asm mov AL,[ESI-2]

    __asm or EDX,ECX
    __asm mov BL,[ESI-1]

    __asm mov [EDI-8],EDX
    __asm mov ECX,[EBP+EAX*4]

    __asm mov EDX,[EBP+1024+EBX*4]

    __asm or EDX,ECX
    __asm pop ECX

    __asm mov [EDI-4],EDX

    __asm dec ECX
    __asm jne NextBlock

    __asm pop EBP

    __asm add ESI,iESIAdd
    __asm add EDI,iEDIAdd

    __asm dec iLines
    __asm jne NextLine

    __asm popad

    // release pointer to surface
    dd(pOffscreen->Unlock(ddsd.lpSurface));

    // blit offscreen image to visible window
    dd(pDisplay->Blt(&rcDisplay, pOffscreen, &rcOffscreen, 0, NULL));

  }

  // helper function because (x << -1) != (x >> 1) in Visual C++ 4.0
  inline static dword Shift(dword dw, int iShift) {
    return iShift >= 0 ? dw << iShift : dw >> -iShift;
  }

  // get pointer to C64 bitmap
  byte* GetC64Screen(int iNewC64Width, int iNewC64Height, const BYTE* pbColor) {

    // verify 16 bit mode
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    dd(pDirectDraw->GetDisplayMode(&ddsd));
    assert((ddsd.dwFlags & DDSD_PIXELFORMAT) != 0);
    assert((ddsd.ddpfPixelFormat.dwFlags & DDPF_RGB) != 0);
    assert(ddsd.ddpfPixelFormat.dwRGBBitCount == 16);

    // get color shift and mask values
    dword adMask[3];
    adMask[0] = ddsd.ddpfPixelFormat.dwRBitMask;
    adMask[1] = ddsd.ddpfPixelFormat.dwGBitMask;
    adMask[2] = ddsd.ddpfPixelFormat.dwBBitMask;
    if (gconf.GetInt("Display", "DirectDraw 16 bit force 5/6/5 (1 or 0)", 0) != 0) {
      adMask[0] = 0xF800;
      adMask[1] = 0x07E0;
      adMask[2] = 0x001F;
    }
    int aiShift[3];
    for (int i = 0; i < 3; i++) {
      assert(adMask[i] != 0);
      aiShift[i] = -8;
      while (Shift(0xFF, aiShift[i]) < adMask[i]) {
        aiShift[i]++;
      }
    }

    // set colors for display and sprites
    for (i = 0; i < 256; i++) {
      int iColor = (i & 15) * 3;
      dword dw = Shift(pbColor[iColor + 0], aiShift[0]) & adMask[0];
      dw |= Shift(pbColor[iColor + 1], aiShift[1]) & adMask[1];
      dw |= Shift(pbColor[iColor + 2], aiShift[2]) & adMask[2];
      adColors[i] = dw;
      adColors[i + 256] = dw << 16;
    }

    // create display surface
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    dd(pDirectDraw->CreateSurface(&ddsd, &pDisplay, NULL));

    // create clipper
    dd(pDirectDraw->CreateClipper(0, &pClipper, NULL));
    dd(pClipper->SetHWnd(0, hwnd));
    dd(pDisplay->SetClipper(pClipper));

    // allocate C64 bitmap
    iC64Pitch = iNewC64Width;
    win(pbC64Screen = MemAlloc(iNewC64Width * iNewC64Height));
    return pbC64Screen;

  }

  // set window size and/or position
  void Resize(const RECT& rcWindow, const RECT& rcC64) {

    // release old offscreen surface
    if (pOffscreen != NULL) {
      dd(pOffscreen->Release());
      pOffscreen = NULL;
    }

    // calculate new parameters for OnFrame()
    rcDisplay = rcWindow;
    rcOffscreen.right = rcC64.right - rcC64.left;
    iWidthBy8 = (rcOffscreen.right + 7) / 8;
    iHeight = rcC64.bottom - rcC64.top;
    rcOffscreen.bottom = iHeight;
    pbC64 = pbC64Screen + rcC64.top * iC64Pitch + rcC64.left;

    // check whether to use video or system memory
    flag fStretch = (rcWindow.right - rcWindow.left != rcC64.right - rcC64.left);
    fStretch |= (rcWindow.bottom - rcWindow.top != rcC64.bottom - rcC64.top);
    flag fVideoRAM;
    if (fStretch) {
      fVideoRAM = gconf.GetInt("Display", "DirectDraw use Video RAM at n*n (1 or 0)", 1);
    } else {
      fVideoRAM = gconf.GetInt("Display", "DirectDraw use Video RAM at 1*1 (1 or 0)", 1);
    }

    // create offscreen surface
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
    ddsd.dwWidth = rcOffscreen.right;
    ddsd.dwHeight = rcOffscreen.bottom;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    if (fVideoRAM) {
      ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    } else {
      ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    }
    HRESULT hResult = pDirectDraw->CreateSurface(&ddsd, &pOffscreen, NULL);
    if (hResult != DD_OK) {
      if (fVideoRAM) {
        trace("Warning: Cannot create offscreen bitmap in Video RAM, trying System RAM");
        ddsd.ddsCaps.dwCaps ^= (DDSCAPS_VIDEOMEMORY ^ DDSCAPS_SYSTEMMEMORY);
      }
      dd(pDirectDraw->CreateSurface(&ddsd, &pOffscreen, NULL));
    }
  }

};

////////////////////////////////////////////////////////////////////////////////

class C64DisplayDirectDraw32 : public C64DisplayBase {

  HWND hwnd;
  IDirectDraw* pDirectDraw;
  IDirectDrawSurface* pDisplay;
  IDirectDrawClipper* pClipper;
  IDirectDrawSurface* pOffscreen;
  byte* pbC64Screen;

  RECT rcDisplay;
  RECT rcOffscreen;
  const byte* pbC64;
  int iC64Pitch;
  int iWidthBy8;
  int iHeight;

  dword adColors[256];

public:

  // constructor
  C64DisplayDirectDraw32(HWND hwndNew, IDirectDraw* pNewDirectDraw) {

    hwnd = hwndNew;
    pDirectDraw = pNewDirectDraw;
    pDisplay = NULL;
    pClipper = NULL;
    pOffscreen = NULL;
    pbC64Screen = NULL;

    memset(&rcDisplay, 0, sizeof rcDisplay);
    memset(&rcOffscreen, 0, sizeof rcOffscreen);
    pbC64 = NULL;
    iC64Pitch = 0;
    iWidthBy8 = 0;
    iHeight = 0;

    memset(adColors, 0, sizeof adColors);

  };

  // destructor
  virtual ~C64DisplayDirectDraw32() {

    if (pbC64Screen != NULL) {
      MemFree(pbC64Screen);
      pbC64Screen = NULL;
    }

    if (pOffscreen != NULL) {
      dd(pOffscreen->Release());
      pOffscreen = NULL;
    }

    if (pDisplay != NULL) {
      dd(pDisplay->Release());
      pDisplay = NULL;
    }

    if (pClipper != NULL) {
      dd(pClipper->Release());
      pClipper = NULL;
    }

  }

  // draw the current frame
  void OnFrame() {

    // check for visible area
    if (rcDisplay.left >= rcDisplay.right || rcDisplay.top >= rcDisplay.bottom) {
      return;
    }

    // restore surface after mode changes
    if (pOffscreen->IsLost()) {
      dd(pOffscreen->Restore());
    }

    // get offscreen surface pointer
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    assert(pOffscreen != NULL);
    if (pOffscreen->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR, NULL) != 0) {
      return;
    }

    // copy member variables to local variables (bug in Visual C++ 4.0)
    dword* pdColors = adColors;
    const byte* pbESI = pbC64;
    int iBlocksStart = iWidthBy8;

    // copy 8 bit C64 screen to 32 bit offscreen surface
    int iESIAdd = iC64Pitch - iWidthBy8 * 8;
    int iEDIAdd = ddsd.lPitch - iWidthBy8 * 8 * 4;
    int iLines = iHeight;
    int iBlocks;

    __asm pushad

    __asm xor EAX,EAX
    __asm xor EBX,EBX
    __asm mov ECX,pdColors
    __asm mov ESI,pbESI
    __asm mov EDI,ddsd.lpSurface

  NextLine:

    __asm mov EDX,iBlocksStart
    __asm mov iBlocks,EDX

  NextBlock:

    __asm mov AL,[ESI+0]
    __asm mov BL,[ESI+1]
    __asm mov EDX,[ECX+EAX*4]
    __asm mov AL,[ESI+2]
    __asm mov [EDI+0],EDX
    __asm mov EDX,[ECX+EBX*4]
    __asm mov BL,[ESI+3]
    __asm mov [EDI+4],EDX
    __asm mov EDX,[ECX+EAX*4]
    __asm mov AL,[ESI+4]
    __asm mov [EDI+8],EDX
    __asm mov EDX,[ECX+EBX*4]
    __asm mov BL,[ESI+5]
    __asm mov [EDI+12],EDX
    __asm mov EDX,[ECX+EAX*4]
    __asm mov AL,[ESI+6]
    __asm mov [EDI+16],EDX
    __asm mov EDX,[ECX+EBX*4]
    __asm mov BL,[ESI+7]
    __asm mov [EDI+20],EDX
    __asm mov EDX,[ECX+EAX*4]
    __asm mov [EDI+24],EDX
    __asm add ESI,8
    __asm mov EDX,[ECX+EBX*4]
    __asm mov [EDI+28],EDX
    __asm add EDI,32

    __asm dec iBlocks
    __asm jne NextBlock

    __asm add ESI,iESIAdd
    __asm add EDI,iEDIAdd
    __asm dec iLines
    __asm jne NextLine

    __asm popad

    // release pointer to surface
    dd(pOffscreen->Unlock(ddsd.lpSurface));

    // blit offscreen image to visible window
    dd(pDisplay->Blt(&rcDisplay, pOffscreen, &rcOffscreen, 0, NULL));

  }

  // set mode and colors
  byte* GetC64Screen(int iNewC64Width, int iNewC64Height, const BYTE* pbColor) {

    // verify 32 bit mode
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    dd(pDirectDraw->GetDisplayMode(&ddsd));
    assert((ddsd.dwFlags & DDSD_PIXELFORMAT) != 0);
    assert((ddsd.ddpfPixelFormat.dwFlags & DDPF_RGB) != 0);
    assert(ddsd.ddpfPixelFormat.dwRGBBitCount == 32);

    // get color shift
    dword adMask[3];
    adMask[0] = ddsd.ddpfPixelFormat.dwRBitMask;
    adMask[1] = ddsd.ddpfPixelFormat.dwGBitMask;
    adMask[2] = ddsd.ddpfPixelFormat.dwBBitMask;
    int aiShift[3];
    for (int i = 0; i < 3; i++) {
      assert(adMask[i] != 0);
      aiShift[i] = 0;
      while ((dword)(0xFF << aiShift[i]) < adMask[i]) {
        aiShift[i]++;
      }
    }

    // set colors for display and sprites
    for (i = 0; i < 256; i++) {
      int iColor = (i & 15) * 3;
      dword dw = (pbColor[iColor + 0] << aiShift[0]) & adMask[0];
      dw |= (pbColor[iColor + 1] << aiShift[1]) & adMask[1];
      dw |= (pbColor[iColor + 2] << aiShift[2]) & adMask[2];
      adColors[i] = dw;
    }

    // create display surface
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    dd(pDirectDraw->CreateSurface(&ddsd, &pDisplay, NULL));

    // create clipper
    dd(pDirectDraw->CreateClipper(0, &pClipper, NULL));
    dd(pClipper->SetHWnd(0, hwnd));
    dd(pDisplay->SetClipper(pClipper));

    // allocate C64 bitmap
    iC64Pitch = iNewC64Width;
    win(pbC64Screen = MemAlloc(iNewC64Width * iNewC64Height));
    return pbC64Screen;

  }

  // set window size and/or position
  void Resize(const RECT& rcWindow, const RECT& rcC64) {

    // release old offscreen surface
    if (pOffscreen != NULL) {
      dd(pOffscreen->Release());
      pOffscreen = NULL;
    }

    // calculate new parameters for OnFrame()
    rcDisplay = rcWindow;
    rcOffscreen.right = rcC64.right - rcC64.left;
    iWidthBy8 = (rcOffscreen.right + 7) / 8;
    iHeight = rcC64.bottom - rcC64.top;
    rcOffscreen.bottom = iHeight;
    pbC64 = pbC64Screen + rcC64.top * iC64Pitch + rcC64.left;

    // check whether to use video or system memory
    flag fStretch = (rcWindow.right - rcWindow.left != rcC64.right - rcC64.left);
    fStretch |= (rcWindow.bottom - rcWindow.top != rcC64.bottom - rcC64.top);
    flag fVideoRAM;
    if (fStretch) {
      fVideoRAM = gconf.GetInt("Display", "DirectDraw use Video RAM at n*n (1 or 0)", 1);
    } else {
      fVideoRAM = gconf.GetInt("Display", "DirectDraw use Video RAM at 1*1 (1 or 0)", 1);
    }

    // create offscreen surface
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
    ddsd.dwWidth = rcOffscreen.right;
    ddsd.dwHeight = rcOffscreen.bottom;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    if (fVideoRAM) {
      ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    } else {
      ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    }
    HRESULT hResult = pDirectDraw->CreateSurface(&ddsd, &pOffscreen, NULL);
    if (hResult != DD_OK) {
      if (fVideoRAM) {
        trace("Warning: Cannot create offscreen bitmap in Video RAM, trying System RAM");
        ddsd.ddsCaps.dwCaps ^= (DDSCAPS_VIDEOMEMORY ^ DDSCAPS_SYSTEMMEMORY);
      }
      dd(pDirectDraw->CreateSurface(&ddsd, &pOffscreen, NULL));
    }
  }

};

////////////////////////////////////////////////////////////////////////////////

class C64DisplayDirectDrawFullscreen : public C64DisplayBase {

  HWND hwnd;
  RECT rcSavedWindow;
  IDirectDraw* pDirectDraw;
  IDirectDrawSurface* pDisplay;
  IDirectDrawPalette* pPalette;

  int iDisplayHeight;
  byte* pbC64Screen;
  byte* pbC64Start;
  int iC64Pitch;

public:

  // constructor
  C64DisplayDirectDrawFullscreen(HWND hwndNew, IDirectDraw* pNewDirectDraw, int iNewDisplayHeight) {

    hwnd = hwndNew;
    memset(&rcSavedWindow, 0, sizeof rcSavedWindow);
    pDirectDraw = pNewDirectDraw;
    pDisplay = NULL;
    pPalette = NULL;

    iDisplayHeight = iNewDisplayHeight;
    pbC64Screen = NULL;
    pbC64Start = NULL;
    iC64Pitch = 0;
  
  };

  // destructor
  virtual ~C64DisplayDirectDrawFullscreen() {

    if (pbC64Screen != NULL) {
      MemFree(pbC64Screen);
      pbC64Screen = NULL;
      pbC64Start = NULL;
    }

    if (pDisplay != NULL) {
      dd(pDisplay->Release());
      pDisplay = NULL;
    }

    if (pPalette != NULL) {
      /*dd*/(pPalette->Release()); // unknown DirectDraw error 1
      pPalette = NULL;
    }

    dd(pDirectDraw->RestoreDisplayMode());
    dd(pDirectDraw->SetCooperativeLevel(hwnd, DDSCL_NORMAL));
    win(SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW));
    win(MoveWindow(hwnd, rcSavedWindow.left, rcSavedWindow.top, rcSavedWindow.right - rcSavedWindow.left, rcSavedWindow.bottom - rcSavedWindow.top, true));

  }

  // draw the current frame
  void OnFrame() {

#if 1

    #define iLinesPerPlane 10

    // copy C64 screen to video RAM at 0xA0000
    byte* pbESI = pbC64Start;
    byte* pbEDI = (byte*)0x000A0000;
    int iESIAdd = iC64Pitch - 320;
    int iGroups = iDisplayHeight / iLinesPerPlane;
    int iLines;

    __asm {

      pushad

    NextGroup:

      mov ESI,pbESI
      mov ECX,iLinesPerPlane*320/32

    FillCache:

      mov EAX,[ESI]
      add ESI,32

      dec ECX
      jne FillCache

      nop
      mov EAX,0x0102

    NextPlane:

      push EAX
      mov EDX,0x03C4

      out DX,AX
      ;unpaired

      mov ESI,pbESI
      mov EDI,pbEDI

      nop
      mov iLines,iLinesPerPlane

    NextLine:

      nop
      mov EDX,320/64

    NextBlock:

      mov AL,[ESI+0*32+8]
      mov BL,[ESI+0*32+24]

      mov AH,[ESI+0*32+12]
      mov BH,[ESI+0*32+28]

      shl EAX,16
      mov CL,[ESI+1*32+8]

      shl EBX,16
      mov AL,[ESI+0*32+0]

      mov BL,[ESI+0*32+16]
      mov AH,[ESI+0*32+4]

      mov BH,[ESI+0*32+20]
      mov [EDI+0*8+0],EAX

      mov CH,[ESI+1*32+12]
      mov AL,[ESI+1*32+24]

      shl ECX,16
      mov AH,[ESI+1*32+28]

      shl EAX,16
      mov [EDI+0*8+4],EBX

      mov CL,[ESI+1*32+0]
      mov AL,[ESI+1*32+16]

      mov CH,[ESI+1*32+4]
      mov AH,[ESI+1*32+20]

      mov [EDI+1*8+0],ECX
      mov [EDI+1*8+4],EAX

      add ESI,64
      add EDI,16

      dec EDX
      jne NextBlock

      nop
      add ESI,iESIAdd

      dec iLines
      jne NextLine

      inc pbESI
      pop EAX

      nop
      add AH,AH

      cmp AH,0x10
      jb NextPlane

      add pbEDI,iLinesPerPlane*80
      sub ESI,3

      nop
      mov pbESI,ESI

      dec iGroups
      jne NextGroup

      popad

    }

#else

    // copy member variables to local variables (bug in Visual C++ 4.0)
    const byte* pbESI = pbC64Start;

    // copy C64 screen to video RAM at 0xA0000
    int iESIAdd = iC64Pitch - 4;
    int iLines = iDisplayHeight;

    __asm pushad

    __asm mov ESI,pbESI
    __asm mov EDI,0x000A0000

  NextLine:

    __asm mov EAX,0x0102

  NextPlane:

    __asm mov EDX,0x03C4
    __asm push EAX

    __asm out DX,AX
    __asm nop

    __asm mov AL,[ESI+0*32+8]
    __asm mov BL,[ESI+0*32+24]

    __asm mov AH,[ESI+0*32+12]
    __asm mov BH,[ESI+0*32+28]

    __asm shl EAX,16
    __asm mov CL,[ESI+1*32+8]

    __asm shl EBX,16
    __asm mov AL,[ESI+0*32+0]

    __asm mov BL,[ESI+0*32+16]
    __asm mov AH,[ESI+0*32+4]

    __asm mov BH,[ESI+0*32+20]
    __asm mov [EDI+0*8+0],EAX

    __asm mov CH,[ESI+1*32+12]
    __asm mov DL,[ESI+1*32+24]

    __asm shl ECX,16
    __asm mov DH,[ESI+1*32+28]

    __asm shl EDX,16
    __asm mov [EDI+0*8+4],EBX

    __asm mov CL,[ESI+1*32+0]
    __asm mov DL,[ESI+1*32+16]

    __asm mov CH,[ESI+1*32+4]
    __asm mov DH,[ESI+1*32+20]

    __asm mov AL,[ESI+2*32+8]
    __asm mov [EDI+1*8+0],ECX

    __asm mov AH,[ESI+2*32+12]
    __asm mov BL,[ESI+2*32+24]

    __asm shl EAX,16
    __asm mov BH,[ESI+2*32+28]

    __asm shl EBX,16
    __asm mov [EDI+1*8+4],EDX

    __asm mov AL,[ESI+2*32+0]
    __asm mov BL,[ESI+2*32+16]

    __asm mov AH,[ESI+2*32+4]
    __asm mov BH,[ESI+2*32+20]

    __asm mov CL,[ESI+3*32+8]
    __asm mov [EDI+2*8+0],EAX

    __asm mov DL,[ESI+3*32+24]
    __asm mov CH,[ESI+3*32+12]

    __asm shl ECX,16
    __asm mov DH,[ESI+3*32+28]

    __asm shl EDX,16
    __asm mov [EDI+2*8+4],EBX

    __asm mov CL,[ESI+3*32+0]
    __asm mov DL,[ESI+3*32+16]

    __asm mov CH,[ESI+3*32+4]
    __asm mov DH,[ESI+3*32+20]

    __asm mov AL,[ESI+4*32+8]
    __asm mov [EDI+3*8+0],ECX

    __asm mov AH,[ESI+4*32+12]
    __asm mov BL,[ESI+4*32+24]

    __asm shl EAX,16
    __asm mov BH,[ESI+4*32+28]

    __asm shl EBX,16
    __asm mov [EDI+3*8+4],EDX

    __asm mov AL,[ESI+4*32+0]
    __asm mov BL,[ESI+4*32+16]

    __asm mov AH,[ESI+4*32+4]
    __asm mov CL,[ESI+5*32+8]

    __asm mov BH,[ESI+4*32+20]
    __asm mov [EDI+4*8+0],EAX

    __asm mov CH,[ESI+5*32+12]
    __asm mov DL,[ESI+5*32+24]

    __asm shl ECX,16
    __asm mov DH,[ESI+5*32+28]

    __asm shl EDX,16
    __asm mov [EDI+4*8+4],EBX

    __asm mov CL,[ESI+5*32+0]
    __asm mov DL,[ESI+5*32+16]

    __asm mov CH,[ESI+5*32+4]
    __asm mov DH,[ESI+5*32+20]

    __asm mov AL,[ESI+6*32+8]
    __asm mov [EDI+5*8+0],ECX

    __asm mov AH,[ESI+6*32+12]
    __asm mov BL,[ESI+6*32+24]

    __asm shl EAX,16
    __asm mov BH,[ESI+6*32+28]

    __asm shl EBX,16
    __asm mov [EDI+5*8+4],EDX

    __asm mov AL,[ESI+6*32+0]
    __asm mov BL,[ESI+6*32+16]

    __asm mov AH,[ESI+6*32+4]
    __asm mov BH,[ESI+6*32+20]

    __asm mov CL,[ESI+7*32+8]
    __asm mov [EDI+6*8+0],EAX

    __asm mov CH,[ESI+7*32+12]
    __asm mov DL,[ESI+7*32+24]

    __asm shl ECX,16
    __asm mov DH,[ESI+7*32+28]

    __asm shl EDX,16
    __asm mov [EDI+6*8+4],EBX

    __asm mov CL,[ESI+7*32+0]
    __asm mov DL,[ESI+7*32+16]

    __asm mov CH,[ESI+7*32+4]
    __asm mov DH,[ESI+7*32+20]

    __asm mov AL,[ESI+8*32+8]
    __asm mov [EDI+7*8+0],ECX

    __asm mov AH,[ESI+8*32+12]
    __asm mov BL,[ESI+8*32+24]

    __asm shl EAX,16
    __asm mov BH,[ESI+8*32+28]

    __asm shl EBX,16
    __asm mov [EDI+7*8+4],EDX

    __asm mov AL,[ESI+8*32+0]
    __asm mov BL,[ESI+8*32+16]

    __asm mov AH,[ESI+8*32+4]
    __asm mov BH,[ESI+8*32+20]

    __asm mov CL,[ESI+9*32+8]
    __asm mov [EDI+8*8+0],EAX

    __asm mov CH,[ESI+9*32+12]
    __asm mov DL,[ESI+9*32+24]

    __asm shl ECX,16
    __asm mov DH,[ESI+9*32+28]

    __asm shl EDX,16
    __asm mov [EDI+8*8+4],EBX

    __asm mov CL,[ESI+9*32+0]
    __asm mov DL,[ESI+9*32+16]

    __asm mov CH,[ESI+9*32+4]
    __asm mov DH,[ESI+9*32+20]

    __asm pop EAX
    __asm mov [EDI+9*8+0],ECX

    __asm add AH,AH
    __asm inc ESI

    __asm cmp AH,0x10
    __asm mov [EDI+9*8+4],EDX

    __asm jb NextPlane

    __asm add ESI,iESIAdd
    __asm add EDI,80
    __asm dec iLines
    __asm jne NextLine

    __asm popad

#endif

  }

  // set mode and colors
  byte* GetC64Screen(int iNewC64Width, int iNewC64Height, const BYTE* pbColor) {

    // set 320x200 or 320x240 display mode
    GetWindowRect(hwnd, &rcSavedWindow);
    win(SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW));
    dd(pDirectDraw->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX));
    dd(pDirectDraw->SetDisplayMode(320, iDisplayHeight, 8));

    // create display surface
    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof ddsd;
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    dd(pDirectDraw->CreateSurface(&ddsd, &pDisplay, NULL));

    // create palette
    PALETTEENTRY aPal[256];
    memset(aPal, 0, sizeof aPal);
    for (int i = 0; i < 256; i++) {
      int iColor = (i & 15) * 3;
      aPal[i].peRed = pbColor[iColor + 0];
      aPal[i].peGreen = pbColor[iColor + 1];
      aPal[i].peBlue = pbColor[iColor + 2];
    }
    dd(pDirectDraw->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, aPal, &pPalette, NULL));
    dd(pDisplay->SetPalette(pPalette));

    // allocate C64 bitmap
    iC64Pitch = iNewC64Width;
    win(pbC64Screen = MemAlloc(iNewC64Width * iNewC64Height));
    pbC64Start = pbC64Screen + (151 - iDisplayHeight / 2) * iNewC64Width + 128;
    return pbC64Screen;

  }

};

////////////////////////////////////////////////////////////////////////////////

/*
//#include <wdirect.h>
//#include <pmode.h>

class C64DisplayWinDirect320x200 : public C64DisplayBase {

  HMODULE hWinDirectLib;

  byte* pbVideo;
  byte* pbC64Screen;
  byte* pbC64Start;
  int iC64Pitch;

public:

  // constructor
  C64DisplayWinDirect320x200() {

    hWinDirectLib = NULL;

    pbVideo = NULL;
    pbC64Screen = NULL;
    pbC64Start = NULL;
    iC64Pitch = 0;

  };

  // destructor
  virtual ~C64DisplayWinDirect320x200() {

    //WD_restoreGDI();

    if (hWinDirectLib != NULL) {
      //GetProcAddress(hWinDirectLib, "WD_restoreGDI")

      win(FreeLibrary(hWinDirectLib));
      hWinDirectLib = NULL;
    }

  }

  // draw the current frame
  void OnFrame() {

    // copy member variables to local variables (bug in Visual C++ 4.0)
    const byte* pbESI = pbC64Start;
    const byte* pbEDI = pbVideo;

    // copy C64 screen to video RAM at 0xA0000
    int iESIAdd = iC64Pitch - 320;

    __asm pushad

    __asm mov ESI,pbESI
    __asm mov EDI,pbEDI
    __asm mov EBX,iESIAdd
    __asm mov EDX,200
    __asm cld

  NextLine:

    __asm mov ECX,320/4
    __asm rep movsd

    __asm add ESI,EBX
    __asm dec EDX
    __asm jne NextLine

    __asm popad

  }

  // set mode and colors
  byte* GetC64Screen(int iNewC64Width, int iNewC64Height, const BYTE* pbColor) {

    // set 320x200 mode
    WD_startFullScreen(NULL, 320, 200);
    RMREGS regs;
    regs.x.ax = 0x13;
    PM_int86(0x10, &regs, &regs);

    // get pointer to video RAM
    pbVideo = (byte*)PM_mapPhysicalAddr(0xA0000, 320 * 200 - 1);

    // set the palette
    for (int i = 0; i < 256; i++) {
      regs.x.ax = 0x13;
      regs.x.bx = (word)i;
      regs.h.dh = (byte)(pbColor[i * 3 + 0] >> 2);
      regs.h.ch = (byte)(pbColor[i * 3 + 1] >> 2);
      regs.h.cl = (byte)(pbColor[i * 3 + 2] >> 2);
      PM_int86(0x10, &regs, &regs);
    }

    // allocate C64 bitmap
    iC64Pitch = iNewC64Width;
    win(pbC64Screen = MemAlloc(iNewC64Width * iNewC64Height));
    pbC64Start = pbC64Screen + 51 * iNewC64Width + 128;
    return pbC64Screen;

  }

};
*/

////////////////////////////////////////////////////////////////////////////////

class C64Display {

  HMODULE hDirectDrawLib;
  IDirectDraw* pDirectDraw;

  C64DisplayBase* pC64Display;
  byte* pbC64Screen;

  char* pcDisplayMode;

public:
  flag fFullscreen;

public:

  // constructor
  C64Display() {

    hDirectDrawLib = NULL;
    pDirectDraw = NULL;

    fFullscreen = false;
    pC64Display = NULL;
    pbC64Screen = NULL;

    pcDisplayMode = NULL;

  }

  // destructor
  virtual ~C64Display() {

    if (pC64Display != NULL) {
      delete pC64Display;
      pC64Display = NULL;
      pbC64Screen = NULL;
    }

    if (pDirectDraw != NULL) {
      dd(pDirectDraw->Release());
      pDirectDraw = NULL;
    }

    if (hDirectDrawLib != NULL) {
      win(FreeLibrary(hDirectDrawLib));
      hDirectDrawLib = NULL;
    }

  }

  // create C64 display depending on the drivers loaded
  C64DisplayBase* CreateC64Display(HWND hwnd) {

    // read parameters
    flag fUseDirectDraw = gconf.GetInt("Display", "Use DirectDraw (1 or 0)", !IsWin32s());
    flag fUseDIBSection = gconf.GetInt("Display", "Use DIBSection (1 or 0)", !IsWin32s());
    flag fUseWinG = gconf.GetInt("Display", "Use WinG (1 or 0)", 1);
    //flag fUseWinDirectFromSciTech = gconf.GetInt("Display", "Use WinDirect from SciTech", 1);
    int iFullscreenWidth = gconf.GetInt("Display", "Fullscreen Width (320)", 320);
    int iFullscreenHeight = gconf.GetInt("Display", "Fullscreen Height (200 or 240)", 200);

    // try WinDirect from SciTech
    /*if (fUseWinDirectFromSciTech && fFullscreen) {
      try {
        trace("Trying WinDirect from SciTech");

        // create fullscreen display object
        trace("Using mode $13 at 320x200");
        return new C64DisplayWinDirect320x200();

      } catch (...) {
        trace("WinDirect from SciTech failed");
      }
    }*/

    // try DirectDraw
    if (fUseDirectDraw && (!fFullscreen || iFullscreenWidth == 320)) {
      try {
        trace("Trying DirectDraw");

        // create directdraw object
        if (hDirectDrawLib == NULL) {
          win(hDirectDrawLib = LoadLibrary("ddraw.dll"));
        }
        if (pDirectDraw == NULL) {
          HRESULT (WINAPI* pfnDirectDrawCreate)(GUID*, LPDIRECTDRAW*, IUnknown*);
          win(pfnDirectDrawCreate = (HRESULT (WINAPI*)(GUID*, LPDIRECTDRAW*, IUnknown*))GetProcAddress(hDirectDrawLib, "DirectDrawCreate"));
          dd(pfnDirectDrawCreate(NULL, &pDirectDraw, NULL));
          dd(pDirectDraw->SetCooperativeLevel(hwnd, DDSCL_NORMAL));
        }

        // create DirectDraw fullscreen display object
        if (fFullscreen) {
          trace("Using fullscreen mode 320x%d", iFullscreenHeight);
          if (iFullscreenHeight == 240) {
            pcDisplayMode = "DirectDraw ModeX 320x240";
          } else {
            pcDisplayMode = "DirectDraw ModeX 320x200";
          }
          return new C64DisplayDirectDrawFullscreen(hwnd, pDirectDraw, iFullscreenHeight);
        } else {

          // get current display mode
          DDSURFACEDESC ddsd;
          memset(&ddsd, 0, sizeof ddsd);
          ddsd.dwSize = sizeof ddsd;
          dd(pDirectDraw->GetDisplayMode(&ddsd));
          assert((ddsd.dwFlags & DDSD_PIXELFORMAT) != 0);

          // create DirectDraw window display object
          if ((ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) != 0) {
            trace("Using 8 bit palette mode");
            pcDisplayMode = "DirectDraw 8 bit";
            return new C64DisplayDirectDraw8(hwnd, pDirectDraw);
          } else {
            if ((ddsd.ddpfPixelFormat.dwFlags & DDPF_RGB) != 0) {
              switch (ddsd.ddpfPixelFormat.dwRGBBitCount) {
              case 16:
                trace("Using 16 bit hicolor mode");
                pcDisplayMode = "DirectDraw 16 bit";
                return new C64DisplayDirectDraw16(hwnd, pDirectDraw);
              case 32:
                pcDisplayMode = "DirectDraw 32 bit";
                trace("Using 32 bit truecolor mode");
                return new C64DisplayDirectDraw32(hwnd, pDirectDraw);
              }
            }
          }
          error("DirectDraw returns unknown color depth\nSupported formats are 8, 16 and 32 bit");

        }

      } catch (...) {
        trace("DirectDraw failed");
      }
    }

    // try DIBSection
    if (fUseDIBSection && !fFullscreen) {
      try {
        trace("Using DIBSection");
        pcDisplayMode = "DIBSection";
        return new C64DisplayDIBSection(hwnd);
      } catch (...) {
        trace("DIBSection failed");
      }
    }

    // try WinG
    if (fUseWinG && !fFullscreen) {
      try {
        trace("Using WinG");
        pcDisplayMode = "WinG";
        return new C64DisplayWinG(hwnd);
      } catch (...) {
        trace("WinG failed");
      }
    }

    error("Couldn't create C64 display");
    return NULL;
  }

  // window size or position has changed
  void UpdateWindowSizeAndPosition(HWND hwnd) {

    // get window size and position
    RECT rcWindow;
    win(GetClientRect(hwnd, &rcWindow));
    win(ClientToScreen(hwnd, (LPPOINT)&rcWindow));
    rcWindow.right += rcWindow.left;
    rcWindow.bottom += rcWindow.top;

    // get C64 size and position
    int iWindowBorderX = gconf.GetInt("Display", "Window Border X (0..32)", 8);
    int iWindowBorderY = gconf.GetInt("Display", "Window Border Y (0..50)", 8);
    RECT rcC64;
    rcC64.left = 128 - iWindowBorderX;
    rcC64.right = 448 + iWindowBorderX;
    rcC64.top = 51 - iWindowBorderY;
    rcC64.bottom = 251 + iWindowBorderY;

    // resize window if factors are not 0.5, 1.0, 1.5, ...
    int iWidth = rcWindow.right - rcWindow.left;
    int iHeight = rcWindow.bottom - rcWindow.top;
    int iC64Width = rcC64.right - rcC64.left;
    int iC64Height = rcC64.bottom - rcC64.top;
    int iFactorX2 = (iWidth * 2 + (iC64Width >> 1)) / iC64Width;
    int iFactorY2 = (iHeight * 2 + (iC64Height >> 1)) / iC64Height;
    static flag fInMove = false;
    if (!IsZoomed(hwnd) && !fInMove && (iWidth != iC64Width * iFactorX2 / 2 || iHeight != iC64Height * iFactorY2 / 2)) {
      trace("resizing window: iFactorX = %d.%d, iFactorY = %d.%d", iFactorX2 / 2, iFactorX2 & 1 ? 5 : 0, iFactorY2 / 2, iFactorY2 & 1 ? 5 : 0);
      RECT rc = rcWindow;
      rc.right = rc.left + iC64Width * iFactorX2 / 2;
      rc.bottom = rc.top + iC64Height * iFactorY2 / 2;
      AdjustWindowRect(&rc, GetWindowLong(hwnd, GWL_STYLE), false);
      fInMove = true;
      MoveWindow(hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, true);
      fInMove = false;
    } else {
      pC64Display->Resize(rcWindow, rcC64);
    }

  }

  // create new display after switching fullscreen/window
  void Init(HWND hwnd, flag fNewFullscreen) {

    // delete old display
    if (pC64Display != NULL) {
      delete pC64Display;
      pC64Display = NULL;
      pbC64Screen = NULL;
    }

    // get colors
    byte abColor[16 * 3] = {
      0x20, 0x20, 0x20, // black
      0xFF, 0xFF, 0xFF, // white
      0xB6, 0x20, 0x20, // red
      0x71, 0xFF, 0xFF, // cyan
      0xB6, 0x20, 0xB6, // purple
      0x20, 0xB6, 0x20, // green
      0x20, 0x20, 0xB6, // blue
      0xFF, 0xFF, 0x20, // yellow
      0xB6, 0x71, 0x20, // orange
      0x91, 0x44, 0x20, // brown
      0xFF, 0x71, 0x71, // light red
      0x71, 0x71, 0x71, // dark grey
      0x91, 0x91, 0x91, // grey
      0x71, 0xFF, 0x71, // light green
      0x71, 0x71, 0xFF, // light blue
      0xB6, 0xB6, 0xB6, // light grey
    };
    for (int i = 0; i < 16; i++) {
      const char* apcName[16] = {
        "0 Black (Default 32,32,32)",
        "1 White (Default 255,255,255)",
        "2 Red (Default 182,32,32)",
        "3 Cyan (Default 113,255,255)",
        "4 Purple (Default 182,32,182)",
        "5 Green (Default 32,182,32)",
        "6 Blue (Default 32,32,182)",
        "7 Yellow (Default 255,255,32)",
        "8 Orange (Default 182,113,32)",
        "9 Brown (Default 145,68,32)",
        "A Light Red (Default 255,113,113)",
        "B Dark Grey (Default 113,113,113)",
        "C Grey (Default 145,145,145)",
        "D Light Green (Default 113,255,113)",
        "E Light Blue (Default 113,113,255)",
        "F Light Grey (Default 182,182,182)"
      };
      char acDefault[40];
      wsprintf(acDefault, "%d,%d,%d", abColor[i * 3 + 0], abColor[i * 3 + 1], abColor[i * 3 + 2]);
      char ac[80];
      gconf.GetString("Colors", apcName[i], ac, sizeof ac, acDefault);
      char* pc = strtok(ac, ", ");
      if (pc != NULL) {
        abColor[i * 3 + 0] = (byte)atoi(pc);
        pc = strtok(NULL, ",");
        if (pc != NULL) {
          abColor[i * 3 + 1] = (byte)atoi(pc);
          pc = strtok(NULL, ",");
          if (pc != NULL) {
            abColor[i * 3 + 2] = (byte)atoi(pc);
          }
        }
      }
    }

    // create new display
    fFullscreen = fNewFullscreen;
    try {
      pC64Display = CreateC64Display(hwnd);
      pbC64Screen = pC64Display->GetC64Screen(512, 312, abColor);
    } catch (...) {
      if (pC64Display != NULL) {
        delete pC64Display;
        pC64Display = NULL;
        pbC64Screen = NULL;
      }
      fFullscreen = !fFullscreen;
      pC64Display = CreateC64Display(hwnd);
      pbC64Screen = pC64Display->GetC64Screen(512, 312, abColor);
    }
    gconf.SetInt("Display", "Fullscreen (1 or 0)", fFullscreen);

    // set window size and position
    if (!fFullscreen) {
      UpdateWindowSizeAndPosition(hwnd);
      pC64Display->RealizePalette(true);
    }

  }

  // window proc handles display related messages and <Alt+Enter>
  LRESULT WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM /*lParam*/, flag& fReturn) {
    try {
      if (pC64Display == NULL) {
        return 0;
      }
      switch(uMsg) {
      case WM_PAINT:
        pC64Display->OnFrame();
        break;
      case WM_SIZE:
      case WM_MOVE:
        if (!fFullscreen) {
          UpdateWindowSizeAndPosition(hwnd);
        }
        break;
      case WM_QUERYNEWPALETTE:
        fReturn = true;
        return pC64Display->RealizePalette(true);
      case WM_PALETTECHANGED:
        if ((HWND)wParam != hwnd) {
          pC64Display->RealizePalette(false);
        }
        break;
      case WM_SYSKEYDOWN:
        if (wParam == VK_RETURN) {
          fReturn = true;
          return 0;
        }
        break;
      case WM_SYSKEYUP:
        if (wParam == VK_RETURN) {
          trace("Alt+Enter pressed, switching screen mode");
          Init(hwnd, !fFullscreen);
          fReturn = true;
          return 0;
        }
        break;
      case WM_DESTROY:
        delete pC64Display;
        pC64Display = NULL;
        break;
      }
    } catch (...) {
      report();
    }
    return 0;
  }  

  // get pointer to C64 screen
  byte* GetC64Screen() {
    return pbC64Screen;
  }

  // display frame
  void OnFrame() {
    pC64Display->OnFrame();
  }

  // get mode string
  const char* GetMode() {
    return pcDisplayMode;
  }

};
