// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: hexdump.cpp 
// Compiler Used: MSVC40, DJGPP 2.7.2.1, GCC 2.7.2.1, HP CPP 10.24
// Produced By: Doug Gaer 
// File Creation Date: 02/13/1996 
// Date Last Modified: 09/04/1998
// ----------------------------------------------------------- // 
// ------------- Program description and details ------------- // 
// ----------------------------------------------------------- // 
/*
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
THE ENTIRE RISK OF THE QUALITY AND PERFORMANCE OF THIS SOFTWARE
IS WITH YOU. SHOULD ANY ELEMENT OF THIS SOFTWARE PROVE DEFECTIVE,
YOU WILL ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR
CORRECTION.
  
This program is used to display the contents of a specified
file, in both hexadecimal and ASCII, to the console. 
*/
// ----------------------------------------------------------- // 
#include <iostream.h>
#include <fstream.h>
#include <ctype.h>
#include <iomanip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hxcrc.hpp"

// Define this macro for DOS file opens (the default is UNIX)
// #ifndef __DOS__
// #define __DOS__
// #endif

// Hexdump version number and program name
const double HexdumpVersionNumber = 1027.101;
const char *ProgramName = "hexdump";

// Program constants
const int MAX_LINE = 255;  // Maximum characters per line
const int hxBuffSize = 16; // Maximum number of bytes per line
const int decBuffSize = 8; // Maximum number of bytes per line

// Constants for text parser
const int MAXWORDLENGTH = 255;
const int MAXWORDS = 255;

// Program globals
char in_file[MAX_LINE];    // Input file 
char out_file[MAX_LINE];   // Optional output file name
int prompting = 0;         // Enable prompting during console dump
int use_console = 1;       // Dump to console if no output file is specified
int decimal_mode = 0;      // Display in decimal mode (Default is HEX)
int building_bin_file = 0; // True if building binary file from text file
int strip_comments = 0;    // True if not adding comments
int display_crc32 = 0;     // True if displaying 32-bit crc checksum
int display_crc16 = 0;     // True if displaying 16-bit crc checksum
int NumLines = 8;          // Default maximum number of lines to display

// Table for HEX to binary conversions
const char *bintab[16] = {
  "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
  "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"
};

// Program Functions 
void ProcessArgs(int argc, char *argv[]);
void HelpMessage(const char *program_name, const double version_number);
int HexFileDump(fstream &infile, ostream &stream);
int DecFileDump(fstream &infile, ostream &stream);
void BuildBinaryFile(char *ifname, char *ofname);
void TruncateLine(char *line, char tline[MAX_LINE], char delimiter1 = '\t',
		  char delimiter2 = ';');
int text_parse(char *string, char words[MAXWORDS][MAXWORDLENGTH],
	       int*numwords, char sepchar1 = ' ', char sepchar2 = ' ');

void HelpMessage(const char *program_name, const double version_number)
{
  char vbuffer[255];
  sprintf(vbuffer, "%.3f", version_number);
  cout << endl;
  cout << "Hexadecimal file dump program version "
       << vbuffer  << endl;
  cout << "Usage: " << program_name << " [switches] infile "
       << "outfile (optional)" << endl;
  cout << "Switches:  -?      = Display this help message." << endl;
  cout << "           -c      = Display a 32-bit CRC checksum value." << endl;
  cout << "           -C      = Display a 16-bit CRC checksum value." << endl;
  cout << "           -d      = Display in decimal mode (Default is HEX)."
       << endl;
  cout << "           -p#     = Prompting after displaying # line: -p10"
       << endl;
  cout << "           -r      = Rebuild binary file from hex dump text file.\n"
       << "                     Only works for files created in hex mode."   
       << endl;
  cout << "           -s      = Strip comments from output." << endl;
  cout << endl;
  cout << "           -x      = Convert 32-bit HEX number to DEC: -Xfefe"
       << endl;
  cout << "           -X      = Convert signed 32-bit HEX number to DEC."
       << endl;
  cout << "           -B      = Convert 32-bit HEX number to binary: -Bfefe"
       << endl;
  cout << "           -D      = Convert DEC number to HEX: -D23" << endl;
  cout << endl;
  exit(0);
}

void ProcessArgs(int argc, char *argv[])
// Process the program's argument list
{
  int i, ibuf;
  char *sbuf;
  unsigned long ulbuf, bbuf;
  long lbuf;
  
  for(i = 1; i < argc; i++ ) {
    if(*argv[i] == '-') {
      char sw = *(argv[i] +1);
      switch(sw) {
	case '?' :
	  HelpMessage(ProgramName, HexdumpVersionNumber);
	  break;

	case 'c':
	  display_crc32 = 1;
	  break;

	case 'C':
	  display_crc16 = 1;
	  break;

	case 'p' :
	  prompting = 1;
	  ibuf = atoi(&argv[i][2]);
	  if(ibuf > 0) NumLines = ibuf; 
	  break;

	case 'd' :
	  decimal_mode = 1;
	  break;

	case 'r' :
	  building_bin_file = 1;
	  break;

	case 's' :
	  strip_comments = 1;
	  break;

	case 'x' : {
	  sbuf = &argv[i][2];
	  cout << endl;
	  sscanf(sbuf, "%x", &ulbuf);
	  cout.setf(ios::uppercase);
	  cout << "0x" << hex << ulbuf << " = " << dec << ulbuf << endl;
	  cout << endl;
	  exit(0);
	  break;
	}
	  
	case 'X' : {
	  sbuf = &argv[i][2];
	  cout << endl;
	  sscanf(sbuf, "%x", &lbuf);
	  cout.setf(ios::uppercase);
	  cout << "0x" << hex << lbuf << " = " << dec << lbuf << endl;
	  cout << endl;
	  exit(0);
	  break;
	}
	
	case 'D' : {
	  sbuf = &argv[i][2];
	  cout << endl;
	  sscanf(sbuf, "%u", &ulbuf);
	  cout.setf(ios::uppercase);
	  cout << ulbuf << " = " << "0x" << hex << ulbuf << endl;
	  cout << endl;
	  exit(0);
	  break;
	}

	case 'B' : {
	  sbuf = &argv[i][2];
	  cout << endl;
	  sscanf(sbuf, "%x", &ulbuf);
	  cout.setf(ios::uppercase);
	  cout << "0x" << hex << ulbuf << " = ";
	  bbuf = (ulbuf & 0xf0000000)>>28;
	  cout << bintab[bbuf] << ' ';
	  bbuf = (ulbuf & 0xf000000)>>24;
	  cout << bintab[bbuf] << ' ';
	  bbuf = (ulbuf & 0xf00000)>>20;
	  cout << bintab[bbuf] << ' ';
	  bbuf = (ulbuf & 0xf0000)>>16;
	  cout << bintab[bbuf] << ' ';
	  bbuf = (ulbuf & 0xf000)>>12;
	  cout << bintab[bbuf] << ' ';
	  bbuf = (ulbuf & 0xf00)>>8;
	  cout << bintab[bbuf] << ' ';
	  bbuf = (ulbuf & 0xf0)>>4;
	  cout << bintab[bbuf] << ' '; 
	  bbuf = ulbuf & 0x0f;
	  cout << bintab[bbuf] << endl; 
	  cout << endl;
	  exit(0);
	  break;
	}
	
	default:
	  cerr << endl;
	  cerr << "Unknown switch " << argv[i] << endl;
	  cerr << "Exiting..." << endl;
	  cerr << endl;
	  exit(0);
	  break;
      }
    }
    else { 
      strcpy(in_file, argv[i]);      // Input file name
      if(argv[i+1]) {
	strcpy(out_file, argv[i+1]); // Output file name
	break;
      }
      else
	break;
    }
  }
}

int HexFileDump(fstream &infile, ostream &stream)
{
  register int i, j;
  int count = 0;
  char c[hxBuffSize];

  // Print all text in upper case characters
  stream.setf(ios::uppercase);

  while(!infile.eof()) {

    for(i = 0; i < hxBuffSize && !infile.eof(); i++) {
      infile.get(c[i]);
    }
      
    if(i < hxBuffSize) i--; // Get rid of eof;
      
    for(j = 0; j < i; j++) {
      long xx = (long)c[j];

      // Set fill chars and reset first 24 bits for one byte display
      stream << setfill('0') << setw(2) << hex << (xx & 0xFF);
      stream << " "; // Print one space between the bytes
    }

    // Set the line spacing to a minimum of bytes per line
    for(; j < hxBuffSize; j++) stream << "   "; // Insert 3 spaces

    if(!strip_comments) {
      // Separate hex output by one tab and denote comments
      // with a semicolon.
      stream << "\t" << "; ";
	  
      // Filter out any non-printable characters
      for(j = 0; j < i; j++)
	if(isgraph(c[j])) stream << c[j];
	else stream << ".";
    }
    
    stream << endl;
      
    if(use_console == 1 && prompting == 1) {
      count++;
      if(count == NumLines) {
	count = 0;
	stream << endl;
	stream << "Press ENTER to continue: ";
	cin.get();
	stream << endl;
      }
    }
  }

  infile.close();

  return 1;
}

int DecFileDump(fstream &infile, ostream &stream)
{
  register int i, j;
  int count = 0;
  char c[decBuffSize];
  
  // Print all text in upper case characters
  stream.setf(ios::uppercase);

  while(!infile.eof()) {

    for(i = 0; i < decBuffSize && !infile.eof(); i++) {
      infile.get(c[i]);
    }
      
    if(i < decBuffSize) i--; // Get rid of eof;
      
    for(j = 0; j < i; j++) {
      long xx = (long)c[j];

      // Set fill chars and reset first 24 bits for one byte display
	stream << setfill('0') << setw(3) << dec << (xx & 0xFF);
	stream << " "; // Print one space between the bytes
    }

    // Set the line spacing to a maximum number of bytes per line
    for(; j < decBuffSize; j++) stream << "    "; // Insert 4 spaces

    if(!strip_comments) {
      // Separate hex output by one tab and denote comments
      // with a semicolon.
      stream << "\t" << "; ";
	  
      // Filter out any non-printable characters
      for(j = 0; j < i; j++)
	if(isgraph(c[j])) stream << c[j];
	else stream << ".";
    }
    
    stream << endl;
      
    if(use_console == 1 && prompting == 1) {
      count++;
      if(count == NumLines) {
	count = 0;
	stream << endl;
	stream << "Press ENTER to continue: ";
	cin.get();
	stream << endl;
      }
    }
  }

  infile.close();

  return 1;
}

void BuildBinaryFile(char *ifname, char *ofname)
{
  cout << "Building new binary file from hex dump text file..." << endl;
  
  if(ifname[0] == 0) {
    cout << endl;
    cout << "You must specify a valid hex dump text file." << endl;
    cout << endl;
    exit(0);
  }

  // Default output file name if none specified on command line
  if(ofname[0] == 0) ofname = "outfile.bin";

  ifstream infile(ifname, ios::in|ios::nocreate);
  if(!infile) {
    cerr << "Could not open " << ifname << endl;
    exit(0);
  }

#if defined(__DOS__)
  // In MS-DOS/Windows there are two file types, text and binary
  fstream outfile(ofname, ios::out|ios::in|ios::trunc|ios::binary);
  // NOTE: There is no ios::in or ios::out default mode for fstream objects.
  // You must specify both modes for read and write files. 
#elif defined(__DJGPP2721__)
  // DJGPP 2.7.2.1 will not create a file using ios::out|ios::in modes
  fstream outfile(ofname, ios::out|ios::binary);
#else 
  // In UNIX there is only one file type
  fstream outfile(ofname, ios::out|ios::in|ios::trunc);
#endif

  if(!outfile) {
    cerr << "Could not create " << ofname << endl;
    exit(0);
  }

  char words[MAXWORDS][MAXWORDLENGTH];
  char LineBuffer[MAX_LINE];
  char InputBuffer[MAX_LINE];
  int i, num, byte_count = 0;
  long word_val;
  unsigned char byte_val;
  unsigned long CRC32 = 0xffffffffL;
  unsigned long outfile_CRC32, CRC32_check;
  hxCRC crc;
  
  // Clear the buffers
  for(i = 0; i < MAX_LINE; i++) LineBuffer[i] = '\0';
  for(i = 0; i < MAX_LINE; i++) InputBuffer[i] = '\0';
  
  while(infile) {
    infile.getline(LineBuffer, MAX_LINE);

    // Truncate the line when the first tab is found
    TruncateLine(LineBuffer, InputBuffer);

    if(text_parse(InputBuffer, words, &num) == 1) {
      cerr << "Parse error! Exiting..." << endl;
      cerr << endl;
      exit(0);
    }

    // Write the hex values in the text file to a binary file
    for(i = 0; i < num; i++) {
      if(words[i] != 0 && (strlen(words[i]) == 2)) {
	sscanf(words[i], "%x", &word_val);
	word_val &= 0xFF; // Reset the first 24 bits
	byte_val = (unsigned char) word_val;

	// Calculate a 32-bit CRC for each byte value
	CRC32=crc.crc32tab[(CRC32 ^ word_val)&0xFF] ^ ((CRC32>>8)&0x00ffffffL);

	outfile.write((unsigned char *) &byte_val, sizeof(byte_val));
	byte_count++;
      }
    }
  
    // Clear the buffers
    for(i = 0; i < MAX_LINE; i++) LineBuffer[i] = '\0';
    for(i = 0; i < MAX_LINE; i++) InputBuffer[i] = '\0';
  }

  // Finish 32-bit CRC calculation for each byte value
  CRC32 ^= 0xffffffffL;

#ifdef __DJGPP2721__
  // DJGPP 2.7.2.1 will not create a file using ios::out|ios::in modes
  // NOTE: There is no ios::in or ios::out default mode for fstream objects.
  // Both modes must be specifed for read and write files. 
  cout << "Finished." << endl;
  cout << byte_count << " bytes written to " << ofname << endl;
  cout << endl;
  infile.close();
  outfile.close();
  return; // Do not calculate CRC for output file under DJGPP
#endif
  
  // Calculate a 32-bit CRC for the output file 
  outfile_CRC32 = crc.calcCRC32(outfile);
  CRC32_check = CRC32 ^ outfile_CRC32; // XOR the CRC values
  if(CRC32_check != 0) { 
    cout << "Checksum error in " << ofname << endl;   
    cout << "File fails CRC-32 test." << endl;
    cout << "CRC of bytes written = ";
    cout.setf(ios::uppercase);
    cout << "0x" << setfill('0') << setw(8) << hex << CRC32 << endl;
    cout.unsetf(ios::uppercase);
    cout << "File CRC = ";
    cout.setf(ios::uppercase);
    cout << "0x" << setfill('0') << setw(8) << hex << outfile_CRC32 << endl;
    cout.unsetf(ios::uppercase);
    cout << endl;
  }
  else {
    cout << "Finished." << endl;
    cout << byte_count << " bytes written to " << ofname << endl;
    cout << endl;
  }
  infile.close();
  outfile.close();
}

void TruncateLine(char *line, char tline[MAX_LINE],
		  char delimiter1, char delimiter2)
// Used to truncate a line of text starting at the first occurrence
// of the delimiter characters.
{
  int len = strlen(line);
  int i, offset = 0;

  for(i = 0; i < len; i++) {
    if((line[i] == delimiter1) || (line[i] == delimiter2)) {
      offset = i;
      break;
    }
  }
  
  if(!offset) { // Delimiter character was not found
    tline[len] = 0; // Ensure null termination
    strcpy(tline, line);
    return; 
  }
  
  len -= offset;
  tline[offset] = 0; // Ensure null termination
  memmove(tline, line, offset);
}

int text_parse(char *string, char words[MAXWORDS][MAXWORDLENGTH],
	       int*numwords, char sepchar1, char sepchar2)
// General purpose text parser. Returns 0 if successful or 1 if a
// parse error occurred.
{
  int i = 0;
  char newword[MAXWORDLENGTH];
  *numwords = 0;

  // First skip over leading blanks. Stop if an ASCII NULL is seen
  while (1) {
    if (*string == '\0') return 0;
    if (*string != ' ') break;
    string++;
  }
  
  while(1) {
    // Check to see if there is room for another word in the array
    if(*numwords == MAXWORDS) return 1;

    i = 0;
    while (i < MAXWORDLENGTH) {
      if(*string == 0 || *string == sepchar1 || *string == sepchar2) break;
      newword[i] = *string;
      string++;
      i++;
    }

    newword[i] = 0; // Ensure an ASCII null at end of newword. 
    strcpy (words[*numwords], newword);  // Install into array 
    (*numwords)++;
    
    // If stopped by an ASCII NULL above, exit loop
    if(*string == 0) break;
    
    string++;
    if(*string == 0) break;
  }
  return 0;
}

int main(int argc, char *argv[])
{
  // If no argument is given print usage message to the screen 
  if(argc < 2) {
    HelpMessage(ProgramName, HexdumpVersionNumber);
    return 0;
  }
  
  // Process the programs command line arguments
  ProcessArgs(argc, argv);

  // If building a binary file from a hex dump text file,
  // execute the build function and terminate the program.
  if(building_bin_file == 1)  {
    BuildBinaryFile(in_file, out_file);
    return 0;
  }
  
  if(in_file[0] == 0 ) {
    cout << endl;
    cout << "You must specify a valid input file name." << endl;
    cout << endl;
    return 0;
  }

#if defined(__DOS__)
  // In MS-DOS/Windows there are two file types, text and binary
  fstream infile(in_file, ios::in | ios::binary | ios::nocreate);
#elif defined(__DJGPP2721__)
  fstream infile(in_file, ios::in | ios::binary | ios::nocreate);
#else 
  // In UNIX there is only one file type
  fstream infile(in_file, ios::in | ios::nocreate);
#endif

  if(!infile) {
    cerr << endl;
    cerr << "Cannot open file: " << in_file << endl;
    cerr << "Exiting..." << endl;
    cerr << endl;
    return 1;
  }

  if(display_crc32) {
    hxCRC crc;
    cout << in_file << endl;
    cout << "CRC-32 = ";
    cout.setf(ios::uppercase);
    unsigned long checksum = crc.calcCRC32(infile);
    cout << "0x" << setfill('0') << setw(8) << hex << checksum << endl;
    cout.unsetf(ios::uppercase);
    return 0;
  }

  if(display_crc16) {
    hxCRC crc;
    cout << in_file << endl;
    cout << "CRC-16 = ";
    cout.setf(ios::uppercase);
    unsigned short checksum = crc.calcCRC16(infile);
    cout << "0x" << setfill('0') << setw(4) << hex << checksum << endl;
    cout.unsetf(ios::uppercase);
    cout << "CCITT CRC-16 = ";
    cout.setf(ios::uppercase);
    checksum = crc.calcCRC16(infile, 0);
    cout << "0x" << setfill('0') << setw(4) << hex << checksum << endl;
    cout.unsetf(ios::uppercase);
    return 0;
  }
  
  if(out_file[0] == 0 ) { // Write output to stdout
    use_console = 1;
    if(decimal_mode == 1)
      DecFileDump(infile, cout);
    else
      HexFileDump(infile, cout);
  }
  else { // Write the output to a file
    ofstream stream(out_file, ios::out); // Open file and truncate it
    if(!stream) {
      cerr << endl;
      cerr << "Cannot create " << out_file << endl;
      cerr << endl;
      return 1;
    }
    use_console = 0;
    cerr << "Processing file..." << endl;
    if(decimal_mode == 1) {
      DecFileDump(infile, stream);
      stream.close();
    }
    else {
      HexFileDump(infile, stream);
      stream.close();
    }
    cerr << "Finished." << endl;
  }

  return 0;
}
// ----------------------------------------------------------- // 
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //
