Top secrets sources NedoPC pentevo

Rev

Rev 800 | 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 "snapshot.h"
#include "tape.h"
#include "memory.h"
#include "opendlg.h"
#include "draw.h"
#include "config.h"
#include "z80.h"
#include "sshot_png.h"
#include "funcs.h"

#include "util.h"

int readSP();
int readSNA48();
int readSNA128();
int readZ80();
int writeSNA(FILE*);

int load_arc(char *fname);

SNAP what_is(char *filename)
{
   FILE *ff = fopen(filename, "rb");
   if (!ff) return snNOFILE;
   snapsize = unsigned(fread(snbuf, 1, sizeof snbuf, ff));
   fclose(ff);
   if (snapsize == sizeof snbuf) return snTOOLARGE;
   SNAP type = snUNKNOWN;
   char *ptr = strrchr(filename, '.');
   unsigned ext = ptr? (*(unsigned*)(ptr+1) | 0x20202020U) : 0;
   if (snapsize < 32) return type;

   if (snapsize == 131103 || snapsize == 147487) type = snSNA_128;
   if (snapsize == 49179) type = snSNA_48;
   if (ext == WORD4('t','a','p',' ')) type = snTAP;
   if (ext == WORD4('z','8','0',' ')) type = snZ80;
   if(ext == WORD4('p', 'a', 'l', ' ') && (snapsize == sizeof(comp.comp_pal)))
   {
       type = snPAL;
       return type;
   }
   if (conf.trdos_present)
   {
      if (!snbuf[13] && snbuf[14] && (int)snapsize == snbuf[14]*256+17) type = snHOB;
      if (snapsize >= 8192 && !(snapsize & 0xFF) && ext == WORD4('t','r','d',' ')) type = snTRD;

      if (snapsize >= 8192 && ext == WORD4('i','s','d',' '))
          type = snISD;

      if (snapsize >= 8192 && ext == WORD4('p','r','o',' '))
          type = snPRO;

      if(snapsize >= 8192 && ext == WORD4('d', 's', 'k', ' '))
          type = snDSK;

      if(snapsize >= 8192 && ext == WORD4('i', 'p', 'f', ' '))
          type = snIPF;

      if (!memcmp(snbuf, "SINCLAIR", 8))
      {
          unsigned nfiles = snbuf[8];
          unsigned nsec = 0;
          for(unsigned i = 0; i < nfiles; i++)
          {
              nsec += snbuf[9+14*i+13];
          }
         
          if(snapsize >= 9 + nfiles * 14 + nsec * 0x100)
              type = snSCL;
      }
      if (!memcmp(snbuf, "FDI", 3) && *(unsigned short*)(snbuf+4) <= MAX_CYLS && *(unsigned short*)(snbuf+6) <= 2) type = snFDI;
      if (((*(short*)snbuf|0x2020) == WORD2('t','d')) && snbuf[4] >= 10 && snbuf[4] <= 21 && snbuf[9] <= 2) type = snTD0;
      if (*(unsigned*)snbuf == WORD4('U','D','I','!') && *(unsigned*)(snbuf+4)==snapsize-4 && snbuf[9] < MAX_CYLS && snbuf[10]<2 && !snbuf[8]) type = snUDI;
   }
   if (snapsize > 10 && !memcmp(snbuf, "ZXTape!\x1A", 8))
   {
       type = snTZX;
   }
   if (snapsize > 0x22 && !memcmp(snbuf, "Compressed Square Wave\x1A", 23)) type = snCSW;
   if (*(unsigned short*)snbuf == WORD2('S','P') && *(unsigned short*)(snbuf+2)+0x26 == (int)snapsize) type = snSP;
   return type;
}

static int readPAL();

int loadsnap(char *filename)
{
   if (load_arc(filename))
       return 1;
   SNAP type = what_is(filename);

   if (type >= snHOB)
   {

      if (trd_toload == -1U)
      {
         unsigned last = -1U;
         for (unsigned i = 0; i < 4; i++) if (trd_loaded[i]) last = i;
         trd_toload = (last == -1U)? 0 : ((type == snHOB)? last : (last+1) & 3);
      }

      for (unsigned k = 0; k < 4; k++)
      {
         if (k != trd_toload && !stricmp(comp.fdd[k].name, filename))
         {
            static char err[] = "This disk image is already loaded to drive X:\n"
                                "Do you still want to load it to drive Y:?";
            err[43] = char(k+'A');
            err[84] = char(trd_toload+'A');
            if (MessageBox(GetForegroundWindow(), err, "Warning", MB_YESNO | MB_ICONWARNING) == IDNO) return 1;
         }
      }

      FDD *drive = comp.fdd + trd_toload;
      if (!drive->test())
          return 0;
      comp.wd.Eject(trd_toload);
      int ok = drive->read(type);
      if (ok)
      {
         if (*conf.appendboot)
             drive->addboot();
         strcpy(drive->name, filename);
         if (GetFileAttributes(filename) & FILE_ATTRIBUTE_READONLY)
            conf.trdos_wp[trd_toload] = 1;
         drive->snaptype = (type == snHOB || type == snSCL)? snTRD : type;
         trd_loaded[trd_toload] = 1;
//---------Alone Coder
         char *name = filename;
         for (char *x = name; *x; x++)
             if (*x == '\\') name = x+1;
         strcpy(temp.LastSnapName, name);
         char *p = strrchr(temp.LastSnapName, '.');
         if(p)
         {
             *p = 0;
         }
         char wintitle[0x200];
         strcpy(wintitle,name);
         strcat(wintitle," - UnrealSpeccy");
         SetWindowText(wnd, wintitle);
//~---------Alone Coder
      }
      return ok;
   }

   if (type == snSP) return readSP();
   if (type == snSNA_48) return readSNA48();
   if (type == snSNA_128) return readSNA128();
   if (type == snZ80) return readZ80();
   if (type == snTAP) return readTAP();
   if (type == snTZX) return readTZX();
   if (type == snCSW) return readCSW();
   if(type == snPAL)
   {
       return readPAL();
   }

   return 0;
}

int readSNA128()
{
   if(conf.mem_model == MM_PENTAGON && conf.Sna128Lock)
   {
       conf.ramsize = 128;
       temp.ram_mask = u8((conf.ramsize-1) >> 4);
   }

   hdrSNA128 *hdr = (hdrSNA128*)snbuf;
   reset(hdr->trdos? RM_DOS : RM_SOS);
   cpu.alt.af = hdr->altaf;
   cpu.alt.bc = hdr->altbc;
   cpu.alt.de = hdr->altde;
   cpu.alt.hl = hdr->althl;
   cpu.af = hdr->af;
   cpu.bc = hdr->bc;
   cpu.de = hdr->de;
   cpu.hl = hdr->hl;
   cpu.ix = hdr->ix;
   cpu.iy = hdr->iy;
   cpu.sp = hdr->sp;
   cpu.pc = hdr->pc;
   cpu.i = hdr->i;
   cpu.r_low = hdr->r;
   cpu.r_hi = hdr->r & 0x80;
   cpu.im = hdr->im;
   cpu.iff1 = cpu.iff2 = (hdr->iff >> 2) & 1;
   comp.p7FFD = hdr->p7FFD;
   comp.pFE = hdr->pFE;
   comp.border_attr = comp.pFE & 7;
   memcpy(memory+PAGE*5, hdr->page5, PAGE);
   memcpy(memory+PAGE*2, hdr->page2, PAGE);
   memcpy(memory+PAGE*(hdr->p7FFD & 7), hdr->active_page, PAGE);
   unsigned char *newpage = snbuf+0xC01F;
   unsigned char mapped = u8(0x24U | (1 << (hdr->p7FFD & 7)));
   for (unsigned char i = 0; i < 8; i++)
   {
      if (!(mapped & (1 << i)))
      {
         memcpy(memory + PAGE*i, newpage, PAGE);
         newpage += PAGE;
      }
   }
   set_banks();
   return 1;
}

int readSNA48()
{
   //conf.mem_model = MM_PENTAGON; conf.ramsize = 128;  // molodcov_alex
   reset(RM_SOS);
   hdrSNA128 *hdr = (hdrSNA128*)snbuf;
   cpu.alt.af = hdr->altaf; cpu.alt.bc = hdr->altbc;
   cpu.alt.de = hdr->altde; cpu.alt.hl = hdr->althl;
   cpu.af = hdr->af; cpu.bc = hdr->bc; cpu.de = hdr->de; cpu.hl = hdr->hl;
   cpu.ix = hdr->ix; cpu.iy = hdr->iy; cpu.sp = hdr->sp;
   cpu.i = hdr->i; cpu.r_low = hdr->r; cpu.r_hi = hdr->r & 0x80; cpu.im = hdr->im;
   cpu.iff1 = cpu.iff2 = (hdr->iff >> 2) & 1; comp.p7FFD = 0x30;
   comp.pEFF7 |= EFF7_LOCKMEM; //Alone Coder
   comp.pFE = hdr->pFE; comp.border_attr = comp.pFE & 7;
   memcpy(memory+PAGE*5, hdr->page5, PAGE);
   memcpy(memory+PAGE*2, hdr->page2, PAGE);
   memcpy(memory+PAGE*0, hdr->active_page, PAGE);
   cpu.pc = cpu.DirectRm(cpu.sp)+0x100*cpu.DirectRm(cpu.sp+1); cpu.sp += 2;
   set_banks(); return 1;
}

int readSP()
{
   //conf.mem_model = MM_PENTAGON; conf.ramsize = 128;  // molodcov_alex
   reset(RM_SOS);
   hdrSP *hdr = (hdrSP*)snbuf;
   cpu.alt.af = hdr->altaf; cpu.alt.bc = hdr->altbc;
   cpu.alt.de = hdr->altde; cpu.alt.hl = hdr->althl;
   cpu.af = hdr->af; cpu.bc = hdr->bc; cpu.de = hdr->de; cpu.hl = hdr->hl;
   cpu.ix = hdr->ix; cpu.iy = hdr->iy; cpu.sp = hdr->sp; cpu.pc = hdr->pc;
   cpu.i = hdr->i; cpu.r_low = hdr->r; cpu.r_hi = hdr->r & 0x80;
   cpu.iff1 = (hdr->flags & 1);
   cpu.im = 1 + ((hdr->flags >> 1) & 1);
   cpu.iff2 = (hdr->flags >> 2) & 1;
   comp.p7FFD = 0x30;
   comp.pEFF7 |= EFF7_LOCKMEM; //Alone Coder
   comp.pFE = hdr->pFE; comp.border_attr = comp.pFE & 7;
   for (unsigned i = 0; i < hdr->len; i++)
      cpu.DirectWm(hdr->start + i, snbuf[i + 0x26]);
   set_banks(); return 1;
}

int writeSNA(FILE *ff)
{
/*   if (conf.ramsize != 128) {
      MessageBox(GetForegroundWindow(), "SNA format can hold only\r\n128kb memory models", "Save", MB_ICONERROR);
      return 0;
   }*/
//Alone Coder
   hdrSNA128 *hdr = (hdrSNA128*)snbuf;
   hdr->trdos = (comp.flags & CF_TRDOS)? 1 : 0;
   hdr->altaf = u16(cpu.alt.af); hdr->altbc = u16(cpu.alt.bc);
   hdr->altde = u16(cpu.alt.de); hdr->althl = u16(cpu.alt.hl);
   hdr->af = u16(cpu.af); hdr->bc = u16(cpu.bc); hdr->de = u16(cpu.de); hdr->hl = u16(cpu.hl);
   hdr->ix = u16(cpu.ix); hdr->iy = u16(cpu.iy); hdr->sp = u16(cpu.sp); hdr->pc = u16(cpu.pc);
   hdr->i = cpu.i; hdr->r = (cpu.r_low & 0x7F)+cpu.r_hi; hdr->im = cpu.im;
   hdr->iff = cpu.iff2 ? 4 : 0;
   hdr->p7FFD = comp.p7FFD;
   hdr->pFE = comp.pFE; comp.border_attr = comp.pFE & 7;
   unsigned savesize = sizeof(hdrSNA128);
   unsigned char mapped = u8(0x24U | (1 << (comp.p7FFD & 7)));
   if (comp.p7FFD == 0x30)
   { // save 48k
      mapped = 0xFF;
      savesize = 0xC01B;
      hdr->sp -= 2;
      cpu.DirectWm(hdr->sp, cpu.pcl);
      cpu.DirectWm(hdr->sp+1, cpu.pch);
   }
   memcpy(hdr->page5, memory+PAGE*5, PAGE);
   memcpy(hdr->page2, memory+PAGE*2, PAGE);
   memcpy(hdr->active_page, memory+PAGE*(comp.p7FFD & 7), PAGE);
   if (fwrite(hdr, 1, savesize, ff) != savesize) return 0;
   for (unsigned char i = 0; i < 8; i++)
      if (!(mapped & (1 << i))) {
         if (fwrite(memory + PAGE*i, 1, PAGE, ff) != PAGE) return 0;
      }
   return 1;
}

static void unpack_page(unsigned char *dst, unsigned dstlen, unsigned char *src, unsigned srclen)
{
   memset(dst, 0, dstlen);
   while (srclen > 0 && dstlen > 0)
   {
      if (srclen >= 4 && src[0] == 0xED && src[1] == 0xED)
      {
         size_t len = src[2];
         memset(dst, src[3], len);
         dstlen -= len;
         srclen -= 4;
         src += 4;
         dst += len;
      }
      else
      {
          *dst++ = *src++;
          dstlen--;
          srclen--;
      }
   }
}

int readZ80()
{
   //conf.mem_model = MM_PENTAGON; conf.ramsize = 128;  // molodcov_alex
   hdrZ80 *hdr = (hdrZ80*)snbuf;
   unsigned char *ptr = snbuf + 30;
   unsigned char model48k = (hdr->model < 3);
   reset((model48k|(hdr->p7FFD & 0x10)) ? RM_SOS : RM_128);
   if (hdr->flags == 0xFF)
       hdr->flags = 1;
   if (hdr->pc == 0)
   { // 2.01
      ptr += 2 + hdr->len;
      hdr->pc = hdr->newpc;
      memset(RAM_BASE_M, 0, PAGE*8); // clear 128k - first 8 pages

      unsigned char * const p48[] =
      {
             base_sos_rom, nullptr, nullptr, nullptr,
             RAM_BASE_M+2*PAGE, RAM_BASE_M+0*PAGE, nullptr, nullptr,
             RAM_BASE_M+5*PAGE, nullptr, nullptr, nullptr
      };
      unsigned char * const p128[] =
      {
             base_sos_rom, base_dos_rom, base_128_rom, RAM_BASE_M+0*PAGE,
             RAM_BASE_M+1*PAGE, RAM_BASE_M+2*PAGE, RAM_BASE_M+3*PAGE, RAM_BASE_M+4*PAGE,
             RAM_BASE_M+5*PAGE, RAM_BASE_M+6*PAGE, RAM_BASE_M+7*PAGE, nullptr
      };

      while (ptr < snbuf+snapsize)
      {
         unsigned len = *(unsigned short*)ptr;
         if (ptr[2] > 11)
             return 0;
         unsigned char *dstpage = model48k ? p48[ptr[2]] : p128[ptr[2]];
         if (!dstpage)
             return 0;
         ptr += 3;
         if (len == 0xFFFF)
         {
             len = PAGE;
             memcpy(dstpage, ptr, len);
         }
         else
             unpack_page(dstpage, PAGE, ptr, len);
         ptr += len;
      }
   }
   else
   {
      unsigned len = snapsize - 30;
      unsigned char *mem48 = ptr;
      if (hdr->flags & 0x20)
         unpack_page(mem48 = snbuf + 4*PAGE, 3*PAGE, ptr, len);
      memcpy(memory + PAGE*5, mem48, PAGE);
      memcpy(memory + PAGE*2, mem48 + PAGE, PAGE);
      memcpy(memory + PAGE*0, mem48 + 2*PAGE, PAGE);
      model48k = 1;
   }
   cpu.a = hdr->a; cpu.f = hdr->f;
   cpu.bc = hdr->bc; cpu.de = hdr->de; cpu.hl = hdr->hl;
   cpu.alt.bc = hdr->bc1; cpu.alt.de = hdr->de1; cpu.alt.hl = hdr->hl1;
   cpu.alt.a = hdr->a1; cpu.alt.f = hdr->f1;
   cpu.pc = hdr->pc; cpu.sp = hdr->sp; cpu.ix = hdr->ix; cpu.iy = hdr->iy;
   cpu.i = hdr->i; cpu.r_low = hdr->r & 0x7F;
   cpu.r_hi = u8((hdr->flags & 1U) << 7U);
   comp.pFE = (hdr->flags >> 1) & 7;
   comp.border_attr = comp.pFE;
   cpu.iff1 = hdr->iff1; cpu.iff2 = hdr->iff2; cpu.im = (hdr->im & 3);
   comp.p7FFD = (model48k) ? 0x30 : hdr->p7FFD;

   if(hdr->len == 55) // version 3.0 (with 1ffd)
       comp.p1FFD = hdr->p1FFD;

   if (model48k)
       comp.pEFF7 |= EFF7_LOCKMEM; //Alone Coder

   set_banks();

   return 1;
}

static int readPAL()
{
    memcpy(comp.comp_pal, snbuf, snapsize);
    temp.comp_pal_changed = 1;
    if(conf.ula_plus)
    {
        comp.ula_plus_en = true; // Åñëè ïðèñóòñòâóåò ULA+, òî ñðàçó è àêòèâèðóåì åå (÷òîáû èçìåíåíèÿ ïàëèòðû áûëè âèäíû)
    }
    return 1;
}

static int writePAL(FILE *ff)
{
    if(fwrite(comp.comp_pal, 1, sizeof(comp.comp_pal), ff) != sizeof(comp.comp_pal))
    {
        return 0;
    }
    return 1;
}

#define arctmp ((char*)rbuf)
static char *arc_fname;
static INT_PTR CALLBACK ArcDlg(HWND dlg, UINT msg, WPARAM wp, LPARAM lp)
{
   (void)lp;

   if (msg == WM_INITDIALOG) {
      for (char *dst = arctmp; *dst; dst += strlen(dst)+1)
         SendDlgItemMessage(dlg, IDC_ARCLIST, LB_ADDSTRING, 0, (LPARAM)dst);
      SendDlgItemMessage(dlg, IDC_ARCLIST, LB_SETCURSEL, 0, 0);
      return 1;
   }
   if ((msg == WM_COMMAND && wp == IDCANCEL) ||
       (msg == WM_SYSCOMMAND && (wp & 0xFFF0) == SC_CLOSE)) EndDialog(dlg, 0);
   if (msg == WM_COMMAND && (LOWORD(wp) == IDOK || (HIWORD(wp)==LBN_DBLCLK && LOWORD(wp) == IDC_ARCLIST)))
   {
      int n = int(SendDlgItemMessage(dlg, IDC_ARCLIST, LB_GETCURSEL, 0, 0));
      char *dst = arctmp;
      for (int q = 0; q < n; q++) dst += strlen(dst)+1;
      arc_fname = dst;
      EndDialog(dlg, 0);
   }
   return 0;
}

static bool filename_ok(char *fname)
{
   for (char *wc = skiparc; *wc; wc += strlen(wc)+1)
      if (wcmatch(fname, wc)) return 0;
   return 1;
}

int load_arc(char *fname)
{
   char *ext = strrchr(fname, '.'); if (!ext) return 0;
   ext++;
   char *cmdtmp;
   char done = 0;
   for (char *x = arcbuffer; *x; x = cmdtmp + strlen(cmdtmp)+1) {
      cmdtmp = x + strlen(x)+1;
      if (stricmp(ext, x)) continue;

      char dir[0x200]; GetCurrentDirectory(sizeof dir, dir);
      char tmp[0x200]; GetTempPath(sizeof tmp, tmp);
      char d1[0x20]; sprintf(d1, "us%08lX", GetTickCount());
      SetCurrentDirectory(tmp);
      CreateDirectory(d1, nullptr);
      SetCurrentDirectory(d1);

      color();
      char cmdln[0x200]; sprintf(cmdln, cmdtmp, fname);
      STARTUPINFO si = { sizeof si };
      si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE;
      PROCESS_INFORMATION pi;
      unsigned flags = CREATE_NEW_CONSOLE;
      HANDLE hc = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
      if(hc != INVALID_HANDLE_VALUE)
      {
          CloseHandle(hc);
          flags = 0;
      }
      if (CreateProcess(nullptr, cmdln, nullptr, nullptr, 0, flags, nullptr, nullptr, &si, &pi)) {
         WaitForSingleObject(pi.hProcess, INFINITE);
         DWORD code; GetExitCodeProcess(pi.hProcess, &code);
         CloseHandle(pi.hThread);
         CloseHandle(pi.hProcess);
         if (!code || MessageBox(GetForegroundWindow(), "Broken archive", nullptr, MB_ICONERROR | MB_OKCANCEL) == IDOK) {
            WIN32_FIND_DATA fd; HANDLE h;
            char *dst = arctmp; unsigned nfiles = 0;
            if ((h = FindFirstFile("*.*", &fd)) != INVALID_HANDLE_VALUE) {
               do {
                  if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && filename_ok(fd.cFileName)) {
                     strcpy(dst, fd.cFileName); dst += strlen(dst)+1;
                     nfiles++;
                  }
               } while (FindNextFile(h, &fd));
               FindClose(h);
            }
            *dst = 0; arc_fname = nullptr;
            if (nfiles == 1) arc_fname = arctmp;
            if (nfiles > 1)
                DialogBox(hIn, MAKEINTRESOURCE(IDD_ARC), GetForegroundWindow(), ArcDlg);
            if (!nfiles) MessageBox(GetForegroundWindow(), "Empty archive!", nullptr, MB_ICONERROR | MB_OK);
            char buf[0x200]; strcpy(buf, tmp); strcat(buf, "\\");
            strcat(buf, d1);
            strcat(buf, "\\");
            if(arc_fname)
            {
                strcat(buf, arc_fname);
                arc_fname = buf;
            }
            if (arc_fname && !(done = (char)loadsnap(arc_fname))) MessageBox(GetForegroundWindow(), "loading error", arc_fname, MB_ICONERROR);
            if (!done) done = -1;
         }
         // delete junk
         SetCurrentDirectory(tmp);
         SetCurrentDirectory(d1);
         WIN32_FIND_DATA fd; HANDLE h;
         if ((h = FindFirstFile("*.*", &fd)) != INVALID_HANDLE_VALUE) {
            do { DeleteFile(fd.cFileName); } while (FindNextFile(h, &fd));
            FindClose(h);
         }
      }
      SetCurrentDirectory(tmp);
      RemoveDirectory(d1);
      SetCurrentDirectory(dir);
      eat(); if (done) return done > 0 ? 1 : 0;
   }
   eat(); return 0;
}

void opensnap(unsigned index)
{
   char mask[0x200]; *mask = 0;
   for(char *x = arcbuffer; *x; )
   {
       strcat(mask, ";*.");
       strcat(mask, x);
       x += strlen(x) + 1;
       x += strlen(x) + 1;
   }

   char fline[0x400];
   const char *src = "all (sna,z80,sp,tap,tzx,csw,trd,scl,fdi,td0,udi,isd,pro,dsk,ipf,hobeta,pal)\0"
               "*.sna;*.z80;*.sp;*.tap;*.tzx;*.csw;*.trd;*.scl;*.td0;*.udi;*.fdi;*.isd;*.pro;*.dsk;*.ipf;*.$?;*.!?;*.pal<\0"
               "Disk B (trd,scl,fdi,td0,udi,isd,pro,dsk,ipf,hobeta)\0*.trd;*.scl;*.fdi;*.udi;*.td0;*.isd;*.pro;*.dsk;*.ipf;*.$?<\0"
               "Disk C (trd,scl,fdi,td0,udi,isd,pro,dsk,ipf,hobeta)\0*.trd;*.scl;*.fdi;*.udi;*.td0;*.isd;*.pro;*.dsk;*.ipf;*.$?<\0"
               "Disk D (trd,scl,fdi,td0,udi,isd,pro,dsk,ipf,hobeta)\0*.trd;*.scl;*.fdi;*.udi;*.td0;*.isd;*.pro;*.dsk;*.ipf;*.$?<\0\0>";
   if (!conf.trdos_present)
      src = "ZX files (sna,z80,tap,tzx,csw,pal)\0*.sna;*.z80;*.tap;*.tzx;*.csw;*.pal<\0\0>";
   for(char *dst = fline; *src != '>'; src++)
   {
       if(*src == '<')
       {
           strcpy(dst, mask);
           dst += strlen(dst);
       }
       else
       {
           *dst++ = *src;
       }
   }

   OPENFILENAME ofn = { };
   char fname[0x200]; *fname = 0;
   char dir[0x200]; GetCurrentDirectory(sizeof dir, dir);

   ofn.lStructSize = (WinVerMajor < 5) ? OPENFILENAME_SIZE_VERSION_400 : sizeof(OPENFILENAME);
   ofn.hwndOwner = GetForegroundWindow();
   ofn.lpstrFilter = fline;
   ofn.lpstrFile = fname; ofn.nMaxFile = sizeof fname;
   ofn.lpstrTitle = "Load Snapshot / Disk / Tape";
   ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
   ofn.nFilterIndex = DWORD(index);
   ofn.lpstrInitialDir = dir;
//   __debugbreak();
   if (GetSnapshotFileName(&ofn, 0))
   {
      trd_toload = ofn.nFilterIndex-1;
      if (!loadsnap(fname))
          MessageBox(GetForegroundWindow(), fname, "loading error", MB_ICONERROR);
   }
   eat();
}

static const int mx_typs = (1+4*6);
static unsigned char snaps[mx_typs];
static unsigned exts[mx_typs];
static unsigned drvs[mx_typs];
static unsigned snp;
static void addref(LPSTR &ptr, unsigned char sntype, const char *ref, unsigned drv, unsigned ext)
{
   strcpy(ptr, ref); ptr += strlen(ptr)+1;
   strcpy(ptr, ref+strlen(ref)+1); ptr += strlen(ptr)+1;
   drvs[snp] = drv; exts[snp] = ext; snaps[snp++] = sntype;
}

void savesnap(int diskindex)
{
again:
   OPENFILENAME ofn = { };
   char fname[0x200]; *fname = 0;
   if (diskindex >= 0) {
      strcpy(fname, comp.fdd[diskindex].name);
      size_t ln = strlen(fname);
      if (ln > 4 && (*(unsigned*)(fname+ln-4) | WORD4(0,0x20,0x20,0x20)) == WORD4('.','s','c','l'))
         *(unsigned*)(fname+ln-4) = WORD4('.','t','r','d');
   }

   snp = 1; char types[600], *ptr = types;

   if (diskindex < 0)
   {
      exts[snp] = WORD4('s','n','a',0); snaps[snp] = snSNA_128; // default
      addref(ptr, snSNA_128, "ZX-Spectrum 128K snapshot (SNA)\0*.sna", -1U, WORD4('s','n','a',0));

      exts[snp] = WORD4('p', 'a', 'l', 0); snaps[snp] = snPAL;
      addref(ptr, snPAL, "Palette (ULA+)\0*.pal", -1U, WORD4('p', 'a', 'l', 0));
   }

   ofn.lStructSize = (WinVerMajor < 5) ? OPENFILENAME_SIZE_VERSION_400 : sizeof(OPENFILENAME);
   ofn.nFilterIndex = 1;

   if (conf.trdos_present)
   {
      static char mask[] = "Disk A (TRD)\0*.trd";
      static const char ex[][3] = { {'T','R','D'}, {'F','D','I'},{'T','D','0'},{'U','D','I'},{'I','S','D'},{'P','R','O'}};
      static const unsigned ex2[] = { snTRD, snFDI, snTD0, snUDI, snISD, snPRO };

      for (unsigned n = 0; n < 4; n++)
      {
         if (!comp.fdd[n].rawdata)
             continue;
         if (diskindex >= 0 && unsigned(diskindex) != n)
             continue;
         mask[5] = char('A'+n);

         for (size_t i = 0; i < sizeof ex/sizeof(ex[0]); i++)
         {
            if (unsigned(diskindex) == n && ex2[i] == comp.fdd[n].snaptype)
                ofn.nFilterIndex = snp;
            memcpy(mask+8, ex[i], 3);
            memcpy(mask+15, ex[i], 3);
            addref(ptr, u8(ex2[i]), mask, n, (*(const unsigned*)ex[i] & 0xFFFFFF) | 0x202020);
         }
      }
   }
   ofn.lpstrFilter = types; *ptr = 0;
   ofn.lpstrFile = fname; ofn.nMaxFile = sizeof fname;
   ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
   ofn.hwndOwner = GetForegroundWindow();
   char *path = strrchr(fname, '\\');
   if (path)
   { // check if directory exists (for files opened from archive)
      char x = *path; *path = 0;
      unsigned atr = GetFileAttributes(fname); *path = x;
      if (atr == -1U || !(atr & FILE_ATTRIBUTE_DIRECTORY)) *fname = 0;
   } else path = fname;
   path = strrchr(path, '.'); if (path) *path = 0; // delete extension

   if (GetSnapshotFileName(&ofn, 1))
   {
      char *fn = strrchr(ofn.lpstrFile, '\\');
      fn = fn? fn+1 : ofn.lpstrFile;
      char *extpos = strrchr(fn, '.');
      if (!extpos || stricmp(extpos+1, (char*)&exts[ofn.nFilterIndex]))
      {
         char *dst = fn + strlen(fn); *dst++ = '.';
         *(unsigned*)dst = exts[ofn.nFilterIndex];
      }
      if (GetFileAttributes(ofn.lpstrFile) != INVALID_FILE_ATTRIBUTES &&
            IDNO == MessageBox(GetForegroundWindow(), "File exists. Overwrite ?", "Save", MB_ICONQUESTION | MB_YESNO))
         goto again;

      FILE *ff = fopen(ofn.lpstrFile, "wb");
      if (ff)
      {
         int res = 0;
         FDD *saveto = comp.fdd + drvs[ofn.nFilterIndex];
         switch (snaps[ofn.nFilterIndex])
         {
            case snSNA_128: res = writeSNA(ff); break;
            case snPAL: res = writePAL(ff); break;
            case snTRD: res = saveto->write_trd(ff); break;
            case snUDI: res = saveto->write_udi(ff); break;
            case snFDI: res = saveto->write_fdi(ff); break;
            case snTD0: res = saveto->write_td0(ff); break;
            case snISD: res = saveto->write_isd(ff); break;
            case snPRO: res = saveto->write_pro(ff); break;
         }
         fclose(ff);
         if (!res)
             MessageBox(GetForegroundWindow(), "write error", "Save", MB_ICONERROR);
         else if (drvs[ofn.nFilterIndex]!=-1U)
         {
             comp.fdd[drvs[ofn.nFilterIndex]].optype=0;
             strcpy(comp.fdd[drvs[ofn.nFilterIndex]].name, ofn.lpstrFile);

             //---------Alone Coder
             char *name = ofn.lpstrFile;
             for (char *x = name; *x; x++)
             {
                 if (*x == '\\')
                     name = x+1;
             }
             char wintitle[0x200];
             strcpy(wintitle,name);
             strcat(wintitle," - UnrealSpeccy");
             SetWindowText(wnd, wintitle);
             //~---------Alone Coder
         }
      }
      else
          MessageBox(GetForegroundWindow(), "Can't open file for writing", "Save", MB_ICONERROR);
   }
   eat();
}

void ConvPal8ToBgr24(u8 *dst, u8 *scrbuf, int dx)
{
    u8 *ds = dst;
    for(unsigned i = 0; i < temp.oy; i++) // convert to BGR24 format
    {
       unsigned char *src = scrbuf + int(i)*dx;
       for (unsigned y = 0; y < temp.ox; y++)
       {
          ds[0] = pal0[src[y]].peBlue;
          ds[1] = pal0[src[y]].peGreen;
          ds[2] = pal0[src[y]].peRed;
          ds += 3;
       }
       ds = (PBYTE)(ULONG_PTR(ds + 3) & ~ULONG_PTR(3)); // êàæäàÿ ñòðîêà âûðàâíåíà íà 4
    }
}

void ConvRgb15ToBgr24(u8 *dst, u8 *scrbuf, int dx)
{
    u8 *ds = dst;
    for(unsigned i = 0; i < temp.oy; i++) // convert to BGR24 format
    {
       unsigned char *src = scrbuf + int(i)*dx;
       for (unsigned y = 0; y < temp.ox; y++)
       {
          unsigned xx;
          xx = *(unsigned*)(src + y*2);

          ds[0] = u8((xx & 0x1F)<<3);
          ds[1] = u8((xx & 0x03E0)>>2);
          ds[2] = u8((xx & 0x7C00)>>7);
          ds += 3;
       }
       ds = (PBYTE)(ULONG_PTR(ds + 3) & ~ULONG_PTR(3)); // êàæäàÿ ñòðîêà âûðàâíåíà íà 4
    }
}

void ConvRgb16ToBgr24(u8 *dst, u8 *scrbuf, int dx)
{
    u8 *ds = dst;
    for(unsigned i = 0; i < temp.oy; i++) // convert to BGR24 format
    {
       unsigned char *src = scrbuf + int(i)*dx;
       for (unsigned y = 0; y < temp.ox; y++)
       {
          unsigned xx;
          xx = *(unsigned*)(src + y*2);

          ds[0] = u8((xx&0x1F)<<3);
          ds[1] = u8((xx&0x07E0)>>3);
          ds[2] = u8((xx&0xF800)>>8);
          ds += 3;
       }
       ds = (PBYTE)(ULONG_PTR(ds + 3) & ~ULONG_PTR(3)); // êàæäàÿ ñòðîêà âûðàâíåíà íà 4
    }
}

void ConvYuy2ToBgr24(u8 *dst, u8 *scrbuf, int dx)
{
    u8 *ds = dst;
    for(unsigned i = 0; i < temp.oy; i++) // convert to BGR24 format
    {
        unsigned char *src = scrbuf + int(i)*dx;
        for (unsigned y = 0; y < temp.ox; y++)
        {
            unsigned xx;
            xx = *(unsigned*)(src + y*2);

            int u = src[y/2*4+1], v = src[y/2*4+3], Y = src[y*2];
            int r = (int)(.4732927654e-2*u-255.3076403+1.989858012*v+.9803921569*Y);
            int g = (int)(-.9756592292*v+186.0716700-.4780256930*u+.9803921569*Y);
            int b = (int)(.9803921569*Y+2.004732928*u-255.3076403-.1014198783e-1*v); // mapple rulez!
            if (r < 0) r = 0; if (r > 255) r = 255;
            if (g < 0) g = 0; if (g > 255) g = 255;
            if (b < 0) b = 0; if (b > 255) b = 255;

            ds[0] = u8(b);
            ds[1] = u8(g);
            ds[2] = u8(r);
            ds += 3;
        }
        ds = (PBYTE)(ULONG_PTR(ds + 3) & ~ULONG_PTR(3)); // êàæäàÿ ñòðîêà âûðàâíåíà íà 4
    }
}

void ConvBgr32ToBgr24(u8 *dst, u8 *scrbuf, int dx)
{
    u8 *ds = dst;
    for(unsigned i = 0; i < temp.oy; i++) // convert to BGR24 format
    {
       unsigned char *src = scrbuf + int(i)*dx;
       for (unsigned x = 0; x < temp.ox; x++)
       {
          ds[0] = src[0];
          ds[1] = src[1];
          ds[2] = src[2];
          src += 4;
          ds += 3;
       }
       ds = (PBYTE)(ULONG_PTR(ds + 3) & ~ULONG_PTR(3)); // êàæäàÿ ñòðîêà âûðàâíåíà íà 4
    }
}


TColorConverter ConvBgr24 = nullptr;

static void SaveBmp(FILE *File, u8 *ds)
{
     static u8 bmpheader32[]=
     {
            // BITMAPFILEHEADER
            0x42,0x4d,           // Type
            0x36,0x10,0x0e,0x00, // Size
            0x00,0x00,           // Reserved1
            0x00,0x00,           // Reserved2
            0x36,0x00,0x00,0x00, // OffBits
            // BITMAPINFOHEADER
            0x28,0x00,0x00,0x00, // Size
            0x80,0x02,0x00,0x00, // Width
            0xe0,0x01,0x00,0x00, // Height
            0x01,0x00,           // Planes
            0x18,0x00,           // BitCount
            0x00,0x00,0x00,0x00, // Compression
            0x00,0x10,0x0e,0x00, // SizeImage
            0x00,0x00,0x00,0x00, // XPixelsPerMeter
            0x00,0x00,0x00,0x00, // YPixelsPerMeter
            0x00,0x00,0x00,0x00, // ClrUsed
            0x00,0x00,0x00,0x00  // ClrImportant
     };

    *(unsigned*)(bmpheader32 + 2) = temp.ox * temp.oy * 3 + sizeof(bmpheader32); // filesize
    *(unsigned*)(bmpheader32 + 0x12) = temp.ox;
    *(unsigned*)(bmpheader32 + 0x16) = temp.oy;
    fwrite(bmpheader32, 1, sizeof(bmpheader32), File);

    for(int y = int(temp.oy - 1); y >= 0 ; y--)
    {
        fwrite(ds + ((unsigned(y) * temp.ox * 3 + 3) & ~3U), 1, temp.ox * 3, File);
    }
}

static void SavePng(FILE *File, u8 *ds)
{
    static png_color bkgColor = {127, 127, 127};

    if(!temp.PngSupport)
        return;

    PngSaveImage(File, ds, int(temp.ox), int(temp.oy), bkgColor);
}

#define MAKE_RGBQUAD(r,g,b) (ULONG) (u32(b) | (u32(g)<<8) | (u32(r)<<16))

static void SaveBmp16c(FILE *File, const u8 *ds)
{
     static u8 bmpheader32[]=
     {
            // BITMAPFILEHEADER
            0x42,0x4d,           // Type
            0x36,0x10,0x0e,0x00, // Size = 320*200/2 + sizeof(bmpheader32)
            0x00,0x00,           // Reserved1
            0x00,0x00,           // Reserved2
            0x76,0x00,0x00,0x00, // OffBits
            // BITMAPINFOHEADER
            0x28,0x00,0x00,0x00, // Size
            0x40,0x01,0x00,0x00, // Width = 320
            0xc8,0x00,0x00,0x00, // Height = 200
            0x01,0x00,           // Planes
            0x04,0x00,           // BitCount = 4
            0x00,0x00,0x00,0x00, // Compression = BI_RGB
            0x00,0x10,0x0e,0x00, // SizeImage
            0x00,0x00,0x00,0x00, // XPixelsPerMeter
            0x00,0x00,0x00,0x00, // YPixelsPerMeter
            0x00,0x00,0x00,0x00, // ClrUsed
            0x00,0x00,0x00,0x00, // ClrImportant
            // PALETTE
            0,0,0,0,             //  0 bgra
            0,0,0,0,             //  1 bgra
            0,0,0,0,             //  2 bgra
            0,0,0,0,             //  3 bgra
            0,0,0,0,             //  4 bgra
            0,0,0,0,             //  5 bgra
            0,0,0,0,             //  6 bgra
            0,0,0,0,             //  7 bgra
            0,0,0,0,             //  8 bgra
            0,0,0,0,             //  9 bgra
            0,0,0,0,             // 10 bgra
            0,0,0,0,             // 11 bgra
            0,0,0,0,             // 12 bgra
            0,0,0,0,             // 13 bgra
            0,0,0,0,             // 14 bgra
            0,0,0,0              // 15 bgra
     };

    *(unsigned*)(bmpheader32 + 2) = 320 * 200 / 2 + sizeof(bmpheader32); // filesize
    for(unsigned i = 0; i < 16; i++)
    {
        // Ôîðìàò äàííûõ ïàëèòðû Gg0Rr0Bb
        // Ðàñêëàäêà ïàëèòðû ýìóëÿòîðà - ULA+
        unsigned PalIdx = ((i & 8) << 1) | (i & 7);
        u8 r = u8(u32((comp.comp_pal[PalIdx] >> 3) & 3) * 255 / 3);
        u8 g = u8(u32((comp.comp_pal[PalIdx] >> 6) & 3) * 255 / 3);
        u8 b = u8(u32(comp.comp_pal[PalIdx] & 3) * 255 / 3);

        *(PULONG)(bmpheader32 + 54 + 4*i) = MAKE_RGBQUAD(r,g,b);
    }
    fwrite(bmpheader32, 1, sizeof(bmpheader32), File);
    fwrite(ds, 1, 320 * 200 / 2, File);
}

static void ConvPal16cAtm1(u8 *Buf)
{
    static const int ega0_ofs = -4*PAGE;
    static const int ega1_ofs = 0;
    static const int ega2_ofs = -4*PAGE+0x2000;
    static const int ega3_ofs = 0x2000;

   for (int y = 200 - 1; y >= 0; y--)
   {
      const u8 *src = temp.base + y*40;

      for(unsigned x = 0; x < 320; x += 8)
      {
          u8 v0 = src[ega0_ofs];
          u8 v1 = src[ega1_ofs];
          u8 v2 = src[ega2_ofs];
          u8 v3 = src[ega3_ofs];
          Buf[0] = u8(((((v0 >> 3) & 8) | (v0 & 0x7)) << 4) |
                   (((v0 & 0x80) | ((v0 << 1) & 0x70)) >> 4));
          Buf[1] = u8(((((v1 >> 3) & 8) | (v1 & 0x7)) << 4) |
                   (((v1 & 0x80) | ((v1 << 1) & 0x70)) >> 4));
          Buf[2] = u8(((((v2 >> 3) & 8) | (v2 & 0x7)) << 4) |
                   (((v2 & 0x80) | ((v2 << 1) & 0x70)) >> 4));
          Buf[3] = u8(((((v3 >> 3) & 8) | (v3 & 0x7)) << 4) |
                   (((v3 & 0x80) | ((v3 << 1) & 0x70)) >> 4));
         
          src++;
          Buf += 4;
      }
   }

}

#define ConvPal16cAtm2 ConvPal16cAtm1

typedef void (*TSaver)(FILE *File, u8 *ds);

void main_scrshot()
{
   char fname[FILENAME_MAX];
   static unsigned sshot = 0;
   static const char *Format[] = { "scr", "bmp", "png" };
   static const TSaver Saver[] = { SaveBmp, SavePng };

   const char *Ext = Format[conf.scrshot];

   if (conf.scrshot == 0 &&
       (
        ((conf.mem_model == MM_ATM710 || conf.mem_model == MM_ATM3) && ((comp.pFF77 & 7) == 0)) ||
        ((conf.mem_model == MM_ATM450) && (((comp.aFE >> 5) & 3) == 0))
       )
      )
   {
       Ext = Format[1];
   }

   snprintf(fname, _countof(fname), "%s\\%s_%06u.%s", conf.scrshot_dir,
       temp.LastSnapName[0] ? temp.LastSnapName : "sshot", sshot, Ext);
   fname[FILENAME_MAX-1] = 0;

   FILE *fileShot = fopen(fname, "wb");
   if (!fileShot)
      return;

   if (conf.scrshot == 0)
   {
      switch(conf.mem_model)
      {
      case MM_ATM710:
      case MM_ATM3:
          {
              switch(comp.pFF77 & 7)
              {
              case 0: // EGA 320x200 16c
                  {
                      u8 *Buf = (u8 *)malloc(320*200/2);
                      ConvPal16cAtm2(Buf);
                      SaveBmp16c(fileShot, Buf);
                      free(Buf);
                  }
              break;
              case 3:
                  goto standard_zx_mode;
              }
          }
      break;

      case MM_ATM450:
          switch((comp.aFE >> 5) & 3)
          {
          case 0: // EGA 320x200 16c
              {
                  u8 *Buf = (u8 *)malloc(320*200/2);
                  ConvPal16cAtm1(Buf);
                  SaveBmp16c(fileShot, Buf);
                  free(Buf);
              }
          break;

          case 3:
              goto standard_zx_mode;
          }
      break;

      default:
standard_zx_mode:;
          fwrite(temp.base, 1, 6912, fileShot);
      }
   }
   else
   {
      unsigned dx = temp.ox * temp.obpp / 8;
      unsigned char *scrbuf_unaligned = (unsigned char*)malloc(dx * temp.oy + CACHE_LINE);
      unsigned char *scrbuf = (unsigned char*)align_by(scrbuf_unaligned, CACHE_LINE);
      memset(scrbuf, 0, dx * temp.oy);
      renders[conf.render].func(scrbuf, dx); // render to memory buffer (PAL8, YUY2, RGB15, RGB16, RGB32)

      u8 *ds = (u8 *)malloc(((temp.ox * 3 + 3) & ~3U) * temp.oy);
      ConvBgr24(ds, scrbuf, int(dx));

      Saver[conf.scrshot - 1](fileShot, ds);

      free(ds);
      free(scrbuf_unaligned);
   }
   fclose(fileShot);
   sprintf(statusline, "saving %s", strrchr(fname, '\\') + 1);
   statcnt = 30;
   sshot++;
}
/*
static void VideoFrameSaver()
{
   char fname[FILENAME_MAX];
   static unsigned FrameNum = 0;
   static const char *Format[] = { "scr", "bmp", "png" };
   static const TSaver Saver[] = { SaveBmp, SavePng };

   sprintf(fname, "video%06u.%s", FrameNum, Format[conf.scrshot]);
   addpath(fname);

   FILE *fileShot = fopen(fname, "wb");
   if (!fileShot)
      return;

   if (conf.scrshot == 0)
   {
      fwrite(temp.base, 1, 6912, fileShot);
   }
   else
   {
       unsigned dx = temp.ox * temp.obpp / 8;
       unsigned char *scrbuf_unaligned = (unsigned char*)malloc(dx * temp.oy + CACHE_LINE);
       unsigned char *scrbuf = (unsigned char*)align_by(scrbuf_unaligned, CACHE_LINE);
       memset(scrbuf, 0, dx * temp.oy);
       renders[conf.render].func(scrbuf, dx); // render to memory buffer (PAL8, YUY2, RGB15, RGB16, RGB32)

       u8 *ds = (u8 *)malloc(((temp.ox * 3 + 3) & ~3) * temp.oy);
       ConvBgr24(ds, scrbuf, dx);

       Saver[conf.scrshot - 1](fileShot, ds);

       free(ds);
       free(scrbuf_unaligned);
   }
   fclose(fileShot);
   FrameNum++;
}

static void VideoNullSaver()
{

}

TVideoSaver VideoSaver = VideoNullSaver;

void main_savevideo()
{
   static bool StartSave = false;

   if(!StartSave)
   {
       sprintf(statusline, "start saving video");
       StartSave = true;
       VideoSaver = VideoFrameSaver;
   }
   else
   {
       sprintf(statusline, "stop saving video");
       StartSave = false;
       VideoSaver = VideoNullSaver;
   }

   statcnt = 30;
}
*/