diff --git a/FDISK/BOOTDISK2.atr b/FDISK/BOOTDISK2.atr new file mode 100644 index 0000000..4100fde Binary files /dev/null and b/FDISK/BOOTDISK2.atr differ diff --git a/FDISK/test.atr b/FDISK/test.atr new file mode 100644 index 0000000..4507681 Binary files /dev/null and b/FDISK/test.atr differ diff --git a/SRC/atr2unix-1.3.c b/SRC/atr2unix-1.3.c new file mode 100644 index 0000000..d8386e4 --- /dev/null +++ b/SRC/atr2unix-1.3.c @@ -0,0 +1,362 @@ +/************************************************************************/ +/* 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); +} diff --git a/SRC/dcmtoatr-1.4.c b/SRC/dcmtoatr-1.4.c new file mode 100644 index 0000000..0f22bda --- /dev/null +++ b/SRC/dcmtoatr-1.4.c @@ -0,0 +1,476 @@ +/* +** dcmtoatr.c -- written by Chad Wagner +** +** I just wanted to throw in some personal comments, I wanted to thank +** Preston Crow, for his work on portability issues, as well as some of +** the ideas he put into this program, Bob Puff, for his hard work on +** The Disk Communicator, an excellent means for transferring disk images, +** and I feel it is the de facto standard, and should remain as such, and +** Jason Duerstock, for writing DCMTODSK and his documents on the DCM +** format, which did help me resolve some of the things I wasn't aware of. +** I am sure there is a few others. Unfortunately I only got one response +** from Bob Puff, he was attempting to help, but I believe he is very busy +** person, and just didn't have the time. +** +** Revision History: +** 31 May 95 cmwagner@gate.net +** 1 Jun 95 cmwagner@gate.net +** added in some portability macros from dcm.c +** added in read_atari16() function from dcm.c +** wrote write_atari16() function +** did a general clean-up of the code +** 1 Jun 95 crow@cs.dartmouth.edu +** did a clean-up of the code, hopefully resolving any +** portability issues +** 2 Jun 95 cmwagner@gate.net and crow@cs.dartmouth.edu +** Allow for multi-file DCM files, after they've been +** combined into a single file +** 5 Jun 95 cmwagner@gate.net +** Fixed decoding routines to handle double density diskettes +** 26 Jun 95 cmwagner@gate.net +** Added in support for creating DCMTOXFD, default compile is +** for DCMTOATR +** 3 Sep 95 cmwagner@gate.net +** Added in -x switch, which creates an XFD image, instead of the +** default ATR image. +** added in soffset() routine, changed around the SOFFSET define, +** so that xfd images could be created, without compiling a +** different version. +** 26 Jun 95 modification is no longer relevant because of this +** modification. +** wrote some more comments, about what certain functions are doing, +** when they were created, and who wrote them. +** 28 Sep 95 lovegrov@student.umass.edu +** Fixed bug in soffset() function, apparently sector 4 would return +** an offset of 16 for ATR images or 0 for XFD images, which is not +** correct. +*/ + +/* Include files */ +#include +#include + +/* prototypes */ +void decode_C1(void); +void decode_C3(void); +void decode_C4(void); +void decode_C6(void); +void decode_C7(void); +void decode_FA(void); +int read_atari16(FILE *); +void write_atari16(FILE *,int); +int read_offset(FILE *); +void read_sector(FILE *); +void write_sector(FILE *); +long soffset(void); + +/* +** 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 +#ifndef SEEK_SET +#define SEEK_SET 0 /* May be missing from stdio.h */ +#endif + +/* version of this program, as seen in usage message */ +#define VERSION "1.4" + +/* Global variables */ +FILE *fin,*fout; +unsigned int secsize; +unsigned short cursec=0,maxsec=0; +unsigned char createdisk=0,working=0,last=0,density,buf[256],atr=16; +int doprint=1; /* True for diagnostic output */ + +/* +** main() +*/ +int main(int argc,char **argv) +{ + unsigned char archivetype; /* Block type for first block */ + unsigned char blocktype; /* Current block type */ + unsigned char tmp; /* Temporary for read without clobber on eof */ + unsigned char imgin[256],imgout[256]; /* file names */ + unsigned char done=0; /* completion flag */ + char *self; /* program name from argv[0] */ + +#ifdef MSDOS + if ((self = strrchr(argv[0],'\\')) == NULL) +#else + if ((self = strrchr(argv[0],'/')) == NULL) +#endif + { + self = argv[0]; + } + else + self++; /* Skip the slash */ + + --argc;++argv; /* Don't look at the filename anymore */ + /* Process switches */ + if (argc) while (*argv[0]=='-') { + int nomore=0; + + ++argv[0]; /* skip the '-' */ + while(*argv[0]) { + switch(*argv[0]) { + case '-': + nomore=1; + break; + case 'q': + case 'Q': + doprint = !doprint; + break; + case 'x': + case 'X': + atr = 16 - atr; + break; + default: + fprintf(stderr,"Unsupported switch: %c\n",*argv[0]); + fprintf(stderr,"%s "VERSION" by cmwagner@gate.net\n",self); + fprintf(stderr,"%s [-qx] input[.dcm] [output[.atr]]\n",self); + exit(1); + } + ++argv[0]; /* We've processed this flag */ + } + --argc;++argv; /* Done processing these flags */ + if(nomore) break; /* Filename may begin with '-' */ + } + + if (argc<1 || argc>2) { + fprintf(stderr,"%s "VERSION" by cmwagner@gate.net\n",self); + fprintf(stderr,"%s [-qx] input[.dcm] [output[.atr]]\n",self); + exit(1); + } + + strcpy(imgin,argv[0]); + if (strrchr(imgin,'.') == NULL) + strcat(imgin,".dcm"); + + if (argc==2) + strcpy(imgout,argv[1]); + else { + char *p; + + strcpy(imgout,imgin); + if ((p = strrchr(imgout,'.')) != NULL) + *p = 0; + } + if (strrchr(imgout,'.') == NULL) + if (atr) { + strcat(imgout,".atr"); + } else { + strcat(imgout,".xfd"); + } + + if ((fin = fopen(imgin,"rb")) == NULL) { + fprintf(stderr,"I couldn't open \"%s\" for reading.\n",imgin); + exit(1); + } + + archivetype = blocktype = fgetc(fin); + switch(blocktype) { + case 0xF9: + case 0xFA: + break; + default: + fprintf(stderr,"0x%02X is an unknown header block.\n",blocktype); + exit(1); + } + + if ((fout = fopen(imgout,"rb")) != NULL) { + fprintf(stderr,"I can't use \"%s\" for output, it already exists.\n",imgout); + exit(1); + } else { + fout = fopen(imgout,"wb"); + } + + rewind(fin); + + do { + if(doprint) printf("\rCurrent sector: %4u",cursec); + +#ifdef MSDOS + if (kbhit()) { + if (getch() == 27) { + fprintf(stderr,"\nProcessing terminated by user.\n"); + exit(1); + } + } +#endif + if (feof(fin)) { + fflush(stdout); /* Possible buffered I/O confusion fix */ + if ((!last) && (blocktype == 0x45) && (archivetype == 0xF9)) { + fprintf(stderr,"\nMulti-part archive error.\n"); + fprintf(stderr,"To process these files, you must first combine the files into a single file.\n"); +#ifdef MSDOS + fprintf(stderr,"\tCOPY /B file1.dcm+file2.dcm+file3.dcm newfile.dcm\n"); +#else + fprintf(stderr,"\tcat file1.dcm file2.dcm file3.dcm > newfile.dcm\n"); +#endif + } + else { + fprintf(stderr,"\nEOF before end block.\n"); + } + exit(1); + } + + tmp = fgetc(fin); /* blocktype is needed on EOF error--don't corrupt it */ + if (feof(fin)) continue; /* Will abort on the check at the top of the loop */ + blocktype = tmp; + switch(blocktype) { + case 0xF9: + case 0xFA: + /* New block */ + decode_FA(); + break; + case 0x45: + /* End block */ + working=0; + if (last) { + if (doprint) printf("\r%s has been successfully decompressed.\n",imgout); + fclose(fin); + fclose(fout); + done=1; /* Normal exit */ + } + break; + case 0x41: + case 0xC1: + decode_C1(); + break; + case 0x43: + case 0xC3: + decode_C3(); + break; + case 0x44: + case 0xC4: + decode_C4(); + break; + case 0x46: + case 0xC6: + decode_C6(); + break; + case 0x47: + case 0xC7: + decode_C7(); + break; + default: + fprintf(stderr,"\n0x%02X is an unknown block type. File may be " + "corrupt.\n",blocktype); + exit(1); + } /* end case */ + + if ((blocktype != 0x45) && (blocktype != 0xFA) && + (blocktype != 0xF9)) { + if (!(blocktype & 0x80)) { + cursec=read_atari16(fin); + fseek(fout,soffset(),SEEK_SET); + } else { + cursec++; + } + } + } while(!done); /* end do */ + return(0); /* Should never be executed */ +} + +void decode_C1(void) +{ + int secoff,tmpoff,c; + + tmpoff=read_offset(fin); + c=fgetc(fin); + for (secoff=0; secoff> 4); + last=((c & 0x80) >> 7); + switch(density) { + case 0: + maxsec=720; + secsize=128; + break; + case 2: + maxsec=720; + secsize=256; + break; + case 4: + maxsec=1040; + secsize=128; + break; + default: + fprintf(stderr,"\nDensity type is unknown, density type=%u\n",density); + exit(1); + } + + if (createdisk == 0) { + createdisk = 1; + if (atr) { + /* write out atr header */ + /* special code, 0x0296 */ + write_atari16(fout,0x296); + /* image size (low) */ + write_atari16(fout,(short)(((long)maxsec * secsize) >> 4)); + /* sector size */ + write_atari16(fout,secsize); + /* image size (high) */ + write_atari16(fout,(short)(((long)maxsec * secsize) >> 20)); + /* 8 bytes unused */ + write_atari16(fout,0); + write_atari16(fout,0); + write_atari16(fout,0); + write_atari16(fout,0); + } + memset(buf,0,256); + for (cursec=0; cursec + * + * 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 +#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]; +}; + +struct trackformat { + int offset[18]; /* sector offset from start: offset[i]==i*100 if no skew */ + int bad[18]; /* Only set with special SIO2Linux command */ +}; + +struct image { + int secsize; /* 128 or 256 */ + int seccount; /* 720, 1040, or whatever */ + enum seekcodes seekcode;/* Image type */ + int diskfd; /* file descriptor */ + int ro; /* non-zero if read-only */ + int active; /* non-zero if Linux is responding for this disk */ + int fakewrite; /* non-zero if writes are accepted but dropped */ + int blank; /* non-zero if disk can grow as needed */ + /* + * Stuff for directories as virtual disk images + */ + DIR *dir; /* NULL if not a directory */ + int filefd; /* fd of open file in directory */ + int afileno; /* afileno (0-63) of open file */ + int secoff; /* sector offset of open file */ + char *dirname; /* directory name, used to append filenames */ + /* + * Stuff for real disks, so that we can analyze the format + */ + int lastsec; /* last sector read for real disks */ + int prevsec; /* sector before last for real disks */ + struct timeval lasttime;/* time that lastsec[] was read */ + struct trackformat track[40]; /* format information derived from observations */ +}; + +/* + * 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); +int firstgood(int disk,int sec); +void addtiming(int disk,int sec); +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) (disks[disk].seekcode==xfd)?SEEK0(n,i):((disks[i].seekcode=atr)?SEEK1(n,i):SEEK2(n,i)) +#define SEEK0(n,i) ((n-1)*disks[i].secsize) +#define SEEK1(n,i) (ATRHEAD + ((n<4)?((n-1)*128):(3*128+(n-4)*disks[i].secsize))) +#define SEEK2(n,i) (ATRHEAD + ((n-1)*disks[i].secsize)) +#define ATRHEAD 16 +#define MAXDISKS 8 +#define TRACK18(n) (((n)-1)/18) /* track of sector 'n' if 18 sectors per track (0-39) */ +#define OFF18(n) (((n)-1)%18) /* offset of sector 'n' in track (0-17) */ +#define TRACKSTART(n) ((((n)-1)/18)*18+1) +#define RPM(_uspr) (60*1000*1000/_uspr) /* microseconds for one revolution -> RPMs */ +#define RPM3(_uspr) ((int)((60*1000ull*1000ull*1000ull/_uspr)%1000ull)) /* Fractional RPMS to 3 decimal points */ +/* + * Default Timings from SIO2PC: + * Time before first ACK: 85us + * Time before second ACK: 1020us + * Time before COMPLETE: 255us + * Time after COMPLETE: 425us + */ +#define ACK1 2000 /* Atari may wait 650-950us to raise the command line; device permitted 0-16ms */ +#define ACK2 1020 /* 850ms min */ +#define COMPLETE1 500 /* 250 is the min, but add more for transmission delays */ +#define COMPLETE2 425 + +/* + * Global variables + */ +struct image disks[MAXDISKS]; +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 */ +int noring; /* If true, the serial port ring detect doesn't work */ +char *serial; +int uspr=208333; /* microseconds per revolution, default for 288 RPM */ +int speed=19200; /* Baud rate */ + +/* + * 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" \ + " -n no ring detect on serial port (some USB converters)\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); + + memset(disks,0,sizeof(disks)); + 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(disks[disk].dir); + readdir(disks[disk].dir); + readdir(disks[disk].dir); + for(i=0;i<8*(sec-361);++i) { + if (!readdir(disks[disk].dir)) { + 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(disks[disk].dir); + fn=(sec-361)*8+i; + start=4+fn*5; + if ( de ) { + memset(&ad,0,sizeof(ad)); + strcpy(path,disks[disk].dirname); + 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 != disks[disk].afileno ) { + if ( !quiet ) printf("-no data-"); + memset(buf,0,size); + sendrawdata(buf,size); + return; + } + seekto=(disks[disk].secoff+off)*125; + } + else { + if ( disks[disk].afileno ) close(disks[disk].afileno); + disks[disk].secoff=0; + disks[disk].afileno=fn; + rewinddir(disks[disk].dir); + readdir(disks[disk].dir); + readdir(disks[disk].dir); + for(i=0;i<=fn;++i) de=readdir(disks[disk].dir); + strcpy(path,disks[disk].dirname); + strcat(path,"/"); + strcat(path,de->d_name); + disks[disk].filefd=open(path,O_RDONLY); + seekto=0; + } + r=lseek(disks[disk].filefd,seekto,SEEK_SET); + if ( r<0 ) { + if ( !quiet ) printf("-lseek errno %d-",errno); + memset(buf,0,size); + sendrawdata(buf,size); + return; + } + r=read(disks[disk].filefd,buf,125); + buf[125]=fn<<2; + buf[126]=sec+1; + if ( off==4 ) { + buf[126] -= 4; + disks[disk].secoff+=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) +{ + unsigned char buf[256]; + int size; + off_t check,to; + int i; + + if ( disks[disk].dir ) { + senddirdata(disk,sec); + return; + } + size=disks[disk].secsize; + if (sec<=3) size=128; + + if ( sec > disks[disk].seccount ) { + memset(buf,0,size); + } + else { + to=SEEK(sec,disk); + check=lseek(disks[disk].diskfd,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(disks[disk].diskfd,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; + struct timeval t1,t2; + int usecs,expected; + + /* + * Compute checksum + */ + for( i=0; i>8); + } + + + gettimeofday(&t1,NULL); + /* + * Send buffer; let the port queue as much as it can handle + */ + for( i=0; i> 8); + } + read(atari,&i,1); + if ((i & 0xff) != (sum & 0xff) && !quiet) printf( "[BAD SUM]" ); + else if (disks[disk].fakewrite) { + if ( !quiet) printf("[write discarded]"); + } + else { + lseek(disks[disk].diskfd,SEEK(sec,disk),SEEK_SET); + i=write(disks[disk].diskfd,mybuf,size); + if (i!=size) if ( !quiet) printf("[write failed: %d]",i); + if ( disks[disk].blank && sec>disks[disk].seccount ) { + disks[disk].seccount=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=disks[disk].secsize; + 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(disks[disk].diskfd,0,SEEK_SET); + + memset(&buf,0,sizeof(buf)); + buf.h0=0x96; + buf.h1=0x02; + paragraphs=disks[disk].seccount*(disks[disk].secsize/16) - (disks[disk].secsize-128)/16; + buf.seccountlo=(paragraphs&0xff); + buf.seccounthi=((paragraphs>>8)&0xff); + buf.hiseccountlo=((paragraphs>>16)&0xff); + buf.hiseccounthi=((paragraphs>>24)&0xff); + buf.secsizelo=(disks[disk].secsize&0xff); + buf.secsizehi=((disks[disk].secsize>>8)&0xff); + write(disks[disk].diskfd,&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; + + /* + * Clear RTS (override hw flow control) + * [Necessary to get some of the cables to work.] + * See: http://www.atarimax.com/flashcart/forum/viewtopic.php?p=2426 + */ + i = TIOCM_RTS; + if ( ioctl(atari, TIOCMBIC, &i) < 0 ) + { + perror("ioctl(TIOCMBIC) failed"); + } + + /* + * Wait for a command + */ + if ( !noring && ioctl(atari,TIOCMIWAIT,TIOCM_RNG) < 0 ) /* Wait for a command */ + { + perror("ioctl(TIOCMIWIAT,TIOCM_RNG) failed"); + } + + if ( tcflush(atari,TCIFLUSH) < 0 ) /* Clear out pre-command garbage */ + { + perror("tcflush(TCIFLUSH) failed"); + } + + /* + * Read 5 bytes + * This should take 2.6ms. *** FIXME *** set an alarm + * Use setitimer(ITIMER_REAL,(struct itimerval),NULL) + */ + i=0; + while (1) { + for ( ; i<5; ++i ) + { + r=read(atari,buf+i,1); + if ( r <=0 ) + { + 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 garbage\n",buf[0]); + buf[0]=buf[1]; + buf[1]=buf[2]; + buf[2]=buf[3]; + buf[3]=buf[4]; + i=4; // Read one more byte and recompute checksum + } +} + +/* + * 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 ( disks[disk].blank ) { + disks[disk].diskfd=open(path,O_RDWR,0644); + if ( disks[disk].diskfd>=0 ) { + exists=1; + } + else { + disks[disk].diskfd=open(path,O_RDWR|O_CREAT,0644); + disks[disk].seekcode=atr; + } + } + else { + disks[disk].diskfd=open(path,(disks[disk].ro||disks[disk].fakewrite)?O_RDONLY:O_RDWR); + if (disks[disk].diskfd<0 && !disks[disk].ro && !disks[disk].fakewrite) { + if ( errno == EACCES ) { + disks[disk].ro=1; + disks[disk].diskfd=open(path,O_RDONLY); + } + else if ( errno == EISDIR ) { + disks[disk].filefd = -1; + disks[disk].afileno = -1; + disks[disk].dir=opendir(path); + if ( !disks[disk].dir ) { + fprintf(stderr,"Unable to open directory %s; drive %d disabled\n",path,disk); + return; + } + disks[disk].active=1; + disks[disk].secsize=128; + disks[disk].seccount=720; + disks[disk].seekcode=direct; + disks[disk].dirname=path; + printf( "D%d: %s simulated disk (%d %d-byte sectors)\n",disk+1,path,disks[disk].seccount,disks[disk].secsize); + return; + } + } + } + + if (disks[disk].diskfd<0) { + fprintf(stderr,"Unable to open disk image %s; drive %d disabled\n",path,disk); + return; + } + disks[disk].active=1; + if ( !disks[disk].blank || exists ) { + + /* + * Determine the file type based on the size + */ + disks[disk].secsize=128; + { + struct stat buf; + + fstat(disks[disk].diskfd,&buf); + disks[disk].seekcode=atrdd3; + if (((buf.st_size-ATRHEAD)%256)==128) disks[disk].seekcode=atr; + if (((buf.st_size)%128)==0) disks[disk].seekcode=xfd; + disks[disk].seccount=buf.st_size/disks[disk].secsize; + } + + /* + * Read disk geometry + */ + if (disks[disk].seekcode!=xfd) { + struct atr_head atr; + long paragraphs; + + read(disks[disk].diskfd,&atr,sizeof(atr)); + disks[disk].secsize=atr.secsizelo+256*atr.secsizehi; + paragraphs=atr.seccountlo+atr.seccounthi*256+ + atr.hiseccountlo*256*256+atr.hiseccounthi*256*256*256; + if (disks[disk].secsize==128) { + disks[disk].seccount=paragraphs/8; + } + else { + paragraphs+=(3*128/16); + disks[disk].seccount=paragraphs/16; + } + } + + } + else { + write_atr_head(disk); + } + printf( "D%d: %s opened%s (%d %d-byte sectors)\n",disk+1,path,disks[disk].ro?" read-only":"",disks[disk].seccount,disks[disk].secsize); +} + +/* + * firstgood() + * + * Return the first non-bad sector in the same track. + */ +int firstgood(int disk,int sec) +{ + int i; + + for(i=TRACKSTART(sec);i 720 ) return; + gettimeofday(&newtime,NULL); + if ( !disks[disk].prevsec || TRACK18(disks[disk].prevsec)!=TRACK18(disks[disk].lastsec) ) { + goto done; + } + + diff=newtime.tv_sec-disks[disk].lasttime.tv_sec; + if ( diff > 1 ) goto done; /* more than a second */ + + diff *= 1000000; + diff += newtime.tv_usec; + diff -= disks[disk].lasttime.tv_usec; + + if ( disks[disk].prevsec==disks[disk].lastsec ) { + uspr = diff; /* Observed microsceonds for one revolution */ + if ( !quiet ) printf(" %d.%03d RPMs ",RPM(uspr),RPM3(uspr)); + goto done; + } + usps = uspr/18; + + revs=diff/uspr; + secs=(diff-revs*uspr)/usps; + secpct = (diff - revs*uspr - secs*usps) * 100 / usps; + + if ( revs>1 ) { + if ( !quiet ) printf(" %d revolutions (%d us) [delayed read]",revs,diff); + goto done; + } + + fgs = firstgood(disk,disks[disk].lastsec); + if ( disks[disk].lastsec != fgs ) { + /* Not the first good sector on the track */ + if ( disks[disk].prevsec==fgs || + disks[disk].track[TRACK18(disks[disk].prevsec)].offset[OFF18(disks[disk].prevsec)]) { + /* We can measure directly or indirectly from the first good sector */ + disks[disk].track[TRACK18(disks[disk].lastsec)].offset[OFF18(disks[disk].lastsec)] = + ( disks[disk].track[TRACK18(disks[disk].prevsec)].offset[OFF18(disks[disk].prevsec)] + secs*100 + secpct ) % 1800; + if ( !quiet ) printf(" sec %d is %d.%02d sectors after sec %d [RECORDED]", + disks[disk].lastsec, + disks[disk].track[TRACK18(disks[disk].lastsec)].offset[OFF18(disks[disk].lastsec)]/100, + disks[disk].track[TRACK18(disks[disk].lastsec)].offset[OFF18(disks[disk].lastsec)]%100, + fgs + ); + goto done; + } + } + + if ( !quiet ) { + printf(" sec %d is %d.%02d sectors after sec %d", disks[disk].lastsec, secs, secpct, disks[disk].prevsec ); + printf(" fgs:%d",fgs); + } + + + done: + disks[disk].prevsec = disks[disk].lastsec; + disks[disk].lastsec = sec; + disks[disk].lasttime = newtime; +} + +/* + * 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&&!disks[disk].active) { 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 'B': + ; + if ( !disks[disk].active ) { + disks[disk].track[TRACK18(sec)].bad[OFF18(sec)]=1; + if ( !quiet ) printf("announce bad sector %d: ",sec); + } + case 'R': + if ( !quiet) printf("read sector %d: ",sec); + if ( !disks[disk].active ) { + addtiming(disk,sec); + 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 ( !disks[disk].active ) break; + usleep(ACK1); + if (disks[disk].ro) { + ack('N'); + if ( !quiet) printf("[Read-only image]"); + break; + } + ack('A'); + recvdata(disk,sec); + usleep(ACK2); + ack('A'); + usleep(COMPLETE1); + ack('C'); + break; + case 'P': + if ( !quiet) printf("put sector %d: ",sec); + if ( !disks[disk].active ) break; + usleep(ACK1); + if (disks[disk].ro) { + ack('N'); + if ( !quiet) printf("[Read-only image]"); + break; + } + ack('A'); + recvdata(disk, sec); + usleep(ACK2); + ack('A'); + usleep(COMPLETE1); + ack('C'); + break; + case 'S': + if ( !quiet) printf( "status:" ); + if ( !disks[disk].active ) 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 unsigned char status[] = { 0x10, 0x00, 1, 0 }; + status[0]=(disks[disk].secsize==128?0x10:0x60); + if (disks[disk].secsize==128 && disks[disk].seccount>720) status[0]=0x80; + if (disks[disk].ro) { + status[0] |= 8; + } + else { + status[0] &= ~8; + } + usleep(COMPLETE1); + ack('C'); + usleep(COMPLETE2); + sendrawdata(status,sizeof(status)); + } + break; + case 'N': + if ( !quiet) printf("815 configuration block read"); + if ( !disks[disk].active ) break; + /* We get 19 of these from DOS 2.0 when you hit reset */ + usleep(ACK1); + ack('A'); + usleep(COMPLETE1); + ack('C'); + { + unsigned char status[12]; + + memset(status,0,sizeof(status)); + status[0]=1; /* 1 big track */ + status[1]=1; /* Why not? */ + status[2]=disks[disk].seccount>>8; + status[3]=disks[disk].seccount&0xff; + status[5]=((disks[disk].secsize==256)?4:0); + status[6]=disks[disk].secsize>>8; + status[7]=disks[disk].secsize&0xff; + sendrawdata(status,sizeof(status)); + } + break; + case 'O': + if ( !quiet) printf("815 configuration block write (ignored)"); + if ( !disks[disk].active ) break; + usleep(ACK1); + 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(" "); + } + usleep(ACK2); + ack('A'); + usleep(COMPLETE1); + ack('C'); + break; + case '"': + if ( !quiet) printf( "format enhanced " ); + if ( !disks[disk].active ) 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 ( !disks[disk].active ) break; + /*** FIXME *** Acknowledge and zero disk image ***/ + usleep(ACK1); + ack('A'); + usleep(ACK1); + ack('C'); + break; + case 0x20: + if ( !quiet) printf( "download " ); + if ( !disks[disk].active ) break; + break; + case 0x54: + if ( !quiet) printf( "readaddr " ); + if ( !disks[disk].active ) break; + break; + case 0x51: + if ( !quiet) printf( "readspin " ); + if ( !disks[disk].active ) break; + break; + case 0x55: + if ( !quiet) printf( "motoron " ); + if ( !disks[disk].active ) break; + break; + case 0x56: + if ( !quiet) printf( "verify " ); + if ( !disks[disk].active ) break; + break; + default: + if ( !quiet) printf( "??? " ); + if ( !disks[disk].active ) 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); +} diff --git a/SRC/unix2atr-0.9.c b/SRC/unix2atr-0.9.c new file mode 100644 index 0000000..bce7442 --- /dev/null +++ b/SRC/unix2atr-0.9.c @@ -0,0 +1,925 @@ +/************************************************************************/ +/* unix2atr.c */ +/* */ +/* Preston Crow */ +/* Public Domain */ +/* */ +/* Extract files from a Unix directory tree and create a MyDOS */ +/* ATR-format disk image file. */ +/* */ +/* To-do for version 1.0: */ +/* Eliminate any bugs */ +/* Fix up the DOS 2.5 boot sectors. */ +/* * Adjust for DOS.SYS if it is there for DOS2.0/2.5. */ +/* */ +/* To-do after 1.0: */ +/* Support for DOS2.0D?? (256-byte sectors without MyDOS) */ +/* * Is it any different from 2.0S? */ +/* Support for SpartaDOS */ +/* * This will probably have to be coded by someone else, */ +/* as I'm not terribly interested in it. */ +/* Support for more MyDOS versions */ +/* * Simply a matter of detecting the version and writing */ +/* out the right boot sectors. */ +/* */ +/* Version History */ +/* */ +/* 14 Mar 98 Version 0.9 Preston Crow */ +/* */ +/* Auto detect MyDOS version and use correct boot sectors. */ +/* This needs to be updated for additional versions; */ +/* currently only 4.50 and 4.53 are supported. */ +/* */ +/* Fixed segfault with directories with > 64 files. */ +/* */ +/* Fixed a bug in the writing of file data--it's right now */ +/* */ +/* Cleaned up the MyDOS boot sector data. It may still */ +/* be sensitive to different versions of MyDOS. */ +/* */ +/* 24 Feb 98 Version 0.8 Preston Crow */ +/* Add '-s' option to skip the first 720 sectors. */ +/* This is useful to protect against corruption from */ +/* files that do raw sector I/O, and to save room to write */ +/* the DOS files. */ +/* Even with this flag, DOS.SYS will be written at sector 4 */ +/* */ +/* VTOC is now generated for DOS2.5 enhanced density images. */ +/* */ +/* Boot sectors are correct for MyDOS and DOS2.0/2.5, */ +/* except that they aren't adjusted to reflect the disk */ +/* parameters or the existence of DOS.SYS. */ +/* */ +/* DOS.SYS, DUP.SYS, and AUTORUN.SYS will be the first */ +/* files written if they are found. */ +/* */ +/* Directory sorting is case-insensitive if '-u' is */ +/* specified, so the resulting image has a sorted directory. */ +/* */ +/* 21 Feb 98 Version 0.7 Preston Crow */ +/* Bug fix: ATR "sector" count is now correct. */ +/* (Why did they use such a strange formula?) */ +/* Initial writing of boot sector and VTOC. */ +/* */ +/* Still no support for extended DOS 2.5 VTOC. */ +/* */ +/* Should include file numbers for non-MyDOS disks. */ +/* */ +/* 7 Feb 98 Version 0.6 Preston Crow */ +/* Bug fixes */ +/* */ +/* 7 Jun 95 Version 0.5 Preston Crow */ +/* Mostly works except: */ +/* MyDos format is the only one that works. */ +/* bitmaps aren't written to the image */ +/* boot sectors aren't created */ +/* */ +/************************************************************************/ + +/************************************************************************/ +/* Include files */ +/************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +/************************************************************************/ +/* Macros and Constants */ +/************************************************************************/ +#define ATRHEAD 16 +#define USAGE "unix2atr [-dumps] #sectors atarifile.atr [directory]\n Flags:\n\t-d Double density sectors\n\t-u Convert filenames to upper case\n\t-m MyDOS format disk image\n\t-s skip the first 720 sectors\n\t-p print debugging stuff\n" +/* SEEK: seek offset in the disk image file for a given sector number */ +#define SEEK(n) (ATRHEAD + ((n<4)?((n-1)*128):(3*128+(n-4)*secsize))) + +/************************************************************************/ +/* Data types */ +/************************************************************************/ +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]; +}; + +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 write_file(FILE *fin,int sector,int fileno); /* Write a file starting at the specified sector */ +void write_dir(int sector); /* Write a directory */ +void set_dos_version(char *fname); /* Determine which version of DOS.SYS it is */ +int use_sector(void); /* Return a sector number that is free to use after marking it */ +int use_8_sector(void); /* Get 8 sectors for a directory */ +void write_boot(void); /* Write first 3 sectors */ +void write_bitmaps(void); /* Write out the free bitmap */ +int afnamecpy(char *an,const char *n); + +/************************************************************************/ +/* Global variables */ +/************************************************************************/ +int secsize; +int mydos=0; +int upcase=0; +int debug=0; +int seccount; +char bitmap[64*1024]; /* Free "bit" map */ +int lastfree=4; +FILE *fout; +int rootdir; /* flag for sorting routine */ +int dos=0; /* true if DOS.SYS is written */ +int dosver=0; /* DOS.SYS version code */ + +/************************************************************************/ +/* main() */ +/* Process command line */ +/* Open input .ATR file */ +/* Interpret .ATR header */ +/************************************************************************/ +int main(int argc,char *argv[]) +{ + struct atr_head head; + int i; + unsigned char buf[256]; + + --argc; ++argv; /* Skip program name */ + + secsize=128; + /* Process flags */ + while (argc) { + int done=0; + + if (**argv=='-') { + ++*argv; + while(**argv) { + switch(**argv) { + case 'd': /* DD */ + secsize=256; + break; + case 'm': /* MyDos disk */ + mydos=1; + break; + case '-': /* Last option */ + done=1; + break; + case 'u': /* strupr names */ + upcase=1; + break; + case 'p': /* debugging */ + debug=1; + break; + case 's': /* skip early sectors */ + lastfree=721; + break; + default: + fprintf(stderr,USAGE); + exit(1); + } + ++*argv; + } + --argc; ++argv; + } + else break; + if (done) break; + } + if (argc<2) { + fprintf(stderr,USAGE); + exit(1); + } + + /* Number of sectors */ + seccount=atoi(*argv); + ++argv;--argc; + if (seccount<368) { + fprintf(stderr,USAGE); + exit(1); + } + + + if ((seccount>1040||(seccount>720&&secsize==256))&&!mydos) { + fprintf(stderr,"Must use MyDos format if more than 1040 SD or 720 DD sectors\n"); + exit(1); + } + + /* Open output file */ + fout=fopen(*argv,"wb"); + if (!fout) { + fprintf(stderr,"Unable to open %s\n%s",*argv,USAGE); + exit(1); + } + --argc; ++argv; + + /* Change directories? */ + if (argc) { + if (chdir(*argv)) { + fprintf(stderr,"Unable to change to directory: %s\n%s",*argv,USAGE); + exit(1); + } + } + + /* Initialize free sectors */ + for(i=1;i<=seccount;++i) bitmap[i]=0; + for(i=seccount+1;i<64*1024;++i) bitmap[i]=1; + for(i=360;i<=368;++i) bitmap[i]=1; + for(i=0;i<=3;++i) bitmap[i]=1; + if (mydos) { + if (seccount>943+(secsize-128)*8) { + for(i=360-((seccount-943+(secsize*8-1))/(secsize*8));i<360;++i) bitmap[i]=1; + } + } + else { + for(i=1024;i<=seccount;++i) { + bitmap[i]=0; /* Don't use sectors 1024 or above */ + } + } + if (!mydos) bitmap[720]=1; + + /* Initialize ATR header */ + head.h0=0x96; + head.h1=0x02; + head.secsizelo=secsize&0xff; + head.secsizehi=secsize/0x100; + { + unsigned long paragraphs; + + paragraphs=(seccount-3)*(secsize/16) + 3*128/16; + head.seccountlo=paragraphs&0xff; + head.seccounthi=(paragraphs>>8)&0xff; + head.hiseccountlo=(paragraphs>>16)&0xff; + head.hiseccounthi=(paragraphs>>24)&0xff; + } + bzero(head.unused,sizeof(head.unused)); + + fwrite(&head,sizeof(head),1,fout); + + /* Initialize sectors */ + if (debug)printf("Creating %d %d-byte sectors (%d byte file)\n",seccount,secsize,SEEK(seccount)+secsize); + for(i=0;i<256;++i)buf[i]=0; + fseek(fout,SEEK(1),SEEK_SET); + for(i=1;i<=3;++i) fwrite(buf,128,1,fout); + for(i=4;i<=seccount;++i) fwrite(buf,secsize,1,fout); + + write_dir(361); + write_boot(); + write_bitmaps(); + return(0); +} + +/************************************************************************/ +/* write_file() */ +/* Write a file starting at the specified sector */ +/************************************************************************/ +void write_file(FILE *fin,int sector,int fileno) +{ + unsigned char buf[256]; + int i; + int sc=1; + unsigned char c; + + c=fgetc(fin); + while (!feof(fin)) { + int nsec=0; + bzero(buf,secsize); + if (!mydos) buf[secsize-3]=(fileno<<2); + for(i=0;i %d\n",sector,nsec); + sector=nsec; + } + if (debug) printf("\t\tFile written, %d sectors\n",sc); +} + +/************************************************************************/ +/* mydirsort() */ +/* */ +/* Compare two directory entries for use in sorting. */ +/* Essentially alphabetical ordering, except for special files that */ +/* go first: DOS.SYS, DUP.SYS, and AUTORUN.SYS. */ +/* */ +/* Note that MyDOS 4.53 seems to use '*AR0' instead of 'AUTORUN.SYS' */ +/* as the auto-loaded menu program. I don't know why. */ +/************************************************************************/ +int mydirsort(const struct dirent *const*a,const struct dirent *const*b) +{ + char an[12],bn[12]; + + /* If the filename isn't valid for Atari, it's order doesn't matter */ + if (!afnamecpy(an,(*a)->d_name)) return(0); + if (!afnamecpy(bn,(*b)->d_name)) return(0); + + /* Place certain files first in the root directory */ + if (rootdir) { + if (strcmp(an,"DOS SYS")==0) return(-1); + if (strcmp(bn,"DOS SYS")==0) return( 1); + if (strcmp(an,"DUP SYS")==0) return(-1); + if (strcmp(bn,"DUP SYS")==0) return( 1); + if (strcmp(an,"AUTORUN SYS")==0) return(-1); + if (strcmp(bn,"AUTORUN SYS")==0) return( 1); + if (strcmp(an,"AUTORUN AR0")==0) return(-1); + if (strcmp(bn,"AUTORUN AR0")==0) return( 1); +#if 1 /* force some reordering to make me happy */ + if (strcmp(an,"FILES LST")==0) return(-1); + if (strcmp(bn,"FILES LST")==0) return( 1); + if (strcmp(an+2,bn+2)==0) return(strcmp(an,bn)); + if (strcmp(an+2," ")==0) return( 1); + if (strcmp(bn+2," ")==0) return(-1); +#endif + } + + /* Compare the converted filenames */ + return(strcmp(an,bn)); +} + +/************************************************************************/ +/* write_dir() */ +/************************************************************************/ +#define SENTRY(n) (secsize==128?n:(n/8)*8+n) +void write_dir(int sector) +{ + struct direct **d; + int used; + int i; + int e; + int count; + int start; + char aname[12]; + struct atari_dirent dir[128]; /* 64 + 64 DD wasted space */ + int lastfreesave=0; + + aname[11]=0; + for(i=0;i<8*256;++i) ((char *)dir)[i]=0; + + rootdir=(sector==361); + i=scandir(".",&d,NULL,mydirsort); + used=0; + while (i) { + /* Process **d */ + do { + struct stat sbuf; + int subdir; + + /* Hidden files--ignore silently */ + if ((*d)->d_name[0]=='.') break; + + if (lastfreesave) { + lastfree=lastfreesave; + lastfreesave=0; + } + + /* Convert name */ + if (!afnamecpy(aname,(*d)->d_name)) { + printf("Warning: %s: Can't convert to Atari name\n",(*d)->d_name); + break; + } + + /* If it's DOS.SYS, start at sector 4, even if we're reserving the first 720 sectors */ + if (rootdir) { + if (strcmp(aname,"DOS SYS")==0) { + set_dos_version((*d)->d_name); + } + if ( + strcmp(aname,"DOS SYS")==0 || + strcmp(aname,"DUP SYS")==0 || + strcmp(aname,"AUTORUN SYS")==0 + ) { + lastfreesave=lastfree; + lastfree=3; + } + } + + /* Determine if it's a file or directory */ + if (stat((*d)->d_name,&sbuf)) { + printf("Warning: %s: Can't stat file\n",(*d)->d_name); + break; + } + subdir=0; + if (!sbuf.st_mode&(S_IFDIR|S_IFREG)) { + printf("Warning: %s: Not a normal file or directory\n",(*d)->d_name); + break; + } + + if (sbuf.st_mode&S_IFDIR) subdir=1; + if (subdir && !mydos) { + printf("Warning: %s/: Can't process subdirectory for standard Atari images\n",(*d)->d_name); + break; + } + + /* Create the entry */ + e=SENTRY(used); + if (used>63) { + printf("Warning: More than 64 files in this directory\n"); + printf(" Additional files or subdirectories ignored\n"); + return; + } + strcpy(dir[e].namelo,aname); + if (subdir) { + dir[e].flag=16; + count=8; + start=use_8_sector(); + } + else { + dir[e].flag=0x46; + if (!sbuf.st_mode&0000200) dir[e].flag &=32; /* locked */ + count=(sbuf.st_size+(secsize-3)-1)/(secsize-3); + start=use_sector(); + } + dir[e].countlo=count&0xff; + dir[e].counthi=count/0x100; + dir[e].startlo=start&0xff; + dir[e].starthi=start/0x100; + + /* Write the directory sector */ + fseek(fout,SEEK(sector),SEEK_SET); + fwrite(dir,secsize,8,fout); + ++used; + + /* Write the file/subdir */ + if (!subdir) { + FILE *fin; + + fin=fopen((*d)->d_name,"rb"); + if (!fin) { + printf("Warning: %s: Can't open file\n",(*d)->d_name); + break; + } + if (debug) printf("\tWriting file %s starting at sector %d\n",(*d)->d_name,start); + write_file(fin,start,used-1); + fclose(fin); + } + else { + if (debug) printf("\tWriting directory %s\n",(*d)->d_name); + if (chdir((*d)->d_name)) { + fprintf(stderr,"Unable to change to directory: %s\n",(*d)->d_name); + break; + } + write_dir(start); + chdir(".."); + } + } while(0); /* for break */ + free(*d); + ++d;--i; + } +} + +/************************************************************************/ +/* set_dos_version() */ +/* Read the first K of dos.sys and make an educated guess as to which */ +/* version it is. */ +/************************************************************************/ +void set_dos_version(char *fname) +{ + FILE *dosfile; + unsigned char buf[1024]; + + dosfile=fopen(fname,"rb"); + if (!dosfile) return; + fread(buf,1,1024,dosfile); + fclose(dosfile); + if (buf[18]==0x08) dosver=450; /* MyDOS 4.50 */ + if (buf[18]==0xFD) dosver=453; /* MyDOS 4.53/4 */ + if (dosver==0) { + printf("Warning: Unable to determine DOS.SYS version\n"); + } + dos=1; +} + +/************************************************************************/ +/* use_sector() */ +/* Return a sector number that is free to use after marking it used. */ +/************************************************************************/ +int use_sector(void) +{ + while (lastfree<=seccount&&bitmap[lastfree]) ++lastfree; + + + if (lastfree>seccount) { + fprintf(stderr,"Disk image full\n"); + write_bitmaps(); + exit(1); + } + bitmap[lastfree]=1; + return(lastfree); +} + +/************************************************************************/ +/* use_8_sector() */ +/* Get 8 consecutive sectors for a directory */ +/************************************************************************/ +int use_8_sector(void) +{ + int i=lastfree; + int j; + + while ((bitmap[i+0]||bitmap[i+1]||bitmap[i+2]||bitmap[i+3]|| + bitmap[i+4]||bitmap[i+5]||bitmap[i+6]||bitmap[i+7]) + && i+7<64*1024) ++i; + if (i+7<64*1024) { + for (j=0;j<8;++j) bitmap[i+j]=1; + return(i); + } + fprintf(stderr,"Disk image full--no room for directory\n"); + write_bitmaps(); + exit(1); +} + +/************************************************************************/ +/* write_boot() */ +/* Write out the first 3 sectors */ +/************************************************************************/ +void write_boot(void) +{ + /* + * Sectors 1 through 3 + * + * Copied from a blank 720-sector DOS2.0S-formatted disk + * DOS2.5 generates the same data for 720- and 1040-sector disks. + * + * Writing DOS files may change some bytes, but I haven't checked + * to see which bytes change. + */ + char dos20init[128*3]={ + 0x00,0x03,0x00,0x07,0x40,0x15,0x4c,0x14,0x07,0x03,0x03,0x00,0x7c,0x1a,0x00,0x04, + 0x00,0x7d,0xcb,0x07,0xac,0x0e,0x07,0xf0,0x36,0xad,0x12,0x07,0x85,0x43,0x8d,0x04, + 0x03,0xad,0x13,0x07,0x85,0x44,0x8d,0x05,0x03,0xad,0x10,0x07,0xac,0x0f,0x07,0x18, + 0xae,0x0e,0x07,0x20,0x6c,0x07,0x30,0x17,0xac,0x11,0x07,0xb1,0x43,0x29,0x03,0x48, + 0xc8,0x11,0x43,0xf0,0x0e,0xb1,0x43,0xa8,0x20,0x57,0x07,0x68,0x4c,0x2f,0x07,0xa9, + 0xc0,0xd0,0x01,0x68,0x0a,0xa8,0x60,0x18,0xa5,0x43,0x6d,0x11,0x07,0x8d,0x04,0x03, + 0x85,0x43,0xa5,0x44,0x69,0x00,0x8d,0x05,0x03,0x85,0x44,0x60,0x8d,0x0b,0x03,0x8c, + 0x0a,0x03,0xa9,0x52,0xa0,0x40,0x90,0x04,0xa9,0x57,0xa0,0x80,0x8d,0x02,0x03,0x8c, + 0x03,0x03,0xa9,0x31,0xa0,0x0f,0x8d,0x00,0x03,0x8c,0x06,0x03,0xa9,0x03,0x8d,0xff, + 0x12,0xa9,0x00,0xa0,0x80,0xca,0xf0,0x04,0xa9,0x01,0xa0,0x00,0x8d,0x09,0x03,0x8c, + 0x08,0x03,0x20,0x59,0xe4,0x10,0x1d,0xce,0xff,0x12,0x30,0x18,0xa2,0x40,0xa9,0x52, + 0xcd,0x02,0x03,0xf0,0x09,0xa9,0x21,0xcd,0x02,0x03,0xf0,0x02,0xa2,0x80,0x8e,0x03, + 0x03,0x4c,0xa2,0x07,0xae,0x01,0x13,0xad,0x03,0x03,0x60,0xaa,0x08,0x14,0x0b,0xbe, + 0x0a,0xcb,0x09,0x00,0x0b,0xa6,0x0b,0x07,0x85,0x44,0xad,0x0a,0x07,0x8d,0xd6,0x12, + 0xad,0x0c,0x07,0x85,0x43,0xad,0x0d,0x07,0x85,0x44,0xad,0x0a,0x07,0x8d,0x0c,0x13, + 0xa2,0x07,0x8e,0x0d,0x13,0x0e,0x0c,0x13,0xb0,0x0d,0xa9,0x00,0x9d,0x11,0x13,0x9d, + 0x29,0x13,0x9d,0x31,0x13,0xf0,0x36,0xa0,0x05,0xa9,0x00,0x91,0x43,0xe8,0x8e,0x01, + 0x03,0xa9,0x53,0x8d,0x02,0x03,0x20,0x53,0xe4,0xa0,0x02,0xad,0xea,0x02,0x29,0x20, + 0xd0,0x01,0x88,0x98,0xae,0x0d,0x13,0x9d,0x11,0x13,0xa5,0x43,0x9d,0x29,0x13,0xa5, + 0x44,0x9d,0x31,0x13,0x20,0x70,0x08,0x88,0xf0,0x03,0x20,0x70,0x08,0xca,0x10,0xb2, + 0xac,0x09,0x07,0xa2,0x00,0xa9,0x00,0x88,0x10,0x01,0x98,0x9d,0x19,0x13,0x98,0x30, + 0x0d,0xa5,0x43,0x9d,0x39,0x13,0xa5,0x44,0x9d,0x49,0x13,0x20,0x70,0x08,0xe8,0xe0, + 0x10,0xd0,0xe2,0xa5,0x43,0x8d,0xe7,0x02,0xa5,0x44,0x8d,0xe8,0x02,0x4c,0x7e,0x08, + 0x18,0xa5,0x43,0x69,0x80,0x85,0x43,0xa5,0x44,0x69,0x00,0x85,0x44,0x60,0xa0,0x7f + }; + /* + * Sectors 1 through 3 + * + * Rick D. reported: + > MYDOS will store the same info in the boot sector as any other DOS. This is + > because the OS must use it to locate and load DOS into ram. + > Sector 1, byte offset 0-19 hold the useful info. b14=1=DOS;=0=No DOS, + > B15 & 16=sector to start of DOS, B18 & B19=DOS load address, B9= #file buffers, + > B10=drive bits, and B17=Disp to sector link(effectivly, disk density). The + > rest of the stuff is either in DOS(ramdisk config), or Mydos guese, i.e. >720 + > sectors, or double sided. + * + */ + char mydosinit453[3*128]={ + 0x4d,0x03,0x00,0x07,0xe0,0x07,0x4c,0x14,0x07,0x03,0x09,0x01,0xe8,0x1b,0x02,0x04 +,0x00,0xfd,0x0a,0x0b,0xac,0x12,0x07,0xad,0x13,0x07,0x20,0x58,0x07,0xad,0x10,0x07 +,0xac,0x0f,0x07,0x18,0xae,0x0e,0x07,0xf0,0x1d,0x20,0x63,0x07,0x30,0x18,0xac,0x11 +,0x07,0xb1,0x43,0x29,0xff,0x48,0xc8,0x11,0x43,0xf0,0x0e,0xb1,0x43,0x48,0x20,0x4d +,0x07,0x68,0xa8,0x68,0x90,0xdd,0xa9,0xc0,0xa0,0x68,0x0a,0xa8,0x60,0xad,0x11,0x07 +,0x18,0x65,0x43,0xa8,0xa5,0x44,0x69,0x00,0x84,0x43,0x85,0x44,0x8c,0x04,0x03,0x8d +,0x05,0x03,0x60,0x8d,0x0b,0x03,0x8c,0x0a,0x03,0xa0,0x03,0xa9,0x52,0x90,0x02,0xa9 +,0x50,0x84,0x48,0x8d,0x02,0x03,0x18,0x8c,0x06,0x03,0xa9,0x80,0xca,0xf0,0x0d,0xae +,0x0b,0x03,0xd0,0x07,0xae,0x0a,0x03,0xe0,0x04,0x90,0x01,0x0a,0x8d,0x08,0x03,0x2a +,0x8d,0x09,0x03,0xa0,0x31,0x8c,0x00,0x03,0xc6,0x48,0x30,0x16,0xae,0x02,0x03,0xe8 +,0x8a,0xa2,0x40,0x29,0x06,0xd0,0x02,0xa2,0x80,0x8e,0x03,0x03,0x20,0x59,0xe4,0x88 +,0x30,0xe6,0xa6,0x2e,0xc8,0x98,0x60,0x10,0x69,0x01,0x00,0x80,0xf6,0x00,0x00,0x00 +,0x23,0x28,0x50,0x4d,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x52,0xd2,0xd2 +,0xd2,0xd2,0xd2,0xd2,0x5c,0x0c,0x5c,0x0e,0x62,0x0d,0xc6,0x0d,0x50,0x0e,0x67,0x10 +,0xa9,0x69,0x8d,0xb8,0x07,0xa9,0x01,0x8d,0xb9,0x07,0xa2,0x08,0x8e,0x01,0x03,0x20 +,0xb6,0x0b,0xbd,0xcb,0x07,0x30,0x12,0x20,0x9a,0x0b,0xf0,0x0d,0xbd,0xcb,0x07,0xc9 +,0x40,0xb0,0x06,0xbc,0xc3,0x07,0x20,0x24,0x0b,0xca,0xd0,0xe0,0xa0,0xae,0x8a,0x99 +,0x55,0x08,0x88,0xd0,0xfa,0xee,0x59,0x08,0xad,0x0c,0x07,0x8d,0xe7,0x02,0xac,0x0d +,0x07,0xa2,0x0f,0xec,0x09,0x07,0x90,0x05,0xde,0xdd,0x08,0x30,0x05,0x98,0x9d,0xed +,0x08,0xc8,0xca,0x10,0xee,0x8c,0xe8,0x02,0xe8,0xe8,0xe8,0xbd,0x18,0x03,0xf0,0x04 +,0xc9,0x44,0xd0,0xf4,0xa9,0x44,0x9d,0x18,0x03,0xa9,0xd4,0x9d,0x19,0x03,0xa9,0x07 +,0x9d,0x1a,0x03,0x4c,0x79,0x1a,0x00,0x00,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00 +,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc8,0x80 +,0xfd,0x00,0x03,0x04,0x00,0x00,0x00,0x00,0x00,0x69,0x01,0x00,0x00,0x00,0x00,0x00 + }; + char mydosinit450[3*128]={ + 0x4d,0x03,0x00,0x07,0xe0,0x07,0x4c,0x14,0x07,0x03,0xff,0x01,0xe9,0x1b,0x02,0x04 +,0x00,0xfd,0x15,0x0b,0xac,0x12,0x07,0xad,0x13,0x07,0x20,0x58,0x07,0xad,0x10,0x07 +,0xac,0x0f,0x07,0x18,0xae,0x0e,0x07,0xf0,0x1d,0x20,0x63,0x07,0x30,0x18,0xac,0x11 +,0x07,0xb1,0x43,0x29,0xff,0x48,0xc8,0x11,0x43,0xf0,0x0e,0xb1,0x43,0x48,0x20,0x4d +,0x07,0x68,0xa8,0x68,0x90,0xdd,0xa9,0xc0,0xa0,0x68,0x0a,0xa8,0x60,0xad,0x11,0x07 +,0x18,0x65,0x43,0xa8,0xa5,0x44,0x69,0x00,0x84,0x43,0x85,0x44,0x8c,0x04,0x03,0x8d +,0x05,0x03,0x60,0x8d,0x0b,0x03,0x8c,0x0a,0x03,0xa0,0x03,0xa9,0x52,0x90,0x03,0xad +,0x79,0x07,0x84,0x48,0x8d,0x02,0x03,0x18,0xa9,0x57,0x8c,0x06,0x03,0xa9,0x80,0xca +,0xf0,0x0d,0xae,0x0b,0x03,0xd0,0x07,0xae,0x0a,0x03,0xe0,0x04,0x90,0x01,0x0a,0x8d +,0x08,0x03,0x2a,0x8d,0x09,0x03,0xa0,0x31,0x8c,0x00,0x03,0xc6,0x48,0x30,0x16,0xae +,0x02,0x03,0xe8,0x8a,0xa2,0x40,0x29,0x06,0xd0,0x02,0xa2,0x80,0x8e,0x03,0x03,0x20 +,0x59,0xe4,0x88,0x30,0xe6,0xa6,0x2e,0xc8,0x98,0x60,0x10,0x71,0x01,0x00,0x80,0xf6 +,0x23,0x28,0x50,0x4d,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x52,0x12,0xd2,0xd2 +,0xd2,0xd2,0xd2,0xd2,0x5c,0x0c,0x5c,0x0e,0x62,0x0d,0xc6,0x0d,0x50,0x0e,0x67,0x10 +,0xa9,0x69,0x8d,0xbb,0x07,0xa9,0x01,0x8d,0xbc,0x07,0xa2,0x08,0x8e,0x01,0x03,0x20 +,0xb6,0x0b,0xbd,0xcb,0x07,0x30,0x1d,0x20,0x9a,0x0b,0xf0,0x18,0xa0,0x09,0xb9,0x25 +,0x0b,0x99,0x02,0x03,0x88,0x10,0xf7,0xbd,0xcb,0x07,0xc9,0x40,0xb0,0x06,0xbc,0xc3 +,0x07,0x20,0x2f,0x0b,0xca,0xd0,0xd5,0xa0,0xae,0x8a,0x99,0x60,0x08,0x88,0xd0,0xfa +,0xee,0x64,0x08,0xad,0x0c,0x07,0x8d,0xe7,0x02,0xac,0x0d,0x07,0xa2,0x0f,0xec,0x09 +,0x07,0x90,0x05,0xde,0xe8,0x08,0x30,0x05,0x98,0x9d,0xf8,0x08,0xc8,0xca,0x10,0xee +,0x8c,0xe8,0x02,0xe8,0xe8,0xe8,0xbd,0x18,0x03,0xf0,0x04,0xc9,0x44,0xd0,0xf4,0xa9 +,0x44,0x9d,0x18,0x03,0xa9,0xd4,0x9d,0x19,0x03,0xa9,0x07,0x9d,0x1a,0x03,0x4c,0x8c +,0x1a,0x00,0x00,0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc8,0x80,0xfd,0x00,0x03,0x04,0x00 + }; + char *mydosinit; + + if (debug && mydos) { + printf("Mydos version: %d\n",dosver); + } + switch (dosver) { + case 450: + mydosinit=mydosinit450; + break; + case 453: + mydosinit=mydosinit453; + break; + default: + mydosinit=mydosinit450; + if (mydos && dos) { + printf("Warning: Failed to detect MyDOS version, may not boot\n"); + } + } + mydosinit[0]='M'; /* Indicate MyDOS 4.5 or later */ + mydosinit[1]=3; /* number of sectors in the boot */ + mydosinit[9]=3; /* Max number of open files at one time */ + mydosinit450[10]=255; /* Ram Disk unit number */ + mydosinit453[10]=9; /* Ram Disk unit number */ + mydosinit[11]=1; /* Default unit number (D:) */ + /* [12],[13]: First byte of free memory */ + mydosinit[14]=((secsize==256)?2:1); + mydosinit[15]=4;mydosinit[16]=0; /* DOS.SYS start sector */ + mydosinit[17]=secsize-3; /* Offset to the sector link field */ + /* [18],[19]: Address to load dos.sys into */ + if (dos) { + mydosinit450[18]=21; + mydosinit453[18]=10; + } + /* + * The following seemed to be correct based on experimentation: + */ + mydosinit453[196]=((secsize==256)?2:1); + mydosinit453[368]=secsize-3; + + fseek(fout,SEEK(1),SEEK_SET); + fwrite(mydos?mydosinit:dos20init,128,3,fout); +} + +/************************************************************************/ +/* write_bitmaps() */ +/* Write out the free bitmap (VTOC) */ +/* I'm a bit clueless on much of this, but it tries to do what I've */ +/* observed from real disk images. */ +/************************************************************************/ +void write_bitmaps(void) +{ + char sec360[256] /******** ={ + 0x02,0xc3,0x02,0xc3,0x02,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }********/; + int i; + int byte,bit; + int free; + + for(i=0;i<256;++i)sec360[i]=0; + + /* + * We need to clean up the computation of total data sectors. + * + * For non-MyDos>720, we need to take into account the second VTOC + * For MyDos, we need to count 720, but not additional VTOC sectors. + */ + { + int total; + + total=seccount-3-9-1; /* Boot, VTOC+DIR, 720 */ + + if (mydos) { + total=seccount-3-8; /* boot and dir */ + total-=(1+(seccount-(943+(secsize-128)*8)+(secsize*8-1))/(secsize*8)); /* VTOC */ + } + else if (seccount>720) { + if (total>1023-3-8-1-1) total=1023-3-8-1-1; + } + sec360[2]=(total)/256; + sec360[1]=(total)%256; + } + + /* + * Record the number of free sectors + * For DOS2.5, this is the number of free sectors in this VTOC. + */ + free=0; + for(i=0;i<=(mydos?seccount:720);++i) { + if (bitmap[i]==0) ++free; + } + sec360[4]=free/256; + sec360[3]=free%256; + if (debug) { + printf("%d sectors %sfree\n",free,mydos?"":"<720 "); + } + + /* + * I'm clueless--is this some sort of version code? + * I think this agrees with how MyDOS and DOS2.5 set this byte. + */ + sec360[0]=2; + if (mydos && seccount>943) sec360[0]+=(seccount-943+(secsize*8-1))/(secsize*8)+1; + + /* + * bitmap[i] is true if the sector has been used + * + * The corresponding bit should be one if the sector is *free* + */ + if (!mydos) { + for(i=0;i<720;++i) { + byte=10+i/8; + bit=i%8; + bit=7-bit; + bit=1<720) { + /* Copy most of first VTOC */ + for(i=0;i<128-16;++i)sec360[i]=sec360[i+16]; + + free=0; + for(i=720;i<1024;++i) { + byte=i/8-6; + bit=i%8; + bit=7-bit; + bit=1<943) { + int ss; + int sn=359; + for(ss=944;ss<=seccount;ss+=8*128,--sn) { + for(i=0;i<128;++i)sec360[i]=0; + for(i=0;i<8*128;++i) { + byte=i/8; + bit=i%8; + bit=7-bit; + bit=1<NO THEN L = L + N256 +499 REM DISPLAY BOUNDS AND GET REPLY +500 ? CHR$(125); S$; "ATARI MEMORY TEST PROGRAM"; CHR$(155); S$; "MEMORY + BOUNDS ARE" +600 ? S$; "LOW = "; L : ? S$; "HIGH = "; H +700 ? S$; "GIVE TEST BOUNDS"; CHR$(155) +800 TRAP 800 : ? S$; "LOW = "; : INPUT LOW : IF LOW < L OR LOW > H THEN 800 +900 TRAP 900 : ? S$; "HIGH = "; : INPUT HIGH : IF HIGH > H OR HIGH < L OR + HIGH-LOW < N256 THEN 900 +999 REM SETUP BOUNDS FOR THE MLP +1000 POKE 205, NO : POKE 206, INT(HIGH/N256) +1100 TRAP 32767 : POKE 203, NO : POKE 204, INT(LOW/N256) +1200 POKE 764, N255 +1299 REM INVOKE THE MLP TO DO THE TEST +1300 POKE 559, NO : POKE 764, N255 : X = USR(1536) +1399 REM CHECK RETURN FROM MLP +1400 IF PEEK(208) = NO THEN 2200 +1499 REM SHOW MEMORY ERROR ON SCREEN +1500 ? " ERROR AT "; (PEEK(203) + PEEK(204) * N256); " EXP = + "; PEEK(207); " ACT = "; PEEK(209) +1600 SOUND NO, PASS, 6, 8 : FOR I = N1 TO 5 : NEXT I : SOUND NO, NO, NO, NO +1699 REM SETUP NEXT BYTE TO TEST SO WE DONT STOP WITH FIRST ERROR +1700 IF PEEK(203) = N255 THEN POKE 204, (PEEK(204) + N1) : POKE 203, NO : GOTO 1900 +1800 POKE 203, (PEEK(203) + N1) +1900 POKE 764, N255 : POKE 559, 34 +1999 REM CONTINUE ONLY IF KEY PRESSED +2000 IF PEEK(764) = N255 THEN 2000 +2099 REM CONTINUE TESTING BAD RANGE +2100 GOTO 1300 +2199 REM GOOD TEST PASS SO SAY SO +2200 PASS = PASS + N1 : ? " GOOD PASS NUMBER "; PASS : SOUND NO, PASS, 10, 8 +2300 FOR I = N1 TO 5 : NEXT I : SOUND NO, NO, NO, NO +2399 REM STOP AND DISPLAY STUFF IF KEY IS PRESSED +2400 IF PEEK(764)< >N255 THEN 2600 +2499 REM CONTINUE WITH NEXT PASS +2500 GOTO 1100 +2600 POKE 764, N255 +2699 REM WAIT HERE UNTIL A KEY IS PRESSED +2700 POKE 559, 34 : IF PEEK(764) = N255 THEN 2700 +2799 REM CONTINUE WITH NEXT PASS +2800 GOTO 1100 +2899 REM READ IN MACHINE LANGUAGE PROGRAM +2900 FOR L = 1536 TO 1576 : READ H : POKE L, H : NEXT L : RETURN +3000 DATA 104, 169, 0, 160, 0, 24, 145, 203, 209, 203, 208, 18, 105, + 1, 208, 246, 200, 208, 242, 230, 204, 166, 204, 228, 206 +3100 DATA 208, 234, 133, 208, 96, 133, 207, 177, 203, 133, 209, 169, + 1, 133, 208, 96 diff --git a/test/MEMTEST2.BAS b/test/MEMTEST2.BAS new file mode 100644 index 0000000..56085c1 --- /dev/null +++ b/test/MEMTEST2.BAS @@ -0,0 +1,57 @@ +51 REM ATARI RAM TEST PROGRAM +52 REM BY ED STEWART 03/82 +53 REM 11025 SAGEBRUSH AVE +54 REM UNIONTOWN OHIO 44685 +99 REM SETUP SOME REQUIRED CONSTANTS +100 N1 = 1 : N2 = N1 + N1 : N255 = 255 : N256 = N255 + N1 +200 DIM S$(N2) : S$(N1, N1) = CHR$(157) : S$(N2, N2) = CHR$(159) +299 REM READ IN THE MACHINE LANGUAGE PROGRAM +300 GOSUB 2900 +399 REM GET LOW AND HIGH MEMORY BOUNDS +400 L = PEEK(15) * N256 : H = PEEK(742) * N256 : IF PEEK(14)< >NO THEN L = L + N256 +499 REM DISPLAY BOUNDS AND GET REPLY +500 ? CHR$(125); S$; "ATARI MEMORY TEST PROGRAM"; CHR$(155); S$; "MEMORY + BOUNDS ARE" +600 ? S$; "LOW = "; L : ? S$; "HIGH = "; H +700 ? S$; "GIVE TEST BOUNDS"; CHR$(155) +800 TRAP 800 : ? S$; "LOW = "; : INPUT LOW : IF LOW < L OR LOW > H THEN 800 +900 TRAP 900 : ? S$; "HIGH = "; : INPUT HIGH : IF HIGH > H OR HIGH < L OR + HIGH-LOW < N256 THEN 900 +999 REM SETUP BOUNDS FOR THE MLP +1000 POKE 205, NO : POKE 206, INT(HIGH/N256) +1100 TRAP 32767 : POKE 203, NO : POKE 204, INT(LOW/N256) +1200 POKE 764, N255 +1299 REM INVOKE THE MLP TO DO THE TEST +1300 POKE 559, NO : POKE 764, N255 : X = USR(1536) +1399 REM CHECK RETURN FROM MLP +1400 IF PEEK(208) = NO THEN 2200 +1499 REM SHOW MEMORY ERROR ON SCREEN +1500 ? " ERROR AT "; (PEEK(203) + PEEK(204) * N256); " EXP = + "; PEEK(207); " ACT = "; PEEK(209) +1600 SOUND NO, PASS, 6, 8 : FOR I = N1 TO 5 : NEXT I : SOUND NO, NO, NO, NO +1699 REM SETUP NEXT BYTE TO TEST SO WE DONT STOP WITH FIRST ERROR +1700 IF PEEK(203) = N255 THEN POKE 204, (PEEK(204) + N1) : POKE 203, NO : GOTO 1900 +1800 POKE 203, (PEEK(203) + N1) +1900 POKE 764, N255 : POKE 559, 34 +1999 REM CONTINUE ONLY IF KEY PRESSED +2000 IF PEEK(764) = N255 THEN 2000 +2099 REM CONTINUE TESTING BAD RANGE +2100 GOTO 1300 +2199 REM GOOD TEST PASS SO SAY SO +2200 PASS = PASS + N1 : ? " GOOD PASS NUMBER "; PASS : SOUND NO, PASS, 10, 8 +2300 FOR I = N1 TO 5 : NEXT I : SOUND NO, NO, NO, NO +2399 REM STOP AND DISPLAY STUFF IF KEY IS PRESSED +2400 IF PEEK(764)< >N255 THEN 2600 +2499 REM CONTINUE WITH NEXT PASS +2500 GOTO 1100 +2600 POKE 764, N255 +2699 REM WAIT HERE UNTIL A KEY IS PRESSED +2700 POKE 559, 34 : IF PEEK(764) = N255 THEN 2700 +2799 REM CONTINUE WITH NEXT PASS +2800 GOTO 1100 +2899 REM READ IN MACHINE LANGUAGE PROGRAM +2900 FOR L = 1536 TO 1576 : READ H : POKE L, H : NEXT L : RETURN +3000 DATA 104, 169, 0, 160, 0, 24, 145, 203, 209, 203, 208, 18, 105, + 1, 208, 246, 200, 208, 242, 230, 204, 166, 204, 228, 206 +3100 DATA 208, 234, 133, 208, 96, 133, 207, 177, 203, 133, 209, 169, + 1, 133, 208, 96 diff --git a/test/MEMTEST3.BAS b/test/MEMTEST3.BAS new file mode 100644 index 0000000..7c72ee7 --- /dev/null +++ b/test/MEMTEST3.BAS @@ -0,0 +1,52 @@ +51 REM ATARI RAM TEST PROGRAM +52 REM BY ED STEWART 03/82 +53 REM 11025 SAGEBRUSH AVE +54 REM UNIONTOWN OHIO 44685 +99 REM SETUP SOME REQUIRED CONSTANTS +100 N1 = 1 : N2 = N1 + N1 : N255 = 255 : N256 = N255 + N1 +200 DIM S$(N2) : S$(N1, N1) = CHR$(157) : S$(N2, N2) = CHR$(159) +299 REM READ IN THE MACHINE LANGUAGE PROGRAM +300 GOSUB 2900 +399 REM GET LOW AND HIGH MEMORY BOUNDS +400 L = PEEK(15) * N256 : H = PEEK(742) * N256 : IF PEEK(14) <> NO THEN L = L + N256 +499 REM DISPLAY BOUNDS AND GET REPLY +500 ? CHR$(125); S$; "ATARI MEMORY TEST PROGRAM"; CHR$(155); S$; "MEMORY BOUNDS ARE " +600 ? S$; "LOW = "; L : ? S$; "HIGH = "; H +700 ? S$; "GIVE TEST BOUNDS"; CHR$(155) +800 TRAP 800 : ? S$; "LOW = "; : INPUT LOW : IF LOW < L OR LOW > H THEN 800 +900 TRAP 900 : ? S$; "HIGH = "; : INPUT HIGH : IF HIGH > H OR HIGH < L OR HIGH-LOW < N256 THEN 900 +999 REM SETUP BOUNDS FOR THE MLP +1000 POKE 205, NO : POKE 206, INT(HIGH/N256) +1100 TRAP 32767 : POKE 203, NO : POKE 204, INT(LOW/N256) +1200 POKE 764, N255 +1299 REM INVOKE THE MLP TO DO THE TEST +1300 POKE 559, NO : POKE 764, N255 : X = USR(1536) +1399 REM CHECK RETURN FROM MLP +1400 IF PEEK(208) = NO THEN 2200 +1499 REM SHOW MEMORY ERROR ON SCREEN +1500 ? " ERROR AT "; (PEEK(203) + PEEK(204) * N256); " EXP = "; PEEK(207); " ACT = "; PEEK(209) +1600 SOUND NO, PASS, 6, 8 : FOR I = N1 TO 5 : NEXT I : SOUND NO, NO, NO, NO +1699 REM SETUP NEXT BYTE TO TEST SO WE DONT STOP WITH FIRST ERROR +1700 IF PEEK(203) = N255 THEN POKE 204, (PEEK(204) + N1) : POKE 203, NO : GOTO 1900 +1800 POKE 203, (PEEK(203) + N1) +1900 POKE 764, N255 : POKE 559, 34 +1999 REM CONTINUE ONLY IF KEY PRESSED +2000 IF PEEK(764) = N255 THEN 2000 +2099 REM CONTINUE TESTING BAD RANGE +2100 GOTO 1300 +2199 REM GOOD TEST PASS SO SAY SO +2200 PASS = PASS + N1 : ? " GOOD PASS NUMBER "; PASS : SOUND NO, PASS, 10, 8 +2300 FOR I = N1 TO 5 : NEXT I : SOUND NO, NO, NO, NO +2399 REM STOP AND DISPLAY STUFF IF KEY IS PRESSED +2400 IF PEEK(764) <> N255 THEN 2600 +2499 REM CONTINUE WITH NEXT PASS +2500 GOTO 1100 +2600 POKE 764, N255 +2699 REM WAIT HERE UNTIL A KEY IS PRESSED +2700 POKE 559, 34 : IF PEEK(764) = N255 THEN 2700 +2799 REM CONTINUE WITH NEXT PASS +2800 GOTO 1100 +2899 REM READ IN MACHINE LANGUAGE PROGRAM +2900 FOR L = 1536 TO 1576 : READ H : POKE L, H : NEXT L : RETURN +3000 DATA 104, 169, 0, 160, 0, 24, 145, 203, 209, 203, 208, 18, 105, 1, 208, 246, 200, 208, 242, 230, 204, 166, 204, 228, 206 +3100 DATA 208, 234, 133, 208, 96, 133, 207, 177, 203, 133, 209, 169, 1, 133, 208, 96 diff --git a/test/P32P1.BAS b/test/P32P1.BAS new file mode 100644 index 0000000..dccb395 Binary files /dev/null and b/test/P32P1.BAS differ diff --git a/test/P33P1.BAS b/test/P33P1.BAS new file mode 100644 index 0000000..1c796ee Binary files /dev/null and b/test/P33P1.BAS differ diff --git a/test/P34P1.BAS b/test/P34P1.BAS new file mode 100644 index 0000000..5033edb Binary files /dev/null and b/test/P34P1.BAS differ diff --git a/test/P34P2.BAS b/test/P34P2.BAS new file mode 100644 index 0000000..b6f2055 Binary files /dev/null and b/test/P34P2.BAS differ diff --git a/test/TEST.BAS b/test/TEST.BAS new file mode 100644 index 0000000..1e749bd Binary files /dev/null and b/test/TEST.BAS differ