2 // Apple 2 floppy disk support
5 // (c) 2005-2019 Underground Software
7 // JLH = James Hammons <jlhamm@acm.org>
10 // --- ---------- -----------------------------------------------------------
11 // JLH 12/03/2005 Created this file
12 // JLH 12/15/2005 Fixed nybblization functions to work properly
13 // JLH 12/27/2005 Added blank disk creation, fixed saving to work properly
16 #include "floppydrive.h"
23 #include "firmware/firmware.h"
26 #include "video.h" // For message spawning... Though there's probably a
27 // better approach than this!
29 // For testing 13-sector disk FW
34 enum { IO_MODE_READ, IO_MODE_WRITE };
36 // Misc. arrays (read only) that are needed
38 static const uint8_t bitMask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
40 static const uint8_t sequencerROM[256] = {
41 0x18, 0x18, 0x18, 0x18, 0x0A, 0x0A, 0x0A, 0x0A,
42 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // $00
43 0x2D, 0x38, 0x2D, 0x38, 0x0A, 0x0A, 0x0A, 0x0A,
44 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, // $10
45 0x38, 0x28, 0xD8, 0x08, 0x0A, 0x0A, 0x0A, 0x0A,
46 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B, // $20
47 0x48, 0x48, 0xD8, 0x48, 0x0A, 0x0A, 0x0A, 0x0A,
48 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, // $30
49 0x58, 0x58, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
50 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, // $40
51 0x68, 0x68, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
52 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, // $50
53 0x78, 0x78, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
54 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, // $60
55 0x88, 0x88, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
56 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, // $70
57 0x98, 0x98, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
58 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // $80
59 0x29, 0xA8, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
60 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, // $90
61 0xBD, 0xB8, 0xCD, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
62 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, // $A0
63 0x59, 0xC8, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
64 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, // $B0
65 0xD9, 0xA0, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
66 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, // $C0
67 0x08, 0xE8, 0xD8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A,
68 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, // $D0
69 0xFD, 0xF8, 0xFD, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A,
70 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // $E0
71 0x4D, 0xE0, 0xDD, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A,
72 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08 // $F0
75 #if 1 // From UTA2E, but need to double check...
76 static const uint8_t sequencerROM13[256] = {
78 0x18, 0x08, 0xD8, 0x18, 0x0A, 0x0A, 0x0A, 0x0A,
79 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // $00
80 0x28, 0x28, 0xD8, 0x28, 0x0A, 0x0A, 0x0A, 0x0A,
81 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, // $10
82 0x38, 0x38, 0xD8, 0x38, 0x0A, 0x0A, 0x0A, 0x0A,
83 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B, // $20
84 0x48, 0x48, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
85 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, // $30
86 0x58, 0x58, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
87 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, // $40
88 0x68, 0x68, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
89 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, // $50
90 0x78, 0x78, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
91 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, // $60
92 0x88, 0x88, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
93 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, // $70
94 0x98, 0x98, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
95 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // $80
96 0x09, 0xA8, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
97 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, // $90
98 0xBD, 0xB8, 0xCD, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
99 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, // $A0
100 0x39, 0xC8, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
101 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, // $B0
102 0xD9, 0xA0, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
103 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, // $C0
104 0x0D, 0xE8, 0x1D, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A,
105 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, // $D0
106 0xFD, 0xF8, 0xFD, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A,
107 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // $E0
108 0x4D, 0xE0, 0xDD, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A,
109 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08 // $F0
113 static const uint8_t sequencerROM13[256] = {
114 0x18, 0x08, 0xD8, 0x18, 0x0A, 0x0A, 0x0A, 0x0A,
115 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
116 0x98, 0x98, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
117 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
118 0x38, 0x38, 0xD8, 0x38, 0x0A, 0x0A, 0x0A, 0x0A,
119 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B,
120 0xBD, 0xB8, 0xCD, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
121 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB,
122 0x58, 0x58, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
123 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
124 0xD9, 0xA0, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
125 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8,
126 0x78, 0x78, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
127 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
128 0xFD, 0xF8, 0xFD, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A,
129 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
130 0x28, 0x28, 0xD8, 0x28, 0x0A, 0x0A, 0x0A, 0x0A,
131 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
132 0x09, 0xA8, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
133 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8,
134 0x48, 0x48, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
135 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
136 0x39, 0xC8, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
137 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8,
138 0x68, 0x68, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
139 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68,
140 0x0D, 0xE8, 0x1D, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A,
141 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8,
142 0x88, 0x88, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A,
143 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88,
144 0x4D, 0xE0, 0xDD, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A,
145 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08
149 static const uint8_t sequencerROM13[256] = {
150 0x88, 0x08, 0xB8, 0x88, 0x0A, 0x0A, 0x0A, 0x0A,
151 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, // $00
152 0x98, 0x98, 0xB8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
153 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // $10
154 0xC8, 0xC8, 0xB8, 0xC8, 0x0A, 0x0A, 0x0A, 0x0A,
155 0xC9, 0xC9, 0xC9, 0xC9, 0xCB, 0xCB, 0xCB, 0xCB, // $20
156 0xDD, 0xD8, 0x3D, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
157 0xD9, 0xD9, 0xD9, 0xD9, 0xDB, 0xDB, 0xDB, 0xDB, // $30
158 0xA8, 0xA8, 0xB8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
159 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, // $40
160 0xB9, 0x50, 0xB9, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
161 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, // $50
162 0xE8, 0xE8, 0xB8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
163 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, // $60
164 0xFD, 0xF8, 0xFD, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A,
165 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // $70
166 0x48, 0x48, 0xB8, 0x48, 0x0A, 0x0A, 0x0A, 0x0A,
167 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, // $80
168 0x09, 0x58, 0xB8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
169 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, // $90
170 0x28, 0x28, 0xB8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
171 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, // $A0
172 0xC9, 0x38, 0xB9, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
173 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, // $B0
174 0x68, 0x68, 0xB8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
175 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, // $C0
176 0x0D, 0x78, 0x8D, 0x78, 0x0A, 0x0A, 0x0A, 0x0A,
177 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, // $D0
178 0x18, 0x18, 0xB8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A,
179 0x08, 0x18, 0x08, 0x18, 0x08, 0x18, 0x08, 0x18, // $E0
180 0x2D, 0x70, 0xBD, 0x70, 0x0A, 0x0A, 0x0A, 0x0A,
181 0x18, 0x08, 0x18, 0x08, 0x18, 0x08, 0x18, 0x08 // $F0
184 static char nameBuf[MAX_PATH];
186 // Static in-line functions, for clarity & speed, for swapping variables
187 static inline void Swap(uint8_t & a, uint8_t & b)
194 static inline void Swap(uint32_t & a, uint32_t & b)
201 static inline void Swap(bool & a, bool & b)
208 static inline void Swap(uint8_t * & a, uint8_t * & b)
216 // FloppyDrive class implementation...
218 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), ioHappened(false), diskImageReady(false)
220 phase[0] = phase[1] = 0;
221 headPos[0] = headPos[1] = 0;
222 trackLength[0] = trackLength[1] = 51200;
223 disk[0] = disk[1] = NULL;
224 diskSize[0] = diskSize[1] = 0;
225 diskType[0] = diskType[1] = DT_EMPTY;
226 imageDirty[0] = imageDirty[1] = false;
227 imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
230 FloppyDrive::~FloppyDrive()
239 bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
241 WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
245 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
249 // Zero out filename, in case it doesn't load
250 imageName[driveNum][0] = 0;
251 //prolly should load EjectImage() first, so we don't have to dick around with crap
252 uint8_t * buffer = ReadFile(filename, &diskSize[driveNum]);
256 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
261 free(disk[driveNum]);
263 disk[driveNum] = buffer;
265 diskImageReady = false;
266 DetectImageType(filename, driveNum);
267 strcpy(imageName[driveNum], filename);
268 diskImageReady = true;
270 WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum);
275 bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
277 // Various sanity checks...
280 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
284 if (diskType[driveNum] == DT_EMPTY)
286 WriteLog("FLOPPY: No image in drive #%u to save\n", driveNum);
290 if (!imageDirty[driveNum])
292 WriteLog("FLOPPY: No need to save unchanged image in drive #%u...\n", driveNum);
296 char * ext = strrchr(imageName[driveNum], '.');
298 if ((ext != NULL) && (diskType[driveNum] != DT_WOZ))
299 memcpy(ext, ".woz", 4);
301 return SaveWOZ(imageName[driveNum], (WOZ2 *)disk[driveNum], diskSize[driveNum]);
304 bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
306 strncpy(imageName[driveNum], filename, MAX_PATH);
307 // Ensure a NULL terminated string here, as strncpy() won't terminate the
308 // string if the source length is >= MAX_PATH
309 imageName[driveNum][MAX_PATH - 1] = 0;
310 return SaveImage(driveNum);
313 void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
315 if (disk[driveNum] != NULL)
316 free(disk[driveNum]);
318 disk[driveNum] = InitWOZ(&diskSize[driveNum]);
319 diskType[driveNum] = DT_WOZ;
320 strcpy(imageName[driveNum], "newblank.woz");
321 SpawnMessage("New blank image inserted in drive %u...", driveNum);
324 void FloppyDrive::SwapImages(void)
326 char imageNameTmp[MAX_PATH];
328 memcpy(imageNameTmp, imageName[0], MAX_PATH);
329 memcpy(imageName[0], imageName[1], MAX_PATH);
330 memcpy(imageName[1], imageNameTmp, MAX_PATH);
332 Swap(disk[0], disk[1]);
333 Swap(diskSize[0], diskSize[1]);
334 Swap(diskType[0], diskType[1]);
335 Swap(imageDirty[0], imageDirty[1]);
337 Swap(phase[0], phase[1]);
338 Swap(headPos[0], headPos[1]);
339 Swap(currentPos[0], currentPos[1]);
340 SpawnMessage("Drive 0: %s...", imageName[0]);
344 Need to add some type of error checking here, so we can report back on bad images, etc. (basically, it does by returning DFT_UNKNOWN, but we could do better)
346 void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum)
348 diskType[driveNum] = DFT_UNKNOWN;
350 uint8_t wozType = CheckWOZType(disk[driveNum], diskSize[driveNum]);
354 // Check WOZ integrity...
355 CheckWOZIntegrity(disk[driveNum], diskSize[driveNum]);
357 // If it's a WOZ type 1 file, upconvert it to type 2
361 uint8_t * buffer = UpconvertWOZ1ToWOZ2(disk[driveNum], diskSize[driveNum], &size);
363 free(disk[driveNum]);
364 disk[driveNum] = buffer;
365 diskSize[driveNum] = size;
366 WriteLog("FLOPPY: Upconverted WOZ type 1 to type 2...\n");
369 WriteLog("FLOPPY: OBT is %d\n", ((WOZ2 *)disk[driveNum])->optimalBitTmg);
370 diskType[driveNum] = DT_WOZ;
372 else if (diskSize[driveNum] == 143360)
374 const char * ext = strrchr(filename, '.');
379 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
381 if (strcasecmp(ext, ".po") == 0)
382 diskType[driveNum] = DT_PRODOS;
383 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
385 // We assume this, but check for a PRODOS fingerprint. Trust, but
387 diskType[driveNum] = DT_DOS33;
389 uint8_t fingerprint[4][4] = {
390 { 0x00, 0x00, 0x03, 0x00 }, // @ $400
391 { 0x02, 0x00, 0x04, 0x00 }, // @ $600
392 { 0x03, 0x00, 0x05, 0x00 }, // @ $800
393 { 0x04, 0x00, 0x00, 0x00 } // @ $A00
396 bool foundProdos = true;
398 for(uint32_t i=0; i<4; i++)
400 for(uint32_t j=0; j<4; j++)
402 if (disk[driveNum][0x400 + (i * 0x200) + j] != fingerprint[i][j])
411 diskType[driveNum] = DT_PRODOS;
414 // Actually, it just might matter WRT to nybblyzing/denybblyzing
415 // (and, it does... :-P)
416 WOZifyImage(driveNum);
418 else if (diskSize[driveNum] == 143488)
420 diskType[driveNum] = DT_DOS33_HDR;
421 WOZifyImage(driveNum);
424 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
425 // No, we don't nybblize anymore. But we should tell the user that the loading failed with a return value
427 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_DOS33 ?
428 "DOS 3.3" : (diskType[driveNum] == DT_DOS33_HDR ?
429 "DOS 3.3 (headered)" : (diskType[driveNum] == DT_PRODOS ? "ProDOS" : (diskType[driveNum] == DT_WOZ ? "WOZ" : "unknown")))));
433 // Write a bitstream (source left justified to bit 7) to destination buffer.
434 // Writes 'bits' number of bits to 'dest', starting at bit position 'dstPtr',
435 // updating 'dstPtr' for the caller.
437 void FloppyDrive::WriteBits(uint8_t * dest, const uint8_t * src, uint16_t bits, uint16_t * dstPtr)
439 for(uint16_t i=0; i<bits; i++)
441 // Get the destination location's bitmask
442 uint8_t dstMask = bitMask[*dstPtr % 8];
444 // Set the bit to one if there's a corresponding one in the source
445 // data, otherwise set it to zero
446 if (src[i / 8] & bitMask[i % 8])
447 dest[*dstPtr / 8] |= dstMask;
449 dest[*dstPtr / 8] &= ~dstMask;
455 void FloppyDrive::WOZifyImage(uint8_t driveNum)
457 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
458 // (not incl. 64 byte track marker)
459 // let's try 394 per sector... & see what happens
460 // let's go back to what we had, and see what happens :-)
461 // [still need to expand them back to what they were]
463 const uint8_t ff10[2] = { 0xFF, 0x00 };
464 uint8_t addressHeader[14] = {
465 0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00,
466 0x00, 0x00, 0x00, 0xDE, 0xAA, 0xEB };
467 const uint8_t sectorHeader[3] = { 0xD5, 0xAA, 0xAD };
468 const uint8_t footer[3] = { 0xDE, 0xAA, 0xEB };
469 const uint8_t diskbyte[0x40] = {
470 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
471 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
472 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
473 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
474 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
475 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
476 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
477 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
478 const uint8_t doSector[16] = {
479 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF };
480 const uint8_t poSector[16] = {
481 0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
484 // Save current image until we're done converting
485 uint8_t * tmpDisk = disk[driveNum];
487 // Set up track index...
488 disk[driveNum] = InitWOZ(&diskSize[driveNum]);
489 WOZ2 & woz = *((WOZ2 *)disk[driveNum]);
491 // Upconvert data from DSK & friends format to WOZ tracks :-)
492 for(uint8_t trk=0; trk<35; trk++)
494 uint16_t dstBitPtr = 0;
495 uint8_t * img = disk[driveNum] + (Uint16LE(woz.track[trk].startingBlock) * 512);
496 //printf("Converting track %u: startingBlock=%u, %u blocks, img=%X\n", trk, Uint16LE(woz.track[trk].startingBlock), Uint16LE(woz.track[trk].blockCount), img);
498 // Write self-sync header bytes (16, should it be 64? Dunno.)
499 for(int i=0; i<64; i++)
500 WriteBits(img, ff10, 10, &dstBitPtr);
502 // Write out the following sectors
503 for(uint8_t sector=0; sector<16; sector++)
505 // Set up the sector address header
506 addressHeader[5] = ((trk >> 1) & 0x55) | 0xAA;
507 addressHeader[6] = (trk & 0x55) | 0xAA;
508 addressHeader[7] = ((sector >> 1) & 0x55) | 0xAA;
509 addressHeader[8] = (sector & 0x55) | 0xAA;
510 addressHeader[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
511 addressHeader[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
513 WriteBits(img, addressHeader, 14 * 8, &dstBitPtr);
515 // Write 5 self-sync bytes for actual sector header
516 for(int i=0; i<5; i++)
517 WriteBits(img, ff10, 10, &dstBitPtr);
519 // Write sector header (D5 AA AD)
520 WriteBits(img, sectorHeader, 3 * 8, &dstBitPtr);
521 uint8_t * bytes = tmpDisk;
523 //Need to fix this so it writes the correct sector in the correct place *and* put the correct sector # into the header above as well. !!! FIX !!!
524 // Figure out location of sector data in disk image
525 if (diskType[driveNum] == DT_DOS33)
526 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
527 else if (diskType[driveNum] == DT_DOS33_HDR)
528 bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
529 else if (diskType[driveNum] == DT_PRODOS)
530 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
532 bytes += (sector * 256) + (trk * 256 * 16);
534 // Convert the 256 8-bit bytes into 342 6-bit bytes.
535 for(uint16_t i=0; i<0x56; i++)
537 tmpNib[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
538 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
539 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
540 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
541 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
542 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
545 tmpNib[0x54] &= 0x3F;
546 tmpNib[0x55] &= 0x3F;
547 memcpy(tmpNib + 0x56, bytes, 256);
549 // XOR the data block with itself, offset by one byte, creating a
550 // 343rd byte which is used as a checksum.
553 for(uint16_t i=342; i>0; i--)
554 tmpNib[i] = tmpNib[i] ^ tmpNib[i - 1];
556 // Using a lookup table, convert the 6-bit bytes into disk bytes.
557 for(uint16_t i=0; i<343; i++)
558 tmpNib[i] = diskbyte[tmpNib[i] >> 2];
560 WriteBits(img, tmpNib, 343 * 8, &dstBitPtr);
562 // Done with the nybblization, now add the epilogue...
563 WriteBits(img, footer, 3 * 8, &dstBitPtr);
565 // (Should the footer be 30 or 48? would be 45 FF10s here for 48)
566 for(int i=0; i<27; i++)
567 WriteBits(img, ff10, 10, &dstBitPtr);
570 // Set the proper bit/byte lengths in the WOZ for this track
571 woz.track[trk].bitCount = Uint16LE(dstBitPtr);
574 // Finally, free the non-WOZ image now that we're done converting
578 const char * FloppyDrive::ImageName(uint8_t driveNum/*= 0*/)
580 // Set up a zero-length string for return value
585 WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum);
589 // Now we attempt to strip out extraneous paths/extensions to get just the filename
590 const char * startOfFile = strrchr(imageName[driveNum], '/');
591 const char * startOfExt = strrchr(imageName[driveNum], '.');
593 // If there isn't a path, assume we're starting at the beginning
594 if (startOfFile == NULL)
595 startOfFile = &imageName[driveNum][0];
599 // If there isn't an extension, assume it's at the terminating NULL
600 if (startOfExt == NULL)
601 startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]);
603 // Now copy the filename (may copy nothing!)
606 for(const char * i=startOfFile; i<startOfExt; i++)
614 void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
617 if (diskType[driveNum] == DT_EMPTY)
620 // Probably want to save a dirty image... ;-)
621 if (SaveImage(driveNum))
622 WriteLog("FLOPPY: Ejected image file '%s' from drive %u...\n", imageName[driveNum], driveNum);
625 free(disk[driveNum]);
627 disk[driveNum] = NULL;
628 diskSize[driveNum] = 0;
629 diskType[driveNum] = DT_EMPTY;
630 imageDirty[driveNum] = false;
631 imageName[driveNum][0] = 0; // Zero out filenames
634 bool FloppyDrive::IsEmpty(uint8_t driveNum/*= 0*/)
638 WriteLog("FLOPPY: Attempted DriveIsEmtpy() for drive #%u!\n", driveNum);
642 return (diskType[driveNum] == DT_EMPTY ? true : false);
645 bool FloppyDrive::IsWriteProtected(uint8_t driveNum/*= 0*/)
649 WriteLog("FLOPPY: Attempted DiskIsWriteProtected() for drive #%u!\n", driveNum);
653 WOZ2 & woz = *((WOZ2 *)disk[driveNum]);
654 return (bool)woz.writeProtected;
657 void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/)
661 WriteLog("FLOPPY: Attempted set write protect for drive #%u!\n", driveNum);
665 WOZ2 & woz = *((WOZ2 *)disk[driveNum]);
666 woz.writeProtected = (uint8_t)state;
669 int FloppyDrive::DriveLightStatus(uint8_t driveNum/*= 0*/)
671 int retval = DLS_OFF;
673 if (activeDrive != driveNum)
677 retval = (ioMode == IO_MODE_READ ? DLS_READ : DLS_WRITE);
683 void FloppyDrive::SaveState(FILE * file)
685 // Internal state vars
686 fputc(motorOn, file);
687 fputc(activeDrive, file);
689 fputc(dataRegister, file);
690 fputc((ioHappened ? 1 : 0), file);
695 WriteLong(file, diskSize[0]);
696 WriteLong(file, diskType[0]);
697 fputc(phase[0], file);
698 fputc(headPos[0], file);
699 WriteLong(file, currentPos[0]);
700 fputc((imageDirty[0] ? 1 : 0), file);
701 fwrite(disk[0], 1, diskSize[0], file);
702 fwrite(imageName[0], 1, MAX_PATH, file);
710 WriteLong(file, diskSize[1]);
711 WriteLong(file, diskType[1]);
712 fputc(phase[1], file);
713 fputc(headPos[1], file);
714 WriteLong(file, currentPos[1]);
715 fputc((imageDirty[1] ? 1 : 0), file);
716 fwrite(disk[1], 1, diskSize[1], file);
717 fwrite(imageName[1], 1, MAX_PATH, file);
723 void FloppyDrive::LoadState(FILE * file)
725 // Eject images if they're loaded
729 // Read internal state variables
730 motorOn = fgetc(file);
731 activeDrive = fgetc(file);
732 ioMode = fgetc(file);
733 dataRegister = fgetc(file);
734 ioHappened = (fgetc(file) == 1 ? true : false);
736 diskSize[0] = ReadLong(file);
740 disk[0] = new uint8_t[diskSize[0]];
741 diskType[0] = (uint8_t)ReadLong(file);
742 phase[0] = fgetc(file);
743 headPos[0] = fgetc(file);
744 currentPos[0] = ReadLong(file);
745 imageDirty[0] = (fgetc(file) == 1 ? true : false);
746 fread(disk[0], 1, diskSize[0], file);
747 fread(imageName[0], 1, MAX_PATH, file);
750 diskSize[1] = ReadLong(file);
754 disk[1] = new uint8_t[diskSize[1]];
755 diskType[1] = (uint8_t)ReadLong(file);
756 phase[1] = fgetc(file);
757 headPos[1] = fgetc(file);
758 currentPos[1] = ReadLong(file);
759 imageDirty[1] = (fgetc(file) == 1 ? true : false);
760 fread(disk[1], 1, diskSize[1], file);
761 fread(imageName[1], 1, MAX_PATH, file);
765 uint32_t FloppyDrive::ReadLong(FILE * file)
769 for(int i=0; i<4; i++)
770 r = (r << 8) | fgetc(file);
775 void FloppyDrive::WriteLong(FILE * file, uint32_t l)
777 for(int i=0; i<4; i++)
779 fputc((l >> 24) & 0xFF, file);
784 // Memory mapped I/O functions + Logic State Sequencer
787 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35
788 tracks of 16 sectors of 256 bytes each, making 143,360 bytes in total. The PO
789 format is exactly the same size as DSK and is also organized as 35 sequential
790 tracks, but the sectors within each track are in a different sequence. The NIB
791 format is a nybblized format: a more direct representation of the disk's data
792 as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
793 6656 bytes each, for a total size of 232,960 bytes. Although this format is
794 much larger, it is also more versatile and can represent the older 13-sector
795 disks, many copy-protected disks, and other unusual encodings.
797 N.B.: Though the NIB format is *closer* to the representation of the disk's
798 data, it's not *quite* 100% as there can be zero bits lurking in the
799 interstices of the bytes written to the disk. There's room for another
800 format that takes this into account (possibly even take phase 1 & 3
801 tracks into account as well).
803 As luck would have it, not long after I wrote that, I found out that some enterprising people have created it already--WOZ format. Which is now supported by apple2. :-D
805 According to Beneath Apple DOS, DOS checks the data register to see if it changes when spinning up a drive: "A sufficient delay should be provided to allow the motor time to come up to speed. Shugart recommends one second, but DOS is able to reduce this delay by watching the read latch until data starts to change." Which means, we can simulate an empty/off drive by leaving the data register alone.
808 uint64_t stepperTime = 0;
809 bool seenReadSinceStep = false;
811 void FloppyDrive::ControlStepper(uint8_t addr)
817 The stepper motor has 4 phase solenoids (numbered 0-3) which corresponds to bits 1-2 of the address. Bit 0 tells the phase solenoid to either energize (1) or de-energize (0). By energizing the phase solenoids in ascending order, the stepper motor moves the head from a low numbered track to a higher numbered track; conversely, by energizing the solenoids in descending order, the stepper motor moves the head from a high numbered track to a lower one. Given that this is a mechanical device, it takes a certain amount of time for the drum in the stepper motor to move from place to place--though pretty much all software written for the Disk II takes this into account.
819 Tracks can apparently go from 0 to 79, though typically only 0 to 69 are usuable. Further, because of the limitations of the read/write head of the drive, not every track can be written to, so typically (about 99.99% of the time in my guesstimation) only every *other* track is written to (phases 0 and 2); some disks exist that have tracks written on phase 1 or 3, but these tend to be the exception rather than the rule.
821 Taking into account the head slew time: ATM nothing seems to look at it, though it could be problematic as how we emulate it is different from how it actually works; namely, the emulator zaps the head to a new track instantly when the write to the phase happens while in the real thing, obviously this takes a non-zero amount of time. As such, none of the states where more than one phase solenoid is active at a time can be written so that they come on instantaneously; it would be fairly easy to write things that work on the real thing that don't on the emulator because of this. But most software (pretty much everything that I've ever seen) is pretty well behaved and this isn't an issue.
823 If it ever *does* become a problem, doing the physical modeling of the head moving at a real velocity shouldn't be that difficult to do.
826 // This is an array of stub positions crossed with solenoid energize
827 // patterns. The numbers represent how many quarter tracks the head will
828 // move given its current position and the pattern of energized solenoids.
829 // N.B.: Patterns for 11 & 13 haven't been filled in as I'm not sure how
830 // the stub(s) would react to those patterns. :-/
831 int16_t step[16][8] = {
832 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [....]
833 { 0, -1, -2, 0, 0, 0, +2, +1 }, // [|...]
834 { +2, +1, 0, -1, -2, 0, 0, 0 }, // [.|..]
835 { +1, 0, -1, -2, -3, 0, +3, +2 }, // [||..]
836 { 0, 0, +2, +1, 0, -1, -2, 0 }, // [..|.]
837 { 0, -1, 0, +1, 0, -1, 0, +1 }, // [|.|.]
838 { +3, +2, +1, 0, -1, -2, -3, 0 }, // [.||.]
839 { +2, +1, 0, -1, -2, -3, 0, +3 }, // [|||.]
840 { -2, 0, 0, 0, +2, +1, 0, -1 }, // [...|]
841 { -1, -2, -3, 0, +3, +2, +1, 0 }, // [|..|]
842 { 0, +1, 0, -1, 0, +1, 0, -1 }, // [.|.|]
843 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [||.|] ???
844 { -3, 0, +3, +2, +1, 0, -1, -2 }, // [..||]
845 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [|.||] ???
846 { 0, +3, +2, +1, 0, -1, -2, -3 }, // [.|||]
847 { -1, +2, +1, 0, -1, -2, +1, 0 } // [||||]
851 if (diskType[activeDrive] == DT_EMPTY)
854 // Convert phase solenoid number into a bit from 1 through 8 [1, 2, 4, 8]:
855 uint8_t phaseBit = 1 << ((addr >> 1) & 0x03);
857 // Set the state of the phase solenoid accessed using the phase bit
859 phase[activeDrive] |= phaseBit;
861 phase[activeDrive] &= ~phaseBit;
863 int16_t oldHeadPos = headPos[activeDrive];
864 int16_t newStep = step[phase[activeDrive]][oldHeadPos & 0x07];
865 int16_t newHeadPos = (int16_t)headPos[activeDrive] + newStep;
866 WriteLog("\nFLOPPY: oldHeadPos=%u, newHeadPos=%i, newStep=%i\n", oldHeadPos, newHeadPos, newStep);
869 // N.B.: This is wrong for 3.5" disks
870 if ((newHeadPos >= 0) && (newHeadPos <= 140))
871 headPos[activeDrive] = (uint8_t)newHeadPos;
873 if (oldHeadPos != headPos[activeDrive])
875 WOZ2 & woz = *((WOZ2 *)disk[activeDrive]);
876 uint8_t newTIdx = woz.tmap[headPos[activeDrive]];
877 float newBitLen = (newTIdx == 0xFF
878 ? 51200.0f : Uint16LE(woz.track[newTIdx].bitCount));
880 uint8_t oldTIdx = woz.tmap[oldHeadPos];
881 float oldBitLen = (oldTIdx == 0xFF
882 ? 51200.0f : Uint16LE(woz.track[oldTIdx].bitCount));
883 WriteLog("FLOPPY: Current pos pre: %u, ", currentPos[activeDrive]);
884 currentPos[activeDrive] = (uint32_t)((float)currentPos[activeDrive] * (newBitLen / oldBitLen));
885 WriteLog("post: %u; newBitLen/old = %.1f/%.1f\n", currentPos[activeDrive], newBitLen, oldBitLen);
887 trackLength[activeDrive] = (uint16_t)newBitLen;
888 SpawnMessage("Stepping to track %u...", headPos[activeDrive] >> 2);
891 // only check the time since the phase was first set ON
894 stepperTime = mainCPU.clock;
895 seenReadSinceStep = false;
897 WriteLog("FLOPPY: Stepper phase %d set to %s [%c%c%c%c] (track=%2.2f) [%u]\n", (addr >> 1) & 0x03, (addr & 0x01 ? "ON " : "off"), (phase[activeDrive] & 0x08 ? '|' : '.'), (phase[activeDrive] & 0x04 ? '|' : '.'), (phase[activeDrive] & 0x02 ? '|' : '.'), (phase[activeDrive] & 0x01 ? '|' : '.'), (float)headPos[activeDrive] / 4.0f, mainCPU.clock & 0xFFFFFFFF);
900 void FloppyDrive::ControlMotor(uint8_t addr)
908 driveOffTimeout = 2000000;
910 WriteLog("FLOPPY: Turning drive motor %s\n", (motorOn ? "ON" : "off"));
913 void FloppyDrive::DriveEnable(uint8_t addr)
917 WriteLog("FLOPPY: Selecting drive #%hhd\n", addr + 1);
921 So for $C08C-F, we have two switches (Q6 & Q7) which combine to make four states ($C-D is off/on for Q6, $E-F is off/on for Q7).
923 So it forms a matrix like so:
926 +-----------------------------------------------------------------------
927 $C08C |Enable READ sequencing |Data reg SHL every 8th clock while writing
928 +----------------------------+------------------------------------------
929 $C08D |Check write prot./init write|Data reg LOAD every 8th clk while writing
931 Looks like reads from even addresses in $C080-F block transfer data from the sequencer to the MPU, does write from odd do the inverse (transfer from MPU to sequencer)? Looks like it.
935 void FloppyDrive::SetShiftLoadSwitch(uint8_t state)
941 void FloppyDrive::SetReadWriteSwitch(uint8_t state)
947 // MMIO: Reads from $C08x to $C0XX on even addresses
948 uint8_t FloppyDrive::DataRegister(void)
951 if (diskType[activeDrive] != DT_EMPTY)
953 WOZ2 & woz = *((WOZ2 *)disk[activeDrive]);
954 uint8_t tIdx = woz.tmap[headPos[activeDrive]];
955 uint32_t bitLen = (tIdx == 0xFF ? 51200
956 : Uint16LE(woz.track[tIdx].bitCount));
957 SpawnMessage("%u:Reading $%02X from track %u, sector %u...",
958 activeDrive, dataRegister, headPos[activeDrive] >> 2, (uint32_t)(((float)currentPos[activeDrive] / (float)bitLen) * 16.0f));
959 ioMode = IO_MODE_READ;
962 if ((seenReadSinceStep == false) && (slSwitch == false) && (rwSwitch == false) && ((iorAddr & 0x0F) == 0x0C))
964 seenReadSinceStep = true;
965 WriteLog("%u:Reading $%02X from track %2.2f, sector %u (delta since seek: %lu cycles) [%u]...\n",
966 activeDrive, dataRegister, (float)headPos[activeDrive] / 4.0f, (uint32_t)(((float)currentPos[activeDrive] / (float)bitLen) * 16.0f), mainCPU.clock - stepperTime, mainCPU.clock & 0xFFFFFFFF);
973 // MMIO: Writes from $C08x to $C0XX on odd addresses
974 void FloppyDrive::DataRegister(uint8_t data)
977 ioMode = IO_MODE_WRITE;
982 OFF switches ON switches
983 Switch Addr Func Addr Func
984 Q0 $C080 Phase 0 off $C081 Phase 0 on
985 Q1 $C082 Phase 1 off $C083 Phase 1 on
986 Q2 $C084 Phase 2 off $C085 Phase 2 on
987 Q3 $C086 Phase 3 off $C087 Phase 3 on
988 Q4 $C088 Drive off $C089 Drive on
989 Q5 $C08A Select Drive 1 $C08B Select Drive 2
990 Q6 $C08C Shift data register $C08D Load data register
991 Q7 $C08E Read $C08F Write
993 From "Beneath Apple ProDOS", description of combinations of $C0EC-EF
995 $C08C, $C08E: Enable read sequencing
996 $C08C, $C08F: Shift data register every four cycles while writing
997 $C08D, $C08E: Check write protect and initialize sequencer for writing
998 $C08D, $C08F: Load data register every four cycles while writing
1001 Sense Write Protect:
1003 LDX #SLOT Put slot number times 16 in X-register.
1005 LDA $C08E, X Sense write protect.
1006 BMI ERROR If high bit set, protected.
1011 PRODOS 8 MLI ERROR CODES
1014 $01: Bad system call number
1015 $04: Bad system call parameter count
1016 $25: Interrupt table full
1018 $28: No device connected
1019 $2B: Disk write protected
1021 $40: Invalid pathname
1022 $42: Maximum number of files open
1023 $43: Invalid reference number
1024 $44: Directory not found
1025 $45: Volume not found
1027 $47: Duplicate filename
1029 $49: Volume directory full
1030 $4A: Incompatible file format, also a ProDOS directory
1031 $4B: Unsupported storage_type
1032 $4C: End of file encountered
1033 $4D: Position out of range
1034 $4E: File access error, also file locked
1036 $51: Directory structure damaged
1037 $52: Not a ProDOS volume
1038 $53: Invalid system call parameter
1039 $55: Volume Control Block table full
1040 $56: Bad buffer address
1041 $57: Duplicate volume
1042 $5A: File structure damaged
1045 // N.B.: The WOZ documentation says that the bitstream is normalized to 4µs.
1046 // Which means on the //e that you would have to run it at that clock
1047 // rate (instead of the //e clock rate 0.9799µs/cycle) to get the
1048 // simulated drive running at 300 RPM. So, instead of doing that, we're
1049 // just gonna run it at twice the clock rate of the base 6502 clock,
1050 // which will make the simulated drive run in the neighborhood of around
1051 // 306 RPM. Should be close enough to get away with it. :-) (And it
1052 // seems to run OK, for the most part.)
1054 // According to EDD 4 the drive is running at 299.1 RPM... :-/
1056 static bool logSeq = false;
1057 char sequence[1024];
1059 // Logic State Sequencer & Data Register
1061 void FloppyDrive::RunSequencer(uint32_t cyclesToRun)
1063 static uint32_t prng = 1;
1066 if (!diskImageReady)
1068 else if (diskType[activeDrive] == DT_EMPTY)
1070 else if (motorOn == false)
1072 if (driveOffTimeout == 0)
1078 WOZ2 & woz = *((WOZ2 *)disk[activeDrive]);
1079 uint8_t tIdx = woz.tmap[headPos[activeDrive]];
1080 uint8_t * tdata = disk[activeDrive] + (Uint16LE(woz.track[tIdx].startingBlock) * 512);
1081 // We have to divide the optimal bit timing by 4 because we only have a 0.5µs granularity here (with the doubling of the "cyclesToRun"). The OBT has a granularity of 0.125µs. Not sure how to fix that--have to separate the pulse handling from the sequencer?
1082 // N.B.: Border Zone has an OBT of 28, when divides evenly by 4, but it still fails...
1083 uint8_t pulseTiming = woz.optimalBitTmg / 4;
1085 // It's x2 because the sequencer clock runs twice as fast as the CPU clock.
1088 //extern bool dumpDis;
1089 //static bool tripwire = false;
1091 //static uint32_t lastPos = 0;
1094 WriteLog("DISKSEQ: Running for %d cycles [rw=%hhd, sl=%hhd, reg=%02X, bus=%02X]\n", cyclesToRun, rwSwitch, slSwitch, dataRegister, cpuDataBus);
1097 while (cyclesToRun-- > 0)
1099 // pulseClock = (pulseClock + 1) & 0x07;
1100 // pulseClock = (pulseClock + 1) % 8;
1101 pulseClock = (pulseClock + 1) % pulseTiming;
1102 // 7 doesn't work... Is that 3.5µs? Seems to be. Which means to get a 0.25µs granularity here, we need to double the # of cycles to run...
1103 // pulseClock = (pulseClock + 1) % 7;
1105 if (pulseClock == 0)
1107 uint16_t bytePos = currentPos[activeDrive] / 8;
1108 uint8_t bitPos = currentPos[activeDrive] % 8;
1113 if (tdata[bytePos] & bitMask[bitPos])
1115 // According to Jim Sather (Understanding the Apple II),
1116 // the Read Pulse, when it happens, is 1µs long, which is 2
1117 // sequencer clock pulses long. (Not sure where, elsewhere, on pg. 9-29 it says it lasts one *sequencer* clock pulse.)
1124 //WriteLog("[%d]", tdata[bytePos] & bitMask[bitPos] ? 1 : 0);
1127 readPulse = (window >> 1) & 0x01; // Read pulse delayed by about 5µs
1128 currentPos[activeDrive] = (currentPos[activeDrive] + 1) % trackLength[activeDrive];
1130 // If we hit more than 2 zero bits in a row, simulate the disk head
1131 // reader's Automatic Gain Control (AGC) turning itself up too high
1132 // by stuffing random bits in the bitstream. We also do this if
1133 // the current track is marked as unformatted.
1135 N.B.: Had to up this to 3 because Up N' Down had some weird sync bytes (FE10). May have to up it some more.
1137 // if ((zeroBitCount > 3) || (tIdx == 0xFF))
1138 if (((window & 0x0F) == 0) || (tIdx == 0xFF))
1142 // This PRNG is called the "Galois configuration".
1151 // Find and run the Sequencer's next state
1152 uint8_t nextState = (sequencerState & 0xF0) | (rwSwitch << 3)
1153 | (slSwitch << 2) | (readPulse ? 0x02 : 0)
1154 | ((dataRegister & 0x80) >> 7);
1156 WriteLog("[%02X:%02X]%s", sequencerState, nextState, (chop == 15 ? "\n" : ""));
1157 chop = (chop + 1) % 20;
1159 sequencerState = sequencerROM[nextState];
1161 // sequencerState = sequencerROM13[nextState];
1162 sequencerState = sequencerROM[nextState];
1166 uint32_t seqLen = strlen(sequence);
1168 if (seqLen + 7 > 1023)
1174 sprintf(&sequence[seqLen], "(%02X)%02X ", nextState, sequencerState);
1177 switch (sequencerState & 0x0F)
1187 // CLR (clear data register; 0)
1192 // NOP (no operation)
1195 // SL0 (shift left, 0 fill LSB)
1199 if (rwSwitch && (tIdx != 0xFF)
1200 && !woz.writeProtected)
1202 imageDirty[activeDrive] = true;
1203 uint16_t bytePos = currentPos[activeDrive] / 8;
1204 uint8_t bitPos = currentPos[activeDrive] % 8;
1206 if (dataRegister & 0x80)
1207 // Fill in the one, if necessary
1208 tdata[bytePos] |= bitMask[bitPos];
1210 // Otherwise, punch in the zero
1211 tdata[bytePos] &= ~bitMask[bitPos];
1214 if (dumpDis || tripwire)
1217 WriteLog("[%s]", (dataRegister & 0x80 ? "1" : "0"));
1218 if (lastPos == currentPos[activeDrive])
1219 WriteLog("{STOMP}");
1220 else if ((lastPos + 1) != currentPos[activeDrive])
1222 lastPos = currentPos[activeDrive];
1230 // SR (shift right write protect bit)
1232 dataRegister |= (woz.writeProtected ? 0x80 : 0x00);
1236 // LD (load data register from data bus)
1237 dataRegister = cpuDataBus;
1240 if (rwSwitch && (tIdx != 0xFF) && !woz.writeProtected)
1242 imageDirty[activeDrive] = true;
1243 uint16_t bytePos = currentPos[activeDrive] / 8;
1244 uint8_t bitPos = currentPos[activeDrive] % 8;
1245 tdata[bytePos] |= bitMask[bitPos];
1247 if (dumpDis || tripwire)
1250 WriteLog("[%s]", (dataRegister & 0x80 ? "1" : "0"));
1251 if (lastPos == currentPos[activeDrive])
1252 WriteLog("{STOMP}");
1253 else if ((lastPos + 1) != currentPos[activeDrive])
1255 lastPos = currentPos[activeDrive];
1262 // SL1 (shift left, 1 fill LSB)
1264 dataRegister |= 0x01;
1267 // 1-7, $C, $E, & $F are all invalid opcodes
1268 WriteLog("Invalid LSS state encountered! (opcode: $%X [full state: $%02X])\n", sequencerState & 0x0F, sequencerState);
1279 FloppyDrive floppyDrive[2];
1281 static uint8_t SlotIOR(uint16_t address)
1283 uint8_t state = address & 0x0F;
1295 floppyDrive[0].ControlStepper(state);
1299 floppyDrive[0].ControlMotor(state & 0x01);
1303 floppyDrive[0].DriveEnable(state & 0x01);
1307 floppyDrive[0].SetShiftLoadSwitch(state & 0x01);
1311 floppyDrive[0].SetReadWriteSwitch(state & 0x01);
1315 //temp, for debugging
1317 // Even addresses return the data register, odd (we suppose) returns a
1318 // floating bus read...
1319 return (address & 0x01 ? ReadFloatingBus(0) : floppyDrive[0].DataRegister());
1322 static void SlotIOW(uint16_t address, uint8_t byte)
1324 uint8_t state = address & 0x0F;
1336 floppyDrive[0].ControlStepper(state);
1340 floppyDrive[0].ControlMotor(state & 0x01);
1344 floppyDrive[0].DriveEnable(state & 0x01);
1348 floppyDrive[0].SetShiftLoadSwitch(state & 0x01);
1352 floppyDrive[0].SetReadWriteSwitch(state & 0x01);
1356 // Odd addresses write to the Data register, even addresses (we assume) go
1359 floppyDrive[0].DataRegister(byte);
1363 // This slot function doesn't need to differentiate between separate instances
1365 static uint8_t SlotROM(uint16_t address)
1368 return diskROM[address];
1370 return diskROM13[address];
1374 void InstallFloppy(uint8_t slot)
1376 SlotData disk = { SlotIOR, SlotIOW, SlotROM, 0, 0, 0 };
1377 InstallSlotHandler(slot, &disk);