apple2.ca - Terence J. Boldt's Apple II Site

Welcome
My ProDOS ROM-Drive
Serial Drive
 

Serial Virtual Drive

What is it?

This is a virtual hard drive accessible through the serial port. A 32 MB hard drive image (compatible with many emulators) is hosted on a Windows based system. The PC and Apple II are connected via null modem over the serial cable.

Why did I create it?

First I made the rom drive but it wasn't writable and was rather small. This is much slower than the hardware rom drive but allows for easy exchange of data between a real Apple II and emulators. Also, backups are much easier to do on a PC.

How do you get it?

Built it from source. This was an unfinished project started in 2001. It works but only on some models of the IIgs due to the hard coded serial port memory locations. Also, the checksums are not calculated and ignored so data loss is entirely possible and will go undetected. This was posted in 2006. Since that time, some of my source code and ideas have been incorporated into many projects including ADTPro, Gary Becker's FPGA Apple II and the upcoming ProDOS.

Note that in various user groups, other people have made numerous improvments to the speed and portability of this code in their own projects.

6502 ASM and Windows C++ Source (written in one weekend in 2001)
* 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


// 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;
}

   
All content Copyright ©1984-2003, Terence J. Boldt, unless noted otherwise.