ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/Frodo4/Src/1541t64.cpp
Revision: 1.2
Committed: 2003-07-01T17:51:17Z (20 years, 9 months ago) by cebix
Branch: MAIN
Changes since 1.1: +1 -1 lines
Log Message:
updated copyright date

File Contents

# Content
1 /*
2 * 1541t64.cpp - 1541 emulation in .t64/LYNX file
3 *
4 * Frodo (C) 1994-1997,2002-2003 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 * - If any file is opened, the contents of the file in the
26 * .t64 file are copied into a temporary file which is used
27 * for reading. This is done to insert the load address.
28 * - C64 LYNX archives are also handled by these routines
29 *
30 * Incompatibilities:
31 * ------------------
32 *
33 * - Only read accesses possible
34 * - No "raw" directory reading
35 * - No relative/sequential/user files
36 * - Only "I" and "UJ" commands implemented
37 */
38
39 #include "sysdeps.h"
40
41 #include "1541t64.h"
42 #include "IEC.h"
43 #include "Prefs.h"
44
45
46 // Access modes
47 enum {
48 FMODE_READ, FMODE_WRITE, FMODE_APPEND
49 };
50
51 // File types
52 enum {
53 FTYPE_PRG, FTYPE_SEQ, FTYPE_USR, FTYPE_REL
54 };
55
56 // Prototypes
57 static bool match(char *p, char *n);
58
59
60 /*
61 * Constructor: Prepare emulation
62 */
63
64 T64Drive::T64Drive(IEC *iec, char *filepath) : Drive(iec)
65 {
66 the_file = NULL;
67 file_info = NULL;
68
69 Ready = false;
70 strcpy(orig_t64_name, filepath);
71 for (int i=0; i<16; i++)
72 file[i] = NULL;
73
74 // Open .t64 file
75 open_close_t64_file(filepath);
76 if (the_file != NULL) {
77 Reset();
78 Ready = true;
79 }
80 }
81
82
83 /*
84 * Destructor
85 */
86
87 T64Drive::~T64Drive()
88 {
89 // Close .t64 file
90 open_close_t64_file("");
91
92 Ready = false;
93 }
94
95
96 /*
97 * Open/close the .t64/LYNX file
98 */
99
100 void T64Drive::open_close_t64_file(char *t64name)
101 {
102 uint8 buf[64];
103 bool parsed_ok = false;
104
105 // Close old .t64, if open
106 if (the_file != NULL) {
107 close_all_channels();
108 fclose(the_file);
109 the_file = NULL;
110 delete[] file_info;
111 file_info = NULL;
112 }
113
114 // Open new .t64 file
115 if (t64name[0]) {
116 if ((the_file = fopen(t64name, "rb")) != NULL) {
117
118 // Check file ID
119 fread(&buf, 64, 1, the_file);
120 if (buf[0] == 0x43 && buf[1] == 0x36 && buf[2] == 0x34) {
121 is_lynx = false;
122 parsed_ok = parse_t64_file();
123 } else if (buf[0x3c] == 0x4c && buf[0x3d] == 0x59 && buf[0x3e] == 0x4e && buf[0x3f] == 0x58) {
124 is_lynx = true;
125 parsed_ok = parse_lynx_file();
126 }
127
128 if (!parsed_ok) {
129 fclose(the_file);
130 the_file = NULL;
131 delete[] file_info;
132 file_info = NULL;
133 return;
134 }
135 }
136 }
137 }
138
139
140 /*
141 * Parse .t64 file and construct FileInfo array
142 */
143
144 bool T64Drive::parse_t64_file(void)
145 {
146 uint8 buf[32];
147 uint8 *buf2;
148 char *p;
149 int max, i, j;
150
151 // Read header and get maximum number of files contained
152 fseek(the_file, 32, SEEK_SET);
153 fread(&buf, 32, 1, the_file);
154 max = (buf[3] << 8) | buf[2];
155
156 memcpy(dir_title, buf+8, 16);
157
158 // Allocate buffer for file records and read them
159 buf2 = new uint8[max*32];
160 fread(buf2, 32, max, the_file);
161
162 // Determine number of files contained
163 for (i=0, num_files=0; i<max; i++)
164 if (buf2[i*32] == 1)
165 num_files++;
166
167 if (!num_files)
168 return false;
169
170 // Construct file information array
171 file_info = new FileInfo[num_files];
172 for (i=0, j=0; i<max; i++)
173 if (buf2[i*32] == 1) {
174 memcpy(file_info[j].name, buf2+i*32+16, 16);
175
176 // Strip trailing spaces
177 file_info[j].name[16] = 0x20;
178 p = file_info[j].name + 16;
179 while (*p-- == 0x20) ;
180 p[2] = 0;
181
182 file_info[j].type = FTYPE_PRG;
183 file_info[j].sa_lo = buf2[i*32+2];
184 file_info[j].sa_hi = buf2[i*32+3];
185 file_info[j].offset = (buf2[i*32+11] << 24) | (buf2[i*32+10] << 16) | (buf2[i*32+9] << 8) | buf2[i*32+8];
186 file_info[j].length = ((buf2[i*32+5] << 8) | buf2[i*32+4]) - ((buf2[i*32+3] << 8) | buf2[i*32+2]);
187 j++;
188 }
189
190 delete[] buf2;
191 return true;
192 }
193
194
195 /*
196 * Parse LYNX file and construct FileInfo array
197 */
198
199 bool T64Drive::parse_lynx_file(void)
200 {
201 uint8 *p;
202 int dir_blocks, cur_offset, num_blocks, last_block, i;
203 char type_char;
204
205 // Dummy directory title
206 strcpy(dir_title, "LYNX ARCHIVE ");
207
208 // Read header and get number of directory blocks and files contained
209 fseek(the_file, 0x60, SEEK_SET);
210 fscanf(the_file, "%d", &dir_blocks);
211 while (fgetc(the_file) != 0x0d)
212 if (feof(the_file))
213 return false;
214 fscanf(the_file, "%d\015", &num_files);
215
216 // Construct file information array
217 file_info = new FileInfo[num_files];
218 cur_offset = dir_blocks * 254;
219 for (i=0; i<num_files; i++) {
220
221 // Read file name
222 fread(file_info[i].name, 16, 1, the_file);
223
224 // Strip trailing shift-spaces
225 file_info[i].name[16] = 0xa0;
226 p = (uint8 *)file_info[i].name + 16;
227 while (*p-- == 0xa0) ;
228 p[2] = 0;
229
230 // Read file length and type
231 fscanf(the_file, "\015%d\015%c\015%d\015", &num_blocks, &type_char, &last_block);
232
233 switch (type_char) {
234 case 'S':
235 file_info[i].type = FTYPE_SEQ;
236 break;
237 case 'U':
238 file_info[i].type = FTYPE_USR;
239 break;
240 case 'R':
241 file_info[i].type = FTYPE_REL;
242 break;
243 default:
244 file_info[i].type = FTYPE_PRG;
245 break;
246 }
247 file_info[i].sa_lo = 0; // Only used for .t64 files
248 file_info[i].sa_hi = 0;
249 file_info[i].offset = cur_offset;
250 file_info[i].length = (num_blocks-1) * 254 + last_block;
251
252 cur_offset += num_blocks * 254;
253 }
254
255 return true;
256 }
257
258
259 /*
260 * Open channel
261 */
262
263 uint8 T64Drive::Open(int channel, char *filename)
264 {
265 set_error(ERR_OK);
266
267 // Channel 15: Execute file name as command
268 if (channel == 15) {
269 execute_command(filename);
270 return ST_OK;
271 }
272
273 // Close previous file if still open
274 if (file[channel]) {
275 fclose(file[channel]);
276 file[channel] = NULL;
277 }
278
279 if (filename[0] == '#') {
280 set_error(ERR_NOCHANNEL);
281 return ST_OK;
282 }
283
284 if (the_file == NULL) {
285 set_error(ERR_NOTREADY);
286 return ST_OK;
287 }
288
289 if (filename[0] == '$')
290 return open_directory(channel, filename+1);
291
292 return open_file(channel, filename);
293 }
294
295
296 /*
297 * Open file
298 */
299
300 uint8 T64Drive::open_file(int channel, char *filename)
301 {
302 char plainname[NAMEBUF_LENGTH];
303 int filemode = FMODE_READ;
304 int filetype = FTYPE_PRG;
305 int num;
306
307 convert_filename(filename, plainname, &filemode, &filetype);
308
309 // Channel 0 is READ PRG, channel 1 is WRITE PRG
310 if (!channel) {
311 filemode = FMODE_READ;
312 filetype = FTYPE_PRG;
313 }
314 if (channel == 1) {
315 filemode = FMODE_WRITE;
316 filetype = FTYPE_PRG;
317 }
318
319 // Allow only read accesses
320 if (filemode != FMODE_READ) {
321 set_error(ERR_WRITEPROTECT);
322 return ST_OK;
323 }
324
325 // Find file
326 if (find_first_file(plainname, filetype, &num)) {
327
328 // Open temporary file
329 if ((file[channel] = tmpfile()) != NULL) {
330
331 // Write load address (.t64 only)
332 if (!is_lynx) {
333 fwrite(&file_info[num].sa_lo, 1, 1, file[channel]);
334 fwrite(&file_info[num].sa_hi, 1, 1, file[channel]);
335 }
336
337 // Copy file contents from .t64 file to temp file
338 uint8 *buf = new uint8[file_info[num].length];
339 fseek(the_file, file_info[num].offset, SEEK_SET);
340 fread(buf, file_info[num].length, 1, the_file);
341 fwrite(buf, file_info[num].length, 1, file[channel]);
342 rewind(file[channel]);
343 delete[] buf;
344
345 if (filemode == FMODE_READ) // Read and buffer first byte
346 read_char[channel] = fgetc(file[channel]);
347 }
348 } else
349 set_error(ERR_FILENOTFOUND);
350
351 return ST_OK;
352 }
353
354
355 /*
356 * Analyze file name, get access mode and type
357 */
358
359 void T64Drive::convert_filename(char *srcname, char *destname, int *filemode, int *filetype)
360 {
361 char *p;
362
363 // Search for ':', p points to first character after ':'
364 if ((p = strchr(srcname, ':')) != NULL)
365 p++;
366 else
367 p = srcname;
368
369 // Remaining string -> destname
370 strncpy(destname, p, NAMEBUF_LENGTH);
371
372 // Search for ','
373 p = destname;
374 while (*p && (*p != ',')) p++;
375
376 // Look for mode parameters seperated by ','
377 p = destname;
378 while ((p = strchr(p, ',')) != NULL) {
379
380 // Cut string after the first ','
381 *p++ = 0;
382
383 switch (*p) {
384 case 'P':
385 *filetype = FTYPE_PRG;
386 break;
387 case 'S':
388 *filetype = FTYPE_SEQ;
389 break;
390 case 'U':
391 *filetype = FTYPE_USR;
392 break;
393 case 'L':
394 *filetype = FTYPE_REL;
395 break;
396 case 'R':
397 *filemode = FMODE_READ;
398 break;
399 case 'W':
400 *filemode = FMODE_WRITE;
401 break;
402 case 'A':
403 *filemode = FMODE_APPEND;
404 break;
405 }
406 }
407 }
408
409
410 /*
411 * Find first file matching wildcard pattern
412 */
413
414 // Return true if name 'n' matches pattern 'p'
415 static bool match(char *p, char *n)
416 {
417 if (!*p) // Null pattern matches everything
418 return true;
419
420 do {
421 if (*p == '*') // Wildcard '*' matches all following characters
422 return true;
423 if ((*p != *n) && (*p != '?')) // Wildcard '?' matches single character
424 return false;
425 p++; n++;
426 } while (*p);
427
428 return !(*n);
429 }
430
431 bool T64Drive::find_first_file(char *name, int type, int *num)
432 {
433 for (int i=0; i<num_files; i++)
434 if (match(name, file_info[i].name) && type == file_info[i].type) {
435 *num = i;
436 return true;
437 }
438
439 return false;
440 }
441
442
443 /*
444 * Open directory, create temporary file
445 */
446
447 uint8 T64Drive::open_directory(int channel, char *filename)
448 {
449 char buf[] = "\001\004\001\001\0\0\022\042 \042 00 2A";
450 char str[NAMEBUF_LENGTH];
451 char pattern[NAMEBUF_LENGTH];
452 char *p, *q;
453 int i, num;
454 int filemode;
455 int filetype;
456
457 // Special treatment for "$0"
458 if (strlen(filename) == 1 && filename[0] == '0')
459 filename += 1;
460
461 // Convert filename ('$' already stripped), filemode/type are ignored
462 convert_filename(filename, pattern, &filemode, &filetype);
463
464 // Create temporary file
465 if ((file[channel] = tmpfile()) == NULL)
466 return ST_OK;
467
468 // Create directory title
469 p = &buf[8];
470 for (i=0; i<16 && dir_title[i]; i++)
471 *p++ = dir_title[i];
472 fwrite(buf, 1, 32, file[channel]);
473
474 // Create and write one line for every directory entry
475 for (num=0; num<num_files; num++) {
476
477 // Include only files matching the pattern
478 if (match(pattern, file_info[num].name)) {
479
480 // Clear line with spaces and terminate with null byte
481 memset(buf, ' ', 31);
482 buf[31] = 0;
483
484 p = buf;
485 *p++ = 0x01; // Dummy line link
486 *p++ = 0x01;
487
488 // Calculate size in blocks (254 bytes each)
489 i = (file_info[num].length + 254) / 254;
490 *p++ = i & 0xff;
491 *p++ = (i >> 8) & 0xff;
492
493 p++;
494 if (i < 10) p++; // Less than 10: add one space
495 if (i < 100) p++; // Less than 100: add another space
496
497 // Convert and insert file name
498 strcpy(str, file_info[num].name);
499 *p++ = '\"';
500 q = p;
501 for (i=0; i<16 && str[i]; i++)
502 *q++ = str[i];
503 *q++ = '\"';
504 p += 18;
505
506 // File type
507 switch (file_info[num].type) {
508 case FTYPE_PRG:
509 *p++ = 'P';
510 *p++ = 'R';
511 *p++ = 'G';
512 break;
513 case FTYPE_SEQ:
514 *p++ = 'S';
515 *p++ = 'E';
516 *p++ = 'Q';
517 break;
518 case FTYPE_USR:
519 *p++ = 'U';
520 *p++ = 'S';
521 *p++ = 'R';
522 break;
523 case FTYPE_REL:
524 *p++ = 'R';
525 *p++ = 'E';
526 *p++ = 'L';
527 break;
528 default:
529 *p++ = '?';
530 *p++ = '?';
531 *p++ = '?';
532 break;
533 }
534
535 // Write line
536 fwrite(buf, 1, 32, file[channel]);
537 }
538 }
539
540 // Final line
541 fwrite("\001\001\0\0BLOCKS FREE. \0\0", 1, 32, file[channel]);
542
543 // Rewind file for reading and read first byte
544 rewind(file[channel]);
545 read_char[channel] = fgetc(file[channel]);
546
547 return ST_OK;
548 }
549
550
551 /*
552 * Close channel
553 */
554
555 uint8 T64Drive::Close(int channel)
556 {
557 if (channel == 15) {
558 close_all_channels();
559 return ST_OK;
560 }
561
562 if (file[channel]) {
563 fclose(file[channel]);
564 file[channel] = NULL;
565 }
566
567 return ST_OK;
568 }
569
570
571 /*
572 * Close all channels
573 */
574
575 void T64Drive::close_all_channels(void)
576 {
577 for (int i=0; i<15; i++)
578 Close(i);
579
580 cmd_len = 0;
581 }
582
583
584 /*
585 * Read from channel
586 */
587
588 uint8 T64Drive::Read(int channel, uint8 *byte)
589 {
590 int c;
591
592 // Channel 15: Error channel
593 if (channel == 15) {
594 *byte = *error_ptr++;
595
596 if (*byte != '\r')
597 return ST_OK;
598 else { // End of message
599 set_error(ERR_OK);
600 return ST_EOF;
601 }
602 }
603
604 if (!file[channel]) return ST_READ_TIMEOUT;
605
606 // Get char from buffer and read next
607 *byte = read_char[channel];
608 c = fgetc(file[channel]);
609 if (c == EOF)
610 return ST_EOF;
611 else {
612 read_char[channel] = c;
613 return ST_OK;
614 }
615 }
616
617
618 /*
619 * Write to channel
620 */
621
622 uint8 T64Drive::Write(int channel, uint8 byte, bool eoi)
623 {
624 // Channel 15: Collect chars and execute command on EOI
625 if (channel == 15) {
626 if (cmd_len >= 40)
627 return ST_TIMEOUT;
628
629 cmd_buffer[cmd_len++] = byte;
630
631 if (eoi) {
632 cmd_buffer[cmd_len] = 0;
633 cmd_len = 0;
634 execute_command(cmd_buffer);
635 }
636 return ST_OK;
637 }
638
639 if (!file[channel])
640 set_error(ERR_FILENOTOPEN);
641 else
642 set_error(ERR_WRITEPROTECT);
643
644 return ST_TIMEOUT;
645 }
646
647
648 /*
649 * Execute command string
650 */
651
652 void T64Drive::execute_command(char *command)
653 {
654 switch (command[0]) {
655 case 'I':
656 close_all_channels();
657 set_error(ERR_OK);
658 break;
659
660 case 'U':
661 if ((command[1] & 0x0f) == 0x0a) {
662 Reset();
663 } else
664 set_error(ERR_SYNTAX30);
665 break;
666
667 case 'G':
668 if (command[1] != ':')
669 set_error(ERR_SYNTAX30);
670 else
671 cht64_cmd(&command[2]);
672 break;
673
674 default:
675 set_error(ERR_SYNTAX30);
676 }
677 }
678
679
680 /*
681 * Execute 'G' command
682 */
683
684 void T64Drive::cht64_cmd(char *t64name)
685 {
686 char str[NAMEBUF_LENGTH];
687 char *p = str;
688
689 // Convert .t64 file name
690 for (int i=0; i<NAMEBUF_LENGTH && (*p++ = conv_from_64(*t64name++, false)); i++) ;
691
692 close_all_channels();
693
694 // G:. resets the .t64 file name to its original setting
695 if (str[0] == '.' && str[1] == 0)
696 open_close_t64_file(orig_t64_name);
697 else
698 open_close_t64_file(str);
699
700 if (the_file == NULL)
701 set_error(ERR_NOTREADY);
702 }
703
704
705 /*
706 * Reset drive
707 */
708
709 void T64Drive::Reset(void)
710 {
711 close_all_channels();
712 cmd_len = 0;
713 set_error(ERR_STARTUP);
714 }
715
716
717 /*
718 * Conversion PETSCII->ASCII
719 */
720
721 uint8 T64Drive::conv_from_64(uint8 c, bool map_slash)
722 {
723 if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z'))
724 return c ^ 0x20;
725 if ((c >= 0xc1) && (c <= 0xda))
726 return c ^ 0x80;
727 if ((c == '/') && map_slash && ThePrefs.MapSlash)
728 return '\\';
729 return c;
730 }