//-------------------------------------------------------------------- // $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 #include #include #include #include #include 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(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(data); data = reinterpret_cast(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(data); } // end FileDisplay::~FileDisplay //-------------------------------------------------------------------- void FileDisplay::resize() { if (data) delete [] reinterpret_cast(data); data = reinterpret_cast(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(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(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(buf), maxLen, "0123456789ABCDEF", true, true); searchLen = packHex(buf); } else { getString(reinterpret_cast(buf), maxLen); searchLen = strlen(reinterpret_cast(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(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: