/*
 *  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 <exec/types.h>
#include <exec/memory.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <stdlib.h>
#include <string.h>

#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, &sector))
    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]-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;
  int len, i;

  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':
            adr = ((UBYTE)command[4] << 8) | ((UBYTE)command[3]);
			len = (UBYTE)command[5];
			for (i=0; i<len; i++)
			  drive->ram[adr+i] = command[i+6];
            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, &sector)) {
    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;
}
