frodo-wii/Src/1541d64.cpp
2008-12-31 16:16:24 +00:00

1026 lines
18 KiB
C++

/*
* 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, &sector))
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<len; i++)
ram[adr+i] = (uint8)command[i+6];
break;
default:
set_error(ERR_SYNTAX30);
}
break;
case 'I':
close_all_channels();
read_sector(18, 0, (uint8 *)bam);
set_error(ERR_OK);
break;
case 'U':
switch (command[1] & 0x0f) {
case 1: // U1/UA: Block-Read
block_read_cmd(&command[2]);
break;
case 2: // U2/UB: Block-Write
set_error(ERR_WRITEPROTECT);
break;
case 10: // U:/UJ: Reset
Reset();
break;
default:
set_error(ERR_SYNTAX30);
break;
}
break;
case 'G':
if (command[1] != ':')
set_error(ERR_SYNTAX30);
else
chd64_cmd(&command[2]);
break;
case 'C':
case 'N':
case 'R':
case 'S':
case 'V':
set_error(ERR_WRITEPROTECT);
break;
default:
set_error(ERR_SYNTAX30);
break;
}
}
/*
* Execute B-R command
*/
void D64Drive::block_read_cmd(char *command)
{
int channel, drvnum, track, sector;
if (parse_bcmd(command, &channel, &drvnum, &track, &sector)) {
if (chan_mode[channel] == CHMOD_DIRECT) {
read_sector(track, sector, buf_ptr[channel] = chan_buf[channel]);
buf_len[channel] = 256;
set_error(ERR_OK);
} else
set_error(ERR_NOCHANNEL);
} else
set_error(ERR_SYNTAX30);
}
/*
* Execute B-P command
*/
void D64Drive::buffer_ptr_cmd(char *command)
{
int channel, pointer, i;
if (parse_bcmd(command, &channel, &pointer, &i, &i)) {
if (chan_mode[channel] == CHMOD_DIRECT) {
buf_ptr[channel] = chan_buf[channel] + pointer;
buf_len[channel] = 256 - pointer;
set_error(ERR_OK);
} else
set_error(ERR_NOCHANNEL);
} else
set_error(ERR_SYNTAX30);
}
/*
* Parse block command parameters
* true: OK, false: error
*/
bool D64Drive::parse_bcmd(char *cmd, int *arg1, int *arg2, int *arg3, int *arg4)
{
int i;
if (*cmd == ':') cmd++;
// Read four parameters separated by space, cursor right or comma
while (*cmd == ' ' || *cmd == 0x1d || *cmd == 0x2c) cmd++;
if (!*cmd) return false;
i = 0;
while (*cmd >= 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<NAMEBUF_LENGTH && (*p++ = conv_from_64(*d64name++, false)); i++) ;
close_all_channels();
// G:. resets the .d64 file name to its original setting
if (str[0] == '.' && str[1] == 0)
open_close_d64_file(orig_d64_name);
else
open_close_d64_file(str);
// Read BAM
read_sector(18, 0, (uint8 *)bam);
}
/*
* Reset drive
*/
void D64Drive::Reset(void)
{
close_all_channels();
read_sector(18, 0, (uint8 *)bam);
cmd_len = 0;
for (int i=0; i<4; i++)
buf_free[i] = true;
set_error(ERR_STARTUP);
}
/*
* Allocate floppy buffer
* -> 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;
}