--- Frodo4/Src/1541t64.cpp 2003/07/01 17:09:43 1.1 +++ Frodo4/Src/1541t64.cpp 2005/06/27 19:55:48 1.8 @@ -1,7 +1,7 @@ /* - * 1541t64.cpp - 1541 emulation in .t64/LYNX file + * 1541t64.cpp - 1541 emulation in archive-type files (.t64/LYNX/.p00) * - * Frodo (C) 1994-1997,2002 Christian Bauer + * Frodo (C) 1994-1997,2002-2005 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,21 +19,20 @@ */ /* - * Notes: - * ------ + * NOTES: + * - This module handles access to files inside (uncompressed) archives + * and makes the archive look like a disk. It supports C64S tape images + * (.t64), C64 LYNX archives and .p00 files. + * - If any file is opened, the contents of the file in the archive file are + * copied into a temporary file which is used for reading. This is done + * to insert the load address. * - * - If any file is opened, the contents of the file in the - * .t64 file are copied into a temporary file which is used - * for reading. This is done to insert the load address. - * - C64 LYNX archives are also handled by these routines - * - * Incompatibilities: - * ------------------ - * - * - Only read accesses possible - * - No "raw" directory reading - * - No relative/sequential/user files - * - Only "I" and "UJ" commands implemented + * Incompatibilities: + * - Only read accesses possible + * - No "raw" directory reading + * - No relative/sequential/user files + * - Unimplemented commands: B-P, M-R, M-W, C, S, P, N + * - Impossible to implement: B-R, B-W, B-E, B-A, B-F, M-E */ #include "sysdeps.h" @@ -42,41 +41,32 @@ #include "IEC.h" #include "Prefs.h" +#define DEBUG 0 +#include "debug.h" -// Access modes -enum { - FMODE_READ, FMODE_WRITE, FMODE_APPEND -}; - -// File types -enum { - FTYPE_PRG, FTYPE_SEQ, FTYPE_USR, FTYPE_REL -}; // Prototypes -static bool match(char *p, char *n); +static bool is_t64_header(const uint8 *header); +static bool is_lynx_header(const uint8 *header); +static bool is_p00_header(const uint8 *header); +static bool parse_t64_file(FILE *f, vector &vec, char *dir_title); +static bool parse_lynx_file(FILE *f, vector &vec, char *dir_title); +static bool parse_p00_file(FILE *f, vector &vec, char *dir_title); /* * Constructor: Prepare emulation */ -T64Drive::T64Drive(IEC *iec, char *filepath) : Drive(iec) +ArchDrive::ArchDrive(IEC *iec, const char *filepath) : Drive(iec), the_file(NULL) { - the_file = NULL; - file_info = NULL; - - Ready = false; - strcpy(orig_t64_name, filepath); for (int i=0; i<16; i++) file[i] = NULL; + Reset(); - // Open .t64 file - open_close_t64_file(filepath); - if (the_file != NULL) { - Reset(); + // Open archive file + if (change_arch(filepath)) Ready = true; - } } @@ -84,175 +74,65 @@ T64Drive::T64Drive(IEC *iec, char *filep * Destructor */ -T64Drive::~T64Drive() -{ - // Close .t64 file - open_close_t64_file(""); - - Ready = false; -} - - -/* - * Open/close the .t64/LYNX file - */ - -void T64Drive::open_close_t64_file(char *t64name) +ArchDrive::~ArchDrive() { - uint8 buf[64]; - bool parsed_ok = false; - - // Close old .t64, if open - if (the_file != NULL) { + // Close archive file + if (the_file) { close_all_channels(); fclose(the_file); - the_file = NULL; - delete[] file_info; - file_info = NULL; - } - - // Open new .t64 file - if (t64name[0]) { - if ((the_file = fopen(t64name, "rb")) != NULL) { - - // Check file ID - fread(&buf, 64, 1, the_file); - if (buf[0] == 0x43 && buf[1] == 0x36 && buf[2] == 0x34) { - is_lynx = false; - parsed_ok = parse_t64_file(); - } else if (buf[0x3c] == 0x4c && buf[0x3d] == 0x59 && buf[0x3e] == 0x4e && buf[0x3f] == 0x58) { - is_lynx = true; - parsed_ok = parse_lynx_file(); - } - - if (!parsed_ok) { - fclose(the_file); - the_file = NULL; - delete[] file_info; - file_info = NULL; - return; - } - } } + Ready = false; } /* - * Parse .t64 file and construct FileInfo array + * Open the archive file */ -bool T64Drive::parse_t64_file(void) +bool ArchDrive::change_arch(const char *path) { - uint8 buf[32]; - uint8 *buf2; - char *p; - int max, i, j; + FILE *new_file; - // Read header and get maximum number of files contained - fseek(the_file, 32, SEEK_SET); - fread(&buf, 32, 1, the_file); - max = (buf[3] << 8) | buf[2]; - - memcpy(dir_title, buf+8, 16); - - // Allocate buffer for file records and read them - buf2 = new uint8[max*32]; - fread(buf2, 32, max, the_file); - - // Determine number of files contained - for (i=0, num_files=0; i 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; - } - } -} - - -/* * Find first file matching wildcard pattern */ // Return true if name 'n' matches pattern 'p' -static bool match(char *p, char *n) +static bool match(const uint8 *p, int p_len, const uint8 *n) { - if (!*p) // Null pattern matches everything - return true; - - do { + while (p_len-- > 0) { 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); + return *n == 0; } -bool T64Drive::find_first_file(char *name, int type, int *num) +bool ArchDrive::find_first_file(const uint8 *pattern, int pattern_len, int &num) { - for (int i=0; i::const_iterator i, end = file_info.end(); + for (i = file_info.begin(), num = 0; i != end; i++, num++) { + if (match(pattern, pattern_len, (uint8 *)i->name)) return true; - } - + } return false; } @@ -444,77 +279,81 @@ bool T64Drive::find_first_file(char *nam * Open directory, create temporary file */ -uint8 T64Drive::open_directory(int channel, char *filename) +uint8 ArchDrive::open_directory(int channel, const uint8 *pattern, int pattern_len) { - char buf[] = "\001\004\001\001\0\0\022\042 \042 00 2A"; - char str[NAMEBUF_LENGTH]; - char pattern[NAMEBUF_LENGTH]; - char *p, *q; - int i, num; - int filemode; - int filetype; - // Special treatment for "$0" - if (strlen(filename) == 1 && filename[0] == '0') - filename += 1; + if (pattern[0] == '0' && pattern_len == 1) { + pattern++; + pattern_len--; + } - // Convert filename ('$' already stripped), filemode/type are ignored - convert_filename(filename, pattern, &filemode, &filetype); + // Skip everything before the ':' in the pattern + uint8 *t = (uint8 *)memchr(pattern, ':', pattern_len); + if (t) { + t++; + pattern_len -= t - pattern; + pattern = t; + } // Create temporary file if ((file[channel] = tmpfile()) == NULL) return ST_OK; // Create directory title - p = &buf[8]; - for (i=0; i<16 && dir_title[i]; i++) - *p++ = dir_title[i]; + uint8 buf[] = "\001\004\001\001\0\0\022\042 \042 00 2A"; + for (int i=0; i<16 && dir_title[i]; i++) + buf[i + 8] = dir_title[i]; fwrite(buf, 1, 32, file[channel]); // Create and write one line for every directory entry - for (num=0; num::const_iterator i, end = file_info.end(); + for (i = file_info.begin(); i != end; i++) { // Include only files matching the pattern - if (match(pattern, file_info[num].name)) { + if (pattern_len == 0 || match(pattern, pattern_len, (uint8 *)i->name)) { // Clear line with spaces and terminate with null byte memset(buf, ' ', 31); buf[31] = 0; - p = buf; + uint8 *p = (uint8 *)buf; *p++ = 0x01; // Dummy line link *p++ = 0x01; // Calculate size in blocks (254 bytes each) - i = (file_info[num].length + 254) / 254; - *p++ = i & 0xff; - *p++ = (i >> 8) & 0xff; + int n = (i->size + 254) / 254; + *p++ = n & 0xff; + *p++ = (n >> 8) & 0xff; p++; - if (i < 10) p++; // Less than 10: add one space - if (i < 100) p++; // Less than 100: add another space + if (n < 10) p++; // Less than 10: add one space + if (n < 100) p++; // Less than 100: add another space // Convert and insert file name - strcpy(str, file_info[num].name); *p++ = '\"'; - q = p; - for (i=0; i<16 && str[i]; i++) - *q++ = str[i]; + uint8 *q = p; + for (int j=0; j<16 && i->name[j]; j++) + *q++ = i->name[j]; *q++ = '\"'; p += 18; // File type - switch (file_info[num].type) { - case FTYPE_PRG: - *p++ = 'P'; - *p++ = 'R'; - *p++ = 'G'; + switch (i->type) { + case FTYPE_DEL: + *p++ = 'D'; + *p++ = 'E'; + *p++ = 'L'; break; case FTYPE_SEQ: *p++ = 'S'; *p++ = 'E'; *p++ = 'Q'; break; + case FTYPE_PRG: + *p++ = 'P'; + *p++ = 'R'; + *p++ = 'G'; + break; case FTYPE_USR: *p++ = 'U'; *p++ = 'S'; @@ -542,7 +381,7 @@ uint8 T64Drive::open_directory(int chann // Rewind file for reading and read first byte rewind(file[channel]); - read_char[channel] = fgetc(file[channel]); + read_char[channel] = getc(file[channel]); return ST_OK; } @@ -552,8 +391,10 @@ uint8 T64Drive::open_directory(int chann * Close channel */ -uint8 T64Drive::Close(int channel) +uint8 ArchDrive::Close(int channel) { + D(bug("ArchDrive::Close channel %d\n", channel)); + if (channel == 15) { close_all_channels(); return ST_OK; @@ -572,7 +413,7 @@ uint8 T64Drive::Close(int channel) * Close all channels */ -void T64Drive::close_all_channels(void) +void ArchDrive::close_all_channels(void) { for (int i=0; i<15; i++) Close(i); @@ -585,15 +426,15 @@ void T64Drive::close_all_channels(void) * Read from channel */ -uint8 T64Drive::Read(int channel, uint8 *byte) +uint8 ArchDrive::Read(int channel, uint8 &byte) { - int c; + D(bug("ArchDrive::Read channel %d\n", channel)); // Channel 15: Error channel if (channel == 15) { - *byte = *error_ptr++; + byte = *error_ptr++; - if (*byte != '\r') + if (byte != '\x0d') return ST_OK; else { // End of message set_error(ERR_OK); @@ -604,8 +445,8 @@ uint8 T64Drive::Read(int channel, uint8 if (!file[channel]) return ST_READ_TIMEOUT; // Get char from buffer and read next - *byte = read_char[channel]; - c = fgetc(file[channel]); + byte = read_char[channel]; + int c = getc(file[channel]); if (c == EOF) return ST_EOF; else { @@ -619,19 +460,22 @@ uint8 T64Drive::Read(int channel, uint8 * Write to channel */ -uint8 T64Drive::Write(int channel, uint8 byte, bool eoi) +uint8 ArchDrive::Write(int channel, uint8 byte, bool eoi) { + D(bug("ArchDrive::Write channel %d, byte %02x, eoi %d\n", channel, byte, eoi)); + // Channel 15: Collect chars and execute command on EOI if (channel == 15) { - if (cmd_len >= 40) + if (cmd_len > 58) { + set_error(ERR_SYNTAX32); return ST_TIMEOUT; + } - cmd_buffer[cmd_len++] = byte; + cmd_buf[cmd_len++] = byte; if (eoi) { - cmd_buffer[cmd_len] = 0; + execute_cmd(cmd_buf, cmd_len); cmd_len = 0; - execute_command(cmd_buffer); } return ST_OK; } @@ -646,85 +490,260 @@ uint8 T64Drive::Write(int channel, uint8 /* - * Execute command string + * Execute drive commands */ -void T64Drive::execute_command(char *command) +// RENAME:new=old +// ^ ^ +// new_file old_file +void ArchDrive::rename_cmd(const uint8 *new_file, int new_file_len, const uint8 *old_file, int old_file_len) { - switch (command[0]) { - case 'I': - close_all_channels(); - set_error(ERR_OK); - break; + // Check if destination file is already present + int num; + if (find_first_file(new_file, new_file_len, num)) { + set_error(ERR_FILEEXISTS); + return; + } - case 'U': - if ((command[1] & 0x0f) == 0x0a) { - Reset(); - } else - set_error(ERR_SYNTAX30); - break; + // Check if source file is present + if (!find_first_file(old_file, old_file_len, num)) { + set_error(ERR_FILENOTFOUND); + return; + } - case 'G': - if (command[1] != ':') - set_error(ERR_SYNTAX30); - else - cht64_cmd(&command[2]); - break; + set_error(ERR_WRITEPROTECT); +} - default: - set_error(ERR_SYNTAX30); - } +// INITIALIZE +void ArchDrive::initialize_cmd(void) +{ + close_all_channels(); +} + +// VALIDATE +void ArchDrive::validate_cmd(void) +{ } /* - * Execute 'G' command + * Reset drive */ -void T64Drive::cht64_cmd(char *t64name) +void ArchDrive::Reset(void) { - char str[NAMEBUF_LENGTH]; - char *p = str; + close_all_channels(); + cmd_len = 0; + set_error(ERR_STARTUP); +} - // Convert .t64 file name - for (int i=0; i &vec, char *dir_title) { - close_all_channels(); - cmd_len = 0; - set_error(ERR_STARTUP); + // Read header and get maximum number of files contained + fseek(f, 32, SEEK_SET); + uint8 buf[32]; + fread(&buf, 32, 1, f); + int max = (buf[3] << 8) | buf[2]; + if (max == 0) + max = 1; + + memcpy(dir_title, buf+8, 16); + + // Allocate buffer for file records and read them + uint8 *buf2 = new uint8[max * 32]; + fread(buf2, 32, max, f); + + // Determine number of files contained + int num_files = 0; + for (int i=0; i &vec, char *dir_title) +{ + // Dummy directory title + strcpy(dir_title, "LYNX ARCHIVE "); -/* - * Conversion PETSCII->ASCII - */ + // Read header and get number of directory blocks and files contained + fseek(f, 0x60, SEEK_SET); + int dir_blocks; + fscanf(f, "%d", &dir_blocks); + while (getc(f) != 0x0d) + if (feof(f)) + return false; + int num_files; + fscanf(f, "%d\x0d", &num_files); + + // Construct file information array + vec.reserve(num_files); + int cur_offset = dir_blocks * 254; + for (int i=0; i &vec, char *dir_title) { - 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; + // Dummy directory title + strcpy(dir_title, ".P00 FILE "); + + // Contains only one file + vec.reserve(1); + + // Read file name and start address + uint8 name_buf[17]; + fseek(f, 8, SEEK_SET); + fread(name_buf, 17, 1, f); + name_buf[16] = 0; + uint8 sa_lo, sa_hi; + fseek(f, 26, SEEK_SET); + fread(&sa_lo, 1, 1, f); + fread(&sa_hi, 1, 1, f); + + // Get file size + fseek(f, 0, SEEK_END); + size_t size = ftell(f) - 26; + + // Add entry + vec.push_back(c64_dir_entry(name_buf, FTYPE_PRG, false, false, size, 26, sa_lo, sa_hi)); + return true; +} + +bool ReadArchDirectory(const char *path, vector &vec) +{ + // Open file + FILE *f = fopen(path, "rb"); + if (f) { + + // Read header + uint8 header[64]; + fread(header, 1, sizeof(header), f); + + // Determine archive type and parse archive + bool result = false; + char dir_title[16]; + if (is_t64_header(header)) + result = parse_t64_file(f, vec, dir_title); + else if (is_lynx_header(header)) + result = parse_lynx_file(f, vec, dir_title); + else if (is_p00_header(header)) + result = parse_p00_file(f, vec, dir_title); + + fclose(f); + return result; + } else + return false; }