/************************************************************************/ /* atr2unix.c */ /* */ /* Preston Crow */ /* Public Domain */ /* */ /* Extract files from an Atari DOS or MyDOS .ATR file */ /* */ /* Version History */ /* 5 Jun 95 Version 1.0 Preston Crow */ /* Initial public release */ /* 20 Dec 95 Version 1.1 Chad Wagner */ /* Ported to MS-DOS machines */ /* 10 Feb 98 Version 1.2 Preston Crow */ /* Expanded 256-byte sector support */ /* 2 Jan 22 Version 1.3 Preston Crow */ /* Enhanced debugging */ /************************************************************************/ /************************************************************************/ /* Portability macros */ /* 1 Jun 95 crow@cs.dartmouth.edu (Preston Crow) */ /************************************************************************/ #if defined(__MSDOS) || defined(__MSDOS__) || defined(_MSDOS) || \ defined(_MSDOS_) #define MSDOS /* icky, icky, icky! */ #endif /************************************************************************/ /* Include files */ /************************************************************************/ #include #include #include #include #include #include #ifdef MSDOS #include #include #else #include #endif /************************************************************************/ /* Macros and Constants */ /************************************************************************/ #define ATRHEAD 16 #define USAGE "atr2unix [-dlmr-] atarifile.atr\n" \ " Flags:\n" \ "\t-l Convert filenames to lower case\n" \ "\t-m MyDOS format disk image\n" \ "\t-- Next argument is not a flag\n" \ "\t-d debugging\n" \ "\t-r={sector} Use non-standard root directory number\n" \ "\t-f Fake run; do not create any files\n" #ifndef SEEK_SET #define SEEK_SET 0 /* May be missing from stdio.h */ #endif #define SEEK(n) (ddshortinit?SEEK1(n):SEEK2(n)) #define SEEK1(n) (ATRHEAD + ((n<4)?((n-1)*128):(3*128+(n-4)*secsize))) #define SEEK2(n) (ATRHEAD + ((n-1)*secsize)) /************************************************************************/ /* Data types */ /************************************************************************/ struct atari_dirent { unsigned char flag; /* set bits: 7->deleted 6->in use 5->locked 0->write open */ unsigned char countlo; /* Number of sectors in file */ unsigned char counthi; unsigned char startlo; /* First sector in file */ unsigned char starthi; char namelo[8]; char namehi[3]; }; struct atr_head { unsigned char h0; /* 0x96 */ unsigned char h1; /* 0x02 */ unsigned char seccountlo; unsigned char seccounthi; unsigned char secsizelo; unsigned char secsizehi; unsigned char hiseccountlo; unsigned char hiseccounthi; unsigned char unused[8]; }; /************************************************************************/ /* Function Prototypes */ /************************************************************************/ void read_dir(FILE *in,int sector); void read_file(char *name,FILE *in,FILE *out,int sector,int count,int filenum); /************************************************************************/ /* Global variables */ /************************************************************************/ int ddshortinit=0; /* True indicates double density with first 3 sectors 128 bytes */ int secsize,seccount; int mydos=0; int lowcase=0; int debug=0; int fake=0; /************************************************************************/ /* main() */ /* Process command line */ /* Open input .ATR file */ /* Interpret .ATR header */ /************************************************************************/ int main(int argc,char *argv[]) { FILE *in; struct atr_head head; int root=361; --argc; ++argv; /* Skip program name */ /* Process flags */ while (argc) { int done=0; if (**argv=='-') { ++*argv; while(**argv) { switch(**argv) { case 'm': /* MyDos disk */ mydos=1; break; case '-': /* Last option */ done=1; break; case 'l': /* strlwr names */ lowcase=1; break; case 'f': /* fake */ fake=1; break; case 'd': /* debugging */ debug=1; break; case 'r': /* root directory sector */ ++*argv; while ( **argv && !isdigit(**argv) ) ++*argv; root=atoi(*argv); while ( argv[0][1] ) ++*argv; break; default: fprintf(stderr,USAGE); exit(1); } ++*argv; } --argc; ++argv; } else break; if (done) break; } if (!argc) { fprintf(stderr,USAGE); exit(1); } in=fopen(*argv,"rb"); if (!in) { fprintf(stderr,"Unable to open %s\n%s",*argv,USAGE); exit(1); } --argc; ++argv; if (argc) { if (chdir(*argv)) { fprintf(stderr,"Unable to change to directory: %s\n%s",*argv,USAGE); exit(1); } } fread(&head,sizeof(head),1,in); if ( head.h0 != 0x96 || head.h1 != 0x02 ) { if ( debug ) printf("File does not have ATR signature\n"); return 1; } secsize=head.secsizelo+256*head.secsizehi; seccount=head.seccountlo+256*head.seccounthi; if ( debug ) printf("ATR image: %d sectors, %d bytes each\n",seccount,secsize); { struct stat buf; size_t expected; fstat(fileno(in),&buf); if (((buf.st_size-ATRHEAD)%256)==128) ddshortinit=1; if (debug) { if (ddshortinit && secsize==256) printf("DD, but first 3 sectors SD\n"); else if (secsize==256) printf("DD, including first 3 sectors\n"); } expected = sizeof(head) + seccount * secsize - ((secsize - 128) * 3 * ddshortinit); if ( (size_t)buf.st_size != expected ) { if ( debug ) { int seccount_real; printf("File size wrong; expected %u bytes, observed %u bytes\n",(unsigned int)expected,(unsigned int)buf.st_size); seccount_real = (buf.st_size - sizeof(head)) / secsize; if ( ddshortinit ) { if ( (size_t)buf.st_size <= sizeof(head) + 3 * 128 ) seccount_real = (buf.st_size - sizeof(head)) / 128; else seccount_real = 3 + (buf.st_size - sizeof(head) - 3*128)/secsize; } printf("Sectors expected: %d, observed: %d\n",seccount,seccount_real); } } } read_dir(in,root); return(0); } /************************************************************************/ /* display_entry() */ /* Display the contents of one directory entry for debugging */ /************************************************************************/ void display_entry(int i,struct atari_dirent *f) { struct atari_dirent empty; memset(&empty,0,sizeof(empty)); if ( memcmp(&empty,f,sizeof(empty)) == 0 ) { printf("%2d: [entry is all zeros]\n",i); return; } unsigned count = f->countlo + 256 * f->counthi; unsigned start = f->startlo + 256 * f->starthi; printf("%2d: %4d %4d %c%c%c%c%c%c%c%c.%c%c%c\n",i,count,start, f->namelo[0],f->namelo[1],f->namelo[2],f->namelo[3],f->namelo[4],f->namelo[5],f->namelo[6],f->namelo[7], f->namehi[0],f->namehi[1],f->namehi[2]); } /************************************************************************/ /* read_dir() */ /* Read the entries in a directory */ /* Call read_file() for files, read_dir() for subdirectories */ /************************************************************************/ void read_dir(FILE *in,int sector) { int i,j,k; struct atari_dirent f; FILE *out; char name[13]; if ( debug ) printf("Parsing directory sector %d\n",sector); for(i=0;i<64;++i) { fseek(in,(long)SEEK(sector)+i*sizeof(f)+(secsize-128)*(i/8),SEEK_SET); fread(&f,sizeof(f),1,in); if ( debug ) display_entry(i,&f); if ( fake ) continue; if (!f.flag) /* No more entries */ { if ( debug ) printf("Directory entry %d: zero indicates end of entries\n",i); return; } if (f.flag&128) /* Deleted file */ { if ( debug ) printf("Directory entry %d: deleted flag\n",i); continue; } for(j=0;j<8;++j) { name[j]=f.namelo[j]; if (name[j]==' ') break; } name[j]='.'; ++j; for(k=0;k<3;++k,++j) { name[j]=f.namehi[k]; if (name[j]==' ') break; } name[j]=0; if (name[j-1]=='.') name[j-1]=0; if(lowcase) for(j=0;name[j];++j) name[j]=tolower(name[j]); if (f.flag ==0x47 ) { /* Seems to work */ printf("Warning: File %s has flag bit 1 set--file ignored\n",name); continue; } if (mydos && f.flag&16) { /* Subdirectory */ if (debug) printf("subdir %s (sec %d);\n",name,f.startlo+256*f.starthi); #ifdef MSDOS mkdir(name); #else mkdir(name,0777); #endif chdir(name); read_dir(in,f.startlo+256*f.starthi); chdir(".."); } else { out=fopen(name,"wb"); if (!out) { fprintf(stderr,"Unable to create file: %s\n",name); exit(2); } if (debug) printf("readfile %s (sec %d,count %d,flags %x);\n",name,f.startlo+256*f.starthi,f.countlo+256*f.counthi,f.flag); read_file(name,in,out,f.startlo+256*f.starthi,f.countlo+256*f.counthi,i); if (f.flag&32) { /* Make locked files read-only */ #ifdef MSDOS chmod(name,S_IREAD); #else mode_t um; um=umask(022); umask(um); chmod(name,0444 & ~um); #endif } } } } /************************************************************************/ /* read_file() */ /* Trace through the sector chain. */ /* Complications: Are the file numbers or high bits on the sector */ /* number? */ /* What about the last block code for 256-byte sectors? */ /************************************************************************/ void read_file(char *name,FILE *in,FILE *out,int sector,int count,int filenum) { unsigned char buf[256]; buf[secsize-1]=0; while(count) { if (sector<1) { fprintf(stderr,"Corrupted file (invalid sector %d): %s\n",sector,name); return; } if (buf[secsize-1]&128 && secsize==128) { fprintf(stderr,"Corrupted file (unexpected EOF): %s\n",name); return; } if (fseek(in,(long)SEEK(sector),SEEK_SET)) { fprintf(stderr,"Corrupted file (next sector %d): %s\n",sector,name); return; } fread(buf,secsize,1,in); fwrite(buf,buf[secsize-1],1,out); if (mydos) { sector=buf[secsize-2]+buf[secsize-3]*256; } else { /* DOS 2.0 */ sector=buf[secsize-2]+(3&buf[secsize-3])*256; if (buf[secsize-3]>>2 != filenum) { fprintf(stderr,"Corrupted file (167: file number mismatch): %s\n",name); return; } } --count; } if (!(buf[secsize-1]&128) && secsize==128 && sector) { fprintf(stderr,"Corrupted file (expected EOF, code %d, next sector %d): %s\n",buf[secsize-1],sector,name); return; } fclose(out); }