/* Hex editor, Simon Pollard (polsy@ruffles.spodnet.org) 12/1998
   Converted from the perl, which was incapable of seeking properly, 3/1999
   Mind you, so is this.  At least as far as lseek() goes.
   Except it works now.  llseek is nice, if non-portable */

/*  The screen is 80 chars wide and at least 24 rows, for the sake of
    simplicity.  And does nice VT things like ^[[2J.  Haven't figured out
    how to use the curses library properly yet... */

#define STDIN 0

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <termios.h>

char filetxt[35]; /* for screen header */
char errmsg[256]; /* general error message buffer */
char stuff[256]; /* general buffer thingy */
char esc = 27;

int fd, state;
/* state: 0 = unedited, 1 = edited
         +2 for readonly */

long long offset;
off_t fsize;
int isdev, eof, retval;
short int data[256];
int x, y;

struct termios termState;

void beep() { printf("\a"); }

void hexerr(char* errtxt) {
  strcpy(errmsg,"hexedit: ");
  strcpy(errmsg+9,errtxt);
  perror(errmsg);
}

void getblock(long long offset, short int* blk) {
  int result;
  int i;
  unsigned char gbBuf[256];
  loff_t seekRes;

  /* get the data from the file, put it in blk */

  seekRes = llseek(fd, offset, SEEK_SET);
  if(seekRes == -1) { hexerr("getblock: llseek"); exit(1); }

  result = read(fd, gbBuf, 256);
  if(result < 0) { hexerr("getblock: read"); exit(1); }
  if(result == 0) { printf("hexedit: getblock: Read 0 bytes. Exiting.\n"); exit(1); }

  for(i=0; i<result; i++) blk[i] = gbBuf[i];
  if(result < 256) {
    eof = 1;
    for(i=255; i>=result; i--) blk[i] = -1;
  } else eof = 0;
}

void getLine(char* buf) {
  retval = tcgetattr(STDIN, &termState);
  if(retval < 0) { hexerr("tcgetattr: "); exit(1); }
  termState.c_lflag |= (ICANON|ECHO); /* line mode, echo on */
  retval = tcsetattr(STDIN, TCSANOW, &termState);
  if(retval < 0) { hexerr("tcsetattr: "); exit(1); }

  fgets(buf, 80, stdin);

  retval = tcgetattr(STDIN, &termState);
  if(retval < 0) { hexerr("tcgetattr: "); exit(1); }
  termState.c_lflag &= ~(ICANON|ECHO); /* char mode, echo off */
  retval = tcsetattr(STDIN, TCSANOW, &termState);
  if(retval < 0) { hexerr("tcsetattr: "); exit(1); }

}

long long getNumber(char* prompt) {
  char reply[80];
  long long result;

  printf("%c[24;5H%s : ", esc, prompt);
  getLine(reply);
  printf("%c[24;0H%c[2K%c[%d;%dH",esc, esc, esc, y, x);

  if(reply[0] == '&') {
    retval = sscanf(reply,"&%Lx",&result);
    if(retval != 1) result = -1;
  } else {
    retval = sscanf(reply,"%Ld",&result);
    if(retval != 1) result = -1;
  }
  return result;
}

int main(int argc, char* argv[]) {

  char *file;
  char asc[16], hex[50];
  struct stat fileinfo;
  int i, j, bit, redraw, key;
  long long whereTo;

  /* check parameters, open file */

  if(argc != 2) {
    printf("Syntax: hexedit filename\n");
    exit(1);
  }
  file = argv[1];
  if(strlen(file)<35) strcpy(filetxt,file); /* last 35 chars of filename for header */
    else { strcpy(filetxt,"..."); strncpy(filetxt+3,file+strlen(file)-32,32); }

  state = 0; /* unedited, read/write */

  fd = open(file,O_RDWR);
  if(fd < 0) { /* open failed */
    if(errno == EACCES) {
      state = 2; /* read-only */ 
      fd = open(file,O_RDONLY);
    }
    if(fd < 0) {
      strcpy(stuff,"Couldn't open ");
      strcpy(stuff+14,file);
      hexerr(stuff);
      exit(1);
    }
  }

  /* set up vars, get some informational stuff about the file */

  offset = 0; /* Start of the file */

  fstat(fd, &fileinfo);
  fsize = fileinfo.st_size;
  isdev=0;
  if(S_ISCHR(fileinfo.st_mode)||S_ISBLK(fileinfo.st_mode)) isdev=1;

  /* main loop */

  /* Draw static parts of the screen */

  printf("%c[H%c[2J", esc, esc); /* Cursor home, clear screen */
  printf("%c[2;5HFile: %s", esc, filetxt);
  printf("%c[2;49HLength: %ld (%lX) %s", esc, fsize, fsize, isdev ? "[device]":"");
  printf("%c[5;12H00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F", esc);

  asc[16] = 0;

  retval = tcgetattr(STDIN, &termState);
  if(retval < 0) { hexerr("tcgetattr: "); exit(1); }
  termState.c_lflag &= ~(ICANON|ECHO); /* char mode, echo off */
  retval = tcsetattr(STDIN, TCSANOW, &termState);
  if(retval < 0) { hexerr("tcsetattr: "); exit(1); }

  for(;;) {
    /* Fill in the data */

    getblock(offset, data);
    x = 12; y = 7;

    /* Draw the screen bits */

    printf("%c[3;3HOffset: %Ld (%LX)%c[K", esc, offset, offset, esc);
    printf("%c[3;50HState: %s%s", esc, state & 1 ? "Edited  ":"Unedited",
            state & 2 ? " (Read-Only)":"");

    /* Display the data */

    printf("%c[7;0H",esc);
    for(i=0;i<16;i++) {
      hex[0] = 0;
      printf("%09LX  ",(i*16)+offset);
      for(j=0;j<16;j++) {
        bit = data[(i*16)+j];
        if(bit == -1) { asc[j] = ' ';  strcat(hex, "   "); }
        else {
          if((bit < 32) || (bit > 126)) asc[j] = '.'; else asc[j] = bit;
          sprintf(stuff, "%02X ", bit); strcat(hex, stuff);
        }
      }
      printf("%s  %s\n", hex, asc);
    }
    printf("%c[%d;%dH", esc, y, x);

    /* Keypress responses */
    redraw = 0;

    while(! redraw) {
      key = getchar();

      switch(key) {
        case 1: offset = 0; redraw = 1; break; /* St^Art */
        case 2: /* ^Backward (-16) */
          if(offset < 16) beep(); else { offset -= 16; redraw = 1; }
          break;
        case 5: /* ^End */
          offset = fsize; offset -= (offset % 16); redraw = 1; break;
        case 6: /* ^Forward (+16) */
          if(eof) beep(); else { offset += 16; redraw = 1; }
          break;
        case 7: /* ^Goto */
          whereTo = getNumber("Go to offset"); 
          if(whereTo == -1) beep();
          else {
            whereTo = whereTo - (whereTo % 16);
              if (whereTo > fsize && !isdev) beep(); /* devices tend to be 0 bytes */
            else { offset = whereTo; redraw = 1; }
          }
       case 12: /* ^L = redraw */
          redraw = 1; break;
       case 14: /* ^Next (+256) */
          if(eof) beep(); else { offset += 256; redraw = 1; }
          break;
       case 16: /* ^Previous (-256) */
          if(offset < 256) {   if (offset == 0) beep();
                             else { offset = 0; redraw = 1; } }
                      else { offset -= 256; redraw = 1; }
          break;
       case 24: /* e^Xit */
          printf("%c[2J", esc);
          exit(0);
      }
    }
  }
}


