--- Frodo4/Src/1541t64.cpp 2004/01/11 00:09:51 1.3 +++ Frodo4/Src/1541t64.cpp 2004/08/30 13:55:30 1.7 @@ -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-2003 Christian Bauer + * Frodo (C) Copyright 1994-2001 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,27 +41,32 @@ #include "IEC.h" #include "Prefs.h" +#define DEBUG 0 +#include "debug.h" + + +// Prototypes +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; - } } @@ -70,175 +74,65 @@ T64Drive::T64Drive(IEC *iec, char *filep * Destructor */ -T64Drive::~T64Drive() +ArchDrive::~ArchDrive() { - // Close .t64 file - open_close_t64_file(""); - - Ready = false; -} - - -/* - * Open/close the .t64/LYNX file - */ - -void T64Drive::open_close_t64_file(char *t64name) -{ - 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; - uint8 *p; - int max, i, j; - - // 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::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; } @@ -384,7 +279,7 @@ bool T64Drive::find_first_file(const uin * Open directory, create temporary file */ -uint8 T64Drive::open_directory(int channel, const uint8 *pattern, int pattern_len) +uint8 ArchDrive::open_directory(int channel, const uint8 *pattern, int pattern_len) { // Special treatment for "$0" if (pattern[0] == '0' && pattern_len == 1) { @@ -411,21 +306,22 @@ uint8 T64Drive::open_directory(int chann fwrite(buf, 1, 32, file[channel]); // Create and write one line for every directory entry - for (int 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 (pattern_len == 0 || match(pattern, pattern_len, 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; - uint8 *p = buf; + uint8 *p = (uint8 *)buf; *p++ = 0x01; // Dummy line link *p++ = 0x01; // Calculate size in blocks (254 bytes each) - int n = (file_info[num].length + 254) / 254; + int n = (i->size + 254) / 254; *p++ = n & 0xff; *p++ = (n >> 8) & 0xff; @@ -434,27 +330,30 @@ uint8 T64Drive::open_directory(int chann if (n < 100) p++; // Less than 100: add another space // Convert and insert file name - uint8 str[NAMEBUF_LENGTH]; - memcpy(str, file_info[num].name, 17); *p++ = '\"'; uint8 *q = p; - for (int i=0; i<16 && str[i]; i++) - *q++ = str[i]; + 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'; @@ -482,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; } @@ -492,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; @@ -512,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); @@ -525,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); @@ -544,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 { @@ -559,12 +460,16 @@ 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 >= 58) + if (cmd_len > 58) { + set_error(ERR_SYNTAX32); return ST_TIMEOUT; + } cmd_buf[cmd_len++] = byte; @@ -591,7 +496,7 @@ uint8 T64Drive::Write(int channel, uint8 // RENAME:new=old // ^ ^ // new_file old_file -void T64Drive::rename_cmd(const uint8 *new_file, int new_file_len, const uint8 *old_file, int old_file_len) +void ArchDrive::rename_cmd(const uint8 *new_file, int new_file_len, const uint8 *old_file, int old_file_len) { // Check if destination file is already present int num; @@ -610,13 +515,13 @@ void T64Drive::rename_cmd(const uint8 *n } // INITIALIZE -void T64Drive::initialize_cmd(void) +void ArchDrive::initialize_cmd(void) { close_all_channels(); } // VALIDATE -void T64Drive::validate_cmd(void) +void ArchDrive::validate_cmd(void) { } @@ -625,9 +530,220 @@ void T64Drive::validate_cmd(void) * Reset drive */ -void T64Drive::Reset(void) +void ArchDrive::Reset(void) { close_all_channels(); cmd_len = 0; set_error(ERR_STARTUP); } + + +/* + * Check whether file with given header (64 bytes) and size looks like one + * of the file types supported by this module + */ + +static bool is_t64_header(const uint8 *header) +{ + if (memcmp(header, "C64S tape file", 14) == 0 + || memcmp(header, "C64 tape image", 14) == 0 + || memcmp(header, "C64S tape image", 15) == 0) + return true; + else + return false; +} + +static bool is_lynx_header(const uint8 *header) +{ + return memcmp(header + 0x38, "USE LYNX", 8) == 0; +} + +static bool is_p00_header(const uint8 *header) +{ + return memcmp(header, "C64File", 7) == 0; +} + +bool IsArchFile(const char *path, const uint8 *header, long size) +{ + return is_t64_header(header) || is_lynx_header(header) || is_p00_header(header); +} + + +/* + * Read directory of archive file into (empty) c64_dir_entry vector, + * returns false on error + */ + +static bool parse_t64_file(FILE *f, vector &vec, char *dir_title) +{ + // 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 "); + + // 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) +{ + // 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; +}