/*
d80.c - simple midnight commander's extfs module for manipulating d80 diskette images
 
comments, patches, improvements are welcome at: dusky at hq dot alert dot sk

2006 dusky
*/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>
#include <stdlib.h>

#define SECSIZE	512
#define FATPOS	1
#define DIRPOS	6
#define FATSIZE	5

#define IMAGE_MDOS	1
#define IMAGE_MDOS3	2

//IllegalCharsShort:      db      """*+,./:;<=>?[\\]|"


char *diskpath;
int diskfd;
int diskaccess;
unsigned char fat[FATSIZE][SECSIZE];

int skip=0;
char *ext="PCNB_SQ";

char *encode_filename(unsigned char *dentry) {
  static char outname[96];
  char *ptr;
  int i, extra1, extra2;
  
  if (!dentry) return NULL;
  
  ptr=outname;
  for (i=1; i<11; i++) {
    if (!*(dentry+i)) continue;
    if (*(dentry+i)>126 || strchr("\"#*+.,/:;<=>?[\\]|{}", *(dentry+i))) {
      sprintf(ptr, "#%02X", *(dentry+i));
      ptr+=2;
    } else *ptr=*(dentry+i);
    ptr++;
  }
  extra1=dentry[13]+256*dentry[14];
  extra2=dentry[15]+256*dentry[16];
  sprintf(ptr, ",#%04X,#%04X", extra1, extra2);
  ptr+=12;
  *ptr='.';
  ptr++;
  if (*dentry>126 || strchr("\"#*+.,/:;<=>?[\\]|{}", *dentry)) {
    sprintf(ptr, "#%02X", *dentry);
    ptr+=2;
  } else {
    switch (*dentry) {
      case 'P': sprintf(ptr, "zx0"); break;
      case 'C': sprintf(ptr, "zx1"); break;
      case 'N': sprintf(ptr, "zx2"); break;
      case 'B': sprintf(ptr, "zx3"); break;
      case 'S': sprintf(ptr, "zx5"); break;
      case 'Q': sprintf(ptr, "zx6"); break;
      default: sprintf(ptr, "#%02X", *dentry);
    }
    ptr+=2;
  }
  ptr++;
  *ptr='\0';
  
  return outname;
}

unsigned char *decode_filename(char *filename) {
  static unsigned char dentry[32];
  char *ptr2;
  int i, extra1, extra2, len, ch, stage, j;
  
  if (!filename) return NULL;
  memset(dentry, 0xE5, 32);
  extra1=0;
  extra2=0;
  ptr2=filename;
  stage=0;
  j=1;
  for (i=0; i<strlen(filename); i++) {
    if (!*ptr2) break;
    switch (*ptr2) {
      case '.':	
        if (stage>2) return NULL;
        dentry[13]=(extra1 & 0xff);
        dentry[14]=(extra1 >> 8) & 0xff;
        dentry[15]=(extra2 & 0xff);
        dentry[16]=(extra2 >> 8) & 0xff;
        stage=3;
        break;
      case ',':
        if (stage>1) return NULL;
        switch (stage) {
          case 0: len=sscanf(ptr2+1, "#%04X", &extra1); ptr2+=len*5; break;
          case 1: len=sscanf(ptr2+1, "#%04X", &extra2); ptr2+=len*5; break;
          default: return NULL;
        }
        stage++;
        break;
      case '#':
        if (stage==3) {
          len=sscanf(ptr2, "#%02X", &ch);
          ptr2+=len*2;
          while (j<11) {
            dentry[j]='\000';
            j++;
          }
          dentry[0]=ch;
          stage++;
        } else
        if (j<11) {
          len=sscanf(ptr2, "#%02X", &ch);
          ptr2+=len*2;
          dentry[j]=ch;
          j++;
        }
        break;
      default:
        switch (stage) {
          case 0:
            if (j<11) {
              dentry[j]=*ptr2;
              j++;
            }
            break;
          case 1:
          case 2: return NULL;
            break;
          case 3:
            if (*ptr2!='z' || *(ptr2+1)!='x') return NULL;
            ch=*(ptr2+2);
            if ((ch<'0') || (ch>'6')) return NULL;
            while (j<11) {
              dentry[j]='\000';
              j++;
            }
            dentry[0]=ext[ch-48];
            ptr2+=2;
        }
      }
      ptr2++;
    }
    
  return dentry;
}

int image_type_guess() {
  unsigned char buff[SECSIZE];

  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return -1;
  }

  lseek(diskfd, 0, SEEK_SET);
  read(diskfd, buff, SECSIZE);
  if (buff[0xCC]==0x53 && buff[0xCD]==0x44 && buff[0xCE]==0x4f && buff[0xCF]==0x53) return IMAGE_MDOS;
  read(diskfd, buff, SECSIZE);
  if (buff[0xCC]==0x53 && buff[0xCD]==0x44 && buff[0xCE]==0x4f && buff[0xCF]==0x53) return IMAGE_MDOS3;
  return -1;
}

void read_fat() {
  int i;

  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return;
  }

  lseek(diskfd, skip + FATPOS * SECSIZE, SEEK_SET);
  for (i=0;i<FATSIZE;i++)
    read(diskfd, fat[i], SECSIZE);
}

void write_fat() {
  int i;

  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return;
  }

  lseek(diskfd, skip + FATPOS * SECSIZE, SEEK_SET);
  for (i=0;i<FATSIZE;i++)
    write(diskfd, fat[i], SECSIZE);
}

int get_fat_next(int sec) {
  int s1,s2,next;
  
  s1=(sec % 341);
  s2=(sec / 341);
  if (s2>=FATSIZE) return 0xDDD;
  if (s1&1)
    next = fat[s2][(s1/2)*3+2] + 256*((fat[s2][(s1/2)*3+1])&15);
  else
    next = fat[s2][(s1/2)*3] + 256*((fat[s2][(s1/2)*3+1])>>4);
  return next;
}

void set_fat_next(int sec, int next) {
  int s1,s2;
  
  s1=(sec % 341);
  s2=(sec / 341);
  if (s2>=FATSIZE) return;
  if (s1&1) {
    fat[s2][(s1/2)*3+2] = (next & 0xff);
    fat[s2][(s1/2)*3+1]&=0xf0;
    fat[s2][(s1/2)*3+1]|=((next >> 8) & 0x0f);
  } else {
    fat[s2][(s1/2)*3] = (next & 0xff);
    fat[s2][(s1/2)*3+1]&=0x0f;
    fat[s2][(s1/2)*3+1]|=((next >> 4) & 0xf0);
  }
}

int find_free_fat(int from) {
  int i;
  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return -1;
  }
  for (i=from;i<1705;i++) {
    if (get_fat_next(i)==0) return i;
  }
  return -1;
}

int check_space(int size) {
  int first,next,i;
  
  if ((first=find_free_fat(0))==-1) return -1;
  if (size==0) return first;
  next=first+1;
  for (i=1;i<((size+511)/512);i++) {
    if ((next=find_free_fat(next))==-1) return -1;
    next++;
  }
  return first;
}

int count_len(int sec) {
  int nsec=get_fat_next(sec);
  if (nsec==0xC00) return 0x000;
  if (nsec>0xE00) return nsec-0xE00;
  if (nsec==0xE00) return 0x200;
  if (nsec>0xC00) return 0x000;
  return 0x200+count_len(nsec);
}

int open_disk(char *fname) {
  int fd,type;
  
  if (!fname) {
    printf("Bad filename.\n");
    return -1;
  }
  if (diskfd!=-1) {
    printf("Image (%s) is already opened.\n", diskpath);
    return -1;
  }
  diskaccess=-1;
  if ((fd=open(fname, O_RDWR))<0) {
    if ((fd=open(fname, O_RDONLY))<0) {
      printf("Can't open image (%s).\n", fname);
      return -1;
    }
    diskaccess++;
  }
  diskaccess++;
  
  diskfd=fd;
  switch (type=image_type_guess()) {
    case IMAGE_MDOS:	skip=0; break;
    case IMAGE_MDOS3:	skip=512; break;
    default:
//      printf("Unknown image type (%d).\n", type);
      close(fd);
      diskfd=-1;
      return -1;
  }
  diskpath=strdup(fname);
  
  return fd;
}

void close_disk() {
  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return;
  }
  if (diskpath) free(diskpath);
  diskpath=NULL;
  close(diskfd);
  diskfd=-1;
}

void list() {
  int i, len, fsec;
  unsigned char dentry[32];
  
  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return;
  }
  lseek(diskfd, skip + DIRPOS * SECSIZE, SEEK_SET);
  for (i=0;i<128;i++) {
    read(diskfd, dentry, 32);
    if (dentry[0]==0xe5) continue;

    len=(unsigned char)dentry[11]+256*(unsigned char)dentry[12]+65536*(unsigned char)dentry[21];
    fsec=(unsigned char)dentry[17]+256*(unsigned char)dentry[18];

    printf("-rw-rw-rw- 1 0 0 %d 01-01-1970 01:00 ", len);
    printf("%s\n", encode_filename(dentry));
  }
}

unsigned char *find_file(char *filename) {
  static unsigned char dentry[32], *dentry2;
  int i;
  
  dentry2=decode_filename(filename);
  
  lseek(diskfd, skip + DIRPOS * SECSIZE, SEEK_SET);
  for (i=0;i<128;i++) {
    read(diskfd, dentry, 32);
    if (dentry[0]==0xe5) continue;
    if (memcmp(dentry, dentry2, 11)) continue;
    return dentry;
  }
/*
  for (i=0;i<11;i++)
    printf("%c",dentry2[i]);
  printf("\n");
  sleep(5);
*/
  return NULL;
}

int find_free_file() {
  static unsigned char dentry[32];
  int i;
  
  lseek(diskfd, skip + DIRPOS * SECSIZE, SEEK_SET);
  for (i=0;i<128;i++) {
    read(diskfd, dentry, 32);
    if (dentry[0]==0xe5) {
      return i;
    }
  }
  return -1;
}

int copyout(char *filename, char *filename2) {
  int len, fsec, nsec;
  int ofd;
  unsigned char *dentry;
  unsigned char buff[SECSIZE];
  
  read_fat();
  if (!filename) {
    printf("Bad filename.\n");
    return -1;
  }
  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return -1;
  }
  dentry=find_file(filename);
  if (!dentry) {
    printf("No such file (%s).\n", filename);
    return -1;
  }
  len=(unsigned char)dentry[11]+256*(unsigned char)dentry[12]+65536*(unsigned char)dentry[21];
  fsec=(unsigned char)dentry[17]+256*(unsigned char)dentry[18];
  ofd=open(filename2, O_WRONLY|O_CREAT|O_TRUNC, 0666);
  while ((nsec=get_fat_next(fsec))!=0xC00) {
    lseek(diskfd, skip + fsec*SECSIZE, SEEK_SET);
    read(diskfd, buff, SECSIZE);
    if (nsec>0xE00) {
      write(ofd, buff, nsec-0xE00);
      break;
    }
    if (nsec==0xE00) {
      write(ofd, buff, 0x200);
      break;
    }
    if (nsec>0xC00) break;
    write(ofd, buff, 0x200);
    fsec=nsec;
  }
  close(ofd);
  return 0;
}

int copyin(char *filename, char *filename2) {
  int len, fsec, nsec, nsec2;
  int ifd, dir, i;
  unsigned char *dentry;
  unsigned char buff[SECSIZE];
  struct stat st;
    
  read_fat();
  if (!filename || !filename2) {
    printf("Bad filename.\n");
    return -1;
  }
  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return -1;
  }
  dentry=find_file(filename);
  if (dentry) {
    printf("File exist (%s).\n", filename);
    return -1;
  }
  if ((dir=find_free_file())==-1) {
    printf("Directory full.\n");
    return -1;
  }
  stat(filename2, &st);
  len=st.st_size;
  fsec=check_space(len);
  if (fsec==-1) {
    printf("No enough free space (%d)\n", len);
    return -1;
  }  
  
  if (len==0) {
    set_fat_next(fsec, 0xC00);
  } else {
    ifd=open(filename2, O_RDONLY);
    nsec=fsec;
    nsec2=nsec;
    for (i=1;i<((len+511)/512);i++) {
      nsec=find_free_fat(nsec2+1);
      set_fat_next(nsec2, nsec);
      read(ifd, buff, SECSIZE);
      lseek(diskfd, skip + nsec2*SECSIZE, SEEK_SET);
      write(diskfd, buff, SECSIZE);
      nsec2=nsec;
    }
    set_fat_next(nsec2, (len%512)+0xE00);
    read(ifd, buff, SECSIZE);
    lseek(diskfd, skip + nsec2*SECSIZE, SEEK_SET);
    write(diskfd, buff, SECSIZE);
    close(ifd);
  }
  dentry=decode_filename(filename);
  dentry[11]=len&0xFF;
  dentry[12]=(len >> 8)&0xFF;
  dentry[21]=(len >> 16)&0xFF;
  dentry[17]=fsec&0xFF;
  dentry[18]=(fsec >> 8)&0xFF;
  dentry[19]=0x00;
  dentry[20]=0x0F;
  write_fat();
  lseek(diskfd, skip + DIRPOS*SECSIZE + dir*32, SEEK_SET);
  write(diskfd, dentry, 32);
  return 0;
}

void rm(char *filename) {
  int fsec, nsec;
  unsigned char *dentry;
  unsigned char buff[SECSIZE];
  
  
  read_fat();
  if (!filename) {
    printf("Bad filename.\n");
    return;
  }
  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return;
  }
  dentry=find_file(filename);
  if (!dentry) {
    printf("No such file (%s).\n", filename);
    return;
  }
  
  lseek(diskfd, -32, SEEK_CUR);
  buff[0]=0xe5;
  write(diskfd, buff, 1);
  
  fsec=(unsigned char)dentry[17]+256*(unsigned char)dentry[18];
  while ((nsec=get_fat_next(fsec))<0xC00) {
    if (!nsec) break;
    set_fat_next(fsec, 0x000);
    fsec=nsec;
  }
  if ((nsec>>8)!=0xd) set_fat_next(fsec, 0x000);
  write_fat();
}

void fat_disk() {
  int i;
  if (diskfd==-1) {
    printf("Image is not opened.\n");
    return;
  }
  for (i=0;i<64;i++) {
    printf("%03X: %03X\n",i, get_fat_next(i));
  }
}

void test_fat() {
  read_fat();
  fat_disk();
  set_fat_next(0,666);
  fat_disk();
}

int main(int argc, char **argv) {
  FILE *fp;
  int i,retval;
  
  diskpath=NULL;
  diskfd=-1;
  
  fp=fopen("/tmp/d80.log","a");
  for (i=0;i<argc;i++) {
    fputs("[", fp);
    fputs(argv[i], fp);
    fputs("]", fp);
  }
  fputs("\n", fp);
  fclose(fp);
  
  if (argc<2) return -1;
  if (!strcmp(argv[1], "list")) {
    if (argc<3) return -1;
    if (open_disk(argv[2])==-1) return -1;
    list();
    close_disk();
    return 0;
  }
  if (!strcmp(argv[1], "copyout")) {
    if (argc<5) return -1;
    if (open_disk(argv[2])==-1) return -1;
    retval=copyout(argv[3], argv[4]);
    close_disk();
    return retval;
  }
  if (!strcmp(argv[1], "copyin")) {
    if (argc<5) return -1;
    if (open_disk(argv[2])==-1) return -1;
    if (!diskaccess) 
      retval=copyin(argv[3], argv[4]);
    close_disk();
    return retval;
  }
  if (!strcmp(argv[1], "rm")) {
    if (argc<3) return -1;
    if (open_disk(argv[2])==-1) return 1;
    if (!diskaccess)
      rm(argv[3]);
    close_disk();
    return 0;
  }
  
  if (!strcmp(argv[1], "testfat")) {
    if (argc<3) return -1;
    if (open_disk(argv[2])==-1) return 1;
    test_fat();
    close_disk();
    return 0;
  }
  
  if (diskfd!=-1) {
    close_disk();
  }
  return -1;
}
