private void ReadCatalogue(object sender, EventArgs e)
        {
            if (!ReadParameters(false))
            {
                return;
            }
            SetProcessing(true);
            BackgroundWorker worker = new BackgroundWorker();

            Log.Info?.Out($"Чтение каталога. DataRate: {Params.DataRate}");
            int successfullyRead   = 0;
            DiskReaderParams npars = new DiskReaderParams()
            {
                FirstSectorNum     = 0,
                LastSectorNum      = 9,
                DataRate           = Params.DataRate,
                Image              = new TrDosImage(9, null),
                SectorReadAttempts = Params.SectorReadAttempts,
                Side = DiskSide.Both,
                CurrentTrackFormat      = new TrackFormat(TrackFormatName.TrDosSequential),
                ReadMode                = ReadMode.Standard,
                TrackLayoutAutodetect   = false,
                UpperSideHeadAutodetect = false,
            };

            diskReader = new DiskReader()
            {
                Params = npars
            };
            worker.DoWork += (object sndr, DoWorkEventArgs ee) =>
            {
                try
                {
                    if (!diskReader.OpenDriver())
                    {
                        return;
                    }
                    successfullyRead = diskReader.ReadForward();
                    diskReader.CloseDriver();
                    Log.Info?.Out($"Чтение каталога завершено. Успешно прочитанных секторов: {successfullyRead}");
                }
                catch (Exception ex)
                {
                    Log.Error?.Out($"Исключение при чтении диска: {ex.Message} | StackTrace: {ex.StackTrace}");
                }
            };
            worker.RunWorkerCompleted += (object sender1, RunWorkerCompletedEventArgs ee) =>
            {
                SetProcessing(false);
                if (MainForm.CatalogueForm == null)
                {
                    MainForm.CatalogueForm = new CatalogueForm();
                }
                if (successfullyRead > 1)
                {
                    MainForm.CatalogueForm.ShowForm(diskReader.Params.Image as TrDosImage, false);
                }
            };
            worker.RunWorkerAsync();
        }
 public IsDosReader(Control parent, DiskReaderParams dparams) : base(parent, 1024, 5, dparams)
 {
     upperSidePanel.Visible   = false;
     readModePanel.Visible    = false;
     map.FileL.Visible        = false;
     map.FileLV.Visible       = false;
     map.ExtenstionLV.Visible = false;
     readCatalogue.Visible    = false;
     showCatalogue.Visible    = false;
     showCatFromTrack.Visible = false;
     Image = new IsDosImage(160 * SectorsOnTrack, map)
     {
         Name = ""
     };
     map.Image   = Image;
     stats.Image = Image;
     map.Repaint();
     stats.Repaint();
     loadImage.Click         += LoadImage;
     mergeImage.Click        += MergeImage;
     newImage.Click          += NewImage;
     saveImage.Click         += SaveImage;
     readForward.Click       += ReadForward;
     readBackward.Click      += ReadBackward;
     readRandomSectors.Click += ReadRandomSectors;
 }
Example #3
0
        public object Clone()
        {
            DiskReaderParams r = (DiskReaderParams)MemberwiseClone();

            r.CurrentTrackFormat = (TrackFormat)CurrentTrackFormat.Clone();
            return(r);
        }
 public TrDosReader(Control parent, DiskReaderParams dparams) : base(parent, 256, 16, dparams)
 {
     Image = new TrDosImage(160 * SectorsOnTrack, map)
     {
         Name = ""
     };
     map.Image   = Image;
     stats.Image = Image;
     map.Repaint();
     stats.Repaint();
     saveImage.Click         += SaveImage;
     readCatalogue.Click     += ReadCatalogue;
     showCatalogue.Click     += ShowCatalogue;
     showCatFromTrack.Click  += ShowCatFromTrack;
     loadImage.Click         += LoadImage;
     mergeImage.Click        += MergeImage;
     newImage.Click          += NewImage;
     readForward.Click       += ReadForward;
     readBackward.Click      += ReadBackward;
     readRandomSectors.Click += ReadRandomSectors;
 }
Example #5
0
        public unsafe void ReadTrack(int track, DiskReaderParams pars)
        {
            // Массив sectors:
            // D0 - Признак обработанного сектора при чтении трека в одной попытке. Обнуляется перед каждой попыткой чтения трека.
            // D1 - Используется в блоке чтения заголовков. Отмечаются сектора заголовки которых были найдены. Вне этого блока не используется.
            // D2 - Запрет чтения сектора из-за того что его заголовок был прочитан SectorReadAttempts раз и не был найден. Параметр сохраняется между попытками чтения трека.

            int *sectors = stackalloc int[pars.SectorsOnTrack];

            for (int i = 0; i < pars.SectorsOnTrack; i++)
            {
                sectors[i] = 0;
            }
            bool formatScanned  = false;
            bool headScanned    = false;
            bool trackScanned   = false;
            bool wasCorrectHead = false;
            bool missedHeaders  = false;

            for (int attempt = 0; attempt < pars.SectorReadAttempts; attempt++)
            {
                if (Aborted)
                {
                    return;
                }
                for (int i = 0; i < pars.SectorsOnTrack; i++)
                {
                    sectors[i] &= ~1;
                }
                bool   wasError      = false;
                double timeAfterSync = 0;
                trackTimer.Restart();
                int        skip             = 0;
                int        processedSectors = 0;
                int        sectorCounter    = 0;
                SectorInfo diskSector;
                while (processedSectors < pars.SectorsOnTrack)
                {
                    bool sync = false;
                    if (pars.ReadMode == ReadMode.Fast && pars.CurrentTrackFormat.IsSync)
                    {
                        bool gcsError;
                        diskSector = pars.CurrentTrackFormat.GetClosestSector(track, 0, skip, out timeAfterSync, out gcsError);
                        sync       = true;
                        // Переключение в стандартный режим чтения в случае если функция GetClosestSector отработала с ошибкой.
                        // Такое было у одного из пользователей и причина ошибки непонятна.
                        if (gcsError)
                        {
                            pars.ReadMode = ReadMode.Standard;
                        }
                    }
                    else
                    {
                        diskSector = pars.Image.StandardFormat.Layout.Data[sectorCounter];
                        sectorCounter++;
                    }
                    int sectorIndex   = pars.Image.StandardFormat.FindSectorIndex(diskSector.SectorNumber);
                    int diskSectorNum = track * Params.SectorsOnTrack + sectorIndex;
                    skip++;
                    if ((sectors[sectorIndex] & 1) != 0)
                    {
                        continue;
                    }
                    sectors[sectorIndex] |= 1;
                    processedSectors++;
                    if ((sectors[sectorIndex] & 4) != 0)
                    {
                        continue;
                    }
                    if (diskSectorNum < pars.FirstSectorNum || diskSectorNum >= pars.LastSectorNum)
                    {
                        continue;
                    }
                    if (pars.Image.Sectors[diskSectorNum] == SectorProcessResult.Good)
                    {
                        continue;
                    }
                    bool badWritten = pars.Image.Sectors[diskSectorNum] == SectorProcessResult.Bad;
                    skip = 0;
                    pars.Image.Map?.ClearHighlight(MapCell.Processing);
                    pars.Image.Map?.MarkAsProcessing(diskSectorNum);
                    WinApi.RtlZeroMemory(memoryHandle, (UIntPtr)diskSector.SizeBytes);
                    int    error            = Driver.ReadSector(DriverHandle, memoryHandle, track, diskSector.SectorNumber, pars.UpperSideHead, diskSector.SizeCode);
                    double curTimeSinceSync = pars.CurrentTrackFormat.Timer.ElapsedMs;
                    if (error == 0)
                    {
                        pars.CurrentTrackFormat.Sync(track, diskSector.SectorNumber);
                        pars.Image.WriteGoodSectors(diskSectorNum, memoryHandle, 1);
                        wasCorrectHead = true;
                        if (Aborted)
                        {
                            return;
                        }

                        // Проверка несовпадения расположения секторов с заданным. Работает только для TR-DOS.

                        if (pars.ReadMode == ReadMode.Fast &&
                            pars.TrackLayoutAutodetect &&
                            sync &&
                            !formatScanned &&
                            !missedHeaders &&
                            curTimeSinceSync < 2 * pars.CurrentTrackFormat.SpinTime)
                        {
                            double remainderTime = curTimeSinceSync;
                            for (int t = 0; remainderTime > 0 && t < 20; remainderTime -= pars.CurrentTrackFormat.SpinTime, t++)
                            {
                                // 6 - половина времени сектора TR-DOS (около 12 мс).
                                if (Math.Abs(remainderTime - timeAfterSync) <= 6)
                                {
                                    goto skip;
                                }
                            }
                            Log.Trace?.Out($"Несовпадение расположения секторов. Calculated Time: {GP.ToString(timeAfterSync, 2)} | Real Time: {GP.ToString(curTimeSinceSync, 2)}");
                            formatScanned = true;
                            bool scanResult = ScanFormat(workTrackFormat, track, true);
                            if (Aborted)
                            {
                                return;
                            }
                            if (scanResult)
                            {
                                Log.Trace?.Out($"Формат трека {track}: {workTrackFormat.FormatName} | {workTrackFormat.Layout.Cnt} sectors | {workTrackFormat.ToStringAsSectorArray()}");
                                if (workTrackFormat.FormatName <= TrackFormatName.TrDosGeneric)
                                {
                                    pars.CurrentTrackFormat.Assign(workTrackFormat);
                                }
                            }
                            //pars.Image.Map?.ClearHighlight(MapCell.Scanning);
                            //findex = pars.TrackFormat.GetNextSectorIndex(track);
                            skip :;
                        }
                    }
                    else
                    {
                        wasError = true;
                        bool writeBad = true;
                        bool noHeader = error == 21 || error == 1112 || error == 1122 || (error == 27 /* && curTimeSinceSync > pars.CurrentTrackFormat.SpinTime*/);
                        if (noHeader)
                        {
                            Make30HeadPositionings(error, track);

                            // Проверка параметра Head верхней стороны.

                            bool scanWholeTrack = true;
                            int  scanHeadResult = 0;
                            if (track % 2 == 1 && !headScanned && !wasCorrectHead && pars.UpperSideHeadAutodetect)
                            {
                                headScanned = true;
                                UpperSideHead head = pars.UpperSideHead;
                                scanHeadResult = ScanHeadParameter(ref head, track, pars.CurrentTrackFormat);
                                if (scanHeadResult == 0)
                                {
                                    if (head != pars.UpperSideHead)
                                    {
                                        Log.Trace?.Out($"Параметр Head верхней стороны для трека {track} определен: {(int)head}");
                                        pars.UpperSideHead   = head;
                                        writeBad             = false;
                                        sectors[sectorIndex] = 0;
                                        processedSectors--;
                                        scanWholeTrack = false;
                                    }
                                    else
                                    {
                                        Log.Trace?.Out($"Параметр Head верхней стороны для трека {track} определен и совпадает с предыдущим: {(int)head}");
                                    }
                                }
                                else
                                {
                                    Log.Trace?.Out($"Параметр Head верхней стороны для трека {track} определить не удалось.");
                                }
                            }

                            // Чтение неформатных областей.
                            // Если встретился NoHeader, то предполагается что на этом треке может быть еще много случаев NoHeader,
                            // поэтому они проверяются сканированием трека целиком. Это происходит быстрее, чем если пытаться читать их по-отдельности.

                            if (scanWholeTrack && !trackScanned)
                            {
                                int sec0Num   = Math.Max(track * pars.SectorsOnTrack, pars.FirstSectorNum);
                                int sec0NtNum = Math.Min((track + 1) * pars.SectorsOnTrack, pars.LastSectorNum);
                                // sectorsToCheck - число секторов которые надо проверить на предмет отсутствия заголовков.
                                // Если подозреваемый сектор только один (т.е. только что прочитанный) и число попыток чтения 1, то sectorsToCheck после цикла будет нулём.
                                bool ignoreCurSector = pars.SectorReadAttempts == 1;
                                writeBad = ignoreCurSector;
                                int sectorsToCheck = ignoreCurSector ? 0 : 1;    // Учет только что обработанного сектора.
                                // Далее к sectorsToCheck добавляются только необработанные сектора.
                                for (int sn = sec0Num, si = sn - track * pars.SectorsOnTrack; sn < sec0NtNum; sn++, si++)
                                {
                                    if (pars.Image.Sectors[sn] != SectorProcessResult.Good && (sectors[si] & 1) == 0)
                                    {
                                        sectorsToCheck++;
                                    }
                                }
                                if (sectorsToCheck > 0)
                                {
                                    trackScanned = true;
                                    int  foundHeaders    = 0;
                                    bool scanResult      = false;
                                    int  maxExtraSectors = 0;
                                    if (!ignoreCurSector)
                                    {
                                        sectors[sectorIndex] &= ~1;
                                    }
                                    int attempts = !ignoreCurSector && sectorsToCheck == 1 ? pars.SectorReadAttempts - 1 : pars.SectorReadAttempts;
                                    for (int attemptnh = 0; attemptnh < attempts; attemptnh++)
                                    {
                                        scanResult = false;
                                        if (scanHeadResult == 1 && attemptnh == 0)
                                        {
                                            workTrackFormat.Layout.Cnt = 0;
                                        }
                                        else
                                        {
                                            scanResult = ScanFormat(workTrackFormat, track, true);
                                        }
                                        if (workTrackFormat.Layout.Cnt == 0)
                                        {
                                            Log.Info?.Out($"На треке {track} заголовки не найдены.");
                                        }
                                        else
                                        {
                                            Log.Trace?.Out($"Формат трека {track}: {workTrackFormat.FormatName} | {workTrackFormat.Layout.Cnt} sectors | {workTrackFormat.ToStringAsSectorArray()}");
                                        }
                                        for (int sn = sec0Num, si = sn - track * pars.SectorsOnTrack; sn < sec0NtNum; sn++, si++)
                                        {
                                            if (pars.Image.Sectors[sn] == SectorProcessResult.Good || (sectors[si] & 1) == 1 || (sectors[si] & 2) != 0)
                                            {
                                                continue;
                                            }
                                            int dSector  = pars.Image.StandardFormat.Layout.Data[si].SectorNumber;
                                            int sindex   = workTrackFormat.FindSectorIndex(dSector);
                                            int head     = pars.UpperSideHead == UpperSideHead.Head1 ? track & 1 : 0;
                                            int sizeCode = pars.CurrentTrackFormat.Layout.Data[pars.CurrentTrackFormat.FindSectorIndex(dSector)].SizeCode;
                                            if (sindex >= 0 && workTrackFormat.Layout.Data[sindex].SizeCode == sizeCode)
                                            {
                                                if (workTrackFormat.Layout.Data[sindex].Cylinder == track / 2 &&
                                                    workTrackFormat.Layout.Data[sindex].Head == head)
                                                {
                                                    sectors[si] |= 2;
                                                    foundHeaders++;
                                                }
                                                else
                                                {
                                                    Log.Info?.Out($"Не совпадает Cylinder или Head в заголовке сектора {dSector} на треке {track} (цилиндр {track / 2} сторона {track % 2}): {workTrackFormat.Layout.Data[sindex]}");
                                                }
                                            }
                                        }

                                        // Поиск экстра-секторов на треке.

                                        int extraSectors = 0;
                                        for (int ii = 0; ii < workTrackFormat.Layout.Cnt; ii++)
                                        {
                                            for (int jj = 0; jj < pars.CurrentTrackFormat.Layout.Cnt; jj++)
                                            {
                                                if (pars.CurrentTrackFormat.Layout.Data[jj].SectorNumber == workTrackFormat.Layout.Data[ii].SectorNumber &&
                                                    pars.CurrentTrackFormat.Layout.Data[jj].SizeCode == workTrackFormat.Layout.Data[ii].SizeCode)
                                                {
                                                    goto found;
                                                }
                                            }
                                            extraSectors++;
                                            found :;
                                        }
                                        if (extraSectors > maxExtraSectors)
                                        {
                                            maxExtraSectors = extraSectors;
                                            longestTrackFormat.Assign(workTrackFormat);
                                        }

                                        if (foundHeaders == sectorsToCheck)
                                        {
                                            break;
                                        }
                                    }
                                    int noHeaderCnt = 0;
                                    for (int sn = sec0Num, si = sn - track * pars.SectorsOnTrack; sn < sec0NtNum; sn++, si++)
                                    {
                                        if (pars.Image.Sectors[sn] == SectorProcessResult.Good || (sectors[si] & 1) == 1)
                                        {
                                            continue;
                                        }
                                        if ((sectors[si] & 2) == 0)
                                        {
                                            // 4 (100b) означает что заголовок не найден за нужное число попыток
                                            // и больше читать этот сектор не надо, т.к. все попытки исчерпаны.
                                            sectors[si] |= 4;
                                            if (!badWritten)
                                            {
                                                pars.Image.WriteBadSectors(sn, 1, true);
                                            }
                                            noHeaderCnt++;
                                        }
                                    }
                                    if (scanResult)
                                    {
                                        pars.CurrentTrackFormat.Sync(workTrackFormat);
                                    }
                                    Log.Trace?.Out($"На треке {track} не найдены заголовки {noHeaderCnt} секторов.");
                                    if (noHeaderCnt > 0)
                                    {
                                        missedHeaders = true;
                                    }
                                    if (maxExtraSectors > 0)
                                    {
                                        Log.Info?.Out($"Трек {track} имеет несоответствующий формат: {longestTrackFormat.GetFormatName()} | {longestTrackFormat.Layout.Cnt} sectors | {longestTrackFormat.ToStringAsSectorArray()}");
                                    }
                                }
                            }
                        }
                        else if (error == 23)
                        {
                            writeBad = false;
                            pars.Image.WriteBadSectors(diskSectorNum, memoryHandle, 1, false);
                        }
                        if (writeBad && !badWritten)
                        {
                            pars.Image.WriteBadSectors(diskSectorNum, 1, noHeader);
                        }
                    }
                    if (Aborted)
                    {
                        return;
                    }
                }
                Log.Trace?.Out($"Время чтения трека: {GP.ToString(trackTimer.ElapsedMs, 2)}");
                trackTimer.Restart();
                if (!wasError)
                {
                    break;
                }
            }
        }
Example #6
0
 public DiskReader(DiskReaderParams pars)
 {
     Params = pars;
 }
        public ReaderBase(Control parent, int sectorSize, int sectorsOnTrack, DiskReaderParams dparams)
        {
            Parent         = parent;
            SectorSize     = sectorSize;
            SectorsOnTrack = sectorsOnTrack;
            parent.SuspendLayout();
            Params   = dparams;
            readSide = new GroupBox()
            {
                Parent = parent, Left = 202, Top = 9, Width = 105, Height = 83, Text = "Read Side"
            };
            side0 = new RadioButton()
            {
                Parent = readSide, Left = 6, Top = 19, Text = "Side 0", AutoSize = true
            };
            side1 = new RadioButton()
            {
                Parent = readSide, Left = 6, Top = 39, Text = "Side 1", AutoSize = true
            };
            sideBoth = new RadioButton()
            {
                Parent = readSide, Left = 6, Top = 60, Text = "Both", AutoSize = true, Checked = true
            };
            sectorReadAttemptsL = new Label()
            {
                Parent = parent, Left = 312, Top = 3, Text = "Sector Read Attempts", AutoSize = true
            };
            sectorReadAttempts = new TextBox()
            {
                Parent = parent, Left = 315, Top = 19, Text = "1"
            };
            firstTrackL = new Label()
            {
                Parent = parent, Left = 313, Top = 44, Text = "First Track", AutoSize = true
            };
            lastTrackL = new Label()
            {
                Parent = parent, Left = 375, Top = 44, Text = "Last Track", AutoSize = true
            };
            firstTrack = new TextBox()
            {
                Parent = parent, Left = 315, Top = 61, Width = 56, Text = "0"
            };
            lastTrack = new TextBox()
            {
                Parent = parent, Left = 378, Top = 61, Width = 56, Text = MainForm.MaxTrack.ToString()
            };
            imageGB = new GroupBox()
            {
                Parent = parent, Left = 651, Top = 5, Width = 381, Height = 213, Text = "Образ"
            };
            newImage = new Button()
            {
                Parent = imageGB, Left = 300, Top = 37, Width = 75, Height = 23, Text = "New"
            };
            loadImage = new Button()
            {
                Parent = imageGB, Left = 300, Top = 66, Width = 75, Height = 23, Text = "Load"
            };
            saveImage = new Button()
            {
                Parent = imageGB, Left = 300, Top = 94, Width = 75, Height = 23, Text = "Save"
            };
            showCatalogue = new Button()
            {
                Parent = imageGB, Left = 170, Top = 184, Width = 75, Height = 23, Text = "Catalogue"
            };
            showCatFromTrack = new Button()
            {
                Parent = imageGB, Left = 250, Top = 184, Width = 128, Height = 23, Text = "Catalogue From Track"
            };
            setSize = new Button()
            {
                Parent = imageGB, Left = 10, Top = 184, Width = 75, Height = 23, Text = "Set Size"
            };
            mergeImage = new Button()
            {
                Parent = imageGB, Left = 90, Top = 184, Width = 75, Height = 23, Text = "Merge"
            };
            readForward = new Button()
            {
                Parent = parent, Left = 549, Top = 10, Width = 96, Height = 23, Text = "Read Forward"
            };
            readBackward = new Button()
            {
                Parent = parent, Left = 549, Top = 36, Width = 96, Height = 23, Text = "Read Backward"
            };
            readRandomSectors = new Button()
            {
                Parent = parent, Left = 511, Top = 72, Width = 134, Height = 23, Text = "Read Random Sectors"
            };
            abortButton = new Button()
            {
                Parent = parent, Left = 568, Top = 193, Width = 75, Height = 23, Text = "Abort"
            };
            readCatalogue = new Button()
            {
                Parent = parent, Left = 6, Top = 50, Width = 144, Height = 23, Text = "Read Catalogue"
            };
            dataRateL = new Label()
            {
                Parent = parent, Left = 3, Top = 10, Text = "Data Rate", AutoSize = true
            };
            dataRate = new ComboBox()
            {
                Parent = parent, Left = 6, Top = 26, Width = 121, Height = 21, DropDownStyle = ComboBoxStyle.DropDownList
            };
            upperSidePanel = new GroupBox()
            {
                Parent = parent, Left = 316, Top = 99, Width = 156, Height = 89, Text = "Upper Side Head Parameter"
            };
            upperSide0 = new RadioButton()
            {
                Parent = upperSidePanel, Left = 6, Top = 20, Text = "Head = 0", AutoSize = true
            };
            upperSide1 = new RadioButton()
            {
                Parent = upperSidePanel, Left = 6, Top = 43, Text = "Head = 1", AutoSize = true
            };
            upperSideAutodetect = new RadioButton()
            {
                Parent = upperSidePanel, Left = 6, Top = 66, Text = "Autodetect", AutoSize = true, Checked = true
            };
            readModePanel = new GroupBox()
            {
                Parent = parent, Left = 202, Top = 99, Width = 105, Height = 62, Text = "Read Mode"
            };
            readModeStandard = new RadioButton()
            {
                Parent = readModePanel, Left = 6, Top = 19, Text = "Standard", AutoSize = true, Checked = !Timer.IsHighResolution
            };
            readModeFast = new RadioButton()
            {
                Parent = readModePanel, Left = 6, Top = 39, Text = "Fast", AutoSize = true, Checked = Timer.IsHighResolution, Enabled = Timer.IsHighResolution
            };
            trackLayoutL = new Label()
            {
                Parent = parent, Left = 0, Top = 205, Text = "Sector Layout:", AutoSize = true
            };
            trackLayoutLV = new Label()
            {
                Parent = parent, Left = 75, Top = 205, Text = "...", AutoSize = true
            };
            drivePanel = new GroupBox()
            {
                Parent = parent, Left = 152, Top = 9, Width = 46, Height = 62, Text = "Drive"
            };
            driveA = new RadioButton()
            {
                Parent = drivePanel, Left = 6, Top = 19, Text = "A:", AutoSize = true, Checked = Params.Drive == Drive.A
            };
            driveB = new RadioButton()
            {
                Parent = drivePanel, Left = 6, Top = 39, Text = "B:", AutoSize = true, Checked = Params.Drive == Drive.B
            };
            trackLayoutL.Visible  = MainForm.Dev;
            trackLayoutLV.Visible = MainForm.Dev;
            for (int i = 0; i < DataRateArray.Length; i++)
            {
                dataRate.Items.Add(DataRateArray[i].ToString().Replace("FD_RATE_", ""));
            }
            dataRate.MouseWheel += DataRate_MouseWheel;
            FillControls();
            stats = new ImageStatsTable(imageGB, SystemColors.Window, SystemColors.ControlText);
            stats.SetPosition(6, 16);
            stats.Repaint();
            map = new Map(MainForm.MaxTrack, SectorSize, SectorsOnTrack, parent, Color.White, stats);
            map.SetPosition(0, 227);
            map.ReadBoundsChanged += Map_ReadBoundsChanged;
            map.FirstTrack         = Params.FirstTrack;
            map.LastTrack          = Params.LastTrack;
            map.Repaint();

            sectorReadAttempts.TextChanged  += SectorReadAttempts_TextChanged;
            firstTrack.TextChanged          += SectorReadAttempts_TextChanged;
            lastTrack.TextChanged           += SectorReadAttempts_TextChanged;
            side0.CheckedChanged            += SectorReadAttempts_TextChanged;
            side1.CheckedChanged            += SectorReadAttempts_TextChanged;
            sideBoth.CheckedChanged         += SectorReadAttempts_TextChanged;
            dataRate.SelectedIndexChanged   += SectorReadAttempts_TextChanged;
            upperSide0.CheckedChanged       += SectorReadAttempts_TextChanged;
            upperSide1.CheckedChanged       += SectorReadAttempts_TextChanged;
            readModeStandard.CheckedChanged += SectorReadAttempts_TextChanged;
            readModeFast.CheckedChanged     += SectorReadAttempts_TextChanged;
            driveA.CheckedChanged           += SectorReadAttempts_TextChanged;
            driveB.CheckedChanged           += SectorReadAttempts_TextChanged;
            abortButton.Click += AbortButton;
            setSize.Click     += SetSize;
            parent.ResumeLayout(false);
        }