/* * 1541d64.cpp - 1541 emulation in .d64 file * * Frodo (C) 1994-1997,2002 Christian Bauer * * * Incompatibilities: * ------------------ * * - Only read accesses possible * - Not all commands implemented * - The .d64 error info is read, but unused */ #include "sysdeps.h" #include "1541d64.h" #include "IEC.h" #include "Prefs.h" // Channel modes (IRC users listen up :-) enum { CHMOD_FREE, // Channel free CHMOD_COMMAND, // Command/error channel CHMOD_DIRECTORY, // Reading directory CHMOD_FILE, // Sequential file open CHMOD_DIRECT // Direct buffer access ('#') }; // Access modes enum { FMODE_READ, FMODE_WRITE, FMODE_APPEND }; // File types enum { FTYPE_PRG, FTYPE_SEQ, FTYPE_USR, FTYPE_REL }; // Number of tracks/sectors const int NUM_TRACKS = 35; const int NUM_SECTORS = 683; // Prototypes static bool match(uint8 *p, uint8 *n); /* * Constructor: Prepare emulation, open .d64 file */ D64Drive::D64Drive(IEC *iec, char *filepath) : Drive(iec) { the_file = NULL; ram = NULL; Ready = false; strcpy(orig_d64_name, filepath); for (int i=0; i<=14; i++) { chan_mode[i] = CHMOD_FREE; chan_buf[i] = NULL; } chan_mode[15] = CHMOD_COMMAND; // Open .d64 file open_close_d64_file(filepath); if (the_file != NULL) { // Allocate 1541 RAM ram = new uint8[0x800]; bam = (BAM *)(ram + 0x700); Reset(); Ready = true; } } /* * Destructor */ D64Drive::~D64Drive() { // Close .d64 file open_close_d64_file(""); delete[] ram; Ready = false; } /* * Open/close the .d64 file */ void D64Drive::open_close_d64_file(char *d64name) { long size; uint8 magic[4]; // Close old .d64, if open if (the_file != NULL) { close_all_channels(); fclose(the_file); the_file = NULL; } // Open new .d64 file if (d64name[0]) { if ((the_file = fopen(d64name, "rb")) != NULL) { // Check length fseek(the_file, 0, SEEK_END); if ((size = ftell(the_file)) < NUM_SECTORS * 256) { fclose(the_file); the_file = NULL; return; } // x64 image? rewind(the_file); fread(&magic, 4, 1, the_file); if (magic[0] == 0x43 && magic[1] == 0x15 && magic[2] == 0x41 && magic[3] == 0x64) image_header = 64; else image_header = 0; // Preset error info (all sectors no error) memset(error_info, 1, NUM_SECTORS); // Load sector error info from .d64 file, if present if (!image_header && size == NUM_SECTORS * 257) { fseek(the_file, NUM_SECTORS * 256, SEEK_SET); fread(&error_info, NUM_SECTORS, 1, the_file); } } } } /* * Open channel */ uint8 D64Drive::Open(int channel, char *filename) { set_error(ERR_OK); // Channel 15: execute file name as command if (channel == 15) { execute_command(filename); return ST_OK; } if (chan_mode[channel] != CHMOD_FREE) { set_error(ERR_NOCHANNEL); return ST_OK; } if (filename[0] == '$') if (channel) return open_file_ts(channel, 18, 0); else return open_directory(filename+1); if (filename[0] == '#') return open_direct(channel, filename); return open_file(channel, filename); } /* * Open file */ uint8 D64Drive::open_file(int channel, char *filename) { char plainname[256]; int filemode = FMODE_READ; int filetype = FTYPE_PRG; int track, sector; convert_filename(filename, plainname, &filemode, &filetype); // Channel 0 is READ PRG, channel 1 is WRITE PRG if (!channel) { filemode = FMODE_READ; filetype = FTYPE_PRG; } if (channel == 1) { filemode = FMODE_WRITE; filetype = FTYPE_PRG; } // Allow only read accesses if (filemode != FMODE_READ) { set_error(ERR_WRITEPROTECT); return ST_OK; } // Find file in directory and open it if (find_file(plainname, &track, §or)) return open_file_ts(channel, track, sector); else set_error(ERR_FILENOTFOUND); return ST_OK; } /* * Analyze file name, get access mode and type */ void D64Drive::convert_filename(char *srcname, char *destname, int *filemode, int *filetype) { char *p; // Search for ':', p points to first character after ':' if ((p = strchr(srcname, ':')) != NULL) p++; else p = srcname; // Remaining string -> destname strncpy(destname, p, NAMEBUF_LENGTH); // Search for ',' p = destname; while (*p && (*p != ',')) p++; // Look for mode parameters seperated by ',' p = destname; while ((p = strchr(p, ',')) != NULL) { // Cut string after the first ',' *p++ = 0; switch (*p) { case 'P': *filetype = FTYPE_PRG; break; case 'S': *filetype = FTYPE_SEQ; break; case 'U': *filetype = FTYPE_USR; break; case 'L': *filetype = FTYPE_REL; break; case 'R': *filemode = FMODE_READ; break; case 'W': *filemode = FMODE_WRITE; break; case 'A': *filemode = FMODE_APPEND; break; } } } /* * Search file in directory, find first track and sector * false: not found, true: found */ bool D64Drive::find_file(char *filename, int *track, int *sector) { int i, j; uint8 *p, *q; DirEntry *de; // Scan all directory blocks dir.next_track = bam->dir_track; dir.next_sector = bam->dir_sector; while (dir.next_track) { if (!read_sector(dir.next_track, dir.next_sector, (uint8 *) &dir.next_track)) return false; // Scan all 8 entries of a block for (j=0; j<8; j++) { de = &dir.entry[j]; *track = de->track; *sector = de->sector; if (de->type) { p = (uint8 *)filename; q = de->name; for (i=0; i<16 && *p; i++, p++, q++) { if (*p == '*') // Wildcard '*' matches all following characters return true; if (*p != *q) { if (*p != '?') goto next_entry; // Wildcard '?' matches single character if (*q == 0xa0) goto next_entry; } } if (i == 16 || *q == 0xa0) return true; } next_entry: ; } } return false; } /* * Open file given track/sector of first block */ uint8 D64Drive::open_file_ts(int channel, int track, int sector) { chan_buf[channel] = new uint8[256]; chan_mode[channel] = CHMOD_FILE; // On the next call to Read, the first block will be read chan_buf[channel][0] = track; chan_buf[channel][1] = sector; buf_len[channel] = 0; return ST_OK; } /* * Prepare directory as BASIC program (channel 0) */ const char type_char_1[] = "DSPUREERSELQGRL?"; const char type_char_2[] = "EERSELQGRL??????"; const char type_char_3[] = "LQGRL???????????"; // Return true if name 'n' matches pattern 'p' static bool match(uint8 *p, uint8 *n) { if (!*p) // Null pattern matches everything return true; do { if (*p == '*') // Wildcard '*' matches all following characters return true; if ((*p != *n) && (*p != '?')) // Wildcard '?' matches single character return false; p++; n++; } while (*p); return *n == 0xa0; } uint8 D64Drive::open_directory(char *pattern) { int i, j, n, m; uint8 *p, *q; DirEntry *de; uint8 c; char *tmppat; // Special treatment for "$0" if (pattern[0] == '0' && pattern[1] == 0) pattern += 1; // Skip everything before the ':' in the pattern if ((tmppat = strchr(pattern, ':')) != NULL) pattern = tmppat + 1; p = buf_ptr[0] = chan_buf[0] = new uint8[8192]; chan_mode[0] = CHMOD_DIRECTORY; // Create directory title *p++ = 0x01; // Load address $0401 (from PET days :-) *p++ = 0x04; *p++ = 0x01; // Dummy line link *p++ = 0x01; *p++ = 0; // Drive number (0) as line number *p++ = 0; *p++ = 0x12; // RVS ON *p++ = '\"'; q = bam->disk_name; for (i=0; i<23; i++) { if ((c = *q++) == 0xa0) *p++ = ' '; // Replace 0xa0 by space else *p++ = c; } *(p-7) = '\"'; *p++ = 0; // Scan all directory blocks dir.next_track = bam->dir_track; dir.next_sector = bam->dir_sector; while (dir.next_track) { if (!read_sector(dir.next_track, dir.next_sector, (uint8 *) &dir.next_track)) return ST_OK; // Scan all 8 entries of a block for (j=0; j<8; j++) { de = &dir.entry[j]; if (de->type && match((uint8 *)pattern, de->name)) { *p++ = 0x01; // Dummy line link *p++ = 0x01; *p++ = de->num_blocks_l; // Line number *p++ = de->num_blocks_h; *p++ = ' '; n = (de->num_blocks_h << 8) + de->num_blocks_l; if (n<10) *p++ = ' '; if (n<100) *p++ = ' '; *p++ = '\"'; q = de->name; m = 0; for (i=0; i<16; i++) { if ((c = *q++) == 0xa0) { if (m) *p++ = ' '; // Replace all 0xa0 by spaces else m = *p++ = '\"'; // But the first by a '"' } else *p++ = c; } if (m) *p++ = ' '; else *p++ = '\"'; // No 0xa0, then append a space if (de->type & 0x80) *p++ = ' '; else *p++ = '*'; *p++ = type_char_1[de->type & 0x0f]; *p++ = type_char_2[de->type & 0x0f]; *p++ = type_char_3[de->type & 0x0f]; if (de->type & 0x40) *p++ = '<'; else *p++ = ' '; *p++ = ' '; if (n >= 10) *p++ = ' '; if (n >= 100) *p++ = ' '; *p++ = 0; } } } // Final line q = p; for (i=0; i<29; i++) *q++ = ' '; n = 0; for (i=0; i<35; i++) n += bam->bitmap[i*4]; *p++ = 0x01; // Dummy line link *p++ = 0x01; *p++ = n & 0xff; // Number of free blocks as line number *p++ = (n >> 8) & 0xff; *p++ = 'B'; *p++ = 'L'; *p++ = 'O'; *p++ = 'C'; *p++ = 'K'; *p++ = 'S'; *p++ = ' '; *p++ = 'F'; *p++ = 'R'; *p++ = 'E'; *p++ = 'E'; *p++ = '.'; p = q; *p++ = 0; *p++ = 0; *p++ = 0; buf_len[0] = p - chan_buf[0]; return ST_OK; } /* * Open channel for direct buffer access */ uint8 D64Drive::open_direct(int channel, char *filename) { int buf = -1; if (filename[1] == 0) buf = alloc_buffer(-1); else if ((filename[1] >= '0') && (filename[1] <= '3') && (filename[2] == 0)) buf = alloc_buffer(filename[1] - '0'); if (buf == -1) { set_error(ERR_NOCHANNEL); return ST_OK; } // The buffers are in the 1541 RAM at $300 and are 256 bytes each chan_buf[channel] = buf_ptr[channel] = ram + 0x300 + (buf << 8); chan_mode[channel] = CHMOD_DIRECT; chan_buf_num[channel] = buf; // Store actual buffer number in buffer *chan_buf[channel] = buf + '0'; buf_len[channel] = 1; return ST_OK; } /* * Close channel */ uint8 D64Drive::Close(int channel) { if (channel == 15) { close_all_channels(); return ST_OK; } switch (chan_mode[channel]) { case CHMOD_FREE: break; case CHMOD_DIRECT: free_buffer(chan_buf_num[channel]); chan_buf[channel] = NULL; chan_mode[channel] = CHMOD_FREE; break; default: delete[] chan_buf[channel]; chan_buf[channel] = NULL; chan_mode[channel] = CHMOD_FREE; break; } return ST_OK; } /* * Close all channels */ void D64Drive::close_all_channels() { for (int i=0; i<15; i++) Close(i); cmd_len = 0; } /* * Read from channel */ uint8 D64Drive::Read(int channel, uint8 *byte) { switch (chan_mode[channel]) { case CHMOD_COMMAND: *byte = *error_ptr++; if (--error_len) return ST_OK; else { set_error(ERR_OK); return ST_EOF; } break; case CHMOD_FILE: // Read next block if necessary if (chan_buf[channel][0] && !buf_len[channel]) { if (!read_sector(chan_buf[channel][0], chan_buf[channel][1], chan_buf[channel])) return ST_READ_TIMEOUT; buf_ptr[channel] = chan_buf[channel] + 2; // Determine block length buf_len[channel] = chan_buf[channel][0] ? 254 : (uint8)chan_buf[channel][1]-1; } if (buf_len[channel] > 0) { *byte = *buf_ptr[channel]++; if (!--buf_len[channel] && !chan_buf[channel][0]) return ST_EOF; else return ST_OK; } else return ST_READ_TIMEOUT; break; case CHMOD_DIRECTORY: case CHMOD_DIRECT: if (buf_len[channel] > 0) { *byte = *buf_ptr[channel]++; if (--buf_len[channel]) return ST_OK; else return ST_EOF; } else return ST_READ_TIMEOUT; break; } return ST_READ_TIMEOUT; } /* * Write byte to channel */ uint8 D64Drive::Write(int channel, uint8 byte, bool eoi) { switch (chan_mode[channel]) { case CHMOD_FREE: set_error(ERR_FILENOTOPEN); break; case CHMOD_COMMAND: // Collect characters and execute command on EOI if (cmd_len >= 40) return ST_TIMEOUT; cmd_buffer[cmd_len++] = byte; if (eoi) { cmd_buffer[cmd_len++] = 0; cmd_len = 0; execute_command(cmd_buffer); } return ST_OK; case CHMOD_DIRECTORY: set_error(ERR_WRITEFILEOPEN); break; } return ST_TIMEOUT; } /* * Execute command string */ void D64Drive::execute_command(char *command) { uint16 adr; int len; switch (command[0]) { case 'B': if (command[1] != '-') set_error(ERR_SYNTAX30); else switch (command[2]) { case 'R': block_read_cmd(&command[3]); break; case 'P': buffer_ptr_cmd(&command[3]); break; case 'A': case 'F': case 'W': set_error(ERR_WRITEPROTECT); break; default: set_error(ERR_SYNTAX30); break; } break; case 'M': if (command[1] != '-') set_error(ERR_SYNTAX30); else switch (command[2]) { case 'R': adr = ((uint8)command[4] << 8) | ((uint8)command[3]); error_ptr = (char *)(ram + (adr & 0x07ff)); if (!(error_len = (uint8)command[5])) error_len = 1; break; case 'W': adr = ((uint8)command[4] << 8) | ((uint8)command[3]); len = (uint8)command[5]; for (int i=0; i= 0x30 && *cmd < 0x40) { i *= 10; i += *cmd++ & 0x0f; } *arg1 = i & 0xff; while (*cmd == ' ' || *cmd == 0x1d || *cmd == 0x2c) cmd++; if (!*cmd) return false; i = 0; while (*cmd >= 0x30 && *cmd < 0x40) { i *= 10; i += *cmd++ & 0x0f; } *arg2 = i & 0xff; while (*cmd == ' ' || *cmd == 0x1d || *cmd == 0x2c) cmd++; if (!*cmd) return false; i = 0; while (*cmd >= 0x30 && *cmd < 0x40) { i *= 10; i += *cmd++ & 0x0f; } *arg3 = i & 0xff; while (*cmd == ' ' || *cmd == 0x1d || *cmd == 0x2c) cmd++; if (!*cmd) return false; i = 0; while (*cmd >= 0x30 && *cmd < 0x40) { i *= 10; i += *cmd++ & 0x0f; } *arg4 = i & 0xff; return true; } /* * Execute 'G' command */ void D64Drive::chd64_cmd(char *d64name) { char str[NAMEBUF_LENGTH]; char *p = str; // Convert .d64 file name for (int i=0; i Desired buffer number or -1 * <- Allocated buffer number or -1 */ int D64Drive::alloc_buffer(int want) { if (want == -1) { for (want=3; want>=0; want--) if (buf_free[want]) { buf_free[want] = false; return want; } return -1; } if (want < 4) if (buf_free[want]) { buf_free[want] = false; return want; } else return -1; else return -1; } /* * Free floppy buffer */ void D64Drive::free_buffer(int buf) { buf_free[buf] = true; } /* * Read sector (256 bytes) * true: success, false: error */ bool D64Drive::read_sector(int track, int sector, uint8 *buffer) { int offset; // Convert track/sector to byte offset in file if ((offset = offset_from_ts(track, sector)) < 0) { set_error(ERR_ILLEGALTS); return false; } if (the_file == NULL) { set_error(ERR_NOTREADY); return false; } #ifdef AMIGA if (offset != ftell(the_file)) fseek(the_file, offset + image_header, SEEK_SET); #else fseek(the_file, offset + image_header, SEEK_SET); #endif fread(buffer, 256, 1, the_file); return true; } /* * Convert track/sector to offset */ const int num_sectors[41] = { 0, 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, 19,19,19,19,19,19,19, 18,18,18,18,18,18, 17,17,17,17,17, 17,17,17,17,17 // Tracks 36..40 }; const int sector_offset[41] = { 0, 0,21,42,63,84,105,126,147,168,189,210,231,252,273,294,315,336, 357,376,395,414,433,452,471, 490,508,526,544,562,580, 598,615,632,649,666, 683,700,717,734,751 // Tracks 36..40 }; int D64Drive::offset_from_ts(int track, int sector) { if ((track < 1) || (track > NUM_TRACKS) || (sector < 0) || (sector >= num_sectors[track])) return -1; return (sector_offset[track] + sector) << 8; } /* * Conversion PETSCII->ASCII */ uint8 D64Drive::conv_from_64(uint8 c, bool map_slash) { if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z')) return c ^ 0x20; if ((c >= 0xc1) && (c <= 0xda)) return c ^ 0x80; if ((c == '/') && map_slash && ThePrefs.MapSlash) return '\\'; return c; }