
;            RESID15.ASM - программа к Главе № 15

; (С) Авторские права на файлы-приложения принадлежат автору книги
; "Ассемблер? Это просто! Учимся программировать под MS-DOS"
; Автор: Калашников Олег Александрович (e-mail: Assembler@Kalashnikoff.ru)
;	 http://www.Kalashnikoff.ru

; --- Ассемблирование (получение *.com файла) ---
;При использовании MASM 6.11 - 6.13:
;ML.EXE resid15.asm /AT

;При использовании TASM:
;TASM.EXE resid15.asm
;TLINK.EXE resid15.obj /t/x


;   === Начало программы: ===
.286
cseg segment
assume cs:cseg, ds:cseg, ss:cseg, es:cseg
org 100h

Begin:
       jmp Init		;На метку инициализации 


; === Процедура обработки 05h прерывания (печать экрана) ===
Int_05h_proc proc
       cli		;Запрещаем прерывания
       pusha		;Сохраняем в стеке все регистры,
       pushf		;включая регистр флагов
       push es		;и сегментные регистры
       push ds

       push 0B800h    ;DS должен быть равен сегменту видеобуфера 0-ой страницы
       pop ds

       push 0B900h	;ES - 1-ой страницы
       pop es

       xor di,di	;Аннулируем DI и SI
       xor si,si

       mov cx,25	;На экране 25 строк...
Next_line:
       push cx
       mov cx,80	;...80 символов в каждой строке
Next_symbol:
       lodsw		;Берем первый символ и атрибут в AX (lodsW)
       stosb		;Заносим символ БЕЗ атрибута из AL в 1-ую
			;видеостраницу (stosB)
       loop Next_symbol ;Следующий символ... 

       mov ax,0A0Dh	;Добавляем в конце каждой строки возврат каретки /
       stosw		;перевод строки (для удобства просмотра в NC / VC / DN)
       pop cx
       loop Next_line	;Следующая строка... 

       push cs		;DS должен быть равен сегменту, в котором мы сейчас
       pop ds		;находимся
       mov ah,3Ch	;Функция создания файла
       xor cx,cx	;Атрибуты обычные
       mov dx,offset Screen_file	;DS:DX указывают на путь и имя файла
       int 21h		;Создаем файл в текущем каталоге
       push ax		;Сохраним номер файла

       push 0B900h	;DS должен указывать на 1-ую видеостраницу
       pop ds
       mov bx,ax	;В BX - номер файла
       mov ah,40h	;Функция записи в файл
       xor dx,dx	;DS:DX должны указывать на буфер, где находятся данные
       mov cx,2050	;Кол-во записываемых байт (80*25 + 2*25 = 2050)
       int 21h		;Записываем в созданный файл содержимое экрана...

       pop bx		;Восстановим номер файла
       mov ah,3Eh	;Функция закрытия файла
       int 21h

       pop ds		;Восстановим сохраненные регистры
       pop es
       popf
       popa

       sti		;Разрешим прерывания
       iret		;Выход (возврат в прерванное место)...

Int_05h_vect dd ?		;Переменная для хранения адреса 05h прерывания
Screen_file db 'Screen.txt',0   ;Имя файла, куда занесем содержимое экрана
Int_05h_proc endp


; === Процедура обработки 09h прерывания (клавиатура) ===
Int_09h_proc proc
       pusha
       in al,60h	;Получим скан-код клавиши
       cmp al,58h	;Это F12?
       jne No_F12	;Нет - передаем управление оригинальному обработчику 

       xor cs:Num_status,1 ;Если нажали F12, то меняем местами бит 1 (on/off)

No_F12:
       popa
       jmp dword ptr cs:[Int_09h_vect]	;Передаем управление оригинальному
					;обработчику 09h...

Int_09h_vect dd ?	;Переменная для хранения адреса 09h прерывания
Int_09h_proc endp


; === Процедура обработки 1Ch прерывания ===
;Это прерывание вызывается само по себе чуть более 18 раз в секунду...
;Т.о. наша процедура (Int_1Ch_proc) будет вызываться автоматически
;примерно 18 раз в секунду.
;Такое вот это прерывание, 1Ch... Запомните его!

Int_1Ch_proc proc
       cli		;Запретим вызов всех прерывания (чтоб быстрее было)
       cmp cs:Num_status,0 ;Num_status НЕ равен 0?
       jnz Go_1Ch	;Да, НЕ равен - тогда не меняем символы... 

       pusha		;Сохраним все регистры
       push es		;Сохраним сегментные регистры
       push ds

       push 0B800h	;ES должен указывать на сегмент видеобуфера
       pop es

       push es		;DS также должен указывать на сегмент видеобуфера
       pop ds

       xor si,si	;С первого символа
       mov cx,2000	;Всего 2000 символов на экране (80*25)

Next_sym:
       mov di,si	;SI должен "идти в ногу" с DI
       lodsw		;Загружаем в AX символ и атрибут очередного символа
			;Эта команда равносильна mov ax,ds:[si]

       cmp al,'A'	;Эта большая буква "A"?
       jne Test_little	;Если нет, то проверим маленькую "a" 

       mov ax,0E4Fh	;Если это "A", то выводим "O" с
			;атрибутом светло-желтый на черном фоне...

Store:
       stosw		;Эта команда равносильна mov es:[di],ax

Next_byte:
       loop Next_sym	;Смотрим следующий символ  (всего 2000 раз)

       pop ds		;Если CX=0 (т.е. цикл завершен), то передаем
			;управление оригинальному прерыванию 1Ch,
       pop es		;предварительно восстановив регистры...
       popa
Go_1Ch:
       sti		;И еще разрешим все прерывания
       jmp dword ptr cs:[Int_1Ch_vect]

Test_little:
       cmp al,'a'	;Проверяем на "a"...
       jne Next_byte	;Если не равно "a", то на метку Next_byte 

       mov ax,0E6Fh	;Меняем "a" на "o"
       jmp short Store	;и туда ...
			;Short значит, что метка находится от JMP не далее,
			;чем +/- 127 байт. В нашем случае это так и есть.
			;При этом мы экономим 1 байт...

Int_1Ch_vect dd ?	;Переменная для хранение оригинального адреса 1Ch.
Num_status db 0		;Это переменная для статуса
; 0 - менять символы или файлов; 1 - не менять
; По умолчанию (изначально) Num_status = 0 (т.е. символы и файлы меняем)
Int_1Ch_proc endp


; === Процедура обработки 21h прерывания ===
Int_21h_proc proc
       cmp ax,9889h	;Проверка на повторную загрузку в память (см. )
       jne Check_funcs

       xchg ah,al	;Если это наш "позывной", то даем ответ, меняя местами
       iret		;AH/AL и немедленно выходим из 21h...

Check_funcs:
       cmp cs:Num_status,0 ;Num_status НЕ равен 0?
       jnz Go_21h	;Да, НЕ равен. Тогда не изменяем файлы... 

       cmp ah,4Eh	;Это функция 4Eh (поиск первого файла)?
       je Do_not	;Да. Поехали на обработку ...

       cmp ah,4Fh	;Это функция 4Fh (поиск следующих файлов)?
       je Do_not	;Да. Поехали на обработку ...

Go_21h:
       jmp dword ptr cs:[Int_21h_vect]	;Иначе - передаем управление
					;оригинальному обработчику 21h
					;Больше в нашу процедуру мы не вернемся
					;(в отличие от CALL'а )!


;Обрабатываем функции 4Eh и 4Fh
Do_not:
       pushf		;Сохраним флаги в стеке (так надо!)
       call dword ptr cs:[Int_21h_vect] ;Вызовем оригинальный обработчик 21h
			;21h отработало. Вернулись сюда (в отличие от JMP'а ).
			;Теперь флагов в стеке нет!!! 

       pusha		;Сохраним используемые регистры в стеке...
       pushf
       push es

       mov ah,2Fh	;Получим текущий адрес DTA
       int 21h		;Теперь он в ES:BX...

       mov ah,2Ch	;Получим текущее время.
       int 21h
			;Теперь CH содержит часы (от 0 до 24)
			;CL - минуты (от 0 до 59)
			;DH - секунды (от 0 до 59)
			;DL - сотые доли секунды (от 0 до 99)

       xor dh,dh	;DH аннулируем
;Меняем атрибут файла в DTA (ES:BX уже указывает на DTA )
       and byte ptr es:[bx+15h],11111000b

       mov es:[bx+1Ah],dx
	;Меняем размер файла (младшее слово):
	;Просто занесем текущее показание сотых долей секунд
	;Что-то типа случайного числа от 0 до 99...

       mov word ptr es:[bx+1Ch],0
	;Аннулируем старшее слово.
	;Т.о. у нас размер файла не будет превышать 99 байт.

       mov word ptr es:[bx+16h],1111011110000000b
	;Модифицируем время...

       mov word ptr es:[bx+18h],1111111111111b
	;Модифицируем дату...

       pop es	;Восстановим регистры
       popf
       popa

       retf 2	;Выйдем из прерывания, не вытаскивая из стека флаги (так надо!)

Int_21h_vect dd ?	;Переменная для хранения адреса 21h
Int_21h_proc endp


; ===============================================================
; Инициализация (подготовка и настройка резидента)
Init:
       mov ax,9889h	;Проверим, в памяти ли мы уже или еще нет
       int 21h		;9889h - наш позывной
       cmp ax,8998h	;Отозвался обработчик (AH/AL должны поменяться местами)?
       jne Set_resident

       mov ah,9		;Если в памяти, то выведем соответствующее сообщение
       mov dx,offset In_memory
       int 21h

       ret		;...И вернемся в DOS

Set_resident: ;Если нас в памяти нет, то установим резидент
; Вначале 1Сh...
       mov ax,351Ch
       int 21h		;Получим и сохраним адрес (вектор) 1Ch прерывания
       mov word ptr Int_1Ch_vect,bx	;Вначале младшее слово (смещение)...
       mov word ptr Int_1Ch_vect+2,es	;Затем старшее (сегмент)

       mov ax,251Ch
       mov dx,offset Int_1Ch_proc
       int 21h		;"Повесим" нашу процедуру на 1Ch прерывание

; Теперь 21h-ое...
       mov ax,3521h
       int 21h		;Получим и сохраним адрес (вектор) 21h прерывания
       mov word ptr Int_21h_vect,bx	;Вначале младшее слово (смещение)...
       mov word ptr Int_21h_vect+2,es	;Затем старшее (сегмент)

       mov ax,2521h
       mov dx,offset Int_21h_proc
       int 21h		;"Повесим" нашу процедуру на 21h прерывание

; Теперь 09h...
       mov ax,3509h
       int 21h		;Получим и сохраним адрес (вектор) 09h прерывания
       mov word ptr Int_09h_vect,bx	;Вначале младшее слово (смещение)...
       mov word ptr Int_09h_vect+2,es	;Затем старшее (сегмент)

       mov ax,2509h
       mov dx,offset Int_09h_proc
       int 21h		;"Повесим" нашу процедуру на 09h прерывание

; И, наконец, 05h...
       mov ax,3505h
       int 21h		;Получим и сохраним адрес (вектор) 05h прерывания
       mov word ptr Int_05h_vect,bx	;Вначале младшее слово (смещение)...
       mov word ptr Int_05h_vect+2,es	;Затем старшее (сегмент)

       mov ax,2505h
       mov dx,offset Int_05h_proc
       int 21h		;"Повесим" нашу процедуру на 05h прерывание

       mov ah,9
       mov dx,offset Mess_hello
       int 21h

       mov dx,offset Init
       int 27h		;Оставим программу резидентной в памяти.

In_memory db 'Программа уже загружена в память!!!!',0Ah,0Dh,0Ah

Mess_hello db 'Резидент к книге "Ассемблер? Это просто! Учимся программировать", Глава № 15.',0Ah,0Dh,0Ah
           db '"Горячие" клавиши: F12 - вкл./выкл. замену букв и области DTА,',0Ah,0Dh
           db '                   Shift+PrintScreen - запись содержимого экрана в файл.',0Ah,0Dh,0Ah
           db 'Автор: Калашников Олег Александрович (Assembler@Kalashnikoff.ru),',0Ah,0Dh
           db 'http://www.Kalashnikoff.ru, Россия, Москва, 2000 год.',0Ah,0Dh,'$'

cseg ends
end Begin
