Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Отключение «опасных» графических режимов в DOS

Радиомир. Ваш компьютер» 12/2002)
Дата последнего редактирования: 22.01.2003.

Допустим, ваша SVGA-видеокарта поддерживает графические режимы с большим разрешением, чем способен отобразить ваш монитор (например, видеокарта «может» 1024x768, а монитор — только 640x480). Тогда вполне возможна ситуация, когда некоторая программа, обнаружив доступность режима с высоким разрешением, возьмёт и включит его. Например, популярный вьювер SEA по умолчанию запускается в режиме 800x600. Также вы сами можете по неосторожности или не догадываясь о том, что делаете, установить в какой-нибудь программе режим с высоким разрешением.

К чему это может привести? В лучшем случае вместо нормального изображения на экране появятся «полосы». Не видя изображения, вы можете и не выйти из программы, придётся перезагружаться, что само по себе достаточно неприятно. В худшем случае — ваш монитор может выйти из строя. Поэтому включение таких графических режимов, конечно, хорошо бы запретить.

Работа с дополнительными графическими режимами происходит с использованием функций, определённых стандартом VESA. Для решения поставленной задачи нам будут нужны только три функции. С помощью одной из них можно получить список номеров дополнительных режимов, с помощью другой — получить по номеру режима информацию о нём (доступен или нет, разрешение по горизонтали и вертикали…), и с помощью ещё одной функции — включить режим с указанным номером.

Напишем резидентную программу, которая будет перехватывать обращение к функции получения информации о конкретном режиме. (Далее я буду называть эту программу резидентом, а ту программу, которой надо запретить включение определённых графических режимов, я буду называть просто программой.) Когда программа обратится к функции получения информации о конкретном режиме, управление будет передано резиденту, который вызовет оригинальный обработчик функции, а затем проверит полученную информацию о режиме: не является ли этот режим «опасным», т.е. с разрешением больше указанного? Если является, то резидент установит в возвращаемой структуре данных признак «режим недоступен». Таким образом, программа посчитает этот режим недоступным и не будет пытаться его включить.

А если будет? А если программа вообще не делает каких-либо проверок перед включением режима? Это, конечно, неправильно, но и исключать такой ситуации нельзя. Поэтому предусмотрим в резиденте перехват ещё одной функции — включения режима, и если включаемый режим относится к «опасным», то вместо его включения будем возвращать признак ошибки, как если бы программа пыталась включить несуществующий режим.

Чтобы резидент мог определить, является ли включаемый режим «опасным» (не обращаясь для этого к функции получения информации о режиме: для неё нужен 256-байтный буфер, а памяти хотелось бы расходовать как можно меньше), он должен при запуске запросить информацию о всех поддерживаемых режимах и запомнить в специальном списке номера «опасных» режимов. Тогда в дальнейшем надо будет лишь проверить, есть номер в этом списке или нет.

Теперь приведу информацию по используемым функциям VESA — только ту, которая необходима для понимания логики работы описываемой резидентной программы. Если вас интересует полная информация, можете ознакомиться с [1] или посетить www.vesa.org.

Обращение к функциям VESA производится с помощью прерывания INT 10h. При этом в AH должно быть число 4Fh, а в AL — номер функции. После обращения к функции в AL должно быть 4Fh, иначе стандарт VESA не поддерживается.

Функция 0 — получение информации о версии VESA, производителе, о списке режимов и т.п. На входе в ES:DI надо указать адрес 256-байтного буфера, куда будет помещена информация, а на выходе в этом буфере по смещению 16 будут расположены четыре байта — указатель на список номеров потенциально поддерживамых видеорежимов (доступность каждого режима надо проверять отдельно, с помощью функции 1). В этом списке под каждый номер выделяется слово (два байта), значение FFFFh служит признаком конца списка. Фактически номер режима занимает младшие 9 битов слова, причем старший из этих битов указывает, принадлежит ли данный режим стандарту VESA (если да, бит равен 1). То есть VESA-режим однозначно определяется младшим байтом слова.

Функция 1 — получение информации о режиме по его номеру. На входе в CX задаётся номер режима (то самое число из списка), а в ES:DI указывается адрес 256-байтного буфера, куда и будет помещена информация о режиме. На выходе в этом буфере по смещению 18 будет слово, где хранится разрешение по горизонтали, а по смещению 20 — разрешение по вертикали. Младший бит первого слова буфера определяет доступность режима (0 — недоступен).

Функция 2 — включение режима. На входе в младших 9 битах регистра BX указывается номер режима, а в старших разрядах — некоторые флаги, которые я здесь рассматривать не буду. Если запрашиваемого режима нет, на выходе в AX будет 1.

Ниже приведён исходный текст резидентной программы с подробными комментариями. Программа предназначена для компиляции в com-файл — с именем, например, «save_mon.com». Перед компиляцией надо подставить в текст программы необходимые значения максимального разрешения по горизонтали и вертикали (константы Max_X_Res и Max_Y_Res). По умолчанию они равны соответственно 640 и 480.

Max_X_Res  equ     640  ;Максимальное разрешение по горизонтали.
Max_Y_Res  equ     480  ;Максимальное разрешение по вертикали.

codesg     segment
           assume  cs:codesg
           assume  ds:codesg
           assume  es:codesg

           org     100h

begin:     jmp     init ;Переход к инициализации.

;Новый обработчик прерывания 10h.

new_10h:   cmp     ax,4f01h   ;Проверка номера вызванной функции.
           jz      new_4f01

           cmp     ax,4f02h
           jz      new_4f02

;Вызвана не перехватываемая функция - передаём управление
;на старый обработчик:

goto_old:  db      0eah       ;Код межсегментного JMP.
old_10h    dd      ?          ;Адрес старого обработчика.

;Обработка функции 1 (получение сведений о режиме):

new_4f01:  push    ds

           push    cs         ;Вызываем
           pop     ds         ;старый
           pushf              ;обработчик.
           call    old_10h

;По адресу ES:DI сформирована структура данных с информацией о режиме.
;Теперь, если горизонтальное (ES:DI+18) или вертикальное (ES:DI+20)
;разрешение режима больше максимального, сбрасываем младший бит первого
;слова структуры данных, тем самым объявляя режим недоступным.

           push    es
           pop     ds
           cmp     word ptr [di+18],Max_X_Res
           ja      main_2
           cmp     word ptr [di+20],Max_Y_Res
           jbe     main_3
main_2:    and     byte ptr [di],0feh

main_3:    pop     ds
           iret

;Обработка функции 2 (установка режима):

new_4f02:  and     bh,1      ;Проверка: это VESA-режим?
           jz      goto_old  ;Если нет - переход на старый обработчик.

;Проверка: номер режима (BL) содержится в списке "опасных" режимов?

           push    ds
           push    di
           push    cx

           push    cs
           pop     ds

           mov     di,offset buf_1
           mov     cx,modes

main_8:    cmp     bl,[di]
           je      main_9
           inc     di
           loop    main_8

;Режим не найден в списке "опасных" (т.е. можно включать).
;Переходим на старый обработчик:

           pop     cx
           pop     di
           pop     ds
           jmp     short goto_old

;Это "опасный" режим - не включаем, выходим с AX=014Fh (признак ошибки):

main_9:    pop     cx
           pop     di
           pop     ds
           mov     ax,014fh
           iret

;Определённые ниже данные должны быть описаны именно в указанном
;порядке - это используется при определении размера резидентной части.

modes      dw      ?               ;Количество "опасных" режимов.
buf_1      db      256 dup (?)     ;Список "опасных" режимов.
buf_2      db      256 dup (?)     ;Вспомогательный буфер.

;Инициализация.

init:      mov     ax,4f00h        ;Получаем информацию о поддерживаемых
           mov     di,offset buf_2 ;режимах (а заодно и о наличии VESA).
           int     10h

           cmp     al,4fh
           je      vesa_ok

;VESA нет (в AL не 4Fh) - выходим с печатью сообщения об ошибке:

           mov     dx,offset err_text_1
           jmp     err_exit

;Из полученного списка режимов (сегмент и смещение которого находятся
;соответственно по адресам buf_2+16 и buf_2+14) копируем в buf_1 номера
;режимов, относящихся к VESA (у них 8-й бит равен 1):

vesa_ok:   mov     ax,word ptr buf_2+16
           push    ax
           pop     es
           mov     bx,word ptr buf_2+14

;ES:BX указывает на список режимов. Копируем, пока не встретим FFFFh -
;признак конца списка:

           mov     di,offset buf_1
           xor     cx,cx    ;Количество VESA-режимов.

init_3:    mov     ax,es:[bx]
           cmp     ax,0ffffh
           je      init_1

           and     ah,1     ;Режим относится к VESA?
           jz      init_2

           mov     [di],al
           inc     di
           inc     cx
           cmp     cx,100h  ;На всякий случай, чтобы buf_1 не переполнился.
           je      init_1

init_2:    add     bx,2
           jmp     short init_3

;Если VESA-режимов не обнаружено (CX=0) - выходим с печатью
;сообщения об ошибке:

init_1:    and     cx,cx
           mov     dx,offset err_text_2
           jz      err_exit

           push    cs
           pop     es

;Оставляем в buf_1 только номера "опасных" режимов - у которых
;разрешение больше указанного:

           mov     di,offset buf_1
           push    di
           pop     si
           xor     bx,bx  ;Количество "опасных" режимов.

init_5:    push    di
           push    si
           push    cx
           push    bx

           mov     ax,4f01h            ;Запрашиваем
           mov     ch,al     ;ch:=1    ;информацию
           mov     cl,[di]             ;о режиме.
           mov     di,offset buf_2
           int     10h

           pop     bx
           pop     cx
           pop     si
           pop     di

           cmp     word ptr [buf_2+18],Max_X_Res
           ja      init_4
           cmp     word ptr [buf_2+20],Max_Y_Res
           jbe     init_6

init_4:    mov     al,[di]
           mov     [si],al
           inc     si
           inc     bx

init_6:    inc     di
           loop    init_5

;Если нет "опасных" режимов (BX=0) - выходим с печатью сообщения
;об ошибке:

           and     bx,bx
           mov     dx,offset err_text_3
           jz      err_exit

           mov     modes,bx  ;Сохраняем количество "опасных" режимов.

           mov     ax,3510h               ;Получаем и запоминаем
           int     21h                    ;адрес старого
           mov     word ptr old_10h,bx    ;обработчика INT 10h.
           mov     word ptr old_10h+2,es

           mov     dx,offset new_10h      ;Устанавливаем
           mov     ax,2510h               ;адрес нового
           int     21h                    ;обработчика INT 10h.

;При выходе оставляем в резидентной части ровно столько байтов
;от buf_1, сколько используется:

           mov     dx,offset buf_1        ;Завершить и остаться
           add     dx,modes               ;резидентом.
           int     27h

;Печать сообщения об ошибке (его адрес задан в DX) и выход:

err_exit:  mov     ah,9
           int     21h
           ret

err_text_1 db      'VESA BIOS not found!',13,10,'$'
err_text_2 db      'VESA modes not found!',13,10,'$'
err_text_3 db      'Hi-res modes not found!',13,10,'$'

codesg     ends
           end     begin

Скачать программу (1 КБ ZIP)
Скачать листинг программы в текстовом виде (2 КБ ZIP)

Размер резидентной части программы зависит от количества обнаруженных «опасных» режимов: каждый такой режим увеличивает длину на один байт. У меня программа занимает в памяти 352 байта.

Если вы используете драйвер UniVBE или аналогичный, то запускать программу следует после него.

Литература

  1. С.Андрианов. «VESA: стандарт новый, проблемы старые». «Мир ПК» 7/1998.

Другие мои статьи о резидентных программах для DOS:

Страница Ивана Рощина > Статьи >