Beispiel #1
0
        private void ShowSector()
        {
            richTextBox1.Clear();
            SectorInfo sector = image[track][sectorIndex];

            TrackLV.Text  = track.ToString();
            SectorLV.Text = sector.SectorNumber.ToString();
            SizeLV.Text   = sector.SizeBytes.ToString();
            int strs = sector.SizeBytes / 16;

            byte[]        temp = new byte[16];
            StringBuilder sb   = new StringBuilder();

            for (int i = 0; i < strs; i++)
            {
                sb.Length = 0;
                sb.Append(BitConverter.ToString(sector.Data, i * 16, 16));
                sb.Append("  ");
                for (int t = 0; t < 16; t++)
                {
                    temp[t] = sector.Data[i * 16 + t] < 32 ? (byte)'.' : sector.Data[i * 16 + t];
                }
                sb.AppendLine(Encoding.ASCII.GetString(temp, 0, 16));
                richTextBox1.AppendText(sb.ToString());
            }
        }
        public void LoadFormatFromXml(string fileName)
        {
            int track = 0;
            MList <SectorInfo> layout = new MList <SectorInfo>(50);
            MList <SectorInfo> temp   = new MList <SectorInfo>(50);

            using (XmlTextReader xml = new XmlTextReader(fileName))
            {
                while (xml.Read())
                {
                    if (xml.NodeType == XmlNodeType.EndElement)
                    {
                        break;
                    }
                    if (xml.NodeType != XmlNodeType.Element)
                    {
                        continue;
                    }
                    if (xml.Name == "DiskFormat")
                    {
                        string name = xml.GetAttribute("Name");
                        if (string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(name))
                        {
                            Name = name;
                        }
                        while (xml.Read())
                        {
                            if (xml.NodeType == XmlNodeType.EndElement)
                            {
                                break;
                            }
                            if (xml.NodeType != XmlNodeType.Element)
                            {
                                continue;
                            }
                            switch (xml.Name)
                            {
                            case "Track":
                                TrackFormat tf = Tracks[track];
                                tf.Layout.EnsureCapacity(Int32.Parse(xml.GetAttribute("SectorCount")));
                                layout.Cnt = 0;
                                if (!xml.IsEmptyElement)
                                {
                                    while (xml.Read())
                                    {
                                        if (xml.NodeType == XmlNodeType.EndElement)
                                        {
                                            break;
                                        }
                                        if (xml.NodeType != XmlNodeType.Element)
                                        {
                                            continue;
                                        }
                                        switch (xml.Name)
                                        {
                                        case "Sector":
                                            SectorInfo s = new SectorInfo()
                                            {
                                                Cylinder     = Int32.Parse(xml.GetAttribute("Cylinder")),
                                                Head         = Int32.Parse(xml.GetAttribute("Head")),
                                                SectorNumber = Int32.Parse(xml.GetAttribute("Number")),
                                                SizeCode     = Int32.Parse(xml.GetAttribute("Size")),
                                                TimeMs       = Double.Parse(xml.GetAttribute("Time"), NumberStyles.Any, CultureInfo.InvariantCulture)
                                            };
                                            layout.Add(s);
                                            break;
                                        }
                                    }
                                }
                                if (tf.Layout.Cnt > 0)
                                {
                                    tf.Layout.CopyTo(temp);
                                    layout.CopyTo(tf.Layout);
                                    for (int i = 0; i < temp.Cnt; i++)
                                    {
                                        int index = tf.FindSectorIndex(temp[i].SectorNumber);
                                        if (index < 0)
                                        {
                                            Log.Info?.Out($"Сектор не найден. Трек: {track} | Сектор: {temp[i].SectorNumber}");
                                            continue;
                                        }
                                        tf.Layout.Data[index].Data           = temp[i].Data;
                                        tf.Layout.Data[index].ProcessResult  = temp[i].ProcessResult;
                                        tf.Layout.Data[index].MapCellValue   = temp[i].MapCellValue;
                                        tf.Layout.Data[index].TimeCalculated = false;
                                    }
                                }
                                else
                                {
                                    layout.CopyTo(tf.Layout);
                                    for (int i = 0; i < layout.Cnt; i++)
                                    {
                                        tf.Layout.Data[i].MapCellValue   = MapCell.Unprocessed;
                                        tf.Layout.Data[i].TimeCalculated = false;
                                    }
                                }
                                tf.FormatName  = tf.GetFormatName();
                                tf.SpinTime    = tf.GetSpinTime();
                                tf.MapModified = true;
                                track++;
                                break;
                            }
                        }
                    }
                }
            }
        }
        public unsafe int ReadRandomSectors(TimeSpan timeout, int stopOnNthFail = 0)
        {
            int goodSectors = Params.Image.GoodSectors;
            int imageTracks = Params.Image.SizeTracks;

            try
            {
                bool          useTimeout  = timeout > TimeSpan.Zero;
                DateTime      timeoutTime = DateTime.Now.Add(timeout);
                Timer         sectorTimer = new Timer();
                Random        random      = new Random();
                MList <Point> sectorArray = new MList <Point>(Params.Image.SizeSectors);
                for (int track = Params.FirstTrack; track < Params.LastTrack; track++)
                {
                    if (Params.Side == DiskSide.Side0 && track % 2 != 0)
                    {
                        continue;
                    }
                    if (Params.Side == DiskSide.Side1 && track % 2 != 1)
                    {
                        continue;
                    }
                    for (int sector = 0; sector < Params.Image[track].Layout.Cnt; sector++)
                    {
                        if (Params.Image[track][sector].ProcessResult != SectorProcessResult.Good)
                        {
                            sectorArray.Add(new Point(track, sector));
                        }
                    }
                }
                int prevCylinder = -1;
                int failCounter  = 0;
                while (sectorArray.Cnt > 0 && ((useTimeout && DateTime.Now < timeoutTime) || !useTimeout) && (stopOnNthFail == 0 || (failCounter < stopOnNthFail)))
                {
                    int         index       = random.Next(sectorArray.Cnt);
                    int         track       = sectorArray[index].X;
                    int         sectorIndex = sectorArray[index].Y;
                    SectorInfo  sector      = Params.Image[track][sectorIndex];
                    TrackFormat tf          = Params.Image[track];
                    int         error       = 23;
                    Params.Map.MarkSectorAsProcessing(track, sectorIndex);
                    bool badWritten = sector.ProcessResult == SectorProcessResult.Bad;
                    for (int attempt = 0; attempt < Params.SectorReadAttempts; attempt++)
                    {
                        if (Aborted)
                        {
                            goto abort;
                        }
                        int cylinder = track / 2;
                        if (cylinder == prevCylinder)
                        {
                            int tempCylinder = cylinder + (random.Next(2) == 0 ? -1 : 1);
                            tempCylinder = Math.Max(0, tempCylinder);
                            tempCylinder = Math.Min(tempCylinder, Params.LastTrack / 2);
                            Driver.Seek(DriverHandle, tempCylinder * 2);
                            if (Aborted)
                            {
                                goto abort;
                            }
                            Thread.Sleep(random.Next((int)TrackFormat.SpinTimeStandard)); // Ждем случайное время чтобы приехать на нужный цилиндр в случайной точке.
                        }
                        prevCylinder = cylinder;
                        Driver.Seek(DriverHandle, track);
                        if (Aborted)
                        {
                            goto abort;
                        }
                        WinApi.RtlZeroMemory(memoryHandle, (UIntPtr)sector.SizeBytes);
                        sectorTimer.Start();
                        error = Driver.ReadSectorF(DriverHandle, memoryHandle, sector.Cylinder, sector.SectorNumber, sector.SizeCode, track & 1, sector.Head, 0x0a, 0xff);
                        sectorTimer.Stop();
                        if (error == 0)
                        {
                            Params.Image.WriteGoodSector(memoryHandle, tf, sectorIndex);
                            sectorArray.RemoveAt(index);
                            failCounter = 0;
                            timeoutTime = DateTime.Now.Add(timeout);
                            break;
                        }
                        // Ошибка 27 может быть как в случае отсутствия заголовка, так и в случае когда заголовок имеет ошибку CRC.
                        // Тут сложный вопрос что с этим делать: писать сектор как CrcError или как NoHeader. Пишется как NoHeader.
                        // (проверить была ошибка CRC в заголовке или заголовок вообще не был найден можно сравнив sectorTimer.ElapsedMs с временем вращения tf.SpinTime)
                        bool noHeader = error == 21 || error == 1112 || error == 27;
                        Make30HeadPositionings(error, track);
                        if (error == 23)
                        {
                            Params.Image.WriteBadSector(memoryHandle, tf, sectorIndex);
                            badWritten = true;
                        }
                        else if (noHeader && !badWritten)
                        {
                            Params.Image.WriteNoHeader(memoryHandle, tf, sectorIndex);
                        }
                    }
                    failCounter++;
                }
                return(Params.Image.GoodSectors - goodSectors);

abort:
                Log.Info?.Out("Чтение прервано.");
            }
            finally
            {
                Params.Map?.ClearHighlight(MapCell.Processing);
            }
            return(Params.Image.GoodSectors - goodSectors);
        }
        /// <summary>
        /// Сканирование трека. Сканировать может в двух режимах: по времени оборота диска и по зацикливанию потока секторов.
        /// При сканировании по времени оборота диска скорость диска должна быть в районе 300 об/мин, т.е. замедлять диск не надо, иначе будут ошибки.
        /// Если byLooping == true, то конец трека определяется по зацикливанию потока секторов, но сектора сканируются не менее 150 мс и не более 1000 мс.
        /// </summary>
        /// <param name="track"></param>
        /// <returns></returns>
        public unsafe bool ScanFormat(TrackFormat result, int track, bool byLooping)
        {
            int ilayoutCnt = 0;

            scanFormatBuffer.Layout.Cnt = 0;
            SectorInfo[]         layout = scanFormatBuffer.Layout.Data;
            tagFD_READ_ID_PARAMS pars   = new tagFD_READ_ID_PARAMS()
            {
                flags = Driver.FD_OPTION_MFM,
                head  = (byte)(track & 1)
            };
            tagFD_CMD_RESULT cmdResult = new tagFD_CMD_RESULT();

            scanFormatStopwatch.Restart();
            scanFormatTotalTime.Stop();
            int firstIndex = 1;

            for (int i = 0; i < scanFormatBuffer.Layout.Capacity; i++)
            {
                if (Aborted)
                {
                    return(false);
                }
                if (!Driver.ReadId(DriverHandle, pars, out cmdResult))
                {
                    int error = Marshal.GetLastWin32Error();
                    Log.Trace?.Out($"Функция ReadId вернула false. i={i}. LastError={error}");
                    if (Aborted)
                    {
                        return(false);
                    }
                    Make30HeadPositionings(error, track);
                    if (scanFormatBuffer.Layout.Cnt == 0)
                    {
                        goto success;
                    }
                    return(false);
                }
                double timeMs = scanFormatStopwatch.ElapsedMs;
                scanFormatStopwatch.Restart();
                if (!scanFormatTotalTime.IsRunning)
                {
                    scanFormatTotalTime.Restart();
                }
                if (!byLooping && scanFormatTotalTime.ElapsedMs > 209)
                {
                    goto success;
                }
                layout[ilayoutCnt] = new SectorInfo()
                {
                    Cylinder     = cmdResult.cyl,
                    Head         = cmdResult.head,
                    SectorNumber = cmdResult.sector,
                    SizeCode     = cmdResult.size,
                    TimeMs       = timeMs
                };
                ilayoutCnt++;
                scanFormatBuffer.Layout.Cnt = ilayoutCnt;

                // Бывают случаи когда заголовок сектора читается на первом обороте и не читается на втором.
                // Из-за этого алгоритм, в котором повтор сектора определяется сравнением с первым прочитанным заголовком, иногда даёт сбои.
                // Поэтому ищем встечался ли ранее только что прочитанный сектор среди всех прочитанных заголовков, а не только сравниваем с первым.
                // По отношению к найденному сектору замеряем время вращения. Если оно больше 250 мс, значит этот сектор был пропущен на одном из оборотов
                // и такую последовательность брать нельзя. Если время меньше 150, значит на треке есть сектора с одинаковыми параметрами.

                if (byLooping)
                {
                    for (int u = 0; u < ilayoutCnt - 1; u++)
                    {
                        if (layout[u].Cylinder == cmdResult.cyl &&
                            layout[u].Head == cmdResult.head &&
                            layout[u].SectorNumber == cmdResult.sector &&
                            layout[u].SizeCode == cmdResult.size)
                        {
                            double spinTime = 0;
                            for (int p = u + 1; p < ilayoutCnt; p++)
                            {
                                spinTime += layout[p].TimeMs;
                            }
                            if (spinTime > 250 || spinTime < 150)
                            {
                                continue;
                            }
                            firstIndex = u + 1;
                            goto success;
                        }
                    }
                    if (scanFormatTotalTime.ElapsedMs > 1000)
                    {
                        Log.Trace?.Out($"Не удалось найти цикл в последовательности секторов из-за нестабильного чтения. Сканироваие прервано по таймауту.");
                        return(false);
                    }
                }
            }
            return(false);

success:
            result.AssignLayout(scanFormatBuffer, track, cmdResult.sector, firstIndex);
            Log.Trace?.Out($"Время сканирования трека {track}: {GP.ToString(scanFormatTotalTime.ElapsedMs, 2)}");
            return(true);
        }
Beispiel #5
0
        public unsafe int ReadRandomSectors(TimeSpan timeout, int stopOnNthFail = 0)
        {
            int  goodSectors = Params.Image.GoodSectors;
            int  imageTracks = Params.Image.SizeTracks;
            int *trackHead   = stackalloc int[imageTracks];

            for (int i = 0; i < imageTracks; i++)
            {
                trackHead[i] = -1;
            }
            try
            {
                bool          useTimeout           = timeout > TimeSpan.Zero;
                DateTime      timeoutTime          = DateTime.Now.Add(timeout);
                Timer         sectorTimer          = new Timer();
                Random        random               = new Random();
                UpperSideHead upperSideHead        = Params.UpperSideHead;
                bool          upperSideHeadScanned = false;
                MList <int>   sectorArray          = new MList <int>(Params.Image.SizeSectors);
                for (int i = Params.FirstSectorNum; i < Params.LastSectorNum; i++)
                {
                    int track = i / Params.Image.SectorsOnTrack;
                    if (Params.Side == DiskSide.Side0 && track % 2 != 0)
                    {
                        continue;
                    }
                    if (Params.Side == DiskSide.Side1 && track % 2 != 1)
                    {
                        continue;
                    }
                    if (Params.Image.Sectors[i] != SectorProcessResult.Good)
                    {
                        sectorArray.Add(i);
                    }
                }
                int prevCylinder = -1;
                int failCounter  = 0;
                while (sectorArray.Cnt > 0 && ((useTimeout && DateTime.Now < timeoutTime) || !useTimeout) && (stopOnNthFail == 0 || (failCounter < stopOnNthFail)))
                {
                    int        index     = random.Next(sectorArray.Cnt);
                    int        sectorNum = sectorArray.Data[index];
                    int        track     = sectorNum / Params.SectorsOnTrack;
                    SectorInfo sector    = Params.Image.StandardFormat.Layout.Data[sectorNum - track * Params.SectorsOnTrack];
                    int        error     = 23;
                    Params.Image.Map?.ClearHighlight(MapCell.Processing);
                    Params.Image.Map?.MarkAsProcessing(sectorNum);
                    if (Params.UpperSideHeadAutodetect && trackHead[track] != -1)
                    {
                        upperSideHead = (UpperSideHead)trackHead[track];
                    }
                    if (Params.UpperSideHeadAutodetect && !upperSideHeadScanned && (track & 1) != 0 && trackHead[track] == -1)
                    {
                        UpperSideHead ush = upperSideHead;
                        if (ScanHeadParameter(ref ush, track, Params.CurrentTrackFormat) == 0)
                        {
                            upperSideHead    = ush;
                            trackHead[track] = (int)upperSideHead;
                            Log.Trace?.Out($"Параметр Head для трека {track} определен: {(int)upperSideHead}");
                        }
                        else
                        {
                            Log.Trace?.Out($"Параметр Head для трека {track} определить не удалось.");
                        }
                        upperSideHeadScanned = true;
                    }
                    bool noHeader   = false;
                    bool badWritten = Params.Image.Sectors[sectorNum] == SectorProcessResult.Bad;
                    for (int attempt = 0; attempt < Params.SectorReadAttempts; attempt++)
                    {
                        if (Aborted)
                        {
                            goto abort;
                        }
                        int cylinder = track / 2;
                        if (cylinder == prevCylinder)
                        {
                            int tempCylinder = cylinder + (random.Next(2) == 0 ? -1 : 1);
                            tempCylinder = Math.Max(0, tempCylinder);
                            tempCylinder = Math.Min(tempCylinder, Params.LastTrack / 2);
                            Driver.Seek(DriverHandle, tempCylinder * 2);
                            if (Aborted)
                            {
                                goto abort;
                            }
                            Thread.Sleep(random.Next((int)TrackFormat.SpinTimeStandard)); // Ждем случайное время чтобы приехать на нужный цилиндр в случайной точке.
                        }
                        prevCylinder = cylinder;
                        Driver.Seek(DriverHandle, track);
                        if (Aborted)
                        {
                            goto abort;
                        }
                        sectorTimer.Start();
                        error = Driver.ReadSector(DriverHandle, memoryHandle, track, sector.SectorNumber, upperSideHead, sector.SizeCode);
                        sectorTimer.Stop();
                        if (error == 0)
                        {
                            goto sectorReadSuccessfully;
                        }
                        noHeader = error == 21 || (error == 27 && sectorTimer.ElapsedMs > Params.CurrentTrackFormat.SpinTime);
                        Make30HeadPositionings(error, track);
                        if (error == 23)
                        {
                            Params.Image.WriteBadSectors(sectorNum, memoryHandle, 1, false);
                            badWritten = true;
                        }
                        if (noHeader && track % 2 == 1 && Params.UpperSideHeadAutodetect && trackHead[track] == -1)
                        {
                            UpperSideHead ush = upperSideHead;
                            if (ScanHeadParameter(ref ush, track, Params.CurrentTrackFormat) == 0)
                            {
                                upperSideHead    = ush;
                                trackHead[track] = (int)upperSideHead;
                                Log.Trace?.Out($"Параметр Head для трека {track} определен: {(int)upperSideHead}");
                                attempt--;
                            }
                            else
                            {
                                Log.Trace?.Out($"Параметр Head для трека {track} определить не удалось.");
                            }
                        }
                    }
                    failCounter++;
                    if (!badWritten)
                    {
                        Params.Image.WriteBadSectors(sectorNum, 1, noHeader);
                    }
                    continue;
sectorReadSuccessfully:
                    Params.Image.WriteGoodSectors(sectorNum, memoryHandle, 1);
                    trackHead[track] = (int)upperSideHead;
                    sectorArray.RemoveAt(index);
                    failCounter = 0;
                    timeoutTime = DateTime.Now.Add(timeout);
                }
                return(Params.Image.GoodSectors - goodSectors);

abort:
                Log.Info?.Out("Чтение прервано.");
            }
            finally
            {
                Params.Image.Map?.ClearHighlight(MapCell.Processing);
            }
            return(Params.Image.GoodSectors - goodSectors);
        }
Beispiel #6
0
        public static unsafe int ReadSectorF(IntPtr driverHandle, IntPtr memoryHandle, int cyl, int sector, int sizeCode, int phead, int head, int gap, int datalen)
        {
            uint dwRet;
            tagFD_READ_WRITE_PARAMS readParams = new tagFD_READ_WRITE_PARAMS()
            {
                flags   = FD_OPTION_MFM,
                phead   = (byte)phead,
                cyl     = (byte)cyl,
                head    = (byte)head,
                sector  = (byte)sector,
                size    = (byte)sizeCode,
                eot     = (byte)(sector + 1),
                gap     = (byte)gap,
                datalen = (byte)datalen,
            };
            bool r     = DeviceIoControl(driverHandle, IOCTL_FDCMD_READ_DATA, (IntPtr)(&readParams), (uint)sizeof(tagFD_READ_WRITE_PARAMS), memoryHandle, (uint)SectorInfo.GetSizeBytes(sizeCode), out dwRet, IntPtr.Zero);
            int  error = !r?Marshal.GetLastWin32Error() : 0;

            Log.Trace?.Out($"Cyl: {cyl} | PHead: {phead} | Head: {head} | Sector: {sector} | Gap: {gap} | DataLen: {datalen} | Error: {error} | Bytes Read: {dwRet}");
            return(error);
        }
Beispiel #7
0
        public static unsafe int ReadSector(IntPtr driverHandle, IntPtr memoryHandle, int track, int sector, UpperSideHead head, int sizeCode)
        {
            uint dwRet;
            tagFD_READ_WRITE_PARAMS readParams = new tagFD_READ_WRITE_PARAMS()
            {
                flags   = FD_OPTION_MFM,
                phead   = (byte)(track & 1),
                cyl     = (byte)(track >> 1),
                head    = head == UpperSideHead.Head1 ? (byte)(track & 1) : (byte)0,
                sector  = (byte)sector,
                size    = (byte)sizeCode,
                eot     = (byte)(sector + 1),
                gap     = 0x0a,
                datalen = 0xff,
            };
            bool r     = DeviceIoControl(driverHandle, IOCTL_FDCMD_READ_DATA, (IntPtr)(&readParams), (uint)sizeof(tagFD_READ_WRITE_PARAMS), memoryHandle, (uint)SectorInfo.GetSizeBytes(sizeCode), out dwRet, IntPtr.Zero);
            int  error = !r?Marshal.GetLastWin32Error() : 0;

            Log.Trace?.Out($"Track: {track} | Sector: {sector} | Error: {error} | Bytes Read: {dwRet}");
            return(error);
        }
        /// <summary>
        /// Получение ближайшего к головке дисковода сектора в соответствии с моделью вращения диска.
        /// </summary>
        /// <param name="track">Трек</param>
        /// <param name="waitTimeMs">Время которое надо пропустить.</param>
        /// <param name="skip">Количество секторов которые надо пропустить.</param>
        /// <param name="timeAfterSync">Время которое должно пройти с момента последней синхронизации до конца возвращенного сектора.</param>
        /// <returns></returns>
        public SectorInfo GetClosestSector(int track, double waitTimeMs, int skip, out double timeAfterSync, out bool error)
        {
            error         = false;
            timeAfterSync = 0;
            int index     = (syncSectorIndex + 1) % Layout.Cnt;
            int indexNext = index;

            if (!IsSync)
            {
                return(Layout.Data[indexNext]);
            }
            double time0 = Timer.ElapsedMs + waitTimeMs;
            double time  = time0 > 0 ? time0 % SpinTime : time0;

            int newLongSectorIndex = LongSectorIndex;

            if (FormatName == TrackFormatName.TrDos5_04T)
            {
                // Вычисление смещения длинного сектора для формата TR-DOS 5.04T относительно трека для которого была определена схема расположения секторов.
                int diff = -2 * (track - LayoutTrack);
                newLongSectorIndex = Mod(LongSectorIndex + diff, Layout.Cnt);

                // Вычисление смещения точки синхронизации.
                int diff0 = -2 * (track - syncTrack);
                index = Mod(index + diff0, Layout.Cnt);
            }

            for (int i = 0; i < Layout.Cnt * 10; i++)
            {
                int x = index;
                if (FormatName == TrackFormatName.TrDos5_04T)
                {
                    // Для формата TR-DOS 5.04T здесь происходит подмена секторов налету если попали на длинный сектор, имитируя его смещение.
                    // Всё делается в расчете на то что все короткие сектора имеют одинаковую длительность порядка 12 мс, и длинный сектор только один.

                    if (index == newLongSectorIndex)
                    {
                        x = LongSectorIndex;    // newLongSectorIndex подменяем на LongSectorIndex, делая его длинным.
                    }
                    else if (index == LongSectorIndex)
                    {
                        x = newLongSectorIndex; // Берем короткий сектор (или длинный, если индексы совпадают) если оказались на старом длинном.
                    }
                }
                timeAfterSync += Layout.Data[x].TimeMs;
                if (time < Layout.Data[x].TimeMs && time <= Layout.Data[x].GetGapTimeSpan())
                {
                    if (skip == 0)
                    {
                        SectorInfo r = Layout.Data[index];
                        r.TimeMs = Layout.Data[x].TimeMs;
                        return(r);
                    }
                    skip--;
                }
                time -= Layout.Data[x].TimeMs;
                index = (index + 1) % Layout.Cnt;
            }
            // Здесь раньше выбрасывалось исключение в предположении что оно никогда не выбросится, пока у одного из пользователей оно не выбросилось (в августе 2020).
            // Почему так произошло до конца непонятно. Произошло это в режиме чтения Fast. Возможно в этом случае надо будет переключаться в режим чтения Standard.
            // Пока решено возвращать ошибку.
            error = true;
            return(Layout.Data[indexNext]);
        }