/* * 1541d64.c - 1541-Emulation in .d64-Datei * * Copyright (C) 1994-1996 by Christian Bauer */ /* * Anmerkungen: * ------------ * * Routinen: * - Die Schnittstelle zu den IEC-Routinen besteht in den Routinen * D64_Init, D64_Exit, D64_Open, D64_Close, D64_Read und D64_Write: * D64_Init bereitet die Emulation vor * D64_Exit beendet die Emulation * D64_Open öffnet einen Kanal * D64_Close schließt einen Kanal * D64_Read liest aus einem Kanal * D64_Write schreibt in einen Kanal * * DriveData: * - lock enthält das FileHandle der .d64-Datei * * Inkompatibilitäten/Verbesserungen: * - Nur Lesezugriffe möglich * - Keine Wildcards beim Directory-Lesen * - Nur 'I'-Befehl implementiert */ #include #include #include #include #include #include #include "IEC.h" #include "1541d64.h" #include "Display.h" #define CATCOMP_NUMBERS 1 #include "LocStrings.h" // Kanalmodi enum { CHMOD_FREE, // Kanal frei CHMOD_COMMAND, // Kommando-/Fehlerkanal CHMOD_DIRECTORY, // Directory wird gelesen CHMOD_FILE, // Sequentielle Datei ist geöffnet CHMOD_DIRECT // Direkter Pufferzugriff ('#') }; // Anzahl Tracks #define NUM_TRACKS 35 // Aus Main.asm extern void ResetC64(void); // Prototypes int open_file(DriveData *drive, int channel, const char *filename); void convert_filename(const char *srcname, char *destname, int *filemode, int *filetype); BOOL find_file(DriveData *drive, const char *filename, int *track, int *sector); int open_file_ts(DriveData *drive, int channel, int track, int sector); int open_directory(DriveData *drive, const char *filename); int open_direct(DriveData *drive, int channel, const char *filename); void close_all_channels(DriveData *drive); void execute_command(DriveData *drive, const char *command); void block_read_cmd(DriveData *drive, char *command); void buffer_ptr_cmd(DriveData *drive, char *command); BOOL parse_bcmd(char *cmd, int *arg1, int *arg2, int *arg3, int *arg4); int alloc_buffer(DriveData *drive, int want); void free_buffer(DriveData *drive, int buf); BOOL read_sector(DriveData *drive, int track, int sector, char *buffer); int offset_from_ts(int track, int sector); /** ** Emulation vorbereiten, .d64-Datei öffnen, prefs zeigt auf den ** Preferences-String **/ struct FileInfoBlock fib; void D64_Init(DriveData *drive, char *prefs) { BPTR lock; int i; ULONG magic; // Dateilänge prüfen if (lock = Lock(prefs, ACCESS_READ)) { Examine(lock, &fib); UnLock(lock); if (fib.fib_Size < 174848) return; } else return; if (drive->ram = malloc(2048)) { drive->BAM = drive->ram + 0x700; if (drive->lock = Open(prefs, MODE_OLDFILE)) { // x64 Image? Read(drive->lock, &magic, 4); drive->image_header = (magic == 0x43154164 ? 64 : 0); // BAM lesen read_sector(drive, 18, 0, (char *)drive->BAM); for (i=0; i<=14; i++) { drive->chan_mode[i] = CHMOD_FREE; drive->chan_buf[i] = NULL; } drive->chan_mode[15] = CHMOD_COMMAND; drive->cmd_length = 0; for (i=0; i<4; i++) drive->buf_free[i] = TRUE; SetError(drive, ERR_STARTUP); } } } /** ** Emulation beenden, .d64-Datei schließen **/ void D64_Exit(DriveData *drive) { if (drive->lock) { close_all_channels(drive); Close(drive->lock); drive->lock = NULL; } if (drive->ram) { free(drive->ram); drive->ram = NULL; } } /** ** Kanal öffnen, filename ist Null-terminiert **/ int D64_Open(DriveData *drive, int channel, char *filename) { SetError(drive, ERR_OK); // Kanal 15: Dateiname als Befehl ausführen if (channel == 15) { execute_command(drive, filename); return ST_OK; } if (drive->chan_mode[channel] != CHMOD_FREE) { SetError(drive, ERR_NOCHANNEL); return ST_OK; } if (filename[0] == '$') if (channel) return open_file_ts(drive, channel, 18, 0); else return open_directory(drive, filename); if (filename[0] == '#') return open_direct(drive, channel, filename); return open_file(drive, channel, filename); } /* * Datei wird geöffnet */ // Zugriffsmodi enum { FMODE_READ, FMODE_WRITE, FMODE_APPEND }; // Dateitypen enum { FTYPE_PRG, FTYPE_SEQ, FTYPE_USR, FTYPE_REL }; int open_file(DriveData *drive, int channel, const char *filename) { char plainname[256]; int filemode = FMODE_READ; int filetype = FTYPE_PRG; int track, sector; convert_filename(filename, plainname, &filemode, &filetype); // Bei Kanal 0 immer PRG lesen, bei Kanal 1 immer PRG schreiben if (!channel) { filemode = FMODE_READ; filetype = FTYPE_PRG; } if (channel == 1) { filemode = FMODE_WRITE; filetype = FTYPE_PRG; } // Nur Lesezugriffe erlaubt if (filemode != FMODE_READ) { SetError(drive, ERR_WRITEPROTECT); return ST_OK; } // Datei im Directory suchen und öffnen if (find_file(drive, plainname, &track, §or)) return open_file_ts(drive, channel, track, sector); else SetError(drive, ERR_FILENOTFOUND); return ST_OK; } /* * Dateibezeichnung analysieren, Dateimodus und -typ ermitteln */ void convert_filename(const char *srcname, char *destname, int *filemode, int *filetype) { char *p, *q; int i; // Nach ':' suchen, p zeigt auf erstes Zeichen hinter dem ':' if (p = strchr(srcname, ':')) p++; else p = srcname; // Reststring -> destname strncpy(destname, p, NAMEBUF_LENGTH); // Nach ',' suchen p = destname; while (*p && (*p != ',')) p++; // Nach Modusparametern, getrennt durch ',' suchen p = destname; while (p = strchr(p, ',')) { // String hinter dem ersten ',' abschneiden *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; } } } /* * Datei im Directory suchen, ersten Track und Sektor ermitteln * FALSE=nicht gefunden, TRUE=gefunden */ BOOL find_file(DriveData *drive, const char *filename, int *track, int *sector) { int i, j; UBYTE *p, *q; DirEntry *dir; // Alle Directory-Blöcke scannen drive->dir.next_track = drive->BAM->dir_track; drive->dir.next_sector = drive->BAM->dir_sector; while (drive->dir.next_track) { if (!read_sector(drive, drive->dir.next_track, drive->dir.next_sector, (char *) &drive->dir)) return FALSE; // Alle 8 Einträge eines Blocks scannen for (j=0; j<8; j++) { dir = &drive->dir.entry[j]; *track = dir->track; *sector = dir->sector; if (dir->type) { p = filename; q = dir->name; for (i=0; i<16 && *p; i++, p++, q++) { if (*p == '*') return TRUE; if (*p != *q) { if (*p != '?') goto next; if (*q == 0xa0) goto next; } } if (i == 16 || *q == 0xa0) return TRUE; } next: } } return FALSE; } /* * Datei öffnen, Track und Sektor des ersten Blocks gegeben */ int open_file_ts(DriveData *drive, int channel, int track, int sector) { if (drive->chan_buf[channel] = malloc(256)) { drive->chan_mode[channel] = CHMOD_FILE; // Beim nächsten D64_Read-Aufruf wird der erste Block gelesen drive->chan_buf[channel][0] = track; drive->chan_buf[channel][1] = sector; drive->buf_len[channel] = 0; } return ST_OK; } /* * Directory als Basic-Programm vorbereiten (Kanal 0) */ const char type_char_1[] = {'D', 'S', 'P', 'U', 'R'}; const char type_char_2[] = {'E', 'E', 'R', 'S', 'E'}; const char type_char_3[] = {'L', 'Q', 'G', 'R', 'L'}; int open_directory(DriveData *drive, const char *filename) { int i, j, n, m; char *p, *q; DirEntry *dir; UBYTE c; if (p = drive->buf_ptr[0] = drive->chan_buf[0] = malloc(8192)) { drive->chan_mode[0] = CHMOD_DIRECTORY; // Directory-Titel erzeugen *p++ = 0x01; // Ladeadresse $0401 (aus PET-Zeiten :-) *p++ = 0x04; *p++ = 0x01; // Dummy-Verkettung *p++ = 0x01; *p++ = 0; // Laufwerksnummer (0) als Zeilennummer *p++ = 0; *p++ = 0x12; // RVS ON *p++ = '\"'; q = drive->BAM->disk_name; for (i=0; i<23; i++) { if ((c = *q++) == 0xa0) *p++ = ' '; // 0xa0 durch Leerzeichen ersetzen else *p++ = c; } *(p-7) = '\"'; *p++ = 0; // Alle Directory-Blöcke scannen drive->dir.next_track = drive->BAM->dir_track; drive->dir.next_sector = drive->BAM->dir_sector; while (drive->dir.next_track) { if (!read_sector(drive, drive->dir.next_track, drive->dir.next_sector, (char *) &drive->dir)) return ST_OK; // Alle 8 Einträge eines Blocks scannen for (j=0; j<8; j++) { dir = &drive->dir.entry[j]; if (dir->type) { *p++ = 0x01; // Dummy-Verkettung *p++ = 0x01; *p++ = dir->num_blocks_l; // Zeilennummer *p++ = dir->num_blocks_h; *p++ = ' '; n = (dir->num_blocks_h << 8) + dir->num_blocks_l; if (n<10) *p++ = ' '; if (n<100) *p++ = ' '; *p++ = '\"'; q = dir->name; m = 0; for (i=0; i<16; i++) { if ((c = *q++) == 0xa0) { if (m) *p++ = ' '; // Alle 0xa0 durch Leerzeichen ersetzen else m = *p++ = '\"'; // Aber das erste durch einen '"' } else *p++ = c; } if (m) *p++ = ' '; else *p++ = '\"'; // Kein 0xa0, dann ein Leerzeichen anhängen if (dir->type & 0x80) *p++ = ' '; else *p++ = '*'; *p++ = type_char_1[dir->type & 0x0f]; *p++ = type_char_2[dir->type & 0x0f]; *p++ = type_char_3[dir->type & 0x0f]; if (dir->type & 0x40) *p++ = '<'; else *p++ = ' '; *p++ = ' '; if (n >= 10) *p++ = ' '; if (n >= 100) *p++ = ' '; *p++ = 0; } } } // Abschlußzeile q = p; for (i=0; i<29; i++) *q++ = ' '; n = 0; for (i=0; i<35; i++) n += drive->BAM->bitmap[i*4]; *p++ = 0x01; // Dummy-Verkettung *p++ = 0x01; *p++ = n & 0xff; // Anzahl freier Blöcke als Zeilennummer *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; drive->buf_len[0] = p - drive->chan_buf[0]; } return ST_OK; } /* * Kanal für direkten Pufferzugriff öffnen */ int open_direct(DriveData *drive, int channel, const char *filename) { int buf = -1; if (filename[1] == 0) buf = alloc_buffer(drive, -1); else if ((filename[1] >= '0') && (filename[1] <= '3') && (filename[2] == 0)) buf = alloc_buffer(drive, filename[1] - '0'); if (buf == -1) { SetError(drive, ERR_NOCHANNEL); return ST_OK; } // Die Puffer liegen im 1541-RAM ab $300 und belegen je 256 Byte drive->chan_buf[channel] = drive->buf_ptr[channel] = drive->ram + 0x300 + (buf << 8); drive->chan_mode[channel] = CHMOD_DIRECT; drive->chan_buf_num[channel] = buf; // Tatsächliche Puffernummer im Puffer ablegen *drive->chan_buf[channel] = buf + '0'; drive->buf_len[channel] = 1; return ST_OK; } /** ** Kanal schließen **/ int D64_Close(DriveData *drive, int channel) { if (channel==15) { close_all_channels(drive); return ST_OK; } switch (drive->chan_mode[channel]) { case CHMOD_FREE: break; case CHMOD_DIRECT: free_buffer(drive, drive->chan_buf_num[channel]); drive->chan_buf[channel] = NULL; drive->chan_mode[channel] = CHMOD_FREE; break; default: free(drive->chan_buf[channel]); drive->chan_buf[channel] = NULL; drive->chan_mode[channel] = CHMOD_FREE; break; } return ST_OK; } /* * Alle Kanäle schließen */ void close_all_channels(DriveData *drive) { int i; for (i=0; i<15; i++) D64_Close(drive, i); drive->cmd_length = 0; } /** ** Ein Byte aus Kanal lesen **/ int D64_Read(DriveData *drive, int channel, char *data) { switch (drive->chan_mode[channel]) { case CHMOD_FREE: return ST_READ_TIMEOUT; break; case CHMOD_COMMAND: *data = *drive->error_ptr++; if (--drive->error_length) return ST_OK; else { SetError(drive, ERR_OK); return ST_EOF; } break; case CHMOD_FILE: // Nächsten Block lesen, wenn notwendig if (drive->chan_buf[channel][0] && !drive->buf_len[channel]) { if (!read_sector(drive, drive->chan_buf[channel][0], drive->chan_buf[channel][1], drive->chan_buf[channel])) return ST_READ_TIMEOUT; drive->buf_ptr[channel] = drive->chan_buf[channel] + 2; // Blocklänge ermitteln drive->buf_len[channel] = drive->chan_buf[channel][0] ? 254 : (UBYTE)drive->chan_buf[channel][1]; } if (drive->buf_len[channel] > 0) { *data = *drive->buf_ptr[channel]++; if (!--drive->buf_len[channel] && !drive->chan_buf[channel][0]) return ST_EOF; else return ST_OK; } else return ST_READ_TIMEOUT; break; case CHMOD_DIRECTORY: case CHMOD_DIRECT: if (drive->buf_len[channel] > 0) { *data = *drive->buf_ptr[channel]++; if (--drive->buf_len[channel]) return ST_OK; else return ST_EOF; } else return ST_READ_TIMEOUT; break; } } /** ** Ein Byte in Kanal schreiben **/ int D64_Write(DriveData *drive, int channel, char data, char eof) { // Kanal 15: Zeichen sammeln und bei EOF Befehl ausführen if (channel == 15) { if (drive->cmd_length >= 40) return ST_TIMEOUT; drive->cmd_buffer[drive->cmd_length++] = data; if (eof < 0) { drive->cmd_buffer[drive->cmd_length++] = 0; drive->cmd_length = 0; execute_command(drive, drive->cmd_buffer); } return ST_OK; } if (drive->chan_mode[channel] == CHMOD_FREE) SetError(drive, ERR_FILENOTOPEN); if (drive->chan_mode[channel] == CHMOD_DIRECTORY) SetError(drive, ERR_WRITEFILEOPEN); return ST_TIMEOUT; } /* * Befehlsstring ausführen */ void execute_command(DriveData *drive, const char *command) { UWORD adr; APTR args; switch (command[0]) { case 'B': if (command[1] != '-') { SetError(drive, ERR_SYNTAX30); } else { switch (command[2]) { case 'R': block_read_cmd(drive, &command[3]); break; case 'P': buffer_ptr_cmd(drive, &command[3]); break; case 'A': case 'F': case 'W': SetError(drive, ERR_WRITEPROTECT); break; case 'E': args = "B-E"; if (ShowRequester(MSG_UNSUPFLOPPYCMD, MSG_REQGADS3, &args)) ResetC64(); SetError(drive, ERR_SYNTAX30); break; default: SetError(drive, ERR_SYNTAX30); break; } } break; case 'M': if (command[1] != '-') { SetError(drive, ERR_SYNTAX30); } else { switch (command[2]) { case 'R': adr = ((UBYTE)command[4] << 8) | ((UBYTE)command[3]); drive->error_ptr = drive->ram + (adr & 0x07ff); if (!(drive->error_length = (UBYTE)command[5])) drive->error_length = 1; break; case 'E': args = "M-E"; if (ShowRequester(MSG_UNSUPFLOPPYCMD, MSG_REQGADS3, &args)) ResetC64(); SetError(drive, ERR_SYNTAX30); break; case 'W': args = "M-W"; if (ShowRequester(MSG_UNSUPFLOPPYCMD, MSG_REQGADS3, &args)) ResetC64(); SetError(drive, ERR_SYNTAX30); break; default: SetError(drive, ERR_SYNTAX30); break; } } break; case 'I': close_all_channels(drive); read_sector(drive, 18, 0, (char *)drive->BAM); SetError(drive, ERR_OK); break; case 'U': switch (command[1] & 0x0f) { case 1: // U1/UA: Block-Read block_read_cmd(drive, &command[2]); break; case 2: // U2/UB: Block-Write SetError(drive, ERR_WRITEPROTECT); break; case 10: // U:/UJ: Reset close_all_channels(drive); read_sector(drive, 18, 0, (char *)drive->BAM); SetError(drive, ERR_STARTUP); break; default: SetError(drive, ERR_SYNTAX30); break; } break; case 'C': case 'N': case 'R': case 'S': case 'V': SetError(drive, ERR_WRITEPROTECT); break; default: SetError(drive, ERR_SYNTAX30); break; } } /* * B-R-Befehl ausführen */ void block_read_cmd(DriveData *drive, char *command) { int channel, drvnum, track, sector; if (parse_bcmd(command, &channel, &drvnum, &track, §or)) { if (drive->chan_mode[channel] == CHMOD_DIRECT) { read_sector(drive, track, sector, drive->buf_ptr[channel] = drive->chan_buf[channel]); drive->buf_len[channel] = 256; SetError(drive, ERR_OK); } else SetError(drive, ERR_NOCHANNEL); } else SetError(drive, ERR_SYNTAX30); } /* * B-P-Befehl ausführen */ void buffer_ptr_cmd(DriveData *drive, char *command) { int channel, pointer, i; if (parse_bcmd(command, &channel, &pointer, &i, &i)) { if (drive->chan_mode[channel] == CHMOD_DIRECT) { drive->buf_ptr[channel] = drive->chan_buf[channel] + pointer; drive->buf_len[channel] = 256 - pointer; SetError(drive, ERR_OK); } else SetError(drive, ERR_NOCHANNEL); } else SetError(drive, ERR_SYNTAX30); } /* * Parameter der Block-Befehle auswerten * TRUE: OK, FALSE: Fehler */ BOOL parse_bcmd(char *cmd, int *arg1, int *arg2, int *arg3, int *arg4) { int i; if (*cmd == ':') cmd++; // Vier durch Leerzeichen, Cursor Right oder Komma getrennte Parameter lesen 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 TRUE; 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 TRUE; i = 0; while (*cmd >= 0x30 && *cmd < 0x40) { i *= 10; i += *cmd++ & 0x0f; } *arg3 = i & 0xff; while (*cmd == ' ' || *cmd == 0x1d || *cmd == 0x2c) cmd++; i = 0; while (*cmd >= 0x30 && *cmd < 0x40) { i *= 10; i += *cmd++ & 0x0f; } *arg4 = i & 0xff; return TRUE; } /* * Einen Floppy-Puffer belegen * -> Gewünschte Puffernummer oder -1 * <- Belegte Puffernummer oder -1 */ int alloc_buffer(DriveData *drive, int want) { if (want == -1) { for (want=3; want>=0; want--) if (drive->buf_free[want]) { drive->buf_free[want] = FALSE; return want; } return -1; } if (want < 4) if (drive->buf_free[want]) { drive->buf_free[want] = FALSE; return want; } else return -1; else return -1; } /* * Einen Floppy-Puffer freigeben */ void free_buffer(DriveData *drive, int buf) { drive->buf_free[buf] = TRUE; } /* * Einen Sektor lesen (256 Bytes) * TRUE: Gelungen, FALSE: Fehler */ BOOL read_sector(DriveData *drive, int track, int sector, char *buffer) { int offset; // Track/Sektor-Angabe in Byteoffset in der Datei umwandeln if ((offset = offset_from_ts(track, sector)) < 0) { SetError(drive, ERR_ILLEGALTS); return FALSE; } Seek(drive->lock, offset + drive->image_header, OFFSET_BEGINNING); Read(drive->lock, buffer, 256); return TRUE; } /* * Track/Sektor in Offset umrechnen */ const int num_sectors[36] = { 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 }; const int sector_offset[36] = { 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 }; int 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; }