コード例 #1
0
ファイル: Track.cs プロジェクト: alfishe/ZXMAK2
        //public int GetHeadPos(long time)
        //{
        //   if (_trackImage == null)
        //      return (int)(_trackTime / 2);
        //   int pos = (int)((time % _trackTime) / _byteTime);
        //   if (pos > _trackImage.Length)
        //      pos = 0;
        //   return pos;
        //}

        #region UTILS

        /// <summary>
        /// Calculate WD1793 CRC, using initial value for 0xA1,0xA1,0xA1 !!!
        /// </summary>
        /// <param name="startIndex">Start index in track image</param>
        /// <param name="size">Size of block</param>
        public ushort MakeCrc(int startIndex, int size)
        {
            if (_trackImage == null)
            {
                return(0);
            }
            return(CrcVg93.Calc3xA1(_trackImage, startIndex, size));
        }
コード例 #2
0
ファイル: Sector.cs プロジェクト: zxmak/ZXMAK2
        public void SetDataCrc(bool valid)
        {
            var    dataBlock = CreateDataBlock()[0];
            ushort crc       = 0xFFFF;

            if (dataBlock.Length > 0)
            {
                crc = CrcVg93.Update(crc, dataBlock, 0, dataBlock.Length - 2);
            }
            if (!valid)
            {
                crc = (ushort)(crc ^ 0xFFFF);
            }
            _dataCrc = crc;
        }
コード例 #3
0
ファイル: Sector.cs プロジェクト: zxmak/ZXMAK2
        public void SetAdCrc(bool valid)
        {
            var    adBlock = CreateAdBlock()[0];
            ushort crc     = 0xFFFF;

            if (adBlock.Length > 0)
            {
                crc = CrcVg93.Update(crc, adBlock, 0, adBlock.Length - 2);
            }
            if (!valid)
            {
                crc = (ushort)(crc ^ 0xFFFF);
            }
            _adCrc = crc;
        }
コード例 #4
0
        private byte[][] generateTrackImage(List <FdiSectorHeader> sectorHeaderList)
        {
            var trackImage = new byte[2][];

            // Вычисляем необходимое число байт под данные:
            var imageSize = 6250;

            var secCount   = sectorHeaderList.Count;
            var trkdatalen = 0;

            for (var ilsec = 0; ilsec < secCount; ilsec++)
            {
                var hdr = sectorHeaderList[ilsec];

                trkdatalen += 2 + 6;     // for marks:   0xA1, 0xFE, 6bytes
                var slen = 128 << hdr.N;

                if ((hdr.Flags & 0x40) != 0)   // заголовок без массива данных
                {
                    slen = 0;
                }
                else
                {
                    trkdatalen += 4;       // for data header/crc: 0xA1, 0xFB, ...,2bytes
                }

                trkdatalen += slen;
            }

            var freeSpace       = imageSize - (trkdatalen + secCount * (3 + 2)); // 3x4E & 2x00 per sector
            var synchroPulseLen = 1;                                             // 1 уже учтен в trkdatalen...
            var firstSpaceLen   = 1;
            var secondSpaceLen  = 1;
            var thirdSpaceLen   = 1;
            var synchroSpaceLen = 1;

            freeSpace -= firstSpaceLen + secondSpaceLen + thirdSpaceLen + synchroSpaceLen;
            if (freeSpace < 0)
            {
                imageSize += -freeSpace;
                freeSpace  = 0;
            }
            // Распределяем длины пробелов и синхропромежутка:
            while (freeSpace > 0)
            {
                if (freeSpace >= (secCount * 2)) // Synchro for ADMARK & DATA
                {
                    if (synchroSpaceLen < 12)
                    {
                        synchroSpaceLen++;
                        freeSpace -= secCount * 2;
                    }
                }
                if (freeSpace < secCount)
                {
                    break;
                }

                if (firstSpaceLen < 10)
                {
                    firstSpaceLen++; freeSpace -= secCount;
                }
                if (freeSpace < secCount)
                {
                    break;
                }
                if (secondSpaceLen < 22)
                {
                    secondSpaceLen++; freeSpace -= secCount;
                }
                if (freeSpace < secCount)
                {
                    break;
                }
                if (thirdSpaceLen < 60)
                {
                    thirdSpaceLen++; freeSpace -= secCount;
                }
                if (freeSpace < secCount)
                {
                    break;
                }

                if ((synchroSpaceLen >= 12) && (firstSpaceLen >= 10) &&
                    (secondSpaceLen >= 22) && (thirdSpaceLen >= 60))
                {
                    break;
                }
            }
            // по возможности делаем три синхроимпульса...
            if (freeSpace > (secCount * 2) + 10)
            {
                synchroPulseLen++; freeSpace -= secCount;
            }
            if (freeSpace > (secCount * 2) + 9)
            {
                synchroPulseLen++;
            }
            if (freeSpace < 0)
            {
                imageSize += -freeSpace;
                freeSpace  = 0;
            }


            // Форматируем дорожку...
            trackImage[0] = new byte[imageSize];
            trackImage[1] = new byte[trackImage[0].Length / 8 + (((trackImage[0].Length & 7) != 0) ? 1 : 0)];

            var tptr = 0;

            for (var sec = 0; sec < secCount; sec++)
            {
                var hdr = sectorHeaderList[sec];

                for (var r = 0; r < firstSpaceLen; r++)        // Первый пробел
                {
                    trackImage[0][tptr]      = 0x4E;
                    trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                    tptr++;
                }
                for (var r = 0; r < synchroSpaceLen; r++)        // Синхропромежуток
                {
                    trackImage[0][tptr]      = 0x00;
                    trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                    tptr++;
                }
                var ptrcrc = tptr;
                for (var r = 0; r < synchroPulseLen; r++)        // Синхроимпульс
                {
                    trackImage[0][tptr]      = 0xA1;
                    trackImage[1][tptr / 8] |= (byte)(1 << (tptr & 7));
                    tptr++;
                }
                trackImage[0][tptr]      = 0xFE;          // Метка "Адрес"
                trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                tptr++;

                trackImage[0][tptr]      = hdr.C;         // cyl
                trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                tptr++;
                trackImage[0][tptr]      = hdr.H;         // head
                trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                tptr++;
                trackImage[0][tptr]      = hdr.R;         // sector #
                trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                tptr++;
                trackImage[0][tptr]      = hdr.N;         // len code
                trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                tptr++;

                ushort vgcrc = CrcVg93.Calculate(trackImage[0], ptrcrc, tptr - ptrcrc);
                trackImage[0][tptr]      = (byte)vgcrc;   // crc
                trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                tptr++;
                trackImage[0][tptr]      = (byte)(vgcrc >> 8); // crc
                trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                tptr++;

                for (var r = 0; r < secondSpaceLen; r++)        // Второй пробел
                {
                    trackImage[0][tptr]      = 0x4E;
                    trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                    tptr++;
                }
                for (var r = 0; r < synchroSpaceLen; r++)        // Синхропромежуток
                {
                    trackImage[0][tptr]      = 0x00;
                    trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                    tptr++;
                }

                byte fdiSectorFlags = hdr.Flags;
                // !!!!!!!!!
                // !WARNING! this feature of FDI format is NOT FULL DOCUMENTED!!!
                // !!!!!!!!!
                //
                //  Flags::bit6 - Возможно, 1 в данном разряде
                //                будет обозначать адресный маркер без области данных.
                //
                if ((fdiSectorFlags & 0x40) == 0) // oh-oh, data area can be not present... ;-)
                {
                    ptrcrc = tptr;
                    for (var r = 0; r < synchroPulseLen; r++)        // Синхроимпульс
                    {
                        trackImage[0][tptr]      = 0xA1;
                        trackImage[1][tptr / 8] |= (byte)(1 << (tptr & 7));
                        tptr++;
                    }

                    if ((fdiSectorFlags & 0x80) != 0)
                    {
                        trackImage[0][tptr] = 0xF8; // Метка "Удаленные данные"
                    }
                    else
                    {
                        trackImage[0][tptr] = 0xFB; // Метка "Данные"
                    }
                    trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                    tptr++;

                    //TODO: sector len from crc flags?
                    var SL = 128 << hdr.N;

                    for (var r = 0; r < SL; r++)        // сектор SL байт
                    {
                        trackImage[0][tptr]      = hdr.DataArray[r];
                        trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                        tptr++;
                    }

                    vgcrc = CrcVg93.Calculate(trackImage[0], ptrcrc, tptr - ptrcrc);

                    if ((fdiSectorFlags & 0x3F) == 0)       // CRC not correct?
                    {
                        vgcrc ^= (ushort)0xFFFF;            // oh-oh, high technology... CRC bad... ;-)
                    }

                    trackImage[0][tptr]      = (byte)vgcrc;   // crc
                    trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                    tptr++;
                    trackImage[0][tptr]      = (byte)(vgcrc >> 8); // crc
                    trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                    tptr++;
                }


                for (var r = 0; r < thirdSpaceLen; r++)        // Третий пробел
                {
                    trackImage[0][tptr]      = 0x4E;
                    trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                    tptr++;
                }
            }
            for (var eoftrk = tptr; eoftrk < trackImage[0].Length; eoftrk++)
            {
                trackImage[0][tptr]      = 0x4E;
                trackImage[1][tptr / 8] &= (byte)~(1 << (tptr & 7));
                tptr++;
            }

            return(trackImage);
        }
コード例 #5
0
        private void process(long toTact)
        {
            /*time = wd93_get_time()*/
            /*comp.t_states + cpu.t*/
            time = toTact;

            // inactive drives disregard HLT bit
            if (time > fdd[drive].motor && (system & 0x08) != 0)
            {
                fdd[drive].motor = 0;
            }

            if (state != WDSTATE.S_WAIT) // KLUDGE: motor emulation to fix SCORPION 128 TRDOS dead lock
            {
                if (fdd[drive].IsREADY)  // RESET
                {
                    status &= ~WD_STATUS.WDS_NOTRDY;
                }
                else
                {
                    status |= WD_STATUS.WDS_NOTRDY;
                }
                //status |= WD_STATUS.WDS_RECORDT;
            }


            if ((cmd & 0x80) == 0 || (cmd & 0xF0) == 0xD0) // seek / step commands
            {
                WD_STATUS old_idx_status = idx_status;
                idx_status &= ~WD_STATUS.WDS_INDEX;
                status     &= ~WD_STATUS.WDS_INDEX;
                if (state != WDSTATE.S_IDLE)
                {
                    status &= ~(WD_STATUS.WDS_TRK00 | WD_STATUS.WDS_INDEX);
                    if (fdd[drive].motor > 0 && (system & 0x08) != 0)
                    {
                        status |= WD_STATUS.WDS_HEADL;
                    }
                    if (fdd[drive].IsTRK00)
                    {
                        status |= WD_STATUS.WDS_TRK00;
                    }
                }

                // todo: test spinning
                if (fdd[drive].IsREADY && fdd[drive].motor > 0 && ((time + tshift) % (Z80FQ / FDD_RPS) < (Z80FQ * 4 / 1000)))
                {
                    if (state == WDSTATE.S_IDLE)
                    {
                        if (time < idx_tmo)
                        {
                            status |= WD_STATUS.WDS_INDEX;
                        }
                    }
                    else
                    {
                        status |= WD_STATUS.WDS_INDEX;
                    }
                    idx_status |= WD_STATUS.WDS_INDEX; // index every turn, len=4ms (if disk present)
                }
            }

            for (; ;)
            {
                switch (state)
                {
                // ----------------------------------------------------
                case WDSTATE.S_IDLE:
                    status &= ~WD_STATUS.WDS_BUSY;
                    if (idx_cnt >= 15 || time > idx_tmo)
                    {
                        idx_cnt          = 15;
                        status          &= ~WD_STATUS.WDS_NOTRDY;
                        status          |= WD_STATUS.WDS_NOTRDY;
                        fdd[drive].motor = 0;
                    }
                    rqs = BETA_STATUS.INTRQ;
                    return;

                case WDSTATE.S_WAIT:
                    if (time < next)
                    {
                        return;
                    }
                    state = state2;
                    break;
                // ----------------------------------------------------

                case WDSTATE.S_DELAY_BEFORE_CMD:
                    if (!wd93_nodelay && (cmd & CMD_DELAY) != 0)
                    {
                        next += (Z80FQ * 15 / 1000);     // 15ms delay

                        // this flag should indicate motor state, but we dont have it :(
                        // so, simulate motor off->on delay when program specify CMD_DELAY
                        status |= WD_STATUS.WDS_NOTRDY;
                    }
                    status = (status | WD_STATUS.WDS_BUSY) & ~(WD_STATUS.WDS_DRQ | WD_STATUS.WDS_LOST | WD_STATUS.WDS_NOTFOUND | WD_STATUS.WDS_RECORDT | WD_STATUS.WDS_WRITEP);
                    state2 = WDSTATE.S_CMD_RW;
                    state  = WDSTATE.S_WAIT;
                    break;

                case WDSTATE.S_CMD_RW:
                    if (((cmd & 0xE0) == 0xA0 || (cmd & 0xF0) == 0xF0) && fdd[drive].IsWP)
                    {
                        status |= WD_STATUS.WDS_WRITEP;
                        state   = WDSTATE.S_IDLE;
                        break;
                    }

                    if ((cmd & 0xC0) == 0x80 || (cmd & 0xF8) == 0xC0)
                    {
                        // read/write sectors or read am - find next AM
                        end_waiting_am = next + 5 * Z80FQ / FDD_RPS;     // max wait disk 5 turns
                        find_marker(toTact);
                        break;
                    }

                    if ((cmd & 0xF8) == 0xF0)     // write track
                    {
                        rqs     = BETA_STATUS.DRQ;
                        status |= WD_STATUS.WDS_DRQ;
                        next   += 3 * fdd[drive].t.ts_byte;
                        state2  = WDSTATE.S_WRTRACK;
                        state   = WDSTATE.S_WAIT;
                        break;
                    }

                    if ((cmd & 0xF8) == 0xE0)     // read track
                    {
                        load();
                        rwptr  = 0;
                        rwlen  = fdd[drive].t.trklen;
                        state2 = WDSTATE.S_READ;
                        getindex();
                        break;
                    }

                    // else unknown command
                    state = WDSTATE.S_IDLE;
                    break;

                case WDSTATE.S_FOUND_NEXT_ID:
                    if (!fdd[drive].IsREADY)
                    {     // no disk - wait again
                        end_waiting_am = next + 5 * Z80FQ / FDD_RPS;
                        //         nextmk:
                        find_marker(toTact);
                        break;
                    }
                    if (next >= end_waiting_am || foundid == -1)
                    {
                        status |= WD_STATUS.WDS_NOTFOUND;
                        state   = WDSTATE.S_IDLE;
                        break;
                    }

                    status &= ~WD_STATUS.WDS_CRCERR;
                    load();

                    if ((cmd & 0x80) == 0)     // verify after seek
                    {
                        if (fdd[drive].t.HeaderList[foundid].c != track)
                        {
                            find_marker(toTact);
                            break;
                        }
                        if (!fdd[drive].t.HeaderList[foundid].c1)
                        {
                            status |= WD_STATUS.WDS_CRCERR;
                            find_marker(toTact);
                            break;
                        }
                        state = WDSTATE.S_IDLE;
                        break;
                    }

                    if ((cmd & 0xF0) == 0xC0)     // read AM
                    {
                        rwptr = fdd[drive].t.HeaderList[foundid].idOffset;
                        rwlen = 6;
                        //         read_first_byte:
                        data = fdd[drive].t.RawRead(rwptr++);
                        rwlen--;
                        rqs    = BETA_STATUS.DRQ; status |= WD_STATUS.WDS_DRQ;
                        next  += fdd[drive].t.ts_byte;
                        state  = WDSTATE.S_WAIT;
                        state2 = WDSTATE.S_READ;
                        break;
                    }

                    // else R/W sector(s)
                    if (fdd[drive].t.HeaderList[foundid].c != track || fdd[drive].t.HeaderList[foundid].n != sector)
                    {
                        find_marker(toTact);
                        break;
                    }
                    if ((cmd & CMD_SIDE_CMP_FLAG) != 0 && (((cmd >> CMD_SIDE_SHIFT) ^ fdd[drive].t.HeaderList[foundid].s) & 1) != 0)
                    {
                        find_marker(toTact);
                        break;
                    }
                    if (!fdd[drive].t.HeaderList[foundid].c1)
                    {
                        status |= WD_STATUS.WDS_CRCERR;
                        find_marker(toTact);
                        break;
                    }

                    if ((cmd & 0x20) != 0)     // write sector(s)
                    {
                        rqs     = BETA_STATUS.DRQ;
                        status |= WD_STATUS.WDS_DRQ;
                        next   += fdd[drive].t.ts_byte * 9;
                        state   = WDSTATE.S_WAIT;
                        state2  = WDSTATE.S_WRSEC;
                        break;
                    }

                    // read sector(s)
                    if (fdd[drive].t.HeaderList[foundid].dataOffset < 0)     // no data block
                    {
                        find_marker(toTact);
                        break;
                    }
                    if (!wd93_nodelay)
                    {
                        next += fdd[drive].t.ts_byte * (fdd[drive].t.HeaderList[foundid].dataOffset - fdd[drive].t.HeaderList[foundid].idOffset);     // Задержка на пропуск заголовка сектора и пробела между заголовком и зоной данных
                    }
                    state = WDSTATE.S_WAIT; state2 = WDSTATE.S_RDSEC;
                    break;

                case WDSTATE.S_RDSEC:
                    //// TODO: Сделать поиск массива данных как и поиск массива заголовка!

                    if (fdd[drive].t.RawRead(fdd[drive].t.HeaderList[foundid].dataOffset - 1) == 0xF8)      // TODO: check dataOffset>=1
                    {
                        status |= WD_STATUS.WDS_RECORDT;
                    }
                    else
                    {
                        status &= ~WD_STATUS.WDS_RECORDT;
                    }
                    rwptr = fdd[drive].t.HeaderList[foundid].dataOffset;     // Смещение зоны данных сектора (в байтах) относительно начала трека
                    rwlen = 128 << (fdd[drive].t.HeaderList[foundid].l & 3); // [vv]
                    //goto read_first_byte;

                    #region us374
                    data = fdd[drive].t.RawRead(rwptr++);
                    rwlen--;
                    rqs     = BETA_STATUS.DRQ;
                    status |= WD_STATUS.WDS_DRQ;
                    next   += fdd[drive].t.ts_byte;
                    state   = WDSTATE.S_WAIT;
                    state2  = WDSTATE.S_READ;
                    #endregion

                    #region ZXMAK
                    //without WDSTATE.S_RDSEC (end of WDSTATE.S_FOUND_NEXT_ID)
                    #region fixed by me (timing fix)
                    //// not fixed:
                    ////data = fdd[drive].t.trkd[rwptr++];
                    ////rwlen--;
                    ////rqs = BETA_STATUS.DRQ;
                    ////status |= WD_STATUS.WDS_DRQ;
                    ////next += fdd[drive].t.ts_byte;
                    ////state = WDSTATE.S_WAIT;
                    ////state2 = WDSTATE.S_READ;

                    //// fixed:
                    //long div = fdd[drive].t.trklen * fdd[drive].t.ts_byte;
                    //int i = (int)(((next + tshift) % div) / fdd[drive].t.ts_byte);  // номер байта который пролетает под головкой
                    //int pos = fdd[drive].t.HeaderList[foundid].dataOffset;
                    //int dist = (pos > i) ? pos - i : fdd[drive].t.trklen + pos - i;
                    //next += dist * fdd[drive].t.ts_byte;
                    //state = WDSTATE.S_WAIT;
                    //state2 = WDSTATE.S_READ;
                    #endregion
                    #endregion
                    break;


                case WDSTATE.S_READ:
                    if (notready())
                    {
                        break;
                    }
                    load();

                    // TODO: really need?
                    if (!fdd[drive].Present)     //if(!seldrive->t.trkd)
                    {
                        status |= WD_STATUS.WDS_NOTFOUND;
                        state   = WDSTATE.S_IDLE;
                        break;
                    }

                    if (rwlen > 0)
                    {
                        if ((rqs & BETA_STATUS.DRQ) != 0)
                        {
                            status |= WD_STATUS.WDS_LOST;
                        }
                        data = fdd[drive].t.RawRead(rwptr++);
                        rwlen--;
                        rqs     = BETA_STATUS.DRQ;
                        status |= WD_STATUS.WDS_DRQ;
                        if (!wd93_nodelay)
                        {
                            next += fdd[drive].t.ts_byte;
                        }
                        else
                        {
                            next = time + 1;
                        }
                        state  = WDSTATE.S_WAIT;
                        state2 = WDSTATE.S_READ;
                    }
                    else
                    {
                        if ((cmd & 0xE0) == 0x80)     // read sector
                        {
                            if (!fdd[drive].t.HeaderList[foundid].c2)
                            {
                                status |= WD_STATUS.WDS_CRCERR;
                            }
                            if ((cmd & CMD_MULTIPLE) != 0)
                            {
                                sector++;
                                state = WDSTATE.S_CMD_RW;
                                break;
                            }
                        }
                        if ((cmd & 0xF0) == 0xC0)     // read address
                        {
                            if (!fdd[drive].t.HeaderList[foundid].c1)
                            {
                                status |= WD_STATUS.WDS_CRCERR;
                            }
                        }
                        state = WDSTATE.S_IDLE;
                    }
                    break;


                case WDSTATE.S_WRSEC:
                    load();

                    if ((rqs & BETA_STATUS.DRQ) != 0)
                    {
                        status |= WD_STATUS.WDS_LOST;
                        state   = WDSTATE.S_IDLE;
                        break;
                    }
                    fdd[drive].ModifyFlag |= ModifyFlag.SectorLevel;
                    rwptr = fdd[drive].t.HeaderList[foundid].idOffset + 6 + 11 + 11;
                    LedWr = true;
                    for (rwlen = 0; rwlen < 12; rwlen++)
                    {
                        fdd[drive].t.RawWrite(rwptr++, 0x00, false);
                    }
                    for (rwlen = 0; rwlen < 3; rwlen++)
                    {
                        fdd[drive].t.RawWrite(rwptr++, 0xA1, true);
                    }
                    fdd[drive].t.RawWrite(rwptr++, (byte)(((cmd & CMD_WRITE_DEL) != 0) ? 0xF8 : 0xFB), false);
                    rwlen = 128 << (fdd[drive].t.HeaderList[foundid].l & 3);        // [vv]
                    state = WDSTATE.S_WRITE;
                    break;

                case WDSTATE.S_WRITE:
                    if (notready())
                    {
                        break;
                    }
                    if ((rqs & BETA_STATUS.DRQ) != 0)
                    {
                        status |= WD_STATUS.WDS_LOST;
                        data    = 0;
                    }
                    fdd[drive].t.RawWrite(rwptr++, data, false);
                    rwlen--;
                    if (rwptr == fdd[drive].t.trklen)
                    {
                        rwptr = 0;
                    }

                    //fdd[drive].t.sf = SEEK_MODE.JUST_SEEK; // invalidate sectors
                    fdd[drive].t.sf = true;         // REFRESH!!!

                    if (rwlen > 0)
                    {
                        if (!wd93_nodelay)
                        {
                            next += fdd[drive].t.ts_byte;
                        }
                        state   = WDSTATE.S_WAIT;
                        state2  = WDSTATE.S_WRITE;
                        rqs     = BETA_STATUS.DRQ;
                        status |= WD_STATUS.WDS_DRQ;
                    }
                    else
                    {
                        int    len = (128 << (fdd[drive].t.HeaderList[foundid].l & 3)) + 1;
                        byte[] sc  = new byte[2056];
                        if (rwptr < len)
                        {
                            for (int memi = 0; memi < rwptr; memi++)
                            {
                                sc[memi] = fdd[drive].t.RawRead(fdd[drive].t.trklen - rwptr + memi);
                            }
                            for (int memi = 0; memi < len - rwptr; memi++)
                            {
                                sc[rwptr + memi] = fdd[drive].t.RawRead(memi);
                            }
                            //memcpy(sc, trkcache.trkd, 0, (int)(trkcache.trklen - rwptr), rwptr);
                            //memcpy(sc, trkcache.trkd, rwptr, 0, len - rwptr);
                        }
                        else
                        {
                            for (int memi = 0; memi < len; memi++)
                            {
                                sc[memi] = fdd[drive].t.RawRead(rwptr - len + memi);
                            }
                            //memcpy(sc, trkcache.trkd, 0, rwptr - len, len);
                        }
                        uint crc = CrcVg93.Calc3xA1(sc, 0, len);
                        fdd[drive].t.RawWrite(rwptr++, (byte)crc, false);
                        fdd[drive].t.RawWrite(rwptr++, (byte)(crc >> 8), false);
                        fdd[drive].t.RawWrite(rwptr, 0xFF, false);
                        if ((cmd & CMD_MULTIPLE) != 0)
                        {
                            sector++;
                            state = WDSTATE.S_CMD_RW;
                            break;
                        }
                        state = WDSTATE.S_IDLE;
                    }
                    break;

                case WDSTATE.S_WRTRACK:
                    if ((rqs & BETA_STATUS.DRQ) != 0)
                    {
                        status |= WD_STATUS.WDS_LOST;
                        state   = WDSTATE.S_IDLE;
                        break;
                    }
                    fdd[drive].ModifyFlag |= ModifyFlag.TrackLevel;
                    state2    = WDSTATE.S_WR_TRACK_DATA;
                    start_crc = 0;
                    getindex();
                    end_waiting_am = next + 5 * Z80FQ / FDD_RPS;
                    break;

                case WDSTATE.S_WR_TRACK_DATA:
                    if (notready())
                    {
                        break;
                    }
                    if ((rqs & BETA_STATUS.DRQ) != 0)
                    {
                        status |= WD_STATUS.WDS_LOST;
                        data    = 0;
                    }
                    //trkcache.seek(fdd[drive], fdd[drive].CurrentTrack, side, SEEK_MODE.JUST_SEEK);
                    //trkcache.sf = SEEK_MODE.JUST_SEEK; // invalidate sectors
                    //                  if (trkcache.sf)
                    //                     trkcache.RefreshHeaders();
                    fdd[drive].t    = fdd[drive].CurrentTrack;
                    fdd[drive].t.sf = true;         // REFRESH!!!

                    bool marker = false;
                    byte _byte  = data;
                    uint _crc   = 0;
                    if (data == 0xF5)
                    {
                        _byte     = 0xA1;
                        marker    = true;
                        start_crc = rwptr + 1;
                    }
                    if (data == 0xF6)
                    {
                        _byte  = 0xC2;
                        marker = true;
                    }
                    if (data == 0xF7)
                    {
                        _crc  = fdd[drive].t.MakeCrc(start_crc, rwptr - start_crc);
                        _byte = (byte)(_crc & 0xFF);
                    }
                    fdd[drive].t.RawWrite(rwptr++, _byte, marker);
                    rwlen--;
                    if (data == 0xF7)
                    {
                        fdd[drive].t.RawWrite(rwptr++, (byte)(_crc >> 8), marker);     // second byte of CRC16
                        rwlen--;
                    }
                    if (rwlen > 0)
                    {
                        if (!wd93_nodelay)
                        {
                            next += fdd[drive].t.ts_byte;
                        }
                        state2  = WDSTATE.S_WR_TRACK_DATA;
                        state   = WDSTATE.S_WAIT;
                        rqs     = BETA_STATUS.DRQ;
                        status |= WD_STATUS.WDS_DRQ;
                        break;
                    }
                    state = WDSTATE.S_IDLE;
                    break;

                // ----------------------------------------------------

                case WDSTATE.S_TYPE1_CMD:
                    status = (status | WD_STATUS.WDS_BUSY) & ~(WD_STATUS.WDS_DRQ | WD_STATUS.WDS_CRCERR | WD_STATUS.WDS_SEEKERR | WD_STATUS.WDS_WRITEP);
                    rqs    = BETA_STATUS.NONE;

                    if (fdd[drive].IsWP)
                    {
                        status |= WD_STATUS.WDS_WRITEP;
                    }
                    fdd[drive].motor = next + 2 * Z80FQ;

                    state2 = WDSTATE.S_SEEKSTART; // default is seek/restore
                    if ((cmd & 0xE0) != 0)        // single step
                    {
                        if ((cmd & 0x40) != 0)
                        {
                            stepdirection = (sbyte)(((cmd & CMD_SEEK_DIR) != 0) ? -1 : 1);
                        }
                        state2 = WDSTATE.S_STEP;
                        // TODO: check!!! break required? error in emulator?
                    }
                    if (!wd93_nodelay)
                    {
                        //next += 1 * Z80FQ / 1000;
                        next += 32;
                    }
                    state = WDSTATE.S_WAIT;
                    break;


                case WDSTATE.S_STEP:
                    // TRK00 sampled only in RESTORE command
                    if (fdd[drive].IsTRK00 && (cmd & 0xF0) == 0)
                    {
                        track = 0;
                        state = WDSTATE.S_VERIFY;
                        break;
                    }

                    if ((cmd & 0xE0) == 0 || (cmd & CMD_SEEK_TRKUPD) != 0)
                    {
                        track = (byte)((int)track + stepdirection);
                    }
                    fdd[drive].HeadCylynder += stepdirection;
                    //                  if (fdd[drive].HeadCylynder == -1) fdd[drive].CurrentTrack = 0;
                    if (fdd[drive].HeadCylynder >= (fdd[drive].CylynderCount - 1))
                    {
                        fdd[drive].HeadCylynder = fdd[drive].CylynderCount - 1;
                    }
                    //trkcache.clear();
                    fdd[drive].t = fdd[drive].CurrentTrack;

                    uint[] steps = new uint[4] {
                        6, 12, 20, 30
                    };                                                  // TODO: static
                    if (!wd93_nodelay)
                    {
                        next += steps[cmd & CMD_SEEK_RATE] * Z80FQ / 1000;
                    }

                    /* ?TODO? -- fdd noise
                     #ifndef MOD_9X
                     * if (!wd93_nodelay && conf.fdd_noise) Beep((stepdirection > 0)? 600 : 800, 2);
                     #endif*/

                    state2 = ((cmd & 0xE0) != 0) ? WDSTATE.S_VERIFY : WDSTATE.S_SEEK;
                    state  = WDSTATE.S_WAIT;
                    break;

                case WDSTATE.S_SEEKSTART:
                    if ((cmd & 0x10) == 0)
                    {
                        track = 0xFF;
                        data  = 0;
                    }
                    // state = S_SEEK; break;

                    //TODO: проверить!!! не ошибка ли это - дальше выполнять блок WDSTATE.SEEK?:
                    // в исходном варианте нет брейка и далее сразу следует case WDSTATE.S_SEEK:
                    if (data == track)
                    {
                        state = WDSTATE.S_VERIFY;
                        break;
                    }
                    stepdirection = (data < track) ? -1 : 1;
                    state         = WDSTATE.S_STEP;
                    break;

                case WDSTATE.S_SEEK:
                    if (data == track)
                    {
                        state = WDSTATE.S_VERIFY;
                        break;
                    }
                    stepdirection = (data < track) ? -1 : 1;
                    state         = WDSTATE.S_STEP;
                    break;

                case WDSTATE.S_VERIFY:
                    if ((cmd & CMD_SEEK_VERIFY) == 0)
                    {
                        status |= WD_STATUS.WDS_BUSY;
                        state2  = WDSTATE.S_IDLE;
                        state   = WDSTATE.S_WAIT;
                        next   += 128;                         //next = time + 1;  // do not use time - CHORDOUT issue
                        idx_tmo = next + 15 * Z80FQ / FDD_RPS; // 15 disk turns
                        break;
                    }
                    end_waiting_am = next + 6 * Z80FQ / FDD_RPS;     // max wait disk 6 turns
                    load();
                    find_marker(toTact);
                    break;


                // ----------------------------------------------------

                case WDSTATE.S_RESET:     // seek to trk0, but don't be busy
                    if (fdd[drive].IsTRK00)
                    {
                        state = WDSTATE.S_IDLE;
                    }
                    else
                    {
                        fdd[drive].HeadCylynder--;
                        //trkcache.clear();
                        fdd[drive].t = fdd[drive].CurrentTrack;
                    }
                    // if (seldrive.TRK00) track = 0;
                    next += 6 * Z80FQ / 1000;
                    break;

                default:
                    throw new InvalidOperationException("WD1793.process - WD1793 in wrong state");
                }
            }
        }