X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Ffloppydrive.cpp;fp=src%2Ffloppydrive.cpp;h=056ca2b7c5a5b3578eccfba4579c8d85cbfe405a;hb=66a747c7d92af86ebdfaf136cf5506a7ab714fd0;hp=0000000000000000000000000000000000000000;hpb=6bdd378e27a9bbf3a074e9c33c61195c8fcfef1c;p=apple2 diff --git a/src/floppydrive.cpp b/src/floppydrive.cpp new file mode 100644 index 0000000..056ca2b --- /dev/null +++ b/src/floppydrive.cpp @@ -0,0 +1,1500 @@ +// +// Apple 2 floppy disk support +// +// by James Hammons +// (c) 2005-2018 Underground Software +// +// JLH = James Hammons +// +// WHO WHEN WHAT +// --- ---------- ----------------------------------------------------------- +// JLH 12/03/2005 Created this file +// JLH 12/15/2005 Fixed nybblization functions to work properly +// JLH 12/27/2005 Added blank disk creation, fixed saving to work properly +// + +#include "floppydrive.h" + +#include +#include +#include "apple2.h" +#include "crc32.h" +#include "firmware.h" +#include "log.h" +#include "mmu.h" +#include "video.h" // For message spawning... Though there's probably a + // better approach than this! + +// Useful enums + +enum { IO_MODE_READ, IO_MODE_WRITE }; + +// FloppyDrive class variable initialization + +uint8_t FloppyDrive::doSector[16] = { + 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF }; +uint8_t FloppyDrive::poSector[16] = { + 0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF }; +uint8_t FloppyDrive::wozHeader[9] = "WOZ1\xFF\x0A\x0D\x0A"; +uint8_t FloppyDrive::wozHeader2[9] = "WOZ2\xFF\x0A\x0D\x0A"; +uint8_t FloppyDrive::standardTMAP[141] = { + 0, 0, 0xFF, 1, 1, 1, 0xFF, 2, 2, 2, 0xFF, 3, 3, 3, 0xFF, 4, 4, 4, 0xFF, + 5, 5, 5, 0xFF, 6, 6, 6, 0xFF, 7, 7, 7, 0xFF, 8, 8, 8, 0xFF, 9, 9, 9, 0xFF, + 10, 10, 10, 0xFF, 11, 11, 11, 0xFF, 12, 12, 12, 0xFF, 13, 13, 13, 0xFF, + 14, 14, 14, 0xFF, 15, 15, 15, 0xFF, 16, 16, 16, 0xFF, 17, 17, 17, 0xFF, + 18, 18, 18, 0xFF, 19, 19, 19, 0xFF, 20, 20, 20, 0xFF, 21, 21, 21, 0xFF, + 22, 22, 22, 0xFF, 23, 23, 23, 0xFF, 24, 24, 24, 0xFF, 25, 25, 25, 0xFF, + 26, 26, 26, 0xFF, 27, 27, 27, 0xFF, 28, 28, 28, 0xFF, 29, 29, 29, 0xFF, + 30, 30, 30, 0xFF, 31, 31, 31, 0xFF, 32, 32, 32, 0xFF, 33, 33, 33, 0xFF, + 34, 34, 34, 0xFF, 0xFF, 0xFF +}; +uint8_t FloppyDrive::bitMask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; +uint8_t FloppyDrive::sequencerROM[256] = { +0x18, 0x18, 0x18, 0x18, 0x0A, 0x0A, 0x0A, 0x0A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x2D, 0x38, 0x2D, 0x38, 0x0A, 0x0A, 0x0A, 0x0A, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, +0x38, 0x28, 0xD8, 0x08, 0x0A, 0x0A, 0x0A, 0x0A, 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B, +0x48, 0x48, 0xD8, 0x48, 0x0A, 0x0A, 0x0A, 0x0A, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, +0x58, 0x58, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, +0x68, 0x68, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, +0x78, 0x78, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, +0x88, 0x88, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, +0x98, 0x98, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, +0x29, 0xA8, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, +0xBD, 0xB8, 0xCD, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, +0x59, 0xC8, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, +0xD9, 0xA0, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, +0x08, 0xE8, 0xD8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, +0xFD, 0xF8, 0xFD, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, +0x4D, 0xE0, 0xDD, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08 +}; + +char FloppyDrive::nameBuf[MAX_PATH]; + + +// Static in-line functions, for clarity & speed, mostly for reading values out +// of the WOZ struct, which stores its data in LE; some for swapping variables +static inline uint16_t Uint16LE(uint16_t v) +{ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + return ((v & 0xFF) << 8) | ((v & 0xFF00) >> 8); +#else + return v; +#endif +} + + +static inline uint32_t Uint32LE(uint32_t v) +{ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + return ((v & 0xFF) << 24) | ((v & 0xFF00) << 8) + | ((v & 0xFF0000) >> 8) | ((v & 0xFF000000) >> 24); +#else + return v; +#endif +} + + +static inline void Swap(uint8_t & a, uint8_t & b) +{ + uint8_t t = a; + a = b; + b = t; +} + + +static inline void Swap(uint32_t & a, uint32_t & b) +{ + uint32_t t = a; + a = b; + b = t; +} + + +static inline void Swap(bool & a, bool & b) +{ + bool t = a; + a = b; + b = t; +} + + +static inline void Swap(uint8_t * & a, uint8_t * & b) +{ + uint8_t * t = a; + a = b; + b = t; +} + + +static inline void Swap(WOZ * & a, WOZ * & b) +{ + WOZ * t = a; + a = b; + b = t; +} + + +// FloppyDrive class implementation... + +FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), ioHappened(false) +{ + phase[0] = phase[1] = 0; + headPos[0] = headPos[1] = 0; + trackLength[0] = trackLength[1] = 51200; + disk[0] = disk[1] = NULL; + woz[0] = woz[1] = NULL; + diskSize[0] = diskSize[1] = 0; + diskType[0] = diskType[1] = DT_EMPTY; + imageDirty[0] = imageDirty[1] = false; + imageName[0][0] = imageName[1][0] = 0; // Zero out filenames +} + + +FloppyDrive::~FloppyDrive() +{ + if (disk[0]) + delete[] disk[0]; + + if (disk[1]) + delete[] disk[1]; +} + + +bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/) +{ + WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum); + + if (driveNum > 1) + { + WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum); + return false; + } + + // Zero out filename, in case it doesn't load + imageName[driveNum][0] = 0; + + FILE * fp = fopen(filename, "rb"); + + if (fp == NULL) + { + WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename); + return false; + } + + if (disk[driveNum]) + delete[] disk[driveNum]; + + fseek(fp, 0, SEEK_END); + diskSize[driveNum] = ftell(fp); + fseek(fp, 0, SEEK_SET); + disk[driveNum] = new uint8_t[diskSize[driveNum]]; + woz[driveNum] = (WOZ *)disk[driveNum]; + fread(disk[driveNum], 1, diskSize[driveNum], fp); + + fclose(fp); +//printf("Read disk image: %u bytes.\n", diskSize); + DetectImageType(filename, driveNum); + strcpy(imageName[driveNum], filename); + + WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum); + + return true; +} + + +bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/) +{ +// comment out for now... +#if 0 + // Various sanity checks... + if (driveNum > 1) + { + WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum); + return false; + } + + if (!imageDirty[driveNum]) + { + WriteLog("FLOPPY: No need to save unchanged image...\n"); + return false; + } + + if (imageName[driveNum][0] == 0) + { + WriteLog("FLOPPY: Attempted to save non-existant image!\n"); + return false; + } + + // Finally, write the damn image + FILE * fp = fopen(imageName[driveNum], "wb"); + + if (fp == NULL) + { + WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]); + return false; + } + + fwrite(disk[driveNum], 1, diskSize[driveNum], fp); + fclose(fp); + + WriteLog("FLOPPY: Successfully wrote image file '%s'...\n", imageName[driveNum]); + + return true; +#else + char * ext = strrchr(imageName[driveNum], '.'); + + if ((ext != NULL) && (diskType[driveNum] != DT_WOZ)) + memcpy(ext, ".woz", 4); + + return SaveWOZ(driveNum); +#endif +} + + +bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/) +{ + strncpy(imageName[driveNum], filename, MAX_PATH); + // Ensure a NULL terminated string here, as strncpy() won't terminate the + // string if the source length is >= MAX_PATH + imageName[driveNum][MAX_PATH - 1] = 0; + return SaveImage(driveNum); +} + + +void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/) +{ + if (disk[driveNum] != NULL) + delete disk[driveNum]; + + InitWOZ(driveNum); + diskType[driveNum] = DT_WOZ; + strcpy(imageName[driveNum], "newblank.woz"); + SpawnMessage("New blank image inserted in drive %u...", driveNum); +} + + +void FloppyDrive::SwapImages(void) +{ +#if 0 +WriteLog("SwapImages BEFORE:\n"); +WriteLog("\tdisk[0]=%X, disk[1]=%X\n", disk[0], disk[1]); +WriteLog("\twoz[0]=%X, woz[1]=%X\n", woz[0], woz[1]); +WriteLog("\tdiskSize[0]=%X, diskSize[1]=%X\n", diskSize[0], diskSize[1]); +WriteLog("\tdiskType[0]=%X, diskType[1]=%X\n", diskType[0], diskType[1]); +WriteLog("\timageDirty[0]=%X, imageDirty[1]=%X\n", imageDirty[0], imageDirty[1]); +WriteLog("\tphase[0]=%X, phase[1]=%X\n", phase[0], phase[1]); +WriteLog("\theadPos[0]=%X, headPos[1]=%X\n", headPos[0], headPos[1]); +WriteLog("\tcurrentPos[0]=%X, currentPos[1]=%X\n", currentPos[0], currentPos[1]); +#endif + char imageNameTmp[MAX_PATH]; + + memcpy(imageNameTmp, imageName[0], MAX_PATH); + memcpy(imageName[0], imageName[1], MAX_PATH); + memcpy(imageName[1], imageNameTmp, MAX_PATH); + + Swap(disk[0], disk[1]); + Swap(woz[0], woz[1]); + Swap(diskSize[0], diskSize[1]); + Swap(diskType[0], diskType[1]); + Swap(imageDirty[0], imageDirty[1]); + + Swap(phase[0], phase[1]); + Swap(headPos[0], headPos[1]); + Swap(currentPos[0], currentPos[1]); +SpawnMessage("Drive 0: %s...", imageName[0]); +#if 0 +WriteLog("SwapImages AFTER:\n"); +WriteLog("\tdisk[0]=%X, disk[1]=%X\n", disk[0], disk[1]); +WriteLog("\twoz[0]=%X, woz[1]=%X\n", woz[0], woz[1]); +WriteLog("\tdiskSize[0]=%X, diskSize[1]=%X\n", diskSize[0], diskSize[1]); +WriteLog("\tdiskType[0]=%X, diskType[1]=%X\n", diskType[0], diskType[1]); +WriteLog("\timageDirty[0]=%X, imageDirty[1]=%X\n", imageDirty[0], imageDirty[1]); +WriteLog("\tphase[0]=%X, phase[1]=%X\n", phase[0], phase[1]); +WriteLog("\theadPos[0]=%X, headPos[1]=%X\n", headPos[0], headPos[1]); +WriteLog("\tcurrentPos[0]=%X, currentPos[1]=%X\n", currentPos[0], currentPos[1]); +#endif +} + + +/* +Need to add some type of error checking here, so we can report back on bad images, etc. +*/ +void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum) +{ + diskType[driveNum] = DFT_UNKNOWN; + + if (memcmp(disk[driveNum], wozHeader, 8) == 0) + { + diskType[driveNum] = DT_WOZ; + /*bool r =*/ CheckWOZ(disk[driveNum], diskSize[driveNum], driveNum); + } + else if (diskSize[driveNum] == 143360) + { + const char * ext = strrchr(filename, '.'); + + if (ext == NULL) + return; + + WriteLog("FLOPPY: Found extension [%s]...\n", ext); + + if (strcasecmp(ext, ".po") == 0) + diskType[driveNum] = DT_PRODOS; + else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0)) + { + // We assume this, but check for a PRODOS fingerprint. Trust, but + // verify. ;-) + diskType[driveNum] = DT_DOS33; + + uint8_t fingerprint[4][4] = { + { 0x00, 0x00, 0x03, 0x00 }, // @ $400 + { 0x02, 0x00, 0x04, 0x00 }, // @ $600 + { 0x03, 0x00, 0x05, 0x00 }, // @ $800 + { 0x04, 0x00, 0x00, 0x00 } // @ $A00 + }; + + bool foundProdos = true; + + for(uint32_t i=0; i<4; i++) + { + for(uint32_t j=0; j<4; j++) + { + if (disk[driveNum][0x400 + (i * 0x200) + j] != fingerprint[i][j]) + { + foundProdos = false; + break; + } + } + } + + if (foundProdos) + diskType[driveNum] = DT_PRODOS; + } + +// Actually, it just might matter WRT to nybblyzing/denybblyzing +// (and, it does... :-P) + WOZifyImage(driveNum); + } + else if (diskSize[driveNum] == 143488) + { + diskType[driveNum] = DT_DOS33_HDR; + WOZifyImage(driveNum); + } + +#warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!" +// No, we don't nybblize anymore. But we should tell the user that the loading failed with a return value + + WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_DOS33 ? + "DOS 3.3 image" : (diskType[driveNum] == DT_DOS33_HDR ? + "DOS 3.3 image (headered)" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : (diskType[driveNum] == DT_WOZ ? "WOZ image" : "unknown"))))); +} + + +// +// Write a bitstream (source left justified to bit 7) to destination buffer. +// Writes 'bits' number of bits to 'dest', starting at bit position 'dstPtr', +// updating 'dstPtr' for the caller. +// +void FloppyDrive::WriteBits(uint8_t * dest, uint8_t * src, uint16_t bits, uint16_t * dstPtr) +{ + for(uint16_t i=0; itmap, standardTMAP, 141); + InitWOZ(driveNum); + + // Upconvert data from DSK & friends format to WOZ tracks :-) + for(uint8_t trk=0; trk<35; trk++) + { + uint16_t dstBitPtr = 0; + uint8_t * img = woz[driveNum]->track[trk].bits; +//already done +// memset(img, 0, 6646); + + // Write self-sync header bytes (16, should it be 64? Dunno.) + for(int i=0; i<64; i++) + WriteBits(img, ff10, 10, &dstBitPtr); + + // Write out the following sectors + for(uint8_t sector=0; sector<16; sector++) + { + // Set up the sector address header + addressHeader[5] = ((trk >> 1) & 0x55) | 0xAA; + addressHeader[6] = (trk & 0x55) | 0xAA; + addressHeader[7] = ((sector >> 1) & 0x55) | 0xAA; + addressHeader[8] = (sector & 0x55) | 0xAA; + addressHeader[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA; + addressHeader[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA; + + WriteBits(img, addressHeader, 14 * 8, &dstBitPtr); + + // Write 5 self-sync bytes for actual sector header + for(int i=0; i<5; i++) + WriteBits(img, ff10, 10, &dstBitPtr); + + // Write sector header (D5 AA AD) + WriteBits(img, sectorHeader, 3 * 8, &dstBitPtr); +// uint8_t * bytes = disk[driveNum]; + uint8_t * bytes = tmpDisk; + +//Need to fix this so it writes the correct sector in the correct place *and* put the correct sector # into the header above as well. !!! FIX !!! + // Figure out location of sector data in disk image + if (diskType[driveNum] == DT_DOS33) + bytes += (doSector[sector] * 256) + (trk * 256 * 16); + else if (diskType[driveNum] == DT_DOS33_HDR) + bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128; + else if (diskType[driveNum] == DT_PRODOS) + bytes += (poSector[sector] * 256) + (trk * 256 * 16); + else + bytes += (sector * 256) + (trk * 256 * 16); + + // Convert the 256 8-bit bytes into 342 6-bit bytes. + for(uint16_t i=0; i<0x56; i++) + { + tmpNib[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7) + | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5) + | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5) + | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3) + | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3) + | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1); + } + + tmpNib[0x54] &= 0x3F; + tmpNib[0x55] &= 0x3F; + memcpy(tmpNib + 0x56, bytes, 256); + + // XOR the data block with itself, offset by one byte, creating a + // 343rd byte which is used as a checksum. + tmpNib[342] = 0x00; + + for(uint16_t i=342; i>0; i--) + tmpNib[i] = tmpNib[i] ^ tmpNib[i - 1]; + + // Using a lookup table, convert the 6-bit bytes into disk bytes. + for(uint16_t i=0; i<343; i++) + tmpNib[i] = diskbyte[tmpNib[i] >> 2]; + + WriteBits(img, tmpNib, 343 * 8, &dstBitPtr); + + // Done with the nybblization, now add the epilogue... + WriteBits(img, footer, 3 * 8, &dstBitPtr); + + // (Should the footer be 30 or 48? would be 45 FF10s here for 48) + for(int i=0; i<27; i++) + WriteBits(img, ff10, 10, &dstBitPtr); + } + + // Set the proper bit/byte lengths in the WOZ for this track + woz[driveNum]->track[trk].bitCount = Uint16LE(dstBitPtr); + woz[driveNum]->track[trk].byteCount = Uint16LE((dstBitPtr + 7) / 8); + } + + delete[] tmpDisk; +} + + +const char * FloppyDrive::ImageName(uint8_t driveNum/*= 0*/) +{ + // Set up a zero-length string for return value + nameBuf[0] = 0; + + if (driveNum > 1) + { + WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum); + return nameBuf; + } + + // Now we attempt to strip out extraneous paths/extensions to get just the filename + const char * startOfFile = strrchr(imageName[driveNum], '/'); + const char * startOfExt = strrchr(imageName[driveNum], '.'); + + // If there isn't a path, assume we're starting at the beginning + if (startOfFile == NULL) + startOfFile = &imageName[driveNum][0]; + else + startOfFile++; + + // If there isn't an extension, assume it's at the terminating NULL + if (startOfExt == NULL) + startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]); + + // Now copy the filename (may copy nothing!) + int j = 0; + + for(const char * i=startOfFile; i 1) + { + WriteLog("FLOPPY: Attempted DriveIsEmtpy() for drive #%u!\n", driveNum); + return true; + } + + return (diskType[driveNum] == DT_EMPTY ? true : false); +} + + +bool FloppyDrive::IsWriteProtected(uint8_t driveNum/*= 0*/) +{ + if (driveNum > 1) + { + WriteLog("FLOPPY: Attempted DiskIsWriteProtected() for drive #%u!\n", driveNum); + return true; + } + + return (bool)woz[driveNum]->writeProtected; +} + + +void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/) +{ + if (driveNum > 1) + { + WriteLog("FLOPPY: Attempted set write protect for drive #%u!\n", driveNum); + return; + } + + woz[driveNum]->writeProtected = (uint8_t)state; +} + + +int FloppyDrive::DriveLightStatus(uint8_t driveNum/*= 0*/) +{ + int retval = DLS_OFF; + + if (activeDrive != driveNum) + return DLS_OFF; + + if (ioHappened) + retval = (ioMode == IO_MODE_READ ? DLS_READ : DLS_WRITE); + + ioHappened = false; + return retval; +} + + +void FloppyDrive::SaveState(FILE * file) +{ + // Internal state vars + fputc(motorOn, file); + fputc(activeDrive, file); + fputc(ioMode, file); + fputc(dataRegister, file); + fputc((ioHappened ? 1 : 0), file); + + // Disk #1 + if (disk[0] != NULL) + { + WriteLong(file, diskSize[0]); + WriteLong(file, diskType[0]); + fputc(phase[0], file); + fputc(headPos[0], file); + WriteLong(file, currentPos[0]); + fputc((imageDirty[0] ? 1 : 0), file); + fwrite(disk[0], 1, diskSize[0], file); + fwrite(imageName[0], 1, MAX_PATH, file); + } + else + WriteLong(file, 0); + + // Disk #2 + if (disk[1] != NULL) + { + WriteLong(file, diskSize[1]); + WriteLong(file, diskType[1]); + fputc(phase[1], file); + fputc(headPos[1], file); + WriteLong(file, currentPos[1]); + fputc((imageDirty[1] ? 1 : 0), file); + fwrite(disk[1], 1, diskSize[1], file); + fwrite(imageName[1], 1, MAX_PATH, file); + } + else + WriteLong(file, 0); +} + + +void FloppyDrive::LoadState(FILE * file) +{ + // Eject images if they're loaded + EjectImage(0); + EjectImage(1); + + // Read internal state variables + motorOn = fgetc(file); + activeDrive = fgetc(file); + ioMode = fgetc(file); + dataRegister = fgetc(file); + ioHappened = (fgetc(file) == 1 ? true : false); + + diskSize[0] = ReadLong(file); + + if (diskSize[0]) + { + disk[0] = new uint8_t[diskSize[0]]; + diskType[0] = (uint8_t)ReadLong(file); + phase[0] = fgetc(file); + headPos[0] = fgetc(file); + currentPos[0] = ReadLong(file); + imageDirty[0] = (fgetc(file) == 1 ? true : false); + fread(disk[0], 1, diskSize[0], file); + fread(imageName[0], 1, MAX_PATH, file); + woz[0] = (WOZ *)disk[0]; + } + + diskSize[1] = ReadLong(file); + + if (diskSize[1]) + { + disk[1] = new uint8_t[diskSize[1]]; + diskType[1] = (uint8_t)ReadLong(file); + phase[1] = fgetc(file); + headPos[1] = fgetc(file); + currentPos[1] = ReadLong(file); + imageDirty[1] = (fgetc(file) == 1 ? true : false); + fread(disk[1], 1, diskSize[1], file); + fread(imageName[1], 1, MAX_PATH, file); + woz[1] = (WOZ *)disk[1]; + } +} + + +uint32_t FloppyDrive::ReadLong(FILE * file) +{ + uint32_t r = 0; + + for(int i=0; i<4; i++) + r = (r << 8) | fgetc(file); + + return r; +} + + +void FloppyDrive::WriteLong(FILE * file, uint32_t l) +{ + for(int i=0; i<4; i++) + { + fputc((l >> 24) & 0xFF, file); + l = l << 8; + } +} + + +void FloppyDrive::WriteLongLE(FILE * file, uint32_t l) +{ + for(int i=0; i<4; i++) + { + fputc(l & 0xFF, file); + l >>= 8; + } +} + + +void FloppyDrive::WriteWordLE(FILE * file, uint16_t w) +{ + fputc(w & 0xFF, file); + fputc((w >> 8) & 0xFF, file); +} + + +void FloppyDrive::WriteZeroes(FILE * file, uint32_t num) +{ + for(uint32_t i=0; i> 1) & 0x03); + + // Set the state of the phase solenoid accessed using the phase bit + if (addr & 0x01) + phase[activeDrive] |= phaseBit; + else + phase[activeDrive] &= ~phaseBit; + + // See if the new phase solenoid is energized, & move the stepper/head + // appropriately. + // N.B.: The head stub is located by bits 1 & 2 of the headPos variable + uint8_t oldHeadPos = headPos[activeDrive]; + uint8_t nextUp = 1 << (((oldHeadPos >> 1) + 1) & 0x03); + uint8_t nextDown = 1 << (((oldHeadPos >> 1) - 1) & 0x03); + + // We simulate cogging here by seeing if there's a valid up and/or down + // position to go to. If both are valid, the head goes nowhere. + if (phase[activeDrive] & nextUp) + headPos[activeDrive] += (headPos[activeDrive] < 140 ? 2 : 0); + + if (phase[activeDrive] & nextDown) + headPos[activeDrive] -= (headPos[activeDrive] > 0 ? 2 : 0); + + if (oldHeadPos != headPos[activeDrive]) + { + uint8_t newTIdx = woz[activeDrive]->tmap[headPos[activeDrive]]; + float newBitLen = (newTIdx == 0xFF ? 51200.0f + : Uint16LE(woz[activeDrive]->track[newTIdx].bitCount)); + + uint8_t oldTIdx = woz[activeDrive]->tmap[oldHeadPos]; + float oldBitLen = (oldTIdx == 0xFF ? 51200.0f + : Uint16LE(woz[activeDrive]->track[oldTIdx].bitCount)); + currentPos[activeDrive] = (uint32_t)((float)currentPos[activeDrive] * (newBitLen / oldBitLen)); + + trackLength[activeDrive] = (uint16_t)newBitLen; + SpawnMessage("Stepping to track %u...", headPos[activeDrive] >> 2); + } + +WriteLog("FLOPPY: Stepper phase %d set to %s [%c%c%c%c] (track=%2.2f)\n", (addr >> 1) & 0x03, (addr & 0x01 ? "ON " : "off"), (phase[activeDrive] & 0x08 ? '|' : '.'), (phase[activeDrive] & 0x04 ? '|' : '.'), (phase[activeDrive] & 0x02 ? '|' : '.'), (phase[activeDrive] & 0x01 ? '|' : '.'), (float)headPos[activeDrive] / 4.0f); +} + + +void FloppyDrive::ControlMotor(uint8_t addr) +{ + // $C0E8 - 9 + motorOn = addr; + + if (motorOn) + readPulse = 0; + else + driveOffTimeout = 2000000; + +WriteLog("FLOPPY: Turning drive motor %s\n", (motorOn ? "ON" : "off")); +} + + +void FloppyDrive::DriveEnable(uint8_t addr) +{ + // $C0EA - B + activeDrive = addr; +WriteLog("FLOPPY: Selecting drive #%hhd\n", addr + 1); +} + + +/* +So for $C08C-F, we have two switches (Q6 & Q7) which combine to make four states ($C-D is off/on for Q6, $E-F is off/on for Q7). + +So it forms a matrix like so: + + $C08E $C08F + +----------------------------------------------------------------------- +$C08C |Enable READ sequencing |Data reg SHL every 8th clock while writing + +----------------------------+------------------------------------------ +$C08D |Check write prot./init write|Data reg LOAD every 8th clk while writing + +Looks like reads from even addresses in $C080-F block transfer data from the sequencer to the MPU, does write from odd do the inverse (transfer from MPU to sequencer)? Looks like it. + +*/ + + +void FloppyDrive::SetShiftLoadSwitch(uint8_t state) +{ + // $C0EC - D + slSwitch = state; +} + + +void FloppyDrive::SetReadWriteSwitch(uint8_t state) +{ + // $C0EE - F + rwSwitch = state; +} + + +// MMIO: Reads from $C08x to $C0XX on even addresses +uint8_t FloppyDrive::DataRegister(void) +{ + // Sanity check + if (diskType[activeDrive] != DT_EMPTY) + { + uint8_t tIdx = woz[activeDrive]->tmap[headPos[activeDrive]]; + uint32_t bitLen = (tIdx == 0xFF ? 51200 + : Uint16LE(woz[activeDrive]->track[tIdx].bitCount)); + SpawnMessage("%u:Reading $%02X from track %u, sector %u...", + activeDrive, dataRegister, headPos[activeDrive] >> 2, (uint32_t)(((float)currentPos[activeDrive] / (float)bitLen) * 16.0f)); + ioMode = IO_MODE_READ; + ioHappened = true; + } + + return dataRegister; +} + + +// MMIO: Writes from $C08x to $C0XX on odd addresses +void FloppyDrive::DataRegister(uint8_t data) +{ + cpuDataBus = data; + ioMode = IO_MODE_WRITE; + ioHappened = true; +} + + +/* + OFF switches ON switches +Switch Addr Func Addr Func +Q0 $C080 Phase 0 off $C081 Phase 0 on +Q1 $C082 Phase 1 off $C083 Phase 1 on +Q2 $C084 Phase 2 off $C085 Phase 2 on +Q3 $C086 Phase 3 off $C087 Phase 3 on +Q4 $C088 Drive off $C089 Drive on +Q5 $C08A Select Drive 1 $C08B Select Drive 2 +Q6 $C08C Shift data register $C08D Load data register +Q7 $C08E Read $C08F Write + +From "Beneath Apple ProDOS", description of combinations of $C0EC-EF + +$C08C, $C08E: Enable read sequencing +$C08C, $C08F: Shift data register every four cycles while writing +$C08D, $C08E: Check write protect and initialize sequencer for writing +$C08D, $C08F: Load data register every four cycles while writing + + +Sense Write Protect: + + LDX #SLOT Put slot number times 16 in X-register. + LDA $C08D, X + LDA $C08E, X Sense write protect. + BMI ERROR If high bit set, protected. + +*/ + +/* +PRODOS 8 MLI ERROR CODES + +$00: No error +$01: Bad system call number +$04: Bad system call parameter count +$25: Interrupt table full +$27: I/O error +$28: No device connected +$2B: Disk write protected +$2E: Disk switched +$40: Invalid pathname +$42: Maximum number of files open +$43: Invalid reference number +$44: Directory not found +$45: Volume not found +$46: File not found +$47: Duplicate filename +$48: Volume full +$49: Volume directory full +$4A: Incompatible file format, also a ProDOS directory +$4B: Unsupported storage_type +$4C: End of file encountered +$4D: Position out of range +$4E: File access error, also file locked +$50: File is open +$51: Directory structure damaged +$52: Not a ProDOS volume +$53: Invalid system call parameter +$55: Volume Control Block table full +$56: Bad buffer address +$57: Duplicate volume +$5A: File structure damaged +*/ + + +// +// This is used mainly to initialize blank disks and upconvert non-WOZ disks +// +void FloppyDrive::InitWOZ(uint8_t driveNum/*= 0*/) +{ + // Sanity check + if (disk[driveNum] != NULL) + { + WriteLog("FLOPPY: Attempted to initialize non-NULL WOZ structure\n"); + return; + } + + diskSize[driveNum] = 256 + (35 * sizeof(WOZTrack)); + disk[driveNum] = new uint8_t[diskSize[driveNum]]; + woz[driveNum] = (WOZ *)disk[driveNum]; + + // Zero out WOZ image in memory + memset(woz[driveNum], 0, diskSize[driveNum]); + + // Set up header (leave CRC as 0 for now) + memcpy(woz[driveNum]->magic, wozHeader, 8); + + // INFO header + memcpy(woz[driveNum]->infoTag, "INFO", 4); + woz[driveNum]->infoSize = Uint32LE(60); + woz[driveNum]->infoVersion = 1; + woz[driveNum]->diskType = 1; + woz[driveNum]->writeProtected = 0; + woz[driveNum]->synchronized = 0; + woz[driveNum]->cleaned = 1; + memset(woz[driveNum]->creator, ' ', 32); + memcpy(woz[driveNum]->creator, "Apple2 emulator v1.0.0", 22); + + // TMAP header + memcpy(woz[driveNum]->tmapTag, "TMAP", 4); + woz[driveNum]->tmapSize = Uint32LE(160); + memcpy(woz[driveNum]->tmap, standardTMAP, 141); + + // TRKS header + memcpy(woz[driveNum]->trksTag, "TRKS", 4); + woz[driveNum]->trksSize = Uint32LE(35 * sizeof(WOZTrack)); + + for(int i=0; i<35; i++) + { + woz[driveNum]->track[i].bitCount = Uint16LE(51200); + woz[driveNum]->track[i].byteCount = Uint16LE((51200 + 7) / 8); + } + + // META header (how to handle? prolly with a separate pointer) +} + + +// +// Do basic sanity checks on the passed in contents (file loaded elsewhere). +// Returns true if successful, false on failure. +// +bool FloppyDrive::CheckWOZ(const uint8_t * wozData, uint32_t wozSize, uint8_t driveNum/*= 0*/) +{ + // Hey! This reference works!! :-D + WOZ & woz1 = *((WOZ *)wozData); + woz[driveNum] = (WOZ *)wozData; + + // Basic sanity checking + if (wozData == NULL) + { + WriteLog("FLOPPY: NULL pointer passed in to CheckWOZ()...\n"); + return false; + } + + if (memcmp(woz1.magic, wozHeader, 8) != 0) + { + WriteLog("FLOPPY: Invalid WOZ header in file\n"); + return false; + } + + uint32_t crc = CRC32(&wozData[12], wozSize - 12); + uint32_t wozCRC = Uint32LE(woz1.crc32); + + if ((wozCRC != 0) && (wozCRC != crc)) + { + WriteLog("FLOPPY: Corrupted data found in WOZ. CRC32: %08X, computed: %08X\n", wozCRC, crc); + return false; + } + else if (wozCRC == 0) + WriteLog("FLOPPY: Warning--WOZ file has no CRC...\n"); + +#if 1 + WriteLog("Track map:\n"); + WriteLog(" 1 1 1 1 1 1 1 1\n"); + WriteLog("0.,.1.,.2.,.3.,.4.,.5.,.6.,.7.,.8.,.9.,.0.,.1.,.2.,.3.,.4.,.5.,.6.,.7.,.\n"); + WriteLog("------------------------------------------------------------------------\n"); + + for(uint8_t j=0; j<2; j++) + { + for(uint8_t i=0; i<72; i++) + { + char buf[64] = ".."; + buf[0] = buf[1] = '.'; + + if (woz1.tmap[i] != 0xFF) + sprintf(buf, "%02d", woz1.tmap[i]); + + WriteLog("%c", buf[j]); + } + + WriteLog("\n"); + } + + WriteLog("\n1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3\n"); + WriteLog("8.,.9.,.0.,.1.,.2.,.3.,.4.,.5.,.6.,.7.,.8.,.9.,.0.,.1.,.2.,.3.,.4.,.5\n"); + WriteLog("---------------------------------------------------------------------\n"); + + for(uint8_t j=0; j<2; j++) + { + for(uint8_t i=72; i<141; i++) + { + char buf[64] = ".."; + + if (woz1.tmap[i] != 0xFF) + sprintf(buf, "%02d", woz1.tmap[i]); + + WriteLog("%c", buf[j]); + } + + WriteLog("\n"); + } + + WriteLog("\n"); + + uint8_t numTracks = woz1.trksSize / sizeof(WOZTrack); + + // N.B.: Need to check the track[] to have this tell the correct track... Right now, it doesn't + for(uint8_t i=0; i 1) + { + WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum); + return false; + } + + if (diskType[driveNum] == DT_EMPTY) + { + WriteLog("FLOPPY: No image in drive #%u to save\n", driveNum); + return false; + } + + if (!imageDirty[driveNum]) + { + WriteLog("FLOPPY: No need to save unchanged image in drive #%u...\n", driveNum); + return false; + } + + // Set up CRC32 before writing + woz[driveNum]->crc32 = Uint32LE(CRC32(&disk[driveNum][12], diskSize[driveNum] - 12)); + + // META header (skip for now) (actually, should be in the disk[] image already) + + // Finally, write the damn image + FILE * fp = fopen(imageName[driveNum], "wb"); + + if (fp == NULL) + { + WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]); + return false; + } + + fwrite(disk[driveNum], 1, diskSize[driveNum], fp); + fclose(fp); + + WriteLog("FLOPPY: Successfully wrote image file '%s'...\n", imageName[driveNum]); + + return true; +} + + +// N.B.: The WOZ documentation says that the bitstream is normalized to 4µs. +// Which means on the //e that you would have to run it at that clock +// rate (instead of the //e clock rate 0.9799µs/cycle) to get the +// simulated drive running at 300 RPM. So, instead of doing that, we're +// just gonna run it at twice the clock rate of the base 6502 clock, +// which will make the simulated drive run in the neighborhood of around +// 306 RPM. Should be close enough to get away with it. :-) (And it +// seems to run OK, for the most part.) + + +static bool logSeq = false; +// +// Logic State Sequencer & Data Register +// +void FloppyDrive::RunSequencer(uint32_t cyclesToRun) +{ + static uint32_t prng = 1; + + // Sanity checks + if (diskType[activeDrive] == DT_EMPTY) + return; + else if (motorOn == false) + { + if (driveOffTimeout == 0) + return; + else + driveOffTimeout--; + } + + // It's x2 because the sequencer clock runs twice as fast as the CPU clock. + cyclesToRun *= 2; + +//extern bool dumpDis; +//static bool tripwire = false; +uint8_t chop = 0; +//static uint32_t lastPos = 0; +if (logSeq) +{ + WriteLog("DISKSEQ: Running for %d cycles [rw=%hhd, sl=%hhd, reg=%02X, bus=%02X]\n", cyclesToRun, rwSwitch, slSwitch, dataRegister, cpuDataBus); +} + + while (cyclesToRun-- > 0) + { + pulseClock = (pulseClock + 1) & 0x07; + + if (pulseClock == 0) + { + uint16_t bytePos = currentPos[activeDrive] / 8; + uint8_t bitPos = currentPos[activeDrive] % 8; + uint8_t tIdx = woz[activeDrive]->tmap[headPos[activeDrive]]; + + if (tIdx != 0xFF) + { + if (woz[activeDrive]->track[tIdx].bits[bytePos] & bitMask[bitPos]) + { + // According to Jim Sather (Understanding the Apple II), + // the Read Pulse, when it happens, is 1µs long, which is 2 + // sequencer clock pulses long. + readPulse = 2; + zeroBitCount = 0; + } + else + zeroBitCount++; +#if 0 + currentPos[activeDrive] = (currentPos[activeDrive] + 1) % Uint16LE(woz[activeDrive]->track[tIdx].bitCount); + } + else + currentPos[activeDrive] = (currentPos[activeDrive] + 1) % 51200; +#else + } + +//this doesn't work reliably for some reason... +//seems to work OK now... + currentPos[activeDrive] = (currentPos[activeDrive] + 1) % trackLength[activeDrive]; +#endif + + // If we hit more than 2 zero bits in a row, simulate the disk head + // reader's Automatic Gain Control (AGC) turning itself up too high + // by stuffing random bits in the bitstream. We also do this if + // the current track is marked as unformatted. +/* +N.B.: Had to up this to 3 because Up N' Down had some weird sync bytes (FE10). May have to up it some more. +*/ + if ((zeroBitCount > 3) || (tIdx == 0xFF)) + { + if (prng & 0x00001) + { + // This PRNG is called the "Galois configuration". + prng ^= 0x24000; + readPulse = 2; + } + + prng >>= 1; + } + } + + // Find and run the Sequencer's next state + uint8_t nextState = (sequencerState & 0xF0) | (rwSwitch << 3) + | (slSwitch << 2) | (readPulse ? 0x02 : 0) + | ((dataRegister & 0x80) >> 7); +if (logSeq) + WriteLog("[%02X:%02X]%s", sequencerState, nextState, (chop == 15 ? "\n" : "")); +chop = (chop + 1) % 20; + sequencerState = sequencerROM[nextState]; + + switch (sequencerState & 0x0F) + { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + // CLR (clear data register) + dataRegister = 0; + break; + case 0x08: + case 0x0C: + // NOP (no operation) + break; + case 0x09: + // SL0 (shift left, 0 fill LSB) + dataRegister <<= 1; +//if (!stopWriting) +{ + uint8_t tIdx = woz[activeDrive]->tmap[headPos[activeDrive]]; + + if (rwSwitch && (tIdx != 0xFF) + && !woz[activeDrive]->writeProtected) + { + imageDirty[activeDrive] = true; + uint16_t bytePos = currentPos[activeDrive] / 8; + uint8_t bitPos = currentPos[activeDrive] % 8; + + if (dataRegister & 0x80) + // Fill in the one, if necessary + woz[activeDrive]->track[tIdx].bits[bytePos] |= bitMask[bitPos]; + else + // Otherwise, punch in the zero + woz[activeDrive]->track[tIdx].bits[bytePos] &= ~bitMask[bitPos]; + +#if 0 +if (dumpDis || tripwire) +{ +tripwire = true; +WriteLog("[%s]", (dataRegister & 0x80 ? "1" : "0")); +if (lastPos == currentPos[activeDrive]) + WriteLog("{STOMP}"); +else if ((lastPos + 1) != currentPos[activeDrive]) + WriteLog("{LAG}"); +lastPos = currentPos[activeDrive]; +} +#endif + } +} + break; + case 0x0A: + case 0x0E: + // SR (shift right write protect bit) + dataRegister >>= 1; + dataRegister |= (woz[activeDrive]->writeProtected ? 0x80 : 0x00); + break; + case 0x0B: + case 0x0F: + // LD (load data register from data bus) + dataRegister = cpuDataBus; +//if (!stopWriting) +{ + uint8_t tIdx = woz[activeDrive]->tmap[headPos[activeDrive]]; + + if (rwSwitch && (tIdx != 0xFF) + && !woz[activeDrive]->writeProtected) + { + imageDirty[activeDrive] = true; + uint16_t bytePos = currentPos[activeDrive] / 8; + uint8_t bitPos = currentPos[activeDrive] % 8; + woz[activeDrive]->track[tIdx].bits[bytePos] |= bitMask[bitPos]; +#if 0 +if (dumpDis || tripwire) +{ +tripwire = true; +WriteLog("[%s]", (dataRegister & 0x80 ? "1" : "0")); +if (lastPos == currentPos[activeDrive]) + WriteLog("{STOMP}"); +else if ((lastPos + 1) != currentPos[activeDrive]) + WriteLog("{LAG}"); +lastPos = currentPos[activeDrive]; +} +#endif + } +} + break; + case 0x0D: + // SL1 (shift left, 1 fill LSB) + dataRegister <<= 1; + dataRegister |= 0x01; + break; + } + + if (readPulse > 0) + readPulse--; + } + +if (logSeq) + WriteLog("\n"); +} + + +FloppyDrive floppyDrive[2]; + +static uint8_t SlotIOR(uint16_t address) +{ + uint8_t state = address & 0x0F; + + switch (state) + { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + floppyDrive[0].ControlStepper(state); + break; + case 0x08: + case 0x09: + floppyDrive[0].ControlMotor(state & 0x01); + break; + case 0x0A: + case 0x0B: + floppyDrive[0].DriveEnable(state & 0x01); + break; + case 0x0C: + case 0x0D: + floppyDrive[0].SetShiftLoadSwitch(state & 0x01); + break; + case 0x0E: + case 0x0F: + floppyDrive[0].SetReadWriteSwitch(state & 0x01); + break; + } + + // Even addresses return the data register, odd (we suppose) returns a + // floating bus read... + return (address & 0x01 ? ReadFloatingBus(0) : floppyDrive[0].DataRegister()); +} + + +static void SlotIOW(uint16_t address, uint8_t byte) +{ + uint8_t state = address & 0x0F; + + switch (state) + { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + floppyDrive[0].ControlStepper(state); + break; + case 0x08: + case 0x09: + floppyDrive[0].ControlMotor(state & 0x01); + break; + case 0x0A: + case 0x0B: + floppyDrive[0].DriveEnable(state & 0x01); + break; + case 0x0C: + case 0x0D: + floppyDrive[0].SetShiftLoadSwitch(state & 0x01); + break; + case 0x0E: + case 0x0F: + floppyDrive[0].SetReadWriteSwitch(state & 0x01); + break; + } + + // Odd addresses write to the Data register, even addresses (we assume) go + // into the ether + if (state & 0x01) + floppyDrive[0].DataRegister(byte); +} + + +// This slot function doesn't need to differentiate between separate instances +// of FloppyDrive +static uint8_t SlotROM(uint16_t address) +{ + return diskROM[address]; +} + + +void InstallFloppy(uint8_t slot) +{ + SlotData disk = { SlotIOR, SlotIOW, SlotROM, 0, 0, 0 }; + InstallSlotHandler(slot, &disk); +} +