#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <conio.h>
#include <stdarg.h>

typedef struct {
	 unsigned long edi, esi, ebp, reserved, ebx, edx, ecx, eax;
	 unsigned flags, es, ds, fs, gs, ip, cs, sp, ss;
} RM_INT_CALL;

#define MONO_MODE           0x07
#define BW_80_MODE          0x02
#define COLOR_80_MODE       0x03

// Макро для вычисления линейного адреса исходя из
// логического адреса реального режима

#define ABSADDR(seg, ofs) \
	 ((((unsigned long) seg) << 4) + ((ofs) & 0xFFFF))

typedef struct {						// байт доступа
	 unsigned accessed   : 1;
	 unsigned read_write : 1;
	 unsigned conf_exp   : 1;
	 unsigned code       : 1;
	 unsigned xsystem    : 1;
	 unsigned dpl        : 2;
	 unsigned present    : 1;
} ACCESS;

typedef struct {                 // дескриптор
	 unsigned        limit;
	 unsigned        addr_lo;
	 unsigned char   addr_hi;
	 ACCESS          access;
	 unsigned        reserved;
} DESCRIPTOR;

// Структура для записи информации о памяти

typedef struct {
	unsigned long avail_block;
	unsigned long max_page;
	unsigned long max_locked;
	unsigned long linadr_space;
	unsigned long total_unlocked;
	unsigned long free_pages;
	unsigned long tot_phys_pages;
	unsigned long free_linspace;
	unsigned long size_fp;
	char reserved[12];
} PMI;


void dos_exit(unsigned);
void dpmi_init(void);
void set_pmode(void);
void cdecl pm_printf(const char *, ...);
void pm_puts(char *);
void pm_putch(int);
int  rm_int(unsigned, unsigned , RM_INT_CALL far *);
int  mi_show(void);
unsigned get_sel(void);
int set_descriptor(unsigned pm_sel, DESCRIPTOR far *desc);
void vi_print(unsigned int x, unsigned int y, char *s, char attr);
void vi_hello_msg(void);



void main() {

	clrscr();
	printf("DOS Protected Mode Interface Demo, (C) Frolov A.V., 1992\n\r"
			 "--------------------------------------------------------\n\r\n\r");

// Определяем текущий видеорежим и
// сегментный адрес видеобуфера

	video_init();

// Инициализируем защищённый режим

	dpmi_init();

	printf("\n\r\n\r\n\rДля входа в защищённый режим нажмите любую клавишу...");
	getch();

// Входим в защищённый режим

	set_pmode();

// Стираем экран и выводим сообщение, находясь в
// защищённом режиме. Пользуемся выводом через
// эмулируемое прерывание реального режима DOS

	textcolor(BLACK);	textbackground(LIGHTGRAY);	clrscr();
	pm_printf(" Установлен защищённый режим работы процессора!\n\r"
			  " ----------------------------------------------\n\r\n\r");

// Выводим текущую информацию о распределении памяти

	mi_show();

	pm_printf("\n\r\n\r\n\r Для продолжения нажмите любую клавишу...");
	getch();

	clrscr();

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

	alloc_videosel();

	pm_printf("\n\r\n\r\n\r Для продолжения нажмите любую клавишу...");
	getch();
	clrscr();

// Выводим сообщения, пользуясь непосредственным доступом
// к видеопамяти

	vi_hello_msg();
	vi_print(0, 3,
		" Для возврата в реальный режим нажмите любую клавишу", 0x7f);
	getch();

// Освобождаем полученный селектор

	free_videosel();

	textcolor(LIGHTGRAY);	textbackground(BLACK);	clrscr();

// Завершаем работу программы выходом в DOS

	dos_exit(0);
}

// -------------------------------------------------
// Процедура для завершения работы программы
// -------------------------------------------------

void dos_exit(unsigned err) {
	 asm mov ax, err
	 asm mov ah, 04ch
	 asm int 21h
}

// -------------------------------------------------
// Инициализация для работы с DPMI
// -------------------------------------------------

	union REGS inregs, outregs;
	struct SREGS segregs;
	void (far *pm_entry)();
	unsigned hostdata_seg, hostdata_size, dpmi_flags;

void dpmi_init(void) {

// Проверяем доступность и параметры сервера DPMI

	inregs.x.ax = 0x1687;
	int86x(0x2F, &inregs, &outregs, &segregs);
	if(outregs.x.ax != 0) {
		printf("Сервер DPMI не активен."); exit(-1);
	}

// Определяем версию сервера DPMI

	printf("Версия сервера DPMI: \t\t\t%d.%d\n",
		outregs.h.dh, outregs.h.dl);

// Определяем тип процессора

	printf("Тип процессора:\t\t\t\t");
	if(outregs.h.cl == 2) printf("80286");
	else if(outregs.h.cl == 3) printf("80386");
	else if(outregs.h.cl == 4) printf("80486");

// Определяем возможность работы с 32-разрядными
// программами

	dpmi_flags = outregs.x.bx;
	printf("\nПоддержка 32-разрядных программ:\t");
	if(dpmi_flags && 1) printf("ПРИСУТСТВУЕТ");
	else printf("ОТСУТСТВУЕТ");

// Определяем размер области памяти для сервера DPMI

	hostdata_size = outregs.x.si;
	printf("\nРазмер памяти для сервера DPMI:\t\t%d байт",
		hostdata_size * 16);

// Определяем адрес точки входа в защищённый режим

	FP_SEG(pm_entry) = segregs.es;
	FP_OFF(pm_entry) = outregs.x.di;
	printf("\nАдрес точки входа в защищённый режим: \t%Fp\n",
		pm_entry);

// Заказываем память для сервера DPMI

	if(hostdata_size) {
		if(_dos_allocmem(hostdata_size, &hostdata_seg) != 0) {
			printf("Мало стандартной памяти"); exit(-1);
		}
	}

}

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

void set_pmode() {

// Входим в защищённый режим

	 asm {
		  mov ax, hostdata_seg
		  mov es, ax
		  mov ax, dpmi_flags
	 }
	 (*pm_entry)();

}

// -------------------------------------------
// Процедура вывода символа на экран в
// защищённом режиме
// -------------------------------------------

void pm_putch(int chr) {

// Структура для вызова прерывания должна
// быть определена как статическая

	 static RM_INT_CALL regs;
	 static RM_INT_CALL far *pregs = (void far *) 0;

// В первый раз инициализируем структуру
// и указатель на неё

	 if (!pregs) {
		  pregs = &regs;
		  memset(pregs, 0, sizeof(RM_INT_CALL));
		  regs.eax = 0x0200;
	 }
	 regs.edx = chr;

// Вызываем прерывание DOS для вывода символа

	 rm_int(0x21, 0, pregs);
}

// -------------------------------------------
// Процедура вывода строки на экран в
// защищённом режиме
// -------------------------------------------

void pm_puts(char *str_ptr) {
	 while (*str_ptr) { pm_putch(*str_ptr); str_ptr++; }
}

// -------------------------------------------
// Процедура вывода строки на экран в
// защищённом режиме, аналог функции printf()
// -------------------------------------------

void cdecl pm_printf(const char *fmt, ...)
{
	 char buffer[512], *sptr=buffer;
	 va_list marker;
	 va_start(marker, fmt);
	 vsprintf(buffer, fmt, marker);
	 va_end(marker);
	 while (*sptr) pm_putch(*sptr++);
}

// --------------------------------------------
// Процедура вызова прерывания реального режима
// --------------------------------------------

int rm_int(unsigned int_number,	// номер прерывания
			  unsigned params,		// количество слов параметров,
											// передаваемых через стек
			  RM_INT_CALL far *rm_call) // адрес структуры
											// для вызова прерывания
{
	 asm {
		  push di
		  push bx
		  push cx
		  mov ax, 0300h      // функция вызова прерывания
		  mov bx, int_number
		  mov cx, params;
		  les di, rm_call  	// запись в ES:DI адреса структуры
		  int 31h            // вызов сервера DPMI
		  jc error
		  mov ax, 0				// нормальное завершение
		  jmp short rm_int_end
	 }
error:  asm mov ax, 0       // завершение с ошибкой
rm_int_end:   asm pop cx
		  asm pop bx
		  asm pop di
}

// -----------------------------------------------------
// Процедура отображает текущее состояние памяти
// -----------------------------------------------------

int mi_show(void) {

	 PMI minfo, far *minfoptr = &minfo;
	 unsigned long psize, far *psizeptr=&psize;
	 unsigned sel;
	 void far *fp;


	 get_mi(minfoptr);

	 pm_printf(" Информация об использовании памяти\n\r"
				  " ----------------------------------\n\r"
	 "\r\n Размер максимального доступного блока:\t\t%ld байт"
	 "\r\n Доступно незафиксированных страниц:\t\t%ld",
	 minfo.avail_block,
	 minfo.max_page);

	 pm_printf("\r\n Доступно зафиксированных страниц:\t\t%ld"
	 "\r\n Размер линейного адресного пространства:\t%ld страниц"
	 "\r\n Всего имеется незафиксированных страниц:\t%ld",
	 minfo.max_locked,
	 minfo.linadr_space,
	 minfo.total_unlocked);

	 pm_printf("\r\n Количество свободных страниц:\t\t\t%ld"
	 "\r\n Общее количество физических страниц:\t\t%ld",
	 minfo.free_pages,
	 minfo.tot_phys_pages);

	 pm_printf("\r\n Свободное линейное адресное пространство:\t%ld страниц"
	 "\r\n Размер файла/раздела для страничного обмена:\t%ld страниц",
	 minfo.free_linspace,
	 minfo.size_fp);

	 get_page_size(psizeptr);
	 pm_printf("\r\n Размер страницы:\t\t\t\t%ld байт\r\n", psize);

// Выводим текущие значения регистров CS и DS

	 asm mov sel,cs
	 pm_printf("\n\r CS = %04.4X,   ",sel);
	 asm mov sel,ds
	 pm_printf("DS = %04.4X",sel);

// Выводим значение текущего приоритетного кольца

	 fp = (void far *) main;
	 sel = FP_SEG(fp) & 3;
	 pm_printf("\n\r Номер приоритетного кольца = %d\n\r",sel);

}

// -----------------------------------------------
// Процедура для получения информации об
// использовании памяти
// -----------------------------------------------

int get_mi(PMI far *minfo) {
	asm {
		  mov ax, 0500h
		  les di, minfo       // ES:DI = адрес структуры DMI
		  int 31h
		  jc error
		  mov ax, 0
		  jmp short get_mi_end
	}
	error: asm mov ax, 1
	get_mi_end:
}

// ------------------------------------------------
// Процедура для получения размера страницы памяти
// ------------------------------------------------

int get_page_size(long far *page_size) {
	asm {
		  mov ax, 0604h
		  int 31h
		  jc error

		  les di, page_size // ES:DI = адрес page_size
		  mov es:[di], cx
		  mov es:[di+2], bx

		  mov ax, 0
		  jmp short gps_end
	}
	error: asm mov ax, 1
	gps_end:
}

// --------------------------------------------------
// Определение сегментного адреса видеопамяти
// --------------------------------------------------

unsigned crt_mode, crt_seg;

int video_init(void) {

	union REGS r;

// Определяем текущий видеорежим

	r.h.ah=15;
	int86(0x10,&r,&r);
	crt_mode = r.h.al;

	if(crt_mode == MONO_MODE) crt_seg = 0xb000;
	else if(crt_mode == BW_80_MODE || crt_mode == COLOR_80_MODE)
		crt_seg = 0xb800;
	else {
		printf("\nИзвините, этот видеорежим недопустим.");
		exit(-1);
	}
}

// ---------------------------------------------------
// Получение селектора для адресации видеопамяти
// ---------------------------------------------------

char far *vid_ptr;
DESCRIPTOR d;
unsigned ldtsel;

int alloc_videosel(void) {

	void far *fp;
	unsigned long addr;

	FP_SEG(vid_ptr) = crt_seg;
	FP_OFF(vid_ptr) = 0;
	pm_printf(" Адрес видеопамяти реального режима:\t %Fp\r\n", vid_ptr);

// Получаем свободный LDT-селектор

	if (! (ldtsel = get_sel())) {
	  pm_printf(" Ошибка при получении селектора");
	  dos_exit(-1);
	}
	pm_printf(" Получен селектор:\t\t\t%04.4X\n\r", ldtsel);

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

	d.limit = 0x2000;
	addr = ABSADDR(crt_seg, 0);
	d.addr_lo = addr & 0xFFFF;
	d.addr_hi = addr >> 16;
	d.access.accessed = 0;      // не использовался
	d.access.read_write = 1;    // разрешены чтение/запись
	d.access.conf_exp = 0;      // не стек
	d.access.code = 0;          // это сегмент данных
	d.access.xsystem = 1;       // не системный дескриптор
	d.access.dpl = 3;  			 // приоритетное кольцо 3
	d.access.present = 1;       // сегмент присутствует в памяти
	d.reserved = 0;

// Устанавливаем дескриптор

	if (!set_descriptor(ldtsel, &d)) {
			pm_printf(" Ошибка при установке дескриптора"); getch();
			dos_exit(-1);
	 }

// Выводим на экран адрес видеопамяти

	 FP_SEG(vid_ptr) = ldtsel;
	 FP_OFF(vid_ptr) = 0;
	 pm_printf(" Адрес видеопамяти защищённого режима:\t%Fp\r\n", vid_ptr);
}

// --------------------------------------------------
// Освобождение селектора видеопамяти
// --------------------------------------------------

int free_videosel(void) {
	 if (!sel_free(ldtsel)) {
			pm_printf(" Ошибка при освобождении селектора");
			dos_exit(-1);
	 }
}

// ----------------------------------------------
// Получить один селектор в LDT
// ----------------------------------------------

unsigned get_sel(void) {
	 asm {
		  mov ax, 0        // получить селектор
		  mov cx, 1        // нужен один селектор
		  int 31h
		  jc error
		  jmp short gs_end // AX содержит новый LDT-селектор
	 }
error: asm mov ax, 0     // произошла ошибка
gs_end:
}

// --------------------------------------------------
// Установить дескриптор для LDT-селектора
// --------------------------------------------------

int set_descriptor(unsigned pm_sel, DESCRIPTOR far *desc) {

	 asm {
		  push di
		  push bx
		  mov ax, 000Ch
		  mov bx, pm_sel
		  les di, desc  
		  int 31h       
		  jc error
		  mov ax, 1     
		  jmp short sd_end
	  }
error:  asm mov ax, 0      
sd_end: asm pop bx
		  asm pop di
}

// --------------------------------------------------
// Освободить LDT-селектор
// --------------------------------------------------

int sel_free(unsigned pmodesel) {
	 asm {
		  mov ax, 0001h   
		  mov bx, pmodesel
		  int 31h         
		  jc error
		  mov ax, 1       
		  jmp short done
	  }
error: asm mov ax, 0         
done:
}

// -------------------------------------------------------
// Вывод символа непосредственной записью в видеопамять
// -------------------------------------------------------

void vi_putch(unsigned int x, unsigned int y ,char c, char attr) {

	register unsigned int offset;
	char far *vid_ptr;

	offset=(y*160) + (x*2);
	vid_ptr=MK_FP(ldtsel, offset);
	*vid_ptr++=c; *vid_ptr=attr;
}
// -------------------------------------------------------
// Вывод строки непосредственной записью в видеопамять
// -------------------------------------------------------

void vi_print(unsigned int x, unsigned int y, char *s, char attr) {
	while(*s) vi_putch(x++, y, *s++, attr);
}
// -------------------------------------------------------
// Вывод сообщения непосредственной записью в видеопамять
// -------------------------------------------------------

void vi_hello_msg(void) {

	vi_print(0, 0,
		" Демонстрация работы с интерфейсом "
		"DPMI                 ║ (C) Frolov A.V., 1992 ", 0x30);

}
