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

# User Rev Content
1 cebix 1.1 /*
2     * 1541t64.cpp - 1541 emulation in .t64/LYNX file
3     *
4 cebix 1.2 * Frodo (C) 1994-1997,2002-2003 Christian Bauer
5 cebix 1.1 *
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     }