Original code from 2001

This commit is contained in:
Terence Boldt 2021-10-09 08:02:30 -04:00
parent da84677439
commit d06e90a576
3 changed files with 414 additions and 1 deletions

View File

@ -1 +1,25 @@
# Serial-Virtual-Drive
# 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

156
src/Apple2VirtualDrive.c Normal file
View File

@ -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 <windows.h>
#include <conio.h>
#include <stdio.h>
#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;
}

233
src/SERIAL.DRIVE.ASM Normal file
View File

@ -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
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