mirror of
https://github.com/Oibaf66/frodo-wii.git
synced 2024-11-29 15:04:23 +01:00
721 lines
13 KiB
C++
721 lines
13 KiB
C++
/*
|
|
* 1541t64.cpp - 1541 emulation in .t64/LYNX file
|
|
*
|
|
* Frodo (C) 1994-1997,2002 Christian Bauer
|
|
|
|
*
|
|
* Notes:
|
|
* ------
|
|
*
|
|
* - 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
|
|
*/
|
|
|
|
#include "sysdeps.h"
|
|
|
|
#include "1541t64.h"
|
|
#include "IEC.h"
|
|
#include "Prefs.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);
|
|
|
|
|
|
/*
|
|
* Constructor: Prepare emulation
|
|
*/
|
|
|
|
T64Drive::T64Drive(IEC *iec, char *filepath) : Drive(iec)
|
|
{
|
|
the_file = NULL;
|
|
file_info = NULL;
|
|
|
|
Ready = false;
|
|
strcpy(orig_t64_name, filepath);
|
|
for (int i=0; i<16; i++)
|
|
file[i] = NULL;
|
|
|
|
// Open .t64 file
|
|
open_close_t64_file(filepath);
|
|
if (the_file != NULL) {
|
|
Reset();
|
|
Ready = true;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
uint8 buf[64];
|
|
bool parsed_ok = false;
|
|
|
|
// Close old .t64, if open
|
|
if (the_file != NULL) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse .t64 file and construct FileInfo array
|
|
*/
|
|
|
|
bool T64Drive::parse_t64_file(void)
|
|
{
|
|
uint8 buf[32];
|
|
uint8 *buf2;
|
|
char *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<max; i++)
|
|
if (buf2[i*32] == 1)
|
|
num_files++;
|
|
|
|
if (!num_files)
|
|
return false;
|
|
|
|
// Construct file information array
|
|
file_info = new FileInfo[num_files];
|
|
for (i=0, j=0; i<max; i++)
|
|
if (buf2[i*32] == 1) {
|
|
memcpy(file_info[j].name, buf2+i*32+16, 16);
|
|
|
|
// Strip trailing spaces
|
|
file_info[j].name[16] = 0x20;
|
|
p = file_info[j].name + 16;
|
|
while (*p-- == 0x20) ;
|
|
p[2] = 0;
|
|
|
|
file_info[j].type = FTYPE_PRG;
|
|
file_info[j].sa_lo = buf2[i*32+2];
|
|
file_info[j].sa_hi = buf2[i*32+3];
|
|
file_info[j].offset = (buf2[i*32+11] << 24) | (buf2[i*32+10] << 16) | (buf2[i*32+9] << 8) | buf2[i*32+8];
|
|
file_info[j].length = ((buf2[i*32+5] << 8) | buf2[i*32+4]) - ((buf2[i*32+3] << 8) | buf2[i*32+2]);
|
|
j++;
|
|
}
|
|
|
|
delete[] buf2;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse LYNX file and construct FileInfo array
|
|
*/
|
|
|
|
bool T64Drive::parse_lynx_file(void)
|
|
{
|
|
uint8 *p;
|
|
int dir_blocks, cur_offset, num_blocks, last_block, i;
|
|
char type_char;
|
|
|
|
// Dummy directory title
|
|
strcpy(dir_title, "LYNX ARCHIVE ");
|
|
|
|
// Read header and get number of directory blocks and files contained
|
|
fseek(the_file, 0x60, SEEK_SET);
|
|
fscanf(the_file, "%d", &dir_blocks);
|
|
while (fgetc(the_file) != 0x0d)
|
|
if (feof(the_file))
|
|
return false;
|
|
fscanf(the_file, "%d\015", &num_files);
|
|
|
|
// Construct file information array
|
|
file_info = new FileInfo[num_files];
|
|
cur_offset = dir_blocks * 254;
|
|
for (i=0; i<num_files; i++) {
|
|
|
|
// Read file name
|
|
fread(file_info[i].name, 16, 1, the_file);
|
|
|
|
// Strip trailing shift-spaces
|
|
file_info[i].name[16] = 0xa0;
|
|
p = (uint8 *)file_info[i].name + 16;
|
|
while (*p-- == 0xa0) ;
|
|
p[2] = 0;
|
|
|
|
// Read file length and type
|
|
fscanf(the_file, "\015%d\015%c\015%d\015", &num_blocks, &type_char, &last_block);
|
|
|
|
switch (type_char) {
|
|
case 'S':
|
|
file_info[i].type = FTYPE_SEQ;
|
|
break;
|
|
case 'U':
|
|
file_info[i].type = FTYPE_USR;
|
|
break;
|
|
case 'R':
|
|
file_info[i].type = FTYPE_REL;
|
|
break;
|
|
default:
|
|
file_info[i].type = FTYPE_PRG;
|
|
break;
|
|
}
|
|
file_info[i].sa_lo = 0; // Only used for .t64 files
|
|
file_info[i].sa_hi = 0;
|
|
file_info[i].offset = cur_offset;
|
|
file_info[i].length = (num_blocks-1) * 254 + last_block;
|
|
|
|
cur_offset += num_blocks * 254;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Open channel
|
|
*/
|
|
|
|
uint8 T64Drive::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;
|
|
}
|
|
|
|
// Close previous file if still open
|
|
if (file[channel]) {
|
|
fclose(file[channel]);
|
|
file[channel] = NULL;
|
|
}
|
|
|
|
if (filename[0] == '#') {
|
|
set_error(ERR_NOCHANNEL);
|
|
return ST_OK;
|
|
}
|
|
|
|
if (the_file == NULL) {
|
|
set_error(ERR_NOTREADY);
|
|
return ST_OK;
|
|
}
|
|
|
|
if (filename[0] == '$')
|
|
return open_directory(channel, filename+1);
|
|
|
|
return open_file(channel, filename);
|
|
}
|
|
|
|
|
|
/*
|
|
* Open file
|
|
*/
|
|
|
|
uint8 T64Drive::open_file(int channel, char *filename)
|
|
{
|
|
char plainname[NAMEBUF_LENGTH];
|
|
int filemode = FMODE_READ;
|
|
int filetype = FTYPE_PRG;
|
|
int num;
|
|
|
|
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
|
|
if (find_first_file(plainname, filetype, &num)) {
|
|
|
|
// Open temporary file
|
|
if ((file[channel] =
|
|
#if defined(GEKKO)
|
|
fopen("/apps/frodo/t64tmp", "w+")
|
|
#else
|
|
tmpfile()
|
|
#endif
|
|
) != NULL) {
|
|
// Write load address (.t64 only)
|
|
if (!is_lynx) {
|
|
fwrite(&file_info[num].sa_lo, 1, 1, file[channel]);
|
|
fwrite(&file_info[num].sa_hi, 1, 1, file[channel]);
|
|
}
|
|
|
|
// Copy file contents from .t64 file to temp file
|
|
uint8 *buf = new uint8[file_info[num].length];
|
|
fseek(the_file, file_info[num].offset, SEEK_SET);
|
|
fread(buf, file_info[num].length, 1, the_file);
|
|
fwrite(buf, file_info[num].length, 1, file[channel]);
|
|
rewind(file[channel]);
|
|
delete[] buf;
|
|
|
|
if (filemode == FMODE_READ) // Read and buffer first byte
|
|
read_char[channel] = fgetc(file[channel]);
|
|
}
|
|
} else
|
|
set_error(ERR_FILENOTFOUND);
|
|
|
|
return ST_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
* Analyze file name, get access mode and type
|
|
*/
|
|
|
|
void T64Drive::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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Find first file matching wildcard pattern
|
|
*/
|
|
|
|
// Return true if name 'n' matches pattern 'p'
|
|
static bool match(char *p, char *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);
|
|
}
|
|
|
|
bool T64Drive::find_first_file(char *name, int type, int *num)
|
|
{
|
|
for (int i=0; i<num_files; i++)
|
|
if (match(name, file_info[i].name) && type == file_info[i].type) {
|
|
*num = i;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Open directory, create temporary file
|
|
*/
|
|
|
|
uint8 T64Drive::open_directory(int channel, char *filename)
|
|
{
|
|
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;
|
|
|
|
// Convert filename ('$' already stripped), filemode/type are ignored
|
|
convert_filename(filename, pattern, &filemode, &filetype);
|
|
|
|
// 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];
|
|
fwrite(buf, 1, 32, file[channel]);
|
|
|
|
// Create and write one line for every directory entry
|
|
for (num=0; num<num_files; num++) {
|
|
|
|
// Include only files matching the pattern
|
|
if (match(pattern, file_info[num].name)) {
|
|
|
|
// Clear line with spaces and terminate with null byte
|
|
memset(buf, ' ', 31);
|
|
buf[31] = 0;
|
|
|
|
p = 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;
|
|
|
|
p++;
|
|
if (i < 10) p++; // Less than 10: add one space
|
|
if (i < 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];
|
|
*q++ = '\"';
|
|
p += 18;
|
|
|
|
// File type
|
|
switch (file_info[num].type) {
|
|
case FTYPE_PRG:
|
|
*p++ = 'P';
|
|
*p++ = 'R';
|
|
*p++ = 'G';
|
|
break;
|
|
case FTYPE_SEQ:
|
|
*p++ = 'S';
|
|
*p++ = 'E';
|
|
*p++ = 'Q';
|
|
break;
|
|
case FTYPE_USR:
|
|
*p++ = 'U';
|
|
*p++ = 'S';
|
|
*p++ = 'R';
|
|
break;
|
|
case FTYPE_REL:
|
|
*p++ = 'R';
|
|
*p++ = 'E';
|
|
*p++ = 'L';
|
|
break;
|
|
default:
|
|
*p++ = '?';
|
|
*p++ = '?';
|
|
*p++ = '?';
|
|
break;
|
|
}
|
|
|
|
// Write line
|
|
fwrite(buf, 1, 32, file[channel]);
|
|
}
|
|
}
|
|
|
|
// Final line
|
|
fwrite("\001\001\0\0BLOCKS FREE. \0\0", 1, 32, file[channel]);
|
|
|
|
// Rewind file for reading and read first byte
|
|
rewind(file[channel]);
|
|
read_char[channel] = fgetc(file[channel]);
|
|
|
|
return ST_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
* Close channel
|
|
*/
|
|
|
|
uint8 T64Drive::Close(int channel)
|
|
{
|
|
if (channel == 15) {
|
|
close_all_channels();
|
|
return ST_OK;
|
|
}
|
|
|
|
if (file[channel]) {
|
|
fclose(file[channel]);
|
|
file[channel] = NULL;
|
|
}
|
|
|
|
return ST_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
* Close all channels
|
|
*/
|
|
|
|
void T64Drive::close_all_channels(void)
|
|
{
|
|
for (int i=0; i<15; i++)
|
|
Close(i);
|
|
|
|
cmd_len = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read from channel
|
|
*/
|
|
|
|
uint8 T64Drive::Read(int channel, uint8 *byte)
|
|
{
|
|
int c;
|
|
|
|
// Channel 15: Error channel
|
|
if (channel == 15) {
|
|
*byte = *error_ptr++;
|
|
|
|
if (*byte != '\r')
|
|
return ST_OK;
|
|
else { // End of message
|
|
set_error(ERR_OK);
|
|
return ST_EOF;
|
|
}
|
|
}
|
|
|
|
if (!file[channel]) return ST_READ_TIMEOUT;
|
|
|
|
// Get char from buffer and read next
|
|
*byte = read_char[channel];
|
|
c = fgetc(file[channel]);
|
|
if (c == EOF)
|
|
return ST_EOF;
|
|
else {
|
|
read_char[channel] = c;
|
|
return ST_OK;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Write to channel
|
|
*/
|
|
|
|
uint8 T64Drive::Write(int channel, uint8 byte, bool eoi)
|
|
{
|
|
// Channel 15: Collect chars and execute command on EOI
|
|
if (channel == 15) {
|
|
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;
|
|
}
|
|
|
|
if (!file[channel])
|
|
set_error(ERR_FILENOTOPEN);
|
|
else
|
|
set_error(ERR_WRITEPROTECT);
|
|
|
|
return ST_TIMEOUT;
|
|
}
|
|
|
|
|
|
/*
|
|
* Execute command string
|
|
*/
|
|
|
|
void T64Drive::execute_command(char *command)
|
|
{
|
|
switch (command[0]) {
|
|
case 'I':
|
|
close_all_channels();
|
|
set_error(ERR_OK);
|
|
break;
|
|
|
|
case 'U':
|
|
if ((command[1] & 0x0f) == 0x0a) {
|
|
Reset();
|
|
} else
|
|
set_error(ERR_SYNTAX30);
|
|
break;
|
|
|
|
case 'G':
|
|
if (command[1] != ':')
|
|
set_error(ERR_SYNTAX30);
|
|
else
|
|
cht64_cmd(&command[2]);
|
|
break;
|
|
|
|
default:
|
|
set_error(ERR_SYNTAX30);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Execute 'G' command
|
|
*/
|
|
|
|
void T64Drive::cht64_cmd(char *t64name)
|
|
{
|
|
char str[NAMEBUF_LENGTH];
|
|
char *p = str;
|
|
|
|
// Convert .t64 file name
|
|
for (int i=0; i<NAMEBUF_LENGTH && (*p++ = conv_from_64(*t64name++, false)); i++) ;
|
|
|
|
close_all_channels();
|
|
|
|
// G:. resets the .t64 file name to its original setting
|
|
if (str[0] == '.' && str[1] == 0)
|
|
open_close_t64_file(orig_t64_name);
|
|
else
|
|
open_close_t64_file(str);
|
|
|
|
if (the_file == NULL)
|
|
set_error(ERR_NOTREADY);
|
|
}
|
|
|
|
|
|
/*
|
|
* Reset drive
|
|
*/
|
|
|
|
void T64Drive::Reset(void)
|
|
{
|
|
close_all_channels();
|
|
cmd_len = 0;
|
|
set_error(ERR_STARTUP);
|
|
}
|
|
|
|
|
|
/*
|
|
* Conversion PETSCII->ASCII
|
|
*/
|
|
|
|
uint8 T64Drive::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;
|
|
}
|