/*==========================================================================
 * Project: ATasm: atari cross assembler
 * File: dimage.c
 *
 * original xfd disk routines based on xfd_tools package by 
 * Ivo van Poorten (P) 1995
 *==========================================================================
 * Started: 07/28/97
 * Modified: 01/11/99  moved from EnvisionPC to ATasm
 *                     added delete_file, recognition of DD density disks
 ===========================================================================*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define lowb(x)     ((x)&0x00ff)
#define highb(x)    (((x)>>8)&0x00ff)

unsigned char secbuf[256];
unsigned char VTOCsec[128];

int secsize,dsksize;
/*=========================================================================*/
int is_xfd(FILE *image)
{
  long lof;
                                          
  secsize=128;
  dsksize=720;
  fseek(image,0L,SEEK_END);
  lof=ftell(image); 
  rewind(image);
  secsize=(lof>(1040*128))?256:128;
  dsksize=lof/secsize;
  
  if (lof==92160)
    return 1;
  else if (lof==184320) 
    fprintf(stderr,"ATasm can currently only handle single density .XFDs\n");
  return 0;
}
/*=========================================================================*/
void convertfname(char *in, char *out)
{
 int x,y;
 char *look;
 
 look=in+strlen(in);
 while ((*look!='\\')&&(*look!='/')&&(look!=in)) {
   look--;
 }
 in=look;
 
 for(x=0; x<11; x++)
   out[x]=32;
 out[11]=0;

 x=0;
 y=*in++;

 while ((y!=0)&&(y!='.')) {
   out[x]=toupper(y);
   if(x!=8) x++;
   y=*in++;
 }
 out[8]=32;
 if (y!=0) {
   x=8;
   y=*in++;
   while((x<11)&&(y)&&(y!='.')) {
	 out[x]=toupper(y);
	 x++;
	 y=*in++;
   }
 }
}
/*=========================================================================*/
int readsec(FILE *image, int nr)
{
 if ((nr>dsksize)||(nr<1))
   return 0;
 fseek(image,(long)(nr-1)*secsize,SEEK_SET);
 fread(secbuf,secsize,1,image);
 return 1;
}
/*=========================================================================*/
int writesec(FILE *image, int nr)
{
 if ((nr>dsksize)||(nr<1))
   return 0;

 fseek(image,(long)(nr-1)*secsize,SEEK_SET);
 fwrite(secbuf,secsize,1,image);
 return 1;
}
/*=========================================================================*/
void readVTOC(FILE *image)
{
 fseek(image,(long)(360-1)*secsize,SEEK_SET);
 fread(VTOCsec,secsize,1,image);
}
/*=========================================================================*/
void writeVTOC(FILE *image)
{
 fseek(image,(long)(360-1)*secsize,SEEK_SET);
 fwrite(VTOCsec,secsize,1,image);
}
/*=========================================================================*/
int find_free_sec(FILE *image)
{
 int x,y;
 int freesec;

 readVTOC(image);

 for(x=10; x<100; x++)
   if (VTOCsec[x]!=0) break;

 freesec=(x-10)*8;

 y=VTOCsec[x];

 if (y&0x80) freesec+=0;
 else if (y&0x40) freesec+=1;
 else if (y&0x20) freesec+=2;
 else if (y&0x10) freesec+=3;
 else if (y&0x08) freesec+=4;
 else if (y&0x04) freesec+=5;
 else if (y&0x02) freesec+=6;
 else if (y&0x01) freesec+=7;

 if (y>=720) y=0;

 return freesec;
}
/*=========================================================================*/
void freesec(FILE *image, int nr) {
 int byte,bit,secsfree;

 readVTOC(image);

 byte=nr/8;
 bit=nr%8;

 VTOCsec[10+byte]|=(0x80>>bit);
 secsfree=VTOCsec[3]+VTOCsec[4]*256;
 secsfree=secsfree+1;
 VTOCsec[3]=lowb(secsfree);
 VTOCsec[4]=highb(secsfree);

 writeVTOC(image);
}
/*=========================================================================*/
void marksec(FILE *image, int nr, int *secsfree)
{
 int byte,bit;

 readVTOC(image);

 byte=nr/8;
 bit=nr%8;

 VTOCsec[10+byte]&=((0x80>>bit)^0xff);
 
 *secsfree=*secsfree-1;
 VTOCsec[3]=lowb(*secsfree);
 VTOCsec[4]=highb(*secsfree);

 writeVTOC(image);
}
/*=========================================================================*/
int scandir(FILE *image, char *filename)
{
 int secnum,cnt,status,length,startsec;
 int endofdir;
 char fname[12];

 endofdir=0;
 secnum=361;
 startsec=-1;
 
 while(!endofdir) {
   readsec(image,secnum);

   for(cnt=0; cnt<8; cnt++) {
     status=secbuf[cnt*16];
     length=secbuf[cnt*16+1]+256*secbuf[cnt*16+2];
     
     if (!status) {
       endofdir=1;
       break;
     }
     
     if (!(status&0x80)) {
       memcpy(fname,&secbuf[cnt*16+5],11);
       fname[11]=0;
       if (strncmp(filename,fname,11)==0)
	 startsec=secbuf[cnt*16+3]+secbuf[cnt*16+4]*256;
     }
   }   
   secnum++;
   if (secnum>368)
     endofdir=1;
 }
 return startsec; 
}
/*=========================================================================*/
void writedirentry(FILE *image, char *file, int startsec, int len, int entry)
{
 int qwe,asd;
 int x;

 qwe=entry/8;
 asd=entry%8;

 readsec(image,361+qwe);

 secbuf[asd*16+0]=0x42;
 secbuf[asd*16+1]=lowb(len/125+1);
 secbuf[asd*16+2]=highb(len/125+1);
 secbuf[asd*16+3]=lowb(startsec);
 secbuf[asd*16+4]=highb(startsec);

 for(x=0;x<12;x++)
   secbuf[asd*16+5+x]=file[x];

 writesec(image,361+qwe);
}
/*=========================================================================*/
int find_newentry(FILE *image) {
 int secnum,cnt,status,entrynum;
 int endofdir;

 endofdir=0;
 secnum=361;
 entrynum=-1;

 while(!endofdir) {
   readsec(image,secnum);

   for(cnt=0;cnt<8;cnt++) {
	 status=secbuf[cnt*16];

	 if ((status==0)||(status&0x80)) {
   entrynum=(secnum-361)*8+cnt;
   endofdir=1;
   break;
	  }

   }
   secnum++;
   if (secnum>368) endofdir=1;
 }
 return entrynum;
}
/*=========================================================================*/
int delete_file(FILE *image, char *filename) {
 int i,secnum,cnt,status,length,startsec;
 int endofdir;
 char fname[12];

 endofdir=0;
 secnum=361;
 startsec=-1;
 
 while(!endofdir) {
   readsec(image,secnum);
   for(cnt=0; cnt<8; cnt++) {
     status=secbuf[cnt*16];
     length=secbuf[cnt*16+1]+256*secbuf[cnt*16+2];
     
     if (!status) {
       endofdir=1;
       break;
     }     
     if (!(status&0x80)) {
       memcpy(fname,&secbuf[cnt*16+5],11);
       fname[11]=0;
       if (!strncmp(filename,fname,11)) {
	 startsec=secbuf[cnt*16+3]+secbuf[cnt*16+4]*256;
	 for(i=0;i<16;i++) {
  	   secbuf[cnt*16+i]=0;
  	 }
  	 writesec(image,secnum);
       }
     }
   }   
   secnum++;
   if (secnum>368)
     endofdir=1;
 }
 if (startsec>=0) {
   i=secnum=startsec;
   while(i) {
     readsec(image,secnum);
     i=secbuf[126]+((secbuf[125]&0x03)<<8);      
     memset(secbuf,0,256);
     writesec(image,secnum); 
     freesec(image,secnum); 
     secnum=i;
   }
   return 1;
 }
 return 0;
}
/*=========================================================================*/
int write_xfd_file(char *image, char *file) {
 FILE *xfd,*in;
 int startsec,cursec,nextsec;
 int entrynum, secsfree;
 int qwe,x,max;
 long lof;
 char fname[12];
 unsigned char data[128];
 
 xfd=fopen(image,"rb+");
 if (!xfd) {                      
   fprintf(stderr,"Cannot open .XFD image");
   return -1;
 }
 if (!is_xfd(xfd)) {
   fprintf(stderr,"'%s' is not a valid .XFD image",image);
   return -2;
 }
 in=fopen(file,"rb");
 if (!in) {
   fprintf(stderr,"Unable to open input binary.\n");
   return -1;
 }
 fseek(in,0L,SEEK_END);
 lof=ftell(in); 
 rewind(in);
 max=lof;
 convertfname(file,fname);
 startsec=scandir(xfd,fname);
 if (startsec>=0)  {
   fprintf(stderr,"*Warning* Removing existing file on .XFD image.\n");
   delete_file(xfd,fname);
 }

 /* check free sectors */
 readVTOC(xfd);
 secsfree=VTOCsec[3]+VTOCsec[4]*256;

 /* input too big??? */
 if ((secsfree*125)<max) {
   fclose(xfd);
   fprintf(stderr,"Not enough room on .XFD image.\n");
   return -1;
 }

  /* find place in directory */
 entrynum=find_newentry(xfd); 
 if (entrynum==-1) {
   fprintf(stderr,"Not enough room on .XFD image.\n");
   return -1;
 }

 /* find first free sector (=startsec) */
 startsec=find_free_sec(xfd);

 /* write directory entry */
 writedirentry(xfd,fname,startsec,max,entrynum);

 /* write file! */
 qwe=max%125;
 for(x=0;x<(max/125); x++) {
   cursec=find_free_sec(xfd);
   marksec(xfd,cursec,&secsfree);
   nextsec=find_free_sec(xfd);
   if (in) {
     fread(data,125,1,in);
     memcpy(secbuf,data,125);      
   } else memcpy(secbuf,data+x*125,125);
   
   secbuf[125]=(entrynum<<2)+((highb(nextsec))&0x03);
   secbuf[126]=lowb(nextsec);
   secbuf[127]=125;
   writesec(xfd,cursec);
 }
 if (qwe) {
   x=max/125;
   cursec=find_free_sec(xfd);
   marksec(xfd,cursec,&secsfree);
   if (in) {
     fread(data,125,1,in);
     memcpy(secbuf,data,qwe);      
   } else memcpy(secbuf,data+x*125,qwe);
   secbuf[125]=(entrynum<<2);
   secbuf[126]=0;
   secbuf[127]=qwe;
   writesec(xfd,cursec);
 } else {
   secbuf[125]=(entrynum<<2);
   secbuf[126]=0;
   writesec(xfd,cursec);
 }
 
 /* close file and exit */
 fclose(xfd);
 fprintf(stderr,"Binary file '%s' saved to .XFD image '%s'\n",file,image);
 return 0;
}
