/*===========================================================================
 * Project: ATasm: atari cross assembler
 * File: state.c
 *
 * Contains code for reading and saving machine state
 * This is based on the state file as defined in Atari800-0.9.8g,
 * the file specification may change as time goes on.
 *
 * This code is largely expirimental, but has been tested successfully with
 * Atari800Win2.5c and Atari800-0.9.8g
 *===========================================================================
 * Created: 12/19/98 mws  (I/O code based off of source from Atari800-0.9.8g)
 *
 * Modifications:  
 *
 *==========================================================================*/
#include <stdio.h>
#include <stdlib.h>
#include "symbol.h"

#ifdef ZLIB_CAPABLE

#include "zlib.h"

#define GZOPEN( X, Y )		gzopen( X, Y )
#define GZCLOSE( X )		gzclose( X )
#define GZREAD( X, Y, Z )	gzread( X, Y, Z )
#define GZWRITE( X, Y, Z )	gzwrite( X, Y, Z )
#else	/* ZLIB_CAPABLE */
#define GZOPEN( X, Y )		fopen( X, Y )
#define GZCLOSE( X )		fclose( X )
#define GZREAD( X, Y, Z )	fread( Y, Z, 1, X )
#define GZWRITE( X, Y, Z )	fwrite( Y, Z, 1, X )
#define gzFile FILE
#endif	/* ZLIB_CAPABLE */

/* Some Emulator specific defines... */
#define UBYTE unsigned char
#define UWORD unsigned short int
#define SAVE_VERSION_NUMBER 2

gzFile *StateIn, *StateOut;

typedef enum {
        TV_PAL,
        TV_NTSC
} TVmode;

typedef enum {
        Atari,
        AtariXL,
        AtariXE,
        Atari320XE,
        Atari5200
} Machine;

#define RAM 0
#define ROM 1
#define HARDWARE 2

/* Work space for saving/loading state files */
TVmode tv_mode;
Machine machine;
int os, pil_on, default_tv_mode, default_system;
int mach_xlxe;
UBYTE Antic4;
UWORD CPU2;

UBYTE *Antic1,*CPU1,*memory, *attrib, *atari_basic, *atarixl_os;
UBYTE *atarixe_memory, *under_atari_basic, *under_atarixl_os;
UWORD *Antic2;
int *Antic3;

/*==========================================================================*/
/* Value is memory location of data, num is number of type to save */
void SaveUBYTE(UBYTE *data, int num) {
  int result;
  
  /* Assumption is that UBYTE = 8bits and the pointer passed in refers
     directly to the active bits if in a padded location. If not (unlikely)
     you'll have to redefine this to save appropriately for cross-platform
     compatibility */
  result = GZWRITE( StateOut, data, num );
  if (result == 0) {
    fprintf(stderr,"Error updating state file.\n");
    exit(-1);
  }
}
/*==========================================================================*/
void ReadUBYTE(UBYTE *data, int num) {
  int result;

  result = GZREAD( StateIn, data, num );
  if (result == 0) {
    fprintf(stderr,"Error updating state file.\n");;
    exit(-1);
  }
}
/*==========================================================================*/
/* Value is memory location of data, num is number of type to save */
void SaveUWORD( UWORD *data, int num ) {
  /* UWORDS are saved as 16bits, regardless of the size on this particular
     platform. Each byte of the UWORD will be pushed out individually in 
     LSB order. The shifts here and in the read routines will work for both
     LSB and MSB architectures. */
  while( num > 0 ) {
    UWORD temp;
    UBYTE byte;
    int	result;
    
    temp = *data;
    byte = temp & 0xff;
    result = GZWRITE( StateOut, &byte, 1 );
    if( result == 0 ) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }

    temp >>= 8;
    byte = temp & 0xff;
    result = GZWRITE( StateOut, &byte, 1 );
    if( result == 0 )
      {
	fprintf(stderr,"Error updating state file.\n");;
	num = 0;
	continue;
      }
    num--;
    data++;
  }
}
/*==========================================================================*/
/* Value is memory location of data, num is number of type to save */
void ReadUWORD( UWORD *data, int num ) {  
  while(num >0) {
    UBYTE	byte1, byte2;
    int	result;
    
    result = GZREAD( StateIn, &byte1, 1 );
    if( result == 0 ) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }

    result = GZREAD( StateIn, &byte2, 1 );
    if( result == 0 ) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }
    *data = (byte2 << 8) | byte1;    
    num--;
    data++;
  }
}
/*==========================================================================*/
void SaveINT( int *data, int num ) {
  unsigned char signbit = 0;

  if( *data < 0 )
    signbit = 0x80;
  
  /* INTs are always saved as 32bits (4 bytes) in the file. They can
     be any size on the platform however. The sign bit is clobbered
     into the fourth byte saved for each int; on read it will be
     extended out to its proper position for the native INT size */
  while( num > 0 ) {
    unsigned int	temp;
    UBYTE	byte;
    int result;
    
    temp = (unsigned int)*data;
    
    byte = temp & 0xff;
    result = GZWRITE( StateOut, &byte, 1 );
    if (result == 0) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;			
    } 
    
    temp >>= 8;
    byte = temp & 0xff;
    result = GZWRITE( StateOut, &byte, 1 );
    if( result == 0 ) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }
    
    temp >>= 8;
    byte = temp & 0xff;
    result = GZWRITE( StateOut, &byte, 1 );
    if( result == 0 ) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }
    
    temp >>= 8;
    byte = temp & 0xff;
    /* Possible sign bit is always saved on fourth byte */
    byte &= signbit;
    result = GZWRITE( StateOut, &byte, 1 );
    if( result == 0 ) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }
    
    num--;
    data++;
  }
}
/*==========================================================================*/
void ReadINT( int *data, int num ) {
  unsigned char signbit = 0;
    
  while (num>0) {
    int	temp;
    UBYTE byte1, byte2, byte3, byte4;
    int result;
    
    result = GZREAD(StateIn, &byte1, 1);
    if (result==0) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;			
    } 
    
    result=GZREAD(StateIn, &byte2, 1);
    if (result==0) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }
    
    result=GZREAD(StateIn, &byte3, 1);
    if(result == 0) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }
    
    result=GZREAD(StateIn, &byte4, 1);
    if(result==0) {
      fprintf(stderr,"Error updating state file.\n");;
      num = 0;
      continue;
    }
    
    signbit = byte4 & 0x80;
    byte4 &= 0x7f;
    
    temp = (byte4 << 24) | (byte3 << 16) | (byte2 << 8) | byte1;
    if( signbit )
      temp = -temp;
    *data = temp;
    
    num--;
    data++;
  }
}
/*==========================================================================*/
void MainStateRead() {
  UBYTE temp;
  
  ReadUBYTE(&temp, 1);
  if(!temp)
    tv_mode = TV_PAL;
  else
    tv_mode = TV_NTSC;
  
  mach_xlxe = 0;
  ReadUBYTE(&temp,1);
  switch(temp) {
  case 0:
    machine = Atari;
    break;    
  case 1:
    machine = AtariXL;
    mach_xlxe = 1;
    break;    
  case 2:
    machine = AtariXE;
    mach_xlxe = 1;
    break;    
  case 3:
    machine = Atari320XE;
    mach_xlxe = 1;
    break;    
  case 4:
    machine = Atari5200;
    break;    
  default:
    machine = AtariXL;
    fprintf(stderr, "Warning: Bad machine type in state save, defaulting to XL" );
    break;
  }
  ReadINT(&os,1);
  ReadINT(&pil_on,1);
  ReadINT(&default_tv_mode,1);
  ReadINT(&default_system,1);
}
/*==========================================================================*/
void MainStateSave() {
  UBYTE temp;
  
  /* Possibly some compilers would handle an enumerated type differently,
     so convert these into unsigned bytes and save them out that way */
  if (tv_mode==TV_PAL)
    temp = 0;
  else
    temp = 1;
  
  SaveUBYTE(&temp, 1);
  
  if( machine == Atari )
    temp = 0;
  if( machine == AtariXL )
    temp = 1;
  if( machine == AtariXE )
    temp = 2;
  if( machine == Atari320XE )
    temp = 3;
  if( machine == Atari5200 )
    temp = 4;
  
  SaveUBYTE(&temp, 1);  
  SaveINT(&os, 1);
  SaveINT(&pil_on, 1);
  SaveINT(&default_tv_mode, 1);
  SaveINT(&default_system, 1);
}
/*==========================================================================*/
void AnticStateRead(void) {
  Antic1=(UBYTE *)malloc(sizeof(UBYTE)*1855);
  Antic2=(UWORD *)malloc(sizeof(UWORD)*37);
  Antic3=(int *)malloc(sizeof(int)*41);
  ReadUBYTE(Antic1,1855);
  ReadUWORD(Antic2,37);
  ReadINT(Antic3,41);
  ReadUBYTE(&Antic4,1);
}
/*==========================================================================*/
void AnticStateSave(void) {  
  SaveUBYTE(Antic1,1855);
  SaveUWORD(Antic2,37);
  SaveINT(Antic3,41);
  SaveUBYTE(&Antic4,1);
  free(Antic1);
  free(Antic2);
  free(Antic3);
}
/*==========================================================================*/
void CpuStateSave(UBYTE SaveVerbose) {
  SaveUBYTE(CPU1,6);
  free(CPU1);
  
  SaveUBYTE(&memory[0],65536);
  SaveUBYTE(&attrib[0],65536);
  free(memory);
  free(attrib);
  
  if (mach_xlxe) {
    if(SaveVerbose!=0) {
      SaveUBYTE(&atari_basic[0],8192);
      free(atari_basic);
    }
    SaveUBYTE(&under_atari_basic[0],8192);
    free(under_atari_basic);
    
    if(SaveVerbose!=0) {
      SaveUBYTE(&atarixl_os[0],16384);
      free(atarixl_os);
    }
    SaveUBYTE( &under_atarixl_os[0],16384);
    free(under_atarixl_os);
  }
  
  if (machine==Atari320XE||machine==AtariXE) {
    SaveUBYTE(&atarixe_memory[0],278528);
    free(atarixe_memory);
  }
  SaveUWORD(&CPU2,1);
}
/*==========================================================================*/
void CpuStateRead(UBYTE SaveVerbose) {
  CPU1=(UBYTE *)malloc(sizeof(UBYTE)*6);
  atari_basic=under_atari_basic=atarixl_os=
    under_atarixl_os=atarixe_memory=NULL;
  
  memory=(UBYTE *)malloc(sizeof(UBYTE)*65536);
  attrib=(UBYTE *)malloc(sizeof(UBYTE)*65536);
  
  ReadUBYTE(CPU1,6);
  ReadUBYTE(&memory[0],65536);
  ReadUBYTE(&attrib[0],65536);
  
  if (mach_xlxe) {
    if (SaveVerbose!=0) {
      atari_basic=(UBYTE *)malloc(sizeof(UBYTE)*8192);
      ReadUBYTE(&atari_basic[0], 8192);
    }
    under_atari_basic=(UBYTE *)malloc(sizeof(UBYTE)*8192);
    ReadUBYTE(&under_atari_basic[0], 8192);
    
    if (SaveVerbose!=0) {
      atarixl_os=(UBYTE *)malloc(sizeof(UBYTE)*16384);
      ReadUBYTE(&atarixl_os[0],16384);
    }
    under_atarixl_os=(UBYTE *)malloc(sizeof(UBYTE)*16384);
    ReadUBYTE( &under_atarixl_os[0],16384);
  }
  
  if (machine==Atari320XE||machine==AtariXE) {
    atarixe_memory=(UBYTE *)malloc(sizeof(UBYTE)*278528);
    ReadUBYTE(&atarixe_memory[0], 278528);
  }
  ReadUWORD(&CPU2,1);
}
/*==========================================================================*/
int FileCopy(gzFile *in, gzFile *out) {
  int result;
  UBYTE *buf;

  buf=(UBYTE *)malloc(16384);
  
  do {
    result=GZREAD(in,buf,16384);
    GZWRITE(out,buf,result);
  } while(result==16384);
  
  free(buf);
  return 1;
}
/*==========================================================================*/
int Update_Mem() {
  unsigned char *scan, *end;
  int a,b,i,walk;
  char *tp[]={"ROM","HARDWARE"};			 
  scan=bitmap;
  end=scan+8096;
  walk=0;

  while(scan!=end) {
    a=*scan;
    b=128;
    for(i=0;i<8;i++) {
      if (a&b) {
	memory[walk]=memmap[walk];
	if (attrib[walk]!=RAM) {
	  fprintf(stderr,"Warning: Compiling to %s at location %.4X\n",
		  tp[attrib[walk]-1],walk);
	}
      }
      b=b>>1;
      walk++;
    }
    scan++;
  }
  return 1;
}
/*==========================================================================*/
int save_state(char *fin, char *fout) {
  int result, result1;
  char	header_string[9];
  UBYTE	StateVersion  = 0; /* The version of the save file */
  UBYTE	SaveVerbose   = 0; /* Verbose mode means save basic, OS if patched */
  
  StateIn=GZOPEN(fin,"rb");
  if(!StateIn) {
    fprintf(stderr,"Could not open template state file '%s'.",fin);
    return 0;
  }
  
  result=GZREAD(StateIn,header_string,8);
  header_string[8]=0;
  if(strcmp(header_string,"ATARI800")) {
    fprintf(stderr, "'%s' is not an Atari800 state file.",fin);
    result=GZCLOSE(StateIn);
    return 0;
  }
  
  result=GZREAD(StateIn,&StateVersion,1);
  result1=GZREAD(StateIn,&SaveVerbose,1);
  if(result==0||result1==0) {
    fprintf(stderr, "Couldn't read from state file '%s'.",fin);
    result = GZCLOSE(StateIn);
    return 0;
  }
  
  if( StateVersion > SAVE_VERSION_NUMBER || StateVersion == 1 ) {
    fprintf(stderr, "'%s' is an incompatible state file version.\n",fin);
    result=GZCLOSE(StateIn);
    return 0;
  }
  
  MainStateRead();
  AnticStateRead();
  CpuStateRead(SaveVerbose);

  StateOut=GZOPEN(fout,"wb");
  if(!StateOut) {
    fprintf(stderr, "Could not open '%s' for state save.\n",fout);
    return 0;
  }
  result = GZWRITE(StateOut,"ATARI800",8);
  if(!result) {
    fprintf(stderr, "Could not save to '%s'\n",fout);
    return 0;
  }
  
  SaveUBYTE(&StateVersion, 1);
  SaveUBYTE(&SaveVerbose, 1);
    
  MainStateSave();
  AnticStateSave();
  Update_Mem();
  CpuStateSave(SaveVerbose);  
  FileCopy(StateIn,StateOut);
    
  result=GZCLOSE(StateOut);
  result=GZCLOSE(StateIn);
  
  fprintf(stderr,"Created Atari800 state file '%s'\n",fout);
  return 1;
}
/*==========================================================================*/
