//
// Для этой программы требуется модель
// памяти Large
//
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <malloc.h>
#include <dos.h>
#include <bios.h>
#include <ctype.h>
#include <string.h>

typedef struct _EBPB_
{
  unsigned sectsize;
  char clustsize;
  unsigned ressecs;
  char fatcnt;
  unsigned rootsize;
  unsigned totsecs;
  char media;
  unsigned fatsize;
  unsigned seccnt;
  unsigned headcnt;
  unsigned hiddensec_low;
  unsigned hiddensec_hi;
  unsigned long drvsecs;
} EBPB;

typedef struct _BOOT_
{
  char jmp[3];
  char oem[8];
  EBPB bpb;
  char drive;
  char reserved;
  char signature;
  unsigned volser_lo;
  unsigned volser_hi;
  char label[11];
  char fat_format[8];
  char boot_code[450];
} BOOT;

typedef struct _FTIME_
{
  unsigned sec : 5, min : 6, hour : 5;
} FTIME;
typedef struct _FDATE_
{
  unsigned day : 5, month : 4, year : 7;
} FDATE;
typedef struct _FITEM_
{
  char name[8];
  char ext[3];
  char attr;
  char reserved[10];
  FTIME time;
  FDATE date;
  unsigned cluster_nu;
  unsigned long size;
} FITEM;

int getboot(BOOT far *boot, int drive);
int fat(char far *b_fat, int t_fat, unsigned idx);

union REGS reg;
struct SREGS segreg;

struct
{
  unsigned long first_sect;
  unsigned nsect;
  void far* buf;
} cb;

int main(void)
{
  BOOT *boot_rec;
  int i,j, k, status, fat_sectors;
  unsigned total_sectors;
  int ffat, root_begin, root_sectors;
  char drive;
  unsigned far *fat_buffer;
  FITEM far *root_buffer, far *rptr;
  char cbuf[128];
  char far *clust_buffer;
  unsigned cur_clust;

  printf("\n\nЧтение каталогов логического диска"
    "\n  (C)Фролов А., 1995\n");

  // Заказываем буфер для чтения BOOT-записи
  boot_rec = (BOOT*)malloc(sizeof(BOOT));
  if(boot_rec == NULL)
  {
    printf("\nМало памяти");
    return -1;
  }

  // Запрашиваем диск, для которого необходимо
  // выполнить чтение загрузочной записи.
  printf("\nВведите обозначение диска (C, D, ...):");
  drive = getche();
  drive = toupper(drive) - 'A';
  if(drive < 2)
  {
    printf("\nМожно указывать только НМД");
    free(boot_rec);
    return(-1);
  }

  // Читаем загрузочную запись в буфер
  status = getboot((BOOT _far*)boot_rec, drive);
  if(status)
  {
    printf("\nОшибка при чтении загрузочного сектора");
    free(boot_rec);
    return(-1);
  }

  // Определяем формат таблицы FAT
  total_sectors = boot_rec->bpb.totsecs;

  // Если мы работаем с расширенным разделом диска,
  // общее количество секторов на диска берем из
  // расширенного PBP
  if(total_sectors == 0)
    total_sectors = boot_rec->bpb.drvsecs;

  // Определяем формат FAT
  if(!strncmp(boot_rec->fat_format, "FAT16", 5))
  {
    printf("\nFAT имеет 16-битовый формат");
    ffat = 16;
  }
  else
  {
    printf("\nFAT имеет 12-битовый формат");
    ffat = 12;
  }

  // Определяем количество секторов, занимаемых FAT
  fat_sectors = boot_rec->bpb.fatsize;

  // Заказываем буфер для FAT
  fat_buffer = (unsigned int*)
    farmalloc((long)fat_sectors * boot_rec->bpb.sectsize);
  if(fat_buffer == NULL)
  {
    printf("\nМало памяти");
    free(boot_rec);
    return -1;
  }

  // Вычисляем номер первого сектора FAT
  j = boot_rec->bpb.ressecs;

  // Читаем FAT в буфер fat_buffer
  // Заполняем регистровые структуры для вызова
  // прерывания DOS INT 25h
  cb.first_sect = j;
  cb.nsect = fat_sectors;
  cb.buf = (void far*)fat_buffer;

  _BX  = FP_OFF(&cb);
  _DS  = FP_SEG(&cb);
  _CX  = 0xffff;
  _DX  = 0;
  _AX  = drive;
  asm int 25h
  asm pop ax

  // Вычисляем номер первого сектора корневого каталога
  root_begin = j + fat_sectors * boot_rec->bpb.fatcnt;

  // Вычисляем длину корневого каталога
  root_sectors = (boot_rec->bpb.rootsize * 32) /
    boot_rec->bpb.sectsize;

  // Заказываем буфер для корневого каталога
  root_buffer = (FITEM far *)
    farmalloc(root_sectors * boot_rec->bpb.sectsize);
  if(root_buffer == NULL)
  {
    printf("\nМало памяти");
    free(boot_rec);
    farfree(fat_buffer);
    return -1;
  }

  cb.first_sect = root_begin;
  cb.nsect = root_sectors;
  cb.buf = (void far*)root_buffer;
  _BX  = FP_OFF(&cb);
  _DS = FP_SEG(&cb);
  _CX  = 0xffff;
  _DX  = 0;
  _AX  = drive;
  asm int 25h
  asm pop ax

  // Показываем содержимое корневого каталога
  printf("\n"
   "\nИмя файла    Аттр. Дата        Время     Кластер Размер"
   "\n------------ ----- ----------  --------  ------- ------");

  for(rptr = root_buffer;;rptr++)
  {
    printf("\n");

    // Признак конца каталога - нулевой байт в начале
    // имени файла
    if(rptr->name[0] == 0) break;

    // Выводим содержимое дескриптора файла
    for(i=0; i<8; i++) printf("%c",rptr->name[i]);
    printf(".");
    for(i=0; i<3; i++) printf("%c",rptr->ext[i]);
    printf(" %02X    %02d-%02d-%02d  %02d:%02d:%02d ",
      rptr->attr, rptr->date.day, rptr->date.month,
      rptr->date.year + 1980, rptr->time.hour,
      rptr->time.min, rptr->time.sec * 2);
    printf(" %-6ld   %lu",
      (long)rptr->cluster_nu, (long)rptr->size);
  }

  // Получаем буфер для чтения кластеров каталога
  clust_buffer = (char far*)
    farmalloc(boot_rec->bpb.clustsize
    * boot_rec->bpb.sectsize);

  if(clust_buffer == NULL)
  {
    printf("\nМало памяти");
    farfree(root_buffer);
    free(boot_rec);
    farfree(fat_buffer);
    return -1;
  }

  printf("\nНомер первого кластера каталога:");
  gets(cbuf);
  cur_clust = atol(cbuf);

  // Переменная k используется в качестве флага.
  // При первом просмотре каталога ее значение равно 0,
  // затем эта переменная устанавливается в 1.
  k = 0;

  for(;;)
  {
    // Сохраняем номер кластера каталога
    unsigned long j = cur_clust;

    // Вычисляем номер следующего кластера, распределенного
    // каталогу
    cur_clust = fat((char far*)fat_buffer, ffat, cur_clust);
    printf("%d ", cur_clust);

    // Читаем кластер в буфер clust_buffer
    cb.first_sect = root_begin + root_sectors
	+ ((j-2)*boot_rec->bpb.clustsize);
    cb.nsect = boot_rec->bpb.clustsize;
    cb.buf = (void far*)clust_buffer;

    _BX  = FP_OFF(&cb);
    _DS = FP_SEG(&cb);
    _CX  = 0xffff;
    _DX  = 0;
    _AX  = drive;
    asm int 25h
    asm pop ax

    // Показываем содержимое каталога
    rptr = (FITEM far *)clust_buffer;

    // Первый дескриптор в каталоге указывает на
    // этот же каталог. В поле имени первого дескриптора
    // находится строка ".       ". Этот факт можно использовать
    // для проверки каталога. Если вы по ошибке указали
    // номер кластера, не принадлежащего каталогу,
    // программа завершит работу с сообщением об ошибке.
     if(k == 0)
     {
       k = 1;
       if(_fstrncmp(rptr->name,".       ",8) != 0)
       {
         printf("\nЭто не каталог !");
         farfree(root_buffer);
         free(boot_rec);
         farfree(fat_buffer);
         farfree(clust_buffer);
	 return(-1);
       }
     }

     printf("\n"
 "\nИмя файла    Аттр. Дата        Время     Кластер Размер"
 "\n------------ ----- ----------  --------  ------- ------");

    for(;;rptr++)
    {
      printf("\n");

      // Признак конца каталога - нулевой байт в начале
      // имени файла
      if(rptr->name[0] == 0) break;

      // Выводим содержимое дескриптора файла
      for(i=0; i<8; i++) printf("%c",rptr->name[i]);
      printf(".");
      for(i=0; i<3; i++) printf("%c",rptr->ext[i]);
      printf(" %02X    %02d-%02d-%02d  %02d:%02d:%02d ",
        rptr->attr,	rptr->date.day,	rptr->date.month,
         rptr->date.year + 1980, rptr->time.hour,
         rptr->time.min, rptr->time.sec * 2);
      printf(" %-6ld   %lu",
        (long)rptr->cluster_nu, (long)rptr->size);
    }

    // Если этот кластер - последний из распределенных каталогу,
    // завершаем работу программы
    if((cur_clust == 0xfff) || (cur_clust == 0xffff)) break;
  }

  farfree(root_buffer);
  free(boot_rec);
  farfree(fat_buffer);
  farfree(clust_buffer);
  return 0;
}

int getboot(BOOT far *boot, int drive)
{
  cb.first_sect = 0;
  cb.nsect = 1;
  cb.buf = (void far*)boot;

  _BX  = FP_OFF(&cb);
  _DS = FP_SEG(&cb);
  _CX  = 0xffff;
  _DX  = 0;
  _AX  = drive;
  asm int 25h
  asm pop ax
  asm jc err

  return 0;
err:
  return 1;
}

/**
* fat
*
* Выбрать элемент из FAT
*
* Функция выбирает элемент с заданным номером из таблицы
* размещения файлов FAT. Формат FAT передается
* функции как параметр
*
* int fat(b_fat, t_fat, idx);
*
*    char far *b_fat  - буфер, содержащий FAT
*
*    int t_fat         - формат FAT (12 или 16)
*
*    unsigned idx      - номер элемента FAT, который
*                        должен быть выбран
*
**/

int fat(char far *b_fat, int t_fat, unsigned idx)
{
  div_t clust_nu ;
  int cluster;

  if(t_fat == 12)
  {
    clust_nu = div(idx * 3, 2);

    if(clust_nu.rem != 0)
       cluster =
         (*((int far*)(b_fat +
         clust_nu.quot)) >> 4) & 0xfff;
    else
       cluster = *((int far*)(b_fat + clust_nu.quot)) & 0xfff;
  }
  else if(t_fat == 16)
  {
    cluster = *((int far*)(b_fat + idx * 2));
  }
  else
  {
    printf("Ошибка в формате FAT\n");
    exit(-100);
  }
  return(cluster);
}
