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 (19 years ago) by cebix
Branch: MAIN
Changes since 1.3: +4 -4 lines
Log Message:
added D64 write support

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 /*
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 uint8 *p;
135 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 uint8 T64Drive::Open(int channel, const uint8 *name, int name_len)
250 {
251 set_error(ERR_OK);
252
253 // Channel 15: Execute file name as command
254 if (channel == 15) {
255 execute_cmd(name, name_len);
256 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 if (name[0] == '#') {
266 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 if (name[0] == '$')
276 return open_directory(channel, name + 1, name_len - 1);
277
278 return open_file(channel, name, name_len);
279 }
280
281
282 /*
283 * Open file
284 */
285
286 uint8 T64Drive::open_file(int channel, const uint8 *name, int name_len)
287 {
288 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 }
309
310 // Allow only read accesses
311 if (writing) {
312 set_error(ERR_WRITEPROTECT);
313 return ST_OK;
314 }
315
316 // Relative files are not supported
317 if (type == FTYPE_REL) {
318 set_error(ERR_UNIMPLEMENTED);
319 return ST_OK;
320 }
321
322 // Find file
323 int num;
324 if (find_first_file(plain_name, plain_name_len, num)) {
325
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 if (mode == FMODE_READ) // Read and buffer first byte
344 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 static bool match(const uint8 *p, int p_len, const uint8 *n)
359 {
360 while (p_len-- > 0) {
361 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 }
367
368 return *n == 0;
369 }
370
371 bool T64Drive::find_first_file(const uint8 *pattern, int pattern_len, int &num)
372 {
373 for (int i=0; i<num_files; i++) {
374 if (match(pattern, pattern_len, file_info[i].name)) {
375 num = i;
376 return true;
377 }
378 }
379 return false;
380 }
381
382
383 /*
384 * Open directory, create temporary file
385 */
386
387 uint8 T64Drive::open_directory(int channel, const uint8 *pattern, int pattern_len)
388 {
389 // Special treatment for "$0"
390 if (pattern[0] == '0' && pattern_len == 1) {
391 pattern++;
392 pattern_len--;
393 }
394
395 // 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
403 // Create temporary file
404 if ((file[channel] = tmpfile()) == NULL)
405 return ST_OK;
406
407 // Create directory title
408 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 fwrite(buf, 1, 32, file[channel]);
412
413 // Create and write one line for every directory entry
414 for (int num=0; num<num_files; num++) {
415
416 // Include only files matching the pattern
417 if (pattern_len == 0 || match(pattern, pattern_len, file_info[num].name)) {
418
419 // Clear line with spaces and terminate with null byte
420 memset(buf, ' ', 31);
421 buf[31] = 0;
422
423 uint8 *p = buf;
424 *p++ = 0x01; // Dummy line link
425 *p++ = 0x01;
426
427 // Calculate size in blocks (254 bytes each)
428 int n = (file_info[num].length + 254) / 254;
429 *p++ = n & 0xff;
430 *p++ = (n >> 8) & 0xff;
431
432 p++;
433 if (n < 10) p++; // Less than 10: add one space
434 if (n < 100) p++; // Less than 100: add another space
435
436 // Convert and insert file name
437 uint8 str[NAMEBUF_LENGTH];
438 memcpy(str, file_info[num].name, 17);
439 *p++ = '\"';
440 uint8 *q = p;
441 for (int i=0; i<16 && str[i]; i++)
442 *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 uint8 T64Drive::Read(int channel, uint8 &byte)
529 {
530 int c;
531
532 // Channel 15: Error channel
533 if (channel == 15) {
534 byte = *error_ptr++;
535
536 if (byte != '\r')
537 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 byte = read_char[channel];
548 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 if (cmd_len >= 58)
567 return ST_TIMEOUT;
568
569 cmd_buf[cmd_len++] = byte;
570
571 if (eoi) {
572 execute_cmd(cmd_buf, cmd_len);
573 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 * Execute drive commands
589 */
590
591 // 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 {
596 // 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
603 // 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
609 set_error(ERR_WRITEPROTECT);
610 }
611
612 // INITIALIZE
613 void T64Drive::initialize_cmd(void)
614 {
615 close_all_channels();
616 }
617
618 // VALIDATE
619 void T64Drive::validate_cmd(void)
620 {
621 }
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 }