Top secrets sources NedoPC pentevo

Rev

Rev 799 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed | ?url?

#include "std.h"

#include "resource.h"
#include "emul.h"
#include "vars.h"
#include "init.h"
#include "dx.h"
#include "draw.h"
#include "dxrend.h"
#include "dxrendch.h"
#include "dxrframe.h"
#include "dxr_text.h"
#include "dxr_rsm.h"
#include "dxr_advm.h"
#include "dxovr.h"
#include "dxerr.h"
#include "sndrender/sndcounter.h"
#include "sound.h"
#include "savesnd.h"
#include "emulkeys.h"
#include "funcs.h"
#include "util.h"

unsigned char active = 0, pause = 0;

static const DWORD SCU_SCALE1 = 0x10;
static const DWORD SCU_SCALE2 = 0x20;
static const DWORD SCU_SCALE3 = 0x30;
static const DWORD SCU_SCALE4 = 0x40;
static const DWORD SCU_LOCK_MOUSE = 0x50;

#define MAXDSPIECE (48000*4/20)
#define DSBUFFER_SZ (2*conf.sound.fq*4)

#define SLEEP_DELAY 2

static HMODULE D3d9Dll = nullptr;
static IDirect3D9 *D3d9 = nullptr;
static IDirect3DDevice9 *D3dDev = nullptr;
static IDirect3DTexture9 *Texture = nullptr;
static IDirect3DSurface9 *SurfTexture = nullptr;

LPDIRECTDRAW2 dd;
LPDIRECTDRAWSURFACE sprim, surf0, surf1;
static LPDIRECTDRAWSURFACE surf2;
static PVOID SurfMem1 = nullptr;
static ULONG SurfPitch1 = 0;

static LPDIRECTINPUTDEVICE dikeyboard;
static LPDIRECTINPUTDEVICE dimouse;
LPDIRECTINPUTDEVICE2 dijoyst;

static LPDIRECTSOUND ds;
static LPDIRECTSOUNDBUFFER dsbf;
static LPDIRECTDRAWPALETTE pal;
static LPDIRECTDRAWCLIPPER clip;

static unsigned dsoffset, dsbuffer_sz;

static void FlipBltAlign4();
static void FlipBltAlign16();

static void (*FlipBltMethod)() = nullptr;

static void StartD3d(HWND Wnd);
static void DoneD3d(bool DeInitDll = true);
static void SetVideoModeD3d(bool Exclusive);

/* ---------------------- renders ------------------------- */

RENDER renders[] =
{
   { "Normal",                    render_small,   "normal",    RF_DRIVER | RF_1X },
   { "Double size",               render_dbl,     "double",    RF_DRIVER | RF_2X },
   { "Triple size",               render_3x,      "triple",    RF_DRIVER | RF_3X },
   { "Quad size",                 render_quad,    "quad",      RF_DRIVER | RF_4X },
   { "Anti-Text64",               render_text,    "text",      RF_DRIVER | RF_2X | RF_USEFONT },
   { "Frame resampler",           render_rsm,     "resampler", RF_DRIVER | RF_8BPCH },

   { "TV Emulation",              render_tv,      "tv",        RF_YUY2 | RF_OVR },
   { "Bilinear filter (MMX,fullscr)", render_bil, "bilinear",  RF_2X | RF_PALB },
   { "Vector scaling (fullscr)",  render_scale,   "scale",     RF_2X },
   { "AdvMAME scale (fullscr)",   render_advmame, "advmame",   RF_DRIVER },

   { "Chunky overlay (fast!)",    render_ch_ov,   "ch_ov",     RF_OVR | 0*RF_128x96 | 0*RF_64x48 | RF_BORDER | RF_USE32AS16 },
   { "Chunky 32bit hardware (flt,fullscr)", render_ch_hw,   "ch_hw",     RF_CLIP | 0*RF_128x96 | 0*RF_64x48 | RF_BORDER | RF_32 | RF_USEC32 },

   { "Chunky 16bit, low-res, (flt,fullscr)",render_c16bl,   "ch_bl",     RF_16 | RF_BORDER | RF_USEC32 },
   { "Chunky 16bit, hi-res, (flt,fullscr)", render_c16b,    "ch_b",      RF_16 | RF_2X | RF_BORDER | RF_USEC32 },
   { "Ch4x4 true color (fullscr)",render_c4x32b,  "ch4true",   RF_32 | RF_2X | RF_BORDER | RF_USEC32 },

   { nullptr,nullptr,nullptr,0 }
};

size_t renders_count = _countof(renders);

const RENDER drivers[] =
{
   { "video memory (8bpp)",  nullptr, "ddraw",  RF_8 },
   { "video memory (16bpp)", nullptr, "ddrawh", RF_16 },
   { "video memory (32bpp)", nullptr, "ddrawt", RF_32 },
   { "gdi device context",   nullptr, "gdi",    RF_GDI },
   { "overlay",              nullptr, "ovr",    RF_OVR },
   { "hardware blitter",     nullptr, "blt",    RF_CLIP },
   { "hardware 3d",          nullptr, "d3d",    RF_D3D },
   { "hardware 3d exclusive",nullptr, "d3de",   RF_D3DE },
   { nullptr,nullptr,nullptr,0 }
};

inline void switch_video()
{
   static unsigned char eff7 = u8(-1U);
   if ((comp.pEFF7 ^ eff7) & EFF7_HWMC)
   {
       video_timing_tables();
       eff7 = comp.pEFF7;
   }
}

static void FlipGdi()
{
    if (needclr)
    {
        needclr--;
        gdi_frame();
    }
    renders[conf.render].func(gdibuf, temp.ox*temp.obpp/8); // render to memory buffer
    // copy bitmap to gdi dc
    SetDIBitsToDevice(temp.gdidc, int(temp.gx), int(temp.gy), temp.ox, temp.oy, 0, 0, 0, temp.oy, gdibuf, &gdibmp.header, DIB_RGB_COLORS);
}

static void FlipBltAlign16()
{
   // Îòðèñîâêà â áóôåð âûðàâíåííûé íà 16 áàéò (íåîáõîäèìî äëÿ çàïñè ÷åðåç sse2)
   renders[conf.render].func(PUCHAR(SurfMem1), SurfPitch1);

restore_lost:;
   DDSURFACEDESC desc;
   desc.dwSize = sizeof(desc);
   HRESULT r = surf1->Lock(nullptr, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, nullptr);
   if (r != DD_OK)
   {
       if(r == DDERR_SURFACELOST)
       {
           surf1->Restore();
           HRESULT r2 = surf1->IsLost();
           if(r2 == DDERR_SURFACELOST)
               Sleep(1);

           if(r2 == DD_OK || r2 == DDERR_SURFACELOST)
               goto restore_lost;
       }
       if (!active)
           return;
       printrdd("IDirectDrawSurface2::Lock() [buffer]", r);
       exit();
   }

   PUCHAR SrcPtr, DstPtr;
   SrcPtr = PUCHAR(SurfMem1);
   DstPtr = PUCHAR(desc.lpSurface);

   size_t LineSize = temp.ox * (temp.obpp >> 3);
   for(unsigned y = 0; y < temp.oy; y++)
   {
       memcpy(DstPtr, SrcPtr, LineSize);
       SrcPtr += SurfPitch1;
       DstPtr += desc.lPitch;
   }

   r = surf1->Unlock(nullptr);
   if(r != DD_OK)
   {
       if(r == DDERR_SURFACELOST)
       {
           surf1->Restore();
           HRESULT r2 = surf1->IsLost();
           if(r2 == DDERR_SURFACELOST)
               Sleep(1);

           if(r2 == DD_OK || r2 == DDERR_SURFACELOST)
               goto restore_lost;
       }
       printrdd("IDirectDrawSurface2::Unlock() [buffer]", r);
       exit();
   }
}

static void FlipBltAlign4()
{
restore_lost:;
   DDSURFACEDESC desc;
   desc.dwSize = sizeof(desc);
   HRESULT r = surf1->Lock(nullptr, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, nullptr);
   if (r != DD_OK)
   {
       if(r == DDERR_SURFACELOST)
       {
           surf1->Restore();
           HRESULT r2 = surf1->IsLost();
           if(r2 == DDERR_SURFACELOST)
               Sleep(1);

           if(r2 == DD_OK || r2 == DDERR_SURFACELOST)
               goto restore_lost;
       }
       if (!active)
           return;
       printrdd("IDirectDrawSurface2::Lock() [buffer]", r);
       exit();
   }

   // Îòðèñîâêà â áóôåð âûðàâíåííûé íà 4 áàéòà
   renders[conf.render].func(PUCHAR(desc.lpSurface), unsigned(desc.lPitch));

   r = surf1->Unlock(nullptr);
   if(r != DD_OK)
   {
       if(r == DDERR_SURFACELOST)
       {
           surf1->Restore();
           HRESULT r2 = surf1->IsLost();
           if(r2 == DDERR_SURFACELOST)
               Sleep(1);

           if(r2 == DD_OK || r2 == DDERR_SURFACELOST)
               goto restore_lost;
       }
       printrdd("IDirectDrawSurface2::Unlock() [buffer]", r);
       exit();
   }
}

static bool CalcFlipRect(LPRECT R)
{
   int w = temp.client.right - temp.client.left;
   int h = temp.client.bottom - temp.client.top;
   int n = min(w / int(temp.ox), h / int(temp.oy));

   POINT P = { 0, 0 };
   ClientToScreen(wnd, &P);
   int x0 = P.x;
   int y0 = P.y;
   R->left = (w - (int(temp.ox)*n)) / 2;
   R->right = (w + (int(temp.ox)*n)) / 2;
   R->top =  (h - (int(temp.oy)*n)) / 2;
   R->bottom = (h + (int(temp.oy)*n)) / 2;
   OffsetRect(R, x0, y0);
   return IsRectEmpty(R) == FALSE;
}

static void FlipBlt()
{
   HRESULT r;

restore_lost:;
   FlipBltMethod();

   assert(!IsRectEmpty(&temp.client));

   DDBLTFX Fx;
   Fx.dwSize = sizeof(Fx);
   Fx.dwDDFX = 0;

   RECT R;
   if(!CalcFlipRect(&R))
       return;

   // Î÷èñòêà back áóôåðà
   Fx.dwFillColor = 0;
   r = surf2->Blt(nullptr, nullptr, nullptr, DDBLT_WAIT | DDBLT_ASYNC |  DDBLT_COLORFILL, &Fx);
   if (r != DD_OK)
   {
       if(r == DDERR_SURFACELOST)
       {
           surf2->Restore();
           if(surf2->IsLost() == DDERR_SURFACELOST)
               Sleep(1);
           goto restore_lost;
       }
       printrdd("IDirectDrawSurface2::Blt(1)", r);
       exit();
   }

   // Ðèñîâàíèå êàðòèíêè ñ ìàñøòàáèðîâàíèåì â n ðàç èç surf1 (320x240) -> surf2 (ðàçìåð ýêðàíà)
   r = surf2->Blt(&R, surf1, nullptr, DDBLT_WAIT | DDBLT_ASYNC | DDBLT_DDFX, &Fx);
   if (r != DD_OK)
   {
       if(r == DDERR_SURFACELOST)
       {
           surf1->Restore();
           surf2->Restore();
           if(surf1->IsLost() == DDERR_SURFACELOST || surf2->IsLost() == DDERR_SURFACELOST)
               Sleep(1);
           goto restore_lost;
       }
       printrdd("IDirectDrawSurface2::Blt(2)", r);
       exit();
   }

   // Êîïèðîâàíèå surf2 íà surf0 (ýêðàí, ðàçìåð ýêðàíà)
   r = surf0->Blt(nullptr, surf2, nullptr, DDBLT_WAIT | DDBLT_ASYNC | DDBLT_DDFX, &Fx);
   if (r != DD_OK)
   {
       if(r == DDERR_SURFACELOST)
       {
           surf0->Restore();
           surf2->Restore();
           if(surf0->IsLost() == DDERR_SURFACELOST || surf2->IsLost() == DDERR_SURFACELOST)
               Sleep(1);
           goto restore_lost;
       }
       printrdd("IDirectDrawSurface2::Blt(3)", r);
       exit();
   }
}

static bool CalcFlipRectD3d(LPRECT R)
{
   int w = temp.client.right - temp.client.left;
   int h = temp.client.bottom - temp.client.top;
   int n = min(w / int(temp.ox), h / int(temp.oy));

   R->left = (w - (int(temp.ox)*n)) / 2;
   R->right = (w + (int(temp.ox)*n)) / 2;
   R->top =  (h - (int(temp.oy)*n)) / 2;
   R->bottom = (h + (int(temp.oy)*n)) / 2;

   return IsRectEmpty(R) == FALSE;
}

static void FlipD3d()
{
    if(!SUCCEEDED(D3dDev->BeginScene()))
        return;

    HRESULT Hr;
    IDirect3DSurface9 *SurfBackBuffer0 = nullptr;

    Hr = D3dDev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &SurfBackBuffer0);
    if (Hr != DD_OK)
    { printrdd("IDirect3DDevice9::GetBackBuffer(0)", Hr); exit(); }

    D3DLOCKED_RECT Rect = { };

    Hr = SurfTexture->LockRect(&Rect, nullptr, D3DLOCK_DISCARD);
    if(FAILED(Hr))
    {
        printrdd("IDirect3DDevice9::LockRect()", Hr);
//        __debugbreak();
        exit();
    }

    renders[conf.render].func((u8 *)Rect.pBits, unsigned(Rect.Pitch));

    Hr = SurfTexture->UnlockRect();
    if(FAILED(Hr))
    {
        printrdd("IDirect3DDevice9::UnlockRect()", Hr);
        //        __debugbreak();
        exit();
    }

    Hr = D3dDev->ColorFill(SurfBackBuffer0, nullptr, D3DCOLOR_XRGB(0, 0, 0));
    if(FAILED(Hr))
    {
        printrdd("IDirect3DDevice9::ColorFill()", Hr);
//        __debugbreak();
        exit();
    }

    RECT R;
    if(!CalcFlipRectD3d(&R))
    {
        D3dDev->EndScene();
        SurfBackBuffer0->Release();
        return;
    }

    Hr = D3dDev->StretchRect(SurfTexture, nullptr, SurfBackBuffer0, &R, D3DTEXF_POINT);
    if(FAILED(Hr))
    {
        printrdd("IDirect3DDevice9::StretchRect()", Hr);
//        __debugbreak();
        exit();
    }
    D3dDev->EndScene();

    // Present the backbuffer contents to the display
    Hr = D3dDev->Present(nullptr, nullptr, nullptr, nullptr);
    SurfBackBuffer0->Release();

    if(FAILED(Hr))
    {
        if(Hr == D3DERR_DEVICELOST)
        {
            SetVideoModeD3d((temp.rflags & RF_D3DE) != 0);
            return;
        }

        printrdd("IDirect3DDevice9::Present()", Hr);
//        __debugbreak();
        exit();
    }
}

static void FlipVideoMem()
{
   // draw direct to video memory, overlay
   LPDIRECTDRAWSURFACE drawsurf = conf.flip ? surf1 : surf0;

   DDSURFACEDESC desc;
   desc.dwSize = sizeof desc;
restore_lost:;
   HRESULT r = drawsurf->Lock(nullptr, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, nullptr);
   if (r != DD_OK)
   {
      if (!active)
          return;
      if(r == DDERR_SURFACELOST)
      {
          drawsurf->Restore();
          if(drawsurf->IsLost() == DDERR_SURFACELOST)
              Sleep(1);
          goto restore_lost;
      }
      printrdd("IDirectDrawSurface2::Lock()", r);
      if (!exitflag)
          exit();
   }
   if (needclr)
   {
       needclr--;
       _render_black((unsigned char*)desc.lpSurface, unsigned(desc.lPitch));
   }
   renders[conf.render].func((unsigned char*)desc.lpSurface + unsigned(desc.lPitch)*temp.ody + temp.odx, unsigned(desc.lPitch));
   drawsurf->Unlock(nullptr);

   if (conf.flip) // draw direct to video memory
   {
      r = surf0->Flip(nullptr, DDFLIP_WAIT);
      if (r != DD_OK)
      {
          if(r == DDERR_SURFACELOST)
          {
              surf0->Restore();
              if(surf0->IsLost() == DDERR_SURFACELOST)
                  Sleep(1);
              goto restore_lost;
          }
          printrdd("IDirectDrawSurface2::Flip()", r);
          exit();
      }
   }
}

void flip()
{
   if(temp.Minimized)
       return;
   switch_video();

   if (conf.flip && (temp.rflags & (RF_GDI | RF_CLIP)))
      dd->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, nullptr);

   if (temp.rflags & RF_GDI) // gdi
   {
      FlipGdi();
      return;
   }

   if (surf0 && surf0->IsLost() == DDERR_SURFACELOST)
       surf0->Restore();
   if (surf1 && surf1->IsLost() == DDERR_SURFACELOST)
       surf1->Restore();

   if (temp.rflags & RF_CLIP) // hardware blitter
   {
      if(IsRectEmpty(&temp.client)) // client area is empty
          return;

      FlipBlt();
      return;
   }

   if (temp.rflags & (RF_D3D | RF_D3DE)) // direct 3d
   {
      if(IsRectEmpty(&temp.client)) // client area is empty
          return;

      FlipD3d();
      return;
   }

   // draw direct to video memory, overlay
   FlipVideoMem();
}

static HWAVEOUT hwo = nullptr;
static WAVEHDR wq[MAXWQSIZE];
static unsigned char wbuffer[MAXWQSIZE*MAXDSPIECE];
static unsigned wqhead, wqtail;

void __fastcall do_sound_none()
{

}

void __fastcall do_sound_wave()
{
   MMRESULT r;

   for (;;) // release all done items
   {
      while ((wqtail != wqhead) && (wq[wqtail].dwFlags & WHDR_DONE))
      { // ready!
         if ((r = waveOutUnprepareHeader(hwo, &wq[wqtail], sizeof(WAVEHDR))) != MMSYSERR_NOERROR)
         {
             printrmm("waveOutUnprepareHeader()", r);
             exit();
         }
         if (++wqtail == conf.soundbuffer)
             wqtail = 0;
      }
      if ((wqhead+1)%conf.soundbuffer != wqtail)
          break; // some empty item in queue
/* [vv]
*/

      if (conf.sleepidle)
          Sleep(SLEEP_DELAY);
   }

   if(!spbsize)
       return;

//   __debugbreak();
   // put new item and play
   PCHAR bfpos = PCHAR(wbuffer + wqhead*MAXDSPIECE);
   memcpy(bfpos, sndplaybuf, spbsize);
   wq[wqhead].lpData = bfpos;
   wq[wqhead].dwBufferLength = spbsize;
   wq[wqhead].dwFlags = 0;

   if ((r = waveOutPrepareHeader(hwo, &wq[wqhead], sizeof(WAVEHDR))) != MMSYSERR_NOERROR)
   {
       printrmm("waveOutPrepareHeader()", r);
       exit();
   }

   if ((r = waveOutWrite(hwo, &wq[wqhead], sizeof(WAVEHDR))) != MMSYSERR_NOERROR)
   {
       printrmm("waveOutWrite()", r);
       exit();
   }

   if (++wqhead == conf.soundbuffer)
       wqhead = 0;

//  int bs = wqhead-wqtail; if (bs<0)bs+=conf.soundbuffer; if (bs < 8) goto again;

}

// directsound part
// begin
static void restore_sound_buffer()
{
//   for (;;) {
      DWORD status = 0;
      dsbf->GetStatus(&status);
      if (status & DSBSTATUS_BUFFERLOST)
      {
          printf("%s\n", __FUNCTION__);
          Sleep(18);
          dsbf->Restore();
          // Sleep(200);
      }
//      else break;
//   }
}

static void clear_buffer()
{
//   printf("%s\n", __FUNCTION__);
//   __debugbreak();

   restore_sound_buffer();
   HRESULT r;
   void *ptr1, *ptr2;
   DWORD sz1, sz2;
   r = dsbf->Lock(0, 0, &ptr1, &sz1, &ptr2, &sz2, DSBLOCK_ENTIREBUFFER);
   if (r != DS_OK)
       return;
   memset(ptr1, 0, sz1);
   //memset(ptr2, 0, sz2);
   dsbf->Unlock(ptr1, sz1, ptr2, sz2);
}

static int maxgap;

void __fastcall do_sound_ds()
{
   HRESULT r;

   do
   {
      int play, write;
      if ((r = dsbf->GetCurrentPosition((DWORD*)&play, (DWORD*)&write)) != DS_OK)
      {
         if (r == DSERR_BUFFERLOST)
         {
             restore_sound_buffer();
             return;
         }

         printrds("IDirectSoundBuffer::GetCurrentPosition()", r);
         exit();
      }

      int gap = write - play;
      if (gap < 0)
          gap += dsbuffer_sz;

      int pos = int(dsoffset) - play;
      if (pos < 0)
          pos += dsbuffer_sz;
      maxgap = max(maxgap, gap);

      if (pos < maxgap || pos > 10 * (int)maxgap)
      {
         dsoffset = unsigned(3 * maxgap);
         clear_buffer();
         dsbf->Stop();
         dsbf->SetCurrentPosition(0);
         break;
      }

      if (pos < 2 * maxgap)
          break;

      if ((r = dsbf->Play(0, 0, DSBPLAY_LOOPING)) != DS_OK)
      {
          if (r == DSERR_BUFFERLOST)
          {
              restore_sound_buffer();
              return;
          }

          if (r == AUDCLNT_E_DEVICE_IN_USE)
          {
              printrds("IDirectSoundBuffer::Play()", r);
              printf("sound device is used exclusively by another apllication, switching to no sound mode\n");
              conf.sound.do_sound = do_sound_none;
              return;
          }

          printrds("IDirectSoundBuffer::Play()", r);
          exit();
      }

      if ((conf.SyncMode == SM_SOUND) && conf.sleepidle)
          Sleep(SLEEP_DELAY);
   } while(conf.SyncMode == SM_SOUND);

   dsoffset %= dsbuffer_sz;

   if(!spbsize)
       return;

   void *ptr1, *ptr2;
   DWORD sz1, sz2;
   r = dsbf->Lock(dsoffset, spbsize, &ptr1, &sz1, &ptr2, &sz2, 0);
   if (r != DS_OK)
   {
       if (r == DSERR_BUFFERLOST)
       {
           restore_sound_buffer();
           return;
       }
//       __debugbreak();
       printf("dsbuffer_sz=%u, dsoffset=%u, spbsize=%u\n", dsbuffer_sz, dsoffset, spbsize);
       printrds("IDirectSoundBuffer::Lock()", r);
       exit();
   }
   memcpy(ptr1, sndplaybuf, sz1);
   if (ptr2)
       memcpy(ptr2, ((char *)sndplaybuf)+sz1, sz2);

   r = dsbf->Unlock(ptr1, sz1, ptr2, sz2);
   if (r != DS_OK)
   {
       if (r == DSERR_BUFFERLOST)
       {
           restore_sound_buffer();
           return;
       }
//       __debugbreak();
       printf("dsbuffer_sz=%u, dsoffset=%u, spbsize=%u\n", dsbuffer_sz, dsoffset, spbsize);
       printrds("IDirectSoundBuffer::Unlock()", r);
       exit();
   }

   dsoffset += spbsize;
   dsoffset %= dsbuffer_sz;
}
// directsound part
// end

static unsigned OldRflags = 0;

void sound_play()
{
//   printf("%s\n", __FUNCTION__);
   maxgap = 2000;
   restart_sound();
}


void sound_stop()
{
//   printf("%s\n", __FUNCTION__);
//   __debugbreak();
   if(dsbf)
   {
      dsbf->Stop(); // don't check
      clear_buffer();
   }
}

void do_sound()
{
   if (savesndtype == 1)
      if (fwrite(sndplaybuf,1,spbsize,savesnd)!=spbsize)
         savesnddialog(); // write error - disk full - close file

   conf.sound.do_sound();
}

void OnEnterGui()
{
//    printf("%s->%p\n", __FUNCTION__, D3dDev);
    sound_stop();

    // Âûâîä GUI ïðè àêòèâíîì d3d exclusive ðåæèìå
    // íà âðåìÿ âûâîäà GUI ïåðåõîä â non exclusive fullscreen
    if((temp.rflags & RF_D3DE) && D3dDev)
    {
        OldRflags = temp.rflags;
        SetVideoModeD3d(false);
        SendMessage(wnd, WM_USER, 0, 0);
        flip();
    }
}

void OnExitGui(bool RestoreVideo)
{
//    printf("%s->%p, %d\n", __FUNCTION__, D3dDev, RestoreVideo);
    sound_play();
    if(!RestoreVideo)
    {
        return;
    }

    if(!D3dDev)
    {
        return;
    }

    // Âîçâðàò èç GUI â d3d exclusive ðåæèì
    if((temp.rflags & RF_D3DE))
    {
        SetVideoModeD3d(true);
    }

    // Ïåðåêëþ÷åíèå RF_D3DE->RF_D3D (â ìåíþ íàñòðîåê ïåðåêëþ÷åí âèäåîäðàéâåð)
    if((OldRflags & RF_D3DE) && (temp.rflags & RF_D3D))
    {
        SetVideoModeD3d(false);
    }
}

#define STATUS_PRIVILEGE_NOT_HELD        ((NTSTATUS)0xC0000061L)
#define SE_INC_BASE_PRIORITY_PRIVILEGE      (14L)
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

typedef NTSTATUS (NTAPI *TRtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN Client, PBOOLEAN WasEnabled);

void set_priority()
{
    if (!conf.highpriority || !conf.sleepidle)
       return;

    SYSTEM_INFO SysInfo;
    GetSystemInfo(&SysInfo);
    ULONG Cpus = SysInfo.dwNumberOfProcessors;

    ULONG HighestPriorityClass = HIGH_PRIORITY_CLASS;

    if(Cpus > 1)
    {
        HMODULE NtDll = GetModuleHandle("ntdll.dll");
        TRtlAdjustPrivilege RtlAdjustPrivilege = (TRtlAdjustPrivilege)GetProcAddress(NtDll, "RtlAdjustPrivilege");

        BOOLEAN WasEnabled = FALSE;
        NTSTATUS Status = RtlAdjustPrivilege(SE_INC_BASE_PRIORITY_PRIVILEGE, TRUE, FALSE, &WasEnabled);
        if(!NT_SUCCESS(Status))
        {
            err_printf("enabling SE_INC_BASE_PRIORITY_PRIVILEGE, %X", Status);
            if(Status == STATUS_PRIVILEGE_NOT_HELD)
            {
                err_printf("program not run as administrator or SE_INC_BASE_PRIORITY_PRIVILEGE is not enabled via group policy");
            }
            printf("REALTIME_PRIORITY_CLASS not available, fallback to HIGH_PRIORITY_CLASS\n");
        }
        else
        {
            HighestPriorityClass = REALTIME_PRIORITY_CLASS;
        }
    }

    SetPriorityClass(GetCurrentProcess(), HighestPriorityClass);
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
}

void adjust_mouse_cursor()
{
   unsigned showcurs = conf.input.joymouse || !active || !(conf.fullscr || conf.lockmouse) || dbgbreak;
   while (ShowCursor(0) >= 0); // hide cursor
   if (showcurs) while (ShowCursor(1) <= 0); // show cursor
   if (active && conf.lockmouse && !dbgbreak)
   {
      RECT rc; GetClientRect(wnd, &rc);
      POINT p = { rc.left, rc.top };
      ClientToScreen(wnd, &p);
      rc.left = p.x;
      rc.top = p.y;
      p.x = rc.right;
      p.y = rc.bottom;
      ClientToScreen(wnd, &p);
      rc.right = p.x;
      rc.bottom = p.y;
      ClipCursor(&rc);
   } else ClipCursor(nullptr);
}

static HCURSOR crs[9];
static unsigned char mousedirs[9] = { 10, 8, 9, 2, 0, 1, 6, 4, 5 };

void updatebitmap()
{
   RECT rc;
   GetClientRect(wnd, &rc);
   DWORD newdx = DWORD(rc.right - rc.left), newdy = DWORD(rc.bottom - rc.top);
   if (hbm && (bm_dx != newdx || bm_dy != newdy))
   {
       DeleteObject(hbm);
       hbm = nullptr;
   }
   if(sprim)
       return; // don't trace window contents in overlay mode

   if(hbm)
   {
        DeleteObject(hbm);
        hbm = nullptr; // keeping bitmap is unsafe - screen paramaters may change
   }

   if(!hbm)
       hbm = CreateCompatibleBitmap(temp.gdidc, int(newdx), int(newdy));

   HDC dc = CreateCompatibleDC(temp.gdidc);

   bm_dx = newdx; bm_dy = newdy;
   HGDIOBJ PrevObj = SelectObject(dc, hbm);
   //SetDIBColorTable(dc, 0, 0x100, (RGBQUAD*)pal0);
   BitBlt(dc, 0, 0, int(newdx), int(newdy), temp.gdidc, 0, 0, SRCCOPY);
   SelectObject(dc, PrevObj);
   DeleteDC(dc);
}

/*
 movesize.c
 These values are indexes that represent rect sides. These indexes are
 used as indexes into rgimpiwx and rgimpiwy (which are indexes into the
 the rect structure) which tell the move code where to store the new x & y
 coordinates. Notice that when two of these values that represent sides
 are added together, we get a unique list of contiguous values starting at
 1 that represent all the ways we can size a rect. That also leaves 0 free
 a initialization value.

 The reason we need rgimpimpiw is for the keyboard interface - we
 incrementally decide what our 'move command' is. With the mouse interface
 we know immediately because we registered a mouse hit on the segment(s)
 we're moving.

     4           5
      \ ___3___ /
       |       |
       1       2
       |_______|
      /    6    \
     7           8
*/


//static const int rgimpimpiw[] = {1, 3, 2, 6};
static const int rgimpiwx[]   =
{
   0, // WMSZ_KEYSIZE
   0, // WMSZ_LEFT
   2, // WMSZ_RIGHT
  -1, // WMSZ_TOP
   0, // WMSZ_TOPLEFT
   2, // WMSZ_TOPRIGHT
  -1, // WMSZ_BOTTOM
   0, // WMSZ_BOTTOMLEFT
   2, // WMSZ_BOTTOMRIGHT
   0  // WMSZ_KEYMOVE
};
static const int rgimpiwy[]   = {0, -1, -1,  1, 1, 1,  3, 3, 3, 1};

#define WMSZ_KEYSIZE        0           // ;Internal
#define WMSZ_LEFT           1
#define WMSZ_RIGHT          2
#define WMSZ_TOP            3
#define WMSZ_TOPLEFT        4
#define WMSZ_TOPRIGHT       5
#define WMSZ_BOTTOM         6
#define WMSZ_BOTTOMLEFT     7
#define WMSZ_BOTTOMRIGHT    8
#define WMSZ_MOVE           9           // ;Internal
#define WMSZ_KEYMOVE        10          // ;Internal
#define WMSZ_SIZEFIRST      WMSZ_LEFT   // ;Internal

struct MOVESIZEDATA
{
    RECT rcDragCursor;
    POINT ptMinTrack;
    POINT ptMaxTrack;
    int cmd;
};

static void SizeRectX(MOVESIZEDATA *Msd, ULONG Pt)
{
    PINT psideDragCursor = ((PINT)(&Msd->rcDragCursor));
    /*
     * DO HORIZONTAL
     */


    /*
     * We know what part of the rect we're moving based on
     * what's in cmd.  We use cmd as an index into rgimpiw? which
     * tells us what part of the rect we're dragging.
     */


    /*
     * Get the approriate array entry.
     */

    int index = (int)rgimpiwx[Msd->cmd];   // AX
//    printf("Msd.cmd = %d, idx_x=%d\n", Msd->cmd, index);

    /*
     * Is it one of the entries we don't map (i.e.  -1)?
     */

    if (index < 0)
        return;

    psideDragCursor[index] = GET_X_LPARAM(Pt);

    int indexOpp = index ^ 0x2;

    /*
     * Now check to see if we're below the min or above the max. Get the width
     * of the rect in this direction (either x or y) and see if it's bad. If
     * so, map the side we're moving to the min or max.
     */

    int w = psideDragCursor[index] - psideDragCursor[indexOpp];

    if (indexOpp & 0x2)
    {
        w = -w;
    }

    int x;
    if(w < Msd->ptMinTrack.x)
    {
        x = Msd->ptMinTrack.x;
    }
    else if(w > Msd->ptMaxTrack.x)
    {
        x = Msd->ptMaxTrack.x;
    }
    else
    {
        return;
    }

    if (indexOpp & 0x2)
    {
        x = -x;
    }

    psideDragCursor[index] = x + psideDragCursor[indexOpp];
}

static void SizeRectY(MOVESIZEDATA *Msd, ULONG Pt)
{
    PINT psideDragCursor = ((PINT)(&Msd->rcDragCursor));
    /*
     * DO VERTICAL
     */


    /*
     * We know what part of the rect we're moving based on
     * what's in cmd.  We use cmd as an index into rgimpiw? which
     * tells us what part of the rect we're dragging.
     */


    /*
     * Get the approriate array entry.
     */

    int index = (int)rgimpiwy[Msd->cmd];   // AX

//    printf("idx_y=%d\n", index);
    /*
     * Is it one of the entries we don't map (i.e.  -1)?
     */

    if (index < 0)
        return;

    psideDragCursor[index] = GET_Y_LPARAM(Pt);

    int indexOpp = index ^ 0x2;

    /*
     * Now check to see if we're below the min or above the max. Get the width
     * of the rect in this direction (either x or y) and see if it's bad. If
     * so, map the side we're moving to the min or max.
     */

    int h = psideDragCursor[index] - psideDragCursor[indexOpp];

    if (indexOpp & 0x2)
    {
        h = -h;
    }

    int y;
    if(h < Msd->ptMinTrack.y)
    {
        y = Msd->ptMinTrack.y;
    }
    else if(h > Msd->ptMaxTrack.y)
    {
        y  = Msd->ptMaxTrack.y;
    }
    else
    {
        return;
    }

    if (indexOpp & 0x2)
    {
        y = -y;
    }

    psideDragCursor[index] = y + psideDragCursor[indexOpp];
}

static void SizeRect(MOVESIZEDATA *Msd, ULONG Pt)
{
    if(size_t(Msd->cmd) >= _countof(rgimpiwx))
    {
        return;
    }

    SizeRectX(Msd, Pt);
    SizeRectY(Msd, Pt);
}

static LRESULT CALLBACK WndProc(HWND hwnd,UINT uMessage,WPARAM wparam,LPARAM lparam)
{
   if(uMessage == WM_QUIT) // never heppens
   {
//       __debugbreak();
//       printf("WM_QUIT\n");
       exit();
   }

   if(uMessage == WM_CLOSE) // never heppens
   {
//       __debugbreak();
//       printf("WM_CLOSE\n");
       return 1;
   }

#if 1
   if (uMessage == WM_SETFOCUS && !pause)
   {
      active = 1; setpal(0);
//      sound_play();
      adjust_mouse_cursor();
      uMessage = WM_USER;
   }

   if (uMessage == WM_KILLFOCUS && !pause)
   {
      if (dd)
          dd->FlipToGDISurface();
      updatebitmap();
      setpal(1);
      active = 0;
//      sound_stop();
      conf.lockmouse = 0;
      adjust_mouse_cursor();
   }

   if(uMessage == WM_SIZING)
   {
//       printf("WM_SIZING(0x%X)\n", uMessage);
       return TRUE;
   }

   static bool Captured = false;
   static int x = 0, y = 0;
   static ULONG Mode;
   static MOVESIZEDATA Msd;

   if(uMessage == WM_LBUTTONUP && Captured)
   {
//       printf("WM_LBUTTONUP(0x%X)\n", uMessage);
       Captured = false;
       ReleaseCapture();
       return 0;
   }

   if(uMessage == WM_MOUSEMOVE)
   {
       if(Captured)
       {
//           printf("WM_MOUSEMOVE(0x%X), w=%d\n", uMessage, wparam);
           if(Mode == SC_MOVE)
           {
               POINT p = { GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
               ClientToScreen(hwnd, &p);
//               printf("nx=%d,ny=%d\n", p.x, p.y);
               int dx=p.x-x, dy=p.y-y;
//               printf("dx=%d,dy=%d\n", dx, dy);
               if(dx == 0 && dy == 0)
               {
                   return 0;
               }
               RECT r={};
               GetWindowRect(hwnd, &r);
               SetWindowPos(hwnd, nullptr, r.left+dx, r.top+dy, 0, 0, SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSIZE | SWP_NOSENDCHANGING);
               x=p.x;y=p.y;
               return 0;
           }
           else if(Mode == SC_SIZE)
           {
               POINT p = { GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
               ClientToScreen(hwnd, &p);
               ULONG pt = ULONG(p.y << 16) | (p.x & 0xFFFF);
               SizeRect(&Msd, pt);
               const RECT &r = Msd.rcDragCursor;
//               printf("r={%d,%d,%d,%d} [%dx%d]\n", r.left, r.top, r.right, r.bottom, r.right - r.left, r.bottom - r.top);
               SetWindowPos(hwnd, nullptr, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING);
               return 0;
           }
       }
   }

   // WM_NCLBUTTONDOWN -> WM_SYSCOMMAND(SC_MOVE) -|-> WM_CAPTURECHANGED -> WM_GETMINMAXINFO -> WM_ENTERSIZEMOVE
   // Ïðè ïîëó÷åíèè WM_SYSCOMMAND(SC_MOVE) ñòàíäàðòíàÿ ïðîöåäóðà îáðàáîòêè ñîîáùåíèÿ çàïóñêàåò ìîäàëüíûé öèêë
   if(uMessage == WM_SYSCOMMAND)
   {
//       printf("WM_SYSCOMMAND(0x%X), w=%X, l=%X\n", uMessage, wparam, lparam);
// Êîäèðîâàíèå ñòîðîí ðàìêè îêíà äëÿ SC_SIZE (êîä óãëà ïîëó÷àåòñÿ ñóììèðîâàíèåì êîäîâ ñìåæíûõ ñòîðîí)
//     4           5
//      \ ___3___ /
//       |       |
//       1       2
//       |_______|
//      /    6    \
//     7           8


       ULONG Cmd = (wparam & 0xFFF0);
       ULONG BrdSide = (wparam & 0xF);
       if(Cmd == SC_MOVE)
       {
           x = GET_X_LPARAM(lparam);
           y = GET_Y_LPARAM(lparam);
           SetCapture(hwnd);
           Captured = true;
           Mode = SC_MOVE;
           return 0;
       }
       else if(Cmd == SC_SIZE)
       {
           Mode = SC_SIZE;

           RECT rcWindow;
           GetWindowRect(hwnd, &rcWindow);
           RECT rcClient;
           GetClientRect(hwnd, &rcClient);
           RECT rcDesktop;
           GetWindowRect(GetDesktopWindow(), &rcDesktop);

           Msd.cmd = int(BrdSide);
           CopyRect(&Msd.rcDragCursor, &rcWindow);
           LONG cw = rcClient.right - rcClient.left, ch = rcClient.bottom - rcClient.top;
           LONG ww = rcWindow.right - rcWindow.left, wh = rcWindow.bottom - rcWindow.top;
           LONG dw = rcDesktop.right - rcDesktop.left, dh = rcDesktop.bottom - rcDesktop.top;
           Msd.ptMinTrack = { LONG(temp.ox) + (ww - cw), LONG(temp.oy) + (wh - ch) };
           Msd.ptMaxTrack = { dw, dh };

           SetCapture(hwnd);
           Captured = true;
           SizeRect(&Msd, ULONG(lparam));
           return 0;
       }
   }

/*
#define WM_NCUAHDRAWCAPTION 0x00AE
#define WM_NCUAHDRAWFRAME   0x00AF
   if(!(uMessage == WM_SIZING || uMessage == WM_SIZE || uMessage == WM_PAINT || uMessage == WM_NCPAINT
     || uMessage == WM_MOUSEMOVE || uMessage == WM_SETCURSOR || uMessage == WM_GETICON
     || uMessage == WM_IME_SETCONTEXT || uMessage == WM_IME_NOTIFY || uMessage == WM_ACTIVATE
     || uMessage == WM_NCUAHDRAWCAPTION || uMessage == WM_NCUAHDRAWFRAME))
   {
       printf("MSG(0x%X)\n", uMessage);
   }
*/


   if (conf.input.joymouse)
   {
      if (uMessage == WM_LBUTTONDOWN || uMessage == WM_LBUTTONUP)
      {
         input.mousejoy = (input.mousejoy & 0x0F) | (uMessage == WM_LBUTTONDOWN ? 0x10 : 0);
         input.kjoy = (input.kjoy & 0x0F) | (uMessage == WM_LBUTTONDOWN ? 0x10 : 0);
      }

      if (uMessage == WM_MOUSEMOVE)
      {
         RECT rc; GetClientRect(hwnd, &rc);
         unsigned xx = LOWORD(lparam)*3/unsigned(rc.right - rc.left);
         unsigned yy = HIWORD(lparam)*3/unsigned(rc.bottom - rc.top);
         unsigned nn = yy*3 + xx;
//         SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG)crs[nn]); //Alone Coder
         SetCursor(crs[nn]); //Alone Coder
         input.mousejoy = (input.mousejoy & 0x10) | mousedirs[nn];
         input.kjoy = (input.kjoy & 0x10) | mousedirs[nn];
         return 0;
      }
   }
   else if (uMessage == WM_LBUTTONDOWN && !conf.lockmouse)
   {
//       printf("%s\n", __FUNCTION__);
       input.nomouse = 20;
       main_mouse();
   }

   if(uMessage == WM_ENTERSIZEMOVE)
   {
//       printf("WM_ENTERSIZEMOVE(0x%X)\n", uMessage);
       sound_stop();
   }

   if(uMessage == WM_EXITSIZEMOVE)
   {
       sound_play();
   }

   if (uMessage == WM_SIZE || uMessage == WM_MOVE || uMessage == WM_USER)
   {
#if 0
      printf("%s(WM_SIZE || WM_MOVE || WM_USER)\n", __FUNCTION__);
      RECT rr = {};
      GetWindowRect(wnd, &rr);
      printf("r={%d,%d,%d,%d} [%dx%d]\n", rr.left, rr.top, rr.right, rr.bottom, rr.right - rr.left, rr.bottom - rr.top);
#endif

      GetClientRect(wnd, &temp.client);
      temp.gdx = unsigned(temp.client.right-temp.client.left);
      temp.gdy = unsigned(temp.client.bottom-temp.client.top);
      temp.gx = (temp.gdx > temp.ox) ? (temp.gdx-temp.ox)/2 : 0;
      temp.gy = (temp.gdy > temp.oy) ? (temp.gdy-temp.oy)/2 : 0;
      ClientToScreen(wnd, (POINT*)&temp.client.left);
      ClientToScreen(wnd, (POINT*)&temp.client.right);
      adjust_mouse_cursor();
      if (sprim)
          uMessage = WM_PAINT;
      needclr = 2;

      if((uMessage == WM_SIZE) && (wparam != SIZE_MINIMIZED) && temp.ox && temp.oy)
      {
          if(wnd && (temp.rflags & RF_D3D)) // skip call from CreateWindow
          {
//              printf("%s(WM_SIZE, temp.rflags & RF_D3D)\n", __FUNCTION__);
              SetVideoModeD3d(false);
          }
      }
   }

   if (uMessage == WM_PAINT)
   {
      if (sprim)
      { // background for overlay
         RECT rc;
         GetClientRect(hwnd, &rc);
         HBRUSH br = CreateSolidBrush(RGB(0xFF,0x00,0xFF));
         FillRect(temp.gdidc, &rc, br);
         DeleteObject(br);
         update_overlay();
      }
      else if (hbm && !active)
      {
//       printf("%s, WM_PAINT\n", __FUNCTION__);
         HDC hcom = CreateCompatibleDC(temp.gdidc);
         HGDIOBJ PrevObj = SelectObject(hcom, hbm);
         BitBlt(temp.gdidc, 0, 0, int(bm_dx), int(bm_dy), hcom, 0, 0, SRCCOPY);
         SelectObject(hcom, PrevObj);
         DeleteDC(hcom);
      }
   }

   if (uMessage == WM_SYSCOMMAND)
   {
//       printf("%s, WM_SYSCOMMAND 0x%04X\n", __FUNCTION__, (ULONG)wparam);

      switch(wparam & 0xFFF0)
      {
      case SCU_SCALE1: temp.scale = 1; wnd_resize(1);  return 0;
      case SCU_SCALE2: temp.scale = 2; wnd_resize(2);  return 0;
      case SCU_SCALE3: temp.scale = 3; wnd_resize(3);  return 0;
      case SCU_SCALE4: temp.scale = 4; wnd_resize(4);  return 0;
      case SCU_LOCK_MOUSE: main_mouse();  return 0;
      case SC_CLOSE:
          if(ConfirmExit())
              correct_exit();
      return 0;
      case SC_MINIMIZE: temp.Minimized = true; break;

      case SC_RESTORE: temp.Minimized = false; break;
      }
   }

   if (uMessage == WM_DROPFILES)
   {
      HDROP hDrop = (HDROP)wparam;
      DragQueryFile(hDrop, 0, droppedFile, sizeof(droppedFile));
      DragFinish(hDrop);
      return 0;
   }
#endif

   return DefWindowProc(hwnd, uMessage, wparam, lparam);
}

void readdevice(VOID *md, DWORD sz, LPDIRECTINPUTDEVICE dev)
{
   if (!active || !dev)
       return;
   HRESULT r = dev->GetDeviceState(sz, md);
   if(r == DIERR_INPUTLOST || r == DIERR_NOTACQUIRED)
   {
      r = dev->Acquire();
      while(r == DIERR_INPUTLOST)
          r = dev->Acquire();

      if(r == DIERR_OTHERAPPHASPRIO) // Ïðèëîæåíèå íàõîäèòñÿ â background
          return;

      if (r != DI_OK)
      {
          printrdi("IDirectInputDevice::Acquire()", r);
          exit();
      }
      r = dev->GetDeviceState(sz, md);
   }
   if(r != DI_OK)
   {
       printrdi("IDirectInputDevice::GetDeviceState()", r);
       exit();
   }
}

void readmouse(DIMOUSESTATE *md)
{
   memset(md, 0, sizeof *md);
   readdevice(md, sizeof *md, dimouse);
}

void ReadKeyboard(PVOID KbdData)
{
    readdevice(KbdData, 256, dikeyboard);
}

void setpal(char system)
{
   if (!active || !dd || !surf0 || !pal) return;
   HRESULT r;
   if (surf0->IsLost() == DDERR_SURFACELOST) surf0->Restore();
   if ((r = pal->SetEntries(0, 0, 0x100, system ? syspalette : pal0)) != DD_OK)
   { printrdd("IDirectDrawPalette::SetEntries()", r); exit(); }
}

static void trim_right(char *str)
{
   size_t i; //Alone Coder 0.36.7
   for (/*unsigned*/ i = strlen(str); i && str[i-1] == ' '; i--);
   str[i] = 0;
}

#define MAX_MODES 512
static struct MODEPARAM {
   unsigned x,y,b,f;
} modes[MAX_MODES];
static unsigned max_modes;

// Äëÿ èíèöèàëèçàöèè fullscreen ðåæèìà íåîáõîäèìî âûïîëíåíèå íåñêîëòêèõ óñëîâèé:
// 1. Ðàçìåð îêíà äîëæåí ñîâïàäàòü ñ ðàçðåøåíèåì ýêðàíà, êîîðäèíàòû ëåâîãî âåðõíåãî óãëà äîëæíû áûòü 0, 0
// 2. Îêíî äîëæíî èìåòü ðàñøèðåííûé ñòèëü WS_EX_TOPMOST
// Ïðè íå âûïîëíåíèè õîòÿ áû îäíîãî èç ïóíêòîâ ïðè âîçâðàòå èç fullscreen ê îêííîìó ðåæèìó íà vista è âûøå áóäåò
// îòêþ÷åí DWM è îêíî ïðèîáðåòåò "óñòàðåâøèé" âèä áîðäþðà

static void SetVideoModeD3d(bool Exclusive)
{
    HRESULT r;
//    printf("%s\n", __FUNCTION__);

    if(!wnd)
    {
        __debugbreak();
    }

    // release textures if exist
    if(SurfTexture)
    {
        ULONG RefCnt = SurfTexture->Release();
        (void)RefCnt;
/*
#ifdef _DEBUG
        if(RefCnt != 0)
        {
            __debugbreak();
        }
#endif
*/

        SurfTexture = nullptr;
    }

    if(Texture)
    {
        ULONG RefCnt = Texture->Release();
#ifdef _DEBUG
        if(RefCnt != 0)
        {
            __debugbreak();
        }
#endif
        Texture = nullptr;
    }

    bool CreateDevice = false;
    if(!D3dDev)
    {
        CreateDevice = true;
        if(!D3d9)
        {
            StartD3d(wnd);
        }
    }

    D3DDISPLAYMODE DispMode;
    r = D3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &DispMode);
    if(r != D3D_OK)
    {
        printrdd("IDirect3D::GetAdapterDisplayMode()", r); exit();
    }

    D3DPRESENT_PARAMETERS D3dPp = { };
    if(Exclusive) // exclusive full screen
    {
#if 0
        printf("exclusive full screen (%ux%u %uHz)\n", DispMode.Width, DispMode.Height, temp.ofq);
        RECT rr = { };
        GetWindowRect(wnd, &rr);
        printf("r1={%d,%d,%d,%d} [%dx%d]\n", rr.left, rr.top, rr.right, rr.bottom, rr.right - rr.left, rr.bottom - rr.top);
        printf("SetWindowPos(%p, HWND_TOPMOST, 0, 0, %u, %u)\n", wnd, DispMode.Width, DispMode.Height);
#endif
        if(!SetWindowPos(wnd, HWND_TOPMOST, 0, 0, int(DispMode.Width), int(DispMode.Height), 0)) // Óñòàíîâêà WS_EX_TOPMOST
        {
            __debugbreak();
        }
#if 0
        rr = { };
        GetWindowRect(wnd, &rr);
        printf("r2={%d,%d,%d,%d} [%dx%d]\n", rr.left, rr.top, rr.right, rr.bottom, rr.right - rr.left, rr.bottom - rr.top);
#endif
        D3dPp.Windowed = FALSE;
        D3dPp.BackBufferWidth = DispMode.Width;
        D3dPp.BackBufferHeight = DispMode.Height;
        D3dPp.BackBufferFormat = DispMode.Format;
        D3dPp.FullScreen_RefreshRateInHz = temp.ofq;
        D3dPp.PresentationInterval = conf.flip ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE;
    }
    else // windowed mode
    {
#if 0
        printf("windowed mode\n");
        RECT rr = { };
        GetWindowRect(wnd, &rr);
        printf("w=%p, r={%d,%d,%d,%d} [%dx%d]\n", wnd, rr.left, rr.top, rr.right, rr.bottom, rr.right - rr.left, rr.bottom - rr.top);
#endif
        D3dPp.Windowed = TRUE;
    }
    D3dPp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    D3dPp.BackBufferCount = 1;
    D3dPp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    D3dPp.hDeviceWindow = wnd;

    if(CreateDevice)
    {
        r = D3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, wnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &D3dPp, &D3dDev);
        if(r != D3D_OK)
        {
            printrdd("IDirect3D::CreateDevice()", r);
            exit();
        }

        if(!SUCCEEDED(r = D3dDev->TestCooperativeLevel()))
        {
            printrdd("IDirect3DDevice::TestCooperativeLevel()", r);
            exit();
        }
    }
    else // reset existing device
    {
        r = D3D_OK;
        do
        {
            if(r != D3D_OK)
            {
                Sleep(10);
            }
            r = D3dDev->Reset(&D3dPp);
        } while(r == D3DERR_DEVICELOST);

        if (r != DD_OK)
        {
            printrdd("IDirect3DDevice9::Reset()", r);
//            __debugbreak();
            exit();
        }
//        printf("D3dDev->Reset(%d, %d)\n", D3dPp.BackBufferWidth, D3dPp.BackBufferHeight);
    }

    // recreate textures
//    printf("IDirect3DDevice9::CreateTexture(%d,%d)\n", temp.ox, temp.oy);
    r = D3dDev->CreateTexture(temp.ox, temp.oy, 1, D3DUSAGE_DYNAMIC, DispMode.Format, D3DPOOL_DEFAULT, &Texture, nullptr);
    if (r != DD_OK)
    { printrdd("IDirect3DDevice9::CreateTexture()", r); exit(); }
    r = Texture->GetSurfaceLevel(0, &SurfTexture);
    if (r != DD_OK)
    { printrdd("IDirect3DTexture::GetSurfaceLevel()", r); exit(); }
    if(!SurfTexture)
        __debugbreak();
}

static bool NeedRestoreDisplayMode = false;

void set_vidmode()
{
//   printf("%s\n", __FUNCTION__);
   if (pal)
   {
       pal->Release();
       pal = nullptr;
   }

   if (surf2)
   {
       surf2->Release();
       surf2 = nullptr;
   }

   if (surf1)
   {
       surf1->Release();
       surf1 = nullptr;
   }

   if (surf0)
   {
       ULONG RefCnt = surf0->Release();
       if (RefCnt != 0)
       { printf("surf0->Release(), RefCnt=%lu\n", RefCnt); exit(); }
       surf0 = nullptr;
   }

   if (sprim)
   {
       sprim->Release();
       sprim = nullptr;
   }

   if (clip)
   {
       clip->Release();
       clip = nullptr;
   }

   if(SurfTexture)
   {
       SurfTexture->Release();
       SurfTexture = nullptr;
   }

   if(Texture)
   {
       ULONG RefCnt = Texture->Release();
       (void)RefCnt;
#ifdef _DEBUG
       if(RefCnt != 0)
       {
           __debugbreak();
       }
#endif
       Texture = nullptr;
   }

   HRESULT r;

   DDSURFACEDESC desc;
   desc.dwSize = sizeof desc;
   r = dd->GetDisplayMode(&desc);
   if (r != DD_OK) { printrdd("IDirectDraw2::GetDisplayMode()", r); exit(); }
   temp.ofq = desc.dwRefreshRate; // nt only?
   if (!temp.ofq)
       temp.ofq = conf.refresh;

   // Ïðîâåðêà íàëè÷èÿ hw overlay
   if(drivers[conf.driver].flags & RF_OVR)
   {
       DDCAPS Caps;
       Caps.dwSize = sizeof(Caps);

       if((r = dd->GetCaps(&Caps, nullptr)) == DD_OK)
       {
           if(Caps.dwMaxVisibleOverlays == 0)
           {
               errexit("HW Overlay not supported");
           }
       }
   }

   // select fullscreen, set window style
   if (temp.rflags & RF_DRIVER)
       temp.rflags |= drivers[conf.driver].flags;
   if (!(temp.rflags & (RF_GDI | RF_OVR | RF_CLIP | RF_D3D)))
       conf.fullscr = 1;
   if ((temp.rflags & RF_32) && desc.ddpfPixelFormat.dwRGBBitCount != 32)
       conf.fullscr = 1; // for chunks via blitter

   static RECT rc;
   LONG oldstyle = GetWindowLong(wnd, GWL_STYLE);
   if (oldstyle & WS_CAPTION)
       GetWindowRect(wnd, &rc);

   LONG style = LONG(conf.fullscr ? (WS_VISIBLE | WS_POPUP) : (WS_VISIBLE | WS_OVERLAPPEDWINDOW));
   if ((oldstyle ^ style) & WS_CAPTION)
   {
//       printf("set style=%X, fullscr=%d\n", style, conf.fullscr);
       SetWindowLong(wnd, GWL_STYLE, style);
   }

   // select exclusive
   u8 excl = conf.fullscr;
   if ((temp.rflags & RF_CLIP) && (desc.ddpfPixelFormat.dwRGBBitCount == 8))
       excl = 1;

   if (!(temp.rflags & (RF_MON | RF_D3D | RF_D3DE)))
   {
      r = dd->SetCooperativeLevel(wnd, excl ? DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN : DDSCL_ALLOWREBOOT | DDSCL_NORMAL);
      if (r != DD_OK) { printrdd("IDirectDraw2::SetCooperativeLevel()", r); exit(); }
   }

   // select resolution
   const unsigned size_x[3] = { 256U, conf.mcx_small, conf.mcx_full };
   const unsigned size_y[3] = { 192U, conf.mcy_small, conf.mcy_full };
   temp.ox = temp.scx = size_x[conf.bordersize];
   temp.oy = temp.scy = size_y[conf.bordersize];

   if (temp.rflags & RF_2X)
   {
      temp.ox *=2; temp.oy *= 2;
      if (conf.fast_sl && (temp.rflags & RF_DRIVER) && (temp.rflags & (RF_CLIP | RF_OVR)))
          temp.oy /= 2;
   }

   if(temp.rflags & RF_3X) { temp.ox *= 3; temp.oy *= 3; }
   if(temp.rflags & RF_4X) { temp.ox *= 4; temp.oy *= 4; }
   if(temp.rflags & RF_64x48) { temp.ox = 64; temp.oy = 48; }
   if(temp.rflags & RF_128x96) { temp.ox = 128; temp.oy = 96; }
   if(temp.rflags & RF_MON) { temp.ox = 640; temp.oy = 480; }

//   printf("temp.ox=%d, temp.oy=%d\n", temp.ox, temp.oy);

   // select color depth
   temp.obpp = 8;
   if (temp.rflags & (RF_CLIP | RF_D3D | RF_D3DE))
       temp.obpp = desc.ddpfPixelFormat.dwRGBBitCount;
   if (temp.rflags & (RF_16 | RF_OVR))
       temp.obpp = 16;
   if (temp.rflags & RF_32)
       temp.obpp = 32;
   if ((temp.rflags & (RF_GDI|RF_8BPCH)) == (RF_GDI|RF_8BPCH))
       temp.obpp = 32;

   if (conf.fullscr || ((temp.rflags & RF_MON) && desc.dwHeight < 480))
   {
      // select minimal screen mode
      unsigned newx = 100000, newy = 100000, newfq = conf.refresh ? conf.refresh : temp.ofq, newb = temp.obpp;
      unsigned minx = temp.ox, miny = temp.oy, needb = temp.obpp;

      if (temp.rflags & (RF_64x48 | RF_128x96))
      {
         needb = (temp.rflags & RF_16)? 16:32;
         minx = desc.dwWidth; if (minx < 640) minx = 640;
         miny = desc.dwHeight; if (miny < 480) miny = 480;
      }
      // if (temp.rflags & RF_MON) // - ox=640, oy=480 - set above

      for (unsigned i = 0; i < max_modes; i++)
      {
         if (modes[i].y < miny || modes[i].x < minx)
             continue;
         if (!(temp.rflags & RF_MON) && modes[i].b != temp.obpp)
             continue;
         if (modes[i].y < conf.minres)
             continue;

         if ((modes[i].x < newx || modes[i].y < newy) && (!conf.refresh || (modes[i].f == newfq)))
         {
             newx = modes[i].x;
             newy = modes[i].y;
             if(!conf.refresh && modes[i].f > newfq)
                 newfq = modes[i].f;
         }
      }

      if (newx==100000)
      {
          color(CONSCLR_ERROR);
          printf("can't find situable mode for %u x %u * %u bits\n", temp.ox, temp.oy, temp.obpp);
          exit();
      }

      // use minimal or current mode
      if (temp.rflags & (RF_OVR | RF_GDI | RF_CLIP | RF_D3D | RF_D3DE))
      {
          // leave screen size, if enough width/height
          newx = desc.dwWidth;
          newy = desc.dwHeight;
          if(newx < minx || newy < miny)
          {
              newx = minx;
              newy = miny;
          }
          // leave color depth, until specified directly
          if(!(temp.rflags & (RF_16 | RF_32)))
              newb = desc.ddpfPixelFormat.dwRGBBitCount;
      }

      if (desc.dwWidth != newx || desc.dwHeight != newy || temp.ofq != newfq || desc.ddpfPixelFormat.dwRGBBitCount != newb)
      {
//         printf("SetDisplayMode:%ux%u %uHz\n", newx, newy, newfq);
         if ((r = dd->SetDisplayMode(newx, newy, newb, newfq, 0)) != DD_OK)
         { printrdd("IDirectDraw2::SetDisplayMode()", r); exit(); }
         GetSystemPaletteEntries(temp.gdidc, 0, 0x100, syspalette);
         if (newfq)
             temp.ofq = newfq;

         NeedRestoreDisplayMode = true;
      }
      temp.odx = temp.obpp*(newx - temp.ox) / 16;
      temp.ody = (newy - temp.oy) / 2;
      temp.rsx = newx;
      temp.rsy = newy;
//      printf("vmode=%ux%u %uHz\n", newx, newy, newfq);
//      ShowWindow(wnd, SW_SHOWMAXIMIZED);
//      printf("SetWindowPos(%p, HWND_TOPMOST, 0, 0, %u, %u)\n", wnd, newx, newy);
      if(!SetWindowPos(wnd, HWND_TOPMOST, 0, 0, int(newx), int(newy), 0)) // Óñòàíîâêà WS_EX_TOPMOST
      {
          __debugbreak();
      }
   }
   else
   {
      // Âîññòàíîâëåíèå ïðåäûäóùåãî âèäåîðåæèìà ïðè âîçâðàòå èç fullscreen (åñëè áûëà ïåðåóñòàíîâêà âèäåîðåæèìà)
      if(NeedRestoreDisplayMode)
      {
        if ((r = dd->RestoreDisplayMode()) != DD_OK)
        { printrdd("IDirectDraw2::SetDisplayMode()", r); exit(); }
        NeedRestoreDisplayMode = false;
      }
      // restore window position to last saved position in non-fullscreen mode
      ShowWindow(wnd, SW_SHOWNORMAL);
      if (temp.rflags & RF_GDI)
      {
         RECT client = { 0,0, LONG(temp.ox), LONG(temp.oy) };
         AdjustWindowRect(&client, WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0);
         rc.right = rc.left + (client.right - client.left);
         rc.bottom = rc.top + (client.bottom - client.top);
      }
      MoveWindow(wnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, 1);
   }

   if (!(temp.rflags & (RF_D3D | RF_D3DE)))
   {
       dd->FlipToGDISurface(); // don't check result
   }

   desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
   desc.dwFlags = DDSD_CAPS;

   DWORD pl[0x101]; pl[0] = 0x01000300; memcpy(pl+1, pal0, 0x400);
   HPALETTE hpal = CreatePalette((LOGPALETTE*)&pl);
   DeleteObject(SelectPalette(temp.gdidc, hpal, 0));
   RealizePalette(temp.gdidc); // for RF_GDI and for bitmap, used in WM_PAINT

   if (temp.rflags & RF_GDI)
   {

      gdibmp.header.bmiHeader.biWidth = LONG(temp.ox);
      gdibmp.header.bmiHeader.biHeight = -LONG(temp.oy);
      gdibmp.header.bmiHeader.biBitCount = WORD(temp.obpp);

   }
   else if (temp.rflags & RF_OVR)
   {

      temp.odx = temp.ody = 0;
      if ((r = dd->CreateSurface(&desc, &sprim, nullptr)) != DD_OK)
      { printrdd("IDirectDraw2::CreateSurface() [primary,test]", r); exit(); }

      desc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
      desc.ddsCaps.dwCaps = DDSCAPS_OVERLAY | DDSCAPS_VIDEOMEMORY;
      desc.dwWidth = temp.ox;
      desc.dwHeight = temp.oy;

      // conf.flip = 0; // overlay always synchronized without Flip()! on radeon videocards
                        // double flip causes fps drop

      if (conf.flip)
      {
         desc.dwBackBufferCount = 1;
         desc.dwFlags |= DDSD_BACKBUFFERCOUNT;
         desc.ddsCaps.dwCaps |= DDSCAPS_FLIP | DDSCAPS_COMPLEX;
      }

      static DDPIXELFORMAT ddpfOverlayFormat16 = { sizeof(DDPIXELFORMAT), DDPF_RGB, 0, {16}, {0xF800}, {0x07E0}, {0x001F}, {0} };
      static DDPIXELFORMAT ddpfOverlayFormat15 = { sizeof(DDPIXELFORMAT), DDPF_RGB, 0, {16}, {0x7C00}, {0x03E0}, {0x001F}, {0} };
      static DDPIXELFORMAT ddpfOverlayFormatYUY2 = { sizeof(DDPIXELFORMAT), DDPF_FOURCC, MAKEFOURCC('Y','U','Y','2'), {0},{0},{0},{0},{0} };

      if (temp.rflags & RF_YUY2)
          goto YUY2;

      temp.hi15 = 0;
      desc.ddpfPixelFormat = ddpfOverlayFormat16;
      r = dd->CreateSurface(&desc, &surf0, nullptr);

      if (r == DDERR_INVALIDPIXELFORMAT)
      {
         temp.hi15 = 1;
         desc.ddpfPixelFormat = ddpfOverlayFormat15;
         r = dd->CreateSurface(&desc, &surf0, nullptr);
      }

      if (r == DDERR_INVALIDPIXELFORMAT /*&& !(temp.rflags & RF_RGB)*/)
      {
       YUY2:
         temp.hi15 = 2;
         desc.ddpfPixelFormat = ddpfOverlayFormatYUY2;
         r = dd->CreateSurface(&desc, &surf0, nullptr);
      }

      if (r != DD_OK)
      { printrdd("IDirectDraw2::CreateSurface() [overlay]", r); exit(); }

   }
   else if(temp.rflags & (RF_D3D | RF_D3DE)) // d3d windowed, d3d full screen exclusive
   {
//      printf("%s(RF_D3D)\n", __FUNCTION__);
      // Ñíà÷àëà íóæíî îòìàñøòàáèðîâàòü îêíî äî íóæíîãî ðàçìåðà, à òîëüêî ïîòîì óñòàíàâëèâàòü âèäåîðåæèì
      // ò.ê. âèäåîðåæèì îïðåäåëÿåò ðàçìåðû back buffer'à èç ðàçìåðîâ îêíà.
   }
   else  // blt, direct video mem
   {
//      desc.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;
      if (conf.flip && !(temp.rflags & RF_CLIP))
      {
         desc.dwBackBufferCount = 1;
         desc.dwFlags |= DDSD_BACKBUFFERCOUNT;
         desc.ddsCaps.dwCaps |= DDSCAPS_FLIP | DDSCAPS_COMPLEX;
      }

      if ((r = dd->CreateSurface(&desc, &surf0, nullptr)) != DD_OK)
      { printrdd("IDirectDraw2::CreateSurface() [primary1]", r); exit(); }

      if (temp.rflags & RF_CLIP)
      {
         DDSURFACEDESC SurfDesc;
         SurfDesc.dwSize = sizeof(SurfDesc);
         if((r = surf0->GetSurfaceDesc(&SurfDesc)) != DD_OK)
         { printrdd("IDirectDrawSurface::GetSurfaceDesc()", r); exit(); }

         desc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
         desc.dwWidth = SurfDesc.dwWidth; desc.dwHeight = SurfDesc.dwHeight;
         desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;

         if ((r = dd->CreateSurface(&desc, &surf2, nullptr)) != DD_OK)
         { printrdd("IDirectDraw2::CreateSurface() [2]", r); exit(); }

         r = dd->CreateClipper(0, &clip, nullptr);
         if (r != DD_OK) { printrdd("IDirectDraw2::CreateClipper()", r); exit(); }

         r = clip->SetHWnd(0, wnd);
         if (r != DD_OK) { printrdd("IDirectDraw2::SetHWnd()", r); exit(); }

         r = surf0->SetClipper(clip);
         if (r != DD_OK) { printrdd("surf0, IDirectDrawSurface2::SetClipper()", r); exit(); }

         r = surf2->SetClipper(clip);
         if (r != DD_OK) { printrdd("surf2, IDirectDrawSurface2::SetClipper()", r); exit(); }

         r = dd->GetDisplayMode(&desc);
         if (r != DD_OK) { printrdd("IDirectDraw2::GetDisplayMode()", r); exit(); }
         if ((temp.rflags & RF_32) && desc.ddpfPixelFormat.dwRGBBitCount != 32)
             errexit("video driver requires 32bit color depth on desktop for this mode");

         desc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
         desc.dwWidth = temp.ox; desc.dwHeight = temp.oy;

         // Âèäåîêàðòû AMD Radeon HD íå ïîääåðæèâàþò surface â ñèñòåìíîé ïàìÿòè
         // èç çà ýòîãî ïðèõîäèòñÿ îòäåëüíûé áóôåð â ñèñòåìíî ïàìÿòè è äåëàòü ïðîãðàììíîå
         // êîïèðîâàíèå â surface âûäåëåííûé â âèäåîïàìÿòè èíà÷å íèêàê íå çàäàòü âûðàâíèâàíèå íà 16 áàéò
         desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;

#ifdef MOD_SSE2
         if(!(renders[conf.render].flags & RF_1X))
         {
             SurfPitch1 = (temp.ox * temp.obpp) >> 3;
             SurfPitch1 = (SurfPitch1 + 15) & ~15U; // Âûðàâíèâàíèå íà 16

             if(SurfMem1)
                 _aligned_free(SurfMem1);
             SurfMem1 = _aligned_malloc(SurfPitch1 * temp.oy, 16);
             FlipBltMethod = FlipBltAlign16;
         }
         else
#endif
         {
             FlipBltMethod = FlipBltAlign4;
         }

         r = dd->CreateSurface(&desc, &surf1, nullptr);
         if (r != DD_OK) { printrdd("IDirectDraw2::CreateSurface()", r); exit(); }
      }

      if (temp.obpp == 16)
      {
         DDPIXELFORMAT fm; fm.dwSize = sizeof fm;
         if ((r = surf0->GetPixelFormat(&fm)) != DD_OK)
         { printrdd("IDirectDrawSurface2::GetPixelFormat()", r); exit(); }

         if (fm.dwRBitMask == 0xF800 && fm.dwGBitMask == 0x07E0 && fm.dwBBitMask == 0x001F)
            temp.hi15 = 0;
         else if (fm.dwRBitMask == 0x7C00 && fm.dwGBitMask == 0x03E0 && fm.dwBBitMask == 0x001F)
            temp.hi15 = 1;
         else
            errexit("invalid pixel format (need RGB:5-6-5 or URGB:1-5-5-5)");

      }
      else if (temp.obpp == 8)
      {

         if ((r = dd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, syspalette, &pal, nullptr)) != DD_OK)
         { printrdd("IDirectDraw2::CreatePalette()", r); exit(); }
         if ((r = surf0->SetPalette(pal)) != DD_OK)
         { printrdd("IDirectDrawSurface2::SetPalette()", r); exit(); }
      }
   }

   if (conf.flip && !(temp.rflags & (RF_GDI|RF_CLIP|RF_D3D|RF_D3DE)))
   {
      DDSCAPS caps = { DDSCAPS_BACKBUFFER };
      if ((r = surf0->GetAttachedSurface(&caps, &surf1)) != DD_OK)
      { printrdd("IDirectDraw2::GetAttachedSurface()", r); exit(); }
   }

   // Íàñòðàèâàåì ôóíêöèþ êîíâåðòèðîâàíèÿ èç òåêóùåãî ôîðìàòà â BGR24
   switch(temp.obpp)
   {
   case 8: ConvBgr24 = ConvPal8ToBgr24; break;
   case 16:
       switch(temp.hi15)
       {
       case 0: ConvBgr24 = ConvRgb16ToBgr24; break; // RGB16
       case 1: ConvBgr24 = ConvRgb15ToBgr24; break; // RGB15
       case 2: ConvBgr24 = ConvYuy2ToBgr24; break; // YUY2
       }
       break;
   case 32: ConvBgr24 = ConvBgr32ToBgr24; break;
   }

   SendMessage(wnd, WM_USER, 0, 0); // setup rectangle for RF_GDI,OVR,CLIP, adjust cursor
   if(!conf.fullscr)
       scale_normal();

   if(temp.rflags & (RF_D3D | RF_D3DE)) // d3d windowed, d3d full screen exclusive
   {
       // Ñíà÷àëà íóæíî îòìàñøòàáèðîâàòü îêíî äî íóæíîãî ðàçìåðà, à òîëüêî ïîòîì óñòàíàâëèâàòü âèäåîðåæèì
       // ò.ê. âèäåîðåæèì îïðåäåëÿåò ðàçìåðû back buffer'à èç ðàçìåðîâ îêíà.
       SetVideoModeD3d((temp.rflags & RF_D3DE) != 0);
   }
}

static HRESULT SetDIDwordProperty(LPDIRECTINPUTDEVICE pdev, REFGUID guidProperty,
                   DWORD dwObject, DWORD dwHow, DWORD dwValue)
{
   DIPROPDWORD dipdw;
   dipdw.diph.dwSize       = sizeof(dipdw);
   dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
   dipdw.diph.dwObj        = dwObject;
   dipdw.diph.dwHow        = dwHow;
   dipdw.dwData            = dwValue;
   return pdev->SetProperty(guidProperty, &dipdw.diph);
}

static BOOL CALLBACK InitJoystickInput(LPCDIDEVICEINSTANCE pdinst, LPVOID pvRef)
{
   HRESULT r;
   LPDIRECTINPUT pdi = (LPDIRECTINPUT)pvRef;
   LPDIRECTINPUTDEVICE dijoyst1;
   LPDIRECTINPUTDEVICE2 dijoyst;
   if ((r = pdi->CreateDevice(pdinst->guidInstance, &dijoyst1, nullptr)) != DI_OK)
   {
       printrdi("IDirectInput::CreateDevice() (joystick)", r);
       return DIENUM_CONTINUE;
   }

   r = dijoyst1->QueryInterface(IID_IDirectInputDevice2, (void**)&dijoyst);
   if (r != S_OK)
   {
      printrdi("IDirectInputDevice::QueryInterface(IID_IDirectInputDevice2) [dx5 not found]", r);
      dijoyst1->Release();
      dijoyst1=nullptr;
      return DIENUM_CONTINUE;
   }
   dijoyst1->Release();

   DIDEVICEINSTANCE dide = { sizeof dide };
   if ((r = dijoyst->GetDeviceInfo(&dide)) != DI_OK)
   {
       printrdi("IDirectInputDevice::GetDeviceInfo()", r);
       return DIENUM_STOP;
   }

   DIDEVCAPS dc = { sizeof dc };
   if ((r = dijoyst->GetCapabilities(&dc)) != DI_OK)
   {
       printrdi("IDirectInputDevice::GetCapabilities()", r);
       return DIENUM_STOP;
   }

   DIPROPDWORD JoyId;
   JoyId.diph.dwSize       = sizeof(JoyId);
   JoyId.diph.dwHeaderSize = sizeof(JoyId.diph);
   JoyId.diph.dwObj        = 0;
   JoyId.diph.dwHow        = DIPH_DEVICE;
   if ((r = dijoyst->GetProperty(DIPROP_JOYSTICKID, &JoyId.diph)) != DI_OK)
   { printrdi("IDirectInputDevice::GetProperty(DIPROP_JOYSTICKID)", r); exit(); }

   trim_right(dide.tszInstanceName);
   trim_right(dide.tszProductName);

   CharToOem(dide.tszInstanceName, dide.tszInstanceName);
   CharToOem(dide.tszProductName, dide.tszProductName);
   if (strcmp(dide.tszProductName, dide.tszInstanceName))
       strcat(dide.tszInstanceName, ", ");
   else
       dide.tszInstanceName[0] = 0;

   bool UseJoy = (JoyId.dwData == conf.input.JoyId);
   color(CONSCLR_HARDINFO);
   printf("%cjoy(%lu): %s%s (%lu axes, %lu buttons, %lu POVs)\n", UseJoy ? '*' : ' ', JoyId.dwData,
      dide.tszInstanceName, dide.tszProductName, dc.dwAxes, dc.dwButtons, dc.dwPOVs);

   if(UseJoy)
   {
       if ((r = dijoyst->SetDataFormat(&c_dfDIJoystick)) != DI_OK)
       {
           printrdi("IDirectInputDevice::SetDataFormat() (joystick)", r);
           exit();
       }

       if ((r = dijoyst->SetCooperativeLevel(wnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND)) != DI_OK)
       {
           printrdi("IDirectInputDevice::SetCooperativeLevel() (joystick)", r);
           exit();
       }

       DIPROPRANGE diprg;
       diprg.diph.dwSize       = sizeof(diprg);
       diprg.diph.dwHeaderSize = sizeof(diprg.diph);
       diprg.diph.dwObj        = DIJOFS_X;
       diprg.diph.dwHow        = DIPH_BYOFFSET;
       diprg.lMin              = -1000;
       diprg.lMax              = +1000;

       if ((r = dijoyst->SetProperty(DIPROP_RANGE, &diprg.diph)) != DI_OK)
       { printrdi("IDirectInputDevice::SetProperty(DIPH_RANGE)", r); exit(); }

       diprg.diph.dwObj        = DIJOFS_Y;

       if ((r = dijoyst->SetProperty(DIPROP_RANGE, &diprg.diph)) != DI_OK)
       { printrdi("IDirectInputDevice::SetProperty(DIPH_RANGE) (y)", r); exit(); }

       if ((r = SetDIDwordProperty(dijoyst, DIPROP_DEADZONE, DIJOFS_X, DIPH_BYOFFSET, 2000)) != DI_OK)
       { printrdi("IDirectInputDevice::SetProperty(DIPH_DEADZONE)", r); exit(); }

       if ((r = SetDIDwordProperty(dijoyst, DIPROP_DEADZONE, DIJOFS_Y, DIPH_BYOFFSET, 2000)) != DI_OK)
       { printrdi("IDirectInputDevice::SetProperty(DIPH_DEADZONE) (y)", r); exit(); }
       ::dijoyst = dijoyst;
   }
   else
   {
      dijoyst->Release();
   }
   return DIENUM_CONTINUE;
}

static HRESULT WINAPI callb(LPDDSURFACEDESC surf, void *lpContext)
{
    (void)lpContext;

   if (max_modes >= MAX_MODES)
       return DDENUMRET_CANCEL;
   modes[max_modes].x = surf->dwWidth;
   modes[max_modes].y = surf->dwHeight;
   modes[max_modes].b = surf->ddpfPixelFormat.dwRGBBitCount;
   modes[max_modes].f = surf->dwRefreshRate;
   max_modes++;
   return DDENUMRET_OK;
}

void scale_normal()
{
    ULONG cmd;
    switch(temp.scale)
    {
    default:
    case 1: cmd = SCU_SCALE1; break;
    case 2: cmd = SCU_SCALE2; break;
    case 3: cmd = SCU_SCALE3; break;
    case 4: cmd = SCU_SCALE4; break;
    }
    SendMessage(wnd, WM_SYSCOMMAND, cmd, 0); // set window size
}

#ifdef _DEBUG
#define D3D_DLL_NAME "d3d9d.dll"
#else
#endif
#define D3D_DLL_NAME "d3d9.dll"

static void DbgPrint(const char *s)
{
    OutputDebugStringA(s);
}

static void StartD3d(HWND Wnd)
{
    (void)Wnd;

#if 0
    OutputDebugString(__FUNCTION__"\n");
    printf("%s\n", __FUNCTION__);
#endif
    if(!D3d9Dll)
    {
        D3d9Dll = LoadLibrary(D3D_DLL_NAME);

        if(!D3d9Dll)
        {
            errexit("unable load d3d9.dll");
        }
    }

    if(!D3d9)
    {
        typedef IDirect3D9 * (WINAPI *TDirect3DCreate9)(UINT SDKVersion);
        TDirect3DCreate9 Direct3DCreate9 = (TDirect3DCreate9)GetProcAddress(D3d9Dll, "Direct3DCreate9");
        D3d9 = Direct3DCreate9(D3D_SDK_VERSION);
    }
}

static void CalcWindowSize()
{
    temp.rflags = renders[conf.render].flags;

    if (renders[conf.render].func == render_advmame)
    {
        if (conf.videoscale == 2)
            temp.rflags |= RF_2X;
        if (conf.videoscale == 3)
            temp.rflags |= RF_3X;
        if (conf.videoscale == 4)
            temp.rflags |= RF_4X;
    }
    if (temp.rflags & RF_DRIVER)
        temp.rflags |= drivers[conf.driver].flags;

    // select resolution
    const unsigned size_x[3] = { 256U, conf.mcx_small, conf.mcx_full };
    const unsigned size_y[3] = { 192U, conf.mcy_small, conf.mcy_full };
    temp.ox = temp.scx = size_x[conf.bordersize];
    temp.oy = temp.scy = size_y[conf.bordersize];

    if (temp.rflags & RF_2X)
    {
        temp.ox *=2; temp.oy *= 2;
        if (conf.fast_sl && (temp.rflags & RF_DRIVER) && (temp.rflags & (RF_CLIP | RF_OVR)))
            temp.oy /= 2;
    }

    if(temp.rflags & RF_3X) { temp.ox *= 3; temp.oy *= 3; }
    if(temp.rflags & RF_4X) { temp.ox *= 4; temp.oy *= 4; }
    if(temp.rflags & RF_64x48) { temp.ox = 64; temp.oy = 48; }
    if(temp.rflags & RF_128x96) { temp.ox = 128; temp.oy = 96; }
    if(temp.rflags & RF_MON) { temp.ox = 640; temp.oy = 480; }
}

static BOOL WINAPI DdEnumDevs(GUID *DevGuid, PSTR DrvDesc, PSTR DrvName, PVOID Ctx, HMONITOR Hm)
{
    (void)DrvDesc;
    (void)DrvName;
    (void)Hm;

    if(DevGuid)
    {
        memcpy(Ctx, DevGuid, sizeof(GUID));
        return FALSE;
    }
    return TRUE;
}

void start_dx()
{
//   printf("%s\n", __FUNCTION__);
   dsbuffer_sz = DSBUFFER_SZ;

   WNDCLASSEX  wc = { };

   wc.cbSize = sizeof(WNDCLASSEX);

   wc.lpfnWndProc = WndProc;
   hIn = wc.hInstance = GetModuleHandle(nullptr);
   wc.lpszClassName = "EMUL_WND";
   wc.hIcon = LoadIcon(hIn, MAKEINTRESOURCE(IDI_ICON2));
   wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
   wc.style = CS_HREDRAW | CS_VREDRAW;
   RegisterClassEx(&wc);

   for (int i = 0; i < 9; i++)
      crs[i] = LoadCursor(hIn, MAKEINTRESOURCE(IDC_C0+i));
//Alone Coder 0.36.6
   RECT rect1;
   SystemParametersInfo(SPI_GETWORKAREA, 0, &rect1, 0);
//~
   CalcWindowSize();

   int cx = int(temp.ox*temp.scale), cy = int(temp.oy*temp.scale);

   RECT Client = { 0, 0, cx, cy };
   AdjustWindowRect(&Client, WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0);
   cx = Client.right - Client.left;
   cy = Client.bottom - Client.top;
   int winx = rect1.left + (rect1.right - rect1.left - cx) / 2;
   int winy = rect1.top + (rect1.bottom - rect1.top - cy) / 2;

   wnd = CreateWindowEx(0, "EMUL_WND", "UnrealSpeccy", WS_VISIBLE|WS_OVERLAPPEDWINDOW,
                    winx, winy, cx, cy, nullptr, nullptr, hIn, nullptr);

   if(!wnd)
   {
       __debugbreak();
   }

   DragAcceptFiles(wnd, 1);

   temp.gdidc = GetDC(wnd);
   GetSystemPaletteEntries(temp.gdidc, 0, 0x100, syspalette);

   HMENU sys = GetSystemMenu(wnd, 0);
   if(sys)
   {
       AppendMenu(sys, MF_SEPARATOR, 0, nullptr);
       AppendMenu(sys, MF_STRING, SCU_SCALE1, "x1");
       AppendMenu(sys, MF_STRING, SCU_SCALE2, "x2");
       AppendMenu(sys, MF_STRING, SCU_SCALE3, "x3");
       AppendMenu(sys, MF_STRING, SCU_SCALE4, "x4");
       AppendMenu(sys, MF_STRING, SCU_LOCK_MOUSE, "&Lock mouse");
   }

   InitCommonControls();

   HRESULT r;
   GUID DdDevGuid;
   if((r = DirectDrawEnumerateEx(DdEnumDevs, &DdDevGuid, DDENUM_ATTACHEDSECONDARYDEVICES)) != DD_OK)
   { printrdd("DirectDrawEnumerate()", r); exit(); }

   LPDIRECTDRAW dd0;
   if ((r = DirectDrawCreate(nullptr /*&DdDevGuid*/, &dd0, nullptr)) != DD_OK)
   { printrdd("DirectDrawCreate()", r); exit(); }

   if ((r = dd0->QueryInterface(IID_IDirectDraw2, (void**)&dd)) != DD_OK)
   { printrdd("IDirectDraw::QueryInterface(IID_IDirectDraw2)", r); exit(); }

   dd0->Release();

   color(CONSCLR_HARDITEM); printf("gfx: ");

   char vmodel[MAX_DDDEVICEID_STRING + 32]; *vmodel = 0;
   if (conf.detect_video)
   {
      LPDIRECTDRAW4 dd4;
      if ((r = dd->QueryInterface(IID_IDirectDraw4, (void**)&dd4)) == DD_OK)
      {
         DDDEVICEIDENTIFIER di;
         if (dd4->GetDeviceIdentifier(&di, 0) == DD_OK)
         {
            trim_right(di.szDescription);
            CharToOem(di.szDescription, di.szDescription);
            sprintf(vmodel, "%04lX-%04lX (%s)", di.dwVendorId, di.dwDeviceId, di.szDescription);
         }
         else
             sprintf(vmodel, "unknown device");
         dd4->Release();
      }
      if (*vmodel)
          strcat(vmodel, ", ");
   }
   DDCAPS caps;
   caps.dwSize = sizeof caps;
   dd->GetCaps(&caps, nullptr);

   color(CONSCLR_HARDINFO);

   const u32 Vmem = caps.dwVidMemTotal;
   printf("%s%uMb VRAM available\n", vmodel, unsigned(Vmem/(1024U*1024U)+((Vmem%(1024U*1024U))>512U*1024U)));

   max_modes = 0;
   dd->EnumDisplayModes(DDEDM_REFRESHRATES | DDEDM_STANDARDVGAMODES, nullptr, nullptr, callb);

   if((temp.rflags & (RF_D3D | RF_D3DE)))
       StartD3d(wnd);

   WAVEFORMATEX wf = { };
   wf.wFormatTag = WAVE_FORMAT_PCM;
   wf.nSamplesPerSec = conf.sound.fq;
   wf.nChannels = 2;
   wf.wBitsPerSample = 16;
   wf.nBlockAlign = 4;
   wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;

   if (conf.sound.do_sound == do_sound_wave)
   {
      MMRESULT mmr;
      if ((mmr = waveOutOpen(&hwo, WAVE_MAPPER, &wf, 0, 0, CALLBACK_NULL)) != MMSYSERR_NOERROR)
      { printrmm("waveOutOpen()", mmr); hwo = nullptr; goto sfail; }
      wqhead = 0;
      wqtail = 0;
   }
   else if (conf.sound.do_sound == do_sound_ds)
   {

      if ((r = DirectSoundCreate(nullptr, &ds, nullptr)) != DS_OK)
      { printrds("DirectSoundCreate()", r); goto sfail; }

      r = -1;
      if (conf.sound.dsprimary) r = ds->SetCooperativeLevel(wnd, DSSCL_WRITEPRIMARY);
      if(r != DS_OK)
      {
          r = ds->SetCooperativeLevel(wnd, DSSCL_NORMAL);
          conf.sound.dsprimary = 0;
      }
      if (r != DS_OK) { printrds("IDirectSound::SetCooperativeLevel()", r); goto sfail; }

      DSBUFFERDESC dsdesc = { sizeof(DSBUFFERDESC) };
      r = -1;

      if (conf.sound.dsprimary)
      {

         dsdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_PRIMARYBUFFER;
         dsdesc.dwBufferBytes = 0;
         dsdesc.lpwfxFormat = nullptr;
         r = ds->CreateSoundBuffer(&dsdesc, &dsbf, nullptr);

         if (r != DS_OK) { printrds("IDirectSound::CreateSoundBuffer() [primary]", r); }
         else
         {
            r = dsbf->SetFormat(&wf);
            if (r != DS_OK) { printrds("IDirectSoundBuffer::SetFormat()", r); goto sfail; }
            DSBCAPS caps; caps.dwSize = sizeof caps; dsbf->GetCaps(&caps);
            dsbuffer_sz = caps.dwBufferBytes;
         }
      }

      if (r != DS_OK)
      {
         dsdesc.lpwfxFormat = &wf;
         dsdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
         dsbuffer_sz = dsdesc.dwBufferBytes = DSBUFFER_SZ;
         if ((r = ds->CreateSoundBuffer(&dsdesc, &dsbf, nullptr)) != DS_OK)
         {
             printrds("IDirectSound::CreateSoundBuffer()", r);
             goto sfail;
         }

         conf.sound.dsprimary = 0;
      }

      dsoffset = dsbuffer_sz/4;

   }
   else
   {
   sfail:
      conf.sound.do_sound = do_sound_none;
   }

   LPDIRECTINPUT di;
   r = DirectInputCreate(hIn,DIRECTINPUT_VERSION,&di,nullptr);

   if ((r != DI_OK) && (r = DirectInputCreate(hIn,0x0300,&di,nullptr)) != DI_OK)
   {
       printrdi("DirectInputCreate()", r);
       exit();
   }

   if((r = di->CreateDevice(GUID_SysKeyboard, &dikeyboard, nullptr)) != DI_OK)
   {
       printrdi("IDirectInputDevice::CreateDevice() (keyboard)", r);
       exit();
   }

   if((r = dikeyboard->SetDataFormat(&c_dfDIKeyboard)) != DI_OK)
   {
       printrdi("IDirectInputDevice::SetDataFormat() (keyboard)", r);
       exit();
   }

   if((r = dikeyboard->SetCooperativeLevel(wnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)) != DI_OK)
   {
       printrdi("IDirectInputDevice::SetCooperativeLevel() (keyboard)", r);
       exit();
   }

   if ((r = di->CreateDevice(GUID_SysMouse, &dimouse, nullptr)) == DI_OK)
   {
      if ((r = dimouse->SetDataFormat(&c_dfDIMouse)) != DI_OK)
      {
          printrdi("IDirectInputDevice::SetDataFormat() (mouse)", r);
          exit();
      }

      if ((r = dimouse->SetCooperativeLevel(wnd, DISCL_FOREGROUND|DISCL_NONEXCLUSIVE)) != DI_OK)
      {
          printrdi("IDirectInputDevice::SetCooperativeLevel() (mouse)", r);
          exit();
      }
      DIPROPDWORD dipdw = { };
      dipdw.diph.dwSize       = sizeof(dipdw);
      dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
      dipdw.diph.dwHow        = DIPH_DEVICE;
      dipdw.dwData            = DIPROPAXISMODE_ABS;
      if ((r = dimouse->SetProperty(DIPROP_AXISMODE, &dipdw.diph)) != DI_OK)
      {
          printrdi("IDirectInputDevice::SetProperty() (mouse)", r);
          exit();
      }
   }
   else
   {
       color(CONSCLR_WARNING);
       printf("warning: no mouse\n");
       dimouse = nullptr;
   }

   if ((r = di->EnumDevices(DIDEVTYPE_JOYSTICK, InitJoystickInput, di, DIEDFL_ATTACHEDONLY)) != DI_OK)
   {
       printrdi("IDirectInput::EnumDevices(DIDEVTYPE_JOYSTICK,...)", r);
       exit();
   }

   di->Release();
//[vv]   SetKeyboardState(kbdpc); // fix bug in win95
}

static void DoneD3d(bool DeInitDll)
{
//    printf("%s(%d)\n", __FUNCTION__, DeInitDll);
    ULONG RefCnt;
    if(SurfTexture)
    {
        RefCnt = SurfTexture->Release();
        (void)RefCnt;
/*
#ifdef _DEBUG
        if(RefCnt != 0)
        {
            __debugbreak();
        }
#endif
*/

        SurfTexture = nullptr;
    }
    if(Texture)
    {
        RefCnt = Texture->Release();
#ifdef _DEBUG
        if(RefCnt != 0)
        {
            __debugbreak();
        }
#endif
        Texture = nullptr;
    }
    if(D3dDev)
    {
        RefCnt = D3dDev->Release();
#ifdef _DEBUG
        if(RefCnt != 0)
        {
            __debugbreak();
        }
#endif
        D3dDev = nullptr;
    }
    if(D3d9)
    {
        RefCnt = D3d9->Release();
#ifdef _DEBUG
        if(RefCnt != 0)
        {
            __debugbreak();
        }
#endif
        D3d9 = nullptr;
    }
    if(DeInitDll && D3d9Dll)
    {
        FreeLibrary(D3d9Dll);
        D3d9Dll = nullptr;
    }
}

void done_dx()
{
   sound_stop();
   if (pal) pal->Release(); pal = nullptr;
   if (surf2) surf2->Release(); surf2 = nullptr;
   if (surf1) surf1->Release(); surf1 = nullptr;
   if (surf0) surf0->Release(); surf0 = nullptr;
   if (sprim) sprim->Release(); sprim = nullptr;
   if (clip) clip->Release(); clip = nullptr;
   if (dd) dd->Release(); dd = nullptr;
   if (SurfMem1) _aligned_free(SurfMem1); SurfMem1 = nullptr;
   if(dikeyboard)
   {
       dikeyboard->Unacquire();
       dikeyboard->Release();
       dikeyboard = nullptr;
   }
   if(dimouse)
   {
       dimouse->Unacquire();
       dimouse->Release();
       dimouse = nullptr;
   }
   if(dijoyst)
   {
       dijoyst->Unacquire();
       dijoyst->Release();
       dijoyst = nullptr;
   }
   if (hwo) { waveOutReset(hwo); /* waveOutUnprepareHeader()'s ? */ waveOutClose(hwo); }
   if (dsbf) dsbf->Release(); dsbf = nullptr;
   if (ds) ds->Release(); ds = nullptr;
   if (hbm) DeleteObject(hbm); hbm = nullptr;
   if (temp.gdidc) ReleaseDC(wnd, temp.gdidc); temp.gdidc = nullptr;
   DoneD3d();
   if (wnd) DestroyWindow(wnd);
}