ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/Frodo4/Src/IEC.cpp
Revision: 1.9
Committed: 2005-06-27T19:55:48Z (19 years, 4 months ago) by cebix
Branch: MAIN
CVS Tags: VERSION_4_2, HEAD
Changes since 1.8: +1 -1 lines
Log Message:
updated copyright dates

File Contents

# Content
1 /*
2 * IEC.cpp - IEC bus routines, 1541 emulation (DOS level)
3 *
4 * Frodo (C) 1994-1997,2002-2005 Christian Bauer
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 /*
22 * Notes:
23 * ------
24 *
25 * - There are three kinds of devices on the IEC bus: controllers,
26 * listeners and talkers. We are always the controller and we
27 * can additionally be either listener or talker. There can be
28 * only one listener and one talker active at the same time (the
29 * real IEC bus allows multiple listeners, but we don't).
30 * - There is one Drive object for every emulated drive (8..11).
31 * A pointer to one of them is stored in "listener"/"talker"
32 * when talk()/listen() is called and is used by the functions
33 * called afterwards.
34 * - The Drive objects have four virtual functions so that the
35 * interface to them is independent of their implementation:
36 * Open() opens a channel
37 * Close() closes a channel
38 * Read() reads from a channel
39 * Write() writes to a channel
40 * - The EOI/EOF signal is special on the IEC bus in that it is
41 * Sent before the last byte, not after it.
42 */
43
44 #include "sysdeps.h"
45
46 #include "IEC.h"
47 #include "1541fs.h"
48 #include "1541d64.h"
49 #include "1541t64.h"
50 #include "Prefs.h"
51 #include "Display.h"
52 #include "main.h"
53
54
55 // IEC command codes
56 enum {
57 CMD_DATA = 0x60, // Data transfer
58 CMD_CLOSE = 0xe0, // Close channel
59 CMD_OPEN = 0xf0 // Open channel
60 };
61
62 // IEC ATN codes
63 enum {
64 ATN_LISTEN = 0x20,
65 ATN_UNLISTEN = 0x30,
66 ATN_TALK = 0x40,
67 ATN_UNTALK = 0x50
68 };
69
70
71 /*
72 * Constructor: Initialize variables
73 */
74
75 Drive *IEC::create_drive(const char *path)
76 {
77 if (IsDirectory(path)) {
78 // Mount host directory
79 return new FSDrive(this, path);
80 } else {
81 // Not a directory, check for mountable file type
82 int type;
83 if (IsMountableFile(path, type)) {
84 if (type == FILE_IMAGE) {
85 // Mount disk image
86 return new ImageDrive(this, path);
87 } else {
88 // Mount archive type file
89 return new ArchDrive(this, path);
90 }
91 } else {
92 // Unknown file type
93 // print error?
94 }
95 }
96 }
97
98 IEC::IEC(C64Display *display) : the_display(display)
99 {
100 int i;
101
102 // Create drives 8..11
103 for (i=0; i<4; i++)
104 drive[i] = NULL; // Important because UpdateLEDs is called from the drive constructors (via set_error)
105
106 if (!ThePrefs.Emul1541Proc) {
107 for (i=0; i<4; i++)
108 drive[i] = create_drive(ThePrefs.DrivePath[i]);
109 }
110
111 listener_active = talker_active = false;
112 listening = false;
113 }
114
115
116 /*
117 * Destructor: Delete drives
118 */
119
120 IEC::~IEC()
121 {
122 for (int i=0; i<4; i++)
123 delete drive[i];
124 }
125
126
127 /*
128 * Reset all drives
129 */
130
131 void IEC::Reset(void)
132 {
133 for (int i=0; i<4; i++)
134 if (drive[i] != NULL && drive[i]->Ready)
135 drive[i]->Reset();
136
137 UpdateLEDs();
138 }
139
140
141 /*
142 * Preferences have changed, prefs points to new preferences,
143 * ThePrefs still holds the previous ones. Check if drive settings
144 * have changed.
145 */
146
147 void IEC::NewPrefs(Prefs *prefs)
148 {
149 // Delete and recreate all changed drives
150 for (int i=0; i<4; i++) {
151 if (strcmp(ThePrefs.DrivePath[i], prefs->DrivePath[i]) || ThePrefs.Emul1541Proc != prefs->Emul1541Proc) {
152 delete drive[i];
153 drive[i] = NULL; // Important because UpdateLEDs is called from drive constructors (via set_error())
154 if (!prefs->Emul1541Proc)
155 drive[i] = create_drive(prefs->DrivePath[i]);
156 }
157 }
158
159 UpdateLEDs();
160 }
161
162
163 /*
164 * Update drive LED display
165 */
166
167 void IEC::UpdateLEDs(void)
168 {
169 if (drive[0] != NULL && drive[1] != NULL && drive[2] != NULL && drive[3] != NULL)
170 the_display->UpdateLEDs(drive[0]->LED, drive[1]->LED, drive[2]->LED, drive[3]->LED);
171 }
172
173
174 /*
175 * Output one byte
176 */
177
178 uint8 IEC::Out(uint8 byte, bool eoi)
179 {
180 if (listener_active) {
181 if (received_cmd == CMD_OPEN)
182 return open_out(byte, eoi);
183 if (received_cmd == CMD_DATA)
184 return data_out(byte, eoi);
185 return ST_TIMEOUT;
186 } else
187 return ST_TIMEOUT;
188 }
189
190
191 /*
192 * Output one byte with ATN (Talk/Listen/Untalk/Unlisten)
193 */
194
195 uint8 IEC::OutATN(uint8 byte)
196 {
197 received_cmd = sec_addr = 0; // Command is sent with secondary address
198 switch (byte & 0xf0) {
199 case ATN_LISTEN:
200 listening = true;
201 return listen(byte & 0x0f);
202 case ATN_UNLISTEN:
203 listening = false;
204 return unlisten();
205 case ATN_TALK:
206 listening = false;
207 return talk(byte & 0x0f);
208 case ATN_UNTALK:
209 listening = false;
210 return untalk();
211 }
212 return ST_TIMEOUT;
213 }
214
215
216 /*
217 * Output secondary address
218 */
219
220 uint8 IEC::OutSec(uint8 byte)
221 {
222 if (listening) {
223 if (listener_active) {
224 sec_addr = byte & 0x0f;
225 received_cmd = byte & 0xf0;
226 return sec_listen();
227 }
228 } else {
229 if (talker_active) {
230 sec_addr = byte & 0x0f;
231 received_cmd = byte & 0xf0;
232 return sec_talk();
233 }
234 }
235 return ST_TIMEOUT;
236 }
237
238
239 /*
240 * Read one byte
241 */
242
243 uint8 IEC::In(uint8 &byte)
244 {
245 if (talker_active && (received_cmd == CMD_DATA))
246 return data_in(byte);
247
248 byte = 0;
249 return ST_TIMEOUT;
250 }
251
252
253 /*
254 * Assert ATN (for Untalk)
255 */
256
257 void IEC::SetATN(void)
258 {
259 // Only needed for real IEC
260 }
261
262
263 /*
264 * Release ATN
265 */
266
267 void IEC::RelATN(void)
268 {
269 // Only needed for real IEC
270 }
271
272
273 /*
274 * Talk-attention turn-around
275 */
276
277 void IEC::Turnaround(void)
278 {
279 // Only needed for real IEC
280 }
281
282
283 /*
284 * System line release
285 */
286
287 void IEC::Release(void)
288 {
289 // Only needed for real IEC
290 }
291
292
293 /*
294 * Listen
295 */
296
297 uint8 IEC::listen(int device)
298 {
299 if ((device >= 8) && (device <= 11)) {
300 if ((listener = drive[device-8]) != NULL && listener->Ready) {
301 listener_active = true;
302 return ST_OK;
303 }
304 }
305
306 listener_active = false;
307 return ST_NOTPRESENT;
308 }
309
310
311 /*
312 * Talk
313 */
314
315 uint8 IEC::talk(int device)
316 {
317 if ((device >= 8) && (device <= 11)) {
318 if ((talker = drive[device-8]) != NULL && talker->Ready) {
319 talker_active = true;
320 return ST_OK;
321 }
322 }
323
324 talker_active = false;
325 return ST_NOTPRESENT;
326 }
327
328
329 /*
330 * Unlisten
331 */
332
333 uint8 IEC::unlisten(void)
334 {
335 listener_active = false;
336 return ST_OK;
337 }
338
339
340 /*
341 * Untalk
342 */
343
344 uint8 IEC::untalk(void)
345 {
346 talker_active = false;
347 return ST_OK;
348 }
349
350
351 /*
352 * Secondary address after Listen
353 */
354
355 uint8 IEC::sec_listen(void)
356 {
357 switch (received_cmd) {
358
359 case CMD_OPEN: // Prepare for receiving the file name
360 name_ptr = name_buf;
361 name_len = 0;
362 return ST_OK;
363
364 case CMD_CLOSE: // Close channel
365 if (listener->LED != DRVLED_ERROR) {
366 listener->LED = DRVLED_OFF; // Turn off drive LED
367 UpdateLEDs();
368 }
369 return listener->Close(sec_addr);
370 }
371 return ST_OK;
372 }
373
374
375 /*
376 * Secondary address after Talk
377 */
378
379 uint8 IEC::sec_talk(void)
380 {
381 return ST_OK;
382 }
383
384
385 /*
386 * Byte after Open command: Store character in file name, open file on EOI
387 */
388
389 uint8 IEC::open_out(uint8 byte, bool eoi)
390 {
391 if (name_len < NAMEBUF_LENGTH) {
392 *name_ptr++ = byte;
393 name_len++;
394 }
395
396 if (eoi) {
397 *name_ptr = 0; // End string
398 listener->LED = DRVLED_ON; // Turn on drive LED
399 UpdateLEDs();
400 return listener->Open(sec_addr, name_buf, name_len);
401 }
402
403 return ST_OK;
404 }
405
406
407 /*
408 * Write byte to channel
409 */
410
411 uint8 IEC::data_out(uint8 byte, bool eoi)
412 {
413 return listener->Write(sec_addr, byte, eoi);
414 }
415
416
417 /*
418 * Read byte from channel
419 */
420
421 uint8 IEC::data_in(uint8 &byte)
422 {
423 return talker->Read(sec_addr, byte);
424 }
425
426
427 /*
428 * Drive constructor
429 */
430
431 Drive::Drive(IEC *iec)
432 {
433 the_iec = iec;
434 LED = DRVLED_OFF;
435 Ready = false;
436 set_error(ERR_STARTUP);
437 }
438
439
440 /*
441 * Set error message on drive
442 */
443
444 // 1541 error messages
445 static const char *Errors_1541[] = {
446 "00, OK,%02d,%02d\x0d",
447 "01, FILES SCRATCHED,%02d,%02d\x0d",
448 "03, UNIMPLEMENTED,%02d,%02d\x0d",
449 "20, READ ERROR,%02d,%02d\x0d",
450 "21, READ ERROR,%02d,%02d\x0d",
451 "22, READ ERROR,%02d,%02d\x0d",
452 "23, READ ERROR,%02d,%02d\x0d",
453 "24, READ ERROR,%02d,%02d\x0d",
454 "25, WRITE ERROR,%02d,%02d\x0d",
455 "26, WRITE PROTECT ON,%02d,%02d\x0d",
456 "27, READ ERROR,%02d,%02d\x0d",
457 "28, WRITE ERROR,%02d,%02d\x0d",
458 "29, DISK ID MISMATCH,%02d,%02d\x0d",
459 "30, SYNTAX ERROR,%02d,%02d\x0d",
460 "31, SYNTAX ERROR,%02d,%02d\x0d",
461 "32, SYNTAX ERROR,%02d,%02d\x0d",
462 "33, SYNTAX ERROR,%02d,%02d\x0d",
463 "34, SYNTAX ERROR,%02d,%02d\x0d",
464 "60, WRITE FILE OPEN,%02d,%02d\x0d",
465 "61, FILE NOT OPEN,%02d,%02d\x0d",
466 "62, FILE NOT FOUND,%02d,%02d\x0d",
467 "63, FILE EXISTS,%02d,%02d\x0d",
468 "64, FILE TYPE MISMATCH,%02d,%02d\x0d",
469 "65, NO BLOCK,%02d,%02d\x0d",
470 "66, ILLEGAL TRACK OR SECTOR,%02d,%02d\x0d",
471 "70, NO CHANNEL,%02d,%02d\x0d",
472 "71, DIR ERROR,%02d,%02d\x0d",
473 "72, DISK FULL,%02d,%02d\x0d",
474 "73, CBM DOS V2.6 1541,%02d,%02d\x0d",
475 "74, DRIVE NOT READY,%02d,%02d\x0d"
476 };
477
478 void Drive::set_error(int error, int track, int sector)
479 {
480 // Write error message to buffer
481 sprintf(error_buf, Errors_1541[error], track, sector);
482 error_ptr = error_buf;
483 error_len = strlen(error_buf);
484 current_error = error;
485
486 // Set drive condition
487 if (error != ERR_OK && error != ERR_SCRATCHED)
488 if (error == ERR_STARTUP)
489 LED = DRVLED_OFF;
490 else
491 LED = DRVLED_ERROR;
492 else if (LED == DRVLED_ERROR)
493 LED = DRVLED_OFF;
494 the_iec->UpdateLEDs();
495 }
496
497
498 /*
499 * Parse file name, determine access mode and file type
500 */
501
502 void Drive::parse_file_name(const uint8 *src, int src_len, uint8 *dest, int &dest_len, int &mode, int &type, int &rec_len, bool convert_charset)
503 {
504 // If the string contains a ':', the file name starts after that
505 const uint8 *p = (const uint8 *)memchr(src, ':', src_len);
506 if (p) {
507 p++;
508 src_len -= p - src;
509 } else
510 p = src;
511
512 // Transfer file name upto ','
513 dest_len = 0;
514 uint8 *q = dest;
515 while (*p != ',' && src_len-- > 0) {
516 if (convert_charset)
517 *q++ = petscii2ascii(*p++);
518 else
519 *q++ = *p++;
520 dest_len++;
521 }
522 *q++ = 0;
523
524 // Strip trailing CRs
525 while (dest_len > 0 && dest[dest_len - 1] == 0x0d)
526 dest[--dest_len] = 0;
527
528 // Look for mode and type parameters separated by ','
529 p++; src_len--;
530 while (src_len > 0) {
531 switch (*p) {
532 case 'D':
533 type = FTYPE_DEL;
534 break;
535 case 'S':
536 type = FTYPE_SEQ;
537 break;
538 case 'P':
539 type = FTYPE_PRG;
540 break;
541 case 'U':
542 type = FTYPE_USR;
543 break;
544 case 'L':
545 type = FTYPE_REL;
546 while (*p != ',' && src_len-- > 0) p++;
547 p++; src_len--;
548 rec_len = *p++; src_len--;
549 if (src_len < 0)
550 rec_len = 0;
551 break;
552 case 'R':
553 mode = FMODE_READ;
554 break;
555 case 'W':
556 mode = FMODE_WRITE;
557 break;
558 case 'A':
559 mode = FMODE_APPEND;
560 break;
561 case 'M':
562 mode = FMODE_M;
563 break;
564 }
565
566 // Skip to ','
567 while (*p != ',' && src_len-- > 0) p++;
568 p++; src_len--;
569 }
570 }
571
572
573 /*
574 * Execute DOS command (parse command and call appropriate routine)
575 */
576
577 static void parse_block_cmd_args(const uint8 *p, int &arg1, int &arg2, int &arg3, int &arg4)
578 {
579 arg1 = arg2 = arg3 = arg4 = 0;
580
581 while (*p == ' ' || *p == 0x1d || *p == ',') p++;
582 while (*p >= '0' && *p < '@')
583 arg1 = arg1 * 10 + (*p++ & 0x0f);
584
585 while (*p == ' ' || *p == 0x1d || *p == ',') p++;
586 while (*p >= '0' && *p < '@')
587 arg2 = arg2 * 10 + (*p++ & 0x0f);
588
589 while (*p == ' ' || *p == 0x1d || *p == ',') p++;
590 while (*p >= '0' && *p < '@')
591 arg3 = arg3 * 10 + (*p++ & 0x0f);
592
593 while (*p == ' ' || *p == 0x1d || *p == ',') p++;
594 while (*p >= '0' && *p < '@')
595 arg4 = arg4 * 10 + (*p++ & 0x0f);
596 }
597
598 void Drive::execute_cmd(const uint8 *cmd, int cmd_len)
599 {
600 // Strip trailing CRs
601 while (cmd_len > 0 && cmd[cmd_len - 1] == 0x0d)
602 cmd_len--;
603
604 // Find token delimiters
605 const uint8 *colon = (const uint8 *)memchr(cmd, ':', cmd_len);
606 const uint8 *equal = colon ? (const uint8 *)memchr(colon, '=', cmd_len - (colon - cmd)) : NULL;
607 const uint8 *comma = (const uint8 *)memchr(cmd, ',', cmd_len);
608 const uint8 *minus = (const uint8 *)memchr(cmd, '-', cmd_len);
609
610 // Parse command name
611 set_error(ERR_OK);
612 switch (cmd[0]) {
613 case 'B': // Block/buffer
614 if (!minus)
615 set_error(ERR_SYNTAX31);
616 else {
617 // Parse arguments (up to 4 decimal numbers separated by
618 // space, cursor right or comma)
619 const uint8 *p = colon ? colon + 1 : cmd + 3;
620 int arg1, arg2, arg3, arg4;
621 parse_block_cmd_args(p, arg1, arg2, arg3, arg4);
622
623 // Switch on command
624 switch (minus[1]) {
625 case 'R':
626 block_read_cmd(arg1, arg3, arg4);
627 break;
628 case 'W':
629 block_write_cmd(arg1, arg3, arg4);
630 break;
631 case 'E':
632 block_execute_cmd(arg1, arg3, arg4);
633 break;
634 case 'A':
635 block_allocate_cmd(arg2, arg3);
636 break;
637 case 'F':
638 block_free_cmd(arg2, arg3);
639 break;
640 case 'P':
641 buffer_pointer_cmd(arg1, arg2);
642 break;
643 default:
644 set_error(ERR_SYNTAX31);
645 break;
646 }
647 }
648 break;
649
650 case 'M': // Memory
651 if (cmd[1] != '-')
652 set_error(ERR_SYNTAX31);
653 else {
654 // Read parameters
655 uint16 adr = uint8(cmd[3]) | (uint8(cmd[4]) << 8);
656 uint8 len = uint8(cmd[5]);
657
658 // Switch on command
659 switch (cmd[2]) {
660 case 'R':
661 mem_read_cmd(adr, (cmd_len < 6) ? 1 : len);
662 break;
663 case 'W':
664 mem_write_cmd(adr, len, (uint8 *)cmd + 6);
665 break;
666 case 'E':
667 mem_execute_cmd(adr);
668 break;
669 default:
670 set_error(ERR_SYNTAX31);
671 break;
672 }
673 }
674 break;
675
676 case 'C': // Copy
677 if (!colon)
678 set_error(ERR_SYNTAX31);
679 else if (!equal || memchr(cmd, '*', cmd_len) || memchr(cmd, '?', cmd_len) || (comma && comma < equal))
680 set_error(ERR_SYNTAX30);
681 else
682 copy_cmd(colon + 1, equal - colon - 1, equal + 1, cmd_len - (equal + 1 - cmd));
683 break;
684
685 case 'R': // Rename
686 if (!colon)
687 set_error(ERR_SYNTAX34);
688 else if (!equal || comma || memchr(cmd, '*', cmd_len) || memchr(cmd, '?', cmd_len))
689 set_error(ERR_SYNTAX30);
690 else
691 rename_cmd(colon + 1, equal - colon - 1, equal + 1, cmd_len - (equal + 1 - cmd));
692 break;
693
694 case 'S': // Scratch
695 if (!colon)
696 set_error(ERR_SYNTAX34);
697 else
698 scratch_cmd(colon + 1, cmd_len - (colon + 1 - cmd));
699 break;
700
701 case 'P': // Position
702 position_cmd(cmd + 1, cmd_len - 1);
703 break;
704
705 case 'I': // Initialize
706 initialize_cmd();
707 break;
708
709 case 'N': // New (format)
710 if (!colon)
711 set_error(ERR_SYNTAX34);
712 else
713 new_cmd(colon + 1, comma ? (comma - colon - 1) : cmd_len - (colon + 1 - cmd), comma);
714 break;
715
716 case 'V': // Validate
717 validate_cmd();
718 break;
719
720 case 'U': // User
721 if (cmd[1] == '0')
722 break;
723 switch (cmd[1] & 0x0f) {
724 case 1: { // U1/UA: Read block
725 const uint8 *p = colon ? colon + 1 : cmd + 2;
726 int arg1, arg2, arg3, arg4;
727 parse_block_cmd_args(p, arg1, arg2, arg3, arg4);
728 block_read_cmd(arg1, arg3, arg4, true);
729 break;
730 }
731 case 2: { // U2/UB: Write block
732 const uint8 *p = colon ? colon + 1 : cmd + 2;
733 int arg1, arg2, arg3, arg4;
734 parse_block_cmd_args(p, arg1, arg2, arg3, arg4);
735 block_write_cmd(arg1, arg3, arg4, true);
736 break;
737 }
738 case 9: // U9/UI: C64/VC20 mode switch
739 if (cmd[2] != '+' && cmd[2] != '-')
740 Reset();
741 break;
742 case 10: // U:/UJ: Reset
743 Reset();
744 break;
745 default:
746 set_error(ERR_UNIMPLEMENTED);
747 break;
748 }
749 break;
750
751 default:
752 set_error(ERR_SYNTAX31);
753 break;
754 }
755 }
756
757 // BLOCK-READ:channel,0,track,sector
758 void Drive::block_read_cmd(int channel, int track, int sector, bool user_cmd)
759 {
760 set_error(ERR_UNIMPLEMENTED);
761 }
762
763 // BLOCK-WRITE:channel,0,track,sector
764 void Drive::block_write_cmd(int channel, int track, int sector, bool user_cmd)
765 {
766 set_error(ERR_UNIMPLEMENTED);
767 }
768
769 // BLOCK-EXECUTE:channel,0,track,sector
770 void Drive::block_execute_cmd(int channel, int track, int sector)
771 {
772 set_error(ERR_UNIMPLEMENTED);
773 }
774
775 // BLOCK-ALLOCATE:0,track,sector
776 void Drive::block_allocate_cmd(int track, int sector)
777 {
778 set_error(ERR_UNIMPLEMENTED);
779 }
780
781 // BLOCK-FREE:0,track,sector
782 void Drive::block_free_cmd(int track, int sector)
783 {
784 set_error(ERR_UNIMPLEMENTED);
785 }
786
787 // BUFFER-POINTER:channel,pos
788 void Drive::buffer_pointer_cmd(int channel, int pos)
789 {
790 set_error(ERR_UNIMPLEMENTED);
791 }
792
793 // M-R<adr low><adr high>[<number>]
794 void Drive::mem_read_cmd(uint16 adr, uint8 len)
795 {
796 unsupp_cmd();
797 error_ptr = error_buf;
798 error_buf[0] = 0;
799 error_len = 0;
800 set_error(ERR_OK);
801 }
802
803 // M-W<adr low><adr high><number><data...>
804 void Drive::mem_write_cmd(uint16 adr, uint8 len, uint8 *p)
805 {
806 set_error(ERR_UNIMPLEMENTED);
807 }
808
809 // M-E<adr low><adr high>
810 void Drive::mem_execute_cmd(uint16 adr)
811 {
812 set_error(ERR_UNIMPLEMENTED);
813 }
814
815 // COPY:new=file1,file2,...
816 // ^ ^
817 // new_file old_files
818 void Drive::copy_cmd(const uint8 *new_file, int new_file_len, const uint8 *old_files, int old_files_len)
819 {
820 set_error(ERR_UNIMPLEMENTED);
821 }
822
823 // RENAME:new=old
824 // ^ ^
825 // new_file old_file
826 void Drive::rename_cmd(const uint8 *new_file, int new_file_len, const uint8 *old_file, int old_file_len)
827 {
828 set_error(ERR_UNIMPLEMENTED);
829 }
830
831 // SCRATCH:file1,file2,...
832 // ^
833 // files
834 void Drive::scratch_cmd(const uint8 *files, int files_len)
835 {
836 set_error(ERR_UNIMPLEMENTED);
837 }
838
839 // P<channel><record low><record high><byte>
840 // ^
841 // cmd
842 void Drive::position_cmd(const uint8 *cmd, int cmd_len)
843 {
844 set_error(ERR_UNIMPLEMENTED);
845 }
846
847 // INITIALIZE
848 void Drive::initialize_cmd(void)
849 {
850 set_error(ERR_UNIMPLEMENTED);
851 }
852
853 // NEW:name,id
854 // ^ ^
855 // name comma (or NULL)
856 void Drive::new_cmd(const uint8 *name, int name_len, const uint8 *comma)
857 {
858 set_error(ERR_UNIMPLEMENTED);
859 }
860
861 // VALIDATE
862 void Drive::validate_cmd(void)
863 {
864 set_error(ERR_UNIMPLEMENTED);
865 }
866
867
868 /*
869 * Notice user of unsupported drive command
870 */
871
872 void Drive::unsupp_cmd(void)
873 {
874 }
875
876
877 /*
878 * Convert PETSCII<->ASCII
879 */
880
881 uint8 ascii2petscii(char c)
882 {
883 if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z'))
884 return c ^ 0x20;
885 return c;
886 }
887
888 void ascii2petscii(uint8 *dest, const char *src, int n)
889 {
890 while (n-- && (*dest++ = ascii2petscii(*src++))) ;
891 }
892
893 char petscii2ascii(uint8 c)
894 {
895 if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z'))
896 return c ^ 0x20;
897 if ((c >= 0xc1) && (c <= 0xda))
898 return c ^ 0x80;
899 return c;
900 }
901
902 void petscii2ascii(char *dest, const uint8 *src, int n)
903 {
904 while (n-- && (*dest++ = petscii2ascii(*src++))) ;
905 }
906
907
908 /*
909 * Check whether file is a mountable disk image or archive file, return type
910 */
911
912 bool IsMountableFile(const char *path, int &type)
913 {
914 // Read header and determine file size
915 uint8 header[64];
916 memset(header, 0, sizeof(header));
917 FILE *f = fopen(path, "rb");
918 if (f == NULL)
919 return false;
920 fseek(f, 0, SEEK_END);
921 long size = ftell(f);
922 fseek(f, 0, SEEK_SET);
923 fread(header, 1, sizeof(header), f);
924 fclose(f);
925
926 if (IsImageFile(path, header, size)) {
927 type = FILE_IMAGE;
928 return true;
929 } else if (IsArchFile(path, header, size)) {
930 type = FILE_ARCH;
931 return true;
932 } else
933 return false;
934 }
935
936
937 /*
938 * Read directory of mountable disk image or archive file into c64_dir_entry vector,
939 * returns false on error
940 */
941
942 bool ReadDirectory(const char *path, int type, vector<c64_dir_entry> &vec)
943 {
944 vec.clear();
945 switch (type) {
946 case FILE_IMAGE:
947 return ReadImageDirectory(path, vec);
948 case FILE_ARCH:
949 return ReadArchDirectory(path, vec);
950 default:
951 return false;
952 }
953 }