#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include "sysp.h"


// Выключаем проверку стека и указателей

#pragma check_stack( off )
#pragma check_pointer( off )

// Макро для подачи звукового сигнала

#define BEEP() _asm { \
								_asm xor  bx, bx    \
								_asm mov  ax, 0E07h \
								_asm int  10h       \
						  }

// Указатели на старые обработчики прерываний

void (_interrupt _far *old8)(void);  // Таймер
void (_interrupt _far *old9)(void);  // Клавиатура
void (_interrupt _far *old28)(void); // Занятость DOS
void (_interrupt _far *old2f)(void); // Мультиплексор

// Новые обработчики прерываний

void _interrupt _far new8(void);
void _interrupt _far new9(void);
void _interrupt _far new28(void);
void _interrupt _far new2f(unsigned _es, unsigned _ds,
									unsigned _di, unsigned _si,
									unsigned _bp, unsigned _sp,
									unsigned _bx, unsigned _dx,
									unsigned _cx, unsigned _ax,
									unsigned _ip, unsigned _cs,
									unsigned flags);

int iniflag; // Флаг запроса на вывод экрана в файл
int outflag; // Флаг начала вывода в файл
int name_counter; // Номер текущего выводимого файла
char _far *crit;  // Адрес флага критической секции DOS


// =======================================

void main(void);
void main(void) {

	 union REGS inregs, outregs;
	 struct SREGS segregs;

	 unsigned size; // Размер резидентной части
						 // TSR-программы

	// Вызываем прерывание мультиплексора с AX = FF00
	// Если программа GRAB уже запускалсь, то новый
	// обработчик прерывания мультиплексора вернет
	// в регистре AX значение 00FF.
	// Таким способом мы избегаем повторного изменения
	// содержимого векторной таблицы прерываний.

	inregs.x.ax = 0xff00;
	int86(0x2f, &inregs, &outregs);

	if(outregs.x.ax == 0x00ff) {
		printf("\nПрограмма GRAB уже загружена\n");
		hello();
		exit(-1);
	}

	// Выдаем инструкцию по работе с программой GRAB

	hello();

	// Вычисляем размер программы в параграфах
	// Добавляем 1 параграф на случай
	// некратной параграфу длины

	size = (12000 >> 4) + 1;


	// Устанавливаем начальные значения флагов

	outflag=iniflag=0;

	// Сбрасываем счетчик файлов. Первый файл будет
	// иметь имя GRAB0.DOC. В дальнейшем этот счетчик
	// будет увеличивать свое значение на 1.

	name_counter=0;

	// Получаем указатель на флаг критической секции DOS.
	// Когда этот флаг равен 0, TSR-программа может
	// пользоваться функциями DOS

	inregs.h.ah = 0x34;
	intdosx( &inregs, &outregs, &segregs );
	crit=(char _far *)FP_MAKE(segregs.es,outregs.x.bx);

	// Устанавливаем собственные обработчики прерываний.

	old9 = _dos_getvect(0x9);
	_dos_setvect(0x9, new9);

	old8 = _dos_getvect(0x8);
	_dos_setvect(0x8, new8);

	old28 = _dos_getvect(0x28);
	_dos_setvect(0x28, new28);

	old2f = _dos_getvect(0x2f);
	_dos_setvect(0x2f, new2f);


	// Завершаем программу и остаемся в памяти

	_dos_keep(0, size);
}

// =======================================

// Новый обработчик прерывания мультиплексора.
// Используется для предохранения программы
// от повторного встраивания в систему как резидентной.

void _interrupt _far new2f(unsigned _es, unsigned _ds,
									unsigned _di, unsigned _si,
									unsigned _bp, unsigned _sp,
									unsigned _bx, unsigned _dx,
									unsigned _cx, unsigned _ax,
									unsigned _ip, unsigned _cs,
									unsigned flags)
{
		// Если прерывание вызвано с содержимым
		// регистра AX, равным FF00, возвращаем
		// в регистре AX значение 00FF,
		// в противном случае передаем управление
		// старому обработчику прерывания

		if(_ax != 0xff00) _chain_intr(old2f);
		else _ax = 0x00ff;

}

// =======================================

// Новый обработчик аппаратного прерывания таймера

void _interrupt _far new8(void) {

	// Вызываем старый обработчик

	(*old8)();

	// Если была нажата комбинация клавиш Ctrl+PrtSc
	// (iniflag при этом устанавливается в 1
	// новым обработчиком прерывания 9) и
	// запись в файл уже не началась,
	// то при значении флага критической секции
	// DOS, равном 0, выводим содержимое экрана
	// в файл

	if((iniflag != 0) && (outflag == 0) && *crit == 0) {

		outflag=1;   // Устанавливаем флаг начала вывода
		_enable();   // Разрешаем прерывания

		write_buf(); // Записываем содержимое
						 // буфера экрана в файл

		outflag=0;   // Сбрасываем флаги в исходное
		iniflag=0;   //    состояние
	}
}

// =======================================

// Новый обработчик прерывания 28, которое вызывает
// DOS, если она ожидает ввода от клавиатуры.
// В этот момент TSR-программа может пользоваться
// функциями DOS.

void _interrupt _far new28(void) {

	// Если была нажата комбинация клавиш Ctrl+PrtSc
	// (iniflag при этом устанавливается в 1
	// новым обработчиком прерывания 9) и
	// если уже не началась запись в файл,
	// то выводим содержимое экрана в файл

	if((iniflag != 0) && (outflag == 0)) {

		outflag=1; // Устанавливаем флаг начала вывода
		_enable(); // Разрешаем прерывания

		write_buf(); // Записываем содержимое видеобуфера
						 // в файл

		outflag=0;   // Сбрасываем флаги в исходное
		iniflag=0;   //   состояние
	}

	// Передаем управление старому обработчику
	// прерывания 28

	_chain_intr(old28);
}

// =======================================

// Новый обработчик клавиатурного прерывания.
// Он фиксирует нажатие комбинации клавиш Ctrl+PrtSc
// и устанавливает флаг iniflag, который сигнализирует
// о необходимости выбрать подходящий момент и
// записать содержимое видеобуфера в файл

void _interrupt _far new9(void) {

 // Если SCAN-код равен 0x37 (клавиша PrtSc),
 // нажата клавиша Ctrl (бит 4 байта состояния
 // клавиатуры, находящийся в области данных
 // BIOS по адресу 0040:0017 установлен в 1)
 // и если не установлен флаг iniflag,
 // то устанавливаем флаг iniflag в 1.

 if((inp(0x60) == 0x37) && (iniflag == 0) &&
	(*(char _far *)FP_MAKE(0x40,0x17) & 4) != 0) {

		// Выдаем звуковой сигнал

		BEEP();
		BEEP();
		BEEP();

		_disable(); // Запрещаем прерывания

		// Разблокируем клавиатуру
		// и разрешаем прерывания

		_asm {
			in  al,61h
			mov ah,al
			or  al,80h
			out 61h,al
			xchg ah,al
			out 61h,al

			mov al,20h
			out 20h,al
		}

		// Устанавливаем флаг запроса
		// на запись содержимого видеобуфера
		// в файл

		iniflag = 1;
		_enable();  // Разрешаем прерывания
 }

 // Если нажали не Ctrl+PrtSc, то
 // передаем управление старому
 // обработчику прерывания 9

 else _chain_intr(old9);

}

// =======================================

// Функция возвращает номер
// текущего видеорежима

int get_vmode(void) {

	char _far *ptr;

	ptr = FP_MAKE(0x40,0x49); // Указатель на байт
									  // текущего видеорежима
	return(*ptr);
}

// =======================================

// Функция возвращает сегментный адрес
// видеобуфера. Учитывается содержимое
// регистров смещения адреса видеобуфера.

int get_vbuf(int vmode) {

	unsigned vbase;
	unsigned adr_6845;
	unsigned high;
	unsigned low;
	unsigned offs;

	// В зависимости от видеорежима базовый адрес
	// видеобуфера может быть 0xb000 или 0xb800

	vbase = (vmode == 7) ? 0xb000 : 0xb800;

	// получаем адрес порта видеоконтроллера 6845

	adr_6845 = *(unsigned _far *)(FP_MAKE(0x40,0x63));

	// Считываем содержимое регистров 12 и 13
	// видеоконтроллера

	outp(adr_6845,0xc);
	high = inp(adr_6845+1);

	outp(adr_6845,0xd);
	low = inp(adr_6845+1);

	offs = ((high << 8) + low) >> 4;

	// Добавляем к базовому адресу видеобуфера
	// смещение, взятое из регистров видеоконтроллера

	vbase += offs;

	return(vbase);

}

// =======================================

// Функция возвращает количество символов в строке
// для текущего видеорежима

int get_column(void) {

	return(*(int _far *)(FP_MAKE(0x40,0x4a)));
}

// =======================================

// Функция возвращает количество строк
// для текущего видеорежима

int get_row(void) {

	unsigned char ega_info;

	ega_info = *(unsigned char _far *)(FP_MAKE(0x40,0x87));

	// Если нет EGA, то используется 25 строк,
	// если EGA присутствует, считываем число
	// строк. Это число находится в области данных
	// BIOS по адресу 0040:0084.

	if(ega_info == 0 || ( (ega_info & 8) != 0) ) {
		return(25);
	}
	else {
		return((*(unsigned char _far *)(FP_MAKE(0x40,0x84)))+1);
	}

}

// =======================================

// Функция записи содержимого видеобуфера в
// файл

int write_buf(void) {

// Видеопамять состоит из байтов символов и байтов
// атрибутов. Нам нужны байты символов chr.

typedef struct _VIDEOBUF_ {
	 unsigned char chr;
	 unsigned char attr;
} VIDEOBUF;

	VIDEOBUF _far *vbuf;
	int i, j, k, max_col, max_row;
	FILE *out_file;
	char fname[20],ext[8];

	i=get_vmode(); // Получаем номер текущего
						// видеорежима

	// Для графического режима ничего не записываем

	if(i > 3 && i != 7) return(-1);

	// Устанавливаем указатель vbuf на видеобуфер

	vbuf=(VIDEOBUF _far *)FP_MAKE(get_vbuf(i),0);

	// Определяем размеры экрана

	max_col = get_column();
	max_row = get_row();

	// Формируем имя файла для записи образа экрана

	itoa(name_counter++,ext,10);
	strcpy(fname,"!grab");
	strcat(fname,ext);
	strcat(fname,".doc");

	out_file=fopen(fname,"wb+");

	// Записываем содержимое видеобуфера в файл

	for(i=0; i<max_row; i++) {
		for(j=0; j<max_col; j++) {

			fputc(vbuf->chr,out_file);
			vbuf++;

		}

		// В конце каждой строки добавляем
		// символы перевода строки и
		// возврата каретки

		fputc(0xd,out_file);
		fputc(0xa,out_file);
	}
	fclose(out_file);
	return(0);
}

// =======================================

// Функция выводит на экран инструкцию по
// использованию программы GRAB

int hello(void) {
	printf("\nУтилита копирования содержимого"
			 "\nэкрана в файл GRAB<n>.DOC"
			 "\nCopyright (C)Frolov A.,1990"
			 "\n"
			 "\nДля копирования нажмите Ctrl+PrtSc"
			 "\n");
}
