.MODEL Tiny .286 .CODE ORG 100h LOCALS ; считать метки, начинающиеся с @@ локальными в пределах процедур TSRKeyCode = 2000h ; код комбинации клавиш Alt+D Start: jmp SetIntVec ; прыжок на установщик вектора ; Обработчик прерывания 9 (прерывание от клавиатуры) Handler9: pushf ; записываем в стек флаги (для вызова старого обработчика прерывания) call dword ptr cs:OldInt9 ; вызываем старый обработчик прерывания 9 ; Проверка комбинации клавиш и флага занятости pusha ; сохраняем все регистры общего назначения cmp byte ptr cs:BusyFlag,0 ; проверяем флаг занятости (признак того, что этот код уже работает - это нужно для того, чтобы повторное нажатие Alt+D не вызвало наш код ещё раз) jne @@Exit9 ; если не ноль, прыгаем mov ah,11h ; функция получения информации о нажатии клавиши (без ожидания) int 16h ; проверяем - нажата ли какая-либо клавиша (и какая) jz @@Exit9 ; прыгаем, если нет cmp ax,TSRKeyCode ; проверяем код нужной нам комбинации клавиш jne @@Exit9 ; прыгаем, если не наша sti ; разрешаем прерывания (чтобы во время отображения не происходило зависания... хотя этого обычно и не происходит, лучше всё же это сделать) mov ah,10h int 16h ; удаляем нажатую комбинацию клавиш из буфера клавиатуры mov byte ptr cs:BusyFlag,1 ; устанавливаем флаг занятости ; Вывод даты/времени push ds push es ; сохраняем DS, ES call MainTSRProc ; вызываем процедуру вывода дампа с сохранением и восстановлением старого текста pop es pop ds ; восстанавливаем ES, DS mov byte ptr cs:BusyFlag,0 ; сбрасываем флаг занятости в 0 (признак того, что этот код отработал - это нужно для того, чтобы Alt+D снова срабатывало) @@Exit9: popa ; восстанавливаем все регистры общего назначения iret ; выходим из прерывания ; Процедура вывода дампа с сохранением и восстановлением старого текста MainTSRProc proc ; Получение информации о видеорежиме mov ah,0Fh int 10h ; получаем в AL текущий видеорежим, в BH номер видеостраницы cmp al,3 ; сравниваем с 3 (стандартный текстовый режим 80х25) je @@OkVideoMode ; прыгаем, если равно ; Действие в случае неверного видеорежима mov al,7 int 29h ; издаём звуковой сигнал (в случае неверного видеорежима) ret ; выходим из процедуры ; Сохранение информации о курсоре, видеостранице, адреса дампа @@OkVideoMode: mov cs:[VideoPage],bh ; сохраняем номер активной видеостраницы mov ah,3 int 10h ; получаем размер курсора в CX mov cs:[OldCurSize],cx ; сохраняем размер mov bp,sp mov ax,[bp+2+4+16] ; смещение адреса дампа (2 - адрес возврата, 4 - сохранённые ds,es, 16 - pusha) mov cs:[DumpAddr],ax mov ax,[bp+2+4+16+2] ; сегмент адреса дампа mov cs:[DumpAddr+2],ax ; Чтение старого изображения xor ax,ax mov ds,ax ; DS = сегмент 0 mov si,ds:[44Eh] ; SI = смещение начала активной видеостраницы, он же адрес для чтения данных (содержимого экрана) mov cs:[VideoOffset],si ; сохраняем его mov ax,0B800h mov ds,ax ; DS = сегмент видеопамяти push cs pop es ; ES = сегмент кода и данных нашей программы lea di,ScreenBuffer ; DI = адрес для записи данных (наш буфер) mov cx,80*25 cld ; прямой порядок копирования данных rep movsw ; копируем содержимое экрана в наш буфер (CX слов из DS:SI в ES:DI) ; Прячем курсор mov ah,1 mov ch,20h ; спрятать курсор int 10h ; прячем курсор ; Рисование рамки push ds pop es ; сегмент видеопамяти push cs pop ds ; DS = сегмент кода и данных нашей программы mov di,[VideoOffset] ; DI = смещение начала активной видеостраницы mov ax,1BC9h ; белый цвет на синем фоне (AH) и код символа левого верхнего угла stosw ; записываем его (AX) в видеопамять (ES:DI, DI=DI+2) mov al,205 ; символ горизонтальной линии mov cx,78 rep stosw ; записываем его 78 раз mov al,187 ; символ правого верхнего угла stosw mov cx,23 @@Vert: mov al,186 ; символ ветикальной линии cmp cl,3 jne @@1 mov al,199 ; вертикальная линия с ответвлением для 3-й строки с конца @@1: stosw mov al,' ' ; пробел cmp cl,3 jne @@2 mov al,196 ; горизонтальная линия для 3-й строки с конца @@2: push cx mov cx,78 rep stosw pop cx mov al,186 ; символ ветикальной линии cmp cl,3 jne @@3 mov al,182 ; вертикальная линия с ответвлением для 3-й строки с конца @@3: stosw loop @@Vert mov al,200 ; символ левого нижнего угла stosw mov al,205 ; символ горизонтальной линии mov cx,78 rep stosw ; записываем его 78 раз mov al,188 ; символ правого нижнего угла stosw ; Вывод текста помощи mov ah,1Eh ; жёлтый текст на синем фоне lea si,HelpText1 mov di,(22*80 + 6)*2 call ShowText ; выводим текст помощи mov di,(23*80 + 3)*2 call ShowText ; выводим текст помощи (SI уже на HelpText2, т.к. он идёт сразу за HelpText1) ; Вывод дампа и обработка клавиш @@MainLoop: call ShowDump ; вывод дампа mov ah,10h int 16h ; ожидание нажатия на клавишу cmp ax,4BE0h ; проверка клавиши влево jne @@R dec [DumpAddr] ; уменьшение смещения на 1 @@R: cmp ax,4DE0h ; проверка клавиши вправо jne @@CL inc [DumpAddr] ; увеличение смещения на 1 @@CL: cmp ax,73E0h ; проверка клавиши Ctrl+влево jne @@CR sub [DumpAddr],100h ; уменьшение смещения на 100h @@CR: cmp ax,74E0h ; проверка клавиши Ctrl+вправо jne @@U add [DumpAddr],100h ; увеличение смещения на 100h @@U: cmp ax,48E0h ; проверка клавиши вверх jne @@D dec [DumpAddr+2] ; уменьшение сегмента на 1 @@D: cmp ax,50E0h ; проверка клавиши вниз jne @@PU inc [DumpAddr+2] ; увеличение сегмента на 1 @@PU: cmp ax,49E0h ; проверка клавиши Page Up jne @@PD sub [DumpAddr+2],100h ; уменьшение сегмента на 100h @@PD: cmp ax,51E0h ; проверка клавиши Page Down jne @@E add [DumpAddr+2],100h ; увеличение сегмента на 100h @@E: cmp ax,11Bh ; проверка нажатия на Esc jne @@MainLoop ; Восстановление старого изображения lea si,ScreenBuffer ; SI = смещение сохранённой области памяти mov di,[VideoOffset] ; DI = смещение начала активной видеостраницы mov cx,80*25 rep movsw ; восстанавливаем изображение: копируем из буфера в область видеопамяти (CX слов из DS:SI в ES:DI) ; Восстановление курсора mov ah,1 mov bh,[VideoPage] ; видеостраница mov cx,[OldCurSize] ; размер курсора int 10h ; восстанавливаем размер курсора ret MainTSRProc endp ; Вывод текста по адресу DS:SI для нулевого символа цветом AH в позицию ES:DI ShowText proc @@Char: lodsb ; читаем символ test al,al ; тестируем на ноль jz @@Ret ; выходим, если ноль stosw ; выводим его вместе с цветом jmp @@Char ; повторяем @@Ret: ret ShowText endp ; Вывод значения AL цветом AH в 16-ричном виде по адресу ES:DI ShowByte proc push ax shr al,4 ; выделяем старшую часть cmp al,9 jbe @@1 add al,'A'-('9'+1) @@1: add al,'0' ; преобразуем в 16-ричную цифру stosw ; выводим pop ax and al,0Fh ; выделяем младшую часть cmp al,9 jbe @@2 add al,'A'-('9'+1) @@2: add al,'0' ; преобразуем в 16-ричную цифру stosw ; выводим ret ShowByte endp ; Вывод значения DX цветом AH в 16-ричном виде по адресу ES:DI ShowWord proc mov al,dh call ShowByte mov al,dl call ShowByte ret ShowWord endp ShowDump proc push ds lds si,dword ptr [DumpAddr] ; DS:SI = адрес дампа mov di, (1*80 + 2)*2 ; стартовая позиция mov cx,20 ; 20 строк дампа @@1: push cx mov ah,1Bh ; светло-голубой цвет на синем фоне mov dx,ds call ShowWord ; вывести сегмент mov al,':' stosw ; вывести двоеточие mov dx,si call ShowWord ; вывести смещение mov cx,16 ; 16 элементов mov ax,1320h ; тёмно-голубой цвет на синем фоне и пробел stosw ; выводим пробел @@2: mov al,' ' stosw ; вывести пробел lodsb ; прочитать символ из памяти DS:SI (SI=SI+1) call ShowByte ; вывести 16-ричное значение байта из памяти loop @@2 mov al,' ' stosw stosw ; вывести 2 пробела mov cx,16 ; 16 символов sub si,cx ; назад на 16 символов mov ah,12h ; тёмно-зелёный цвет на синем фоне @@3: lodsb ; прочитать символ из памяти DS:SI (SI=SI+1) stosw ; вывести символ из памяти loop @@3 pop cx add di,160-(9+1+3*16+2+16)*2 ; переходим на новую строку loop @@1 pop ds ret ShowDump endp BusyFlag db 0 ; флаг занятости процедуры отображения дампа (0 - не занята, 1 - занята) HelpText1 db 27,'/',26,' - one byte offset scroll, Ctrl',27,'/Ctrl',26,' - 100h bytes offset scroll',0 HelpText2 db 24,'/',25,' - one segment scroll, PageUp/PageDn - 100h segments scroll, Esc - exit',0 TSRCodeEnd = $ ; конец кода резидентной части (дальше идут буферы) OldInt9 dw ?,? ; адрес старого обработчика прерывания 9 DumpAddr dw ?,? ; адрес дампа OldCurSize dw ? ; старый размер курсора VideoOffset dw ? ; смещение начала области текущей видеопамяти (для активной видеостраницы) VideoPage db ? ; номер активной видеостраницы ScreenBuffer dw 80*25 dup (?) ; буфер для хранения старого изображения с экрана TSREnd = $ ; конец резидентной части программы ORG TSRCodeEnd ; для экономии места на диске следующий код будет начинаться здесь (на месте начала буфера, всё равно этот буфер не инициализирован) ; Установка векторов прерывания SetIntVec: mov ax,3509h ; функция получения вектора прерывания 9 (прерывание от клавиатуры) int 21h ; получаем вектор mov OldInt9[0],bx ; сохраняем смещение mov OldInt9[2],es ; сохраняем сегмент mov ah,25h ; функция установки обработчика прерывания - записи вектора прерывания (AL=9 не менялся после вызова предыдущей функции 35h) lea dx,Handler9 ; DS:DX = адрес нашего обработчика int 21h ; устанавливаем наш обработчик mov ah,49h ; функция освобождения памяти mov es,ds:[2Ch] ; сегмент переменных окружения int 21h ; освобождаем сегмент с переменными окружения (чтобы программа занимала меньше памяти) mov ah,9 ; функция вывода сообщения (до '$') lea dx,MsgInstalled ; адрес сообщения int 21h ; выводим сообщение lea dx,TSREnd ; конец обработчика прерывания int 27h ; выходим, оставив программу резидентной MsgInstalled db 'DumpTSR is successfully installed!',13,10 db 'Press + to activate.',13,10,'$' END Start