ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/Frodo4/Src/1541t64.cpp
Revision: 1.4
Committed: 2004-01-11T14:03:29Z (20 years, 9 months ago) by cebix
Branch: MAIN
Changes since 1.3: +4 -4 lines
Log Message:
added D64 write support

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     /*
47     * Constructor: Prepare emulation
48     */
49    
50     T64Drive::T64Drive(IEC *iec, char *filepath) : Drive(iec)
51     {
52     the_file = NULL;
53     file_info = NULL;
54    
55     Ready = false;
56     strcpy(orig_t64_name, filepath);
57     for (int i=0; i<16; i++)
58     file[i] = NULL;
59    
60     // Open .t64 file
61     open_close_t64_file(filepath);
62     if (the_file != NULL) {
63     Reset();
64     Ready = true;
65     }
66     }
67    
68    
69     /*
70     * Destructor
71     */
72    
73     T64Drive::~T64Drive()
74     {
75     // Close .t64 file
76     open_close_t64_file("");
77    
78     Ready = false;
79     }
80    
81    
82     /*
83     * Open/close the .t64/LYNX file
84     */
85    
86     void T64Drive::open_close_t64_file(char *t64name)
87     {
88     uint8 buf[64];
89     bool parsed_ok = false;
90    
91     // Close old .t64, if open
92     if (the_file != NULL) {
93     close_all_channels();
94     fclose(the_file);
95     the_file = NULL;
96     delete[] file_info;
97     file_info = NULL;
98     }
99    
100     // Open new .t64 file
101     if (t64name[0]) {
102     if ((the_file = fopen(t64name, "rb")) != NULL) {
103    
104     // Check file ID
105     fread(&buf, 64, 1, the_file);
106     if (buf[0] == 0x43 && buf[1] == 0x36 && buf[2] == 0x34) {
107     is_lynx = false;
108     parsed_ok = parse_t64_file();
109     } else if (buf[0x3c] == 0x4c && buf[0x3d] == 0x59 && buf[0x3e] == 0x4e && buf[0x3f] == 0x58) {
110     is_lynx = true;
111     parsed_ok = parse_lynx_file();
112     }
113    
114     if (!parsed_ok) {
115     fclose(the_file);
116     the_file = NULL;
117     delete[] file_info;
118     file_info = NULL;
119     return;
120     }
121     }
122     }
123     }
124    
125    
126     /*
127     * Parse .t64 file and construct FileInfo array
128     */
129    
130     bool T64Drive::parse_t64_file(void)
131     {
132     uint8 buf[32];
133     uint8 *buf2;
134 cebix 1.3 uint8 *p;
135 cebix 1.1 int max, i, j;
136    
137     // Read header and get maximum number of files contained
138     fseek(the_file, 32, SEEK_SET);
139     fread(&buf, 32, 1, the_file);
140     max = (buf[3] << 8) | buf[2];
141    
142     memcpy(dir_title, buf+8, 16);
143    
144     // Allocate buffer for file records and read them
145     buf2 = new uint8[max*32];
146     fread(buf2, 32, max, the_file);
147    
148     // Determine number of files contained
149     for (i=0, num_files=0; i<max; i++)
150     if (buf2[i*32] == 1)
151     num_files++;
152    
153     if (!num_files)
154     return false;
155    
156     // Construct file information array
157     file_info = new FileInfo[num_files];
158     for (i=0, j=0; i<max; i++)
159     if (buf2[i*32] == 1) {
160     memcpy(file_info[j].name, buf2+i*32+16, 16);
161    
162     // Strip trailing spaces
163     file_info[j].name[16] = 0x20;
164     p = file_info[j].name + 16;
165     while (*p-- == 0x20) ;
166     p[2] = 0;
167    
168     file_info[j].type = FTYPE_PRG;
169     file_info[j].sa_lo = buf2[i*32+2];
170     file_info[j].sa_hi = buf2[i*32+3];
171     file_info[j].offset = (buf2[i*32+11] << 24) | (buf2[i*32+10] << 16) | (buf2[i*32+9] << 8) | buf2[i*32+8];
172     file_info[j].length = ((buf2[i*32+5] << 8) | buf2[i*32+4]) - ((buf2[i*32+3] << 8) | buf2[i*32+2]);
173     j++;
174     }
175    
176     delete[] buf2;
177     return true;
178     }
179    
180    
181     /*
182     * Parse LYNX file and construct FileInfo array
183     */
184    
185     bool T64Drive::parse_lynx_file(void)
186     {
187     uint8 *p;
188     int dir_blocks, cur_offset, num_blocks, last_block, i;
189     char type_char;
190    
191     // Dummy directory title
192     strcpy(dir_title, "LYNX ARCHIVE ");
193    
194     // Read header and get number of directory blocks and files contained
195     fseek(the_file, 0x60, SEEK_SET);
196     fscanf(the_file, "%d", &dir_blocks);
197     while (fgetc(the_file) != 0x0d)
198     if (feof(the_file))
199     return false;
200     fscanf(the_file, "%d\015", &num_files);
201    
202     // Construct file information array
203     file_info = new FileInfo[num_files];
204     cur_offset = dir_blocks * 254;
205     for (i=0; i<num_files; i++) {
206    
207     // Read file name
208     fread(file_info[i].name, 16, 1, the_file);
209    
210     // Strip trailing shift-spaces
211     file_info[i].name[16] = 0xa0;
212     p = (uint8 *)file_info[i].name + 16;
213     while (*p-- == 0xa0) ;
214     p[2] = 0;
215    
216     // Read file length and type
217     fscanf(the_file, "\015%d\015%c\015%d\015", &num_blocks, &type_char, &last_block);
218    
219     switch (type_char) {
220     case 'S':
221     file_info[i].type = FTYPE_SEQ;
222     break;
223     case 'U':
224     file_info[i].type = FTYPE_USR;
225     break;
226     case 'R':
227     file_info[i].type = FTYPE_REL;
228     break;
229     default:
230     file_info[i].type = FTYPE_PRG;
231     break;
232     }
233     file_info[i].sa_lo = 0; // Only used for .t64 files
234     file_info[i].sa_hi = 0;
235     file_info[i].offset = cur_offset;
236     file_info[i].length = (num_blocks-1) * 254 + last_block;
237    
238     cur_offset += num_blocks * 254;
239     }
240    
241     return true;
242     }
243    
244    
245     /*
246     * Open channel
247     */
248    
249 cebix 1.3 uint8 T64Drive::Open(int channel, const uint8 *name, int name_len)
250 cebix 1.1 {
251     set_error(ERR_OK);
252    
253     // Channel 15: Execute file name as command
254     if (channel == 15) {
255 cebix 1.3 execute_cmd(name, name_len);
256 cebix 1.1 return ST_OK;
257     }
258    
259     // Close previous file if still open
260     if (file[channel]) {
261     fclose(file[channel]);
262     file[channel] = NULL;
263     }
264    
265 cebix 1.3 if (name[0] == '#') {
266 cebix 1.1 set_error(ERR_NOCHANNEL);
267     return ST_OK;
268     }
269    
270     if (the_file == NULL) {
271     set_error(ERR_NOTREADY);
272     return ST_OK;
273     }
274    
275 cebix 1.3 if (name[0] == '$')
276     return open_directory(channel, name + 1, name_len - 1);
277 cebix 1.1
278 cebix 1.3 return open_file(channel, name, name_len);
279 cebix 1.1 }
280    
281    
282     /*
283     * Open file
284     */
285    
286 cebix 1.3 uint8 T64Drive::open_file(int channel, const uint8 *name, int name_len)
287 cebix 1.1 {
288 cebix 1.3 uint8 plain_name[NAMEBUF_LENGTH];
289     int plain_name_len;
290     int mode = FMODE_READ;
291     int type = FTYPE_PRG;
292     int rec_len;
293     parse_file_name(name, name_len, plain_name, plain_name_len, mode, type, rec_len);
294    
295     // Channel 0 is READ, channel 1 is WRITE
296     if (channel == 0 || channel == 1) {
297     mode = channel ? FMODE_WRITE : FMODE_READ;
298     if (type == FTYPE_DEL)
299     type = FTYPE_PRG;
300     }
301    
302     bool writing = (mode == FMODE_WRITE || mode == FMODE_APPEND);
303    
304     // Wildcards are only allowed on reading
305     if (writing && (strchr((const char *)plain_name, '*') || strchr((const char *)plain_name, '?'))) {
306     set_error(ERR_SYNTAX33);
307     return ST_OK;
308 cebix 1.1 }
309    
310     // Allow only read accesses
311 cebix 1.3 if (writing) {
312 cebix 1.1 set_error(ERR_WRITEPROTECT);
313     return ST_OK;
314     }
315    
316 cebix 1.3 // Relative files are not supported
317     if (type == FTYPE_REL) {
318     set_error(ERR_UNIMPLEMENTED);
319     return ST_OK;
320     }
321    
322 cebix 1.1 // Find file
323 cebix 1.3 int num;
324     if (find_first_file(plain_name, plain_name_len, num)) {
325 cebix 1.1
326     // Open temporary file
327     if ((file[channel] = tmpfile()) != NULL) {
328    
329     // Write load address (.t64 only)
330     if (!is_lynx) {
331     fwrite(&file_info[num].sa_lo, 1, 1, file[channel]);
332     fwrite(&file_info[num].sa_hi, 1, 1, file[channel]);
333     }
334    
335     // Copy file contents from .t64 file to temp file
336     uint8 *buf = new uint8[file_info[num].length];
337     fseek(the_file, file_info[num].offset, SEEK_SET);
338     fread(buf, file_info[num].length, 1, the_file);
339     fwrite(buf, file_info[num].length, 1, file[channel]);
340     rewind(file[channel]);
341     delete[] buf;
342    
343 cebix 1.3 if (mode == FMODE_READ) // Read and buffer first byte
344 cebix 1.1 read_char[channel] = fgetc(file[channel]);
345     }
346     } else
347     set_error(ERR_FILENOTFOUND);
348    
349     return ST_OK;
350     }
351    
352    
353     /*
354     * Find first file matching wildcard pattern
355     */
356    
357     // Return true if name 'n' matches pattern 'p'
358 cebix 1.3 static bool match(const uint8 *p, int p_len, const uint8 *n)
359 cebix 1.1 {
360 cebix 1.3 while (p_len-- > 0) {
361 cebix 1.1 if (*p == '*') // Wildcard '*' matches all following characters
362     return true;
363     if ((*p != *n) && (*p != '?')) // Wildcard '?' matches single character
364     return false;
365     p++; n++;
366 cebix 1.3 }
367 cebix 1.1
368 cebix 1.3 return *n == 0;
369 cebix 1.1 }
370    
371 cebix 1.3 bool T64Drive::find_first_file(const uint8 *pattern, int pattern_len, int &num)
372 cebix 1.1 {
373 cebix 1.3 for (int i=0; i<num_files; i++) {
374     if (match(pattern, pattern_len, file_info[i].name)) {
375     num = i;
376 cebix 1.1 return true;
377     }
378 cebix 1.3 }
379 cebix 1.1 return false;
380     }
381    
382    
383     /*
384     * Open directory, create temporary file
385     */
386    
387 cebix 1.3 uint8 T64Drive::open_directory(int channel, const uint8 *pattern, int pattern_len)
388 cebix 1.1 {
389     // Special treatment for "$0"
390 cebix 1.3 if (pattern[0] == '0' && pattern_len == 1) {
391     pattern++;
392     pattern_len--;
393     }
394 cebix 1.1
395 cebix 1.3 // Skip everything before the ':' in the pattern
396     uint8 *t = (uint8 *)memchr(pattern, ':', pattern_len);
397     if (t) {
398     t++;
399     pattern_len -= t - pattern;
400     pattern = t;
401     }
402 cebix 1.1
403     // Create temporary file
404     if ((file[channel] = tmpfile()) == NULL)
405     return ST_OK;
406    
407     // Create directory title
408 cebix 1.3 uint8 buf[] = "\001\004\001\001\0\0\022\042 \042 00 2A";
409     for (int i=0; i<16 && dir_title[i]; i++)
410     buf[i + 8] = dir_title[i];
411 cebix 1.1 fwrite(buf, 1, 32, file[channel]);
412    
413     // Create and write one line for every directory entry
414 cebix 1.3 for (int num=0; num<num_files; num++) {
415 cebix 1.1
416     // Include only files matching the pattern
417 cebix 1.3 if (pattern_len == 0 || match(pattern, pattern_len, file_info[num].name)) {
418 cebix 1.1
419     // Clear line with spaces and terminate with null byte
420     memset(buf, ' ', 31);
421     buf[31] = 0;
422    
423 cebix 1.3 uint8 *p = buf;
424 cebix 1.1 *p++ = 0x01; // Dummy line link
425     *p++ = 0x01;
426    
427     // Calculate size in blocks (254 bytes each)
428 cebix 1.3 int n = (file_info[num].length + 254) / 254;
429     *p++ = n & 0xff;
430     *p++ = (n >> 8) & 0xff;
431 cebix 1.1
432     p++;
433 cebix 1.3 if (n < 10) p++; // Less than 10: add one space
434     if (n < 100) p++; // Less than 100: add another space
435 cebix 1.1
436     // Convert and insert file name
437 cebix 1.3 uint8 str[NAMEBUF_LENGTH];
438     memcpy(str, file_info[num].name, 17);
439 cebix 1.1 *p++ = '\"';
440 cebix 1.3 uint8 *q = p;
441     for (int i=0; i<16 && str[i]; i++)
442 cebix 1.1 *q++ = str[i];
443     *q++ = '\"';
444     p += 18;
445    
446     // File type
447     switch (file_info[num].type) {
448     case FTYPE_PRG:
449     *p++ = 'P';
450     *p++ = 'R';
451     *p++ = 'G';
452     break;
453     case FTYPE_SEQ:
454     *p++ = 'S';
455     *p++ = 'E';
456     *p++ = 'Q';
457     break;
458     case FTYPE_USR:
459     *p++ = 'U';
460     *p++ = 'S';
461     *p++ = 'R';
462     break;
463     case FTYPE_REL:
464     *p++ = 'R';
465     *p++ = 'E';
466     *p++ = 'L';
467     break;
468     default:
469     *p++ = '?';
470     *p++ = '?';
471     *p++ = '?';
472     break;
473     }
474    
475     // Write line
476     fwrite(buf, 1, 32, file[channel]);
477     }
478     }
479    
480     // Final line
481     fwrite("\001\001\0\0BLOCKS FREE. \0\0", 1, 32, file[channel]);
482    
483     // Rewind file for reading and read first byte
484     rewind(file[channel]);
485     read_char[channel] = fgetc(file[channel]);
486    
487     return ST_OK;
488     }
489    
490    
491     /*
492     * Close channel
493     */
494    
495     uint8 T64Drive::Close(int channel)
496     {
497     if (channel == 15) {
498     close_all_channels();
499     return ST_OK;
500     }
501    
502     if (file[channel]) {
503     fclose(file[channel]);
504     file[channel] = NULL;
505     }
506    
507     return ST_OK;
508     }
509    
510    
511     /*
512     * Close all channels
513     */
514    
515     void T64Drive::close_all_channels(void)
516     {
517     for (int i=0; i<15; i++)
518     Close(i);
519    
520     cmd_len = 0;
521     }
522    
523    
524     /*
525     * Read from channel
526     */
527    
528 cebix 1.4 uint8 T64Drive::Read(int channel, uint8 &byte)
529 cebix 1.1 {
530     int c;
531    
532     // Channel 15: Error channel
533     if (channel == 15) {
534 cebix 1.4 byte = *error_ptr++;
535 cebix 1.1
536 cebix 1.4 if (byte != '\r')
537 cebix 1.1 return ST_OK;
538     else { // End of message
539     set_error(ERR_OK);
540     return ST_EOF;
541     }
542     }
543    
544     if (!file[channel]) return ST_READ_TIMEOUT;
545    
546     // Get char from buffer and read next
547 cebix 1.4 byte = read_char[channel];
548 cebix 1.1 c = fgetc(file[channel]);
549     if (c == EOF)
550     return ST_EOF;
551     else {
552     read_char[channel] = c;
553     return ST_OK;
554     }
555     }
556    
557    
558     /*
559     * Write to channel
560     */
561    
562     uint8 T64Drive::Write(int channel, uint8 byte, bool eoi)
563     {
564     // Channel 15: Collect chars and execute command on EOI
565     if (channel == 15) {
566 cebix 1.3 if (cmd_len >= 58)
567 cebix 1.1 return ST_TIMEOUT;
568    
569 cebix 1.3 cmd_buf[cmd_len++] = byte;
570 cebix 1.1
571     if (eoi) {
572 cebix 1.3 execute_cmd(cmd_buf, cmd_len);
573 cebix 1.1 cmd_len = 0;
574     }
575     return ST_OK;
576     }
577    
578     if (!file[channel])
579     set_error(ERR_FILENOTOPEN);
580     else
581     set_error(ERR_WRITEPROTECT);
582    
583     return ST_TIMEOUT;
584     }
585    
586    
587     /*
588 cebix 1.3 * Execute drive commands
589 cebix 1.1 */
590    
591 cebix 1.3 // RENAME:new=old
592     // ^ ^
593     // new_file old_file
594     void T64Drive::rename_cmd(const uint8 *new_file, int new_file_len, const uint8 *old_file, int old_file_len)
595 cebix 1.1 {
596 cebix 1.3 // Check if destination file is already present
597     int num;
598     if (find_first_file(new_file, new_file_len, num)) {
599     set_error(ERR_FILEEXISTS);
600     return;
601     }
602 cebix 1.1
603 cebix 1.3 // Check if source file is present
604     if (!find_first_file(old_file, old_file_len, num)) {
605     set_error(ERR_FILENOTFOUND);
606     return;
607     }
608 cebix 1.1
609 cebix 1.3 set_error(ERR_WRITEPROTECT);
610 cebix 1.1 }
611    
612 cebix 1.3 // INITIALIZE
613     void T64Drive::initialize_cmd(void)
614 cebix 1.1 {
615     close_all_channels();
616 cebix 1.3 }
617 cebix 1.1
618 cebix 1.3 // VALIDATE
619     void T64Drive::validate_cmd(void)
620     {
621 cebix 1.1 }
622    
623    
624     /*
625     * Reset drive
626     */
627    
628     void T64Drive::Reset(void)
629     {
630     close_all_channels();
631     cmd_len = 0;
632     set_error(ERR_STARTUP);
633     }