ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/Frodo4/Src/1541t64.cpp
Revision: 1.6
Committed: 2004-01-14T16:54:46Z (20 years, 10 months ago) by cebix
Branch: MAIN
Changes since 1.5: +331 -220 lines
Log Message:
- backported T64 code from Frodo V5, handles .P00 files
- T64Drive renamed to ArchDrive
- PETSCII characters are uint8s, not chars

File Contents

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