Exemplo n.º 1
0
 public void ReadFromXml(XmlTextReader xml)
 {
     Drive                   = (Drive)Enum.Parse(typeof(Drive), xml.GetAttribute("Drive"));
     DataRate                = (DataRate)Enum.Parse(typeof(DataRate), xml.GetAttribute("DataRate"));
     Side                    = (DiskSide)Enum.Parse(typeof(DiskSide), xml.GetAttribute("Side"));
     ReadMode                = (ReadMode)Enum.Parse(typeof(ReadMode), xml.GetAttribute("ReadMode"));
     UpperSideHead           = (UpperSideHead)Enum.Parse(typeof(UpperSideHead), xml.GetAttribute("UpperSideHead"));
     UpperSideHeadAutodetect = bool.Parse(xml.GetAttribute("UpperSideHeadAutodetect"));
     SectorReadAttempts      = Math.Max(0, int.Parse(xml.GetAttribute("SectorReadAttempts")));
     FirstTrack              = Math.Max(0, int.Parse(xml.GetAttribute("FirstTrack")));
     FirstTrack              = Math.Min(171, FirstTrack);
     LastTrack               = Math.Min(172, int.Parse(xml.GetAttribute("LastTrack")));
     LastTrack               = Math.Max(0, LastTrack);
 }
Exemplo n.º 2
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="result">0 - head определен. 1 - Заголовки не найдены. 2 - Заголовок найден, но он не TR-DOS</param>
        /// <param name="track"></param>
        /// <param name="trackFormat"></param>
        /// <returns></returns>
        private int ScanHeadParameter(ref UpperSideHead result, int track, TrackFormat trackFormat)
        {
            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();

            if (!Driver.ReadId(DriverHandle, pars, out cmdResult))
            {
                Log.Trace?.Out($"Функция ReadId вернула false. Cylinder: {cmdResult.cyl} | Sector: {cmdResult.sector} | Size: {cmdResult.size} | Head: {cmdResult.head} | LastError: {Marshal.GetLastWin32Error()}");
                return(1);
            }
            //if (cmdResult.sector < 1 || cmdResult.sector > 16 || cmdResult.size != 1)
            //{
            //    Log.Trace?.Out($"Формат не TR-DOS. Cylinder: {cmdResult.cyl} | Sector: {cmdResult.sector} | Size: {cmdResult.size} | Head: {cmdResult.head}");
            //    return 2;
            //}
            trackFormat.SyncByHeader(track, cmdResult.sector);
            Log.Trace?.Out($"Cylinder: {cmdResult.cyl} | Sector: {cmdResult.sector} | Size: {cmdResult.size} | Head: {cmdResult.head}");
            result = (UpperSideHead)cmdResult.head;
            return(0);
        }
Exemplo n.º 3
0
        public static unsafe int ScanTrack(IntPtr driverHandle, int track, UpperSideHead upperSideHead)
        {
            tagFD_SEEK_PARAMS seekParams = new tagFD_SEEK_PARAMS()
            {
                cyl  = (byte)(track >> 1),
                head = upperSideHead == UpperSideHead.Head1 ? (byte)(track & 1) : (byte)0
            };
            uint dwRet;

            DeviceIoControl(driverHandle, IOCTL_FDCMD_SEEK, (IntPtr)(&seekParams), (uint)sizeof(tagFD_SEEK_PARAMS), IntPtr.Zero, 0, out dwRet, IntPtr.Zero);
            tagFD_SCAN_PARAMS scanParams = new tagFD_SCAN_PARAMS()
            {
                Flags = 0,
                Head  = (byte)(track & 1)
            };
            tagFD_SCAN_RESULT scanResult = new tagFD_SCAN_RESULT();
            bool r = DeviceIoControl(driverHandle, IOCTL_FD_SCAN_TRACK, (IntPtr)(&scanParams), (uint)sizeof(tagFD_SCAN_PARAMS),
                                     (IntPtr)(&scanResult), (uint)sizeof(tagFD_SCAN_RESULT), out dwRet, IntPtr.Zero);
            int error = !r?Marshal.GetLastWin32Error() : 0;

            ScanResult scanResultNew = new ScanResult(scanResult);

            return(error);
        }
Exemplo n.º 4
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);
        }
Exemplo n.º 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;
                }
            }
        }
Exemplo n.º 6
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);
        }