gt: 		      int b1 = readMandatoryByte( in );
context: /*
   * (c) 2009-2017 Jens Mueller
   *
   * Kleincomputer-Emulator
   *
   * Emulation einer Diskette, deren Daten in einer TeleDisk-Datei vorliegen
   *
   * Der hier implementierte Algorithmus basiert auf den Informationen von:
   *   http://www.willsworks.net/wteledsk.htm
   *   http://www.classiccmp.org/dunfield/img54306/td0notes.txt
   */
  
  package jkcemu.disk;
  
  import java.awt.Frame;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.lang.*;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Calendar;
  import java.util.GregorianCalendar;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Properties;
  import java.util.Random;
  import java.util.SortedSet;
  import java.util.TreeSet;
  import java.util.zip.GZIPInputStream;
  import jkcemu.base.EmuUtil;
  import jkcemu.etc.CRC16;
  import jkcemu.text.CharConverter;
  
  
  public class TeleDisk extends AbstractFloppyDisk
  {
    private static final int CRC_POLYNOM = 0xA097;
    private static final int CRC_INIT    = 0;
  
    private String                                  fileName;
    private String                                  remark;
    private java.util.Date                          diskDate;
    private Map<Integer,java.util.List<SectorData>> side0;
    private Map<Integer,java.util.List<SectorData>> side1;
  
  
    public static String export(
  			AbstractFloppyDisk disk,
  			File               file,
  			String             remark ) throws IOException
    {
      // Kommentar aufbereiten
      byte[] remarkBytes = null;
      if( remark == null ) {
        remark = disk.getRemark();
      }
      if( remark != null ) {
        int len = remark.length();
        if( len > 0 ) {
  	ByteArrayOutputStream  buf
  			= new ByteArrayOutputStream( remark.length() + 8 );
  	Calendar       calendar = new GregorianCalendar();
  	java.util.Date diskDate = disk.getDiskDate();
  	if( diskDate != null ) {
  	  calendar.clear();
  	  calendar.setTime( diskDate );
  	}
  	buf.write( calendar.get( Calendar.YEAR ) - 1900 );
  	buf.write( calendar.get( Calendar.MONTH ) );
  	buf.write( calendar.get( Calendar.DAY_OF_MONTH ) );
  	buf.write( calendar.get( Calendar.HOUR_OF_DAY ) );
  	buf.write( calendar.get( Calendar.MINUTE ) );
  	buf.write( calendar.get( Calendar.SECOND ) );
  	CharConverter cc = new CharConverter( CharConverter.Encoding.CP850 );
  	for( int i = 0; i < len; i++ ) {
  	  char ch = remark.charAt( i );
  	  if( (ch == '\u0000') || (ch == '\u001A') ) {
  	    break;
  	  }
  	  ch = (char) cc.toCharsetByte( ch );
  	  if( (ch > '\u0000') && (buf.size() < 0x7E) ) {
  	    buf.write( ch );
  	  }
  	}
  	if( buf.size() > 6 ) {
  	  buf.write( 0 );
  	  remarkBytes = buf.toByteArray();
  	}
        }
      }
  
      // Datei oeffnen
      OutputStream out = null;
      try {
        int nCyls  = disk.getCylinders();
        int nSides = disk.getSides();
        if( (nCyls < 1) || (nSides < 1) ) {
  	throw new IOException( "Kein Inhalt vorhanden" );
        }
  
        // Datei oeffnen
        out       = EmuUtil.createOptionalGZipOutputStream( file );
        CRC16 crc = new CRC16( CRC_POLYNOM, CRC_INIT );
  
        // Kennung
        writeByte( out, 'T', crc );
        writeByte( out, 'D', crc );
        writeByte( out, 0, crc );
  
        /*
         * Pruefsequenz:
         *   Alle Dateien eines Multi-File-Archivs muessen
         *   hier den gleichen Wert haben.
         *   Zwischen den Archiven sollte sich dieser Wert moeglichst
         *   unterscheiden.
         *   Deshalb wird hier eine Zufallszahl geschrieben.
         */
        writeByte(
  		out,
  		(new Random( System.currentTimeMillis() )).nextInt( 0x100 ),
  		crc );
  
        // TeleDisk-Format
        writeByte( out, 0x15, crc );
  
        // Datenrate
        int dataRate = 0;					// 250 kbps
        if( disk.getDiskSize() >= (1024 * 1024) ) {
  	dataRate = 2;					// 500 kbps
        }
        writeByte( out, dataRate, crc );
  
        /*
         * Laufwerkstyp:
         *   Es kann auf Basis der Parameter nur ein wahrscheinlicher
         *   Laufwerkstyp ermittelt werden.
         */
        int driveType = 1;			// 5.25 Zoll, 360 KByte
        if( disk.getDiskSize() >= (360 * 1024) ) {
  	driveType = 2;				// 5.25 Zoll, 48 tpi
        }
        if( (disk.getCylinders() >= 50)
  	  && (disk.getSectorSize() == 512) )
        {
  	int n = disk.getSectorsPerCylinder();
  	if( (n >= 8) && (n <= 10) ) {
  	  driveType = 3;			// 3.5 Zoll DD
  	} else if( n >= 17 ) {
  	  driveType = 4;			// 3.5 Zoll HD
  	}
        }
        writeByte( out, driveType, crc );
  
        // Stepping und Remark Flag
        writeByte(
  		out,
  		remarkBytes != null ? 0x80 : 0,	// Flag + Einzelschritt
  		crc );
  
        // DOS Allocation Flag
        writeByte( out, 0, crc );
  
        // Anzahl Seiten
        writeByte( out, disk.getSides(), crc );
  
        // CRC des Dateikopfs
        long v = crc.getValue();
        out.write( (int) v );
        out.write( (int) (v >> 8) );
  
        // Kommentar
        if( remarkBytes != null ) {
  	int len = remarkBytes.length - 6;	// Laenge ohne Zeit
  	crc.reset();
  	crc.update( len );
  	crc.update( len >> 8 );
  	crc.update( remarkBytes, 0, remarkBytes.length );
  	int crcValue = (int) crc.getValue();
  	out.write( crcValue );
  	out.write( crcValue >> 8 );
  	out.write( len );
  	out.write( len >> 8 );
  	out.write( remarkBytes );
        }
  
        // Spuren
        int lastCyl      = -1;
        int lastHead     = -1;
        int lastTrackCRC = -1;
        for( int physCyl = 0; physCyl < nCyls; physCyl++ ) {
  	lastCyl = physCyl;
  	for( int physHead = 0; physHead < nSides; physHead++ ) {
  	  lastHead = physHead;
  
  	  // Sektoren der Spur
  	  int n = disk.getSectorsOfCylinder( physCyl, physHead );
  	  if( n > 0 ) {
  	    java.util.List<SectorData> sectors = new ArrayList<>( n );
  	    for( int i = 0; i < n; i++ ) {
  	      SectorData sector = disk.getSectorByIndex(
  						physCyl,
  						physHead,
  						i );
  	      if( sector != null ) {
  		sectors.add( sector );
  	      }
  	    }
  	    n = sectors.size();
  	    if( n > 0 ) {
  	      crc.reset();
  	      writeByte( out, n, crc );
  	      writeByte( out, physCyl, crc );
  	      writeByte( out, physHead, crc );
  	      lastTrackCRC = (int) crc.getValue();
  	      out.write( lastTrackCRC );
  	      for( SectorData sector : sectors ) {
  		int dataLen = sector.getDataLength();
  
  		// Sektor-ID
  		crc.reset();
  		writeByte( out, sector.getCylinder(), crc );
  		writeByte( out, sector.getHead(), crc );
  		writeByte( out, sector.getSectorNum(), crc );
  		writeByte( out, sector.getSizeCode(), crc );
  
  		// Control-Byte
  		int secCtrl = 0;
  		if( sector.checkError() ) {
  		  secCtrl |= 0x02;
  		}
  		if( sector.isDeleted() ) {
  		  secCtrl |= 0x04;
  		}
  		if( sector.hasBogusID() ) {
  		  secCtrl |= 0x40;
  		}
  		if( dataLen <= 0 ) {
  		  secCtrl |= 0x20;
  		}
  
  		// Pruefsumme des Sektorkopfs
  		writeByte( out, secCtrl, crc );
  
  		// Sektordaten aufbereiten
  		if( dataLen > 0 ) {
  		  crc.reset();
  		  boolean allEquals = true;
  		  int     firstByte = sector.getDataByte( 0 );
  		  crc.update( firstByte );
  		  for( int i = 1; i < dataLen; i++ ) {
  		    int b = sector.getDataByte( i );
  		    crc.update( b );
  		    if( b != firstByte ) {
  		      allEquals = false;
  		    }
  		  }
  		  out.write( (int) crc.getValue() );
  		  if( allEquals ) {
  		    ByteArrayOutputStream buf
  				= new ByteArrayOutputStream( 128 );
  		    int nRemain = dataLen;
  		    while( nRemain > 5 ) {
  		      int nSeg = nRemain / 2;
  		      if( nSeg > 255 ) {
  			nSeg = 255;
  		      }
  		      buf.write( 1 );		// RLE
  		      buf.write( nSeg );
  		      buf.write( firstByte );
  		      buf.write( firstByte );
  		      nRemain -= (nSeg * 2);
  		    }
  		    buf.write( 0 );		// COPY
  		    buf.write( nRemain );
  		    while( nRemain > 0 ) {
  		      buf.write( firstByte );
  		      --nRemain;
  		    }
  		    int size = buf.size() + 1;
  		    out.write( size );
  		    out.write( size >> 8 );
  		    out.write( 2 );		// Kodierung: segmentiert
  		    buf.writeTo( out );
  		  } else {
  		    int len = dataLen + 1;	// + 1 Byte Kodierung
  		    out.write( len );
  		    out.write( len >> 8 );
  		    out.write( 0 );		// Kodierung: unkomprimiert
  		    sector.writeTo( out, dataLen );
  		  }
  		} else {
  		  out.write( (int) crc.getValue() );
  		}
  	      }
  	    }
  	  }
  	}
        }
  
        // Dateiende
        out.write( 0xFF );
        out.write( lastCyl );
        out.write( lastHead );
        out.write( lastTrackCRC );
        out.close();
        out = null;
      }
      finally {
        EmuUtil.closeSilent( out );
      }
      return null;
    }
  
  
    public static boolean isTeleDiskFileHeader( byte[] header )
    {
      boolean rv = false;
      if( header != null ) {
        if( header.length >= 3 ) {
  	if( (((header[ 0 ] == 'T') && (header[ 1 ] == 'D'))
  		|| ((header[ 0 ] == 't') && (header[ 1 ] == 'd')))
              && (header[ 2 ] == 0) )
  
  	{
  	  rv = true;
  	}
        }
      }
      return rv;
    }
  
  
    public static TeleDisk readFile(
  				Frame   owner,
  				File    file,
  				boolean enableAutoRepair ) throws IOException
    {
      TeleDisk    rv    = null;
      InputStream in    = null;
      Exception   errEx = null;
      try {
        in = new FileInputStream( file );
        if( EmuUtil.isGZipFile( file ) ) {
  	in = new GZIPInputStream( in );
        }
        boolean autoRepaired = false;
  
        // Kopfblock lesen
        int head0 = in.read();
        int head1 = in.read();
        int head2 = in.read();
        if( (head0 == 't') && (head1 == 'd') ) {
  	throwUnsupportedTeleDiskFmt(
  		"Advanced Compression nicht unterst\u00FCtzt" );
        }
        if( (head0 != 'T') || (head1 != 'D') || (head2 != 0) ) {
  	throw new IOException( "Datei ist keine TeleDisk-Datei." );
        }
        readMandatoryByte( in );				// Check Sequence
        int fmtVersion = readMandatoryByte( in );		// Version
        if( fmtVersion != 0x15 ) {
  	throwUnsupportedTeleDiskFmt(
  		String.format(
  			"Formatversion %02X nicht unterst\u00FCtzt",
  			fmtVersion ) );
        }
        readMandatoryByte( in );			// Data Rate
        readMandatoryByte( in );			// Drive Type
  						// Stepping und Remark Flag
        boolean hasRemark = ((readMandatoryByte( in ) & 0x80) != 0);
        readMandatoryByte( in );			// DOS Allocation Flag
        int sides = readMandatoryByte( in );	// Anzahl Seiten
        if( sides != 1 ) {
  	sides = 2;
        }
        readMandatoryWord( in );			// CRC Kopfbereich
  
        // Kommantarblock mit Zeitstempel lesen
        String         remark   = null;
        String         dateText = null;
        java.util.Date diskDate = null;
        if( hasRemark ) {
  	int crcValue = readMandatoryWord( in );
  	int len      = readMandatoryWord( in );
  	int year     = readMandatoryByte( in ) + 1900;
  	int month    = readMandatoryByte( in );
  	int day      = readMandatoryByte( in );
  	int hour     = readMandatoryByte( in );
  	int minute   = readMandatoryByte( in );
  	int second   = readMandatoryByte( in );
  	if( (month < 12) && (day >= 1) && (day <= 31)
  	    && (hour < 24) && (minute < 60) && (second < 60) )
  	{
  	  diskDate = (new GregorianCalendar(
  				year, month, day,
  				hour, minute, second )).getTime();
  	}
  	if( len < 0 ) {
  	  len = 0;
  	}
  	StringBuilder buf = new StringBuilder( len + 32 );
  	CharConverter cc  = new CharConverter( CharConverter.Encoding.CP850 );
  	if( len > 0 ) {
  	  int trimLen = -1;
  	  while( len > 0 ) {
  	    int ch = readMandatoryByte( in );
  	    if( ch == '\u0000' ) {
  	      ch = '\n';
  	      if( trimLen < 0 ) {
  		trimLen = buf.length();
  	      }
  	    } else {
  	      trimLen = -1;
  	    }
  	    ch = cc.toUnicode( ch );
  	    if( ch > '\u0000' ) {
  	      buf.append( (char) ch );
  	    }
  	    --len;
  	  }
  	  if( trimLen >= 0 ) {
  	    buf.setLength( trimLen );
  	  }
  	}
  	remark = buf.toString().trim();
        }
  
        // Spuren einlesen
        Map<Integer,java.util.List<SectorData>> side0 = null;
        Map<Integer,java.util.List<SectorData>> side1 = null;
  
        int cyls           = 0;
        int sectorsPerCyl  = 0;
        int diskSectorSize = 0;
        for(;;) {
  	int nSec  = in.read();
  	int track = in.read();
  	int head  = in.read();
  	in.read();			// CRC Kopfbereich der Spur
  
  	if( (nSec == 0xFF)
  	    || (nSec < 0)
  	    || (track < 0)
  	    || (head < 0) )
  	{
  	  break;
  	}
  
  	if( nSec > 0 ) {
  	  head &= 0x01;
  	  if( track >= cyls ) {
  	    cyls = track + 1;
  	  }
  	  if( sectorsPerCyl == 0 ) {
  	    sectorsPerCyl = nSec;
  	  }
  
  	  // Sektoren lesen
  	  boolean                    abnormalFmt        = false;
  	  java.util.List<SectorData> bogusHeaderSectors = null;
  	  for( int i = 0; i < nSec; i++ ) {
  	    int secTrack    = readMandatoryByte( in );
  	    int secHead     = readMandatoryByte( in );
  	    int secNum      = readMandatoryByte( in );
  	    int secSizeCode = readMandatoryByte( in );
  	    int secCtrl     = readMandatoryByte( in );
  	    int secCrcValue = readMandatoryByte( in );
  
  	    // Datenpuffer anlegen
  	    byte[] secBuf = null;
  	    if( (secSizeCode >= 0) && (secSizeCode <= 6) ) {
  	      int secSize = 128;
  	      if( secSizeCode > 0 ) {
  		secSize <<= secSizeCode;
  	      }
  	      secBuf = new byte[ secSize ];
  	      Arrays.fill( secBuf, (byte) 0 );
  	      if( secSize > diskSectorSize ) {
  		diskSectorSize = secSize;
  	      }
  	    }
  	    if( secBuf == null ) {
  	      throwUnsupportedTeleDiskFmt(
  		String.format(
  			"Code=%02X f\u00FCr Sektorgr\u00F6\u00DFe"
  				+ " nicht unterst\u00FCtzt",
  			secSizeCode ) );
  	    }
  
  	    /*
  	     * Bits in secCtrl:
  	     *   0x01: Sektor mehrfach auf der Spur enthalten
  	     *   0x02: Sektor mit CRC-Fehler gelesen
  	     *   0x04: Sektor hat Deleted Data Address Mark
  	     *   0x10: Datenbereich wurde uebersprungen
  	     *         (keine Daten enthalten)
  	     *   0x20: Sektor hat ID-Feld, aber keine Daten
  	     *   0x40: Sektor hat Daten, aber keinen Kopf
  	     *         (Kopfdaten generiert)
  	     */
  	    boolean secCrcError = ((secCtrl & 0x02) != 0);
  	    boolean secDeleted  = ((secCtrl & 0x04) != 0);
  	    boolean bogusHeader = ((secCtrl & 0x40) != 0);
  	    if( !bogusHeader
  		&& ((secTrack != track) || (secHead != head)) )
  	    {
  	      abnormalFmt = true;
  	    }
  
  	    if( (secCtrl & 0x30) == 0 ) {
  	      int len = readMandatoryWord( in );
  	      if( len > 0 ) {
  		int secEncoding = readMandatoryByte( in );
  		--len;
  
  		int pos = 0;
  		switch( secEncoding ) {
  		  case 0:
  		    while( len > 0 ) {
  		      int b = readMandatoryByte( in );
  		      if( pos < secBuf.length ) {
  			secBuf[ pos++ ] = (byte) b;
  		      }
  		      --len;
  		    }
  		    break;
  
  		  case 1:
  		    if( len >= 4 ) {
  		      int n  = readMandatoryWord( in );
  		      int b0 = readMandatoryByte( in );
  