
/*
 * Copyright (c) Des Herriott 1993, 1994
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the copyright holder not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The copyright holder makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors: Russell Marks, Ian Collier, Des Herriott
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "z80.h"
#include "resource.h"
#include "saveload.h"

#include "auxfuncs.c"

extern void scf(void);
extern void ret(void);
extern void and_a(void);

extern void refresh_screen(void);

extern int ReadSNAFormat(char *);

/*
 * This is Russell Marks' SAVE/LOAD implementation
 */

#ifdef LOAD_SAVE_1
static void
twobytepoke(uns16 addr, uns16 val)
{
	theMemory[addr] = val & 0xff;
	theMemory[addr+1] = (val >> 8);
}


/* hacked-up routine for trapping LOAD and SAVE to redirect them
 * to disk files. Notice Speccy basic only supports 10-char filenames.
 * LOAD looks for (given_filename).sna first, then (given_filename).zxf.
 * The .zxf file is the text "ZXF1" followed by the 17-byte tape header
 * (don't worry, though, renaming files is OK), followed by the data.
 * LOAD only supports Basic and CODE at the moment, and the Basic
 * LINE (to start program at) won't work - it'll load fine, but just
 * dump you at a '0 OK 0:1' prompt.
 *
 * SAVE, however, works fine with number/characters arrays too, in addition
 * to the other two filetypes.
 *
 * *Please* note that LOAD for Basic files is *not fully tested*.
 * (it's also based on a rather half-assed approach, so be careful!)
 * And there is precious little error detection.
 * -rjm.
 */
void
load_or_save(void)
{
	int problem;
	char buf[256],*ptr;
	int c1,c2,tmp;

	problem=0;

	if(*pc == LOAD_ADDR) {
		FILE *in;
		int addr,len,type;

		buf[10]=0;
		strncpy(buf,theMemory+(*ix)+1,10);
		if((ptr=strchr(buf,32))!=NULL)
			*ptr=0;
		if((ptr=strchr(buf,255))!=NULL)
			*ptr=0;

		/* if buf[0]==0 now, someone did LOAD "".
		 * Perhaps this could be used to implement listing of
		 * *.zxf files with types, start, length etc.?
		 * (I don't know enough about programming X to do this nicely.)
		 */

		strcat(buf,".sna");

		if((in=fopen(buf,"rb"))!=NULL) {
			if(ReadSNAFormat(buf)==-1)
			problem=1;
		} else {
			/* try to read .zxf file ("ZXF1" plus Speccy header plus data)
			 * we save (well, had saved) 17 bytes of header like the ROM does
			 * yeah, I know I should check for "ZXF1"... :)
			 */
			if((ptr = strrchr(buf, '.')) == NULL)
				problem=1;
			else {
				*ptr=0;
				strcat(buf,".zxf");
				if((in=fopen(buf,"rb"))==NULL)
					problem=1;
				else {
				/* loads in at address held at offset 0x0D in the header,
				 * i.e. bytes 13-14. length is 11-12
				 * ...but we read start address from copy in memory to cope
				 * with e.g. LOAD "wibble" CODE 32768
				 */
				fseek(in,4,SEEK_SET);
				type=fgetc(in);
				fseek(in,11+4,SEEK_SET);
				c1=fgetc(in); c2=fgetc(in);
				len=c1|(c2<<8);
				/* we use the file's start address value if none specified. */
				c1=fgetc(in); c2=fgetc(in);
				addr=c1|(c2<<8);
				c1=theMemory[(*ix)+13]; c2=theMemory[(*ix)+14];
				if(type != 0 && (c1+c2)!=0)
					addr=c1|(c2<<8);
		
				/* check implied type and type of file match */
				if(type!=theMemory[*ix])
					problem=1;
				else {
					if(type==0) {
						int newvars, line_no;
			
						/* 'addr' is actually the LINE value for basic files */
						line_no = addr;
						/* use value of PROG */
						addr=theMemory[0x5C53]|(theMemory[0x5C54]<<8);
						/* fix the value of VARS */
						c1=fgetc(in); c2=fgetc(in);
						newvars=(c1|(c2<<8))+addr;
						theMemory[0x5C4B]=newvars%256;
						theMemory[0x5C4C]=(newvars>>8);

						/* reset values of ELINE, WORKSP, STKBOT and STKEND
						 * and hack the memory a bit accordingly.
						 * this is less of a kludge than it used to be.
						 */
						theMemory[addr+len]=0x80;
						tmp=addr+len+1;
						twobytepoke(0x5C59,tmp);
						theMemory[tmp++]=0x0D;
						theMemory[tmp++]=0x80;
						twobytepoke(0x5C61,tmp);
						theMemory[tmp++]=0x0D;
						twobytepoke(0x5C63,tmp);
						twobytepoke(0x5C65,tmp);
						/* that *should* be all the Basic stuff fixed. */
		  
						if (line_no >= 10000) {
							PopValue;
							(*pc)=0x1BB0;  /* bit in ROM that does '0 OK' */
						} else {
							twobytepoke(0x5C42, line_no); /* adjust NEWPPC */
							theMemory[0x5C44] = 0;        /* ...and NSPCC */
							theMemory[0x5C3A] = 255;      /* set ERR NR */
							scf(); ret();
						}
					}
					fseek(in,17+4,SEEK_SET);
					/* now load it all in... */
					fread(&(theMemory[0])+addr,1,len,in);
					if(type!=0) {
						scf(); /* worked ok */
						ret(); /* finish up */
					}
				} /* end of 'if types match' */
				fclose(in);
			}
		  }
		}
	} else {
		/* here's the SAVE stuff */
		FILE *out;
		int data_addr;

		/* number on stack is addr of data, IX = addr of header */
		buf[10] = 0;
		strncpy(buf, theMemory+(*ix)+1, 10);
		if((ptr = strchr(buf,32)) != NULL)
		*ptr = 0;
		if((ptr=strchr(buf,255)) != NULL)
		*ptr = 0;

		strcat(buf,".zxf");
		if((out = fopen(buf, "wb")) == NULL)
			problem = 1;
		else {
			data_addr=PopValue;
			fprintf(out,"ZXF1");
			fwrite(theMemory+(*ix),1,17,out);
			c1=theMemory[(*ix)+11]; c2=theMemory[(*ix)+12];
			tmp=c1|(c2<<8);
			fwrite(theMemory+data_addr,1,tmp,out);
			fclose(out);
			scf();
			ret();
		}
	}

	if(problem) {
		and_a();     /* clear carry */
		(*pc)=0x806; /* a bit in ROM that does 'R Tape loading error' */
	}

	/* update all of the screen, in case it was a SCREEN$ loaded...
	 * Yeah suboptimal, but there you go...
	 */
	for(tmp=16384;tmp<22528;tmp++)
	  screen_write((uns16)tmp,(uns8)theMemory[tmp]);

	for(tmp=22528;tmp<23296;tmp++)
	  attr_write((uns16)tmp,(uns8)theMemory[tmp]);

	refresh_screen();
}
#endif


/*
 * This is Ian Collier's SAVE/LOAD implementation
 */

#ifdef LOAD_SAVE_2

#define XZX_MAGIC "ZXF1"

static char xzx_magic[4];

extern char *RequestFilename(char *prompt);

static FILE *in=0,*out=0;

typedef unsigned char byte;
typedef unsigned short word;

static struct header {
   byte type;
   char name[10];
   byte length[2];
   byte start[2];
   byte line[2];
}  prepared={3,"xxxxxxxxxx",{0,0},{0,128},{0,0}};

void loader(){  /* whenever PC gets to 0556 */
   refresh_screen();
   if(*a==0){  /* header */
      /* two header structures used by BASIC; one at IX and one at IX-17. */
      struct header *hd1=(struct header *)(theMemory+*ix-17);
      struct header *hd2=(struct header *)(theMemory+*ix);
      char hdrname[11];
      char *name=hdrname;
      int basic= hd1->type<4 && *ix<theMemory[23653]+256*theMemory[23654];
      int i;    /* the above is a kludgey test to see whether to trust hd1 */

      memcpy(name,hd1->name,10);      /* first attempt at finding a name */
      for(i=9;i>=0&&name[i]==' ';i--);
      name[i+1]=0;
      if(i<0 || name[0]==(char)255)basic=0; /* ignore basic header for LOAD"" */
      if(in){ /* second successive header */
         fclose(in), in=0;
         name=RequestFilename("\007Header rejected - enter another file name");
gotname:
		 if(!name[0]){   /* request was rejected */
            *pc=0x0806;  /* "tape loading error" */
            return;
         }
      }
      else if(!basic){
         name=RequestFilename("\007Enter file name for load");
         if(!name[0]){
            *pc=0x0806;
            return;
         }
      }
      in=fopen(name,"r");
      if(!in){
         fputs("xzx: ",stderr);  /* couldn't open - print message, */
         perror(name);
         name = RequestFilename("\007Enter another file name");
         goto gotname;
      }
      if(fread(xzx_magic,1,sizeof xzx_magic,in)<sizeof xzx_magic ||
         fread((char*)hd2,1,17,in)<17 ||
         memcmp(xzx_magic,XZX_MAGIC,sizeof xzx_magic)){
         /* it isn't an xzx file.  Fill in the header */

         struct stat st;
         rewind(in);
         memcpy((char*)hd2,(char*)&prepared,17);
         if(basic)memcpy(hd2->name,hd1->name,10);
         else {
            for(i=0;i<10 && (hd2->name[i]=name[i]); i++);
            while(i<10)hd2->name[i++]=' ';
         }
         if(fstat(fileno(in),&st)){
            fputs("xzx: fstat:",stderr);
            perror(name);
            st.st_size=0;
         }
         hd2->length[0]=st.st_size%256;
         hd2->length[1]=st.st_size/256;
         if(st.st_size==49179){  /* it's a snapshot */
            fclose(in);
            in=0;
            ReadSNAFormat(name);
            return;
         }
         else if(st.st_size>49152)
            fprintf(stderr,"%s has length %d!\n",name,st.st_size);
      }
      else { /* It is an xzx file.  Kludge the file name. */
         if(basic)memcpy(hd2->name,hd1->name,10);
         else {
            for(i=0;i<10 && (hd2->name[i]=name[i]); i++);
            while(i<10)hd2->name[i++]=' ';
         }
      }
   }
   else { /* data */ 
      int load=Tst(carryFlag);
      if(!in){  /* headerless load */
         char *name=RequestFilename("\007Enter file name for headerless load");
         if(!name[0]){
            *pc=0x0806;
            return;
         }
         in=fopen(name,"r");
         if(!in){
            fputs("xzx: ",stderr);
            perror(name);
            Clr(carryFlag);
            ret();
            return;
         }
      }
      Set(carryFlag);
      while((*de)--){
         int ch=getc(in);
         if(ch==EOF){
            Clr(carryFlag); /* carry reset on error */
            break;
         }
         else if(load) mem_write((*ix)++,ch);
         else if(theMemory[(*ix)++]!=(byte)ch){
            Clr(carryFlag);
            break;
         }
      }
      fclose(in);
      in=0;
   }
   ret();
}

void saver(){  /* whenever PC gets to 04C2 */
   refresh_screen();
   if(*a==0){ /* header */
      struct header *hd=(struct header *)(theMemory+*ix);
      char hdrname[11];
      char newname[14];
      char *name=hdrname;
      int i;
      if(out)fclose(out);/* second successive header.  Not much we can do...*/
      memcpy(name,hd->name,10);
      for(i=9;i>0&&name[i]==' ';i--);
      name[i+1]=0;
      strcpy(newname,name);
      strcat(newname,".bak");
      rename(name,newname);
      out=fopen(name,"w");
      if(!out){
         fputs("xzx: ",stderr);
         perror(name);
         *pc=0x0806;
         return;
      }
      fwrite(XZX_MAGIC,1,sizeof xzx_magic,out);
   }
   else if(!out){        /* headerless save */
      char *name=RequestFilename("\007Enter file name for headerless save");
      if(!name[0]){
         *pc=0x0806;
         return;
      }
      out=fopen(name,"w");
      if(!out){
         fputs("xzx: ",stderr);
         perror(name);
         *pc=0x0806;
         return;
      }
   }
   if(fwrite(theMemory+*ix,1,*de,out)<*de)
      perror("xzx: write error");
   *ix += *de;
   *de=0;
   if(*a!=0)fclose(out),out=0;
   ret();
}
#endif



