Доступ через MSCDEX драйвер
Знаменитый MSCDEX, созданный еще во времена царствования MS-DOS, несмотря на свои многочисленныех недостатки, все-таки обеспечивал программистов всем необходимым им функционалом и достаточно полно поддерживал возможности существующих в то время приводов. Так, например, чтение отдельных секторов осуществлялось функцией 1508h прерывания INT2Fh, а если возникала необходимость спуститься на "сырой" уровень, мы всегда могли "попросить" драйвер MSCDEX передать приводу ATAPI-пакет напрямую, чем занималась функция 1510h того же прерывания (загляните в список прерываний (Interrupt List[n2k143] ), если нуждаетесь в более подробной информации).
Забавно, но возможности штатного драйвера "новейшей" и "могучей" Windows 9x, не в пример беднее и спуститься на секторный уровень, при этом, не набиве разорвав себе задницу[n2k144] , под ее управлением, по-видимому, нельзя. Судя по всему, архитекторы системы сочли секторный обмен ненужным и к тому же системно-зависимым, а "правильные" приложения должны разрабатываться как полностью переносимые и довольствующиеся исключительно стандартными вызовами интерфейса win32 API. Все остальное от лукавого!
Между тем, для сохранения обратной совместимости с программами, написанными для MS-DOS и Windows 3.1, операционная система Windows 95 поддерживает MSCDEX-интерфейс, причем по соображениям производительности, реализует его не в "настоящем" MSCDEX, который и вовсе может отсутствовать на диске, а в драйвере CD-ROM драйвере, исполняющемся в 32-разрядном защищенном режиме. Выходит, что весь необходимый нам функционал в системе все-таки есть, а значит, есть и надежда как-то до него добраться. Естественно, с уровня ядра эта задача решается без проблем, но… писать свой собственный драйвер только для того, чтобы "пробить интерфейсную шахту" к уже существующему драйверу, –— это маразм какой-то!
К счастью, готовый (и даже задокументированный!) интерфейс между win32-приложениями и MSCDEX-драйвером в системе Windows 9x действительно есть.
К несчастью, он реализован через ж…опу (и … именно через ж…)опу и не надо пытаться вычеркнуть эту фразу, иначе я шибко разозлюсь (последняя фраза предназначается в первую очередь для редакторов). В общих чертах схема "прокладывания туннеля" к драйверу MSCDEX'у выглядит приблизительно так: создав 16-разрядную динамически подключаемую библиотеку (DLL), мы получаем возможность взаимодействовать с интерфейсом DPMI через функции прерывания INT 31h.
Замечание
DPMI (DOS Protected Mode Interface) –— интерфейс, спроектированный специально для того, чтобы разработчики приложений защищенного режима, исполняющихся в среде MS-DOS, могли пользоваться функциями 16-разрядной операционной системы реального режима, коей MS-DOS и является.
Конкретно нас будет интересовать функция 1508h, –— DPMI Simulate Real Mode Interrupt, позволяющая вызывать прерывания реального режима из защищенного. Обращаясь к эмулятору MSCDEX-драйвера через родное для него прерывание INT 2Fh, мы можем делать с приводом практически все, что нам только вздумается, поскольку интерфейс драйвера MSCDEX'a,'а как уже отмечалось ранее, могуч и велик.
Таким образом, вырисовывается следующий программистский маршрут: win32 приложение à 16-разрядная DLL à DMPI Simulate RM Interrupt à MSCDEX à CDFS. Не слишком ли наворочено, а? Уж лучше воспользоваться интерфейсом ASPI (благо в Windows 95 оно[n2k145] присутствуетесть) или засесть за написание собственного драйвера. Тем не менее, даже если вы не собираетесь управлять приводом посредством драйверачерез MSCDEX, знать о существовании такого способа взаимодействия с оборудованием все-таки небесполезно, особенно, если вы планируете заняться взломом чужих программ. В этом случае точки останова, установленные на API- функции, ничего не дадут, поскольку чтение секторов осуществляется через прерывания INT 31h (DMPI) и INT 2Fh.
К сожалению, прямая установка точек останова на последние дает очень много ложных срабатываний, а применение фильтров вряд ли окажется эффективным, поскольку количество возможных вариаций слишком велико. Уж лучше поискать вызовы прерываний в дизассемблерном тексте программы!
Дополнительную информацию по этому вопросу можно найти в технической заметке Q137813, входящей в состав MSDN, распространяемой вместе с Microsoft Visual Studio и озаглавленную как "How Win32 Applications Can Read CD-ROM Sectors in Windows 95". Полный перечень DMPI- и MSCDEX-функций содержится в списке Interrupt-List'e'е Ральфа Брауна[Y146] ,[n2k147] так что никаких проблем с использованием данного приема у вас возникнуть не должно (правда, раздобыть компилятор, способный генерировать 16-разрядный код и "линкер" (linker, иначе компановщик) под Windows 3.1 сегодня не так-то просто! К слову сказать, Microsoft Visual Studio 6.0 для этой цели уже не подходит, ибо, начиная с некоторой версии –— уже сейчас и не вспомню какой –— он утратил возможность создания проектов под операционные системы MS-DOS/Windows 3.1).
ДалееНиже приводится ключевой фрагмент программы (листинг 1.4.29), позаимствованный из MSDN, и демонстрирующий технику вызова прерываний реального режима из 16-разрядных динамически подключаемых библиотек (DLL), исполняющихся в среде Windows.
Листинг 2.1.4.3029. Ключевой фрагмент программы, демонстрирующей технику взаимодействия с драйвером MSCDEX из 16-разрядного защищенного режима
BOOL FAR PASCAL MSCDEX_ReadSector(BYTE bDrive, DWORD StartSector, LPBYTE RMlpBuffer)
{
RMCS callStruct;
BOOL fResult;
// Prepare DPMI Simulate Real Mode Interrupt call structure with
// the register values used to make the MSCDEX Absolute read call.
// Then, call MSCDEX using DPMI and check for errors in both the DPMI
// call and the MSCDEX call
BuildRMCS (&callStruct);
callStruct.eax = 0x1508; // MSCDEX функция "ABSOLUTE READ"
callStruct.ebx = LOWORD(RMlpBuffer); // смещение буфера для чтения сектора
callStruct.es = HIWORD(RMlpBuffer); // сегмент буфера для чтения сектора
callStruct.ecx = bDrive; // буква привода 0=A, 1=B, 2=C и т.д.
callStruct.edx = 1; // читаем один сектор
callStruct.esi = HIWORD(StartSector); // номер читаемого сектора(старшее слово)
callStruct.edi = LOWORD(StartSector); // номер читаемого сектора(младшее слово)
// вызываем прерывание реального режима
if (fResult = SimulateRM_Int (0x2F, &callStruct))
fResult = !(callStruct.wFlags & CARRY_FLAG);
return fResult;
}
BOOL FAR PASCAL SimulateRM_Int(BYTE bIntNum, LPRMCS lpCallStruct)
{
BOOL fRetVal = FALSE; // Assume failure
__asm
{
push di ; сохраняем регистр DI
mov ax, 0300h ; DPMI Simulate Real Mode Interrupt
mov bl, bIntNum ; номер прерывания реального режима для вызова
mov bh, 01h ; бит 0 = 1; все остальные должны быть равны нулю
xor cx, cx ; ничего не копируем из стека PM в стек RM
les di, lpCallStruct ; указатель на структуру со значением регистров
int 31h ; шлюз к DMPI
jc END1 ; если ошибка, – прыгаем на END1
mov fRetVal, TRUE ; все ОК
END1:
pop di ; восстанавливаем регистр DI
}
// возвращаемся
return (fRetVal);
}