5

I'm playing with emulator and file format programming again.

One of the common game snapshot formats for the Sinclair ZX Spectrum is the .sna format.

I know it comes in a 48k format and a newer 128k format with extra fields.
The 48k format is very simple and just has a 27-byte header before a dump of the 48k of RAM. As such it's common to assume any file with the .sna suffix will likely be a Spectrum snapshot if its length is exactly 49179 bytes.

I'm not familiar with the 128k .sna format but it has an extra three byte footer and then a dump of the remaining RAM banks, also making for a fixed file size of exactly 131103 bytes.

I found 16k versions mentioned in only one place. I can't find this stated anywhere else and nor can I find any 16k files:

".sna" files are NMI Snapshots originally made on real 48k Spectrums. This is the best supported format by any emulator, but it has some draw-backs: First, it can only save a ZX Spectrum 48k snapshot. (16k snapshots are supported by zxsp, but are very rarely used.)

Anyway I'd like to filter .sna files based on length. I know three lengths are valid. But if there is also another valid length for specifically .sna snapshot files for 16k Spectrums that I'll support that too.

But I'd like to know if it really exists or not. I'll accept proof such as the documentation of any emulator specifically mentioning it, or a .sna file found in the wild on a retro game site, etc.

I'm fully aware that all 16k games can also be saved as 48k, 49179-byte .sna files, but for this question I'm not concerned with those. They'll already work fine with my code of course.

3
  • 2
    It is also possible (though rare) to encounter 48k .SNA snapshots that are 49280 bytes long -- if they had been generated or processed on a Spectrum +3, then the length would have been rounded up to the nearest multiple of 128 bytes.
    – john_e
    Commented Aug 28, 2022 at 17:04
  • @john_e Would these be ones produced by the actual real hardware Snapshot devices we had back in the day? Commented Aug 29, 2022 at 2:24
  • @john_e Oh OK I found it. It's an artefact of +3DOS that rounds up the file sizes, not a snapshot device used on a +3: groups.google.com/g/comp.sys.sinclair/c/MIOD3UQC_-c/m/… Commented Aug 29, 2022 at 2:43

2 Answers 2

5

IIRC *.sna fileformat originated from FDC/FDD peripherials I have this one:

which was a FDC+FDD for 48K ZXS using MDOS ... there was no 16K or 128K support. The 128K support was added years later but I never heard of 16K...

If it helps here are save/load routines (C++/VCL) from my emulator (I wrote years ago):

//---------------------------------------------------------------------------
void ZXSpectrum::load_sna(AnsiString name)
    {
    BYTE dat[27];
    int hnd,siz;
    reset();
    hnd=FileOpen(name,fmOpenRead);
    if (hnd<0) return;
    siz=FileSeek(hnd,0,2);
        FileSeek(hnd,0,0);
    if (siz== 49179) set_model(_ZXSpectrum_model_48K);
    if (siz==131103) set_model(_ZXSpectrum_model_128K);
    if (siz<27) { FileClose(hnd); return; }
    FileRead(hnd,dat,27);
    int adr=0;
    cpu.reg.r8.i=dat[adr]; adr++;
    cpu.reg.r8._l=dat[adr]; adr++;
    cpu.reg.r8._h=dat[adr]; adr++;
    cpu.reg.r8._e=dat[adr]; adr++;
    cpu.reg.r8._d=dat[adr]; adr++;
    cpu.reg.r8._c=dat[adr]; adr++;
    cpu.reg.r8._b=dat[adr]; adr++;
    cpu.reg.r8._f=dat[adr]; adr++;
    cpu.reg.r8._a=dat[adr]; adr++;
    cpu.reg.r8.l=dat[adr]; adr++;
    cpu.reg.r8.h=dat[adr]; adr++;
    cpu.reg.r8.e=dat[adr]; adr++;
    cpu.reg.r8.d=dat[adr]; adr++;
    cpu.reg.r8.c=dat[adr]; adr++;
    cpu.reg.r8.b=dat[adr]; adr++;
    cpu.reg.r8.iyl=dat[adr]; adr++;
    cpu.reg.r8.iyh=dat[adr]; adr++;
    cpu.reg.r8.ixl=dat[adr]; adr++;
    cpu.reg.r8.ixh=dat[adr]; adr++;
    cpu.iff2=0; if (BYTE(dat[adr]&4)!=0) cpu.iff2=1; adr++;
    cpu.reg.r8.r=dat[adr]; adr++;
    cpu.reg.r8.f=dat[adr]; adr++;
    cpu.reg.r8.a=dat[adr]; adr++;
    cpu.reg.r8.spl=dat[adr]; adr++;
    cpu.reg.r8.sph=dat[adr]; adr++;
    cpu.im=dat[adr]; adr++;
    port_wr(254,dat[adr]); adr++;
    if (siz== 49179)
        {
        load_48k(hnd,16384, 48*1024);

        union { BYTE db[2]; WORD dw; } ubw;     //retn
        ubw.db[0]=mem_rd(cpu.reg.r16.sp);
        cpu.reg.r16.sp++;
        ubw.db[1]=mem_rd(cpu.reg.r16.sp);
        cpu.reg.r16.sp++;
        cpu.reg.r16.pc=ubw.dw;
        cpu.iff1=cpu.iff2;

        enable_bank_switching=false;
        }
    if (siz==131103)
        {
        int i,n;
        BYTE q;
        load_128k(hnd,5,16384);
        load_128k(hnd,2,16384);
        FileSeek(hnd,49179,0);
        FileRead(hnd,&cpu.reg.r16.pc,2);
        FileRead(hnd,&q,1); port_wr(0x7FFD,q); n=rambank;
        FileRead(hnd,&q,1); // if (q) rom=TRDOS
        FileSeek(hnd,32795,0);
        load_128k(hnd,n,16384);
        FileSeek(hnd,49183,0);
        for (i=0;i<8;i++)
            {
            if (i==n) continue;
            if (i==5) continue;
            if (i==2) continue;
            load_128k(hnd,i,16384);
            }
        cpu.iff1=cpu.iff2;
        }
    FileClose(hnd);
    }
//---------------------------------------------------------------------------
void ZXSpectrum::save_sna(AnsiString name)
    {
    BYTE origstackL,origstackH;
    BYTE dat[27];
    int hnd,siz;
    hnd=FileCreate(name);
    if (hnd<0) return;
    if ((model==_ZXSpectrum_model_48K)
      ||(model==_ZXSpectrum_model_RAM64K)
      ||(model==_ZXSpectrum_model_GAMA_87)
      ||(model==_ZXSpectrum_model_GAMA_88)
      ||(model==_ZXSpectrum_model_GAMA_89)
      ||(model==_ZXSpectrum_model_M)
      ||(model==_ZXSpectrum_model_KOMPAKT))
        {
        siz= 49179;
        union { BYTE db[2]; WORD dw; } ubw;     //(retn)^-1
        ubw.dw=cpu.reg.r16.pc;  // change stack to return from interrupt to start adress (PC)
        cpu.reg.r16.sp--;
        origstackH=mem_rd(cpu.reg.r16.sp);
        mem_wr(cpu.reg.r16.sp,ubw.db[1]);
        cpu.reg.r16.sp--;
        origstackL=mem_rd(cpu.reg.r16.sp);
        mem_wr(cpu.reg.r16.sp,ubw.db[0]);
        }
    if ((model==_ZXSpectrum_model_128K)
      ||(model==_ZXSpectrum_model_128K_2)
      ||(model==_ZXSpectrum_model_128K_2A)
      ||(model==_ZXSpectrum_model_128K_3))
        {
        siz=131103;
        }

    int adr=0;
    dat[adr]=cpu.reg.r8.i    ; adr++;
    dat[adr]=cpu.reg.r8._l   ; adr++;
    dat[adr]=cpu.reg.r8._h   ; adr++;
    dat[adr]=cpu.reg.r8._e   ; adr++;
    dat[adr]=cpu.reg.r8._d   ; adr++;
    dat[adr]=cpu.reg.r8._c   ; adr++;
    dat[adr]=cpu.reg.r8._b   ; adr++;
    dat[adr]=cpu.reg.r8._f   ; adr++;
    dat[adr]=cpu.reg.r8._a   ; adr++;
    dat[adr]=cpu.reg.r8.l    ; adr++;
    dat[adr]=cpu.reg.r8.h    ; adr++;
    dat[adr]=cpu.reg.r8.e    ; adr++;
    dat[adr]=cpu.reg.r8.d    ; adr++;
    dat[adr]=cpu.reg.r8.c    ; adr++;
    dat[adr]=cpu.reg.r8.b    ; adr++;
    dat[adr]=cpu.reg.r8.iyl  ; adr++;
    dat[adr]=cpu.reg.r8.iyh  ; adr++;
    dat[adr]=cpu.reg.r8.ixl  ; adr++;
    dat[adr]=cpu.reg.r8.ixh  ; adr++;
    if (cpu.iff2) dat[adr]|=4; adr++;
    dat[adr]=cpu.reg.r8.r    ; adr++;
    dat[adr]=cpu.reg.r8.f    ; adr++;
    dat[adr]=cpu.reg.r8.a    ; adr++;
    dat[adr]=cpu.reg.r8.spl  ; adr++;
    dat[adr]=cpu.reg.r8.sph  ; adr++;
    dat[adr]=cpu.im          ; adr++;
    dat[adr]=port00FE        ; adr++;
    FileWrite(hnd,dat,27);
    if (siz== 49179)
        {
        save_48k(hnd,16384, 48*1024);
        mem_wr(cpu.reg.r16.sp,origstackL);      // restore stack to original state
        cpu.reg.r16.sp++;
        mem_wr(cpu.reg.r16.sp,origstackH);
        cpu.reg.r16.sp++;
        }
    if (siz==131103)
        {
        int i,n;
        BYTE q;
        save_128k(hnd,5,16384);
        save_128k(hnd,2,16384);
        save_128k(hnd,rambank,16384);
        FileWrite(hnd,&cpu.reg.r16.pc,2);
        FileWrite(hnd,&port7FFD,1);
        q=0;                 // if (q) rom=TRDOS
        FileWrite(hnd,&q,1);
        for (i=0;i<8;i++)
            {
            if (i==rambank) continue;
            if (i==5) continue;
            if (i==2) continue;
            save_128k(hnd,i,16384);
            }
        }
    FileClose(hnd);
    }
//---------------------------------------------------------------------------
3
  • I've found one site that mentions in passing that 16k ones are very rare but with no details and nothing to substantiate it. If I find that mention again I'll include it in the question. Commented Aug 30, 2022 at 12:44
  • 4
    If it adds anything: SNA originated with the Mirage Microdriver, an independent peripheral with no storage solution of its own that could save and load snapshots either to/from tape or Microdrive. It was then appropriated by things like disk interfaces.
    – Tommy
    Commented Aug 30, 2022 at 15:02
  • 2
    @Tommy: Yes I was thinking it came from one of those "action replay" type devices that was called "Snapshot" but I had only vague memories of those devices. Then coincidentally the new episode of the Spectrum Show came out detaining the whole history of those devices for the Speccy! Commented Aug 31, 2022 at 11:01
1

Both the 16 KB RAM and the 48 KB RAM version of the ZX Spectrum was released in 1982. The 16k snapshots probably came from the 16 KB RAM version of the ZX Spectrum as original source. Probably from a Multiface One device used to backup the game originally.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .