/* * All Input is assumed to be going to RAM (no longer, ROM works, too.) * All Output is assumed to be coming from either RAM or ROM * */ #define Peek(a) (memory[(a)]) #define DPeek(a) ( memory[(a)]+( memory[(a)+1]<<8 ) ) #define Poke(a,b) ( memory[(a)] = (b) ) /* PM Notes: note the versions in Thors emu are able to deal with ROM better than these ones. These are only used for the 'fake' fast SIO replacement */ #include #include #include #ifdef WIN32 #include #include #include #else #ifdef VMS #include #include #else #include #ifndef AMIGA #include #endif #endif #endif #ifdef DJGPP #include "djgpp.h" #endif extern int DELAYED_SERIN_IRQ; extern int DELAYED_SEROUT_IRQ; extern int DELAYED_XMTDONE_IRQ; typedef int ATPtr; static char *rcsid = "$Id: sio.c,v 1.9 1998/02/21 14:55:21 david Exp $"; #define FALSE 0 #define TRUE 1 #include "atari.h" #include "cpu.h" #include "sio.h" #include "pokeysnd.h" #include "platform.h" #include "log.h" void CopyFromMem(int to, UBYTE *, int size); void CopyToMem(UBYTE *, ATPtr from, int size); #define MAGIC1 0x96 #define MAGIC2 0x02 struct ATR_Header { unsigned char magic1; unsigned char magic2; unsigned char seccountlo; unsigned char seccounthi; unsigned char secsizelo; unsigned char secsizehi; unsigned char hiseccountlo; unsigned char hiseccounthi; unsigned char gash[8]; }; #ifdef WIN32 extern void Atari_Set_Disk_LED( int unit, int state ); #endif /* * it seems, there are two different ATR formats with different handling for * DD sectors */ static int atr_format[MAX_DRIVES]; typedef enum Format { XFD, ATR } Format; static Format format[MAX_DRIVES]; static int disk[MAX_DRIVES] = {-1, -1, -1, -1, -1, -1, -1, -1}; static int sectorcount[MAX_DRIVES]; static int sectorsize[MAX_DRIVES]; static enum DriveStatus { Off, NoDisk, ReadOnly, ReadWrite } drive_status[MAX_DRIVES]; char sio_filename[MAX_DRIVES][FILENAME_LEN]; static char tmp_filename[MAX_DRIVES][FILENAME_LEN]; static UBYTE istmpfile[MAX_DRIVES] = {0, 0, 0, 0, 0, 0, 0, 0}; /* Serial I/O emulation support */ UBYTE CommandFrame[6]; int CommandIndex = 0; UBYTE DataBuffer[256 + 3]; char sio_status[256]; int DataIndex = 0; int TransferStatus = 0; int ExpectedBytes = 0; extern int opendcm( int diskno, char *infilename, char *outfilename ); extern int openzlib(int diskno, char *infilename, char *outfilename ); void SIO_Initialise(int *argc, char *argv[]) { int i; for (i = 0; i < MAX_DRIVES; i++) { strcpy(sio_filename[i], "Empty"); memset(tmp_filename[i], 0, FILENAME_LEN ); istmpfile[i] = 0; } TransferStatus = SIO_NoFrame; } /* This is to clean temp files that were not caught with a dismount. It should be called from Atari_Exit() */ void Clear_Temp_Files( void ) { int i; for( i = 0; i < MAX_DRIVES; i++ ) { if( istmpfile[i] != 0 ) { if( disk[i] != -1 ) { close(disk[i]); disk[i] = -1; } remove( tmp_filename[i] ); memset( tmp_filename[i], 0, FILENAME_LEN ); istmpfile[i] = 0; } } } int SIO_Mount(int diskno, char *filename) { char upperfile[ FILENAME_LEN ]; struct ATR_Header header; int fd, i; memset( upperfile, 0, FILENAME_LEN ); for( i=0; i < (int)strlen( filename ); i++ ) upperfile[i] = toupper( filename[i] ); /* If file is DCM, open it with opendcm to create a temp file */ if( !strcmp( &upperfile[ strlen( upperfile )-3 ], "DCM" ) ) { istmpfile[ diskno -1 ] = 1; drive_status[ diskno -1 ] = ReadOnly; fd = opendcm( diskno, filename, tmp_filename[diskno - 1] ); if( fd == -1 ) istmpfile[ diskno - 1] = 0; } else if( !strcmp( &upperfile[ strlen( upperfile )-3], "ATZ" ) || !strcmp( &upperfile[strlen( upperfile )-6], "ATR.GZ" ) || !strcmp( &upperfile[ strlen( upperfile )-3], "XFZ" ) || !strcmp( &upperfile[strlen( upperfile )-6], "XFD.GZ") ) { istmpfile[ diskno -1 ] = 1; drive_status[ diskno -1 ] = ReadOnly; fd = openzlib( diskno, filename, tmp_filename[ diskno - 1] ); if( fd == -1 ) istmpfile[ diskno - 1] = 0; } else /* Normal ATR, XFD disk */ { drive_status[diskno - 1] = ReadWrite; strcpy(sio_filename[diskno - 1], "Empty"); fd = open(filename, O_RDWR | O_BINARY, 0777); if (fd == -1) { fd = open(filename, O_RDONLY | O_BINARY, 0777); drive_status[diskno - 1] = ReadOnly; } } if (fd >= 0) { int status; ULONG file_length = lseek(fd, 0L, SEEK_END); lseek(fd, 0L, SEEK_SET); status = read(fd, &header, sizeof(struct ATR_Header)); if (status == -1) { close(fd); disk[diskno - 1] = -1; return FALSE; } strcpy(sio_filename[diskno - 1], filename); if ((header.magic1 == MAGIC1) && (header.magic2 == MAGIC2)) { format[diskno - 1] = ATR; sectorcount[diskno - 1] = header.hiseccounthi << 24 | header.hiseccountlo << 16 | header.seccounthi << 8 | header.seccountlo; sectorsize[diskno - 1] = header.secsizehi << 8 | header.secsizelo; sectorcount[diskno - 1] /= 8; if (sectorsize[diskno - 1] == 256) { sectorcount[diskno - 1] += 3; /* Compensate for first 3 sectors */ sectorcount[diskno - 1] /= 2; } /* ok, try to check if it's a SIO2PC ATR */ { ULONG len = (sectorcount[diskno - 1] - 3) * sectorsize[diskno - 1] + 16 + 3*128; if (file_length == len) atr_format[diskno - 1] = SIO2PC_ATR; else atr_format[diskno - 1] = OTHER_ATR; } #ifdef DEBUG Aprint("ATR: sectorcount = %d, sectorsize = %d", sectorcount[diskno - 1], sectorsize[diskno - 1]); Aprint("%s ATR\n", atr_format[diskno - 1] == SIO2PC_ATR ? "SIO2PC" : "???"); #endif } else { format[diskno - 1] = XFD; /* XFD might be of double density ! (PS) */ sectorsize[diskno - 1] = (file_length > (1040 * 128)) ? 256 : 128; sectorcount[diskno - 1] = file_length / sectorsize[diskno - 1]; } } else { drive_status[diskno - 1] = NoDisk; } disk[diskno - 1] = fd; return (disk[diskno - 1] != -1) ? TRUE : FALSE; } void SIO_Dismount(int diskno) { if (disk[diskno - 1] != -1) { close(disk[diskno - 1]); disk[diskno - 1] = -1; drive_status[diskno - 1] = NoDisk; strcpy(sio_filename[diskno - 1], "Empty"); if( istmpfile[ diskno - 1] ) { remove( tmp_filename[ diskno - 1] ); memset( tmp_filename[ diskno - 1], 0, FILENAME_LEN ); istmpfile[ diskno - 1] = 0; } } } void SIO_DisableDrive(int diskno) { drive_status[diskno - 1] = Off; strcpy(sio_filename[diskno - 1], "Off"); } void SizeOfSector(UBYTE unit, int sector, int *sz, ULONG * ofs) { int size = sectorsize[unit]; ULONG offset = 0; switch (format[unit]) { case XFD: offset = (sector - 1) * size; break; case ATR: if (sector < 4) /* special case for first three sectors in ATR image */ size = 128; if (atr_format[unit] == SIO2PC_ATR && sector >= 4) offset = (sector - 4) * size + 16 + 3*128; else offset = (sector - 1) * size + 16; break; default: Aprint("Fatal Error in atari_sio.c"); Atari800_Exit(FALSE); return; } if (sz) *sz = size; if (ofs) *ofs = offset; } int SeekSector(int unit, int sector) { ULONG offset; int size; sprintf(sio_status, "%d: %d", unit + 1, sector); SizeOfSector((UBYTE)unit, sector, &size, &offset); /* printf("Sector %x,Offset: %x\n",sector,offset); */ if ((long)offset > lseek(disk[unit], 0L, SEEK_END)) { #ifdef DEBUG Aprint("Seek after end of file"); #endif /* Atari800_Exit(FALSE); exit(1); */ } else lseek(disk[unit], offset, SEEK_SET); return size; } /* Unit counts from zero up */ int ReadSector(int unit, int sector, UBYTE * buffer) { int size; if (drive_status[unit] != Off) { if (disk[unit] != -1) { if (sector <= sectorcount[unit]) { #ifdef WIN32 Atari_Set_Disk_LED( unit, LED_READ ); #endif size = SeekSector(unit, sector); read(disk[unit], buffer, size); return 'C'; } else return 'E'; } else return 'N'; } else return 0; } int WriteSector(int unit, int sector, UBYTE * buffer) { int size; if (drive_status[unit] != Off) { if (disk[unit] != -1) { if (drive_status[unit] == ReadWrite) { #ifdef WIN32 Atari_Set_Disk_LED( unit, LED_WRITE ); #endif if (sector <= sectorcount[unit]) { size = SeekSector(unit, sector); write(disk[unit], buffer, size); return 'C'; } else return 'E'; } else return 'E'; } else return 'N'; } else return 0; } /* * FormatSingle is used on the XF551 for formating SS/DD and DS/DD too * however, I have to check if they expect a 256 byte buffer or if 128 * is ok either */ int FormatSingle(int unit, UBYTE * buffer) { int i; if (drive_status[unit] != Off) { if (disk[unit] != -1) { if (drive_status[unit] == ReadWrite) { sectorcount[unit] = 720; sectorsize[unit] = 128; format[unit] = XFD; SeekSector(unit, 1); memset(buffer, 0, 128); for (i = 1; i <= 720; i++) write(disk[unit], buffer, 128); memset(buffer, 0xff, 128); return 'C'; } else return 'E'; } else return 'N'; } else return 0; } int FormatEnhanced(int unit, UBYTE * buffer) { int i; if (drive_status[unit] != Off) { if (disk[unit] != -1) { if (drive_status[unit] == ReadWrite) { sectorcount[unit] = 1040; sectorsize[unit] = 128; format[unit] = XFD; SeekSector(unit, 1); memset(buffer, 0, 128); for (i = 1; i <= 1040; i++) write(disk[unit], buffer, 128); memset(buffer, 0xff, 128); return 'C'; } else return 'E'; } else return 'N'; } else return 0; } int WriteStatusBlock(int unit, UBYTE * buffer) { int size; if (drive_status[unit] != Off) { /* * We only care about the density and the sector count * here. Setting everything else right here seems to * be non-sense */ if (format[unit] == ATR) { /* I'm not sure about this density settings, my XF551 * honnors only the sector size and ignore the density */ size = buffer[6] * 256 + buffer[7]; if (size == 128 || size == 256) sectorsize[unit] = size; else if (buffer[5] == 8) { sectorsize[unit] = 256; } else { sectorsize[unit] = 128; } /* Note, that the number of heads are minus 1 */ sectorcount[unit] = buffer[0] * (buffer[2] * 256 + buffer[3]) * (buffer[4] + 1); return 'C'; } else return 'E'; } else return 0; } /* * My german "Atari Profi Buch" says, buffer[4] holds the number of * heads. However, BiboDos and my XF551 think thatīs the number minus 1. * * ??? */ int ReadStatusBlock(int unit, UBYTE * buffer) { int size, spt, heads; if (drive_status[unit] != Off) { spt = sectorcount[unit] / 40; heads = (spt > 26) ? 2 : 1; SizeOfSector((UBYTE)unit, 0x168, &size, NULL); buffer[0] = 40; /* # of tracks */ buffer[1] = 1; /* step rate. No idea what this means */ buffer[2] = spt >> 8; /* sectors per track. HI byte */ buffer[3] = spt & 0xFF; /* sectors per track. LO byte */ buffer[4] = heads-1; /* # of heads */ if (size == 128) { buffer[5] = 4; /* density */ buffer[6] = 0; /* HI bytes per sector */ buffer[7] = 128; /* LO bytes per sector */ } else { buffer[5] = 8; /* double density */ buffer[6] = 1; /* HI bytes per sector */ buffer[7] = 0; /* LO bytes per sector */ } buffer[8] = 1; /* drive is online */ buffer[9] = 192; /* transfer speed. Whatever this means */ return 'C'; } else return 0; } /* Status Request from Atari 400/800 Technical Reference Notes DVSTAT + 0 Command Status DVSTAT + 1 Hardware Status DVSTAT + 2 Timeout DVSTAT + 3 Unused Command Status Bits Bit 0 = 1 indicates an invalid command frame was received Bit 1 = 1 indicates an invalid data frame was received Bit 2 = 1 indicates that a PUT operation was unsuccessful Bit 3 = 1 indicates that the diskete is write protected Bit 4 = 1 indicates active/standby plus Bit 5 = 1 indicates double density Bit 7 = 1 indicates duel density disk (1050 format) */ int DriveStatus(int unit, UBYTE * buffer) { if (drive_status[unit] != Off) { if (drive_status[unit] == ReadWrite) { buffer[0] = (sectorsize[unit] == 256) ? (32 + 16) : (16); /* buffer[1] = (disk[unit] != -1) ? (128) : (0); */ buffer[1] = (disk[unit] != -1) ? (255) : (0); /* for StripPoker */ } else { buffer[0] = (sectorsize[unit] == 256) ? (32) : (0); buffer[1] = (disk[unit] != -1) ? (192) : (64); } if (sectorcount[unit] == 1040) buffer[0] |= 128; buffer[2] = 1; buffer[3] = 0; return 'C'; } else return 0; } void SIO(void) { int sector = DPeek(0x30a); UBYTE unit = Peek(0x301) - 1; UBYTE result = 0x00; ATPtr data = DPeek(0x304); int length = DPeek(0x308); int realsize = 0; int cmd = Peek(0x302); #ifdef SET_LED Atari_Set_LED(1); #endif /* printf("SIO: Unit %x,Sector %x,Data %x,Length %x,CMD %x\n",unit,sector,data,length,cmd); */ if (Peek(0x300) == 0x31) switch (cmd) { case 0x4e: /* Read Status Block */ if (12 == length) { result = ReadStatusBlock(unit, DataBuffer); if (result == 'C') CopyToMem(DataBuffer, data, 12); } else result = 'E'; break; case 0x4f: /* Write Status Block */ if (12 == length) { CopyFromMem(data, DataBuffer, 12); result = WriteStatusBlock(unit, DataBuffer); } else result = 'E'; break; case 0x50: /* Write */ case 0x57: SizeOfSector(unit, sector, &realsize, NULL); if (realsize == length) { CopyFromMem(data, DataBuffer, realsize); result = WriteSector(unit, sector, DataBuffer); } else result = 'E'; break; case 0x52: /* Read */ SizeOfSector(unit, sector, &realsize, NULL); if (realsize == length) { result = ReadSector(unit, sector, DataBuffer); if (result == 'C') CopyToMem(DataBuffer, data, realsize); } else result = 'E'; break; case 0x53: /* Status */ if (4 == length) { result = DriveStatus(unit, DataBuffer); CopyToMem(DataBuffer, data, 4); } else result = 'E'; break; case 0x21: /* Single Density Format */ if (length == 128) { result = FormatSingle(unit, DataBuffer); if (result == 'C') CopyToMem(DataBuffer, data, realsize); } else result = 'E'; break; case 0x22: /* Enhanced Density Format */ if (length == 128) { result = FormatEnhanced(unit, DataBuffer); if (result == 'C') CopyToMem(DataBuffer, data, realsize); } else result = 'E'; break; case 0x66: /* US Doubler Format - I think! */ result = 'A'; /* Not yet supported... to be done later... */ break; default: result = 'N'; } switch (result) { case 0x00: /* Device disabled, generate timeout */ regY = 138; SetN; break; case 'A': /* Device acknoledge */ case 'C': /* Operation complete */ regY = 1; ClrN; break; case 'N': /* Device NAK */ regY = 144; SetN; break; case 'E': /* Device error */ default: regY = 146; SetN; break; } Poke(0x0303, regY); #ifdef SET_LED Atari_Set_LED(0); #endif } void SIO_Initialize(void) { TransferStatus = SIO_NoFrame; } UBYTE ChkSum(UBYTE * buffer, UWORD length) { int i; int checksum = 0; for (i = 0; i < length; i++, buffer++) { checksum += *buffer; while (checksum > 255) checksum -= 255; } return checksum; } void Command_Frame(void) { int unit; int result = 'A'; int sector; int realsize; sector = CommandFrame[2] | (((UWORD) (CommandFrame[3])) << 8); unit = CommandFrame[0] - '1'; if (unit > 8) { /* UBYTE - range ! */ Aprint("Unknown command frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); result = 0; } else switch (CommandFrame[1]) { case 0x4e: /* Read Status */ DataBuffer[0] = ReadStatusBlock(unit, DataBuffer + 1); DataBuffer[13] = ChkSum(DataBuffer + 1, 12); DataIndex = 0; ExpectedBytes = 14; TransferStatus = SIO_ReadFrame; DELAYED_SERIN_IRQ += SERIN_INTERVAL; break; case 0x4f: ExpectedBytes = 13; DataIndex = 0; TransferStatus = SIO_WriteFrame; break; case 0x50: /* Write */ case 0x57: SizeOfSector((UBYTE)unit, sector, &realsize, NULL); ExpectedBytes = realsize + 1; DataIndex = 0; TransferStatus = SIO_WriteFrame; break; case 0x52: /* Read */ SizeOfSector((UBYTE)unit, sector, &realsize, NULL); DataBuffer[0] = ReadSector(unit, sector, DataBuffer + 1); DataBuffer[1 + realsize] = ChkSum(DataBuffer + 1, (UWORD)realsize); DataIndex = 0; ExpectedBytes = 2 + realsize; TransferStatus = SIO_ReadFrame; DELAYED_SERIN_IRQ += SERIN_INTERVAL; break; case 0x53: /* Status */ DataBuffer[0] = DriveStatus(unit, DataBuffer + 1); DataBuffer[1 + 4] = ChkSum(DataBuffer + 1, 4); DataIndex = 0; ExpectedBytes = 6; TransferStatus = SIO_ReadFrame; DELAYED_SERIN_IRQ += SERIN_INTERVAL; break; case 0x21: /* Single Density Format */ DataBuffer[0] = FormatSingle(unit, DataBuffer + 1); DataBuffer[1 + 128] = ChkSum(DataBuffer + 1, 128); DataIndex = 0; ExpectedBytes = 2 + 128; TransferStatus = SIO_FormatFrame; DELAYED_SERIN_IRQ += SERIN_INTERVAL; break; case 0x22: /* Duel Density Format */ DataBuffer[0] = FormatEnhanced(unit, DataBuffer + 1); DataBuffer[1 + 128] = ChkSum(DataBuffer + 1, 128); DataIndex = 0; ExpectedBytes = 2 + 128; TransferStatus = SIO_FormatFrame; DELAYED_SERIN_IRQ += SERIN_INTERVAL; break; case 0x66: /* US Doubler Format - I think! */ result = 'A'; /* Not yet supported... to be done later... */ break; default: Aprint("Command frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); break; result = 0; } if (result == 0) TransferStatus = SIO_NoFrame; } /* Enable/disable the command frame */ void SwitchCommandFrame(int onoff) { if (onoff) { /* Enabled */ if (TransferStatus != SIO_NoFrame) Aprint("Unexpected command frame %x.", TransferStatus); CommandIndex = 0; DataIndex = 0; ExpectedBytes = 5; TransferStatus = SIO_CommandFrame; #ifdef SET_LED Atari_Set_LED(1); #endif /* printf("Command frame expecting.\n"); */ } else { if (TransferStatus != SIO_StatusRead && TransferStatus != SIO_NoFrame && TransferStatus != SIO_ReadFrame) { if (!(TransferStatus == SIO_CommandFrame && CommandIndex == 0)) Aprint("Command frame %02x unfinished.", TransferStatus); TransferStatus = SIO_NoFrame; } CommandIndex = 0; } } UBYTE WriteSectorBack(void) { UWORD sector; UBYTE unit; UBYTE result; sector = CommandFrame[2] | (((UWORD) (CommandFrame[3])) << 8); unit = CommandFrame[0] - '1'; if (unit > 8) { /* UBYTE range ! */ result = 0; } else switch (CommandFrame[1]) { case 0x4f: /* Write Status Block */ result = WriteStatusBlock(unit, DataBuffer); break; case 0x50: /* Write */ case 0x57: result = WriteSector(unit, sector, DataBuffer); break; default: result = 'E'; } return result; } /* Put a byte that comes out of POKEY. So get it here... */ void SIO_PutByte(int byte) { UBYTE sum, result; switch (TransferStatus) { case SIO_CommandFrame: if (CommandIndex < ExpectedBytes) { CommandFrame[CommandIndex++] = byte; if (CommandIndex >= ExpectedBytes) { /* printf("%x\n",CommandFrame[0]); */ if (((CommandFrame[0] >= 0x31) && (CommandFrame[0] <= 0x38))) { TransferStatus = SIO_StatusRead; /* printf("Command frame done.\n"); */ DELAYED_SERIN_IRQ += SERIN_INTERVAL + ACK_INTERVAL; } else TransferStatus = SIO_NoFrame; } } else { Aprint("Invalid command frame!"); TransferStatus = SIO_NoFrame; } break; case SIO_WriteFrame: /* Expect data */ if (DataIndex < ExpectedBytes) { DataBuffer[DataIndex++] = byte; if (DataIndex >= ExpectedBytes) { sum = ChkSum(DataBuffer, (UWORD)(ExpectedBytes - 1)); if (sum == DataBuffer[ExpectedBytes - 1]) { result = WriteSectorBack(); if (result) { DataBuffer[0] = 'A'; DataBuffer[1] = result; DataIndex = 0; ExpectedBytes = 2; DELAYED_SERIN_IRQ += SERIN_INTERVAL + ACK_INTERVAL; TransferStatus = SIO_FinalStatus; /* printf("Sector written, result= %x.\n",result); */ } else TransferStatus = SIO_NoFrame; } else { DataBuffer[0] = 'E'; DataIndex = 0; ExpectedBytes = 1; DELAYED_SERIN_IRQ += SERIN_INTERVAL + ACK_INTERVAL; TransferStatus = SIO_FinalStatus; } } } else { Aprint("Invalid data frame!"); } break; default: Aprint("Unexpected data output :%x", byte); } DELAYED_SEROUT_IRQ += SEROUT_INTERVAL; } /* Get a byte from the floppy to the pokey. */ int SIO_GetByte(void) { int byte = 0; switch (TransferStatus) { case SIO_StatusRead: byte = 'A'; /* Command acknoledged */ /* printf("Command status read\n"); */ Command_Frame(); /* Handle now the command */ break; case SIO_FormatFrame: TransferStatus = SIO_ReadFrame; DELAYED_SERIN_IRQ += SERIN_INTERVAL << 3; case SIO_ReadFrame: if (DataIndex < ExpectedBytes) { byte = DataBuffer[DataIndex++]; if (DataIndex >= ExpectedBytes) { TransferStatus = SIO_NoFrame; #ifdef SET_LED Atari_Set_LED(0); #endif /* printf("Transfer complete.\n"); */ } else { DELAYED_SERIN_IRQ += SERIN_INTERVAL; } } else { Aprint("Invalid read frame!"); TransferStatus = SIO_NoFrame; } break; case SIO_FinalStatus: if (DataIndex < ExpectedBytes) { byte = DataBuffer[DataIndex++]; if (DataIndex >= ExpectedBytes) { TransferStatus = SIO_NoFrame; #ifdef SET_LED Atari_Set_LED(0); #endif /* printf("Write complete.\n"); */ } else { if (DataIndex == 0) DELAYED_SERIN_IRQ += SERIN_INTERVAL + ACK_INTERVAL; else DELAYED_SERIN_IRQ += SERIN_INTERVAL; } } else { Aprint("Invalid read frame!"); #ifdef SET_LED Atari_Set_LED(0); #endif TransferStatus = SIO_NoFrame; } break; default: Aprint("Unexpected data reading!"); break; } return byte; } void CopyFromMem(ATPtr from, UBYTE * to, int size) { memcpy(to, from + memory, size); } extern UBYTE attrib[65536]; void CopyToMem(UBYTE * from, ATPtr to, int size) { int i; for (i = 0; i < size; i++) { if (!attrib[to]) Poke(to, *from); from++, to++; } }