├── .gitignore ├── GPL2.txt ├── MPL2.txt ├── Makefile ├── README.md ├── floppybridge ├── ADFBridge.cpp ├── ADFBridge.h ├── ArduinoFloppyBridge.cpp ├── ArduinoFloppyBridge.h ├── ArduinoInterface.cpp ├── ArduinoInterface.h ├── CommonBridgeTemplate.cpp ├── CommonBridgeTemplate.h ├── GreaseWeazleBridge.cpp ├── GreaseWeazleBridge.h ├── GreaseWeazleInterface.cpp ├── GreaseWeazleInterface.h ├── RotationExtractor.cpp ├── RotationExtractor.h ├── SerialIO.cpp ├── SerialIO.h ├── SuperCardProBridge.cpp ├── SuperCardProBridge.h ├── SuperCardProInterface.cpp ├── SuperCardProInterface.h ├── floppybridge_abstract.h ├── floppybridge_common.h ├── floppybridge_config.h ├── floppybridge_lib.cpp ├── floppybridge_lib.h ├── ftdi.cpp ├── ftdi.h ├── pll.cpp ├── pll.h └── resource.h └── windows ├── FloppyBridge.cpp ├── FloppyBridge.h ├── FloppyBridge.sln ├── FloppyBridge.vcxproj ├── FloppyBridge.vcxproj.filters ├── FloppyBridge.vcxproj.user ├── Resource.aps ├── Resource.rc ├── bridge00.bmp ├── bridge01.bmp ├── bridge02.bmp ├── bridgeProfileEditor.cpp ├── bridgeProfileEditor.h ├── bridgeProfileListEditor.cpp ├── bridgeProfileListEditor.h ├── cpp.hint ├── donate.bmp ├── patreon.bmp ├── resource.h ├── sbridge00.bmp ├── sbridge01.bmp └── sbridge02.bmp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | /windows/Debug 34 | /windows/Release 35 | /windows/x64/Debug 36 | /windows/x64/Release 37 | /windows/.vs/ 38 | /floppybridge/.vs/ 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX ?= g++ 2 | TARGET = FloppyBridge.so 3 | 4 | SOURCE = floppybridge/ArduinoFloppyBridge.cpp \ 5 | floppybridge/ArduinoInterface.cpp \ 6 | floppybridge/CommonBridgeTemplate.cpp \ 7 | floppybridge/ftdi.cpp \ 8 | floppybridge/pll.cpp \ 9 | floppybridge/GreaseWeazleBridge.cpp \ 10 | floppybridge/GreaseWeazleInterface.cpp \ 11 | floppybridge/RotationExtractor.cpp \ 12 | floppybridge/SerialIO.cpp \ 13 | floppybridge/SuperCardProBridge.cpp \ 14 | floppybridge/SuperCardProInterface.cpp \ 15 | windows/FloppyBridge.cpp 16 | DEPS = $(SOURCE:%.cpp=%.d) 17 | 18 | CPPFLAGS +=-shared -fPIC -ldl -Ifloppybridge -Iwindows 19 | 20 | .PHONY : all clean 21 | 22 | $(TARGET) : $(SOURCE) 23 | $(CXX) $(CPPFLAGS) -o $(TARGET) $(SOURCE) 24 | 25 | all : $(TARGET) 26 | 27 | install : $(TARGET) 28 | cp $(TARGET) /usr/local/lib 29 | 30 | clean : 31 | rm -f $(DEPS) 32 | rm -f $(TARGET) 33 | 34 | cleanprofile: 35 | rm -f $(OBJS:%.o=%.gcda) 36 | 37 | -include $(DEPS) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FloppyDriveBridge 2 | 3 | # FloppyDriveBridges for *UAE 4 | Bridges are what I have chosen to call the name of each 'real' drive added to the emulator. 5 | This folder contains the latest implementations for real floppy disk access within *UAE emulators. 6 | For these to work, the main emulator must have had the ''disk.cpp'' and ''expansion.cpp'' updated from the original 7 | This only needs doing once, and again if it is updated (ie: the no-click support below). 8 | You can see these files at https://github.com/RobSmithDev/WinUAE 9 | 10 | There is also a DLL version of this library for use with the official WinUAE build. 11 | Latest version includes several improvements and testing with the help of Dimitris Panokostas aka MiDWaN (Amiberry) 12 | 13 | # Licence 14 | The majority of the source code is available multi-licensed under the terms of the Mozilla Public License Version 2.0 15 | as published by Mozilla Corporation and the GNU General Public License, version 2 or later, as published by the Free 16 | Software Foundation, with the exception of: 17 | floppybridge_lib.cpp 18 | floppybridge_lib.h 19 | floppybridge_common.h 20 | floppybridge_abstract.h 21 | floppybridge_config.h 22 | Which are separately licenced as UnLicence - see http://unlicense.org 23 | Those are also the files to use if you want to use this plugin in something you're making! 24 | 25 | # Updates: 26 | * July 2024 (1.6.4) Faster direct read/write access to floppy disks 27 | Updated Windows update check code 28 | Fixed some compile issues 29 | Fixed a strange crash bug relating to data not being present when expected 30 | * May 2024 (1.6) Sped up the main thread by refactoring and changing how a lock worked that was fixed in 1.5. It now loads disks *slightly* faster 31 | Added support for "Direct Mode" which allows direct MFM buffer reading and writing so you can use this plugin outside of WinUAE 32 | Added some extra commands to the library to make it easier to use outside of WinUAE 33 | Changed donate link from PayPal to KoFi 34 | Updated the writing code for DrawBridge, Greaseweazle and SupercardPRO so it now calculates precompensation correctly when writing to disks 35 | Some refactoring (slightly breaking - namespace changes) to help use this as a shared dynamic library with less header files needed 36 | Added support for the host application to be able to detect if the device is no longer working (or present!) 37 | * Feb 2024 (1.5) Fixed issue with locks on new Apple M1/2 devices for Amiberry 38 | * April 2023 (1.4 still) Fixed issues with serial access on MacOS 39 | * March 2023 (1.4): Fixed issues with stalling mode not working properly, or not as expected 40 | Updated stalling mode to work better with diskspare device which seems to be very impatient! 41 | Updated the icons for the supported hardware and rendered them transparently 42 | * June 2022 (1.3): Fixed Greaseweazle Drive A/B not selectable properly 43 | Fixed issue with warning message about diskchange on Greaseweazle always re-appearing 44 | Fixed Greaseweazle Shugart support. (**Does not support disk change** - it will manually check for disks. A PC Drive is strongly recommended) 45 | Re-worked the profile listing dialog a little 46 | * June 2022 (1.2): Added support for track 82 and 83 reading 47 | Changed the behaviour to disk check to be no-click for manual detection 48 | Updated Greaseweazle driver to also support Shugart interfaces 49 | Some cosmetic fixes 50 | * End of Feb 2022: Greatly increased compatability due to changes in the PLL and Rotation Extractor handling. Check the games that didnt boot before! 51 | * February 2022: Fixed random crash in Rotation Extractor 52 | Further stability improvements for Linux (or rather not-Windows) 53 | * Janurary 2022: Added support for Supercard Pro (V1.3 firmware required) 54 | Changed read mode to use a more accurate PLL (taken from SCP design from SCP.cpp in *UAE) which **improves compatability with some games** 55 | Added support for DrawBridge new PLL read command for better accuracy (Firmware 1.9.23) - HIGHLY recommended 56 | Fixed a few bugs in rotation extractore that caused a few crashes 57 | In "Normal" mode, if a perfect revolution cannot be guessed it automatically switches to compatiable for that track 58 | Fixed 64-Bit support for Greaseweazle under some linux distros 59 | * October 2021: Support for HD disks, turbo speed and a few other things - Requires new disk.cpp from https://github.com/RobSmithDev/WinUAE 60 | Fixed 64-bit build with the FTDI driver 61 | * 19th June 2021: Massivly improve write system. Requires new disk.cpp from https://github.com/RobSmithDev/WinUAE 62 | Small update for Bridge to support 'no-click' option 63 | Fixed some issues with thread scheduling on Linux (Pi) 64 | * October 2021: Added HD support, 'Turbo' mode, a DLL version, and loads of bug fixes 65 | 66 | # Supported Devices: 67 | ## DrawBridge aka Arduino Reader/Writer at https://amiga.robsmithdev.co.uk 68 | Requires a mod to the original (pre 2021) circuit to give access to the DISKCHANGE pin. Requires firmware V1.8+ 69 | 70 | ## Greeseweazle at https://github.com/keirf/Greaseweazle 71 | Requires firmware 0.27 or newer. Only some boards support the DISKCHANGE pin. The rest will be simulated by spinning up the disk several times. 72 | Greaseweazle supports Amiga drives, but this isn't recommended as the DISKCHANGE pin isn't available for these so will cause additional wear on the disk. 73 | 74 | ## Supercard Pro at https://www.cbmstuff.com/ 75 | Requires firmware V1.3 76 | 77 | # Contribute 78 | If you have another device you would like to contribute to this or have a suggestion or improvement let me know/make a pull request. 79 | 80 | RobSmithDev 81 | https://www.patreon.com/RobSmithDev 82 | https://amiga.robsmithdev.co.uk/winuae 83 | https://www.youtube.com/c/robsmithdev 84 | -------------------------------------------------------------------------------- /floppybridge/ADFBridge.cpp: -------------------------------------------------------------------------------- 1 | #include "floppybridge_abstract.h" 2 | #include "ADFBridge.h" 3 | #include "RotationExtractor.h" 4 | #include 5 | 6 | 7 | /***************************************************************************** 8 | * ADFBridge - THIS IS NOT MEANT TO BE USED IN PRODUCTION 9 | * THIS IS PURELY FOR TESTING THE 'BRIDGE' INTERFACE WITH A KNOWN WORKING FILE 10 | ****************************************************************************/ 11 | 12 | 13 | #define MFM_MASK 0x55555555L 14 | #define AMIGA_WORD_SYNC 0x4489 // Disk SYNC code for the Amiga start of sector 15 | #define SECTOR_BYTES 512 // Number of bytes in a decoded sector 16 | #define NUM_SECTORS_PER_TRACK 11 // Number of sectors per track 17 | #define RAW_SECTOR_SIZE (8+56+SECTOR_BYTES+SECTOR_BYTES) // Size of a sector, *Including* the sector sync word longs 18 | #define ADF_TRACK_SIZE (SECTOR_BYTES*NUM_SECTORS_PER_TRACK) // Bytes required for a single track 19 | 20 | typedef unsigned char RawEncodedSector[RAW_SECTOR_SIZE]; 21 | typedef unsigned char RawDecodedSector[SECTOR_BYTES]; 22 | typedef RawDecodedSector RawDecodedTrack[NUM_SECTORS_PER_TRACK]; 23 | typedef unsigned char RawMFMData[SECTOR_BYTES + SECTOR_BYTES]; 24 | 25 | 26 | typedef struct alignas(1) { 27 | unsigned char filler1[4]; // Padding at the start of the track. This will be set to 0xaa 28 | // Raw sector data 29 | RawEncodedSector sectors[NUM_SECTORS_PER_TRACK]; // 11968 bytes 30 | // Blank "Filler" gap content. (this may get overwritten by the sectors a little) 31 | unsigned char filler2[696]; 32 | } FullDiskTrack; 33 | typedef struct alignas(8) { 34 | unsigned char trackFormat; // This will be 0xFF for Amiga 35 | unsigned char trackNumber; // Current track number (this is actually (tracknumber*2) + side 36 | unsigned char sectorNumber; // The sector we just read (0 to 11) 37 | unsigned char sectorsRemaining; // How many more sectors remain until the gap (0 to 10) 38 | 39 | unsigned long sectorLabel[4]; // OS Recovery Data, we ignore this 40 | 41 | unsigned long headerChecksum; // Read from the header, header checksum 42 | unsigned long dataChecksum; // Read from the header, data checksum 43 | 44 | unsigned long headerChecksumCalculated; // The header checksum we calculate 45 | unsigned long dataChecksumCalculated; // The data checksum we calculate 46 | 47 | RawDecodedSector data; // decoded sector data 48 | 49 | RawMFMData rawSector; // raw track data, for analysis of invalid sectors 50 | } DecodedSector; 51 | struct DecodedTrack { 52 | // A list of valid sectors where the checksums are OK 53 | std::vector validSectors; 54 | // A list of sectors found with invalid checksums. These are used if ignore errors is triggered 55 | // We keep copies of each one so we can perform a statistical analysis to see if we can get a working one based on which bits are mostly set the same 56 | std::vector invalidSectors[NUM_SECTORS_PER_TRACK]; 57 | }; 58 | 59 | 60 | 61 | // MFM encoding algorithm part 1 - this just writes the actual data bits in the right places 62 | // *input; RAW data buffer (size == data_size) 63 | // *output; MFM encoded buffer (size == data_size*2) 64 | // Returns the checksum calculated over the data 65 | unsigned long encodeMFMdataPart1(const unsigned long* input, unsigned long* output, const unsigned int data_size) { 66 | unsigned long chksum = 0L; 67 | unsigned int count; 68 | 69 | unsigned long* outputOdd = output; 70 | unsigned long* outputEven = (unsigned long*)(((unsigned char*)output) + data_size); 71 | 72 | // Encode over two passes. First split out the odd and even data, then encode the MFM values, the /4 is because we're working in longs, not bytes 73 | for (count = 0; count < data_size / 4; count++) { 74 | *outputEven = *input & MFM_MASK; 75 | *outputOdd = ((*input) >> 1) & MFM_MASK; 76 | outputEven++; 77 | outputOdd++; 78 | input++; 79 | } 80 | 81 | // Checksum calculator 82 | // Encode over two passes. First split out the odd and even data, then encode the MFM values, the /4 is because we're working in longs, not bytes 83 | for (count = 0; count < (data_size / 4) * 2; count++) { 84 | chksum ^= *output; 85 | output++; 86 | } 87 | 88 | return chksum & MFM_MASK; 89 | } 90 | 91 | 92 | // Encode a sector into the correct format for disk 93 | void encodeSector(const unsigned int trackNumber, const ADFBridge::DiskSurface surface, const unsigned int sectorNumber, const RawDecodedSector& input, RawEncodedSector& encodedSector, unsigned char& lastByte) { 94 | // Sector Start 95 | encodedSector[0] = (lastByte & 1) ? 0x2A : 0xAA; 96 | encodedSector[1] = 0xAA; 97 | encodedSector[2] = 0xAA; 98 | encodedSector[3] = 0xAA; 99 | // Sector Sync 100 | encodedSector[4] = 0x44; 101 | encodedSector[5] = 0x89; 102 | encodedSector[6] = 0x44; 103 | encodedSector[7] = 0x89; 104 | 105 | // MFM Encoded header 106 | DecodedSector header; 107 | memset(&header, 0, sizeof(header)); 108 | 109 | header.trackFormat = 0xFF; 110 | header.trackNumber = (trackNumber << 1) | ((surface == ADFBridge::DiskSurface::dsUpper) ? 1 : 0); 111 | header.sectorNumber = sectorNumber; 112 | header.sectorsRemaining = NUM_SECTORS_PER_TRACK - sectorNumber; //1..11 113 | 114 | 115 | header.headerChecksumCalculated = encodeMFMdataPart1((const unsigned long*)&header, (unsigned long*)&encodedSector[8], 4); 116 | // Then theres the 16 bytes of the volume label that isnt used anyway 117 | header.headerChecksumCalculated ^= encodeMFMdataPart1((const unsigned long*)&header.sectorLabel, (unsigned long*)&encodedSector[16], 16); 118 | // Thats 40 bytes written as everything doubles (8+4+4+16+16). - Encode the header checksum 119 | encodeMFMdataPart1((const unsigned long*)&header.headerChecksumCalculated, (unsigned long*)&encodedSector[48], 4); 120 | // And move on to the data section. Next should be the checksum, but we cant encode that until we actually know its value! 121 | header.dataChecksumCalculated = encodeMFMdataPart1((const unsigned long*)&input, (unsigned long*)&encodedSector[64], SECTOR_BYTES); 122 | // And add the checksum 123 | encodeMFMdataPart1((const unsigned long*)&header.dataChecksumCalculated, (unsigned long*)&encodedSector[56], 4); 124 | 125 | // Now fill in the MFM clock bits 126 | bool lastBit = encodedSector[7] & (1 << 0); 127 | bool thisBit = lastBit; 128 | 129 | // Clock bits are bits 7, 5, 3 and 1 130 | // Data is 6, 4, 2, 0 131 | for (int count = 8; count < RAW_SECTOR_SIZE; count++) { 132 | for (int bit = 7; bit >= 1; bit -= 2) { 133 | lastBit = thisBit; 134 | thisBit = encodedSector[count] & (1 << (bit - 1)); 135 | 136 | if (!(lastBit || thisBit)) { 137 | // Encode a 1! 138 | encodedSector[count] |= (1 << bit); 139 | } 140 | } 141 | } 142 | 143 | lastByte = encodedSector[RAW_SECTOR_SIZE - 1]; 144 | } 145 | 146 | ADFBridge::ADFBridge(int flags, const bool canStall, const bool useIndex) { 147 | HANDLE fle = CreateFile(L"file.adf", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); 148 | 149 | RawDecodedTrack track; 150 | 151 | int surfaceIndex = 0; 152 | int currentTrack = 0; 153 | FullDiskTrack disktrack; 154 | 155 | memset(disktrack.filler1, 0xAA, sizeof(disktrack.filler1)); // Pad with "0"s which as an MFM encoded byte is 0xAA 156 | memset(disktrack.filler2, 0xAA, sizeof(disktrack.filler2)); // Pad with "0"s which as an MFM encoded byte is 0xAA 157 | 158 | DWORD read; 159 | while (ReadFile(fle, &track, sizeof(track), &read, NULL)) { 160 | if (read != sizeof(track)) break; 161 | DiskSurface surface = (surfaceIndex == 1) ? DiskSurface::dsUpper : DiskSurface::dsLower; 162 | 163 | unsigned char lastByte = disktrack.filler1[sizeof(disktrack.filler1) - 1]; 164 | for (unsigned int sector = 0; sector < NUM_SECTORS_PER_TRACK; sector++) 165 | encodeSector(currentTrack, surface, sector, track[sector], disktrack.sectors[sector], lastByte); 166 | if (lastByte & 1) disktrack.filler2[0] = 0x2F; else disktrack.filler2[0] = 0xFF; 167 | unsigned char* buffer = (unsigned char*)&disktrack; 168 | memcpy_s(m_mfmRead[currentTrack][(int)surface].mfmBuffer, MFM_BUFFER_MAX_TRACK_LENGTH, &disktrack, sizeof(disktrack)); 169 | m_mfmRead[currentTrack][(int)surface].amountReadInBits = sizeof(disktrack) * 8; 170 | // Goto the next surface/track 171 | surfaceIndex++; 172 | if (surfaceIndex > 1) { 173 | surfaceIndex = 0; 174 | currentTrack++; 175 | } 176 | // But there is a physical limit 177 | if (currentTrack > 81) break; 178 | 179 | } 180 | 181 | CloseHandle(fle); 182 | } 183 | 184 | ADFBridge::~ADFBridge() { 185 | 186 | } 187 | 188 | // Call to start the system up 189 | bool ADFBridge::initialise() { 190 | return true; 191 | } 192 | 193 | // This is called prior to closing down, but shoudl reverse initialise 194 | void ADFBridge::shutdown() { 195 | } 196 | 197 | // Returns TRUE when the last command requested has completed 198 | bool ADFBridge::isReady() { 199 | return m_isMotorRunning && (GetTickCount()- m_turnOnTime > 800); 200 | }; 201 | 202 | 203 | // While not doing anything else, the library should be continuously streaming the current track if the motor is on. mfmBufferPosition is in BITS 204 | bool ADFBridge::getMFMBit(const int mfmPositionBits) { 205 | // Internally manage loops until the data is ready 206 | const int mfmPositionByte = mfmPositionBits >> 3; 207 | int mfmPositionBit = 7- (mfmPositionBits & 7); 208 | 209 | return (m_mfmRead[m_currentTrack][(int)m_floppySide].mfmBuffer[mfmPositionByte] & (1 << mfmPositionBit)) != 0; 210 | } 211 | 212 | // Set the status of the motor. 213 | void ADFBridge::setMotorStatus(bool side, bool turnOn) { 214 | if ((!m_isMotorRunning) && (turnOn)) m_turnOnTime = GetTickCount(); 215 | m_isMotorRunning = turnOn; 216 | } 217 | 218 | // Return the maximum size of the internal track buffer in BITS 219 | int ADFBridge::maxMFMBitPosition() { 220 | return m_mfmRead[m_currentTrack][(int)m_floppySide].amountReadInBits; 221 | } 222 | 223 | // This is called to switch to a different copy of the track so multiple revolutions can ve read 224 | void ADFBridge::mfmSwitchBuffer(bool side) { 225 | m_floppySide = side ? DiskSurface::dsUpper : DiskSurface::dsLower; 226 | } 227 | 228 | // Seek to a specific track 229 | void ADFBridge::gotoCylinder(int trackNumber, bool side) { 230 | m_floppySide = side ? DiskSurface::dsUpper : DiskSurface::dsLower; 231 | m_currentTrack = trackNumber; 232 | } 233 | 234 | // If we're on track 0, this is the emulator trying to seek to track -1. We catch this as a special case. 235 | // Should perform the same operations as setCurrentCylinder in terms of diskchange etc but without changing the current cylinder 236 | // Return FALSE if this is not supported by the bridge 237 | void ADFBridge::handleNoClickStep(bool side) { 238 | m_floppySide = side ? DiskSurface::dsUpper : DiskSurface::dsLower; 239 | } 240 | 241 | 242 | // Submits a single WORD of data received during a DMA transfer to the disk buffer. This needs to be saved. It is usually flushed when commitWriteBuffer is called 243 | // You should reset this buffer if side or track changes. mfmPosition is provided purely for any index sync you may wish to do 244 | void ADFBridge::writeShortToBuffer(bool side, unsigned int track, unsigned short mfmData, int mfmPosition) { 245 | } 246 | 247 | // Requests that any data received via writeShortToBuffer be saved to disk. The side and track should match against what you have been collected 248 | // and the buffer should be reset upon completion. You should return the new tracklength (maxMFMBitPosition) with optional padding if needed 249 | unsigned int ADFBridge::commitWriteBuffer(bool side, unsigned int track) { 250 | m_floppySide = side ? DiskSurface::dsUpper : DiskSurface::dsLower; 251 | m_currentTrack = track; 252 | return maxMFMBitPosition(); 253 | } 254 | 255 | // Return TRUE if we're at the INDEX marker 256 | bool ADFBridge::isMFMPositionAtIndex(int mfmPositionBits) { 257 | return mfmPositionBits == 0; 258 | } 259 | 260 | bool ADFBridge::resetDrive(int trackNumber) { 261 | m_currentTrack = trackNumber; 262 | m_isMotorRunning = false; 263 | return true; 264 | }; 265 | 266 | -------------------------------------------------------------------------------- /floppybridge/ADFBridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /***************************************************************************** 3 | * ADFBridge - THIS IS NOT MEANT TO BE USED IN PRODUCTION 4 | * THIS IS PURELY FOR TESTING THE 'BRIDGE' INTERFACE WITH A KNOWN WORKING FILE 5 | ****************************************************************************/ 6 | 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "floppybridge_abstract.h" 16 | 17 | 18 | #define MFM_BUFFER_MAX_TRACK_LENGTH 0x3800 19 | #define MAX_CYLINDER_BRIDGE 82 20 | class ADFBridge : public FloppyDiskBridge { 21 | public: 22 | enum class DiskSurface { dsUpper = 1, dsLower = 0 }; 23 | 24 | private: 25 | struct MFMCache { 26 | unsigned char mfmBuffer[MFM_BUFFER_MAX_TRACK_LENGTH]; 27 | int amountReadInBits = 1024; 28 | }; 29 | // Cache of entire disk (or what we have so far) 30 | MFMCache m_mfmRead[MAX_CYLINDER_BRIDGE][2]; 31 | int m_currentTrack = 0; 32 | bool m_isMotorRunning = false; 33 | DiskSurface m_floppySide = DiskSurface::dsLower; 34 | unsigned int m_turnOnTime = 0; 35 | public: 36 | ADFBridge(int flags, const bool canStall, const bool useIndex); 37 | virtual ~ADFBridge(); 38 | virtual bool initialise() override final; 39 | virtual void shutdown() override final; 40 | virtual const TCHAR* getDriveIDName() override final { 41 | return L"ADFBridge"; 42 | } 43 | virtual DriveTypeID getDriveTypeID() override final { return DriveTypeID::dti35DD; }; 44 | virtual unsigned int getLastErrorMessage(TCHAR* errorMessage, unsigned int length) override { return 0; }; 45 | virtual bool isAtCylinder0() override final { return m_currentTrack == 0; } 46 | virtual unsigned char getMaxCylinder() override final { return MAX_CYLINDER_BRIDGE; }; 47 | virtual bool isMotorRunning() override final { return m_isMotorRunning; }; 48 | virtual void handleNoClickStep(bool side) override final; 49 | virtual bool isReady() override; 50 | virtual bool isDiskInDrive() override final { return true; }; 51 | virtual bool hasDiskChanged() override final { return false; }; 52 | virtual unsigned char getCurrentCylinderNumber() override final { return m_currentTrack; }; 53 | virtual bool isWriteProtected() override final { return true; }; 54 | virtual int getMFMSpeed(const int mfmPositionBits) override { return 1000; } 55 | virtual bool getMFMBit(const int mfmPositionBits) override; 56 | virtual void setMotorStatus(bool side, bool turnOn) override final; 57 | virtual int maxMFMBitPosition() override final; 58 | virtual void mfmSwitchBuffer(bool side) override final; 59 | virtual void gotoCylinder(int trackNumber, bool side) override final; 60 | virtual void writeShortToBuffer(bool side, unsigned int track, unsigned short mfmData, int mfmPosition) override final; 61 | virtual unsigned int commitWriteBuffer(bool side, unsigned int track) override final; 62 | virtual bool isMFMPositionAtIndex(int mfmPositionBits) override final; 63 | virtual bool resetDrive(int trackNumber) override final; 64 | }; 65 | 66 | -------------------------------------------------------------------------------- /floppybridge/ArduinoFloppyBridge.cpp: -------------------------------------------------------------------------------- 1 | /* DrawBridge (Arduino Reader/Writer) Bridge for *UAE 2 | * 3 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 4 | * https://amiga.robsmithdev.co.uk 5 | * 6 | * This file is multi-licensed under the terms of the Mozilla Public 7 | * License Version 2.0 as published by Mozilla Corporation and the 8 | * GNU General Public License, version 2 or later, as published by the 9 | * Free Software Foundation. 10 | * 11 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 12 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 13 | * 14 | * This file, along with currently active and supported interfaces 15 | * are maintained from by GitHub repo at 16 | * https://github.com/RobSmithDev/FloppyDriveBridge 17 | */ 18 | 19 | ///////////////////////////////////////////////////////////////////////////////////////////////////////// 20 | // This class provides an interface between WinUAE and the Arduino Floppy Reader/Writer aka DrawBridge // 21 | ///////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | // 23 | // ROMTYPE_ARDUINOREADER_WRITER must be defined for this to work 24 | 25 | #include "floppybridge_config.h" 26 | 27 | #ifdef ROMTYPE_ARDUINOREADER_WRITER 28 | 29 | #include "floppybridge_abstract.h" 30 | #include "CommonBridgeTemplate.h" 31 | #include "ArduinoFloppyBridge.h" 32 | #include "ArduinoInterface.h" 33 | 34 | 35 | static const FloppyDiskBridge::BridgeDriver DriverArduinoFloppy = { 36 | "DrawBridge aka Arduino Reader/Writer", "https://amiga.robsmithdev.co.uk", "RobSmithDev", "RobSmithDev", CONFIG_OPTIONS_COMPORT | CONFIG_OPTIONS_COMPORT_AUTODETECT | CONFIG_OPTIONS_SMARTSPEED | CONFIG_OPTIONS_AUTOCACHE 37 | }; 38 | 39 | // Flags from WINUAE 40 | ArduinoFloppyDiskBridge::ArduinoFloppyDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, bool enableAutoCache, bool useSmartSpeed, bool autoDetectComPort, char* comPort) : 41 | CommonBridgeTemplate(bridgeMode, bridgeDensity, enableAutoCache, useSmartSpeed), m_comPort(autoDetectComPort ? "" : comPort) { 42 | } 43 | 44 | // This is for the static version 45 | ArduinoFloppyDiskBridge::ArduinoFloppyDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, int uaeSettings) : 46 | CommonBridgeTemplate(bridgeMode, bridgeDensity, false, false) { 47 | 48 | if (uaeSettings > 0) { 49 | char buffer[20]; 50 | #ifdef _WIN32 51 | sprintf_s(buffer, "COM%i", uaeSettings); 52 | #else 53 | snprintf(buffer, 20, "COM%i", uaeSettings); 54 | #endif 55 | m_comPort = buffer; 56 | } 57 | else m_comPort = ""; 58 | } 59 | 60 | 61 | ArduinoFloppyDiskBridge::~ArduinoFloppyDiskBridge() { 62 | } 63 | 64 | // If your device supports being able to abort a disk read, mid-read then implement this 65 | void ArduinoFloppyDiskBridge::abortDiskReading() { 66 | m_io.abortReadStreaming(); 67 | }; 68 | 69 | // A manual way to detect a disk change event 70 | bool ArduinoFloppyDiskBridge::attemptToDetectDiskChange() { 71 | return getDiskChangeStatus(true); 72 | } 73 | 74 | // If your device supports the DiskChange option then return TRUE here. If not, then the code will simulate it 75 | bool ArduinoFloppyDiskBridge::supportsDiskChange() { 76 | return m_io.getFirwareVersion().fullControlMod; 77 | } 78 | 79 | // Called when the class is about to shut down 80 | void ArduinoFloppyDiskBridge::closeInterface() { 81 | // Turn everything off 82 | m_io.enableReading(false); 83 | m_io.closePort(); 84 | } 85 | 86 | // Returns the COM port number to use 87 | std::wstring ArduinoFloppyDiskBridge::getComPort() { 88 | // 0 means auto-detect. 89 | if (m_comPort.length()) { 90 | std::wstring widePort; 91 | quicka2w(m_comPort, widePort); 92 | return widePort; 93 | } 94 | 95 | // Returns a list of ports this could be available on 96 | std::vector portsAvailable; 97 | ArduinoFloppyReader::ArduinoInterface::enumeratePorts(portsAvailable); 98 | 99 | for (const std::wstring& port : portsAvailable) 100 | if (ArduinoFloppyReader::ArduinoInterface::isPortCorrect(port)) { 101 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 102 | return port; 103 | } 104 | 105 | return L""; 106 | } 107 | 108 | // Called to start the interface, you should update any error messages if it fails. This needs to be ready to see to any cylinder and so should already know where cylinder 0 is 109 | bool ArduinoFloppyDiskBridge::openInterface(std::string& errorMessage) { 110 | 111 | const std::wstring prt = getComPort(); 112 | if (prt.empty()) { 113 | errorMessage = "The serial port could not be found or detected. Please try re-connecting the interface."; 114 | return false; 115 | } 116 | 117 | ArduinoFloppyReader::DiagnosticResponse error = m_io.openPort(prt); 118 | if (error == ArduinoFloppyReader::DiagnosticResponse::drOK) { 119 | m_currentCylinder = 0; 120 | 121 | // See which version it is 122 | ArduinoFloppyReader::FirmwareVersion fv = m_io.getFirwareVersion(); 123 | if ((fv.major <= 1) && (fv.minor < 8)) { 124 | // Must be at least V1.8 125 | char buf[20]; 126 | #ifdef _WIN32 127 | sprintf_s(buf, "%i.%i.%i", fv.major, fv.minor, fv.buildNumber); 128 | #else 129 | sprintf(buf, "%i.%i.%i", fv.major, fv.minor, fv.buildNumber); 130 | #endif 131 | errorMessage = "DrawBridge aka Arduino Floppy Reader/Writer Firmware is Out Of Date\n\nWinUAE requires V1.8 (and ideally with the modded circuit design).\n\n"; 132 | errorMessage += "You are currently using V" + std::string(buf) + ". Please update the firmware."; 133 | } 134 | else { 135 | #ifdef _WIN32 136 | 137 | if (!fv.fullControlMod) { 138 | DWORD hasBeenSeen = 0; 139 | DWORD dataSize = sizeof(hasBeenSeen); 140 | 141 | HKEY key = 0; 142 | if (SUCCEEDED(RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\RobSmithDev\\ArduinoReaderWriter", 0, NULL, 0, KEY_READ | KEY_WRITE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY, NULL, &key, NULL))) 143 | if (!RegQueryValueEx(key, L"WarningShown", NULL, NULL, (LPBYTE)&hasBeenSeen, &dataSize)) dataSize = 0; 144 | 145 | if (!hasBeenSeen) { 146 | hasBeenSeen = 1; 147 | if (key) RegSetValueEx(key, L"WarningShown", NULL, REG_DWORD, (LPBYTE)&hasBeenSeen, sizeof(hasBeenSeen)); 148 | errorMessage = "DrawBridge aka Arduino Reader / Writer hasn't had the 'hardware mod' applied for optimal WinUAE Support.\nThis mod is highly recommended for best experience and will reduce disk access."; 149 | } 150 | if (key) RegCloseKey(key); 151 | } 152 | #endif 153 | m_io.findTrack0(); 154 | return true; 155 | } 156 | } 157 | else { 158 | errorMessage = m_io.getLastErrorStr(); 159 | } 160 | 161 | return false; 162 | } 163 | 164 | // Return TRUE if the drive is still connected and working 165 | bool ArduinoFloppyDiskBridge::isStillWorking() { 166 | return !m_wasIOError; 167 | } 168 | 169 | 170 | // Called when a disk is inserted so that you can (re)populate the response to _getDriveTypeID() 171 | void ArduinoFloppyDiskBridge::checkDiskType() { 172 | bool capacity; 173 | 174 | if (m_io.checkDiskCapacity(capacity) == ArduinoFloppyReader::DiagnosticResponse::drOK) { 175 | m_isHDDisk = capacity; 176 | m_io.setDiskCapacity(m_isHDDisk); 177 | } 178 | else { 179 | m_isHDDisk = false; 180 | m_io.setDiskCapacity(m_isHDDisk); 181 | } 182 | } 183 | 184 | // Called to force into DD or HD mode. Overrides checkDiskType() until checkDiskType() is called again 185 | void ArduinoFloppyDiskBridge::forceDiskDensity(bool forceHD) { 186 | m_isHDDisk = forceHD; 187 | m_io.setDiskCapacity(m_isHDDisk); 188 | } 189 | 190 | const FloppyDiskBridge::BridgeDriver* ArduinoFloppyDiskBridge::staticBridgeInformation() { 191 | return &DriverArduinoFloppy; 192 | } 193 | 194 | // Get the name of the drive 195 | const FloppyDiskBridge::BridgeDriver* ArduinoFloppyDiskBridge::_getDriverInfo() { 196 | return staticBridgeInformation(); 197 | } 198 | 199 | // Duplicate of the one below, but here for consistency - Returns the name of interface. This pointer should remain valid after the class is destroyed 200 | const FloppyDiskBridge::DriveTypeID ArduinoFloppyDiskBridge::_getDriveTypeID() { 201 | return m_isHDDisk ? DriveTypeID::dti35HD : DriveTypeID::dti35DD; 202 | } 203 | 204 | // Called to switch which head is being used right now. Returns success or not 205 | bool ArduinoFloppyDiskBridge::setActiveSurface(const DiskSurface activeSurface) { 206 | return m_io.selectSurface(activeSurface == DiskSurface::dsUpper ? ArduinoFloppyReader::DiskSurface::dsUpper : ArduinoFloppyReader::DiskSurface::dsLower) == ArduinoFloppyReader::DiagnosticResponse::drOK; 207 | } 208 | 209 | // Set the status of the motor on the drive. The motor should maintain this status until switched off or reset. This should *NOT* wait for the motor to spin up 210 | bool ArduinoFloppyDiskBridge::setMotorStatus(const bool switchedOn) { 211 | return m_io.enableReading(switchedOn, false, true) == ArduinoFloppyReader::DiagnosticResponse::drOK; 212 | } 213 | 214 | // Called to ask the drive what the current write protect status is - return true if its write protected 215 | bool ArduinoFloppyDiskBridge::checkWriteProtectStatus(const bool forceCheck) { 216 | return m_io.checkIfDiskIsWriteProtected(forceCheck) == ArduinoFloppyReader::DiagnosticResponse::drWriteProtected; 217 | } 218 | 219 | // If the above is TRUE then this is called to get the status of the DiskChange line. Basically, this is TRUE if there is a disk in the drive. 220 | // If force is true you should re-check, if false, then you are allowed to return a cached value from the last disk operation (eg: seek) 221 | bool ArduinoFloppyDiskBridge::getDiskChangeStatus(const bool forceCheck) { 222 | if ((forceCheck) && (m_io.getFirwareVersion().fullControlMod)) { 223 | if (m_currentCylinder == 0) { 224 | if (performNoClickSeek()) { 225 | return m_io.isDiskInDrive(); 226 | } 227 | } 228 | } 229 | 230 | // Perform the check, if required 231 | switch (m_io.checkForDisk(forceCheck)) { 232 | case ArduinoFloppyReader::DiagnosticResponse::drNoDiskInDrive: return false; 233 | case ArduinoFloppyReader::DiagnosticResponse::drOK: return true; 234 | case ArduinoFloppyReader::DiagnosticResponse::drOldFirmware: return false; 235 | default:m_wasIOError = true; return false; 236 | } 237 | } 238 | 239 | // If we're on track 0, this is the emulator trying to seek to track -1. We catch this as a special case. 240 | // Should perform the same operations as setCurrentCylinder in terms of disk change etc but without changing the current cylinder 241 | // Return FALSE if this is not supported by the bridge 242 | bool ArduinoFloppyDiskBridge::performNoClickSeek() { 243 | // Claim we did it anyway 244 | if (!m_io.getFirwareVersion().fullControlMod) return true; 245 | 246 | switch (m_io.performNoClickSeek()) { 247 | case ArduinoFloppyReader::DiagnosticResponse::drOK: 248 | updateLastManualCheckTime(); 249 | return true; 250 | case ArduinoFloppyReader::DiagnosticResponse::drOldFirmware: 251 | return false; 252 | case ArduinoFloppyReader::DiagnosticResponse::drReadResponseFailed: 253 | case ArduinoFloppyReader::DiagnosticResponse::drSendFailed: 254 | case ArduinoFloppyReader::DiagnosticResponse::drSendParameterFailed: 255 | m_wasIOError = true; 256 | return false; 257 | } 258 | 259 | return false; 260 | } 261 | 262 | // Trigger a seek to the requested cylinder, this can block until complete 263 | bool ArduinoFloppyDiskBridge::setCurrentCylinder(const unsigned int cylinder) { 264 | m_currentCylinder = cylinder; 265 | 266 | // No need if its busy 267 | bool ignoreDiskCheck = isMotorRunning() && !isReady(); 268 | 269 | // If we don't support disk change then don't allow the hardware to check for a disk as it takes time, unless it's due anyway 270 | if (!m_io.getFirwareVersion().fullControlMod) ignoreDiskCheck |= !isReadyForManualDiskCheck(); 271 | 272 | // Go! - and don't ask 273 | ArduinoFloppyReader::DiagnosticResponse dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssVeryFast, ignoreDiskCheck); 274 | if (dr != ArduinoFloppyReader::DiagnosticResponse::drOK) dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssFast, ignoreDiskCheck); 275 | if (dr != ArduinoFloppyReader::DiagnosticResponse::drOK) dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssNormal, ignoreDiskCheck); 276 | if (dr != ArduinoFloppyReader::DiagnosticResponse::drOK) dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssNormal, ignoreDiskCheck); 277 | if (dr == ArduinoFloppyReader::DiagnosticResponse::drOK) { 278 | if (!ignoreDiskCheck) updateLastManualCheckTime(); 279 | setWriteProtectStatus(m_io.checkIfDiskIsWriteProtected(false) == ArduinoFloppyReader::DiagnosticResponse::drWriteProtected); 280 | 281 | return true; 282 | } 283 | 284 | return false; 285 | } 286 | 287 | // Called when data should be read from the drive. 288 | // pll: supplied if you use it 289 | // maxBufferSize: Maximum number of RotationExtractor::MFMSample in the buffer. If we're trying to detect a disk, this might be set VERY LOW 290 | // buffer: Where to save to. When a buffer is saved, position 0 MUST be where the INDEX pulse is. RevolutionExtractor will do this for you 291 | // indexMarker: Used by rotationExtractor if you use it, to help be consistent where the INDEX position is read back at 292 | // onRotation: A function you should call for each complete revolution received. If the function returns FALSE then you should abort reading, else keep sending revolutions 293 | // Returns: ReadResponse, explains its self 294 | CommonBridgeTemplate::ReadResponse ArduinoFloppyDiskBridge::readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker, 295 | std::function onRotation) { 296 | 297 | ArduinoFloppyReader::DiagnosticResponse result = m_io.readRotation(*pll.rotationExtractor(), maxBufferSize, buffer, indexMarker, 298 | [&onRotation](RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits) -> bool { 299 | return onRotation(*mfmData, dataLengthInBits); 300 | }, true); 301 | 302 | switch (result) { 303 | case ArduinoFloppyReader::DiagnosticResponse::drOK: return ReadResponse::rrOK; 304 | case ArduinoFloppyReader::DiagnosticResponse::drNoDiskInDrive: return ReadResponse::rrNoDiskInDrive; 305 | default: return ReadResponse::rrError; 306 | } 307 | } 308 | 309 | // Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor 310 | // pll: required 311 | // Returns: ReadResponse, explains its self 312 | CommonBridgeTemplate::ReadResponse ArduinoFloppyDiskBridge::readLinearData(PLL::BridgePLL& pll) { 313 | ArduinoFloppyReader::DiagnosticResponse result = m_io.readData(pll); 314 | 315 | switch (result) { 316 | case ArduinoFloppyReader::DiagnosticResponse::drOK: return ReadResponse::rrOK; 317 | case ArduinoFloppyReader::DiagnosticResponse::drNoDiskInDrive: return ReadResponse::rrNoDiskInDrive; 318 | default: return ReadResponse::rrError; 319 | } 320 | } 321 | 322 | // Called when a cylinder revolution should be written to the disk. 323 | // Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte 324 | // numBytes Number of bits in the buffer to write 325 | // writeFromIndex If an attempt should be made to write this from the INDEX pulse rather than just a random position 326 | // suggestUsingPrecompensation A suggestion that you might want to use write pre-compensation, optional 327 | // Returns TRUE if success, or false if it fails. Largely doesn't matter as most stuff should verify with a read straight after 328 | bool ArduinoFloppyDiskBridge::writeData(const unsigned char* rawMFMData, const unsigned int numBits, const bool writeFromIndex, const bool suggestUsingPrecompensation) { 329 | switch (m_io.writeCurrentTrackPrecomp(rawMFMData, (numBits + 7) / 8, writeFromIndex, suggestUsingPrecompensation)) { 330 | case ArduinoFloppyReader::DiagnosticResponse::drOK: return true; 331 | case ArduinoFloppyReader::DiagnosticResponse::drWriteProtected: setWriteProtectStatus(true); return false; 332 | default: return false; 333 | } 334 | } 335 | 336 | #endif 337 | -------------------------------------------------------------------------------- /floppybridge/ArduinoFloppyBridge.h: -------------------------------------------------------------------------------- 1 | #ifndef ARDUINO_FLOPPY_BRIDGE 2 | #define ARDUINO_FLOPPY_BRIDGE 3 | /* DrawBridge (Arduino Reader/Writer) Bridge for *UAE 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * This file, along with currently active and supported interfaces 17 | * are maintained from by GitHub repo at 18 | * https://github.com/RobSmithDev/FloppyDriveBridge 19 | */ 20 | 21 | ///////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | // This class provides an interface between WinUAE and the Arduino Floppy Reader/Writer aka DrawBridge // 23 | ///////////////////////////////////////////////////////////////////////////////////////////////////////// 24 | // 25 | // ROMTYPE_ARDUINOREADER_WRITER must be defined for this to work 26 | 27 | 28 | #ifdef ROMTYPE_ARDUINOREADER_WRITER 29 | 30 | #include "floppybridge_abstract.h" 31 | #include "CommonBridgeTemplate.h" 32 | #include "ArduinoInterface.h" 33 | #include "pll.h" 34 | 35 | class ArduinoFloppyDiskBridge : public CommonBridgeTemplate { 36 | private: 37 | // Which com port should we use? or blank for automatic 38 | std::string m_comPort; 39 | 40 | // type of disk inserted 41 | bool m_isHDDisk = false; 42 | 43 | // Used to detect disconnection of DrawBridge while in use 44 | bool m_wasIOError = false; 45 | 46 | // Hardware connection 47 | ArduinoFloppyReader::ArduinoInterface m_io; 48 | 49 | // Returns the serial port to use 50 | std::wstring getComPort(); 51 | 52 | // Remember where we are 53 | int m_currentCylinder = 0; 54 | protected: 55 | // Called when a disk is inserted so that you can (re)populate the response to _getDriveTypeID() 56 | virtual void checkDiskType() override; 57 | 58 | // Called to force into DD or HD mode. Overrides checkDiskType() until checkDiskType() is called again 59 | virtual void forceDiskDensity(bool forceHD) override; 60 | 61 | // If your device supports being able to abort a disk read, mid-read then implement this 62 | virtual void abortDiskReading() override; 63 | 64 | // Return TRUE if the drive is still connected and working 65 | virtual bool isStillWorking() override; 66 | 67 | // If your device supports the DiskChange option then return TRUE here. If not, then the code will simulate it 68 | virtual bool supportsDiskChange() override; 69 | 70 | // If the above is TRUE then this is called to get the status of the DiskChange line. Basically, this is TRUE if there is a disk in the drive. 71 | // If force is true you should re-check, if false, then you are allowed to return a cached value from the last disk operation (eg: seek) 72 | virtual bool getDiskChangeStatus(const bool forceCheck) override; 73 | 74 | // Called when the class is about to shut down 75 | virtual void closeInterface() override; 76 | 77 | // Called to start the interface, you should update any error messages if it fails. This needs to be ready to see to any cylinder and so should already know where cylinder 0 is 78 | virtual bool openInterface(std::string& errorMessage) override; 79 | 80 | // Called to ask the drive what the current write protect status is - return true if its write protected. If forceCheck is true you should actually check the drive, 81 | // else use the last status checked which was probably from a SEEK setCurrentCylinder call. If you can ONLY get this information here then you should always force check 82 | virtual bool checkWriteProtectStatus(const bool forceCheck) override; 83 | 84 | // Get the name of the drive 85 | virtual const BridgeDriver* _getDriverInfo() override; 86 | 87 | // Duplicate of the one below, but here for consistency - Returns the name of interface. This pointer should remain valid after the class is destroyed 88 | virtual const DriveTypeID _getDriveTypeID() override; 89 | 90 | // Called to switch which head is being used right now. Returns success or not 91 | virtual bool setActiveSurface(const DiskSurface activeSurface) override; 92 | 93 | // Set the status of the motor on the drive. The motor should maintain this status until switched off or reset. This should *NOT* wait for the motor to spin up 94 | virtual bool setMotorStatus(const bool switchedOn) override; 95 | 96 | // Trigger a seek to the requested cylinder, this can block until complete 97 | virtual bool setCurrentCylinder(const unsigned int cylinder) override; 98 | 99 | // If we're on track 0, this is the emulator trying to seek to track -1. We catch this as a special case. 100 | // Should perform the same operations as setCurrentCylinder in terms of disk change etc but without changing the current cylinder 101 | // Return FALSE if this is not supported by the bridge 102 | virtual bool performNoClickSeek() override; 103 | 104 | // Called when data should be read from the drive. 105 | // pll: supplied if you use it 106 | // maxBufferSize: Maximum number of RotationExtractor::MFMSample in the buffer. If we're trying to detect a disk, this might be set VERY LOW 107 | // buffer: Where to save to. When a buffer is saved, position 0 MUST be where the INDEX pulse is. RevolutionExtractor will do this for you 108 | // indexMarker: Used by rotationExtractor if you use it, to help be consistent where the INDEX position is read back at 109 | // onRotation: A function you should call for each complete revolution received. If the function returns FALSE then you should abort reading, else keep sending revolutions 110 | // Returns: ReadResponse, explains its self 111 | virtual ReadResponse readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker, 112 | std::function onRotation) override; 113 | 114 | // Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor 115 | // pll: required 116 | // Returns: ReadResponse, explains its self 117 | virtual ReadResponse readLinearData(PLL::BridgePLL& pll) override; 118 | 119 | // Called when a cylinder revolution should be written to the disk. 120 | // Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte 121 | // numBytes Number of bits in the buffer to write 122 | // writeFromIndex If an attempt should be made to write this from the INDEX pulse rather than just a random position 123 | // suggestUsingPrecompensation A suggestion that you might want to use write precompensation, optional 124 | // Returns TRUE if success, or false if it fails. Largely doesn't matter as most stuff should verify with a read straight after 125 | virtual bool writeData(const unsigned char* rawMFMData, const unsigned int numBits, const bool writeFromIndex, const bool suggestUsingPrecompensation) override; 126 | 127 | // A manual way to check for disk change. This is simulated by issuing a read message and seeing if there's any data. Returns TRUE if data or an INDEX pulse was detected 128 | // It's virtual as the default method issues a read and looks for data. If you have a better implementation then override this 129 | virtual bool attemptToDetectDiskChange() override; 130 | 131 | public: 132 | ArduinoFloppyDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, bool enableAutoCache, bool useSmartSpeed, bool autoDetectComPort, char* comPort); 133 | 134 | // This is for the static version 135 | ArduinoFloppyDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, int uaeSettings); 136 | 137 | virtual ~ArduinoFloppyDiskBridge(); 138 | 139 | static const BridgeDriver* staticBridgeInformation(); 140 | }; 141 | 142 | 143 | 144 | #endif 145 | #endif -------------------------------------------------------------------------------- /floppybridge/ArduinoInterface.h: -------------------------------------------------------------------------------- 1 | #ifndef ARDUINO_FLOPPY_READER_WRITER_INTERFACE 2 | #define ARDUINO_FLOPPY_READER_WRITER_INTERFACE 3 | /* ArduinoFloppyReaderWriter aka DrawBridge 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * This file, along with currently active and supported interfaces 17 | * are maintained from by GitHub repo at 18 | * https://github.com/RobSmithDev/FloppyDriveBridge 19 | */ 20 | 21 | //////////////////////////////////////////////////////////////////////////////////////// 22 | // Class to manage the communication between the computer and the Arduino V2.7 // 23 | //////////////////////////////////////////////////////////////////////////////////////// 24 | // 25 | // Purpose: 26 | // The class handles the command interface to the arduino. It doesn't do any decoding 27 | // Just open ports, switch motors on and off, seek to tracks etc. 28 | // 29 | // 30 | // 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "RotationExtractor.h" 38 | #include "pll.h" 39 | #include "SerialIO.h" 40 | 41 | // Paula on the Amiga used to find the SYNC then read 1900 WORDS. (12868 bytes) 42 | // As the PC is doing the SYNC we need to read more than this to allow a further overlap 43 | // This number must match what the sketch in the Arduino is set to. 44 | #define RAW_TRACKDATA_LENGTH_DD (0x1900*2+0x440) 45 | #define RAW_TRACKDATA_LENGTH_HD (2*RAW_TRACKDATA_LENGTH_DD) 46 | // With the disk spinning at 300rpm, and data rate of 500kbps, for a full revolution we should receive 12500 bytes of data (12.5k) 47 | // The above buffer assumes a full Paula data capture plus the size of a sector. 48 | 49 | 50 | #define FLAGS_HIGH_PRECISION_SUPPORT (1 << 0) 51 | #define FLAGS_DISKCHANGE_SUPPORT (1 << 1) 52 | #define FLAGS_DRAWBRIDGE_PLUSMODE (1 << 2) 53 | #define FLAGS_DENSITYDETECT_ENABLED (1 << 3) 54 | #define FLAGS_SLOWSEEKING_MODE (1 << 4) 55 | #define FLAGS_INDEX_ALIGN_MODE (1 << 5) 56 | #define FLAGS_FLUX_READ (1 << 6) 57 | #define FLAGS_FIRMWARE_BETA (1 << 7) 58 | 59 | namespace ArduinoFloppyReader { 60 | 61 | // Array to hold data from a floppy disk read 62 | typedef unsigned char RawTrackDataDD[RAW_TRACKDATA_LENGTH_DD]; 63 | typedef unsigned char RawTrackDataHD[RAW_TRACKDATA_LENGTH_HD]; 64 | 65 | // Sketch firmware version 66 | struct FirmwareVersion { 67 | unsigned char major, minor; 68 | bool fullControlMod; 69 | 70 | // Extra in V1.9 71 | unsigned char deviceFlags1; 72 | unsigned char deviceFlags2; 73 | unsigned char buildNumber; 74 | }; 75 | 76 | // Represent which side of the disk we're looking at 77 | enum class DiskSurface { 78 | dsUpper, // The upper side of the disk 79 | dsLower // The lower side of the disk 80 | }; 81 | 82 | // Speed at which the head will seek to a track 83 | enum class TrackSearchSpeed { 84 | tssSlow = 0, 85 | tssNormal = 1, 86 | tssFast = 2, 87 | tssVeryFast = 3 88 | }; 89 | 90 | // Diagnostic responses from the interface 91 | enum class DiagnosticResponse { 92 | drOK, 93 | 94 | // Responses from openPort 95 | drPortInUse, 96 | drPortNotFound, 97 | drPortError, 98 | drAccessDenied, 99 | drComportConfigError, 100 | drBaudRateNotSupported, 101 | drErrorReadingVersion, 102 | drErrorMalformedVersion, 103 | drOldFirmware, 104 | 105 | // Responses from commands 106 | drSendFailed, 107 | drSendParameterFailed, 108 | drReadResponseFailed, 109 | drWriteTimeout, 110 | drSerialOverrun, 111 | drFramingError, 112 | drError, 113 | 114 | // Response from selectTrack 115 | drTrackRangeError, 116 | drSelectTrackError, 117 | drWriteProtected, 118 | drStatusError, 119 | drSendDataFailed, 120 | drTrackWriteResponseError, 121 | 122 | // Returned if there is no disk in the drive 123 | drNoDiskInDrive, 124 | 125 | drDiagnosticNotAvailable, 126 | drUSBSerialBad, 127 | drCTSFailure, 128 | drRewindFailure, 129 | 130 | drMediaTypeMismatch 131 | }; 132 | 133 | enum class LastCommand { 134 | lcOpenPort, 135 | lcGetVersion, 136 | lcEnableWrite, 137 | lcRewind, 138 | lcDisableMotor, 139 | lcEnableMotor, 140 | lcGotoTrack, 141 | lcSelectSurface, 142 | lcReadTrack, 143 | lcWriteTrack, 144 | lcRunDiagnostics, 145 | lcSwitchDiskMode, 146 | lcReadTrackStream, 147 | lcCheckDiskInDrive, 148 | lcCheckDiskWriteProtected, 149 | lcEraseTrack, 150 | lcNoClickCheck, 151 | lcCheckDensity, 152 | lcMeasureRPM, 153 | lcEEPROMRead, 154 | lcEEPROMWrite, 155 | lcWriteFlux, 156 | lcEraseFlux 157 | }; 158 | 159 | class ArduinoInterface { 160 | private: 161 | // Windows handle to the serial port device 162 | SerialIO m_comPort; 163 | FirmwareVersion m_version; 164 | bool m_inWriteMode; 165 | LastCommand m_lastCommand; 166 | DiagnosticResponse m_lastError; 167 | bool m_abortStreaming; 168 | bool m_isWriteProtected; 169 | bool m_diskInDrive; 170 | bool m_abortSignalled; 171 | bool m_isStreaming; 172 | bool m_isHDMode; 173 | std::mutex m_protectAbort; 174 | void* m_tempBuffer = nullptr; 175 | 176 | // Read a desired number of bytes into the target pointer 177 | bool deviceRead(void* target, const unsigned int numBytes, const bool failIfNotAllRead = false); 178 | // Writes a desired number of bytes from the the pointer 179 | bool deviceWrite(const void* source, const unsigned int numBytes); 180 | 181 | // Version of the above where the command has a parameter on the end (as long as its not char 0) 182 | DiagnosticResponse runCommand(const char command, const char parameter = '\0', char* actualResponse = nullptr); 183 | 184 | // Apply and change the timeouts on the com port 185 | void applyCommTimeouts(bool shortTimeouts); 186 | 187 | // Attempts to write a sector back to the disk. This must be pre-formatted and MFM encoded correctly depending on usePrecomp 188 | DiagnosticResponse internalWriteTrack(const unsigned char* data, const unsigned short numBytes, const bool writeFromIndexPulse, bool usePrecomp); 189 | 190 | // Attempts to verify if the reader/writer is running on this port 191 | static DiagnosticResponse internalOpenPort(const std::wstring& portName, bool enableCTSflowcontrol, bool triggerReset, std::string& versionString, SerialIO& port); 192 | 193 | // Attempt to sync and get version 194 | static DiagnosticResponse attemptToSync(std::string& versionString, SerialIO& port); 195 | 196 | // HD - a bit like the precomp as its more accurate, but no precomp 197 | DiagnosticResponse writeCurrentTrackHD(const unsigned char* mfmData, const unsigned short numBytes, const bool writeFromIndexPulse); 198 | 199 | // Read from the EEPROM 200 | DiagnosticResponse eepromRead(unsigned char position, unsigned char& value); 201 | 202 | // Write to the eeprom 203 | DiagnosticResponse eepromWrite(unsigned char position, unsigned char value); 204 | public: 205 | // Constructor for this class 206 | ArduinoInterface(); 207 | 208 | // Free me 209 | ~ArduinoInterface(); 210 | 211 | const LastCommand getLastFailedCommand() const { return m_lastCommand; } 212 | const DiagnosticResponse getLastError() const { return m_lastError; } 213 | 214 | // Uses the above fields to constructor a suitable error message, hopefully useful in diagnosing the issue 215 | const std::string getLastErrorStr() const; 216 | 217 | const bool isOpen() const { return m_comPort.isPortOpen(); } 218 | const bool isInWriteMode() const { return m_inWriteMode; } 219 | 220 | // Returns a list of ports this could be available on 221 | static void enumeratePorts(std::vector& portList); 222 | 223 | // Returns TRUE if there is a disk in the drive. This is ONLY updated by checkForDisk or checkIfDiskIsWriteProtected 224 | bool isDiskInDrive() const { return m_diskInDrive; } 225 | 226 | // Get the current firmware version. Only valid if openPort is successful 227 | const FirmwareVersion getFirwareVersion() const { return m_version; } 228 | 229 | // Turns on and off the reading interface. For the new modded firmware this also allows writing as such the function below is no longer needed 230 | DiagnosticResponse enableReading(const bool enable, const bool reset = true, const bool dontWait = false); 231 | 232 | // Turns on and off the reading interface. If irError is returned the disk is write protected. This is no longer needed if you are using the new modded firmware 233 | DiagnosticResponse enableWriting(const bool enable, const bool reset = true); 234 | 235 | // Attempts to open the reader running on the COM port number provided. Port MUST support 2M baud 236 | DiagnosticResponse openPort(const std::wstring& portName, bool enableCTSflowcontrol = true); 237 | 238 | // Attempts to verify if the reader/writer is running on this port 239 | static bool isPortCorrect(const std::wstring& portName); 240 | 241 | // Checks if the disk is write protected. If forceCheck=false then the last cached version is returned. This is also updated by checkForDisk() as well as this function 242 | DiagnosticResponse checkIfDiskIsWriteProtected(bool forceCheck); 243 | 244 | // Seek to track 0 245 | DiagnosticResponse findTrack0(); 246 | 247 | // Check to see if a disk is inserted in the drive. If forceCheck then the last cached version is returned, which is also updated by diskStepOnce. 248 | DiagnosticResponse checkForDisk(bool forceCheck); 249 | 250 | // Reads a complete rotation of the disk, and returns it using the callback function which can return FALSE to stop 251 | // An instance of BridgePLL is required. 252 | DiagnosticResponse readRotation(MFMExtractionTarget& extractor, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, std::function onRotation, bool useHalfPLL); 253 | // Same as the above, but this uses the newer much more accurate flux read 254 | DiagnosticResponse readFlux(PLL::BridgePLL& pll, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, std::function onRotation); 255 | 256 | // Reset reason information 257 | DiagnosticResponse getResetReason(bool& WD, bool& BOD, bool& ExtReset, bool& PowerOn); 258 | DiagnosticResponse clearResetReason(); 259 | 260 | // Stops the read streaming immediately and any data in the buffer will be discarded. The above function will exit when the Arduino has also stopped streaming data 261 | bool abortReadStreaming(); 262 | 263 | // Returns TRUE if the disk si currently streaming data 264 | bool isStreaming() { return m_isStreaming; } 265 | 266 | // Check if an index pulse can be detected from the drive 267 | DiagnosticResponse testIndexPulse(); 268 | 269 | // Check if data can be detected from the drive 270 | DiagnosticResponse testDataPulse(); 271 | 272 | // Query the RPM of the drive 273 | DiagnosticResponse measureDriveRPM(float& rpm); 274 | 275 | // Checks to see what the density of the current disk is most likely to be, Ideally this should be done while at track 0, probably lower head 276 | DiagnosticResponse checkDiskCapacity(bool& isHD); 277 | 278 | // Check and switch to HD disk 279 | DiagnosticResponse setDiskCapacity(bool switchToHD_Disk); 280 | 281 | // Guess if the drive is actually a PLUS Mode drive 282 | DiagnosticResponse guessPlusMode(bool& isProbablyPlus); 283 | 284 | // Select the track, this makes the motor seek to this position 285 | DiagnosticResponse selectTrack(const unsigned char trackIndex, const TrackSearchSpeed searchSpeed = TrackSearchSpeed::tssNormal, bool ignoreDiskInsertCheck = false); 286 | 287 | // If the drive is on track 0, this does a test seek to -1 if supported 288 | DiagnosticResponse performNoClickSeek(); 289 | 290 | // Choose which surface of the disk to read from 291 | DiagnosticResponse selectSurface(const DiskSurface side); 292 | 293 | // Reads just enough data to fulfill most extractions needed, but doesnt care about rotation position or index - pll should have the LinearExtractor configured 294 | DiagnosticResponse readData(PLL::BridgePLL& pll); 295 | 296 | // Read RAW data from the current track and surface selected - this works properly with the HD and DD options 297 | // dataLength must be either RAW_TRACKDATA_LENGTH or RAW_TRACKDATA_LENGTH_HD. If you mismatch the type then the function will return an error 298 | DiagnosticResponse readCurrentTrack(void* trackData, const int dataLength, const bool readFromIndexPulse); 299 | 300 | // Read RAW data from the current track and surface selected in DD 301 | DiagnosticResponse readCurrentTrack(RawTrackDataDD& trackData, const bool readFromIndexPulse); 302 | 303 | // Attempts to write a track back to the disk. This must be pre-formatted and MFM encoded correctly 304 | DiagnosticResponse writeCurrentTrack(const unsigned char* data, const unsigned short numBytes, const bool writeFromIndexPulse); 305 | // The precomp version of the above. 306 | DiagnosticResponse writeCurrentTrackPrecomp(const unsigned char* mfmData, const unsigned short numBytes, const bool writeFromIndexPulse, bool usePrecomp); 307 | 308 | // Writes the flux timings (in nanoseconds) to the drive. The Drive RPM is needed tp compensate and correct the flux times (if enabled). 309 | DiagnosticResponse writeFlux(const std::vector& fluxTimes, const DWORD offsetFromIndex, const float driveRPM, bool compensateFluxTimings, bool terminateAtIndex = false); 310 | 311 | // Removes all flux transitions from the current track 312 | DiagnosticResponse eraseFluxOnTrack(); 313 | 314 | // Erases the current track by writing 0xAA to it 315 | DiagnosticResponse eraseCurrentTrack(); 316 | 317 | // Check CTS status - you must open with CTS disabled for this to work 318 | DiagnosticResponse testCTS(); 319 | 320 | // Check if the USB to Serial device can keep up properly 321 | DiagnosticResponse testTransferSpeed(); 322 | 323 | // Returns true if the track actually contains some data, else its considered blank or unformatted 324 | bool trackContainsData(const RawTrackDataDD& trackData) const; 325 | 326 | // Closes the port down 327 | void closePort(); 328 | 329 | // Check the EEPROM setting for advanced controller 330 | DiagnosticResponse eeprom_IsAdvancedController(bool& enabled); 331 | DiagnosticResponse eeprom_IsDrawbridgePlusMode(bool& enabled); 332 | DiagnosticResponse eeprom_IsDensityDetectDisabled(bool& enabled); 333 | DiagnosticResponse eeprom_IsSlowSeekMode(bool& enabled); 334 | DiagnosticResponse eeprom_IsIndexAlignMode(bool& enabled); 335 | 336 | // Check the EEPROM setting for advanced controller 337 | DiagnosticResponse eeprom_SetAdvancedController(bool enabled); 338 | DiagnosticResponse eeprom_SetDrawbridgePlusMode(bool enabled); 339 | DiagnosticResponse eeprom_SetDensityDetectDisabled(bool enabled); 340 | DiagnosticResponse eeprom_SetSlowSeekMode(bool enabled); 341 | DiagnosticResponse eeprom_SetIndexAlignMode(bool enabled); 342 | }; 343 | }; 344 | 345 | 346 | #endif -------------------------------------------------------------------------------- /floppybridge/GreaseWeazleBridge.h: -------------------------------------------------------------------------------- 1 | #ifndef GREASEWEAZLE_FLOPPY_BRIDGE 2 | #define GREASEWEAZLE_FLOPPY_BRIDGE 3 | /* WinUAE Greaseweazle Interface for *UAE 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * This file, along with currently active and supported interfaces 17 | * are maintained from by GitHub repo at 18 | * https://github.com/RobSmithDev/FloppyDriveBridge 19 | */ 20 | 21 | // 22 | // ROMTYPE_GREASEWEAZLEREADER_WRITER must be defined for this to work 23 | 24 | 25 | #ifdef ROMTYPE_GREASEWEAZLEREADER_WRITER 26 | 27 | #include "floppybridge_abstract.h" 28 | #include "CommonBridgeTemplate.h" 29 | #include "GreaseWeazleInterface.h" 30 | #include "pll.h" 31 | 32 | class GreaseWeazleDiskBridge : public CommonBridgeTemplate { 33 | private: 34 | // When the motor last switched on 35 | std::chrono::time_point m_motorTurnOnTime; 36 | bool m_motorIsEnabled = false; 37 | 38 | // Which com port should we use? or blank for automatic 39 | std::string m_comPort; 40 | 41 | // Used to detect disconnection of Greaseweazle while in use 42 | bool m_wasIOError = false; 43 | 44 | // Which drive to use 45 | FloppyBridge::DriveSelection m_useDrive; 46 | 47 | // Is this a HD disk? 48 | bool m_isHDDisk = false; 49 | 50 | // Hardware connection 51 | GreaseWeazle::GreaseWeazleInterface m_io; 52 | 53 | // Remember where we are 54 | int m_currentCylinder = 0; 55 | 56 | protected: 57 | // Called when a disk is inserted so that you can (re)populate the response to _getDriveTypeID() 58 | virtual void checkDiskType() override; 59 | 60 | // Called to force into DD or HD mode. Overrides checkDiskType() until checkDiskType() is called again 61 | virtual void forceDiskDensity(bool forceHD) override; 62 | 63 | // This is called by the main thread in case you need to do anything specific at regular intervals 64 | virtual void poll() override; 65 | 66 | // Return TRUE if the drive is still connected and working 67 | virtual bool isStillWorking() override; 68 | 69 | // If your device supports being able to abort a disk read, mid-read then implement this 70 | virtual void abortDiskReading() override; 71 | 72 | // If your device supports the DiskChange option then return TRUE here. If not, then the code will simulate it 73 | virtual bool supportsDiskChange() override; 74 | 75 | // If the above is TRUE then this is called to get the status of the DiskChange line. Basically, this is TRUE if there is a disk in the drive. 76 | // If force is true you should re-check, if false, then you are allowed to return a cached value from the last disk operation (eg: seek) 77 | virtual bool getDiskChangeStatus(const bool forceCheck) override; 78 | 79 | // Called when the class is about to shut down 80 | virtual void closeInterface() override; 81 | 82 | // Called to start the interface, you should update any error messages if it fails. This needs to be ready to see to any cylinder and so should already know where cylinder 0 is 83 | virtual bool openInterface(std::string& errorMessage) override; 84 | 85 | // Called to ask the drive what the current write protect status is - return true if its write protected. If forceCheck is true you should actually check the drive, 86 | // else use the last status checked which was probably from a SEEK setCurrentCylinder call. If you can ONLY get this information here then you should always force check 87 | virtual bool checkWriteProtectStatus(const bool forceCheck) override; 88 | 89 | // Get the name of the drive 90 | virtual const BridgeDriver* _getDriverInfo() override; 91 | 92 | // Duplicate of the one below, but here for consistency - Returns the name of interface. This pointer should remain valid after the class is destroyed 93 | virtual const DriveTypeID _getDriveTypeID() override; 94 | 95 | // Called to switch which head is being used right now. Returns success or not 96 | virtual bool setActiveSurface(const DiskSurface activeSurface) override; 97 | 98 | // Set the status of the motor on the drive. The motor should maintain this status until switched off or reset. This should *NOT* wait for the motor to spin up 99 | virtual bool setMotorStatus(const bool switchedOn) override; 100 | 101 | // Trigger a seek to the requested cylinder, this can block until complete 102 | virtual bool setCurrentCylinder(const unsigned int cylinder) override; 103 | 104 | // If we're on track 0, this is the emulator trying to seek to track -1. We catch this as a special case. 105 | // Should perform the same operations as setCurrentCylinder in terms of disk change etc but without changing the current cylinder 106 | // Return FALSE if this is not supported by the bridge 107 | virtual bool performNoClickSeek() override; 108 | 109 | // Called when data should be read from the drive. 110 | // pll: supplied if you use it 111 | // maxBufferSize: Maximum number of RotationExtractor::MFMSample in the buffer. If we're trying to detect a disk, this might be set VERY LOW 112 | // buffer: Where to save to. When a buffer is saved, position 0 MUST be where the INDEX pulse is. RevolutionExtractor will do this for you 113 | // indexMarker: Used by rotationExtractor if you use it, to help be consistent where the INDEX position is read back at 114 | // onRotation: A function you should call for each complete revolution received. If the function returns FALSE then you should abort reading, else keep sending revolutions 115 | // Returns: ReadResponse, explains its self 116 | virtual ReadResponse readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker, 117 | std::function onRotation) override; 118 | 119 | // Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor 120 | // pll: required 121 | // Returns: ReadResponse, explains its self 122 | virtual ReadResponse readLinearData(PLL::BridgePLL& pll) override; 123 | 124 | // Called when a cylinder revolution should be written to the disk. 125 | // Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte 126 | // numBytes Number of bits in the buffer to write 127 | // writeFromIndex If an attempt should be made to write this from the INDEX pulse rather than just a random position 128 | // suggestUsingPrecompensation A suggestion that you might want to use write precompensation, optional 129 | // Returns TRUE if success, or false if it fails. Largely doesnt matter as most stuff should verify with a read straight after 130 | virtual bool writeData(const unsigned char* rawMFMData, const unsigned int numBits, const bool writeFromIndex, const bool suggestUsingPrecompensation) override; 131 | 132 | // A manual way to check for disk change. This is simulated by issuing a read message and seeing if there's any data. Returns TRUE if data or an INDEX pulse was detected 133 | // It's virtual as the default method issues a read and looks for data. If you have a better implementation then override this 134 | virtual bool attemptToDetectDiskChange() override; 135 | 136 | public: 137 | GreaseWeazleDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, bool enableAutoCache, bool useSmartSpeed, bool autoDetectComPort, char* comPort, FloppyBridge::DriveSelection drive); 138 | 139 | // This is for the static version 140 | GreaseWeazleDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, int uaeSettings); 141 | 142 | virtual ~GreaseWeazleDiskBridge(); 143 | 144 | static const BridgeDriver* staticBridgeInformation(); 145 | }; 146 | 147 | 148 | 149 | #endif 150 | #endif -------------------------------------------------------------------------------- /floppybridge/GreaseWeazleInterface.h: -------------------------------------------------------------------------------- 1 | #ifndef GREASEWEAZLE_INTERFACE 2 | #define GREASEWEAZLE_INTERFACE 3 | /* Greaseweazle C++ Interface for reading and writing Amiga Disks 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * Based on the excellent code by Keir Fraser 17 | * https://github.com/keirf/Greaseweazle/ 18 | * 19 | * This file, along with currently active and supported interfaces 20 | * are maintained from by GitHub repo at 21 | * https://github.com/RobSmithDev/FloppyDriveBridge 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include "RotationExtractor.h" 28 | #include "SerialIO.h" 29 | #include "pll.h" 30 | 31 | namespace GreaseWeazle { 32 | 33 | // Represent which side of the disk we're looking at 34 | enum class DiskSurface { 35 | dsUpper, // The upper side of the disk 36 | dsLower // The lower side of the disk 37 | }; 38 | 39 | // Speed at which the head will seek to a track 40 | enum class TrackSearchSpeed { 41 | tssSlow, 42 | tssNormal, 43 | tssFast, 44 | tssVeryFast 45 | }; 46 | 47 | // Command set 48 | enum class Cmd { 49 | GetInfo = 0, 50 | Update = 1, 51 | Seek = 2, 52 | Head = 3, 53 | SetParams = 4, 54 | GetParams = 5, 55 | Motor = 6, 56 | ReadFlux = 7, 57 | WriteFlux = 8, 58 | GetFluxStatus = 9, 59 | GetIndexTimes = 10, 60 | SwitchFwMode = 11, 61 | Select = 12, 62 | Deselect = 13, 63 | SetBusType = 14, 64 | SetPin = 15, 65 | Reset = 16, 66 | EraseFlux = 17, 67 | SourceBytes = 18, 68 | SinkBytes = 19, 69 | GetPin = 20, 70 | TestMode = 21, 71 | NoClickStep = 22 72 | }; 73 | 74 | // Command responses/acknowledgments 75 | enum class Ack { 76 | Okay = 0, 77 | BadCommand = 1, 78 | NoIndex = 2, 79 | NoTrk0 = 3, 80 | FluxOverflow = 4, 81 | FluxUnderflow = 5, 82 | Wrprot = 6, 83 | NoUnit = 7, 84 | NoBus = 8, 85 | BadUnit = 9, 86 | BadPin = 10, 87 | BadCylinder = 11 88 | }; 89 | 90 | 91 | // Diagnostic responses from the interface 92 | enum class GWResponse { 93 | drOK, 94 | 95 | // Responses from openPort 96 | drPortInUse, 97 | drPortNotFound, 98 | drPortError, 99 | drAccessDenied, 100 | drComportConfigError, 101 | drErrorMalformedVersion, 102 | drOldFirmware, 103 | drInUpdateMode, 104 | 105 | // Responses from commands 106 | drReadResponseFailed, 107 | drSerialOverrun, 108 | drError, 109 | 110 | // Response from selectTrack 111 | drTrackRangeError, 112 | drSelectTrackError, 113 | drWriteProtected, 114 | 115 | // Returned if there is no disk in the drive 116 | drNoDiskInDrive, 117 | 118 | drRewindFailure, 119 | }; 120 | 121 | 122 | #pragma pack(1) 123 | // <4BI3B21x 124 | struct GWVersionInformation { 125 | unsigned char major, minor, is_main_firmware, max_cmd; 126 | uint32_t sample_freq; // sample_freq * 1e-6 = Mhz 127 | unsigned char hw_model, hw_submodel, usb_speed; 128 | 129 | unsigned char padding[21]; 130 | }; 131 | 132 | // <5H 133 | struct GWDriveDelays { 134 | uint16_t select_delay, step_delay; // in uSec 135 | uint16_t seek_settle_delay, motor_delay, watchdog_delay; // in mSec 136 | }; 137 | 138 | // ## Cmd.SetBusType values 139 | enum class BusType { Invalid = 0, IBMPC = 1, Shugart = 2 }; 140 | 141 | enum class DriveSelection { dsA=0, dsB=1, ds0=2, ds1=3, ds2=4, ds3=5 }; 142 | 143 | #pragma pack() 144 | 145 | 146 | 147 | class GreaseWeazleInterface { 148 | private: 149 | // Windows handle to the serial port device 150 | SerialIO m_comPort; 151 | BusType m_currentBusType; 152 | unsigned char m_currentDriveIndex; 153 | bool m_diskInDrive; 154 | bool m_motorIsEnabled; 155 | bool m_shouldAbortReading = false; 156 | bool m_pinDskChangeAvailable = false; 157 | bool m_pinWrProtectAvailable = false; 158 | bool m_isWriteProtected = false; 159 | bool m_selectStatus = false; 160 | bool m_inHDMode = false; 161 | 162 | // Version information read during openPort 163 | GWVersionInformation m_gwVersionInformation{}; 164 | 165 | // Delay settings 166 | GWDriveDelays m_gwDriveDelays{}; 167 | 168 | // Apply and change the timeouts on the com port 169 | void applyCommTimeouts(bool shortTimeouts); 170 | 171 | // send a command out to the GW and receive its response. Returns FALSE on error 172 | bool sendCommand(Cmd command, const std::vector& params, Ack& response, unsigned char extraResponseSize = 0); 173 | bool sendCommand(Cmd command, void* params, unsigned int paramsLength, Ack& response, unsigned char extraResponseSize = 0); 174 | bool sendCommand(Cmd command, unsigned char param, Ack& response, unsigned char extraResponseSize = 0); 175 | 176 | // Update the delay counts from m_gwDriveDelays 177 | bool updateDriveDelays(); 178 | 179 | // Trigger selecting this drive 180 | bool selectDrive(bool select); 181 | 182 | // Polls for the state of pins on the board 183 | bool checkPins(); 184 | public: 185 | // Constructor for this class 186 | GreaseWeazleInterface(); 187 | 188 | // Free me 189 | ~GreaseWeazleInterface(); 190 | 191 | const bool isOpen() const { return m_comPort.isPortOpen(); } 192 | 193 | // Returns the timr before the motor switches back off 194 | int getMotorTimeout() const { return m_gwDriveDelays.watchdog_delay; } 195 | 196 | bool supportsDiskChange() const { return m_pinDskChangeAvailable; } 197 | BusType currentBusType() const { return m_currentBusType; }; 198 | bool isWriteProtected() const { return m_isWriteProtected; } 199 | 200 | // Change the disk capacity we're using 201 | void setDiskCapacity(bool hd) { m_inHDMode = hd; } 202 | 203 | // Test the inserted disk and see if its HD or not 204 | GWResponse checkDiskCapacity(bool& isHD); 205 | 206 | // Attempts to open the reader running on the COM port provided (or blank for auto-detect) 207 | GWResponse openPort(const std::string& comPort, DriveSelection drive); 208 | 209 | // Reads a complete rotation of the disk, and returns it using the callback function which can return FALSE to stop 210 | // An instance of BridgePLL is required. This is purely to save on re-allocations. It is internally reset each time 211 | GWResponse readRotation(PLL::BridgePLL& pll, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, 212 | std::function onRotation); 213 | 214 | // Reads "enough" data to extract data from the disk. This doesn't care about creating a perfect revolution - pll should have the LinearExtractor configured 215 | GWResponse readData(PLL::BridgePLL& pll); 216 | 217 | // Turns on and off the reading interface. dontWait disables the GW timeout waiting, so you must instead. Returns drOK, drError, 218 | GWResponse enableMotor(const bool enable, const bool dontWait = false); 219 | 220 | // Seek to track 0 - Can return drRewindFailure, drSelectTrackError, drOK 221 | GWResponse findTrack0(); 222 | 223 | // Select the track, this makes the motor seek to this position. Can return drRewindFailure, drSelectTrackError, drOK, drTrackRangeError 224 | GWResponse selectTrack(const unsigned char trackIndex, const TrackSearchSpeed searchSpeed = TrackSearchSpeed::tssNormal, bool ignoreDiskInsertCheck = false); 225 | 226 | // Special command that asks GW to do a 'seek to track -1' which isnt allowed but can be used for disk detection 227 | GWResponse performNoClickSeek(); 228 | 229 | // Choose which surface of the disk to read from. Can return drError or drOK 230 | GWResponse selectSurface(const DiskSurface side); 231 | 232 | // Write data to the disk. Can return drReadResponseFailed, drWriteProtected, drSerialOverrun, drWriteProtected, drOK 233 | GWResponse writeCurrentTrackPrecomp(const unsigned char* mfmData, const uint16_t numBytes, const bool writeFromIndexPulse, bool usePrecomp); 234 | 235 | // Attempt to abort reading 236 | void abortReadStreaming(); 237 | 238 | // Check if a disk is present in the drive 239 | GWResponse checkForDisk(bool force); 240 | 241 | // Closes the port down 242 | void closePort(); 243 | }; 244 | 245 | }; 246 | 247 | #endif -------------------------------------------------------------------------------- /floppybridge/RotationExtractor.h: -------------------------------------------------------------------------------- 1 | #ifndef READERWRITER_ROTATION_EXTRACTOR 2 | #define READERWRITER_ROTATION_EXTRACTOR 3 | /* ArduinoFloppyReader (and writer) - Rotation Extractor 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * This file, along with currently active and supported interfaces 17 | * are maintained from by GitHub repo at 18 | * https://github.com/RobSmithDev/FloppyDriveBridge 19 | */ 20 | 21 | //////////////////////////////////////////////////////////////////////////////////////// 22 | // Class to manage finding *exact* disk revolutions // 23 | //////////////////////////////////////////////////////////////////////////////////////// 24 | // 25 | // Purpose: 26 | // The class attempts to guess where an exact disk revolution occurs, and then re-aligns 27 | // it such that it starts at the index pulse. This means we don't need to wait for an index 28 | // pulse to work out a revolution of the disk. The first time a disk is used we calculate 29 | // the time of a single revolution and then use that as a guide to how long a revolution will 30 | // take in the future. 31 | // 32 | // This isn't 100% perfect but does seem to work. 33 | 34 | // With this defined you get speed data per bit. Undefined, per 8 bits. Nothing seems to notice much 35 | // Inside *UAE it uses one speed value for 16 bits of MFM data so that's probably why. 36 | // and besides, with this *undefined* we save about 700k of ram 37 | //#define HIGH_RESOLUTION_MODE 38 | 39 | // Instead of outputting "speed" or "density" values this will output bit-times in ns 40 | //#define OUTPUT_TIME_IN_NS 41 | 42 | // So worse case is the disk takes 210ms to spin, and every sequence is VERY fast, and every sequence is 01, this number is highly unlikely though 43 | // The x2 is for HD disks 44 | #define MAX_REVOLUTION_SEQUENCES (120000 * 2) 45 | 46 | // Number of sequences to match to find the index overlap position or the rotation overlap position 47 | // The higher the number, the more chance of perfect revolution alignment, but higher processing required at the end of each revolution 48 | #define OVERLAP_SEQUENCE_MATCHES 1024 49 | 50 | // When looking for overlap for indexes it doesn't need anywhere near as much data checking 51 | #define OVERLAP_SEQUENCE_MATCHES_INDEXMODE 1024 52 | 53 | // How many incorrect alignment values are allowed before switching to index mode 54 | #define MAX_BAD_VALUES_BEFORE_SWITCH 4 55 | 56 | // Extra window either side. this allows more of a search range 57 | #define OVERLAP_EXTRA_BUFFER 6 58 | 59 | // Signal for index was not found 60 | #define INDEX_NOT_FOUND 0xFFFFFFFF 61 | 62 | #include 63 | 64 | // A class that can receive data 65 | class MFMExtractionTarget { 66 | public: 67 | // Enum for the possible sequences we support - mfm1 is not normally allowed. 68 | enum class MFMSequence : unsigned char { mfm1 = 0, mfm01 = 1, mfm001 = 2, mfm0001 = 3, mfm000 = 4 }; 69 | 70 | // A single sequence of MFM data 71 | struct MFMSequenceInfo { 72 | // *real* Total time it took to read this in NS 73 | uint16_t timeNS; 74 | 75 | // Total time (pll adjusted) 76 | uint16_t pllTimeNS; 77 | 78 | // The MFM sequence discovered 79 | MFMSequence mfm; 80 | }; 81 | 82 | // Decoded version of the above 83 | struct MFMSample { 84 | #ifdef OUTPUT_TIME_IN_NS 85 | // This is the time for each 'bit' 86 | uint16_t bittime[8]; 87 | #else 88 | #ifdef HIGH_RESOLUTION_MODE 89 | // This is the speed of each 'bit' as a % 90 | uint16_t speed[8]; 91 | #else 92 | // This is the average speed of all 8 bits as a % 93 | uint16_t speed; 94 | #endif 95 | #endif 96 | 97 | // This is the raw MFM bit-data 98 | unsigned char mfmData; 99 | }; 100 | 101 | // Struct for tracking what the index start looks like so we get it perfect (or at least consistent) 102 | struct IndexSequenceMarker { 103 | // Sequences found 104 | MFMSequence sequences[OVERLAP_SEQUENCE_MATCHES_INDEXMODE]; 105 | 106 | // If this is actually valid 107 | bool valid = false; 108 | }; 109 | 110 | // Return the total amount of time data received so far 111 | [[nodiscard]] virtual uint32_t totalTimeReceived() const = 0; 112 | 113 | // Get and set the sequence identified as data round the INDEX pulse so that next time we get consistent revolution starting points 114 | virtual void setIndexSequence(const IndexSequenceMarker& sequence) = 0; 115 | virtual void getIndexSequence(IndexSequenceMarker& sequence) const = 0; 116 | 117 | // Reset this back to "empty" 118 | virtual void reset(bool isHD) = 0; 119 | 120 | // Submit a single sequence to the list - abstract function 121 | virtual void submitSequence(const MFMSequenceInfo& sequence, bool isIndex, bool discardEarlySamples = true) = 0; 122 | 123 | // Returns TRUE if we are readt to extract (eg: full revolution or buffer full) 124 | [[nodiscard]] virtual bool canExtract() const = 0; 125 | 126 | // Returns TRUE if this has learnt the time of a disk revolution 127 | [[nodiscard]] virtual bool hasLearntRotationSpeed() const = 0; 128 | 129 | // Returns TRUE if we're in INDEX mode 130 | [[nodiscard]] virtual bool isInIndexMode() const = 0; 131 | 132 | // Extracts the data we have so far. Might need canExtract to be true depending on the implementation 133 | [[nodiscard]] virtual bool extractRotation(MFMSample* output, uint32_t& outputBits, uint32_t maxBufferSizeBytes, bool usePLLTime = false) = 0; 134 | 135 | // I want the destructor virtual 136 | virtual ~MFMExtractionTarget() {}; 137 | }; 138 | 139 | 140 | // Class to extract a single rotation from an incoming mfm data sequence and ensure it's a perfect MFM rotation 141 | class RotationExtractor : public MFMExtractionTarget { 142 | private: 143 | // How long a revolution is 144 | uint32_t m_revolutionTime = 0; 145 | // An amount of time whereby we 'nearly' have a complete revolution of data 146 | uint32_t m_revolutionTimeNearlyComplete = 0; 147 | // Used while working out the above 148 | uint32_t m_revolutionTimeCounting = 0; 149 | // Where the first index pulse was discovered 150 | uint32_t m_sequenceIndex = INDEX_NOT_FOUND; 151 | // Where the second index pulse was discovered 152 | uint32_t m_nextSequenceIndex = INDEX_NOT_FOUND; 153 | // Where we were when we reached m_revolutionTime worth of data 154 | uint32_t m_revolutionReadyAt = INDEX_NOT_FOUND; 155 | // And a flag to set this as good 156 | bool m_revolutionReady = false; 157 | // Is simple mode enabled? 158 | bool m_useSimpleMode = false; 159 | // Is this an HD disk? 160 | bool m_isHD = false; 161 | // If we should always use the index marker when finding revolutions 162 | bool m_useIndex = false; 163 | // Current amount of data in the buffer in ns 164 | uint32_t m_currentTime = 0; 165 | // Current position of the buffer 166 | uint32_t m_sequencePos = 0; 167 | // Used to track exactly how much data has been submitted 168 | uint32_t m_timeReceived = 0; 169 | // Sequences received thus far 170 | MFMSequenceInfo* m_sequences; // [MAX_REVOLUTION_SEQUENCES] ; 171 | // In index mode, this holds the initial sequences before the first index marker 172 | MFMSequenceInfo* m_initialSequences; // [OVERLAP_SEQUENCE_MATCHES * OVERLAP_EXTRA_BUFFER] ; 173 | // Length of the above datat in use 174 | uint32_t m_initialSequencesLength = 0; 175 | // Where we're writing to as its a circular buffer 176 | uint32_t m_initialSequencesWritePos = 0; 177 | // Sequences discovered around the index marker 178 | IndexSequenceMarker m_indexSequence; 179 | 180 | // Finds the overlap between the start of the data and where we currently are 181 | uint32_t getOverlapPosition(uint32_t& numberOfBadMatches) const; 182 | 183 | // is almost identical 184 | uint32_t getTrueIndexPosition(uint32_t nextRevolutionStart, 185 | uint32_t startingPoint = INDEX_NOT_FOUND); 186 | public: 187 | RotationExtractor(); 188 | virtual ~RotationExtractor(); 189 | 190 | // Get and set the sequence identified as data round the INDEX pulse so that next time we get consistent revolution starting points 191 | virtual void setIndexSequence(const IndexSequenceMarker& sequence) override { m_indexSequence = sequence; } 192 | virtual void getIndexSequence(IndexSequenceMarker& sequence) const override { sequence = m_indexSequence; } 193 | 194 | // Reset this back to "empty" 195 | virtual void reset(bool isHD) override; 196 | 197 | // Return TRUE if we're in HD mode 198 | [[nodiscard]] bool isHD() const { return m_isHD; } 199 | 200 | // Signal new disk, or maybe a motor restarted. Need to re-calculate rotation speed. In HD mode, the data must be fed in at DD speeds (4, 6 and 8us) 201 | void newDisk(bool isHD) { 202 | reset(isHD); 203 | m_revolutionTime = 0; 204 | m_revolutionTimeCounting = 0; 205 | m_revolutionTimeNearlyComplete = 0; 206 | } 207 | 208 | // Return the current revolution time 209 | [[nodiscard]] uint32_t getRevolutionTime() const { return m_revolutionTime; } 210 | 211 | // Set the current revolution time 212 | void setRevolutionTime(const uint32_t time) { m_revolutionTime = time; m_revolutionTimeNearlyComplete = (uint32_t)(time * 0.9f); } 213 | 214 | // Return the total amount of time data received so far 215 | [[nodiscard]] virtual uint32_t totalTimeReceived() const override { return m_timeReceived; }; 216 | 217 | // Returns TRUE if this has learnt the time of a disk revolution 218 | [[nodiscard]] bool hasLearntRotationSpeed() const override { return m_revolutionTime > (m_isHD ? 300000000U : 150000000U); } 219 | 220 | // Returns TRUE if we're in INDEX mode 221 | [[nodiscard]] bool isInIndexMode() const override { return m_useIndex; } 222 | 223 | // Sets the code so it always uses the index marker when finding revolutions 224 | void setAlwaysUseIndex(bool useIndex) { m_useIndex = useIndex; } 225 | 226 | // In simple mode, the rotation is matched purely on Index pulses alone, the data is not matched. Index mode must be enabled. This is fine for SCP reading etc 227 | void setSimpleMode(bool simpleMode) { m_useSimpleMode = simpleMode; } 228 | 229 | // If this is about to spit out a revolution in a very small amount of time 230 | [[nodiscard]] bool isNearlyReady() const { return (m_revolutionTimeNearlyComplete) && (m_currentTime >= m_revolutionTimeNearlyComplete) && (!m_useIndex); } 231 | 232 | // Submit a single sequence to the list 233 | virtual void submitSequence(const MFMSequenceInfo& sequence, bool isIndex, bool discardEarlySamples = true) override; 234 | 235 | // Returns TRUE if we should be able to extract a revolution 236 | [[nodiscard]] virtual bool canExtract() const override { return (m_revolutionReadyAt != INDEX_NOT_FOUND) && (m_revolutionReady) && (m_sequencePos>100); } 237 | 238 | // Extracts a single rotation and updates the buffer to remove it. Returns FALSE if no rotation is available 239 | // If calculateSpeedFactor is true, we're in INDEX mode, and HIGH_RESOLUTION_MODE is defined then this will output time in NS rather than the speed factor value 240 | [[nodiscard]] virtual bool extractRotation(MFMSample* output, uint32_t& outputBits, uint32_t maxBufferSizeBytes, bool usePLLTime = false) override; 241 | }; 242 | 243 | 244 | // Simple class to just receive that data being sent from the PLL into a linear MFM buffer. It doesn't try to find rotations 245 | class LinearExtractor : public MFMExtractionTarget { 246 | private: 247 | uint8_t* m_outputBuffer = nullptr; 248 | uint8_t* m_currentPosition = nullptr; 249 | uint32_t m_outputStreamPos = 0; 250 | uint32_t m_outputStreamBit = 0; 251 | uint32_t m_totalSize = 0; 252 | uint32_t m_totalTime = 0; 253 | 254 | // Write a 0 or 1 to the bit stream 255 | inline void writeLinearBit(const bool value); 256 | public: 257 | // Dont care about this 258 | virtual void setIndexSequence(const IndexSequenceMarker& sequence) override {}; 259 | virtual void getIndexSequence(IndexSequenceMarker& sequence) const override {}; 260 | virtual bool hasLearntRotationSpeed() const override { return true; }; 261 | virtual bool isInIndexMode() const override { return false; }; 262 | virtual bool extractRotation(MFMSample* output, uint32_t& outputBits, uint32_t maxBufferSizeBytes, bool usePLLTime = false) override { return false; }; 263 | 264 | // Returns TRUE if we are readt to extract (eg: full revolution or buffer full) 265 | virtual bool canExtract() const override { return m_outputStreamPos >= m_totalSize; }; 266 | 267 | // Set where the data should be saved to 268 | void setOutputBuffer(void* outputBuffer, const uint32_t bufferSizeInBytes); 269 | 270 | // Finalise the buffer (shifting the bits for the current byte into place) and returns the total number of bits received 271 | uint32_t finaliseAndGetNumBits(); 272 | 273 | // Return the total amount of time data received so far 274 | virtual uint32_t totalTimeReceived() const override { return m_totalTime; }; 275 | 276 | // Reset this back to "empty" 277 | virtual void reset(bool isHD) override; 278 | 279 | // Copies the supplied buffer directly in 280 | void copyToBuffer(void* data, const uint32_t dataSize); 281 | 282 | // Submit a single sequence to the list - abstract function 283 | virtual void submitSequence(const MFMSequenceInfo& sequence, bool isIndex, bool discardEarlySamples = true) override; 284 | }; 285 | 286 | 287 | 288 | #endif -------------------------------------------------------------------------------- /floppybridge/SerialIO.h: -------------------------------------------------------------------------------- 1 | #ifndef DISKREADERWRITER_SERIAL_IO 2 | #define DISKREADERWRITER_SERIAL_IO 3 | /* ArduinoFloppyReader (and writer) 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * This file, along with currently active and supported interfaces 17 | * are maintained from by GitHub repo at 18 | * https://github.com/RobSmithDev/FloppyDriveBridge 19 | */ 20 | 21 | //////////////////////////////////////////////////////////////////////////////////////// 22 | // Class to manage the communication between the computer and a serial port // 23 | //////////////////////////////////////////////////////////////////////////////////////// 24 | // 25 | // 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #ifdef _WIN32 32 | #ifdef USING_MFC 33 | #include 34 | #else 35 | #include 36 | #endif 37 | #else 38 | #include 39 | #endif 40 | 41 | #include "ftdi.h" 42 | 43 | #define FTDI_PORT_PREFIX "FTDI:" 44 | 45 | extern void quickw2a(const std::wstring& wstr, std::string& str); 46 | extern void quicka2w(const std::string& str, std::wstring& wstr); 47 | 48 | 49 | class SerialIO { 50 | private: 51 | unsigned int m_readTimeout = 0, m_readTimeoutMultiplier = 0; 52 | unsigned int m_writeTimeout = 0, m_writeTimeoutMultiplier = 0; 53 | #ifdef FTDI_D2XX_AVAILABLE 54 | FTDI::FTDIInterface m_ftdi; 55 | #endif 56 | 57 | #ifdef _WIN32 58 | HANDLE m_portHandle = INVALID_HANDLE_VALUE; 59 | #else 60 | int m_portHandle = -1; 61 | #ifdef HAVE_STRUCT_TERMIOS2 62 | struct termios2 term; 63 | #else 64 | struct termios term; 65 | #endif 66 | #endif 67 | 68 | // Update timeouts 69 | void updateTimeouts(); 70 | 71 | public: 72 | // Definition of a serial port 73 | struct SerialPortInformation { 74 | // The "file" name of the port 75 | std::wstring portName; 76 | // Port details 77 | unsigned int vid = 0, pid = 0; 78 | // Product name 79 | std::wstring productName; 80 | // Instance ID 81 | std::wstring instanceID; 82 | #ifdef FTDI_D2XX_AVAILABLE 83 | // Set if this is an FTDI device index 84 | int ftdiIndex = -1; 85 | #endif 86 | }; 87 | 88 | // Configuration settings for the port 89 | struct Configuration { 90 | unsigned int baudRate = 9600; 91 | bool ctsFlowControl = false; 92 | }; 93 | 94 | enum class Response { rOK, rInUse, rNotFound, rUnknownError, rNotImplemented }; 95 | 96 | // Constructor etc 97 | SerialIO(); 98 | virtual ~SerialIO(); 99 | 100 | // Sets the status of the DTR line 101 | void setDTR(bool enableDTR); 102 | 103 | // Sets the status of the RTS line 104 | void setRTS(bool enableRTS); 105 | 106 | // Returns the status of the CTS pin 107 | bool getCTSStatus(); 108 | 109 | // Returns TRUE if the port is open 110 | bool isPortOpen() const; 111 | 112 | // Returns a list of serial ports discovered on the system 113 | static void enumSerialPorts(std::vector& serialPorts); 114 | 115 | // Purge any data left in the buffer 116 | void purgeBuffers(); 117 | 118 | // Returns the number of bytes waiting to be read 119 | unsigned int getBytesWaiting(); 120 | 121 | // Check if we were quick enough reading the data 122 | bool checkForOverrun(); 123 | 124 | // Open a port by name 125 | Response openPort(const std::wstring& portName); 126 | 127 | // Shuts the port down 128 | void closePort(); 129 | 130 | // Attempt ot change the size of the buffers used by the OS 131 | void setBufferSizes(const unsigned int rxSize, const unsigned int txSize); 132 | 133 | // Changes the configuration on the port 134 | Response configurePort(const Configuration& configuration); 135 | 136 | // Attempts to write some data to the port. Returns how much it actually wrote. 137 | unsigned int write(const void* data, unsigned int dataLength); 138 | 139 | // Attempts to read some data from the port. Returns how much it actually read. 140 | // Returns how mcuh it actually read 141 | unsigned int read(void* data, unsigned int dataLength); 142 | 143 | // A very simple, uncluttered version of the above, mainly for linux 144 | unsigned int justRead(void* data, unsigned int dataLength); 145 | 146 | // Sets the read timeouts. The actual timeout is calculated as waitTimetimeout + (multiplier * num bytes) 147 | void setReadTimeouts(unsigned int waitTimetimeout, unsigned int multiplier); 148 | 149 | // Sets the write timeouts. The actual timeout is calculated as waitTimetimeout + (multiplier * num bytes) 150 | void setWriteTimeouts(unsigned int waitTimetimeout, unsigned int multiplier); 151 | }; 152 | 153 | 154 | #endif -------------------------------------------------------------------------------- /floppybridge/SuperCardProBridge.cpp: -------------------------------------------------------------------------------- 1 | /* WinUAE Supercard Pro Interface for *UAE 2 | * 3 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 4 | * https://amiga.robsmithdev.co.uk 5 | * 6 | * This file is multi-licensed under the terms of the Mozilla Public 7 | * License Version 2.0 as published by Mozilla Corporation and the 8 | * GNU General Public License, version 2 or later, as published by the 9 | * Free Software Foundation. 10 | * 11 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 12 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 13 | * 14 | * This file, along with currently active and supported interfaces 15 | * are maintained from by GitHub repo at 16 | * https://github.com/RobSmithDev/FloppyDriveBridge 17 | */ 18 | 19 | #include "floppybridge_config.h" 20 | 21 | #ifdef ROMTYPE_SUPERCARDPRO_WRITER 22 | 23 | #include "floppybridge_abstract.h" 24 | #include "CommonBridgeTemplate.h" 25 | #include "SuperCardProBridge.h" 26 | #include "SuperCardProInterface.h" 27 | 28 | 29 | using namespace SuperCardPro; 30 | 31 | 32 | static const FloppyDiskBridge::BridgeDriver DriverSupercardProFloppy = { 33 | "SuperCard Pro", "https://www.cbmstuff.com/", "Jim Drew", "RobSmithDev", CONFIG_OPTIONS_COMPORT | CONFIG_OPTIONS_COMPORT_AUTODETECT | CONFIG_OPTIONS_DRIVE_AB | CONFIG_OPTIONS_SMARTSPEED | CONFIG_OPTIONS_AUTOCACHE 34 | }; 35 | 36 | 37 | // Flags from WINUAE 38 | SupercardProDiskBridge::SupercardProDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, bool enableAutoCache, bool useSmartSpeed, bool autoDetectComPort, char* comPort, bool driveOnB) : 39 | CommonBridgeTemplate(bridgeMode, bridgeDensity, enableAutoCache, useSmartSpeed), m_comPort(autoDetectComPort ? comPort : ""), m_useDriveA(!driveOnB) { 40 | } 41 | 42 | // This is for the static version 43 | SupercardProDiskBridge::SupercardProDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, int uaeSettings) : 44 | CommonBridgeTemplate(bridgeMode, bridgeDensity, false, false), m_useDriveA((uaeSettings & 0x0F) == 0), m_comPort("") { 45 | } 46 | 47 | 48 | SupercardProDiskBridge::~SupercardProDiskBridge() { 49 | } 50 | 51 | // If your device supports being able to abort a disk read, mid-read then implement this 52 | void SupercardProDiskBridge::abortDiskReading() { 53 | m_io.abortReadStreaming(); 54 | }; 55 | 56 | // A manual way to detect a disk change event 57 | bool SupercardProDiskBridge::attemptToDetectDiskChange() { 58 | switch (m_io.checkForDisk(true)) { 59 | case SCPErr::scpOK: return true; 60 | case SCPErr::scpNoDiskInDrive: return false; 61 | case SCPErr::scpUnknownError: m_wasIOError = true; return false; 62 | default: return isDiskInDrive(); 63 | } 64 | } 65 | 66 | // If your device supports the DiskChange option then return TRUE here. If not, then the code will simulate it 67 | bool SupercardProDiskBridge::supportsDiskChange() { 68 | return true; 69 | } 70 | 71 | // Return TRUE if the drive is still connected and working 72 | bool SupercardProDiskBridge::isStillWorking() { 73 | return !m_wasIOError; 74 | } 75 | 76 | // Called when the class is about to shut down 77 | void SupercardProDiskBridge::closeInterface() { 78 | // Turn everything off 79 | m_io.enableMotor(false); 80 | m_io.closePort(); 81 | } 82 | 83 | // Called to start the interface, you should update any error messages if it fails. This needs to be ready to see to any cylinder and so should already know where cylinder 0 is 84 | bool SupercardProDiskBridge::openInterface(std::string& errorMessage) { 85 | SCPErr error = m_io.openPort(m_useDriveA); 86 | 87 | if (error == SCPErr::scpOK) { 88 | m_io.findTrack0(); 89 | m_currentCylinder = 0; 90 | return true; 91 | } 92 | else { 93 | switch (error) { 94 | case SCPErr::scpFirmwareTooOld: errorMessage = "SuperCard Pro firmware must be updated to V1.3"; break; 95 | case SCPErr::scpNotFound: errorMessage = "SuperCard Pro board was not detected."; break; 96 | case SCPErr::scpInUse: errorMessage = "SuperCard Pro board is already in use."; break; 97 | default: errorMessage = "An unknown error occurred connecting to your SuperCard Pro."; break; 98 | } 99 | } 100 | 101 | return false; 102 | } 103 | 104 | 105 | const FloppyDiskBridge::BridgeDriver* SupercardProDiskBridge::staticBridgeInformation() { 106 | return &DriverSupercardProFloppy; 107 | } 108 | 109 | // Get the name of the drive 110 | const FloppyDiskBridge::BridgeDriver* SupercardProDiskBridge::_getDriverInfo() { 111 | return staticBridgeInformation(); 112 | } 113 | 114 | // Duplicate of the one below, but here for consistency - Returns the name of interface. This pointer should remain valid after the class is destroyed 115 | const FloppyDiskBridge::DriveTypeID SupercardProDiskBridge::_getDriveTypeID() { 116 | return m_isHDDisk ? DriveTypeID::dti35HD : DriveTypeID::dti35DD; 117 | } 118 | 119 | // Called when a disk is inserted so that you can (re)populate the response to _getDriveTypeID() 120 | void SupercardProDiskBridge::checkDiskType() { 121 | bool capacity; 122 | 123 | // Check capacity 124 | if (m_io.checkDiskCapacity(capacity)) { 125 | m_isHDDisk = capacity; 126 | m_io.selectDiskDensity(m_isHDDisk); 127 | } 128 | else { 129 | m_isHDDisk = false; 130 | m_io.selectDiskDensity(m_isHDDisk); 131 | } 132 | } 133 | 134 | 135 | // Called to force into DD or HD mode. Overrides checkDiskType() until checkDiskType() is called again 136 | void SupercardProDiskBridge::forceDiskDensity(bool forceHD) { 137 | m_isHDDisk = forceHD; 138 | m_io.selectDiskDensity(m_isHDDisk); 139 | } 140 | 141 | 142 | // Called to switch which head is being used right now. Returns success or not 143 | bool SupercardProDiskBridge::setActiveSurface(const DiskSurface activeSurface) { 144 | return m_io.selectSurface(activeSurface == DiskSurface::dsUpper ? SuperCardPro::DiskSurface::dsUpper : SuperCardPro::DiskSurface::dsLower); 145 | } 146 | 147 | // Set the status of the motor on the drive. The motor should maintain this status until switched off or reset. This should *NOT* wait for the motor to spin up 148 | bool SupercardProDiskBridge::setMotorStatus(const bool switchedOn) { 149 | m_motorIsEnabled = switchedOn; 150 | m_motorTurnOnTime = std::chrono::steady_clock::now(); 151 | return m_io.enableMotor(switchedOn, true); 152 | } 153 | 154 | // Called to ask the drive what the current write protect status is - return true if its write protected 155 | bool SupercardProDiskBridge::checkWriteProtectStatus(const bool forceCheck) { 156 | return m_io.isWriteProtected(); 157 | } 158 | 159 | // If the above is TRUE then this is called to get the status of the DiskChange line. Basically, this is TRUE if there is a disk in the drive. 160 | // If force is true you should re-check, if false, then you are allowed to return a cached value from the last disk operation (eg: seek) 161 | bool SupercardProDiskBridge::getDiskChangeStatus(const bool forceCheck) { 162 | // We actually trigger a SEEK operation to ensure this is right 163 | if (forceCheck) { 164 | switch (m_io.checkForDisk(forceCheck)) { 165 | case SCPErr::scpNoDiskInDrive: 166 | if (m_currentCylinder == 0) { 167 | m_io.performNoClickSeek(); 168 | } 169 | else { 170 | m_io.selectTrack((m_currentCylinder > 40) ? m_currentCylinder - 1 : m_currentCylinder + 1, true); 171 | m_io.selectTrack(m_currentCylinder, true); 172 | } 173 | break; 174 | case SCPErr::scpUnknownError: m_wasIOError = true; return false; 175 | } 176 | } 177 | 178 | switch (m_io.checkForDisk(forceCheck)) { 179 | case SCPErr::scpOK: return true; 180 | case SCPErr::scpNoDiskInDrive: return false; 181 | case SCPErr::scpUnknownError: m_wasIOError = true; return false; 182 | default: return isDiskInDrive(); 183 | } 184 | } 185 | 186 | // If we're on track 0, this is the emulator trying to seek to track -1. We catch this as a special case. 187 | // Should perform the same operations as setCurrentCylinder in terms of disk change etc but without changing the current cylinder 188 | // Return FALSE if this is not supported by the bridge 189 | bool SupercardProDiskBridge::performNoClickSeek() { 190 | if (m_io.performNoClickSeek()) { 191 | updateLastManualCheckTime(); 192 | return true; 193 | } 194 | return false; 195 | } 196 | 197 | 198 | // Trigger a seek to the requested cylinder, this can block until complete 199 | bool SupercardProDiskBridge::setCurrentCylinder(const unsigned int cylinder) { 200 | m_currentCylinder = cylinder; 201 | 202 | // No need if its busy 203 | bool ignoreDiskCheck = (isMotorRunning()) && (!isReady()); 204 | 205 | // Go! 206 | if (m_io.selectTrack(cylinder, ignoreDiskCheck)) { 207 | if (!ignoreDiskCheck) updateLastManualCheckTime(); 208 | return true; 209 | } 210 | 211 | return false; 212 | } 213 | 214 | // Called when data should be read from the drive. 215 | // pll: supplied if you use it 216 | // maxBufferSize: Maximum number of RotationExtractor::MFMSample in the buffer. If we're trying to detect a disk, this might be set VERY LOW 217 | // buffer: Where to save to. When a buffer is saved, position 0 MUST be where the INDEX pulse is. RevolutionExtractor will do this for you 218 | // indexMarker: Used by rotationExtractor if you use it, to help be consistent where the INDEX position is read back at 219 | // onRotation: A function you should call for each complete revolution received. If the function returns FALSE then you should abort reading, else keep sending revolutions 220 | // Returns: ReadResponse, explains its self 221 | CommonBridgeTemplate::ReadResponse SupercardProDiskBridge::readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker, 222 | std::function onRotation) { 223 | SCPErr result = m_io.readRotation(pll, maxBufferSize, buffer, indexMarker, 224 | [&onRotation](RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits) -> bool { 225 | return onRotation(*mfmData, dataLengthInBits); 226 | }); 227 | m_motorTurnOnTime = std::chrono::steady_clock::now(); 228 | 229 | switch (result) { 230 | case SCPErr::scpOK: return ReadResponse::rrOK; 231 | case SCPErr::scpNoDiskInDrive: return ReadResponse::rrNoDiskInDrive; 232 | default: return ReadResponse::rrError; 233 | } 234 | } 235 | 236 | 237 | // Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor 238 | // pll: required 239 | // Returns: ReadResponse, explains its self 240 | CommonBridgeTemplate::ReadResponse SupercardProDiskBridge::readLinearData(PLL::BridgePLL& pll) { 241 | SCPErr result = m_io.readData(pll); 242 | m_motorTurnOnTime = std::chrono::steady_clock::now(); 243 | 244 | switch (result) { 245 | case SCPErr::scpOK: return ReadResponse::rrOK; 246 | case SCPErr::scpNoDiskInDrive: return ReadResponse::rrNoDiskInDrive; 247 | default: return ReadResponse::rrError; 248 | } 249 | } 250 | 251 | 252 | // Called when a cylinder revolution should be written to the disk. 253 | // Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte 254 | // numBytes Number of bits in the buffer to write 255 | // writeFromIndex If an attempt should be made to write this from the INDEX pulse rather than just a random position 256 | // suggestUsingPrecompensation A suggestion that you might want to use write pre-compensation, optional 257 | // Returns TRUE if success, or false if it fails. Largely doesn't matter as most stuff should verify with a read straight after 258 | bool SupercardProDiskBridge::writeData(const unsigned char* rawMFMData, const unsigned int numBits, const bool writeFromIndex, const bool suggestUsingPrecompensation) { 259 | SCPErr response = m_io.writeCurrentTrackPrecomp(rawMFMData, (numBits + 7) / 8, writeFromIndex, suggestUsingPrecompensation); 260 | 261 | m_motorTurnOnTime = std::chrono::steady_clock::now(); 262 | 263 | switch (response) { 264 | case SCPErr::scpOK: return true; 265 | case SCPErr::scpWriteProtected: setWriteProtectStatus(true); return false; 266 | default: return false; 267 | } 268 | } 269 | 270 | // This is called by the main thread in case you need to do anything specific at regular intervals 271 | void SupercardProDiskBridge::poll() { 272 | // SCP has a watchdog timeout and after a while the motor will switch off after inactivity. We can't allow this as doesn't work for how the Amiga might do things 273 | if (m_motorIsEnabled) { 274 | const auto timePassed = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_motorTurnOnTime).count(); 275 | if (timePassed > (m_io.getMotorIdleTimeoutTime() / 2)) { 276 | m_io.enableMotor(true, true); 277 | m_motorTurnOnTime = std::chrono::steady_clock::now(); 278 | } 279 | } 280 | } 281 | 282 | 283 | #endif -------------------------------------------------------------------------------- /floppybridge/SuperCardProBridge.h: -------------------------------------------------------------------------------- 1 | #ifndef SUPERCARDPRO_FLOPPY_BRIDGE 2 | #define SUPERCARDPRO_FLOPPY_BRIDGE 3 | /* WinUAE Supercard Pro Interface for *UAE 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * This file, along with currently active and supported interfaces 17 | * are maintained from by GitHub repo at 18 | * https://github.com/RobSmithDev/FloppyDriveBridge 19 | */ 20 | 21 | // 22 | // ROMTYPE_SUPERCARDPRO_WRITER must be defined for this to work 23 | 24 | #ifdef ROMTYPE_SUPERCARDPRO_WRITER 25 | 26 | #include "floppybridge_abstract.h" 27 | #include "CommonBridgeTemplate.h" 28 | #include "SuperCardProInterface.h" 29 | #include "pll.h" 30 | 31 | 32 | class SupercardProDiskBridge : public CommonBridgeTemplate { 33 | private: 34 | // When the motor last switched on 35 | std::chrono::time_point m_motorTurnOnTime; 36 | bool m_motorIsEnabled = false; 37 | 38 | // Which com port should we use? or blank for automatic 39 | std::string m_comPort; 40 | 41 | // Used to detect disconnection of SupercardPRO while in use 42 | bool m_wasIOError = false; 43 | 44 | // Which drive to use 45 | bool m_useDriveA = false; 46 | 47 | // Is this a HD disk? 48 | bool m_isHDDisk = false; 49 | 50 | // Hardware connection 51 | SuperCardPro::SCPInterface m_io; 52 | 53 | // Remember where we are 54 | int m_currentCylinder = 0; 55 | 56 | protected: 57 | // Called when a disk is inserted so that you can (re)populate the response to _getDriveTypeID() 58 | virtual void checkDiskType() override; 59 | 60 | // Called to force into DD or HD mode. Overrides checkDiskType() until checkDiskType() is called again 61 | virtual void forceDiskDensity(bool forceHD) override; 62 | 63 | // This is called by the main thread in case you need to do anything specific at regular intervals 64 | virtual void poll() override; 65 | 66 | // Return TRUE if the drive is still connected and working 67 | virtual bool isStillWorking() override; 68 | 69 | // If your device supports being able to abort a disk read, mid-read then implement this 70 | virtual void abortDiskReading() override; 71 | 72 | // If your device supports the DiskChange option then return TRUE here. If not, then the code will simulate it 73 | virtual bool supportsDiskChange() override; 74 | 75 | // If the above is TRUE then this is called to get the status of the DiskChange line. Basically, this is TRUE if there is a disk in the drive. 76 | // If force is true you should re-check, if false, then you are allowed to return a cached value from the last disk operation (eg: seek) 77 | virtual bool getDiskChangeStatus(const bool forceCheck) override; 78 | 79 | // Called when the class is about to shut down 80 | virtual void closeInterface() override; 81 | 82 | // Called to start the interface, you should update any error messages if it fails. This needs to be ready to see to any cylinder and so should already know where cylinder 0 is 83 | virtual bool openInterface(std::string& errorMessage) override; 84 | 85 | // Called to ask the drive what the current write protect status is - return true if its write protected. If forceCheck is true you should actually check the drive, 86 | // else use the last status checked which was probably from a SEEK setCurrentCylinder call. If you can ONLY get this information here then you should always force check 87 | virtual bool checkWriteProtectStatus(const bool forceCheck) override; 88 | 89 | // Get the name of the drive 90 | virtual const BridgeDriver* _getDriverInfo() override; 91 | 92 | // Duplicate of the one below, but here for consistency - Returns the name of interface. This pointer should remain valid after the class is destroyed 93 | virtual const DriveTypeID _getDriveTypeID() override; 94 | 95 | // Called to switch which head is being used right now. Returns success or not 96 | virtual bool setActiveSurface(const DiskSurface activeSurface) override; 97 | 98 | // Set the status of the motor on the drive. The motor should maintain this status until switched off or reset. This should *NOT* wait for the motor to spin up 99 | virtual bool setMotorStatus(const bool switchedOn) override; 100 | 101 | // Trigger a seek to the requested cylinder, this can block until complete 102 | virtual bool setCurrentCylinder(const unsigned int cylinder) override; 103 | 104 | // If we're on track 0, this is the emulator trying to seek to track -1. We catch this as a special case. 105 | // Should perform the same operations as setCurrentCylinder in terms of disk change etc but without changing the current cylinder 106 | // Return FALSE if this is not supported by the bridge 107 | virtual bool performNoClickSeek() override; 108 | 109 | // Called when data should be read from the drive. 110 | // pll: supplied if you use it 111 | // maxBufferSize: Maximum number of RotationExtractor::MFMSample in the buffer. If we're trying to detect a disk, this might be set VERY LOW 112 | // buffer: Where to save to. When a buffer is saved, position 0 MUST be where the INDEX pulse is. RevolutionExtractor will do this for you 113 | // indexMarker: Used by rotationExtractor if you use it, to help be consistent where the INDEX position is read back at 114 | // onRotation: A function you should call for each complete revolution received. If the function returns FALSE then you should abort reading, else keep sending revolutions 115 | // Returns: ReadResponse, explains its self 116 | virtual ReadResponse readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker, 117 | std::function onRotation) override; 118 | 119 | // Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor 120 | // pll: required 121 | // Returns: ReadResponse, explains its self 122 | virtual ReadResponse readLinearData(PLL::BridgePLL& pll) override; 123 | 124 | // Called when a cylinder revolution should be written to the disk. 125 | // Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte 126 | // numBytes Number of bits in the buffer to write 127 | // writeFromIndex If an attempt should be made to write this from the INDEX pulse rather than just a random position 128 | // suggestUsingPrecompensation A suggestion that you might want to use write pre-compensation, optional 129 | // Returns TRUE if success, or false if it fails. Largely doesnt matter as most stuff should verify with a read straight after 130 | virtual bool writeData(const unsigned char* rawMFMData, const unsigned int numBits, const bool writeFromIndex, const bool suggestUsingPrecompensation) override; 131 | 132 | // A manual way to check for disk change. This is simulated by issuing a read message and seeing if there's any data. Returns TRUE if data or an INDEX pulse was detected 133 | // It's virtual as the default method issues a read and looks for data. If you have a better implementation then override this 134 | virtual bool attemptToDetectDiskChange() override; 135 | 136 | public: 137 | SupercardProDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, bool enableAutoCache, bool useSmartSpeed, bool autoDetectComPort, char* comPort, bool driveOnB); 138 | 139 | // This is for the static version 140 | SupercardProDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, int uaeSettings); 141 | 142 | 143 | virtual ~SupercardProDiskBridge(); 144 | 145 | static const BridgeDriver* staticBridgeInformation(); 146 | }; 147 | 148 | 149 | #endif 150 | #endif -------------------------------------------------------------------------------- /floppybridge/SuperCardProInterface.h: -------------------------------------------------------------------------------- 1 | #ifndef SUPERCARDPRO_INTERFACE 2 | #define SUPERCARDPRO_INTERFACE 3 | /* Supercard Pro C++ Interface for *UAE 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * Based on the documentation and hardware by Jim Drew at 17 | * https://www.cbmstuff.com/ 18 | * 19 | * This requires firmware V1.3 20 | * 21 | * This file, along with currently active and supported interfaces 22 | * are maintained from by GitHub repo at 23 | * https://github.com/RobSmithDev/FloppyDriveBridge 24 | */ 25 | 26 | 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "RotationExtractor.h" 33 | #include "SerialIO.h" 34 | #include "pll.h" 35 | 36 | namespace SuperCardPro { 37 | 38 | // Represent which side of the disk we're looking at 39 | enum class DiskSurface { 40 | dsUpper, // The upper side of the disk 41 | dsLower // The lower side of the disk 42 | }; 43 | 44 | // Potential responses from the SCP 45 | enum class SCPResponse : unsigned char { 46 | pr_Unused = 0x00, // not used(to disallow NULL responses) 47 | pr_BadCommand = 0x01, // bad command 48 | pr_CommandErr = 0x02, // command error(bad structure, etc.) 49 | pr_Checksum = 0x03, // packet checksum failed 50 | pr_Timeout = 0x04, // USB timeout 51 | pr_NoTrk0 = 0x05, // track zero never found(possibly no drive online) 52 | pr_NoDriveSel = 0x06, // no drive selected 53 | pr_NoMotorSel = 0x07, // motor not enabled(required for read or write) 54 | pr_NotReady = 0x08, // drive not ready(disk change is high) 55 | pr_NoIndex = 0x09, // no index pulse detected 56 | pr_ZeroRevs = 0x0A, // zero revolutions chosen 57 | pr_ReadTooLong = 0x0B, // read data was more than RAM would hold 58 | pr_BadLength = 0x0C, // invalid length value 59 | pr_Reserved1 = 0x0D, // reserved for future use 60 | pr_BoundaryOdd = 0x0E, // location boundary is odd 61 | pr_WPEnabled = 0x0F, // disk is write protected 62 | pr_BadRAM = 0x10, // RAM test failed 63 | pr_NoDisk = 0x11, // no disk in drive 64 | pr_BadBaud = 0x12, // bad baud rate selected 65 | pr_BadCmdOnPort = 0x13, // command is not available for this type of port 66 | pr_NoStream = 0x14, // stream attempted to be stop when it was not occurring! 67 | pr_Overrun = 0x15, // buffer overrun occurred 68 | pr_Ok = 0x4F, // packet good(letter 'O' for OK) 69 | 70 | 71 | pr_writeError = 0xFF // Used internally 72 | }; 73 | 74 | enum class SCPCommand : unsigned char { 75 | DoCMD_SELA = 0x80, // select drive A 76 | DoCMD_SELB = 0x81, // select drive B 77 | DoCMD_DSELA = 0x82, // deselect drive A 78 | DoCMD_DSELB = 0x83, // deselect drive B 79 | 80 | DoCMD_MTRAON = 0x84, // turn motor A on 81 | DoCMD_MTRBON = 0x85, // turn motor B on 82 | DoCMD_MTRAOFF = 0x86, // turn motor A off 83 | DoCMD_MTRBOFF = 0x87, // turn motor B off 84 | 85 | DoCMD_SEEK0 = 0x88, // seek track 0 86 | DoCMD_STEPTO = 0x89, // step to specified track 87 | DoCMD_STEPIN = 0x8A, // step towards inner(higher) track 88 | DoCMD_STEPOUT = 0x8B, // step towards outer(lower) track 89 | 90 | DoCMD_SELDENS = 0x8C, // select density 91 | 92 | DoCMD_SIDE = 0x8D, // select side 93 | DoCMD_STATUS = 0x8E, // get drive status 94 | DoCMD_GETPARAMS = 0x90, // get parameters 95 | DoCMD_SETPARAMS = 0x91, // set parameters 96 | DoCMD_RAMTEST = 0x92, // do RAM test 97 | DoCMD_SETPIN33 = 0x93, // set pin 33 of floppy connector 98 | DoCMD_READFLUX = 0xA0, // read flux level 99 | DoCMD_GETFLUXINFO = 0xA1, // get info for last flux read 100 | DoCMD_WRITEFLUX = 0xA2, // write flux level 101 | DoCMD_SENDRAM_USB = 0xA9, // send data from buffer to USB 102 | DoCMD_LOADRAM_USB = 0xAA, // get data from USB and store in buffer 103 | DoCMD_SENDRAM_232 = 0xAB, // send data from buffer to the serial port 104 | DoCMD_LOADRAM_232 = 0xAC, // get data from the serial port and store in buffer 105 | DoCMD_STARTSTREAM = 0xAE, // start streaming flux data from the drive (requires firmware 1.3) 106 | DoCMD_STOPSTREAM = 0xAF, // Stop an active stream 107 | DoCMD_SCPInfo = 0xD0, // get info about SCP hardware / firmware 108 | DoCMD_SETBAUD1 = 0xD1, // sets the baud rate of port labeled RS232, // 1 109 | DoCMD_SETBAUD2 = 0xD2 // sets the baud rate of port labeled RS232, // 2 110 | }; 111 | 112 | enum class SCPErr { 113 | scpOK, 114 | scpNotFound, 115 | scpInUse, 116 | scpNoDiskInDrive, 117 | scpWriteProtected, 118 | scpFirmwareTooOld, 119 | scpOverrun, 120 | scpUnknownError 121 | }; 122 | 123 | class SCPInterface { 124 | private: 125 | // Windows handle to the serial port device 126 | SerialIO m_comPort; 127 | bool m_diskInDrive; 128 | bool m_motorIsEnabled = false; 129 | bool m_isWriteProtected = false; 130 | bool m_useDriveA = false; 131 | bool m_isAtTrack0 = false; 132 | int m_currentTrack = -1; 133 | bool m_isHDMode = false; 134 | bool m_selectStatus = false; 135 | std::mutex m_protectAbort; 136 | bool m_abortStreaming = false; 137 | bool m_abortSignalled = true; 138 | bool m_isStreaming = false; 139 | 140 | struct { 141 | unsigned char hardwareVersion =0, hardwareRevision =0; 142 | unsigned char firmwareVersion =0, firmwareRevision =0; 143 | } m_firmwareVersion; 144 | 145 | // Apply and change the timeouts on the com port 146 | void applyCommTimeouts(bool shortTimeouts); 147 | 148 | // Trigger selecting this drive 149 | bool selectDrive(bool select); 150 | 151 | // Polls for the state of pins on the board 152 | bool checkPins(); 153 | 154 | // Various variants 155 | bool sendCommand(const SCPCommand command, SCPResponse& response); 156 | bool sendCommand(const SCPCommand command, const unsigned char parameter, SCPResponse& response); 157 | bool sendCommand(const SCPCommand command, const unsigned char* payload, const unsigned char payloadLength, SCPResponse& response, bool readResponse = true); 158 | 159 | // Read ram from the SCP 160 | //bool readSCPRam(const unsigned int offset, const unsigned int length); 161 | public: 162 | // Constructor for this class 163 | SCPInterface(); 164 | 165 | // Free me 166 | ~SCPInterface(); 167 | 168 | const bool isOpen() const { return m_comPort.isPortOpen(); }; 169 | 170 | bool isWriteProtected() const { return m_isWriteProtected; }; 171 | 172 | // Attempts to open the reader running on the COM port number provided. 173 | SCPErr openPort(bool useDriveA); 174 | 175 | // Reads a complete rotation of the disk, and returns it using the callback function whcih can return FALSE to stop 176 | // An instance of PLL is required which contains a rotation extractor. This is purely to save on re-allocations. It is internally reset each time 177 | SCPErr readRotation(PLL::BridgePLL& pll, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, 178 | std::function onRotation); 179 | 180 | // Reads just enough data to fulfill most extractions needed, but doesnt care about rotation position or index - pll should have the LinearExtractor configured 181 | SCPErr readData(PLL::BridgePLL& pll); 182 | 183 | // Turns on and off the reading interface. dontWait disables the GW timeout waiting, so you must instead. 184 | bool enableMotor(const bool enable, const bool dontWait = false); 185 | 186 | // Seek to track 0 - Can return drRewindFailure, drSelectTrackError, drOK 187 | bool findTrack0(); 188 | 189 | // Test if its an HD disk 190 | bool checkDiskCapacity(bool& isHD); 191 | 192 | // Select the track, this makes the motor seek to this position. Can return drRewindFailure, drSelectTrackError, drOK, drTrackRangeError 193 | bool selectTrack(const unsigned char trackIndex, bool ignoreDiskInsertCheck = false); 194 | 195 | // Special command that asks to do a 'seek to track -1' which isnt allowed but can be used for disk detection 196 | bool performNoClickSeek(); 197 | 198 | // Choose which surface of the disk to read from. Can return drError or drOK 199 | bool selectSurface(const DiskSurface side); 200 | 201 | // Write data to the disk. Can return drReadResponseFailed, drWriteProtected, drSerialOverrun, drWriteProtected, drOK 202 | SCPErr writeCurrentTrackPrecomp(const unsigned char* mfmData, const unsigned short numBytes, const bool writeFromIndexPulse, bool usePrecomp); 203 | 204 | // Attempt to abort reading 205 | bool abortReadStreaming(); 206 | 207 | // Check if adisk is present in the drive 208 | SCPErr checkForDisk(bool force); 209 | 210 | // Closes the port down 211 | void closePort(); 212 | 213 | // Switch the density mode 214 | bool selectDiskDensity(bool hdMode); 215 | 216 | // Returns the motor iddle (auto switch off) time 217 | unsigned int getMotorIdleTimeoutTime(); 218 | }; 219 | 220 | }; 221 | 222 | #endif -------------------------------------------------------------------------------- /floppybridge/floppybridge_abstract.h: -------------------------------------------------------------------------------- 1 | /* floppybridge_abstract 2 | * 3 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 4 | * https://amiga.robsmithdev.co.uk 5 | * 6 | * This library defines a standard interface for connecting physical disk drives to *UAE 7 | * 8 | * Derived classes must be implemented so they are unlikely to cause a delay in any function 9 | * as doing so would cause audio and mouse cursors to stutter 10 | * 11 | * This is free and unencumbered released into the public domain. 12 | * See the file COPYING for more details, or visit . 13 | * 14 | */ 15 | 16 | 17 | /* 18 | * This file, along with currently active and supported interfaces 19 | * are maintained from by GitHub repo at 20 | * https://github.com/RobSmithDev/FloppyDriveBridge 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | 28 | #ifdef _WIN32 29 | #include 30 | #else 31 | #ifndef TCHAR 32 | #ifdef _UNICODE 33 | #define TCHAR wchar_t 34 | #define _T(x) L ##x 35 | #else 36 | #define TCHAR char 37 | #define _T(x) x 38 | #endif 39 | #endif 40 | #endif 41 | 42 | 43 | 44 | class FloppyDiskBridge { 45 | public: 46 | 47 | // Driver information 48 | struct BridgeDriver { 49 | // Details about the driver 50 | const char* name; 51 | const char* url; 52 | const char* manufacturer; 53 | const char* driverAuthor; 54 | 55 | // Which options in configuration it can support, aside from the standard ones 56 | const unsigned int configOptions; 57 | }; 58 | 59 | // Definition of the type of drive 60 | enum class DriveTypeID : unsigned char { dti35DD = 0, dti35HD = 1, dti5255SD = 2 }; 61 | 62 | FloppyDiskBridge() {} 63 | // This is just to force this being virtual 64 | virtual ~FloppyDiskBridge() {} 65 | 66 | // Call to start the system up. Return false if it fails 67 | virtual bool initialise() = 0; 68 | 69 | // This is called prior to closing down, but should reverse initialise 70 | virtual void shutdown() {} 71 | 72 | // Returns the name of interface. This pointer should remain valid *after* the class is destroyed so should be static 73 | virtual const BridgeDriver* getDriverInfo() = 0; 74 | 75 | // Return the 'bit cell' time in uSec. Standard DD Amiga disks this would be 2uS, HD disks would be 1us I guess, but mainly used for =4 for SD I think 76 | virtual unsigned char getBitSpeed() { return 2; } 77 | 78 | // Return the type of disk connected. This is used to tell WinUAE if we're DD or HD. This must return INSTANTLY 79 | virtual DriveTypeID getDriveTypeID() = 0; 80 | 81 | // Call to get the last error message. If the board initialised this may return a compatibility warning instead 82 | virtual const char* getLastErrorMessage() { return NULL; } 83 | 84 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 85 | 86 | 87 | 88 | 89 | // Reset the drive. This should reset it to the state it would be at powerup, ie: motor switched off etc. The current cylinder number can be 'unknown' at this point 90 | virtual bool resetDrive(int trackNumber) = 0; 91 | 92 | 93 | 94 | 95 | /////////////////////// Head movement Controls ////////////////////////////////////////// 96 | // Return TRUE if the drive is currently on cylinder 0 97 | virtual bool isAtCylinder0() = 0; 98 | 99 | // Return the number of cylinders the drive supports. Eg: 80 or 82 (or 40) 100 | virtual unsigned char getMaxCylinder() = 0; 101 | 102 | // Seek to a specific cylinder 103 | virtual void gotoCylinder(int cylinderNumber, bool side) = 0; 104 | 105 | // Handle the drive stepping to track -1 - this is used to 'no-click' detect the disk 106 | virtual void handleNoClickStep(bool side) = 0; 107 | 108 | // Return the current cylinder number we're on 109 | virtual unsigned char getCurrentCylinderNumber() = 0; 110 | 111 | 112 | 113 | /////////////////////// Drive Motor Controls ///////////////////////////////////////////// 114 | // Return true if the motor is spinning, but not necessarily up to speed 115 | virtual bool isMotorRunning() = 0; 116 | 117 | // Turn on and off the motor 118 | virtual void setMotorStatus(bool side, bool turnOn) = 0; 119 | 120 | // Returns TRUE if the drive is ready (ie: the motor has spun up to speed to speed) 121 | virtual bool isReady() = 0; 122 | 123 | // Returns the currently selected side 124 | virtual bool getCurrentSide() = 0; 125 | 126 | /////////////////////// Disk Detection /////////////////////////////////////////////////// 127 | // Return TRUE if there is a disk in the drive. This is usually called after gotoCylinder 128 | virtual bool isDiskInDrive() = 0; 129 | 130 | // Check if the disk has changed. Basically returns FALSE if there's no disk in the drive 131 | virtual bool hasDiskChanged() = 0; 132 | 133 | // Return TRUE if the drive is still connected and working 134 | virtual bool isStillWorking() { return true; }; 135 | 136 | /////////////////////// Reading Data ///////////////////////////////////////////////////// 137 | // Return TRUE if we're at the INDEX marker/sensor. mfmPositionBits is in BITS 138 | virtual bool isMFMPositionAtIndex(int mfmPositionBits) = 0; 139 | 140 | // Returns TRUE if data is ready and available 141 | virtual bool isMFMDataAvailable() = 0; 142 | 143 | // This returns a single MFM bit at the position provided 144 | virtual bool getMFMBit(const int mfmPositionBits) = 0; 145 | 146 | // Requests an entire track of data. Returns 0 if the track is not available 147 | // The return value is the wrap point in bits (last byte is shifted to MSB) 148 | virtual int getMFMTrack(bool side, unsigned int track, bool resyncRotation, const int bufferSizeInBytes, void* output) = 0; 149 | 150 | // write data to the MFM track buffer to be written to disk - poll isWriteComplete to check for completion 151 | virtual bool writeMFMTrackToBuffer(bool side, unsigned int track, bool writeFromIndex, int sizeInBytes, void* mfmData) = 0; 152 | 153 | // A special mode that DISABLES FloppyBridge from auto-reading tracks and allows writeMFMTrackToBuffer and getMFMTrack to operate directly. 154 | virtual bool setDirectMode(bool directModeEnable) = 0; 155 | 156 | // This asks the time taken to read the bit at mfmPositionBits. 1000=100%, <1000 data is read faster, >1000 data is read slower. 157 | // This number is used in a loop (scaled) starting with a number, and decrementing by this number. 158 | // Each loop a single bit is read. So the smaller the number, the more loops that occur, and the more bits that are read 159 | virtual int getMFMSpeed(const int mfmPositionBits) = 0; 160 | 161 | // This is called in both modes. It is called when WinUAE detects a full revolution of data has been read. This could allow you to switch to a different recording of the same cylinder if needed. 162 | virtual void mfmSwitchBuffer(bool side) = 0; 163 | 164 | // Quick confirmation from UAE that we're actually on the same side 165 | virtual void setSurface(bool side) = 0; 166 | 167 | // Return the maximum size of bits available in this revolution. This is the maximum passed to getMFMBit 168 | virtual int maxMFMBitPosition() = 0; 169 | 170 | /////////////////////// Writing Data ///////////////////////////////////////////////////// 171 | 172 | // Submits a single WORD of data received during a DMA transfer to the disk buffer. This needs to be saved. It is usually flushed when commitWriteBuffer is called 173 | // You should reset this buffer if side or track changes, mfmPosition is provided purely for any index sync you may wish to do 174 | virtual void writeShortToBuffer(bool side, unsigned int track, unsigned short mfmData, int mfmPosition) = 0; 175 | 176 | // Return TRUE if the currently inserted disk is write protected 177 | virtual bool isWriteProtected() = 0; 178 | 179 | // Requests that any data received via writeShortToBuffer be saved to disk. The side and track should match against what you have been collected 180 | // and the buffer should be reset upon completion. You should return the new track length (maxMFMBitPosition) with optional padding if needed 181 | virtual unsigned int commitWriteBuffer(bool side, unsigned int track) = 0; 182 | 183 | // Returns TRUE if commitWriteBuffer has been called but not written to disk yet 184 | virtual bool isWritePending() = 0; 185 | 186 | // Returns TRUE if a write is no longer pending. This should only return TRUE the first time, and then should reset 187 | virtual bool isWriteComplete() = 0; 188 | 189 | // Set to TRUE if turbo writing is allowed (this is a sneaky DMA bypass trick) 190 | virtual bool canTurboWrite() = 0; 191 | 192 | // Return TRUE if there is data ready to be committed to disk 193 | virtual bool isReadyToWrite() = 0; 194 | }; 195 | 196 | 197 | -------------------------------------------------------------------------------- /floppybridge/floppybridge_common.h: -------------------------------------------------------------------------------- 1 | /* floppybridge_common 2 | * 3 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 4 | * https://amiga.robsmithdev.co.uk 5 | * 6 | * Shared class between the dll and class to connect to it. 7 | * 8 | * This is free and unencumbered released into the public domain 9 | * But please don't remove the above information 10 | * 11 | * For more details visit . 12 | * 13 | */ 14 | #pragma once 15 | 16 | 17 | typedef void* BridgeDriverHandle; 18 | 19 | namespace FloppyBridge { 20 | 21 | // Type of bridge mode 22 | enum class BridgeMode : unsigned char { 23 | bmFast = 0, 24 | bmCompatible = 1, 25 | bmTurboAmigaDOS = 2, 26 | bmStalling = 3, 27 | bmMax = 3 28 | }; 29 | 30 | // How to use disk density 31 | enum class BridgeDensityMode : unsigned char { 32 | bdmAuto = 0, 33 | bdmDDOnly = 1, 34 | bdmHDOnly = 2, 35 | bdmMax = 2 36 | }; 37 | 38 | // Used to select the drive connected 39 | enum class DriveSelection : unsigned char { 40 | dsDriveA = 0, // IBM PC 41 | dsDriveB = 1, 42 | dsDrive0 = 2, // SHUGART 43 | dsDrive1 = 3, 44 | dsDrive2 = 4, 45 | dsDrive3 = 5 46 | }; 47 | 48 | 49 | // Used by BRIDGE_About 50 | struct BridgeAbout { 51 | const char* about; 52 | const char* url; 53 | unsigned int majorVersion, minorVersion; 54 | unsigned int isBeta; 55 | unsigned int isUpdateAvailable; 56 | unsigned int updateMajorVersion, updateMinorVersion; 57 | }; 58 | 59 | 60 | // Information about a floppy bridge profile 61 | struct FloppyBridgeProfileInformationDLL { 62 | // Unique ID of this profile 63 | unsigned int profileID; 64 | 65 | // Driver Index, in case it's shown on the GUI 66 | unsigned int driverIndex; 67 | 68 | // Some basic information 69 | BridgeMode bridgeMode; 70 | BridgeDensityMode bridgeDensityMode; 71 | 72 | // Profile name 73 | char* name; 74 | 75 | // Pointer to the Configuration data for this profile. - Be careful. Assume this pointer is invalid after calling *any* of the *profile* functions apart from getAllProfiles 76 | char* profileConfig; 77 | }; 78 | 79 | 80 | 81 | }; -------------------------------------------------------------------------------- /floppybridge/floppybridge_config.h: -------------------------------------------------------------------------------- 1 | #ifndef FLOPPY_BRIDGE_CONFIG_H 2 | #define FLOPPY_BRIDGE_CONFIG_H 3 | /* floppybridge_config 4 | * 5 | * Copyright (C) 2021-2022 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This library defines which interfaces are enabled within *UAE 9 | * 10 | * This file, along with currently active and supported interfaces 11 | * are maintained from by GitHub repo at 12 | * https://github.com/RobSmithDev/FloppyDriveBridge 13 | * 14 | * This is free and unencumbered released into the public domain. 15 | * See the file COPYING for more details, or visit . 16 | * 17 | */ 18 | 19 | // DrawBridge aka Arduino floppy reader/writer 20 | #define ROMTYPE_ARDUINOREADER_WRITER ROMTYPE_FLOPYBRDGE0 21 | #include "ArduinoFloppyBridge.h" 22 | 23 | // GreaseWeazle floppy reader/writer 24 | #define ROMTYPE_GREASEWEAZLEREADER_WRITER ROMTYPE_FLOPYBRDGE1 25 | #include "GreaseWeazleBridge.h" 26 | 27 | // Supercard Pro floppy reader/writer 28 | #define ROMTYPE_SUPERCARDPRO_WRITER ROMTYPE_FLOPYBRDGE2 29 | #include "SuperCardProBridge.h" 30 | 31 | #endif -------------------------------------------------------------------------------- /floppybridge/ftdi.h: -------------------------------------------------------------------------------- 1 | /* ArduinoFloppyReader (and writer) 2 | * 3 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 4 | * https://amiga.robsmithdev.co.uk 5 | * 6 | * This file is multi-licensed under the terms of the Mozilla Public 7 | * License Version 2.0 as published by Mozilla Corporation and the 8 | * GNU General Public License, version 2 or later, as published by the 9 | * Free Software Foundation. 10 | * 11 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 12 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 13 | * 14 | * This file, along with currently active and supported interfaces 15 | * are maintained from by GitHub repo at 16 | * https://github.com/RobSmithDev/FloppyDriveBridge 17 | */ 18 | 19 | /* 20 | This is a FTDI driver class, based on the FTDI2XX.h file from FTDI. 21 | This library dynamically loads the DLL rather than static linking to it. 22 | */ 23 | 24 | #ifndef FTDI_CLASS_H 25 | #define FTDI_CLASS_H 26 | 27 | #define FTDI_D2XX_AVAILABLE 28 | 29 | #ifdef FTDI_D2XX_AVAILABLE 30 | 31 | namespace FTDI { 32 | 33 | 34 | #ifdef _WIN32 35 | #include 36 | #else 37 | #include 38 | #include 39 | #ifndef DWORD 40 | #define DWORD uint32_t 41 | #endif 42 | #ifndef LPDWORD 43 | #define LPDWORD uint32_t* 44 | #endif 45 | #ifndef ULONG 46 | #define ULONG uint32_t 47 | #endif 48 | #ifndef LONG 49 | #define LONG int32_t 50 | #endif 51 | #ifndef LPLONG 52 | #define LPLONG int32_t* 53 | #endif 54 | #ifndef UCHAR 55 | #define UCHAR unsigned char 56 | #endif 57 | #ifndef USHORT 58 | #define USHORT uint16_t 59 | #endif 60 | #ifndef LPVOID 61 | #define LPVOID void* 62 | #endif 63 | #ifndef LPOVERLAPPED 64 | #define LPOVERLAPPED void* 65 | #endif 66 | #endif 67 | 68 | typedef void* FT_HANDLE; 69 | 70 | // Device status 71 | enum class FT_STATUS : DWORD { 72 | FT_OK = 0, 73 | FT_INVALID_HANDLE = 1, 74 | FT_DEVICE_NOT_FOUND = 2, 75 | FT_DEVICE_NOT_OPENED = 3, 76 | FT_IO_ERROR = 4, 77 | FT_INSUFFICIENT_RESOURCES = 5, 78 | FT_INVALID_PARAMETER = 6, 79 | FT_INVALID_BAUD_RATE = 7, 80 | FT_DEVICE_NOT_OPENED_FOR_ERASE = 8, 81 | FT_DEVICE_NOT_OPENED_FOR_WRITE = 9, 82 | FT_FAILED_TO_WRITE_DEVICE = 10, 83 | FT_EEPROM_READ_FAILED = 11, 84 | FT_EEPROM_WRITE_FAILED = 12, 85 | FT_EEPROM_ERASE_FAILED = 13, 86 | FT_EEPROM_NOT_PRESENT = 14, 87 | FT_EEPROM_NOT_PROGRAMMED = 15, 88 | FT_INVALID_ARGS = 16, 89 | FT_NOT_SUPPORTED = 17, 90 | FT_OTHER_ERROR = 18, 91 | FT_DRIVER_NOT_AVAILABLE 92 | }; 93 | 94 | #define FT_SUCCESS(status) ((status) == FT_OK) 95 | 96 | // FT_OpenEx Flags 97 | enum class FT_OPENEX : UCHAR { 98 | BY_SERIAL_NUMBER = 1, 99 | BY_DESCRIPTION = 2, 100 | BY_LOCATION = 4 101 | }; 102 | 103 | // FT_ListDevices Flags (used in conjunction with FT_OpenEx Flags 104 | #define FT_LIST_NUMBER_ONLY 0x80000000 105 | #define FT_LIST_BY_INDEX 0x40000000 106 | #define FT_LIST_ALL 0x20000000 107 | 108 | #define FT_LIST_MASK (FT_LIST_NUMBER_ONLY|FT_LIST_BY_INDEX|FT_LIST_ALL) 109 | 110 | // Device type 111 | enum class FT_DEVICE : DWORD { 112 | _232BM = 0, 113 | _232AM = 1, 114 | _100AX = 2, 115 | _UNKNOWN = 3, 116 | _2232C = 4, 117 | _232R = 5, 118 | _2232H = 6, 119 | _4232H = 7, 120 | _232H = 8, 121 | _X_SERIES = 9 122 | }; 123 | 124 | // Common Baud Rates 125 | #define FT_BAUD_300 300 126 | #define FT_BAUD_600 600 127 | #define FT_BAUD_1200 1200 128 | #define FT_BAUD_2400 2400 129 | #define FT_BAUD_4800 4800 130 | #define FT_BAUD_9600 9600 131 | #define FT_BAUD_14400 14400 132 | #define FT_BAUD_19200 19200 133 | #define FT_BAUD_38400 38400 134 | #define FT_BAUD_57600 57600 135 | #define FT_BAUD_115200 115200 136 | #define FT_BAUD_230400 230400 137 | #define FT_BAUD_460800 460800 138 | #define FT_BAUD_921600 921600 139 | 140 | #define FT_MODEM_STATUS_BI 0x01 // Break Interrupt 141 | #define FT_MODEM_STATUS_OE 0x02 // Overrun Error 142 | #define FT_MODEM_STATUS_PE 0x04 // Parity Error 143 | #define FT_MODEM_STATUS_FE 0x08 // Framing Error 144 | #define FT_MODEM_STATUS_CTS 0x10 // Clear to Send 145 | #define FT_MODEM_STATUS_DSR 0x20 // Data Set Ready 146 | #define FT_MODEM_STATUS_RI 0x40 // Ring Indicator 147 | #define FT_MODEM_STATUS_DCD 0x80 // Data Carrier Detect 148 | 149 | // Word Lengths 150 | enum class FT_BITS : UCHAR { 151 | _8 = 8, 152 | _7 = 7, 153 | _6 = 6, 154 | _5 = 5 155 | }; 156 | 157 | // Stop Bits 158 | enum class FT_STOP_BITS : UCHAR { 159 | _1 = 0, 160 | _1_5 = 1, 161 | _2 = 2 162 | }; 163 | 164 | // Parity 165 | enum class FT_PARITY : UCHAR { 166 | NONE = 0, 167 | ODD = 1, 168 | EVEN = 2, 169 | MARK = 3, 170 | SPACE = 4 171 | }; 172 | 173 | // Flow Control 174 | #define FT_FLOW_NONE 0x0000 175 | #define FT_FLOW_RTS_CTS 0x0100 176 | #define FT_FLOW_DTR_DSR 0x0200 177 | #define FT_FLOW_XON_XOFF 0x0400 178 | 179 | // Events 180 | typedef void (*PFT_EVENT_HANDLER)(DWORD,DWORD); 181 | 182 | #define FT_EVENT_RXCHAR 1 183 | #define FT_EVENT_MODEM_STATUS 2 184 | #define FT_EVENT_LINE_STATUS 4 185 | 186 | // FT_DEVICE_LIST_INFO_NODE 187 | typedef struct _ft_device_list_info_node { 188 | DWORD Flags; 189 | DWORD Type; 190 | DWORD ID; 191 | DWORD LocId; 192 | char SerialNumber[16]; 193 | char Description[64]; 194 | FT_HANDLE ftHandle; 195 | } FT_DEVICE_LIST_INFO_NODE; 196 | 197 | // Driver Type 198 | enum class FT_DTIVER_TYPE : DWORD { 199 | D2XX = 0, 200 | VCP = 1 201 | }; 202 | 203 | // Timeouts 204 | #define FT_DEFAULT_RX_TIMEOUT 300 205 | #define FT_DEFAULT_TX_TIMEOUT 300 206 | 207 | 208 | class FTDIInterface { 209 | private: 210 | FT_HANDLE m_handle = 0; 211 | public: 212 | 213 | FTDIInterface(); 214 | ~FTDIInterface(); 215 | 216 | // Return TRUE if the port is open 217 | bool isOpen() const { return m_handle != 0; }; 218 | 219 | FT_STATUS FT_Open(int deviceNumber); 220 | FT_STATUS FT_OpenEx(LPVOID pArg1, DWORD Flags); 221 | 222 | FT_STATUS FT_ListDevices(LPVOID pArg1, LPVOID pArg2, DWORD Flags); 223 | 224 | FT_STATUS FT_Close(); 225 | 226 | FT_STATUS FT_Read(LPVOID lpBuffer, DWORD nBufferSize, LPDWORD lpBytesReturned); 227 | FT_STATUS FT_Write(LPVOID lpBuffer, DWORD nBufferSize, LPDWORD lpBytesWritten); 228 | 229 | FT_STATUS FT_IoCtl(DWORD dwIoControlCode, LPVOID lpInBuf, DWORD nInBufSize, LPVOID lpOutBuf, DWORD nOutBufSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped); 230 | 231 | FT_STATUS FT_SetBaudRate(ULONG BaudRate); 232 | FT_STATUS FT_SetDivisor(USHORT Divisor); 233 | FT_STATUS FT_SetTimeouts(ULONG ReadTimeout, ULONG WriteTimeout); 234 | 235 | FT_STATUS FT_SetDataCharacteristics(FT_BITS WordLength, FT_STOP_BITS StopBits, FT_PARITY Parity); 236 | FT_STATUS FT_SetFlowControl(USHORT FlowControl, UCHAR XonChar, UCHAR XoffChar); 237 | 238 | FT_STATUS FT_ResetDevice(); 239 | FT_STATUS FT_GetQueueStatus(DWORD* dwRxBytes); 240 | 241 | FT_STATUS FT_SetDtr(); 242 | FT_STATUS FT_ClrDtr(); 243 | FT_STATUS FT_SetRts(); 244 | FT_STATUS FT_ClrRts(); 245 | 246 | FT_STATUS FT_GetModemStatus(ULONG* pModemStatus); 247 | 248 | FT_STATUS FT_SetChars(UCHAR EventChar, UCHAR EventCharEnabled, UCHAR ErrorChar, UCHAR ErrorCharEnabled); 249 | 250 | FT_STATUS FT_Purge(bool purgeRX, bool purgeTX); 251 | 252 | 253 | FT_STATUS FT_SetEventNotification(DWORD Mask,LPVOID Param); 254 | FT_STATUS FT_GetEventStatus(DWORD* dwEventDWord); 255 | FT_STATUS FT_GetStatus(DWORD* dwRxBytes, DWORD* dwTxBytes, DWORD* dwEventDWord); 256 | 257 | FT_STATUS FT_SetBreakOn(); 258 | FT_STATUS FT_SetBreakOff(); 259 | 260 | FT_STATUS FT_SetWaitMask(DWORD Mask); 261 | FT_STATUS FT_WaitOnMask(DWORD* Mask); 262 | 263 | FT_STATUS FT_CreateDeviceInfoList(LPDWORD lpdwNumDevs); 264 | FT_STATUS FT_GetDeviceInfoList(FT_DEVICE_LIST_INFO_NODE* pDest, LPDWORD lpdwNumDevs); 265 | FT_STATUS FT_GetDeviceInfoDetail(DWORD dwIndex, LPDWORD lpdwFlags, LPDWORD lpdwType, LPDWORD lpdwID, LPDWORD lpdwLocId, char* pcSerialNumber, char* pcDescription, FT_HANDLE* handle); 266 | FT_STATUS FT_GetDriverVersion(LPDWORD lpdwDriverVersion); 267 | FT_STATUS FT_GetLibraryVersion(LPDWORD lpdwDLLVersion); 268 | FT_STATUS FT_ResetPort(); 269 | FT_STATUS FT_CyclePort(); 270 | FT_STATUS FT_GetComPortNumber(FT_HANDLE handle, LPLONG port); 271 | FT_STATUS FT_SetUSBParameters(DWORD dwInTransferSize, DWORD dwOutTransferSize); 272 | FT_STATUS FT_SetLatencyTimer(UCHAR ucTimer); 273 | }; 274 | }; 275 | 276 | #endif 277 | #endif -------------------------------------------------------------------------------- /floppybridge/pll.cpp: -------------------------------------------------------------------------------- 1 | /* Dynamic PLL for *UAE 2 | * 3 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 4 | * https://amiga.robsmithdev.co.uk 5 | * 6 | * This file is multi-licensed under the terms of the Mozilla Public 7 | * License Version 2.0 as published by Mozilla Corporation and the 8 | * GNU General Public License, version 2 or later, as published by the 9 | * Free Software Foundation. 10 | * 11 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 12 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 13 | * 14 | * This file, along with currently active and supported interfaces 15 | * are maintained from by GitHub repo at 16 | * https://github.com/RobSmithDev/FloppyDriveBridge 17 | */ 18 | 19 | /* 20 | * This file, along with currently active and supported interfaces 21 | * are maintained from by GitHub repo at 22 | * https://github.com/RobSmithDev/FloppyDriveBridge 23 | */ 24 | 25 | // This is an improved PLL designed, which is more like a real drive. 26 | // This is roughly based on PLL design in scp.cpp (UAE) by Keir Fraser 27 | // This also can record the data supplied and re-play it with a small amount 28 | // of jitter. This allows more than revolution of data to be instantly available 29 | // which may help with unformatted areas and weak-bits, although I have switched it 30 | // off as its quite experimental 31 | 32 | 33 | 34 | #include "pll.h" 35 | 36 | using namespace PLL; 37 | 38 | #define CLOCK_CENTRE 2000 /* 2000ns = 2us */ 39 | #define CLOCK_MAX_ADJ 10 /* +/- 10% adjustment */ 40 | #define CLOCK_MIN ((CLOCK_CENTRE * (100 - CLOCK_MAX_ADJ)) / 100) 41 | #define CLOCK_MAX ((CLOCK_CENTRE * (100 + CLOCK_MAX_ADJ)) / 100) 42 | 43 | // Constructor 44 | BridgePLL::BridgePLL(bool enabled, bool enableReplay) : m_enabled(enabled) 45 | #ifdef ENABLE_REPLY 46 | , m_useReplay(enableReplay) 47 | #endif 48 | { 49 | reset(); 50 | } 51 | 52 | // Reset the PLL 53 | void BridgePLL::reset() { 54 | m_clock = CLOCK_CENTRE; 55 | m_nFluxSoFar = 0; 56 | m_indexFound = false; 57 | m_latency = 0; 58 | m_totalRealFlux = 0; 59 | m_prevLatency = 0; 60 | #ifdef ENABLE_REPLY 61 | m_fluxReplayData.clear(); 62 | #endif 63 | } 64 | 65 | // Prepare this to be used, by preparing the rotation extractor 66 | void BridgePLL::prepareExtractor(bool isHD, const RotationExtractor::IndexSequenceMarker& indexSequence) { 67 | #ifdef ENABLE_REPLY 68 | m_fluxReplayData.clear(); 69 | #endif 70 | m_extractor->reset(isHD); 71 | m_extractor->setIndexSequence(indexSequence); 72 | } 73 | 74 | // Re-plays the data back into the rotation extractor 75 | void BridgePLL::rePlayData(const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker, 76 | std::function onRotation) { 77 | #ifdef ENABLE_REPLY 78 | if (!m_useReplay) return; 79 | m_useReplay = false; 80 | 81 | m_clock = CLOCK_CENTRE; 82 | m_nFluxSoFar = 0; 83 | m_indexFound = false; 84 | m_latency = 0; 85 | m_totalRealFlux = 0; 86 | m_prevLatency = 0; 87 | 88 | m_extractor->reset(m_extractor->isHD()); 89 | m_extractor->setIndexSequence(indexMarker); 90 | 91 | for (const ReplayData& data : m_fluxReplayData) { 92 | 93 | if (data.fluxTime>250) 94 | submitFlux(data.fluxTime + (rand() % 100) - 50, data.isIndex); 95 | else submitFlux(data.fluxTime, data.isIndex); 96 | 97 | // Is it ready to extract? 98 | if (canExtract()) { 99 | unsigned int bits = 0; 100 | // Go! 101 | if (extractRotation(buffer, bits, maxBufferSize)) { 102 | if (!onRotation(buffer, bits)) { 103 | // And if the callback says so we stop. 104 | break; 105 | } 106 | } 107 | } 108 | } 109 | m_useReplay = true; 110 | #endif 111 | } 112 | 113 | // Submit flux to the PLL 114 | void BridgePLL::submitFlux(uint32_t timeInNanoSeconds, bool isAtIndex) { 115 | #ifdef ENABLE_REPLY 116 | if (m_useReplay) { 117 | m_fluxReplayData.push_back({ (uint32_t)timeInNanoSeconds, isAtIndex }); 118 | } 119 | #endif 120 | 121 | m_indexFound |= isAtIndex; 122 | 123 | // Add on the next flux 124 | m_nFluxSoFar += (int32_t)timeInNanoSeconds; 125 | m_totalRealFlux += timeInNanoSeconds; 126 | if (m_nFluxSoFar < (m_clock / 2)) return; 127 | 128 | // Work out how many zeros, and remaining flux 129 | const int clockedZeros = (m_nFluxSoFar - (m_clock / 2)) / m_clock; 130 | m_nFluxSoFar -= ((clockedZeros + 1) * m_clock); 131 | 132 | if (m_enabled) { 133 | m_latency += ((clockedZeros + 1) * m_clock); 134 | 135 | // PLL: Adjust clock frequency according to phase mismatch. 136 | if ((clockedZeros >= 1) && (clockedZeros <= 3)) { 137 | // In sync: adjust base clock by 10% of phase mismatch. 138 | m_clock += (m_nFluxSoFar / (int)(clockedZeros + 1)) / 10; 139 | } 140 | else { 141 | // Out of sync: adjust base clock towards centre. 142 | m_clock += (CLOCK_CENTRE - m_clock) / 10; 143 | } 144 | 145 | // Clamp the clock's adjustment range. 146 | m_clock = std::max(CLOCK_MIN, std::min(CLOCK_MAX, m_clock)); 147 | 148 | // Authentic PLL: Do not snap the timing window to each flux transition. 149 | const uint32_t new_flux = m_nFluxSoFar / 2; 150 | m_latency += m_nFluxSoFar - new_flux; 151 | m_nFluxSoFar = new_flux; 152 | // This actually works ok if m_totalRealFlux is used instead of m_latency - m_prevLatency but we'll leave it there for good measure 153 | addToExtractor(clockedZeros, m_latency - m_prevLatency, m_totalRealFlux); 154 | m_prevLatency = m_latency; 155 | } 156 | else { 157 | m_nFluxSoFar = 0; 158 | // This actually works ok if m_totalRealFlux is used instead of m_latency - m_prevLatency but we'll leave it there for good measure 159 | addToExtractor(clockedZeros, m_totalRealFlux, m_totalRealFlux); 160 | } 161 | 162 | m_totalRealFlux = 0; 163 | } 164 | 165 | // Add data to the Rotation Extractor 166 | void BridgePLL::addToExtractor(unsigned int numZeros, unsigned int pllTimeInNS, unsigned int realTimeInNS) { 167 | if (numZeros < 0) numZeros = 0; 168 | 169 | // More than 3 zeros. This is not normal MFM, but is allowed 170 | if (numZeros >= 4) { 171 | unsigned int realTimePerBitcell = realTimeInNS / (numZeros + 1); 172 | unsigned int pllTimePerBitcell = pllTimeInNS / (numZeros + 1); 173 | 174 | // Based on the rules we can't output a sequence this big and times must be accurate so we output as many 000's as possible 175 | while (numZeros > 3) { 176 | RotationExtractor::MFMSequenceInfo sample; 177 | sample.mfm = RotationExtractor::MFMSequence::mfm000; 178 | sample.timeNS = realTimePerBitcell * 3; 179 | sample.pllTimeNS = pllTimePerBitcell * 3; 180 | realTimeInNS -= sample.timeNS; 181 | pllTimeInNS -= sample.pllTimeNS; 182 | m_extractor->submitSequence(sample, m_indexFound); 183 | m_indexFound = false; 184 | numZeros -= 3; 185 | } 186 | } 187 | 188 | RotationExtractor::MFMSequenceInfo sample; 189 | sample.mfm = (RotationExtractor::MFMSequence)numZeros; 190 | sample.timeNS = realTimeInNS; 191 | sample.pllTimeNS = pllTimeInNS; 192 | 193 | m_extractor->submitSequence(sample, m_indexFound); 194 | m_indexFound = false; 195 | } 196 | -------------------------------------------------------------------------------- /floppybridge/pll.h: -------------------------------------------------------------------------------- 1 | #ifndef FLOPPYBRIDGE_PLL 2 | #define FLOPPYBRIDGE_PLL 3 | /* Dynamic PLL for *UAE 4 | * 5 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 6 | * https://amiga.robsmithdev.co.uk 7 | * 8 | * This file is multi-licensed under the terms of the Mozilla Public 9 | * License Version 2.0 as published by Mozilla Corporation and the 10 | * GNU General Public License, version 2 or later, as published by the 11 | * Free Software Foundation. 12 | * 13 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 14 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 15 | * 16 | * This file, along with currently active and supported interfaces 17 | * are maintained from by GitHub repo at 18 | * https://github.com/RobSmithDev/FloppyDriveBridge 19 | */ 20 | 21 | /* 22 | * This file, along with currently active and supported interfaces 23 | * are maintained from by GitHub repo at 24 | * https://github.com/RobSmithDev/FloppyDriveBridge 25 | */ 26 | 27 | // This is an improved PLL designed, which is more like a real drive. 28 | // This is roughly based on PLL design in scp.cpp (UAE) by Keir Fraser 29 | // This also can record the data supplied and re-play it with a small amount 30 | // of jitter. This allows more than revolution of data to be instantly available 31 | // which may help with unformatted areas and weak-bits, although I have switched it 32 | // off as its quite experimental 33 | 34 | 35 | #ifndef _WIN32 36 | #include 37 | #endif 38 | #include 39 | #include "RotationExtractor.h" 40 | #include 41 | #include 42 | 43 | 44 | 45 | namespace PLL { 46 | 47 | class BridgePLL { 48 | private: 49 | #ifdef ENABLE_REPLY 50 | struct ReplayData { 51 | uint32_t fluxTime; 52 | bool isIndex; 53 | }; 54 | #endif 55 | const bool m_enabled; 56 | 57 | // Rotation extractor 58 | MFMExtractionTarget* m_extractor = nullptr; 59 | 60 | // Clock 61 | int32_t m_clock = 0; 62 | int32_t m_latency = 0; 63 | int32_t m_prevLatency = 0; 64 | int32_t m_totalRealFlux = 0; 65 | 66 | // Current flux total in nanoseconds 67 | int32_t m_nFluxSoFar = 0; 68 | 69 | #ifdef ENABLE_REPLY 70 | // If re-play is enabled 71 | bool m_useReplay; 72 | 73 | // For storing all flux data so far for "reply with jitter" 74 | std::vector m_fluxReplayData; 75 | #endif 76 | // If the index was discovered 77 | bool m_indexFound = false; 78 | 79 | // Add data to the Rotation Extractor 80 | void addToExtractor(unsigned int numZeros, unsigned int pllTimeInNS, unsigned int realTimeInNS); 81 | 82 | public: 83 | // Make me - if disabled this behaves very basic which might be useful for extraction of flux to SCP 84 | BridgePLL(bool enabled, bool enableReplay); 85 | 86 | // Submit flux to the PLL 87 | void submitFlux(uint32_t timeInNanoSeconds, bool isAtIndex); 88 | 89 | // Reset the PLL 90 | void reset(); 91 | 92 | // Prepare this to be used, by preparing the rotation extractor 93 | void prepareExtractor(bool isHD, const RotationExtractor::IndexSequenceMarker& indexSequence); 94 | 95 | // Change the rotation extractor 96 | void setRotationExtractor(MFMExtractionTarget* extractor) { m_extractor = extractor; } 97 | 98 | // Re-plays the data back into the rotation extractor but with (random) +/- 64ns of jitter 99 | void rePlayData(const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker, 100 | std::function onRotation); 101 | 102 | // Return the active rotation extractor 103 | MFMExtractionTarget* rotationExtractor() { return m_extractor; } 104 | 105 | // Pass on some functions from the extractor 106 | bool canExtract() { return m_extractor->canExtract(); } 107 | bool extractRotation(RotationExtractor::MFMSample* output, unsigned int& outputBits, const unsigned int maxBufferSizeBytes, const bool usePLLTime = false) { return m_extractor->extractRotation(output, outputBits, maxBufferSizeBytes, usePLLTime); } 108 | void getIndexSequence(RotationExtractor::IndexSequenceMarker& sequence) const { m_extractor->getIndexSequence(sequence); } 109 | unsigned int totalTimeReceived() const { return m_extractor->totalTimeReceived(); } 110 | }; 111 | }; 112 | 113 | 114 | #endif -------------------------------------------------------------------------------- /floppybridge/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Resource.rc 4 | // 5 | #define IDB_BRIDGELOGO0 101 6 | #define IDB_BRIDGELOGO1 102 7 | #define IDB_BRIDGELOGO2 103 8 | #define IDD_PROFILEEDITOR 104 9 | #define IDB_BITMAP1 108 10 | #define IDB_PATREON 108 11 | #define IDD_PROFILELISTEDITOR 109 12 | #define IDC_LIST1 1001 13 | #define IDCREATE 1004 14 | #define IDC_PROFILENAME 1004 15 | #define IDEDIT 1005 16 | #define IDC_DRIVER 1005 17 | #define IDDELETE 1006 18 | #define IDC_MODE 1006 19 | #define IDCREATE2 1007 20 | #define IDC_DISKTYPE 1007 21 | #define IDC_SMART 1008 22 | #define IDC_CABLE 1009 23 | #define IDC_COMPORT 1010 24 | #define IDC_AUTOCACHE 1011 25 | #define IDC_URL 1012 26 | #define IDC_AUTODETECT 1013 27 | #define IDC_URL_MAIN 1014 28 | #define IDC_PATREON 1019 29 | #define IDC_PROFILELIST_TITLE 1200 30 | #define IDC_CHECKUPDATES 1202 31 | 32 | // Next default values for new objects 33 | // 34 | #ifdef APSTUDIO_INVOKED 35 | #ifndef APSTUDIO_READONLY_SYMBOLS 36 | #define _APS_NEXT_RESOURCE_VALUE 109 37 | #define _APS_NEXT_COMMAND_VALUE 40001 38 | #define _APS_NEXT_CONTROL_VALUE 1015 39 | #define _APS_NEXT_SYMED_VALUE 101 40 | #endif 41 | #endif 42 | -------------------------------------------------------------------------------- /windows/FloppyBridge.h: -------------------------------------------------------------------------------- 1 | /* FloppyBridge DLL for *UAE 2 | * 3 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 4 | * https://amiga.robsmithdev.co.uk 5 | * 6 | * This file is multi-licensed under the terms of the Mozilla Public 7 | * License Version 2.0 as published by Mozilla Corporation and the 8 | * GNU General Public License, version 2 or later, as published by the 9 | * Free Software Foundation. 10 | * 11 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 12 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 13 | * 14 | * This file, along with currently active and supported interfaces 15 | * are maintained from by GitHub repo at 16 | * https://github.com/RobSmithDev/FloppyDriveBridge 17 | */ 18 | 19 | // The following ifdef block is the standard way of creating macros which make exporting 20 | // from a DLL simpler. All files within this DLL are compiled with the FLOPPYBRIDGE_EXPORTS 21 | // symbol defined on the command line. This symbol should not be defined on any project 22 | // that uses this DLL. This way any other project whose source files include this file see 23 | // FLOPPYBRIDGE_API functions as being imported from a DLL, whereas this DLL sees symbols 24 | // defined with this macro as being exported. 25 | 26 | 27 | #ifdef _WIN32 28 | 29 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 30 | // Windows Header Files 31 | #include 32 | 33 | #define CALLING_CONVENSION _cdecl 34 | #define FLOPPYBRIDGE_API __declspec(dllexport) 35 | 36 | #else 37 | 38 | #define CALLING_CONVENSION 39 | #define FLOPPYBRIDGE_API 40 | 41 | #endif 42 | 43 | #define BRIDGEDRIVERHANDLE 44 | #include "floppybridge_common.h" 45 | 46 | #include "CommonBridgeTemplate.h" 47 | 48 | #define MAX_NUM_DRIVERS 3 49 | 50 | 51 | // Config for a bridge setup 52 | class BridgeConfig { 53 | private: 54 | // Last serialisation of the below data, made when you call toString() 55 | char lastSerialise[255] = { 0 }; 56 | public: 57 | // Which type of interface to open 58 | unsigned int bridgeIndex = 0; 59 | 60 | // The settings that will be used to open the above bridge 61 | FloppyBridge::BridgeMode bridgeMode = FloppyBridge::BridgeMode::bmFast; 62 | FloppyBridge::BridgeDensityMode bridgeDensity = FloppyBridge::BridgeDensityMode::bdmAuto; 63 | 64 | // This isn't saved with fromString and toString 65 | char profileName[128] = { 0 }; 66 | 67 | // Not all of these settings are used. 68 | char comPortToUse[128] = { 0 }; 69 | bool autoDetectComPort = true; 70 | FloppyBridge::DriveSelection driveCable = FloppyBridge::DriveSelection::dsDriveA; 71 | bool autoCache = false; 72 | bool smartSpeed = false; 73 | 74 | // Serialising into a nice string 75 | bool fromString(char* string); 76 | void toString(char** serialisedOptions); //the pointer returned is actually lastSerialise 77 | }; 78 | 79 | 80 | 81 | // This is private, from the outside of the DLL its just a pointer to this 82 | struct BridgeOpened { 83 | /// Details about the driver 84 | FloppyDiskBridge::BridgeDriver* driverDetails = nullptr; 85 | 86 | // The bridge, when created. 87 | CommonBridgeTemplate* bridge = nullptr; 88 | 89 | // Last error/warning message received 90 | char lastMessage[255] = { 0 }; 91 | 92 | // Currently active config 93 | BridgeConfig config; 94 | }; -------------------------------------------------------------------------------- /windows/FloppyBridge.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31402.337 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FloppyBridge", "FloppyBridge.vcxproj", "{EBF9EB12-0BF9-4299-A86F-C36236EABA45}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {EBF9EB12-0BF9-4299-A86F-C36236EABA45}.Debug|x64.ActiveCfg = Debug|x64 17 | {EBF9EB12-0BF9-4299-A86F-C36236EABA45}.Debug|x64.Build.0 = Debug|x64 18 | {EBF9EB12-0BF9-4299-A86F-C36236EABA45}.Debug|x86.ActiveCfg = Debug|Win32 19 | {EBF9EB12-0BF9-4299-A86F-C36236EABA45}.Debug|x86.Build.0 = Debug|Win32 20 | {EBF9EB12-0BF9-4299-A86F-C36236EABA45}.Release|x64.ActiveCfg = Release|x64 21 | {EBF9EB12-0BF9-4299-A86F-C36236EABA45}.Release|x64.Build.0 = Release|x64 22 | {EBF9EB12-0BF9-4299-A86F-C36236EABA45}.Release|x86.ActiveCfg = Release|Win32 23 | {EBF9EB12-0BF9-4299-A86F-C36236EABA45}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {83665D48-034C-4434-BECD-5E4B5E97407E} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /windows/FloppyBridge.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {ebf9eb12-0bf9-4299-a86f-c36236eaba45} 25 | FloppyBridge 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | FloppyBridge 76 | $(SolutionDir)$(Platform)\$(Configuration)\ 77 | $(ProjectDir)..\FloppyBridge;$(IncludePath) 78 | 79 | 80 | false 81 | FloppyBridge 82 | $(ProjectDir)..\FloppyBridge;$(IncludePath) 83 | $(SolutionDir)$(Platform)\$(Configuration)\ 84 | 85 | 86 | true 87 | FloppyBridge_x64 88 | .dll 89 | $(SolutionDir)$(Platform)\$(Configuration)\ 90 | $(ProjectDir)..\FloppyBridge;$(IncludePath) 91 | 92 | 93 | false 94 | FloppyBridge_x64 95 | .dll 96 | $(ProjectDir)..\FloppyBridge;$(IncludePath) 97 | 98 | 99 | 100 | Level3 101 | true 102 | NOMINMAX;WIN32;_DEBUG;FLOPPYBRIDGE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 103 | true 104 | NotUsing 105 | 106 | 107 | stdcpp17 108 | 109 | 110 | Windows 111 | true 112 | false 113 | 114 | 115 | 116 | 117 | Level3 118 | true 119 | true 120 | true 121 | NOMINMAX;WIN32;NDEBUG;FLOPPYBRIDGE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 122 | true 123 | NotUsing 124 | 125 | 126 | MultiThreaded 127 | stdcpp17 128 | 129 | 130 | Windows 131 | true 132 | true 133 | false 134 | false 135 | 136 | 137 | 138 | 139 | Level3 140 | true 141 | NOMINMAX;_DEBUG;FLOPPYBRIDGE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 142 | true 143 | NotUsing 144 | 145 | 146 | ..\floppybridge;%(AdditionalIncludeDirectories) 147 | stdcpp17 148 | 149 | 150 | Windows 151 | true 152 | false 153 | 154 | 155 | 156 | 157 | Level3 158 | true 159 | true 160 | true 161 | NOMINMAX;NDEBUG;FLOPPYBRIDGE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 162 | true 163 | NotUsing 164 | 165 | 166 | MultiThreaded 167 | stdcpp17 168 | 169 | 170 | Windows 171 | true 172 | true 173 | false 174 | false 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /windows/FloppyBridge.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | Header Files 71 | 72 | 73 | Header Files 74 | 75 | 76 | 77 | 78 | Source Files 79 | 80 | 81 | Source Files 82 | 83 | 84 | Source Files 85 | 86 | 87 | Source Files 88 | 89 | 90 | Source Files 91 | 92 | 93 | Source Files 94 | 95 | 96 | Source Files 97 | 98 | 99 | Source Files 100 | 101 | 102 | Source Files 103 | 104 | 105 | Source Files 106 | 107 | 108 | Source Files 109 | 110 | 111 | Source Files 112 | 113 | 114 | Source Files 115 | 116 | 117 | Source Files 118 | 119 | 120 | 121 | 122 | Resource Files 123 | 124 | 125 | 126 | 127 | Resource Files 128 | 129 | 130 | Resource Files 131 | 132 | 133 | Resource Files 134 | 135 | 136 | Resource Files 137 | 138 | 139 | Resource Files 140 | 141 | 142 | Resource Files 143 | 144 | 145 | Resource Files 146 | 147 | 148 | -------------------------------------------------------------------------------- /windows/FloppyBridge.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | C:\Program Files\WinUAE\winuae64.exe 5 | WindowsLocalDebugger 6 | 7 | 8 | C:\Program Files\WinUAE\winuae.exe 9 | WindowsLocalDebugger 10 | 11 | -------------------------------------------------------------------------------- /windows/Resource.aps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/Resource.aps -------------------------------------------------------------------------------- /windows/Resource.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "winres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // English (United Kingdom) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) 19 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK 20 | #pragma code_page(1252) 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Version 51 | // 52 | 53 | VS_VERSION_INFO VERSIONINFO 54 | FILEVERSION 1,6,0,5 55 | PRODUCTVERSION 1,6,0,5 56 | FILEFLAGSMASK 0x3fL 57 | #ifdef _DEBUG 58 | FILEFLAGS 0x3L 59 | #else 60 | FILEFLAGS 0x2L 61 | #endif 62 | FILEOS 0x40004L 63 | FILETYPE 0x2L 64 | FILESUBTYPE 0x0L 65 | BEGIN 66 | BLOCK "StringFileInfo" 67 | BEGIN 68 | BLOCK "080904b0" 69 | BEGIN 70 | VALUE "CompanyName", "RobSmithDev https://amiga.robsmithdev.co.uk" 71 | VALUE "FileDescription", "UAE Floppy Bridge https://amiga.robsmithdev.co.uk" 72 | VALUE "FileVersion", "1.6.0.5" 73 | VALUE "InternalName", "FloppyBridge.dll" 74 | VALUE "LegalCopyright", "Copyright (C) 2021-2024 RobSmithDev" 75 | VALUE "OriginalFilename", "FloppyBridge.dll" 76 | VALUE "ProductName", "UAE Floppy Bridge" 77 | VALUE "ProductVersion", "1.6.0.5" 78 | END 79 | END 80 | BLOCK "VarFileInfo" 81 | BEGIN 82 | VALUE "Translation", 0x809, 1200 83 | END 84 | END 85 | 86 | 87 | ///////////////////////////////////////////////////////////////////////////// 88 | // 89 | // Bitmap 90 | // 91 | 92 | IDB_BRIDGELOGO0 BITMAP "bridge00.bmp" 93 | 94 | IDB_BRIDGELOGO1 BITMAP "bridge01.bmp" 95 | 96 | IDB_BRIDGELOGO2 BITMAP "bridge02.bmp" 97 | 98 | IDB_DONATE BITMAP "donate.bmp" 99 | 100 | IDB_SMALLLOGO0 BITMAP "sbridge00.bmp" 101 | 102 | IDB_SMALLLOGO1 BITMAP "sbridge01.bmp" 103 | 104 | IDB_SMALLLOGO2 BITMAP "sbridge02.bmp" 105 | 106 | 107 | ///////////////////////////////////////////////////////////////////////////// 108 | // 109 | // Dialog 110 | // 111 | 112 | IDD_PROFILELISTEDITOR DIALOGEX 0, 0, 405, 228 113 | STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU 114 | CAPTION "FloppyBridge - Profile Manager" 115 | FONT 8, "MS Shell Dlg", 400, 0, 0x1 116 | BEGIN 117 | LTEXT "Static",IDC_PROFILELIST_TITLE,7,7,198,8,SS_NOPREFIX 118 | PUSHBUTTON "Check for Updates",IDC_CHECKUPDATES,227,5,76,14 119 | LISTBOX IDC_LIST1,7,32,391,170,LBS_OWNERDRAWVARIABLE | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP 120 | PUSHBUTTON "Create...",IDCREATE,7,206,50,14 121 | PUSHBUTTON "Edit...",IDEDIT,61,206,50,14 122 | PUSHBUTTON "Delete...",IDDELETE,115,206,50,14 123 | DEFPUSHBUTTON "OK",IDOK,294,206,50,14 124 | PUSHBUTTON "Cancel",IDCANCEL,348,206,50,14 125 | CONTROL IDB_DONATE,IDC_DONATE,"Static",SS_BITMAP | SS_NOTIFY | SS_CENTERIMAGE,309,5,89,24,WS_EX_TRANSPARENT 126 | LTEXT "url",IDC_URL_MAIN,7,18,197,8,SS_NOTIFY,WS_EX_TRANSPARENT 127 | CONTROL "Check Automatically",IDC_AUTOCHECK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,228,19,77,10 128 | CTEXT "",IDC_UPDATENOW,225,7,77,8,SS_NOTIFY | NOT WS_VISIBLE,WS_EX_TRANSPARENT 129 | END 130 | 131 | IDD_PROFILEEDITOR DIALOGEX 0, 0, 323, 195 132 | STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU 133 | CAPTION "FloppyBridge - Edit Profile" 134 | FONT 8, "MS Shell Dlg", 400, 0, 0x1 135 | BEGIN 136 | DEFPUSHBUTTON "OK",IDOK,212,174,50,14 137 | PUSHBUTTON "Cancel",IDCANCEL,266,174,50,14 138 | RTEXT "Profile Name:",IDC_STATIC,9,10,44,8 139 | EDITTEXT IDC_PROFILENAME,57,8,259,14,ES_AUTOHSCROLL 140 | RTEXT "Driver:",IDC_STATIC,29,30,23,8 141 | COMBOBOX IDC_DRIVER,57,28,259,165,CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | WS_VSCROLL | WS_TABSTOP 142 | RTEXT "Mode:",IDC_STATIC,31,82,21,8 143 | COMBOBOX IDC_MODE,57,80,259,85,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP 144 | RTEXT "Disk Type:",IDC_STATIC,18,133,34,8 145 | COMBOBOX IDC_DISKTYPE,57,131,259,47,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP 146 | RTEXT "Cable Select:",IDC_STATIC,9,152,43,8 147 | COMBOBOX IDC_CABLE,57,149,259,60,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP 148 | RTEXT "COM Port:",IDC_STATIC,18,63,34,8 149 | COMBOBOX IDC_COMPORT,57,61,197,124,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP 150 | CONTROL "Smart Speed (Dynamically switch on Turbo - *might* break copy protection)",IDC_SMART, 151 | "Button",BS_AUTOCHECKBOX | WS_TABSTOP,57,100,259,10 152 | CONTROL "Auto-Cache (continue to cache disk data while drive is idle)",IDC_AUTOCACHE, 153 | "Button",BS_AUTOCHECKBOX | WS_TABSTOP,57,114,259,10 154 | LTEXT "url",IDC_URL,57,46,259,8,SS_NOTIFY,WS_EX_TRANSPARENT 155 | CONTROL "Auto Detect",IDC_AUTODETECT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,262,63,54,10 156 | LTEXT "https://amiga.robsmithdev.co.uk",IDC_URL2,9,180,118,8,SS_NOTIFY,WS_EX_TRANSPARENT 157 | END 158 | 159 | 160 | ///////////////////////////////////////////////////////////////////////////// 161 | // 162 | // DESIGNINFO 163 | // 164 | 165 | #ifdef APSTUDIO_INVOKED 166 | GUIDELINES DESIGNINFO 167 | BEGIN 168 | IDD_PROFILELISTEDITOR, DIALOG 169 | BEGIN 170 | LEFTMARGIN, 7 171 | RIGHTMARGIN, 398 172 | TOPMARGIN, 7 173 | BOTTOMMARGIN, 220 174 | END 175 | 176 | IDD_PROFILEEDITOR, DIALOG 177 | BEGIN 178 | LEFTMARGIN, 9 179 | RIGHTMARGIN, 316 180 | VERTGUIDE, 52 181 | VERTGUIDE, 57 182 | TOPMARGIN, 8 183 | BOTTOMMARGIN, 188 184 | END 185 | END 186 | #endif // APSTUDIO_INVOKED 187 | 188 | 189 | ///////////////////////////////////////////////////////////////////////////// 190 | // 191 | // AFX_DIALOG_LAYOUT 192 | // 193 | 194 | IDD_PROFILELISTEDITOR AFX_DIALOG_LAYOUT 195 | BEGIN 196 | 0, 197 | 0, 0, 0, 0, 198 | 0, 0, 0, 0, 199 | 0, 0, 0, 0, 200 | 0, 0, 0, 0, 201 | 0, 0, 0, 0, 202 | 0, 0, 0, 0, 203 | 100, 100, 0, 0, 204 | 0, 0, 0, 0, 205 | 0, 0, 0, 0, 206 | 0, 0, 0, 0, 207 | 0, 0, 0, 0, 208 | 0, 0, 0, 0 209 | END 210 | 211 | IDD_PROFILEEDITOR AFX_DIALOG_LAYOUT 212 | BEGIN 213 | 0 214 | END 215 | 216 | #endif // English (United Kingdom) resources 217 | ///////////////////////////////////////////////////////////////////////////// 218 | 219 | 220 | 221 | #ifndef APSTUDIO_INVOKED 222 | ///////////////////////////////////////////////////////////////////////////// 223 | // 224 | // Generated from the TEXTINCLUDE 3 resource. 225 | // 226 | 227 | 228 | ///////////////////////////////////////////////////////////////////////////// 229 | #endif // not APSTUDIO_INVOKED 230 | 231 | -------------------------------------------------------------------------------- /windows/bridge00.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/bridge00.bmp -------------------------------------------------------------------------------- /windows/bridge01.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/bridge01.bmp -------------------------------------------------------------------------------- /windows/bridge02.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/bridge02.bmp -------------------------------------------------------------------------------- /windows/bridgeProfileEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* Bridge Profile Editor (Windows) 3 | * 4 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 5 | * https://amiga.robsmithdev.co.uk 6 | * 7 | * This file is multi-licensed under the terms of the Mozilla Public 8 | * License Version 2.0 as published by Mozilla Corporation and the 9 | * GNU General Public License, version 2 or later, as published by the 10 | * Free Software Foundation. 11 | * 12 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 13 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 14 | * 15 | * This file, along with currently active and supported interfaces 16 | * are maintained from by GitHub repo at 17 | * https://github.com/RobSmithDev/FloppyDriveBridge 18 | */ 19 | 20 | #include 21 | #include 22 | #include "../floppybridge/SerialIO.h" 23 | 24 | class BridgeConfig; 25 | 26 | class BridgeProfileEditor { 27 | private: 28 | HINSTANCE m_hInstance; 29 | HWND m_hwndParent; 30 | BridgeConfig* m_profile; 31 | std::vector& m_bridgeLogos; 32 | HCURSOR m_busyCursor, m_handPoint; 33 | HWND m_dialogBox; 34 | HDC m_hdcTemp; 35 | HBRUSH m_hNormalBackground; 36 | HBRUSH m_hSelectedBackground; 37 | DWORD m_hNormalBackgroundColor, m_hSelectedBackgroundColor; 38 | DWORD m_hNormalTextColor, m_hSelectedTextColor; 39 | HFONT m_boldFont; 40 | bool m_notDetected; 41 | 42 | std::vector m_portList; 43 | 44 | // Init dialog 45 | void handleInitDialog(HWND hwnd); 46 | 47 | void handleDrawListBox(PDRAWITEMSTRUCT item); 48 | 49 | void onDriverSelected(); 50 | 51 | void handleURLClicked(); 52 | 53 | void handleNameChange(); 54 | 55 | void saveToProfile(); 56 | 57 | public: 58 | BridgeProfileEditor(HINSTANCE hInstance, HWND hwndParent, std::vector& bridgeLogos, BridgeConfig* profile); 59 | ~BridgeProfileEditor(); 60 | 61 | // Run the modal code, returns TRUE if OK was pressed. 62 | bool doModal(); 63 | 64 | // Dialog window message handler 65 | INT_PTR handleDialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); 66 | }; -------------------------------------------------------------------------------- /windows/bridgeProfileListEditor.cpp: -------------------------------------------------------------------------------- 1 | /* Bridge Profile List Editor (Windows) 2 | * 3 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 4 | * https://amiga.robsmithdev.co.uk 5 | * 6 | * This file is multi-licensed under the terms of the Mozilla Public 7 | * License Version 2.0 as published by Mozilla Corporation and the 8 | * GNU General Public License, version 2 or later, as published by the 9 | * Free Software Foundation. 10 | * 11 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 12 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 13 | * 14 | * This file, along with currently active and supported interfaces 15 | * are maintained from by GitHub repo at 16 | * https://github.com/RobSmithDev/FloppyDriveBridge 17 | */ 18 | 19 | 20 | #include "bridgeProfileListEditor.h" 21 | #include "bridgeProfileEditor.h" 22 | #include "resource.h" 23 | #include "FloppyBridge.h" 24 | 25 | #pragma comment(lib,"Msimg32.lib") 26 | 27 | // Returns a pointer to information about the project 28 | void handleAbout(bool checkForUpdates, FloppyBridge::BridgeAbout** output); 29 | bool handleGetDriverInfo(unsigned int driverIndex, FloppyDiskBridge::BridgeDriver** driverInformation); 30 | 31 | 32 | INT_PTR CALLBACK dialogCallback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 33 | if (msg == WM_INITDIALOG) SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)lParam); 34 | BridgeProfileListEditor* dlg = (BridgeProfileListEditor*)GetWindowLongPtr(hwnd, GWLP_USERDATA); 35 | if (dlg) return dlg->handleDialogProc(hwnd, msg, wParam, lParam); else return FALSE; 36 | } 37 | 38 | // Create a new profile 39 | void BridgeProfileListEditor::handleCreateProfile() { 40 | BridgeConfig* profile = new BridgeConfig(); 41 | 42 | // Pinch some settings from the current one 43 | HWND hwndList = GetDlgItem(m_dialogBox, IDC_LIST1); 44 | int lbItem = (int)SendMessage(hwndList, LB_GETCURSEL, 0, 0); 45 | if (lbItem >= 0) { 46 | unsigned int profileID = (unsigned int)SendMessage(hwndList, LB_GETITEMDATA, lbItem, 0); 47 | auto f = m_profileList.find(profileID); 48 | if (f != m_profileList.end()) { 49 | profile->bridgeIndex = f->second->bridgeIndex; 50 | } 51 | } 52 | else { 53 | if (m_profileList.size()) 54 | profile->bridgeIndex = m_profileList.begin()->second->bridgeIndex; 55 | } 56 | 57 | 58 | BridgeProfileEditor editor(m_hInstance, m_dialogBox, m_bridgeLogos, profile); 59 | if (editor.doModal()) { 60 | HWND hwndList = GetDlgItem(m_dialogBox, IDC_LIST1); 61 | 62 | LRESULT pos = SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)profile->profileName); 63 | 64 | // Work out a new profile ID 65 | unsigned int profileID = 1; 66 | while (m_profileList.find(profileID) != m_profileList.end()) profileID++; 67 | m_profileList.insert(std::make_pair(profileID, profile)); 68 | 69 | SendMessage(hwndList, LB_SETITEMDATA, (WPARAM)pos, (LPARAM)profileID); 70 | SendMessage(hwndList, LB_SETCURSEL, (WPARAM)pos, 0); 71 | } 72 | else delete profile; 73 | } 74 | 75 | void BridgeProfileListEditor::handleEditSelectedProfile() { 76 | HWND hwndList = GetDlgItem(m_dialogBox, IDC_LIST1); 77 | int lbItem = (int)SendMessage(hwndList, LB_GETCURSEL, 0, 0); 78 | if (lbItem >= 0) { 79 | unsigned int profileID = (unsigned int)SendMessage(hwndList, LB_GETITEMDATA, lbItem, 0); 80 | auto f = m_profileList.find(profileID); 81 | if (f != m_profileList.end()) { 82 | BridgeProfileEditor editor(m_hInstance, m_dialogBox, m_bridgeLogos, f->second); 83 | // If it updates, trigger invaldate on the listbox 84 | if (editor.doModal()) redrawSelectedItem(); 85 | } 86 | } 87 | } 88 | 89 | void BridgeProfileListEditor::handleDeleteSelectedProfile() { 90 | HWND hwndList = GetDlgItem(m_dialogBox, IDC_LIST1); 91 | int lbItem = (int)SendMessage(hwndList, LB_GETCURSEL, 0, 0); 92 | if (lbItem >= 0) { 93 | unsigned int profileID = (unsigned int)SendMessage(hwndList, LB_GETITEMDATA, lbItem, 0); 94 | auto f = m_profileList.find(profileID); 95 | if (f == m_profileList.end()) { 96 | // ERROR, not found. just delete it. Should never happen 97 | SendMessage(hwndList, LB_DELETESTRING, lbItem, 0); 98 | } 99 | else { 100 | char buffer[200]; 101 | sprintf_s(buffer, "Are you sure you want to delete the profile \"%s\"?", f->second->profileName); 102 | if (MessageBoxA(m_dialogBox, buffer, "Delete Profile", MB_YESNO | MB_ICONQUESTION) == IDYES) { 103 | SendMessage(hwndList, LB_DELETESTRING, lbItem, 0); 104 | delete f->second; 105 | m_profileList.erase(f); 106 | } 107 | } 108 | } 109 | } 110 | 111 | void BridgeProfileListEditor::handleURLClicked(bool isPatreon) { 112 | FloppyBridge::BridgeAbout* about; 113 | 114 | SetCursor(m_busyCursor); 115 | handleAbout(false, &about); 116 | ShellExecuteA(m_dialogBox, "OPEN", isPatreon ? "https://Ko-fi.com/robsmithdev" : about->url, NULL, NULL, SW_SHOW); 117 | } 118 | 119 | void BridgeProfileListEditor::handleProfileListMessages(WPARAM wParam, LPARAM lParam) { 120 | switch (HIWORD(wParam)) 121 | { 122 | case LBN_SELCHANGE: 123 | { 124 | HWND hwndList = GetDlgItem(m_dialogBox, IDC_LIST1); 125 | int lbItem = (int)SendMessage(hwndList, LB_GETCURSEL, 0, 0); 126 | EnableWindow(GetDlgItem(m_dialogBox, IDEDIT), lbItem >= 0); 127 | EnableWindow(GetDlgItem(m_dialogBox, IDDELETE), lbItem >= 0); 128 | } 129 | break; 130 | 131 | case LBN_DBLCLK: 132 | PostMessage(m_dialogBox, WM_COMMAND, IDEDIT, (LPARAM)GetDlgItem(m_dialogBox, IDEDIT)); 133 | break; 134 | 135 | case LBN_SETFOCUS: 136 | redrawSelectedItem(); 137 | break; 138 | case LBN_KILLFOCUS: 139 | redrawSelectedItem(); 140 | break; 141 | } 142 | } 143 | 144 | void BridgeProfileListEditor::redrawSelectedItem() { 145 | HWND hwndList = GetDlgItem(m_dialogBox, IDC_LIST1); 146 | int lbItem = (int)SendMessage(hwndList, LB_GETCURSEL, 0, 0); 147 | if (lbItem >= 0) { 148 | RECT r; 149 | if (SendMessage(hwndList, LB_GETITEMRECT, lbItem, (LPARAM)&r) != LB_ERR) InvalidateRect(hwndList, &r, 0); 150 | } 151 | } 152 | 153 | void BridgeProfileListEditor::handleDrawListBox(PDRAWITEMSTRUCT item) { 154 | if (!item) return; 155 | 156 | auto f = m_profileList.find((unsigned int)item->itemData); 157 | if (f == m_profileList.end()) return; 158 | 159 | if (item->itemState & ODS_SELECTED) { 160 | SetBkColor(item->hDC, m_hSelectedBackgroundColor); 161 | SetTextColor(item->hDC, m_hSelectedTextColor); 162 | FillRect(item->hDC, &item->rcItem, m_hSelectedBackground); 163 | } 164 | else { 165 | SetBkColor(item->hDC, m_hNormalBackgroundColor); 166 | SetTextColor(item->hDC, m_hNormalTextColor); 167 | FillRect(item->hDC, &item->rcItem, m_hNormalBackground); 168 | } 169 | 170 | HGDIOBJ oldBmp = SelectObject(m_hdcTemp, m_bridgeLogos[f->second->bridgeIndex]); 171 | 172 | int bitmapSize = 66; 173 | 174 | if (item->rcItem.bottom - item->rcItem.top < 66) { 175 | bitmapSize = item->rcItem.bottom - item->rcItem.top; 176 | } 177 | SetStretchBltMode(item->hDC, HALFTONE); 178 | SetBrushOrgEx(item->hDC, 0, 0, NULL); 179 | TransparentBlt(item->hDC, item->rcItem.left + 1, item->rcItem.top + 1, bitmapSize - 2, bitmapSize - 2, m_hdcTemp, 0, 0, 64, 64, 0x00FF00); 180 | SetStretchBltMode(item->hDC, COLORONCOLOR); 181 | SelectObject(m_hdcTemp, oldBmp); 182 | 183 | RECT rec = item->rcItem; 184 | rec.left += bitmapSize+4; rec.top += 4; 185 | FloppyDiskBridge::BridgeDriver* info; 186 | handleGetDriverInfo(f->second->bridgeIndex, &info); 187 | 188 | // MAke a bold version fi it doesnt exist 189 | if (!m_boldFont) { 190 | LOGFONT logFont; 191 | GetObject(GetCurrentObject(item->hDC, OBJ_FONT), sizeof(logFont), (LPVOID)&logFont); 192 | logFont.lfWeight = FW_BOLD; 193 | m_boldFont = CreateFontIndirect(&logFont); 194 | } 195 | HGDIOBJ oldFont = SelectObject(item->hDC, m_boldFont); 196 | rec.top += DrawTextA(item->hDC, f->second->profileName, (int)strlen(f->second->profileName), &rec, DT_LEFT | DT_NOPREFIX) + 5; 197 | SelectObject(item->hDC, oldFont); 198 | 199 | char buffer[128]; 200 | sprintf_s(buffer, "%s (%s)", info->name, info->manufacturer); 201 | rec.top += DrawTextA(item->hDC, buffer, (int)strlen(buffer), &rec, DT_LEFT | DT_NOPREFIX); 202 | sprintf_s(buffer, "URL: %s", info->url); 203 | rec.top += DrawTextA(item->hDC, buffer, (int)strlen(buffer), &rec, DT_LEFT | DT_NOPREFIX); 204 | sprintf_s(buffer, "Driver by %s", info->driverAuthor); 205 | rec.top += DrawTextA(item->hDC, buffer, (int)strlen(buffer), &rec, DT_LEFT | DT_NOPREFIX); 206 | 207 | if (((item->itemState & (ODS_FOCUS|ODS_SELECTED))== (ODS_FOCUS | ODS_SELECTED))) { 208 | rec = item->rcItem; 209 | rec.left += bitmapSize; 210 | DrawFocusRect(item->hDC, &rec); 211 | } 212 | } 213 | 214 | // Dialog window message handler 215 | INT_PTR BridgeProfileListEditor::handleDialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 216 | switch (msg) { 217 | case WM_INITDIALOG: 218 | handleInitDialog(hwnd); 219 | return TRUE; 220 | 221 | case WM_SETCURSOR: 222 | if (((HWND)wParam == GetDlgItem(m_dialogBox, IDC_URL_MAIN)) || ((HWND)wParam == GetDlgItem(m_dialogBox, IDC_DONATE)) || ((HWND)wParam == GetDlgItem(m_dialogBox, IDC_UPDATENOW))) { 223 | SetCursor(m_handPoint); 224 | SetWindowLongPtr(hwnd, DWLP_MSGRESULT, (LONG)TRUE); 225 | return TRUE; 226 | } 227 | break; 228 | 229 | case WM_CTLCOLORSTATIC: 230 | if (((HWND)lParam == GetDlgItem(m_dialogBox, IDC_URL_MAIN)) || ((HWND)lParam == GetDlgItem(m_dialogBox, IDC_DONATE)) || ((HWND)lParam == GetDlgItem(m_dialogBox, IDC_UPDATENOW))) { 231 | SetTextColor((HDC)wParam, GetSysColor(COLOR_HOTLIGHT)); 232 | SetBkColor((HDC)wParam, GetSysColor(COLOR_3DFACE)); 233 | SetWindowLongPtr(hwnd, DWLP_MSGRESULT, (LONG_PTR)GetSysColorBrush(COLOR_3DFACE)); 234 | return (INT_PTR)GetSysColorBrush(COLOR_3DFACE); // Should be TRUE but doesnt work right unless its this!?! 235 | } 236 | break; 237 | 238 | case WM_COMMAND: 239 | switch (LOWORD(wParam)) { 240 | case IDC_CHECKUPDATES: 241 | doCheckForUpdates(); 242 | return TRUE; 243 | 244 | case IDCREATE: 245 | handleCreateProfile(); 246 | return TRUE; 247 | 248 | case IDEDIT: 249 | handleEditSelectedProfile(); 250 | return TRUE; 251 | 252 | case IDDELETE: 253 | handleDeleteSelectedProfile(); 254 | return TRUE; 255 | 256 | case IDC_URL_MAIN: 257 | case IDC_UPDATENOW: 258 | handleURLClicked(false); 259 | return TRUE; 260 | 261 | case IDC_AUTOCHECK: 262 | setShouldCheckForUpdates(SendMessage(GetDlgItem(hwnd, IDC_AUTOCHECK), BM_GETCHECK, 0,0)); 263 | return TRUE; 264 | 265 | case IDC_DONATE: 266 | handleURLClicked(true); 267 | return TRUE; 268 | 269 | case IDC_LIST1: 270 | handleProfileListMessages(wParam, lParam); 271 | return TRUE; 272 | 273 | case IDOK: 274 | EndDialog(hwnd, TRUE); 275 | return TRUE; 276 | 277 | case IDCANCEL: 278 | EndDialog(hwnd, FALSE); 279 | return TRUE; 280 | } 281 | break; // WM_COMMAND 282 | 283 | 284 | // Set listbox height 285 | case WM_MEASUREITEM: 286 | ((PMEASUREITEMSTRUCT)lParam)->itemHeight = 66; 287 | return TRUE; 288 | 289 | // Draw profile 290 | case WM_DRAWITEM: 291 | handleDrawListBox((PDRAWITEMSTRUCT)lParam); 292 | return TRUE; 293 | 294 | // Handle close 295 | case WM_SYSCOMMAND: 296 | switch (wParam) { 297 | case SC_CLOSE: 298 | EndDialog(hwnd, FALSE); 299 | return TRUE; 300 | } 301 | } 302 | 303 | return FALSE; 304 | } 305 | 306 | // Check for updates 307 | void BridgeProfileListEditor::doCheckForUpdates() { 308 | FloppyBridge::BridgeAbout* about; 309 | 310 | SetCursor(m_busyCursor); 311 | handleAbout(true, &about); 312 | if (about->isUpdateAvailable) { 313 | char buffer[128]; 314 | sprintf_s(buffer, "There is an update (V%i.%i) available.\r\n\r\nWould you like to download it?", about->updateMajorVersion, about->updateMinorVersion); 315 | if (MessageBoxA(m_dialogBox, buffer, "Update Available!", MB_ICONINFORMATION | MB_YESNO) == IDYES) { 316 | ShellExecuteA(m_dialogBox, "OPEN", about->url, NULL, NULL, SW_SHOW); 317 | } 318 | } 319 | else { 320 | MessageBoxA(m_dialogBox, "You are already using the latest version.", "No Update Available", MB_ICONINFORMATION | MB_OK); 321 | } 322 | } 323 | 324 | // Returns TRUE if we shoudl auto-check for updates 325 | bool BridgeProfileListEditor::shouldAutoCheckForUpdates() { 326 | WCHAR buf[256]; 327 | buf[0] = '\0'; 328 | LONG len = 10; 329 | RegQueryValueW(HKEY_CURRENT_USER, L"Software\\FloppyBridge\\AutoUpdateCheck", buf, &len); 330 | 331 | return _wtoi(buf) == 1; 332 | } 333 | 334 | // Should check for updates? 335 | void BridgeProfileListEditor::setShouldCheckForUpdates(bool shouldCheck) { 336 | char buf[256]; 337 | buf[0] = shouldCheck ? '1' : '0'; 338 | buf[1] = '\0'; 339 | LONG len = 1; 340 | RegSetValueA(HKEY_CURRENT_USER, "Software\\FloppyBridge\\AutoUpdateCheck", REG_SZ, buf, (DWORD)strlen(buf)); 341 | } 342 | 343 | 344 | 345 | // Init dialog 346 | void BridgeProfileListEditor::handleInitDialog(HWND hwnd) { 347 | m_dialogBox = hwnd; 348 | 349 | FloppyBridge::BridgeAbout* about; 350 | handleAbout(false, &about); 351 | char display[256]; 352 | 353 | HWND w = GetDlgItem(hwnd, IDC_PROFILELIST_TITLE); 354 | sprintf_s(display, "%s V%i.%i %s", about->about, about->majorVersion, about->minorVersion, about->isBeta ? "beta" : ""); 355 | SetWindowTextA(w, display); 356 | 357 | w = GetDlgItem(hwnd, IDC_URL_MAIN); 358 | sprintf_s(display, "%s", about->url); 359 | SetWindowTextA(w, display); 360 | 361 | // Populate the list 362 | w = GetDlgItem(hwnd, IDC_LIST1); 363 | for (auto& f : m_profileList) { 364 | LRESULT pos = SendMessage(w, LB_ADDSTRING, 0, (LPARAM)f.second->profileName); 365 | SendMessage(w, LB_SETITEMDATA, (WPARAM)pos, (LPARAM)f.first); 366 | } 367 | 368 | EnableWindow(GetDlgItem(m_dialogBox, IDEDIT), FALSE); 369 | EnableWindow(GetDlgItem(m_dialogBox, IDDELETE), FALSE); 370 | 371 | // Auto-update check checkbox 372 | w = GetDlgItem(hwnd, IDC_AUTOCHECK); 373 | SendMessage(w, BM_SETCHECK, shouldAutoCheckForUpdates() ? BST_CHECKED : BST_UNCHECKED, 0); 374 | 375 | if (shouldAutoCheckForUpdates()) { 376 | SetCursor(m_busyCursor); 377 | handleAbout(true, &about); 378 | if (about->isUpdateAvailable) { 379 | char buffer[128]; 380 | sprintf_s(buffer, "V%i.%i Update Available", about->updateMajorVersion, about->updateMinorVersion); 381 | SetWindowTextA(GetDlgItem(hwnd, IDC_UPDATENOW), buffer); 382 | ShowWindow(GetDlgItem(hwnd, IDC_UPDATENOW), SW_SHOW); 383 | ShowWindow(GetDlgItem(hwnd, IDC_CHECKUPDATES), SW_HIDE); 384 | } 385 | } 386 | } 387 | 388 | 389 | BridgeProfileListEditor::BridgeProfileListEditor(HINSTANCE hInstance, HWND hwndParent, std::vector& bridgeLogos, std::unordered_map& profileList) : 390 | m_profileList(profileList), m_hInstance(hInstance), m_hwndParent(hwndParent), m_bridgeLogos(bridgeLogos), m_boldFont(0) { 391 | m_busyCursor = (HCURSOR)LoadCursor(NULL, IDC_WAIT); 392 | m_handPoint = (HCURSOR)LoadCursor(NULL, IDC_HAND); 393 | 394 | m_hdcTemp = CreateCompatibleDC(0); 395 | 396 | m_hNormalBackgroundColor = GetSysColor(COLOR_WINDOW); 397 | m_hNormalBackground = CreateSolidBrush(m_hNormalBackgroundColor); 398 | m_hSelectedBackgroundColor = GetSysColor(COLOR_HIGHLIGHT); 399 | m_hSelectedBackground = CreateSolidBrush(m_hSelectedBackgroundColor); 400 | 401 | m_hNormalTextColor = GetSysColor(COLOR_WINDOWTEXT); 402 | m_hSelectedTextColor = GetSysColor(COLOR_HIGHLIGHTTEXT); 403 | } 404 | 405 | BridgeProfileListEditor::~BridgeProfileListEditor() { 406 | DeleteDC(m_hdcTemp); 407 | DestroyCursor(m_busyCursor); 408 | DestroyCursor(m_handPoint); 409 | DeleteObject(m_hNormalBackground); 410 | DeleteObject(m_hSelectedBackground); 411 | if (m_boldFont) DeleteObject(m_boldFont); 412 | } 413 | 414 | // Run the modal code, returns TRUE if OK was pressed. 415 | bool BridgeProfileListEditor::doModal() { 416 | return DialogBoxParam(m_hInstance, MAKEINTRESOURCE(IDD_PROFILELISTEDITOR), m_hwndParent, dialogCallback, (LPARAM)this); 417 | } 418 | 419 | -------------------------------------------------------------------------------- /windows/bridgeProfileListEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* Bridge Profile List Editor (Windows) 3 | * 4 | * Copyright (C) 2021-2024 Robert Smith (@RobSmithDev) 5 | * https://amiga.robsmithdev.co.uk 6 | * 7 | * This file is multi-licensed under the terms of the Mozilla Public 8 | * License Version 2.0 as published by Mozilla Corporation and the 9 | * GNU General Public License, version 2 or later, as published by the 10 | * Free Software Foundation. 11 | * 12 | * MPL2: https://www.mozilla.org/en-US/MPL/2.0/ 13 | * GPL2: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 14 | * 15 | * This file, along with currently active and supported interfaces 16 | * are maintained from by GitHub repo at 17 | * https://github.com/RobSmithDev/FloppyDriveBridge 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | class BridgeConfig; 25 | 26 | class BridgeProfileListEditor { 27 | private: 28 | HINSTANCE m_hInstance; 29 | HWND m_hwndParent; 30 | std::unordered_map& m_profileList; 31 | std::vector& m_bridgeLogos; 32 | HCURSOR m_busyCursor, m_handPoint; 33 | HWND m_dialogBox = 0; 34 | HDC m_hdcTemp; 35 | HBRUSH m_hNormalBackground; 36 | HBRUSH m_hSelectedBackground; 37 | DWORD m_hNormalBackgroundColor, m_hSelectedBackgroundColor; 38 | DWORD m_hNormalTextColor, m_hSelectedTextColor; 39 | HFONT m_boldFont; 40 | 41 | 42 | // Init dialog 43 | void handleInitDialog(HWND hwnd); 44 | 45 | // Perform update check 46 | void doCheckForUpdates(); 47 | 48 | // Create a new profile 49 | void handleCreateProfile(); 50 | 51 | // Edit selected profile 52 | void handleEditSelectedProfile(); 53 | 54 | // Delete selected profile 55 | void handleDeleteSelectedProfile(); 56 | 57 | // URL clicked 58 | void handleURLClicked(bool isPatreon); 59 | 60 | // Listbox notifications 61 | void handleProfileListMessages(WPARAM wParam, LPARAM lParam); 62 | 63 | // OwnerDraw for listbox 64 | void handleDrawListBox(PDRAWITEMSTRUCT item); 65 | 66 | // Trigger redrawing the selected item 67 | void redrawSelectedItem(); 68 | 69 | // Should check for updates? 70 | void setShouldCheckForUpdates(bool shouldCheck); 71 | public: 72 | BridgeProfileListEditor(HINSTANCE hInstance, HWND hwndParent, std::vector& m_bridgeLogos, std::unordered_map& profileList); 73 | ~BridgeProfileListEditor(); 74 | 75 | // Run the modal code, returns TRUE if OK was pressed. 76 | bool doModal(); 77 | 78 | // Dialog window message handler 79 | INT_PTR handleDialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); 80 | 81 | // Returns TRUE if we shoudl auto-check for updates 82 | static bool shouldAutoCheckForUpdates(); 83 | }; -------------------------------------------------------------------------------- /windows/cpp.hint: -------------------------------------------------------------------------------- 1 | #define FLOPPYBRIDGE_API __declspec(dllexport) 2 | #define FLOPPYBRIDGE_API __declspec(dllimport) 3 | -------------------------------------------------------------------------------- /windows/donate.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/donate.bmp -------------------------------------------------------------------------------- /windows/patreon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/patreon.bmp -------------------------------------------------------------------------------- /windows/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Resource.rc 4 | // 5 | #define IDB_BRIDGELOGO0 101 6 | #define IDB_BRIDGELOGO1 102 7 | #define IDB_BRIDGELOGO2 103 8 | #define IDD_PROFILEEDITOR 104 9 | #define IDD_PROFILELISTEDITOR 109 10 | #define IDB_DONATE 110 11 | #define IDB_SMALLLOGO0 111 12 | #define IDB_SMALLLOGO1 112 13 | #define IDB_SMALLLOGO3 113 14 | #define IDB_SMALLLOGO2 113 15 | #define IDC_LIST1 1001 16 | #define IDCREATE 1004 17 | #define IDC_PROFILENAME 1004 18 | #define IDEDIT 1005 19 | #define IDC_DRIVER 1005 20 | #define IDDELETE 1006 21 | #define IDC_MODE 1006 22 | #define IDCREATE2 1007 23 | #define IDC_DISKTYPE 1007 24 | #define IDC_SMART 1008 25 | #define IDC_CABLE 1009 26 | #define IDC_COMPORT 1010 27 | #define IDC_AUTOCACHE 1011 28 | #define IDC_URL 1012 29 | #define IDC_AUTODETECT 1013 30 | #define IDC_URL_MAIN 1014 31 | #define IDC_AUTOCHECK 1015 32 | #define IDC_URL2 1015 33 | #define IDC_UPDATENOW 1016 34 | #define IDC_DONATE 1017 35 | #define IDC_PROFILELIST_TITLE 1200 36 | #define IDC_CHECKUPDATES 1202 37 | 38 | // Next default values for new objects 39 | // 40 | #ifdef APSTUDIO_INVOKED 41 | #ifndef APSTUDIO_READONLY_SYMBOLS 42 | #define _APS_NEXT_RESOURCE_VALUE 114 43 | #define _APS_NEXT_COMMAND_VALUE 40001 44 | #define _APS_NEXT_CONTROL_VALUE 1018 45 | #define _APS_NEXT_SYMED_VALUE 101 46 | #endif 47 | #endif 48 | -------------------------------------------------------------------------------- /windows/sbridge00.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/sbridge00.bmp -------------------------------------------------------------------------------- /windows/sbridge01.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/sbridge01.bmp -------------------------------------------------------------------------------- /windows/sbridge02.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobSmithDev/FloppyDriveBridge/710fa15cb200303f8c4bde1c931786175f301a68/windows/sbridge02.bmp --------------------------------------------------------------------------------