Logo Search packages:      
Sourcecode: vbindiff version File versions  Download package

vbindiff.cpp

//--------------------------------------------------------------------
// $Id: vbindiff.cpp 4649 2005-10-13 21:57:26Z cjm $
//--------------------------------------------------------------------
//
//   Visual Binary Diff
//   Copyright 1995-2005 by Christopher J. Madsen
//
//   Visual display of differences in binary files
//
//   This program is free software; you can redistribute it and/or
//   modify it under the terms of the GNU General Public License as
//   published by the Free Software Foundation; either version 2 of
//   the License, or (at your option) any later version.
//
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program; if not, write to the Free Software
//   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//--------------------------------------------------------------------

#include "config.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include <algorithm>
#include <iostream>
#include <sstream>
using namespace std;

#include "GetOpt/GetOpt.hpp"

#include "ConWin.hpp"
#include "FileIO.hpp"

const char titleString[] =
  "\nVBinDiff " PACKAGE_VERSION "\nCopyright 1995-2005 Christopher J. Madsen";

void exitMsg(int status, const char* message);
void usage(bool showHelp=true, int exitStatus=0);

//====================================================================
// Type definitions:

typedef unsigned char   Byte;
typedef unsigned short  Word;

typedef Byte  Command;

enum LockState { lockNeither = 0, lockTop, lockBottom };

//====================================================================
// Constants:

const Command  cmmMove        = 0x80;

const Command  cmmMoveSize    = 0x03;
const Command  cmmMoveForward = 0x04;
const Command  cmmMoveTop     = 0x08;
const Command  cmmMoveBottom  = 0x10;

const Command  cmmMoveByte    = 0x00; // Move 1 byte
const Command  cmmMoveLine    = 0x01; // Move 1 line
const Command  cmmMovePage    = 0x02; // Move 1 page
const Command  cmmMoveAll     = 0x03; // Move to beginning or end

const Command  cmmMoveBoth    = cmmMoveTop|cmmMoveBottom;

const Command  cmgGoto        = 0x04; // Commands 4-7
const Command  cmgGotoTop     = 0x01;
const Command  cmgGotoBottom  = 0x02;
const Command  cmgGotoBoth    = cmgGotoTop|cmgGotoBottom;
const Command  cmgGotoMask    = ~cmgGotoBoth;

const Command  cmNothing      = 0;
const Command  cmNextDiff     = 1;
const Command  cmQuit         = 2;
const Command  cmEditTop      = 8;
const Command  cmEditBottom   = 9;
const Command  cmUseTop       = 10;
const Command  cmUseBottom    = 11;
const Command  cmToggleASCII  = 12;
const Command  cmFind         = 16; // Commands 16-19

const short  leftMar  = 11;     // Starting column of hex display
const short  leftMar2 = 61;     // Starting column of ASCII display

const int  lineWidth = 16;      // Number of bytes displayed per line

const int  promptHeight = 4;    // Height of prompt window
const int  inWidth = 10;        // Width of input window (excluding border)
const int  screenWidth = 80;

const int  maxPath = 260;

#include "tables.h"             // ASCII and EBCDIC tables

//====================================================================
// Class Declarations:

void showEditPrompt();
void showPrompt();

class Difference;

union FileBuffer
{
  Byte  line[1][lineWidth];
  Byte  buffer[lineWidth];
}; // end FileBuffer

class FileDisplay
{
  friend class Difference;

 protected:
  int                bufContents;
  FileBuffer*        data;
  const Difference*  diffs;
  File               file;
  char               fileName[maxPath];
  FPos               offset;
  ConWindow          win;
  bool               writable;
  int                yPos;
 public:
  FileDisplay();
  ~FileDisplay();
  void         init(int y, const Difference* aDiff=NULL,
                    const char* aFileName=NULL);
  void         resize();
  void         shutDown();
  void         display();
  bool         edit(const FileDisplay* other);
  const Byte*  getBuffer() const { return data->buffer; };
  void         move(int step)    { moveTo(offset + step); };
  void         moveTo(FPos newOffset);
  bool         moveTo(Byte* searchFor, int searchLen);
  void         moveToEnd(FileDisplay* other);
  bool         setFile(const char* aFileName);
 protected:
  void  setByte(short x, short y, Byte b);
}; // end FileDisplay

class Difference
{
  friend void FileDisplay::display();

 protected:
  FileBuffer*         data;
  const FileDisplay*  file1;
  const FileDisplay*  file2;
  int                 numDiffs;
 public:
  Difference(const FileDisplay* aFile1, const FileDisplay* aFile2);
  ~Difference();
  int  compute();
  int  getNumDiffs() const { return numDiffs; };
  void resize();
}; // end Difference

//====================================================================
// Global Variables:

ConWindow    promptWin,inWin;
FileDisplay  file1, file2;
Difference   diffs(&file1, &file2);
const char*  displayTable = asciiDisplayTable;
const char*  program_name; // Name under which this program was invoked
LockState    lockState = lockNeither;
bool         singleFile = false;

int  numLines  = 9;       // Number of lines of each file to display
int  bufSize   = numLines * lineWidth;
int  linesBetween = 1;    // Number of lines of padding between files

// The number of bytes to move for each possible step size:
//   See cmmMoveByte, cmmMoveLine, cmmMovePage
int  steps[4] = {1, lineWidth, bufSize-lineWidth, 0};


//====================================================================
// Class Difference:
//
// Member Variables:
//   file1, file2:
//     The FileDisplay objects being compared
//   numDiffs:
//     The number of differences between the two FileDisplay buffers
//   line/table:
//     An array of bools for each byte in the FileDisplay buffers
//     True marks differences
//
//--------------------------------------------------------------------
// Constructor:
//
// Input:
//   aFile1, aFile2:
//     Pointers to the FileDisplay objects to compare

Difference::Difference(const FileDisplay* aFile1, const FileDisplay* aFile2)
: data(NULL),
  file1(aFile1),
  file2(aFile2)
{
} // end Difference::Difference

//--------------------------------------------------------------------
Difference::~Difference()
{
  delete [] reinterpret_cast<Byte*>(data);
} // end Difference::~Difference

//--------------------------------------------------------------------
// Compute differences:
//
// Input Variables:
//   file1, file2:  The files to compare
//
// Returns:
//   The number of differences between the buffers
//   -1 if both buffers are empty
//
// Output Variables:
//   numDiffs:  The number of differences between the buffers

int Difference::compute()
{
  if (singleFile)
    // We return 1 so that cmNextDiff won't keep searching:
    return (file1->bufContents ? 1 : -1);

  memset(data->buffer, 0, bufSize); // Clear the difference table

  int  different = 0;

  const Byte*  buf1 = file1->data->buffer;
  const Byte*  buf2 = file2->data->buffer;

  int  size = min(file1->bufContents, file2->bufContents);

  int  i;
  for (i = 0; i < size; i++)
    if (*(buf1++) != *(buf2++)) {
      data->buffer[i] = true;
      ++different;
    }

  size = max(file1->bufContents, file2->bufContents);

  if (i < size) {
    // One buffer has more data than the other:
    different += size - i;
    for (; i < size; i++)
      data->buffer[i] = true;   // These bytes are only in 1 buffer
  } else if (!size)
    return -1;                  // Both buffers are empty

  numDiffs = different;

  return different;
} // end Difference::compute

//--------------------------------------------------------------------
void Difference::resize()
{
  if (singleFile) return;

  if (data)
    delete [] reinterpret_cast<Byte*>(data);

  data = reinterpret_cast<FileBuffer*>(new Byte[bufSize]);
} // end Difference::resize

//====================================================================
// Class FileDisplay:
//
// Member Variables:
//   bufContents:
//     The number of bytes in the file buffer
//   diffs:
//     A pointer to the Difference object related to this file
//   file:
//     The file being displayed
//   fileName:
//     The relative pathname of the file being displayed
//   offset:
//     The position in the file of the first byte in the buffer
//   win:
//     The handle of the window used for display
//   yPos:
//     The vertical position of the display window
//   buffer/line:
//     The currently displayed portion of the file
//
//--------------------------------------------------------------------
// Constructor:

FileDisplay::FileDisplay()
: bufContents(0),
  data(NULL),
  diffs(NULL),
  offset(0),
  writable(false),
  yPos(0)
{
  fileName[0] = '\0';
} // end FileDisplay::FileDisplay

//--------------------------------------------------------------------
// Initialize:
//
// Creates the display window and opens the file.
//
// Input:
//   y:          The vertical position of the display window
//   aDiff:      The Difference object related to this buffer
//   aFileName:  The name of the file to display

void FileDisplay::init(int y, const Difference* aDiff,
                       const char* aFileName)
{
  diffs = aDiff;
  yPos  = y;

  win.init(0,y, screenWidth, (numLines + 1 + ((y==0) ? linesBetween : 0)),
           cFileWin);

  resize();

  if (aFileName)
    setFile(aFileName);
} // end FileDisplay::init

//--------------------------------------------------------------------
// Destructor:

FileDisplay::~FileDisplay()
{
  shutDown();
  CloseFile(file);
  delete [] reinterpret_cast<Byte*>(data);
} // end FileDisplay::~FileDisplay

//--------------------------------------------------------------------
void FileDisplay::resize()
{
  if (data)
    delete [] reinterpret_cast<Byte*>(data);

  data = reinterpret_cast<FileBuffer*>(new Byte[bufSize]);

  // FIXME resize window
} // end FileDisplay::resize

//--------------------------------------------------------------------
// Shut down the file display:
//
// Deletes the display window.

void FileDisplay::shutDown()
{
  win.close();
} // end FileDisplay::shutDown

//--------------------------------------------------------------------
// Display the file contents:

void FileDisplay::display()
{
  if (!fileName[0]) return;

  FPos  lineOffset = offset;

  short i,j,index,lineLength;
  char  buf[lineWidth + lineWidth/8 + 1];
  buf[sizeof(buf)-1] = '\0';

  char  buf2[screenWidth+1];
  buf2[screenWidth] = '\0';

  memset(buf, ' ', sizeof(buf)-1);

  for (i = 0; i < numLines; i++) {
//    cerr << i << '\n';
    char*  str = buf2;
    str +=
      sprintf(str, "%04X %04X:",Word(lineOffset>>16),Word(lineOffset&0xFFFF));

    lineLength  = min(lineWidth, bufContents - i*lineWidth);

    for (j = 0, index = -1; j < lineLength; j++) {
      if (j % 8 == 0) {
        *(str++) = ' ';
        ++index;
      }
      str += sprintf(str, "%02X ", data->line[i][j]);

      buf[index++] = displayTable[data->line[i][j]];
    }
    memset(buf + index, ' ', sizeof(buf) - index - 1);
    memset(str, ' ', screenWidth - (str - buf2));

    win.put(0,i+1, buf2);
    win.put(leftMar2,i+1, buf);

    if (diffs)
      for (j = 0; j < lineWidth; j++)
        if (diffs->data->line[i][j]) {
          win.putAttribs(j*3 + leftMar  + (j>7),i+1, cFileDiff,2);
          win.putAttribs(j   + leftMar2 + (j>7),i+1, cFileDiff,1);
        }
    lineOffset += lineWidth;
  } // end for i up to numLines

  win.update();
} // end FileDisplay::display

//--------------------------------------------------------------------
// Edit the file:
//
// Returns:
//   true:  File changed
//   false:  File did not change

bool FileDisplay::edit(const FileDisplay* other)
{
  if (!bufContents && offset)
    return false;               // You must not be completely past EOF

  if (!writable) {
    File w = OpenFile(fileName, true);
    if (w == InvalidFile) return false;
    CloseFile(file);
    file = w;
    writable = true;
  }

  if (bufContents < bufSize)
    memset(data->buffer + bufContents, 0, bufSize - bufContents);

  short x = 0;
  short y = 0;
  bool  hiNib = true;
  bool  ascii = false;
  bool  changed = false;
  int   key;

  const Byte *const inputTable = ((displayTable == ebcdicDisplayTable)
                                  ? ascii2ebcdicTable
                                  : NULL); // No translation

  showEditPrompt();
  win.setCursor(leftMar,1);
  ConWindow::showCursor();

  for (;;) {
    win.setCursor((ascii ? leftMar2 + x : leftMar + 3*x + !hiNib) + (x / 8),
                  y+1);
    key = win.readKey();

    switch (key) {
     case KEY_ESCAPE: goto done;
     case KEY_TAB:
      hiNib = true;
      ascii = !ascii;
      break;

     case KEY_DELETE:
     case KEY_BACKSPACE:
     case KEY_LEFT:
      if (!hiNib)
        hiNib = true;
      else {
        if (!ascii) hiNib = false;
        if (--x < 0) x = lineWidth-1;
      }
      if (hiNib || (x < lineWidth-1))
        break;
      // else fall thru
     case KEY_UP:   if (--y < 0) y = numLines-1; break;

     default: {
       short newByte = -1;
       if ((key == KEY_RETURN) && other &&
           (other->bufContents > x + y*lineWidth)) {
         newByte = other->data->line[y][x]; // Copy from other file
         hiNib = ascii; // Always advance cursor to next byte
       } else if (ascii) {
         if (isprint(key)) newByte = (inputTable ? inputTable[key] : key);
       } else { // hex
         if (isdigit(key))
           newByte = key - '0';
         else if (isxdigit(key))
           newByte = toupper(key) - 'A' + 10;
         if (newByte >= 0)
           if (hiNib)
             newByte = (newByte * 0x10) | (0x0F & data->line[y][x]);
           else
             newByte |= 0xF0 & data->line[y][x];
       } // end else hex
       if (newByte >= 0) {
         changed = true;
         setByte(x,y,newByte);
       } else
         break;
     } // end default and fall thru
     case KEY_RIGHT:
      if (hiNib && !ascii)
        hiNib = false;
      else {
        hiNib = true;
        if (++x >= lineWidth) x = 0;
      }
      if (x || !hiNib)
        break;
      // else fall thru
     case KEY_DOWN: if (++y >= numLines) y = 0;  break;

    } // end switch

  } // end forever

 done:
  if (changed) {
    promptWin.clear();
    promptWin.border();
    promptWin.put(30,1,"Save changes (Y/N):");
    promptWin.update();
    promptWin.setCursor(50,1);
    key = promptWin.readKey();
    if (toupper(key) != 'Y') {
      changed = false;
      moveTo(offset);           // Re-read buffer contents
    } else {
      SeekFile(file, offset);
      WriteFile(file, data->buffer, bufContents);
    }
  }
  showPrompt();
  ConWindow::hideCursor();
  return changed;
} // end FileDisplay::edit

//--------------------------------------------------------------------
void FileDisplay::setByte(short x, short y, Byte b)
{
  if (x + y*lineWidth >= bufContents) {
    if (x + y*lineWidth > bufContents) {
      short y1 = bufContents / lineWidth;
      short x1 = bufContents % lineWidth;
      while (y1 <= numLines) {
        while (x1 < lineWidth) {
          if ((x1 == x) && (y1 == y)) goto done;
          setByte(x1,y1,0);
          ++x1;
        }
        x1 = 0;
        ++y1;
      } // end while y1
    } // end if more than 1 byte past the end
   done:
    ++bufContents;
    data->line[y][x] = b ^ 1;         // Make sure it's different
  } // end if past the end

  if (data->line[y][x] != b) {
    data->line[y][x] = b;
    char str[3];
    sprintf(str, "%02X", b);
    win.setAttribs(cFileEdit);
    win.put(leftMar + 3*x + (x / 8), y+1, str);
    str[0] = displayTable[b];
    str[1] = '\0';
    win.put(leftMar2 + x + (x / 8), y+1, str);
    win.setAttribs(cFileWin);
    win.update();
  }
} // end FileDisplay::setByte

//--------------------------------------------------------------------
// Change the file position:
//
// Changes the file offset and updates the buffer.
// Does not update the display.
//
// Input:
//   step:
//     The number of bytes to move
//     A negative value means to move backward
//
// void FileDisplay::move(int step) /* Inline function */

//--------------------------------------------------------------------
// Change the file position:
//
// Changes the file offset and updates the buffer.
// Does not update the display.
//
// Input:
//   newOffset:
//     The new position of the file

void FileDisplay::moveTo(FPos newOffset)
{
  if (!fileName[0]) return;     // No file

  offset = newOffset;

  if (offset < 0)
    offset = 0;

  SeekFile(file, offset);
  bufContents = ReadFile(file, data->buffer, bufSize);
} // end FileDisplay::moveTo

//--------------------------------------------------------------------
// Change the file position by searching:
//
// Changes the file offset and updates the buffer.
// Does not update the display.
//
// Input:
//   searchFor:  The bytes to search for
//   searchLen:  The number of bytes in searchFor
//
// Returns:
//   true:   The search was successful
//   false:  Search unsuccessful, file not moved

bool FileDisplay::moveTo(Byte* searchFor, int searchLen)
{
  if (!fileName[0]) return false; // No file

  // Using algorithm based on QuickSearch:
  //   http://www-igm.univ-mlv.fr/~lecroq/string/node19.htm

  // Compute offset table:
  int i;
  int moveOver[256];

  for (i = 0; i < 256; ++i)
    moveOver[i] = searchLen + 1;
  for (i = 0; i < searchLen; ++i)
    moveOver[searchFor[i]] = searchLen - i;

  // Prepare the search buffer:

  const int
    blockSize  = 8 * 1024,
    moveLength = searchLen,
    restartAt  = blockSize - moveLength,
    fullStop   = blockSize * 2 - moveLength;

  Byte *const  searchBuf = new Byte[2 * blockSize];

  Byte *const  copyTo         = searchBuf + restartAt;
  const Byte *const copyFrom  = searchBuf + fullStop;

  char *const  readAt = reinterpret_cast<char*>(searchBuf) + blockSize;

  FPos  newPos = offset + 1;

  SeekFile(file, newPos);
  Size bytesRead = ReadFile(file, searchBuf, blockSize * 2);
  int stopAt = bytesRead - moveLength;

  // Start the search:
  i = 0;
  for (;;) {
    if (stopAt < fullStop) ++stopAt;

    while (i < stopAt) {
      if (memcmp(searchFor, searchBuf + i, searchLen) == 0)
        goto done;

      i += moveOver[searchBuf[i + searchLen]]; // shift
    } // end while more buffer to search

    if (stopAt != fullStop) {
      i = -1;
      goto done;
    } // Nothing more to read

    newPos += blockSize;
    i -= blockSize;
    memcpy(copyTo, copyFrom, moveLength);
    bytesRead = ReadFile(file, readAt, blockSize);
    stopAt = bytesRead + blockSize - moveLength;
  } // end forever

 done:
  delete [] searchBuf;

  if (i < 0) return false;      // No match

  moveTo(newPos + i);

  return true;
} // end FileDisplay::moveTo

//--------------------------------------------------------------------
// Move to the end of the file:
//
// Input:
//   other:  If non NULL, move both files to the end of the shorter file

void FileDisplay::moveToEnd(FileDisplay* other)
{
  if (!fileName[0]) return;     // No file

  FPos  end = SeekFile(file, 0, SeekEnd);
  FPos  diff = 0;

  if (other) {
    // If the files aren't currently at the same position,
    // we want to keep them offset by the same amount:
    diff = other->offset - offset;

    end = min(end, SeekFile(other->file, 0, SeekEnd) - diff);
  } // end if moving other file too

  end -= steps[cmmMovePage];
  end -= end % 0x10;

  moveTo(end);
  if (other) other->moveTo(end + diff);
} // end FileDisplay::moveToEnd

//--------------------------------------------------------------------
// Open a file for display:
//
// Opens the file, updates the filename display, and reads the start
// of the file into the buffer.
//
// Input:
//   aFileName:  The name of the file to open
//
// Returns:
//   True:   Operation successful
//   False:  Unable to open file (call ErrorMsg for error message)

bool FileDisplay::setFile(const char* aFileName)
{
  strncpy(fileName, aFileName, maxPath);
  fileName[maxPath-1] = '\0';

  win.put(0,0, fileName);
  win.putAttribs(0,0, cFileName, screenWidth);
  win.update();                 // FIXME

  bufContents = 0;
  file = OpenFile(fileName);
  writable = false;

  if (file == InvalidFile)
    return false;

  offset = 0;
  bufContents = ReadFile(file, data->buffer, bufSize);

  return true;
} // end FileDisplay::setFile

//====================================================================
// Main Program:
//--------------------------------------------------------------------
void calcScreenLayout(bool resize = true)
{
  int  screenX, screenY;

  ConWindow::getScreenSize(screenX, screenY);

  if (screenX < screenWidth) {
    ostringstream  err;
    err << "The screen must be at least "
        << screenWidth << " characters wide.";
    exitMsg(2, err.str().c_str());
  }

  if (screenY < promptHeight + 4) {
    ostringstream  err;
    err << "The screen must be at least "
        << (promptHeight + 4) << " lines high.";
    exitMsg(2, err.str().c_str());
  }

  numLines = screenY - promptHeight - (singleFile ? 1 : 2);

  if (singleFile)
    linesBetween = 0;
  else {
    linesBetween = numLines % 2;
    numLines = (numLines - linesBetween) / 2;
  }

  bufSize = numLines * lineWidth;

  steps[cmmMovePage] = bufSize-lineWidth;

  // FIXME resize existing windows
} // end calcScreenLayout

//--------------------------------------------------------------------
void displayCharacterSet()
{
  const bool isASCII = (displayTable == asciiDisplayTable);

  promptWin.putAttribs(3,2, (isASCII ? cCurrentMode : cBackground), 5);
  promptWin.putAttribs(9,2, (isASCII ? cBackground : cCurrentMode), 6);

  promptWin.update();
} // end displayCharacterSet

//--------------------------------------------------------------------
void displayLockState()
{
#ifndef WIN32_CONSOLE     // The Win32 version uses Ctrl & Alt instead
  if (singleFile) return;

  promptWin.putAttribs(63,1,
                       ((lockState == lockBottom) ? cCurrentMode : cBackground),
                       8);
  promptWin.putAttribs(63,2,
                       ((lockState == lockTop)    ? cCurrentMode : cBackground),
                       11);
#endif
} // end displayLockState

//--------------------------------------------------------------------
// Print a message to stderr and exit:
//
// Input:
//   status:   The exit status to use
//   message:  The message to print

void exitMsg(int status, const char* message)
{
  ConWindow::shutdown();

  cerr << endl << message << endl;
  exit(status);
} // end exitMsg

//--------------------------------------------------------------------
// Get a string using inWin:
//
// Input:
//   buf:       The buffer where the string will be stored
//   maxLen:    The maximum number of chars to accept (not including NUL byte)
//   restrict:  If not NULL, accept only chars in this string
//   upcase:    If true, convert all chars with toupper

void getString(char* buf, int maxLen, const char* restrict=NULL,
               bool upcase=false, bool splitHex=false)
{
  inWin.setCursor(2,1);
  ConWindow::showCursor();

  bool  done = false;
  int   i = 0;

  memset(buf, ' ', maxLen);
  buf[maxLen] = '\0';

  while (!done) {
    inWin.put(2,1,buf);
    inWin.update();
    inWin.setCursor(2+i,1);
    int key = inWin.readKey();
    if (upcase) key = toupper(key);

    switch (key) {
     case KEY_RETURN:  buf[i] = '\0';  done = true;  break; // Enter
     case KEY_ESCAPE:  buf[0] = '\0';  done = true;  break; // ESC

     case KEY_BACKSPACE:
     case KEY_DC:
     case KEY_LEFT:
     case KEY_DELETE:
     case 0x08:                 // Backspace
      if (!i) continue;
      if (splitHex && buf[i-1] == ' ') --i;
      buf[--i] = ' ';
      break;

     default:
      if (isprint(key) && (!restrict || strchr(restrict, key))) {
        if (i >= maxLen) continue;
        buf[i++] = key;
        if (splitHex && (i < maxLen) && (i % 3 == 2))
          ++i;
      }
    } // end switch key
  } // end while

  ConWindow::hideCursor();
  inWin.hide();
} // end getString

//--------------------------------------------------------------------
// Convert hex string to bytes:
//
// Input:
//   buf:  Must contain a well-formed string of hex characters
//         (each byte must be separated by spaces)
//
// Output:
//   buf:  Contains the translated bytes
//
// Returns:
//   The number of bytes in buf

int packHex(Byte* buf)
{
  unsigned long val;

  char* in  = reinterpret_cast<char*>(buf);
  Byte* out = buf;

  while (*in) {
    if (*in == ' ')
      ++in;
    else {
      val = strtoul(in, &in, 16);
      *(out++) = Byte(val);
    }
  }

  return out - buf;
} // end packHex

//--------------------------------------------------------------------
// Position the input window:
//
// Input:
//   cmd:    Indicates where the window should be positioned
//   width:  The width of the window
//   title:  The title for the window

void positionInWin(Command cmd, short width, const char* title)
{
  inWin.resize(width, 3);
  inWin.move((screenWidth-width)/2,
             ((!singleFile && (cmd & cmgGotoBottom))
              ? ((cmd & cmgGotoTop)
                 ? numLines + linesBetween                   // Moving both
                 : numLines + numLines/2 + 1 + linesBetween) // Moving bottom
              : numLines/2));                                // Moving top

  inWin.border();
  inWin.put((width-strlen(title))/2,0, title);
} // end positionInWin

//--------------------------------------------------------------------
// Display prompt window for editing:

void showEditPrompt()
{
  promptWin.clear();
  promptWin.border();
  promptWin.put(3,1, "Arrow keys move cursor        TAB hex\x3C\x3E"
                "ASCII       ESC done");
  if (displayTable == ebcdicDisplayTable)
    promptWin.put(42,1, "EBCDIC");

  promptWin.putAttribs( 3,1, cPromptKey, 10);
  promptWin.putAttribs(33,1, cPromptKey, 3);
  promptWin.putAttribs(54,1, cPromptKey, 3);

  if (!singleFile) {
    promptWin.put(25,2, "RET copy byte from other file");
    promptWin.putAttribs(25,2, cPromptKey, 3);
  }
  promptWin.update();
} // end showEditPrompt

//--------------------------------------------------------------------
// Display prompt window:

void showPrompt()
{
  promptWin.clear();
  promptWin.border();

#ifdef WIN32_CONSOLE
  promptWin.put(1,1, "Arrow keys move  F find      "
                "RET next difference  ESC quit  ALT  freeze top");
  promptWin.put(1,2, "C ASCII/EBCDIC   E edit file   "
                "G goto position      Q quit  CTRL freeze bottom");
  const short
    topBotLength = 4,
    topLength    = 15;
#else // curses
  promptWin.put(1,1, "Arrow keys move  F find      "
                "RET next difference  ESC quit  T move top");
  promptWin.put(1,2, "C ASCII/EBCDIC   E edit file   "
                "G goto position      Q quit  B move bottom");
  const short
    topBotLength = 1,
    topLength    = 10;
#endif

  promptWin.putAttribs( 1,1, cPromptKey, 10);
  promptWin.putAttribs(18,1, cPromptKey, 1);
  promptWin.putAttribs(30,1, cPromptKey, 3);
  promptWin.putAttribs(51,1, cPromptKey, 3);
  promptWin.putAttribs( 1,2, cPromptKey, 1);
  promptWin.putAttribs(18,2, cPromptKey, 1);
  promptWin.putAttribs(32,2, cPromptKey, 1);
  promptWin.putAttribs(53,2, cPromptKey, 1);
  if (singleFile) {
    // Erase "move top" & "move bottom":
    promptWin.putChar(61,1, ' ', topLength);
    promptWin.putChar(61,2, ' ', topLength + 3);
  } else {
    promptWin.putAttribs(61,1, cPromptKey, topBotLength);
    promptWin.putAttribs(61,2, cPromptKey, topBotLength);
  }
  displayLockState();
  displayCharacterSet();        // Calls promptWin.update()
} // end showPrompt

//--------------------------------------------------------------------
// Initialize program:
//
// Returns:
//   True:   Initialization complete
//   False:  Error

bool initialize()
{
  if (!ConWindow::startup())
    return false;

  ConWindow::hideCursor();

  calcScreenLayout(false);

  inWin.init(0,0, inWidth+2,3, cPromptBdr);
  inWin.border();
  inWin.put((inWidth-4)/2,0, " Goto ");
  inWin.setAttribs(cPromptWin);
  inWin.hide();

  int y;
  if (singleFile) y = numLines + 1;
  else            y = numLines * 2 + linesBetween + 2;

  promptWin.init(0,y, screenWidth,promptHeight, cBackground);
  showPrompt();

  if (!singleFile) diffs.resize();

  file1.init(0, (singleFile ? NULL : &diffs));

  if (!singleFile) file2.init(numLines + linesBetween + 1, &diffs);

  return true;
} // end initialize

//--------------------------------------------------------------------
// Get a command from the keyboard:
//
// Returns:
//   Command code

#ifdef WIN32_CONSOLE
Command getCommand()
{
  KEY_EVENT_RECORD e;
  Command  cmd = cmNothing;

  while (cmd == cmNothing) {
    ConWindow::readKey(e);

    switch (toupper(e.uChar.AsciiChar)) {
     case KEY_RETURN:           // Enter
      cmd = cmNextDiff;
      break;

     case 0x05:                 // Ctrl+E
     case 'E':
      if (e.dwControlKeyState & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED))
        cmd = cmEditBottom;
      else
        cmd = cmEditTop;
      break;

     case 'F':
      if (e.dwControlKeyState & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED))
        cmd = cmFind|cmgGotoBottom;
      else
        cmd = cmFind|cmgGotoBoth;
      break;

     case 0x06:               // Ctrl+F
      cmd = cmFind|cmgGotoTop;
      break;

     case 'G':
      if (e.dwControlKeyState & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED))
        cmd = cmgGoto|cmgGotoBottom;
      else
        cmd = cmgGoto|cmgGotoBoth;
      break;

     case 0x07:               // Ctrl+G
      cmd = cmgGoto|cmgGotoTop;
      break;

     case KEY_ESCAPE:         // Esc
     case 0x03:               // Ctrl+C
     case 'Q':
      cmd = cmQuit;
      break;

     case 'C':  cmd = cmToggleASCII;  break;

     default:                 // Try extended codes
      switch (e.wVirtualKeyCode) {
       case VK_DOWN:   cmd = cmmMove|cmmMoveLine|cmmMoveForward;  break;
       case VK_RIGHT:  cmd = cmmMove|cmmMoveByte|cmmMoveForward;  break;
       case VK_NEXT:   cmd = cmmMove|cmmMovePage|cmmMoveForward;  break;
       case VK_END:    cmd = cmmMove|cmmMoveAll|cmmMoveForward;   break;
       case VK_LEFT:   cmd = cmmMove|cmmMoveByte;                 break;
       case VK_UP:     cmd = cmmMove|cmmMoveLine;                 break;
       case VK_PRIOR:  cmd = cmmMove|cmmMovePage;                 break;
       case VK_HOME:   cmd = cmmMove|cmmMoveAll;                  break;
      } // end switch virtual key code
      break;
    } // end switch ASCII code
  } // end while no command

  if (cmd & cmmMove) {
    if ((e.dwControlKeyState & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED)) == 0)
      cmd |= cmmMoveTop;
    if ((e.dwControlKeyState & (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED)) == 0)
      cmd |= cmmMoveBottom;
  } // end if move command

  return cmd;
} // end getCommand

#else // using curses interface
Command getCommand()
{
  Command  cmd = cmNothing;

  while (cmd == cmNothing) {
    int e = promptWin.readKey();

    switch (toupper(e)) {
     case KEY_RETURN:               // Enter
      cmd = cmNextDiff;
      break;

     case 'E':
      if (lockState == lockTop)
        cmd = cmEditBottom;
      else
        cmd = cmEditTop;
      break;

     case 'F':
      cmd = cmFind;
      if (lockState != lockTop)    cmd |= cmgGotoTop;
      if (lockState != lockBottom) cmd |= cmgGotoBottom;
      break;

     case 'G':
      cmd = cmgGoto;
      if (lockState != lockTop)    cmd |= cmgGotoTop;
      if (lockState != lockBottom) cmd |= cmgGotoBottom;
      break;

     case KEY_ESCAPE:
     case 0x03:               // Ctrl+C
     case 'Q':
      cmd = cmQuit;
      break;

     case 'C':  cmd = cmToggleASCII;  break;

     case 'B':  if (!singleFile) cmd = cmUseBottom;              break;
     case 'T':  if (!singleFile) cmd = cmUseTop;                 break;

     case KEY_DOWN:   cmd = cmmMove|cmmMoveLine|cmmMoveForward;  break;
     case KEY_RIGHT:  cmd = cmmMove|cmmMoveByte|cmmMoveForward;  break;
     case KEY_NPAGE:  cmd = cmmMove|cmmMovePage|cmmMoveForward;  break;
     case KEY_END:    cmd = cmmMove|cmmMoveAll|cmmMoveForward;   break;
     case KEY_LEFT:   cmd = cmmMove|cmmMoveByte;                 break;
     case KEY_UP:     cmd = cmmMove|cmmMoveLine;                 break;
     case KEY_PPAGE:  cmd = cmmMove|cmmMovePage;                 break;
     case KEY_HOME:   cmd = cmmMove|cmmMoveAll;                  break;
    } // end switch ASCII code
  } // end while no command

  if (cmd & cmmMove) {
    if (lockState != lockTop)    cmd |= cmmMoveTop;
    if (lockState != lockBottom) cmd |= cmmMoveBottom;
  } // end if move command

  return cmd;
} // end getCommand
#endif  // end else curses interface

//--------------------------------------------------------------------
// Get a file position and move there:

void gotoPosition(Command cmd)
{
  positionInWin(cmd, inWidth+2, " Goto ");

  const int  maxLen = inWidth-2;
  char  buf[maxLen+1];

  getString(buf, maxLen, "0123456789ABCDEF", true);

  if (!buf[0])
    return;

  FPos  pos = strtoul(buf, NULL, 16);

  if (cmd & cmgGotoTop)
    file1.moveTo(pos);
  if (cmd & cmgGotoBottom)
    file2.moveTo(pos);
} // end gotoPosition

//--------------------------------------------------------------------
// Search for text or bytes in the files:

void searchFiles(Command cmd)
{
  positionInWin(cmd, 32, " Find ");

  inWin.put(2, 1,"H Hex search   T Text search");
  inWin.putAttribs( 2,1, cPromptKey, 1);
  inWin.putAttribs(17,1, cPromptKey, 1);
  inWin.update();
  int key = inWin.readKey();

  bool hex = false;

  if (key == KEY_ESCAPE) {
    inWin.hide();
    return;
  } else if (toupper(key) == 'H')
    hex = true;

  positionInWin(cmd, screenWidth, (hex ? " Find Hex Bytes" : " Find Text "));

  const int  maxLen = screenWidth-4;
  Byte  buf[maxLen+1];
  int   searchLen;

  if (hex) {
    getString(reinterpret_cast<char*>(buf), maxLen, "0123456789ABCDEF",
              true, true);
    searchLen = packHex(buf);
  } else {
    getString(reinterpret_cast<char*>(buf), maxLen);

    searchLen = strlen(reinterpret_cast<char*>(buf));
    if (displayTable == ebcdicDisplayTable) {
      for (int i = 0; i < searchLen; ++i)
        buf[i] = ascii2ebcdicTable[buf[i]];
    } // end if in EBCDIC mode
  } // end else text search

  if (!searchLen) return;

  if (cmd & cmgGotoTop)
    file1.moveTo(buf, searchLen);
  if (cmd & cmgGotoBottom)
    file2.moveTo(buf, searchLen);
} // end searchFiles

//--------------------------------------------------------------------
// Handle a command:
//
// Input:
//   cmd:  The command to be handled

void handleCmd(Command cmd)
{
  if (cmd & cmmMove) {
    int  step = steps[cmd & cmmMoveSize];

    if ((cmd & cmmMoveForward) == 0)
      step *= -1;               // We're moving backward

    if ((cmd & cmmMoveForward) && !step) {
      if (cmd & cmmMoveTop)
        file1.moveToEnd((!singleFile && (cmd & cmmMoveBottom)) ? &file2 : NULL);
      else
        file2.moveToEnd(NULL);
    } else {
      if (cmd & cmmMoveTop)
        if (step)
          file1.move(step);
        else
          file1.moveTo(0);

      if (cmd & cmmMoveBottom)
        if (step)
          file2.move(step);
        else
          file2.moveTo(0);
    } // end else not moving to end
  } // end if move
  else if ((cmd & cmgGotoMask) == cmgGoto)
    gotoPosition(cmd);
  else if ((cmd & cmgGotoMask) == cmFind)
    searchFiles(cmd);
  else if (cmd == cmNextDiff) {
    if (lockState) {
      lockState = lockNeither;
      displayLockState();
    }
    do {
      file1.move(bufSize);
      file2.move(bufSize);
    } while (!diffs.compute());
  } // end else if cmNextDiff
  else if (cmd == cmUseTop) {
    if (lockState == lockBottom)
      lockState = lockNeither;
    else
      lockState = lockBottom;
    displayLockState();
  }
  else if (cmd == cmUseBottom) {
    if (lockState == lockTop)
      lockState = lockNeither;
    else
      lockState = lockTop;
    displayLockState();
  }
  else if (cmd == cmToggleASCII) {
    displayTable = ((displayTable == asciiDisplayTable)
                    ? ebcdicDisplayTable
                    : asciiDisplayTable );
    displayCharacterSet();
  }
  else if (cmd == cmEditTop)
    file1.edit(singleFile ? NULL : &file2);
  else if (cmd == cmEditBottom)
    file2.edit(&file1);

  // Make sure we haven't gone past the end of both files:
  while (diffs.compute() < 0) {
    file1.move(-steps[cmmMovePage]);
    file2.move(-steps[cmmMovePage]);
  }

  file1.display();
  file2.display();
} // end handleCmd

//====================================================================
// Initialization and option processing:
//====================================================================
// Display license information and exit:

bool license(GetOpt*, const GetOpt::Option*, const char*,
             GetOpt::Connection, const char*, int*)
{
  puts(titleString);
  puts("\n"
"This program is free software; you can redistribute it and/or\n"
"modify it under the terms of the GNU General Public License as\n"
"published by the Free Software Foundation; either version 2 of\n"
"the License, or (at your option) any later version.\n"
"\n"
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
"GNU General Public License for more details.\n"
"\n"
"You should have received a copy of the GNU General Public License\n"
"along with this program; if not, write to the Free Software\n"
"Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."
  );

  exit(0);
  return false;                 // Never happens
} // end license

//--------------------------------------------------------------------
// Display version & usage information and exit:
//
// Input:
//   showHelp:    True means display usage information
//   exitStatus:  Status code to pass to exit()

void usage(bool showHelp, int exitStatus)
{
  if (exitStatus > 1)
    cerr << "Try `" << program_name << " --help' for more information.\n";
  else {
    cout << titleString << endl;

    if (showHelp)
      cout << "Usage: " << program_name << " FILE1 [FILE2]\n\
Compare FILE1 and FILE2 byte by byte.\n\
If FILE2 is omitted, just display FILE1.\n\
\n\
Options:\n\
      --help               display this help information and exit\n\
      -L, --license        display license & warranty information and exit\n\
      -V, --version        display version information and exit\n";
  }

  exit(exitStatus);
} // end usage

bool usage(GetOpt* getopt, const GetOpt::Option* option,
           const char*, GetOpt::Connection, const char*, int*)
{
  usage(option->shortName == '?');
  return false;                 // Never happens
} // end usage

//--------------------------------------------------------------------
// Handle options:
//
// Input:
//   argc, argv:  The parameters passed to main
//
// Output:
//   argc, argv:
//     Modified to list only the non-option arguments
//     Note: argv[0] may not be the executable name

void processOptions(int& argc, char**& argv)
{
  static const GetOpt::Option options[] =
  {
    { '?', "help",       NULL, 0, &usage },
    { 'L', "license",    NULL, 0, &license },
    { 'V', "version",    NULL, 0, &usage },
    { 0 }
  };

  GetOpt getopt(options);
  int argi = getopt.process(argc, const_cast<const char**>(argv));
  if (getopt.error)
    usage(true, 1);

  if (argi >= argc)
    argc = 1;           // No arguments
  else {
    argc -= --argi;     // Reduce argc by number of arguments used
    argv += argi;       // And adjust argv[1] to the next argument
  }
} // end processOptions

//====================================================================
// Main Program:
//====================================================================
int main(int argc, char* argv[])
{
  if ((program_name = strrchr(argv[0], '\\')))
    // Isolate the filename:
    ++program_name;
  else
    program_name = argv[0];

  processOptions(argc, argv);

  if (argc < 2 || argc > 3)
    usage(1);

  cout << "\
VBinDiff " PACKAGE_VERSION ", Copyright 1995-2005 Christopher J. Madsen\n\
VBinDiff comes with ABSOLUTELY NO WARRANTY; for details type `vbindiff -L'.\n";

  singleFile = (argc == 2);
  if (!initialize()) {
    cerr << '\n' << program_name << ": Unable to initialize windows\n";
    return 1;
  }

  {
    ostringstream errMsg;

    if (!file1.setFile(argv[1])) {
      const char* errStr = ErrorMsg();
      errMsg << "Unable to open " << argv[1] << ": " << errStr;
    }
    else if (!singleFile && !file2.setFile(argv[2])) {
      const char* errStr = ErrorMsg();
      errMsg << "Unable to open " << argv[2] << ": " << errStr;
    }
    string error(errMsg.str());
    if (error.length())
      exitMsg(1, error.c_str());
  } // end block around errMsg

  diffs.compute();

  file1.display();
  file2.display();

  Command  cmd;
  while ((cmd = getCommand()) != cmQuit)
    handleCmd(cmd);

  file1.shutDown();
  file2.shutDown();
  inWin.close();
  promptWin.close();

  ConWindow::shutdown();

  return 0;
} // end main

//--------------------------------------------------------------------
// Local Variables:
//     c-file-style: "cjm"
// End:

Generated by  Doxygen 1.6.0   Back to index