diff --git a/README.md b/README.md index 8de6b4a..f39287c 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ -# Serial-Virtual-Drive \ No newline at end of file +# Serial-Virtual-Drive + +Also known as Virtual Serial Drive. + +This creates a virtual drive over the serial port on an Apple II computer where the drive data is hosted by another PC. I wrote this in one weekend back in 2001 out of necessity. I was the final Apple II librarian for L.O.G.I.C.'s shareware collection and wanted to preserve the disks from the 1980's before they became unreadable. At the time, I made it work with just an Apple IIgs as it had a built-in serial port. Years later, others discovered the code on my website and it was improved and used in many other projects. This code repo is not maintained and only posted here for historical purposes. + +## Some noteable projects using code inspired by this project + +https://github.com/ADTPro/adtpro + +https://forum.arduino.cc/t/virtual-serial-drive-for-apple-ii-ported-to-arduino/143166 + +https://www.applefritter.com/node/19543 + +## L.O.G.I.C Disk Archive + +https://www.applefritter.com/content/logic-toronto-user-groupbbs-apple-ii-disk-library-now-internet-archive + +https://archive.org/search.php?query=creator%3A%22LOGIC%20%28“Loyal%20Ontario%20Group%20Interested%20In%20Computers”%29%22 + +## Replacement project + +Since a cheap, tiny and energy efficient Raspberry Pi Zero W is more powerful than the computer that originally hosted the Serial Virtual Drive code, I designed a new expansion board with the Pi attached which can host many virtual drives on the microSD card. It also can allow the Apple II to execute Linux commands, connect to wifi and more. + +https://github.com/tjboldt/Apple2-IO-RPi diff --git a/src/Apple2VirtualDrive.c b/src/Apple2VirtualDrive.c new file mode 100644 index 0000000..05ed906 --- /dev/null +++ b/src/Apple2VirtualDrive.c @@ -0,0 +1,156 @@ +// Apple II virtual drive via COM1 +// Copyright (C)2001 Terence J. Boldt +// You may copy and modify this source if +// you give me credit for it. + + +#include +#include +#include + +#define READBLOCK 1 +#define WRITEBLOCK 2 + +// READ: the Apple sends a command block with READBLOCK, blocknumber and an XOR of those 3 bytes +// in response, the PC echos the command block and then sends 512 byte block followed by +// a checksum (XOR of all bytes in block) + +// WRITE: the Apple sends a command block with WRITEBLOCK, blocknumber and checksum +// it also sends the 512 block and a checksum +// in response, the PC echos the command if successful or garbage command if failure + +int main(int argc, char* argv[]) +{ + HANDLE hPort, hFile; + DCB dcb; + DWORD byteswritten; + COMMTIMEOUTS commtimeouts; + DWORD bytesread; + unsigned char datablock[513]; + unsigned char command[6]; + boolean cmdlineerr = false; + + if (argc != 3) { + cmdlineerr = true; + } + + if (cmdlineerr) { + printf("Apple2VirtualDrive (c)2001 Terence J. Boldt\n"); + printf("Note: This program requires a null modem connection\n"); + printf(" to an Apple II computer at 19,200 bps and the\n"); + printf(" Apple must be running SERIAL.DRIVE under ProDOS.\n\n"); + printf("USAGE: Apple2VirtualDrive COMx FILENAME.hdv\n"); + return 1; + } + + printf("\nWaiting for command from Apple II via COM1...\n"); + + // set up comm port + hPort=CreateFile( argv[1], + GENERIC_READ | GENERIC_WRITE, //bidirectional + 0, + NULL, //no security + OPEN_EXISTING, //this must be set; the ports are already created + FILE_ATTRIBUTE_NORMAL, // maybe with | FILE_FLAG_OVERLAPPED + NULL ); + if (hPort == INVALID_HANDLE_VALUE) { + printf("Failed to connect to %s.",argv[1]); + return 1; + } + + GetCommState(hPort, &dcb); + + dcb.BaudRate = CBR_19200; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.fBinary = 1; + dcb.fParity = 0; + dcb.fOutX = 0; + dcb.fInX = 0; + + SetCommState(hPort, &dcb); + + commtimeouts.ReadIntervalTimeout = 0; + commtimeouts.ReadTotalTimeoutConstant = 0; + commtimeouts.ReadTotalTimeoutMultiplier = 1000; + commtimeouts.WriteTotalTimeoutConstant = 0; + commtimeouts.WriteTotalTimeoutMultiplier = 1000; + + SetCommTimeouts(hPort, &commtimeouts); + + // set up hard drive file - create if does not exist + hFile=CreateFile( argv[2], + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, //no security + OPEN_ALWAYS, + FILE_FLAG_RANDOM_ACCESS, + NULL ); + + if (hFile == INVALID_HANDLE_VALUE) { + CloseHandle(hPort); + printf("Failed to open or create file %s.",argv[2]); + return 1; + } + + + while( !_kbhit() ) { + + + // wait for a four byte command from the Apple II + do { + ReadFile(hPort, &command, 4, &bytesread, NULL); + } + while (bytesread == 0 && !_kbhit()); + + if (!_kbhit()) { + if (bytesread == 4) { + if (command[0] == WRITEBLOCK) { + printf("write block %04X\n",command[2]*256+command[1]); + // read data block from port + ReadFile(hPort, &datablock, 513, &bytesread, NULL); + // check data integrity + // write to file + SetFilePointer(hFile, // handle to file + 512*command[1]+131072*command[2], // bytes to move pointer + NULL, // bytes to move pointer + FILE_BEGIN // starting point + ); + WriteFile(hFile, datablock, 512, &byteswritten, NULL); + // echo command to port + WriteFile(hPort, command, 4, &byteswritten, NULL); + } + else if (command[0] == READBLOCK) { + printf("read block %04X\n",command[2]*256+command[1]); + // check data integrity + // echo command to port + WriteFile(hPort, command, 4, &byteswritten, NULL); + // read from file + SetFilePointer(hFile, // handle to file + 512*command[1]+131072*command[2], // bytes to move pointer + NULL, // bytes to move pointer + FILE_BEGIN // starting point + ); + ReadFile(hFile, datablock, 512, &bytesread, NULL); + // calculate checksum + // write block to port + WriteFile(hPort, datablock, 513, &byteswritten, NULL); + } + else { + printf("invalid command: %02X %02X %02X %02X\n",command[0], command[1], command[2], command[3]); + } + } + else { + printf("invalid command %d bytes instead of 4: %02X %02X %02X %02X\n",bytesread, command[0], command[1], command[2], command[3]); + } + } + } + + _getch(); // clear key + + CloseHandle(hPort); + CloseHandle(hFile); + + return 0; +} diff --git a/src/SERIAL.DRIVE.ASM b/src/SERIAL.DRIVE.ASM new file mode 100644 index 0000000..371020c --- /dev/null +++ b/src/SERIAL.DRIVE.ASM @@ -0,0 +1,233 @@ +* VIRTUAL HARD DRIVE VIA SERIAL PORT TO PC +* (C)2001 TERENCE J. BOLDT +* You may copy and modify this code if you give me credit for it. + +* PRODOS GLOBAL PAGE VALUES +DEV2S1 EQU $BF14 ;POINTER FOR SLOT 2 DRIVE 1 DRIVER +DEVCNT EQU $BF31 ;DEVICE COUNT -1 +DEVLST EQU $BF32 ;DEVICE LIST + +* PRODOS ZERO PAGE VALUES +COMMAND EQU $42 ;PRODOS COMMAND +UNIT EQU $43 ;PRODOS SLOT/DRIVE +BUFLO EQU $44 ;LOW BUFFER +BUFHI EQU $45 ;HI BUFFER +BLKLO EQU $46 ;LOW BLOCK +BLKHI EQU $47 ;HI BLOCK + +* PRODOS ERROR CODES +IOERR EQU $27 +NODEV EQU $28 +WPERR EQU $2B + +* GS SPECIFIC SERIAL PORT +SSCINIT EQU $C245 +SSCREAD EQU $C246 +SSCWRITE EQU $C247 +SSCSTAT EQU $C248 + +* ROM LOCATIONS +RESETC8 EQU $CFFF + + ORG $1800 + + +* INITIALISE DRIVER +INIT EQU * +* ADD POINTER TO DRIVER + LDA #DRIVER + STA DEV2S1+1 +* ADD TO DEVICE LIST + INC DEVCNT + LDY DEVCNT + LDA #$20 ;SLOT 2 DRIVE 1 + STA DEVLST,Y + + RTS + +* DRIVER CODE +DRIVER EQU * +* CHECK THAT WE HAVE THE RIGHT DRIVE + LDA UNIT + CMP #$20 ;SLOT 2 DRIVE 1 + BEQ DOCMD ;YEP, DO COMMAND + SEC ;NOPE, FAIL + LDA #NODEV + RTS + +* CHECK WHICH COMMAND IS REQUESTED +DOCMD EQU * + LDA COMMAND + BNE NOTSTAT ;0 IS STATUS + JMP GETSTAT +NOTSTAT CMP #$01 + BNE NOREAD ;1 IS READ + JMP READBLK +NOREAD CMP #$02 + BNE NOWRITE ;2 IS WRITE + JMP WRITEBLK +NOWRITE LDA #$00 ; CLEAR ERROR + CLC + RTS + + +* STATUS +GETSTAT EQU * + LDA #$00 + LDX #$FF + LDY #$FF + CLC + RTS + +* READ +READBLK EQU * +* SEND COMMAND TO PC + LDA RESETC8 ; RESET C8 PAGE + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCINIT ; INIT SERIAL CARD + LDA #$01 ; SEND ^A Z TO SERIAL DRIVER TO ZAP CONTROL CHARS + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA #$5A + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA #$01 ; READ COMMAND + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA BLKLO + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA BLKHI + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA #$00 ; CHECKSUM + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE +* READ ECHO'D COMMAND AND VERIFY + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD +* READ BLOCK AND VERIFY + LDX #$00 +RDBKLOOP LDY #$00 +RDLOOP EQU * + PHX + PHY + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + PLY + PLX + STA (BUFLO),Y + INY + BNE RDLOOP + + INC BUFHI + INX + CPX #$02 + BNE RDBKLOOP + + DEC BUFHI + + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + + LDA #$00 + CLC + RTS + +* WRITE +WRITEBLK EQU * +* SEND COMMAND TO PC + LDA RESETC8 + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCINIT ; INIT SERIAL CARD + LDA #$01 ; SEND ^A Z TO SERIAL DRIVER TO ZAP CONTROL CHARS + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA #$5A + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA #$02 ; WRITE COMMAND + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA BLKLO + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA BLKHI + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + LDA #$00 ; CHECKSUM + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + +* WRITE BLOCK AND CHECKSUM + LDX #$00 +WRBKLOOP LDY #$00 +WRLOOP EQU * + PHX + PHY + LDA (BUFLO),Y + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + PLY + PLX + INY + BNE WRLOOP + + INC BUFHI + INX + CPX #$02 + BNE WRBKLOOP + + DEC BUFHI + + LDA #$00 + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCWRITE + +* READ ECHO'D COMMAND AND VERIFY + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + LDX #$C2 ; SET X AND Y FOR SSC CALLS + LDY #$20 + JSR SSCREAD + + LDA #$00 + CLC + RTS \ No newline at end of file