Создадим файл keyrb.inc. Он будет ответственен за обработку нажатия клавиш. Это будет работать так: мы создадим бесконечный цикл, в начале которого будет читаться клавиша. Если это одна из нужных нам клавиш, то будут выполняться определённые действия.

KeybInput:

@keypress:
xor ax,ax ;
читаем клавишу
int 16h

test al,al ; проверим, расширенный ASCII или нет 
jz @extended_scancode

jmp @keypress

@extended_scancode:
cmp ah, 48h ;
стрелка вверх
jz MoveCursorUp

cmp ah, 50h ; стрелка вниз
jz MoveCursorDown
jmp @keypress
ret

При нажатии кнопок «вврех» и «вниз» вызываются соответствующие подпрограммы перемещения курсора MoveCursorUp и MoveCursorDown.

Теперь добавим процедуры перемещения курсора MoveCursorUp и MoveCursorDown в graphic.inc. Для удобства напишем вспомогательную процедуру для вывода одного символа на экран
PrintSymbol. Нам понадобятся две подфункции ah=02h — установить курсор (dh,dl — строка, столбец) и ah=09h — вывод символа в al на экран (cx — количество символов, bl — цвет). Для обоих необходимо указать номер видеостраницы в bh.

; Процедура вывода на экран одного символа
;
dh,dl — строка,столбец; bl — цвет; al — символ

PrintSymbol:
pusha
xor bh,bh
mov ah,02h ;
установим курсор 
int 10h
mov ah,09h ;
и выведем символ в al на экран
mov cx,1 
int 10h
popa
ret

Мы должны организовать перемещение курсора вверх и вниз, при этом необходимо ограничить движение курсора, чтобы он не выходил за границы меню. Чтобы определять положение курсора, создадим переменную MenuItemSelected, которая будет изменяться каждай раз, как меняется положение курсора и будет показывать номер выбранного пункта меню (начиная с нуля). Для этого нам понадобятся ещё две переменные (в принципе их можно было бы объявить константами) — MenuFirstItemY — позиция первого пункта меню и MenuLastItemY — позиция последнего пункта меню по оси Y. Конечно удобнее было бы указать положение первого пункта и число элементов меню, но это повлечёт увеличение кода, да и каждый раз пересчитывать MenuLastItemY не разумно. Если координата курсора будет равна одному из крайних положений, то перерисовывать его не имеет смысла. Не следует забывать, что нумерация строк и столбцов начинается с нуля, а не с еденицы.

PositionXY dw 0000h ; в этой переменной будем сохранять координаты курсора
MenuItemSelected db 00h
MenuFirstItemY db 00h
MenuLastItemY db 00h

MoveCursorUp:
pusha
mov dx,[PositionXY]
cmp dh,[MenuFirstItemY] ;
проверим, находится ли курсор в крайнем положении
jz @1 ; если да, то прыжок в конец процедуры
mov bl,2 ; цвет — зелёный
mov al,’ ‘ ; сотрём старый курсор, написав на его месте пробел
call PrintSymbol
dec dh ;
уменьшим координату Y курсора, тем самым подняв его вверх
mov al,’>’ ; в al — символ курсора
call PrintSymbol 
mov [PositionXY],dx ;
сохраним новые координаты курсора
sub dh,[MenuFirstItemY] ; посчитаем номер выбранного пункта меню
mov [MenuItemSelected],dh
@1:
popa
jmp @keypress

; В этой процедуре всё то же, только координата Y курсора увеличивается на
единицу

MoveCursorDown:
pusha
mov dx,[PositionXY]
cmp dh,[MenuLastItemY]
jz @2
mov bl,2 
mov al,’ ‘ 
call PrintSymbol
inc dh ;
увеличим координату Y курсора, тем самым опустив его вниз
mov al,’>’ 
call PrintSymbol 
mov [PositionXY],dx 
sub dh,[MenuFirstItemY] 
mov [MenuItemSelected],dh 
@2:
popa
jmp @keypress

Ну что же, теперь у нас уже есть что посмотреть. Для этого нам и понадобится эмулятор Bochs. Если его найти не удалось, то можно воспользоваться программой RawwriteWin
(http://uranus.it.swin.edu.au/~jn/linux) или аналогичной, которая может записывать образ на дискету. Такие программы несложно найти в интернете.
Создадим файл main.asm. 

Main_Program:
call DrawInterface

mov ax,0303h ; зададим начальное положение курсора (X=3, Y=3)
mov [PositionXY],ax

mov [MenuFirstItemY],2 ; зададим область, в которой может двигаться курсор
mov [MenuLastItemY],6

call MoveCursorUp ; для того, чтобы нарисовать курсор, поместим его на
единицу ниже 

call KeybInput
ret

Пока здесь происходит вызов процедур, отвечающих за оформление и клавиатуру и
инициализация переменных, отвечающих за курсор меню. Не забудем подключить соответствующие файлы:

include ‘graphic.inc’
include ‘keyb.inc’

Создадим файл make.bat. Кючевое слово include фактически объединяет два файла в один, поэтому достаточно написать файл, с которого всё начинается:

fasm bootsect.asm image.bin

Запустив этот .bat, мы получим готовый файл образа image.bin. Неплохо было бы сделать, чтобы окно не закрывалось сразу, так как если не создался конечный файл, выведется ошибка, поэтому в конце 
bat’ника удобно написать 

pause

а) Bochs.

Для начала эмуляции Bochs необходимо настроить — задать параметры эмулируемой машины. Для этого служит конфигурационный файл. Создадим файл adjustments следующего содержания
(это для версии 2.1, но практически то же и в других версиях, подробную информацию можно найти в документации по Bochs
http://bochs.sf.net/docs-html
и http://bochs.sf.net/doc/docbook/index.html):

floppya: 1_44= диск\…\…\image.bin, status=inserted
floppyb: 1_44= a:, status=ejected
romimage: file=BIOS-bochs-latest, address=0xf0000
vgaromimage: VGABIOS-elpin-2.40
megs: 32
boot: floppy
vga_update_interval: 30000
keyboard_serial_delay: 250
floppy_command_delay: 5000
ips: 500000
mouse: enabled=0
private_colormap: enabled=0
i440fxsupport: enabled=0
time0: 0
newharddrivesupport: enabled=1
log: —
panic: action=ask
error: action=report
info: action=report
debug: action=ignore

Этот файл должен находится в папке, где находится
Bochs. После последней строчки обязательно должен стоять переход на следующую
строчку, в противном случае Bochs начинает орать и не запускается.
Дисковод B: (реальный дисковод) можно включить, нажав в Bochs соответствующий значок и поставив галочку у inserted.

Далее создадим файл run.bat:

cd диск\папка, где установлен Bochs\
bochs -q -f adjustments

В Bochs можно грузится и с реальной дискеты, для этого надо написать (за место первой строки)

floppya: 1_44=A:, status=inserted

б) При отсутствии Bochs или другого эмулятора

При отсутствии эмулятора придётся всё делать вручную. На дискету (совсем необязательно форматированную) с помощью какой либо программы (например, Rawwrite или RawwriteWin) записыается образ, после чего компьютер перезагружается, а в BIOS устанавливается загрузка с дискеты. Если вы используете RawwriteWin, то можно создать .bat файл:

rawwritewin —write —copies 1 —drive 0 image.bin

В этом случае run.bat не нужен.

Примечание

На настоящей машине, конечно, более надёжно, так как эмулятор это всё-таки эмулятор. Я несколько раз сталкивался с ситуацией, когда в эмуляторе программа совершенно отказывалась нормально работать, хотя прекрасно работала на реальной машине (это не относится к программе, которую мы сейчас пишем, она работала и там, и там). Зато на реальной машине нет таких возможностей дебаггинга (если на реальной машине и в отсутствие операционной системы они вообще есть 🙂 ), да при использовании эмулятора компьютер не надо постоянно перезагружать.

Запускаем run.bat и наслаждаемся результатом 😉 .Правда, при отсутствии эмулятора наслаждение будет несколько испорчено необходимостью перезагрузки компьютера.

Курсор двигается, но пока ничего выбрать нельзя, поэтому в файл keyb.bat добавим обработку нажатия клавиши Enter.
Добавим проверку на enter в бесконечный цикл:

cmp al, 0dh ;enter
jz @key_enter 

В этом обработчике мы будем смотреть, что надо делать в зависимости от номера пункта меню.

; обработка нажатия Enter
@key_enter:
cmp [MenuItemSelected],0
jz BootFromCD

cmp [MenuItemSelected],1
jz BootFromFloppy

cmp [MenuItemSelected],2
jz BootFromHDA1

cmp [MenuItemSelected],3
jz BootFromHDB1

cmp [MenuItemSelected],4
jz @MenuItem_Int19h
jmp @keypress

;Quick reboot (using int19h)
@MenuItem_Int19h:
int 19h ;
«горячая» перезагрузка
jmp @keypress

Для «горячей» перезагрузки (при которой BIOS заново проверяет всё, с чего можно загрузится не перезагружая компьютер полностью) требуется вызвать прерывание int 19h безо всяких параметров.

Теперь создадим файл boot.inc, в котором опишем соответствующие процедуры. Общий код чтения для всех диcков один (идёт после метки @boot_system), так как бутсектор всегда находится в одном месте: головка 0, цилиндр 0, сектор 1. После загрузки с CD-ROM’а дисковод станет диском B, образ при эмуляции будет диском A. Итак, устройства: dl = 01h — диск B, dl = 80h — Primary Master, dl = 81h — Primary Slave. После попытки чтения идёт проверка на ошибки. Если их нет, то проверим наличие сигнатуры 55AAh, и при её наличии передадим управление загруженному бутсектору. 

BootFromFloppy:
mov dl,01h 
jmp @boot_system

BootFromHDA1:
mov dl,80h 
jmp @boot_system

BootFromHDB1:
mov dl,81h 

@boot_system:
push 0000
pop es
mov bx,7c00h

mov ah,02h 
mov cl,01h
mov al,01h
mov dh,00h
mov ch,00h
int 13h
jc @read_error ;
проверим, не было ли ошибок чтения

mov bx,7DFEh
mov ax,[bx]
cmp ax,55AAh
jz @invalid_signature

@boot_anyway:

jmp 7C00h

В случае какой-либо ошибки на экран будут выводиться ругательные надписи. Для этого напишем вспомогательную процедуру вывода строки, а для того, чтобы каждый раз не указывать в dx координаты курсора, в bl цвет и т.д., напишем вспомогательную процедуру. Эта процедура будет выводить на экран строку следующего формата: X,Y,цвет,’текст’,0

PrintStrF:
pusha
mov dx,[si]
add si,2
mov bl,[si]
inc si
@put_symb:
lodsb
cmp al,0
jz @end_of_string
call PrintSymbol
inc dl
jmp @put_symb 
@end_of_string:
popa
ret

Создадим файл text.inc, в котором будем хранить все надписи, и не забудем подключить его:

include ‘text.inc’

Чтобы не возникло проблем, что на экране осталась часть старой надписи, приведём все надписи к одинаковой длине пробелами.

FloppyDriveReadError db 5,9,4, ‘Error reading Floppy Drive B: ‘,0
HDA1ReadEror db 5,9,4, ‘Error reading Primary Master Hard Disk ‘,0
HDB1ReadEror db 5,9,4, ‘Error reading Primary Slave Hard Disk ‘,0
InvalidSignature db 5,9,4, ‘Invalid Boot Signature. Continue anyway (y/n)? ‘,0

Выведем различные сообщения, в зависимости от того, какая ошибка произошла (boot.inc):

@read_error:
lea si,[FloppyDriveReadError]
cmp dl,01h
jz @PrintError

lea si,[HDA1ReadEror]
cmp dl,80h
jz @PrintError

lea si,[HDB1ReadEror]

@PrintError:
call PrintStrF
jmp @keypress

Если у прочитанного сектора неправильная сигнатура, то сообщим об этом пользователю, и спросим, надо
ли передавать бутсектору управление: 

@invalid_signature:
lea si,[InvalidSignature]
call PrintStrF
xor ax,ax ;
прочитаем клавишу 
int 16h
cmp al,’Y’ ;
если Y, то продолжаем
jz @boot_anyway
jmp @keypress

Теперь начинается самой интересное — загрузка с дискеты, на которой записана наша программа. Ведь нормального бутсектора там, где он должен быть — нет. Если у кого возникнет желание, тут вы можете сами написать загрузчик для DOS’a. А мы поступим проще — воспользуемся готовым бутсектором. Чтобы его получить, создадим нормальную загрузочную дискету, запустив дос’овский format c параметром \s:

format a: \s

К сожалению, это без проблем работает только для пользователей Windows 98 и DOS’а, а этим
вряд ли кто-то сейчас пользуется. Пользователи NT систем столкнутся с проблемой — а где взять дос. Ну, наверняка у вас найдётся какой-нибудь загрузочный компакт-диск, а уж там-то (если это не лицензионная винда или
Linux) дос есть. Вытащить его оттуда непросто — он находится в виде образа или спрятан где-то вначале диска и не виден как файл, да и не нужно. Надо просто загрузится с этого диска и отформатировать дискету как описано выше.

Теперь бутсектор необходимо извлечь. Для этого можно воспользоваться Norton Disk Editoro’м или любой другой аналогичной программой. Программу для извлечения бутсектора можно написать и самому. Просто прочитаем первый сектор дискеты (как делали это выше) и с помощью функций DOS сохраним его. Использование функций ДОС’а вовсе не означает неработоспособности её под Windows — она работает под любой версией.

(Продолжение следует)

Check Also

Основы цифровой схемотехники. Собираем сумматор с ускоренным переносом из дискретных микросхем

Говорят, древние программисты собирали свои компьютеры самостоятельно, уверенно обращались…

Оставить мнение