/* * * Copyrights are held by the respective authors listed below. * Licensed for distribution under the GNU Public License version 2.0 or later. * * You need to use sio2pc cable to run this * * * Compilation: * gcc -W -Wall -o sio2linux sio2linux-2.0.c * * Currently, this does not support the 'format' or 'verify' SIO * commands. * * TO-DO: * * * Add support for missing drive commands. * * * Add a watch/copy mode where it watches I/O to a real drive and * uses that to create a copy. Hence, all you need to do to copy * a disk is read every sector from the Atari. * This might not be possible. It seems that the read responses from * my 1050 aren't visible on the Linux serial port. This may be * a limitation in my sio2pc cable. * * * Add a keyboard user interface to add/remove/swap disks during * run time. * * * Enhance support for dynamic disk images that are live access to the * Linux file system. It could use My-DOS style subdirectories. * Support for creating new files could be added. * Support for more than one file open at a time could be added. * * * Add support for cassette transfers, allowing BASIC programming with * fast I/O, but without the memory cost of DOS. * * Version History: * * Version 2.0 19 Aug 2005 Preston Crow * * Renamed to sio2linux. * Clean up to read the commands properly. * Add features: Create blank images, quiet mode, skip image, specify serial device * * Version 1.4 22 Mar 1998 Preston Crow * * Added support for read-only images. Any image that can't * be opened for read/write will instead be opened read-only. * Also, if a '-r' option appears before the image, it will * be opened read-only. * * Cleaned up a few things. The system speed is now determined * dynamically, though it still uses the Pentium cycle counter. * A status request will now send write-protect information. * Added a short usage blurb for when no options are specified. * * It should be slightly more tollerant of other devices active * on the SIO bus, but it could still confuse it. * * Version 1.3 20 Mar 1998 Preston Crow * * The status command responds correctly for DD and ED images. * * This version is fully functional. Improvements beyond this * release will focus on adding a nice user interface, and * making it better at recognizing commands, so as to interact * safely with real SIO devices. A possible copy-protection * mode may be nice, where the program watches all the activity * on D1: while the program loads off of a real device, recording * all data, timing, and status information. Whether yet another * file format should be used, or some existing format, is an open * matter. * * Version 1.2 17 Mar 1998 Preston Crow * * I've added in support for checking the ring status after reading * a byte to determine if it is part of a command. However, as this * requires a separate system call, it may be too slow. If that proves * to be the case, it may be necessary to resort to direct assembly- * language access to the port (though this would eliminate compatibility * with non-Intel Linux systems). That seems to not work well; many * commands aren't recognized, at least when using the system call to * check the ring status, so I've implemented a rolling buffer that will * assume it has a command when the last five bytes have a valid checksum. * That may cause problems if a non-SIO2PC drive is used. * * It seems to work great for reading SD disk images right now. * I haven't tested writing, but I suspect it will also work. * It has problems when doing DD disk images. I suspect the * problem has to do with the status command returning hard-coded * information. * * The debugging output should be easier to read now, and should always * be printed in the same order as the data is transmitted or received. * * Version 1.1 Preston Crow * Lots of disk management added. * In theory, it should handle almost any ATR or XFD disk image * file now, both reading and writing. * Unfortunately, it is quite broken right now. I suspect timing * problems, though it may be problems with incorrect ACK/COMPLETE * signals or some sort of control signal separate from the data. * * Version 1.0 Pavel Machek * * This is Floppy EMULator - it will turn your linux machine into * atari 800's floppy drive. Copyright 1997 Pavel Machek * distribute under GPL. */ /* * Standard include files */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif /* * Data structures */ 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]; }; enum seekcodes { xfd, /* This is a xfd (raw sd) image */ atr, /* This is a regular ATR image */ atrdd3, /* This is a dd ATR image, including the first 3 sectors */ direct /* This is a directory pretending to be a disk image */ }; struct atari_dirent { unsigned char flag; /* set bits: 7->deleted 6->normal file 5->locked 4->MyDOS subdirectory 3->??? 2->??? \ one for >720, one for >1024 ? 1->??? / all for MyDOS? 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]; }; /* * Prototypes */ static void err(const char *s); static void raw(int fd); static void ack(unsigned char c); static void senddata(int disk,int sec); static void sendrawdata(unsigned char *buf,int size); static void recvdata(int disk,int sec); static int get_atari(void); void getcmd(unsigned char *buf); static void loaddisk(char *path,int disk); static void decode(unsigned char *buf); void write_atr_head(int disk); void snoopread(int disk,int sec); int afnamecpy(char *an,const char *n); /* * Macros */ #define SEEK(n,i) (seekcode[i]==xfd)?SEEK0(n,i):((seekcode[i]=atr)?SEEK1(n,i):SEEK2(n,i)) #define SEEK0(n,i) ((n-1)*secsize[i]) #define SEEK1(n,i) (ATRHEAD + ((n<4)?((n-1)*128):(3*128+(n-4)*secsize[i]))) #define SEEK2(n,i) (ATRHEAD + ((n-1)*secsize[i])) #define ATRHEAD 16 #define MAXDISKS 8 /* * Default Timings from SIO2PC: * Time before first ACK: 85us * Time before second ACK: 1020us * Time before COMPLETE: 255us * Time after COMPLETE: 425us */ #define ACK1 85 #define ACK2 1020 #define COMPLETE1 255 #define COMPLETE2 425 /* * Global variables */ static int secsize[MAXDISKS]; static int seccount[MAXDISKS]; static enum seekcodes seekcode[MAXDISKS]; static int diskfd[MAXDISKS]; static int ro[MAXDISKS]; static int active[MAXDISKS]; static int fakewrite[MAXDISKS]; static int blank[MAXDISKS]; static DIR *dir[MAXDISKS]; /* NULL if not a directory */ static int filefd[MAXDISKS]; /* fd of open file in directory */ static int afileno[MAXDISKS]; /* afileno (0-63) of open file */ static int secoff[MAXDISKS]; /* sector offset of open file */ static char *dirname[MAXDISKS]; static int atari; /* fd of the serial port hooked up to the SIO2PC cable */ /* Config options */ int snoop; /* If true, display detailed data on unmapped drives */ int quiet; /* If true, don't display per-I/O data */ char *serial; /* * main() * * Read the command line, open the disk images, connect to the Atari, * and listen for commands. * * This never terminates. */ int main(int argc,char *argv[]) { int i; int numdisks=0; /* * Parse command-line options */ #define USAGE \ "Options:\n" \ " -r next parameter is read-only image\n"\ " -f next parameter is image, fake accepting writes (no change to image)\n"\ " -s next parameter is serial device to use (default: /dev/ttyS0)\n"\ " -b next parameter is blank single-density image to create\n" \ " -B next parameter is blank double-density image to create\n" \ " -x skip next drive image\n" \ " disk image to mount as next disk (D1 through D8 in order)\n" \ " directory to mount as next disk\n" if (argc==1) { fprintf(stderr,"SIO2Linux: The Atari floppy drive emulator\n"); fprintf(stderr,USAGE); fprintf(stderr,"Example:\n %s boot.atr -x -b d3.atr\n(D1: is boot.atr, D2: is ignored, D3: is a new blank image)\n",argv[0]); exit(1); } setvbuf(stdout,NULL,_IONBF,0); setvbuf(stderr,NULL,_IONBF,0); for(i=0;i=4+64*5 && i!=720 && (i<360 || i>368)) { buf[byte]|=bit; } } sendrawdata(buf,size); return; } if ( sec>=361 && sec<=368 ) { /* Create directory */ rewinddir(dir[disk]); readdir(dir[disk]); readdir(dir[disk]); for(i=0;i<8*(sec-361);++i) { if (!readdir(dir[disk])) { sendrawdata(buf,size); return; } } for(i=0;i<8;++i) { int start; int count; int fn; struct stat sb; struct atari_dirent ad; de=readdir(dir[disk]); fn=(sec-361)*8+i; start=4+fn*5; if ( de ) { memset(&ad,0,sizeof(ad)); strcpy(path,dirname[disk]); strcat(path,"/"); strcat(path,de->d_name); r=stat(path,&sb); count=(sb.st_size+125)/125; ad.countlo=count%256; ad.counthi=count/256; ad.startlo=start%256; ad.starthi=start/256; ad.flag=0x80; /* If unable to convert name, deleted file */ if ( !r && afnamecpy(ad.namelo,de->d_name) ) { ad.flag=0x42; } memcpy(buf+16*i,&ad,sizeof(ad)); } else break; } sendrawdata(buf,size); return; } if ( sec>=4 && sec<4+64*5 ) { /* send file data */ int fn; int off; off_t seekto; fn=(sec-4)/5; off=sec-4-fn*5; if ( off ) { /* This file had better be open already */ if ( fn != afileno[disk] ) { if ( !quiet ) printf("-no data-"); memset(buf,0,size); sendrawdata(buf,size); return; } seekto=(secoff[disk]+off)*125; } else { if ( afileno[disk] ) close(afileno[disk]); secoff[disk]=0; afileno[disk]=fn; rewinddir(dir[disk]); readdir(dir[disk]); readdir(dir[disk]); for(i=0;i<=fn;++i) de=readdir(dir[disk]); strcpy(path,dirname[disk]); strcat(path,"/"); strcat(path,de->d_name); filefd[disk]=open(path,O_RDONLY); seekto=0; } r=lseek(filefd[disk],seekto,SEEK_SET); if ( r<0 ) { if ( !quiet ) printf("-lseek errno %d-",errno); memset(buf,0,size); sendrawdata(buf,size); return; } r=read(filefd[disk],buf,125); buf[125]=fn<<2; buf[126]=sec+1; if ( off==4 ) { buf[126] -= 4; secoff[disk]+=4; } buf[127]=r; if ( r<125 ) { buf[126]=0; } sendrawdata(buf,size); return; } memset(buf,0,size); sendrawdata(buf,size); return; } static void senddata(int disk,int sec) { char buf[256]; int size; off_t check,to; int i; if ( dir[disk] ) { senddirdata(disk,sec); return; } size=secsize[disk]; if (sec<=3) size=128; if ( sec > seccount[disk] ) { memset(buf,0,size); } else { to=SEEK(sec,disk); check=lseek(diskfd[disk],to,SEEK_SET); if (check!=to) { if (errno) perror("lseek"); fprintf(stderr,"lseek failed, went to %ld instead of %ld\n",check,to); exit(1); } /* printf("-%d-",check); */ i=read(diskfd[disk],buf,size); if (i!=size) { if (i<0) perror("read"); fprintf(stderr,"Incomplete read\n"); exit(1); } } sendrawdata(buf,size); } static void sendrawdata(unsigned char *buf,int size) { int i, sum = 0; int c=0; for( i=0; i>8); } write( atari, &sum, 1 ); if (c!=1) { if (errno) perror("write"); fprintf(stderr,"write failed\n"); exit(1); } if ( !quiet) printf("-%d bytes+sum-",size); } static void recvdata(int disk,int sec) { int i, sum = 0; unsigned char mybuf[ 2048 ]; int size; size=secsize[disk]; if (sec<=3) size=128; for( i=0; i> 8); } read(atari,&i,1); if ((i & 0xff) != (sum & 0xff) && !quiet) printf( "[BAD SUM]" ); else if (fakewrite[disk]) { if ( !quiet) printf("[write discarded]"); } else { lseek(diskfd[disk],SEEK(sec,disk),SEEK_SET); i=write(diskfd[disk],mybuf,size); if (i!=size) if ( !quiet) printf("[write failed: %d]",i); if ( blank[disk] && sec>seccount[disk] ) { seccount[disk]=sec; write_atr_head(disk); } } if ( !quiet) printf("-%d bytes+sum recvd-",size); } void snoopread(int disk,int sec) { int i, sum = 0; unsigned char mybuf[ 2048 ]; int size; int r; size=secsize[disk]; if (sec<=3 || size<128 ) size=128; r=read(atari,&i,1); if ( r!=1 ) { fprintf(stderr,"snoop read failed\n"); return; } if ( !quiet ) printf("[%c]",i); if ( i!='A' ) { return; } r=read(atari,&i,1); if ( r!=1 ) { fprintf(stderr,"snoop read failed\n"); return; } if ( !quiet ) printf("[%c]",i); if ( i!='C' ) { return; } for( i=0; i> 8); } read(atari,&i,1); if ((i & 0xff) != (sum & 0xff)) { if (!quiet) printf( "[BAD SUM]" ); return; } } void write_atr_head(int disk) { struct atr_head buf; int paragraphs; lseek(diskfd[disk],0,SEEK_SET); memset(&buf,0,sizeof(buf)); buf.h0=0x96; buf.h1=0x02; paragraphs=seccount[disk]*(secsize[disk]/16) - (secsize[disk]-128)/16; buf.seccountlo=(paragraphs&0xff); buf.seccounthi=((paragraphs>>8)&0xff); buf.hiseccountlo=((paragraphs>>16)&0xff); buf.hiseccounthi=((paragraphs>>24)&0xff); buf.secsizelo=(secsize[disk]&0xff); buf.secsizehi=((secsize[disk]>>8)&0xff); write(diskfd[disk],&buf,16); } /* * get_atari() * * Open the serial device and return the file descriptor. * It assumes that it is /dev/ttyS0 unless there's a symlink * from /dev/mouse to that, in which case /dev/ttyS1 is used. */ static int get_atari(void) { int fd; struct stat stat_mouse,stat_tty; if (stat("/dev/mouse",&stat_mouse)==0) { stat(serial,&stat_tty); if (stat_mouse.st_rdev==stat_tty.st_rdev) { printf("/dev/ttyS0 is the mouse, using ttyS1\n"); serial="/dev/ttyS1"; } } fd = open(serial,O_RDWR); if (fd<0) { fprintf(stderr,"Can't open %s\n",serial); exit(1); } raw(fd); /* Set up port parameters */ return(fd); } /* * getcmd() * * Read one 5-byte command * * The Atari will activate the command line while sending * the 5-byte command. */ void getcmd(unsigned char *buf) { int i,r; while (1) { /* * Wait for a command */ ioctl(atari,TIOCMIWAIT,TIOCM_RNG); /* Wait for a command */ tcflush(atari,TCIFLUSH); /* Clear out pre-command garbage */ /* * Read 5 bytes * This should take 2.6ms. *** FIXME *** set an alarm */ i=0; while (i<5) { r=read(atari,buf+i,5-i); if (r>0) i+=r; else { perror("read from serial port failed"); fprintf(stderr,"read returned %d\n",r); exit(1); } } /* * Compute the checksum */ { int sum=0; for(i=0;i<4;++i) { sum+=buf[i]; sum = (sum&0xff) + (sum>>8); } if (buf[4]==sum) { return; /* Match; normal return */ } } /* * Error -- bad checksum */ if ( !quiet) printf("%02x %02x %02x %02x %02x Bad checksum\n",buf[0],buf[1],buf[2],buf[3],buf[4]); } } /* * loaddisk() * * Ready a disk image. * The type of file (xfd/atr) is determined by the file size. */ static void loaddisk(char *path,int disk) { int exists=0; if (disk>=MAXDISKS) { fprintf(stderr,"Attempt to load invalid disk number %d\n",disk+1); exit(1); } if ( blank[disk] ) { diskfd[disk]=open(path,O_RDWR,0644); if ( diskfd[disk]>=0 ) { exists=1; } else { diskfd[disk]=open(path,O_RDWR|O_CREAT,0644); seekcode[disk]=atr; } } else { diskfd[disk]=open(path,(ro[disk]||fakewrite[disk])?O_RDONLY:O_RDWR); if (diskfd[disk]<0 && !ro[disk] && !fakewrite[disk]) { if ( errno == EACCES ) { ro[disk]=1; diskfd[disk]=open(path,O_RDONLY); } else if ( errno == EISDIR ) { filefd[disk]= -1; afileno[disk]= -1; dir[disk]=opendir(path); if ( !dir[disk] ) { fprintf(stderr,"Unable to open directory %s; drive %d disabled\n",path,disk); return; } active[disk]=1; secsize[disk]=128; seccount[disk]=720; seekcode[disk]=direct; dirname[disk]=path; printf( "D%d: %s simulated disk (%d %d-byte sectors)\n",disk+1,path,seccount[disk],secsize[disk]); return; } } } if (diskfd[disk]<0) { fprintf(stderr,"Unable to open disk image %s; drive %d disabled\n",path,disk); return; } active[disk]=1; if ( !blank[disk] || exists ) { /* * Determine the file type based on the size */ secsize[disk]=128; { struct stat buf; fstat(diskfd[disk],&buf); seekcode[disk]=atrdd3; if (((buf.st_size-ATRHEAD)%256)==128) seekcode[disk]=atr; if (((buf.st_size)%128)==0) seekcode[disk]=xfd; seccount[disk]=buf.st_size/secsize[disk]; } /* * Read disk geometry */ if (seekcode[disk]!=xfd) { struct atr_head atr; long paragraphs; read(diskfd[disk],&atr,sizeof(atr)); secsize[disk]=atr.secsizelo+256*atr.secsizehi; paragraphs=atr.seccountlo+atr.seccounthi*256+ atr.hiseccountlo*256*256+atr.hiseccounthi*256*256*256; if (secsize[disk]==128) { seccount[disk]=paragraphs/8; } else { paragraphs+=(3*128/16); seccount[disk]=paragraphs/16; } } } else { write_atr_head(disk); } printf( "D%d: %s opened%s (%d %d-byte sectors)\n",disk+1,path,ro[disk]?" read-only":"",seccount[disk],secsize[disk]); } /* * decode() * * Given a command frame (5-bytes), decode it and * do whatever needs to be done. */ static void decode(unsigned char *buf) { int disk = -1, rs = -1, printer = -1; int sec; if ( !quiet) printf( "%02x %02x %02x %02x %02x ",buf[0],buf[1],buf[2],buf[3],buf[4]); switch( buf[0] ) { case 0x31: if ( !quiet) printf( "D1: " ); disk = 0; break; case 0x32: if ( !quiet) printf( "D2: " ); disk = 1; break; case 0x33: if ( !quiet) printf( "D3: " ); disk = 2; break; case 0x34: if ( !quiet) printf( "D4: " ); disk = 3; break; case 0x35: if ( !quiet) printf( "D5: " ); disk = 4; break; case 0x36: if ( !quiet) printf( "D6: " ); disk = 5; break; case 0x37: if ( !quiet) printf( "D7: " ); disk = 6; break; case 0x38: if ( !quiet) printf( "D8: " ); disk = 7; break; case 0x40: if ( !quiet) printf( "P: " ); printer = 0; break; case 0x41: if ( !quiet) printf( "P1: " ); printer = 0; break; case 0x42: if ( !quiet) printf( "P2: " ); printer = 1; break; case 0x43: if ( !quiet) printf( "P3: " ); printer = 2; break; case 0x44: if ( !quiet) printf( "P4: " ); printer = 3; break; case 0x45: if ( !quiet) printf( "P5: " ); printer = 4; break; case 0x46: if ( !quiet) printf( "P6: " ); printer = 5; break; case 0x47: if ( !quiet) printf( "P7: " ); printer = 6; break; case 0x48: if ( !quiet) printf( "P8: " ); printer = 7; break; case 0x50: if ( !quiet) printf( "R1: " ); rs = 0; break; case 0x51: if ( !quiet) printf( "R2: " ); rs = 1; break; case 0x52: if ( !quiet) printf( "R3: " ); rs = 2; break; case 0x53: if ( !quiet) printf( "R4: " ); rs = 3; break; default: if ( !quiet) printf( "???: ignored\n");return; } if (disk>=0&&!active[disk]) { if ( !quiet) printf( "[no image] " ); } if (printer>=0) {if ( !quiet) printf("[Printers not supported]\n"); return; } if (rs>=0) {if ( !quiet) printf("[Serial ports not supported]\n"); return; } sec = buf[2] + 256*buf[3]; switch( buf[1] ) { case 'R': if ( !quiet) printf("read sector %d: ",sec); if ( !active[disk] ) { if ( snoop ) snoopread(disk,sec); break; } usleep(ACK1); ack('A'); usleep(COMPLETE1); ack('C'); usleep(COMPLETE2); senddata(disk,sec); break; case 'W': if ( !quiet) printf("write sector %d: ",sec); if ( !active[disk] ) break; usleep(ACK1); if (ro[disk]) { ack('N'); if ( !quiet) printf("[Read-only image]"); break; } ack('A'); recvdata(disk,sec); ack('A'); ack('C'); break; case 'S': if ( !quiet) printf( "status:" ); if ( !active[disk] ) break; usleep(ACK1); ack('A'); { /* * Bob Woolley wrote on comp.sys.atari.8bit: * * at your end of the process, the bytes are * CMD status, H/W status, Timeout and unused. * CMD is the $2EA value previously * memtioned. Bit 7 indicates an ED disk. Bits * 6 and 5 ($6x) indicate DD. Bit 3 indicates * write protected. Bits 0-2 indicate different * error conditions. H/W is the FDD controller * chip status. Timeout is the device timeout * value for CIO to use if it wants. * * So, I expect you want to send a $60 as the * first byte if you want the OS to think you * are in DD. OK? */ static char status[] = { 0x10, 0x00, 1, 0 }; status[0]=(secsize[disk]==128?0x10:0x60); if (secsize[disk]==128 && seccount[disk]>720) status[0]=0x80; if (ro[disk]) { status[0] |= 8; } else { status[0] &= ~8; } usleep(COMPLETE1); ack('C'); usleep(COMPLETE2); sendrawdata(status,sizeof(status)); } break; case 'P': if ( !quiet) printf("put sector %d: ",sec); if ( !active[disk] ) break; usleep(ACK1); if (ro[disk]) { ack('N'); if ( !quiet) printf("[Read-only image]"); break; } ack('A'); recvdata(disk, sec); ack('A'); ack('C'); break; case 'N': if ( !quiet) printf("815 configuration block read"); if ( !active[disk] ) break; /* We get 19 of these from DOS 2.0 when you hit reset */ usleep(ACK1); ack('A'); ack('C'); { char status[12]; memset(status,0,sizeof(status)); status[0]=1; /* 1 big track */ status[1]=1; /* Why not? */ status[2]=seccount[disk]>>8; status[3]=seccount[disk]&0xff; status[5]=((secsize[disk]==256)?4:0); status[6]=secsize[disk]>>8; status[7]=secsize[disk]&0xff; sendrawdata(status,sizeof(status)); } break; case 'O': if ( !quiet) printf("815 configuration block write (ignored)"); if ( !active[disk] ) break; ack('A'); { int i; char s; int sum=0; for( i=0; i<12; i++ ) { read( atari, &s, 1 ); if ( !quiet) printf(" %02x",s); sum = sum + s; sum = (sum & 0xff) + (sum >> 8); } read(atari,&s,1); if ((s & 0xff) != (sum & 0xff)) if ( !quiet) printf( "[BAD SUM %02x]",sum ); if ( !quiet) printf(" "); } ack('A'); ack('C'); break; case '"': if ( !quiet) printf( "format enhanced " ); if ( !active[disk] ) break; /*** FIXME *** Acknowledge and zero disk image ***/ usleep(ACK1); ack('A'); usleep(COMPLETE1); ack('C'); usleep(COMPLETE2); senddata(disk,99999); break; case '!': if ( !quiet) printf( "format " ); if ( !active[disk] ) break; /*** FIXME *** Acknowledge and zero disk image ***/ usleep(ACK1); ack('A'); usleep(ACK1); ack('C'); break; case 0x20: if ( !quiet) printf( "download " ); if ( !active[disk] ) break; break; case 0x54: if ( !quiet) printf( "readaddr " ); if ( !active[disk] ) break; break; case 0x51: if ( !quiet) printf( "readspin " ); if ( !active[disk] ) break; break; case 0x55: if ( !quiet) printf( "motoron " ); if ( !active[disk] ) break; break; case 0x56: if ( !quiet) printf( "verify " ); if ( !active[disk] ) break; break; default: if ( !quiet) printf( "??? " ); if ( !active[disk] ) break; break; } if ( !quiet) printf( "\n" ); } /* * wait_for_cmd() * * Wait for the ring indicator to specify that a command block is being sent */ void wait_for_cmd(int fd) { int r; r=ioctl(fd,TIOCMIWAIT,TIOCM_RNG); } /************************************************************************/ /* afnamecpy() */ /* Convert a Unix filename to an Atari filename. */ /* Return 0 on failure. */ /************************************************************************/ int afnamecpy(char *an,const char *n) { int i; for(i=0;i<11;++i) an[i]=' '; /* Space fill the Atari name */ an[11]=0; for(i=0;i<8;++i) { if (!*n) return(1); /* Ok */ if (*n=='.') break; /* Extension */ if (*n==':') return(0); /* Illegal name */ if (1) an[i]=toupper(*n); else an[i]= *n; ++n; } if (*n=='.') ++n; for(i=8;i<11;++i) { if (!*n) return(1); /* Ok */ if (*n=='.') return(0); /* Illegal name */ if (*n==':') return(0); /* Illegal name */ if (1) an[i]=toupper(*n); else an[i]= *n; ++n; } if (*n) return(0); /* Extension too long or more than 11 characters */ return(1); }