#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <dos.h>
#include <bios.h>
#include <process.h>
#include "tsrdemo.h"

// Структуры для работы с функциями прерываний
union  REGS  regs;
struct SREGS sregs;

// Идентификатор для мультиплексного прерывания
unsigned char tsrid = 0xff;

// Указатель на стек, который будет использован
// после активизации TSR
char far *tsr_stack;

// Флаг активности TSR. Равен 1, если TSR активна
int tsr_already_active = 0;

// Флаг активизации TSR. Равен 1,
// если нажали клавишу активизации
int popup_while_dos_busy = 0;

// Флаг обработки прерывания INT 28h.
// Равен 1, когда работает INT 28
int int_28_in_progress = 0;

// Флаг обработки прерывания INT 13h.
// Равен 1, когда работает INT 13
int unsafe_flag = 0;

// Код клавиши, при помощи которой была
// активизирована TSR
unsigned keycode;

// Сегмент PSP прерванного процесса
unsigned foreground_psp;

// Сегмент и смещение блока DTA
// прерванного процесса
unsigned foreground_dta_seg;
unsigned foreground_dta_off;

// Адрес, по которому передается управление
// при деинсталляции TSR
unsigned long ExitAddress;

// Область сохранения расширенной
// информации об ошибках
struct ExtErr ErrInfo;

// Указатели на флаги MS-DOS
char far *indos_ptr = 0;
char far *crit_err_ptr=0;

// Область сохранения для старых прерываний
INTADDR old_int8, old_int9, old_int10,
	old_int13, old_int1b, old_int23;
INTADDR old_int24, old_int28,
	old_int2f, old_int1c;

typedef unsigned int (far *s_arrayptr);
typedef void interrupt (*fptr)(void);

// Определение размера кучи и стека
// с учетом области статических данных
extern unsigned _heaplen = STACK_SIZE +
   HEAP_RESERVED + STATIC_SIZE;
extern unsigned _stklen  = STACK_SYSTEM;

// ========================================
// Вход в программу - функция main()
// ========================================
void main(int argc, char *argv[])
{
  int tsrsize, i;

  printf("\n\nРезидентная программа TSRDEMO,"
	 " v1.1, (C) Фролов А.В., 1995\n");

  // Проверяем версию MS-DOS
  if(_osmajor < 4)
  {
    printf("\nИзвините, вы пользуетесь слишком"
           "старой версией MS-DOS");
    return;
  }

  // Если есть параметр, проверяем его
  if(argc > 1)
  {
    // Выгрузка из памяти
    if(argv[1][0] == 'u') unload();

    // Вывод инструкции на экран
    else
      printf("\nЗапуск TSRDEMO:\n"
	 "tsrdemo   : загрузка в память\n"
	 "tsrdemo u : выгрузка из памяти\n"
	 "<Ctrl+R>  : запись содержимого экрана"
	 " в файл !grabXXX.scr");
    return;
  }

  // Выход, если уже загружена
  if(tsrloaded())
  {
    printf("Программа TSRDEMO уже загружена\n"
	   "Введите 'tsrdemo u' для выгрузки");
    return;
  }

  // Инициализация TSR
  if(!tsrinit())
  {
    printf("Мало памяти для загрузки TSR");
    return;
  }

  // Оставляем резидентно в памяти
  tsrsize =  (_DS - _CS) + (_SP / 16);
  _dos_keep(0, tsrsize + 1);
}

// ========================================
// activate_tsr
// Функция вызывается при активизации TSR
// ========================================
void activate_tsr()
{
  // Для работы в активном режиме
  // устанавливаем свой стек
  set_stack();

  // Определяем, когда TSR активизировалась -
  // во время работы DOS или нет
  if(DosBusy() && !int_28_in_progress)

    // Если активизировалась, когда
    // работает функция MS-DOS, просто
    // устанавливаем флаг. Активизация будет
    // отложена до вызова INT 8h или INT 28h
    popup_while_dos_busy = 1;

  else
  {
    // Если никакая функция прерывания MS-DOS
    // не вызвана, сбрасываем флаг и начинаем
    // активизацию TSR
    popup_while_dos_busy = 0;

    // Сохраняем адреса прерываний
    // <Ctrl+Break>, <Ctrl+C> и прерывания по
    // критической ошибке ввода/вывода
    old_int1b = _dos_getvect(0x1b);
    old_int23 = _dos_getvect(0x23);
    old_int24 = _dos_getvect(0x24);

    // Устанавливаем свои обработчики прерываний
    _dos_setvect(0x1b, new_int1b);
    _dos_setvect(0x23, new_int23);
    _dos_setvect(0x24, new_int24);

    // Сохраняем текущий PSP и устанавливаем свой PSP
    foreground_psp = GetPSP();
    SetPSP(_psp);

    // Сохраняем текущую область DTA
    regs.h.ah = 0x2f;
    intdosx(&regs, &regs, &sregs);
    foreground_dta_seg = sregs.es;
    foreground_dta_off = regs.x.bx;

    // Устанавливаем свою область DTA.
    // Используем DTA в своем PSP
    regs.h.ah = 0x1a;
    regs.x.dx = 0x80;
    sregs.ds = _psp;
    intdosx(&regs, &regs, &sregs);

    // Сохраняем расширенную информацию
    // об ошибках
    GetExtErr(&ErrInfo);

    // Очищаем буфер клавиатуры
    while(_bios_keybrd(_KEYBRD_READY))
    _bios_keybrd(_KEYBRD_READ);

    // Вызываем функцию, выполняющую все то,
    // что должно быть сделано
    // при активизации TSR
    application();

    // Очищаем буфер клавиатуры
    while(_bios_keybrd(_KEYBRD_READY))
      _bios_keybrd(_KEYBRD_READ);

    // Восстанавливаем информацию об ошибках
    SetExtErr(&ErrInfo);

    // Восстанавливаем DTA
    regs.h.ah = 0x1a;
    regs.x.dx = foreground_dta_off;
    sregs.ds  = foreground_dta_seg;
    intdosx(&regs, &regs, &sregs);

    // Восстанавливаем PSP
    SetPSP(foreground_psp);

    // Восстанавливаем адреса прерываний
    // <Ctrl+Break>, <Ctrl+C> и прерывания по
    // критической ошибке ввода/вывода
    _dos_setvect(0x1b, old_int1b);
    _dos_setvect(0x23, old_int23);
    _dos_setvect(0x24, old_int24);
  }

  // Восстанавливаем стек и переводим TSR
  // в неактивное систояние
  restore_stack();
}

// ========================================
// new_int1c
// Обработчик прерывания 1Ch.
// Выводит в правом верхнем углу мигающие
// символы "*" и "+", сигнализирующие
// о нормальной работе TSR
// ========================================
void interrupt new_int1c()
{
  s_arrayptr screen[80]; // видеопамять
  static int count;      // счетчик

  // Для цветного дисплея адрес сегмента
  // видеобуфера равен B800h, для ч/б - B000.
  // Устанавливаем указатель на видеобуфер
  screen[0] = (s_arrayptr) MK_FP(0xB800,0);

  // Увеличиваем содержимое счетчика
  // с ограничением
  count++;
  count %= 6;

  // Выводим символ, соответствующий значению
  // счетчика, в правый верхний угол экрана
  screen[0][79] =
    ((count > 2) ? '*' : '+') + ATTR;

  // Передаем управление по цепочке прерываний
  _chain_intr(old_int1c);
}

// ========================================
// new_int2f
// Новый обработчик мультиплексного
// прерывания INT 2Fh
// ========================================
#pragma argsused
void interrupt far new_int2f(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  // Если ax == 0xff00, выполняется проверка,
  // была ли данная TSR загружена ранее
  // Возвращаем 0x00ff. Это означает, что
  // программа уже загружена
  if(ax == 0xff00) ax = 0x00ff;

  // Если ax == 0xff01, была запрошена
  // выгрузка программы из памяти.
  // Пытаемся ее выполнить
  else if(ax == 0xff01)
  {
    // Записываем переданный адрес завершения
    ExitAddress = ((long)bx << 16) + dx;

    // Выполняем выгрузку TSR из памяти,
    // если она неактивна
    if(!tsr_already_active)
    {
      _enable(); // разрешаем прерывания
      tsr_exit(); // пытаемся выгрузить TSR

      // Если попали управление передано в это
      // место программы, значит деинсталляция
      // не удалась,так как какая-то программа
      // уже перехватила наши прерывания.
      // В этом случае устанавливаем признак
      // неудачной выгрузки и блокируем
      // работу TSR
      ax = 0xFFFF;
      tsr_already_active = -ax;
    }
  }

  // Если TSR активна, передаем
  // управление по цепочке
  else _chain_intr(old_int2f);
}

// ========================================
// new_int28
// Новый обработчик прерывания INT 28h
// Вызывается DOS, когда она неактивна.
// Используется для активизации TSR
// ========================================
#pragma argsused
void interrupt far new_int28(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  // Увеличиваем счетчик рекурсивных
  // вызовов прерывания INT 28h
  int_28_in_progress++;

  // Если это безопасно, активизируем TSR.
  // Учитываем возможность рекурсивного вызова
  // прерывания INT 28h.
  // Не запускаемся, если TSR уже активизирована,
  // если вызвано прерывание INT 13h,
  // предназначенное для работы с диском, или
  // если есть рекурсия при вызове INT 28h
  if(popup_while_dos_busy && (!Int28DosBusy())
     && !tsr_already_active && !unsafe_flag)
  {
    // Устанавливаем флаг активизации
    tsr_already_active = 1;

    activate_tsr(); // активизируем TSR

    // Сбрасываем флаг активизации
    tsr_already_active = 0;
  }

  // Уменьшаем счетчик рекурсивных вызовов INT 28
  int_28_in_progress--;

  // Передаем управление по цепочке прерываний
  _chain_intr(old_int28);
}

// ========================================
// new_int9
// Новый обработчик аппаратного прерывания
// от клавиатуры INT 9h
// ========================================
#pragma argsused
void interrupt far new_int9(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  // Если TSR активна, ничего не делаем,
  // передавая управление по цепочке
  if(tsr_already_active) _chain_intr(old_int9);

  // Проверяем, была ли нажата клавиша <Ctrl>.
  // Если нет, передаем управление по цепочке.
  keycode = inp(KEYBOARD_PORT);
  if((_bios_keybrd(_KEYBRD_SHIFTSTATUS) & ShiftKey)
    != ShiftKey)
	 _chain_intr(old_int9);

  // Проверяем коды активизации. Если не наши коды,
  // передаем управление по цепочке.
  if(!(keycode == HotKeyRecording))
	_chain_intr(old_int9);

  // Если немедленная активизация невозможна,
  // устанавливаем флаг запроса на активизацию
  // при занятой MS-DOS. Активизация будет
  // выполнена при первой же возможности
  // обработчиками INT 8 или INT 28h
  popup_while_dos_busy = 1;

  // Завершаем обработку аппаратного прерывания
  asm cli
  asm in al, 61h
  asm mov ah, al
  asm or al, 80h
  asm out 61h, al
  asm xchg ah, al
  asm out 61h, al
  asm mov al, 20h
  asm out 20h, al
  asm sti
}

// ========================================
// new_int8
// Новый обработчик прерывания INT 8h
// Вызывается по прерываниям таймера
// Используется для активизации TSR
// ========================================
#pragma argsused
void interrupt far new_int8(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  // Если это безопасно, активизируем TSR
  // Не запускаемся, если TSR уже активизирована,
  // если вызвано прерывание INT 13h для
  // работы с диском, если MS-DOS занята
  if(!tsr_already_active   &&
      popup_while_dos_busy &&
     !DosBusy() && !unsafe_flag)
  {
    // Активизация TSR
    popup_while_dos_busy = 0;
    tsr_already_active = 1;

    // Вызываем старый обработчик INT 8h
#pragma warn -pro
    (*old_int8)();
#pragma warn +pro

    _enable(); // разрешаем прерывания
    activate_tsr(); // активизируем TSR
    tsr_already_active = 0;
  }

  // Если активизация невозможна, передаем
  // управление по цепочке
#pragma warn -pro
  else (*old_int8)();
#pragma warn +pro
}

// ========================================
// new_int1b
// Обработчик прерывания <Ctrl+Break>
// ========================================
#pragma argsused
void interrupt far new_int1b(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  // Игнорируем попытку прервать TSR
}

// ========================================
// new_int23
// Обработчик прерывания <Ctrl+C>
// ========================================
#pragma argsused
void interrupt far new_int23(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  // Игнорируем попытку прервать TSR
}

// ========================================
// new_int24
// Обработка критической ошибки
// ========================================
#pragma argsused
void interrupt far new_int24(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  ax = 3; // возвращаем код ошибки 3
}

// ========================================
// RestoreIntVect
// Восстановление векторов прерываний.
// Возможно только в том случае, если
// они не были еще раз переустановлены
// уже после запуска TSR
// ========================================
int RestoreIntVect(int Vect,
  INTADDR NewInt, INTADDR OldInt)
{
  // Сравниваем текущий вектор прерывания с тем
  // значением, которое установил для себя TSR
  if(NewInt == _dos_getvect(Vect))
  {
    _dos_setvect(Vect, OldInt);
    return 0; // восстановили
  }
  return 1;   // не смогли восстановить
}

// ========================================
// tsr_exit
// Выгрузка резидентной программы
// ========================================
void tsr_exit(void)
{
  set_stack(); // используем стек TSR

  // Восстанавливаем обработчики прерываний,
  // если они не были переназначены
  // после запуска TSR
  _disable();
  if(!(
    RestoreIntVect(0x1c, new_int1c, old_int1c) |
    RestoreIntVect(8, new_int8, old_int8)      |
    RestoreIntVect(9, new_int9, old_int9)      |
    RestoreIntVect(0x13, new_int13, old_int13) |
    RestoreIntVect(0x28, new_int28, old_int28) |
    RestoreIntVect(0x2f, new_int2f, old_int2f)))
  {

    // Устанавливаем родительский PSP,
    // записанный в нашем PSP
    *(int far *)(((long)_psp << 16) + PSP_PARENT_PSP)
      = GetPSP();

    // Устанавливаем в нашем PSP адрес завершения
    *(long far *)(((long)_psp << 16) + PSP_TERMINATE)
      = ExitAddress;

    // Устанавливаем наш PSP
    SetPSP(_psp);
    _enable();

    bdos(DOS_EXIT, 0, 0); // завершаем работу TSR
  }

  // Если прерывания отсоединить не удалось,
  // восстанавливаем стек и возвращаем
  // управление
  restore_stack();
  _enable();
}

// ========================================
// tsrinit
// Инициализация резидентной программы
// ========================================
int tsrinit(void)
{
  char * wk_ptr;
  unsigned far *fp;

// Резервируем место для области,
// которая будет использована при
// активизации TSR для динамического
// выделения памяти
  wk_ptr = malloc(HEAP_RESERVED);
    if(wk_ptr == NULL) return 0;

  // Заказываем стек для резидентной программы
  tsr_stack = malloc(STACK_SIZE);
    if(tsr_stack == NULL) return 0;

  // Устанавливаем указатель стека,
  // который будет использоваться при
  // активизации TSR
  tsr_stack += STACK_SIZE;

  // Возвращаем зарезервированную память
  free(wk_ptr);

  // Инициализируем указатель на флаг InDos
  getInDosFlag();

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

  // Получаем старое значение векторов прерываний
  old_int1c = _dos_getvect(0x1C);
  old_int2f = _dos_getvect(0x2F);
  old_int8  = _dos_getvect(8);
  old_int9  = _dos_getvect(9);
  old_int13 = _dos_getvect(0x13);
  old_int28 = _dos_getvect(0x28);

  // Сохраняем адрес обработчика INT 13h
  get_int_13();

  // Устанавливаем новые обработчики прерываний
  _dos_setvect(0x1C, (fptr)new_int1c);
  _dos_setvect(0x2F, (fptr)new_int2f);
  _dos_setvect(8,    (fptr)new_int8);
  _dos_setvect(9,    (fptr)new_int9);
  _dos_setvect(0x13, (fptr)new_int13);
  _dos_setvect(0x28, (fptr)new_int28);

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

  // Освобождаем блок среды MS-DOS
   FP_SEG(fp) = _psp;
   FP_OFF(fp) = 0x2c;
   _dos_freemem(*fp);

   return 1;
}

// ========================================
// tsrloaded
// Проверяем, была ли уже загружена программа.
// Если была, завершаемся без установки
// ========================================
int tsrloaded(void)
{
  // Проверяем, была ли уже запущена программа
  // Для этого вызываем мультиплексное прерывание
  // со специальным кодом 0xff00
  regs.h.ah = tsrid;
  regs.h.al = 0x00;
  int86(0x2f, &regs, &regs);
  if(regs.x.ax == 0x00ff)
    return 1;
  else
    return 0;
}


// ========================================
// unload
// Выгрузка из памяти
// ========================================
void unload(void)
{
   // Вызываем программу выгрузки TSR из
   // оперативной памяти
   switch (tsrunload())
   {
     case 1:
       printf("TSRDEMO не была запущена");
       break;

     case 2:
       printf("TSRDEMO успешно выгружена из памяти");
       break;

     default:
       printf("TSRDEMO отключена, но не выгружена");
       break;
   }
}

// ========================================
// getInDosFlag
// Инициализация указателей на флаг InDos
// ========================================
void getInDosFlag(void)
{
  regs.h.ah = 0x34;
  intdosx(&regs, &regs, &sregs);

  // Указатель на флаг InDos возвращается в ES:BX
  FP_SEG(indos_ptr) = sregs.es;
  FP_OFF(indos_ptr) = regs.x.bx;

  // Находим флаг критических ошибок
  regs.x.ax = 0x5D06;
  intdosx(&regs,&regs,&sregs);

  // Указатель на флаг находится в DS:SI
  FP_SEG(crit_err_ptr) = sregs.ds;
  FP_OFF(crit_err_ptr) = regs.x.si;
}

// ========================================
// DosBusy
// Возвращает 0xFFFF, если MS-DOS занята
// (если флаги не установлены)
// ========================================
int DosBusy(void)
{
  if(indos_ptr && crit_err_ptr)
    return(*crit_err_ptr || *indos_ptr);
  else
    return 0xFFFF;
}

// ========================================
// Int28DosBusy
// Функция возвращает ненулевое значение,
// если значение флага InDOS > 1 или
// установлен флаг критической ошибки.
// В этом случае MS-DOS занята
// ========================================
int Int28DosBusy(void)
{
  if(indos_ptr && crit_err_ptr)
    return (*crit_err_ptr || (*indos_ptr > 1));
  else
    return 0xFFFF;
}

// ========================================
// GetExtErr
// Получение расширенной информации об ошибках
// ========================================
void GetExtErr(struct ExtErr * ErrInfo)
{
  union  REGS regs;

  regs.h.ah = 0x59;
  regs.x.bx = 0;
  intdos(&regs,&regs);
  ErrInfo->errax = regs.x.ax;
  ErrInfo->errbx = regs.x.bx;
  ErrInfo->errcx = regs.x.cx;
}

// ========================================
// SetExtErr
// Установка расширенной информации об ошибках
// ========================================
void SetExtErr(struct ExtErr near * ErrInfo)
{
  union  REGS regs;
  struct SREGS segregs;

  regs.x.ax = 0x5d0a;
  regs.x.bx = 0;

  // Запись адреса информации об ошибке в DS:DX
  segread(&segregs);
  regs.x.dx = (int) ErrInfo;
  intdosx(&regs,&regs,&segregs);
}


// ========================================
// GetPSP
// Получить адрес текущего блока PSP
// ========================================
unsigned GetPSP(void)
{
  regs.h.ah = 0x62;
  intdos(&regs,&regs);
  return regs.x.bx;
}

// ========================================
// SetPSP
// Установить текущий PSP
// ========================================
void SetPSP(unsigned segPSP)
{
  if(!crit_err_ptr)
    // Нельзя вызывать InitInDos
    return;

  *crit_err_ptr = 0xFF;
  regs.h.ah = 0x50;
  regs.x.bx = segPSP;

  intdos(&regs,&regs);
  *crit_err_ptr = 0;
}
